1/** 2 * @license 3 * Video.js 7.15.4 <http://videojs.com/> 4 * Copyright Brightcove, Inc. <https://www.brightcove.com/> 5 * Available under Apache License Version 2.0 6 * <https://github.com/videojs/video.js/blob/main/LICENSE> 7 * 8 * Includes vtt.js <https://github.com/mozilla/vtt.js> 9 * Available under Apache License Version 2.0 10 * <https://github.com/mozilla/vtt.js/blob/main/LICENSE> 11 */ 12 13 window.vjsInit = function () {(function (global, factory) { 14 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 15 typeof define === 'function' && define.amd ? define(factory) : 16 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojs = factory()); 17}(this, (function () { 'use strict'; 18 19 var version$5 = "7.15.4"; 20 21 var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 22 23 function createCommonjsModule(fn, module) { 24 return module = { exports: {} }, fn(module, module.exports), module.exports; 25 } 26 27 var win; 28 29 if (typeof window !== "undefined") { 30 win = window; 31 } else if (typeof commonjsGlobal !== "undefined") { 32 win = commonjsGlobal; 33 } else if (typeof self !== "undefined") { 34 win = self; 35 } else { 36 win = {}; 37 } 38 39 var window_1 = win; 40 41 /** 42 * An Object that contains lifecycle hooks as keys which point to an array 43 * of functions that are run when a lifecycle is triggered 44 * 45 * @private 46 */ 47 var hooks_ = {}; 48 /** 49 * Get a list of hooks for a specific lifecycle 50 * 51 * @param {string} type 52 * the lifecyle to get hooks from 53 * 54 * @param {Function|Function[]} [fn] 55 * Optionally add a hook (or hooks) to the lifecycle that your are getting. 56 * 57 * @return {Array} 58 * an array of hooks, or an empty array if there are none. 59 */ 60 61 var hooks = function hooks(type, fn) { 62 hooks_[type] = hooks_[type] || []; 63 64 if (fn) { 65 hooks_[type] = hooks_[type].concat(fn); 66 } 67 68 return hooks_[type]; 69 }; 70 /** 71 * Add a function hook to a specific videojs lifecycle. 72 * 73 * @param {string} type 74 * the lifecycle to hook the function to. 75 * 76 * @param {Function|Function[]} 77 * The function or array of functions to attach. 78 */ 79 80 81 var hook = function hook(type, fn) { 82 hooks(type, fn); 83 }; 84 /** 85 * Remove a hook from a specific videojs lifecycle. 86 * 87 * @param {string} type 88 * the lifecycle that the function hooked to 89 * 90 * @param {Function} fn 91 * The hooked function to remove 92 * 93 * @return {boolean} 94 * The function that was removed or undef 95 */ 96 97 98 var removeHook = function removeHook(type, fn) { 99 var index = hooks(type).indexOf(fn); 100 101 if (index <= -1) { 102 return false; 103 } 104 105 hooks_[type] = hooks_[type].slice(); 106 hooks_[type].splice(index, 1); 107 return true; 108 }; 109 /** 110 * Add a function hook that will only run once to a specific videojs lifecycle. 111 * 112 * @param {string} type 113 * the lifecycle to hook the function to. 114 * 115 * @param {Function|Function[]} 116 * The function or array of functions to attach. 117 */ 118 119 120 var hookOnce = function hookOnce(type, fn) { 121 hooks(type, [].concat(fn).map(function (original) { 122 var wrapper = function wrapper() { 123 removeHook(type, wrapper); 124 return original.apply(void 0, arguments); 125 }; 126 127 return wrapper; 128 })); 129 }; 130 131 var minDoc = {}; 132 133 var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {}; 134 var doccy; 135 136 if (typeof document !== 'undefined') { 137 doccy = document; 138 } else { 139 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; 140 141 if (!doccy) { 142 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; 143 } 144 } 145 146 var document_1 = doccy; 147 148 /** 149 * @file fullscreen-api.js 150 * @module fullscreen-api 151 * @private 152 */ 153 /** 154 * Store the browser-specific methods for the fullscreen API. 155 * 156 * @type {Object} 157 * @see [Specification]{@link https://fullscreen.spec.whatwg.org} 158 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} 159 */ 160 161 var FullscreenApi = { 162 prefixed: true 163 }; // browser API methods 164 165 var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit 166 ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla 167 ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft 168 ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']]; 169 var specApi = apiMap[0]; 170 var browserApi; // determine the supported set of functions 171 172 for (var i = 0; i < apiMap.length; i++) { 173 // check for exitFullscreen function 174 if (apiMap[i][1] in document_1) { 175 browserApi = apiMap[i]; 176 break; 177 } 178 } // map the browser API names to the spec API names 179 180 181 if (browserApi) { 182 for (var _i = 0; _i < browserApi.length; _i++) { 183 FullscreenApi[specApi[_i]] = browserApi[_i]; 184 } 185 186 FullscreenApi.prefixed = browserApi[0] !== specApi[0]; 187 } 188 189 /** 190 * @file create-logger.js 191 * @module create-logger 192 */ 193 194 var history = []; 195 /** 196 * Log messages to the console and history based on the type of message 197 * 198 * @private 199 * @param {string} type 200 * The name of the console method to use. 201 * 202 * @param {Array} args 203 * The arguments to be passed to the matching console method. 204 */ 205 206 var LogByTypeFactory = function LogByTypeFactory(name, log) {
207 return function (type, level, args) { 208 var lvl = log.levels[level]; 209 var lvlRegExp = new RegExp("^(" + lvl + ")$"); 210 211 if (type !== 'log') { 212 // Add the type to the front of the message when it's not "log". 213 args.unshift(type.toUpperCase() + ':'); 214 } // Add console prefix after adding to history. 215 216 217 args.unshift(name + ':'); // Add a clone of the args at this point to history. 218 219 if (history) { 220 history.push([].concat(args)); // only store 1000 history entries 221 222 var splice = history.length - 1000; 223 history.splice(0, splice > 0 ? splice : 0); 224 } // If there's no console then don't try to output messages, but they will 225 // still be stored in history. 226 227 228 if (!window_1.console) { 229 return; 230 } // Was setting these once outside of this function, but containing them 231 // in the function makes it easier to test cases where console doesn't exist 232 // when the module is executed. 233 234 235 var fn = window_1.console[type]; 236 237 if (!fn && type === 'debug') { 238 // Certain browsers don't have support for console.debug. For those, we 239 // should default to the closest comparable log. 240 fn = window_1.console.info || window_1.console.log; 241 } // Bail out if there's no console or if this type is not allowed by the 242 // current logging level. 243 244 245 if (!fn || !lvl || !lvlRegExp.test(type)) { 246 return; 247 } 248 249 fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args); 250 }; 251 }; 252 253 function createLogger$1(name) { 254 // This is the private tracking variable for logging level. 255 var level = 'info'; // the curried logByType bound to the specific log and history 256 257 var logByType; 258 /** 259 * Logs plain debug messages. Similar to `console.log`. 260 * 261 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 262 * of our JSDoc template, we cannot properly document this as both a function 263 * and a namespace, so its function signature is documented here. 264 * 265 * #### Arguments 266 * ##### *args 267 * Mixed[] 268 * 269 * Any combination of values that could be passed to `console.log()`. 270 * 271 * #### Return Value 272 * 273 * `undefined` 274 * 275 * @namespace 276 * @param {Mixed[]} args 277 * One or more messages or objects that should be logged. 278 */ 279 280 var log = function log() { 281 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 282 args[_key] = arguments[_key]; 283 } 284 285 logByType('log', level, args); 286 }; // This is the logByType helper that the logging methods below use 287 288 289 logByType = LogByTypeFactory(name, log); 290 /** 291 * Create a new sublogger which chains the old name to the new name. 292 * 293 * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following: 294 * ```js 295 * mylogger('foo'); 296 * // > VIDEOJS: player: foo 297 * ``` 298 * 299 * @param {string} name 300 * The name to add call the new logger 301 * @return {Object} 302 */ 303 304 log.createLogger = function (subname) { 305 return createLogger$1(name + ': ' + subname); 306 }; 307 /** 308 * Enumeration of available logging levels, where the keys are the level names 309 * and the values are `|`-separated strings containing logging methods allowed 310 * in that logging level. These strings are used to create a regular expression 311 * matching the function name being called. 312 * 313 * Levels provided by Video.js are: 314 * 315 * - `off`: Matches no calls. Any value that can be cast to `false` will have 316 * this effect. The most restrictive. 317 * - `all`: Matches only Video.js-provided functions (`debug`, `log`, 318 * `log.warn`, and `log.error`). 319 * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. 320 * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. 321 * - `warn`: Matches `log.warn` and `log.error` calls. 322 * - `error`: Matches only `log.error` calls. 323 * 324 * @type {Object} 325 */ 326 327 328 log.levels = { 329 all: 'debug|log|warn|error', 330 off: '', 331 debug: 'debug|log|warn|error', 332 info: 'log|warn|error', 333 warn: 'warn|error', 334 error: 'error', 335 DEFAULT: level 336 }; 337 /** 338 * Get or set the current logging level. 339 * 340 * If a string matching a key from {@link module:log.levels} is provided, acts 341 * as a setter. 342 * 343 * @param {string} [lvl] 344 * Pass a valid level to set a new logging level. 345 * 346 * @return {string} 347 * The current logging level. 348 */ 349 350 log.level = function (lvl) { 351 if (typeof lvl === 'string') { 352 if (!log.levels.hasOwnProperty(lvl)) { 353 throw new Error("\"" + lvl + "\" in not a valid log level"); 354 } 355 356 level = lvl; 357 } 358 359 return level; 360 }; 361 /** 362 * Returns an array containing everything that has been logged to the history. 363 * 364 * This array is a shallow clone of the internal history record. However, its 365 * contents are _not_ cloned; so, mutating objects inside this array will 366 * mutate them in history. 367 * 368 * @return {Array} 369 */ 370 371 372 log.history = function () { 373 return history ? [].concat(history) : []; 374 }; 375 /** 376 * Allows you to filter the history by the given logger name 377 * 378 * @param {string} fname 379 * The name to filter by 380 * 381 * @return {Array} 382 * The filtered list to return 383 */ 384 385 386 log.history.filter = function (fname) { 387 return (history || []).filter(function (historyItem) { 388 // if the first item in each historyItem includes `fname`, then it's a match 389 return new RegExp(".*" + fname + ".*").test(historyItem[0]); 390 }); 391 }; 392 /** 393 * Clears the internal history tracking, but does not prevent further history 394 * tracking. 395 */ 396 397 398 log.history.clear = function () { 399 if (history) { 400 history.length = 0; 401 } 402 }; 403 /** 404 * Disable history tracking if it is currently enabled. 405 */ 406 407 408 log.history.disable = function () { 409 if (history !== null) { 410 history.length = 0; 411 history = null; 412 } 413 }; 414 /** 415 * Enable history tracking if it is currently disabled. 416 */ 417 418 419 log.history.enable = function () { 420 if (history === null) { 421 history = []; 422 } 423 }; 424 /** 425 * Logs error messages. Similar to `console.error`. 426 * 427 * @param {Mixed[]} args 428 * One or more messages or objects that should be logged as an error 429 */ 430 431 432 log.error = function () { 433 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 434 args[_key2] = arguments[_key2]; 435 } 436
437 return logByType('error', level, args); 438 }; 439 /** 440 * Logs warning messages. Similar to `console.warn`. 441 * 442 * @param {Mixed[]} args 443 * One or more messages or objects that should be logged as a warning. 444 */ 445 446 447 log.warn = function () { 448 for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 449 args[_key3] = arguments[_key3]; 450 } 451 452 return logByType('warn', level, args); 453 }; 454 /** 455 * Logs debug messages. Similar to `console.debug`, but may also act as a comparable 456 * log if `console.debug` is not available 457 * 458 * @param {Mixed[]} args 459 * One or more messages or objects that should be logged as debug. 460 */ 461 462 463 log.debug = function () { 464 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 465 args[_key4] = arguments[_key4]; 466 } 467 468 return logByType('debug', level, args); 469 }; 470 471 return log; 472 } 473 474 /** 475 * @file log.js 476 * @module log 477 */ 478 var log$1 = createLogger$1('VIDEOJS'); 479 var createLogger = log$1.createLogger; 480 481 var _extends_1 = createCommonjsModule(function (module) { 482 function _extends() { 483 module.exports = _extends = Object.assign || function (target) { 484 for (var i = 1; i < arguments.length; i++) { 485 var source = arguments[i]; 486 487 for (var key in source) { 488 if (Object.prototype.hasOwnProperty.call(source, key)) { 489 target[key] = source[key]; 490 } 491 } 492 } 493 494 return target; 495 }; 496 497 return _extends.apply(this, arguments); 498 } 499 500 module.exports = _extends; 501 }); 502 503 /** 504 * @file obj.js 505 * @module obj 506 */ 507 508 /** 509 * @callback obj:EachCallback 510 * 511 * @param {Mixed} value 512 * The current key for the object that is being iterated over. 513 * 514 * @param {string} key 515 * The current key-value for object that is being iterated over 516 */ 517 518 /** 519 * @callback obj:ReduceCallback 520 * 521 * @param {Mixed} accum 522 * The value that is accumulating over the reduce loop. 523 * 524 * @param {Mixed} value 525 * The current key for the object that is being iterated over. 526 * 527 * @param {string} key 528 * The current key-value for object that is being iterated over 529 * 530 * @return {Mixed} 531 * The new accumulated value. 532 */ 533 var toString$1 = Object.prototype.toString; 534 /** 535 * Get the keys of an Object 536 * 537 * @param {Object} 538 * The Object to get the keys from 539 * 540 * @return {string[]} 541 * An array of the keys from the object. Returns an empty array if the 542 * object passed in was invalid or had no keys. 543 * 544 * @private 545 */ 546 547 var keys = function keys(object) { 548 return isObject$1(object) ? Object.keys(object) : []; 549 }; 550 /** 551 * Array-like iteration for objects. 552 * 553 * @param {Object} object 554 * The object to iterate over 555 * 556 * @param {obj:EachCallback} fn 557 * The callback function which is called for each key in the object. 558 */ 559 560 561 function each(object, fn) { 562 keys(object).forEach(function (key) { 563 return fn(object[key], key); 564 }); 565 } 566 /** 567 * Array-like reduce for objects. 568 * 569 * @param {Object} object 570 * The Object that you want to reduce. 571 * 572 * @param {Function} fn 573 * A callback function which is called for each key in the object. It 574 * receives the accumulated value and the per-iteration value and key 575 * as arguments. 576 * 577 * @param {Mixed} [initial = 0] 578 * Starting value 579 * 580 * @return {Mixed} 581 * The final accumulated value. 582 */ 583 584 function reduce(object, fn, initial) { 585 if (initial === void 0) { 586 initial = 0; 587 } 588 589 return keys(object).reduce(function (accum, key) { 590 return fn(accum, object[key], key); 591 }, initial); 592 } 593 /** 594 * Object.assign-style object shallow merge/extend. 595 * 596 * @param {Object} target 597 * @param {Object} ...sources 598 * @return {Object} 599 */ 600 601 function assign(target) { 602 for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 603 sources[_key - 1] = arguments[_key]; 604 } 605 606 if (Object.assign) { 607 return _extends_1.apply(void 0, [target].concat(sources)); 608 } 609
610 sources.forEach(function (source) { 611 if (!source) { 612 return; 613 } 614 615 each(source, function (value, key) { 616 target[key] = value; 617 }); 618 }); 619 return target; 620 } 621 /** 622 * Returns whether a value is an object of any kind - including DOM nodes, 623 * arrays, regular expressions, etc. Not functions, though. 624 * 625 * This avoids the gotcha where using `typeof` on a `null` value 626 * results in `'object'`. 627 * 628 * @param {Object} value 629 * @return {boolean} 630 */ 631 632 function isObject$1(value) { 633 return !!value && typeof value === 'object'; 634 } 635 /** 636 * Returns whether an object appears to be a "plain" object - that is, a 637 * direct instance of `Object`. 638 * 639 * @param {Object} value 640 * @return {boolean} 641 */ 642 643 function isPlain(value) { 644 return isObject$1(value) && toString$1.call(value) === '[object Object]' && value.constructor === Object; 645 } 646 647 /** 648 * @file computed-style.js 649 * @module computed-style 650 */ 651 /** 652 * A safe getComputedStyle. 653 * 654 * This is needed because in Firefox, if the player is loaded in an iframe with 655 * `display:none`, then `getComputedStyle` returns `null`, so, we do a 656 * null-check to make sure that the player doesn't break in these cases. 657 * 658 * @function 659 * @param {Element} el 660 * The element you want the computed style of 661 * 662 * @param {string} prop 663 * The property name you want 664 * 665 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 666 */ 667 668 function computedStyle(el, prop) { 669 if (!el || !prop) { 670 return ''; 671 } 672 673 if (typeof window_1.getComputedStyle === 'function') { 674 var computedStyleValue; 675 676 try { 677 computedStyleValue = window_1.getComputedStyle(el); 678 } catch (e) { 679 return ''; 680 } 681 682 return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : ''; 683 } 684 685 return ''; 686 } 687 688 /** 689 * @file browser.js 690 * @module browser 691 */ 692 var USER_AGENT = window_1.navigator && window_1.navigator.userAgent || ''; 693 var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); 694 var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; 695 /** 696 * Whether or not this device is an iPod. 697 * 698 * @static 699 * @const 700 * @type {Boolean} 701 */ 702 703 var IS_IPOD = /iPod/i.test(USER_AGENT); 704 /** 705 * The detected iOS version - or `null`. 706 * 707 * @static 708 * @const 709 * @type {string|null} 710 */ 711 712 var IOS_VERSION = function () { 713 var match = USER_AGENT.match(/OS (\d+)_/i); 714 715 if (match && match[1]) { 716 return match[1]; 717 } 718 719 return null; 720 }(); 721 /** 722 * Whether or not this is an Android device. 723 * 724 * @static 725 * @const 726 * @type {Boolean} 727 */ 728 729 var IS_ANDROID = /Android/i.test(USER_AGENT); 730 /** 731 * The detected Android version - or `null`. 732 * 733 * @static 734 * @const 735 * @type {number|string|null} 736 */ 737 738 var ANDROID_VERSION = function () { 739 // This matches Android Major.Minor.Patch versions
740 // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned 741 var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); 742 743 if (!match) { 744 return null; 745 } 746 747 var major = match[1] && parseFloat(match[1]); 748 var minor = match[2] && parseFloat(match[2]); 749 750 if (major && minor) { 751 return parseFloat(match[1] + '.' + match[2]); 752 } else if (major) { 753 return major; 754 } 755 756 return null; 757 }(); 758 /** 759 * Whether or not this is a native Android browser. 760 * 761 * @static 762 * @const 763 * @type {Boolean} 764 */ 765 766 var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; 767 /** 768 * Whether or not this is Mozilla Firefox. 769 * 770 * @static 771 * @const 772 * @type {Boolean} 773 */ 774 775 var IS_FIREFOX = /Firefox/i.test(USER_AGENT); 776 /** 777 * Whether or not this is Microsoft Edge. 778 * 779 * @static 780 * @const 781 * @type {Boolean} 782 */ 783 784 var IS_EDGE = /Edg/i.test(USER_AGENT); 785 /** 786 * Whether or not this is Google Chrome. 787 * 788 * This will also be `true` for Chrome on iOS, which will have different support 789 * as it is actually Safari under the hood. 790 * 791 * @static 792 * @const 793 * @type {Boolean} 794 */ 795 796 var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); 797 /** 798 * The detected Google Chrome version - or `null`. 799 * 800 * @static 801 * @const 802 * @type {number|null} 803 */ 804 805 var CHROME_VERSION = function () { 806 var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); 807 808 if (match && match[2]) { 809 return parseFloat(match[2]); 810 } 811 812 return null; 813 }(); 814 /** 815 * The detected Internet Explorer version - or `null`. 816 * 817 * @static 818 * @const 819 * @type {number|null} 820 */ 821 822 var IE_VERSION = function () { 823 var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); 824 var version = result && parseFloat(result[1]); 825 826 if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { 827 // IE 11 has a different user agent string than other IE versions 828 version = 11.0; 829 } 830 831 return version; 832 }(); 833 /** 834 * Whether or not this is desktop Safari. 835 * 836 * @static 837 * @const 838 * @type {Boolean} 839 */ 840 841 var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; 842 /** 843 * Whether or not this is a Windows machine. 844 * 845 * @static 846 * @const 847 * @type {Boolean} 848 */ 849 850 var IS_WINDOWS = /Windows/i.test(USER_AGENT); 851 /** 852 * Whether or not this device is touch-enabled. 853 * 854 * @static 855 * @const 856 * @type {Boolean} 857 */ 858 859 var TOUCH_ENABLED = Boolean(isReal() && ('ontouchstart' in window_1 || window_1.navigator.maxTouchPoints || window_1.DocumentTouch && window_1.document instanceof window_1.DocumentTouch)); 860 /** 861 * Whether or not this device is an iPad. 862 * 863 * @static 864 * @const 865 * @type {Boolean} 866 */ 867 868 var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT); 869 /** 870 * Whether or not this device is an iPhone. 871 * 872 * @static 873 * @const 874 * @type {Boolean} 875 */ 876 // The Facebook app's UIWebView identifies as both an iPhone and iPad, so 877 // to identify iPhones, we need to exclude iPads. 878 // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ 879 880 var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; 881 /** 882 * Whether or not this is an iOS device. 883 * 884 * @static 885 * @const 886 * @type {Boolean} 887 */ 888 889 var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; 890 /** 891 * Whether or not this is any flavor of Safari - including iOS. 892 * 893 * @static 894 * @const 895 * @type {Boolean} 896 */ 897 898 var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; 899 900 var browser = /*#__PURE__*/Object.freeze({ 901 __proto__: null, 902 IS_IPOD: IS_IPOD, 903 IOS_VERSION: IOS_VERSION, 904 IS_ANDROID: IS_ANDROID, 905 ANDROID_VERSION: ANDROID_VERSION, 906 IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, 907 IS_FIREFOX: IS_FIREFOX, 908 IS_EDGE: IS_EDGE, 909 IS_CHROME: IS_CHROME, 910 CHROME_VERSION: CHROME_VERSION, 911 IE_VERSION: IE_VERSION, 912 IS_SAFARI: IS_SAFARI, 913 IS_WINDOWS: IS_WINDOWS, 914 TOUCH_ENABLED: TOUCH_ENABLED, 915 IS_IPAD: IS_IPAD, 916 IS_IPHONE: IS_IPHONE, 917 IS_IOS: IS_IOS, 918 IS_ANY_SAFARI: IS_ANY_SAFARI 919 }); 920 921 /** 922 * @file dom.js 923 * @module dom 924 */ 925 /** 926 * Detect if a value is a string with any non-whitespace characters. 927 * 928 * @private 929 * @param {string} str 930 * The string to check 931 * 932 * @return {boolean} 933 * Will be `true` if the string is non-blank, `false` otherwise. 934 * 935 */ 936 937 function isNonBlankString(str) { 938 // we use str.trim as it will trim any whitespace characters 939 // from the front or back of non-whitespace characters. aka
940 // Any string that contains non-whitespace characters will 941 // still contain them after `trim` but whitespace only strings 942 // will have a length of 0, failing this check. 943 return typeof str === 'string' && Boolean(str.trim()); 944 } 945 /** 946 * Throws an error if the passed string has whitespace. This is used by 947 * class methods to be relatively consistent with the classList API. 948 * 949 * @private 950 * @param {string} str 951 * The string to check for whitespace. 952 * 953 * @throws {Error} 954 * Throws an error if there is whitespace in the string. 955 */ 956 957 958 function throwIfWhitespace(str) { 959 // str.indexOf instead of regex because str.indexOf is faster performance wise. 960 if (str.indexOf(' ') >= 0) { 961 throw new Error('class has illegal whitespace characters'); 962 } 963 } 964 /** 965 * Produce a regular expression for matching a className within an elements className. 966 * 967 * @private 968 * @param {string} className 969 * The className to generate the RegExp for. 970 * 971 * @return {RegExp} 972 * The RegExp that will check for a specific `className` in an elements 973 * className. 974 */ 975 976 977 function classRegExp(className) { 978 return new RegExp('(^|\\s)' + className + '($|\\s)'); 979 } 980 /** 981 * Whether the current DOM interface appears to be real (i.e. not simulated). 982 * 983 * @return {boolean} 984 * Will be `true` if the DOM appears to be real, `false` otherwise. 985 */ 986 987 988 function isReal() { 989 // Both document and window will never be undefined thanks to `global`. 990 return document_1 === window_1.document; 991 } 992 /** 993 * Determines, via duck typing, whether or not a value is a DOM element. 994 * 995 * @param {Mixed} value 996 * The value to check. 997 * 998 * @return {boolean} 999 * Will be `true` if the value is a DOM element, `false` otherwise. 1000 */ 1001 1002 function isEl(value) { 1003 return isObject$1(value) && value.nodeType === 1; 1004 } 1005 /** 1006 * Determines if the current DOM is embedded in an iframe. 1007 * 1008 * @return {boolean} 1009 * Will be `true` if the DOM is embedded in an iframe, `false` 1010 * otherwise. 1011 */ 1012 1013 function isInFrame() { 1014 // We need a try/catch here because Safari will throw errors when attempting 1015 // to get either `parent` or `self` 1016 try { 1017 return window_1.parent !== window_1.self; 1018 } catch (x) { 1019 return true; 1020 } 1021 } 1022 /** 1023 * Creates functions to query the DOM using a given method. 1024 * 1025 * @private 1026 * @param {string} method 1027 * The method to create the query with. 1028 * 1029 * @return {Function} 1030 * The query method 1031 */ 1032 1033 function createQuerier(method) { 1034 return function (selector, context) { 1035 if (!isNonBlankString(selector)) { 1036 return document_1[method](null); 1037 } 1038 1039 if (isNonBlankString(context)) { 1040 context = document_1.querySelector(context); 1041 } 1042 1043 var ctx = isEl(context) ? context : document_1; 1044 return ctx[method] && ctx[method](selector); 1045 }; 1046 } 1047 /** 1048 * Creates an element and applies properties, attributes, and inserts content. 1049 * 1050 * @param {string} [tagName='div'] 1051 * Name of tag to be created. 1052 * 1053 * @param {Object} [properties={}] 1054 * Element properties to be applied. 1055 * 1056 * @param {Object} [attributes={}] 1057 * Element attributes to be applied. 1058 * 1059 * @param {module:dom~ContentDescriptor} content 1060 * A content descriptor object. 1061 * 1062 * @return {Element} 1063 * The element that was created. 1064 */ 1065 1066 1067 function createEl(tagName, properties, attributes, content) { 1068 if (tagName === void 0) { 1069 tagName = 'div'; 1070 } 1071 1072 if (properties === void 0) { 1073 properties = {}; 1074 } 1075 1076 if (attributes === void 0) { 1077 attributes = {}; 1078 } 1079 1080 var el = document_1.createElement(tagName); 1081 Object.getOwnPropertyNames(properties).forEach(function (propName) { 1082 var val = properties[propName]; // See #2176 1083 // We originally were accepting both properties and attributes in the 1084 // same object, but that doesn't work so well. 1085 1086 if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { 1087 log$1.warn('Setting attributes in the second argument of createEl()\n' + 'has been deprecated. Use the third argument instead.\n' + ("createEl(type, properties, attributes). Attempting to set " + propName + " to " + val + ".")); 1088 el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a 1089 // method for it. 1090 } else if (propName === 'textContent') { 1091 textContent(el, val); 1092 } else if (el[propName] !== val || propName === 'tabIndex') { 1093 el[propName] = val; 1094 } 1095 });
vendor: 9,134 bytes, lines 1096-1415
1096 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 1097 el.setAttribute(attrName, attributes[attrName]); 1098 }); 1099 1100 if (content) { 1101 appendContent(el, content); 1102 } 1103 1104 return el; 1105 } 1106 /** 1107 * Injects text into an element, replacing any existing contents entirely. 1108 * 1109 * @param {Element} el 1110 * The element to add text content into 1111 * 1112 * @param {string} text 1113 * The text content to add. 1114 * 1115 * @return {Element} 1116 * The element with added text content. 1117 */ 1118 1119 function textContent(el, text) { 1120 if (typeof el.textContent === 'undefined') { 1121 el.innerText = text; 1122 } else { 1123 el.textContent = text; 1124 } 1125 1126 return el; 1127 } 1128 /** 1129 * Insert an element as the first child node of another 1130 * 1131 * @param {Element} child 1132 * Element to insert 1133 * 1134 * @param {Element} parent 1135 * Element to insert child into 1136 */ 1137 1138 function prependTo(child, parent) { 1139 if (parent.firstChild) { 1140 parent.insertBefore(child, parent.firstChild); 1141 } else { 1142 parent.appendChild(child); 1143 } 1144 } 1145 /** 1146 * Check if an element has a class name. 1147 * 1148 * @param {Element} element 1149 * Element to check 1150 * 1151 * @param {string} classToCheck 1152 * Class name to check for 1153 * 1154 * @return {boolean} 1155 * Will be `true` if the element has a class, `false` otherwise. 1156 * 1157 * @throws {Error} 1158 * Throws an error if `classToCheck` has white space. 1159 */ 1160 1161 function hasClass(element, classToCheck) { 1162 throwIfWhitespace(classToCheck); 1163 1164 if (element.classList) { 1165 return element.classList.contains(classToCheck); 1166 } 1167 1168 return classRegExp(classToCheck).test(element.className); 1169 } 1170 /** 1171 * Add a class name to an element. 1172 * 1173 * @param {Element} element 1174 * Element to add class name to. 1175 * 1176 * @param {string} classToAdd 1177 * Class name to add. 1178 * 1179 * @return {Element} 1180 * The DOM element with the added class name. 1181 */ 1182 1183 function addClass(element, classToAdd) { 1184 if (element.classList) { 1185 element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it 1186 // in the case of classList not being supported. 1187 } else if (!hasClass(element, classToAdd)) { 1188 element.className = (element.className + ' ' + classToAdd).trim(); 1189 } 1190 1191 return element; 1192 } 1193 /** 1194 * Remove a class name from an element. 1195 * 1196 * @param {Element} element 1197 * Element to remove a class name from. 1198 * 1199 * @param {string} classToRemove 1200 * Class name to remove 1201 * 1202 * @return {Element} 1203 * The DOM element with class name removed. 1204 */ 1205 1206 function removeClass(element, classToRemove) { 1207 // Protect in case the player gets disposed 1208 if (!element) { 1209 log$1.warn("removeClass was called with an element that doesn't exist"); 1210 return null; 1211 } 1212 1213 if (element.classList) { 1214 element.classList.remove(classToRemove); 1215 } else { 1216 throwIfWhitespace(classToRemove); 1217 element.className = element.className.split(/\s+/).filter(function (c) { 1218 return c !== classToRemove; 1219 }).join(' '); 1220 } 1221 1222 return element; 1223 } 1224 /** 1225 * The callback definition for toggleClass. 1226 * 1227 * @callback module:dom~PredicateCallback 1228 * @param {Element} element 1229 * The DOM element of the Component. 1230 * 1231 * @param {string} classToToggle 1232 * The `className` that wants to be toggled 1233 * 1234 * @return {boolean|undefined} 1235 * If `true` is returned, the `classToToggle` will be added to the 1236 * `element`. If `false`, the `classToToggle` will be removed from 1237 * the `element`. If `undefined`, the callback will be ignored. 1238 */ 1239 1240 /** 1241 * Adds or removes a class name to/from an element depending on an optional 1242 * condition or the presence/absence of the class name. 1243 * 1244 * @param {Element} element 1245 * The element to toggle a class name on. 1246 * 1247 * @param {string} classToToggle 1248 * The class that should be toggled. 1249 * 1250 * @param {boolean|module:dom~PredicateCallback} [predicate] 1251 * See the return value for {@link module:dom~PredicateCallback} 1252 * 1253 * @return {Element} 1254 * The element with a class that has been toggled. 1255 */ 1256 1257 function toggleClass(element, classToToggle, predicate) { 1258 // This CANNOT use `classList` internally because IE11 does not support the 1259 // second parameter to the `classList.toggle()` method! Which is fine because 1260 // `classList` will be used by the add/remove functions. 1261 var has = hasClass(element, classToToggle); 1262 1263 if (typeof predicate === 'function') { 1264 predicate = predicate(element, classToToggle); 1265 } 1266 1267 if (typeof predicate !== 'boolean') { 1268 predicate = !has; 1269 } // If the necessary class operation matches the current state of the 1270 // element, no action is required. 1271 1272 1273 if (predicate === has) { 1274 return; 1275 } 1276 1277 if (predicate) { 1278 addClass(element, classToToggle); 1279 } else { 1280 removeClass(element, classToToggle); 1281 } 1282 1283 return element; 1284 } 1285 /** 1286 * Apply attributes to an HTML element. 1287 * 1288 * @param {Element} el 1289 * Element to add attributes to. 1290 * 1291 * @param {Object} [attributes] 1292 * Attributes to be applied. 1293 */ 1294 1295 function setAttributes(el, attributes) { 1296 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 1297 var attrValue = attributes[attrName]; 1298 1299 if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { 1300 el.removeAttribute(attrName); 1301 } else { 1302 el.setAttribute(attrName, attrValue === true ? '' : attrValue); 1303 } 1304 }); 1305 } 1306 /** 1307 * Get an element's attribute values, as defined on the HTML tag. 1308 * 1309 * Attributes are not the same as properties. They're defined on the tag 1310 * or with setAttribute. 1311 * 1312 * @param {Element} tag 1313 * Element from which to get tag attributes. 1314 * 1315 * @return {Object} 1316 * All attributes of the element. Boolean attributes will be `true` or 1317 * `false`, others will be strings. 1318 */ 1319 1320 function getAttributes(tag) { 1321 var obj = {}; // known boolean attributes 1322 // we can check for matching boolean properties, but not all browsers 1323 // and not all tags know about these attributes, so, we still want to check them manually 1324 1325 var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; 1326 1327 if (tag && tag.attributes && tag.attributes.length > 0) { 1328 var attrs = tag.attributes; 1329 1330 for (var i = attrs.length - 1; i >= 0; i--) { 1331 var attrName = attrs[i].name; 1332 var attrVal = attrs[i].value; // check for known booleans 1333 // the matching element property will return a value for typeof 1334 1335 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { 1336 // the value of an included boolean attribute is typically an empty 1337 // string ('') which would equal false if we just check for a false value. 1338 // we also don't want support bad code like autoplay='false' 1339 attrVal = attrVal !== null ? true : false; 1340 } 1341 1342 obj[attrName] = attrVal; 1343 } 1344 } 1345 1346 return obj; 1347 } 1348 /** 1349 * Get the value of an element's attribute. 1350 * 1351 * @param {Element} el 1352 * A DOM element. 1353 * 1354 * @param {string} attribute 1355 * Attribute to get the value of. 1356 * 1357 * @return {string} 1358 * The value of the attribute. 1359 */ 1360 1361 function getAttribute(el, attribute) { 1362 return el.getAttribute(attribute); 1363 } 1364 /** 1365 * Set the value of an element's attribute. 1366 * 1367 * @param {Element} el 1368 * A DOM element. 1369 * 1370 * @param {string} attribute 1371 * Attribute to set. 1372 * 1373 * @param {string} value 1374 * Value to set the attribute to. 1375 */ 1376 1377 function setAttribute(el, attribute, value) { 1378 el.setAttribute(attribute, value); 1379 } 1380 /** 1381 * Remove an element's attribute. 1382 * 1383 * @param {Element} el 1384 * A DOM element. 1385 * 1386 * @param {string} attribute 1387 * Attribute to remove. 1388 */ 1389 1390 function removeAttribute(el, attribute) { 1391 el.removeAttribute(attribute); 1392 } 1393 /** 1394 * Attempt to block the ability to select text. 1395 */ 1396 1397 function blockTextSelection() { 1398 document_1.body.focus(); 1399 1400 document_1.onselectstart = function () { 1401 return false; 1402 }; 1403 } 1404 /** 1405 * Turn off text selection blocking. 1406 */ 1407 1408 function unblockTextSelection() { 1409 document_1.onselectstart = function () { 1410 return true; 1411 }; 1412 } 1413 /** 1414 * Identical to the native `getBoundingClientRect` function, but ensures that 1415 * the method is supported at all (it is in all browsers we claim to supp
1415ort) 1416 * and that the element is in the DOM before continuing. 1417 * 1418 * This wrapper function also shims properties which are not provided by some 1419 * older browsers (namely, IE8). 1420 * 1421 * Additionally, some browsers do not support adding properties to a 1422 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard 1423 * properties (except `x` and `y` which are not widely supported). This helps 1424 * avoid implementations where keys are non-enumerable. 1425 * 1426 * @param {Element} el 1427 * Element whose `ClientRect` we want to calculate. 1428 * 1429 * @return {Object|undefined} 1430 * Always returns a plain object - or `undefined` if it cannot. 1431 */ 1432 1433 function getBoundingClientRect(el) { 1434 if (el && el.getBoundingClientRect && el.parentNode) { 1435 var rect = el.getBoundingClientRect(); 1436 var result = {}; 1437 ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { 1438 if (rect[k] !== undefined) { 1439 result[k] = rect[k]; 1440 } 1441 }); 1442 1443 if (!result.height) { 1444 result.height = parseFloat(computedStyle(el, 'height')); 1445 } 1446 1447 if (!result.width) { 1448 result.width = parseFloat(computedStyle(el, 'width')); 1449 } 1450 1451 return result; 1452 } 1453 } 1454 /** 1455 * Represents the position of a DOM element on the page. 1456 * 1457 * @typedef {Object} module:dom~Position 1458 * 1459 * @property {number} left 1460 * Pixels to the left. 1461 * 1462 * @property {number} top 1463 * Pixels from the top. 1464 */ 1465 1466 /** 1467 * Get the position of an element in the DOM. 1468 * 1469 * Uses `getBoundingClientRect` technique from John Resig. 1470 * 1471 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ 1472 * 1473 * @param {Element} el 1474 * Element from which to get offset. 1475 * 1476 * @return {module:dom~Position} 1477 * The position of the element that was passed in. 1478 */ 1479 1480 function findPosition(el) { 1481 if (!el || el && !el.offsetParent) { 1482 return { 1483 left: 0, 1484 top: 0, 1485 width: 0, 1486 height: 0 1487 }; 1488 } 1489 1490 var width = el.offsetWidth; 1491 var height = el.offsetHeight; 1492 var left = 0; 1493 var top = 0; 1494 1495 while (el.offsetParent && el !== document_1[FullscreenApi.fullscreenElement]) { 1496 left += el.offsetLeft; 1497 top += el.offsetTop; 1498 el = el.offsetParent; 1499 } 1500 1501 return { 1502 left: left, 1503 top: top, 1504 width: width, 1505 height: height 1506 }; 1507 } 1508 /** 1509 * Represents x and y coordinates for a DOM element or mouse pointer. 1510 * 1511 * @typedef {Object} module:dom~Coordinates 1512 * 1513 * @property {number} x 1514 * x coordinate in pixels 1515 * 1516 * @property {number} y 1517 * y coordinate in pixels 1518 */ 1519 1520 /** 1521 * Get the pointer position within an element. 1522 * 1523 * The base on the coordinates are the bottom left of the element. 1524 * 1525 * @param {Element} el 1526 * Element on which to get the pointer position on. 1527 * 1528 * @param {EventTarget~Event} event 1529 * Event object. 1530 * 1531 * @return {module:dom~Coordinates} 1532 * A coordinates object corresponding to the mouse position. 1533 * 1534 */ 1535 1536 function getPointerPosition(el, event) { 1537 var translated = { 1538 x: 0, 1539 y: 0 1540 }; 1541 1542 if (IS_IOS) { 1543 var item = el; 1544 1545 while (item && item.nodeName.toLowerCase() !== 'html') { 1546 var transform = computedStyle(item, 'transform'); 1547 1548 if (/^matrix/.test(transform)) {
1549 var values = transform.slice(7, -1).split(/,\s/).map(Number); 1550 translated.x += values[4]; 1551 translated.y += values[5]; 1552 } else if (/^matrix3d/.test(transform)) { 1553 var _values = transform.slice(9, -1).split(/,\s/).map(Number); 1554 1555 translated.x += _values[12]; 1556 translated.y += _values[13]; 1557 } 1558 1559 item = item.parentNode; 1560 } 1561 } 1562 1563 var position = {}; 1564 var boxTarget = findPosition(event.target); 1565 var box = findPosition(el); 1566 var boxW = box.width; 1567 var boxH = box.height; 1568 var offsetY = event.offsetY - (box.top - boxTarget.top); 1569 var offsetX = event.offsetX - (box.left - boxTarget.left); 1570 1571 if (event.changedTouches) { 1572 offsetX = event.changedTouches[0].pageX - box.left; 1573 offsetY = event.changedTouches[0].pageY + box.top; 1574 1575 if (IS_IOS) { 1576 offsetX -= translated.x; 1577 offsetY -= translated.y; 1578 } 1579 } 1580 1581 position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH)); 1582 position.x = Math.max(0, Math.min(1, offsetX / boxW)); 1583 return position; 1584 } 1585 /** 1586 * Determines, via duck typing, whether or not a value is a text node. 1587 * 1588 * @param {Mixed} value 1589 * Check if this value is a text node. 1590 * 1591 * @return {boolean} 1592 * Will be `true` if the value is a text node, `false` otherwise. 1593 */ 1594 1595 function isTextNode(value) { 1596 return isObject$1(value) && value.nodeType === 3; 1597 } 1598 /** 1599 * Empties the contents of an element. 1600 * 1601 * @param {Element} el 1602 * The element to empty children from 1603 * 1604 * @return {Element} 1605 * The element with no children 1606 */ 1607 1608 function emptyEl(el) { 1609 while (el.firstChild) { 1610 el.removeChild(el.firstChild); 1611 } 1612 1613 return el; 1614 } 1615 /** 1616 * This is a mixed value that describes content to be injected into the DOM 1617 * via some method. It can be of the following types: 1618 * 1619 * Type | Description 1620 * -----------|------------- 1621 * `string` | The value will be normalized into a text node. 1622 * `Element` | The value will be accepted as-is. 1623 * `TextNode` | The value will be accepted as-is. 1624 * `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). 1625 * `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. 1626 * 1627 * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor 1628 */ 1629 1630 /** 1631 * Normalizes content for eventual insertion into the DOM. 1632 * 1633 * This allows a wide range of content definition methods, but helps protect 1634 * from falling into the trap of simply writing to `innerHTML`, which could 1635 * be an XSS concern. 1636 * 1637 * The content for an element can be passed in multiple types and 1638 * combinations, whose behavior is as follows: 1639 * 1640 * @param {module:dom~ContentDescriptor} content 1641 * A content descriptor value. 1642 * 1643 * @return {Array} 1644 * All of the content that was passed in, normalized to an array of 1645 * elements or text nodes. 1646 */ 1647 1648 function normalizeContent(content) { 1649 // First, invoke content if it is a function. If it produces an array, 1650 // that needs to happen before normalization. 1651 if (typeof content === 'function') { 1652 content = content(); 1653 } // Next up, normalize to an array, so one or many items can be normalized, 1654 // filtered, and returned. 1655 1656 1657 return (Array.isArray(content) ? content : [content]).map(function (value) { 1658 // First, invoke value if it is a function to produce a new value, 1659 // which will be subsequently normalized to a Node of some kind. 1660 if (typeof value === 'function') { 1661 value = value(); 1662 } 1663 1664 if (isEl(value) || isTextNode(value)) { 1665 return value; 1666 } 1667 1668 if (typeof value === 'string' && /\S/.test(value)) { 1669 return document_1.createTextNode(value); 1670 } 1671 }).filter(function (value) { 1672 return value; 1673 });
1674 } 1675 /** 1676 * Normalizes and appends content to an element. 1677 * 1678 * @param {Element} el 1679 * Element to append normalized content to. 1680 * 1681 * @param {module:dom~ContentDescriptor} content 1682 * A content descriptor value. 1683 * 1684 * @return {Element} 1685 * The element with appended normalized content. 1686 */ 1687 1688 function appendContent(el, content) { 1689 normalizeContent(content).forEach(function (node) { 1690 return el.appendChild(node); 1691 }); 1692 return el; 1693 } 1694 /** 1695 * Normalizes and inserts content into an element; this is identical to 1696 * `appendContent()`, except it empties the element first. 1697 * 1698 * @param {Element} el 1699 * Element to insert normalized content into. 1700 * 1701 * @param {module:dom~ContentDescriptor} content 1702 * A content descriptor value. 1703 * 1704 * @return {Element} 1705 * The element with inserted normalized content. 1706 */ 1707 1708 function insertContent(el, content) { 1709 return appendContent(emptyEl(el), content); 1710 } 1711 /** 1712 * Check if an event was a single left click. 1713 * 1714 * @param {EventTarget~Event} event 1715 * Event object. 1716 * 1717 * @return {boolean} 1718 * Will be `true` if a single left click, `false` otherwise. 1719 */ 1720 1721 function isSingleLeftClick(event) { 1722 // Note: if you create something draggable, be sure to 1723 // call it on both `mousedown` and `mousemove` event, 1724 // otherwise `mousedown` should be enough for a button 1725 if (event.button === undefined && event.buttons === undefined) { 1726 // Why do we need `buttons` ? 1727 // Because, middle mouse sometimes have this: 1728 // e.button === 0 and e.buttons === 4 1729 // Furthermore, we want to prevent combination click, something like 1730 // HOLD middlemouse then left click, that would be 1731 // e.button === 0, e.buttons === 5 1732 // just `button` is not gonna work 1733 // Alright, then what this block does ? 1734 // this is for chrome `simulate mobile devices` 1735 // I want to support this as well 1736 return true; 1737 } 1738 1739 if (event.button === 0 && event.buttons === undefined) { 1740 // Touch screen, sometimes on some specific device, `buttons` 1741 // doesn't have anything (safari on ios, blackberry...) 1742 return true; 1743 } // `mouseup` event on a single left click has 1744 // `button` and `buttons` equal to 0 1745 1746 1747 if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) { 1748 return true; 1749 } 1750 1751 if (event.button !== 0 || event.buttons !== 1) { 1752 // This is the reason we have those if else block above 1753 // if any special case we can catch and let it slide 1754 // we do it above, when get to here, this definitely 1755 // is-not-left-click 1756 return false; 1757 } 1758 1759 return true; 1760 } 1761 /** 1762 * Finds a single DOM element matching `selector` within the optional 1763 * `context` of another DOM element (defaulting to `document`). 1764 * 1765 * @param {string} selector 1766 * A valid CSS selector, which will be passed to `querySelector`. 1767 * 1768 * @param {Element|String} [context=document] 1769 * A DOM element within which to query. Can also be a selector 1770 * string in which case the first matching element will be used 1771 * as context. If missing (or no element matches selector), falls 1772 * back to `document`. 1773 * 1774 * @return {Element|null} 1775 * The element that was found or null. 1776 */ 1777 1778 var $ = createQuerier('querySelector'); 1779 /** 1780 * Finds a all DOM elements matching `selector` within the optional 1781 * `context` of another DOM element (defaulting to `document`). 1782 * 1783 * @param {string} selector 1784 * A valid CSS selector, which will be passed to `querySelectorAll`. 1785 * 1786 * @param {Element|String} [context=document] 1787 * A DOM element within which to query. Can also be a selector 1788 * string in which case the first matching element will be used 1789 * as context. If missing (or no element matches selector), falls 1790 * back to `document`. 1791 * 1792 * @return {NodeList} 1793 * A element list of elements that were found. Will be empty if none 1794 * were found. 1795 * 1796 */ 1797 1798 var $$ = createQuerier('querySelectorAll'); 1799 1800 var Dom = /*#__PURE__*/Object.freeze({ 1801 __proto__: null, 1802 isReal: isReal, 1803 isEl: isEl, 1804 isInFrame: isInFrame, 1805 createEl: createEl, 1806 textContent: textContent,
1807 prependTo: prependTo, 1808 hasClass: hasClass, 1809 addClass: addClass, 1810 removeClass: removeClass, 1811 toggleClass: toggleClass, 1812 setAttributes: setAttributes, 1813 getAttributes: getAttributes, 1814 getAttribute: getAttribute, 1815 setAttribute: setAttribute, 1816 removeAttribute: removeAttribute, 1817 blockTextSelection: blockTextSelection, 1818 unblockTextSelection: unblockTextSelection, 1819 getBoundingClientRect: getBoundingClientRect, 1820 findPosition: findPosition, 1821 getPointerPosition: getPointerPosition, 1822 isTextNode: isTextNode, 1823 emptyEl: emptyEl, 1824 normalizeContent: normalizeContent, 1825 appendContent: appendContent, 1826 insertContent: insertContent, 1827 isSingleLeftClick: isSingleLeftClick, 1828 $: $, 1829 $$: $$ 1830 }); 1831 1832 /** 1833 * @file setup.js - Functions for setting up a player without 1834 * user interaction based on the data-setup `attribute` of the video tag. 1835 * 1836 * @module setup 1837 */ 1838 var _windowLoaded = false; 1839 var videojs$1; 1840 /** 1841 * Set up any tags that have a data-setup `attribute` when the player is started. 1842 */ 1843 1844 var autoSetup = function autoSetup() { 1845 if (videojs$1.options.autoSetup === false) { 1846 return; 1847 } 1848 1849 var vids = Array.prototype.slice.call(document_1.getElementsByTagName('video')); 1850 var audios = Array.prototype.slice.call(document_1.getElementsByTagName('audio')); 1851 var divs = Array.prototype.slice.call(document_1.getElementsByTagName('video-js')); 1852 var mediaEls = vids.concat(audios, divs); // Check if any media elements exist 1853 1854 if (mediaEls && mediaEls.length > 0) { 1855 for (var i = 0, e = mediaEls.length; i < e; i++) { 1856 var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. 1857 1858 if (mediaEl && mediaEl.getAttribute) { 1859 // Make sure this player hasn't already been set up. 1860 if (mediaEl.player === undefined) { 1861 var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. 1862 // We only auto-setup if they've added the data-setup attr. 1863 1864 if (options !== null) { 1865 // Create new video.js instance. 1866 videojs$1(mediaEl); 1867 } 1868 } // If getAttribute isn't defined, we need to wait for the DOM. 1869 1870 } else { 1871 autoSetupTimeout(1); 1872 break; 1873 } 1874 } // No videos were found, so keep looping unless page is finished loading. 1875 1876 } else if (!_windowLoaded) { 1877 autoSetupTimeout(1); 1878 } 1879 }; 1880 /** 1881 * Wait until the page is loaded before running autoSetup. This will be called in 1882 * autoSetup if `hasLoaded` returns false. 1883 * 1884 * @param {number} wait 1885 * How long to wait in ms 1886 * 1887 * @param {module:videojs} [vjs] 1888 * The videojs library function 1889 */ 1890 1891 1892 function autoSetupTimeout(wait, vjs) { 1893 // Protect against breakage in non-browser environments 1894 if (!isReal()) { 1895 return; 1896 } 1897 1898 if (vjs) { 1899 videojs$1 = vjs; 1900 } 1901 1902 window_1.setTimeout(autoSetup, wait); 1903 } 1904 /** 1905 * Used to set the internal tracking of window loaded state to true. 1906 * 1907 * @private 1908 */ 1909 1910 1911 function setWindowLoaded() { 1912 _windowLoaded = true; 1913 window_1.removeEventListener('load', setWindowLoaded); 1914 } 1915 1916 if (isReal()) { 1917 if (document_1.readyState === 'complete') { 1918 setWindowLoaded(); 1919 } else { 1920 /** 1921 * Listen for the load event on window, and set _windowLoaded to true. 1922 * 1923 * We use a standard event listener here to avoid incrementing the GUID 1924 * before any players are created. 1925 * 1926 * @listens load 1927 */ 1928 window_1.addEventListener('load', setWindowLoaded); 1929 } 1930 } 1931 1932 /** 1933 * @file stylesheet.js 1934 * @module stylesheet 1935 */ 1936 /** 1937 * Create a DOM syle element given a className for it. 1938 * 1939 * @param {string} className 1940 * The className to add to the created style element. 1941 * 1942 * @return {Element} 1943 * The element that was created. 1944 */ 1945 1946 var createStyleElement = function createStyleElement(className) { 1947 var style = document_1.createElement('style'); 1948 style.className = className; 1949 return style; 1950 }; 1951 /** 1952 * Add text to a DOM element. 1953 * 1954 * @param {Element} el 1955 * The Element to add text content to. 1956 * 1957 * @param {string} content 1958 * The text to add to the element. 1959 */ 1960 1961 var setTextContent = function setTextContent(el, content) { 1962 if (el.styleSheet) { 1963 el.styleSheet.cssText = content; 1964 } else { 1965 el.textContent = content; 1966 } 1967 }; 1968 1969 /** 1970 * @file guid.js 1971 * @module guid 1972 */ 1973 // Default value for GUIDs. This allows us to reset the GUID counter in tests. 1974 // 1975 // The initial GUID is 3 because some users have come to rely on the first 1976 // default player ID ending up as `vjs_video_3`. 1977 // 1978 // See: https://github.com/videojs/video.js/pull/6216 1979 var _initialGuid = 3; 1980 /** 1981 * Unique ID for an element or function 1982 * 1983 * @type {Number} 1984 */ 1985 1986 var _guid = _initialGuid; 1987 /** 1988 * Get a unique auto-incrementing ID by number that has not been returned before. 1989 * 1990 * @return {number} 1991 * A new unique ID. 1992 */ 1993 1994 function newGUID() { 1995 return _guid++; 1996 } 1997 1998 /** 1999 * @file dom-data.js 2000 * @module dom-data 2001 */ 2002 var FakeWeakMap; 2003 2004 if (!window_1.WeakMap) { 2005 FakeWeakMap = /*#__PURE__*/function () { 2006 function FakeWeakMap() { 2007 this.vdata = 'vdata' + Math.floor(window_1.performance && window_1.performance.now() || Date.now()); 2008 this.data = {}; 2009 } 2010 2011 var _proto = FakeWeakMap.prototype; 2012 2013 _proto.set = function set(key, value) { 2014 var access = key[this.vdata] || newGUID(); 2015 2016 if (!key[this.vdata]) { 2017 key[this.vdata] = access; 2018 } 2019 2020 this.data[access] = value; 2021 return this; 2022 }; 2023 2024 _proto.get = function get(key) { 2025 var access = key[this.vdata]; // we have data, return it 2026 2027 if (access) { 2028 return this.data[access]; 2029 } // we don't have data, return nothing. 2030 // return undefined explicitly as that's the contract for this method 2031 2032 2033 log$1('We have no data for this element', key); 2034 return undefined; 2035 }; 2036 2037 _proto.has = function has(key) { 2038 var access = key[this.vdata]; 2039 return access in this.data; 2040 }; 2041 2042 _proto["delete"] = function _delete(key) { 2043 var access = key[this.vdata]; 2044 2045 if (access) { 2046 delete this.data[access]; 2047 delete key[this.vdata]; 2048 } 2049 }; 2050 2051 return FakeWeakMap; 2052 }(); 2053 } 2054 /** 2055 * Element Data Store. 2056 * 2057 * Allows for binding data to an element without putting it directly on the 2058 * element. Ex. Event listeners are stored here. 2059 * (also from jsninja.com, slightly modified and updated for closure compiler) 2060 * 2061 * @type {Object} 2062 * @private 2063 */ 2064 2065 2066 var DomData = window_1.WeakMap ? new WeakMap() : new FakeWeakMap(); 2067 2068 /** 2069 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) 2070 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) 2071 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as 2072 * robust as jquery's, so there's probably some differences. 2073 * 2074 * @file events.js 2075 * @module events 2076 */ 2077 /** 2078 * Clean up the listener cache and dispatchers 2079 *
2080 * @param {Element|Object} elem 2081 * Element to clean up 2082 * 2083 * @param {string} type 2084 * Type of event to clean up 2085 */ 2086 2087 function _cleanUpEvents(elem, type) { 2088 if (!DomData.has(elem)) { 2089 return; 2090 } 2091 2092 var data = DomData.get(elem); // Remove the events of a particular type if there are none left 2093 2094 if (data.handlers[type].length === 0) { 2095 delete data.handlers[type]; // data.handlers[type] = null; 2096 // Setting to null was causing an error with data.handlers 2097 // Remove the meta-handler from the element 2098 2099 if (elem.removeEventListener) { 2100 elem.removeEventListener(type, data.dispatcher, false); 2101 } else if (elem.detachEvent) { 2102 elem.detachEvent('on' + type, data.dispatcher); 2103 } 2104 } // Remove the events object if there are no types left 2105 2106 2107 if (Object.getOwnPropertyNames(data.handlers).length <= 0) { 2108 delete data.handlers; 2109 delete data.dispatcher; 2110 delete data.disabled; 2111 } // Finally remove the element data if there is no data left 2112 2113 2114 if (Object.getOwnPropertyNames(data).length === 0) { 2115 DomData["delete"](elem); 2116 } 2117 } 2118 /** 2119 * Loops through an array of event types and calls the requested method for each type. 2120 * 2121 * @param {Function} fn 2122 * The event method we want to use. 2123 * 2124 * @param {Element|Object} elem 2125 * Element or object to bind listeners to 2126 * 2127 * @param {string} type 2128 * Type of event to bind to. 2129 * 2130 * @param {EventTarget~EventListener} callback 2131 * Event listener. 2132 */ 2133 2134 2135 function _handleMultipleEvents(fn, elem, types, callback) { 2136 types.forEach(function (type) { 2137 // Call the event method for each one of the types 2138 fn(elem, type, callback); 2139 }); 2140 } 2141 /** 2142 * Fix a native event to have standard property values 2143 * 2144 * @param {Object} event 2145 * Event object to fix. 2146 * 2147 * @return {Object} 2148 * Fixed event object. 2149 */ 2150 2151 2152 function fixEvent(event) { 2153 if (event.fixed_) { 2154 return event; 2155 } 2156 2157 function returnTrue() { 2158 return true; 2159 } 2160 2161 function returnFalse() { 2162 return false; 2163 } // Test if fixing up is needed 2164 // Used to check if !event.stopPropagation instead of isPropagationStopped 2165 // But native events return true for stopPropagation, but don't have 2166 // other expected methods like isPropagationStopped. Seems to be a problem 2167 // with the Javascript Ninja code. So we're just overriding all events now. 2168 2169 2170 if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) { 2171 var old = event || window_1.event; 2172 event = {}; // Clone the old object so that we can modify the values event = {}; 2173 // IE8 Doesn't like when you mess with native event properties 2174 // Firefox returns false for event.hasOwnProperty('type') and other props 2175 // which makes copying more difficult. 2176 // TODO: Probably best to create a whitelist of event props 2177 2178 for (var key in old) { 2179 // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y 2180 // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation 2181 // and webkitMovementX/Y 2182 if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { 2183 // Chrome 32+ warns if you try to copy deprecated returnValue, but 2184 // we still want to if preventDefault isn't supported (IE8). 2185 if (!(key === 'returnValue' && old.preventDefault)) { 2186 event[key] = old[key]; 2187 } 2188 } 2189 } // The event occurred on this element 2190 2191 2192 if (!event.target) { 2193 event.target = event.srcElement || document_1; 2194 } // Handle which other element the event is related to 2195 2196 2197 if (!event.relatedTarget) { 2198 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 2199 } // Stop the default browser action 2200 2201 2202 event.preventDefault = function () { 2203 if (old.preventDefault) { 2204 old.preventDefault(); 2205 } 2206 2207 event.returnValue = false; 2208 old.returnValue = false; 2209 event.defaultPrevented = true; 2210 }; 2211 2212 event.defaultPrevented = false; // Stop the event from bubbling 2213 2214 event.stopPropagation = function () { 2215 if (old.stopPropagation) { 2216 old.stopPropagation(); 2217 } 2218 2219 event.cancelBubble = true; 2220 old.cancelBubble = true; 2221 event.isPropagationStopped = returnTrue; 2222 }; 2223 2224 event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers 2225 2226 event.stopImmediatePropagation = function () { 2227 if (old.stopImmediatePropagation) { 2228 old.stopImmediatePropagation(); 2229 } 2230 2231 event.isImmediatePropagationStopped = returnTrue; 2232 event.stopPropagation(); 2233 }; 2234 2235 event.isImmediatePropagationStopped = returnFalse; // Handle mouse position 2236 2237 if (event.clientX !== null && event.clientX !== undefined) { 2238 var doc = document_1.documentElement; 2239 var body = document_1.body; 2240 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 2241 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 2242 } // Handle key presses 2243 2244 2245 event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: 2246 // 0 == left; 1 == middle; 2 == right 2247 2248 if (event.button !== null && event.button !== undefined) { 2249 // The following is disabled because it does not pass videojs-standard 2250 // and... yikes. 2251 2252 /* eslint-disable */ 2253 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; 2254 /* eslint-enable */ 2255 } 2256 } 2257 2258 event.fixed_ = true; // Returns fixed-up instance 2259 2260 return event; 2261 } 2262 /** 2263 * Whether passive event listeners are supported 2264 */ 2265 2266 var _supportsPassive; 2267 2268 var supportsPassive = function supportsPassive() { 2269 if (typeof _supportsPassive !== 'boolean') { 2270 _supportsPassive = false; 2271
2272 try { 2273 var opts = Object.defineProperty({}, 'passive', { 2274 get: function get() { 2275 _supportsPassive = true; 2276 } 2277 }); 2278 window_1.addEventListener('test', null, opts); 2279 window_1.removeEventListener('test', null, opts); 2280 } catch (e) {// disregard 2281 } 2282 } 2283 2284 return _supportsPassive; 2285 }; 2286 /** 2287 * Touch events Chrome expects to be passive 2288 */ 2289 2290 2291 var passiveEvents = ['touchstart', 'touchmove']; 2292 /** 2293 * Add an event listener to element 2294 * It stores the handler function in a separate cache object 2295 * and adds a generic handler to the element's event, 2296 * along with a unique id (guid) to the element. 2297 * 2298 * @param {Element|Object} elem 2299 * Element or object to bind listeners to 2300 * 2301 * @param {string|string[]} type 2302 * Type of event to bind to. 2303 * 2304 * @param {EventTarget~EventListener} fn 2305 * Event listener. 2306 */ 2307 2308 function on(elem, type, fn) { 2309 if (Array.isArray(type)) { 2310 return _handleMultipleEvents(on, elem, type, fn); 2311 } 2312 2313 if (!DomData.has(elem)) { 2314 DomData.set(elem, {}); 2315 } 2316 2317 var data = DomData.get(elem); // We need a place to store all our handler data 2318 2319 if (!data.handlers) { 2320 data.handlers = {}; 2321 } 2322 2323 if (!data.handlers[type]) { 2324 data.handlers[type] = []; 2325 } 2326 2327 if (!fn.guid) { 2328 fn.guid = newGUID(); 2329 } 2330 2331 data.handlers[type].push(fn); 2332 2333 if (!data.dispatcher) { 2334 data.disabled = false; 2335 2336 data.dispatcher = function (event, hash) { 2337 if (data.disabled) { 2338 return; 2339 } 2340 2341 event = fixEvent(event); 2342 var handlers = data.handlers[event.type]; 2343 2344 if (handlers) { 2345 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 2346 var handlersCopy = handlers.slice(0); 2347 2348 for (var m = 0, n = handlersCopy.length; m < n; m++) { 2349 if (event.isImmediatePropagationStopped()) { 2350 break; 2351 } else { 2352 try { 2353 handlersCopy[m].call(elem, event, hash); 2354 } catch (e) { 2355 log$1.error(e); 2356 } 2357 } 2358 } 2359 } 2360 }; 2361 } 2362 2363 if (data.handlers[type].length === 1) { 2364 if (elem.addEventListener) { 2365 var options = false; 2366 2367 if (supportsPassive() && passiveEvents.indexOf(type) > -1) { 2368 options = { 2369 passive: true 2370 }; 2371 } 2372 2373 elem.addEventListener(type, data.dispatcher, options); 2374 } else if (elem.attachEvent) { 2375 elem.attachEvent('on' + type, data.dispatcher); 2376 } 2377 } 2378 } 2379 /** 2380 * Removes event listeners from an element 2381 * 2382 * @param {Element|Object} elem 2383 * Object to remove listeners from. 2384 * 2385 * @param {string|string[]} [type] 2386 * Type of listener to remove. Don't include to remove all events from element. 2387 * 2388 * @param {EventTarget~EventListener} [fn] 2389 * Specific listener to remove. Don't include to remove listeners for an event 2390 * type. 2391 */ 2392 2393 function off(elem, type, fn) { 2394 // Don't want to add a cache object through getElData if not needed 2395 if (!DomData.has(elem)) { 2396 return; 2397 } 2398 2399 var data = DomData.get(elem); // If no events exist, nothing to unbind 2400 2401 if (!data.handlers) { 2402 return; 2403 } 2404 2405 if (Array.isArray(type)) { 2406 return _handleMultipleEvents(off, elem, type, fn); 2407 } // Utility function 2408 2409 2410 var removeType = function removeType(el, t) { 2411 data.handlers[t] = []; 2412 2413 _cleanUpEvents(el, t); 2414 }; // Are we removing all bound events? 2415 2416 2417 if (type === undefined) { 2418 for (var t in data.handlers) { 2419 if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { 2420 removeType(elem, t); 2421 } 2422 } 2423 2424 return; 2425 } 2426 2427 var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind 2428 2429 if (!handlers) { 2430 return; 2431 } // If no listener was provided, remove all listeners for type 2432 2433 2434 if (!fn) { 2435 removeType(elem, type); 2436 return; 2437 } // We're only removing a single handler 2438 2439 2440 if (fn.guid) { 2441 for (var n = 0; n < handlers.length; n++) { 2442 if (handlers[n].guid === fn.guid) { 2443 handlers.splice(n--, 1); 2444 } 2445 } 2446 } 2447 2448 _cleanUpEvents(elem, type); 2449 } 2450 /** 2451 * Trigger an event for an element 2452 *
2453 * @param {Element|Object} elem 2454 * Element to trigger an event on 2455 * 2456 * @param {EventTarget~Event|string} event 2457 * A string (the type) or an event object with a type attribute 2458 * 2459 * @param {Object} [hash] 2460 * data hash to pass along with the event 2461 * 2462 * @return {boolean|undefined} 2463 * Returns the opposite of `defaultPrevented` if default was 2464 * prevented. Otherwise, returns `undefined` 2465 */ 2466 2467 function trigger(elem, event, hash) { 2468 // Fetches element data and a reference to the parent (for bubbling). 2469 // Don't want to add a data object to cache for every parent, 2470 // so checking hasElData first. 2471 var elemData = DomData.has(elem) ? DomData.get(elem) : {}; 2472 var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, 2473 // handler; 2474 // If an event name was passed as a string, creates an event out of it 2475 2476 if (typeof event === 'string') { 2477 event = { 2478 type: event, 2479 target: elem 2480 }; 2481 } else if (!event.target) { 2482 event.target = elem; 2483 } // Normalizes the event properties. 2484 2485 2486 event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. 2487 2488 if (elemData.dispatcher) { 2489 elemData.dispatcher.call(elem, event, hash); 2490 } // Unless explicitly stopped or the event does not bubble (e.g. media events) 2491 // recursively calls this function to bubble the event up the DOM. 2492 2493 2494 if (parent && !event.isPropagationStopped() && event.bubbles === true) { 2495 trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled. 2496 } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) { 2497 if (!DomData.has(event.target)) { 2498 DomData.set(event.target, {}); 2499 } 2500 2501 var targetData = DomData.get(event.target); // Checks if the target has a default action for this event. 2502 2503 if (event.target[event.type]) { 2504 // Temporarily disables event dispatching on the target as we have already executed the handler. 2505 targetData.disabled = true; // Executes the default action. 2506 2507 if (typeof event.target[event.type] === 'function') { 2508 event.target[event.type](); 2509 } // Re-enables event dispatching. 2510 2511 2512 targetData.disabled = false; 2513 } 2514 } // Inform the triggerer if the default was prevented by returning false 2515 2516 2517 return !event.defaultPrevented; 2518 } 2519 /** 2520 * Trigger a listener only once for an event. 2521 * 2522 * @param {Element|Object} elem 2523 * Element or object to bind to. 2524 * 2525 * @param {string|string[]} type 2526 * Name/type of event 2527 * 2528 * @param {Event~EventListener} fn 2529 * Event listener function 2530 */ 2531 2532 function one(elem, type, fn) { 2533 if (Array.isArray(type)) { 2534 return _handleMultipleEvents(one, elem, type, fn); 2535 } 2536 2537 var func = function func() { 2538 off(elem, type, func); 2539 fn.apply(this, arguments); 2540 }; // copy the guid to the new function so it can removed using the original function's ID 2541 2542 2543 func.guid = fn.guid = fn.guid || newGUID(); 2544 on(elem, type, func); 2545 } 2546 /** 2547 * Trigger a listener only once and then turn if off for all 2548 * configured events 2549 * 2550 * @param {Element|Object} elem 2551 * Element or object to bind to. 2552 * 2553 * @param {string|string[]} type 2554 * Name/type of event 2555 * 2556 * @param {Event~EventListener} fn 2557 * Event listener function 2558 */ 2559 2560 function any(elem, type, fn) { 2561 var func = function func() { 2562 off(elem, type, func); 2563 fn.apply(this, arguments); 2564 }; // copy the guid to the new function so it can removed using the original function's ID 2565 2566 2567 func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything 2568 2569 on(elem, type, func); 2570 } 2571 2572 var Events = /*#__PURE__*/Object.freeze({ 2573 __proto__: null, 2574 fixEvent: fixEvent, 2575 on: on, 2576 off: off, 2577 trigger: trigger, 2578 one: one, 2579 any: any 2580 }); 2581 2582 /** 2583 * @file fn.js 2584 * @module fn 2585 */ 2586 var UPDATE_REFRESH_INTERVAL = 30; 2587 /**
2588 * Bind (a.k.a proxy or context). A simple method for changing the context of 2589 * a function. 2590 * 2591 * It also stores a unique id on the function so it can be easily removed from 2592 * events. 2593 * 2594 * @function 2595 * @param {Mixed} context 2596 * The object to bind as scope. 2597 * 2598 * @param {Function} fn 2599 * The function to be bound to a scope. 2600 * 2601 * @param {number} [uid] 2602 * An optional unique ID for the function to be set 2603 * 2604 * @return {Function} 2605 * The new function that will be bound into the context given 2606 */ 2607 2608 var bind = function bind(context, fn, uid) { 2609 // Make sure the function has a unique ID 2610 if (!fn.guid) { 2611 fn.guid = newGUID(); 2612 } // Create the new function that changes the context 2613 2614 2615 var bound = fn.bind(context); // Allow for the ability to individualize this function 2616 // Needed in the case where multiple objects might share the same prototype 2617 // IF both items add an event listener with the same function, then you try to remove just one 2618 // it will remove both because they both have the same guid. 2619 // when using this, you need to use the bind method when you remove the listener as well. 2620 // currently used in text tracks 2621 2622 bound.guid = uid ? uid + '_' + fn.guid : fn.guid; 2623 return bound; 2624 }; 2625 /** 2626 * Wraps the given function, `fn`, with a new function that only invokes `fn` 2627 * at most once per every `wait` milliseconds. 2628 * 2629 * @function 2630 * @param {Function} fn 2631 * The function to be throttled. 2632 * 2633 * @param {number} wait 2634 * The number of milliseconds by which to throttle. 2635 * 2636 * @return {Function} 2637 */ 2638 2639 var throttle = function throttle(fn, wait) { 2640 var last = window_1.performance.now(); 2641 2642 var throttled = function throttled() { 2643 var now = window_1.performance.now(); 2644 2645 if (now - last >= wait) { 2646 fn.apply(void 0, arguments); 2647 last = now; 2648 } 2649 }; 2650 2651 return throttled; 2652 }; 2653 /** 2654 * Creates a debounced function that delays invoking `func` until after `wait` 2655 * milliseconds have elapsed since the last time the debounced function was 2656 * invoked. 2657 * 2658 * Inspired by lodash and underscore implementations. 2659 * 2660 * @function 2661 * @param {Function} func 2662 * The function to wrap with debounce behavior. 2663 * 2664 * @param {number} wait 2665 * The number of milliseconds to wait after the last invocation. 2666 * 2667 * @param {boolean} [immediate] 2668 * Whether or not to invoke the function immediately upon creation. 2669 * 2670 * @param {Object} [context=window] 2671 * The "context" in which the debounced function should debounce. For 2672 * example, if this function should be tied to a Video.js player, 2673 * the player can be passed here. Alternatively, defaults to the 2674 * global `window` object. 2675 * 2676 * @return {Function} 2677 * A debounced function. 2678 */ 2679 2680 var debounce = function debounce(func, wait, immediate, context) { 2681 if (context === void 0) { 2682 context = window_1; 2683 } 2684 2685 var timeout; 2686 2687 var cancel = function cancel() { 2688 context.clearTimeout(timeout); 2689 timeout = null; 2690 }; 2691 /* eslint-disable consistent-this */ 2692 2693 2694 var debounced = function debounced() { 2695 var self = this; 2696 var args = arguments; 2697 2698 var _later = function later() { 2699 timeout = null; 2700 _later = null; 2701 2702 if (!immediate) { 2703 func.apply(self, args); 2704 } 2705 }; 2706 2707 if (!timeout && immediate) { 2708 func.apply(self, args); 2709 } 2710 2711 context.clearTimeout(timeout); 2712 timeout = context.setTimeout(_later, wait); 2713 }; 2714 /* eslint-enable consistent-this */ 2715 2716 2717 debounced.cancel = cancel; 2718 return debounced; 2719 }; 2720 2721 /** 2722 * @file src/js/event-target.js 2723 */ 2724 /** 2725 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It 2726 * adds shorthand functions that wrap around lengthy functions. For example: 2727 * the `on` function is a wrapper around `addEventListener`. 2728 * 2729 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} 2730 * @class EventTarget 2731 */ 2732 2733 var EventTarget$2 = function EventTarget() {}; 2734 /** 2735 * A Custom DOM event. 2736 * 2737 * @typedef {Object} EventTarget~Event 2738 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} 2739 */ 2740 2741 /** 2742 * All event listeners should follow the following format. 2743 * 2744 * @callback EventTarget~EventListener 2745 * @this {EventTarget} 2746 * 2747 * @param {EventTarget~Event} event 2748 * the event that triggered this function 2749 * 2750 * @param {Object} [hash] 2751 * hash of data sent during the event 2752 */ 2753 2754 /**
2755 * An object containing event names as keys and booleans as values. 2756 * 2757 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} 2758 * will have extra functionality. See that function for more information. 2759 * 2760 * @property EventTarget.prototype.allowedEvents_ 2761 * @private 2762 */ 2763 2764 2765 EventTarget$2.prototype.allowedEvents_ = {}; 2766 /** 2767 * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a 2768 * function that will get called when an event with a certain name gets triggered. 2769 * 2770 * @param {string|string[]} type 2771 * An event name or an array of event names. 2772 * 2773 * @param {EventTarget~EventListener} fn 2774 * The function to call with `EventTarget`s 2775 */ 2776 2777 EventTarget$2.prototype.on = function (type, fn) { 2778 // Remove the addEventListener alias before calling Events.on 2779 // so we don't get into an infinite type loop 2780 var ael = this.addEventListener; 2781 2782 this.addEventListener = function () {}; 2783 2784 on(this, type, fn); 2785 this.addEventListener = ael; 2786 }; 2787 /** 2788 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic 2789 * the standard DOM API. 2790 * 2791 * @function 2792 * @see {@link EventTarget#on} 2793 */ 2794 2795 2796 EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on; 2797 /** 2798 * Removes an `event listener` for a specific event from an instance of `EventTarget`. 2799 * This makes it so that the `event listener` will no longer get called when the 2800 * named event happens. 2801 * 2802 * @param {string|string[]} type 2803 * An event name or an array of event names. 2804 * 2805 * @param {EventTarget~EventListener} fn 2806 * The function to remove. 2807 */ 2808 2809 EventTarget$2.prototype.off = function (type, fn) { 2810 off(this, type, fn); 2811 }; 2812 /** 2813 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic 2814 * the standard DOM API. 2815 * 2816 * @function 2817 * @see {@link EventTarget#off} 2818 */ 2819 2820 2821 EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off; 2822 /** 2823 * This function will add an `event listener` that gets triggered only once. After the 2824 * first trigger it will get removed. This is like adding an `event listener` 2825 * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. 2826 * 2827 * @param {string|string[]} type 2828 * An event name or an array of event names. 2829 * 2830 * @param {EventTarget~EventListener} fn 2831 * The function to be called once for each event name. 2832 */ 2833 2834 EventTarget$2.prototype.one = function (type, fn) { 2835 // Remove the addEventListener aliasing Events.on 2836 // so we don't get into an infinite type loop 2837 var ael = this.addEventListener; 2838 2839 this.addEventListener = function () {}; 2840 2841 one(this, type, fn); 2842 this.addEventListener = ael; 2843 }; 2844 2845 EventTarget$2.prototype.any = function (type, fn) { 2846 // Remove the addEventListener aliasing Events.on 2847 // so we don't get into an infinite type loop 2848 var ael = this.addEventListener; 2849 2850 this.addEventListener = function () {}; 2851 2852 any(this, type, fn); 2853 this.addEventListener = ael; 2854 }; 2855 /** 2856 * This function causes an event to happen. This will then cause any `event listeners` 2857 * that are waiting for that event, to get called. If there are no `event listeners` 2858 * for an event then nothing will happen. 2859 * 2860 * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. 2861 * Trigger will also call the `on` + `uppercaseEventName` function. 2862 * 2863 * Example: 2864 * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call 2865 * `onClick` if it exists. 2866 * 2867 * @param {string|EventTarget~Event|Object} event 2868 * The name of the event, an `Event`, or an object with a key of type set to 2869 * an event name. 2870 */ 2871 2872 2873 EventTarget$2.prototype.trigger = function (event) { 2874 var type = event.type || event; // deprecation 2875 // In a future version we should default target to `this` 2876 // similar to how we default the target to `elem` in 2877 // `Events.trigger`. Right now the default `target` will be 2878 // `document` due to the `Event.fixEvent` call. 2879 2880 if (typeof event === 'string') { 2881 event = { 2882 type: type 2883 }; 2884 } 2885 2886 event = fixEvent(event); 2887 2888 if (this.allowedEvents_[type] && this['on' + type]) { 2889 this['on' + type](event); 2890 } 2891 2892 trigger(this, event); 2893 }; 2894 /** 2895 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic 2896 * the standard DOM API. 2897 * 2898 * @function 2899 * @see {@link EventTarget#trigger} 2900 */ 2901 2902 2903 EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger; 2904 var EVENT_MAP; 2905 2906 EventTarget$2.prototype.queueTrigger = function (event) { 2907 var _this = this; 2908 2909 // only set up EVENT_MAP if it'll be used 2910 if (!EVENT_MAP) { 2911 EVENT_MAP = new Map(); 2912 } 2913 2914 var type = event.type || event; 2915 var map = EVENT_MAP.get(this); 2916 2917 if (!map) { 2918 map = new Map(); 2919 EVENT_MAP.set(this, map); 2920 } 2921 2922 var oldTimeout = map.get(type); 2923 map["delete"](type); 2924 window_1.clearTimeout(oldTimeout); 2925 var timeout = window_1.setTimeout(function () { 2926 // if we cleared out all timeouts for the current target, delete its map 2927 if (map.size === 0) { 2928 map = null; 2929 EVENT_MAP["delete"](_this); 2930 } 2931 2932 _this.trigger(event); 2933 }, 0); 2934 map.set(type, timeout); 2935 }; 2936 2937 /** 2938 * @file mixins/evented.js 2939 * @module evented 2940 */ 2941 2942 var objName = function objName(obj) { 2943 if (typeof obj.name === 'function') { 2944 return obj.name(); 2945 } 2946 2947 if (typeof obj.name === 'string') { 2948 return obj.name; 2949 } 2950 2951 if (obj.name_) { 2952 return obj.name_; 2953 } 2954 2955 if (obj.constructor && obj.constructor.name) { 2956 return obj.constructor.name; 2957 } 2958 2959 return typeof obj; 2960 }; 2961 /** 2962 * Returns whether or not an object has had the evented mixin applied. 2963 * 2964 * @param {Object} object 2965 * An object to test. 2966 * 2967 * @return {boolean} 2968 * Whether or not the object appears to be evented. 2969 */ 2970 2971 2972 var isEvented = function isEvented(object) { 2973 return object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { 2974 return typeof object[k] === 'function'; 2975 });
2976 }; 2977 /** 2978 * Adds a callback to run after the evented mixin applied. 2979 * 2980 * @param {Object} object 2981 * An object to Add 2982 * @param {Function} callback 2983 * The callback to run. 2984 */ 2985 2986 2987 var addEventedCallback = function addEventedCallback(target, callback) { 2988 if (isEvented(target)) { 2989 callback(); 2990 } else { 2991 if (!target.eventedCallbacks) { 2992 target.eventedCallbacks = []; 2993 } 2994 2995 target.eventedCallbacks.push(callback); 2996 } 2997 }; 2998 /** 2999 * Whether a value is a valid event type - non-empty string or array. 3000 * 3001 * @private 3002 * @param {string|Array} type 3003 * The type value to test. 3004 * 3005 * @return {boolean} 3006 * Whether or not the type is a valid event type. 3007 */ 3008 3009 3010 var isValidEventType = function isValidEventType(type) { 3011 return (// The regex here verifies that the `type` contains at least one non- 3012 // whitespace character. 3013 typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length 3014 ); 3015 }; 3016 /** 3017 * Validates a value to determine if it is a valid event target. Throws if not. 3018 * 3019 * @private 3020 * @throws {Error} 3021 * If the target does not appear to be a valid event target. 3022 * 3023 * @param {Object} target 3024 * The object to test. 3025 * 3026 * @param {Object} obj 3027 * The evented object we are validating for 3028 * 3029 * @param {string} fnName 3030 * The name of the evented mixin function that called this. 3031 */ 3032 3033 3034 var validateTarget = function validateTarget(target, obj, fnName) { 3035 if (!target || !target.nodeName && !isEvented(target)) { 3036 throw new Error("Invalid target for " + objName(obj) + "#" + fnName + "; must be a DOM node or evented object."); 3037 } 3038 }; 3039 /** 3040 * Validates a value to determine if it is a valid event target. Throws if not. 3041 * 3042 * @private 3043 * @throws {Error} 3044 * If the type does not appear to be a valid event type. 3045 * 3046 * @param {string|Array} type 3047 * The type to test. 3048 * 3049 * @param {Object} obj 3050 * The evented object we are validating for 3051 * 3052 * @param {string} fnName 3053 * The name of the evented mixin function that called this. 3054 */ 3055 3056 3057 var validateEventType = function validateEventType(type, obj, fnName) { 3058 if (!isValidEventType(type)) { 3059 throw new Error("Invalid event type for " + objName(obj) + "#" + fnName + "; must be a non-empty string or array."); 3060 } 3061 }; 3062 /** 3063 * Validates a value to determine if it is a valid listener. Throws if not. 3064 * 3065 * @private 3066 * @throws {Error} 3067 * If the listener is not a function. 3068 * 3069 * @param {Function} listener 3070 * The listener to test. 3071 * 3072 * @param {Object} obj 3073 * The evented object we are validating for 3074 * 3075 * @param {string} fnName 3076 * The name of the evented mixin function that called this. 3077 */ 3078 3079 3080 var validateListener = function validateListener(listener, obj, fnName) { 3081 if (typeof listener !== 'function') { 3082 throw new Error("Invalid listener for " + objName(obj) + "#" + fnName + "; must be a function."); 3083 } 3084 }; 3085 /** 3086 * Takes an array of arguments given to `on()` or `one()`, validates them, and 3087 * normalizes them into an object. 3088 * 3089 * @private 3090 * @param {Object} self 3091 * The evented object on which `on()` or `one()` was called. This 3092 * object will be bound as the `this` value for the listener. 3093 * 3094 * @param {Array} args 3095 * An array of arguments passed to `on()` or `one()`. 3096 * 3097 * @param {string} fnName 3098 * The name of the evented mixin function that called this. 3099 * 3100 * @return {Object} 3101 * An object containing useful values for `on()` or `one()` calls. 3102 */ 3103 3104 3105 var normalizeListenArgs = function normalizeListenArgs(self, args, fnName) { 3106 // If the number of arguments is less than 3, the target is always the 3107 // evented object itself. 3108 var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; 3109 var target; 3110 var type; 3111 var listener; 3112 3113 if (isTargetingSelf) { 3114 target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to 3115 // the evented object itself. 3116 3117 if (args.length >= 3) { 3118 args.shift(); 3119 } 3120 3121 type = args[0]; 3122 listener = args[1]; 3123 } else { 3124 target = args[0]; 3125 type = args[1]; 3126 listener = args[2]; 3127 } 3128 3129 validateTarget(target, self, fnName); 3130 validateEventType(type, self, fnName); 3131 validateListener(listener, self, fnName); 3132 listener = bind(self, listener); 3133 return { 3134 isTargetingSelf: isTargetingSelf, 3135 target: target, 3136 type: type, 3137 listener: listener 3138 }; 3139 }; 3140 /** 3141 * Adds the listener to the event type(s) on the target, normalizing for 3142 * the type of target. 3143 * 3144 * @private
3145 * @param {Element|Object} target 3146 * A DOM node or evented object. 3147 * 3148 * @param {string} method 3149 * The event binding method to use ("on" or "one"). 3150 * 3151 * @param {string|Array} type 3152 * One or more event type(s). 3153 * 3154 * @param {Function} listener 3155 * A listener function. 3156 */ 3157 3158 3159 var listen = function listen(target, method, type, listener) { 3160 validateTarget(target, target, method); 3161 3162 if (target.nodeName) { 3163 Events[method](target, type, listener); 3164 } else { 3165 target[method](type, listener); 3166 } 3167 }; 3168 /** 3169 * Contains methods that provide event capabilities to an object which is passed 3170 * to {@link module:evented|evented}. 3171 * 3172 * @mixin EventedMixin 3173 */ 3174 3175 3176 var EventedMixin = { 3177 /** 3178 * Add a listener to an event (or events) on this object or another evented 3179 * object. 3180 * 3181 * @param {string|Array|Element|Object} targetOrType 3182 * If this is a string or array, it represents the event type(s) 3183 * that will trigger the listener. 3184 * 3185 * Another evented object can be passed here instead, which will 3186 * cause the listener to listen for events on _that_ object. 3187 * 3188 * In either case, the listener's `this` value will be bound to 3189 * this object. 3190 * 3191 * @param {string|Array|Function} typeOrListener 3192 * If the first argument was a string or array, this should be the 3193 * listener function. Otherwise, this is a string or array of event 3194 * type(s). 3195 * 3196 * @param {Function} [listener] 3197 * If the first argument was another evented object, this will be 3198 * the listener function. 3199 */ 3200 on: function on() { 3201 var _this = this; 3202 3203 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 3204 args[_key] = arguments[_key]; 3205 } 3206 3207 var _normalizeListenArgs = normalizeListenArgs(this, args, 'on'), 3208 isTargetingSelf = _normalizeListenArgs.isTargetingSelf, 3209 target = _normalizeListenArgs.target, 3210 type = _normalizeListenArgs.type, 3211 listener = _normalizeListenArgs.listener; 3212 3213 listen(target, 'on', type, listener); // If this object is listening to another evented object. 3214 3215 if (!isTargetingSelf) { 3216 // If this object is disposed, remove the listener. 3217 var removeListenerOnDispose = function removeListenerOnDispose() { 3218 return _this.off(target, type, listener); 3219 }; // Use the same function ID as the listener so we can remove it later it 3220 // using the ID of the original listener. 3221 3222 3223 removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures 3224 // that if the target is disposed BEFORE this object, we remove the 3225 // removal listener that was just added. Otherwise, we create a memory leak. 3226 3227 var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { 3228 return _this.off('dispose', removeListenerOnDispose); 3229 }; // Use the same function ID as the listener so we can remove it later 3230 // it using the ID of the original listener. 3231 3232 3233 removeRemoverOnTargetDispose.guid = listener.guid; 3234 listen(this, 'on', 'dispose', removeListenerOnDispose); 3235 listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); 3236 } 3237 }, 3238 3239 /** 3240 * Add a listener to an event (or events) on this object or another evented 3241 * object. The listener will be called once per event and then removed. 3242 * 3243 * @param {string|Array|Element|Object} targetOrType 3244 * If this is a string or array, it represents the event type(s) 3245 * that will trigger the listener. 3246 * 3247 * Another evented object can be passed here instead, which will 3248 * cause the listener to listen for events on _that_ object. 3249 * 3250 * In either case, the listener's `this` value will be bound to 3251 * this object. 3252 * 3253 * @param {string|Array|Function} typeOrListener 3254 * If the first argument was a string or array, this should be the 3255 * listener function. Otherwise, this is a string or array of event 3256 * type(s). 3257 * 3258 * @param {Function} [listener] 3259 * If the first argument was another evented object, this will be 3260 * the listener function. 3261 */ 3262 one: function one() { 3263 var _this2 = this; 3264 3265 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 3266 args[_key2] = arguments[_key2]; 3267 } 3268 3269 var _normalizeListenArgs2 = normalizeListenArgs(this, args, 'one'), 3270 isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, 3271 target = _normalizeListenArgs2.target, 3272 type = _normalizeListenArgs2.type, 3273 listener = _normalizeListenArgs2.listener; // Targeting this evented object. 3274 3275 3276 if (isTargetingSelf) { 3277 listen(target, 'one', type, listener); // Targeting another evented object. 3278 } else { 3279 // TODO: This wrapper is incorrect! It should only 3280 // remove the wrapper for the event type that called it. 3281 // Instead all listners are removed on the first trigger! 3282 // see https://github.com/videojs/video.js/issues/5962 3283 var wrapper = function wrapper() { 3284 _this2.off(target, type, wrapper); 3285 3286 for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 3287 largs[_key3] = arguments[_key3]; 3288 } 3289 3290 listener.apply(null, largs); 3291 }; // Use the same function ID as the listener so we can remove it later 3292 // it using the ID of the original listener. 3293 3294 3295 wrapper.guid = listener.guid; 3296 listen(target, 'one', type, wrapper); 3297 } 3298 }, 3299 3300 /** 3301 * Add a listener to an event (or events) on this object or another evented 3302 * object. The listener will only be called once for the first event that is triggered 3303 * then removed. 3304 *
3305 * @param {string|Array|Element|Object} targetOrType 3306 * If this is a string or array, it represents the event type(s) 3307 * that will trigger the listener. 3308 * 3309 * Another evented object can be passed here instead, which will 3310 * cause the listener to listen for events on _that_ object. 3311 * 3312 * In either case, the listener's `this` value will be bound to 3313 * this object. 3314 * 3315 * @param {string|Array|Function} typeOrListener 3316 * If the first argument was a string or array, this should be the 3317 * listener function. Otherwise, this is a string or array of event 3318 * type(s). 3319 * 3320 * @param {Function} [listener] 3321 * If the first argument was another evented object, this will be 3322 * the listener function. 3323 */ 3324 any: function any() { 3325 var _this3 = this; 3326 3327 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 3328 args[_key4] = arguments[_key4]; 3329 } 3330 3331 var _normalizeListenArgs3 = normalizeListenArgs(this, args, 'any'), 3332 isTargetingSelf = _normalizeListenArgs3.isTargetingSelf, 3333 target = _normalizeListenArgs3.target, 3334 type = _normalizeListenArgs3.type, 3335 listener = _normalizeListenArgs3.listener; // Targeting this evented object. 3336 3337 3338 if (isTargetingSelf) { 3339 listen(target, 'any', type, listener); // Targeting another evented object. 3340 } else { 3341 var wrapper = function wrapper() { 3342 _this3.off(target, type, wrapper); 3343 3344 for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 3345 largs[_key5] = arguments[_key5]; 3346 } 3347 3348 listener.apply(null, largs); 3349 }; // Use the same function ID as the listener so we can remove it later 3350 // it using the ID of the original listener. 3351 3352 3353 wrapper.guid = listener.guid; 3354 listen(target, 'any', type, wrapper); 3355 } 3356 }, 3357 3358 /** 3359 * Removes listener(s) from event(s) on an evented object. 3360 * 3361 * @param {string|Array|Element|Object} [targetOrType] 3362 * If this is a string or array, it represents the event type(s). 3363 * 3364 * Another evented object can be passed here instead, in which case 3365 * ALL 3 arguments are _required_. 3366 * 3367 * @param {string|Array|Function} [typeOrListener] 3368 * If the first argument was a string or array, this may be the 3369 * listener function. Otherwise, this is a string or array of event 3370 * type(s). 3371 * 3372 * @param {Function} [listener] 3373 * If the first argument was another evented object, this will be 3374 * the listener function; otherwise, _all_ listeners bound to the 3375 * event type(s) will be removed. 3376 */ 3377 off: function off$1(targetOrType, typeOrListener, listener) { 3378 // Targeting this evented object. 3379 if (!targetOrType || isValidEventType(targetOrType)) { 3380 off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object. 3381 } else { 3382 var target = targetOrType; 3383 var type = typeOrListener; // Fail fast and in a meaningful way! 3384 3385 validateTarget(target, this, 'off'); 3386 validateEventType(type, this, 'off'); 3387 validateListener(listener, this, 'off'); // Ensure there's at least a guid, even if the function hasn't been used 3388 3389 listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given 3390 // the same guid as the event listener in on(). 3391 3392 this.off('dispose', listener); 3393 3394 if (target.nodeName) { 3395 off(target, type, listener); 3396 off(target, 'dispose', listener); 3397 } else if (isEvented(target)) { 3398 target.off(type, listener); 3399 target.off('dispose', listener); 3400 } 3401 } 3402 }, 3403 3404 /** 3405 * Fire an event on this evented object, causing its listeners to be called. 3406 * 3407 * @param {string|Object} event 3408 * An event type or an object with a type property. 3409 * 3410 * @param {Object} [hash] 3411 * An additional object to pass along to listeners. 3412 * 3413 * @return {boolean} 3414 * Whether or not the default behavior was prevented. 3415 */ 3416 trigger: function trigger$1(event, hash) { 3417 validateTarget(this.eventBusEl_, this, 'trigger'); 3418 var type = event && typeof event !== 'string' ? event.type : event; 3419 3420 if (!isValidEventType(type)) { 3421 var 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.'; 3422 3423 if (event) { 3424 (this.log || log$1).error(error); 3425 } else { 3426 throw new Error(error); 3427 } 3428 } 3429 3430 return trigger(this.eventBusEl_, event, hash); 3431 } 3432 }; 3433 /** 3434 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. 3435 * 3436 * @param {Object} target 3437 * The object to which to add event methods. 3438 * 3439 * @param {Object} [options={}] 3440 * Options for customizing the mixin behavior. 3441 * 3442 * @param {string} [options.eventBusKey] 3443 * By default, adds a `eventBusEl_` DOM element to the target object, 3444 * which is used as an event bus. If the target object already has a 3445 * DOM element that should be used, pass its key here. 3446 * 3447 * @return {Object} 3448 * The target object. 3449 */ 3450 3451 function evented(target, options) { 3452 if (options === void 0) { 3453 options = {}; 3454 } 3455 3456 var _options = options, 3457 eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_. 3458
3459 if (eventBusKey) { 3460 if (!target[eventBusKey].nodeName) { 3461 throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element."); 3462 } 3463 3464 target.eventBusEl_ = target[eventBusKey]; 3465 } else { 3466 target.eventBusEl_ = createEl('span', { 3467 className: 'vjs-event-bus' 3468 }); 3469 } 3470 3471 assign(target, EventedMixin); 3472 3473 if (target.eventedCallbacks) { 3474 target.eventedCallbacks.forEach(function (callback) { 3475 callback(); 3476 }); 3477 } // When any evented object is disposed, it removes all its listeners. 3478 3479 3480 target.on('dispose', function () { 3481 target.off(); 3482 [target, target.el_, target.eventBusEl_].forEach(function (val) { 3483 if (val && DomData.has(val)) { 3484 DomData["delete"](val); 3485 } 3486 }); 3487 window_1.setTimeout(function () { 3488 target.eventBusEl_ = null; 3489 }, 0); 3490 }); 3491 return target; 3492 } 3493 3494 /** 3495 * @file mixins/stateful.js 3496 * @module stateful 3497 */ 3498 /** 3499 * Contains methods that provide statefulness to an object which is passed 3500 * to {@link module:stateful}. 3501 * 3502 * @mixin StatefulMixin 3503 */ 3504 3505 var StatefulMixin = { 3506 /** 3507 * A hash containing arbitrary keys and values representing the state of 3508 * the object. 3509 * 3510 * @type {Object} 3511 */ 3512 state: {}, 3513 3514 /** 3515 * Set the state of an object by mutating its 3516 * {@link module:stateful~StatefulMixin.state|state} object in place. 3517 * 3518 * @fires module:stateful~StatefulMixin#statechanged 3519 * @param {Object|Function} stateUpdates 3520 * A new set of properties to shallow-merge into the plugin state. 3521 * Can be a plain object or a function returning a plain object. 3522 * 3523 * @return {Object|undefined} 3524 * An object containing changes that occurred. If no changes 3525 * occurred, returns `undefined`. 3526 */ 3527 setState: function setState(stateUpdates) { 3528 var _this = this; 3529 3530 // Support providing the `stateUpdates` state as a function. 3531 if (typeof stateUpdates === 'function') { 3532 stateUpdates = stateUpdates(); 3533 } 3534 3535 var changes; 3536 each(stateUpdates, function (value, key) { 3537 // Record the change if the value is different from what's in the 3538 // current state. 3539 if (_this.state[key] !== value) { 3540 changes = changes || {}; 3541 changes[key] = { 3542 from: _this.state[key], 3543 to: value 3544 }; 3545 } 3546 3547 _this.state[key] = value; 3548 }); // Only trigger "statechange" if there were changes AND we have a trigger 3549 // function. This allows us to not require that the target object be an 3550 // evented object. 3551 3552 if (changes && isEvented(this)) { 3553 /** 3554 * An event triggered on an object that is both 3555 * {@link module:stateful|stateful} and {@link module:evented|evented} 3556 * indicating that its state has changed. 3557 * 3558 * @event module:stateful~StatefulMixin#statechanged 3559 * @type {Object} 3560 * @property {Object} changes 3561 * A hash containing the properties that were changed and 3562 * the values they were changed `from` and `to`. 3563 */ 3564 this.trigger({ 3565 changes: changes, 3566 type: 'statechanged' 3567 }); 3568 } 3569 3570 return changes; 3571 } 3572 }; 3573 /** 3574 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target 3575 * object. 3576 * 3577 * If the target object is {@link module:evented|evented} and has a 3578 * `handleStateChanged` method, that method will be automatically bound to the 3579 * `statechanged` event on itself. 3580 * 3581 * @param {Object} target 3582 * The object to be made stateful. 3583 * 3584 * @param {Object} [defaultState] 3585 * A default set of properties to populate the newly-stateful object's 3586 * `state` property. 3587 * 3588 * @return {Object} 3589 * Returns the `target`. 3590 */ 3591 3592 function stateful(target, defaultState) { 3593 assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state` 3594 // added in that step. 3595 3596 target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists. 3597 3598 if (typeof target.handleStateChanged === 'function' && isEvented(target)) { 3599 target.on('statechanged', target.handleStateChanged); 3600 } 3601 3602 return target; 3603 } 3604 3605 /** 3606 * @file string-cases.js 3607 * @module to-lower-case 3608 */ 3609 3610 /** 3611 * Lowercase the first letter of a string. 3612 * 3613 * @param {string} string 3614 * String to be lowercased 3615 * 3616 * @return {string} 3617 * The string with a lowercased first letter 3618 */ 3619 var toLowerCase = function toLowerCase(string) { 3620 if (typeof string !== 'string') { 3621 return string; 3622 } 3623 3624 return string.replace(/./, function (w) { 3625 return w.toLowerCase(); 3626 });
3627 }; 3628 /** 3629 * Uppercase the first letter of a string. 3630 * 3631 * @param {string} string 3632 * String to be uppercased 3633 * 3634 * @return {string} 3635 * The string with an uppercased first letter 3636 */ 3637 3638 var toTitleCase$1 = function toTitleCase(string) { 3639 if (typeof string !== 'string') { 3640 return string; 3641 } 3642 3643 return string.replace(/./, function (w) { 3644 return w.toUpperCase(); 3645 }); 3646 }; 3647 /** 3648 * Compares the TitleCase versions of the two strings for equality. 3649 * 3650 * @param {string} str1 3651 * The first string to compare 3652 * 3653 * @param {string} str2 3654 * The second string to compare 3655 * 3656 * @return {boolean} 3657 * Whether the TitleCase versions of the strings are equal 3658 */ 3659 3660 var titleCaseEquals = function titleCaseEquals(str1, str2) { 3661 return toTitleCase$1(str1) === toTitleCase$1(str2); 3662 }; 3663 3664 /** 3665 * @file merge-options.js 3666 * @module merge-options 3667 */ 3668 /** 3669 * Merge two objects recursively. 3670 * 3671 * Performs a deep merge like 3672 * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges 3673 * plain objects (not arrays, elements, or anything else). 3674 * 3675 * Non-plain object values will be copied directly from the right-most 3676 * argument. 3677 * 3678 * @static 3679 * @param {Object[]} sources 3680 * One or more objects to merge into a new object. 3681 * 3682 * @return {Object} 3683 * A new object that is the merged result of all sources. 3684 */ 3685 3686 function mergeOptions$3() { 3687 var result = {}; 3688 3689 for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) { 3690 sources[_key] = arguments[_key]; 3691 } 3692 3693 sources.forEach(function (source) { 3694 if (!source) { 3695 return; 3696 } 3697 3698 each(source, function (value, key) { 3699 if (!isPlain(value)) { 3700 result[key] = value; 3701 return; 3702 } 3703 3704 if (!isPlain(result[key])) { 3705 result[key] = {}; 3706 } 3707 3708 result[key] = mergeOptions$3(result[key], value); 3709 }); 3710 }); 3711 return result; 3712 } 3713 3714 var MapSham = /*#__PURE__*/function () { 3715 function MapSham() { 3716 this.map_ = {}; 3717 } 3718 3719 var _proto = MapSham.prototype; 3720 3721 _proto.has = function has(key) { 3722 return key in this.map_; 3723 }; 3724 3725 _proto["delete"] = function _delete(key) { 3726 var has = this.has(key); 3727 delete this.map_[key]; 3728 return has; 3729 }; 3730 3731 _proto.set = function set(key, value) { 3732 this.map_[key] = value; 3733 return this; 3734 }; 3735 3736 _proto.forEach = function forEach(callback, thisArg) { 3737 for (var key in this.map_) { 3738 callback.call(thisArg, this.map_[key], key, this); 3739 } 3740 }; 3741 3742 return MapSham; 3743 }(); 3744 3745 var Map$1 = window_1.Map ? window_1.Map : MapSham; 3746 3747 var SetSham = /*#__PURE__*/function () { 3748 function SetSham() { 3749 this.set_ = {}; 3750 } 3751 3752 var _proto = SetSham.prototype; 3753 3754 _proto.has = function has(key) { 3755 return key in this.set_; 3756 }; 3757 3758 _proto["delete"] = function _delete(key) { 3759 var has = this.has(key); 3760 delete this.set_[key]; 3761 return has; 3762 }; 3763 3764 _proto.add = function add(key) { 3765 this.set_[key] = 1; 3766 return this; 3767 }; 3768 3769 _proto.forEach = function forEach(callback, thisArg) { 3770 for (var key in this.set_) { 3771 callback.call(thisArg, key, key, this); 3772 } 3773 }; 3774 3775 return SetSham; 3776 }(); 3777 3778 var Set = window_1.Set ? window_1.Set : SetSham; 3779 3780 /** 3781 * Player Component - Base class for all UI objects 3782 * 3783 * @file component.js 3784 */ 3785 /** 3786 * Base class for all UI Components. 3787 * Components are UI objects which represent both a javascript object and an element 3788 * in the DOM. They can be children of other components, and can have 3789 * children themselves. 3790 * 3791 * Components can also use methods from {@link EventTarget} 3792 */ 3793 3794 var Component$1 = /*#__PURE__*/function () { 3795 /** 3796 * A callback that is called when a component is ready. Does not have any 3797 * paramters and any callback value will be ignored. 3798 * 3799 * @callback Component~ReadyCallback 3800 * @this Component 3801 */ 3802 3803 /** 3804 * Creates an instance of this class. 3805 * 3806 * @param {Player} player 3807 * The `Player` that this class should be attached to. 3808 * 3809 * @param {Object} [options] 3810 * The key/value store of player options. 3811 * 3812 * @param {Object[]} [options.children] 3813 * An array of children objects to intialize this component with. Children objects have 3814 * a name property that will be used if more than one component of the same type needs to be 3815 * added. 3816 * 3817 * @param {Component~ReadyCallback} [ready] 3818 * Function that gets called when the `Component` is ready. 3819 */ 3820 function Component(player, options, ready) { 3821 // The component might be the player itself and we can't pass `this` to super 3822 if (!player && this.play) { 3823 this.player_ = player = this; // eslint-disable-line 3824 } else { 3825 this.player_ = player; 3826 } 3827 3828 this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method 3829 3830 this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults 3831 3832 this.options_ = mergeOptions$3({}, this.options_); // Updated options with supplied options 3833 3834 options = this.options_ = mergeOptions$3(this.options_, options); // Get ID from options or options element if one is supplied 3835 3836 this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one 3837 3838 if (!this.id_) { 3839 // Don't require the player ID function in the case of mock players 3840 var id = player && player.id && player.id() || 'no_player'; 3841 this.id_ = id + "_component_" + newGUID(); 3842 } 3843 3844 this.name_ = options.name || null; // Create element if one wasn't provided in options 3845 3846 if (options.el) { 3847 this.el_ = options.el; 3848 } else if (options.createEl !== false) { 3849 this.el_ = this.createEl(); 3850 } // if evented is anything except false, we want to mixin in evented 3851 3852 3853 if (options.evented !== false) { 3854 // Make this an evented object and use `el_`, if available, as its event bus 3855 evented(this, { 3856 eventBusKey: this.el_ ? 'el_' : null 3857 }); 3858 this.handleLanguagechange = this.handleLanguagechange.bind(this); 3859 this.on(this.player_, 'languagechange', this.handleLanguagechange); 3860 } 3861 3862 stateful(this, this.constructor.defaultState); 3863 this.children_ = []; 3864 this.childIndex_ = {}; 3865 this.childNameIndex_ = {}; 3866 this.setTimeoutIds_ = new Set(); 3867 this.setIntervalIds_ = new Set(); 3868 this.rafIds_ = new Set(); 3869 this.namedRafs_ = new Map$1(); 3870 this.clearingTimersOnDispose_ = false; // Add any child components in options 3871 3872 if (options.initChildren !== false) { 3873 this.initChildren();
3874 } // Don't want to trigger ready here or it will go before init is actually 3875 // finished for all children that run this constructor 3876 3877 3878 this.ready(ready); 3879 3880 if (options.reportTouchActivity !== false) { 3881 this.enableTouchActivity(); 3882 } 3883 } 3884 /** 3885 * Dispose of the `Component` and all child components. 3886 * 3887 * @fires Component#dispose 3888 */ 3889 3890 3891 var _proto = Component.prototype; 3892 3893 _proto.dispose = function dispose() { 3894 // Bail out if the component has already been disposed. 3895 if (this.isDisposed_) { 3896 return; 3897 } 3898 3899 if (this.readyQueue_) { 3900 this.readyQueue_.length = 0; 3901 } 3902 /** 3903 * Triggered when a `Component` is disposed. 3904 * 3905 * @event Component#dispose 3906 * @type {EventTarget~Event} 3907 * 3908 * @property {boolean} [bubbles=false] 3909 * set to false so that the dispose event does not 3910 * bubble up 3911 */ 3912 3913 3914 this.trigger({ 3915 type: 'dispose', 3916 bubbles: false 3917 }); 3918 this.isDisposed_ = true; // Dispose all children. 3919 3920 if (this.children_) { 3921 for (var i = this.children_.length - 1; i >= 0; i--) { 3922 if (this.children_[i].dispose) { 3923 this.children_[i].dispose(); 3924 } 3925 } 3926 } // Delete child references 3927 3928 3929 this.children_ = null; 3930 this.childIndex_ = null; 3931 this.childNameIndex_ = null; 3932 this.parentComponent_ = null; 3933 3934 if (this.el_) { 3935 // Remove element from DOM 3936 if (this.el_.parentNode) { 3937 this.el_.parentNode.removeChild(this.el_); 3938 } 3939 3940 this.el_ = null; 3941 } // remove reference to the player after disposing of the element 3942 3943 3944 this.player_ = null; 3945 } 3946 /** 3947 * Determine whether or not this component has been disposed. 3948 * 3949 * @return {boolean} 3950 * If the component has been disposed, will be `true`. Otherwise, `false`. 3951 */ 3952 ; 3953 3954 _proto.isDisposed = function isDisposed() { 3955 return Boolean(this.isDisposed_); 3956 } 3957 /** 3958 * Return the {@link Player} that the `Component` has attached to. 3959 * 3960 * @return {Player} 3961 * The player that this `Component` has attached to. 3962 */ 3963 ; 3964 3965 _proto.player = function player() { 3966 return this.player_; 3967 } 3968 /** 3969 * Deep merge of options objects with new options. 3970 * > Note: When both `obj` and `options` contain properties whose values are objects. 3971 * The two properties get merged using {@link module:mergeOptions} 3972 * 3973 * @param {Object} obj 3974 * The object that contains new options. 3975 * 3976 * @return {Object} 3977 * A new object of `this.options_` and `obj` merged together. 3978 */ 3979 ; 3980 3981 _proto.options = function options(obj) { 3982 if (!obj) { 3983 return this.options_; 3984 } 3985 3986 this.options_ = mergeOptions$3(this.options_, obj); 3987 return this.options_; 3988 } 3989 /** 3990 * Get the `Component`s DOM element 3991 * 3992 * @return {Element} 3993 * The DOM element for this `Component`. 3994 */ 3995 ; 3996 3997 _proto.el = function el() { 3998 return this.el_; 3999 } 4000 /** 4001 * Create the `Component`s DOM element. 4002 * 4003 * @param {string} [tagName] 4004 * Element's DOM node type. e.g. 'div' 4005 * 4006 * @param {Object} [properties] 4007 * An object of properties that should be set. 4008 * 4009 * @param {Object} [attributes] 4010 * An object of attributes that should be set. 4011 * 4012 * @return {Element} 4013 * The element that gets created. 4014 */ 4015 ; 4016 4017 _proto.createEl = function createEl$1(tagName, properties, attributes) { 4018 return createEl(tagName, properties, attributes); 4019 } 4020 /** 4021 * Localize a string given the string in english. 4022 * 4023 * If tokens are provided, it'll try and run a simple token replacement on the provided string. 4024 * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. 4025 * 4026 * If a `defaultValue` is provided, it'll use that over `string`, 4027 * if a value isn't found in provided language files. 4028 * This is useful if you want to have a descriptive key for token replacement 4029 * but have a succinct localized string and not require `en.json` to be included. 4030 * 4031 * Currently, it is used for the progress bar timing. 4032 * ```js 4033 * { 4034 * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" 4035 * } 4036 * ``` 4037 * It is then used like so: 4038 * ```js 4039 * this.localize('progress bar timing: currentTime={1} duration{2}', 4040 * [this.player_.currentTime(), this.player_.duration()], 4041 * '{1} of {2}'); 4042 * ``` 4043 * 4044 * Which outputs something like: `01:23 of 24:56`.
4045 * 4046 * 4047 * @param {string} string 4048 * The string to localize and the key to lookup in the language files. 4049 * @param {string[]} [tokens] 4050 * If the current item has token replacements, provide the tokens here. 4051 * @param {string} [defaultValue] 4052 * Defaults to `string`. Can be a default value to use for token replacement 4053 * if the lookup key is needed to be separate. 4054 * 4055 * @return {string} 4056 * The localized string or if no localization exists the english string. 4057 */ 4058 ; 4059 4060 _proto.localize = function localize(string, tokens, defaultValue) { 4061 if (defaultValue === void 0) { 4062 defaultValue = string; 4063 } 4064 4065 var code = this.player_.language && this.player_.language(); 4066 var languages = this.player_.languages && this.player_.languages(); 4067 var language = languages && languages[code]; 4068 var primaryCode = code && code.split('-')[0]; 4069 var primaryLang = languages && languages[primaryCode]; 4070 var localizedString = defaultValue; 4071 4072 if (language && language[string]) { 4073 localizedString = language[string]; 4074 } else if (primaryLang && primaryLang[string]) { 4075 localizedString = primaryLang[string]; 4076 } 4077 4078 if (tokens) { 4079 localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { 4080 var value = tokens[index - 1]; 4081 var ret = value; 4082 4083 if (typeof value === 'undefined') { 4084 ret = match; 4085 } 4086 4087 return ret; 4088 }); 4089 } 4090 4091 return localizedString; 4092 } 4093 /** 4094 * Handles language change for the player in components. Should be overriden by sub-components. 4095 * 4096 * @abstract 4097 */ 4098 ; 4099 4100 _proto.handleLanguagechange = function handleLanguagechange() {} 4101 /** 4102 * Return the `Component`s DOM element. This is where children get inserted. 4103 * This will usually be the the same as the element returned in {@link Component#el}. 4104 * 4105 * @return {Element} 4106 * The content element for this `Component`. 4107 */ 4108 ; 4109 4110 _proto.contentEl = function contentEl() { 4111 return this.contentEl_ || this.el_; 4112 } 4113 /** 4114 * Get this `Component`s ID 4115 * 4116 * @return {string} 4117 * The id of this `Component` 4118 */ 4119 ; 4120 4121 _proto.id = function id() { 4122 return this.id_; 4123 } 4124 /** 4125 * Get the `Component`s name. The name gets used to reference the `Component` 4126 * and is set during registration. 4127 * 4128 * @return {string} 4129 * The name of this `Component`. 4130 */ 4131 ; 4132 4133 _proto.name = function name() { 4134 return this.name_; 4135 } 4136 /** 4137 * Get an array of all child components 4138 * 4139 * @return {Array} 4140 * The children 4141 */ 4142 ; 4143 4144 _proto.children = function children() { 4145 return this.children_; 4146 } 4147 /** 4148 * Returns the child `Component` with the given `id`. 4149 * 4150 * @param {string} id 4151 * The id of the child `Component` to get. 4152 * 4153 * @return {Component|undefined} 4154 * The child `Component` with the given `id` or undefined. 4155 */ 4156 ; 4157 4158 _proto.getChildById = function getChildById(id) { 4159 return this.childIndex_[id]; 4160 } 4161 /** 4162 * Returns the child `Component` with the given `name`. 4163 * 4164 * @param {string} name 4165 * The name of the child `Component` to get. 4166 * 4167 * @return {Component|undefined} 4168 * The child `Component` with the given `name` or undefined. 4169 */ 4170 ; 4171 4172 _proto.getChild = function getChild(name) { 4173 if (!name) { 4174 return; 4175 } 4176 4177 return this.childNameIndex_[name]; 4178 } 4179 /** 4180 * Returns the descendant `Component` following the givent 4181 * descendant `names`. For instance ['foo', 'bar', 'baz'] would 4182 * try to get 'foo' on the current component, 'bar' on the 'foo' 4183 * component and 'baz' on the 'bar' component and return undefined 4184 * if any of those don't exist. 4185 * 4186 * @param {...string[]|...string} names 4187 * The name of the child `Component` to get. 4188 * 4189 * @return {Component|undefined} 4190 * The descendant `Component` following the given desce
4190ndant 4191 * `names` or undefined. 4192 */ 4193 ; 4194 4195 _proto.getDescendant = function getDescendant() { 4196 for (var _len = arguments.length, names = new Array(_len), _key = 0; _key < _len; _key++) { 4197 names[_key] = arguments[_key]; 4198 } 4199 4200 // flatten array argument into the main array 4201 names = names.reduce(function (acc, n) { 4202 return acc.concat(n); 4203 }, []); 4204 var currentChild = this; 4205 4206 for (var i = 0; i < names.length; i++) { 4207 currentChild = currentChild.getChild(names[i]); 4208 4209 if (!currentChild || !currentChild.getChild) { 4210 return; 4211 } 4212 } 4213 4214 return currentChild; 4215 } 4216 /** 4217 * Add a child `Component` inside the current `Component`. 4218 * 4219 * 4220 * @param {string|Component} child 4221 * The name or instance of a child to add. 4222 * 4223 * @param {Object} [options={}] 4224 * The key/value store of options that will get passed to children of 4225 * the child. 4226 * 4227 * @param {number} [index=this.children_.length] 4228 * The index to attempt to add a child into. 4229 * 4230 * @return {Component} 4231 * The `Component` that gets added as a child. When using a string the 4232 * `Component` will get created by this process. 4233 */ 4234 ; 4235 4236 _proto.addChild = function addChild(child, options, index) { 4237 if (options === void 0) { 4238 options = {}; 4239 } 4240 4241 if (index === void 0) { 4242 index = this.children_.length; 4243 } 4244 4245 var component; 4246 var componentName; // If child is a string, create component with options 4247 4248 if (typeof child === 'string') { 4249 componentName = toTitleCase$1(child); 4250 var componentClassName = options.componentClass || componentName; // Set name through options 4251 4252 options.name = componentName; // Create a new object & element for this controls set 4253 // If there's no .player_, this is a player 4254 4255 var ComponentClass = Component.getComponent(componentClassName); 4256 4257 if (!ComponentClass) { 4258 throw new Error("Component " + componentClassName + " does not exist"); 4259 } // data stored directly on the videojs object may be 4260 // misidentified as a component to retain 4261 // backwards-compatibility with 4.x. check to make sure the 4262 // component class can be instantiated. 4263 4264 4265 if (typeof ComponentClass !== 'function') { 4266 return null; 4267 } 4268 4269 component = new ComponentClass(this.player_ || this, options); // child is a component instance 4270 } else { 4271 component = child; 4272 } 4273 4274 if (component.parentComponent_) { 4275 component.parentComponent_.removeChild(component); 4276 } 4277 4278 this.children_.splice(index, 0, component); 4279 component.parentComponent_ = this; 4280 4281 if (typeof component.id === 'function') { 4282 this.childIndex_[component.id()] = component; 4283 } // If a name wasn't used to create the component, check if we can use the 4284 // name function of the component 4285 4286 4287 componentName = componentName || component.name && toTitleCase$1(component.name()); 4288 4289 if (componentName) { 4290 this.childNameIndex_[componentName] = component; 4291 this.childNameIndex_[toLowerCase(componentName)] = component; 4292 } // Add the UI object's element to the container div (box) 4293 // Having an element is not required 4294 4295 4296 if (typeof component.el === 'function' && component.el()) { 4297 // If inserting before a component, insert before that component's element 4298 var refNode = null; 4299 4300 if (this.children_[index + 1]) { 4301 // Most children are components, but the video tech is an HTML element 4302 if (this.children_[index + 1].el_) { 4303 refNode = this.children_[index + 1].el_; 4304 } else if (isEl(this.children_[index + 1])) { 4305 refNode = this.children_[index + 1]; 4306 } 4307 } 4308 4309 this.contentEl().insertBefore(component.el(), refNode); 4310 } // Return so it can stored on parent object if desired. 4311 4312 4313 return component; 4314 } 4315 /** 4316 * Remove a child `Component` from this `Component`s list of children. Also removes 4317 * the child `Component`s element from this `Component`s element. 4318 * 4319 * @param {Component} component 4320 * The child `Component` to remove. 4321 */ 4322 ; 4323 4324 _proto.removeChild = function removeChild(component) { 4325 if (typeof component === 'string') { 4326 component = this.getChild(component); 4327 } 4328 4329 if (!component || !this.children_) { 4330 return; 4331 } 4332 4333 var childFound = false; 4334 4335 for (var i = this.children_.length - 1; i >= 0; i--) { 4336 if (this.children_[i] === component) { 4337 childFound = true; 4338 this.children_.splice(i, 1); 4339 break; 4340 } 4341 } 4342 4343 if (!childFound) { 4344 return; 4345 } 4346 4347 component.parentComponent_ = null; 4348 this.childIndex_[component.id()] = null; 4349 this.childNameIndex_[toTitleCase$1(component.name())] = null; 4350 this.childNameIndex_[toLowerCase(component.name())] = null; 4351 var compEl = component.el(); 4352 4353 if (compEl && compEl.parentNode === this.contentEl()) { 4354 this.contentEl().removeChild(component.el()); 4355 } 4356 } 4357 /** 4358 * Add and initialize default child `Component`s based upon options. 4359 */ 4360 ; 4361 4362 _proto.initChildren = function initChildren() { 4363 var _this = this; 4364 4365 var children = this.options_.children; 4366 4367 if (children) { 4368 // `this` is `parent` 4369 var parentOptions = this.options_; 4370 4371 var handleAdd = function handleAdd(child) { 4372 var name = child.name; 4373 var opts = child.opts; // Allow options for children to be set at the parent options 4374 // e.g. videojs(id, { controlBar: false }); 4375 // instead of videojs(id, { children: { controlBar: false }); 4376 4377 if (parentOptions[name] !== undefined) { 4378 opts = parentOptions[name]; 4379 } // Allow for disabling default components 4380 // e.g. options['children']['posterImage'] = false 4381 4382 4383 if (opts === false) { 4384 return; 4385 } // Allow options to be passed as a simple boolean if no configuration 4386 // is necessary. 4387 4388 4389 if (opts === true) { 4390 opts = {}; 4391 } // We also want to pass the original player options 4392 // to each component as well so they don't need to 4393 // reach back into the player for options later. 4394 4395 4396 opts.playerOptions = _this.options_.playerOptions; // Create and add the child component. 4397 // Add a direct reference to the child by name on the parent instance. 4398 // If two of the same component are used, different names should be supplied 4399 // for each 4400 4401 var newChild = _this.addChild(name, opts); 4402 4403 if (newChild) { 4404 _this[name] = newChild; 4405 } 4406 }; // Allow for an array of children details to passed in the options 4407 4408 4409 var workingChildren; 4410 var Tech = Component.getComponent('Tech'); 4411 4412 if (Array.isArray(children)) { 4413 workingChildren = children; 4414 } else { 4415 workingChildren = Object.keys(children); 4416 } 4417 4418 workingChildren // children that are in this.options_ but also in workingChildren would 4419 // give us extra children we do not want. So, we want to filter them out. 4420 .concat(Object.keys(this.options_).filter(function (child) { 4421 return !workingChildren.some(function (wchild) { 4422 if (typeof wchild === 'string') { 4423 return child === wchild; 4424 } 4425 4426 return child === wchild.name; 4427 }); 4428 })).map(function (child) { 4429 var name; 4430 var opts; 4431 4432 if (typeof child === 'string') { 4433 name = child; 4434 opts = children[name] || _this.options_[name] || {}; 4435 } else { 4436 name = child.name; 4437 opts = child; 4438 } 4439 4440 return { 4441 name: name, 4442 opts: opts 4443 }; 4444 }).filter(function (child) { 4445 // we have to make sure that child.name isn't in the techOrder since 4446 // techs are registerd as Components but can't aren't compatible 4447 // See https://github.com/videojs/video.js/issues/2772 4448 var c = Component.getComponent(child.opts.componentClass || toTitleCase$1(child.name)); 4449 return c && !Tech.isTech(c); 4450 }
4450).forEach(handleAdd); 4451 } 4452 } 4453 /** 4454 * Builds the default DOM class name. Should be overriden by sub-components. 4455 * 4456 * @return {string} 4457 * The DOM class name for this object. 4458 * 4459 * @abstract 4460 */ 4461 ; 4462 4463 _proto.buildCSSClass = function buildCSSClass() { 4464 // Child classes can include a function that does: 4465 // return 'CLASS NAME' + this._super(); 4466 return ''; 4467 } 4468 /** 4469 * Bind a listener to the component's ready state. 4470 * Different from event listeners in that if the ready event has already happened 4471 * it will trigger the function immediately. 4472 * 4473 * @return {Component} 4474 * Returns itself; method can be chained. 4475 */ 4476 ; 4477 4478 _proto.ready = function ready(fn, sync) { 4479 if (sync === void 0) { 4480 sync = false; 4481 } 4482 4483 if (!fn) { 4484 return; 4485 } 4486 4487 if (!this.isReady_) { 4488 this.readyQueue_ = this.readyQueue_ || []; 4489 this.readyQueue_.push(fn); 4490 return; 4491 } 4492 4493 if (sync) { 4494 fn.call(this); 4495 } else { 4496 // Call the function asynchronously by default for consistency 4497 this.setTimeout(fn, 1); 4498 } 4499 } 4500 /** 4501 * Trigger all the ready listeners for this `Component`. 4502 * 4503 * @fires Component#ready 4504 */ 4505 ; 4506 4507 _proto.triggerReady = function triggerReady() { 4508 this.isReady_ = true; // Ensure ready is triggered asynchronously 4509 4510 this.setTimeout(function () { 4511 var readyQueue = this.readyQueue_; // Reset Ready Queue 4512 4513 this.readyQueue_ = []; 4514 4515 if (readyQueue && readyQueue.length > 0) { 4516 readyQueue.forEach(function (fn) { 4517 fn.call(this); 4518 }, this); 4519 } // Allow for using event listeners also 4520 4521 /** 4522 * Triggered when a `Component` is ready. 4523 * 4524 * @event Component#ready 4525 * @type {EventTarget~Event} 4526 */ 4527 4528 4529 this.trigger('ready'); 4530 }, 1); 4531 } 4532 /** 4533 * Find a single DOM element matching a `selector`. This can be within the `Component`s 4534 * `contentEl()` or another custom context. 4535 * 4536 * @param {string} selector 4537 * A valid CSS selector, which will be passed to `querySelector`. 4538 * 4539 * @param {Element|string} [context=this.contentEl()] 4540 * A DOM element within which to query. Can also be a selector string in 4541 * which case the first matching element will get used as context. If 4542 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 4543 * nothing it falls back to `document`. 4544 * 4545 * @return {Element|null} 4546 * the dom element that was found, or null 4547 * 4548 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 4549 */ 4550 ; 4551 4552 _proto.$ = function $$1(selector, context) { 4553 return $(selector, context || this.contentEl()); 4554 } 4555 /** 4556 * Finds all DOM element matching a `selector`. This can be within the `Component`s 4557 * `contentEl()` or another custom context. 4558 * 4559 * @param {string} selector 4560 * A valid CSS selector, which will be passed to `querySelectorAll`. 4561 * 4562 * @param {Element|string} [context=this.contentEl()] 4563 * A DOM element within which to query. Can also be a selector string in 4564 * which case the first matching element will get used as context. If 4565 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 4566 * nothing it falls back to `document`. 4567 * 4568 * @return {NodeList} 4569 * a list of dom elements that were found 4570 * 4571 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 4572 */ 4573 ; 4574 4575 _proto.$$ = function $$$1(selector, context) { 4576 return $$(selector, context || this.contentEl()); 4577 } 4578 /** 4579 * Check if a component's element has a CSS class name. 4580 * 4581 * @param {string} classToCheck 4582 * CSS class name to check. 4583 * 4584 * @return {boolean}
4585 * - True if the `Component` has the class. 4586 * - False if the `Component` does not have the class` 4587 */ 4588 ; 4589 4590 _proto.hasClass = function hasClass$1(classToCheck) { 4591 return hasClass(this.el_, classToCheck); 4592 } 4593 /** 4594 * Add a CSS class name to the `Component`s element. 4595 * 4596 * @param {string} classToAdd 4597 * CSS class name to add 4598 */ 4599 ; 4600 4601 _proto.addClass = function addClass$1(classToAdd) { 4602 addClass(this.el_, classToAdd); 4603 } 4604 /** 4605 * Remove a CSS class name from the `Component`s element. 4606 * 4607 * @param {string} classToRemove 4608 * CSS class name to remove 4609 */ 4610 ; 4611 4612 _proto.removeClass = function removeClass$1(classToRemove) { 4613 removeClass(this.el_, classToRemove); 4614 } 4615 /** 4616 * Add or remove a CSS class name from the component's element. 4617 * - `classToToggle` gets added when {@link Component#hasClass} would return false. 4618 * - `classToToggle` gets removed when {@link Component#hasClass} would return true. 4619 * 4620 * @param {string} classToToggle 4621 * The class to add or remove based on (@link Component#hasClass} 4622 * 4623 * @param {boolean|Dom~predicate} [predicate] 4624 * An {@link Dom~predicate} function or a boolean 4625 */ 4626 ; 4627 4628 _proto.toggleClass = function toggleClass$1(classToToggle, predicate) { 4629 toggleClass(this.el_, classToToggle, predicate); 4630 } 4631 /** 4632 * Show the `Component`s element if it is hidden by removing the 4633 * 'vjs-hidden' class name from it. 4634 */ 4635 ; 4636 4637 _proto.show = function show() { 4638 this.removeClass('vjs-hidden'); 4639 } 4640 /** 4641 * Hide the `Component`s element if it is currently showing by adding the 4642 * 'vjs-hidden` class name to it. 4643 */ 4644 ; 4645 4646 _proto.hide = function hide() { 4647 this.addClass('vjs-hidden'); 4648 } 4649 /** 4650 * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' 4651 * class name to it. Used during fadeIn/fadeOut. 4652 * 4653 * @private 4654 */ 4655 ; 4656 4657 _proto.lockShowing = function lockShowing() { 4658 this.addClass('vjs-lock-showing'); 4659 } 4660 /** 4661 * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' 4662 * class name from it. Used during fadeIn/fadeOut. 4663 * 4664 * @private 4665 */ 4666 ; 4667 4668 _proto.unlockShowing = function unlockShowing() { 4669 this.removeClass('vjs-lock-showing'); 4670 } 4671 /** 4672 * Get the value of an attribute on the `Component`s element. 4673 * 4674 * @param {string} attribute 4675 * Name of the attribute to get the value from. 4676 * 4677 * @return {string|null} 4678 * - The value of the attribute that was asked for. 4679 * - Can be an empty string on some browsers if the attribute does not exist 4680 * or has no value 4681 * - Most browsers will return null if the attibute does not exist or has 4682 * no value. 4683 * 4684 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} 4685 */ 4686 ; 4687 4688 _proto.getAttribute = function getAttribute$1(attribute) { 4689 return getAttribute(this.el_, attribute); 4690 } 4691 /** 4692 * Set the value of an attribute on the `Component`'s element 4693 * 4694 * @param {string} attribute 4695 * Name of the attribute to set. 4696 * 4697 * @param {string} value 4698 * Value to set the attribute to. 4699 * 4700 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} 4701 */ 4702 ; 4703 4704 _proto.setAttribute = function setAttribute$1(attribute, value) { 4705 setAttribute(this.el_, attribute, value); 4706 } 4707 /** 4708 * Remove an attribute from the `Component`s element. 4709 * 4710 * @param {string} attribute 4711 * Name of the attribute to remove. 4712 * 4713 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} 4714 */ 4715 ; 4716 4717 _proto.removeAttribute = function removeAttribute$1(attribute) { 4718 removeAttribute(this.el_, attribute); 4719 } 4720 /** 4721 * Get or set the width of the component based upon the CSS styles. 4722 * See {@link Component#dimension} for more detailed information. 4723 * 4724 * @param {number|string} [num] 4725 * The width that you want to set postfixed with '%', 'px' or nothing. 4726 * 4727 * @param {boolean} [skipListeners] 4728 * Skip the componentresize event trigger 4729 * 4730 * @return {number|string} 4731 * The width when getting, zero if there is no width. Can be a string 4732 * postpixed with '%' or 'px'. 4733 */ 4734 ; 4735 4736 _proto.width = function width(num, skipListeners) { 4737 return this.dimension('width', num, skipListeners); 4738 } 4739 /** 4740 * Get or set the height of the component based upon the CSS styles. 4741 * See {@link Component#dimension} for more detailed information. 4742 * 4743 * @param {number|string} [num] 4744 * The height that you want to set postfixed with '%', 'px' or nothing. 4745 * 4746 * @param {boolean} [skipListeners] 4747 * Skip the componentresize event trigger 4748 * 4749 * @return {number|string} 4750 * The width when getting, zero if there is no width. Can be a string 4751 * postpixed with '%' or 'px'. 4752 */ 4753 ; 4754 4755 _proto.height = function height(num, skipListeners) { 4756 return this.dimension('height', num, skipListeners); 4757 } 4758 /** 4759 * Set both the width and height of the `Component` element at the same time. 4760 * 4761 * @param {number|string} width 4762 * Width to set the `Component`s element to. 4763 * 4764 * @param {number|string} height 4765 * Height to set the `Component`s element to. 4766 */ 4767 ; 4768 4769 _proto.dimensions = function dimensions(width, height) { 4770 // Skip componentresize listeners on width for optimization 4771 this.width(width, true); 4772 this.height(height); 4773 } 4774 /** 4775 * Get or set width or height of the `Component` element. This is the shared code 4776 * for the {@link Component#width} and {@link Component#height}. 4777 * 4778 * Things to know: 4779 * - If the width or height in an number this will return the number postfixed with 'px'. 4780 * - If the width/height is a percent this will return the percent postfixed with '%' 4781 * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function 4782 * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. 4783 * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
4783sing-width/} 4784 * for more information 4785 * - If you want the computed style of the component, use {@link Component#currentWidth} 4786 * and {@link {Component#currentHeight} 4787 * 4788 * @fires Component#componentresize 4789 * 4790 * @param {string} widthOrHeight 4791 8 'width' or 'height' 4792 * 4793 * @param {number|string} [num] 4794 8 New dimension 4795 * 4796 * @param {boolean} [skipListeners] 4797 * Skip componentresize event trigger 4798 * 4799 * @return {number} 4800 * The dimension when getting or 0 if unset 4801 */ 4802 ; 4803 4804 _proto.dimension = function dimension(widthOrHeight, num, skipListeners) { 4805 if (num !== undefined) { 4806 // Set to zero if null or literally NaN (NaN !== NaN) 4807 if (num === null || num !== num) { 4808 num = 0; 4809 } // Check if using css width/height (% or px) and adjust 4810 4811 4812 if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { 4813 this.el_.style[widthOrHeight] = num; 4814 } else if (num === 'auto') { 4815 this.el_.style[widthOrHeight] = ''; 4816 } else { 4817 this.el_.style[widthOrHeight] = num + 'px'; 4818 } // skipListeners allows us to avoid triggering the resize event when setting both width and height 4819 4820 4821 if (!skipListeners) { 4822 /** 4823 * Triggered when a component is resized. 4824 * 4825 * @event Component#componentresize 4826 * @type {EventTarget~Event} 4827 */ 4828 this.trigger('componentresize'); 4829 } 4830 4831 return; 4832 } // Not setting a value, so getting it 4833 // Make sure element exists 4834 4835 4836 if (!this.el_) { 4837 return 0; 4838 } // Get dimension value from style 4839 4840 4841 var val = this.el_.style[widthOrHeight]; 4842 var pxIndex = val.indexOf('px'); 4843 4844 if (pxIndex !== -1) { 4845 // Return the pixel value with no 'px' 4846 return parseInt(val.slice(0, pxIndex), 10); 4847 } // No px so using % or no style was set, so falling back to offsetWidth/height 4848 // If component has display:none, offset will return 0 4849 // TODO: handle display:none and no dimension style using px 4850 4851 4852 return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10); 4853 } 4854 /** 4855 * Get the computed width or the height of the component's element. 4856 * 4857 * Uses `window.getComputedStyle`. 4858 * 4859 * @param {string} widthOrHeight 4860 * A string containing 'width' or 'height'. Whichever one you want to get. 4861 * 4862 * @return {number} 4863 * The dimension that gets asked for or 0 if nothing was set 4864 * for that dimension. 4865 */ 4866 ; 4867 4868 _proto.currentDimension = function currentDimension(widthOrHeight) { 4869 var computedWidthOrHeight = 0; 4870 4871 if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { 4872 throw new Error('currentDimension only accepts width or height value'); 4873 } 4874 4875 computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer 4876 4877 computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying 4878 // and we want to check the offset values. 4879 // This code also runs wherever getComputedStyle doesn't exist. 4880 4881 if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) { 4882 var rule = "offset" + toTitleCase$1(widthOrHeight); 4883 computedWidthOrHeight = this.el_[rule]; 4884 } 4885 4886 return computedWidthOrHeight; 4887 } 4888 /** 4889 * An object that contains width and height values of the `Component`s 4890 * computed style. Uses `window.getComputedStyle`. 4891 * 4892 * @typedef {Object} Component~DimensionObject 4893 * 4894 * @property {number} width 4895 * The width of the `Component`s computed style. 4896 * 4897 * @property {number} height 4898 * The height of the `Component`s computed style. 4899 */ 4900 4901 /** 4902 * Get an object that contains computed width and height values of the 4903 * component's element. 4904 * 4905 * Uses `window.getComputedStyle`. 4906 * 4907 * @return {Component~DimensionObject} 4908 * The computed dimensions of the component's element. 4909 */ 4910 ; 4911 4912 _proto.currentDimensions = function currentDimensions() { 4913 return { 4914 width: this.currentDimension('width'), 4915 height: this.currentDimension('height') 4916 }; 4917 } 4918 /** 4919 * Get the computed width of the component's element. 4920 * 4921 * Uses `window.getComputedStyle`. 4922 * 4923 * @return {number} 4924 * The computed width of the component's element. 4925 */ 4926 ; 4927 4928 _proto.currentWidth = function currentWidth() { 4929 return this.currentDimension('width'); 4930 } 4931 /** 4932 * Get the computed height of the component's element. 4933 * 4934 * Uses `window.getComputedStyle`. 4935 * 4936 * @return {number} 4937 * The computed height of the component's element. 4938 */ 4939 ; 4940 4941 _proto.currentHeight = function currentHeight() { 4942 return this.currentDimension('height'); 4943 } 4944 /** 4945 * Set the focus to this component 4946 */ 4947 ; 4948 4949 _proto.focus = function focus() { 4950 this.el_.focus(); 4951 } 4952 /** 4953 * Remove the focus from this component 4954 */ 4955 ; 4956 4957 _proto.blur = function blur() { 4958 this.el_.blur(); 4959 } 4960 /** 4961 * When this Component receives a `keydown` event which it does not process, 4962 * it passes the event to the Player for handling. 4963 * 4964 * @param {EventTarget~Event} event
4965 * The `keydown` event that caused this function to be called. 4966 */ 4967 ; 4968 4969 _proto.handleKeyDown = function handleKeyDown(event) { 4970 if (this.player_) { 4971 // We only stop propagation here because we want unhandled events to fall 4972 // back to the browser. 4973 event.stopPropagation(); 4974 this.player_.handleKeyDown(event); 4975 } 4976 } 4977 /** 4978 * Many components used to have a `handleKeyPress` method, which was poorly 4979 * named because it listened to a `keydown` event. This method name now 4980 * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` 4981 * will not see their method calls stop working. 4982 * 4983 * @param {EventTarget~Event} event 4984 * The event that caused this function to be called. 4985 */ 4986 ; 4987 4988 _proto.handleKeyPress = function handleKeyPress(event) { 4989 this.handleKeyDown(event); 4990 } 4991 /** 4992 * Emit a 'tap' events when touch event support gets detected. This gets used to 4993 * support toggling the controls through a tap on the video. They get enabled 4994 * because every sub-component would have extra overhead otherwise. 4995 * 4996 * @private 4997 * @fires Component#tap 4998 * @listens Component#touchstart 4999 * @listens Component#touchmove 5000 * @listens Component#touchleave 5001 * @listens Component#touchcancel 5002 * @listens Component#touchend 5003 */ 5004 ; 5005 5006 _proto.emitTapEvents = function emitTapEvents() { 5007 // Track the start time so we can determine how long the touch lasted 5008 var touchStart = 0; 5009 var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap 5010 // Other popular libs use anywhere from 2 (hammer.js) to 15, 5011 // so 10 seems like a nice, round number. 5012 5013 var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap 5014 5015 var touchTimeThreshold = 200; 5016 var couldBeTap; 5017 this.on('touchstart', function (event) { 5018 // If more than one finger, don't consider treating this as a click 5019 if (event.touches.length === 1) { 5020 // Copy pageX/pageY from the object 5021 firstTouch = { 5022 pageX: event.touches[0].pageX, 5023 pageY: event.touches[0].pageY 5024 }; // Record start time so we can detect a tap vs. "touch and hold" 5025 5026 touchStart = window_1.performance.now(); // Reset couldBeTap tracking 5027 5028 couldBeTap = true; 5029 } 5030 }); 5031 this.on('touchmove', function (event) { 5032 // If more than one finger, don't consider treating this as a click 5033 if (event.touches.length > 1) { 5034 couldBeTap = false; 5035 } else if (firstTouch) { 5036 // Some devices will throw touchmoves for all but the slightest of taps. 5037 // So, if we moved only a small distance, this could still be a tap 5038 var xdiff = event.touches[0].pageX - firstTouch.pageX; 5039 var ydiff = event.touches[0].pageY - firstTouch.pageY; 5040 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); 5041 5042 if (touchDistance > tapMovementThreshold) { 5043 couldBeTap = false; 5044 } 5045 } 5046 }); 5047 5048 var noTap = function noTap() { 5049 couldBeTap = false; 5050 }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s 5051 5052 5053 this.on('touchleave', noTap); 5054 this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate 5055 // event 5056 5057 this.on('touchend', function (event) { 5058 firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen 5059 5060 if (couldBeTap === true) { 5061 // Measure how long the touch lasted 5062 var touchTime = window_1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap 5063 5064 if (touchTime < touchTimeThreshold) { 5065 // Don't let browser turn this into a click 5066 event.preventDefault(); 5067 /** 5068 * Triggered when a `Component` is tapped. 5069 * 5070 * @event Component#tap 5071 * @type {EventTarget~Event} 5072 */ 5073 5074 this.trigger('tap'); // It may be good to copy the touchend event object and change the 5075 // type to tap, if the other event properties aren't exact after 5076 // Events.fixEvent runs (e.g. event.target) 5077 } 5078 } 5079 }); 5080 } 5081 /** 5082 * This function reports user activity whenever touch events happen. This can get 5083 * turned off by any sub-components that wants touch events to act another way. 5084 * 5085 * Report user touch activity when touch events occur. User activity gets used to 5086 * determine when controls should show/hide. It is simple when it comes to mouse 5087 * events, because any mouse event should show the controls. So we capture mouse 5088 * events that bubble up to the player and report activity when that happens. 5089 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player 5090 * controls. So touch events can't help us at the player level either. 5091 * 5092 * User activity gets checked asynchronously. So what could happen is a tap event 5093 * on the video turns the controls off. Then the `touchend` event bubbles up to 5094 * the player. Which, if it reported user activity, would turn the controls right 5095 * back on. We also don't want to completely block touch events from bubbling up. 5096 * Furthermore a `touchmove` event and anything other than a tap, should not turn 5097 * controls back on. 5098 * 5099 * @listens Component#touchstart 5100 * @listens Component#touchmove 5101 * @listens Component#touchend 5102 * @listens Component#touchcancel 5103 */ 5104 ; 5105 5106 _proto.enableTouchActivity = function enableTouchActivity() { 5107 // Don't continue if the root player doesn't support reporting user activity 5108 if (!this.player() || !this.player().reportUserActivity) { 5109 return; 5110 } // listener for reporting that the user is active 5111 5112 5113 var report = bind(this.player(), this.player().reportUserActivity); 5114 var touchHolding; 5115 this.on('touchstart', function () { 5116 report(); // For as long as the they are touching the device or have their mouse down, 5117 // we consider them active even if they're not moving their finger or mouse. 5118 // So we want to continue to update that they are active 5119 5120 this.clearInterval(touchHolding); // report at the same interval as activityCheck 5121 5122 touchHolding = this.setInterval(report, 250); 5123 }); 5124 5125 var touchEnd = function touchEnd(event) { 5126 report(); // stop the interval that maintains activity if the touch is holding 5127 5128 this.clearInterval(touchHolding); 5129 }; 5130 5131 this.on('touchmove', report); 5132 this.on('touchend', touchEnd); 5133 this.on('touchcancel', touchEnd); 5134 } 5135 /** 5136 * A callback that has no parameters and is bound into `Component`s context. 5137 * 5138 * @callback Component~GenericCallback 5139 * @this Component 5140 */ 5141 5142 /** 5143 * Creates a function that runs after an `x` millisecond timeout. This function is a 5144 * wrapper around `window.setTimeout`. There are a few reasons to use this one 5145 * instead though: 5146 * 1. It gets cleared via {@link Component#clearTimeout} when 5147 * {@link Component#dispose} gets called. 5148 * 2. The function callback will gets turned into a {@link Component~GenericCallback} 5149 * 5150 * > Note: You can't use `window.clearTimeout` on the id returned by this function. This 5151 * will cause its dispose listener not to get cleaned up! Please use 5152 * {@link Component#clearTimeout} or {@link Component#dispose} instead. 5153 * 5154 * @param {Component~GenericCallback} fn 5155 * The function that will be run after `timeout`. 5156 * 5157 * @param {number} timeout 5158 * Timeout in milliseconds to delay before executing the specified function. 5159 * 5160 * @return {number} 5161 * Returns a timeout ID that gets used to identify the timeout. It can also 5162 * get used in {@link Component#clearTimeout} to clear the timeout that 5163 * was set. 5164 * 5165 * @listens Component#dispose 5166 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} 5167 */ 5168 ; 5169 5170 _proto.setTimeout = function setTimeout(fn, timeout) { 5171 var _this2 = this; 5172 5173 // declare as variables so they are properly available in timeout function 5174 // eslint-disable-next-line 5175 var timeoutId; 5176 fn = bind(this, fn); 5177 this.clearTimersOnDispose_(); 5178 timeoutId = window_1.setTimeout(function () { 5179 if (_this2.setTimeoutIds_.has(timeoutId)) { 5180 _this2.setTimeoutIds_["delete"](timeoutId); 5181 } 5182 5183 fn(); 5184 }, timeout); 5185 this.setTimeoutIds_.add(timeoutId); 5186 return timeoutId; 5187 } 5188 /** 5189 * Clears a timeout that gets created via `window.setTimeout` or 5190 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} 5191 * use this function instead of `window.clearTimout`. If you don't your dispose 5192 * listener will not get cleaned up until {@link Component#dispose}! 5193 * 5194 * @param {number} timeoutId 5195 * The id of the timeout to clear. The return value of 5196 * {@link Component#setTimeout} or `window.setTimeout`. 5197 * 5198 * @return {number} 5199 * Returns the timeout id that was cleared. 5200 * 5201 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} 5202 */ 5203 ; 5204 5205 _proto.clearTimeout = function clearTimeout(timeoutId) { 5206 if (this.setTimeoutIds_.has(timeoutId)) { 5207 this.setTimeoutIds_["delete"](timeoutId); 5208 window_1.clearTimeout(timeoutId); 5209 } 5210 5211 return timeoutId; 5212 } 5213 /** 5214 * Creates a function that gets run every `x` milliseconds. This function is a wrapper 5215 * around `window.setInterval`. There are a few reasons to use this one instead though. 5216 * 1. It gets cleared via {@link Component#clearInterval} when 5217 * {@link Component#dispose} gets called. 5218 * 2. The function callback will be a {@link Component~GenericCallback} 5219 * 5220 * @param {Component~GenericCallback} fn 5221 * The function to run every `x` seconds. 5222 * 5223 * @param {number} interval 5224 * Execute the specified function every `x` milliseconds. 5225 * 5226 * @return {number}
5227 * Returns an id that can be used to identify the interval. It can also be be used in 5228 * {@link Component#clearInterval} to clear the interval. 5229 * 5230 * @listens Component#dispose 5231 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} 5232 */ 5233 ; 5234 5235 _proto.setInterval = function setInterval(fn, interval) { 5236 fn = bind(this, fn); 5237 this.clearTimersOnDispose_(); 5238 var intervalId = window_1.setInterval(fn, interval); 5239 this.setIntervalIds_.add(intervalId); 5240 return intervalId; 5241 } 5242 /** 5243 * Clears an interval that gets created via `window.setInterval` or 5244 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} 5245 * use this function instead of `window.clearInterval`. If you don't your dispose 5246 * listener will not get cleaned up until {@link Component#dispose}! 5247 * 5248 * @param {number} intervalId 5249 * The id of the interval to clear. The return value of 5250 * {@link Component#setInterval} or `window.setInterval`. 5251 * 5252 * @return {number} 5253 * Returns the interval id that was cleared. 5254 * 5255 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} 5256 */ 5257 ; 5258 5259 _proto.clearInterval = function clearInterval(intervalId) { 5260 if (this.setIntervalIds_.has(intervalId)) { 5261 this.setIntervalIds_["delete"](intervalId); 5262 window_1.clearInterval(intervalId); 5263 } 5264 5265 return intervalId; 5266 } 5267 /** 5268 * Queues up a callback to be passed to requestAnimationFrame (rAF), but 5269 * with a few extra bonuses: 5270 * 5271 * - Supports browsers that do not support rAF by falling back to 5272 * {@link Component#setTimeout}. 5273 * 5274 * - The callback is turned into a {@link Component~GenericCallback} (i.e. 5275 * bound to the component). 5276 * 5277 * - Automatic cancellation of the rAF callback is handled if the component 5278 * is disposed before it is called. 5279 * 5280 * @param {Component~GenericCallback} fn 5281 * A function that will be bound to this component and executed just 5282 * before the browser's next repaint. 5283 * 5284 * @return {number} 5285 * Returns an rAF ID that gets used to identify the timeout. It can 5286 * also be used in {@link Component#cancelAnimationFrame} to cancel 5287 * the animation frame callback. 5288 * 5289 * @listens Component#dispose 5290 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} 5291 */ 5292 ; 5293 5294 _proto.requestAnimationFrame = function requestAnimationFrame(fn) { 5295 var _this3 = this; 5296 5297 // Fall back to using a timer. 5298 if (!this.supportsRaf_) { 5299 return this.setTimeout(fn, 1000 / 60); 5300 } 5301 5302 this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function 5303 // eslint-disable-next-line 5304 5305 var id; 5306 fn = bind(this, fn); 5307 id = window_1.requestAnimationFrame(function () { 5308 if (_this3.rafIds_.has(id)) { 5309 _this3.rafIds_["delete"](id); 5310 } 5311 5312 fn(); 5313 }); 5314 this.rafIds_.add(id); 5315 return id; 5316 } 5317 /** 5318 * Request an animation frame, but only one named animation 5319 * frame will be queued. Another will never be added until 5320 * the previous one finishes. 5321 * 5322 * @param {string} name 5323 * The name to give this requestAnimationFrame 5324 * 5325 * @param {Component~GenericCallback} fn 5326 * A function that will be bound to this component and executed just 5327 * before the browser's next repaint. 5328 */ 5329 ; 5330 5331 _proto.requestNamedAnimationFrame = function requestNamedAnimationFrame(name, fn) { 5332 var _this4 = this; 5333 5334 if (this.namedRafs_.has(name)) { 5335 return; 5336 } 5337 5338 this.clearTimersOnDispose_(); 5339 fn = bind(this, fn); 5340 var id = this.requestAnimationFrame(function () { 5341 fn(); 5342 5343 if (_this4.namedRafs_.has(name)) { 5344 _this4.namedRafs_["delete"](name); 5345 } 5346 }); 5347 this.namedRafs_.set(name, id); 5348 return name; 5349 } 5350 /** 5351 * Cancels a current named animation frame if it exists. 5352 * 5353 * @param {string} name 5354 * The name of the requestAnimationFrame to cancel. 5355 */ 5356 ; 5357 5358 _proto.cancelNamedAnimationFrame = function cancelNamedAnimationFrame(name) { 5359 if (!this.namedRafs_.has(name)) { 5360 return; 5361 } 5362 5363 this.cancelAnimationFrame(this.namedRafs_.get(name)); 5364 this.namedRafs_["delete"](name); 5365 } 5366 /** 5367 * Cancels a queued callback passed to {@link Component#requestAnimationFrame} 5368 * (rAF). 5369 * 5370 * If you queue an rAF callback via {@link Component#requestAnimationFrame}, 5371 * use this function instead of `window.cancelAnimationFrame`. If you don't, 5372 * your dispose listener will not get cleaned up until {@link Component#dispose}! 5373 * 5374 * @param {number} id 5375 * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. 5376 * 5377 * @return {number} 5378 * Returns the rAF ID that was cleared. 5379 * 5380 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} 5381 */ 5382 ; 5383 5384 _proto.cancelAnimationFrame = function cancelAnimationFrame(id) { 5385 // Fall back to using a timer. 5386 if (!this.supportsRaf_) { 5387 return this.clearTimeout(id); 5388 } 5389 5390 if (this.rafIds_.has(id)) { 5391 this.rafIds_["delete"](id); 5392 window_1.cancelAnimationFrame(id); 5393 } 5394 5395 return id; 5396 } 5397 /** 5398 * A function to setup `requestAnimationFrame`, `setTimeout`, 5399 * and `setInterval`, clearing on dispose. 5400 * 5401 * > Previously each timer added and removed dispose listeners on it's own. 5402 * For better performance it was decided to batch them all, and use `Set`s 5403 * to track outstanding timer ids. 5404 * 5405 * @private 5406 */ 5407 ; 5408 5409 _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() { 5410 var _this5 = this; 5411 5412 if (this.clearingTimersOnDispose_) { 5413 return; 5414 } 5415 5416 this.clearingTimersOnDispose_ = true; 5417 this.one('dispose', function () { 5418 [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEac
5418h(function (_ref) { 5419 var idName = _ref[0], 5420 cancelName = _ref[1]; 5421 5422 // for a `Set` key will actually be the value again 5423 // so forEach((val, val) =>` but for maps we want to use 5424 // the key. 5425 _this5[idName].forEach(function (val, key) { 5426 return _this5[cancelName](key); 5427 }); 5428 }); 5429 _this5.clearingTimersOnDispose_ = false; 5430 }); 5431 } 5432 /** 5433 * Register a `Component` with `videojs` given the name and the component. 5434 * 5435 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s 5436 * should be registered using {@link Tech.registerTech} or 5437 * {@link videojs:videojs.registerTech}. 5438 * 5439 * > NOTE: This function can also be seen on videojs as 5440 * {@link videojs:videojs.registerComponent}. 5441 * 5442 * @param {string} name 5443 * The name of the `Component` to register. 5444 * 5445 * @param {Component} ComponentToRegister 5446 * The `Component` class to register. 5447 * 5448 * @return {Component} 5449 * The `Component` that was registered. 5450 */ 5451 ; 5452 5453 Component.registerComponent = function registerComponent(name, ComponentToRegister) { 5454 if (typeof name !== 'string' || !name) { 5455 throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string."); 5456 } 5457 5458 var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered. 5459 5460 var isTech = Tech && Tech.isTech(ComponentToRegister); 5461 var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); 5462 5463 if (isTech || !isComp) { 5464 var reason; 5465 5466 if (isTech) { 5467 reason = 'techs must be registered using Tech.registerTech()'; 5468 } else { 5469 reason = 'must be a Component subclass'; 5470 } 5471 5472 throw new Error("Illegal component, \"" + name + "\"; " + reason + "."); 5473 } 5474 5475 name = toTitleCase$1(name); 5476 5477 if (!Component.components_) { 5478 Component.components_ = {}; 5479 } 5480 5481 var Player = Component.getComponent('Player'); 5482 5483 if (name === 'Player' && Player && Player.players) { 5484 var players = Player.players; 5485 var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be 5486 // in Players.players. So, we must loop through and verify that the value 5487 // for each item is not null. This allows registration of the Player component 5488 // after all players have been disposed or before any were created. 5489 5490 if (players && playerNames.length > 0 && playerNames.map(function (pname) { 5491 return players[pname]; 5492 }).every(Boolean)) { 5493 throw new Error('Can not register Player component after player has been created.'); 5494 } 5495 } 5496 5497 Component.components_[name] = ComponentToRegister; 5498 Component.components_[toLowerCase(name)] = ComponentToRegister; 5499 return ComponentToRegister; 5500 } 5501 /** 5502 * Get a `Component` based on the name it was registered with. 5503 * 5504 * @param {string} name 5505 * The Name of the component to get. 5506 * 5507 * @return {Component} 5508 * The `Component` that got registered under the given name. 5509 * 5510 * @deprecated In `videojs` 6 this will not return `Component`s that were not 5511 * registered using {@link Component.registerComponent}. Currently we 5512 * check the global `videojs` object for a `Component` name and 5513 * return that if it exists. 5514 */ 5515 ; 5516 5517 Component.getComponent = function getComponent(name) { 5518 if (!name || !Component.components_) { 5519 return; 5520 } 5521 5522 return Component.components_[name]; 5523 }; 5524 5525 return Component; 5526 }(); 5527 /** 5528 * Whether or not this component supports `requestAnimationFrame`. 5529 * 5530 * This is exposed primarily for testing purposes. 5531 * 5532 * @private 5533 * @type {Boolean} 5534 */ 5535 5536 5537 Component$1.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function'; 5538 Component$1.registerComponent('Component', Component$1); 5539 5540 function _assertThisInitialized(self) { 5541 if (self === void 0) { 5542 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 5543 } 5544 5545 return self; 5546 } 5547 5548 var assertThisInitialized = _assertThisInitialized; 5549 5550 function _inheritsLoose(subClass, superClass) { 5551 subClass.prototype = Object.create(superClass.prototype); 5552 subClass.prototype.constructor = subClass; 5553 subClass.__proto__ = superClass; 5554 } 5555 5556 var inheritsLoose = _inheritsLoose; 5557 5558 /** 5559 * @file time-ranges.js 5560 * @module time-ranges 5561 */ 5562 /** 5563 * Returns the time for the specified index at the start or end 5564 * of a TimeRange object. 5565 * 5566 * @typedef {Function} TimeRangeIndex 5567 * 5568 * @param {number} [index=0] 5569 * The range number to return the time for. 5570 * 5571 * @return {number} 5572 * The time offset at the specified index. 5573 * 5574 * @deprecated The index argument must be provided. 5575 * In the future, leaving it out will throw an error. 5576 */ 5577 5578 /** 5579 * An object that contains ranges of time. 5580 * 5581 * @typedef {Object} TimeRange 5582 * 5583 * @property {number} length 5584 * The number of time ranges represented by this object. 5585 * 5586 * @property {module:time-ranges~TimeRangeIndex} start 5587 * Returns the time offset at which a specified time range begins. 5588 * 5589 * @property {module:time-ranges~TimeRangeIndex} end 5590 * Returns the time offset at which a specified time range ends. 5591 * 5592 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges 5593 */ 5594 5595 /** 5596 * Check if any of the time ranges are over the maximum index. 5597 * 5598 * @private 5599 * @param {string} fnName 5600 * The function name to use for logging 5601 * 5602 * @param {number} index 5603 * The index to check 5604 * 5605 * @param {number} maxIndex 5606 * The maximum possible index 5607 * 5608 * @throws {Error} if the timeRanges provided are over the maxIndex 5609 */ 5610 5611 function rangeCheck(fnName, index, maxIndex) { 5612 if (typeof index !== 'number' || index < 0 || index > maxIndex) { 5613 throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ")."); 5614 } 5615 } 5616 /** 5617 * Get the time for the specified index at the start or end 5618 * of a TimeRange object. 5619 * 5620 * @private 5621 * @param {string} fnName 5622 * The function name to use for logging 5623 * 5624 * @param {string} valueIndex 5625 * The property that should be used to get the time. should be 5626 * 'start' or 'end' 5627 * 5628 * @param {Array} ranges 5629 * An array of time ranges 5630 * 5631 * @param {Array} [rangeIndex=0] 5632 * The index to start the search at 5633 * 5634 * @return {number} 5635 * The time that offset at the specified index. 5636 * 5637 * @deprecated rangeIndex must be set to a value, in the future this will throw an error. 5638 * @throws {Error} if rangeIndex is more than the length of ranges 5639 */ 5640 5641 5642 function getRange(fnName, valueIndex, ranges, rangeIndex) { 5643 rangeCheck(fnName, rangeIndex, ranges.length - 1); 5644 return ranges[rangeIndex][valueIndex]; 5645 } 5646 /** 5647 * Create a time range object given ranges of time. 5648 * 5649 * @private 5650 * @param {Array} [ranges] 5651 * An array of time ranges. 5652 */ 5653 5654 5655 function createTimeRangesObj(ranges) { 5656 var timeRangesObj; 5657 5658 if (ranges === undefined || ranges.length === 0) { 5659 timeRangesObj = { 5660 length: 0, 5661 start: function start() { 5662 throw new Error('This TimeRanges object is empty'); 5663 }, 5664 end: function end() { 5665 throw new Error('This TimeRanges object is empty'); 5666 } 5667 }; 5668 } else { 5669 timeRangesObj = { 5670 length: ranges.length, 5671 start: getRange.bind(null, 'start', 0, ranges), 5672 end: getRange.bind(null, 'end', 1, ranges) 5673 }; 5674 } 5675 5676 if (window_1.Symbol && window_1.Symbol.iterator) { 5677 timeRangesObj[window_1.Symbol.iterator] = function () { 5678 return (ranges || []).values();
5679 }; 5680 } 5681 5682 return timeRangesObj; 5683 } 5684 /** 5685 * Create a `TimeRange` object which mimics an 5686 * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. 5687 * 5688 * @param {number|Array[]} start 5689 * The start of a single range (a number) or an array of ranges (an 5690 * array of arrays of two numbers each). 5691 * 5692 * @param {number} end 5693 * The end of a single range. Cannot be used with the array form of 5694 * the `start` argument. 5695 */ 5696 5697 5698 function createTimeRanges(start, end) { 5699 if (Array.isArray(start)) { 5700 return createTimeRangesObj(start); 5701 } else if (start === undefined || end === undefined) { 5702 return createTimeRangesObj(); 5703 } 5704 5705 return createTimeRangesObj([[start, end]]); 5706 } 5707 5708 /** 5709 * @file buffer.js 5710 * @module buffer 5711 */ 5712 /** 5713 * Compute the percentage of the media that has been buffered. 5714 * 5715 * @param {TimeRange} buffered 5716 * The current `TimeRange` object representing buffered time ranges 5717 * 5718 * @param {number} duration 5719 * Total duration of the media 5720 * 5721 * @return {number} 5722 * Percent buffered of the total duration in decimal form. 5723 */ 5724 5725 function bufferedPercent(buffered, duration) { 5726 var bufferedDuration = 0; 5727 var start; 5728 var end; 5729 5730 if (!duration) { 5731 return 0; 5732 } 5733 5734 if (!buffered || !buffered.length) { 5735 buffered = createTimeRanges(0, 0); 5736 } 5737 5738 for (var i = 0; i < buffered.length; i++) { 5739 start = buffered.start(i); 5740 end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction 5741 5742 if (end > duration) { 5743 end = duration; 5744 } 5745 5746 bufferedDuration += end - start; 5747 } 5748 5749 return bufferedDuration / duration; 5750 } 5751 5752 /** 5753 * @file media-error.js 5754 */ 5755 /** 5756 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. 5757 * 5758 * @param {number|string|Object|MediaError} value 5759 * This can be of multiple types: 5760 * - number: should be a standard error code 5761 * - string: an error message (the code will be 0) 5762 * - Object: arbitrary properties 5763 * - `MediaError` (native): used to populate a video.js `MediaError` object 5764 * - `MediaError` (video.js): will return itself if it's already a 5765 * video.js `MediaError` object. 5766 * 5767 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} 5768 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} 5769 * 5770 * @class MediaError 5771 */ 5772 5773 function MediaError(value) { 5774 // Allow redundant calls to this constructor to avoid having `instanceof` 5775 // checks peppered around the code. 5776 if (value instanceof MediaError) { 5777 return value; 5778 } 5779 5780 if (typeof value === 'number') { 5781 this.code = value; 5782 } else if (typeof value === 'string') {
5783 // default code is zero, so this is a custom error 5784 this.message = value; 5785 } else if (isObject$1(value)) { 5786 // We assign the `code` property manually because native `MediaError` objects 5787 // do not expose it as an own/enumerable property of the object. 5788 if (typeof value.code === 'number') { 5789 this.code = value.code; 5790 } 5791 5792 assign(this, value); 5793 } 5794 5795 if (!this.message) { 5796 this.message = MediaError.defaultMessages[this.code] || ''; 5797 } 5798 } 5799 /** 5800 * The error code that refers two one of the defined `MediaError` types 5801 * 5802 * @type {Number} 5803 */ 5804 5805 5806 MediaError.prototype.code = 0; 5807 /** 5808 * An optional message that to show with the error. Message is not part of the HTML5 5809 * video spec but allows for more informative custom errors. 5810 * 5811 * @type {String} 5812 */ 5813 5814 MediaError.prototype.message = ''; 5815 /** 5816 * An optional status code that can be set by plugins to allow even more detail about 5817 * the error. For example a plugin might provide a specific HTTP status code and an 5818 * error message for that code. Then when the plugin gets that error this class will 5819 * know how to display an error message for it. This allows a custom message to show 5820 * up on the `Player` error overlay. 5821 * 5822 * @type {Array} 5823 */ 5824 5825 MediaError.prototype.status = null; 5826 /** 5827 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the 5828 * specification listed under {@link MediaError} for more information. 5829 * 5830 * @enum {array} 5831 * @readonly 5832 * @property {string} 0 - MEDIA_ERR_CUSTOM 5833 * @property {string} 1 - MEDIA_ERR_ABORTED 5834 * @property {string} 2 - MEDIA_ERR_NETWORK 5835 * @property {string} 3 - MEDIA_ERR_DECODE 5836 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED 5837 * @property {string} 5 - MEDIA_ERR_ENCRYPTED 5838 */ 5839 5840 MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; 5841 /** 5842 * The default `MediaError` messages based on the {@link MediaError.errorTypes}. 5843 * 5844 * @type {Array} 5845 * @constant 5846 */ 5847 5848 MediaError.defaultMessages = { 5849 1: 'You aborted the media playback', 5850 2: 'A network error caused the media download to fail part-way.', 5851 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 5852 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 5853 5: 'The media is encrypted and we do not have the keys to decrypt it.' 5854 }; // Add types as properties on MediaError 5855 // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 5856 5857 for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { 5858 MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance 5859 5860 MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; 5861 } // jsdocs for instance/static members added above 5862 5863 var tuple = SafeParseTuple; 5864 5865 function SafeParseTuple(obj, reviver) { 5866 var json; 5867 var error = null; 5868 5869 try { 5870 json = JSON.parse(obj, reviver); 5871 } catch (err) { 5872 error = err; 5873 } 5874 5875 return [error, json]; 5876 } 5877 5878 /** 5879 * Returns whether an object is `Promise`-like (i.e. has a `then` method). 5880 * 5881 * @param {Object} value 5882 * An object that may or may not be `Promise`-like. 5883 * 5884 * @return {boolean} 5885 * Whether or not the object is `Promise`-like. 5886 */ 5887 function isPromise(value) { 5888 return value !== undefined && value !== null && typeof value.then === 'function'; 5889 } 5890 /** 5891 * Silence a Promise-like object. 5892 * 5893 * This is useful for avoiding non-harmful, but potentially confusing "uncaught 5894 * play promise" rejection error messages. 5895 * 5896 * @param {Object} value 5897 * An object that may or may not be `Promise`-like. 5898 */ 5899 5900 function silencePromise(value) { 5901 if (isPromise(value)) { 5902 value.then(null, function (e) {}); 5903 } 5904 } 5905 5906 /** 5907 * @file text-track-list-converter.js Utilities for capturing text track state and 5908 * re-creating tracks based on a capture. 5909 * 5910 * @module text-track-list-converter 5911 */ 5912 5913 /** 5914 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that 5915 * represents the {@link TextTrack}'s state. 5916 * 5917 * @param {TextTrack} track 5918 * The text track to query. 5919 * 5920 * @return {Object} 5921 * A serializable javascript representation of the TextTrack. 5922 * @private 5923 */ 5924 var trackToJson_ = function trackToJson_(track) { 5925 var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { 5926 if (track[prop]) { 5927 acc[prop] = track[prop]; 5928 } 5929 5930 return acc; 5931 }, { 5932 cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { 5933 return { 5934 startTime: cue.startTime, 5935 endTime: cue.endTime, 5936 text: cue.text, 5937 id: cue.id 5938 }; 5939 }) 5940 });
5941 return ret; 5942 }; 5943 /** 5944 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the 5945 * state of all {@link TextTrack}s currently configured. The return array is compatible with 5946 * {@link text-track-list-converter:jsonToTextTracks}. 5947 * 5948 * @param {Tech} tech 5949 * The tech object to query 5950 * 5951 * @return {Array} 5952 * A serializable javascript representation of the {@link Tech}s 5953 * {@link TextTrackList}. 5954 */ 5955 5956 5957 var textTracksToJson = function textTracksToJson(tech) { 5958 var trackEls = tech.$$('track'); 5959 var trackObjs = Array.prototype.map.call(trackEls, function (t) { 5960 return t.track; 5961 }); 5962 var tracks = Array.prototype.map.call(trackEls, function (trackEl) { 5963 var json = trackToJson_(trackEl.track); 5964 5965 if (trackEl.src) { 5966 json.src = trackEl.src; 5967 } 5968 5969 return json; 5970 }); 5971 return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { 5972 return trackObjs.indexOf(track) === -1; 5973 }).map(trackToJson_)); 5974 }; 5975 /** 5976 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript 5977 * object {@link TextTrack} representations. 5978 * 5979 * @param {Array} json 5980 * An array of `TextTrack` representation objects, like those that would be 5981 * produced by `textTracksToJson`. 5982 * 5983 * @param {Tech} tech 5984 * The `Tech` to create the `TextTrack`s on. 5985 */ 5986 5987 5988 var jsonToTextTracks = function jsonToTextTracks(json, tech) { 5989 json.forEach(function (track) { 5990 var addedTrack = tech.addRemoteTextTrack(track, true).track; 5991 5992 if (!track.src && track.cues) { 5993 track.cues.forEach(function (cue) { 5994 return addedTrack.addCue(cue); 5995 }); 5996 } 5997 }); 5998 return tech.textTracks(); 5999 }; 6000 6001 var textTrackConverter = { 6002 textTracksToJson: textTracksToJson, 6003 jsonToTextTracks: jsonToTextTracks, 6004 trackToJson_: trackToJson_ 6005 }; 6006 6007 var keycode = createCommonjsModule(function (module, exports) { 6008 // Source: http://jsfiddle.net/vWx8V/ 6009 // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes 6010 6011 /** 6012 * Conenience method returns corresponding value for given keyName or keyCode. 6013 * 6014 * @param {Mixed} keyCode {Number} or keyName {String} 6015 * @return {Mixed} 6016 * @api public 6017 */ 6018 function keyCode(searchInput) { 6019 // Keyboard Events 6020 if (searchInput && 'object' === typeof searchInput) { 6021 var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; 6022 if (hasKeyCode) searchInput = hasKeyCode; 6023 } // Numbers 6024 6025 6026 if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string) 6027 6028 var search = String(searchInput); // check codes 6029 6030 var foundNamedKey = codes[search.toLowerCase()]; 6031 if (foundNamedKey) return foundNamedKey; // check aliases 6032 6033 var foundNamedKey = aliases[search.toLowerCase()]; 6034 if (foundNamedKey) return foundNamedKey; // weird character? 6035 6036 if (search.length === 1) return search.charCodeAt(0); 6037 return undefined; 6038 } 6039 /** 6040 * Compares a keyboard event with a given keyCode or keyName. 6041 * 6042 * @param {Event} event Keyboard event that should be tested 6043 * @param {Mixed} keyCode {Number} or keyName {String} 6044 * @return {Boolean} 6045 * @api public 6046 */ 6047 6048 6049 keyCode.isEventKey = function isEventKey(event, nameOrCode) { 6050 if (event && 'object' === typeof event) { 6051 var keyCode = event.which || event.keyCode || event.charCode; 6052 6053 if (keyCode === null || keyCode === undefined) { 6054 return false; 6055 } 6056 6057 if (typeof nameOrCode === 'string') { 6058 // check codes 6059 var foundNamedKey = codes[nameOrCode.toLowerCase()]; 6060 6061 if (foundNamedKey) { 6062 return foundNamedKey === keyCode; 6063 } // check aliases 6064 6065 6066 var foundNamedKey = aliases[nameOrCode.toLowerCase()]; 6067 6068 if (foundNamedKey) { 6069 return foundNamedKey === keyCode; 6070 } 6071 } else if (typeof nameOrCode === 'number') { 6072 return nameOrCode === keyCode; 6073 } 6074 6075 return false; 6076 } 6077 }; 6078 6079 exports = module.exports = keyCode; 6080 /** 6081 * Get by name 6082 * 6083 * exports.code['enter'] // => 13 6084 */ 6085 6086 var codes = exports.code = exports.codes = { 6087 'backspace': 8, 6088 'tab': 9, 6089 'enter': 13, 6090 'shift': 16, 6091 'ctrl': 17, 6092 'alt': 18, 6093 'pause/break': 19, 6094 'caps lock': 20, 6095 'esc': 27, 6096 'space': 32, 6097 'page up': 33, 6098 'page down': 34, 6099 'end': 35, 6100 'home': 36, 6101 'left': 37, 6102 'up': 38, 6103 'right': 39, 6104 'down': 40, 6105 'insert': 45, 6106 'delete': 46, 6107 'command': 91, 6108 'left command': 91, 6109 'right command': 93, 6110 'numpad *': 106, 6111 'numpad +': 107, 6112 'numpad -': 109, 6113 'numpad .': 110, 6114 'numpad /': 111, 6115 'num lock': 144, 6116 'scroll lock': 145, 6117 'my computer': 182, 6118 'my calculator': 183, 6119 ';': 186, 6120 '=': 187, 6121 ',': 188, 6122 '-': 189, 6123 '.': 190, 6124 '/': 191, 6125 '`': 192, 6126 '[': 219, 6127 '\\': 220, 6128 ']': 221, 6129 "'": 222 6130 }; // Helper aliases 6131 6132 var aliases = exports.aliases = { 6133 'windows': 91, 6134 'â§': 16, 6135 'â¥': 18, 6136 'â': 17, 6137 'â': 91, 6138 'ctl': 17, 6139 'control': 17, 6140 'option': 18, 6141 'pause': 19, 6142 'break': 19, 6143 'caps': 20, 6144 'return': 13, 6145 'escape': 27, 6146 'spc': 32, 6147 'spacebar': 32, 6148 'pgup': 33, 6149 'pgdn': 34, 6150 'ins': 45, 6151 'del': 46, 6152 'cmd': 91 6153 }; 6154 /*! 6155 * Programatically add the following 6156 */ 6157 // lower case chars 6158 6159 for (i = 97; i < 123; i++) {
6160 codes[String.fromCharCode(i)] = i - 32; 6161 } // numbers 6162 6163 6164 for (var i = 48; i < 58; i++) { 6165 codes[i - 48] = i; 6166 } // function keys 6167 6168 6169 for (i = 1; i < 13; i++) { 6170 codes['f' + i] = i + 111; 6171 } // numpad keys 6172 6173 6174 for (i = 0; i < 10; i++) { 6175 codes['numpad ' + i] = i + 96; 6176 } 6177 /** 6178 * Get by code 6179 * 6180 * exports.name[13] // => 'Enter' 6181 */ 6182 6183 6184 var names = exports.names = exports.title = {}; // title for backward compat 6185 // Create reverse mapping 6186 6187 for (i in codes) { 6188 names[codes[i]] = i; 6189 } // Add aliases 6190 6191 6192 for (var alias in aliases) { 6193 codes[alias] = aliases[alias]; 6194 } 6195 }); 6196 keycode.code; 6197 keycode.codes; 6198 keycode.aliases; 6199 keycode.names; 6200 keycode.title; 6201 6202 var MODAL_CLASS_NAME = 'vjs-modal-dialog'; 6203 /** 6204 * The `ModalDialog` displays over the video and its controls, which blocks 6205 * interaction with the player until it is closed. 6206 * 6207 * Modal dialogs include a "Close" button and will close when that button 6208 * is activated - or when ESC is pressed anywhere. 6209 * 6210 * @extends Component 6211 */ 6212 6213 var ModalDialog = /*#__PURE__*/function (_Component) { 6214 inheritsLoose(ModalDialog, _Component); 6215 6216 /** 6217 * Create an instance of this class. 6218 * 6219 * @param {Player} player 6220 * The `Player` that this class should be attached to. 6221 * 6222 * @param {Object} [options] 6223 * The key/value store of player options. 6224 * 6225 * @param {Mixed} [options.content=undefined] 6226 * Provide customized content for this modal. 6227 * 6228 * @param {string} [options.description] 6229 * A text description for the modal, primarily for accessibility. 6230 * 6231 * @param {boolean} [options.fillAlways=false] 6232 * Normally, modals are automatically filled only the first time 6233 * they open. This tells the modal to refresh its content 6234 * every time it opens. 6235 * 6236 * @param {string} [options.label] 6237 * A text label for the modal, primarily for accessibility. 6238 * 6239 * @param {boolean} [options.pauseOnOpen=true] 6240 * If `true`, playback will will be paused if playing when 6241 * the modal opens, and resumed when it closes. 6242 * 6243 * @param {boolean} [options.temporary=true] 6244 * If `true`, the modal can only be opened once; it will be 6245 * disposed as soon as it's closed. 6246 * 6247 * @param {boolean} [options.uncloseable=false] 6248 * If `true`, the user will not be able to close the modal 6249 * through the UI in the normal ways. Programmatic closing is 6250 * still possible. 6251 */ 6252 function ModalDialog(player, options) { 6253 var _this; 6254 6255 _this = _Component.call(this, player, options) || this; 6256 6257 _this.handleKeyDown_ = function (e) { 6258 return _this.handleKeyDown(e); 6259 }; 6260 6261 _this.close_ = function (e) { 6262 return _this.close(e); 6263 }; 6264 6265 _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; 6266 6267 _this.closeable(!_this.options_.uncloseable); 6268 6269 _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized 6270 // because we only want the contents of the modal in the contentEl 6271 // (not the UI elements like the close button). 6272 6273 6274 _this.contentEl_ = createEl('div', { 6275 className: MODAL_CLASS_NAME + "-content" 6276 }, { 6277 role: 'document' 6278 }); 6279 _this.descEl_ = createEl('p', { 6280 className: MODAL_CLASS_NAME + "-description vjs-control-text", 6281 id: _this.el().getAttribute('aria-describedby') 6282 }); 6283 textContent(_this.descEl_, _this.description()); 6284 6285 _this.el_.appendChild(_this.descEl_); 6286 6287 _this.el_.appendChild(_this.contentEl_); 6288 6289 return _this; 6290 } 6291 /** 6292 * Create the `ModalDialog`'s DOM element 6293 * 6294 * @return {Element} 6295 * The DOM element that gets created. 6296 */ 6297 6298 6299 var _proto = ModalDialog.prototype; 6300 6301 _proto.createEl = function createEl() { 6302 return _Component.prototype.createEl.call(this, 'div', { 6303 className: this.buildCSSClass(), 6304 tabIndex: -1 6305 }, { 6306 'aria-describedby': this.id() + "_description", 6307 'aria-hidden': 'true', 6308 'aria-label': this.label(), 6309 'role': 'dialog' 6310 }); 6311 }; 6312 6313 _proto.dispose = function dispose() { 6314 this.contentEl_ = null; 6315 this.descEl_ = null; 6316 this.previouslyActiveEl_ = null; 6317 6318 _Component.prototype.dispose.call(this); 6319 } 6320 /** 6321 * Builds the default DOM `className`. 6322 * 6323 * @return {string} 6324 * The DOM `className` for this object. 6325 */ 6326 ; 6327 6328 _proto.buildCSSClass = function buildCSSClass() { 6329 return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this); 6330 } 6331 /**
6332 * Returns the label string for this modal. Primarily used for accessibility. 6333 * 6334 * @return {string} 6335 * the localized or raw label of this modal. 6336 */ 6337 ; 6338 6339 _proto.label = function label() { 6340 return this.localize(this.options_.label || 'Modal Window'); 6341 } 6342 /** 6343 * Returns the description string for this modal. Primarily used for 6344 * accessibility. 6345 * 6346 * @return {string} 6347 * The localized or raw description of this modal. 6348 */ 6349 ; 6350 6351 _proto.description = function description() { 6352 var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable. 6353 6354 if (this.closeable()) { 6355 desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); 6356 } 6357 6358 return desc; 6359 } 6360 /** 6361 * Opens the modal. 6362 * 6363 * @fires ModalDialog#beforemodalopen 6364 * @fires ModalDialog#modalopen 6365 */ 6366 ; 6367 6368 _proto.open = function open() { 6369 if (!this.opened_) { 6370 var player = this.player(); 6371 /** 6372 * Fired just before a `ModalDialog` is opened. 6373 * 6374 * @event ModalDialog#beforemodalopen 6375 * @type {EventTarget~Event} 6376 */ 6377 6378 this.trigger('beforemodalopen'); 6379 this.opened_ = true; // Fill content if the modal has never opened before and 6380 // never been filled. 6381 6382 if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { 6383 this.fill(); 6384 } // If the player was playing, pause it and take note of its previously 6385 // playing state. 6386 6387 6388 this.wasPlaying_ = !player.paused(); 6389 6390 if (this.options_.pauseOnOpen && this.wasPlaying_) { 6391 player.pause(); 6392 } 6393 6394 this.on('keydown', this.handleKeyDown_); // Hide controls and note if they were enabled. 6395 6396 this.hadControls_ = player.controls(); 6397 player.controls(false); 6398 this.show(); 6399 this.conditionalFocus_(); 6400 this.el().setAttribute('aria-hidden', 'false'); 6401 /** 6402 * Fired just after a `ModalDialog` is opened. 6403 * 6404 * @event ModalDialog#modalopen 6405 * @type {EventTarget~Event} 6406 */ 6407 6408 this.trigger('modalopen'); 6409 this.hasBeenOpened_ = true; 6410 } 6411 } 6412 /** 6413 * If the `ModalDialog` is currently open or closed. 6414 * 6415 * @param {boolean} [value] 6416 * If given, it will open (`true`) or close (`false`) the modal. 6417 * 6418 * @return {boolean} 6419 * the current open state of the modaldialog 6420 */ 6421 ; 6422 6423 _proto.opened = function opened(value) { 6424 if (typeof value === 'boolean') { 6425 this[value ? 'open' : 'close'](); 6426 } 6427 6428 return this.opened_; 6429 } 6430 /** 6431 * Closes the modal, does nothing if the `ModalDialog` is 6432 * not open. 6433 * 6434 * @fires ModalDialog#beforemodalclose 6435 * @fires ModalDialog#modalclose 6436 */ 6437 ; 6438 6439 _proto.close = function close() { 6440 if (!this.opened_) { 6441 return; 6442 } 6443 6444 var player = this.player(); 6445 /** 6446 * Fired just before a `ModalDialog` is closed. 6447 * 6448 * @event ModalDialog#beforemodalclose 6449 * @type {EventTarget~Event} 6450 */ 6451 6452 this.trigger('beforemodalclose'); 6453 this.opened_ = false; 6454 6455 if (this.wasPlaying_ && this.options_.pauseOnOpen) { 6456 player.play(); 6457 } 6458 6459 this.off('keydown', this.handleKeyDown_); 6460 6461 if (this.hadControls_) { 6462 player.controls(true); 6463 } 6464 6465 this.hide(); 6466 this.el().setAttribute('aria-hidden', 'true'); 6467 /** 6468 * Fired just after a `ModalDialog` is closed. 6469 * 6470 * @event ModalDialog#modalclose 6471 * @type {EventTarget~Event} 6472 */ 6473 6474 this.trigger('modalclose'); 6475 this.conditionalBlur_(); 6476 6477 if (this.options_.temporary) { 6478 this.dispose(); 6479 } 6480 } 6481 /** 6482 * Check to see if the `ModalDialog` is closeable via the UI. 6483 * 6484 * @param {boolean} [value] 6485 * If given as a boolean, it will set the `closeable` option. 6486 * 6487 * @return {boolean} 6488 * Returns the final value of the closable option. 6489 */ 6490 ; 6491 6492 _proto.closeable = function closeable(value) { 6493 if (typeof value === 'boolean') { 6494 var closeable = this.closeable_ = !!value; 6495 var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one. 6496 6497 if (closeable && !close) { 6498 // The close button should be a child of the modal - n
6498ot its 6499 // content element, so temporarily change the content element. 6500 var temp = this.contentEl_; 6501 this.contentEl_ = this.el_; 6502 close = this.addChild('closeButton', { 6503 controlText: 'Close Modal Dialog' 6504 }); 6505 this.contentEl_ = temp; 6506 this.on(close, 'close', this.close_); 6507 } // If this is being made uncloseable and has a close button, remove it. 6508 6509 6510 if (!closeable && close) { 6511 this.off(close, 'close', this.close_); 6512 this.removeChild(close); 6513 close.dispose(); 6514 } 6515 } 6516 6517 return this.closeable_; 6518 } 6519 /** 6520 * Fill the modal's content element with the modal's "content" option. 6521 * The content element will be emptied before this change takes place. 6522 */ 6523 ; 6524 6525 _proto.fill = function fill() { 6526 this.fillWith(this.content()); 6527 } 6528 /** 6529 * Fill the modal's content element with arbitrary content. 6530 * The content element will be emptied before this change takes place. 6531 * 6532 * @fires ModalDialog#beforemodalfill 6533 * @fires ModalDialog#modalfill 6534 * 6535 * @param {Mixed} [content] 6536 * The same rules apply to this as apply to the `content` option. 6537 */ 6538 ; 6539 6540 _proto.fillWith = function fillWith(content) { 6541 var contentEl = this.contentEl(); 6542 var parentEl = contentEl.parentNode; 6543 var nextSiblingEl = contentEl.nextSibling; 6544 /** 6545 * Fired just before a `ModalDialog` is filled with content. 6546 * 6547 * @event ModalDialog#beforemodalfill 6548 * @type {EventTarget~Event} 6549 */ 6550 6551 this.trigger('beforemodalfill'); 6552 this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing 6553 // manipulation to avoid modifying the live DOM multiple times. 6554 6555 parentEl.removeChild(contentEl); 6556 this.empty(); 6557 insertContent(contentEl, content); 6558 /** 6559 * Fired just after a `ModalDialog` is filled with content. 6560 * 6561 * @event ModalDialog#modalfill 6562 * @type {EventTarget~Event} 6563 */ 6564 6565 this.trigger('modalfill'); // Re-inject the re-filled content element. 6566 6567 if (nextSiblingEl) { 6568 parentEl.insertBefore(contentEl, nextSiblingEl); 6569 } else { 6570 parentEl.appendChild(contentEl); 6571 } // make sure that the close button is last in the dialog DOM 6572 6573 6574 var closeButton = this.getChild('closeButton'); 6575 6576 if (closeButton) { 6577 parentEl.appendChild(closeButton.el_); 6578 } 6579 } 6580 /** 6581 * Empties the content element. This happens anytime the modal is filled. 6582 * 6583 * @fires ModalDialog#beforemodalempty 6584 * @fires ModalDialog#modalempty 6585 */ 6586 ; 6587 6588 _proto.empty = function empty() { 6589 /** 6590 * Fired just before a `ModalDialog` is emptied. 6591 * 6592 * @event ModalDialog#beforemodalempty 6593 * @type {EventTarget~Event} 6594 */ 6595 this.trigger('beforemodalempty'); 6596 emptyEl(this.contentEl()); 6597 /** 6598 * Fired just after a `ModalDialog` is emptied. 6599 * 6600 * @event ModalDialog#modalempty 6601 * @type {EventTarget~Event} 6602 */ 6603 6604 this.trigger('modalempty'); 6605 } 6606 /** 6607 * Gets or sets the modal content, which gets normalized before being 6608 * rendered into the DOM. 6609 * 6610 * This does not update the DOM or fill the modal, but it is called during 6611 * that process. 6612 * 6613 * @param {Mixed} [value] 6614 * If defined, sets the internal content value to be used on the 6615 * next call(s) to `fill`. This value is normalized before being 6616 * inserted. To "clear" the internal content value, pass `null`. 6617 * 6618 * @return {Mixed} 6619 * The current content of the modal dialog 6620 */ 6621 ; 6622 6623 _proto.content = function content(value) { 6624 if (typeof value !== 'undefined') { 6625 this.content_ = value; 6626 } 6627 6628 return this.content_; 6629 } 6630 /** 6631 * conditionally focus the modal dialog if focus was previously on the player. 6632 * 6633 * @private 6634 */ 6635 ; 6636 6637 _proto.conditionalFocus_ = function conditionalFocus_() { 6638 var activeEl = document_1.activeElement; 6639 var playerEl = this.player_.el_; 6640 this.previouslyActiveEl_ = null; 6641 6642 if (playerEl.contains(activeEl) || playerEl === activeEl) { 6643 this.previouslyActiveEl_ = activeEl; 6644 this.focus(); 6645 } 6646 } 6647 /** 6648 * conditionally blur the element and refocus the last focused element 6649 * 6650 * @private 6651 */ 6652 ; 6653 6654 _proto.conditionalBlur_ = function conditionalBlur_() { 6655 if (this.previouslyActiveEl_) { 6656 this.previouslyActiveEl_.focus(); 6657 this.previouslyActiveEl_ = null; 6658 } 6659 } 6660 /** 6661 * Keydown handler. Attached when modal is focused. 6662 * 6663 * @listens keydown 6664 */ 6665 ; 6666 6667 _proto.handleKeyDown = function handleKeyDown(event) {
6668 // Do not allow keydowns to reach out of the modal dialog. 6669 event.stopPropagation(); 6670 6671 if (keycode.isEventKey(event, 'Escape') && this.closeable()) { 6672 event.preventDefault(); 6673 this.close(); 6674 return; 6675 } // exit early if it isn't a tab key 6676 6677 6678 if (!keycode.isEventKey(event, 'Tab')) { 6679 return; 6680 } 6681 6682 var focusableEls = this.focusableEls_(); 6683 var activeEl = this.el_.querySelector(':focus'); 6684 var focusIndex; 6685 6686 for (var i = 0; i < focusableEls.length; i++) { 6687 if (activeEl === focusableEls[i]) { 6688 focusIndex = i; 6689 break; 6690 } 6691 } 6692 6693 if (document_1.activeElement === this.el_) { 6694 focusIndex = 0; 6695 } 6696 6697 if (event.shiftKey && focusIndex === 0) { 6698 focusableEls[focusableEls.length - 1].focus(); 6699 event.preventDefault(); 6700 } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { 6701 focusableEls[0].focus(); 6702 event.preventDefault(); 6703 } 6704 } 6705 /** 6706 * get all focusable elements 6707 * 6708 * @private 6709 */ 6710 ; 6711 6712 _proto.focusableEls_ = function focusableEls_() { 6713 var allChildren = this.el_.querySelectorAll('*'); 6714 return Array.prototype.filter.call(allChildren, function (child) { 6715 return (child instanceof window_1.HTMLAnchorElement || child instanceof window_1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window_1.HTMLInputElement || child instanceof window_1.HTMLSelectElement || child instanceof window_1.HTMLTextAreaElement || child instanceof window_1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window_1.HTMLIFrameElement || child instanceof window_1.HTMLObjectElement || child instanceof window_1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); 6716 }); 6717 }; 6718 6719 return ModalDialog; 6720 }(Component$1); 6721 /** 6722 * Default options for `ModalDialog` default options. 6723 * 6724 * @type {Object} 6725 * @private 6726 */ 6727 6728 6729 ModalDialog.prototype.options_ = { 6730 pauseOnOpen: true, 6731 temporary: true 6732 }; 6733 Component$1.registerComponent('ModalDialog', ModalDialog); 6734 6735 /** 6736 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and 6737 * {@link VideoTrackList} 6738 * 6739 * @extends EventTarget 6740 */ 6741 6742 var TrackList = /*#__PURE__*/function (_EventTarget) { 6743 inheritsLoose(TrackList, _EventTarget); 6744 6745 /** 6746 * Create an instance of this class 6747 * 6748 * @param {Track[]} tracks 6749 * A list of tracks to initialize the list with. 6750 * 6751 * @abstract 6752 */ 6753 function TrackList(tracks) { 6754 var _this; 6755 6756 if (tracks === void 0) { 6757 tracks = []; 6758 } 6759 6760 _this = _EventTarget.call(this) || this; 6761 _this.tracks_ = []; 6762 /** 6763 * @memberof TrackList 6764 * @member {number} length 6765 * The current number of `Track`s in the this Trackist. 6766 * @instance 6767 */ 6768 6769 Object.defineProperty(assertThisInitialized(_this), 'length', { 6770 get: function get() { 6771 return this.tracks_.length; 6772 } 6773 }); 6774 6775 for (var i = 0; i < tracks.length; i++) { 6776 _this.addTrack(tracks[i]); 6777 } 6778 6779 return _this; 6780 } 6781 /** 6782 * Add a {@link Track} to the `TrackList` 6783 * 6784 * @param {Track} track 6785 * The audio, video, or text track to add to the list. 6786 * 6787 * @fires TrackList#addtrack 6788 */ 6789 6790 6791 var _proto = TrackList.prototype; 6792 6793 _proto.addTrack = function addTrack(track) { 6794 var _this2 = this; 6795 6796 var index = this.tracks_.length; 6797 6798 if (!('' + index in this)) { 6799 Object.defineProperty(this, index, { 6800 get: function get() { 6801 return this.tracks_[index]; 6802 } 6803 }); 6804 } // Do not add duplicate tracks 6805 6806 6807 if (this.tracks_.indexOf(track) === -1) { 6808 this.tracks_.push(track); 6809 /** 6810 * Triggered when a track is added to a track list. 6811 * 6812 * @event TrackList#addtrack 6813 * @type {EventTarget~Event} 6814 * @property {Track} track 6815 * A reference to track that was added. 6816 */ 6817 6818 this.trigger({ 6819 track: track, 6820 type: 'addtrack', 6821 target: this 6822 }); 6823 } 6824 /** 6825 * Triggered when a track label is changed. 6826 * 6827 * @event TrackList#addtrack 6828 * @type {EventTarget~Event} 6829 * @property {Track} track 6830 * A reference to track that was added. 6831 */ 6832 6833 6834 track.labelchange_ = function () { 6835 _this2.trigger({ 6836 track: track, 6837 type: 'labelchange', 6838 target: _this2 6839 }); 6840 }; 6841 6842 if (isEvented(track)) { 6843 track.addEventListener('labelchange', track.labelchange_); 6844 } 6845 } 6846 /** 6847 * Remove a {@link Track} from the `TrackList` 6848 * 6849 * @param {Track} rtrack 6850 * The audio, video, or text track to remove from the list. 6851 * 6852 * @fires TrackList#removetrack 6853 */ 6854 ; 6855 6856 _proto.removeTrack = function removeTrack(rtrack) { 6857 var track; 6858 6859 for (var i = 0, l = this.length; i < l; i++) { 6860 if (this[i] === rtrack) { 6861 track = this[i]; 6862 6863 if (track.off) { 6864 track.off(); 6865 } 6866 6867 this.tracks_.splice(i, 1); 6868 break; 6869 } 6870 } 6871 6872 if (!track) { 6873 return; 6874 } 6875 /** 6876 * Triggered when a track is removed from track list. 6877 * 6878 * @event TrackList#removetrack 6879 * @type {EventTarget~Event} 6880 * @property {Track} track 6881 * A reference to track that was removed. 6882 */ 6883 6884 6885 this.trigger({ 6886 track: track, 6887 type: 'removetrack', 6888 target: this 6889 }); 6890 } 6891 /** 6892 * Get a Track from the TrackList by a tracks id 6893 * 6894 * @param {string} id - the id of the track to get 6895 * @method getTrackById 6896 * @return {Track} 6897 * @private 6898 */ 6899 ; 6900 6901 _proto.getTrackById = function getTrackById(id) { 6902 var result = null; 6903 6904 for (var i = 0, l = this.length; i < l; i++) { 6905 var track = this[i]; 6906 6907 if (track.id === id) { 6908 result = track; 6909 break; 6910 } 6911 } 6912 6913 return result; 6914 }; 6915 6916 return TrackList; 6917 }(EventTarget$2); 6918 /** 6919 * Triggered when a different track is selected/enabled. 6920 * 6921 * @event TrackList#change 6922 * @type {EventTarget~Event} 6923 */ 6924 6925 /** 6926 * Events that can be called with on + eventName. See {@link EventHandler}. 6927 * 6928 * @property {Object} TrackList#allowedEvents_ 6929 * @private 6930 */ 6931 6932 6933 TrackList.prototype.allowedEvents_ = { 6934 change: 'change', 6935 addtrack: 'addtrack', 6936 removetrack: 'removetrack', 6937 labelchange: 'labelchange' 6938 }; // emulate attribute EventHandler support to allow for feature detection 6939 6940 for (var event in TrackList.prototype.allowedEvents_) { 6941 TrackList.prototype['on' + event] = null; 6942 } 6943 6944 /** 6945 * Anywhere we call this function we diverge from the spec 6946 * as we only support one enabled audiotrack at a time 6947 * 6948 * @param {AudioTrackList} list 6949 * list to work on 6950 * 6951 * @param {AudioTrack} track 6952 * The track to skip 6953 * 6954 * @private 6955 */ 6956 6957 var disableOthers$1 = function disableOthers(list, track) { 6958 for (var i = 0; i < list.length; i++) { 6959 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6960 continue; 6961 } // another audio track is enabled, disable it 6962 6963 6964 list[i].enabled = false; 6965 } 6966 }; 6967 /** 6968 * The current list of {@link AudioTrack} for a media file. 6969 * 6970 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} 6971 * @extends TrackList 6972 */ 6973 6974 6975 var AudioTrackList = /*#__PURE__*/function (_TrackList) { 6976 inheritsLoose(AudioTrackList, _TrackList); 6977 6978 /** 6979 * Create an instance of this class. 6980 * 6981 * @param {AudioTrack[]} [tracks=[]] 6982 * A list of `AudioTrack` to instantiate the list with. 6983 */ 6984 function AudioTrackList(tracks) { 6985 var _this; 6986 6987 if (tracks === void 0) { 6988 tracks = []; 6989 } 6990 6991 // make sure only 1 track is enabled 6992 // sorted from last index to first index 6993 for (var i = tracks.length - 1; i >= 0; i--) { 6994 if (tracks[i].enabled) { 6995 disableOthers$1(tracks, tracks[i]); 6996 break; 6997 } 6998 } 6999 7000 _this = _TrackList.call(this, tracks) || this; 7001 _this.changing_ = false; 7002 return _this; 7003 } 7004 /** 7005 * Add an {@link AudioTrack} to the `AudioTrackList`. 7006 * 7007 * @param {AudioTrack} track 7008 * The AudioTrack to add to the list 7009 * 7010 * @fires TrackList#addtrack 7011 */ 7012 7013 7014 var _proto = AudioTrackList.prototype; 7015 7016 _proto.addTrack = function addTrack(track) { 7017 var _this2 = this; 7018 7019 if (track.enabled) { 7020 disableOthers$1(this, track); 7021 } 7022 7023 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 7024 7025 7026 if (!track.addEventListener) { 7027 return; 7028 } 7029 7030 track.enabledChange_ = function () { 7031 // when we are disabling other tracks (since we don't support 7032 // more than one track at a time) we will set changing_ 7033 // to true so that we don't trigger additional change events 7034 if (_this2.changing_) { 7035 return; 7036 } 7037 7038 _this2.changing_ = true; 7039 disableOthers$1(_this2, track); 7040 _this2.changing_ = false; 7041 7042 _this2.trigger('change'); 7043 }; 7044 /** 7045 * @listens AudioTrack#enabledchange 7046 * @fires TrackList#change 7047 */ 7048 7049 7050 track.addEventListener('enabledchange', track.enabledChange_); 7051 }; 7052 7053 _proto.removeTrack = function removeTrack(rtrack) { 7054 _TrackList.prototype.removeTrack.call(this, rtrack); 7055 7056 if (rtrack.removeEventListener && rtrack.enabledChange_) { 7057 rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); 7058 rtrack.enabledChange_ = null; 7059 } 7060 }; 7061 7062 return AudioTrackList; 7063 }(TrackList); 7064 7065 /** 7066 * Un-select all other {@link VideoTrack}s that are selected. 7067 * 7068 * @param {VideoTrackList} list 7069 * list to work on 7070 * 7071 * @param {VideoTrack} track 7072 * The track to skip 7073 * 7074 * @private 7075 */ 7076 7077 var disableOthers = function disableOthers(list, track) { 7078 for (var i = 0; i < list.length; i++) { 7079 if (!Object.keys(list[i]).length || track.id === list[i].id) { 7080 continue; 7081 } // another video track is enabled, disable it 7082 7083 7084 list[i].selected = false; 7085 } 7086 }; 7087 /** 7088 * The current list of {@link VideoTrack} for a video. 7089 * 7090 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} 7091 * @extends TrackList 7092 */ 7093 7094 7095 var VideoTrackList = /*#__PURE__*/function (_TrackList) { 7096 inheritsLoose(VideoTrackList, _TrackList); 7097 7098 /** 7099 * Create an instance of this class. 7100 * 7101 * @param {VideoTrack[]} [tracks=[]] 7102 * A list of `VideoTrack` to instantiate the list with. 7103 */ 7104 function VideoTrackList(tracks) { 7105 var _this; 7106 7107 if (tracks === void 0) { 7108 tracks = []; 7109 } 7110 7111 // make sure only 1 track is enabled 7112 // sorted from last index to first index 7113 for (var i = tracks.length - 1; i >= 0; i--) { 7114 if (tracks[i].selected) { 7115 disableOthers(tracks, tracks[i]); 7116 break; 7117 } 7118 } 7119 7120 _this = _TrackList.call(this, tracks) || this; 7121 _this.changing_ = false; 7122 /** 7123 * @member {number} VideoTrackList#selectedIndex 7124 * The current index of the selected {@link VideoTrack`}. 7125 */ 7126 7127 Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', { 7128 get: function get() { 7129 for (var _i = 0; _i < this.length; _i++) { 7130 if (this[_i].selected) { 7131 return _i; 7132 } 7133 } 7134 7135 return -1; 7136 }, 7137 set: function set() {} 7138 }); 7139 return _this; 7140 } 7141 /** 7142 * Add a {@link VideoTrack} to the `VideoTrackList`. 7143 * 7144 * @param {VideoTrack} track 7145 * The VideoTrack to add to the list 7146 * 7147 * @fires TrackList#addtrack 7148 */ 7149 7150 7151 var _proto = VideoTrackList.prototype; 7152 7153 _proto.addTrack = function addTrack(track) { 7154 var _this2 = this; 7155 7156 if (track.selected) { 7157 disableOthers(this, track); 7158 } 7159 7160 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 7161 7162 7163 if (!track.addEventListener) { 7164 return; 7165 } 7166 7167 track.selectedChange_ = function () { 7168 if (_this2.changing_) { 7169 return; 7170 } 7171 7172 _this2.changing_ = true; 7173 disableOthers(_this2, track); 7174 _this2.changing_ = false; 7175 7176 _this2.trigger('change'); 7177 }; 7178 /** 7179 * @listens VideoTrack#selectedchange 7180 * @fires TrackList#change 7181 */ 7182 7183 7184 track.addEventListener('selectedchange', track.selectedChange_); 7185 }; 7186 7187 _proto.removeTrack = function removeTrack(rtrack) { 7188 _TrackList.prototype.removeTrack.call(this, rtrack); 7189 7190 if (rtrack.removeEventListener && rtrack.selectedChange_) { 7191 rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); 7192 rtrack.selectedChange_ = null; 7193 } 7194 }; 7195 7196 return VideoTrackList; 7197 }(TrackList); 7198 7199 /** 7200 * The current list of {@link TextTrack} for a media file. 7201 * 7202 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} 7203 * @extends TrackList 7204 */ 7205 7206 var TextTrackList = /*#__PURE__*/function (_TrackList) { 7207 inheritsLoose(TextTrackList, _TrackList); 7208 7209 function TextTrackList() { 7210 return _TrackList.apply(this, arguments) || this; 7211 } 7212 7213 var _proto = TextTrackList.prototype; 7214 7215 /** 7216 * Add a {@link TextTrack} to the `TextTrackList` 7217 * 7218 * @param {TextTrack} track 7219 * The text track to add to the list. 7220 * 7221 * @fires TrackList#addtrack 7222 */ 7223 _proto.addTrack = function addTrack(track) { 7224 var _this = this; 7225 7226 _TrackList.prototype.addTrack.call(this, track); 7227 7228 if (!this.queueChange_) { 7229 this.queueChange_ = function () { 7230 return _this.queueTrigger('change'); 7231 }; 7232 } 7233 7234 if (!this.triggerSelectedlanguagechange) { 7235 this.triggerSelectedlanguagechange_ = function () { 7236 return _this.trigger('selectedlanguagechange'); 7237 }; 7238 } 7239 /** 7240 * @listens TextTrack#modechange 7241 * @fires TrackList#change 7242 */ 7243 7244 7245 track.addEventListener('modechange', this.queueChange_); 7246 var nonLanguageTextTrackKind = ['metadata', 'chapters']; 7247 7248 if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { 7249 track.addEventListener('modechange', this.triggerSelectedlanguagechange_); 7250 } 7251 }; 7252 7253 _proto.removeTrack = function removeTrack(rtrack) { 7254 _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added 7255 7256 7257 if (rtrack.removeEventListener) { 7258 if (this.queueChange_) { 7259 rtrack.removeEventListener('modechange', this.queueChange_); 7260 } 7261 7262 if (this.selectedlanguagechange_) { 7263 rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); 7264 } 7265 } 7266 }; 7267 7268 return TextTrackList; 7269 }(TrackList); 7270 7271 /** 7272 * @file html-track-element-list.js 7273 */ 7274 7275 /** 7276 * The current list of {@link HtmlTrackElement}s. 7277 */ 7278 var HtmlTrackElementList = /*#__PURE__*/function () { 7279 /** 7280 * Create an instance of this class. 7281 * 7282 * @param {HtmlTrackElement[]} [tracks=[]] 7283 * A list of `HtmlTrackElement` to instantiate the list with. 7284 */ 7285 function HtmlTrackElementList(trackElements) { 7286 if (tra
7286ckElements === void 0) { 7287 trackElements = []; 7288 } 7289 7290 this.trackElements_ = []; 7291 /** 7292 * @memberof HtmlTrackElementList 7293 * @member {number} length 7294 * The current number of `Track`s in the this Trackist. 7295 * @instance 7296 */ 7297 7298 Object.defineProperty(this, 'length', { 7299 get: function get() { 7300 return this.trackElements_.length; 7301 } 7302 }); 7303 7304 for (var i = 0, length = trackElements.length; i < length; i++) { 7305 this.addTrackElement_(trackElements[i]); 7306 } 7307 } 7308 /** 7309 * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` 7310 * 7311 * @param {HtmlTrackElement} trackElement 7312 * The track element to add to the list. 7313 * 7314 * @private 7315 */ 7316 7317 7318 var _proto = HtmlTrackElementList.prototype; 7319 7320 _proto.addTrackElement_ = function addTrackElement_(trackElement) { 7321 var index = this.trackElements_.length; 7322 7323 if (!('' + index in this)) { 7324 Object.defineProperty(this, index, { 7325 get: function get() { 7326 return this.trackElements_[index]; 7327 } 7328 }); 7329 } // Do not add duplicate elements 7330 7331 7332 if (this.trackElements_.indexOf(trackElement) === -1) { 7333 this.trackElements_.push(trackElement); 7334 } 7335 } 7336 /** 7337 * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an 7338 * {@link TextTrack}. 7339 * 7340 * @param {TextTrack} track 7341 * The track associated with a track element. 7342 * 7343 * @return {HtmlTrackElement|undefined} 7344 * The track element that was found or undefined. 7345 * 7346 * @private 7347 */ 7348 ; 7349 7350 _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { 7351 var trackElement_; 7352 7353 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 7354 if (track === this.trackElements_[i].track) { 7355 trackElement_ = this.trackElements_[i]; 7356 break; 7357 } 7358 } 7359 7360 return trackElement_; 7361 } 7362 /** 7363 * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` 7364 * 7365 * @param {HtmlTrackElement} trackElement 7366 * The track element to remove from the list. 7367 * 7368 * @private 7369 */ 7370 ; 7371 7372 _proto.removeTrackElement_ = function removeTrackElement_(trackElement) { 7373 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 7374 if (trackElement === this.trackElements_[i]) { 7375 if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { 7376 this.trackElements_[i].track.off(); 7377 } 7378 7379 if (typeof this.trackElements_[i].off === 'function') { 7380 this.trackElements_[i].off(); 7381 } 7382 7383 this.trackElements_.splice(i, 1); 7384 break; 7385 } 7386 } 7387 }; 7388 7389 return HtmlTrackElementList; 7390 }(); 7391 7392 /** 7393 * @file text-track-cue-list.js 7394 */ 7395 7396 /** 7397 * @typedef {Object} TextTrackCueList~TextTrackCue 7398 * 7399 * @property {string} id 7400 * The unique id for this text track cue 7401 * 7402 * @property {number} startTime 7403 * The start time for this text track cue 7404 * 7405 * @property {number} endTime 7406 * The end time for this text track cue 7407 * 7408 * @property {boolean} pauseOnExit 7409 * Pause when the end time is reached if true. 7410 * 7411 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} 7412 */ 7413 7414 /** 7415 * A List of TextTrackCues. 7416 * 7417 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} 7418 */ 7419 var TextTrackCueList = /*#__PURE__*/function () { 7420 /** 7421 * Create an instance of this class.. 7422 * 7423 * @param {Array} cues 7424 * A list of cues to be initialized with 7425 */ 7426 function TextTrackCueList(cues) { 7427 TextTrackCueList.prototype.setCues_.call(this, cues); 7428 /** 7429 * @memberof TextTrackCueList 7430 * @member {number} length 7431 * The current number of `TextTrackCue`s in the TextTrackCueList. 7432 * @instance 7433 */ 7434 7435 Object.defineProperty(this, 'length', { 7436 get: function get() { 7437 return this.length_; 7438 } 7439 }); 7440 } 7441 /** 7442 * A setter for cues in this list. Creates getters 7443 * an an index for the cues. 7444 * 7445 * @param {Array} cues 7446 * An array of cues to set 7447 * 7448 * @private 7449 */ 7450 7451 7452 var _proto = TextTrackCueList.prototype; 7453 7454 _proto.setCues_ = function setCues_(cues) { 7455 var oldLength = this.length || 0; 7456 var i = 0; 7457 var l = cues.length; 7458 this.cues_ = cues; 7459 this.length_ = cues.length; 7460 7461 var defineProp = function defineProp(index) { 7462 if (!('' + index in this)) { 7463 Object.defineProperty(this, '' + index, { 7464 get: function get() { 7465 return this.cues_[index]; 7466 } 7467 }); 7468 } 7469 }; 7470 7471 if (oldLength < l) { 7472 i = oldLength; 7473 7474 for (; i < l; i++) { 7475 defineProp.call(this, i); 7476 } 7477 } 7478 } 7479 /** 7480 * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. 7481 * 7482 * @param {string} id 7483 * The id of the cue that should be searched for. 7484 * 7485 * @return {TextTrackCueList~TextTrackCue|null} 7486 * A single cue or null if none was found. 7487 */ 7488 ; 7489 7490 _proto.getCueById = function getCueById(id) { 7491 var result = null; 7492 7493 for (var i = 0, l = this.length; i < l; i++) { 7494 var cue = this[i]; 7495 7496 if (cue.id === id) { 7497 result = cue; 7498 break; 7499 } 7500 } 7501 7502 return result; 7503 }; 7504 7505 return TextTrackCueList; 7506 }(); 7507 7508 /** 7509 * @file track-kinds.js 7510 */ 7511 7512 /** 7513 * All possible `VideoTrackKind`s 7514 * 7515 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind 7516 * @typedef VideoTrack~Kind 7517 * @enum 7518 */ 7519 var VideoTrackKind = { 7520 alternative: 'alternative', 7521 captions: 'captions', 7522 main: 'main', 7523 sign: 'sign', 7524 subtitles: 'subtitles', 7525 commentary: 'commentary' 7526 }; 7527 /** 7528 * All possible `AudioTrackKind`s 7529 * 7530 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind 7531 * @typedef AudioTrack~Kind 7532 * @enum 7533 */ 7534 7535 var AudioTrackKind = { 7536 'alternative': 'alternative',
7537 'descriptions': 'descriptions', 7538 'main': 'main', 7539 'main-desc': 'main-desc', 7540 'translation': 'translation', 7541 'commentary': 'commentary' 7542 }; 7543 /** 7544 * All possible `TextTrackKind`s 7545 * 7546 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind 7547 * @typedef TextTrack~Kind 7548 * @enum 7549 */ 7550 7551 var TextTrackKind = { 7552 subtitles: 'subtitles', 7553 captions: 'captions', 7554 descriptions: 'descriptions', 7555 chapters: 'chapters', 7556 metadata: 'metadata' 7557 }; 7558 /** 7559 * All possible `TextTrackMode`s 7560 * 7561 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode 7562 * @typedef TextTrack~Mode 7563 * @enum 7564 */ 7565 7566 var TextTrackMode = { 7567 disabled: 'disabled', 7568 hidden: 'hidden', 7569 showing: 'showing' 7570 }; 7571 7572 /** 7573 * A Track class that contains all of the common functionality for {@link AudioTrack}, 7574 * {@link VideoTrack}, and {@link TextTrack}. 7575 * 7576 * > Note: This class should not be used directly 7577 * 7578 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} 7579 * @extends EventTarget 7580 * @abstract 7581 */ 7582 7583 var Track = /*#__PURE__*/function (_EventTarget) { 7584 inheritsLoose(Track, _EventTarget); 7585 7586 /** 7587 * Create an instance of this class. 7588 * 7589 * @param {Object} [options={}] 7590 * Object of option names and values 7591 * 7592 * @param {string} [options.kind=''] 7593 * A valid kind for the track type you are creating. 7594 * 7595 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7596 * A unique id for this AudioTrack. 7597 * 7598 * @param {string} [options.label=''] 7599 * The menu label for this track. 7600 * 7601 * @param {string} [options.language=''] 7602 * A valid two character language code. 7603 * 7604 * @abstract 7605 */ 7606 function Track(options) { 7607 var _this; 7608 7609 if (options === void 0) { 7610 options = {}; 7611 } 7612 7613 _this = _EventTarget.call(this) || this; 7614 var trackProps = { 7615 id: options.id || 'vjs_track_' + newGUID(), 7616 kind: options.kind || '', 7617 language: options.language || '' 7618 }; 7619 var label = options.label || ''; 7620 /** 7621 * @memberof Track 7622 * @member {string} id 7623 * The id of this track. Cannot be changed after creation. 7624 * @instance 7625 * 7626 * @readonly 7627 */ 7628 7629 /** 7630 * @memberof Track 7631 * @member {string} kind 7632 * The kind of track that this is. Cannot be changed after creation. 7633 * @instance 7634 * 7635 * @readonly 7636 */ 7637 7638 /** 7639 * @memberof Track 7640 * @member {string} language 7641 * The two letter language code for this track. Cannot be changed after 7642 * creation. 7643 * @instance 7644 * 7645 * @readonly 7646 */ 7647 7648 var _loop = function _loop(key) { 7649 Object.defineProperty(assertThisInitialized(_this), key, { 7650 get: function get() { 7651 return trackProps[key]; 7652 }, 7653 set: function set() {} 7654 }); 7655 }; 7656 7657 for (var key in trackProps) { 7658 _loop(key); 7659 } 7660 /** 7661 * @memberof Track 7662 * @member {string} label 7663 * The label of this track. Cannot be changed after creation. 7664 * @instance 7665 * 7666 * @fires Track#labelchange 7667 */ 7668 7669 7670 Object.defineProperty(assertThisInitialized(_this), 'label', { 7671 get: function get() { 7672 return label; 7673 }, 7674 set: function set(newLabel) { 7675 if (newLabel !== label) { 7676 label = newLabel; 7677 /** 7678 * An event that fires when label changes on this track. 7679 * 7680 * > Note: This is not part of the spec! 7681 * 7682 * @event Track#labelchange 7683 * @type {EventTarget~Event} 7684 */ 7685 7686 this.trigger('labelchange'); 7687 } 7688 } 7689 }); 7690 return _this; 7691 } 7692 7693 return Track; 7694 }(EventTarget$2); 7695 7696 /** 7697 * @file url.js 7698 * @module url 7699 */ 7700 /** 7701 * @typedef {Object} url:URLObject 7702 * 7703 * @property {string} protocol 7704 * The protocol of the url that was parsed. 7705 * 7706 * @property {string} hostname 7707 * The hostname of the url that was parsed. 7708 * 7709 * @property {string} port 7710 * The port of the url that was parsed. 7711 * 7712 * @property {string} pathname 7713 * The pathname of the url that was parsed. 7714 * 7715 * @property {string} search 7716 * The search query of the url that was parsed. 7717 * 7718 * @property {string} hash 7719 * The hash of the url that was parsed. 7720 * 7721 * @property {string} host 7722 * The host of the url that was parsed. 7723 */ 7724 7725 /** 7726 * Resolve and parse the elements of a URL. 7727 * 7728 * @function 7729 * @param {String} url 7730 * The url to parse 7731 * 7732 * @return {url:URLObject} 7733 * An object of url details 7734 */ 7735 7736 var parseUrl = function parseUrl(url) { 7737 // This entire method can be replace with URL once we are able to drop IE11 7738 var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL 7739 7740 var a = document_1.createElement('a'); 7741 a.href = url; // Copy the specific URL properties to a new object
7742 // This is also needed for IE because the anchor loses its 7743 // properties when it's removed from the dom 7744 7745 var details = {}; 7746 7747 for (var i = 0; i < props.length; i++) { 7748 details[props[i]] = a[props[i]]; 7749 } // IE adds the port to the host property unlike everyone else. If 7750 // a port identifier is added for standard ports, strip it. 7751 7752 7753 if (details.protocol === 'http:') { 7754 details.host = details.host.replace(/:80$/, ''); 7755 } 7756 7757 if (details.protocol === 'https:') { 7758 details.host = details.host.replace(/:443$/, ''); 7759 } 7760 7761 if (!details.protocol) { 7762 details.protocol = window_1.location.protocol; 7763 } 7764 /* istanbul ignore if */ 7765 7766 7767 if (!details.host) { 7768 details.host = window_1.location.host; 7769 } 7770 7771 return details; 7772 }; 7773 /** 7774 * Get absolute version of relative URL. Used to tell Flash the correct URL. 7775 * 7776 * @function 7777 * @param {string} url 7778 * URL to make absolute 7779 * 7780 * @return {string} 7781 * Absolute URL 7782 * 7783 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 7784 */ 7785 7786 var getAbsoluteURL = function getAbsoluteURL(url) { 7787 // Check if absolute URL 7788 if (!url.match(/^https?:\/\//)) { 7789 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 7790 // add the url to an anchor and let the browser parse the URL 7791 var a = document_1.createElement('a'); 7792 a.href = url; 7793 url = a.href; 7794 } 7795 7796 return url; 7797 }; 7798 /** 7799 * Returns the extension of the passed file name. It will return an empty string 7800 * if passed an invalid path. 7801 * 7802 * @function 7803 * @param {string} path 7804 * The fileName path like '/path/to/file.mp4' 7805 * 7806 * @return {string} 7807 * The extension in lower case or an empty string if no 7808 * extension could be found. 7809 */ 7810 7811 var getFileExtension = function getFileExtension(path) { 7812 if (typeof path === 'string') { 7813 var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/; 7814 var pathParts = splitPathRe.exec(path); 7815 7816 if (pathParts) { 7817 return pathParts.pop().toLowerCase(); 7818 } 7819 } 7820 7821 return ''; 7822 }; 7823 /** 7824 * Returns whether the url passed is a cross domain request or not. 7825 * 7826 * @function 7827 * @param {string} url 7828 * The url to check. 7829 * 7830 * @param {Object} [winLoc] 7831 * the domain to check the url against, defaults to window.location 7832 * 7833 * @param {string} [winLoc.protocol] 7834 * The window location protocol defaults to window.location.protocol 7835 * 7836 * @param {string} [winLoc.host] 7837 * The window location host defaults to window.location.host 7838 * 7839 * @return {boolean} 7840 * Whether it is a cross domain request or not. 7841 */ 7842 7843 var isCrossOrigin = function isCrossOrigin(url, winLoc) { 7844 if (winLoc === void 0) { 7845 winLoc = window_1.location; 7846 } 7847 7848 var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol 7849 7850 var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin 7851 // IE8 doesn't know location.origin, so we won't rely on it here 7852 7853 var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; 7854 return crossOrigin; 7855 }; 7856 7857 var Url = /*#__PURE__*/Object.freeze({ 7858 __proto__: null, 7859 parseUrl: parseUrl, 7860 getAbsoluteURL: getAbsoluteURL, 7861 getFileExtension: getFileExtension, 7862 isCrossOrigin: isCrossOrigin 7863 }); 7864 7865 var isFunction_1 = isFunction; 7866 var toString = Object.prototype.toString; 7867 7868 function isFunction(fn) { 7869 if (!fn) { 7870 return false; 7871 } 7872 7873 var string = toString.call(fn); 7874 return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below 7875 fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt); 7876 } 7877 7878 var httpResponseHandler = function httpResponseHandler(callback, decodeResponseBody) { 7879 if (decodeResponseBody === void 0) { 7880 decodeResponseBody = false; 7881 } 7882 7883 return function (err, response, responseBody) { 7884 // if the XHR failed, return that error 7885 if (err) { 7886 callback(err); 7887 return; 7888 } // if the HTTP status code is 4xx or 5xx, the request also failed 7889 7890 7891 if (response.statusCode >= 400 && response.statusCode <= 599) { 7892 var cause = responseBody; 7893 7894 if (decodeResponseBody) { 7895 if (window_1.TextDecoder) { 7896 var charset = getCharset(response.headers && response.headers['content-type']); 7897 7898 try { 7899 cause = new TextDecoder(charset).decode(responseBody); 7900 } catch (e) {} 7901 } else { 7902 cause = String.fromCharCode.apply(null, new Uint8Array(responseBody)); 7903 } 7904 } 7905 7906 callback({ 7907 cause: cause 7908 }); 7909 return; 7910 } // otherwise, request succeeded 7911 7912 7913 callback(null, responseBody); 7914 }; 7915 }; 7916 7917 function getCharset(contentTypeHeader) { 7918 if (contentTypeHeader === void 0) { 7919 contentTypeHeader = ''; 7920 } 7921 7922 return contentTypeHeader.toLowerCase().split(';').reduce(function (charset, contentType) { 7923 var _contentType$split = contentType.split('='), 7924 type = _contentType$split[0], 7925 value = _contentType$split[1]; 7926 7927 if (type.trim() === 'charset') { 7928 return value.trim(); 7929 } 7930 7931 return charset; 7932 }, 'utf-8'); 7933 } 7934 7935 var httpHandler = httpResponseHandler; 7936 7937 createXHR.httpHandler = httpHandler; 7938 /** 7939 * @license 7940 * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/> 7941 * Copyright (c) 2014 David Björklund 7942 * Available under the MIT license 7943 * <https://github.com/kesla/parse-headers/blob/master/LICENCE> 7944 */ 7945 7946 var parseHeaders = function parseHeaders(headers) { 7947 var result = {}; 7948 7949 if (!headers) { 7950 return result; 7951 } 7952 7953 headers.trim().split('\n').forEach(function (row) { 7954 var index = row.indexOf(':'); 7955 var key = row.slice(0, index).trim().toLowerCase(); 7956 var value = row.slice(index + 1).trim(); 7957 7958 if (typeof result[key] === 'undefined') { 7959 result[key] = value; 7960 } else if (Array.isArray(result[key])) { 7961 result[key].push(value); 7962 } else { 7963 result[key] = [result[key], value]; 7964 } 7965 });
vendor: 4,360 bytes, lines 7966-8132
7966 return result; 7967 }; 7968 7969 var lib = createXHR; // Allow use of default import syntax in TypeScript 7970 7971 var default_1 = createXHR; 7972 createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop$1; 7973 createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window_1.XDomainRequest; 7974 forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) { 7975 createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) { 7976 options = initParams(uri, options, callback); 7977 options.method = method.toUpperCase(); 7978 return _createXHR(options); 7979 }; 7980 }); 7981 7982 function forEachArray(array, iterator) { 7983 for (var i = 0; i < array.length; i++) { 7984 iterator(array[i]); 7985 } 7986 } 7987 7988 function isEmpty(obj) { 7989 for (var i in obj) { 7990 if (obj.hasOwnProperty(i)) return false; 7991 } 7992 7993 return true; 7994 } 7995 7996 function initParams(uri, options, callback) { 7997 var params = uri; 7998 7999 if (isFunction_1(options)) { 8000 callback = options; 8001 8002 if (typeof uri === "string") { 8003 params = { 8004 uri: uri 8005 }; 8006 } 8007 } else { 8008 params = _extends_1({}, options, { 8009 uri: uri 8010 }); 8011 } 8012 8013 params.callback = callback; 8014 return params; 8015 } 8016 8017 function createXHR(uri, options, callback) { 8018 options = initParams(uri, options, callback); 8019 return _createXHR(options); 8020 } 8021 8022 function _createXHR(options) { 8023 if (typeof options.callback === "undefined") { 8024 throw new Error("callback argument missing"); 8025 } 8026 8027 var called = false; 8028 8029 var callback = function cbOnce(err, response, body) { 8030 if (!called) { 8031 called = true; 8032 options.callback(err, response, body); 8033 } 8034 }; 8035 8036 function readystatechange() { 8037 if (xhr.readyState === 4) { 8038 setTimeout(loadFunc, 0); 8039 } 8040 } 8041 8042 function getBody() { 8043 // Chrome with requestType=blob throws errors arround when even testing access to responseText 8044 var body = undefined; 8045 8046 if (xhr.response) { 8047 body = xhr.response; 8048 } else { 8049 body = xhr.responseText || getXml(xhr); 8050 } 8051 8052 if (isJson) { 8053 try { 8054 body = JSON.parse(body); 8055 } catch (e) {} 8056 } 8057 8058 return body; 8059 } 8060 8061 function errorFunc(evt) { 8062 clearTimeout(timeoutTimer); 8063 8064 if (!(evt instanceof Error)) { 8065 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error")); 8066 } 8067 8068 evt.statusCode = 0; 8069 return callback(evt, failureResponse); 8070 } // will load the data & process the response in a special response object 8071 8072 8073 function loadFunc() { 8074 if (aborted) return; 8075 var status; 8076 clearTimeout(timeoutTimer); 8077 8078 if (options.useXDR && xhr.status === undefined) { 8079 //IE8 CORS GET successful response doesn't have a status field, but body is fine 8080 status = 200; 8081 } else { 8082 status = xhr.status === 1223 ? 204 : xhr.status; 8083 } 8084 8085 var response = failureResponse; 8086 var err = null; 8087 8088 if (status !== 0) { 8089 response = { 8090 body: getBody(), 8091 statusCode: status, 8092 method: method, 8093 headers: {}, 8094 url: uri, 8095 rawRequest: xhr 8096 }; 8097 8098 if (xhr.getAllResponseHeaders) { 8099 //remember xhr can in fact be XDR for CORS in IE 8100 response.headers = parseHeaders(xhr.getAllResponseHeaders()); 8101 } 8102 } else { 8103 err = new Error("Internal XMLHttpRequest Error"); 8104 } 8105 8106 return callback(err, response, response.body); 8107 } 8108 8109 var xhr = options.xhr || null; 8110 8111 if (!xhr) { 8112 if (options.cors || options.useXDR) { 8113 xhr = new createXHR.XDomainRequest(); 8114 } else { 8115 xhr = new createXHR.XMLHttpRequest(); 8116 } 8117 } 8118 8119 var key; 8120 var aborted; 8121 var uri = xhr.url = options.uri || options.url; 8122 var method = xhr.method = options.method || "GET"; 8123 var body = options.body || options.data; 8124 var headers = xhr.headers = options.headers || {}; 8125 var sync = !!options.sync; 8126 var isJson = false; 8127 var timeoutTimer; 8128 var failureResponse = { 8129 body: undefined, 8130 headers: {}, 8131 statusCode: 0, 8132 method: method,
vendor: 4,119 bytes, lines 8133-8264
8133 url: uri, 8134 rawRequest: xhr 8135 }; 8136 8137 if ("json" in options && options.json !== false) { 8138 isJson = true; 8139 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 8140 8141 if (method !== "GET" && method !== "HEAD") { 8142 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 8143 8144 body = JSON.stringify(options.json === true ? body : options.json); 8145 } 8146 } 8147 8148 xhr.onreadystatechange = readystatechange; 8149 xhr.onload = loadFunc; 8150 xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function. 8151 8152 xhr.onprogress = function () {// IE must die 8153 }; 8154 8155 xhr.onabort = function () { 8156 aborted = true; 8157 }; 8158 8159 xhr.ontimeout = errorFunc; 8160 xhr.open(method, uri, !sync, options.username, options.password); //has to be after open 8161 8162 if (!sync) { 8163 xhr.withCredentials = !!options.withCredentials; 8164 } // Cannot set timeout with sync request 8165 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 8166 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 8167 8168 8169 if (!sync && options.timeout > 0) { 8170 timeoutTimer = setTimeout(function () { 8171 if (aborted) return; 8172 aborted = true; //IE9 may still call readystatechange 8173 8174 xhr.abort("timeout"); 8175 var e = new Error("XMLHttpRequest timeout"); 8176 e.code = "ETIMEDOUT"; 8177 errorFunc(e); 8178 }, options.timeout); 8179 } 8180 8181 if (xhr.setRequestHeader) { 8182 for (key in headers) { 8183 if (headers.hasOwnProperty(key)) { 8184 xhr.setRequestHeader(key, headers[key]); 8185 } 8186 } 8187 } else if (options.headers && !isEmpty(options.headers)) { 8188 throw new Error("Headers cannot be set on an XDomainRequest object"); 8189 } 8190 8191 if ("responseType" in options) { 8192 xhr.responseType = options.responseType; 8193 } 8194 8195 if ("beforeSend" in options && typeof options.beforeSend === "function") { 8196 options.beforeSend(xhr); 8197 } // Microsoft Edge browser sends "undefined" when send is called with undefined value. 8198 // XMLHttpRequest spec says to pass null as body to indicate no body 8199 // See https://github.com/naugtur/xhr/issues/100. 8200 8201 8202 xhr.send(body || null); 8203 return xhr; 8204 } 8205 8206 function getXml(xhr) { 8207 // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" 8208 // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. 8209 try { 8210 if (xhr.responseType === "document") { 8211 return xhr.responseXML; 8212 } 8213 8214 var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 8215 8216 if (xhr.responseType === "" && !firefoxBugTakenEffect) { 8217 return xhr.responseXML; 8218 } 8219 } catch (e) {} 8220 8221 return null; 8222 } 8223 8224 function noop$1() {} 8225 lib["default"] = default_1; 8226 8227 /** 8228 * Takes a webvtt file contents and parses it into cues 8229 * 8230 * @param {string} srcContent 8231 * webVTT file contents 8232 * 8233 * @param {TextTrack} track 8234 * TextTrack to add cues to. Cues come from the srcContent. 8235 * 8236 * @private 8237 */ 8238 8239 var parseCues = function parseCues(srcContent, track) { 8240 var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder()); 8241 var errors = []; 8242 8243 parser.oncue = function (cue) { 8244 track.addCue(cue); 8245 }; 8246 8247 parser.onparsingerror = function (error) { 8248 errors.push(error); 8249 }; 8250 8251 parser.onflush = function () { 8252 track.trigger({ 8253 type: 'loadeddata', 8254 target: track 8255 }); 8256 }; 8257 8258 parser.parse(srcContent); 8259 8260 if (errors.length > 0) { 8261 if (window_1.console && window_1.console.groupCollapsed) { 8262 window_1.console.groupCollapsed("Text Track parsing errors for " + track.src); 8263 } 8264
8265 errors.forEach(function (error) { 8266 return log$1.error(error); 8267 }); 8268 8269 if (window_1.console && window_1.console.groupEnd) { 8270 window_1.console.groupEnd(); 8271 } 8272 } 8273 8274 parser.flush(); 8275 }; 8276 /** 8277 * Load a `TextTrack` from a specified url. 8278 * 8279 * @param {string} src 8280 * Url to load track from. 8281 * 8282 * @param {TextTrack} track 8283 * Track to add cues to. Comes from the content at the end of `url`. 8284 * 8285 * @private 8286 */ 8287 8288 8289 var loadTrack = function loadTrack(src, track) { 8290 var opts = { 8291 uri: src 8292 }; 8293 var crossOrigin = isCrossOrigin(src); 8294 8295 if (crossOrigin) { 8296 opts.cors = crossOrigin; 8297 } 8298 8299 var withCredentials = track.tech_.crossOrigin() === 'use-credentials'; 8300 8301 if (withCredentials) { 8302 opts.withCredentials = withCredentials; 8303 } 8304 8305 lib(opts, bind(this, function (err, response, responseBody) { 8306 if (err) { 8307 return log$1.error(err, response); 8308 } 8309 8310 track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 8311 // NOTE: this is only used for the alt/video.novtt.js build 8312 8313 if (typeof window_1.WebVTT !== 'function') { 8314 if (track.tech_) { 8315 // to prevent use before define eslint error, we define loadHandler 8316 // as a let here 8317 track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) { 8318 if (event.type === 'vttjserror') { 8319 log$1.error("vttjs failed to load, stopping trying to process " + track.src); 8320 return; 8321 } 8322 8323 return parseCues(responseBody, track); 8324 }); 8325 } 8326 } else { 8327 parseCues(responseBody, track); 8328 } 8329 })); 8330 }; 8331 /** 8332 * A representation of a single `TextTrack`. 8333 * 8334 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} 8335 * @extends Track 8336 */ 8337 8338 8339 var TextTrack = /*#__PURE__*/function (_Track) { 8340 inheritsLoose(TextTrack, _Track); 8341 8342 /** 8343 * Create an instance of this class. 8344 * 8345 * @param {Object} options={} 8346 * Object of option names and values 8347 * 8348 * @param {Tech} options.tech 8349 * A reference to the tech that owns this TextTrack. 8350 * 8351 * @param {TextTrack~Kind} [options.kind='subtitles'] 8352 * A valid text track kind. 8353 * 8354 * @param {TextTrack~Mode} [options.mode='disabled'] 8355 * A valid text track mode. 8356 * 8357 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8358 * A unique id for this TextTrack. 8359 * 8360 * @param {string} [options.label=''] 8361 * The menu label for this track. 8362 * 8363 * @param {string} [options.language=''] 8364 * A valid two character language code. 8365 * 8366 * @param {string} [options.srclang=''] 8367 * A valid two character language code. An alternative, but deprioritized 8368 * version of `options.language` 8369 * 8370 * @param {string} [options.src] 8371 * A url to TextTrack cues. 8372 * 8373 * @param {boolean} [options.default] 8374 * If this track should default to on or off. 8375 */ 8376 function TextTrack(options) { 8377 var _this; 8378 8379 if (options === void 0) { 8380 options = {}; 8381 } 8382 8383 if (!options.tech) { 8384 throw new Error('A tech was not provided.'); 8385 } 8386 8387 var settings = mergeOptions$3(options, { 8388 kind: TextTrackKind[options.kind] || 'subtitles', 8389 language: options.language || options.srclang || '' 8390 }); 8391 var mode = TextTrackMode[settings.mode] || 'disabled'; 8392 var default_ = settings["default"]; 8393 8394 if (settings.kind === 'metadata' || settings.kind === 'chapters') { 8395 mode = 'hidden'; 8396 } 8397 8398 _this = _Track.call(this, settings) || this; 8399 _this.tech_ = settings.tech; 8400 _this.cues_ = []; 8401 _this.activeCues_ = []; 8402 _this.preload_ = _this.tech_.preloadTextTracks !== false; 8403 var cues = new TextTrackCueList(_this.cues_); 8404 var activeCues = new TextTrackCueList(_this.activeCues_); 8405 var changed = false; 8406 var timeupdateHandler = bind(assertThisInitialized(_this), function () { 8407 if (!this.tech_.isReady_ || this.tech_.isDisposed()) { 8408 return; 8409 } // Accessing this.activeCues for the side-effects of updating itself 8410 // due to its nature as a getter function. Do not remove or cues will 8411 // stop updating!
8412 // Use the setter to prevent deletion from uglify (pure_getters rule) 8413 8414 8415 this.activeCues = this.activeCues; 8416 8417 if (changed) { 8418 this.trigger('cuechange'); 8419 changed = false; 8420 } 8421 }); 8422 8423 var disposeHandler = function disposeHandler() { 8424 _this.tech_.off('timeupdate', timeupdateHandler); 8425 }; 8426 8427 _this.tech_.one('dispose', disposeHandler); 8428 8429 if (mode !== 'disabled') { 8430 _this.tech_.on('timeupdate', timeupdateHandler); 8431 } 8432 8433 Object.defineProperties(assertThisInitialized(_this), { 8434 /** 8435 * @memberof TextTrack 8436 * @member {boolean} default 8437 * If this track was set to be on or off by default. Cannot be changed after 8438 * creation. 8439 * @instance 8440 * 8441 * @readonly 8442 */ 8443 "default": { 8444 get: function get() { 8445 return default_; 8446 }, 8447 set: function set() {} 8448 }, 8449 8450 /** 8451 * @memberof TextTrack 8452 * @member {string} mode 8453 * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will 8454 * not be set if setting to an invalid mode. 8455 * @instance 8456 * 8457 * @fires TextTrack#modechange 8458 */ 8459 mode: { 8460 get: function get() { 8461 return mode; 8462 }, 8463 set: function set(newMode) { 8464 if (!TextTrackMode[newMode]) { 8465 return; 8466 } 8467 8468 if (mode === newMode) { 8469 return; 8470 } 8471 8472 mode = newMode; 8473 8474 if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) { 8475 // On-demand load. 8476 loadTrack(this.src, this); 8477 } 8478 8479 this.tech_.off('timeupdate', timeupdateHandler); 8480 8481 if (mode !== 'disabled') { 8482 this.tech_.on('timeupdate', timeupdateHandler); 8483 } 8484 /** 8485 * An event that fires when mode changes on this track. This allows 8486 * the TextTrackList that holds this track to act accordingly. 8487 * 8488 * > Note: This is not part of the spec! 8489 * 8490 * @event TextTrack#modechange 8491 * @type {EventTarget~Event} 8492 */ 8493 8494 8495 this.trigger('modechange'); 8496 } 8497 }, 8498 8499 /** 8500 * @memberof TextTrack 8501 * @member {TextTrackCueList} cues 8502 * The text track cue list for this TextTrack. 8503 * @instance 8504 */ 8505 cues: { 8506 get: function get() { 8507 if (!this.loaded_) { 8508 return null; 8509 } 8510 8511 return cues; 8512 }, 8513 set: function set() {} 8514 }, 8515 8516 /** 8517 * @memberof TextTrack 8518 * @member {TextTrackCueList} activeCues 8519 * The list text track cues that are currently active for this TextTrack. 8520 * @instance 8521 */ 8522 activeCues: { 8523 get: function get() { 8524 if (!this.loaded_) { 8525 return null; 8526 } // nothing to do 8527 8528 8529 if (this.cues.length === 0) { 8530 return activeCues; 8531 } 8532 8533 var ct = this.tech_.currentTime(); 8534 var active = []; 8535 8536 for (var i = 0, l = this.cues.length; i < l; i++) { 8537 var cue = this.cues[i]; 8538 8539 if (cue.startTime <= ct && cue.endTime >= ct) { 8540 active.push(cue); 8541 } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { 8542 active.push(cue); 8543 } 8544 } 8545 8546 changed = false; 8547 8548 if (active.length !== this.activeCues_.length) { 8549 changed = true; 8550 } else { 8551 for (var _i = 0; _i < active.length; _i++) { 8552 if (this.activeCues_.indexOf(active[_i]) === -1) { 8553 changed = true; 8554 } 8555 } 8556 } 8557 8558 this.activeCues_ = active; 8559 activeCues.setCues_(this.activeCues_); 8560 return activeCues; 8561 }, 8562 // /!\ Keep this setter empty (see the timeupdate handler above) 8563 set: function set() {} 8564 } 8565 }); 8566 8567 if (settings.src) { 8568 _this.src = settings.src; 8569 8570 if (!_this.preload_) { 8571 // Tracks will load on-demand. 8572 // Act like we're loaded for other purposes. 8573 _this.loaded_ = true; 8574 } 8575 8576 if (_this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') { 8577 loadTrack(_this.src, assertThisInitialized(_this)); 8578 } 8579 } else { 8580 _this.loaded_ = true; 8581 } 8582 8583 return _this; 8584 } 8585 /** 8586 * Add a cue to the internal list of cues. 8587 * 8588 * @param {TextTrack~Cue} cue 8589 * The cue to add to our internal list 8590 */ 8591 8592 8593 var _proto = TextTrack.prototype; 8594 8595 _proto.addCue = function addCue(originalCue) { 8596 var cue = originalCue; 8597 8598 if (window_1.vttjs && !(originalCue instanceof window_1.vttjs.VTTCue)) { 8599 cue = new window_1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); 8600 8601 for (var prop in originalCue) { 8602 if (!(prop in cue)) { 8603 cue[prop] = originalCue[prop]; 8604 } 8605 } // make sure that `id` is copied over 8606 8607 8608 cue.id = originalCue.id; 8609 cue.originalCue_ = originalCue; 8610 } 8611 8612 var tracks = this.tech_.textTracks(); 8613 8614 for (var i = 0; i < tracks.length; i++) { 8615 if (tracks[i] !== this) { 8616 tracks[i].removeCue(cue); 8617 } 8618 } 8619 8620 this.cues_.push(cue); 8621 this.cues.setCues_(this.cues_); 8622 } 8623 /** 8624 * Remove a cue from our internal list 8625 * 8626 * @param {TextTrack~Cue} removeCue 8627 * The cue to remove from our internal list 8628 */ 8629 ; 8630 8631 _proto.removeCue = function removeCue(_removeCue) { 8632 var i = this.cues_.length; 8633 8634 while (i--) { 8635 var cue = this.cues_[i]; 8636 8637 if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { 8638 this.cues_.splice(i, 1); 8639 this.cues.setCues_(this.cues_); 8640 break; 8641 } 8642 } 8643 }; 8644 8645 return TextTrack; 8646 }(Track); 8647 /** 8648 * cuechange - One or more cues in the track have become active or stopped being active. 8649 */ 8650 8651 8652 TextTrack.prototype.allowedEvents_ = { 8653 cuechange: 'cuechange' 8654 }; 8655 8656 /** 8657 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} 8658 * only one `AudioTrack` in the list will be enabled at a time. 8659 * 8660 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} 8661 * @extends Track 8662 */ 8663 8664 var AudioTrack = /*#__PURE__*/function (_Track) { 8665 inheritsLoose(AudioTrack, _Track); 8666 8667 /** 8668 * Create an instance of this class. 8669 * 8670 * @param {Object} [options={}] 8671 * Object of option names and values 8672 * 8673 * @param {AudioTrack~Kind} [options.kind=''] 8674 * A valid audio track kind 8675 * 8676 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8677 * A unique id for this AudioTrack. 8678 * 8679 * @param {string} [options.label=''] 8680 * The menu label for this track. 8681 * 8682 * @param {string} [options.language=''] 8683 * A valid two character language code. 8684 * 8685 * @param {boolean} [options.enabled] 8686 * If this track is the one that is currently playing. If this track is part of 8687 * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. 8688 */ 8689 function AudioTrack(options) { 8690 var _this; 8691 8692 if (options === void 0) { 8693 options = {}; 8694 } 8695 8696 var settings = mergeOptions$3(options, { 8697 kind: AudioTrackKind[options.kind] || '' 8698 }); 8699 _this = _Track.call(this, settings) || this; 8700 var enabled = false; 8701 /** 8702 * @memberof AudioTrack 8703 * @member {boolean} enabled 8704 * If this `AudioTrack` is enabled or not. When setting this will 8705 * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. 8706 * @instance 8707 * 8708 * @fires VideoTrack#selectedchange 8709 */ 8710 8711 Object.defineProperty(assertThisInitialized(_this), 'enabled', { 8712 get: function get() { 8713 return enabled; 8714 }, 8715 set: function set(newEnabled) {
8716 // an invalid or unchanged value 8717 if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { 8718 return; 8719 } 8720 8721 enabled = newEnabled; 8722 /** 8723 * An event that fires when enabled changes on this track. This allows 8724 * the AudioTrackList that holds this track to act accordingly. 8725 * 8726 * > Note: This is not part of the spec! Native tracks will do 8727 * this internally without an event. 8728 * 8729 * @event AudioTrack#enabledchange 8730 * @type {EventTarget~Event} 8731 */ 8732 8733 this.trigger('enabledchange'); 8734 } 8735 }); // if the user sets this track to selected then 8736 // set selected to that true value otherwise 8737 // we keep it false 8738 8739 if (settings.enabled) { 8740 _this.enabled = settings.enabled; 8741 } 8742 8743 _this.loaded_ = true; 8744 return _this; 8745 } 8746 8747 return AudioTrack; 8748 }(Track); 8749 8750 /** 8751 * A representation of a single `VideoTrack`. 8752 * 8753 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} 8754 * @extends Track 8755 */ 8756 8757 var VideoTrack = /*#__PURE__*/function (_Track) { 8758 inheritsLoose(VideoTrack, _Track); 8759 8760 /** 8761 * Create an instance of this class. 8762 * 8763 * @param {Object} [options={}] 8764 * Object of option names and values 8765 * 8766 * @param {string} [options.kind=''] 8767 * A valid {@link VideoTrack~Kind} 8768 * 8769 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8770 * A unique id for this AudioTrack. 8771 * 8772 * @param {string} [options.label=''] 8773 * The menu label for this track. 8774 * 8775 * @param {string} [options.language=''] 8776 * A valid two character language code. 8777 * 8778 * @param {boolean} [options.selected] 8779 * If this track is the one that is currently playing. 8780 */ 8781 function VideoTrack(options) { 8782 var _this; 8783 8784 if (options === void 0) { 8785 options = {}; 8786 } 8787 8788 var settings = mergeOptions$3(options, { 8789 kind: VideoTrackKind[options.kind] || '' 8790 }); 8791 _this = _Track.call(this, settings) || this; 8792 var selected = false; 8793 /** 8794 * @memberof VideoTrack 8795 * @member {boolean} selected 8796 * If this `VideoTrack` is selected or not. When setting this will 8797 * fire {@link VideoTrack#selectedchange} if the state of selected changed. 8798 * @instance 8799 * 8800 * @fires VideoTrack#selectedchange 8801 */ 8802 8803 Object.defineProperty(assertThisInitialized(_this), 'selected', { 8804 get: function get() { 8805 return selected; 8806 }, 8807 set: function set(newSelected) { 8808 // an invalid or unchanged value 8809 if (typeof newSelected !== 'boolean' || newSelected === selected) { 8810 return; 8811 } 8812 8813 selected = newSelected; 8814 /** 8815 * An event that fires when selected changes on this track. This allows 8816 * the VideoTrackList that holds this track to act accordingly. 8817 * 8818 * > Note: This is not part of the spec! Native tracks will do 8819 * this internally without an event. 8820 * 8821 * @event VideoTrack#selectedchange 8822 * @type {EventTarget~Event} 8823 */ 8824 8825 this.trigger('selectedchange'); 8826 } 8827 }); // if the user sets this track to selected then 8828 // set selected to that true value otherwise 8829 // we keep it false 8830 8831 if (settings.selected) { 8832 _this.selected = settings.selected; 8833 } 8834 8835 return _this; 8836 } 8837 8838 return VideoTrack; 8839 }(Track); 8840 8841 /** 8842 * @memberof HTMLTrackElement 8843 * @typedef {HTMLTrackElement~ReadyState} 8844 * @enum {number} 8845 */ 8846 8847 var NONE = 0; 8848 var LOADING = 1; 8849 var LOADED = 2; 8850 var ERROR = 3; 8851 /** 8852 * A single track represented in the DOM. 8853 * 8854 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8855 * @extends EventTarget 8856 */ 8857 8858 var HTMLTrackElement = /*#__PURE__*/function (_EventTarget) { 8859 inheritsLoose(HTMLTrackElement, _EventTarget); 8860 8861 /** 8862 * Create an instance of this class. 8863 * 8864 * @param {Object} options={} 8865 * Object of option names and values 8866 * 8867 * @param {Tech} options.tech 8868 * A reference to the tech that owns this HTMLTrackElement. 8869 * 8870 * @param {TextTrack~Kind} [options.kind='subtitles'] 8871 * A valid text track kind. 8872 * 8873 * @param {TextTrack~Mode} [options.mode='disabled'] 8874 * A valid text track mode. 8875 * 8876 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8877 * A unique id for this TextTrack. 8878 * 8879 * @param {string} [options.label=''] 8880 * The menu label for this track. 8881 * 8882 * @param {string} [options.language=''] 8883 * A valid two character language code. 8884 * 8885 * @param {string} [options.srclang=''] 8886 * A valid two character language code. An alternative, but deprioritized 8887 * vesion of `options.language` 8888 * 8889 * @param {string} [options.src] 8890 * A url to TextTrack cues. 8891 * 8892 * @param {boolean} [options.default] 8893 * If this track should default to on or off. 8894 */ 8895 function HTMLTrackElement(options) { 8896 var _this; 8897 8898 if (options === void 0) { 8899 options = {}; 8900 } 8901 8902 _this = _EventTarget.call(this) || this; 8903 var readyState; 8904 var track = new TextTrack(options); 8905 _this.kind = track.kind; 8906 _this.src = track.src; 8907 _this.srclang = track.language; 8908 _this.label = track.label; 8909 _this["default"] = track["default"]; 8910 Object.defineProperties(assertThisInitialized(_this), { 8911 /** 8912 * @memberof HTMLTrackElement 8913 * @member {HTMLTrackElement~ReadyState} readyState 8914 * The current ready state of the track element. 8915 * @instance 8916 */ 8917 readyState: { 8918 get: function get() { 8919 return readyState; 8920 } 8921 }, 8922 8923 /** 8924 * @memberof HTMLTrackElement 8925 * @member {TextTrack} track 8926 * The underlying TextTrack object. 8927 * @instance 8928 * 8929 */ 8930 track: { 8931 get: function get() { 8932 return track; 8933 } 8934 } 8935 }); 8936 readyState = NONE; 8937 /** 8938 * @listens TextTrack#loadeddata 8939 * @fires HTMLTrackElement#load 8940 */ 8941 8942 track.addEventListener('loadeddata', function () { 8943 readyState = LOADED; 8944 8945 _this.trigger({ 8946 type: 'load', 8947 target: assertThisInitialized(_this) 8948 }); 8949 }); 8950 return _this; 8951 } 8952 8953 return HTMLTrackElement; 8954 }(EventTarget$2); 8955 8956 HTMLTrackElement.prototype.allowedEvents_ = { 8957 load: 'load' 8958 }; 8959 HTMLTrackElement.NONE = NONE; 8960 HTMLTrackElement.LOADING = LOADING; 8961 HTMLTrackElement.LOADED = LOADED; 8962 HTMLTrackElement.ERROR = ERROR; 8963 8964 /* 8965 * This file contains all track properties that are used in 8966 * player.js, tech.js, html5.js and possibly other techs in the future. 8967 */ 8968 8969 var NORMAL = { 8970 audio: { 8971 ListClass: AudioTrackList, 8972 TrackClass: AudioTrack, 8973 capitalName: 'Audio' 8974 }, 8975 video: { 8976 ListClass: VideoTrackList, 8977 TrackClass: VideoTrack, 8978 capitalName: 'Video' 8979 }, 8980 text: { 8981 ListClass: TextTrackList, 8982 TrackClass: TextTrack, 8983 capitalName: 'Text' 8984 } 8985 }; 8986 Object.keys(NORMAL).forEach(function (type) { 8987 NORMAL[type].getterName = type + "Tracks"; 8988 NORMAL[type].privateName = type + "Tracks_"; 8989 }); 8990 var REMOTE = { 8991 remoteText: { 8992 ListClass: TextTrackList, 8993 TrackClass: TextTrack, 8994 capitalName: 'RemoteText', 8995 getterName: 'remoteTextTracks', 8996 privateName: 'remoteTextTracks_' 8997 }, 8998 remoteTextEl: { 8999 ListClass: HtmlTrackElementList, 9000 TrackClass: HTMLTrackElement, 9001 capitalName: 'RemoteTextTrackEls', 9002 getterName: 'remoteTextTrackEls', 9003 privateName: 'remoteTextTrackEls_' 9004 } 9005 }; 9006 9007 var ALL = _extends_1({}, NORMAL, REMOTE); 9008 9009 REMOTE.names = Object.keys(REMOTE); 9010 NORMAL.names = Object.keys(NORMAL); 9011 ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); 9012 9013 /** 9014 * Copyright 2013 vtt.js Contributors 9015 * 9016 * Licensed under the Apache License, Version 2.0 (the "License");
9017 * you may not use this file except in compliance with the License. 9018 * You may obtain a copy of the License at 9019 * 9020 * http://www.apache.org/licenses/LICENSE-2.0 9021 * 9022 * Unless required by applicable law or agreed to in writing, software 9023 * distributed under the License is distributed on an "AS IS" BASIS, 9024 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9025 * See the License for the specific language governing permissions and 9026 * limitations under the License. 9027 */ 9028 9029 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 9030 9031 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 9032 9033 var _objCreate = Object.create || function () { 9034 function F() {} 9035 9036 return function (o) { 9037 if (arguments.length !== 1) { 9038 throw new Error('Object.create shim only accepts one parameter.'); 9039 } 9040 9041 F.prototype = o; 9042 return new F(); 9043 }; 9044 }(); // Creates a new ParserError object from an errorData object. The errorData 9045 // object should have default code and message properties. The default message 9046 // property can be overriden by passing in a message parameter. 9047 // See ParsingError.Errors below for acceptable errors. 9048 9049 9050 function ParsingError(errorData, message) { 9051 this.name = "ParsingError"; 9052 this.code = errorData.code; 9053 this.message = message || errorData.message; 9054 } 9055 9056 ParsingError.prototype = _objCreate(Error.prototype); 9057 ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors. 9058 9059 ParsingError.Errors = { 9060 BadSignature: { 9061 code: 0, 9062 message: "Malformed WebVTT signature." 9063 }, 9064 BadTimeStamp: { 9065 code: 1, 9066 message: "Malformed time stamp." 9067 } 9068 }; // Try to parse input as a time stamp. 9069 9070 function parseTimeStamp(input) { 9071 function computeSeconds(h, m, s, f) { 9072 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 9073 } 9074 9075 var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/); 9076 9077 if (!m) { 9078 return null; 9079 } 9080 9081 if (m[3]) { 9082 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 9083 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 9084 } else if (m[1] > 59) { 9085 // Timestamp takes the form of [hours]:[minutes].[milliseconds] 9086 // First position is hours as it's over 59. 9087 return computeSeconds(m[1], m[2], 0, m[4]); 9088 } else { 9089 // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 9090 return computeSeconds(0, m[1], m[2], m[4]); 9091 } 9092 } // A settings object holds key/value pairs and will ignore anything but the first 9093 // assignment to a specific key. 9094 9095 9096 function Settings() { 9097 this.values = _objCreate(null); 9098 } 9099 9100 Settings.prototype = { 9101 // Only accept the first assignment to any key. 9102 set: function set(k, v) { 9103 if (!this.get(k) && v !== "") { 9104 this.values[k] = v; 9105 } 9106 }, 9107 // Return the value for a key, or a default value. 9108 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 9109 // a number of possible default values as properties where 'defaultKey' is 9110 // the key of the property that will be chosen; otherwise it's assumed to be 9111 // a single value. 9112 get: function get(k, dflt, defaultKey) { 9113 if (defaultKey) { 9114 return this.has(k) ? this.values[k] : dflt[defaultKey]; 9115 } 9116 9117 return this.has(k) ? this.values[k] : dflt; 9118 }, 9119 // Check whether we have a value for a key. 9120 has: function has(k) { 9121 return k in this.values; 9122 }, 9123 // Accept a setting if its one of the given alternatives. 9124 alt: function alt(k, v, a) { 9125 for (var n = 0; n < a.length; ++n) { 9126 if (v === a[n]) { 9127 this.set(k, v); 9128 break; 9129 } 9130 } 9131 }, 9132 // Accept a setting if its a valid (signed) integer. 9133 integer: function integer(k, v) { 9134 if (/^-?\d+$/.test(v)) { 9135 // integer 9136 this.set(k, parseInt(v, 10)); 9137 } 9138 }, 9139 // Accept a setting if its a valid percentage. 9140 percent: function percent(k, v) { 9141 9142 if (v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) { 9143 v = parseFloat(v); 9144 9145 if (v >= 0 && v <= 100) { 9146 this.set(k, v); 9147 return true; 9148 } 9149 } 9150 9151 return false; 9152 } 9153 }; // Helper function to parse input into groups separated by 'groupDelim', and 9154 // interprete each group as a key/value pair separated by 'keyValueDelim'. 9155 9156 function parseOptions(input, callback, keyValueDelim, groupDelim) { 9157 var groups = groupDelim ? input.split(groupDelim) : [input]; 9158 9159 for (var i in groups) { 9160 if (typeof groups[i] !== "string") { 9161 continue; 9162 } 9163 9164 var kv = groups[i].split(keyValueDelim); 9165 9166 if (kv.length !== 2) { 9167 continue; 9168 } 9169 9170 var k = kv[0]; 9171 var v = kv[1]; 9172 callback(k, v); 9173 } 9174 } 9175 9176 function parseCue(input, cue, regionList) { 9177 // Remember the original input if we need to throw an error. 9178 var oInput = input; // 4.1 WebVTT timestamp 9179 9180 function consumeTimeStamp() { 9181 var ts = parseTimeStamp(input); 9182 9183 if (ts === null) { 9184 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput); 9185 } // Remove time stamp from input. 9186 9187 9188 input = input.replace(/^[^\sa-zA-Z-]+/, ""); 9189 return ts; 9190 } // 4.4.2 WebVTT cue settings 9191 9192 9193 function consumeCueSettings(input, cue) { 9194 var settings = new Settings();
vendor: 26,374 bytes, lines 9195-9949
9195 parseOptions(input, function (k, v) { 9196 switch (k) { 9197 case "region": 9198 // Find the last region we parsed with the same region id. 9199 for (var i = regionList.length - 1; i >= 0; i--) { 9200 if (regionList[i].id === v) { 9201 settings.set(k, regionList[i].region); 9202 break; 9203 } 9204 } 9205 9206 break; 9207 9208 case "vertical": 9209 settings.alt(k, v, ["rl", "lr"]); 9210 break; 9211 9212 case "line": 9213 var vals = v.split(","), 9214 vals0 = vals[0]; 9215 settings.integer(k, vals0); 9216 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 9217 settings.alt(k, vals0, ["auto"]); 9218 9219 if (vals.length === 2) { 9220 settings.alt("lineAlign", vals[1], ["start", "center", "end"]); 9221 } 9222 9223 break; 9224 9225 case "position": 9226 vals = v.split(","); 9227 settings.percent(k, vals[0]); 9228 9229 if (vals.length === 2) { 9230 settings.alt("positionAlign", vals[1], ["start", "center", "end"]); 9231 } 9232 9233 break; 9234 9235 case "size": 9236 settings.percent(k, v); 9237 break; 9238 9239 case "align": 9240 settings.alt(k, v, ["start", "center", "end", "left", "right"]); 9241 break; 9242 } 9243 }, /:/, /\s/); // Apply default values for any missing fields. 9244 9245 cue.region = settings.get("region", null); 9246 cue.vertical = settings.get("vertical", ""); 9247 9248 try { 9249 cue.line = settings.get("line", "auto"); 9250 } catch (e) {} 9251 9252 cue.lineAlign = settings.get("lineAlign", "start"); 9253 cue.snapToLines = settings.get("snapToLines", true); 9254 cue.size = settings.get("size", 100); // Safari still uses the old middle value and won't accept center 9255 9256 try { 9257 cue.align = settings.get("align", "center"); 9258 } catch (e) { 9259 cue.align = settings.get("align", "middle"); 9260 } 9261 9262 try { 9263 cue.position = settings.get("position", "auto"); 9264 } catch (e) { 9265 cue.position = settings.get("position", { 9266 start: 0, 9267 left: 0, 9268 center: 50, 9269 middle: 50, 9270 end: 100, 9271 right: 100 9272 }, cue.align); 9273 } 9274 9275 cue.positionAlign = settings.get("positionAlign", { 9276 start: "start", 9277 left: "start", 9278 center: "center", 9279 middle: "center", 9280 end: "end", 9281 right: "end" 9282 }, cue.align); 9283 } 9284 9285 function skipWhitespace() { 9286 input = input.replace(/^\s+/, ""); 9287 } // 4.1 WebVTT cue timings. 9288 9289 9290 skipWhitespace(); 9291 cue.startTime = consumeTimeStamp(); // (1) collect cue start time 9292 9293 skipWhitespace(); 9294 9295 if (input.substr(0, 3) !== "-->") { 9296 // (3) next characters must match "-->" 9297 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput); 9298 } 9299 9300 input = input.substr(3); 9301 skipWhitespace(); 9302 cue.endTime = consumeTimeStamp(); // (5) collect cue end time 9303 // 4.1 WebVTT cue settings list. 9304 9305 skipWhitespace(); 9306 consumeCueSettings(input, cue); 9307 } // When evaluating this file as part of a Webpack bundle for server 9308 // side rendering, `document` is an empty object. 9309 9310 9311 var TEXTAREA_ELEMENT = document_1.createElement && document_1.createElement("textarea"); 9312 var TAG_NAME = { 9313 c: "span", 9314 i: "i", 9315 b: "b", 9316 u: "u", 9317 ruby: "ruby", 9318 rt: "rt", 9319 v: "span", 9320 lang: "span" 9321 }; // 5.1 default text color 9322 // 5.2 default text background color is equivalent to text color with bg_ prefix 9323 9324 var DEFAULT_COLOR_CLASS = { 9325 white: 'rgba(255,255,255,1)', 9326 lime: 'rgba(0,255,0,1)', 9327 cyan: 'rgba(0,255,255,1)', 9328 red: 'rgba(255,0,0,1)', 9329 yellow: 'rgba(255,255,0,1)', 9330 magenta: 'rgba(255,0,255,1)', 9331 blue: 'rgba(0,0,255,1)', 9332 black: 'rgba(0,0,0,1)' 9333 }; 9334 var TAG_ANNOTATION = { 9335 v: "title", 9336 lang: "lang" 9337 }; 9338 var NEEDS_PARENT = { 9339 rt: "ruby" 9340 }; // Parse content into a document fragment. 9341 9342 function parseContent(window, input) { 9343 function nextToken() { 9344 // Check for end-of-string. 9345 if (!input) { 9346 return null; 9347 } // Consume 'n' characters from the input. 9348 9349 9350 function consume(result) { 9351 input = input.substr(result.length); 9352 return result; 9353 } 9354 9355 var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return 9356 // the tag. 9357 9358 return consume(m[1] ? m[1] : m[2]); 9359 } 9360 9361 function unescape(s) { 9362 TEXTAREA_ELEMENT.innerHTML = s; 9363 s = TEXTAREA_ELEMENT.textContent; 9364 TEXTAREA_ELEMENT.textContent = ""; 9365 return s; 9366 } 9367 9368 function shouldAdd(current, element) { 9369 return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName; 9370 } // Create an element for this tag. 9371 9372 9373 function createElement(type, annotation) { 9374 var tagName = TAG_NAME[type]; 9375 9376 if (!tagName) { 9377 return null; 9378 } 9379 9380 var element = window.document.createElement(tagName); 9381 var name = TAG_ANNOTATION[type]; 9382 9383 if (name && annotation) { 9384 element[name] = annotation.trim(); 9385 } 9386 9387 return element; 9388 } 9389 9390 var rootDiv = window.document.createElement("div"), 9391 current = rootDiv, 9392 t, 9393 tagStack = []; 9394 9395 while ((t = nextToken()) !== null) { 9396 if (t[0] === '<') { 9397 if (t[1] === "/") { 9398 // If the closing tag matches, move back up to the parent node. 9399 if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 9400 tagStack.pop(); 9401 current = current.parentNode; 9402 } // Otherwise just ignore the end tag. 9403 9404 9405 continue; 9406 } 9407 9408 var ts = parseTimeStamp(t.substr(1, t.length - 2)); 9409 var node; 9410 9411 if (ts) { 9412 // Timestamps are lead nodes as well. 9413 node = window.document.createProcessingInstruction("timestamp", ts); 9414 current.appendChild(node); 9415 continue; 9416 } 9417 9418 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag. 9419 9420 if (!m) { 9421 continue; 9422 } // Try to construct an element, and ignore the tag if we couldn't. 9423 9424 9425 node = createElement(m[1], m[3]); 9426 9427 if (!node) { 9428 continue; 9429 } // Determine if the tag should be added based on the context of where it 9430 // is placed in the cuetext. 9431 9432 9433 if (!shouldAdd(current, node)) { 9434 continue; 9435 } // Set the class list (as a list of classes, separated by space). 9436 9437 9438 if (m[2]) { 9439 var classes = m[2].split('.'); 9440 classes.forEach(function (cl) { 9441 var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color 9442 9443 var colorName = bgColor ? cl.slice(3) : cl; 9444 9445 if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) { 9446 var propName = bgColor ? 'background-color' : 'color'; 9447 var propValue = DEFAULT_COLOR_CLASS[colorName]; 9448 node.style[propName] = propValue; 9449 } 9450 }); 9451 node.className = classes.join(' '); 9452 } // Append the node to the current node, and enter the scope of the new 9453 // node. 9454 9455 9456 tagStack.push(m[1]); 9457 current.appendChild(node); 9458 current = node; 9459 continue; 9460 } // Text nodes are leaf nodes. 9461 9462 9463 current.appendChild(window.document.createTextNode(unescape(t))); 9464 } 9465 9466 return rootDiv; 9467 } // This is a list of all the Unicode characters that have a strong 9468 // right-to-left category. What this means is that these characters are 9469 // written right-to-left for sure. It was generated by pulling all the strong 9470 // right-to-left characters out of the Unicode data table. That table can 9471 // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 9472 9473 9474 var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]]; 9475 9476 function isStrongRTLChar(charCode) { 9477 for (var i = 0; i < strongRTLRanges.length; i++) { 9478 var currentRange = strongRTLRanges[i]; 9479 9480 if (charCode >= currentRange[0] && charCode <= currentRange[1]) { 9481 return true; 9482 } 9483 } 9484 9485 return false; 9486 } 9487 9488 function determineBidi(cueDiv) { 9489 var nodeStack = [], 9490 text = "", 9491 charCode; 9492 9493 if (!cueDiv || !cueDiv.childNodes) { 9494 return "ltr"; 9495 } 9496 9497 function pushNodes(nodeStack, node) { 9498 for (var i = node.childNodes.length - 1; i >= 0; i--) { 9499 nodeStack.push(node.childNodes[i]); 9500 } 9501 } 9502 9503 function nextTextNode(nodeStack) { 9504 if (!nodeStack || !nodeStack.length) { 9505 return null; 9506 } 9507 9508 var node = nodeStack.pop(), 9509 text = node.textContent || node.innerText; 9510 9511 if (text) { 9512 // TODO: This should match all unicode type B characters (paragraph 9513 // separator characters). See issue #115. 9514 var m = text.match(/^.*(\n|\r)/); 9515 9516 if (m) { 9517 nodeStack.length = 0; 9518 return m[0]; 9519 } 9520 9521 return text; 9522 } 9523 9524 if (node.tagName === "ruby") { 9525 return nextTextNode(nodeStack); 9526 } 9527 9528 if (node.childNodes) { 9529 pushNodes(nodeStack, node); 9530 return nextTextNode(nodeStack); 9531 } 9532 } 9533 9534 pushNodes(nodeStack, cueDiv); 9535 9536 while (text = nextTextNode(nodeStack)) { 9537 for (var i = 0; i < text.length; i++) { 9538 charCode = text.charCodeAt(i); 9539 9540 if (isStrongRTLChar(charCode)) { 9541 return "rtl"; 9542 } 9543 } 9544 } 9545 9546 return "ltr"; 9547 } 9548 9549 function computeLinePos(cue) { 9550 if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) { 9551 return cue.line; 9552 } 9553 9554 if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) { 9555 return -1; 9556 } 9557 9558 var track = cue.track, 9559 trackList = track.textTrackList, 9560 count = 0; 9561 9562 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 9563 if (trackList[i].mode === "showing") { 9564 count++; 9565 } 9566 } 9567 9568 return ++count * -1; 9569 } 9570 9571 function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the 9572 // div on 'this'. 9573 9574 9575 StyleBox.prototype.applyStyles = function (styles, div) { 9576 div = div || this.div; 9577 9578 for (var prop in styles) { 9579 if (styles.hasOwnProperty(prop)) { 9580 div.style[prop] = styles[prop]; 9581 } 9582 } 9583 }; 9584 9585 StyleBox.prototype.formatStyle = function (val, unit) { 9586 return val === 0 ? 0 : val + unit; 9587 }; // Constructs the computed display state of the cue (a div). Places the div 9588 // into the overlay which should be a block level element (usually a div). 9589 9590 9591 function CueStyleBox(window, cue, styleOptions) { 9592 StyleBox.call(this); 9593 this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 9594 // have inline positioning and will function as the cue background box. 9595 9596 this.cueDiv = parseContent(window, cue.text); 9597 var styles = { 9598 color: "rgba(255, 255, 255, 1)", 9599 backgroundColor: "rgba(0, 0, 0, 0.8)", 9600 position: "relative", 9601 left: 0, 9602 right: 0, 9603 top: 0, 9604 bottom: 0, 9605 display: "inline", 9606 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9607 unicodeBidi: "plaintext" 9608 }; 9609 this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue 9610 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 9611 // mirrors of them except middle instead of center on Safari. 9612 9613 this.div = window.document.createElement("div"); 9614 styles = { 9615 direction: determineBidi(this.cueDiv), 9616 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9617 unicodeBidi: "plaintext", 9618 textAlign: cue.align === "middle" ? "center" : cue.align, 9619 font: styleOptions.font, 9620 whiteSpace: "pre-line", 9621 position: "absolute" 9622 }; 9623 this.applyStyles(styles); 9624 this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text 9625 // position of the cue box. The reference edge will be resolved later when 9626 // the box orientation styles are applied. 9627 9628 var textPos = 0; 9629 9630 switch (cue.positionAlign) { 9631 case "start": 9632 textPos = cue.position; 9633 break; 9634 9635 case "center": 9636 textPos = cue.position - cue.size / 2; 9637 break; 9638 9639 case "end": 9640 textPos = cue.position - cue.size; 9641 break; 9642 } // Horizontal box orientation; textPos is the distance from the left edge of the 9643 // area to the left edge of the box and cue.size is the distance extending to 9644 // the right from there. 9645 9646 9647 if (cue.vertical === "") { 9648 this.applyStyles({ 9649 left: this.formatStyle(textPos, "%"), 9650 width: this.formatStyle(cue.size, "%") 9651 }); // Vertical box orientation; textPos is the distance from the top edge of the 9652 // area to the top edge of the box and cue.size is the height extending 9653 // downwards from there. 9654 } else { 9655 this.applyStyles({ 9656 top: this.formatStyle(textPos, "%"), 9657 height: this.formatStyle(cue.size, "%") 9658 }); 9659 } 9660 9661 this.move = function (box) { 9662 this.applyStyles({ 9663 top: this.formatStyle(box.top, "px"), 9664 bottom: this.formatStyle(box.bottom, "px"), 9665 left: this.formatStyle(box.left, "px"), 9666 right: this.formatStyle(box.right, "px"), 9667 height: this.formatStyle(box.height, "px"), 9668 width: this.formatStyle(box.width, "px") 9669 }); 9670 }; 9671 } 9672 9673 CueStyleBox.prototype = _objCreate(StyleBox.prototype); 9674 CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily 9675 // compute things with such as if it overlaps or intersects with another Element. 9676 // Can initialize it with either a StyleBox or another BoxPosition. 9677 9678 function BoxPosition(obj) { 9679 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 9680 // was passed in and we need to copy the results of 'getBoundingClientRect' 9681 // as the object returned is readonly. All co-ordinate values are in reference 9682 // to the viewport origin (top left). 9683 var lh, height, width, top; 9684 9685 if (obj.div) { 9686 height = obj.div.offsetHeight; 9687 width = obj.div.offsetWidth; 9688 top = obj.div.offsetTop; 9689 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); 9690 obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of 9691 // the inner div's lines. This could be due to bold text, etc, on some platforms. 9692 // In this case we should get the average line height and use that. This will 9693 // result in the desired behaviour. 9694 9695 lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0; 9696 } 9697 9698 this.left = obj.left; 9699 this.right = obj.right; 9700 this.top = obj.top || top; 9701 this.height = obj.height || height; 9702 this.bottom = obj.bottom || top + (obj.height || height); 9703 this.width = obj.width || width; 9704 this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 9705 } // Move the box along a particular axis. Optionally pass in an amount to move 9706 // the box. If no amount is passed then the default is the line height of the 9707 // box. 9708 9709 9710 BoxPosition.prototype.move = function (axis, toMove) { 9711 toMove = toMove !== undefined ? toMove : this.lineHeight; 9712 9713 switch (axis) { 9714 case "+x": 9715 this.left += toMove; 9716 this.right += toMove; 9717 break; 9718 9719 case "-x": 9720 this.left -= toMove; 9721 this.right -= toMove; 9722 break; 9723 9724 case "+y": 9725 this.top += toMove; 9726 this.bottom += toMove; 9727 break; 9728 9729 case "-y": 9730 this.top -= toMove; 9731 this.bottom -= toMove; 9732 break; 9733 } 9734 }; // Check if this box overlaps another box, b2. 9735 9736 9737 BoxPosition.prototype.overlaps = function (b2) { 9738 return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top; 9739 }; // Check if this box overlaps any other boxes in boxes. 9740 9741 9742 BoxPosition.prototype.overlapsAny = function (boxes) { 9743 for (var i = 0; i < boxes.length; i++) { 9744 if (this.overlaps(boxes[i])) { 9745 return true; 9746 } 9747 } 9748 9749 return false; 9750 }; // Check if this box is within another box. 9751 9752 9753 BoxPosition.prototype.within = function (container) { 9754 return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right; 9755 }; // Check if this box is entirely within the container or it is overlapping 9756 // on the edge opposite of the axis direction passed. For example, if "+x" is 9757 // passed and the box is overlapping on the left edge of the container, then 9758 // return true. 9759 9760 9761 BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) { 9762 switch (axis) { 9763 case "+x": 9764 return this.left < container.left; 9765 9766 case "-x": 9767 return this.right > container.right; 9768 9769 case "+y": 9770 return this.top < container.top; 9771 9772 case "-y": 9773 return this.bottom > container.bottom; 9774 } 9775 }; // Find the percentage of the area that this box is overlapping with another 9776 // box. 9777 9778 9779 BoxPosition.prototype.intersectPercentage = function (b2) { 9780 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 9781 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 9782 intersectArea = x * y; 9783 return intersectArea / (this.height * this.width); 9784 }; // Convert the positions from this box to CSS compatible positions using 9785 // the reference container's positions. This has to be done because this 9786 // box's positions are in reference to the viewport origin, whereas, CSS 9787 // values are in referecne to their respective edges. 9788 9789 9790 BoxPosition.prototype.toCSSCompatValues = function (reference) { 9791 return { 9792 top: this.top - reference.top, 9793 bottom: reference.bottom - this.bottom, 9794 left: this.left - reference.left, 9795 right: reference.right - this.right, 9796 height: this.height, 9797 width: this.width 9798 }; 9799 }; // Get an object that represents the box's position without anything extra. 9800 // Can pass a StyleBox, HTMLElement, or another BoxPositon. 9801 9802 9803 BoxPosition.getSimpleBoxPosition = function (obj) { 9804 var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; 9805 var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; 9806 var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; 9807 obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; 9808 var ret = { 9809 left: obj.left, 9810 right: obj.right, 9811 top: obj.top || top, 9812 height: obj.height || height, 9813 bottom: obj.bottom || top + (obj.height || height), 9814 width: obj.width || width 9815 }; 9816 return ret; 9817 }; // Move a StyleBox to its specified, or next best, position. The containerBox 9818 // is the box that contains the StyleBox, such as a div. boxPositions are 9819 // a list of other boxes that the styleBox can't overlap with. 9820 9821 9822 function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 9823 // Find the best position for a cue box, b, on the video. The axis parameter 9824 // is a list of axis, the order of which, it will move the box along. For example: 9825 // Passing ["+x", "-x"] will move the box first along the x axis in the positive 9826 // direction. If it doesn't find a good position for it there it will then move 9827 // it along the x axis in the negative direction. 9828 function findBestPosition(b, axis) { 9829 var bestPosition, 9830 specifiedPosition = new BoxPosition(b), 9831 percentage = 1; // Highest possible so the first thing we get is better. 9832 9833 for (var i = 0; i < axis.length; i++) { 9834 while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) { 9835 b.move(axis[i]); 9836 } // We found a spot where we aren't overlapping anything. This is our 9837 // best position. 9838 9839 9840 if (b.within(containerBox)) { 9841 return b; 9842 } 9843 9844 var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try 9845 // then remember this position as the best position. 9846 9847 if (percentage > p) { 9848 bestPosition = new BoxPosition(b); 9849 percentage = p; 9850 } // Reset the box position to the specified position. 9851 9852 9853 b = new BoxPosition(specifiedPosition); 9854 } 9855 9856 return bestPosition || specifiedPosition; 9857 } 9858 9859 var boxPosition = new BoxPosition(styleBox), 9860 cue = styleBox.cue, 9861 linePos = computeLinePos(cue), 9862 axis = []; // If we have a line number to align the cue to. 9863 9864 if (cue.snapToLines) { 9865 var size; 9866 9867 switch (cue.vertical) { 9868 case "": 9869 axis = ["+y", "-y"]; 9870 size = "height"; 9871 break; 9872 9873 case "rl": 9874 axis = ["+x", "-x"]; 9875 size = "width"; 9876 break; 9877 9878 case "lr": 9879 axis = ["-x", "+x"]; 9880 size = "width"; 9881 break; 9882 } 9883 9884 var step = boxPosition.lineHeight, 9885 position = step * Math.round(linePos), 9886 maxPosition = containerBox[size] + step, 9887 initialAxis = axis[0]; // If the specified intial position is greater then the max position then 9888 // clamp the box to the amount of steps it would take for the box to 9889 // reach the max position. 9890 9891 if (Math.abs(position) > maxPosition) { 9892 position = position < 0 ? -1 : 1; 9893 position *= Math.ceil(maxPosition / step) * step; 9894 } // If computed line position returns negative then line numbers are 9895 // relative to the bottom of the video instead of the top. Therefore, we 9896 // need to increase our initial position by the length or width of the 9897 // video, depending on the writing direction, and reverse our axis directions. 9898 9899 9900 if (linePos < 0) { 9901 position += cue.vertical === "" ? containerBox.height : containerBox.width; 9902 axis = axis.reverse(); 9903 } // Move the box to the specified position. This may not be its best 9904 // position. 9905 9906 9907 boxPosition.move(initialAxis, position); 9908 } else { 9909 // If we have a percentage line value for the cue. 9910 var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100; 9911 9912 switch (cue.lineAlign) { 9913 case "center": 9914 linePos -= calculatedPercentage / 2; 9915 break; 9916 9917 case "end": 9918 linePos -= calculatedPercentage; 9919 break; 9920 } // Apply initial line position to the cue box. 9921 9922 9923 switch (cue.vertical) { 9924 case "": 9925 styleBox.applyStyles({ 9926 top: styleBox.formatStyle(linePos, "%") 9927 }); 9928 break; 9929 9930 case "rl": 9931 styleBox.applyStyles({ 9932 left: styleBox.formatStyle(linePos, "%") 9933 }); 9934 break; 9935 9936 case "lr": 9937 styleBox.applyStyles({ 9938 right: styleBox.formatStyle(linePos, "%") 9939 }); 9940 break; 9941 } 9942 9943 axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning 9944 // to it. 9945 9946 boxPosition = new BoxPosition(styleBox); 9947 } 9948 9949 var bestPosition = findBestPosition(boxPosition, axis);
vendor: 5,111 bytes, lines 9950-10116
9950 styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 9951 } 9952 9953 function WebVTT$1() {// Nothing 9954 } // Helper to allow strings to be decoded instead of the default binary utf8 data. 9955 9956 9957 WebVTT$1.StringDecoder = function () { 9958 return { 9959 decode: function decode(data) { 9960 if (!data) { 9961 return ""; 9962 } 9963 9964 if (typeof data !== "string") { 9965 throw new Error("Error - expected string data."); 9966 } 9967 9968 return decodeURIComponent(encodeURIComponent(data)); 9969 } 9970 }; 9971 }; 9972 9973 WebVTT$1.convertCueToDOMTree = function (window, cuetext) { 9974 if (!window || !cuetext) { 9975 return null; 9976 } 9977 9978 return parseContent(window, cuetext); 9979 }; 9980 9981 var FONT_SIZE_PERCENT = 0.05; 9982 var FONT_STYLE = "sans-serif"; 9983 var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it. 9984 // @param overlay A block level element (usually a div) that the computed cues 9985 // and regions will be placed into. 9986 9987 WebVTT$1.processCues = function (window, cues, overlay) { 9988 if (!window || !cues || !overlay) { 9989 return null; 9990 } // Remove all previous children. 9991 9992 9993 while (overlay.firstChild) { 9994 overlay.removeChild(overlay.firstChild); 9995 } 9996 9997 var paddedOverlay = window.document.createElement("div"); 9998 paddedOverlay.style.position = "absolute"; 9999 paddedOverlay.style.left = "0"; 10000 paddedOverlay.style.right = "0"; 10001 paddedOverlay.style.top = "0"; 10002 paddedOverlay.style.bottom = "0"; 10003 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 10004 overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could 10005 // be the case if a cue's state has been changed since the last computation or 10006 // if it has not been computed yet. 10007 10008 function shouldCompute(cues) { 10009 for (var i = 0; i < cues.length; i++) { 10010 if (cues[i].hasBeenReset || !cues[i].displayState) { 10011 return true; 10012 } 10013 } 10014 10015 return false; 10016 } // We don't need to recompute the cues' display states. Just reuse them. 10017 10018 10019 if (!shouldCompute(cues)) { 10020 for (var i = 0; i < cues.length; i++) { 10021 paddedOverlay.appendChild(cues[i].displayState); 10022 } 10023 10024 return; 10025 } 10026 10027 var boxPositions = [], 10028 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 10029 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 10030 var styleOptions = { 10031 font: fontSize + "px " + FONT_STYLE 10032 }; 10033 10034 (function () { 10035 var styleBox, cue; 10036 10037 for (var i = 0; i < cues.length; i++) { 10038 cue = cues[i]; // Compute the intial position and styles of the cue div. 10039 10040 styleBox = new CueStyleBox(window, cue, styleOptions); 10041 paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. 10042 10043 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later 10044 // if we don't have too. 10045 10046 cue.displayState = styleBox.div; 10047 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 10048 } 10049 })(); 10050 }; 10051 10052 WebVTT$1.Parser = function (window, vttjs, decoder) { 10053 if (!decoder) { 10054 decoder = vttjs; 10055 vttjs = {}; 10056 } 10057 10058 if (!vttjs) { 10059 vttjs = {}; 10060 } 10061 10062 this.window = window; 10063 this.vttjs = vttjs; 10064 this.state = "INITIAL"; 10065 this.buffer = ""; 10066 this.decoder = decoder || new TextDecoder("utf8"); 10067 this.regionList = []; 10068 }; 10069 10070 WebVTT$1.Parser.prototype = { 10071 // If the error is a ParsingError then report it to the consumer if 10072 // possible. If it's not a ParsingError then throw it like normal. 10073 reportOrThrowError: function reportOrThrowError(e) { 10074 if (e instanceof ParsingError) { 10075 this.onparsingerror && this.onparsingerror(e); 10076 } else { 10077 throw e; 10078 } 10079 }, 10080 parse: function parse(data) { 10081 var self = this; // If there is no data then we won't decode it, but will just try to parse 10082 // whatever is in buffer already. This may occur in circumstances, for 10083 // example when flush() is called. 10084 10085 if (data) { 10086 // Try to decode the data that we received. 10087 self.buffer += self.decoder.decode(data, { 10088 stream: true 10089 }); 10090 } 10091 10092 function collectNextLine() { 10093 var buffer = self.buffer; 10094 var pos = 0; 10095 10096 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 10097 ++pos; 10098 } 10099 10100 var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below. 10101 10102 if (buffer[pos] === '\r') { 10103 ++pos; 10104 } 10105 10106 if (buffer[pos] === '\n') { 10107 ++pos; 10108 } 10109 10110 self.buffer = buffer.substr(pos); 10111 return line; 10112 } // 3.4 WebVTT region and WebVTT region settings syntax 10113 10114 10115 function parseRegion(input) { 10116 var settings = new Settings();
vendor: 8,124 bytes, lines 10117-10373
10117 parseOptions(input, function (k, v) { 10118 switch (k) { 10119 case "id": 10120 settings.set(k, v); 10121 break; 10122 10123 case "width": 10124 settings.percent(k, v); 10125 break; 10126 10127 case "lines": 10128 settings.integer(k, v); 10129 break; 10130 10131 case "regionanchor": 10132 case "viewportanchor": 10133 var xy = v.split(','); 10134 10135 if (xy.length !== 2) { 10136 break; 10137 } // We have to make sure both x and y parse, so use a temporary 10138 // settings object here. 10139 10140 10141 var anchor = new Settings(); 10142 anchor.percent("x", xy[0]); 10143 anchor.percent("y", xy[1]); 10144 10145 if (!anchor.has("x") || !anchor.has("y")) { 10146 break; 10147 } 10148 10149 settings.set(k + "X", anchor.get("x")); 10150 settings.set(k + "Y", anchor.get("y")); 10151 break; 10152 10153 case "scroll": 10154 settings.alt(k, v, ["up"]); 10155 break; 10156 } 10157 }, /=/, /\s/); // Create the region, using default values for any values that were not 10158 // specified. 10159 10160 if (settings.has("id")) { 10161 var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); 10162 region.width = settings.get("width", 100); 10163 region.lines = settings.get("lines", 3); 10164 region.regionAnchorX = settings.get("regionanchorX", 0); 10165 region.regionAnchorY = settings.get("regionanchorY", 100); 10166 region.viewportAnchorX = settings.get("viewportanchorX", 0); 10167 region.viewportAnchorY = settings.get("viewportanchorY", 100); 10168 region.scroll = settings.get("scroll", ""); // Register the region. 10169 10170 self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that 10171 // reference it. 10172 10173 self.regionList.push({ 10174 id: settings.get("id"), 10175 region: region 10176 }); 10177 } 10178 } // draft-pantos-http-live-streaming-20 10179 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 10180 // 3.5 WebVTT 10181 10182 10183 function parseTimestampMap(input) { 10184 var settings = new Settings(); 10185 parseOptions(input, function (k, v) { 10186 switch (k) { 10187 case "MPEGT": 10188 settings.integer(k + 'S', v); 10189 break; 10190 10191 case "LOCA": 10192 settings.set(k + 'L', parseTimeStamp(v)); 10193 break; 10194 } 10195 }, /[^\d]:/, /,/); 10196 self.ontimestampmap && self.ontimestampmap({ 10197 "MPEGTS": settings.get("MPEGTS"), 10198 "LOCAL": settings.get("LOCAL") 10199 }); 10200 } // 3.2 WebVTT metadata header syntax 10201 10202 10203 function parseHeader(input) { 10204 if (input.match(/X-TIMESTAMP-MAP/)) { 10205 // This line contains HLS X-TIMESTAMP-MAP metadata 10206 parseOptions(input, function (k, v) { 10207 switch (k) { 10208 case "X-TIMESTAMP-MAP": 10209 parseTimestampMap(v); 10210 break; 10211 } 10212 }, /=/); 10213 } else { 10214 parseOptions(input, function (k, v) { 10215 switch (k) { 10216 case "Region": 10217 // 3.3 WebVTT region metadata header syntax 10218 parseRegion(v); 10219 break; 10220 } 10221 }, /:/); 10222 } 10223 } // 5.1 WebVTT file parsing. 10224 10225 10226 try { 10227 var line; 10228 10229 if (self.state === "INITIAL") { 10230 // We can't start parsing until we have the first line. 10231 if (!/\r\n|\n/.test(self.buffer)) { 10232 return this; 10233 } 10234 10235 line = collectNextLine(); 10236 var m = line.match(/^WEBVTT([ \t].*)?$/); 10237 10238 if (!m || !m[0]) { 10239 throw new ParsingError(ParsingError.Errors.BadSignature); 10240 } 10241 10242 self.state = "HEADER"; 10243 } 10244 10245 var alreadyCollectedLine = false; 10246 10247 while (self.buffer) { 10248 // We can't parse a line until we have the full line. 10249 if (!/\r\n|\n/.test(self.buffer)) { 10250 return this; 10251 } 10252 10253 if (!alreadyCollectedLine) { 10254 line = collectNextLine(); 10255 } else { 10256 alreadyCollectedLine = false; 10257 } 10258 10259 switch (self.state) { 10260 case "HEADER": 10261 // 13-18 - Allow a header (metadata) under the WEBVTT line. 10262 if (/:/.test(line)) { 10263 parseHeader(line); 10264 } else if (!line) { 10265 // An empty line terminates the header and starts the body (cues). 10266 self.state = "ID"; 10267 } 10268 10269 continue; 10270 10271 case "NOTE": 10272 // Ignore NOTE blocks. 10273 if (!line) { 10274 self.state = "ID"; 10275 } 10276 10277 continue; 10278 10279 case "ID": 10280 // Check for the start of NOTE blocks. 10281 if (/^NOTE($|[ \t])/.test(line)) { 10282 self.state = "NOTE"; 10283 break; 10284 } // 19-29 - Allow any number of line terminators, then initialize new cue values. 10285 10286 10287 if (!line) { 10288 continue; 10289 } 10290 10291 self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center 10292 10293 try { 10294 self.cue.align = "center"; 10295 } catch (e) { 10296 self.cue.align = "middle"; 10297 } 10298 10299 self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data. 10300 10301 if (line.indexOf("-->") === -1) { 10302 self.cue.id = line; 10303 continue; 10304 } 10305 10306 // Process line as start of a cue. 10307 10308 /*falls through*/ 10309 10310 case "CUE": 10311 // 40 - Collect cue timings and settings. 10312 try { 10313 parseCue(line, self.cue, self.regionList); 10314 } catch (e) { 10315 self.reportOrThrowError(e); // In case of an error ignore rest of the cue. 10316 10317 self.cue = null; 10318 self.state = "BADCUE"; 10319 continue; 10320 } 10321 10322 self.state = "CUETEXT"; 10323 continue; 10324 10325 case "CUETEXT": 10326 var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue. 10327 // 35 - If we have the special substring '-->' then report the cue, 10328 // but do not collect the line as we need to process the current 10329 // one as a new cue. 10330 10331 if (!line || hasSubstring && (alreadyCollectedLine = true)) { 10332 // We are done parsing self cue. 10333 self.oncue && self.oncue(self.cue); 10334 self.cue = null; 10335 self.state = "ID"; 10336 continue; 10337 } 10338 10339 if (self.cue.text) { 10340 self.cue.text += "\n"; 10341 } 10342 10343 self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n'); 10344 continue; 10345 10346 case "BADCUE": 10347 // BADCUE 10348 // 54-62 - Collect and discard the remaining cue. 10349 if (!line) { 10350 self.state = "ID"; 10351 } 10352 10353 continue; 10354 } 10355 } 10356 } catch (e) { 10357 self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have. 10358 10359 if (self.state === "CUETEXT" && self.cue && self.oncue) { 10360 self.oncue(self.cue); 10361 } 10362 10363 self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise 10364 // another exception occurred so enter BADCUE state. 10365 10366 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 10367 } 10368 10369 return this; 10370 }, 10371 flush: function flush() { 10372 var self = this; 10373
10374 try { 10375 // Finish decoding the stream. 10376 self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region. 10377 10378 if (self.cue || self.state === "HEADER") { 10379 self.buffer += "\n\n"; 10380 self.parse(); 10381 } // If we've flushed, parsed, and we're still on the INITIAL state then 10382 // that means we don't have enough of the stream to parse the first 10383 // line. 10384 10385 10386 if (self.state === "INITIAL") { 10387 throw new ParsingError(ParsingError.Errors.BadSignature); 10388 } 10389 } catch (e) { 10390 self.reportOrThrowError(e); 10391 } 10392 10393 self.onflush && self.onflush(); 10394 return this; 10395 } 10396 }; 10397 var vtt = WebVTT$1; 10398 10399 /** 10400 * Copyright 2013 vtt.js Contributors 10401 * 10402 * Licensed under the Apache License, Version 2.0 (the "License"); 10403 * you may not use this file except in compliance with the License. 10404 * You may obtain a copy of the License at 10405 * 10406 * http://www.apache.org/licenses/LICENSE-2.0 10407 * 10408 * Unless required by applicable law or agreed to in writing, software 10409 * distributed under the License is distributed on an "AS IS" BASIS, 10410 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10411 * See the License for the specific language governing permissions and 10412 * limitations under the License. 10413 */ 10414 var autoKeyword = "auto"; 10415 var directionSetting = { 10416 "": 1, 10417 "lr": 1, 10418 "rl": 1 10419 }; 10420 var alignSetting = { 10421 "start": 1, 10422 "center": 1, 10423 "end": 1, 10424 "left": 1, 10425 "right": 1, 10426 "auto": 1, 10427 "line-left": 1, 10428 "line-right": 1 10429 }; 10430 10431 function findDirectionSetting(value) { 10432 if (typeof value !== "string") { 10433 return false; 10434 } 10435 10436 var dir = directionSetting[value.toLowerCase()]; 10437 return dir ? value.toLowerCase() : false; 10438 } 10439 10440 function findAlignSetting(value) { 10441 if (typeof value !== "string") { 10442 return false; 10443 } 10444 10445 var align = alignSetting[value.toLowerCase()]; 10446 return align ? value.toLowerCase() : false; 10447 } 10448 10449 function VTTCue(startTime, endTime, text) { 10450 /** 10451 * Shim implementation specific properties. These properties are not in 10452 * the spec. 10453 */ 10454 // Lets us know when the VTTCue's data has changed in such a way that we need 10455 // to recompute its display state. This lets us compute its display state 10456 // lazily. 10457 this.hasBeenReset = false; 10458 /** 10459 * VTTCue and TextTrackCue properties 10460 * http://dev.w3.org/html5/webvtt/#vttcue-interface 10461 */ 10462 10463 var _id = ""; 10464 var _pauseOnExit = false; 10465 var _startTime = startTime; 10466 var _endTime = endTime; 10467 var _text = text; 10468 var _region = null; 10469 var _vertical = ""; 10470 var _snapToLines = true; 10471 var _line = "auto"; 10472 var _lineAlign = "start"; 10473 var _position = "auto"; 10474 var _positionAlign = "auto"; 10475 var _size = 100; 10476 var _align = "center"; 10477 Object.defineProperties(this, { 10478 "id": { 10479 enumerable: true, 10480 get: function get() { 10481 return _id; 10482 }, 10483 set: function set(value) { 10484 _id = "" + value; 10485 } 10486 }, 10487 "pauseOnExit": { 10488 enumerable: true, 10489 get: function get() { 10490 return _pauseOnExit; 10491 }, 10492 set: function set(value) { 10493 _pauseOnExit = !!value; 10494 } 10495 }, 10496 "startTime": { 10497 enumerable: true, 10498 get: function get() { 10499 return _startTime; 10500 }, 10501 set: function set(value) { 10502 if (typeof value !== "number") { 10503 throw new TypeError("Start time must be set to a number."); 10504 } 10505 10506 _startTime = value; 10507 this.hasBeenReset = true; 10508 } 10509 }, 10510 "endTime": { 10511 enumerable: true, 10512 get: function get() { 10513 return _endTime; 10514 }, 10515 set: function set(value) { 10516 if (typeof value !== "number") { 10517 throw new TypeError("End time must be set to a number."); 10518 } 10519 10520 _endTime = value; 10521 this.hasBeenReset = true; 10522 } 10523 }, 10524 "text": { 10525 enumerable: true, 10526 get: function get() { 10527 return _text; 10528 }, 10529 set: function set(value) { 10530 _text = "" + value; 10531 this.hasBeenReset = true; 10532 } 10533 }, 10534 "region": { 10535 enumerable: true, 10536 get: function get() { 10537 return _region; 10538 }, 10539 set: function set(value) { 10540 _region = value; 10541 this.hasBeenReset = true; 10542 } 10543 }, 10544 "vertical": { 10545 enumerable: true, 10546 get: function get() { 10547 return _vertical; 10548 }, 10549 set: function set(value) { 10550 var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. 10551 10552 if (setting === false) {
10553 throw new SyntaxError("Vertical: an invalid or illegal direction string was specified."); 10554 } 10555 10556 _vertical = setting; 10557 this.hasBeenReset = true; 10558 } 10559 }, 10560 "snapToLines": { 10561 enumerable: true, 10562 get: function get() { 10563 return _snapToLines; 10564 }, 10565 set: function set(value) { 10566 _snapToLines = !!value; 10567 this.hasBeenReset = true; 10568 } 10569 }, 10570 "line": { 10571 enumerable: true, 10572 get: function get() { 10573 return _line; 10574 }, 10575 set: function set(value) { 10576 if (typeof value !== "number" && value !== autoKeyword) { 10577 throw new SyntaxError("Line: an invalid number or illegal string was specified."); 10578 } 10579 10580 _line = value; 10581 this.hasBeenReset = true; 10582 } 10583 }, 10584 "lineAlign": { 10585 enumerable: true, 10586 get: function get() { 10587 return _lineAlign; 10588 }, 10589 set: function set(value) { 10590 var setting = findAlignSetting(value); 10591 10592 if (!setting) { 10593 console.warn("lineAlign: an invalid or illegal string was specified."); 10594 } else { 10595 _lineAlign = setting; 10596 this.hasBeenReset = true; 10597 } 10598 } 10599 }, 10600 "position": { 10601 enumerable: true, 10602 get: function get() { 10603 return _position; 10604 }, 10605 set: function set(value) { 10606 if (value < 0 || value > 100) { 10607 throw new Error("Position must be between 0 and 100."); 10608 } 10609 10610 _position = value; 10611 this.hasBeenReset = true; 10612 } 10613 }, 10614 "positionAlign": { 10615 enumerable: true, 10616 get: function get() { 10617 return _positionAlign; 10618 }, 10619 set: function set(value) { 10620 var setting = findAlignSetting(value); 10621 10622 if (!setting) { 10623 console.warn("positionAlign: an invalid or illegal string was specified."); 10624 } else { 10625 _positionAlign = setting; 10626 this.hasBeenReset = true; 10627 } 10628 } 10629 }, 10630 "size": { 10631 enumerable: true, 10632 get: function get() { 10633 return _size; 10634 }, 10635 set: function set(value) { 10636 if (value < 0 || value > 100) { 10637 throw new Error("Size must be between 0 and 100."); 10638 } 10639 10640 _size = value; 10641 this.hasBeenReset = true; 10642 } 10643 }, 10644 "align": { 10645 enumerable: true, 10646 get: function get() { 10647 return _align; 10648 }, 10649 set: function set(value) { 10650 var setting = findAlignSetting(value); 10651 10652 if (!setting) { 10653 throw new SyntaxError("align: an invalid or illegal alignment string was specified."); 10654 } 10655 10656 _align = setting; 10657 this.hasBeenReset = true; 10658 } 10659 } 10660 }); 10661 /** 10662 * Other <track> spec defined properties 10663 */ 10664 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state 10665 10666 this.displayState = undefined; 10667 } 10668 /** 10669 * VTTCue methods 10670 */ 10671 10672 10673 VTTCue.prototype.getCueAsHTML = function () { 10674 // Assume WebVTT.convertCueToDOMTree is on the global. 10675 return WebVTT.convertCueToDOMTree(window, this.text); 10676 }; 10677 10678 var vttcue = VTTCue; 10679 10680 /** 10681 * Copyright 2013 vtt.js Contributors 10682 * 10683 * Licensed under the Apache License, Version 2.0 (the "License"); 10684 * you may not use this file except in compliance with the License. 10685 * You may obtain a copy of the License at 10686 * 10687 * http://www.apache.org/licenses/LICENSE-2.0 10688 * 10689 * Unless required by applicable law or agreed to in writing, software 10690 * distributed under the License is distributed on an "AS IS" BASIS, 10691 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10692 * See the License for the specific language governing permissions and 10693 * limitations under the License. 10694 */ 10695 var scrollSetting = { 10696 "": true, 10697 "up": true 10698 }; 10699 10700 function findScrollSetting(value) { 10701 if (typeof value !== "string") { 10702 return false; 10703 } 10704 10705 var scroll = scrollSetting[value.toLowerCase()]; 10706 return scroll ? value.toLowerCase() : false; 10707 } 10708 10709 function isValidPercentValue(value) { 10710 return typeof value === "number" && value >= 0 && value <= 100; 10711 } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 10712 10713 10714 function VTTRegion() { 10715 var _width = 100; 10716 var _lines = 3; 10717 var _regionAnchorX = 0; 10718 var _regionAnchorY = 100; 10719 var _viewportAnchorX = 0; 10720 var _viewportAnchorY = 100; 10721 var _scroll = ""; 10722 Object.defineProperties(this, { 10723 "width": { 10724 enumerable: true, 10725 get: function get() { 10726 return _width; 10727 }, 10728 set: function set(value) { 10729 if (!isValidPercentValue(value)) { 10730 throw new Error("Width must be between 0 and 100."); 10731 } 10732 10733 _width = value; 10734 } 10735 }, 10736 "lines": { 10737 enumerable: true, 10738 get: function get() { 10739 return _lines; 10740 }, 10741 set: function set(value) {
10742 if (typeof value !== "number") { 10743 throw new TypeError("Lines must be set to a number."); 10744 } 10745 10746 _lines = value; 10747 } 10748 }, 10749 "regionAnchorY": { 10750 enumerable: true, 10751 get: function get() { 10752 return _regionAnchorY; 10753 }, 10754 set: function set(value) { 10755 if (!isValidPercentValue(value)) { 10756 throw new Error("RegionAnchorX must be between 0 and 100."); 10757 } 10758 10759 _regionAnchorY = value; 10760 } 10761 }, 10762 "regionAnchorX": { 10763 enumerable: true, 10764 get: function get() { 10765 return _regionAnchorX; 10766 }, 10767 set: function set(value) { 10768 if (!isValidPercentValue(value)) { 10769 throw new Error("RegionAnchorY must be between 0 and 100."); 10770 } 10771 10772 _regionAnchorX = value; 10773 } 10774 }, 10775 "viewportAnchorY": { 10776 enumerable: true, 10777 get: function get() { 10778 return _viewportAnchorY; 10779 }, 10780 set: function set(value) { 10781 if (!isValidPercentValue(value)) { 10782 throw new Error("ViewportAnchorY must be between 0 and 100."); 10783 } 10784 10785 _viewportAnchorY = value; 10786 } 10787 }, 10788 "viewportAnchorX": { 10789 enumerable: true, 10790 get: function get() { 10791 return _viewportAnchorX; 10792 }, 10793 set: function set(value) { 10794 if (!isValidPercentValue(value)) { 10795 throw new Error("ViewportAnchorX must be between 0 and 100."); 10796 } 10797 10798 _viewportAnchorX = value; 10799 } 10800 }, 10801 "scroll": { 10802 enumerable: true, 10803 get: function get() { 10804 return _scroll; 10805 }, 10806 set: function set(value) { 10807 var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value. 10808 10809 if (setting === false) { 10810 console.warn("Scroll: an invalid or illegal string was specified."); 10811 } else { 10812 _scroll = setting; 10813 } 10814 } 10815 } 10816 }); 10817 } 10818 10819 var vttregion = VTTRegion; 10820 10821 var browserIndex = createCommonjsModule(function (module) { 10822 /** 10823 * Copyright 2013 vtt.js Contributors 10824 * 10825 * Licensed under the Apache License, Version 2.0 (the "License"); 10826 * you may not use this file except in compliance with the License. 10827 * You may obtain a copy of the License at 10828 * 10829 * http://www.apache.org/licenses/LICENSE-2.0 10830 * 10831 * Unless required by applicable law or agreed to in writing, software 10832 * distributed under the License is distributed on an "AS IS" BASIS, 10833 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10834 * See the License for the specific language governing permissions and 10835 * limitations under the License. 10836 */ 10837 // Default exports for Node. Export the extended versions of VTTCue and 10838 // VTTRegion in Node since we likely want the capability to convert back and 10839 // forth between JSON. If we don't then it's not that big of a deal since we're 10840 // off browser. 10841 var vttjs = module.exports = { 10842 WebVTT: vtt, 10843 VTTCue: vttcue, 10844 VTTRegion: vttregion 10845 }; 10846 window_1.vttjs = vttjs; 10847 window_1.WebVTT = vttjs.WebVTT; 10848 var cueShim = vttjs.VTTCue; 10849 var regionShim = vttjs.VTTRegion; 10850 var nativeVTTCue = window_1.VTTCue; 10851 var nativeVTTRegion = window_1.VTTRegion; 10852 10853 vttjs.shim = function () { 10854 window_1.VTTCue = cueShim; 10855 window_1.VTTRegion = regionShim; 10856 }; 10857 10858 vttjs.restore = function () { 10859 window_1.VTTCue = nativeVTTCue; 10860 window_1.VTTRegion = nativeVTTRegion; 10861 }; 10862 10863 if (!window_1.VTTCue) { 10864 vttjs.shim(); 10865 } 10866 }); 10867 browserIndex.WebVTT; 10868 browserIndex.VTTCue; 10869 browserIndex.VTTRegion; 10870 10871 /** 10872 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string 10873 * that just contains the src url alone. 10874 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` 10875 * `var SourceString = 'http://example.com/some-video.mp4';` 10876 * 10877 * @typedef {Object|string} Tech~SourceObject 10878 * 10879 * @property {string} src 10880 * The url to the source 10881 * 10882 * @property {string} type 10883 * The mime type of the source 10884 */ 10885 10886 /** 10887 * A function used by {@link Tech} to create a new {@link TextTrack}. 10888 * 10889 * @private 10890 * 10891 * @param {Tech} self 10892 * An instance of the Tech class. 10893 * 10894 * @param {string} kind 10895 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10896 * 10897 * @param {string} [label] 10898 * Label to identify the text track 10899 * 10900 * @param {string} [language] 10901 * Two letter language abbreviation 10902 * 10903 * @param {Object} [options={}] 10904 * An object with additional text track options 10905 * 10906 * @return {TextTrack} 10907 * The text track that was created. 10908 */ 10909 10910 function createTrackHelper(self, kind, label, language, options) { 10911 if (options === void 0) { 10912 options = {}; 10913 } 10914 10915 var tracks = self.textTracks(); 10916 options.kind = kind; 10917 10918 if (label) { 10919 options.label = label; 10920 } 10921 10922 if (language) { 10923 options.language = language; 10924 } 10925 10926 options.tech = self; 10927 var track = new ALL.text.TrackClass(options); 10928 tracks.addTrack(track); 10929 return track; 10930 } 10931 /** 10932 * This is the base class for media playback technology controllers, such as 10933 * {@link HTML5} 10934 * 10935 * @extends Component 10936 */ 10937 10938 10939 var Tech = /*#__PURE__*/function (_Component) { 10940 inheritsLoose(Tech, _Component); 10941 10942 /** 10943 * Create an instance of this Tech. 10944 * 10945 * @param {Object} [options] 10946 * The key/value store of player options. 10947 * 10948 * @param {Component~ReadyCallback} ready 10949 * Callback function to call when the `HTML5` Tech is ready. 10950 */ 10951 function Tech(options, ready) { 10952 var _this; 10953 10954 if (options === void 0) { 10955 options = {}; 10956 } 10957 10958 if (ready === void 0) { 10959 ready = function ready() {}; 10960 } 10961 10962 // we don't want the tech to report user activity automatically.
vendor: 10,830 bytes, lines 10963-11308
10963 // This is done manually in addControlsListeners 10964 options.reportTouchActivity = false; 10965 _this = _Component.call(this, null, options, ready) || this; 10966 10967 _this.onDurationChange_ = function (e) { 10968 return _this.onDurationChange(e); 10969 }; 10970 10971 _this.trackProgress_ = function (e) { 10972 return _this.trackProgress(e); 10973 }; 10974 10975 _this.trackCurrentTime_ = function (e) { 10976 return _this.trackCurrentTime(e); 10977 }; 10978 10979 _this.stopTrackingCurrentTime_ = function (e) { 10980 return _this.stopTrackingCurrentTime(e); 10981 }; 10982 10983 _this.disposeSourceHandler_ = function (e) { 10984 return _this.disposeSourceHandler(e); 10985 }; // keep track of whether the current source has played at all to 10986 // implement a very limited played() 10987 10988 10989 _this.hasStarted_ = false; 10990 10991 _this.on('playing', function () { 10992 this.hasStarted_ = true; 10993 }); 10994 10995 _this.on('loadstart', function () { 10996 this.hasStarted_ = false; 10997 }); 10998 10999 ALL.names.forEach(function (name) { 11000 var props = ALL[name]; 11001 11002 if (options && options[props.getterName]) { 11003 _this[props.privateName] = options[props.getterName]; 11004 } 11005 }); // Manually track progress in cases where the browser/tech doesn't report it. 11006 11007 if (!_this.featuresProgressEvents) { 11008 _this.manualProgressOn(); 11009 } // Manually track timeupdates in cases where the browser/tech doesn't report it. 11010 11011 11012 if (!_this.featuresTimeupdateEvents) { 11013 _this.manualTimeUpdatesOn(); 11014 } 11015 11016 ['Text', 'Audio', 'Video'].forEach(function (track) { 11017 if (options["native" + track + "Tracks"] === false) { 11018 _this["featuresNative" + track + "Tracks"] = false; 11019 } 11020 }); 11021 11022 if (options.nativeCaptions === false || options.nativeTextTracks === false) { 11023 _this.featuresNativeTextTracks = false; 11024 } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { 11025 _this.featuresNativeTextTracks = true; 11026 } 11027 11028 if (!_this.featuresNativeTextTracks) { 11029 _this.emulateTextTracks(); 11030 } 11031 11032 _this.preloadTextTracks = options.preloadTextTracks !== false; 11033 _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); 11034 11035 _this.initTrackListeners(); // Turn on component tap events only if not using native controls 11036 11037 11038 if (!options.nativeControlsForTouch) { 11039 _this.emitTapEvents(); 11040 } 11041 11042 if (_this.constructor) { 11043 _this.name_ = _this.constructor.name || 'Unknown Tech'; 11044 } 11045 11046 return _this; 11047 } 11048 /** 11049 * A special function to trigger source set in a way that will allow player 11050 * to re-trigger if the player or tech are not ready yet. 11051 * 11052 * @fires Tech#sourceset 11053 * @param {string} src The source string at the time of the source changing. 11054 */ 11055 11056 11057 var _proto = Tech.prototype; 11058 11059 _proto.triggerSourceset = function triggerSourceset(src) { 11060 var _this2 = this; 11061 11062 if (!this.isReady_) { 11063 // on initial ready we have to trigger source set 11064 // 1ms after ready so that player can watch for it. 11065 this.one('ready', function () { 11066 return _this2.setTimeout(function () { 11067 return _this2.triggerSourceset(src); 11068 }, 1); 11069 }); 11070 } 11071 /** 11072 * Fired when the source is set on the tech causing the media element 11073 * to reload. 11074 * 11075 * @see {@link Player#event:sourceset} 11076 * @event Tech#sourceset 11077 * @type {EventTarget~Event} 11078 */ 11079 11080 11081 this.trigger({ 11082 src: src, 11083 type: 'sourceset' 11084 }); 11085 } 11086 /* Fallbacks for unsupported event types 11087 ================================================================================ */ 11088 11089 /** 11090 * Polyfill the `progress` event for browsers that don't support it natively. 11091 * 11092 * @see {@link Tech#trackProgress} 11093 */ 11094 ; 11095 11096 _proto.manualProgressOn = function manualProgressOn() { 11097 this.on('durationchange', this.onDurationChange_); 11098 this.manualProgress = true; // Trigger progress watching when a source begins loading 11099 11100 this.one('ready', this.trackProgress_); 11101 } 11102 /** 11103 * Turn off the polyfill for `progress` events that was created in 11104 * {@link Tech#manualProgressOn} 11105 */ 11106 ; 11107 11108 _proto.manualProgressOff = function manualProgressOff() { 11109 this.manualProgress = false; 11110 this.stopTrackingProgress(); 11111 this.off('durationchange', this.onDurationChange_); 11112 } 11113 /** 11114 * This is used to trigger a `progress` event when the buffered percent changes. It 11115 * sets an interval function that will be called every 500 milliseconds to check if the 11116 * buffer end percent has changed. 11117 * 11118 * > This function is called by {@link Tech#manualProgressOn} 11119 * 11120 * @param {EventTarget~Event} event 11121 * The `ready` event that caused this to run. 11122 * 11123 * @listens Tech#ready 11124 * @fires Tech#progress 11125 */ 11126 ; 11127 11128 _proto.trackProgress = function trackProgress(event) { 11129 this.stopTrackingProgress(); 11130 this.progressInterval = this.setInterval(bind(this, function () { 11131 // Don't trigger unless buffered amount is greater than last time 11132 var numBufferedPercent = this.bufferedPercent(); 11133 11134 if (this.bufferedPercent_ !== numBufferedPercent) { 11135 /** 11136 * See {@link Player#progress} 11137 * 11138 * @event Tech#progress 11139 * @type {EventTarget~Event} 11140 */ 11141 this.trigger('progress'); 11142 } 11143 11144 this.bufferedPercent_ = numBufferedPercent; 11145 11146 if (numBufferedPercent === 1) { 11147 this.stopTrackingProgress(); 11148 } 11149 }), 500); 11150 } 11151 /** 11152 * Update our internal duration on a `durationchange` event by calling 11153 * {@link Tech#duration}. 11154 * 11155 * @param {EventTarget~Event} event 11156 * The `durationchange` event that caused this to run. 11157 * 11158 * @listens Tech#durationchange 11159 */ 11160 ; 11161 11162 _proto.onDurationChange = function onDurationChange(event) { 11163 this.duration_ = this.duration(); 11164 } 11165 /** 11166 * Get and create a `TimeRange` object for buffering. 11167 * 11168 * @return {TimeRange} 11169 * The time range object that was created. 11170 */ 11171 ; 11172 11173 _proto.buffered = function buffered() { 11174 return createTimeRanges(0, 0); 11175 } 11176 /** 11177 * Get the percentage of the current video that is currently buffered. 11178 * 11179 * @return {number} 11180 * A number from 0 to 1 that represents the decimal percentage of the 11181 * video that is buffered. 11182 * 11183 */ 11184 ; 11185 11186 _proto.bufferedPercent = function bufferedPercent$1() { 11187 return bufferedPercent(this.buffered(), this.duration_); 11188 } 11189 /** 11190 * Turn off the polyfill for `progress` events that was created in 11191 * {@link Tech#manualProgressOn} 11192 * Stop manually tracking progress events by clearing the interval that was set in 11193 * {@link Tech#trackProgress}. 11194 */ 11195 ; 11196 11197 _proto.stopTrackingProgress = function stopTrackingProgress() { 11198 this.clearInterval(this.progressInterval); 11199 } 11200 /** 11201 * Polyfill the `timeupdate` event for browsers that don't support it. 11202 * 11203 * @see {@link Tech#trackCurrentTime} 11204 */ 11205 ; 11206 11207 _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() { 11208 this.manualTimeUpdates = true; 11209 this.on('play', this.trackCurrentTime_); 11210 this.on('pause', this.stopTrackingCurrentTime_); 11211 } 11212 /** 11213 * Turn off the polyfill for `timeupdate` events that was created in 11214 * {@link Tech#manualTimeUpdatesOn} 11215 */ 11216 ; 11217 11218 _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() { 11219 this.manualTimeUpdates = false; 11220 this.stopTrackingCurrentTime(); 11221 this.off('play', this.trackCurrentTime_); 11222 this.off('pause', this.stopTrackingCurrentTime_); 11223 } 11224 /** 11225 * Sets up an interval function to track current time and trigger `timeupdate` every 11226 * 250 milliseconds. 11227 * 11228 * @listens Tech#play 11229 * @triggers Tech#timeupdate 11230 */ 11231 ; 11232 11233 _proto.trackCurrentTime = function trackCurrentTime() { 11234 if (this.currentTimeInterval) { 11235 this.stopTrackingCurrentTime(); 11236 } 11237 11238 this.currentTimeInterval = this.setInterval(function () { 11239 /** 11240 * Triggered at an interval of 250ms to indicated that time is passing in the video. 11241 * 11242 * @event Tech#timeupdate 11243 * @type {EventTarget~Event} 11244 */ 11245 this.trigger({ 11246 type: 'timeupdate', 11247 target: this, 11248 manuallyTriggered: true 11249 }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 11250 }, 250); 11251 } 11252 /** 11253 * Stop the interval function created in {@link Tech#trackCurrentTime} so that the 11254 * `timeupdate` event is no longer triggered. 11255 * 11256 * @listens {Tech#pause} 11257 */ 11258 ; 11259 11260 _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() { 11261 this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen, 11262 // the progress bar won't make it all the way to the end 11263 11264 this.trigger({ 11265 type: 'timeupdate', 11266 target: this, 11267 manuallyTriggered: true 11268 }); 11269 } 11270 /** 11271 * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, 11272 * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. 11273 * 11274 * @fires Component#dispose 11275 */ 11276 ; 11277 11278 _proto.dispose = function dispose() { 11279 // clear out all tracks because we can't reuse them between techs 11280 this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking 11281 11282 if (this.manualProgress) { 11283 this.manualProgressOff(); 11284 } 11285 11286 if (this.manualTimeUpdates) { 11287 this.manualTimeUpdatesOff(); 11288 } 11289 11290 _Component.prototype.dispose.call(this); 11291 } 11292 /** 11293 * Clear out a single `TrackList` or an array of `TrackLists` given their names. 11294 * 11295 * > Note: Techs without source handlers should call this between sources for `video` 11296 * & `audio` tracks. You don't want to use them between tracks! 11297 * 11298 * @param {string[]|string} types 11299 * TrackList names to clear, valid names are `video`, `audio`, and 11300 * `text`. 11301 */ 11302 ; 11303 11304 _proto.clearTracks = function clearTracks(types) { 11305 var _this3 = this; 11306 11307 types = [].concat(types); // clear out all tracks because we can't reuse them between techs 11308
11309 types.forEach(function (type) { 11310 var list = _this3[type + "Tracks"]() || []; 11311 var i = list.length; 11312 11313 while (i--) { 11314 var track = list[i]; 11315 11316 if (type === 'text') { 11317 _this3.removeRemoteTextTrack(track); 11318 } 11319 11320 list.removeTrack(track); 11321 } 11322 }); 11323 } 11324 /** 11325 * Remove any TextTracks added via addRemoteTextTrack that are 11326 * flagged for automatic garbage collection 11327 */ 11328 ; 11329 11330 _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() { 11331 var list = this.autoRemoteTextTracks_ || []; 11332 var i = list.length; 11333 11334 while (i--) { 11335 var track = list[i]; 11336 this.removeRemoteTextTrack(track); 11337 } 11338 } 11339 /** 11340 * Reset the tech, which will removes all sources and reset the internal readyState. 11341 * 11342 * @abstract 11343 */ 11344 ; 11345 11346 _proto.reset = function reset() {} 11347 /** 11348 * Get the value of `crossOrigin` from the tech. 11349 * 11350 * @abstract 11351 * 11352 * @see {Html5#crossOrigin} 11353 */ 11354 ; 11355 11356 _proto.crossOrigin = function crossOrigin() {} 11357 /** 11358 * Set the value of `crossOrigin` on the tech. 11359 * 11360 * @abstract 11361 * 11362 * @param {string} crossOrigin the crossOrigin value 11363 * @see {Html5#setCrossOrigin} 11364 */ 11365 ; 11366 11367 _proto.setCrossOrigin = function setCrossOrigin() {} 11368 /** 11369 * Get or set an error on the Tech. 11370 * 11371 * @param {MediaError} [err] 11372 * Error to set on the Tech 11373 * 11374 * @return {MediaError|null} 11375 * The current error object on the tech, or null if there isn't one. 11376 */ 11377 ; 11378 11379 _proto.error = function error(err) { 11380 if (err !== undefined) { 11381 this.error_ = new MediaError(err); 11382 this.trigger('error'); 11383 } 11384 11385 return this.error_; 11386 } 11387 /** 11388 * Returns the `TimeRange`s that have been played through for the current source. 11389 * 11390 * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. 11391 * It only checks whether the source has played at all or not. 11392 * 11393 * @return {TimeRange} 11394 * - A single time range if this video has played 11395 * - An empty set of ranges if not. 11396 */ 11397 ; 11398 11399 _proto.played = function played() { 11400 if (this.hasStarted_) { 11401 return createTimeRanges(0, 0); 11402 } 11403 11404 return createTimeRanges(); 11405 } 11406 /** 11407 * Start playback 11408 * 11409 * @abstract 11410 * 11411 * @see {Html5#play} 11412 */ 11413 ; 11414 11415 _proto.play = function play() {} 11416 /** 11417 * Set whether we are scrubbing or not 11418 * 11419 * @abstract 11420 * 11421 * @see {Html5#setScrubbing} 11422 */ 11423 ; 11424 11425 _proto.setScrubbing = function setScrubbing() {} 11426 /** 11427 * Get whether we are scrubbing or not 11428 * 11429 * @abstract 11430 * 11431 * @see {Html5#scrubbing} 11432 */ 11433 ; 11434 11435 _proto.scrubbing = function scrubbing() {} 11436 /** 11437 * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was 11438 * previously called. 11439 * 11440 * @fires Tech#timeupdate 11441 */ 11442 ; 11443 11444 _proto.setCurrentTime = function setCurrentTime() { 11445 // improve the accuracy of manual timeupdates 11446 if (this.manualTimeUpdates) { 11447 /** 11448 * A manual `timeupdate` event. 11449 * 11450 * @event Tech#timeupdate 11451 * @type {EventTarget~Event} 11452 */ 11453 this.trigger({ 11454 type: 'timeupdate', 11455 target: this, 11456 manuallyTriggered: true 11457 }); 11458 } 11459 } 11460 /** 11461 * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and 11462 * {@link TextTrackList} events. 11463 * 11464 * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. 11465 * 11466 * @fires Tech#audiotrackchange 11467 * @fires Tech#videotrackchange 11468 * @fires Tech#texttrackchange 11469 */ 11470 ; 11471 11472 _proto.initTrackListeners = function initTrackListeners() { 11473 var _this4 = this; 11474 11475 /** 11476 * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} 11477 * 11478 * @event Tech#audiotrackchange 11479 * @type {EventTarget~Event} 11480 */ 11481 11482 /** 11483 * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} 11484 * 11485 * @event Tech#videotrackchange 11486 * @type {EventTarget~Event} 11487 */ 11488 11489 /** 11490 * Triggered when tracks are added or removed on the Tech {@link TextTrackList} 11491 * 11492 * @event Tech#texttrackchange 11493 * @type {EventTarget~Event} 11494 */
vendor: 30,070 bytes, lines 11495-12501
11495 NORMAL.names.forEach(function (name) { 11496 var props = NORMAL[name]; 11497 11498 var trackListChanges = function trackListChanges() { 11499 _this4.trigger(name + "trackchange"); 11500 }; 11501 11502 var tracks = _this4[props.getterName](); 11503 11504 tracks.addEventListener('removetrack', trackListChanges); 11505 tracks.addEventListener('addtrack', trackListChanges); 11506 11507 _this4.on('dispose', function () { 11508 tracks.removeEventListener('removetrack', trackListChanges); 11509 tracks.removeEventListener('addtrack', trackListChanges); 11510 }); 11511 }); 11512 } 11513 /** 11514 * Emulate TextTracks using vtt.js if necessary 11515 * 11516 * @fires Tech#vttjsloaded 11517 * @fires Tech#vttjserror 11518 */ 11519 ; 11520 11521 _proto.addWebVttScript_ = function addWebVttScript_() { 11522 var _this5 = this; 11523 11524 if (window_1.WebVTT) { 11525 return; 11526 } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system 11527 // signals that the Tech is ready at which point Tech.el_ is part of the DOM 11528 // before inserting the WebVTT script 11529 11530 11531 if (document_1.body.contains(this.el())) { 11532 // load via require if available and vtt.js script location was not passed in 11533 // as an option. novtt builds will turn the above require call into an empty object 11534 // which will cause this if check to always fail. 11535 if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { 11536 this.trigger('vttjsloaded'); 11537 return; 11538 } // load vtt.js via the script location option or the cdn of no location was 11539 // passed in 11540 11541 11542 var script = document_1.createElement('script'); 11543 script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; 11544 11545 script.onload = function () { 11546 /** 11547 * Fired when vtt.js is loaded. 11548 * 11549 * @event Tech#vttjsloaded 11550 * @type {EventTarget~Event} 11551 */ 11552 _this5.trigger('vttjsloaded'); 11553 }; 11554 11555 script.onerror = function () { 11556 /** 11557 * Fired when vtt.js was not loaded due to an error 11558 * 11559 * @event Tech#vttjsloaded 11560 * @type {EventTarget~Event} 11561 */ 11562 _this5.trigger('vttjserror'); 11563 }; 11564 11565 this.on('dispose', function () { 11566 script.onload = null; 11567 script.onerror = null; 11568 }); // but have not loaded yet and we set it to true before the inject so that 11569 // we don't overwrite the injected window.WebVTT if it loads right away 11570 11571 window_1.WebVTT = true; 11572 this.el().parentNode.appendChild(script); 11573 } else { 11574 this.ready(this.addWebVttScript_); 11575 } 11576 } 11577 /** 11578 * Emulate texttracks 11579 * 11580 */ 11581 ; 11582 11583 _proto.emulateTextTracks = function emulateTextTracks() { 11584 var _this6 = this; 11585 11586 var tracks = this.textTracks(); 11587 var remoteTracks = this.remoteTextTracks(); 11588 11589 var handleAddTrack = function handleAddTrack(e) { 11590 return tracks.addTrack(e.track); 11591 }; 11592 11593 var handleRemoveTrack = function handleRemoveTrack(e) { 11594 return tracks.removeTrack(e.track); 11595 }; 11596 11597 remoteTracks.on('addtrack', handleAddTrack); 11598 remoteTracks.on('removetrack', handleRemoveTrack); 11599 this.addWebVttScript_(); 11600 11601 var updateDisplay = function updateDisplay() { 11602 return _this6.trigger('texttrackchange'); 11603 }; 11604 11605 var textTracksChanges = function textTracksChanges() { 11606 updateDisplay(); 11607 11608 for (var i = 0; i < tracks.length; i++) { 11609 var track = tracks[i]; 11610 track.removeEventListener('cuechange', updateDisplay); 11611 11612 if (track.mode === 'showing') { 11613 track.addEventListener('cuechange', updateDisplay); 11614 } 11615 } 11616 }; 11617 11618 textTracksChanges(); 11619 tracks.addEventListener('change', textTracksChanges); 11620 tracks.addEventListener('addtrack', textTracksChanges); 11621 tracks.addEventListener('removetrack', textTracksChanges); 11622 this.on('dispose', function () { 11623 remoteTracks.off('addtrack', handleAddTrack); 11624 remoteTracks.off('removetrack', handleRemoveTrack); 11625 tracks.removeEventListener('change', textTracksChanges); 11626 tracks.removeEventListener('addtrack', textTracksChanges); 11627 tracks.removeEventListener('removetrack', textTracksChanges); 11628 11629 for (var i = 0; i < tracks.length; i++) { 11630 var track = tracks[i]; 11631 track.removeEventListener('cuechange', updateDisplay); 11632 } 11633 }); 11634 } 11635 /** 11636 * Create and returns a remote {@link TextTrack} object. 11637 * 11638 * @param {string} kind 11639 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 11640 * 11641 * @param {string} [label] 11642 * Label to identify the text track 11643 * 11644 * @param {string} [language] 11645 * Two letter language abbreviation 11646 * 11647 * @return {TextTrack} 11648 * The TextTrack that gets created. 11649 */ 11650 ; 11651 11652 _proto.addTextTrack = function addTextTrack(kind, label, language) { 11653 if (!kind) { 11654 throw new Error('TextTrack kind is required but was not provided'); 11655 } 11656 11657 return createTrackHelper(this, kind, label, language); 11658 } 11659 /** 11660 * Create an emulated TextTrack for use by addRemoteTextTrack 11661 * 11662 * This is intended to be overridden by classes that inherit from 11663 * Tech in order to create native or custom TextTracks. 11664 * 11665 * @param {Object} options 11666 * The object should contain the options to initialize the TextTrack with. 11667 * 11668 * @param {string} [options.kind] 11669 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 11670 * 11671 * @param {string} [options.label]. 11672 * Label to identify the text track 11673 * 11674 * @param {string} [options.language] 11675 * Two letter language abbreviation. 11676 * 11677 * @return {HTMLTrackElement} 11678 * The track element that gets created. 11679 */ 11680 ; 11681 11682 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 11683 var track = mergeOptions$3(options, { 11684 tech: this 11685 }); 11686 return new REMOTE.remoteTextEl.TrackClass(track); 11687 } 11688 /** 11689 * Creates a remote text track object and returns an html track element. 11690 * 11691 * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. 11692 * 11693 * @param {Object} options 11694 * See {@link Tech#createRemoteTextTrack} for more detailed properties. 11695 * 11696 * @param {boolean} [manualCleanup=true] 11697 * - When false: the TextTrack will be automatically removed from the video 11698 * element whenever the source changes 11699 * - When True: The TextTrack will have to be cleaned up manually 11700 * 11701 * @return {HTMLTrackElement} 11702 * An Html Track Element. 11703 * 11704 * @deprecated The default functionality for this function will be equivalent 11705 * to "manualCleanup=false" in the future. The manualCleanup parameter will 11706 * also be removed. 11707 */ 11708 ; 11709 11710 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 11711 var _this7 = this; 11712 11713 if (options === void 0) { 11714 options = {}; 11715 } 11716 11717 var htmlTrackElement = this.createRemoteTextTrack(options); 11718 11719 if (manualCleanup !== true && manualCleanup !== false) { 11720 // deprecation warning 11721 log$1.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); 11722 manualCleanup = true; 11723 } // store HTMLTrackElement and TextTrack to remote list 11724 11725 11726 this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); 11727 this.remoteTextTracks().addTrack(htmlTrackElement.track); 11728 11729 if (manualCleanup !== true) { 11730 // create the TextTrackList if it doesn't exist 11731 this.ready(function () { 11732 return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); 11733 }); 11734 } 11735 11736 return htmlTrackElement; 11737 } 11738 /** 11739 * Remove a remote text track from the remote `TextTrackList`. 11740 * 11741 * @param {TextTrack} track 11742 * `TextTrack` to remove from the `TextTrackList` 11743 */ 11744 ; 11745 11746 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 11747 var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list 11748 11749 this.remoteTextTrackEls().removeTrackElement_(trackElement); 11750 this.remoteTextTracks().removeTrack(track); 11751 this.autoRemoteTextTracks_.removeTrack(track); 11752 } 11753 /** 11754 * Gets available media playback quality metrics as specified by the W3C's Media 11755 * Playback Quality API. 11756 * 11757 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 11758 * 11759 * @return {Object} 11760 * An object with supported media playback quality metrics 11761 * 11762 * @abstract 11763 */ 11764 ; 11765 11766 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 11767 return {}; 11768 } 11769 /** 11770 * Attempt to create a floating video window always on top of other windows 11771 * so that users may continue consuming media while they interact with other 11772 * content sites, or applications on their device. 11773 * 11774 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 11775 * 11776 * @return {Promise|undefined} 11777 * A promise with a Picture-in-Picture window if the browser supports 11778 * Promises (or one was passed in as an option). It returns undefined 11779 * otherwise. 11780 * 11781 * @abstract 11782 */ 11783 ; 11784 11785 _proto.requestPictureInPicture = function requestPictureInPicture() { 11786 var PromiseClass = this.options_.Promise || window_1.Promise; 11787 11788 if (PromiseClass) { 11789 return PromiseClass.reject(); 11790 } 11791 } 11792 /** 11793 * A method to check for the value of the 'disablePictureInPicture' <video> property. 11794 * Defaults to true, as it should be considered disabled if the tech does not support pip 11795 * 11796 * @abstract 11797 */ 11798 ; 11799 11800 _proto.disablePictureInPicture = function disablePictureInPicture() { 11801 return true; 11802 } 11803 /** 11804 * A method to set or unset the 'disablePictureInPicture' <video> property. 11805 * 11806 * @abstract 11807 */ 11808 ; 11809 11810 _proto.setDisablePictureInPicture = function setDisablePictureInPicture() {} 11811 /** 11812 * A method to set a poster from a `Tech`. 11813 * 11814 * @abstract 11815 */ 11816 ; 11817 11818 _proto.setPoster = function setPoster() {} 11819 /** 11820 * A method to check for the presence of the 'playsinline' <video> attribute. 11821 * 11822 * @abstract 11823 */ 11824 ; 11825 11826 _proto.playsinline = function playsinline() {} 11827 /** 11828 * A method to set or unset the 'playsinline' <video> attribute. 11829 * 11830 * @abstract 11831 */ 11832 ; 11833 11834 _proto.setPlaysinline = function setPlaysinline() {} 11835 /** 11836 * Attempt to force override of native audio tracks. 11837 * 11838 * @param {boolean} override - If set to true native audio will be overridden, 11839 * otherwise native audio will potentially be used. 11840 * 11841 * @abstract 11842 */ 11843 ; 11844 11845 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {} 11846 /** 11847 * Attempt to force override of native video tracks. 11848 * 11849 * @param {boolean} override - If set to true native video will be overridden, 11850 * otherwise native video will potentially be used. 11851 * 11852 * @abstract 11853 */ 11854 ; 11855 11856 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {} 11857 /* 11858 * Check if the tech can support the given mime-type. 11859 * 11860 * The base tech does not support any type, but source handlers might 11861 * overwrite this. 11862 * 11863 * @param {string} type 11864 * The mimetype to check for support 11865 * 11866 * @return {string} 11867 * 'probably', 'maybe', or empty string 11868 * 11869 * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType} 11870 * 11871 * @abstract 11872 */ 11873 ; 11874 11875 _proto.canPlayType = function canPlayType() { 11876 return ''; 11877 } 11878 /** 11879 * Check if the type is supported by this tech. 11880 * 11881 * The base tech does not support any type, but source handlers might 11882 * overwrite this. 11883 * 11884 * @param {string} type 11885 * The media type to check 11886 * @return {string} Returns the native video element's response 11887 */ 11888 ; 11889 11890 Tech.canPlayType = function canPlayType() { 11891 return ''; 11892 } 11893 /** 11894 * Check if the tech can support the given source 11895 * 11896 * @param {Object} srcObj 11897 * The source object 11898 * @param {Object} options 11899 * The options passed to the tech 11900 * @return {string} 'probably', 'maybe', or '' (empty string) 11901 */ 11902 ; 11903 11904 Tech.canPlaySource = function canPlaySource(srcObj, options) { 11905 return Tech.canPlayType(srcObj.type); 11906 } 11907 /* 11908 * Return whether the argument is a Tech or not. 11909 * Can be passed either a Class like `Html5` or a instance like `player.tech_` 11910 * 11911 * @param {Object} component 11912 * The item to check 11913 * 11914 * @return {boolean} 11915 * Whether it is a tech or not 11916 * - True if it is a tech 11917 * - False if it is not 11918 */ 11919 ; 11920 11921 Tech.isTech = function isTech(component) { 11922 return component.prototype instanceof Tech || component instanceof Tech || component === Tech; 11923 } 11924 /** 11925 * Registers a `Tech` into a shared list for videojs. 11926 * 11927 * @param {string} name 11928 * Name of the `Tech` to register. 11929 * 11930 * @param {Object} tech 11931 * The `Tech` class to register. 11932 */ 11933 ; 11934 11935 Tech.registerTech = function registerTech(name, tech) { 11936 if (!Tech.techs_) { 11937 Tech.techs_ = {}; 11938 } 11939 11940 if (!Tech.isTech(tech)) { 11941 throw new Error("Tech " + name + " must be a Tech"); 11942 } 11943 11944 if (!Tech.canPlayType) { 11945 throw new Error('Techs must have a static canPlayType method on them'); 11946 } 11947 11948 if (!Tech.canPlaySource) { 11949 throw new Error('Techs must have a static canPlaySource method on them'); 11950 } 11951 11952 name = toTitleCase$1(name); 11953 Tech.techs_[name] = tech; 11954 Tech.techs_[toLowerCase(name)] = tech; 11955 11956 if (name !== 'Tech') { 11957 // camel case the techName for use in techOrder 11958 Tech.defaultTechOrder_.push(name); 11959 } 11960 11961 return tech; 11962 } 11963 /** 11964 * Get a `Tech` from the shared list by name. 11965 * 11966 * @param {string} name 11967 * `camelCase` or `TitleCase` name of the Tech to get 11968 * 11969 * @return {Tech|undefined} 11970 * The `Tech` or undefined if there was no tech with the name requested. 11971 */ 11972 ; 11973 11974 Tech.getTech = function getTech(name) { 11975 if (!name) { 11976 return; 11977 } 11978 11979 if (Tech.techs_ && Tech.techs_[name]) { 11980 return Tech.techs_[name]; 11981 } 11982 11983 name = toTitleCase$1(name); 11984 11985 if (window_1 && window_1.videojs && window_1.videojs[name]) { 11986 log$1.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)"); 11987 return window_1.videojs[name]; 11988 } 11989 }; 11990 11991 return Tech; 11992 }(Component$1); 11993 /** 11994 * Get the {@link VideoTrackList} 11995 * 11996 * @returns {VideoTrackList} 11997 * @method Tech.prototype.videoTracks 11998 */ 11999 12000 /** 12001 * Get the {@link AudioTrackList} 12002 * 12003 * @returns {AudioTrackList} 12004 * @method Tech.prototype.audioTracks 12005 */ 12006 12007 /** 12008 * Get the {@link TextTrackList} 12009 * 12010 * @returns {TextTrackList} 12011 * @method Tech.prototype.textTracks 12012 */ 12013 12014 /** 12015 * Get the remote element {@link TextTrackList} 12016 * 12017 * @returns {TextTrackList} 12018 * @method Tech.prototype.remoteTextTracks 12019 */ 12020 12021 /** 12022 * Get the remote element {@link HtmlTrackElementList} 12023 * 12024 * @returns {HtmlTrackElementList} 12025 * @method Tech.prototype.remoteTextTrackEls 12026 */ 12027 12028 12029 ALL.names.forEach(function (name) { 12030 var props = ALL[name]; 12031 12032 Tech.prototype[props.getterName] = function () { 12033 this[props.privateName] = this[props.privateName] || new props.ListClass(); 12034 return this[props.privateName]; 12035 }; 12036 }); 12037 /** 12038 * List of associated text tracks 12039 * 12040 * @type {TextTrackList} 12041 * @private 12042 * @property Tech#textTracks_ 12043 */ 12044 12045 /** 12046 * List of associated audio tracks. 12047 * 12048 * @type {AudioTrackList} 12049 * @private 12050 * @property Tech#audioTracks_ 12051 */ 12052 12053 /** 12054 * List of associated video tracks. 12055 * 12056 * @type {VideoTrackList} 12057 * @private 12058 * @property Tech#videoTracks_ 12059 */ 12060 12061 /** 12062 * Boolean indicating whether the `Tech` supports volume control. 12063 * 12064 * @type {boolean} 12065 * @default 12066 */ 12067 12068 Tech.prototype.featuresVolumeControl = true; 12069 /** 12070 * Boolean indicating whether the `Tech` supports muting volume. 12071 * 12072 * @type {bolean} 12073 * @default 12074 */ 12075 12076 Tech.prototype.featuresMuteControl = true; 12077 /** 12078 * Boolean indicating whether the `Tech` supports fullscreen resize control. 12079 * Resizing plugins using request fullscreen reloads the plugin 12080 * 12081 * @type {boolean} 12082 * @default 12083 */ 12084 12085 Tech.prototype.featuresFullscreenResize = false; 12086 /** 12087 * Boolean indicating whether the `Tech` supports changing the speed at which the video 12088 * plays. Examples: 12089 * - Set player to play 2x (twice) as fast 12090 * - Set player to play 0.5x (half) as fast 12091 * 12092 * @type {boolean} 12093 * @default 12094 */ 12095 12096 Tech.prototype.featuresPlaybackRate = false; 12097 /** 12098 * Boolean indicating whether the `Tech` supports the `progress` event. This is currently 12099 * not triggered by video-js-swf. This will be used to determine if 12100 * {@link Tech#manualProgressOn} should be called. 12101 * 12102 * @type {boolean} 12103 * @default 12104 */ 12105 12106 Tech.prototype.featuresProgressEvents = false; 12107 /** 12108 * Boolean indicating whether the `Tech` supports the `sourceset` event. 12109 * 12110 * A tech should set this to `true` and then use {@link Tech#triggerSourceset} 12111 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting 12112 * a new source. 12113 * 12114 * @type {boolean} 12115 * @default 12116 */ 12117 12118 Tech.prototype.featuresSourceset = false; 12119 /** 12120 * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently 12121 * not triggered by video-js-swf. This will be used to determine if 12122 * {@link Tech#manualTimeUpdates} should be called. 12123 * 12124 * @type {boolean} 12125 * @default 12126 */ 12127 12128 Tech.prototype.featuresTimeupdateEvents = false; 12129 /** 12130 * Boolean indicating whether the `Tech` supports the native `TextTrack`s. 12131 * This will help us integrate with native `TextTrack`s if the browser supports them. 12132 * 12133 * @type {boolean} 12134 * @default 12135 */ 12136 12137 Tech.prototype.featuresNativeTextTracks = false; 12138 /** 12139 * A functional mixin for techs that want to use the Source Handler pattern. 12140 * Source handlers are scripts for handling specific formats. 12141 * The source handler pattern is used for adaptive formats (HLS, DASH) that 12142 * manually load video data and feed it into a Source Buffer (Media Source Extensions) 12143 * Example: `Tech.withSourceHandlers.call(MyTech);` 12144 * 12145 * @param {Tech} _Tech 12146 * The tech to add source handler functions to. 12147 * 12148 * @mixes Tech~SourceHandlerAdditions 12149 */ 12150 12151 Tech.withSourceHandlers = function (_Tech) { 12152 /** 12153 * Register a source handler 12154 * 12155 * @param {Function} handler 12156 * The source handler class 12157 * 12158 * @param {number} [index] 12159 * Register it at the following index 12160 */ 12161 _Tech.registerSourceHandler = function (handler, index) { 12162 var handlers = _Tech.sourceHandlers; 12163 12164 if (!handlers) { 12165 handlers = _Tech.sourceHandlers = []; 12166 } 12167 12168 if (index === undefined) { 12169 // add to the end of the list 12170 index = handlers.length; 12171 } 12172 12173 handlers.splice(index, 0, handler); 12174 }; 12175 /** 12176 * Check if the tech can support the given type. Also checks the 12177 * Techs sourceHandlers. 12178 * 12179 * @param {string} type 12180 * The mimetype to check. 12181 * 12182 * @return {string} 12183 * 'probably', 'maybe', or '' (empty string) 12184 */ 12185 12186 12187 _Tech.canPlayType = function (type) { 12188 var handlers = _Tech.sourceHandlers || []; 12189 var can; 12190 12191 for (var i = 0; i < handlers.length; i++) { 12192 can = handlers[i].canPlayType(type); 12193 12194 if (can) { 12195 return can; 12196 } 12197 } 12198 12199 return ''; 12200 }; 12201 /** 12202 * Returns the first source handler that supports the source. 12203 * 12204 * TODO: Answer question: should 'probably' be prioritized over 'maybe' 12205 * 12206 * @param {Tech~SourceObject} source 12207 * The source object 12208 * 12209 * @param {Object} options 12210 * The options passed to the tech 12211 * 12212 * @return {SourceHandler|null} 12213 * The first source handler that supports the source or null if 12214 * no SourceHandler supports the source 12215 */ 12216 12217 12218 _Tech.selectSourceHandler = function (source, options) { 12219 var handlers = _Tech.sourceHandlers || []; 12220 var can; 12221 12222 for (var i = 0; i < handlers.length; i++) { 12223 can = handlers[i].canHandleSource(source, options); 12224 12225 if (can) { 12226 return handlers[i]; 12227 } 12228 } 12229 12230 return null; 12231 }; 12232 /** 12233 * Check if the tech can support the given source. 12234 * 12235 * @param {Tech~SourceObject} srcObj 12236 * The source object 12237 * 12238 * @param {Object} options 12239 * The options passed to the tech 12240 * 12241 * @return {string} 12242 * 'probably', 'maybe', or '' (empty string) 12243 */ 12244 12245 12246 _Tech.canPlaySource = function (srcObj, options) { 12247 var sh = _Tech.selectSourceHandler(srcObj, options); 12248 12249 if (sh) { 12250 return sh.canHandleSource(srcObj, options); 12251 } 12252 12253 return ''; 12254 }; 12255 /** 12256 * When using a source handler, prefer its implementation of 12257 * any function normally provided by the tech. 12258 */ 12259 12260 12261 var deferrable = ['seekable', 'seeking', 'duration']; 12262 /** 12263 * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable 12264 * function if it exists, with a fallback to the Techs seekable function. 12265 * 12266 * @method _Tech.seekable 12267 */ 12268 12269 /** 12270 * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration 12271 * function if it exists, otherwise it will fallback to the techs duration function. 12272 * 12273 * @method _Tech.duration 12274 */ 12275 12276 deferrable.forEach(function (fnName) { 12277 var originalFn = this[fnName]; 12278 12279 if (typeof originalFn !== 'function') { 12280 return; 12281 } 12282 12283 this[fnName] = function () { 12284 if (this.sourceHandler_ && this.sourceHandler_[fnName]) { 12285 return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); 12286 } 12287 12288 return originalFn.apply(this, arguments); 12289 }; 12290 }, _Tech.prototype); 12291 /** 12292 * Create a function for setting the source using a source object 12293 * and source handlers. 12294 * Should never be called unless a source handler was found. 12295 * 12296 * @param {Tech~SourceObject} source 12297 * A source object with src and type keys 12298 */ 12299 12300 _Tech.prototype.setSource = function (source) { 12301 var sh = _Tech.selectSourceHandler(source, this.options_); 12302 12303 if (!sh) { 12304 // Fall back to a native source hander when unsupported sources are 12305 // deliberately set 12306 if (_Tech.nativeSourceHandler) { 12307 sh = _Tech.nativeSourceHandler; 12308 } else { 12309 log$1.error('No source handler found for the current source.'); 12310 } 12311 } // Dispose any existing source handler 12312 12313 12314 this.disposeSourceHandler(); 12315 this.off('dispose', this.disposeSourceHandler_); 12316 12317 if (sh !== _Tech.nativeSourceHandler) { 12318 this.currentSource_ = source; 12319 } 12320 12321 this.sourceHandler_ = sh.handleSource(source, this, this.options_); 12322 this.one('dispose', this.disposeSourceHandler_); 12323 }; 12324 /** 12325 * Clean up any existing SourceHandlers and listeners when the Tech is disposed. 12326 * 12327 * @listens Tech#dispose 12328 */ 12329 12330 12331 _Tech.prototype.disposeSourceHandler = function () { 12332 // if we have a source and get another one 12333 // then we are loading something new 12334 // than clear all of our current tracks 12335 if (this.currentSource_) { 12336 this.clearTracks(['audio', 'video']); 12337 this.currentSource_ = null; 12338 } // always clean up auto-text tracks 12339 12340 12341 this.cleanupAutoTextTracks(); 12342 12343 if (this.sourceHandler_) { 12344 if (this.sourceHandler_.dispose) { 12345 this.sourceHandler_.dispose(); 12346 } 12347 12348 this.sourceHandler_ = null; 12349 } 12350 }; 12351 }; // The base Tech class needs to be registered as a Component. It is the only 12352 // Tech that can be registered as a Component. 12353 12354 12355 Component$1.registerComponent('Tech', Tech); 12356 Tech.registerTech('Tech', Tech); 12357 /** 12358 * A list of techs that should be added to techOrder on Players 12359 * 12360 * @private 12361 */ 12362 12363 Tech.defaultTechOrder_ = []; 12364 12365 /** 12366 * @file middleware.js 12367 * @module middleware 12368 */ 12369 var middlewares = {}; 12370 var middlewareInstances = {}; 12371 var TERMINATOR = {}; 12372 /** 12373 * A middleware object is a plain JavaScript object that has methods that 12374 * match the {@link Tech} methods found in the lists of allowed 12375 * {@link module:middleware.allowedGetters|getters}, 12376 * {@link module:middleware.allowedSetters|setters}, and 12377 * {@link module:middleware.allowedMediators|mediators}. 12378 * 12379 * @typedef {Object} MiddlewareObject 12380 */ 12381 12382 /** 12383 * A middleware factory function that should return a 12384 * {@link module:middleware~MiddlewareObject|MiddlewareObject}. 12385 * 12386 * This factory will be called for each player when needed, with the player 12387 * passed in as an argument. 12388 * 12389 * @callback MiddlewareFactory 12390 * @param {Player} player 12391 * A Video.js player. 12392 */ 12393 12394 /** 12395 * Define a middleware that the player should use by way of a factory function 12396 * that returns a middleware object. 12397 * 12398 * @param {string} type 12399 * The MIME type to match or `"*"` for all MIME types. 12400 * 12401 * @param {MiddlewareFactory} middleware 12402 * A middleware factory function that will be executed for 12403 * matching types. 12404 */ 12405 12406 function use(type, middleware) { 12407 middlewares[type] = middlewares[type] || []; 12408 middlewares[type].push(middleware); 12409 } 12410 /** 12411 * Asynchronously sets a source using middleware by recursing through any 12412 * matching middlewares and calling `setSource` on each, passing along the 12413 * previous returned value each time. 12414 * 12415 * @param {Player} player 12416 * A {@link Player} instance. 12417 * 12418 * @param {Tech~SourceObject} src 12419 * A source object. 12420 * 12421 * @param {Function} 12422 * The next middleware to run. 12423 */ 12424 12425 function setSource(player, src, next) { 12426 player.setTimeout(function () { 12427 return setSourceHelper(src, middlewares[src.type], next, player); 12428 }, 1); 12429 } 12430 /** 12431 * When the tech is set, passes the tech to each middleware's `setTech` method. 12432 * 12433 * @param {Object[]} middleware 12434 * An array of middleware instances. 12435 * 12436 * @param {Tech} tech 12437 * A Video.js tech. 12438 */ 12439 12440 function setTech(middleware, tech) { 12441 middleware.forEach(function (mw) { 12442 return mw.setTech && mw.setTech(tech); 12443 }); 12444 } 12445 /** 12446 * Calls a getter on the tech first, through each middleware 12447 * from right to left to the player. 12448 * 12449 * @param {Object[]} middleware 12450 * An array of middleware instances. 12451 * 12452 * @param {Tech} tech 12453 * The current tech. 12454 * 12455 * @param {string} method 12456 * A method name. 12457 * 12458 * @return {Mixed} 12459 * The final value from the tech after middleware has intercepted it. 12460 */ 12461 12462 function get(middleware, tech, method) { 12463 return middleware.reduceRight(middlewareIterator(method), tech[method]()); 12464 } 12465 /** 12466 * Takes the argument given to the player and calls the setter method on each 12467 * middleware from left to right to the tech. 12468 * 12469 * @param {Object[]} middleware 12470 * An array of middleware instances. 12471 * 12472 * @param {Tech} tech 12473 * The current tech. 12474 * 12475 * @param {string} method 12476 * A method name. 12477 * 12478 * @param {Mixed} arg 12479 * The value to set on the tech. 12480 * 12481 * @return {Mixed} 12482 * The return value of the `method` of the `tech`. 12483 */ 12484 12485 function set(middleware, tech, method, arg) { 12486 return tech[method](middleware.reduce(middlewareIterator(method), arg)); 12487 } 12488 /** 12489 * Takes the argument given to the player and calls the `call` version of the 12490 * method on each middleware from left to right. 12491 * 12492 * Then, call the passed in method on the tech and return the result unchanged 12493 * back to the player, through middleware, this time from right to left. 12494 * 12495 * @param {Object[]} middleware 12496 * An array of middleware instances. 12497 * 12498 * @param {Tech} tech 12499 * The current tech. 12500 * 12501 * @param {string} method
12502 * A method name. 12503 * 12504 * @param {Mixed} arg 12505 * The value to set on the tech. 12506 * 12507 * @return {Mixed} 12508 * The return value of the `method` of the `tech`, regardless of the 12509 * return values of middlewares. 12510 */ 12511 12512 function mediate(middleware, tech, method, arg) { 12513 if (arg === void 0) { 12514 arg = null; 12515 } 12516 12517 var callMethod = 'call' + toTitleCase$1(method); 12518 var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg); 12519 var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to 12520 // prevent confusion if a techs method actually returns null. 12521 12522 var returnValue = terminated ? null : tech[method](middlewareValue); 12523 executeRight(middleware, method, returnValue, terminated); 12524 return returnValue; 12525 } 12526 /** 12527 * Enumeration of allowed getters where the keys are method names. 12528 * 12529 * @type {Object} 12530 */ 12531 12532 var allowedGetters = { 12533 buffered: 1, 12534 currentTime: 1, 12535 duration: 1, 12536 muted: 1, 12537 played: 1, 12538 paused: 1, 12539 seekable: 1, 12540 volume: 1, 12541 ended: 1 12542 }; 12543 /** 12544 * Enumeration of allowed setters where the keys are method names. 12545 * 12546 * @type {Object} 12547 */ 12548 12549 var allowedSetters = { 12550 setCurrentTime: 1, 12551 setMuted: 1, 12552 setVolume: 1 12553 }; 12554 /** 12555 * Enumeration of allowed mediators where the keys are method names. 12556 * 12557 * @type {Object} 12558 */ 12559 12560 var allowedMediators = { 12561 play: 1, 12562 pause: 1 12563 }; 12564 12565 function middlewareIterator(method) { 12566 return function (value, mw) { 12567 // if the previous middleware terminated, pass along the termination 12568 if (value === TERMINATOR) { 12569 return TERMINATOR; 12570 } 12571 12572 if (mw[method]) { 12573 return mw[method](value); 12574 } 12575 12576 return value; 12577 }; 12578 } 12579 12580 function executeRight(mws, method, value, terminated) { 12581 for (var i = mws.length - 1; i >= 0; i--) { 12582 var mw = mws[i]; 12583 12584 if (mw[method]) { 12585 mw[method](terminated, value); 12586 } 12587 } 12588 } 12589 /** 12590 * Clear the middleware cache for a player. 12591 * 12592 * @param {Player} player 12593 * A {@link Player} instance. 12594 */ 12595 12596 12597 function clearCacheForPlayer(player) { 12598 middlewareInstances[player.id()] = null; 12599 } 12600 /** 12601 * { 12602 * [playerId]: [[mwFactory, mwInstance], ...] 12603 * } 12604 * 12605 * @private 12606 */ 12607 12608 function getOrCreateFactory(player, mwFactory) { 12609 var mws = middlewareInstances[player.id()]; 12610 var mw = null; 12611 12612 if (mws === undefined || mws === null) { 12613 mw = mwFactory(player); 12614 middlewareInstances[player.id()] = [[mwFactory, mw]]; 12615 return mw; 12616 } 12617 12618 for (var i = 0; i < mws.length; i++) { 12619 var _mws$i = mws[i], 12620 mwf = _mws$i[0], 12621 mwi = _mws$i[1]; 12622 12623 if (mwf !== mwFactory) { 12624 continue; 12625 } 12626 12627 mw = mwi; 12628 } 12629 12630 if (mw === null) { 12631 mw = mwFactory(player); 12632 mws.push([mwFactory, mw]); 12633 } 12634 12635 return mw; 12636 } 12637 12638 function setSourceHelper(src, middleware, next, player, acc, lastRun) { 12639 if (src === void 0) { 12640 src = {}; 12641 } 12642 12643 if (middleware === void 0) { 12644 middleware = []; 12645 } 12646 12647 if (acc === void 0) { 12648 acc = []; 12649 } 12650 12651 if (lastRun === void 0) { 12652 lastRun = false; 12653 } 12654 12655 var _middleware = middleware, 12656 mwFactory = _middleware[0], 12657 mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road 12658 12659 12660 if (typeof mwFactory === 'string') { 12661 setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw, 12662 // then call the mw's setSource method 12663 } else if (mwFactory) { 12664 var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware 12665 12666 if (!mw.setSource) { 12667 acc.push(mw); 12668 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12669 } 12670 12671 mw.setSource(assign({}, src), function (err, _src) { 12672 // something happened, try the next middleware on the current level 12673 // make sure to use the old src 12674 if (err) { 12675 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12676 } // we've succeeded, now we need to go deeper 12677 12678 12679 acc.push(mw); // if it's the same type, continue down the current chain 12680 // otherwise, we want to go down the new chain 12681 12682 setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); 12683 }); 12684 } else if (mwrest.length) { 12685 setSourceHelper(src, mwrest, next, player, acc, lastRun); 12686 } else if (lastRun) { 12687 next(src, acc); 12688 } else { 12689 setSourceHelper(src, middlewares['*'], next, player, acc, true); 12690 } 12691 } 12692 12693 /** 12694 * Mimetypes 12695 * 12696 * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm 12697 * @typedef Mimetypes~Kind 12698 * @enum 12699 */ 12700 12701 var MimetypesKind = { 12702 opus: 'video/ogg', 12703 ogv: 'video/ogg', 12704 mp4: 'video/mp4', 12705 mov: 'video/mp4', 12706 m4v: 'video/mp4', 12707 mkv: 'video/x-matroska', 12708 m4a: 'audio/mp4', 12709 mp3: 'audio/mpeg', 12710 aac: 'audio/aac', 12711 caf: 'audio/x-caf', 12712 flac: 'audio/flac', 12713 oga: 'audio/ogg', 12714 wav: 'audio/wav', 12715 m3u8: 'application/x-mpegURL', 12716 jpg: 'image/jpeg', 12717 jpeg: 'image/jpeg', 12718 gif: 'image/gif', 12719 png: 'image/png', 12720 svg: 'image/svg+xml', 12721 webp: 'image/webp' 12722 }; 12723 /** 12724 * Get the mimetype of a given src url if possible 12725 * 12726 * @param {string} src 12727 * The url to the src 12728 * 12729 * @return {string} 12730 * return the mimetype if it was known or empty string otherwise 12731 */ 12732 12733 var getMimetype = function getMimetype(src) { 12734 if (src === void 0) { 12735 src = ''; 12736 } 12737 12738 var ext = getFileExtension(src); 12739 var mimetype = MimetypesKind[ext.toLowerCase()]; 12740 return mimetype || ''; 12741 }; 12742 /** 12743 * Find the mime type of a given source string if possible. Uses the player 12744 * source cache. 12745 * 12746 * @param {Player} player 12747 * The player object 12748 * 12749 * @param {string} src 12750 * The source string 12751 * 12752 * @return {string} 12753 * The type that was found 12754 */ 12755 12756 var findMimetype = function findMimetype(player, src) { 12757 if (!src) { 12758 return ''; 12759 } // 1. check for the type in the `source` cache 12760 12761 12762 if (player.cache_.source.src === src && player.cache_.source.type) { 12763 return player.cache_.source.type; 12764 } // 2. see if we have this source in our `currentSources` cache 12765 12766 12767 var matchingSources = player.cache_.sources.filter(function (s) { 12768 return s.src === src; 12769 });
12770 12771 if (matchingSources.length) { 12772 return matchingSources[0].type; 12773 } // 3. look for the src url in source elements and use the type there 12774 12775 12776 var sources = player.$$('source'); 12777 12778 for (var i = 0; i < sources.length; i++) { 12779 var s = sources[i]; 12780 12781 if (s.type && s.src && s.src === src) { 12782 return s.type; 12783 } 12784 } // 4. finally fallback to our list of mime types based on src url extension 12785 12786 12787 return getMimetype(src); 12788 }; 12789 12790 /** 12791 * @module filter-source 12792 */ 12793 /** 12794 * Filter out single bad source objects or multiple source objects in an 12795 * array. Also flattens nested source object arrays into a 1 dimensional 12796 * array of source objects. 12797 * 12798 * @param {Tech~SourceObject|Tech~SourceObject[]} src 12799 * The src object to filter 12800 * 12801 * @return {Tech~SourceObject[]} 12802 * An array of sourceobjects containing only valid sources 12803 * 12804 * @private 12805 */ 12806 12807 var filterSource = function filterSource(src) { 12808 // traverse array 12809 if (Array.isArray(src)) { 12810 var newsrc = []; 12811 src.forEach(function (srcobj) { 12812 srcobj = filterSource(srcobj); 12813 12814 if (Array.isArray(srcobj)) { 12815 newsrc = newsrc.concat(srcobj); 12816 } else if (isObject$1(srcobj)) { 12817 newsrc.push(srcobj); 12818 } 12819 }); 12820 src = newsrc; 12821 } else if (typeof src === 'string' && src.trim()) { 12822 // convert string into object 12823 src = [fixSource({ 12824 src: src 12825 })]; 12826 } else if (isObject$1(src) && typeof src.src === 'string' && src.src && src.src.trim()) { 12827 // src is already valid 12828 src = [fixSource(src)]; 12829 } else { 12830 // invalid source, turn it into an empty array 12831 src = []; 12832 } 12833 12834 return src; 12835 }; 12836 /** 12837 * Checks src mimetype, adding it when possible 12838 * 12839 * @param {Tech~SourceObject} src 12840 * The src object to check 12841 * @return {Tech~SourceObject} 12842 * src Object with known type 12843 */ 12844 12845 12846 function fixSource(src) { 12847 if (!src.type) { 12848 var mimetype = getMimetype(src.src); 12849 12850 if (mimetype) { 12851 src.type = mimetype; 12852 } 12853 } 12854 12855 return src; 12856 } 12857 12858 /** 12859 * The `MediaLoader` is the `Component` that decides which playback technology to load 12860 * when a player is initialized. 12861 * 12862 * @extends Component 12863 */ 12864 12865 var MediaLoader = /*#__PURE__*/function (_Component) { 12866 inheritsLoose(MediaLoader, _Component); 12867 12868 /** 12869 * Create an instance of this class. 12870 * 12871 * @param {Player} player 12872 * The `Player` that this class should attach to. 12873 * 12874 * @param {Object} [options] 12875 * The key/value store of player options. 12876 * 12877 * @param {Component~ReadyCallback} [ready] 12878 * The function that is run when this component is ready. 12879 */ 12880 function MediaLoader(player, options, ready) { 12881 var _this; 12882 12883 // MediaLoader has no element 12884 var options_ = mergeOptions$3({ 12885 createEl: false 12886 }, options); 12887 _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized, 12888 // load the first supported playback technology. 12889 12890 if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { 12891 for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { 12892 var techName = toTitleCase$1(j[i]); 12893 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 12894 // Remove once that deprecated behavior is removed. 12895 12896 if (!techName) { 12897 tech = Component$1.getComponent(techName); 12898 } // Check if the browser supports this technology 12899 12900 12901 if (tech && tech.isSupported()) { 12902 player.loadTech_(techName); 12903 break; 12904 } 12905 } 12906 } else { 12907 // Loop through playback technologies (e.g. HTML5) and check for support. 12908 // Then load the best source. 12909 // A few assumptions here: 12910 // All playback technologies respect preload false. 12911 player.src(options.playerOptions.sources); 12912 } 12913 12914 return _this; 12915 } 12916 12917 return MediaLoader; 12918 }(Component$1); 12919 12920 Component$1.registerComponent('MediaLoader', MediaLoader); 12921 12922 /** 12923 * Component which is clickable or keyboard actionable, but is not a 12924 * native HTML button. 12925 * 12926 * @extends Component 12927 */ 12928 12929 var ClickableComponent = /*#__PURE__*/function (_Component) { 12930 inheritsLoose(ClickableComponent, _Component); 12931 12932 /** 12933 * Creates an instance of this class. 12934 * 12935 * @param {Player} player 12936 * The `Player` that this class should be attached to. 12937 * 12938 * @param {Object} [options] 12939 * The key/value store of player options. 12940 * 12941 * @param {function} [options.clickHandler] 12942 * The function to call when the button is clicked / activated 12943 */ 12944 function ClickableComponent(player, options) { 12945 var _this; 12946 12947 _this = _Component.call(this, player, options) || this; 12948 12949 _this.handleMouseOver_ = function (e) { 12950 return _this.handleMouseOver(e); 12951 }; 12952 12953 _this.handleMouseOut_ = function (e) { 12954 return _this.handleMouseOut(e); 12955 }; 12956 12957 _this.handleClick_ = function (e) { 12958 return _this.handleClick(e); 12959 }; 12960 12961 _this.handleKeyDown_ = function (e) { 12962 return _this.handleKeyDown(e); 12963 }; 12964 12965 _this.emitTapEvents(); 12966 12967 _this.enable(); 12968 12969 return _this; 12970 } 12971 /** 12972 * Create the `ClickableComponent`s DOM element. 12973 * 12974 * @param {string} [tag=div] 12975 * The element's node type. 12976 * 12977 * @param {Object} [props={}] 12978 * An object of properties that should be set on the element. 12979 * 12980 * @param {Object} [attributes={}] 12981 * An object of attributes that should be set on the element. 12982 * 12983 * @return {Element} 12984 * The element that gets created. 12985 */ 12986 12987 12988 var _proto = ClickableComponent.prototype; 12989 12990 _proto.createEl = function createEl$1(tag, props, attributes) { 12991 if (tag === void 0) { 12992 tag = 'div'; 12993 } 12994 12995 if (props === void 0) { 12996 props = {}; 12997 } 12998 12999 if (attributes === void 0) { 13000 attributes = {}; 13001 } 13002 13003 props = assign({ 13004 className: this.buildCSSClass(), 13005 tabIndex: 0 13006 }, props); 13007 13008 if (tag === 'button') { 13009 log$1.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead."); 13010 } // Add ARIA attributes for clickable element which is not a native HTML button 13011 13012 13013 attributes = assign({ 13014 role: 'button' 13015 }, attributes); 13016 this.tabIndex_ = props.tabIndex; 13017 var el = createEl(tag, props, attributes); 13018 el.appendChild(createEl('span', { 13019 className: 'vjs-icon-placeholder' 13020 }, { 13021 'aria-hidden': true 13022 })); 13023 this.createControlTextEl(el); 13024 return el; 13025 }; 13026 13027 _proto.dispose = function dispose() { 13028 // remove controlTextEl_ on dispose 13029 this.controlTextEl_ = null; 13030 13031 _Component.prototype.dispose.call(this); 13032 } 13033 /** 13034 * Create a control text element on this `ClickableComponent` 13035 * 13036 * @param {Element} [el] 13037 * Parent element for the control text. 13038 * 13039 * @return {Element} 13040 * The control text element that gets created. 13041 */ 13042 ; 13043 13044 _proto.createControlTextEl = function createControlTextEl(el) { 13045 this.controlTextEl_ = createEl('span', { 13046 className: 'vjs-control-text' 13047 }, { 13048 // let the screen reader user know that the text of the element may change 13049 'aria-live': 'polite' 13050 }); 13051 13052 if (el) { 13053 el.appendChild(this.controlTextEl_); 13054 } 13055 13056 this.controlText(this.controlText_, el); 13057 return this.controlTextEl_; 13058 } 13059 /** 13060 * Get or set the localize text to use for the controls on the `ClickableComponent`. 13061 * 13062 * @param {string} [text] 13063 * Control text for element. 13064 * 13065 * @param {Element} [el=this.el()] 13066 * Element to set the title on. 13067 * 13068 * @return {string} 13069 * - The control text when getting 13070 */ 13071 ; 13072 13073 _proto.controlText = function controlText(text, el) { 13074 if (el === void 0) { 13075 el = this.el(); 13076 } 13077 13078 if (text === undefined) { 13079 return this.controlText_ || 'Need Text'; 13080 } 13081 13082 var localizedText = this.localize(text); 13083 this.controlText_ = text; 13084 textContent(this.controlTextEl_, localizedText); 13085 13086 if (!this.nonIconControl && !this.player_.options_.noUITitleAttributes) { 13087 // Set title attribute if only an icon is shown 13088 el.setAttribute('title', localizedText); 13089 } 13090 } 13091 /** 13092 * Builds the default DOM `className`. 13093 * 13094 * @return {string} 13095 * The DOM `className` for this object. 13096 */ 13097 ; 13098 13099 _proto.buildCSSClass = function buildCSSClass() { 13100 return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this); 13101 } 13102 /** 13103 * Enable this `ClickableComponent` 13104 */ 13105 ; 13106 13107 _proto.enable = function enable() { 13108 if (!this.enabled_) { 13109 this.enabled_ = true; 13110 this.removeClass('vjs-disabled'); 13111 this.el_.setAttribute('aria-disabled', 'false'); 13112 13113 if (typeof this.tabIndex_ !== 'undefined') { 13114 this.el_.setAttribute('tabIndex', this.tabIndex_); 13115 } 13116 13117 this.on(['tap', 'click'], this.handleClick_); 13118 this.on('keydown', this.handleKeyDown_); 13119 } 13120 } 13121 /** 13122 * Disable this `ClickableComponent` 13123 */ 13124 ; 13125 13126 _proto.disable = function disable() { 13127 this.enabled_ = false; 13128 this.addClass('vjs-disabled'); 13129 this.el_.setAttribute('aria-disabled', 'true'); 13130 13131 if (typeof this.tabIndex_ !== 'undefined') { 13132 this.el_.removeAttribute('tabIndex'); 13133 } 13134 13135 this.off('mouseover', this.handleMouseOver_); 13136 this.off('mouseout', this.handleMouseOut_); 13137 this.off(['tap', 'click'], this.handleClick_); 13138 this.off('keydown', this.handleKeyDown_); 13139 } 13140 /** 13141 * Handles language change in ClickableComponent for the player in components 13142 * 13143 * 13144 */ 13145 ; 13146 13147 _proto.handleLanguagechange = function handleLanguagechange() { 13148 this.controlText(this.controlText_); 13149 } 13150 /** 13151 * Event handler that is called when a `ClickableComponent` receives a 13152 * `click` or `tap` event. 13153 * 13154 * @param {EventTarget~Event} event 13155 * The `tap` or `click` event that caused this function to be called. 13156 * 13157 * @listens tap 13158 * @listens click 13159 * @abstract 13160 */ 13161 ; 13162 13163 _proto.handleClick = function handleClick(event) { 13164 if (this.options_.clickHandler) { 13165 this.options_.clickHandler.call(this, arguments); 13166 } 13167 } 13168 /** 13169 * Event handler that is called when a `ClickableComponent` receives a 13170 * `keydown` event. 13171 * 13172 * By default, if the key is Space or Enter, it will trigger a `click` event. 13173 * 13174 * @param {EventTarget~Event} event
13175 * The `keydown` event that caused this function to be called. 13176 * 13177 * @listens keydown 13178 */ 13179 ; 13180 13181 _proto.handleKeyDown = function handleKeyDown(event) { 13182 // Support Space or Enter key operation to fire a click event. Also, 13183 // prevent the event from propagating through the DOM and triggering 13184 // Player hotkeys. 13185 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 13186 event.preventDefault(); 13187 event.stopPropagation(); 13188 this.trigger('click'); 13189 } else { 13190 // Pass keypress handling up for unsupported keys 13191 _Component.prototype.handleKeyDown.call(this, event); 13192 } 13193 }; 13194 13195 return ClickableComponent; 13196 }(Component$1); 13197 13198 Component$1.registerComponent('ClickableComponent', ClickableComponent); 13199 13200 /** 13201 * A `ClickableComponent` that handles showing the poster image for the player. 13202 * 13203 * @extends ClickableComponent 13204 */ 13205 13206 var PosterImage = /*#__PURE__*/function (_ClickableComponent) { 13207 inheritsLoose(PosterImage, _ClickableComponent); 13208 13209 /** 13210 * Create an instance of this class. 13211 * 13212 * @param {Player} player 13213 * The `Player` that this class should attach to. 13214 * 13215 * @param {Object} [options] 13216 * The key/value store of player options. 13217 */ 13218 function PosterImage(player, options) { 13219 var _this; 13220 13221 _this = _ClickableComponent.call(this, player, options) || this; 13222 13223 _this.update(); 13224 13225 _this.update_ = function (e) { 13226 return _this.update(e); 13227 }; 13228 13229 player.on('posterchange', _this.update_); 13230 return _this; 13231 } 13232 /** 13233 * Clean up and dispose of the `PosterImage`. 13234 */ 13235 13236 13237 var _proto = PosterImage.prototype; 13238 13239 _proto.dispose = function dispose() { 13240 this.player().off('posterchange', this.update_); 13241 13242 _ClickableComponent.prototype.dispose.call(this); 13243 } 13244 /** 13245 * Create the `PosterImage`s DOM element. 13246 * 13247 * @return {Element} 13248 * The element that gets created. 13249 */ 13250 ; 13251 13252 _proto.createEl = function createEl$1() { 13253 var el = createEl('div', { 13254 className: 'vjs-poster', 13255 // Don't want poster to be tabbable. 13256 tabIndex: -1 13257 }); 13258 return el; 13259 } 13260 /** 13261 * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. 13262 * 13263 * @listens Player#posterchange 13264 * 13265 * @param {EventTarget~Event} [event] 13266 * The `Player#posterchange` event that triggered this function. 13267 */ 13268 ; 13269 13270 _proto.update = function update(event) { 13271 var url = this.player().poster(); 13272 this.setSrc(url); // If there's no poster source we should display:none on this component 13273 // so it's not still clickable or right-clickable 13274 13275 if (url) { 13276 this.show(); 13277 } else { 13278 this.hide(); 13279 } 13280 } 13281 /** 13282 * Set the source of the `PosterImage` depending on the display method. 13283 * 13284 * @param {string} url 13285 * The URL to the source for the `PosterImage`. 13286 */ 13287 ; 13288 13289 _proto.setSrc = function setSrc(url) { 13290 var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise 13291 // this will throw an extra error 13292 13293 if (url) { 13294 backgroundImage = "url(\"" + url + "\")"; 13295 } 13296 13297 this.el_.style.backgroundImage = backgroundImage; 13298 } 13299 /** 13300 * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See 13301 * {@link ClickableComponent#handleClick} for instances where this will be triggered. 13302 * 13303 * @listens tap 13304 * @listens click 13305 * @listens keydown 13306 * 13307 * @param {EventTarget~Event} event 13308 + The `click`, `tap` or `keydown` event that caused this function to be called. 13309 */ 13310 ; 13311 13312 _proto.handleClick = function handleClick(event) { 13313 // We don't want a click to trigger playback when controls are disabled 13314 if (!this.player_.controls()) { 13315 return; 13316 } 13317 13318 var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0;
13319 13320 if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where 13321 // calling .focus() on the video element causes the video to go black, 13322 // so we avoid it in that specific case 13323 !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) { 13324 this.player_.tech(true).focus(); 13325 } 13326 13327 if (this.player_.paused()) { 13328 silencePromise(this.player_.play()); 13329 } else { 13330 this.player_.pause(); 13331 } 13332 }; 13333 13334 return PosterImage; 13335 }(ClickableComponent); 13336 13337 Component$1.registerComponent('PosterImage', PosterImage); 13338 13339 var darkGray = '#222'; 13340 var lightGray = '#ccc'; 13341 var fontMap = { 13342 monospace: 'monospace', 13343 sansSerif: 'sans-serif', 13344 serif: 'serif', 13345 monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', 13346 monospaceSerif: '"Courier New", monospace', 13347 proportionalSansSerif: 'sans-serif', 13348 proportionalSerif: 'serif', 13349 casual: '"Comic Sans MS", Impact, fantasy', 13350 script: '"Monotype Corsiva", cursive', 13351 smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' 13352 }; 13353 /** 13354 * Construct an rgba color from a given hex color code. 13355 * 13356 * @param {number} color 13357 * Hex number for color, like #f0e or #f604e2. 13358 * 13359 * @param {number} opacity 13360 * Value for opacity, 0.0 - 1.0. 13361 * 13362 * @return {string} 13363 * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. 13364 */ 13365 13366 function constructColor(color, opacity) { 13367 var hex; 13368 13369 if (color.length === 4) { 13370 // color looks like "#f0e" 13371 hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; 13372 } else if (color.length === 7) { 13373 // color looks like "#f604e2" 13374 hex = color.slice(1); 13375 } else { 13376 throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.'); 13377 } 13378 13379 return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')'; 13380 } 13381 /** 13382 * Try to update the style of a DOM element. Some style changes will throw an error, 13383 * particularly in IE8. Those should be noops. 13384 * 13385 * @param {Element} el 13386 * The DOM element to be styled. 13387 * 13388 * @param {string} style 13389 * The CSS property on the element that should be styled. 13390 * 13391 * @param {string} rule 13392 * The style rule that should be applied to the property. 13393 * 13394 * @private 13395 */ 13396 13397 function tryUpdateStyle(el, style, rule) { 13398 try { 13399 el.style[style] = rule; 13400 } catch (e) { 13401 // Satisfies linter. 13402 return; 13403 } 13404 } 13405 /** 13406 * The component for displaying text track cues. 13407 * 13408 * @extends Component 13409 */ 13410 13411 13412 var TextTrackDisplay = /*#__PURE__*/function (_Component) { 13413 inheritsLoose(TextTrackDisplay, _Component); 13414 13415 /** 13416 * Creates an instance of this class. 13417 * 13418 * @param {Player} player 13419 * The `Player` that this class should be attached to. 13420 * 13421 * @param {Object} [options] 13422 * The key/value store of player options. 13423 * 13424 * @param {Component~ReadyCallback} [ready] 13425 * The function to call when `TextTrackDisplay` is ready. 13426 */ 13427 function TextTrackDisplay(player, options, ready) { 13428 var _this; 13429 13430 _this = _Component.call(this, player, options, ready) || this; 13431 13432 var updateDisplayHandler = function updateDisplayHandler(e) { 13433 return _this.updateDisplay(e); 13434 }; 13435 13436 player.on('loadstart', function (e) { 13437 return _this.toggleDisplay(e); 13438 }); 13439 player.on('texttrackchange', updateDisplayHandler); 13440 player.on('loadedmetadata', function (e) { 13441 return _this.preselectTrack(e); 13442 }); // This used to be called during player init, but was causing an error 13443 // if a track should show by default and the display hadn't loaded yet. 13444 // Should probably be moved to an external track loader when we support 13445 // tracks that don't need a display. 13446 13447 player.ready(bind(assertThisInitialized(_this), function () { 13448 if (player.tech_ && player.tech_.featuresNativeTextTracks) { 13449 this.hide(); 13450 return; 13451 } 13452 13453 player.on('fullscreenchange', updateDisplayHandler); 13454 player.on('playerresize', updateDisplayHandler); 13455 window_1.addEventListener('orientationchange', updateDisplayHandler); 13456 player.on('dispose', function () { 13457 return window_1.removeEventListener('orientationchange', updateDisplayHandler); 13458 }); 13459 var tracks = this.options_.playerOptions.tracks || []; 13460 13461 for (var i = 0; i < tracks.length; i++) { 13462 this.player_.addRemoteTextTrack(tracks[i], true); 13463 } 13464 13465 this.preselectTrack(); 13466 })); 13467 return _this; 13468 } 13469 /** 13470 * Preselect a track following this precedence: 13471 * - matches the previously selected {@link TextTrack}'s language and kind 13472 * - matches the previously selected {@link TextTrack}'s language only 13473 * - is the first default captions track 13474 * - is the first default descriptions track 13475 * 13476 * @listens Player#loadstart 13477 */ 13478 13479 13480 var _proto = TextTrackDisplay.prototype; 13481 13482 _proto.preselectTrack = function preselectTrack() { 13483 var modes = { 13484 captions: 1, 13485 subtitles: 1 13486 }; 13487 var trackList = this.player_.textTracks(); 13488 var userPref = this.player_.cache_.selectedLanguage; 13489 var firstDesc; 13490 var firstCaptions; 13491 var preferredTrack; 13492 13493 for (var i = 0; i < trackList.length; i++) { 13494 var track = trackList[i]; 13495 13496 if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) { 13497 // Always choose the track that matches both language and kind 13498 if (track.kind === userPref.kind) { 13499 preferredTrack = track; // or choose the first track that matches language 13500 } else if (!preferredTrack) { 13501 preferredTrack = track; 13502 } // clear everything if offTextTrackMenuItem was clicked 13503 13504 } else if (userPref && !userPref.enabled) { 13505 preferredTrack = null; 13506 firstDesc = null; 13507 firstCaptions = null; 13508 } else if (track["default"]) { 13509 if (track.kind === 'descriptions' && !firstDesc) { 13510 firstDesc = track; 13511 } else if (track.kind in modes && !firstCaptions) { 13512 firstCaptions = track; 13513 } 13514 } 13515 } // The preferredTrack matches the user preference and takes 13516 // precedence over all the other tracks. 13517 // So, display the preferredTrack before the first default track 13518 // and the subtitles/captions track before the descriptions track 13519 13520 13521 if (preferredTrack) { 13522 preferredTrack.mode = 'showing'; 13523 } else if (firstCaptions) { 13524 firstCaptions.mode = 'showing'; 13525 } else if (firstDesc) { 13526 firstDesc.mode = 'showing'; 13527 } 13528 } 13529 /** 13530 * Turn display of {@link TextTrack}'s from the current state into the other state. 13531 * There are only two states: 13532 * - 'shown' 13533 * - 'hidden' 13534 * 13535 * @listens Player#loadstart 13536 */ 13537 ; 13538 13539 _proto.toggleDisplay = function toggleDisplay() { 13540 if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { 13541 this.hide(); 13542 } else { 13543 this.show(); 13544 } 13545 } 13546 /** 13547 * Create the {@link Component}'s DOM element. 13548 * 13549 * @return {Element} 13550 * The element that was created. 13551 */ 13552 ; 13553 13554 _proto.createEl = function createEl() { 13555 return _Component.prototype.createEl.call(this, 'div', { 13556 className: 'vjs-text-track-display' 13557 }, { 13558 'aria-live': 'off', 13559 'aria-atomic': 'true' 13560 }); 13561 } 13562 /** 13563 * Clear all displayed {@link TextTrack}s. 13564 */ 13565 ; 13566 13567 _proto.clearDisplay = function clearDisplay() { 13568 if (typeof window_1.WebVTT === 'function') { 13569 window_1.WebVTT.processCues(window_1, [], this.el_); 13570 } 13571 } 13572 /** 13573 * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or 13574 * a {@link Player#fullscreenchange} is fired. 13575 * 13576 * @listens Player#texttrackchange 13577 * @listens Player#fullscreenchange 13578 */ 13579 ; 13580 13581 _proto.updateDisplay = function updateDisplay() { 13582 var tracks = this.player_.textTracks(); 13583 var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks; 13584 this.clearDisplay(); 13585 13586 if (allowMultipleShowingTracks) { 13587 var showingTracks = []; 13588 13589 for (var _i = 0; _i < tracks.length; ++_i) { 13590 var track = tracks[_i]; 13591 13592 if (track.mode !== 'showing') { 13593 continue; 13594 } 13595 13596 showingTracks.push(track); 13597 } 13598 13599 this.updateForTrack(showingTracks); 13600 return; 13601 } // Track display prioritization model: if multiple tracks are 'showing', 13602 // display the first 'subtitles' or 'captions' track which is 'showing', 13603 // otherwise display the first 'descriptions' track which is 'showing' 13604 13605 13606 var descriptionsTrack = null; 13607 var captionsSubtitlesTrack = null; 13608 var i = tracks.length; 13609 13610 while (i--) { 13611 var _track = tracks[i]; 13612 13613 if (_track.mode === 'showing') { 13614 if (_track.kind === 'descriptions') { 13615 descriptionsTrack = _track; 13616 } else { 13617 captionsSubtitlesTrack = _track; 13618 } 13619 } 13620 } 13621 13622 if (captionsSubtitlesTrack) { 13623 if (this.getAttribute('aria-live') !== 'off') { 13624 this.setAttribute('aria-live', 'off'); 13625 } 13626 13627 this.updateForTrack(captionsSubtitlesTrack); 13628 } else if (descriptionsTrack) { 13629 if (this.getAttribute('aria-live') !== 'assertive') { 13630 this.setAttribute('aria-live', 'assertive'); 13631 } 13632 13633 this.updateForTrack(descriptionsTrack); 13634 } 13635 } 13636 /** 13637 * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}. 13638 * 13639 * @param {TextTrack} track
13640 * Text track object containing active cues to style. 13641 */ 13642 ; 13643 13644 _proto.updateDisplayState = function updateDisplayState(track) { 13645 var overrides = this.player_.textTrackSettings.getValues(); 13646 var cues = track.activeCues; 13647 var i = cues.length; 13648 13649 while (i--) { 13650 var cue = cues[i]; 13651 13652 if (!cue) { 13653 continue; 13654 } 13655 13656 var cueDiv = cue.displayState; 13657 13658 if (overrides.color) { 13659 cueDiv.firstChild.style.color = overrides.color; 13660 } 13661 13662 if (overrides.textOpacity) { 13663 tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); 13664 } 13665 13666 if (overrides.backgroundColor) { 13667 cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; 13668 } 13669 13670 if (overrides.backgroundOpacity) { 13671 tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); 13672 } 13673 13674 if (overrides.windowColor) { 13675 if (overrides.windowOpacity) { 13676 tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); 13677 } else { 13678 cueDiv.style.backgroundColor = overrides.windowColor; 13679 } 13680 } 13681 13682 if (overrides.edgeStyle) { 13683 if (overrides.edgeStyle === 'dropshadow') { 13684 cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray; 13685 } else if (overrides.edgeStyle === 'raised') { 13686 cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray; 13687 } else if (overrides.edgeStyle === 'depressed') { 13688 cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray; 13689 } else if (overrides.edgeStyle === 'uniform') { 13690 cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray; 13691 } 13692 } 13693 13694 if (overrides.fontPercent && overrides.fontPercent !== 1) { 13695 var fontSize = window_1.parseFloat(cueDiv.style.fontSize); 13696 cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; 13697 cueDiv.style.height = 'auto'; 13698 cueDiv.style.top = 'auto'; 13699 } 13700 13701 if (overrides.fontFamily && overrides.fontFamily !== 'default') { 13702 if (overrides.fontFamily === 'small-caps') { 13703 cueDiv.firstChild.style.fontVariant = 'small-caps'; 13704 } else { 13705 cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; 13706 } 13707 } 13708 } 13709 } 13710 /** 13711 * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}. 13712 * 13713 * @param {TextTrack|TextTrack[]} tracks 13714 * Text track object or text track array to be added to the list. 13715 */ 13716 ; 13717 13718 _proto.updateForTrack = function updateForTrack(tracks) { 13719 if (!Array.isArray(tracks)) { 13720 tracks = [tracks]; 13721 } 13722 13723 if (typeof window_1.WebVTT !== 'function' || tracks.every(function (track) { 13724 return !track.activeCues; 13725 })) { 13726 return; 13727 } 13728 13729 var cues = []; // push all active track cues 13730 13731 for (var i = 0; i < tracks.length; ++i) { 13732 var track = tracks[i]; 13733 13734 for (var j = 0; j < track.activeCues.length; ++j) { 13735 cues.push(track.activeCues[j]); 13736 } 13737 } // removes all cues before it processes new ones 13738 13739 13740 window_1.WebVTT.processCues(window_1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary 13741 13742 for (var _i2 = 0; _i2 < tracks.length; ++_i2) { 13743 var _track2 = tracks[_i2]; 13744 13745 for (var _j = 0; _j < _track2.activeCues.length; ++_j) { 13746 var cueEl = _track2.activeCues[_j].displayState; 13747 addClass(cueEl, 'vjs-text-track-cue'); 13748 addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2)); 13749 } 13750 13751 if (this.player_.textTrackSettings) { 13752 this.updateDisplayState(_track2); 13753 } 13754 } 13755 }; 13756 13757 return TextTrackDisplay; 13758 }(Component$1); 13759 13760 Component$1.registerComponent('TextTrackDisplay', TextTrackDisplay); 13761 13762 /** 13763 * A loading spinner for use during waiting/loading events. 13764 * 13765 * @extends Component 13766 */ 13767 13768 var LoadingSpinner = /*#__PURE__*/function (_Component) { 13769 inheritsLoose(LoadingSpinner, _Component); 13770 13771 function LoadingSpinner() { 13772 return _Component.apply(this, arguments) || this; 13773 } 13774 13775 var _proto = LoadingSpinner.prototype; 13776 13777 /** 13778 * Create the `LoadingSpinner`s DOM element. 13779 * 13780 * @return {Element} 13781 * The dom element that gets created. 13782 */ 13783 _proto.createEl = function createEl$1() { 13784 var isAudio = this.player_.isAudio(); 13785 var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player'); 13786 var controlText = createEl('span', { 13787 className: 'vjs-control-text',
13788 textContent: this.localize('{1} is loading.', [playerType]) 13789 }); 13790 13791 var el = _Component.prototype.createEl.call(this, 'div', { 13792 className: 'vjs-loading-spinner', 13793 dir: 'ltr' 13794 }); 13795 13796 el.appendChild(controlText); 13797 return el; 13798 }; 13799 13800 return LoadingSpinner; 13801 }(Component$1); 13802 13803 Component$1.registerComponent('LoadingSpinner', LoadingSpinner); 13804 13805 /** 13806 * Base class for all buttons. 13807 * 13808 * @extends ClickableComponent 13809 */ 13810 13811 var Button = /*#__PURE__*/function (_ClickableComponent) { 13812 inheritsLoose(Button, _ClickableComponent); 13813 13814 function Button() { 13815 return _ClickableComponent.apply(this, arguments) || this; 13816 } 13817 13818 var _proto = Button.prototype; 13819 13820 /** 13821 * Create the `Button`s DOM element. 13822 * 13823 * @param {string} [tag="button"] 13824 * The element's node type. This argument is IGNORED: no matter what 13825 * is passed, it will always create a `button` element. 13826 * 13827 * @param {Object} [props={}] 13828 * An object of properties that should be set on the element. 13829 * 13830 * @param {Object} [attributes={}] 13831 * An object of attributes that should be set on the element. 13832 * 13833 * @return {Element} 13834 * The element that gets created. 13835 */ 13836 _proto.createEl = function createEl$1(tag, props, attributes) { 13837 if (props === void 0) { 13838 props = {}; 13839 } 13840 13841 if (attributes === void 0) { 13842 attributes = {}; 13843 } 13844 13845 tag = 'button'; 13846 props = assign({ 13847 className: this.buildCSSClass() 13848 }, props); // Add attributes for button element 13849 13850 attributes = assign({ 13851 // Necessary since the default button type is "submit" 13852 type: 'button' 13853 }, attributes); 13854 13855 var el = createEl(tag, props, attributes); 13856 13857 el.appendChild(createEl('span', { 13858 className: 'vjs-icon-placeholder' 13859 }, { 13860 'aria-hidden': true 13861 })); 13862 this.createControlTextEl(el); 13863 return el; 13864 } 13865 /** 13866 * Add a child `Component` inside of this `Button`. 13867 * 13868 * @param {string|Component} child 13869 * The name or instance of a child to add. 13870 * 13871 * @param {Object} [options={}] 13872 * The key/value store of options that will get passed to children of 13873 * the child. 13874 * 13875 * @return {Component} 13876 * The `Component` that gets added as a child. When using a string the 13877 * `Component` will get created by this process. 13878 * 13879 * @deprecated since version 5 13880 */ 13881 ; 13882 13883 _proto.addChild = function addChild(child, options) { 13884 if (options === void 0) { 13885 options = {}; 13886 } 13887 13888 var className = this.constructor.name; 13889 log$1.warn("Adding an actionable (user controllable) child to a Button (" + className + ") is not supported; use a ClickableComponent instead."); // Avoid the error message generated by ClickableComponent's addChild method 13890 13891 return Component$1.prototype.addChild.call(this, child, options); 13892 } 13893 /** 13894 * Enable the `Button` element so that it can be activated or clicked. Use this with 13895 * {@link Button#disable}. 13896 */ 13897 ; 13898 13899 _proto.enable = function enable() { 13900 _ClickableComponent.prototype.enable.call(this); 13901 13902 this.el_.removeAttribute('disabled'); 13903 } 13904 /** 13905 * Disable the `Button` element so that it cannot be activated or clicked. Use this with 13906 * {@link Button#enable}. 13907 */ 13908 ; 13909 13910 _proto.disable = function disable() { 13911 _ClickableComponent.prototype.disable.call(this); 13912 13913 this.el_.setAttribute('disabled', 'disabled'); 13914 } 13915 /** 13916 * This gets called when a `Button` has focus and `keydown` is triggered via a key 13917 * press. 13918 * 13919 * @param {EventTarget~Event} event 13920 * The event that caused this function to get called. 13921 * 13922 * @listens keydown 13923 */ 13924 ; 13925 13926 _proto.handleKeyDown = function handleKeyDown(event) { 13927 // Ignore Space or Enter key operation, which is handled by the browser for 13928 // a button - though not for its super class, ClickableComponent. Also, 13929 // prevent the event from propagating through the DOM and triggering Player 13930 // hotkeys. We do not preventDefault here because we _want_ the browser to 13931 // handle it. 13932 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 13933 event.stopPropagation(); 13934 return; 13935 } // Pass keypress handling up for unsupported keys 13936 13937 13938 _ClickableComponent.prototype.handleKeyDown.call(this, event); 13939 }; 13940 13941 return Button; 13942 }(ClickableComponent); 13943 13944 Component$1.registerComponent('Button', Button); 13945 13946 /** 13947 * The initial play button that shows before the video has played. The hiding of the 13948 * `BigPlayButton` get done via CSS and `Player` states. 13949 * 13950 * @extends Button 13951 */ 13952 13953 var BigPlayButton = /*#__PURE__*/function (_Button) { 13954 inheritsLoose(BigPlayButton, _Button); 13955 13956 function BigPlayButton(player, options) { 13957 var _this; 13958 13959 _this = _Button.call(this, player, options) || this; 13960 _this.mouseused_ = false; 13961 13962 _this.on('mousedown', function (e) { 13963 return _this.handleMouseDown(e); 13964 }); 13965 13966 return _this; 13967 } 13968 /** 13969 * Builds the default DOM `className`. 13970 * 13971 * @return {string} 13972 * The DOM `className` for this object. Always returns 'vjs-big-play-button'. 13973 */ 13974 13975 13976 var _proto = BigPlayButton.prototype; 13977 13978 _proto.buildCSSClass = function buildCSSClass() { 13979 return 'vjs-big-play-button'; 13980 } 13981 /** 13982 * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} 13983 * for more detailed information on what a click can be. 13984 * 13985 * @param {EventTarget~Event} event
13986 * The `keydown`, `tap`, or `click` event that caused this function to be 13987 * called. 13988 * 13989 * @listens tap 13990 * @listens click 13991 */ 13992 ; 13993 13994 _proto.handleClick = function handleClick(event) { 13995 var playPromise = this.player_.play(); // exit early if clicked via the mouse 13996 13997 if (this.mouseused_ && event.clientX && event.clientY) { 13998 var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0; 13999 silencePromise(playPromise); 14000 14001 if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where 14002 // calling .focus() on the video element causes the video to go black, 14003 // so we avoid it in that specific case 14004 !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) { 14005 this.player_.tech(true).focus(); 14006 } 14007 14008 return; 14009 } 14010 14011 var cb = this.player_.getChild('controlBar'); 14012 var playToggle = cb && cb.getChild('playToggle'); 14013 14014 if (!playToggle) { 14015 this.player_.tech(true).focus(); 14016 return; 14017 } 14018 14019 var playFocus = function playFocus() { 14020 return playToggle.focus(); 14021 }; 14022 14023 if (isPromise(playPromise)) { 14024 playPromise.then(playFocus, function () {}); 14025 } else { 14026 this.setTimeout(playFocus, 1); 14027 } 14028 }; 14029 14030 _proto.handleKeyDown = function handleKeyDown(event) { 14031 this.mouseused_ = false; 14032 14033 _Button.prototype.handleKeyDown.call(this, event); 14034 }; 14035 14036 _proto.handleMouseDown = function handleMouseDown(event) { 14037 this.mouseused_ = true; 14038 }; 14039 14040 return BigPlayButton; 14041 }(Button); 14042 /** 14043 * The text that should display over the `BigPlayButton`s controls. Added to for localization. 14044 * 14045 * @type {string} 14046 * @private 14047 */ 14048 14049 14050 BigPlayButton.prototype.controlText_ = 'Play Video'; 14051 Component$1.registerComponent('BigPlayButton', BigPlayButton); 14052 14053 /** 14054 * The `CloseButton` is a `{@link Button}` that fires a `close` event when 14055 * it gets clicked. 14056 * 14057 * @extends Button 14058 */ 14059 14060 var CloseButton = /*#__PURE__*/function (_Button) { 14061 inheritsLoose(CloseButton, _Button); 14062 14063 /** 14064 * Creates an instance of the this class. 14065 * 14066 * @param {Player} player 14067 * The `Player` that this class should be attached to. 14068 * 14069 * @param {Object} [options] 14070 * The key/value store of player options. 14071 */ 14072 function CloseButton(player, options) { 14073 var _this; 14074 14075 _this = _Button.call(this, player, options) || this; 14076 14077 _this.controlText(options && options.controlText || _this.localize('Close')); 14078 14079 return _this; 14080 } 14081 /** 14082 * Builds the default DOM `className`. 14083 * 14084 * @return {string} 14085 * The DOM `className` for this object. 14086 */ 14087 14088 14089 var _proto = CloseButton.prototype; 14090 14091 _proto.buildCSSClass = function buildCSSClass() { 14092 return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this); 14093 } 14094 /** 14095 * This gets called when a `CloseButton` gets clicked. See 14096 * {@link ClickableComponent#handleClick} for more information on when 14097 * this will be triggered 14098 * 14099 * @param {EventTarget~Event} event 14100 * The `keydown`, `tap`, or `click` event that caused this function to be 14101 * called. 14102 * 14103 * @listens tap 14104 * @listens click 14105 * @fires CloseButton#close 14106 */ 14107 ; 14108 14109 _proto.handleClick = function handleClick(event) { 14110 /** 14111 * Triggered when the a `CloseButton` is clicked. 14112 * 14113 * @event CloseButton#close 14114 * @type {EventTarget~Event} 14115 * 14116 * @property {boolean} [bubbles=false] 14117 * set to false so that the close event does not 14118 * bubble up to parents if there is no listener 14119 */ 14120 this.trigger({ 14121 type: 'close', 14122 bubbles: false 14123 }); 14124 } 14125 /** 14126 * Event handler that is called when a `CloseButton` receives a 14127 * `keydown` event. 14128 * 14129 * By default, if the key is Esc, it will trigger a `click` event. 14130 * 14131 * @param {EventTarget~Event} event
14132 * The `keydown` event that caused this function to be called. 14133 * 14134 * @listens keydown 14135 */ 14136 ; 14137 14138 _proto.handleKeyDown = function handleKeyDown(event) { 14139 // Esc button will trigger `click` event 14140 if (keycode.isEventKey(event, 'Esc')) { 14141 event.preventDefault(); 14142 event.stopPropagation(); 14143 this.trigger('click'); 14144 } else { 14145 // Pass keypress handling up for unsupported keys 14146 _Button.prototype.handleKeyDown.call(this, event); 14147 } 14148 }; 14149 14150 return CloseButton; 14151 }(Button); 14152 14153 Component$1.registerComponent('CloseButton', CloseButton); 14154 14155 /** 14156 * Button to toggle between play and pause. 14157 * 14158 * @extends Button 14159 */ 14160 14161 var PlayToggle = /*#__PURE__*/function (_Button) { 14162 inheritsLoose(PlayToggle, _Button); 14163 14164 /** 14165 * Creates an instance of this class. 14166 * 14167 * @param {Player} player 14168 * The `Player` that this class should be attached to. 14169 * 14170 * @param {Object} [options={}] 14171 * The key/value store of player options. 14172 */ 14173 function PlayToggle(player, options) { 14174 var _this; 14175 14176 if (options === void 0) { 14177 options = {}; 14178 } 14179 14180 _this = _Button.call(this, player, options) || this; // show or hide replay icon 14181 14182 options.replay = options.replay === undefined || options.replay; 14183 14184 _this.on(player, 'play', function (e) { 14185 return _this.handlePlay(e); 14186 }); 14187 14188 _this.on(player, 'pause', function (e) { 14189 return _this.handlePause(e); 14190 }); 14191 14192 if (options.replay) { 14193 _this.on(player, 'ended', function (e) { 14194 return _this.handleEnded(e); 14195 }); 14196 } 14197 14198 return _this; 14199 } 14200 /** 14201 * Builds the default DOM `className`. 14202 * 14203 * @return {string} 14204 * The DOM `className` for this object. 14205 */ 14206 14207 14208 var _proto = PlayToggle.prototype; 14209 14210 _proto.buildCSSClass = function buildCSSClass() { 14211 return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this); 14212 } 14213 /** 14214 * This gets called when an `PlayToggle` is "clicked". See 14215 * {@link ClickableComponent} for more detailed information on what a click can be. 14216 * 14217 * @param {EventTarget~Event} [event] 14218 * The `keydown`, `tap`, or `click` event that caused this function to be 14219 * called. 14220 * 14221 * @listens tap 14222 * @listens click 14223 */ 14224 ; 14225 14226 _proto.handleClick = function handleClick(event) { 14227 if (this.player_.paused()) { 14228 silencePromise(this.player_.play()); 14229 } else { 14230 this.player_.pause(); 14231 } 14232 } 14233 /** 14234 * This gets called once after the video has ended and the user seeks so that 14235 * we can change the replay button back to a play button. 14236 * 14237 * @param {EventTarget~Event} [event] 14238 * The event that caused this function to run. 14239 * 14240 * @listens Player#seeked 14241 */ 14242 ; 14243 14244 _proto.handleSeeked = function handleSeeked(event) { 14245 this.removeClass('vjs-ended'); 14246 14247 if (this.player_.paused()) { 14248 this.handlePause(event); 14249 } else { 14250 this.handlePlay(event); 14251 } 14252 } 14253 /** 14254 * Add the vjs-playing class to the element so it can change appearance. 14255 * 14256 * @param {EventTarget~Event} [event] 14257 * The event that caused this function to run. 14258 * 14259 * @listens Player#play 14260 */ 14261 ; 14262 14263 _proto.handlePlay = function handlePlay(event) { 14264 this.removeClass('vjs-ended'); 14265 this.removeClass('vjs-paused'); 14266 this.addClass('vjs-playing'); // change the button text to "Pause" 14267 14268 this.controlText('Pause'); 14269 } 14270 /** 14271 * Add the vjs-paused class to the element so it can change appearance. 14272 * 14273 * @param {EventTarget~Event} [event] 14274 * The event that caused this function to run. 14275 * 14276 * @listens Player#pause 14277 */ 14278 ; 14279 14280 _proto.handlePause = function handlePause(event) { 14281 this.removeClass('vjs-playing'); 14282 this.addClass('vjs-paused'); // change the button text to "Play" 14283 14284 this.controlText('Play'); 14285 } 14286 /** 14287 * Add the vjs-ended class to the element so it can change appearance 14288 * 14289 * @param {EventTarget~Event} [event] 14290 * The event that caused this function to run. 14291 * 14292 * @listens Player#ended 14293 */ 14294 ; 14295 14296 _proto.handleEnded = function handleEnded(event) { 14297 var _this2 = this; 14298 14299 this.removeClass('vjs-playing'); 14300 this.addClass('vjs-ended'); // change the button text to "Replay" 14301 14302 this.controlText('Replay'); // on the next seek remove the replay button 14303 14304 this.one(this.player_, 'seeked', function (e) { 14305 return _this2.handleSeeked(e); 14306 }); 14307 }; 14308 14309 return PlayToggle; 14310 }(Button); 14311 /** 14312 * The text that should display over the `PlayToggle`s controls. Added for localization. 14313 * 14314 * @type {string} 14315 * @private 14316 */ 14317 14318 14319 PlayToggle.prototype.controlText_ = 'Play'; 14320 Component$1.registerComponent('PlayToggle', PlayToggle); 14321 14322 /** 14323 * @file format-time.js 14324 * @module format-time 14325 */ 14326 14327 /** 14328 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in 14329 * seconds) will force a number of leading zeros to cover the length of the 14330 * guide. 14331 * 14332 * @private 14333 * @param {number} seconds 14334 * Number of seconds to be turned into a string 14335 * 14336 * @param {number} guide 14337 * Number (in seconds) to model the string after 14338 * 14339 * @return {string} 14340 * Time formatted as H:MM:SS or M:SS 14341 */ 14342 var defaultImplementation = function defaultImplementation(seconds, guide) { 14343 seconds = seconds < 0 ? 0 : seconds; 14344 var s = Math.floor(seconds % 60); 14345 var m = Math.floor(seconds / 60 % 60); 14346 var h = Math.floor(seconds / 3600); 14347 var gm = Math.floor(guide / 60 % 60); 14348 var gh = Math.floor(guide / 3600); // handle invalid times 14349 14350 if (isNaN(seconds) || seconds === Infinity) { 14351 // '-' is false for all relational operators (e.g. <, >
14351=) so this setting 14352 // will add the minimum number of fields specified by the guide 14353 h = m = s = '-'; 14354 } // Check if we need to show hours 14355 14356 14357 h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero. 14358 // Always show at least one digit of minutes. 14359 14360 m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds 14361 14362 s = s < 10 ? '0' + s : s; 14363 return h + m + s; 14364 }; // Internal pointer to the current implementation. 14365 14366 14367 var implementation = defaultImplementation; 14368 /** 14369 * Replaces the default formatTime implementation with a custom implementation. 14370 * 14371 * @param {Function} customImplementation 14372 * A function which will be used in place of the default formatTime 14373 * implementation. Will receive the current time in seconds and the 14374 * guide (in seconds) as arguments. 14375 */ 14376 14377 function setFormatTime(customImplementation) { 14378 implementation = customImplementation; 14379 } 14380 /** 14381 * Resets formatTime to the default implementation. 14382 */ 14383 14384 function resetFormatTime() { 14385 implementation = defaultImplementation; 14386 } 14387 /** 14388 * Delegates to either the default time formatting function or a custom 14389 * function supplied via `setFormatTime`. 14390 * 14391 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a 14392 * guide (in seconds) will force a number of leading zeros to cover the 14393 * length of the guide. 14394 * 14395 * @static 14396 * @example formatTime(125, 600) === "02:05" 14397 * @param {number} seconds 14398 * Number of seconds to be turned into a string 14399 * 14400 * @param {number} guide 14401 * Number (in seconds) to model the string after 14402 * 14403 * @return {string} 14404 * Time formatted as H:MM:SS or M:SS 14405 */ 14406 14407 function formatTime(seconds, guide) { 14408 if (guide === void 0) { 14409 guide = seconds; 14410 } 14411 14412 return implementation(seconds, guide); 14413 } 14414 14415 /** 14416 * Displays time information about the video 14417 * 14418 * @extends Component 14419 */ 14420 14421 var TimeDisplay = /*#__PURE__*/function (_Component) { 14422 inheritsLoose(TimeDisplay, _Component); 14423 14424 /** 14425 * Creates an instance of this class. 14426 * 14427 * @param {Player} player 14428 * The `Player` that this class should be attached to. 14429 * 14430 * @param {Object} [options] 14431 * The key/value store of player options. 14432 */ 14433 function TimeDisplay(player, options) { 14434 var _this; 14435 14436 _this = _Component.call(this, player, options) || this; 14437 14438 _this.on(player, ['timeupdate', 'ended'], function (e) { 14439 return _this.updateContent(e); 14440 }); 14441 14442 _this.updateTextNode_(); 14443 14444 return _this; 14445 } 14446 /** 14447 * Create the `Component`'s DOM element 14448 * 14449 * @return {Element} 14450 * The element that was created. 14451 */ 14452 14453 14454 var _proto = TimeDisplay.prototype; 14455 14456 _proto.createEl = function createEl$1() { 14457 var className = this.buildCSSClass(); 14458 14459 var el = _Component.prototype.createEl.call(this, 'div', { 14460 className: className + " vjs-time-control vjs-control" 14461 }); 14462 14463 var span = createEl('span', { 14464 className: 'vjs-control-text', 14465 textContent: this.localize(this.labelText_) + "\xA0" 14466 }, { 14467 role: 'presentation' 14468 }); 14469 el.appendChild(span); 14470 this.contentEl_ = createEl('span', { 14471 className: className + "-display" 14472 }, { 14473 // tell screen readers not to automatically read the time as it changes 14474 'aria-live': 'off', 14475 // span elements have no implicit role, but some screen readers (notably VoiceOver) 14476 // treat them as a break between items in the DOM when using arrow keys 14477 // (or left-to-right swipes on iOS) to read contents of a page. Using 14478 // role='presentation' causes VoiceOver to NOT treat this span as a break. 14479 'role': 'presentation' 14480 }); 14481 el.appendChild(this.contentEl_); 14482 return el; 14483 }; 14484 14485 _proto.dispose = function dispose() { 14486 this.contentEl_ = null; 14487 this.textNode_ = null; 14488 14489 _Component.prototype.dispose.call(this); 14490 } 14491 /** 14492 * Updates the time display text node with a new time 14493 * 14494 * @param {number} [time=0] the time to update to 14495 * 14496 * @private 14497 */ 14498 ; 14499 14500 _proto.updateTextNode_ = function updateTextNode_(time) { 14501 var _this2 = this; 14502 14503 if (time === void 0) { 14504 time = 0; 14505 } 14506 14507 time = formatTime(time); 14508 14509 if (this.formattedTime_ === time) { 14510 return; 14511 } 14512 14513 this.formattedTime_ = time; 14514 this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', function () { 14515 if (!_this2.contentEl_) { 14516 return; 14517 } 14518 14519 var oldNode = _this2.textNode_; 14520 14521 if (oldNode && _this2.contentEl_.firstChild !== oldNode) { 14522 oldNode = null; 14523 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.'); 14524 } 14525 14526 _this2.textNode_ = document_1.createTextNode(_this2.formattedTime_); 14527 14528 if (!_this2.textNode_) { 14529 return; 14530 } 14531 14532 if (oldNode) { 14533 _this2.contentEl_.replaceChild(_this2.textNode_, oldNode); 14534 } else { 14535 _this2.contentEl_.appendChild(_this2.textNode_); 14536 } 14537 }); 14538 } 14539 /** 14540 * To be filled out in the child class, should update the displayed time 14541 * in accordance with the fact that the current time has changed. 14542 * 14543 * @param {EventTarget~Event} [event] 14544 * The `timeupdate` event that caused this to run. 14545 * 14546 * @listens Player#timeupdate 14547 */ 14548 ; 14549 14550 _proto.updateContent = function updateContent(event) {}; 14551 14552 return TimeDisplay; 14553 }(Component$1); 14554 /** 14555 * The text that is added to the `TimeDisplay` for screen reader users. 14556 * 14557 * @type {string} 14558 * @private 14559 */ 14560 14561 14562 TimeDisplay.prototype.labelText_ = 'Time'; 14563 /** 14564 * The text that should display over the `TimeDisplay`s controls. Added to for localization. 14565 * 14566 * @type {string} 14567 * @private 14568 *
14569 * @deprecated in v7; controlText_ is not used in non-active display Components 14570 */ 14571 14572 TimeDisplay.prototype.controlText_ = 'Time'; 14573 Component$1.registerComponent('TimeDisplay', TimeDisplay); 14574 14575 /** 14576 * Displays the current time 14577 * 14578 * @extends Component 14579 */ 14580 14581 var CurrentTimeDisplay = /*#__PURE__*/function (_TimeDisplay) { 14582 inheritsLoose(CurrentTimeDisplay, _TimeDisplay); 14583 14584 function CurrentTimeDisplay() { 14585 return _TimeDisplay.apply(this, arguments) || this; 14586 } 14587 14588 var _proto = CurrentTimeDisplay.prototype; 14589 14590 /** 14591 * Builds the default DOM `className`. 14592 * 14593 * @return {string} 14594 * The DOM `className` for this object. 14595 */ 14596 _proto.buildCSSClass = function buildCSSClass() { 14597 return 'vjs-current-time'; 14598 } 14599 /** 14600 * Update current time display 14601 * 14602 * @param {EventTarget~Event} [event] 14603 * The `timeupdate` event that caused this function to run. 14604 * 14605 * @listens Player#timeupdate 14606 */ 14607 ; 14608 14609 _proto.updateContent = function updateContent(event) { 14610 // Allows for smooth scrubbing, when player can't keep up. 14611 var time; 14612 14613 if (this.player_.ended()) { 14614 time = this.player_.duration(); 14615 } else { 14616 time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 14617 } 14618 14619 this.updateTextNode_(time); 14620 }; 14621 14622 return CurrentTimeDisplay; 14623 }(TimeDisplay); 14624 /** 14625 * The text that is added to the `CurrentTimeDisplay` for screen reader users. 14626 * 14627 * @type {string} 14628 * @private 14629 */ 14630 14631 14632 CurrentTimeDisplay.prototype.labelText_ = 'Current Time'; 14633 /** 14634 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization. 14635 * 14636 * @type {string} 14637 * @private 14638 * 14639 * @deprecated in v7; controlText_ is not used in non-active display Components 14640 */ 14641 14642 CurrentTimeDisplay.prototype.controlText_ = 'Current Time'; 14643 Component$1.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); 14644 14645 /** 14646 * Displays the duration 14647 * 14648 * @extends Component 14649 */ 14650 14651 var DurationDisplay = /*#__PURE__*/function (_TimeDisplay) { 14652 inheritsLoose(DurationDisplay, _TimeDisplay); 14653 14654 /** 14655 * Creates an instance of this class. 14656 * 14657 * @param {Player} player 14658 * The `Player` that this class should be attached to. 14659 * 14660 * @param {Object} [options] 14661 * The key/value store of player options. 14662 */ 14663 function DurationDisplay(player, options) { 14664 var _this; 14665 14666 _this = _TimeDisplay.call(this, player, options) || this; 14667 14668 var updateContent = function updateContent(e) { 14669 return _this.updateContent(e); 14670 }; // we do not want to/need to throttle duration changes, 14671 // as they should always display the changed duration as 14672 // it has changed 14673 14674 14675 _this.on(player, 'durationchange', updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded, 14676 // but the durationchange on the user agent will not fire. 14677 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 14678 14679 14680 _this.on(player, 'loadstart', updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those 14681 // listeners could have broken dependent applications/libraries. These 14682 // can likely be removed for 7.0. 14683 14684 14685 _this.on(player, 'loadedmetadata', updateContent); 14686 14687 return _this; 14688 } 14689 /** 14690 * Builds the default DOM `className`. 14691 * 14692 * @return {string} 14693 * The DOM `className` for this object. 14694 */ 14695 14696 14697 var _proto = DurationDisplay.prototype; 14698 14699 _proto.buildCSSClass = function buildCSSClass() { 14700 return 'vjs-duration'; 14701 } 14702 /** 14703 * Update duration time display. 14704 * 14705 * @param {EventTarget~Event} [event] 14706 * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused 14707 * this function to be called. 14708 * 14709 * @listens Player#durationchange 14710 * @listens Player#timeupdate 14711 * @listens Player#loadedmetadata 14712 */ 14713 ; 14714 14715 _proto.updateContent = function updateContent(event) { 14716 var duration = this.player_.duration(); 14717 this.updateTextNode_(duration); 14718 }; 14719 14720 return DurationDisplay; 14721 }(TimeDisplay); 14722 /** 14723 * The text that is added to the `DurationDisplay` for screen reader users. 14724 * 14725 * @type {string} 14726 * @private 14727 */ 14728 14729 14730 DurationDisplay.prototype.labelText_ = 'Duration'; 14731 /** 14732 * The text that should display over the `DurationDisplay`s controls. Added to for localization. 14733 * 14734 * @type {string} 14735 * @private 14736 *
14737 * @deprecated in v7; controlText_ is not used in non-active display Components 14738 */ 14739 14740 DurationDisplay.prototype.controlText_ = 'Duration'; 14741 Component$1.registerComponent('DurationDisplay', DurationDisplay); 14742 14743 /** 14744 * The separator between the current time and duration. 14745 * Can be hidden if it's not needed in the design. 14746 * 14747 * @extends Component 14748 */ 14749 14750 var TimeDivider = /*#__PURE__*/function (_Component) { 14751 inheritsLoose(TimeDivider, _Component); 14752 14753 function TimeDivider() { 14754 return _Component.apply(this, arguments) || this; 14755 } 14756 14757 var _proto = TimeDivider.prototype; 14758 14759 /** 14760 * Create the component's DOM element 14761 * 14762 * @return {Element} 14763 * The element that was created. 14764 */ 14765 _proto.createEl = function createEl() { 14766 var el = _Component.prototype.createEl.call(this, 'div', { 14767 className: 'vjs-time-control vjs-time-divider' 14768 }, { 14769 // this element and its contents can be hidden from assistive techs since 14770 // it is made extraneous by the announcement of the control text 14771 // for the current time and duration displays 14772 'aria-hidden': true 14773 }); 14774 14775 var div = _Component.prototype.createEl.call(this, 'div'); 14776 14777 var span = _Component.prototype.createEl.call(this, 'span', { 14778 textContent: '/' 14779 }); 14780 14781 div.appendChild(span); 14782 el.appendChild(div); 14783 return el; 14784 }; 14785 14786 return TimeDivider; 14787 }(Component$1); 14788 14789 Component$1.registerComponent('TimeDivider', TimeDivider); 14790 14791 /** 14792 * Displays the time left in the video 14793 * 14794 * @extends Component 14795 */ 14796 14797 var RemainingTimeDisplay = /*#__PURE__*/function (_TimeDisplay) { 14798 inheritsLoose(RemainingTimeDisplay, _TimeDisplay); 14799 14800 /** 14801 * Creates an instance of this class. 14802 * 14803 * @param {Player} player 14804 * The `Player` that this class should be attached to. 14805 * 14806 * @param {Object} [options] 14807 * The key/value store of player options. 14808 */ 14809 function RemainingTimeDisplay(player, options) { 14810 var _this; 14811 14812 _this = _TimeDisplay.call(this, player, options) || this; 14813 14814 _this.on(player, 'durationchange', function (e) { 14815 return _this.updateContent(e); 14816 }); 14817 14818 return _this; 14819 } 14820 /** 14821 * Builds the default DOM `className`. 14822 * 14823 * @return {string} 14824 * The DOM `className` for this object. 14825 */ 14826 14827 14828 var _proto = RemainingTimeDisplay.prototype; 14829 14830 _proto.buildCSSClass = function buildCSSClass() { 14831 return 'vjs-remaining-time'; 14832 } 14833 /** 14834 * Create the `Component`'s DOM element with the "minus" characted prepend to the time 14835 * 14836 * @return {Element} 14837 * The element that was created. 14838 */ 14839 ; 14840 14841 _proto.createEl = function createEl$1() { 14842 var el = _TimeDisplay.prototype.createEl.call(this); 14843 14844 el.insertBefore(createEl('span', {}, { 14845 'aria-hidden': true 14846 }, '-'), this.contentEl_); 14847 return el; 14848 } 14849 /** 14850 * Update remaining time display. 14851 * 14852 * @param {EventTarget~Event} [event] 14853 * The `timeupdate` or `durationchange` event that caused this to run. 14854 * 14855 * @listens Player#timeupdate 14856 * @listens Player#durationchange 14857 */ 14858 ; 14859 14860 _proto.updateContent = function updateContent(event) { 14861 if (typeof this.player_.duration() !== 'number') { 14862 return; 14863 } 14864 14865 var time; // @deprecated We should only use remainingTimeDisplay 14866 // as of video.js 7 14867 14868 if (this.player_.ended()) { 14869 time = 0; 14870 } else if (this.player_.remainingTimeDisplay) { 14871 time = this.player_.remainingTimeDisplay(); 14872 } else { 14873 time = this.player_.remainingTime(); 14874 } 14875 14876 this.updateTextNode_(time); 14877 }; 14878 14879 return RemainingTimeDisplay; 14880 }(TimeDisplay); 14881 /** 14882 * The text that is added to the `RemainingTimeDisplay` for screen reader users. 14883 * 14884 * @type {string} 14885 * @private 14886 */ 14887 14888 14889 RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time'; 14890 /** 14891 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization. 14892 * 14893 * @type {string} 14894 * @private 14895 *
14896 * @deprecated in v7; controlText_ is not used in non-active display Components 14897 */ 14898 14899 RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time'; 14900 Component$1.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); 14901 14902 /** 14903 * Displays the live indicator when duration is Infinity. 14904 * 14905 * @extends Component 14906 */ 14907 14908 var LiveDisplay = /*#__PURE__*/function (_Component) { 14909 inheritsLoose(LiveDisplay, _Component); 14910 14911 /** 14912 * Creates an instance of this class. 14913 * 14914 * @param {Player} player 14915 * The `Player` that this class should be attached to. 14916 * 14917 * @param {Object} [options] 14918 * The key/value store of player options. 14919 */ 14920 function LiveDisplay(player, options) { 14921 var _this; 14922 14923 _this = _Component.call(this, player, options) || this; 14924 14925 _this.updateShowing(); 14926 14927 _this.on(_this.player(), 'durationchange', function (e) { 14928 return _this.updateShowing(e); 14929 }); 14930 14931 return _this; 14932 } 14933 /** 14934 * Create the `Component`'s DOM element 14935 * 14936 * @return {Element} 14937 * The element that was created. 14938 */ 14939 14940 14941 var _proto = LiveDisplay.prototype; 14942 14943 _proto.createEl = function createEl$1() { 14944 var el = _Component.prototype.createEl.call(this, 'div', { 14945 className: 'vjs-live-control vjs-control' 14946 }); 14947 14948 this.contentEl_ = createEl('div', { 14949 className: 'vjs-live-display' 14950 }, { 14951 'aria-live': 'off' 14952 }); 14953 this.contentEl_.appendChild(createEl('span', { 14954 className: 'vjs-control-text', 14955 textContent: this.localize('Stream Type') + "\xA0" 14956 })); 14957 this.contentEl_.appendChild(document_1.createTextNode(this.localize('LIVE'))); 14958 el.appendChild(this.contentEl_); 14959 return el; 14960 }; 14961 14962 _proto.dispose = function dispose() { 14963 this.contentEl_ = null; 14964 14965 _Component.prototype.dispose.call(this); 14966 } 14967 /** 14968 * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide 14969 * it accordingly 14970 * 14971 * @param {EventTarget~Event} [event] 14972 * The {@link Player#durationchange} event that caused this function to run. 14973 * 14974 * @listens Player#durationchange 14975 */ 14976 ; 14977 14978 _proto.updateShowing = function updateShowing(event) { 14979 if (this.player().duration() === Infinity) { 14980 this.show(); 14981 } else { 14982 this.hide(); 14983 } 14984 }; 14985 14986 return LiveDisplay; 14987 }(Component$1); 14988 14989 Component$1.registerComponent('LiveDisplay', LiveDisplay); 14990 14991 /** 14992 * Displays the live indicator when duration is Infinity. 14993 * 14994 * @extends Component 14995 */ 14996 14997 var SeekToLive = /*#__PURE__*/function (_Button) { 14998 inheritsLoose(SeekToLive, _Button); 14999 15000 /** 15001 * Creates an instance of this class. 15002 * 15003 * @param {Player} player 15004 * The `Player` that this class should be attached to. 15005 * 15006 * @param {Object} [options] 15007 * The key/value store of player options. 15008 */ 15009 function SeekToLive(player, options) { 15010 var _this; 15011 15012 _this = _Button.call(this, player, options) || this; 15013 15014 _this.updateLiveEdgeStatus(); 15015 15016 if (_this.player_.liveTracker) { 15017 _this.updateLiveEdgeStatusHandler_ = function (e) { 15018 return _this.updateLiveEdgeStatus(e); 15019 }; 15020 15021 _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatusHandler_); 15022 } 15023 15024 return _this; 15025 } 15026 /** 15027 * Create the `Component`'s DOM element 15028 * 15029 * @return {Element} 15030 * The element that was created. 15031 */ 15032 15033 15034 var _proto = SeekToLive.prototype; 15035 15036 _proto.createEl = function createEl$1() { 15037 var el = _Button.prototype.createEl.call(this, 'button', { 15038 className: 'vjs-seek-to-live-control vjs-control' 15039 }); 15040 15041 this.textEl_ = createEl('span', { 15042 className: 'vjs-seek-to-live-text',
15043 textContent: this.localize('LIVE') 15044 }, { 15045 'aria-hidden': 'true' 15046 }); 15047 el.appendChild(this.textEl_); 15048 return el; 15049 } 15050 /** 15051 * Update the state of this button if we are at the live edge 15052 * or not 15053 */ 15054 ; 15055 15056 _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus() { 15057 // default to live edge 15058 if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) { 15059 this.setAttribute('aria-disabled', true); 15060 this.addClass('vjs-at-live-edge'); 15061 this.controlText('Seek to live, currently playing live'); 15062 } else { 15063 this.setAttribute('aria-disabled', false); 15064 this.removeClass('vjs-at-live-edge'); 15065 this.controlText('Seek to live, currently behind live'); 15066 } 15067 } 15068 /** 15069 * On click bring us as near to the live point as possible. 15070 * This requires that we wait for the next `live-seekable-change` 15071 * event which will happen every segment length seconds. 15072 */ 15073 ; 15074 15075 _proto.handleClick = function handleClick() { 15076 this.player_.liveTracker.seekToLiveEdge(); 15077 } 15078 /** 15079 * Dispose of the element and stop tracking 15080 */ 15081 ; 15082 15083 _proto.dispose = function dispose() { 15084 if (this.player_.liveTracker) { 15085 this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatusHandler_); 15086 } 15087 15088 this.textEl_ = null; 15089 15090 _Button.prototype.dispose.call(this); 15091 }; 15092 15093 return SeekToLive; 15094 }(Button); 15095 15096 SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live'; 15097 Component$1.registerComponent('SeekToLive', SeekToLive); 15098 15099 /** 15100 * Keep a number between a min and a max value 15101 * 15102 * @param {number} number 15103 * The number to clamp 15104 * 15105 * @param {number} min 15106 * The minimum value 15107 * @param {number} max 15108 * The maximum value 15109 * 15110 * @return {number} 15111 * the clamped number 15112 */ 15113 var clamp = function clamp(number, min, max) { 15114 number = Number(number); 15115 return Math.min(max, Math.max(min, isNaN(number) ? min : number)); 15116 }; 15117 15118 /** 15119 * The base functionality for a slider. Can be vertical or horizontal. 15120 * For instance the volume bar or the seek bar on a video is a slider. 15121 * 15122 * @extends Component 15123 */ 15124 15125 var Slider = /*#__PURE__*/function (_Component) { 15126 inheritsLoose(Slider, _Component); 15127 15128 /** 15129 * Create an instance of this class 15130 * 15131 * @param {Player} player 15132 * The `Player` that this class should be attached to. 15133 * 15134 * @param {Object} [options] 15135 * The key/value store of player options. 15136 */ 15137 function Slider(player, options) { 15138 var _this; 15139 15140 _this = _Component.call(this, player, options) || this; 15141 15142 _this.handleMouseDown_ = function (e) { 15143 return _this.handleMouseDown(e); 15144 }; 15145 15146 _this.handleMouseUp_ = function (e) { 15147 return _this.handleMouseUp(e); 15148 }; 15149 15150 _this.handleKeyDown_ = function (e) { 15151 return _this.handleKeyDown(e); 15152 }; 15153 15154 _this.handleClick_ = function (e) { 15155 return _this.handleClick(e); 15156 }; 15157 15158 _this.handleMouseMove_ = function (e) { 15159 return _this.handleMouseMove(e); 15160 }; 15161 15162 _this.update_ = function (e) { 15163 return _this.update(e); 15164 }; // Set property names to bar to match with the child Slider class is looking for 15165 15166 15167 _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type 15168 15169 _this.vertical(!!_this.options_.vertical); 15170 15171 _this.enable(); 15172 15173 return _this; 15174 } 15175 /** 15176 * Are controls are currently enabled for this slider or not. 15177 * 15178 * @return {boolean} 15179 * true if controls are enabled, false otherwise 15180 */ 15181 15182 15183 var _proto = Slider.prototype; 15184 15185 _proto.enabled = function enabled() { 15186 return this.enabled_; 15187 } 15188 /** 15189 * Enable controls for this slider if they are disabled 15190 */ 15191 ; 15192 15193 _proto.enable = function enable() { 15194 if (this.enabled()) { 15195 return; 15196 } 15197 15198 this.on('mousedown', this.handleMouseDown_); 15199 this.on('touchstart', this.handleMouseDown_); 15200 this.on('keydown', this.handleKeyDown_); 15201 this.on('click', this.handleClick_); // TODO: deprecated, controlsvisible does not seem to be fired 15202 15203 this.on(this.player_, 'controlsvisible', this.update); 15204 15205 if (this.playerEvent) { 15206 this.on(this.player_, this.playerEvent, this.update); 15207 } 15208 15209 this.removeClass('disabled'); 15210 this.setAttribute('tabindex', 0); 15211 this.enabled_ = true; 15212 } 15213 /** 15214 * Disable controls for this slider if they are enabled 15215 */ 15216 ; 15217 15218 _proto.disable = function disable() { 15219 if (!this.enabled()) { 15220 return; 15221 } 15222 15223 var doc = this.bar.el_.ownerDocument; 15224 this.off('mousedown', this.handleMouseDown_); 15225 this.off('touchstart', this.handleMouseDown_); 15226 this.off('keydown', this.handleKeyDown_); 15227 this.off('click', this.handleClick_); 15228 this.off(this.player_, 'controlsvisible', this.update_); 15229 this.off(doc, 'mousemove', this.handleMouseMove_); 15230 this.off(doc, 'mouseup', this.handleMouseUp_); 15231 this.off(doc, 'touchmove', this.handleMouseMove_); 15232 this.off(doc, 'touchend', this.handleMouseUp_); 15233 this.removeAttribute('tabindex'); 15234 this.addClass('disabled'); 15235 15236 if (this.playerEvent) { 15237 this.off(this.player_, this.playerEvent, this.update); 15238 } 15239 15240 this.enabled_ = false; 15241 } 15242 /** 15243 * Create the `Slider`s DOM element. 15244 * 15245 * @param {string} type 15246 * Type of element to create. 15247 * 15248 * @param {Object} [props={}] 15249 * List of properties in Object form. 15250 * 15251 * @param {Object} [attributes={}] 15252 * list of attributes in Object form. 15253 * 15254 * @return {Element} 15255 * The element that gets created. 15256 */ 15257 ; 15258 15259 _proto.createEl = function createEl(type, props, attributes) { 15260 if (props === void 0) { 15261 props = {}; 15262 } 15263 15264 if (attributes === void 0) { 15265 attributes = {}; 15266 } 15267 15268 // Add the slider element class to all sub classes 15269 props.className = props.className + ' vjs-slider'; 15270 props = assign({ 15271 tabIndex: 0 15272 }, props); 15273 attributes = assign({ 15274 'role': 'slider', 15275 'aria-valuenow': 0, 15276 'aria-valuemin': 0, 15277 'aria-valuemax': 100, 15278 'tabIndex': 0 15279 }, attributes); 15280 return _Component.prototype.createEl.call(this, type, props, attributes); 15281 } 15282 /** 15283 * Handle `mousedown` or `touchstart` events on the `Slider`. 15284 * 15285 * @param {EventTarget~Event} event 15286 * `mousedown` or `touchstart` event that triggered this function 15287 * 15288 * @listens mousedown 15289 * @listens touchstart 15290 * @fires Slider#slideractive 15291 */ 15292 ; 15293 15294 _proto.handleMouseDown = function handleMouseDown(event) { 15295 var doc = this.bar.el_.ownerDocument; 15296 15297 if (event.type === 'mousedown') { 15298 event.preventDefault(); 15299 } // Do not call preventDefault() on touchstart in Chrome 15300 // to avoid console warnings. Use a 'touch-action: none' style 15301 // instead to prevent unintented scrolling. 15302 // https://developers.google.com/web/updates/2017/01/scrolling-intervention 15303 15304 15305 if (event.type === 'touchstart' && !IS_CHROME) { 15306 event.preventDefault(); 15307 } 15308 15309 blockTextSelection(); 15310 this.addClass('vjs-sliding'); 15311 /**
15312 * Triggered when the slider is in an active state 15313 * 15314 * @event Slider#slideractive 15315 * @type {EventTarget~Event} 15316 */ 15317 15318 this.trigger('slideractive'); 15319 this.on(doc, 'mousemove', this.handleMouseMove_); 15320 this.on(doc, 'mouseup', this.handleMouseUp_); 15321 this.on(doc, 'touchmove', this.handleMouseMove_); 15322 this.on(doc, 'touchend', this.handleMouseUp_); 15323 this.handleMouseMove(event); 15324 } 15325 /** 15326 * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. 15327 * The `mousemove` and `touchmove` events will only only trigger this function during 15328 * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and 15329 * {@link Slider#handleMouseUp}. 15330 * 15331 * @param {EventTarget~Event} event 15332 * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered 15333 * this function 15334 * 15335 * @listens mousemove 15336 * @listens touchmove 15337 */ 15338 ; 15339 15340 _proto.handleMouseMove = function handleMouseMove(event) {} 15341 /** 15342 * Handle `mouseup` or `touchend` events on the `Slider`. 15343 * 15344 * @param {EventTarget~Event} event 15345 * `mouseup` or `touchend` event that triggered this function. 15346 * 15347 * @listens touchend 15348 * @listens mouseup 15349 * @fires Slider#sliderinactive 15350 */ 15351 ; 15352 15353 _proto.handleMouseUp = function handleMouseUp() { 15354 var doc = this.bar.el_.ownerDocument; 15355 unblockTextSelection(); 15356 this.removeClass('vjs-sliding'); 15357 /** 15358 * Triggered when the slider is no longer in an active state. 15359 * 15360 * @event Slider#sliderinactive 15361 * @type {EventTarget~Event} 15362 */ 15363 15364 this.trigger('sliderinactive'); 15365 this.off(doc, 'mousemove', this.handleMouseMove_); 15366 this.off(doc, 'mouseup', this.handleMouseUp_); 15367 this.off(doc, 'touchmove', this.handleMouseMove_); 15368 this.off(doc, 'touchend', this.handleMouseUp_); 15369 this.update(); 15370 } 15371 /** 15372 * Update the progress bar of the `Slider`. 15373 * 15374 * @return {number} 15375 * The percentage of progress the progress bar represents as a 15376 * number from 0 to 1. 15377 */ 15378 ; 15379 15380 _proto.update = function update() { 15381 var _this2 = this; 15382 15383 // In VolumeBar init we have a setTimeout for update that pops and update 15384 // to the end of the execution stack. The player is destroyed before then 15385 // update will cause an error 15386 // If there's no bar... 15387 if (!this.el_ || !this.bar) { 15388 return; 15389 } // clamp progress between 0 and 1 15390 // and only round to four decimal places, as we round to two below 15391 15392 15393 var progress = this.getProgress(); 15394 15395 if (progress === this.progress_) { 15396 return progress; 15397 } 15398 15399 this.progress_ = progress; 15400 this.requestNamedAnimationFrame('Slider#update', function () { 15401 // Set the new bar width or height 15402 var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value 15403 15404 _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%'; 15405 }); 15406 return progress; 15407 } 15408 /** 15409 * Get the percentage of the bar that should be filled 15410 * but clamped and rounded. 15411 * 15412 * @return {number} 15413 * percentage filled that the slider is 15414 */ 15415 ; 15416 15417 _proto.getProgress = function getProgress() { 15418 return Number(clamp(this.getPercent(), 0, 1).toFixed(4)); 15419 } 15420 /** 15421 * Calculate distance for slider 15422 * 15423 * @param {EventTarget~Event} event 15424 * The event that caused this function to run. 15425 * 15426 * @return {number} 15427 * The current position of the Slider. 15428 * - position.x for vertical `Slider`s 15429 * - position.y for horizontal `Slider`s 15430 */ 15431 ; 15432 15433 _proto.calculateDistance = function calculateDistance(event) { 15434 var position = getPointerPosition(this.el_, event); 15435 15436 if (this.vertical()) { 15437 return position.y; 15438 } 15439 15440 return position.x; 15441 } 15442 /** 15443 * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down 15444 * arrow keys. This function will only be called when the slider has focus. See 15445 * {@link Slider#handleFocus} and {@link Slider#handleBlur}. 15446 * 15447 * @param {EventTarget~Event} event 15448 * the `keydown` event that caused this function to run. 15449 * 15450 * @listens keydown 15451 */ 15452 ; 15453 15454 _proto.handleKeyDown = function handleKeyDown(event) { 15455 // Left and Down Arrows 15456 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 15457 event.preventDefault(); 15458 event.stopPropagation(); 15459 this.stepBack(); // Up and Right Arrows 15460 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 15461 event.preventDefault(); 15462 event.stopPropagation(); 15463 this.stepForward(); 15464 } else { 15465 // Pass keydown handling up for unsupported keys 15466 _Component.prototype.handleKeyDown.call(this, event); 15467 } 15468 } 15469 /** 15470 * Listener for click events on slider, used to prevent clicks 15471 * from bubbling up to parent elements like button menus. 15472 * 15473 * @param {Object} event 15474 * Event that caused this object to run 15475 */ 15476 ; 15477 15478 _proto.handleClick = function handleClick(event) { 15479 event.stopPropagation(); 15480 event.preventDefault(); 15481 } 15482 /** 15483 * Get/set if slider is horizontal for vertical 15484 * 15485 * @param {boolean} [bool] 15486 * - true if slider is vertical, 15487 * - false is horizontal 15488 * 15489 * @return {boolean} 15490 * - true if slider is vertical, and getting 15491 * - false if the slider is horizontal, and getting 15492 */ 15493 ; 15494 15495 _proto.vertical = function vertical(bool) { 15496 if (bool === undefined) { 15497 return this.vertical_ || false; 15498 } 15499 15500 this.vertical_ = !!bool; 15501 15502 if (this.vertical_) { 15503 this.addClass('vjs-slider-vertical'); 15504 } else { 15505 this.addClass('vjs-slider-horizontal'); 15506 } 15507 }; 15508 15509 return Slider; 15510 }(Component$1); 15511 15512 Component$1.registerComponent('Slider', Slider); 15513 15514 var percentify = function percentify(time, end) { 15515 return clamp(time / end * 100, 0, 100).toFixed(2) + '%'; 15516 }; 15517 /** 15518 * Shows loading progress 15519 * 15520 * @extends Component 15521 */ 15522 15523 15524 var LoadProgressBar = /*#__PURE__*/function (_Component) { 15525 inheritsLoose(LoadProgressBar, _Component); 15526 15527 /** 15528 * Creates an instance of this class. 15529 * 15530 * @param {Player} player 15531 * The `Player` that this class should be attached to. 15532 * 15533 * @param {Object} [options] 15534 * The key/value store of player options. 15535 */ 15536 function LoadProgressBar(player, options) { 15537 var _this; 15538 15539 _this = _Component.call(this, player, options) || this; 15540 _this.partEls_ = []; 15541 15542 _this.on(player, 'progress', function (e) { 15543 return _this.update(e); 15544 }); 15545 15546 return _this; 15547 } 15548 /** 15549 * Create the `Component`'s DOM element 15550 * 15551 * @return {Element} 15552 * The element that was created. 15553 */ 15554 15555 15556 var _proto = LoadProgressBar.prototype; 15557 15558 _proto.createEl = function createEl$1() { 15559 var el = _Component.prototype.createEl.call(this, 'div', { 15560 className: 'vjs-load-progress' 15561 }); 15562 15563 var wrapper = createEl('span', { 15564 className: 'vjs-control-text' 15565 }); 15566 var loadedText = createEl('span', { 15567 textContent: this.localize('Loaded') 15568 }); 15569 var separator = document_1.createTextNode(': '); 15570 this.percentageEl_ = createEl('span', { 15571 className: 'vjs-control-text-loaded-percentage', 15572 textContent: '0%' 15573 }); 15574 el.appendChild(wrapper); 15575 wrapper.appendChild(loadedText); 15576 wrapper.appendChild(separator); 15577 wrapper.appendChild(this.percentageEl_); 15578 return el; 15579 }; 15580 15581 _proto.dispose = function dispose() { 15582 this.partEls_ = null; 15583 this.percentageEl_ = null; 15584 15585 _Component.prototype.dispose.call(this); 15586 } 15587 /** 15588 * Update progress bar 15589 * 15590 * @param {EventTarget~Event} [event] 15591 * The `progress` event that caused this function to run. 15592 * 15593 * @listens Player#progress 15594 */ 15595 ; 15596 15597 _proto.update = function update(event) { 15598 var _this2 = this; 15599 15600 this.requestNamedAnimationFrame('LoadProgressBar#update', function () { 15601 var liveTracker = _this2.player_.liveTracker; 15602 15603 var buffered = _this2.player_.buffered(); 15604 15605 var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration(); 15606 15607 var bufferedEnd = _this2.player_.bufferedEnd(); 15608 15609 var children = _this2.partEls_; 15610 var percent = percentify(bufferedEnd, duration); 15611 15612 if (_this2.percent_ !== percent) { 15613 // update the width of the progress bar 15614 _this2.el_.style.width = percent; // update the control-text 15615 15616 textContent(_this2.percentageEl_, percent); 15617 _this2.percent_ = percent; 15618 } // add child elements to represent the individual buffered time ranges 15619 15620 15621 for (var i = 0; i < buffered.length; i++) { 15622 var start = buffered.start(i); 15623 var end = buffered.end(i); 15624 var part = children[i]; 15625 15626 if (!part) { 15627 part = _this2.el_.appendChild(createEl()); 15628 children[i] = part; 15629 } // only update if changed 15630 15631 15632 if (part.dataset.start === start && part.dataset.end === end) { 15633 continue; 15634 } 15635 15636 part.dataset.start = start; 15637 part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd) 15638 15639 part.style.left = percentify(start, bufferedEnd); 15640 part.style.width = percentify(end - start, bufferedEnd); 15641 } // remove unused buffered range elements 15642 15643 15644 for (var _i = children.length; _i > buffered.length; _i--) { 15645 _this2.el_.removeChild(children[_i - 1]); 15646 } 15647 15648 children.length = buffered.length; 15649 }); 15650 }; 15651 15652 return LoadProgressBar; 15653 }(Component$1); 15654 15655 Component$1.registerComponent('LoadProgressBar', LoadProgressBar); 15656 15657 /** 15658 * Time tooltips display a time above the progress bar. 15659 * 15660 * @extends Component 15661 */ 15662 15663 var TimeTooltip = /*#__PURE__*/function (_Component) { 15664 inheritsLoose(TimeTooltip, _Component); 15665 15666 /** 15667 * Creates an instance of this class. 15668 * 15669 * @param {Player} player 15670 * The {@link Player} that this class should be attached to. 15671 * 15672 * @param {Object} [options] 15673 * The key/value store of player options. 15674 */ 15675 function TimeTooltip(player, options) { 15676 var _this; 15677 15678 _this = _Component.call(this, player, options) || this; 15679 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15680 return _this; 15681 } 15682 /** 15683 * Create the time tooltip DOM element 15684 * 15685 * @return {Element} 15686 * The element that was created. 15687 */ 15688 15689 15690 var _proto = TimeTooltip.prototype; 15691 15692 _proto.createEl = function createEl() { 15693 return _Component.prototype.createEl.call(this, 'div', { 15694 className: 'vjs-time-tooltip' 15695 }, { 15696 'aria-hidden': 'true' 15697 }); 15698 } 15699 /** 15700 * Updates the position of the time tooltip relative to the `SeekBar`. 15701 * 15702 * @param {Object} seekBarRect 15703 * The `ClientRect` for the {@link SeekBar} element. 15704 * 15705 * @param {number} seekBarPoint 15706 * A number from 0 to 1, representing a horizontal reference point 15707 * from the left edge of the {@link SeekBar} 15708 */ 15709 ; 15710 15711 _proto.update = function update(seekBarRect, seekBarPoint, content) { 15712 var tooltipRect = findPosition(this.el_); 15713 var playerRect = getBoundingClientRect(this.player_.el()); 15714 var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available 15715 // for example, if the player isn't in the DOM for testing 15716 15717 if (!playerRect || !tooltipRect) { 15718 return; 15719 } // This is the space left of the `seekBarPoint` available within the bounds 15720 // of the player. We calculate any gap between the left edge of the player 15721 // and the left edge of the `SeekBar` and add the number of pixels in the 15722 // `SeekBar` before hitting the `seekBarPoint` 15723 15724 15725 var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds 15726 // of the player. We calculate the number of pixels from the `seekBarPoint` 15727 // to the right edge of the `SeekBar` and add to that any gap between the 15728 // right edge of the `SeekBar` and the player. 15729 15730 var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled 15731 // further to the right to center it over the `seekBarPoint`. 15732 15733 var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on 15734 // the results of the space calculations above. 15735 15736 if (spaceLeftOfPoint < pullTooltipBy) { 15737 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 15738 }
15738 else if (spaceRightOfPoint < pullTooltipBy) { 15739 pullTooltipBy = spaceRightOfPoint; 15740 } // Due to the imprecision of decimal/ratio based calculations and varying 15741 // rounding behaviors, there are cases where the spacing adjustment is off 15742 // by a pixel or two. This adds insurance to these calculations. 15743 15744 15745 if (pullTooltipBy < 0) { 15746 pullTooltipBy = 0; 15747 } else if (pullTooltipBy > tooltipRect.width) { 15748 pullTooltipBy = tooltipRect.width; 15749 } // prevent small width fluctuations within 0.4px from 15750 // changing the value below. 15751 // This really helps for live to prevent the play 15752 // progress time tooltip from jittering 15753 15754 15755 pullTooltipBy = Math.round(pullTooltipBy); 15756 this.el_.style.right = "-" + pullTooltipBy + "px"; 15757 this.write(content); 15758 } 15759 /** 15760 * Write the time to the tooltip DOM element. 15761 * 15762 * @param {string} content 15763 * The formatted time for the tooltip. 15764 */ 15765 ; 15766 15767 _proto.write = function write(content) { 15768 textContent(this.el_, content); 15769 } 15770 /** 15771 * Updates the position of the time tooltip relative to the `SeekBar`. 15772 * 15773 * @param {Object} seekBarRect 15774 * The `ClientRect` for the {@link SeekBar} element. 15775 * 15776 * @param {number} seekBarPoint 15777 * A number from 0 to 1, representing a horizontal reference point 15778 * from the left edge of the {@link SeekBar} 15779 * 15780 * @param {number} time 15781 * The time to update the tooltip to, not used during live playback 15782 * 15783 * @param {Function} cb 15784 * A function that will be called during the request animation frame 15785 * for tooltips that need to do additional animations from the default 15786 */ 15787 ; 15788 15789 _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) { 15790 var _this2 = this; 15791 15792 this.requestNamedAnimationFrame('TimeTooltip#updateTime', function () { 15793 var content; 15794 15795 var duration = _this2.player_.duration(); 15796 15797 if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) { 15798 var liveWindow = _this2.player_.liveTracker.liveWindow(); 15799 15800 var secondsBehind = liveWindow - seekBarPoint * liveWindow; 15801 content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow); 15802 } else { 15803 content = formatTime(time, duration); 15804 } 15805 15806 _this2.update(seekBarRect, seekBarPoint, content); 15807 15808 if (cb) { 15809 cb(); 15810 } 15811 }); 15812 }; 15813 15814 return TimeTooltip; 15815 }(Component$1); 15816 15817 Component$1.registerComponent('TimeTooltip', TimeTooltip); 15818 15819 /** 15820 * Used by {@link SeekBar} to display media playback progress as part of the 15821 * {@link ProgressControl}. 15822 * 15823 * @extends Component 15824 */ 15825 15826 var PlayProgressBar = /*#__PURE__*/function (_Component) { 15827 inheritsLoose(PlayProgressBar, _Component); 15828 15829 /** 15830 * Creates an instance of this class. 15831 * 15832 * @param {Player} player 15833 * The {@link Player} that this class should be attached to. 15834 * 15835 * @param {Object} [options] 15836 * The key/value store of player options. 15837 */ 15838 function PlayProgressBar(player, options) { 15839 var _this; 15840 15841 _this = _Component.call(this, player, options) || this; 15842 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15843 return _this; 15844 } 15845 /** 15846 * Create the the DOM element for this class. 15847 * 15848 * @return {Element} 15849 * The element that was created. 15850 */ 15851 15852 15853 var _proto = PlayProgressBar.prototype; 15854 15855 _proto.createEl = function createEl() { 15856 return _Component.prototype.createEl.call(this, 'div', { 15857 className: 'vjs-play-progress vjs-slider-bar' 15858 }, { 15859 'aria-hidden': 'true' 15860 }); 15861 } 15862 /** 15863 * Enqueues updates to its own DOM as well as the DOM of its 15864 * {@link TimeTooltip} child. 15865 * 15866 * @param {Object} seekBarRect 15867 * The `ClientRect` for the {@link SeekBar} element. 15868 * 15869 * @param {number} seekBarPoint 15870 * A number from 0 to 1, representing a horizontal reference point 15871 * from the left edge of the {@link SeekBar} 15872 */ 15873 ; 15874 15875 _proto.update = function update(seekBarRect, seekBarPoint) { 15876 var timeTooltip = this.getChild('timeTooltip'); 15877 15878 if (!timeTooltip) { 15879 return; 15880 } 15881 15882 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 15883 timeTooltip.updateTime(seekBarRect, seekBarPoint, time); 15884 }; 15885 15886 return PlayProgressBar; 15887 }(Component$1); 15888 /** 15889 * Default options for {@link PlayProgressBar}. 15890 * 15891 * @type {Object} 15892 * @private 15893 */ 15894 15895 15896 PlayProgressBar.prototype.options_ = { 15897 children: [] 15898 }; // Time tooltips should not be added to a player on mobile devices 15899 15900 if (!IS_IOS && !IS_ANDROID) { 15901 PlayProgressBar.prototype.options_.children.push('timeTooltip'); 15902 } 15903 15904 Component$1.registerComponent('PlayProgressBar', PlayProgressBar); 15905 15906 /** 15907 * The {@link MouseTimeDisplay} component tracks mouse movement over the 15908 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} 15909 * indicating the time which is represented by a given point in the 15910 * {@link ProgressControl}. 15911 * 15912 * @extends Component 15913 */ 15914 15915 var MouseTimeDisplay = /*#__PURE__*/function (_Component) { 15916 inheritsLoose(MouseTimeDisplay, _Component); 15917 15918 /** 15919 * Creates an instance of this class. 15920 * 15921 * @param {Player} player 15922 * The {@link Player} that this class should be attached to. 15923 * 15924 * @param {Object} [options] 15925 * The key/value store of player options. 15926 */ 15927 function MouseTimeDisplay(player, options) { 15928 var _this; 15929 15930 _this = _Component.call(this, player, options) || this; 15931 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15932 return _this; 15933 } 15934 /** 15935 * Create the DOM element for this class. 15936 * 15937 * @return {Element} 15938 * The element that was created. 15939 */ 15940 15941 15942 var _proto = MouseTimeDisplay.prototype; 15943 15944 _proto.createEl = function createEl() { 15945 return _Component.prototype.createEl.call(this, 'div', { 15946 className: 'vjs-mouse-display' 15947 }); 15948 } 15949 /** 15950 * Enqueues updates to its own DOM as well as the DOM of its 15951 * {@link TimeTooltip} child. 15952 * 15953 * @param {Object} seekBarRect 15954 * The `ClientRect` for the {@link SeekBar} element. 15955 * 15956 * @param {number} seekBarPoint 15957 * A number from 0 to 1, representing a horizontal reference point 15958 * from the left edge of the {@link SeekBar} 15959 */ 15960 ; 15961 15962 _proto.update = function update(seekBarRect, seekBarPoint) { 15963 var _this2 = this; 15964 15965 var time = seekBarPoint * this.player_.duration(); 15966 this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () { 15967 _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px"; 15968 }); 15969 }; 15970 15971 return MouseTimeDisplay; 15972 }(Component$1); 15973 /** 15974 * Default options for `MouseTimeDisplay` 15975 * 15976 * @type {Object} 15977 * @private 15978 */ 15979 15980 15981 MouseTimeDisplay.prototype.options_ = { 15982 children: ['timeTooltip'] 15983 }; 15984 Component$1.registerComponent('MouseTimeDisplay', MouseTimeDisplay); 15985 15986 var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline. 15987 15988 var PAGE_KEY_MULTIPLIER = 12; 15989 /** 15990 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} 15991 * as its `bar`. 15992 * 15993 * @extends Slider 15994 */ 15995
15996 var SeekBar = /*#__PURE__*/function (_Slider) { 15997 inheritsLoose(SeekBar, _Slider); 15998 15999 /** 16000 * Creates an instance of this class. 16001 * 16002 * @param {Player} player 16003 * The `Player` that this class should be attached to. 16004 * 16005 * @param {Object} [options] 16006 * The key/value store of player options. 16007 */ 16008 function SeekBar(player, options) { 16009 var _this; 16010 16011 _this = _Slider.call(this, player, options) || this; 16012 16013 _this.setEventHandlers_(); 16014 16015 return _this; 16016 } 16017 /** 16018 * Sets the event handlers 16019 * 16020 * @private 16021 */ 16022 16023 16024 var _proto = SeekBar.prototype; 16025 16026 _proto.setEventHandlers_ = function setEventHandlers_() { 16027 var _this2 = this; 16028 16029 this.update_ = bind(this, this.update); 16030 this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL); 16031 this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update); 16032 16033 if (this.player_.liveTracker) { 16034 this.on(this.player_.liveTracker, 'liveedgechange', this.update); 16035 } // when playing, let's ensure we smoothly update the play progress bar 16036 // via an interval 16037 16038 16039 this.updateInterval = null; 16040 16041 this.enableIntervalHandler_ = function (e) { 16042 return _this2.enableInterval_(e); 16043 }; 16044 16045 this.disableIntervalHandler_ = function (e) { 16046 return _this2.disableInterval_(e); 16047 }; 16048 16049 this.on(this.player_, ['playing'], this.enableIntervalHandler_); 16050 this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_); // we don't need to update the play progress if the document is hidden, 16051 // also, this causes the CPU to spike and eventually crash the page on IE11. 16052 16053 if ('hidden' in document_1 && 'visibilityState' in document_1) { 16054 this.on(document_1, 'visibilitychange', this.toggleVisibility_); 16055 } 16056 }; 16057 16058 _proto.toggleVisibility_ = function toggleVisibility_(e) { 16059 if (document_1.visibilityState === 'hidden') { 16060 this.cancelNamedAnimationFrame('SeekBar#update'); 16061 this.cancelNamedAnimationFrame('Slider#update'); 16062 this.disableInterval_(e); 16063 } else { 16064 if (!this.player_.ended() && !this.player_.paused()) { 16065 this.enableInterval_(); 16066 } // we just switched back to the page and someone may be looking, so, update ASAP 16067 16068 16069 this.update(); 16070 } 16071 }; 16072 16073 _proto.enableInterval_ = function enableInterval_() { 16074 if (this.updateInterval) { 16075 return; 16076 } 16077 16078 this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL); 16079 }; 16080 16081 _proto.disableInterval_ = function disableInterval_(e) { 16082 if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') { 16083 return; 16084 } 16085 16086 if (!this.updateInterval) { 16087 return; 16088 } 16089 16090 this.clearInterval(this.updateInterval); 16091 this.updateInterval = null; 16092 } 16093 /** 16094 * Create the `Component`'s DOM element 16095 * 16096 * @return {Element} 16097 * The element that was created. 16098 */ 16099 ; 16100 16101 _proto.createEl = function createEl() { 16102 return _Slider.prototype.createEl.call(this, 'div', { 16103 className: 'vjs-progress-holder' 16104 }, { 16105 'aria-label': this.localize('Progress Bar') 16106 }); 16107 } 16108 /** 16109 * This function updates the play progress bar and accessibility 16110 * attributes to whatever is passed in. 16111 * 16112 * @param {EventTarget~Event} [event] 16113 * The `timeupdate` or `ended` event that caused this to run. 16114 * 16115 * @listens Player#timeupdate 16116 * 16117 * @return {number} 16118 * The current percent at a number from 0-1 16119 */ 16120 ; 16121 16122 _proto.update = function update(event) { 16123 var _this3 = this; 16124 16125 // ignore updates while the tab is hidden 16126 if (document_1.visibilityState === 'hidden') { 16127 return; 16128 } 16129 16130 var percent = _Slider.prototype.update.call(this); 16131 16132 this.requestNamedAnimationFrame('SeekBar#update', function () { 16133 var currentTime = _this3.player_.ended() ? _this3.player_.duration() : _this3.getCurrentTime_(); 16134 var liveTracker = _this3.player_.liveTracker; 16135 16136 var duration = _this3.player_.duration(); 16137 16138 if (liveTracker && liveTracker.isLive()) { 16139 duration = _this3.player_.liveTracker.liveCurrentTime(); 16140 } 16141 16142 if (_this3.percent_ !== percent) { 16143 // machine readable value of progress bar (percentage complete) 16144 _this3.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); 16145 16146 _this3.percent_ = percent; 16147 } 16148 16149 if (_this3.currentTime_ !== currentTime || _this3.duration_ !== duration) { 16150 // human readable value of progress bar (time complete) 16151 _this3.el_.setAttribute('aria-valuetext', _this3.localize('progress bar timing: currentTime={1} duration={2}
16151', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); 16152 16153 _this3.currentTime_ = currentTime; 16154 _this3.duration_ = duration; 16155 } // update the progress bar time tooltip with the current time 16156 16157 16158 if (_this3.bar) { 16159 _this3.bar.update(getBoundingClientRect(_this3.el()), _this3.getProgress()); 16160 } 16161 }); 16162 return percent; 16163 } 16164 /** 16165 * Prevent liveThreshold from causing seeks to seem like they 16166 * are not happening from a user perspective. 16167 * 16168 * @param {number} ct 16169 * current time to seek to 16170 */ 16171 ; 16172 16173 _proto.userSeek_ = function userSeek_(ct) { 16174 if (this.player_.liveTracker && this.player_.liveTracker.isLive()) { 16175 this.player_.liveTracker.nextSeekedFromUser(); 16176 } 16177 16178 this.player_.currentTime(ct); 16179 } 16180 /** 16181 * Get the value of current time but allows for smooth scrubbing, 16182 * when player can't keep up. 16183 * 16184 * @return {number} 16185 * The current time value to display 16186 * 16187 * @private 16188 */ 16189 ; 16190 16191 _proto.getCurrentTime_ = function getCurrentTime_() { 16192 return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 16193 } 16194 /** 16195 * Get the percentage of media played so far. 16196 * 16197 * @return {number} 16198 * The percentage of media played so far (0 to 1). 16199 */ 16200 ; 16201 16202 _proto.getPercent = function getPercent() { 16203 var currentTime = this.getCurrentTime_(); 16204 var percent; 16205 var liveTracker = this.player_.liveTracker; 16206 16207 if (liveTracker && liveTracker.isLive()) { 16208 percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge 16209 16210 if (liveTracker.atLiveEdge()) { 16211 percent = 1; 16212 } 16213 } else { 16214 percent = currentTime / this.player_.duration(); 16215 } 16216 16217 return percent; 16218 } 16219 /** 16220 * Handle mouse down on seek bar 16221 * 16222 * @param {EventTarget~Event} event 16223 * The `mousedown` event that caused this to run. 16224 * 16225 * @listens mousedown 16226 */ 16227 ; 16228 16229 _proto.handleMouseDown = function handleMouseDown(event) { 16230 if (!isSingleLeftClick(event)) { 16231 return; 16232 } // Stop event propagation to prevent double fire in progress-control.js 16233 16234 16235 event.stopPropagation(); 16236 this.player_.scrubbing(true); 16237 this.videoWasPlaying = !this.player_.paused(); 16238 this.player_.pause(); 16239 16240 _Slider.prototype.handleMouseDown.call(this, event); 16241 } 16242 /** 16243 * Handle mouse move on seek bar 16244 * 16245 * @param {EventTarget~Event} event 16246 * The `mousemove` event that caused this to run. 16247 * 16248 * @listens mousemove 16249 */ 16250 ; 16251 16252 _proto.handleMouseMove = function handleMouseMove(event) { 16253 if (!isSingleLeftClick(event)) { 16254 return; 16255 } 16256 16257 var newTime; 16258 var distance = this.calculateDistance(event); 16259 var liveTracker = this.player_.liveTracker; 16260 16261 if (!liveTracker || !liveTracker.isLive()) { 16262 newTime = distance * this.player_.duration(); // Don't let video end while scrubbing. 16263 16264 if (newTime === this.player_.duration()) { 16265 newTime = newTime - 0.1; 16266 } 16267 } else { 16268 if (distance >= 0.99) { 16269 liveTracker.seekToLiveEdge(); 16270 return; 16271 } 16272 16273 var seekableStart = liveTracker.seekableStart(); 16274 var seekableEnd = liveTracker.liveCurrentTime(); 16275 newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing. 16276 16277 if (newTime >= seekableEnd) { 16278 newTime = seekableEnd; 16279 } // Compensate for precision differences so that currentTime is not less 16280 // than seekable start 16281 16282 16283 if (newTime <= seekableStart) { 16284 newTime = seekableStart + 0.1; 16285 } // On android seekableEnd can be Infinity sometimes, 16286 // this will cause newTime to be Infinity, which is 16287 // not a valid currentTime. 16288 16289 16290 if (newTime === Infinity) { 16291 return; 16292 } 16293 } // Set new time (tell player to seek to new time) 16294 16295
16296 this.userSeek_(newTime); 16297 }; 16298 16299 _proto.enable = function enable() { 16300 _Slider.prototype.enable.call(this); 16301 16302 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 16303 16304 if (!mouseTimeDisplay) { 16305 return; 16306 } 16307 16308 mouseTimeDisplay.show(); 16309 }; 16310 16311 _proto.disable = function disable() { 16312 _Slider.prototype.disable.call(this); 16313 16314 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 16315 16316 if (!mouseTimeDisplay) { 16317 return; 16318 } 16319 16320 mouseTimeDisplay.hide(); 16321 } 16322 /** 16323 * Handle mouse up on seek bar 16324 * 16325 * @param {EventTarget~Event} event 16326 * The `mouseup` event that caused this to run. 16327 * 16328 * @listens mouseup 16329 */ 16330 ; 16331 16332 _proto.handleMouseUp = function handleMouseUp(event) { 16333 _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js 16334 16335 16336 if (event) { 16337 event.stopPropagation(); 16338 } 16339 16340 this.player_.scrubbing(false); 16341 /** 16342 * Trigger timeupdate because we're done seeking and the time has changed. 16343 * This is particularly useful for if the player is paused to time the time displays. 16344 * 16345 * @event Tech#timeupdate 16346 * @type {EventTarget~Event} 16347 */ 16348 16349 this.player_.trigger({ 16350 type: 'timeupdate', 16351 target: this, 16352 manuallyTriggered: true 16353 }); 16354 16355 if (this.videoWasPlaying) { 16356 silencePromise(this.player_.play()); 16357 } else { 16358 // We're done seeking and the time has changed. 16359 // If the player is paused, make sure we display the correct time on the seek bar. 16360 this.update_(); 16361 } 16362 } 16363 /** 16364 * Move more quickly fast forward for keyboard-only users 16365 */ 16366 ; 16367 16368 _proto.stepForward = function stepForward() { 16369 this.userSeek_(this.player_.currentTime() + STEP_SECONDS); 16370 } 16371 /** 16372 * Move more quickly rewind for keyboard-only users 16373 */ 16374 ; 16375 16376 _proto.stepBack = function stepBack() { 16377 this.userSeek_(this.player_.currentTime() - STEP_SECONDS); 16378 } 16379 /** 16380 * Toggles the playback state of the player 16381 * This gets called when enter or space is used on the seekbar 16382 * 16383 * @param {EventTarget~Event} event 16384 * The `keydown` event that caused this function to be called 16385 * 16386 */ 16387 ; 16388 16389 _proto.handleAction = function handleAction(event) { 16390 if (this.player_.paused()) { 16391 this.player_.play(); 16392 } else { 16393 this.player_.pause(); 16394 } 16395 } 16396 /** 16397 * Called when this SeekBar has focus and a key gets pressed down. 16398 * Supports the following keys: 16399 * 16400 * Space or Enter key fire a click event 16401 * Home key moves to start of the timeline 16402 * End key moves to end of the timeline 16403 * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline 16404 * PageDown key moves back a larger step than ArrowDown 16405 * PageUp key moves forward a large step 16406 * 16407 * @param {EventTarget~Event} event 16408 * The `keydown` event that caused this function to be called. 16409 * 16410 * @listens keydown 16411 */ 16412 ; 16413 16414 _proto.handleKeyDown = function handleKeyDown(event) { 16415 var liveTracker = this.player_.liveTracker; 16416 16417 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 16418 event.preventDefault(); 16419 event.stopPropagation(); 16420 this.handleAction(event); 16421 } else if (keycode.isEventKey(event, 'Home')) { 16422 event.preventDefault(); 16423 event.stopPropagation(); 16424 this.userSeek_(0); 16425 } else if (keycode.isEventKey(event, 'End')) { 16426 event.preventDefault(); 16427 event.stopPropagation(); 16428 16429 if (liveTracker && liveTracker.isLive()) { 16430 this.userSeek_(liveTracker.liveCurrentTime()); 16431 } else { 16432 this.userSeek_(this.player_.duration()); 16433 } 16434 } else if (/^[0-9]$/.test(keycode(event))) { 16435 event.preventDefault(); 16436 event.stopPropagation(); 16437 var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0; 16438 16439 if (liveTracker && liveTracker.isLive()) { 16440 this.userSeek_(liveTracker.seekableStart() + liveTracker.liveWindow() * gotoFraction); 16441 } else { 16442 this.userSeek_(this.player_.duration() * gotoFraction); 16443 } 16444 } else if (keycode.isEventKey(event, 'PgDn')) { 16445 event.preventDefault(); 16446 event.stopPropagation(); 16447 this.userSeek_(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER); 16448 } else if (keycode.isEventKey(event, 'PgUp')) { 16449 event.preventDefault(); 16450 event.stopPropagation(); 16451 this.userSeek_(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER); 16452 } else { 16453 // Pass keydown handling up for unsupported keys 16454 _Slider.prototype.handleKeyDown.call(this, event); 16455 } 16456 }; 16457 16458 _proto.dispose = function dispose() { 16459 this.disableInterval_(); 16460 this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update); 16461 16462 if (this.player_.liveTracker) { 16463 this.off(this.player_.liveTracker, 'liveedgechange', this.update); 16464 } 16465 16466 this.off(this.player_, ['playing'], this.enableIntervalHandler_); 16467 this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableIntervalHandler_); // we don't need to update the play progress if the document is hidden, 16468 // also, this causes the CPU to spike and eventually crash the page on IE11. 16469 16470 if ('hidden' in document_1 && 'visibilityState' in document_1) { 16471 this.off(document_1, 'visibilitychange', this.toggleVisibility_); 16472 } 16473 16474 _Slider.prototype.dispose.call(this); 16475 }; 16476 16477 return SeekBar; 16478 }
16478(Slider); 16479 /** 16480 * Default options for the `SeekBar` 16481 * 16482 * @type {Object} 16483 * @private 16484 */ 16485 16486 16487 SeekBar.prototype.options_ = { 16488 children: ['loadProgressBar', 'playProgressBar'], 16489 barName: 'playProgressBar' 16490 }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices 16491 16492 if (!IS_IOS && !IS_ANDROID) { 16493 SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); 16494 } 16495 16496 Component$1.registerComponent('SeekBar', SeekBar); 16497 16498 /** 16499 * The Progress Control component contains the seek bar, load progress, 16500 * and play progress. 16501 * 16502 * @extends Component 16503 */ 16504 16505 var ProgressControl = /*#__PURE__*/function (_Component) { 16506 inheritsLoose(ProgressControl, _Component); 16507 16508 /** 16509 * Creates an instance of this class. 16510 * 16511 * @param {Player} player 16512 * The `Player` that this class should be attached to. 16513 * 16514 * @param {Object} [options] 16515 * The key/value store of player options. 16516 */ 16517 function ProgressControl(player, options) { 16518 var _this; 16519 16520 _this = _Component.call(this, player, options) || this; 16521 _this.handleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 16522 _this.throttledHandleMouseSeek = throttle(bind(assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL); 16523 16524 _this.handleMouseUpHandler_ = function (e) { 16525 return _this.handleMouseUp(e); 16526 }; 16527 16528 _this.handleMouseDownHandler_ = function (e) { 16529 return _this.handleMouseDown(e); 16530 }; 16531 16532 _this.enable(); 16533 16534 return _this; 16535 } 16536 /** 16537 * Create the `Component`'s DOM element 16538 * 16539 * @return {Element} 16540 * The element that was created. 16541 */ 16542 16543 16544 var _proto = ProgressControl.prototype; 16545 16546 _proto.createEl = function createEl() { 16547 return _Component.prototype.createEl.call(this, 'div', { 16548 className: 'vjs-progress-control vjs-control' 16549 }); 16550 } 16551 /** 16552 * When the mouse moves over the `ProgressControl`, the pointer position 16553 * gets passed down to the `MouseTimeDisplay` component. 16554 * 16555 * @param {EventTarget~Event} event 16556 * The `mousemove` event that caused this function to run. 16557 * 16558 * @listen mousemove 16559 */ 16560 ; 16561 16562 _proto.handleMouseMove = function handleMouseMove(event) { 16563 var seekBar = this.getChild('seekBar'); 16564 16565 if (!seekBar) { 16566 return; 16567 } 16568 16569 var playProgressBar = seekBar.getChild('playProgressBar'); 16570 var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); 16571 16572 if (!playProgressBar && !mouseTimeDisplay) { 16573 return; 16574 } 16575 16576 var seekBarEl = seekBar.el(); 16577 var seekBarRect = findPosition(seekBarEl); 16578 var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means 16579 // that it's possible to trigger this behavior outside the boundaries of 16580 // the `SeekBar`. This ensures we stay within it at all times. 16581 16582 seekBarPoint = clamp(seekBarPoint, 0, 1); 16583 16584 if (mouseTimeDisplay) { 16585 mouseTimeDisplay.update(seekBarRect, seekBarPoint); 16586 } 16587 16588 if (playProgressBar) { 16589 playProgressBar.update(seekBarRect, seekBar.getProgress()); 16590 } 16591 } 16592 /** 16593 * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. 16594 * 16595 * @method ProgressControl#throttledHandleMouseSeek 16596 * @param {EventTarget~Event} event 16597 * The `mousemove` event that caused this function to run. 16598 * 16599 * @listen mousemove 16600 * @listen touchmove 16601 */ 16602 16603 /** 16604 * Handle `mousemove` or `touchmove` events on the `ProgressControl`. 16605 * 16606 * @param {EventTarget~Event} event 16607 * `mousedown` or `touchstart` event that triggered this function 16608 * 16609 * @listens mousemove 16610 * @listens touchmove 16611 */ 16612 ; 16613 16614 _proto.handleMouseSeek = function handleMouseSeek(event) { 16615 var seekBar = this.getChild('seekBar'); 16616 16617 if (seekBar) { 16618 seekBar.handleMouseMove(event); 16619 } 16620 } 16621 /**
16622 * Are controls are currently enabled for this progress control. 16623 * 16624 * @return {boolean} 16625 * true if controls are enabled, false otherwise 16626 */ 16627 ; 16628 16629 _proto.enabled = function enabled() { 16630 return this.enabled_; 16631 } 16632 /** 16633 * Disable all controls on the progress control and its children 16634 */ 16635 ; 16636 16637 _proto.disable = function disable() { 16638 this.children().forEach(function (child) { 16639 return child.disable && child.disable(); 16640 }); 16641 16642 if (!this.enabled()) { 16643 return; 16644 } 16645 16646 this.off(['mousedown', 'touchstart'], this.handleMouseDownHandler_); 16647 this.off(this.el_, 'mousemove', this.handleMouseMove); 16648 this.removeListenersAddedOnMousedownAndTouchstart(); 16649 this.addClass('disabled'); 16650 this.enabled_ = false; // Restore normal playback state if controls are disabled while scrubbing 16651 16652 if (this.player_.scrubbing()) { 16653 var seekBar = this.getChild('seekBar'); 16654 this.player_.scrubbing(false); 16655 16656 if (seekBar.videoWasPlaying) { 16657 silencePromise(this.player_.play()); 16658 } 16659 } 16660 } 16661 /** 16662 * Enable all controls on the progress control and its children 16663 */ 16664 ; 16665 16666 _proto.enable = function enable() {
16667 this.children().forEach(function (child) { 16668 return child.enable && child.enable(); 16669 }); 16670 16671 if (this.enabled()) { 16672 return; 16673 } 16674 16675 this.on(['mousedown', 'touchstart'], this.handleMouseDownHandler_); 16676 this.on(this.el_, 'mousemove', this.handleMouseMove); 16677 this.removeClass('disabled'); 16678 this.enabled_ = true; 16679 } 16680 /** 16681 * Cleanup listeners after the user finishes interacting with the progress controls 16682 */ 16683 ; 16684 16685 _proto.removeListenersAddedOnMousedownAndTouchstart = function removeListenersAddedOnMousedownAndTouchstart() { 16686 var doc = this.el_.ownerDocument; 16687 this.off(doc, 'mousemove', this.throttledHandleMouseSeek); 16688 this.off(doc, 'touchmove', this.throttledHandleMouseSeek); 16689 this.off(doc, 'mouseup', this.handleMouseUpHandler_); 16690 this.off(doc, 'touchend', this.handleMouseUpHandler_); 16691 } 16692 /** 16693 * Handle `mousedown` or `touchstart` events on the `ProgressControl`. 16694 * 16695 * @param {EventTarget~Event} event 16696 * `mousedown` or `touchstart` event that triggered this function 16697 * 16698 * @listens mousedown 16699 * @listens touchstart 16700 */ 16701 ; 16702 16703 _proto.handleMouseDown = function handleMouseDown(event) { 16704 var doc = this.el_.ownerDocument; 16705 var seekBar = this.getChild('seekBar'); 16706 16707 if (seekBar) { 16708 seekBar.handleMouseDown(event); 16709 } 16710 16711 this.on(doc, 'mousemove', this.throttledHandleMouseSeek); 16712 this.on(doc, 'touchmove', this.throttledHandleMouseSeek); 16713 this.on(doc, 'mouseup', this.handleMouseUpHandler_); 16714 this.on(doc, 'touchend', this.handleMouseUpHandler_); 16715 } 16716 /** 16717 * Handle `mouseup` or `touchend` events on the `ProgressControl`. 16718 * 16719 * @param {EventTarget~Event} event 16720 * `mouseup` or `touchend` event that triggered this function. 16721 * 16722 * @listens touchend 16723 * @listens mouseup 16724 */ 16725 ; 16726 16727 _proto.handleMouseUp = function handleMouseUp(event) { 16728 var seekBar = this.getChild('seekBar'); 16729 16730 if (seekBar) { 16731 seekBar.handleMouseUp(event); 16732 } 16733 16734 this.removeListenersAddedOnMousedownAndTouchstart(); 16735 }; 16736 16737 return ProgressControl; 16738 }(Component$1); 16739 /** 16740 * Default options for `ProgressControl` 16741 * 16742 * @type {Object} 16743 * @private 16744 */ 16745 16746 16747 ProgressControl.prototype.options_ = { 16748 children: ['seekBar'] 16749 }; 16750 Component$1.registerComponent('ProgressControl', ProgressControl); 16751 16752 /** 16753 * Toggle Picture-in-Picture mode 16754 * 16755 * @extends Button 16756 */ 16757 16758 var PictureInPictureToggle = /*#__PURE__*/function (_Button) { 16759 inheritsLoose(PictureInPictureToggle, _Button); 16760 16761 /** 16762 * Creates an instance of this class. 16763 * 16764 * @param {Player} player 16765 * The `Player` that this class should be attached to. 16766 * 16767 * @param {Object} [options] 16768 * The key/value store of player options. 16769 * 16770 * @listens Player#enterpictureinpicture 16771 * @listens Player#leavepictureinpicture 16772 */ 16773 function PictureInPictureToggle(player, options) { 16774 var _this; 16775 16776 _this = _Button.call(this, player, options) || this; 16777 16778 _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], function (e) { 16779 return _this.handlePictureInPictureChange(e); 16780 }); 16781 16782 _this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], function (e) { 16783 return _this.handlePictureInPictureEnabledChange(e); 16784 }); // TODO: Deactivate button on player emptied event. 16785 16786 16787 _this.disable(); 16788 16789 return _this; 16790 } 16791 /** 16792 * Builds the default DOM `className`. 16793 * 16794 * @return {string} 16795 * The DOM `className` for this object. 16796 */ 16797 16798 16799 var _proto = PictureInPictureToggle.prototype; 16800 16801 _proto.buildCSSClass = function buildCSSClass() { 16802 return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this); 16803 } 16804 /** 16805 * Enables or disables button based on document.pictureInPictureEnabled property value 16806 * or on value returned by player.disablePictureInPicture() method. 16807 */ 16808 ; 16809 16810 _proto.handlePictureInPictureEnabledChange = function handlePictureInPictureEnabledChange() { 16811 if (document_1.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false) { 16812 this.enable(); 16813 } else { 16814 this.disable(); 16815 } 16816 } 16817 /** 16818 * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly. 16819 * 16820 * @param {EventTarget~Event} [event] 16821 * The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be 16822 * called. 16823 * 16824 * @listens Player#enterpictureinpicture 16825 * @listens Player#leavepictureinpicture 16826 */ 16827 ; 16828 16829 _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) { 16830 if (this.player_.isInPictureInPicture()) { 16831 this.controlText('Exit Picture-in-Picture'); 16832 } else { 16833 this.controlText('Picture-in-Picture'); 16834 } 16835 16836 this.handlePictureInPictureEnabledChange(); 16837 } 16838 /** 16839 * This gets called when an `PictureInPictureToggle` is "clicked". See 16840 * {@link ClickableComponent} for more detailed information on what a click can be. 16841 * 16842 * @param {EventTarget~Event} [event]
16843 * The `keydown`, `tap`, or `click` event that caused this function to be 16844 * called. 16845 * 16846 * @listens tap 16847 * @listens click 16848 */ 16849 ; 16850 16851 _proto.handleClick = function handleClick(event) { 16852 if (!this.player_.isInPictureInPicture()) { 16853 this.player_.requestPictureInPicture(); 16854 } else { 16855 this.player_.exitPictureInPicture(); 16856 } 16857 }; 16858 16859 return PictureInPictureToggle; 16860 }(Button); 16861 /** 16862 * The text that should display over the `PictureInPictureToggle`s controls. Added for localization. 16863 * 16864 * @type {string} 16865 * @private 16866 */ 16867 16868 16869 PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture'; 16870 Component$1.registerComponent('PictureInPictureToggle', PictureInPictureToggle); 16871 16872 /** 16873 * Toggle fullscreen video 16874 * 16875 * @extends Button 16876 */ 16877 16878 var FullscreenToggle = /*#__PURE__*/function (_Button) { 16879 inheritsLoose(FullscreenToggle, _Button); 16880 16881 /** 16882 * Creates an instance of this class. 16883 * 16884 * @param {Player} player 16885 * The `Player` that this class should be attached to. 16886 * 16887 * @param {Object} [options] 16888 * The key/value store of player options. 16889 */ 16890 function FullscreenToggle(player, options) { 16891 var _this; 16892 16893 _this = _Button.call(this, player, options) || this; 16894 16895 _this.on(player, 'fullscreenchange', function (e) { 16896 return _this.handleFullscreenChange(e); 16897 }); 16898 16899 if (document_1[player.fsApi_.fullscreenEnabled] === false) { 16900 _this.disable(); 16901 } 16902 16903 return _this; 16904 } 16905 /** 16906 * Builds the default DOM `className`. 16907 * 16908 * @return {string} 16909 * The DOM `className` for this object. 16910 */ 16911 16912 16913 var _proto = FullscreenToggle.prototype; 16914 16915 _proto.buildCSSClass = function buildCSSClass() { 16916 return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this); 16917 } 16918 /** 16919 * Handles fullscreenchange on the player and change control text accordingly. 16920 * 16921 * @param {EventTarget~Event} [event] 16922 * The {@link Player#fullscreenchange} event that caused this function to be 16923 * called. 16924 * 16925 * @listens Player#fullscreenchange 16926 */ 16927 ; 16928 16929 _proto.handleFullscreenChange = function handleFullscreenChange(event) { 16930 if (this.player_.isFullscreen()) { 16931 this.controlText('Non-Fullscreen'); 16932 } else { 16933 this.controlText('Fullscreen'); 16934 } 16935 } 16936 /** 16937 * This gets called when an `FullscreenToggle` is "clicked". See 16938 * {@link ClickableComponent} for more detailed information on what a click can be. 16939 * 16940 * @param {EventTarget~Event} [event] 16941 * The `keydown`, `tap`, or `click` event that caused this function to be 16942 * called. 16943 * 16944 * @listens tap 16945 * @listens click 16946 */ 16947 ; 16948 16949 _proto.handleClick = function handleClick(event) { 16950 if (!this.player_.isFullscreen()) { 16951 this.player_.requestFullscreen(); 16952 } else { 16953 this.player_.exitFullscreen(); 16954 } 16955 }; 16956 16957 return FullscreenToggle; 16958 }(Button); 16959 /** 16960 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 16961 * 16962 * @type {string} 16963 * @private 16964 */ 16965 16966 16967 FullscreenToggle.prototype.controlText_ = 'Fullscreen'; 16968 Component$1.registerComponent('FullscreenToggle', FullscreenToggle); 16969 16970 /** 16971 * Check if volume control is supported and if it isn't hide the 16972 * `Component` that was passed using the `vjs-hidden` class. 16973 * 16974 * @param {Component} self 16975 * The component that should be hidden if volume is unsupported 16976 * 16977 * @param {Player} player 16978 * A reference to the player 16979 * 16980 * @private 16981 */ 16982 var checkVolumeSupport = function checkVolumeSupport(self, player) { 16983 // hide volume controls when they're not supported by the current tech 16984 if (player.tech_ && !player.tech_.featuresVolumeControl) { 16985 self.addClass('vjs-hidden'); 16986 } 16987 16988 self.on(player, 'loadstart', function () { 16989 if (!player.tech_.featuresVolumeControl) { 16990 self.addClass('vjs-hidden'); 16991 } else { 16992 self.removeClass('vjs-hidden'); 16993 } 16994 });
16995 }; 16996 16997 /** 16998 * Shows volume level 16999 * 17000 * @extends Component 17001 */ 17002 17003 var VolumeLevel = /*#__PURE__*/function (_Component) { 17004 inheritsLoose(VolumeLevel, _Component); 17005 17006 function VolumeLevel() { 17007 return _Component.apply(this, arguments) || this; 17008 } 17009 17010 var _proto = VolumeLevel.prototype; 17011 17012 /** 17013 * Create the `Component`'s DOM element 17014 * 17015 * @return {Element} 17016 * The element that was created. 17017 */ 17018 _proto.createEl = function createEl() { 17019 var el = _Component.prototype.createEl.call(this, 'div', { 17020 className: 'vjs-volume-level' 17021 }); 17022 17023 el.appendChild(_Component.prototype.createEl.call(this, 'span', { 17024 className: 'vjs-control-text' 17025 })); 17026 return el; 17027 }; 17028 17029 return VolumeLevel; 17030 }(Component$1); 17031 17032 Component$1.registerComponent('VolumeLevel', VolumeLevel); 17033 17034 /** 17035 * Volume level tooltips display a volume above or side by side the volume bar. 17036 * 17037 * @extends Component 17038 */ 17039 17040 var VolumeLevelTooltip = /*#__PURE__*/function (_Component) { 17041 inheritsLoose(VolumeLevelTooltip, _Component); 17042 17043 /** 17044 * Creates an instance of this class. 17045 * 17046 * @param {Player} player 17047 * The {@link Player} that this class should be attached to. 17048 * 17049 * @param {Object} [options] 17050 * The key/value store of player options. 17051 */ 17052 function VolumeLevelTooltip(player, options) { 17053 var _this; 17054 17055 _this = _Component.call(this, player, options) || this; 17056 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 17057 return _this; 17058 } 17059 /** 17060 * Create the volume tooltip DOM element 17061 * 17062 * @return {Element} 17063 * The element that was created. 17064 */ 17065 17066 17067 var _proto = VolumeLevelTooltip.prototype; 17068 17069 _proto.createEl = function createEl() { 17070 return _Component.prototype.createEl.call(this, 'div', { 17071 className: 'vjs-volume-tooltip' 17072 }, { 17073 'aria-hidden': 'true' 17074 }); 17075 } 17076 /** 17077 * Updates the position of the tooltip relative to the `VolumeBar` and 17078 * its content text. 17079 * 17080 * @param {Object} rangeBarRect 17081 * The `ClientRect` for the {@link VolumeBar} element. 17082 * 17083 * @param {number} rangeBarPoint 17084 * A number from 0 to 1, representing a horizontal/vertical reference point 17085 * from the left edge of the {@link VolumeBar} 17086 * 17087 * @param {boolean} vertical 17088 * Referees to the Volume control position 17089 * in the control bar{@link VolumeControl} 17090 * 17091 */ 17092 ; 17093 17094 _proto.update = function update(rangeBarRect, rangeBarPoint, vertical, content) { 17095 if (!vertical) { 17096 var tooltipRect = getBoundingClientRect(this.el_); 17097 var playerRect = getBoundingClientRect(this.player_.el()); 17098 var volumeBarPointPx = rangeBarRect.width * rangeBarPoint; 17099 17100 if (!playerRect || !tooltipRect) { 17101 return; 17102 } 17103 17104 var spaceLeftOfPoint = rangeBarRect.left - playerRect.left + volumeBarPointPx; 17105 var spaceRightOfPoint = rangeBarRect.width - volumeBarPointPx + (playerRect.right - rangeBarRect.right); 17106 var pullTooltipBy = tooltipRect.width / 2; 17107 17108 if (spaceLeftOfPoint < pullTooltipBy) { 17109 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 17110 } else if (spaceRightOfPoint < pullTooltipBy) { 17111 pullTooltipBy = spaceRightOfPoint; 17112 } 17113 17114 if (pullTooltipBy < 0) { 17115 pullTooltipBy = 0; 17116 } else if (pullTooltipBy > tooltipRect.width) { 17117 pullTooltipBy = tooltipRect.width; 17118 } 17119 17120 this.el_.style.right = "-" + pullTooltipBy + "px"; 17121 } 17122 17123 this.write(content + "%"); 17124 } 17125 /** 17126 * Write the volume to the tooltip DOM element. 17127 * 17128 * @param {string} content 17129 * The formatted volume for the tooltip. 17130 */ 17131 ; 17132 17133 _proto.write = function write(content) { 17134 textContent(this.el_, content); 17135 } 17136 /** 17137 * Updates the position of the volume tooltip relative to the `VolumeBar`. 17138 * 17139 * @param {Object} rangeBarRect 17140 * The `ClientRect` for the {@link VolumeBar} element. 17141 * 17142 * @param {number} rangeBarPoint 17143 * A number from 0 to 1, representing a horizontal/vertical reference point 17144 * from the left edge of the {@link VolumeBar} 17145 * 17146 * @param {boolean} vertical 17147 * Referees to the Volume control position 17148 * in the control bar{@link VolumeControl} 17149 * 17150 * @param {number} volume 17151 * The volume level to update the tooltip to 17152 * 17153 * @param {Function} cb 17154 * A function that will be called during the request animation frame 17155 * for tooltips that need to do additional animations from the default 17156 */ 17157 ; 17158 17159 _proto.updateVolume = function updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) { 17160 var _this2 = this; 17161 17162 this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', function () { 17163 _this2.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0)); 17164 17165 if (cb) { 17166 cb(); 17167 } 17168 }); 17169 }; 17170 17171 return VolumeLevelTooltip; 17172 }(Component$1); 17173 17174 Component$1.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip); 17175 17176 /** 17177 * The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the 17178 * {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip} 17179 * indicating the volume level which is represented by a given point in the 17180 * {@link VolumeBar}. 17181 * 17182 * @extends Component 17183 */ 17184 17185 var MouseVolumeLevelDisplay = /*#__PURE__*/function (_Component) { 17186 inheritsLoose(MouseVolumeLevelDisplay, _Component); 17187 17188 /** 17189 * Creates an instance of this class. 17190 * 17191 * @param {Player} player 17192 * The {@link Player} that this class should be attached to. 17193 * 17194 * @param {Object} [options] 17195 * The key/value store of player options. 17196 */ 17197 function MouseVolumeLevelDisplay(player, options) { 17198 var _this; 17199 17200 _this = _Component.call(this, player, options) || this; 17201 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 17202 return _this; 17203 } 17204 /** 17205 * Create the DOM element for this class. 17206 * 17207 * @return {Element} 17208 * The element that was created. 17209 */ 17210 17211 17212 var _proto = MouseVolumeLevelDisplay.prototype; 17213 17214 _proto.createEl = function createEl() { 17215 return _Component.prototype.createEl.call(this, 'div', { 17216 className: 'vjs-mouse-display' 17217 }); 17218 } 17219 /** 17220 * Enquires updates to its own DOM as well as the DOM of its 17221 * {@link VolumeLevelTooltip} child. 17222 * 17223 * @param {Object} rangeBarRect 17224 * The `ClientRect` for the {@link VolumeBar} element. 17225 * 17226 * @param {number} rangeBarPoint 17227 * A number from 0 to 1, representing a horizontal/vertical reference point 17228 * from the left edge of the {@link VolumeBar} 17229 * 17230 * @param {boolean} vertical 17231 * Referees to the Volume control position 17232 * in the control bar{@link VolumeControl} 17233 * 17234 */ 17235 ; 17236 17237 _proto.update = function update(rangeBarRect, rangeBarPoint, vertical) { 17238 var _this2 = this; 17239 17240 var volume = 100 * rangeBarPoint; 17241 this.getChild('volumeLevelTooltip').updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, function () { 17242 if (vertical) { 17243 _this2.el_.style.bottom = rangeBarRect.height * rangeBarPoint + "px"; 17244 } else { 17245 _this2.el_.style.left = rangeBarRect.width * rangeBarPoint + "px"; 17246 } 17247 }); 17248 }; 17249 17250 return MouseVolumeLevelDisplay; 17251 }(Component$1); 17252 /** 17253 * Default options for `MouseVolumeLevelDisplay` 17254 * 17255 * @type {Object} 17256 * @private 17257 */ 17258 17259 17260 MouseVolumeLevelDisplay.prototype.options_ = { 17261 children: ['volumeLevelTooltip'] 17262 }; 17263 Component$1.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay); 17264 17265 /** 17266 * The bar that contains the volume level and can be clicked on to adjust the level 17267 * 17268 * @extends Slider 17269 */ 17270
17271 var VolumeBar = /*#__PURE__*/function (_Slider) { 17272 inheritsLoose(VolumeBar, _Slider); 17273 17274 /** 17275 * Creates an instance of this class. 17276 * 17277 * @param {Player} player 17278 * The `Player` that this class should be attached to. 17279 * 17280 * @param {Object} [options] 17281 * The key/value store of player options. 17282 */ 17283 function VolumeBar(player, options) { 17284 var _this; 17285 17286 _this = _Slider.call(this, player, options) || this; 17287 17288 _this.on('slideractive', function (e) { 17289 return _this.updateLastVolume_(e); 17290 }); 17291 17292 _this.on(player, 'volumechange', function (e) { 17293 return _this.updateARIAAttributes(e); 17294 }); 17295 17296 player.ready(function () { 17297 return _this.updateARIAAttributes(); 17298 }); 17299 return _this; 17300 } 17301 /** 17302 * Create the `Component`'s DOM element 17303 * 17304 * @return {Element} 17305 * The element that was created. 17306 */ 17307 17308 17309 var _proto = VolumeBar.prototype; 17310 17311 _proto.createEl = function createEl() { 17312 return _Slider.prototype.createEl.call(this, 'div', { 17313 className: 'vjs-volume-bar vjs-slider-bar' 17314 }, { 17315 'aria-label': this.localize('Volume Level'), 17316 'aria-live': 'polite' 17317 }); 17318 } 17319 /** 17320 * Handle mouse down on volume bar 17321 * 17322 * @param {EventTarget~Event} event 17323 * The `mousedown` event that caused this to run. 17324 * 17325 * @listens mousedown 17326 */ 17327 ; 17328 17329 _proto.handleMouseDown = function handleMouseDown(event) { 17330 if (!isSingleLeftClick(event)) { 17331 return; 17332 } 17333 17334 _Slider.prototype.handleMouseDown.call(this, event); 17335 } 17336 /** 17337 * Handle movement events on the {@link VolumeMenuButton}. 17338 * 17339 * @param {EventTarget~Event} event 17340 * The event that caused this function to run. 17341 * 17342 * @listens mousemove 17343 */ 17344 ; 17345 17346 _proto.handleMouseMove = function handleMouseMove(event) { 17347 var mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay'); 17348 17349 if (mouseVolumeLevelDisplay) { 17350 var volumeBarEl = this.el(); 17351 var volumeBarRect = getBoundingClientRect(volumeBarEl); 17352 var vertical = this.vertical(); 17353 var volumeBarPoint = getPointerPosition(volumeBarEl, event); 17354 volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x; // The default skin has a gap on either side of the `VolumeBar`. This means 17355 // that it's possible to trigger this behavior outside the boundaries of 17356 // the `VolumeBar`. This ensures we stay within it at all times. 17357 17358 volumeBarPoint = clamp(volumeBarPoint, 0, 1); 17359 mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical); 17360 } 17361 17362 if (!isSingleLeftClick(event)) { 17363 return; 17364 } 17365 17366 this.checkMuted(); 17367 this.player_.volume(this.calculateDistance(event)); 17368 } 17369 /** 17370 * If the player is muted unmute it. 17371 */ 17372 ; 17373 17374 _proto.checkMuted = function checkMuted() { 17375 if (this.player_.muted()) { 17376 this.player_.muted(false); 17377 } 17378 } 17379 /** 17380 * Get percent of volume level 17381 * 17382 * @return {number} 17383 * Volume level percent as a decimal number. 17384 */ 17385 ; 17386 17387 _proto.getPercent = function getPercent() { 17388 if (this.player_.muted()) { 17389 return 0; 17390 } 17391 17392 return this.player_.volume(); 17393 } 17394 /** 17395 * Increase volume level for keyboard users 17396 */ 17397 ; 17398 17399 _proto.stepForward = function stepForward() { 17400 this.checkMuted(); 17401 this.player_.volume(this.player_.volume() + 0.1); 17402 } 17403 /** 17404 * Decrease volume level for keyboard users 17405 */ 17406 ; 17407 17408 _proto.stepBack = function stepBack() { 17409 this.checkMuted(); 17410 this.player_.volume(this.player_.volume() - 0.1); 17411 } 17412 /** 17413 * Update ARIA accessibility attributes 17414 * 17415 * @param {EventTarget~Event} [event] 17416 * The `volumechange` event that caused this function to run. 17417 * 17418 * @listens Player#volumechange 17419 */ 17420 ; 17421 17422 _proto.updateARIAAttributes = function updateARIAAttributes(event) { 17423 var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); 17424 this.el_.setAttribute('aria-valuenow', ariaValue); 17425 this.el_.setAttribute('aria-valuetext', ariaValue + '%'); 17426 } 17427 /** 17428 * Returns the current value of the player volume as a percentage 17429 * 17430 * @private 17431 */ 17432 ; 17433 17434 _proto.volumeAsPercentage_ = function volumeAsPercentage_() { 17435 return Math.round(this.player_.volume() * 100); 17436 } 17437 /** 17438 * When user starts dragging the VolumeBar, store the volume and listen for 17439 * the end of the drag. When the drag ends, if the volume was set to zero, 17440 * set lastVolume to the stored volume. 17441 * 17442 * @listens slideractive 17443 * @private 17444 */ 17445 ; 17446 17447 _proto.updateLastVolume_ = function updateLastVolume_() { 17448 var _this2 = this; 17449 17450 var volumeBeforeDrag = this.player_.volume(); 17451 this.one('sliderinactive', function () { 17452 if (_this2.player_.volume() === 0) { 17453 _this2.player_.lastVolume_(volumeBeforeDrag); 17454 } 17455 }); 17456 }; 17457 17458 return VolumeBar; 17459 }
17459(Slider); 17460 /** 17461 * Default options for the `VolumeBar` 17462 * 17463 * @type {Object} 17464 * @private 17465 */ 17466 17467 17468 VolumeBar.prototype.options_ = { 17469 children: ['volumeLevel'], 17470 barName: 'volumeLevel' 17471 }; // MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices 17472 17473 if (!IS_IOS && !IS_ANDROID) { 17474 VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay'); 17475 } 17476 /** 17477 * Call the update event for this Slider when this event happens on the player. 17478 * 17479 * @type {string} 17480 */ 17481 17482 17483 VolumeBar.prototype.playerEvent = 'volumechange'; 17484 Component$1.registerComponent('VolumeBar', VolumeBar); 17485 17486 /** 17487 * The component for controlling the volume level 17488 * 17489 * @extends Component 17490 */ 17491 17492 var VolumeControl = /*#__PURE__*/function (_Component) { 17493 inheritsLoose(VolumeControl, _Component); 17494 17495 /** 17496 * Creates an instance of this class. 17497 * 17498 * @param {Player} player 17499 * The `Player` that this class should be attached to. 17500 * 17501 * @param {Object} [options={}] 17502 * The key/value store of player options. 17503 */ 17504 function VolumeControl(player, options) { 17505 var _this; 17506 17507 if (options === void 0) { 17508 options = {}; 17509 } 17510 17511 options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if 17512 // the VolumeBar is turned on. 17513 17514 if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { 17515 options.volumeBar = options.volumeBar || {}; 17516 options.volumeBar.vertical = options.vertical; 17517 } 17518 17519 _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing 17520 17521 checkVolumeSupport(assertThisInitialized(_this), player); 17522 _this.throttledHandleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 17523 17524 _this.handleMouseUpHandler_ = function (e) { 17525 return _this.handleMouseUp(e); 17526 }; 17527 17528 _this.on('mousedown', function (e) { 17529 return _this.handleMouseDown(e); 17530 }); 17531 17532 _this.on('touchstart', function (e) { 17533 return _this.handleMouseDown(e); 17534 }); 17535 17536 _this.on('mousemove', function (e) { 17537 return _this.handleMouseMove(e); 17538 }); // while the slider is active (the mouse has been pressed down and 17539 // is dragging) or in focus we do not want to hide the VolumeBar 17540 17541 17542 _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { 17543 _this.volumeBar.addClass('vjs-slider-active'); 17544 17545 _this.addClass('vjs-slider-active'); 17546 17547 _this.trigger('slideractive'); 17548 }); 17549 17550 _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { 17551 _this.volumeBar.removeClass('vjs-slider-active'); 17552 17553 _this.removeClass('vjs-slider-active'); 17554 17555 _this.trigger('sliderinactive'); 17556 }); 17557 17558 return _this; 17559 } 17560 /** 17561 * Create the `Component`'s DOM element 17562 * 17563 * @return {Element} 17564 * The element that was created. 17565 */ 17566 17567 17568 var _proto = VolumeControl.prototype; 17569 17570 _proto.createEl = function createEl() { 17571 var orientationClass = 'vjs-volume-horizontal'; 17572 17573 if (this.options_.vertical) { 17574 orientationClass = 'vjs-volume-vertical'; 17575 } 17576 17577 return _Component.prototype.createEl.call(this, 'div', { 17578 className: "vjs-volume-control vjs-control " + orientationClass 17579 }); 17580 } 17581 /** 17582 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 17583 * 17584 * @param {EventTarget~Event} event 17585 * `mousedown` or `touchstart` event that triggered this function 17586 * 17587 * @listens mousedown 17588 * @listens touchstart 17589 */ 17590 ; 17591 17592 _proto.handleMouseDown = function handleMouseDown(event) { 17593 var doc = this.el_.ownerDocument; 17594 this.on(doc, 'mousemove', this.throttledHandleMouseMove); 17595 this.on(doc, 'touchmove', this.throttledHandleMouseMove); 17596 this.on(doc, 'mouseup', this.handleMouseUpHandler_); 17597 this.on(doc, 'touchend', this.handleMouseUpHandler_); 17598 } 17599 /** 17600 * Handle `mouseup` or `touchend` events on the `VolumeControl`. 17601 * 17602 * @param {EventTarget~Event} event 17603 * `mouseup` or `touchend` event that triggered this function. 17604 * 17605 * @listens touchend 17606 * @listens mouseup 17607 */ 17608 ; 17609 17610 _proto.handleMouseUp = function handleMouseUp(event) { 17611 var doc = this.el_.ownerDocument; 17612 this.off(doc, 'mousemove', this.throttledHandleMouseMove); 17613 this.off(doc, 'touchmove', this.throttledHandleMouseMove); 17614 this.off(doc, 'mouseup', this.handleMouseUpHandler_); 17615 this.off(doc, 'touchend', this.handleMouseUpHandler_); 17616 } 17617 /** 17618 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 17619 * 17620 * @param {EventTarget~Event} event 17621 * `mousedown` or `touchstart` event that triggered this function 17622 * 17623 * @listens mousedown 17624 * @listens touchstart 17625 */ 17626 ; 17627 17628 _proto.handleMouseMove = function handleMouseMove(event) { 17629 this.volumeBar.handleMouseMove(event); 17630 }; 17631 17632 return VolumeControl; 17633 }(Component$1); 17634 /** 17635 * Default options for the `VolumeControl` 17636 * 17637 * @type {Object} 17638 * @private 17639 */ 17640 17641 17642 VolumeControl.prototype.options_ = { 17643 children: ['volumeBar'] 17644 }; 17645 Component$1.registerComponent('VolumeControl', VolumeControl); 17646 17647 /** 17648 * Check if muting volume is supported and if it isn't hide the mute toggle 17649 * button. 17650 * 17651 * @param {Component} self 17652 * A reference to the mute toggle button 17653 * 17654 * @param {Player} player 17655 * A reference to the player 17656 * 17657 * @private 17658 */ 17659 var checkMuteSupport = function checkMuteSupport(self, player) { 17660 // hide mute toggle button if it's not supported by the current tech 17661 if (player.tech_ && !player.tech_.featuresMuteControl) { 17662 self.addClass('vjs-hidden'); 17663 } 17664 17665 self.on(player, 'loadstart', function () { 17666 if (!player.tech_.featuresMuteControl) { 17667 self.addClass('vjs-hidden'); 17668 } else { 17669 self.removeClass('vjs-hidden'); 17670 } 17671 });
17672 }; 17673 17674 /** 17675 * A button component for muting the audio. 17676 * 17677 * @extends Button 17678 */ 17679 17680 var MuteToggle = /*#__PURE__*/function (_Button) { 17681 inheritsLoose(MuteToggle, _Button); 17682 17683 /** 17684 * Creates an instance of this class. 17685 * 17686 * @param {Player} player 17687 * The `Player` that this class should be attached to. 17688 * 17689 * @param {Object} [options] 17690 * The key/value store of player options. 17691 */ 17692 function MuteToggle(player, options) { 17693 var _this; 17694 17695 _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing 17696 17697 checkMuteSupport(assertThisInitialized(_this), player); 17698 17699 _this.on(player, ['loadstart', 'volumechange'], function (e) { 17700 return _this.update(e); 17701 }); 17702 17703 return _this; 17704 } 17705 /** 17706 * Builds the default DOM `className`. 17707 * 17708 * @return {string} 17709 * The DOM `className` for this object. 17710 */ 17711 17712 17713 var _proto = MuteToggle.prototype; 17714 17715 _proto.buildCSSClass = function buildCSSClass() { 17716 return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this); 17717 } 17718 /** 17719 * This gets called when an `MuteToggle` is "clicked". See 17720 * {@link ClickableComponent} for more detailed information on what a click can be. 17721 * 17722 * @param {EventTarget~Event} [event] 17723 * The `keydown`, `tap`, or `click` event that caused this function to be 17724 * called. 17725 * 17726 * @listens tap 17727 * @listens click 17728 */ 17729 ; 17730 17731 _proto.handleClick = function handleClick(event) { 17732 var vol = this.player_.volume(); 17733 var lastVolume = this.player_.lastVolume_(); 17734 17735 if (vol === 0) { 17736 var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; 17737 this.player_.volume(volumeToSet); 17738 this.player_.muted(false); 17739 } else { 17740 this.player_.muted(this.player_.muted() ? false : true); 17741 } 17742 } 17743 /** 17744 * Update the `MuteToggle` button based on the state of `volume` and `muted` 17745 * on the player. 17746 * 17747 * @param {EventTarget~Event} [event] 17748 * The {@link Player#loadstart} event if this function was called 17749 * through an event. 17750 * 17751 * @listens Player#loadstart 17752 * @listens Player#volumechange 17753 */ 17754 ; 17755 17756 _proto.update = function update(event) { 17757 this.updateIcon_(); 17758 this.updateControlText_(); 17759 } 17760 /** 17761 * Update the appearance of the `MuteToggle` icon. 17762 * 17763 * Possible states (given `level` variable below): 17764 * - 0: crossed out 17765 * - 1: zero bars of volume 17766 * - 2: one bar of volume 17767 * - 3: two bars of volume 17768 * 17769 * @private 17770 */ 17771 ; 17772 17773 _proto.updateIcon_ = function updateIcon_() { 17774 var vol = this.player_.volume(); 17775 var level = 3; // in iOS when a player is loaded with muted attribute 17776 // and volume is changed with a native mute button 17777 // we want to make sure muted state is updated 17778 17779 if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) { 17780 this.player_.muted(this.player_.tech_.el_.muted); 17781 } 17782 17783 if (vol === 0 || this.player_.muted()) { 17784 level = 0; 17785 } else if (vol < 0.33) { 17786 level = 1; 17787 } else if (vol < 0.67) { 17788 level = 2; 17789 } // TODO improve muted icon classes 17790 17791 17792 for (var i = 0; i < 4; i++) { 17793 removeClass(this.el_, "vjs-vol-" + i); 17794 } 17795 17796 addClass(this.el_, "vjs-vol-" + level); 17797 } 17798 /** 17799 * If `muted` has changed on the player, update the control text 17800 * (`title` attribute on `vjs-mute-control` element and content of 17801 * `vjs-control-text` element). 17802 * 17803 * @private 17804 */ 17805 ; 17806 17807 _proto.updateControlText_ = function updateControlText_() { 17808 var soundOff = this.player_.muted() || this.player_.volume() === 0; 17809 var text = soundOff ? 'Unmute' : 'Mute'; 17810 17811 if (this.controlText() !== text) { 17812 this.controlText(text); 17813 } 17814 }; 17815 17816 return MuteToggle; 17817 }(Button); 17818 /** 17819 * The text that should display over the `MuteToggle`s controls. Added for localization. 17820 * 17821 * @type {string} 17822 * @private 17823 */ 17824 17825 17826 MuteToggle.prototype.controlText_ = 'Mute'; 17827 Component$1.registerComponent('MuteToggle', MuteToggle); 17828 17829 /** 17830 * A Component to contain the MuteToggle and VolumeControl so that 17831 * they can work together. 17832 * 17833 * @extends Component 17834 */ 17835 17836 var VolumePanel = /*#__PURE__*/function (_Component) { 17837 inheritsLoose(VolumePanel, _Component); 17838 17839 /** 17840 * Creates an instance of this class. 17841 * 17842 * @param {Player} player 17843 * The `Player` that this class should be attached to. 17844 * 17845 * @param {Object} [options={}] 17846 * The key/value store of player options. 17847 */ 17848 function VolumePanel(player, options) { 17849 var _this; 17850 17851 if (options === void 0) { 17852 options = {}; 17853 } 17854 17855 if (typeof options.inline !== 'undefined') { 17856 options.inline = options.inline; 17857 } else { 17858 options.inline = true; 17859 } // pass the inline option down to the VolumeControl as vertical if 17860 // the VolumeControl is on. 17861 17862 17863 if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { 17864 options.volumeControl = options.volumeControl || {}; 17865 options.volumeControl.vertical = !options.inline; 17866 } 17867 17868 _this = _Component.call(this, player, options) || this; // this handler is used by mouse handler methods below 17869 17870 _this.handleKeyPressHandler_ = function (e) { 17871 return _this.handleKeyPress(e); 17872 }; 17873 17874 _this.on(player, ['loadstart'], function (e) { 17875 return _this.volumePanelState_(e); 17876 }); 17877
17878 _this.on(_this.muteToggle, 'keyup', function (e) { 17879 return _this.handleKeyPress(e); 17880 }); 17881 17882 _this.on(_this.volumeControl, 'keyup', function (e) { 17883 return _this.handleVolumeControlKeyUp(e); 17884 }); 17885 17886 _this.on('keydown', function (e) { 17887 return _this.handleKeyPress(e); 17888 }); 17889 17890 _this.on('mouseover', function (e) { 17891 return _this.handleMouseOver(e); 17892 }); 17893 17894 _this.on('mouseout', function (e) { 17895 return _this.handleMouseOut(e); 17896 }); // while the slider is active (the mouse has been pressed down and 17897 // is dragging) we do not want to hide the VolumeBar 17898 17899 17900 _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); 17901 17902 _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); 17903 17904 return _this; 17905 } 17906 /** 17907 * Add vjs-slider-active class to the VolumePanel 17908 * 17909 * @listens VolumeControl#slideractive 17910 * @private 17911 */ 17912 17913 17914 var _proto = VolumePanel.prototype; 17915 17916 _proto.sliderActive_ = function sliderActive_() { 17917 this.addClass('vjs-slider-active'); 17918 } 17919 /** 17920 * Removes vjs-slider-active class to the VolumePanel 17921 * 17922 * @listens VolumeControl#sliderinactive 17923 * @private 17924 */ 17925 ; 17926 17927 _proto.sliderInactive_ = function sliderInactive_() { 17928 this.removeClass('vjs-slider-active'); 17929 } 17930 /** 17931 * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel 17932 * depending on MuteToggle and VolumeControl state 17933 * 17934 * @listens Player#loadstart 17935 * @private 17936 */ 17937 ; 17938 17939 _proto.volumePanelState_ = function volumePanelState_() { 17940 // hide volume panel if neither volume control or mute toggle 17941 // are displayed 17942 if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) { 17943 this.addClass('vjs-hidden'); 17944 } // if only mute toggle is visible we don't want 17945 // volume panel expanding when hovered or active 17946 17947 17948 if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) { 17949 this.addClass('vjs-mute-toggle-only'); 17950 } 17951 } 17952 /** 17953 * Create the `Component`'s DOM element 17954 * 17955 * @return {Element} 17956 * The element that was created. 17957 */ 17958 ; 17959 17960 _proto.createEl = function createEl() { 17961 var orientationClass = 'vjs-volume-panel-horizontal'; 17962 17963 if (!this.options_.inline) { 17964 orientationClass = 'vjs-volume-panel-vertical'; 17965 } 17966 17967 return _Component.prototype.createEl.call(this, 'div', { 17968 className: "vjs-volume-panel vjs-control " + orientationClass 17969 }); 17970 } 17971 /** 17972 * Dispose of the `volume-panel` and all child components. 17973 */ 17974 ; 17975 17976 _proto.dispose = function dispose() { 17977 this.handleMouseOut(); 17978 17979 _Component.prototype.dispose.call(this); 17980 } 17981 /** 17982 * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes 17983 * the volume panel and sets focus on `MuteToggle`. 17984 * 17985 * @param {EventTarget~Event} event 17986 * The `keyup` event that caused this function to be called. 17987 * 17988 * @listens keyup 17989 */ 17990 ; 17991 17992 _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) { 17993 if (keycode.isEventKey(event, 'Esc')) { 17994 this.muteToggle.focus(); 17995 } 17996 } 17997 /** 17998 * This gets called when a `VolumePanel` gains hover via a `mouseover` event. 17999 * Turns on listening for `mouseover` event. When they happen it 18000 * calls `this.handleMouseOver`. 18001 * 18002 * @param {EventTarget~Event} event 18003 * The `mouseover` event that caused this function to be called. 18004 * 18005 * @listens mouseover 18006 */ 18007 ; 18008 18009 _proto.handleMouseOver = function handleMouseOver(event) { 18010 this.addClass('vjs-hover'); 18011 on(document_1, 'keyup', this.handleKeyPressHandler_); 18012 } 18013 /** 18014 * This gets called when a `VolumePanel` gains hover via a `mouseout` event. 18015 * Turns on listening for `mouseout` event. When they happen it 18016 * calls `this.handleMouseOut`. 18017 * 18018 * @param {EventTarget~Event} event 18019 * The `mouseout` event that caused this function to be called. 18020 * 18021 * @listens mouseout 18022 */ 18023 ; 18024 18025 _proto.handleMouseOut = function handleMouseOut(event) { 18026 this.removeClass('vjs-hover'); 18027 off(document_1, 'keyup', this.handleKeyPressHandler_); 18028 } 18029 /** 18030 * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`, 18031 * looking for ESC, which hides the `VolumeControl`. 18032 * 18033 * @param {EventTarget~Event} event 18034 * The keypress that triggered this event. 18035 * 18036 * @listens keydown | keyup 18037 */ 18038 ; 18039 18040 _proto.handleKeyPress = function handleKeyPress(event) { 18041 if (keycode.isEventKey(event, 'Esc')) { 18042 this.handleMouseOut(); 18043 } 18044 }; 18045 18046 return VolumePanel; 18047 }(Component$1); 18048 /** 18049 * Default options for the `VolumeControl` 18050 * 18051 * @type {Object} 18052 * @private 18053 */ 18054 18055 18056 VolumePanel.prototype.options_ = { 18057 children: ['muteToggle', 'volumeControl'] 18058 }; 18059 Component$1.registerComponent('VolumePanel', VolumePanel); 18060 18061 /** 18062 * The Menu component is used to build popup menus, including subtitle and 18063 * captions selection menus. 18064 * 18065 * @extends Component 18066 */ 18067 18068 var Menu = /*#__PURE__*/function (_Component) { 18069 inheritsLoose(Menu, _Component); 18070 18071 /** 18072 * Create an instance of this class. 18073 * 18074 * @param {Player} player 18075 * the player that this component should attach to 18076 * 18077 * @param {Object} [options] 18078 * Object of option names and values 18079 * 18080 */ 18081 function Menu(player, options) { 18082 var _this; 18083 18084 _this = _Component.call(this, player, options) || this; 18085 18086 if (options) { 18087 _this.menuButton_ = options.menuButton; 18088 } 18089 18090 _this.focusedChild_ = -1; 18091 18092 _this.on('keydown', function (e) { 18093 return _this.handleKeyDown(e); 18094 }); // All the menu item instances share the same blur handler provided by the menu container. 18095 18096 18097 _this.boundHandleBlur_ = function (e) { 18098 return _this.handleBlur(e); 18099 }; 18100 18101 _this.boundHandleTapClick_ = function (e) { 18102 return _this.handleTapClick(e); 18103 }; 18104 18105 return _this; 18106 } 18107 /** 18108 * Add event listeners to the {@link MenuItem}. 18109 * 18110 * @param {Object} component 18111 * The instance of the `MenuItem` to add listeners to. 18112 * 18113 */ 18114 18115
18116 var _proto = Menu.prototype; 18117 18118 _proto.addEventListenerForItem = function addEventListenerForItem(component) { 18119 if (!(component instanceof Component$1)) { 18120 return; 18121 } 18122 18123 this.on(component, 'blur', this.boundHandleBlur_); 18124 this.on(component, ['tap', 'click'], this.boundHandleTapClick_); 18125 } 18126 /** 18127 * Remove event listeners from the {@link MenuItem}. 18128 * 18129 * @param {Object} component 18130 * The instance of the `MenuItem` to remove listeners. 18131 * 18132 */ 18133 ; 18134 18135 _proto.removeEventListenerForItem = function removeEventListenerForItem(component) { 18136 if (!(component instanceof Component$1)) { 18137 return; 18138 } 18139 18140 this.off(component, 'blur', this.boundHandleBlur_); 18141 this.off(component, ['tap', 'click'], this.boundHandleTapClick_); 18142 } 18143 /** 18144 * This method will be called indirectly when the component has been added 18145 * before the component adds to the new menu instance by `addItem`. 18146 * In this case, the original menu instance will remove the component 18147 * by calling `removeChild`. 18148 * 18149 * @param {Object} component 18150 * The instance of the `MenuItem` 18151 */ 18152 ; 18153 18154 _proto.removeChild = function removeChild(component) { 18155 if (typeof component === 'string') { 18156 component = this.getChild(component); 18157 } 18158 18159 this.removeEventListenerForItem(component); 18160 18161 _Component.prototype.removeChild.call(this, component); 18162 } 18163 /** 18164 * Add a {@link MenuItem} to the menu. 18165 * 18166 * @param {Object|string} component 18167 * The name or instance of the `MenuItem` to add. 18168 * 18169 */ 18170 ; 18171 18172 _proto.addItem = function addItem(component) { 18173 var childComponent = this.addChild(component); 18174 18175 if (childComponent) { 18176 this.addEventListenerForItem(childComponent); 18177 } 18178 } 18179 /** 18180 * Create the `Menu`s DOM element. 18181 * 18182 * @return {Element} 18183 * the element that was created 18184 */ 18185 ; 18186 18187 _proto.createEl = function createEl$1() { 18188 var contentElType = this.options_.contentElType || 'ul'; 18189 this.contentEl_ = createEl(contentElType, { 18190 className: 'vjs-menu-content' 18191 }); 18192 this.contentEl_.setAttribute('role', 'menu'); 18193 18194 var el = _Component.prototype.createEl.call(this, 'div', { 18195 append: this.contentEl_, 18196 className: 'vjs-menu' 18197 }); 18198 18199 el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons, 18200 // where a click on the parent is significant 18201 on(el, 'click', function (event) { 18202 var target = (window.$(event.target).hasClass('vjs-menu-item'))? window.$(event.target):window.$(event.target).parents('.vjs-menu-item').first(); 18203 if(window.$(event.target).parents('.vjs-subs-caps-button').length > 0 && target.length>0){ 18204 window.$('button.vjs-subs-caps-button')[0].innerHTML = window._subtitleList.find(subtitle => subtitle.language.toLowerCase() == (target.data('label').toLowerCase() || 'off')).icon; 18205 window.$('.vjs-subs-caps-button .vjs-menu-item').removeClass('vjs-selected'); 18206 target.addClass('vjs-selected'); 18207 } 18208 event.preventDefault(); 18209 event.stopImmediatePropagation(); 18210 }); 18211 18212 on(el, 'touchend', function (event) { 18213 var target = (window.$(event.target).hasClass('vjs-menu-item'))? window.$(event.target):window.$(event.target).parents('.vjs-menu-item').first(); 18214 if(window.$(event.target).parents('.vjs-subs-caps-button').length > 0 && target.length>0){ 18215 window.$('button.vjs-subs-caps-button')[0].innerHTML = window._subtitleList.find(subtitle => subtitle.language.toLowerCase() == (target.data('label').toLowerCase() || 'off')).icon; 18216 window.$('.vjs-subs-caps-button .vjs-menu-item').removeClass('vjs-selected'); 18217 target.addClass('vjs-selected'); 18218 } 18219 event.preventDefault(); 18220 event.stopImmediatePropagation(); 18221 }); 18222 return el; 18223 }; 18224 18225 _proto.dispose = function dispose() { 18226 this.contentEl_ = null; 18227 this.boundHandleBlur_ = null; 18228 this.boundHandleTapClick_ = null; 18229 18230 _Component.prototype.dispose.call(this); 18231 } 18232 /** 18233 * Called when a `MenuItem` loses focus. 18234 * 18235 * @param {EventTarget~Event} event 18236 * The `blur` event that caused this function to be called. 18237 * 18238 * @listens blur 18239 */ 18240 ; 18241 18242 _proto.handleBlur = function handleBlur(event) { 18243 var relatedTarget = event.relatedTarget || document_1.activeElement; // Close menu popup when a user clicks outside the menu 18244 18245 if (!this.children().some(function (element) { 18246 return element.el() === relatedTarget; 18247 })) { 18248 var btn = this.menuButton_; 18249 18250 if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) { 18251 btn.unpressButton(); 18252 } 18253 } 18254 } 18255 /** 18256 * Called when a `MenuItem` gets clicked or tapped. 18257 * 18258 * @param {EventTarget~Event} event 18259 * The `click` or `tap` event that caused this function to be called. 18260 * 18261 * @listens click,tap 18262 */ 18263 ; 18264 18265 _proto.handleTapClick = function handleTapClick(event) { 18266 // Unpress the associated MenuButton, and move focus back to it 18267 if (this.menuButton_) { 18268 this.menuButton_.unpressButton(); 18269 var childComponents = this.children();
18270 18271 if (!Array.isArray(childComponents)) { 18272 return; 18273 } 18274 18275 var foundComponent = childComponents.filter(function (component) { 18276 return component.el() === event.target; 18277 })[0]; 18278 18279 if (!foundComponent) { 18280 return; 18281 } // don't focus menu button if item is a caption settings item 18282 // because focus will move elsewhere 18283 18284 18285 if (foundComponent.name() !== 'CaptionSettingsMenuItem') { 18286 this.menuButton_.focus(); 18287 } 18288 } 18289 } 18290 /** 18291 * Handle a `keydown` event on this menu. This listener is added in the constructor. 18292 * 18293 * @param {EventTarget~Event} event 18294 * A `keydown` event that happened on the menu. 18295 * 18296 * @listens keydown 18297 */ 18298 ; 18299 18300 _proto.handleKeyDown = function handleKeyDown(event) { 18301 // Left and Down Arrows 18302 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 18303 event.preventDefault(); 18304 event.stopPropagation(); 18305 this.stepForward(); // Up and Right Arrows 18306 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 18307 event.preventDefault(); 18308 event.stopPropagation(); 18309 this.stepBack(); 18310 } 18311 } 18312 /** 18313 * Move to next (lower) menu item for keyboard users. 18314 */ 18315 ; 18316 18317 _proto.stepForward = function stepForward() { 18318 var stepChild = 0; 18319 18320 if (this.focusedChild_ !== undefined) { 18321 stepChild = this.focusedChild_ + 1; 18322 } 18323 18324 this.focus(stepChild); 18325 } 18326 /** 18327 * Move to previous (higher) menu item for keyboard users. 18328 */ 18329 ; 18330 18331 _proto.stepBack = function stepBack() { 18332 var stepChild = 0; 18333 18334 if (this.focusedChild_ !== undefined) { 18335 stepChild = this.focusedChild_ - 1; 18336 } 18337 18338 this.focus(stepChild); 18339 } 18340 /** 18341 * Set focus on a {@link MenuItem} in the `Menu`. 18342 * 18343 * @param {Object|string} [item=0] 18344 * Index of child item set focus on. 18345 */ 18346 ; 18347 18348 _proto.focus = function focus(item) { 18349 if (item === void 0) { 18350 item = 0; 18351 } 18352 18353 var children = this.children().slice(); 18354 var haveTitle = children.length && children[0].hasClass('vjs-menu-title'); 18355 18356 if (haveTitle) { 18357 children.shift(); 18358 } 18359 18360 if (children.length > 0) { 18361 if (item < 0) { 18362 item = 0; 18363 } else if (item >= children.length) { 18364 item = children.length - 1; 18365 } 18366 18367 this.focusedChild_ = item; 18368 children[item].el_.focus(); 18369 } 18370 }; 18371 18372 return Menu; 18373 }(Component$1); 18374 18375 Component$1.registerComponent('Menu', Menu); 18376 18377 /** 18378 * A `MenuButton` class for any popup {@link Menu}. 18379 * 18380 * @extends Component 18381 */ 18382 18383 var MenuButton = /*#__PURE__*/function (_Component) { 18384 inheritsLoose(MenuButton, _Component); 18385 18386 /** 18387 * Creates an instance of this class. 18388 * 18389 * @param {Player} player 18390 * The `Player` that this class should be attached to. 18391 * 18392 * @param {Object} [options={}] 18393 * The key/value store of player options. 18394 */ 18395 function MenuButton(player, options) { 18396 var _this; 18397 18398 if (options === void 0) { 18399 options = {}; 18400 } 18401 18402 _this = _Component.call(this, player, options) || this; 18403 _this.menuButton_ = new Button(player, options); 18404 18405 _this.menuButton_.controlText(_this.controlText_); 18406 18407 _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper 18408 18409 18410 var buttonClass = Button.prototype.buildCSSClass(); 18411 _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; 18412 18413 _this.menuButton_.removeClass('vjs-control'); 18414 18415 _this.addChild(_this.menuButton_); 18416 18417 _this.update(); 18418 18419 _this.enabled_ = true; 18420 18421 var handleClick = function handleClick(e) { 18422 return _this.handleClick(e); 18423 }; 18424 18425 _this.handleMenuKeyUp_ = function (e) { 18426 return _this.handleMenuKeyUp(e); 18427 }; 18428 18429 _this.on(_this.menuButton_, 'tap', handleClick); 18430 18431 _this.on(_this.menuButton_, 'click', handleClick); 18432 18433 _this.on(_this.menuButton_, 'keydown', function (e) { 18434 return _this.handleKeyDown(e); 18435 }); 18436 18437 _this.on(_this.menuButton_, 'mouseenter', function () { 18438 _this.addClass('vjs-hover'); 18439 18440 _this.menu.show(); 18441 18442 on(document_1, 'keyup', _this.handleMenuKeyUp_); 18443 }); 18444 18445 _this.on('mouseleave', function (e) { 18446 return _this.handleMouseLeave(e); 18447 }); 18448 18449 _this.on('keydown', function (e) { 18450 return _this.handleSubmenuKeyDown(e); 18451 }); 18452 18453 return _this; 18454 } 18455 /** 18456 * Update the menu based on the current state of its items. 18457 */ 18458 18459
18460 var _proto = MenuButton.prototype; 18461 18462 _proto.update = function update() { 18463 var menu = this.createMenu(); 18464 18465 if (this.menu) { 18466 this.menu.dispose(); 18467 this.removeChild(this.menu); 18468 } 18469 18470 this.menu = menu; 18471 this.addChild(menu); 18472 /** 18473 * Track the state of the menu button 18474 * 18475 * @type {Boolean} 18476 * @private 18477 */ 18478 18479 this.buttonPressed_ = false; 18480 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 18481 18482 if (this.items && this.items.length <= this.hideThreshold_) { 18483 this.hide(); 18484 } else { 18485 this.show(); 18486 } 18487 } 18488 /** 18489 * Create the menu and add all items to it. 18490 * 18491 * @return {Menu} 18492 * The constructed menu 18493 */ 18494 ; 18495 18496 _proto.createMenu = function createMenu() { 18497 var menu = new Menu(this.player_, { 18498 menuButton: this 18499 }); 18500 /** 18501 * Hide the menu if the number of items is less than or equal to this threshold. This defaults 18502 * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list 18503 * it here because every time we run `createMenu` we need to reset the value. 18504 * 18505 * @protected 18506 * @type {Number} 18507 */ 18508 18509 this.hideThreshold_ = 0; // Add a title list item to the top 18510 18511 if (this.options_.title) { 18512 var titleEl = createEl('li', { 18513 className: 'vjs-menu-title', 18514 textContent: toTitleCase$1(this.options_.title), 18515 tabIndex: -1 18516 }); 18517 var titleComponent = new Component$1(this.player_, { 18518 el: titleEl 18519 }); 18520 menu.addItem(titleComponent); 18521 } 18522 18523 this.items = this.createItems(); 18524 18525 if (this.items) { 18526 // Add menu items to the menu 18527 for (var i = 0; i < this.items.length; i++) { 18528 menu.addItem(this.items[i]); 18529 } 18530 } 18531 18532 return menu; 18533 } 18534 /** 18535 * Create the list of menu items. Specific to each subclass. 18536 * 18537 * @abstract 18538 */ 18539 ; 18540 18541 _proto.createItems = function createItems() {} 18542 /** 18543 * Create the `MenuButtons`s DOM element. 18544 * 18545 * @return {Element} 18546 * The element that gets created. 18547 */ 18548 ; 18549 18550 _proto.createEl = function createEl() { 18551 return _Component.prototype.createEl.call(this, 'div', { 18552 className: this.buildWrapperCSSClass() 18553 }, {}); 18554 } 18555 /** 18556 * Allow sub components to stack CSS class names for the wrapper element 18557 * 18558 * @return {string} 18559 * The constructed wrapper DOM `className` 18560 */ 18561 ; 18562 18563 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18564 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 18565 18566 if (this.options_.inline === true) { 18567 menuButtonClass += '-inline'; 18568 } else { 18569 menuButtonClass += '-popup'; 18570 } // TODO: Fix the CSS so that this isn't necessary 18571 18572 18573 var buttonClass = Button.prototype.buildCSSClass(); 18574 return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this); 18575 } 18576 /** 18577 * Builds the default DOM `className`. 18578 * 18579 * @return {string} 18580 * The DOM `className` for this object. 18581 */ 18582 ; 18583 18584 _proto.buildCSSClass = function buildCSSClass() { 18585 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 18586 18587 if (this.options_.inline === true) { 18588 menuButtonClass += '-inline'; 18589 } else { 18590 menuButtonClass += '-popup'; 18591 } 18592 18593 return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this); 18594 } 18595 /** 18596 * Get or set the localized control text that will be used for accessibility. 18597 * 18598 * > NOTE: This will come from the internal `menuButton_` element. 18599 * 18600 * @param {string} [text] 18601 * Control text for element. 18602 * 18603 * @param {Element} [el=this.menuButton_.el()] 18604 * Element to set the title on. 18605 * 18606 * @return {string} 18607 * - The control text when getting 18608 */ 18609 ; 18610 18611 _proto.controlText = function controlText(text, el) { 18612 if (el === void 0) { 18613 el = this.menuButton_.el(); 18614 } 18615 18616 return this.menuButton_.controlText(text, el); 18617 } 18618 /**
18619 * Dispose of the `menu-button` and all child components. 18620 */ 18621 ; 18622 18623 _proto.dispose = function dispose() { 18624 this.handleMouseLeave(); 18625 18626 _Component.prototype.dispose.call(this); 18627 } 18628 /** 18629 * Handle a click on a `MenuButton`. 18630 * See {@link ClickableComponent#handleClick} for instances where this is called. 18631 * 18632 * @param {EventTarget~Event} event 18633 * The `keydown`, `tap`, or `click` event that caused this function to be 18634 * called. 18635 * 18636 * @listens tap 18637 * @listens click 18638 */ 18639 ; 18640 18641 _proto.handleClick = function handleClick(event) { 18642 if (this.buttonPressed_) { 18643 this.unpressButton(); 18644 } else { 18645 this.pressButton(); 18646 } 18647 } 18648 /** 18649 * Handle `mouseleave` for `MenuButton`. 18650 * 18651 * @param {EventTarget~Event} event 18652 * The `mouseleave` event that caused this function to be called. 18653 * 18654 * @listens mouseleave 18655 */ 18656 ; 18657 18658 _proto.handleMouseLeave = function handleMouseLeave(event) { 18659 this.removeClass('vjs-hover'); 18660 off(document_1, 'keyup', this.handleMenuKeyUp_); 18661 } 18662 /** 18663 * Set the focus to the actual button, not to this element 18664 */ 18665 ; 18666 18667 _proto.focus = function focus() { 18668 this.menuButton_.focus(); 18669 } 18670 /** 18671 * Remove the focus from the actual button, not this element 18672 */ 18673 ; 18674 18675 _proto.blur = function blur() { 18676 this.menuButton_.blur(); 18677 } 18678 /** 18679 * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See 18680 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 18681 * 18682 * @param {EventTarget~Event} event 18683 * The `keydown` event that caused this function to be called. 18684 * 18685 * @listens keydown 18686 */ 18687 ; 18688 18689 _proto.handleKeyDown = function handleKeyDown(event) { 18690 // Escape or Tab unpress the 'button' 18691 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 18692 if (this.buttonPressed_) { 18693 this.unpressButton(); 18694 } // Don't preventDefault for Tab key - we still want to lose focus 18695 18696 18697 if (!keycode.isEventKey(event, 'Tab')) { 18698 event.preventDefault(); // Set focus back to the menu button's button 18699 18700 this.menuButton_.focus(); 18701 } // Up Arrow or Down Arrow also 'press' the button to open the menu 18702 18703 } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) { 18704 if (!this.buttonPressed_) { 18705 event.preventDefault(); 18706 this.pressButton(); 18707 } 18708 } 18709 } 18710 /** 18711 * Handle a `keyup` event on a `MenuButton`. The listener for this is added in 18712 * the constructor. 18713 * 18714 * @param {EventTarget~Event} event 18715 * Key press event 18716 * 18717 * @listens keyup 18718 */ 18719 ; 18720 18721 _proto.handleMenuKeyUp = function handleMenuKeyUp(event) { 18722 // Escape hides popup menu 18723 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 18724 this.removeClass('vjs-hover'); 18725 } 18726 } 18727 /** 18728 * This method name now delegates to `handleSubmenuKeyDown`. This means 18729 * anyone calling `handleSubmenuKeyPress` will not see their method calls 18730 * stop working. 18731 * 18732 * @param {EventTarget~Event} event 18733 * The event that caused this function to be called. 18734 */ 18735 ; 18736 18737 _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { 18738 this.handleSubmenuKeyDown(event); 18739 } 18740 /** 18741 * Handle a `keydown` event on a sub-menu. The listener for this is added in 18742 * the constructor. 18743 * 18744 * @param {EventTarget~Event} event 18745 * Key press event 18746 * 18747 * @listens keydown 18748 */ 18749 ; 18750 18751 _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) { 18752 // Escape or Tab unpress the 'button' 18753 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 18754 if (this.buttonPressed_) { 18755 this.unpressButton(); 18756 } // Don't preventDefault for Tab key - we still want to lose focus 18757 18758 18759 if (!keycode.isEventKey(event, 'Tab')) { 18760 event.preventDefault(); // Set focus back to the menu button's button 18761 18762 this.menuButton_.focus(); 18763 } 18764 } 18765 } 18766 /** 18767 * Put the current `MenuButton` into a pressed state. 18768 */ 18769 ; 18770 18771 _proto.pressButton = function pressButton() { 18772 if (this.enabled_) { 18773 this.buttonPressed_ = true; 18774 this.menu.show(); 18775 this.menu.lockShowing(); 18776 this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in 18777 // undesired scrolling behavior when the player is in an iframe 18778 18779 if (IS_IOS && isInFrame()) { 18780 // Return early so that the menu isn't focused 18781 return; 18782 } 18783 18784 this.menu.focus(); 18785 } 18786 } 18787 /** 18788 * Take the current `MenuButton` out of a pressed state. 18789 */ 18790 ; 18791 18792 _proto.unpressButton = function unpressButton() { 18793 if (this.enabled_) { 18794 this.buttonPressed_ = false; 18795 this.menu.unlockShowing(); 18796 this.menu.hide(); 18797 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 18798 } 18799 } 18800 /** 18801 * Disable the `MenuButton`. Don't allow it to be clicked. 18802 */ 18803 ; 18804 18805 _proto.disable = function disable() { 18806 this.unpressButton(); 18807 this.enabled_ = false; 18808 this.addClass('vjs-disabled'); 18809 this.menuButton_.disable(); 18810 } 18811 /** 18812 * Enable the `MenuButton`. Allow it to be clicked. 18813 */ 18814 ; 18815 18816 _proto.enable = function enable() { 18817 this.enabled_ = true; 18818 this.removeClass('vjs-disabled'); 18819 this.menuButton_.enable(); 18820 }; 18821
18822 return MenuButton; 18823 }(Component$1); 18824 18825 Component$1.registerComponent('MenuButton', MenuButton); 18826 18827 /** 18828 * The base class for buttons that toggle specific track types (e.g. subtitles). 18829 * 18830 * @extends MenuButton 18831 */ 18832 18833 var TrackButton = /*#__PURE__*/function (_MenuButton) { 18834 inheritsLoose(TrackButton, _MenuButton); 18835 18836 /** 18837 * Creates an instance of this class. 18838 * 18839 * @param {Player} player 18840 * The `Player` that this class should be attached to. 18841 * 18842 * @param {Object} [options] 18843 * The key/value store of player options. 18844 */ 18845 function TrackButton(player, options) { 18846 var _this; 18847 18848 var tracks = options.tracks; 18849 _this = _MenuButton.call(this, player, options) || this; 18850 18851 if (_this.items.length <= 1) { 18852 _this.hide(); 18853 } 18854 18855 if (!tracks) { 18856 return assertThisInitialized(_this); 18857 } 18858 18859 var updateHandler = bind(assertThisInitialized(_this), _this.update); 18860 tracks.addEventListener('removetrack', updateHandler); 18861 tracks.addEventListener('addtrack', updateHandler); 18862 tracks.addEventListener('labelchange', updateHandler); 18863 18864 _this.player_.on('ready', updateHandler); 18865 18866 _this.player_.on('dispose', function () { 18867 tracks.removeEventListener('removetrack', updateHandler); 18868 tracks.removeEventListener('addtrack', updateHandler); 18869 tracks.removeEventListener('labelchange', updateHandler); 18870 }); 18871 18872 return _this; 18873 } 18874 18875 return TrackButton; 18876 }(MenuButton); 18877 18878 Component$1.registerComponent('TrackButton', TrackButton); 18879 18880 /** 18881 * @file menu-keys.js 18882 */ 18883 18884 /** 18885 * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`) 18886 * Note that 'Enter' and 'Space' are not included here (otherwise they would 18887 * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable) 18888 * @typedef MenuKeys 18889 * @array 18890 */ 18891 var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left']; 18892 18893 /** 18894 * The component for a menu item. `<li>` 18895 * 18896 * @extends ClickableComponent 18897 */ 18898 18899 var MenuItem = /*#__PURE__*/function (_ClickableComponent) { 18900 inheritsLoose(MenuItem, _ClickableComponent); 18901 18902 /** 18903 * Creates an instance of the this class. 18904 * 18905 * @param {Player} player 18906 * The `Player` that this class should be attached to. 18907 * 18908 * @param {Object} [options={}] 18909 * The key/value store of player options. 18910 * 18911 */ 18912 function MenuItem(player, options) { 18913 var _this; 18914 18915 _this = _ClickableComponent.call(this, player, options) || this; 18916 _this.selectable = options.selectable; 18917 _this.isSelected_ = options.selected || false; 18918 _this.multiSelectable = options.multiSelectable; 18919 18920 _this.selected(_this.isSelected_); 18921 18922 if (_this.selectable) { 18923 if (_this.multiSelectable) { 18924 _this.el_.setAttribute('role', 'menuitemcheckbox'); 18925 } else { 18926 _this.el_.setAttribute('role', 'menuitemradio'); 18927 } 18928 } else { 18929 _this.el_.setAttribute('role', 'menuitem'); 18930 } 18931 18932 return _this; 18933 } 18934 /** 18935 * Create the `MenuItem's DOM element 18936 * 18937 * @param {string} [type=li] 18938 * Element's node type, not actually used, always set to `li`. 18939 * 18940 * @param {Object} [props={}] 18941 * An object of properties that should be set on the element 18942 * 18943 * @param {Object} [attrs={}] 18944 * An object of attributes that should be set on the element 18945 * 18946 * @return {Element} 18947 * The element that gets created. 18948 */ 18949 18950 18951 var _proto = MenuItem.prototype; 18952 18953 _proto.createEl = function createEl$1(type, props, attrs) { 18954 // The control is textual, not just an icon 18955 this.nonIconControl = true; 18956 18957 var el = _ClickableComponent.prototype.createEl.call(this, 'li', assign({ 18958 className: 'vjs-menu-item', 18959 tabIndex: -1 18960 }, props), attrs); // swap icon with menu item text. 18961 18962 18963 el.replaceChild(createEl('span', { 18964 className: 'vjs-menu-item-text',
18965 textContent: this.localize(this.options_.label) 18966 }), el.querySelector('.vjs-icon-placeholder')); 18967 18968 el.setAttribute("data-label", this?.options_?.label); 18969 18970 return el; 18971 } 18972 /** 18973 * Ignore keys which are used by the menu, but pass any other ones up. See 18974 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 18975 * 18976 * @param {EventTarget~Event} event 18977 * The `keydown` event that caused this function to be called. 18978 * 18979 * @listens keydown 18980 */ 18981 ; 18982 18983 _proto.handleKeyDown = function handleKeyDown(event) { 18984 if (!MenuKeys.some(function (key) { 18985 return keycode.isEventKey(event, key); 18986 })) { 18987 // Pass keydown handling up for unused keys 18988 _ClickableComponent.prototype.handleKeyDown.call(this, event); 18989 } 18990 } 18991 /** 18992 * Any click on a `MenuItem` puts it into the selected state. 18993 * See {@link ClickableComponent#handleClick} for instances where this is called. 18994 * 18995 * @param {EventTarget~Event} event 18996 * The `keydown`, `tap`, or `click` event that caused this function to be 18997 * called. 18998 * 18999 * @listens tap 19000 * @listens click 19001 */ 19002 ; 19003 19004 _proto.handleClick = function handleClick(event) { 19005 this.selected(true); 19006 } 19007 /** 19008 * Set the state for this menu item as selected or not. 19009 * 19010 * @param {boolean} selected 19011 * if the menu item is selected or not 19012 */ 19013 ; 19014 19015 _proto.selected = function selected(_selected) { 19016 if (this.selectable) { 19017 if (_selected) { 19018 this.addClass('vjs-selected'); 19019 this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers, 19020 // so indicate selected state to screen reader in the control text. 19021 19022 this.controlText(', selected'); 19023 this.isSelected_ = true; 19024 } else { 19025 this.removeClass('vjs-selected'); 19026 this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader 19027 19028 this.controlText(''); 19029 this.isSelected_ = false; 19030 } 19031 } 19032 }; 19033 19034 return MenuItem; 19035 }(ClickableComponent); 19036 19037 Component$1.registerComponent('MenuItem', MenuItem); 19038 19039 /** 19040 * The specific menu item type for selecting a language within a text track kind 19041 * 19042 * @extends MenuItem 19043 */ 19044 19045 var TextTrackMenuItem = /*#__PURE__*/function (_MenuItem) { 19046 inheritsLoose(TextTrackMenuItem, _MenuItem); 19047 19048 /** 19049 * Creates an instance of this class. 19050 * 19051 * @param {Player} player 19052 * The `Player` that this class should be attached to. 19053 * 19054 * @param {Object} [options] 19055 * The key/value store of player options. 19056 */ 19057 function TextTrackMenuItem(player, options) { 19058 var _this; 19059 19060 var track = options.track; 19061 var tracks = player.textTracks(); // Modify options for parent MenuItem class's init. 19062 19063 options.label = track.label || track.language || 'Unknown'; 19064 options.selected = track.mode === 'showing'; 19065 _this = _MenuItem.call(this, player, options) || this; 19066 _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter 19067 // out empty kinds. 19068 19069 _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean); 19070 19071 var changeHandler = function changeHandler() { 19072 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 19073 args[_key] = arguments[_key]; 19074 } 19075 19076 _this.handleTracksChange.apply(assertThisInitialized(_this), args); 19077 }; 19078 19079 var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() { 19080 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 19081 args[_key2] = arguments[_key2]; 19082 } 19083 19084 _this.handleSelectedLanguageChange.apply(assertThisInitialized(_this), args); 19085 }; 19086 19087 player.on(['loadstart', 'texttrackchange'], changeHandler); 19088 tracks.addEventListener('change', changeHandler); 19089 tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 19090 19091 _this.on('dispose', function () { 19092 player.off(['loadstart', 'texttrackchange'], changeHandler); 19093 tracks.removeEventListener('change', changeHandler); 19094 tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 19095 }); // iOS7 doesn't dispatch change events to TextTrackLists when an 19096 // associated track's mode changes. Without something like 19097 // Object.observe() (also not present on iOS7), it's not 19098 // possible to detect changes to the mode attribute and polyfill 19099 // the change event. As a poor substitute, we manually dispatch 19100 // change events whenever the controls modify the mode. 19101 19102 19103 if (tracks.onchange === undefined) { 19104 var event; 19105 19106 _this.on(['tap', 'click'], function () { 19107 if (typeof window_1.Event !== 'object') { 19108 // Android 2.3 throws an Illegal Constructor error for window.Event 19109 try { 19110 event = new window_1.Event('change'); 19111 } catch (err) {// continue regardless of error 19112 } 19113 } 19114 19115 if (!event) { 19116 event = document_1.createEvent('Event'); 19117 event.initEvent('change', true, true); 19118 } 19119 19120 tracks.dispatchEvent(event); 19121 }); 19122 } // set the default state based on current tracks 19123 19124 19125 _this.handleTracksChange(); 19126 19127 return _this; 19128 } 19129 /** 19130 * This gets called when an `TextTrackMenuItem` is "clicked". See 19131 * {@link ClickableComponent} for more detailed information on what a click can be. 19132 * 19133 * @param {EventTarget~Event} event
19134 * The `keydown`, `tap`, or `click` event that caused this function to be 19135 * called. 19136 * 19137 * @listens tap 19138 * @listens click 19139 */ 19140 19141 19142 var _proto = TextTrackMenuItem.prototype; 19143 19144 _proto.handleClick = function handleClick(event) { 19145 var referenceTrack = this.track; 19146 var tracks = this.player_.textTracks(); 19147 19148 _MenuItem.prototype.handleClick.call(this, event); 19149 19150 if (!tracks) { 19151 return; 19152 } 19153 19154 for (var i = 0; i < tracks.length; i++) { 19155 var track = tracks[i]; // If the track from the text tracks list is not of the right kind, 19156 // skip it. We do not want to affect tracks of incompatible kind(s). 19157 19158 if (this.kinds.indexOf(track.kind) === -1) { 19159 continue; 19160 } // If this text track is the component's track and it is not showing, 19161 // set it to showing. 19162 19163 19164 if (track === referenceTrack) { 19165 if (track.mode !== 'showing') { 19166 track.mode = 'showing'; 19167 } // If this text track is not the component's track and it is not 19168 // disabled, set it to disabled. 19169 19170 } else if (track.mode !== 'disabled') { 19171 track.mode = 'disabled'; 19172 } 19173 } 19174 } 19175 /** 19176 * Handle text track list change 19177 * 19178 * @param {EventTarget~Event} event 19179 * The `change` event that caused this function to be called. 19180 * 19181 * @listens TextTrackList#change 19182 */ 19183 ; 19184 19185 _proto.handleTracksChange = function handleTracksChange(event) { 19186 var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause 19187 // screen readers to read the appended control text unnecessarily 19188 19189 if (shouldBeSelected !== this.isSelected_) { 19190 this.selected(shouldBeSelected); 19191 } 19192 }; 19193 19194 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 19195 if (this.track.mode === 'showing') { 19196 var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language 19197 19198 if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { 19199 return; 19200 } 19201 19202 this.player_.cache_.selectedLanguage = { 19203 enabled: true, 19204 language: this.track.language, 19205 kind: this.track.kind 19206 }; 19207 } 19208 }; 19209 19210 _proto.dispose = function dispose() { 19211 // remove reference to track object on dispose 19212 this.track = null; 19213 19214 _MenuItem.prototype.dispose.call(this); 19215 }; 19216 19217 return TextTrackMenuItem; 19218 }(MenuItem); 19219 19220 Component$1.registerComponent('TextTrackMenuItem', TextTrackMenuItem); 19221 19222 /** 19223 * A special menu item for turning of a specific type of text track 19224 * 19225 * @extends TextTrackMenuItem 19226 */ 19227 19228 var OffTextTrackMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) { 19229 inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem); 19230 19231 /** 19232 * Creates an instance of this class. 19233 * 19234 * @param {Player} player 19235 * The `Player` that this class should be attached to. 19236 * 19237 * @param {Object} [options] 19238 * The key/value store of player options. 19239 */ 19240 function OffTextTrackMenuItem(player, options) { 19241 // Create pseudo track info 19242 // Requires options['kind'] 19243 options.track = { 19244 player: player, 19245 // it is no longer necessary to store `kind` or `kinds` on the track itself 19246 // since they are now stored in the `kinds` property of all instances of 19247 // TextTrackMenuItem, but this will remain for backwards compatibility 19248 kind: options.kind, 19249 kinds: options.kinds, 19250 "default": false, 19251 mode: 'disabled' 19252 }; 19253 19254 if (!options.kinds) { 19255 options.kinds = [options.kind]; 19256 } 19257 19258 if (options.label) { 19259 options.track.label = options.label; 19260 } else { 19261 options.track.label = options.kinds.join(' and ') + ' off'; 19262 options.track.label = 'OFF'; 19263 } // MenuItem is selectable 19264 19265 19266 options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 19267 19268 options.multiSelectable = false; 19269 return _TextTrackMenuItem.call(this, player, options) || this; 19270 } 19271 /** 19272 * Handle text track change 19273 * 19274 * @param {EventTarget~Event} event 19275 * The event that caused this function to run 19276 */ 19277 19278 19279 var _proto = OffTextTrackMenuItem.prototype; 19280 19281 _proto.handleTracksChange = function handleTracksChange(event) { 19282 var tracks = this.player().textTracks(); 19283 var shouldBeSelected = true; 19284 19285 for (var i = 0, l = tracks.length; i < l; i++) { 19286 var track = tracks[i]; 19287 19288 if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { 19289 shouldBeSelected = false; 19290 break; 19291 } 19292 } // Prevent redundant selected() calls because they may cause 19293 // screen readers to read the appended control text unnecessarily 19294 19295 19296 if (shouldBeSelected !== this.isSelected_) { 19297 this.selected(shouldBeSelected); 19298 } 19299 }; 19300 19301 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 19302 var tracks = this.player().textTracks(); 19303 var allHidden = true; 19304 19305 for (var i = 0, l = tracks.length; i < l; i++) { 19306 var track = tracks[i]; 19307 19308 if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { 19309 allHidden = false; 19310 break; 19311 } 19312 } 19313 19314 if (allHidden) { 19315 this.player_.cache_.selectedLanguage = { 19316 enabled: false 19317 }; 19318 } 19319 }; 19320 19321 return OffTextTrackMenuItem; 19322 }(TextTrackMenuItem); 19323 19324 Component$1.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); 19325 19326 /** 19327 * The base class for buttons that toggle specific text track types (e.g. subtitles) 19328 * 19329 * @extends MenuButton 19330 */ 19331 19332 var TextTrackButton = /*#__PURE__*/function (_TrackButton) { 19333 inheritsLoose(TextTrackButton, _TrackButton); 19334 19335 /** 19336 * Creates an instance of this class. 19337 * 19338 * @param {Player} player 19339 * The `Player` that this class should be attached to. 19340 * 19341 * @param {Object} [options={}] 19342 * The key/value store of player options. 19343 */ 19344 function TextTrackButton(player, options) { 19345 if (options === void 0) { 19346 options = {}; 19347 } 19348 19349 options.tracks = player.textTracks(); 19350 return _TrackButton.call(this, player, options) || this; 19351 } 19352 /** 19353 * Create a menu item for each text track 19354 * 19355 * @param {TextTrackMenuItem[]} [items=[]] 19356 * Existing array of items to use during creation 19357 * 19358 * @return {TextTrackMenuItem[]} 19359 * Array of menu items that were created 19360 */ 19361 19362 19363 var _proto = TextTrackButton.prototype; 19364 19365 _proto.createItems = function createItems(items, TrackMenuItem) { 19366 if (items === void 0) { 19367 items = []; 19368 } 19369 19370 if (TrackMenuItem === void 0) { 19371 TrackMenuItem = TextTrackMenuItem; 19372 } 19373 19374 // Label is an override for the [track] off label 19375 // USed to localise captions/subtitles 19376 var label; 19377 19378 if (this.label_) { 19379 //label = this.label_ + " off"; 19380 label = "OFF"; 19381 } // Add an OFF menu item to turn all tracks off 19382 19383 19384 items.push(new OffTextTrackMenuItem(this.player_, { 19385 kinds: this.kinds_, 19386 kind: this.kind_, 19387 label: label 19388 })); 19389 this.hideThreshold_ += 1; 19390 var tracks = this.player_.textTracks(); 19391 19392 if (!Array.isArray(this.kinds_)) { 19393 this.kinds_ = [this.kind_]; 19394 } 19395 19396 for (var i = 0; i < tracks.length; i++) { 19397 var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label 19398 19399 if (this.kinds_.indexOf(track.kind) > -1) { 19400 var item = new TrackMenuItem(this.player_, { 19401 track: track, 19402 kinds: this.kinds_, 19403 kind: this.kind_, 19404 // MenuItem is selectable 19405 selectable: true, 19406 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 19407 multiSelectable: false 19408 });
19409 item.addClass("vjs-" + track.kind + "-menu-item"); 19410 items.push(item); 19411 } 19412 } 19413 19414 return items; 19415 }; 19416 19417 return TextTrackButton; 19418 }(TrackButton); 19419 19420 Component$1.registerComponent('TextTrackButton', TextTrackButton); 19421 19422 /** 19423 * The chapter track menu item 19424 * 19425 * @extends MenuItem 19426 */ 19427 19428 var ChaptersTrackMenuItem = /*#__PURE__*/function (_MenuItem) { 19429 inheritsLoose(ChaptersTrackMenuItem, _MenuItem); 19430 19431 /** 19432 * Creates an instance of this class. 19433 * 19434 * @param {Player} player 19435 * The `Player` that this class should be attached to. 19436 * 19437 * @param {Object} [options] 19438 * The key/value store of player options. 19439 */ 19440 function ChaptersTrackMenuItem(player, options) { 19441 var _this; 19442 19443 var track = options.track; 19444 var cue = options.cue; 19445 var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init. 19446 19447 options.selectable = true; 19448 options.multiSelectable = false; 19449 options.label = cue.text; 19450 options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; 19451 _this = _MenuItem.call(this, player, options) || this; 19452 _this.track = track; 19453 _this.cue = cue; 19454 track.addEventListener('cuechange', bind(assertThisInitialized(_this), _this.update)); 19455 return _this; 19456 } 19457 /** 19458 * This gets called when an `ChaptersTrackMenuItem` is "clicked". See 19459 * {@link ClickableComponent} for more detailed information on what a click can be. 19460 * 19461 * @param {EventTarget~Event} [event] 19462 * The `keydown`, `tap`, or `click` event that caused this function to be 19463 * called. 19464 * 19465 * @listens tap 19466 * @listens click 19467 */ 19468 19469 19470 var _proto = ChaptersTrackMenuItem.prototype; 19471 19472 _proto.handleClick = function handleClick(event) { 19473 _MenuItem.prototype.handleClick.call(this); 19474 19475 this.player_.currentTime(this.cue.startTime); 19476 this.update(this.cue.startTime); 19477 } 19478 /** 19479 * Update chapter menu item 19480 * 19481 * @param {EventTarget~Event} [event] 19482 * The `cuechange` event that caused this function to run. 19483 * 19484 * @listens TextTrack#cuechange 19485 */ 19486 ; 19487 19488 _proto.update = function update(event) { 19489 var cue = this.cue; 19490 var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime); 19491 19492 this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); 19493 }; 19494 19495 return ChaptersTrackMenuItem; 19496 }(MenuItem); 19497 19498 Component$1.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); 19499 19500 /** 19501 * The button component for toggling and selecting chapters 19502 * Chapters act much differently than other text tracks 19503 * Cues are navigation vs. other tracks of alternative languages 19504 * 19505 * @extends TextTrackButton 19506 */ 19507 19508 var ChaptersButton = /*#__PURE__*/function (_TextTrackButton) { 19509 inheritsLoose(ChaptersButton, _TextTrackButton); 19510 19511 /** 19512 * Creates an instance of this class. 19513 * 19514 * @param {Player} player 19515 * The `Player` that this class should be attached to. 19516 * 19517 * @param {Object} [options] 19518 * The key/value store of player options. 19519 * 19520 * @param {Component~ReadyCallback} [ready] 19521 * The function to call when this function is ready. 19522 */ 19523 function ChaptersButton(player, options, ready) { 19524 return _TextTrackButton.call(this, player, options, ready) || this; 19525 } 19526 /** 19527 * Builds the default DOM `className`. 19528 * 19529 * @return {string} 19530 * The DOM `className` for this object. 19531 */ 19532 19533 19534 var _proto = ChaptersButton.prototype; 19535 19536 _proto.buildCSSClass = function buildCSSClass() { 19537 return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19538 }; 19539 19540 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19541 return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19542 } 19543 /** 19544 * Update the menu based on the current state of its items. 19545 * 19546 * @param {EventTarget~Event} [event] 19547 * An event that triggered this function to run. 19548 * 19549 * @listens TextTrackList#addtrack 19550 * @listens TextTrackList#removetrack 19551 * @listens TextTrackList#change 19552 */ 19553 ; 19554 19555 _proto.update = function update(event) { 19556 if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
vendor: 1,553 bytes, lines 19557-19610
19557 this.setTrack(this.findChaptersTrack()); 19558 } 19559 19560 _TextTrackButton.prototype.update.call(this); 19561 } 19562 /** 19563 * Set the currently selected track for the chapters button. 19564 * 19565 * @param {TextTrack} track 19566 * The new track to select. Nothing will change if this is the currently selected 19567 * track. 19568 */ 19569 ; 19570 19571 _proto.setTrack = function setTrack(track) { 19572 if (this.track_ === track) { 19573 return; 19574 } 19575 19576 if (!this.updateHandler_) { 19577 this.updateHandler_ = this.update.bind(this); 19578 } // here this.track_ refers to the old track instance 19579 19580 19581 if (this.track_) { 19582 var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 19583 19584 if (remoteTextTrackEl) { 19585 remoteTextTrackEl.removeEventListener('load', this.updateHandler_); 19586 } 19587 19588 this.track_ = null; 19589 } 19590 19591 this.track_ = track; // here this.track_ refers to the new track instance 19592 19593 if (this.track_) { 19594 this.track_.mode = 'hidden'; 19595 19596 var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 19597 19598 if (_remoteTextTrackEl) { 19599 _remoteTextTrackEl.addEventListener('load', this.updateHandler_); 19600 } 19601 } 19602 } 19603 /** 19604 * Find the track object that is currently in use by this ChaptersButton 19605 * 19606 * @return {TextTrack|undefined} 19607 * The current track or undefined if none was found. 19608 */ 19609 ; 19610
19611 _proto.findChaptersTrack = function findChaptersTrack() { 19612 var tracks = this.player_.textTracks() || []; 19613 19614 for (var i = tracks.length - 1; i >= 0; i--) { 19615 // We will always choose the last track as our chaptersTrack 19616 var track = tracks[i]; 19617 19618 if (track.kind === this.kind_) { 19619 return track; 19620 } 19621 } 19622 } 19623 /** 19624 * Get the caption for the ChaptersButton based on the track label. This will also 19625 * use the current tracks localized kind as a fallback if a label does not exist. 19626 * 19627 * @return {string} 19628 * The tracks current label or the localized track kind. 19629 */ 19630 ; 19631 19632 _proto.getMenuCaption = function getMenuCaption() { 19633 if (this.track_ && this.track_.label) { 19634 return this.track_.label; 19635 } 19636 19637 return this.localize(toTitleCase$1(this.kind_)); 19638 } 19639 /** 19640 * Create menu from chapter track 19641 * 19642 * @return {Menu} 19643 * New menu for the chapter buttons 19644 */ 19645 ; 19646 19647 _proto.createMenu = function createMenu() { 19648 this.options_.title = this.getMenuCaption(); 19649 return _TextTrackButton.prototype.createMenu.call(this); 19650 } 19651 /** 19652 * Create a menu item for each text track 19653 * 19654 * @return {TextTrackMenuItem[]} 19655 * Array of menu items 19656 */ 19657 ; 19658 19659 _proto.createItems = function createItems() { 19660 var items = []; 19661 19662 if (!this.track_) { 19663 return items; 19664 } 19665 19666 var cues = this.track_.cues; 19667 19668 if (!cues) { 19669 return items; 19670 } 19671 19672 for (var i = 0, l = cues.length; i < l; i++) { 19673 var cue = cues[i]; 19674 var mi = new ChaptersTrackMenuItem(this.player_, { 19675 track: this.track_, 19676 cue: cue 19677 }); 19678 items.push(mi); 19679 } 19680 19681 return items; 19682 }; 19683 19684 return ChaptersButton; 19685 }(TextTrackButton); 19686 /** 19687 * `kind` of TextTrack to look for to associate it with this menu. 19688 * 19689 * @type {string} 19690 * @private 19691 */ 19692 19693 19694 ChaptersButton.prototype.kind_ = 'chapters'; 19695 /** 19696 * The text that should display over the `ChaptersButton`s controls. Added for localization. 19697 * 19698 * @type {string} 19699 * @private 19700 */ 19701 19702 ChaptersButton.prototype.controlText_ = 'Chapters'; 19703 Component$1.registerComponent('ChaptersButton', ChaptersButton); 19704 19705 /** 19706 * The button component for toggling and selecting descriptions 19707 * 19708 * @extends TextTrackButton 19709 */ 19710 19711 var DescriptionsButton = /*#__PURE__*/function (_TextTrackButton) { 19712 inheritsLoose(DescriptionsButton, _TextTrackButton); 19713 19714 /** 19715 * Creates an instance of this class. 19716 * 19717 * @param {Player} player 19718 * The `Player` that this class should be attached to. 19719 * 19720 * @param {Object} [options] 19721 * The key/value store of player options. 19722 * 19723 * @param {Component~ReadyCallback} [ready] 19724 * The function to call when this component is ready. 19725 */ 19726 function DescriptionsButton(player, options, ready) { 19727 var _this; 19728 19729 _this = _TextTrackButton.call(this, player, options, ready) || this; 19730 var tracks = player.textTracks(); 19731 var changeHandler = bind(assertThisInitialized(_this), _this.handleTracksChange); 19732 tracks.addEventListener('change', changeHandler); 19733 19734 _this.on('dispose', function () { 19735 tracks.removeEventListener('change', changeHandler); 19736 }); 19737 19738 return _this; 19739 } 19740 /** 19741 * Handle text track change 19742 * 19743 * @param {EventTarget~Event} event 19744 * The event that caused this function to run 19745 * 19746 * @listens TextTrackList#change 19747 */ 19748 19749 19750 var _proto = DescriptionsButton.prototype; 19751 19752 _proto.handleTracksChange = function handleTracksChange(event) { 19753 var tracks = this.player().textTracks(); 19754 var disabled = false; // Check whether a track of a different kind is showing 19755 19756 for (var i = 0, l = tracks.length; i < l; i++) { 19757 var track = tracks[i]; 19758 19759 if (track.kind !== this.kind_ && track.mode === 'showing') { 19760 disabled = true; 19761 break; 19762 } 19763 } // If another track is showing, disable this menu button 19764 19765 19766 if (disabled) { 19767 this.disable(); 19768 } else { 19769 this.enable(); 19770 } 19771 } 19772 /** 19773 * Builds the default DOM `className`. 19774 * 19775 * @return {string} 19776 * The DOM `className` for this object. 19777 */ 19778 ; 19779 19780 _proto.buildCSSClass = function buildCSSClass() { 19781 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19782 }; 19783 19784 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19785 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19786 }; 19787 19788 return DescriptionsButton; 19789 }(TextTrackButton); 19790 /** 19791 * `kind` of TextTrack to look for to associate it with this menu. 19792 * 19793 * @type {string} 19794 * @private 19795 */ 19796 19797 19798 DescriptionsButton.prototype.kind_ = 'descriptions'; 19799 /** 19800 * The text that should display over the `DescriptionsButton`s controls. Added for localization. 19801 * 19802 * @type {string} 19803 * @private 19804 */ 19805 19806 DescriptionsButton.prototype.controlText_ = 'Descriptions'; 19807 Component$1.registerComponent('DescriptionsButton', DescriptionsButton); 19808 19809 /** 19810 * The button component for toggling and selecting subtitles 19811 * 19812 * @extends TextTrackButton 19813 */ 19814 19815 var SubtitlesButton = /*#__PURE__*/function (_TextTrackButton) { 19816 inheritsLoose(SubtitlesButton, _TextTrackButton); 19817 19818 /** 19819 * Creates an instance of this class. 19820 * 19821 * @param {Player} player 19822 * The `Player` that this class should be attached to. 19823 * 19824 * @param {Object} [options] 19825 * The key/value store of player options. 19826 * 19827 * @param {Component~ReadyCallback} [ready] 19828 * The function to call when this component is ready. 19829 */
19830 function SubtitlesButton(player, options, ready) { 19831 return _TextTrackButton.call(this, player, options, ready) || this; 19832 } 19833 /** 19834 * Builds the default DOM `className`. 19835 * 19836 * @return {string} 19837 * The DOM `className` for this object. 19838 */ 19839 19840 19841 var _proto = SubtitlesButton.prototype; 19842 19843 _proto.buildCSSClass = function buildCSSClass() { 19844 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19845 }; 19846 19847 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19848 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19849 }; 19850 19851 return SubtitlesButton; 19852 }(TextTrackButton); 19853 /** 19854 * `kind` of TextTrack to look for to associate it with this menu. 19855 * 19856 * @type {string} 19857 * @private 19858 */ 19859 19860 19861 SubtitlesButton.prototype.kind_ = 'subtitles'; 19862 /** 19863 * The text that should display over the `SubtitlesButton`s controls. Added for localization. 19864 * 19865 * @type {string} 19866 * @private 19867 */ 19868 19869 SubtitlesButton.prototype.controlText_ = 'Subtitles'; 19870 Component$1.registerComponent('SubtitlesButton', SubtitlesButton); 19871 19872 /** 19873 * The menu item for caption track settings menu 19874 * 19875 * @extends TextTrackMenuItem 19876 */ 19877 19878 var CaptionSettingsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) { 19879 inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem); 19880 19881 /** 19882 * Creates an instance of this class. 19883 * 19884 * @param {Player} player 19885 * The `Player` that this class should be attached to. 19886 * 19887 * @param {Object} [options] 19888 * The key/value store of player options. 19889 */ 19890 function CaptionSettingsMenuItem(player, options) { 19891 var _this; 19892 19893 options.track = { 19894 player: player, 19895 kind: options.kind, 19896 label: options.kind + ' settings', 19897 selectable: false, 19898 "default": false, 19899 mode: 'disabled' 19900 }; // CaptionSettingsMenuItem has no concept of 'selected' 19901 19902 options.selectable = false; 19903 options.name = 'CaptionSettingsMenuItem'; 19904 _this = _TextTrackMenuItem.call(this, player, options) || this; 19905 19906 _this.addClass('vjs-texttrack-settings'); 19907 19908 _this.controlText(', opens ' + options.kind + ' settings dialog'); 19909 19910 return _this; 19911 } 19912 /** 19913 * This gets called when an `CaptionSettingsMenuItem` is "clicked". See 19914 * {@link ClickableComponent} for more detailed information on what a click can be. 19915 * 19916 * @param {EventTarget~Event} [event] 19917 * The `keydown`, `tap`, or `click` event that caused this function to be 19918 * called. 19919 * 19920 * @listens tap 19921 * @listens click 19922 */ 19923 19924 19925 var _proto = CaptionSettingsMenuItem.prototype; 19926 19927 _proto.handleClick = function handleClick(event) { 19928 this.player().getChild('textTrackSettings').open(); 19929 }; 19930 19931 return CaptionSettingsMenuItem; 19932 }(TextTrackMenuItem); 19933 19934 Component$1.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); 19935 19936 /** 19937 * The button component for toggling and selecting captions 19938 * 19939 * @extends TextTrackButton 19940 */ 19941 19942 var CaptionsButton = /*#__PURE__*/function (_TextTrackButton) { 19943 inheritsLoose(CaptionsButton, _TextTrackButton); 19944 19945 /** 19946 * Creates an instance of this class. 19947 * 19948 * @param {Player} player 19949 * The `Player` that this class should be attached to. 19950 * 19951 * @param {Object} [options] 19952 * The key/value store of player options. 19953 * 19954 * @param {Component~ReadyCallback} [ready] 19955 * The function to call when this component is ready. 19956 */ 19957 function CaptionsButton(player, options, ready) { 19958 return _TextTrackButton.call(this, player, options, ready) || this; 19959 } 19960 /** 19961 * Builds the default DOM `className`. 19962 * 19963 * @return {string} 19964 * The DOM `className` for this object. 19965 */ 19966 19967 19968 var _proto = CaptionsButton.prototype; 19969 19970 _proto.buildCSSClass = function buildCSSClass() { 19971 return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19972 }; 19973 19974 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19975 return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19976 } 19977 /** 19978 * Create caption menu items 19979 * 19980 * @return {CaptionSettingsMenuItem[]} 19981 * The array of current menu items. 19982 */ 19983 ; 19984 19985 _proto.createItems = function createItems() { 19986 var items = []; 19987 19988 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 19989 items.push(new CaptionSettingsMenuItem(this.player_, { 19990 kind: this.kind_ 19991 })); 19992 this.hideThreshold_ += 1; 19993 } 19994 19995 return _TextTrackButton.prototype.createItems.call(this, items); 19996 }; 19997 19998 return CaptionsButton; 19999 }(TextTrackButton); 20000 /** 20001 * `kind` of TextTrack to look for to associate it with this menu. 20002 * 20003 * @type {string} 20004 * @private 20005 */ 20006 20007 20008 CaptionsButton.prototype.kind_ = 'captions'; 20009 /** 20010 * The text that should display over the `CaptionsButton`s controls. Added for localization. 20011 * 20012 * @type {string} 20013 * @private 20014 */ 20015 20016 CaptionsButton.prototype.controlText_ = 'Captions'; 20017 Component$1.registerComponent('CaptionsButton', CaptionsButton); 20018 20019 /** 20020 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles 20021 * in the SubsCapsMenu. 20022 * 20023 * @extends TextTrackMenuItem 20024 */ 20025 20026 var SubsCapsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) {
20027 inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem); 20028 20029 function SubsCapsMenuItem() { 20030 return _TextTrackMenuItem.apply(this, arguments) || this; 20031 } 20032 20033 var _proto = SubsCapsMenuItem.prototype; 20034 20035 _proto.createEl = function createEl$1(type, props, attrs) { 20036 var el = _TextTrackMenuItem.prototype.createEl.call(this, type, props, attrs); 20037 20038 var parentSpan = el.querySelector('.vjs-menu-item-text'); 20039 20040 if (this.options_.track.kind === 'captions') { 20041 parentSpan.appendChild(createEl('span', { 20042 className: 'vjs-icon-placeholder' 20043 }, { 20044 'aria-hidden': true 20045 })); 20046 parentSpan.appendChild(createEl('span', { 20047 className: 'vjs-control-text', 20048 // space added as the text will visually flow with the 20049 // label 20050 textContent: " " + this.localize('Captions') 20051 })); 20052 } 20053 20054 return el; 20055 }; 20056 20057 return SubsCapsMenuItem; 20058 }(TextTrackMenuItem); 20059 20060 Component$1.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); 20061 20062 /** 20063 * The button component for toggling and selecting captions and/or subtitles 20064 * 20065 * @extends TextTrackButton 20066 */ 20067 20068 var SubsCapsButton = /*#__PURE__*/function (_TextTrackButton) { 20069 inheritsLoose(SubsCapsButton, _TextTrackButton); 20070 20071 function SubsCapsButton(player, options) { 20072 var _this; 20073 20074 if (options === void 0) { 20075 options = {}; 20076 } 20077 20078 _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for 20079 // "captions and subtitles" other locales use "subtitles" 20080 20081 _this.label_ = 'subtitles'; 20082 20083 if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { 20084 _this.label_ = 'captions'; 20085 } 20086 20087 _this.menuButton_.controlText(toTitleCase$1(_this.label_)); 20088 20089 return _this; 20090 } 20091 /** 20092 * Builds the default DOM `className`. 20093 * 20094 * @return {string} 20095 * The DOM `className` for this object. 20096 */ 20097 20098 20099 var _proto = SubsCapsButton.prototype; 20100 20101 _proto.buildCSSClass = function buildCSSClass() { 20102 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 20103 }; 20104 20105 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 20106 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 20107 } 20108 /** 20109 * Create caption/subtitles menu items 20110 * 20111 * @return {CaptionSettingsMenuItem[]} 20112 * The array of current menu items. 20113 */ 20114 ; 20115 20116 _proto.createItems = function createItems() { 20117 var items = []; 20118 20119 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 20120 items.push(new CaptionSettingsMenuItem(this.player_, { 20121 kind: this.label_ 20122 })); 20123 this.hideThreshold_ += 1; 20124 } 20125 20126 items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); 20127 return items; 20128 }; 20129 20130 return SubsCapsButton; 20131 }(TextTrackButton); 20132 /** 20133 * `kind`s of TextTrack to look for to associate it with this menu. 20134 * 20135 * @type {array} 20136 * @private 20137 */ 20138 20139 20140 SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; 20141 /** 20142 * The text that should display over the `SubsCapsButton`s controls. 20143 * 20144 * 20145 * @type {string} 20146 * @private 20147 */ 20148 20149 SubsCapsButton.prototype.controlText_ = 'Subtitles'; 20150 Component$1.registerComponent('SubsCapsButton', SubsCapsButton); 20151 20152 /** 20153 * An {@link AudioTrack} {@link MenuItem} 20154 * 20155 * @extends MenuItem 20156 */ 20157 20158 var AudioTrackMenuItem = /*#__PURE__*/function (_MenuItem) { 20159 inheritsLoose(AudioTrackMenuItem, _MenuItem); 20160 20161 /** 20162 * Creates an instance of this class. 20163 * 20164 * @param {Player} player 20165 * The `Player` that this class should be attached to. 20166 * 20167 * @param {Object} [options] 20168 * The key/value store of player options. 20169 */ 20170 function AudioTrackMenuItem(player, options) { 20171 var _this; 20172 20173 var track = options.track; 20174 var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init. 20175 20176 options.label = track.label || track.language || 'Unknown'; 20177 options.selected = track.enabled; 20178 _this = _MenuItem.call(this, player, options) || this; 20179 _this.track = track; 20180
20181 _this.addClass("vjs-" + track.kind + "-menu-item"); 20182 20183 var changeHandler = function changeHandler() { 20184 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 20185 args[_key] = arguments[_key]; 20186 } 20187 20188 _this.handleTracksChange.apply(assertThisInitialized(_this), args); 20189 }; 20190 20191 tracks.addEventListener('change', changeHandler); 20192 20193 _this.on('dispose', function () { 20194 tracks.removeEventListener('change', changeHandler); 20195 }); 20196 20197 return _this; 20198 } 20199 20200 var _proto = AudioTrackMenuItem.prototype; 20201 20202 _proto.createEl = function createEl(type, props, attrs) { 20203 var el = _MenuItem.prototype.createEl.call(this, type, props, attrs); 20204 20205 var parentSpan = el.querySelector('.vjs-menu-item-text'); 20206 20207 if (this.options_.track.kind === 'main-desc') { 20208 parentSpan.appendChild(_MenuItem.prototype.createEl.call(this, 'span', { 20209 className: 'vjs-icon-placeholder' 20210 }, { 20211 'aria-hidden': true 20212 })); 20213 parentSpan.appendChild(_MenuItem.prototype.createEl.call(this, 'span', { 20214 className: 'vjs-control-text', 20215 textContent: this.localize('Descriptions') 20216 })); 20217 } 20218 20219 return el; 20220 } 20221 /** 20222 * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} 20223 * for more detailed information on what a click can be. 20224 * 20225 * @param {EventTarget~Event} [event] 20226 * The `keydown`, `tap`, or `click` event that caused this function to be 20227 * called. 20228 * 20229 * @listens tap 20230 * @listens click 20231 */ 20232 ; 20233 20234 _proto.handleClick = function handleClick(event) { 20235 _MenuItem.prototype.handleClick.call(this, event); // the audio track list will automatically toggle other tracks 20236 // off for us. 20237 20238 20239 this.track.enabled = true; 20240 } 20241 /** 20242 * Handle any {@link AudioTrack} change. 20243 * 20244 * @param {EventTarget~Event} [event] 20245 * The {@link AudioTrackList#change} event that caused this to run. 20246 * 20247 * @listens AudioTrackList#change 20248 */ 20249 ; 20250 20251 _proto.handleTracksChange = function handleTracksChange(event) { 20252 this.selected(this.track.enabled); 20253 }; 20254 20255 return AudioTrackMenuItem; 20256 }(MenuItem); 20257 20258 Component$1.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); 20259 20260 /** 20261 * The base class for buttons that toggle specific {@link AudioTrack} types. 20262 * 20263 * @extends TrackButton 20264 */ 20265 20266 var AudioTrackButton = /*#__PURE__*/function (_TrackButton) { 20267 inheritsLoose(AudioTrackButton, _TrackButton); 20268 20269 /** 20270 * Creates an instance of this class. 20271 * 20272 * @param {Player} player 20273 * The `Player` that this class should be attached to. 20274 * 20275 * @param {Object} [options={}] 20276 * The key/value store of player options. 20277 */ 20278 function AudioTrackButton(player, options) { 20279 if (options === void 0) { 20280 options = {}; 20281 } 20282 20283 options.tracks = player.audioTracks(); 20284 return _TrackButton.call(this, player, options) || this; 20285 } 20286 /** 20287 * Builds the default DOM `className`. 20288 * 20289 * @return {string} 20290 * The DOM `className` for this object. 20291 */ 20292 20293 20294 var _proto = AudioTrackButton.prototype; 20295 20296 _proto.buildCSSClass = function buildCSSClass() { 20297 return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this); 20298 }; 20299 20300 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 20301 return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this); 20302 } 20303 /** 20304 * Create a menu item for each audio track 20305 * 20306 * @param {AudioTrackMenuItem[]} [items=[]] 20307 * An array of existing menu items to use. 20308 * 20309 * @return {AudioTrackMenuItem[]} 20310 * An array of menu items 20311 */ 20312 ; 20313 20314 _proto.createItems = function createItems(items) { 20315 if (items === void 0) { 20316 items = []; 20317 } 20318 20319 // if there's only one audio track, there no point in showing it 20320 this.hideThreshold_ = 1; 20321 var tracks = this.player_.audioTracks(); 20322 20323 for (var i = 0; i < tracks.length; i++) { 20324 var track = tracks[i]; 20325 items.push(new AudioTrackMenuItem(this.player_, { 20326 track: track, 20327 // MenuItem is selectable 20328 selectable: true, 20329 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 20330 multiSelectable: false 20331 })); 20332 } 20333 20334 return items; 20335 }; 20336 20337 return AudioTrackButton; 20338 }(TrackButton); 20339 /** 20340 * The text that should display over the `AudioTrackButton`s controls. Added for localization. 20341 * 20342 * @type {string} 20343 * @private 20344 */ 20345 20346 20347 AudioTrackButton.prototype.controlText_ = 'Audio Track'; 20348 Component$1.registerComponent('AudioTrackButton', AudioTrackButton); 20349 20350 /** 20351 * The specific menu item type for selecting a playback rate. 20352 * 20353 * @extends MenuItem 20354 */ 20355 20356 var PlaybackRateMenuItem = /*#__PURE__*/function (_MenuItem) { 20357 inheritsLoose(PlaybackRateMenuItem, _MenuItem); 20358 20359 /** 20360 * Creates an instance of this class. 20361 * 20362 * @param {Player} player 20363 * The `Player` that this class should be attached to. 20364 * 20365 * @param {Object} [options] 20366 * The key/value store of player options. 20367 */ 20368 function PlaybackRateMenuItem(player, options) { 20369 var _this; 20370 20371 var label = options.rate; 20372 var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init. 20373 20374 options.label = label; 20375 options.selected = rate === player.playbackRate(); 20376 options.selectable = true; 20377 options.multiSelectable = false; 20378 _this = _MenuItem.call(this, player, options) || this; 20379 _this.label = label; 20380 _this.rate = rate; 20381 20382 _this.on(player, 'ratechange', function (e) { 20383 return _this.update(e); 20384 }); 20385 20386 return _this; 20387 } 20388 /** 20389 * This gets called when an `PlaybackRateMenuItem` is "clicked". See 20390 * {@link ClickableComponent} for more detailed information on what a click can be. 20391 * 20392 * @param {EventTarget~Event} [event]
20393 * The `keydown`, `tap`, or `click` event that caused this function to be 20394 * called. 20395 * 20396 * @listens tap 20397 * @listens click 20398 */ 20399 20400 20401 var _proto = PlaybackRateMenuItem.prototype; 20402 20403 _proto.handleClick = function handleClick(event) { 20404 _MenuItem.prototype.handleClick.call(this); 20405 20406 this.player().playbackRate(this.rate); 20407 } 20408 /** 20409 * Update the PlaybackRateMenuItem when the playbackrate changes. 20410 * 20411 * @param {EventTarget~Event} [event] 20412 * The `ratechange` event that caused this function to run. 20413 * 20414 * @listens Player#ratechange 20415 */ 20416 ; 20417 20418 _proto.update = function update(event) { 20419 this.selected(this.player().playbackRate() === this.rate); 20420 }; 20421 20422 return PlaybackRateMenuItem; 20423 }(MenuItem); 20424 /** 20425 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. 20426 * 20427 * @type {string} 20428 * @private 20429 */ 20430 20431 20432 PlaybackRateMenuItem.prototype.contentElType = 'button'; 20433 Component$1.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); 20434 20435 /** 20436 * The component for controlling the playback rate. 20437 * 20438 * @extends MenuButton 20439 */ 20440 20441 var PlaybackRateMenuButton = /*#__PURE__*/function (_MenuButton) { 20442 inheritsLoose(PlaybackRateMenuButton, _MenuButton); 20443 20444 /** 20445 * Creates an instance of this class. 20446 * 20447 * @param {Player} player 20448 * The `Player` that this class should be attached to. 20449 * 20450 * @param {Object} [options] 20451 * The key/value store of player options. 20452 */ 20453 function PlaybackRateMenuButton(player, options) { 20454 var _this; 20455 20456 _this = _MenuButton.call(this, player, options) || this; 20457 20458 _this.menuButton_.el_.setAttribute('aria-describedby', _this.labelElId_); 20459 20460 _this.updateVisibility(); 20461 20462 _this.updateLabel(); 20463 20464 _this.on(player, 'loadstart', function (e) { 20465 return _this.updateVisibility(e); 20466 }); 20467 20468 _this.on(player, 'ratechange', function (e) { 20469 return _this.updateLabel(e); 20470 }); 20471 20472 _this.on(player, 'playbackrateschange', function (e) { 20473 return _this.handlePlaybackRateschange(e); 20474 }); 20475 20476 return _this; 20477 } 20478 /** 20479 * Create the `Component`'s DOM element 20480 * 20481 * @return {Element} 20482 * The element that was created. 20483 */ 20484 20485 20486 var _proto = PlaybackRateMenuButton.prototype; 20487 20488 _proto.createEl = function createEl$1() { 20489 var el = _MenuButton.prototype.createEl.call(this); 20490 20491 this.labelElId_ = 'vjs-playback-rate-value-label-' + this.id_; 20492 this.labelEl_ = createEl('div', { 20493 className: 'vjs-playback-rate-value', 20494 id: this.labelElId_, 20495 textContent: '1x' 20496 }); 20497 el.appendChild(this.labelEl_); 20498 return el; 20499 }; 20500 20501 _proto.dispose = function dispose() { 20502 this.labelEl_ = null; 20503 20504 _MenuButton.prototype.dispose.call(this); 20505 } 20506 /** 20507 * Builds the default DOM `className`. 20508 * 20509 * @return {string} 20510 * The DOM `className` for this object. 20511 */ 20512 ; 20513 20514 _proto.buildCSSClass = function buildCSSClass() { 20515 return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this); 20516 }; 20517 20518 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 20519 return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this); 20520 } 20521 /** 20522 * Create the list of menu items. Specific to each subclass. 20523 * 20524 */ 20525 ; 20526 20527 _proto.createItems = function createItems() { 20528 var rates = this.playbackRates(); 20529 var items = []; 20530 20531 for (var i = rates.length - 1; i >= 0; i--) { 20532 items.push(new PlaybackRateMenuItem(this.player(), { 20533 rate: rates[i] + 'x' 20534 })); 20535 } 20536 20537 return items; 20538 } 20539 /** 20540 * Updates ARIA accessibility attributes 20541 */ 20542 ; 20543 20544 _proto.updateARIAAttributes = function updateARIAAttributes() { 20545 // Current playback rate 20546 this.el().setAttribute('aria-valuenow', this.player().playbackRate()); 20547 } 20548 /** 20549 * This gets called when an `PlaybackRateMenuButton` is "clicked". See 20550 * {@link ClickableComponent} for more detailed information on what a click can be. 20551 * 20552 * @param {EventTarget~Event} [event]
20553 * The `keydown`, `tap`, or `click` event that caused this function to be 20554 * called. 20555 * 20556 * @listens tap 20557 * @listens click 20558 */ 20559 ; 20560 20561 _proto.handleClick = function handleClick(event) { 20562 // select next rate option 20563 var currentRate = this.player().playbackRate(); 20564 var rates = this.playbackRates(); // this will select first one if the last one currently selected 20565 20566 var newRate = rates[0]; 20567 20568 for (var i = 0; i < rates.length; i++) { 20569 if (rates[i] > currentRate) { 20570 newRate = rates[i]; 20571 break; 20572 } 20573 } 20574 20575 this.player().playbackRate(newRate); 20576 } 20577 /** 20578 * On playbackrateschange, update the menu to account for the new items. 20579 * 20580 * @listens Player#playbackrateschange 20581 */ 20582 ; 20583 20584 _proto.handlePlaybackRateschange = function handlePlaybackRateschange(event) { 20585 this.update(); 20586 } 20587 /** 20588 * Get possible playback rates 20589 * 20590 * @return {Array} 20591 * All possible playback rates 20592 */ 20593 ; 20594 20595 _proto.playbackRates = function playbackRates() { 20596 var player = this.player(); 20597 return player.playbackRates && player.playbackRates() || []; 20598 } 20599 /** 20600 * Get whether playback rates is supported by the tech 20601 * and an array of playback rates exists 20602 * 20603 * @return {boolean} 20604 * Whether changing playback rate is supported 20605 */ 20606 ; 20607 20608 _proto.playbackRateSupported = function playbackRateSupported() { 20609 return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; 20610 } 20611 /** 20612 * Hide playback rate controls when they're no playback rate options to select 20613 * 20614 * @param {EventTarget~Event} [event] 20615 * The event that caused this function to run. 20616 * 20617 * @listens Player#loadstart 20618 */ 20619 ; 20620 20621 _proto.updateVisibility = function updateVisibility(event) { 20622 if (this.playbackRateSupported()) { 20623 this.removeClass('vjs-hidden'); 20624 } else { 20625 this.addClass('vjs-hidden'); 20626 } 20627 } 20628 /** 20629 * Update button label when rate changed 20630 * 20631 * @param {EventTarget~Event} [event] 20632 * The event that caused this function to run. 20633 * 20634 * @listens Player#ratechange 20635 */ 20636 ; 20637 20638 _proto.updateLabel = function updateLabel(event) { 20639 if (this.playbackRateSupported()) { 20640 this.labelEl_.textContent = this.player().playbackRate() + 'x'; 20641 } 20642 }; 20643 20644 return PlaybackRateMenuButton; 20645 }(MenuButton); 20646 /** 20647 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 20648 * 20649 * @type {string} 20650 * @private 20651 */ 20652 20653 20654 PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; 20655 Component$1.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); 20656 20657 /** 20658 * Just an empty spacer element that can be used as an append point for plugins, etc. 20659 * Also can be used to create space between elements when necessary. 20660 * 20661 * @extends Component 20662 */ 20663 20664 var Spacer = /*#__PURE__*/function (_Component) { 20665 inheritsLoose(Spacer, _Component); 20666 20667 function Spacer() { 20668 return _Component.apply(this, arguments) || this; 20669 } 20670 20671 var _proto = Spacer.prototype; 20672 20673 /** 20674 * Builds the default DOM `className`. 20675 * 20676 * @return {string} 20677 * The DOM `className` for this object. 20678 */ 20679 _proto.buildCSSClass = function buildCSSClass() { 20680 return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this); 20681 } 20682 /** 20683 * Create the `Component`'s DOM element 20684 * 20685 * @return {Element} 20686 * The element that was created. 20687 */ 20688 ; 20689 20690 _proto.createEl = function createEl(tag, props, attributes) { 20691 if (tag === void 0) { 20692 tag = 'div'; 20693 } 20694 20695 if (props === void 0) { 20696 props = {}; 20697 } 20698 20699 if (attributes === void 0) { 20700 attributes = {}; 20701 } 20702 20703 if (!props.className) { 20704 props.className = this.buildCSSClass(); 20705 } 20706 20707 return _Component.prototype.createEl.call(this, tag, props, attributes); 20708 }; 20709 20710 return Spacer; 20711 }(Component$1); 20712 20713 Component$1.registerComponent('Spacer', Spacer); 20714 20715 /** 20716 * Spacer specifically meant to be used as an insertion point for new plugins, etc. 20717 * 20718 * @extends Spacer 20719 */ 20720
20721 var CustomControlSpacer = /*#__PURE__*/function (_Spacer) { 20722 inheritsLoose(CustomControlSpacer, _Spacer); 20723 20724 function CustomControlSpacer() { 20725 return _Spacer.apply(this, arguments) || this; 20726 } 20727 20728 var _proto = CustomControlSpacer.prototype; 20729 20730 /** 20731 * Builds the default DOM `className`. 20732 * 20733 * @return {string} 20734 * The DOM `className` for this object. 20735 */ 20736 _proto.buildCSSClass = function buildCSSClass() { 20737 return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this); 20738 } 20739 /** 20740 * Create the `Component`'s DOM element 20741 * 20742 * @return {Element} 20743 * The element that was created. 20744 */ 20745 ; 20746 20747 _proto.createEl = function createEl() { 20748 return _Spacer.prototype.createEl.call(this, 'div', { 20749 className: this.buildCSSClass(), 20750 // No-flex/table-cell mode requires there be some content 20751 // in the cell to fill the remaining space of the table. 20752 textContent: "\xA0" 20753 }); 20754 }; 20755 20756 return CustomControlSpacer; 20757 }(Spacer); 20758 20759 Component$1.registerComponent('CustomControlSpacer', CustomControlSpacer); 20760 20761 /** 20762 * Container of main controls. 20763 * 20764 * @extends Component 20765 */ 20766 20767 var ControlBar = /*#__PURE__*/function (_Component) { 20768 inheritsLoose(ControlBar, _Component); 20769 20770 function ControlBar() { 20771 return _Component.apply(this, arguments) || this; 20772 } 20773 20774 var _proto = ControlBar.prototype; 20775 20776 /** 20777 * Create the `Component`'s DOM element 20778 * 20779 * @return {Element} 20780 * The element that was created. 20781 */ 20782 _proto.createEl = function createEl() { 20783 return _Component.prototype.createEl.call(this, 'div', { 20784 className: 'vjs-control-bar', 20785 dir: 'ltr' 20786 }); 20787 }; 20788 20789 return ControlBar; 20790 }(Component$1); 20791 /** 20792 * Default options for `ControlBar` 20793 * 20794 * @type {Object} 20795 * @private 20796 */ 20797 20798 20799 ControlBar.prototype.options_ = { 20800 children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] 20801 }; 20802 20803 if ('exitPictureInPicture' in document_1) { 20804 ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle'); 20805 } 20806 20807 Component$1.registerComponent('ControlBar', ControlBar); 20808 20809 /** 20810 * A display that indicates an error has occurred. This means that the video 20811 * is unplayable. 20812 * 20813 * @extends ModalDialog 20814 */ 20815 20816 var ErrorDisplay = /*#__PURE__*/function (_ModalDialog) { 20817 inheritsLoose(ErrorDisplay, _ModalDialog); 20818 20819 /** 20820 * Creates an instance of this class. 20821 * 20822 * @param {Player} player 20823 * The `Player` that this class should be attached to. 20824 * 20825 * @param {Object} [options] 20826 * The key/value store of player options. 20827 */ 20828 function ErrorDisplay(player, options) { 20829 var _this; 20830 20831 _this = _ModalDialog.call(this, player, options) || this; 20832 20833 _this.on(player, 'error', function (e) { 20834 return _this.open(e); 20835 }); 20836 20837 return _this; 20838 } 20839 /** 20840 * Builds the default DOM `className`. 20841 * 20842 * @return {string} 20843 * The DOM `className` for this object. 20844 * 20845 * @deprecated Since version 5. 20846 */ 20847 20848 20849 var _proto = ErrorDisplay.prototype; 20850 20851 _proto.buildCSSClass = function buildCSSClass() { 20852 return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this); 20853 } 20854 /** 20855 * Gets the localized error message based on the `Player`s error. 20856 * 20857 * @return {string} 20858 * The `Player`s error message localized or an empty string. 20859 */ 20860 ; 20861 20862 _proto.content = function content() { 20863 var error = this.player().error(); 20864 return error ? this.localize(error.message) : ''; 20865 }; 20866 20867 return ErrorDisplay; 20868 }(ModalDialog); 20869 /** 20870 * The default options for an `ErrorDisplay`. 20871 * 20872 * @private 20873 */ 20874 20875 20876 ErrorDisplay.prototype.options_ = _extends_1({}, ModalDialog.prototype.options_, {
20877 pauseOnOpen: false, 20878 fillAlways: true, 20879 temporary: false, 20880 uncloseable: true 20881 }); 20882 Component$1.registerComponent('ErrorDisplay', ErrorDisplay); 20883 20884 var LOCAL_STORAGE_KEY$1 = 'vjs-text-track-settings'; 20885 var COLOR_BLACK = ['#000', 'Black']; 20886 var COLOR_BLUE = ['#00F', 'Blue']; 20887 var COLOR_CYAN = ['#0FF', 'Cyan']; 20888 var COLOR_GREEN = ['#0F0', 'Green']; 20889 var COLOR_MAGENTA = ['#F0F', 'Magenta']; 20890 var COLOR_RED = ['#F00', 'Red']; 20891 var COLOR_WHITE = ['#FFF', 'White']; 20892 var COLOR_YELLOW = ['#FF0', 'Yellow']; 20893 var OPACITY_OPAQUE = ['1', 'Opaque']; 20894 var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; 20895 var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component. 20896 // 20897 // Possible keys include: 20898 // 20899 // `default`: 20900 // The default option index. Only needs to be provided if not zero. 20901 // `parser`: 20902 // A function which is used to parse the value from the selected option in 20903 // a customized way. 20904 // `selector`: 20905 // The selector used to find the associated <select> element. 20906 20907 var selectConfigs = { 20908 backgroundColor: { 20909 selector: '.vjs-bg-color > select', 20910 id: 'captions-background-color-%s', 20911 label: 'Color', 20912 options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 20913 }, 20914 backgroundOpacity: { 20915 selector: '.vjs-bg-opacity > select', 20916 id: 'captions-background-opacity-%s', 20917 label: 'Transparency', 20918 options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] 20919 }, 20920 color: { 20921 selector: '.vjs-fg-color > select', 20922 id: 'captions-foreground-color-%s', 20923 label: 'Color', 20924 options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 20925 }, 20926 edgeStyle: { 20927 selector: '.vjs-edge-style > select', 20928 id: '%s', 20929 label: 'Text Edge Style', 20930 options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] 20931 }, 20932 fontFamily: { 20933 selector: '.vjs-font-family > select', 20934 id: 'captions-font-family-%s', 20935 label: 'Font Family', 20936 options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] 20937 }, 20938 fontPercent: { 20939 selector: '.vjs-font-percent > select', 20940 id: 'captions-font-size-%s', 20941 label: 'Font Size', 20942 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%']], 20943 "default": 2, 20944 parser: function parser(v) { 20945 return v === '1.00' ? null : Number(v); 20946 } 20947 }, 20948 textOpacity: { 20949 selector: '.vjs-text-opacity > select', 20950 id: 'captions-foreground-opacity-%s', 20951 label: 'Transparency', 20952 options: [OPACITY_OPAQUE, OPACITY_SEMI] 20953 }, 20954 // Options for this object are defined below. 20955 windowColor: { 20956 selector: '.vjs-window-color > select', 20957 id: 'captions-window-color-%s', 20958 label: 'Color' 20959 }, 20960 // Options for this object are defined below. 20961 windowOpacity: { 20962 selector: '.vjs-window-opacity > select', 20963 id: 'captions-window-opacity-%s', 20964 label: 'Transparency', 20965 options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] 20966 } 20967 }; 20968 selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; 20969 /** 20970 * Get the actual value of an option. 20971 * 20972 * @param {string} value 20973 * The value to get 20974 * 20975 * @param {Function} [parser] 20976 * Optional function to adjust the value. 20977 * 20978 * @return {Mixed} 20979 * - Will be `undefined` if no value exists 20980 * - Will be `undefined` if the given value is "none". 20981 * - Will be the actual value otherwise. 20982 * 20983 * @private 20984 */ 20985 20986 function parseOptionValue(value, parser) { 20987 if (parser) { 20988 value = parser(value); 20989 } 20990 20991 if (value && value !== 'none') { 20992 return value; 20993 } 20994 } 20995 /** 20996 * Gets the value of the selected <option> element within a <select> element. 20997 * 20998 * @param {Element} el 20999 * the element to look in 21000 * 21001 * @param {Function} [parser] 21002 * Optional function to adjust the value. 21003 * 21004 * @return {Mixed} 21005 * - Will be `undefined` if no value exists 21006 * - Will be `undefined` if the given value is "none". 21007 * - Will be the actual value otherwise. 21008 * 21009 * @private 21010 */ 21011 21012 21013 function getSelectedOptionValue(el, parser) { 21014 var value = el.options[el.options.selectedIndex].value; 21015 return parseOptionValue(value, parser); 21016 } 21017 /** 21018 * Sets the selected <option> element within a <select> element based on a 21019 * given value. 21020 * 21021 * @param {Element} el 21022 * The element to look in. 21023 * 21024 * @param {string} value 21025 * the property to look on. 21026 * 21027 * @param {Function} [parser] 21028 * Optional function to adjust the value before comparing. 21029 * 21030 * @private 21031 */ 21032 21033 21034 function setSelectedOption(el, value, parser) { 21035 if (!value) { 21036 return; 21037 } 21038 21039 for (var i = 0; i < el.options.length; i++) { 21040 if (parseOptionValue(el.options[i].value, parser) === value) { 21041 el.selectedIndex = i; 21042 break; 21043 } 21044 } 21045 } 21046 /** 21047 * Manipulate Text Tracks settings. 21048 * 21049 * @extends ModalDialog 21050 */ 21051 21052 21053 var TextTrackSettings = /*#__PURE__*/function (_ModalDialog) { 21054 inheritsLoose(TextTrackSettings, _ModalDialog); 21055 21056 /** 21057 * Creates an instance of this class. 21058 * 21059 * @param {Player} player 21060 * The `Player` that this class should be attached to. 21061 * 21062 * @param {Object} [options] 21063 * The key/value store of player options. 21064 */ 21065 function TextTrackSettings(player, options) { 21066 var _this; 21067 21068 options.temporary = false; 21069 _this = _ModalDialog.call(this, player, options) || this; 21070 _this.updateDisplay = _this.updateDisplay.bind(assertThisInitialized(_this));
21070 // fill the modal and pretend we have opened it 21071 21072 _this.fill(); 21073 21074 _this.hasBeenOpened_ = _this.hasBeenFilled_ = true; 21075 _this.endDialog = createEl('p', { 21076 className: 'vjs-control-text', 21077 textContent: _this.localize('End of dialog window.') 21078 }); 21079 21080 _this.el().appendChild(_this.endDialog); 21081 21082 _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options 21083 21084 21085 if (options.persistTextTrackSettings === undefined) { 21086 _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings; 21087 } 21088 21089 _this.on(_this.$('.vjs-done-button'), 'click', function () { 21090 _this.saveSettings(); 21091 21092 _this.close(); 21093 }); 21094 21095 _this.on(_this.$('.vjs-default-button'), 'click', function () { 21096 _this.setDefaults(); 21097 21098 _this.updateDisplay(); 21099 }); 21100 21101 each(selectConfigs, function (config) { 21102 _this.on(_this.$(config.selector), 'change', _this.updateDisplay); 21103 }); 21104 21105 if (_this.options_.persistTextTrackSettings) { 21106 _this.restoreSettings(); 21107 } 21108 21109 return _this; 21110 } 21111 21112 var _proto = TextTrackSettings.prototype; 21113 21114 _proto.dispose = function dispose() { 21115 this.endDialog = null; 21116 21117 _ModalDialog.prototype.dispose.call(this); 21118 } 21119 /** 21120 * Create a <select> element with configured options. 21121 * 21122 * @param {string} key 21123 * Configuration key to use during creation. 21124 * 21125 * @return {string} 21126 * An HTML string. 21127 * 21128 * @private 21129 */ 21130 ; 21131 21132 _proto.createElSelect_ = function createElSelect_(key, legendId, type) { 21133 var _this2 = this; 21134 21135 if (legendId === void 0) { 21136 legendId = ''; 21137 } 21138 21139 if (type === void 0) { 21140 type = 'label'; 21141 } 21142 21143 var config = selectConfigs[key]; 21144 var id = config.id.replace('%s', this.id_); 21145 var selectLabelledbyIds = [legendId, id].join(' ').trim(); 21146 return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) { 21147 var optionId = id + '-' + o[1].replace(/\W+/g, ''); 21148 return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join(''); 21149 })).concat('</select>').join(''); 21150 } 21151 /** 21152 * Create foreground color element for the component 21153 * 21154 * @return {string} 21155 * An HTML string. 21156 * 21157 * @private 21158 */ 21159 ; 21160 21161 _proto.createElFgColor_ = function createElFgColor_() { 21162 var legendId = "captions-text-legend-" + this.id_; 21163 return ['<fieldset class="vjs-fg-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Text'), '</legend>', this.createElSelect_('color', legendId), '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join(''); 21164 } 21165 /** 21166 * Create background color element for the component 21167 * 21168 * @return {string} 21169 * An HTML string. 21170 * 21171 * @private 21172 */ 21173 ; 21174 21175 _proto.createElBgColor_ = function createElBgColor_() { 21176 var legendId = "captions-background-" + this.id_; 21177 return ['<fieldset class="vjs-bg-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Background'), '</legend>', this.createElSelect_('backgroundColor', legendId), '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join(''); 21178 } 21179 /** 21180 * Create window color element for the component 21181 * 21182 * @return {string} 21183 * An HTML string. 21184 * 21185 * @private 21186 */ 21187 ; 21188 21189 _proto.createElWinColor_ = function createElWinColor_() { 21190 var legendId = "captions-window-" + this.id_; 21191 return ['<fieldset class="vjs-window-color vjs-track-setting">', "<legend id=\"" + legendId + "\">", this.localize('Window'), '</legend>', this.createElSelect_('windowColor', legendId), '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join(''); 21192 } 21193 /** 21194 * Create color elements for the component 21195 * 21196 * @return {Element} 21197 * The element that was created 21198 * 21199 * @private 21200 */ 21201 ; 21202 21203 _proto.createElColors_ = function createElColors_() { 21204 return createEl('div', { 21205 className: 'vjs-track-settings-colors', 21206 innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') 21207 }); 21208 } 21209 /** 21210 * Create font elements for the component 21211 * 21212 * @return {Element} 21213 * The element that was created. 21214 * 21215 * @private 21216 */ 21217 ; 21218 21219 _proto.createElFont_ = function createElFont_() { 21220 return createEl('div', { 21221 className: 'vjs-track-settings-font', 21222 innerHTML: ['<fieldset class="vjs-font-percent vjs-track-setting">', this.createElSelect_('fontPercent', '', 'legend'), '</fieldset>', '<fieldset class="vjs-edge-style vjs-track-setting">', this.createElSelect_('edgeStyle', '', 'legend'), '</fieldset>', '<fieldset class="vjs-font-family vjs-track-setting">', this.createElSelect_('fontFamily', '', 'legend'), '</fieldset>'].join('') 21223 }); 21224 } 21225 /** 21226 * Create controls for the component 21227 * 21228 * @return {Element} 21229 * The element that was created. 21230 * 21231 * @private 21232 */ 21233 ; 21234 21235 _proto.createElControls_ = function createElControls_() { 21236 var defaultsDescription = this.localize('restore all settings to the default values'); 21237 return createEl('div', { 21238 className: 'vjs-track-settings-controls', 21239 innerHTML: ["<button type=\"button\" class=\"vjs-default-button\" title=\"" + defaultsDescription + "\">", this.localize('Reset'), "<span class=\"vjs-control-text\"> " + defaultsDescription + "</span>", '</button>', "<button type=\"button\" class=\"vjs-done-button\">" + this.localize('Done') + "</button>"].join('') 21240 }); 21241 }; 21242 21243 _proto.content = function content() { 21244 return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; 21245 }; 21246 21247 _proto.label = function label() { 21248 return this.localize('Caption Settings Dialog'); 21249 }; 21250 21251 _proto.description = function description() { 21252 return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); 21253 }; 21254 21255 _proto.buildCSSClass = function buildCSSClass() { 21256 return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; 21257 } 21258 /** 21259 * Gets an object of text track settings (or null). 21260 * 21261 * @return {Object} 21262 * An object with config values parsed from the DOM or localStorage. 21263 */ 21264 ; 21265 21266 _proto.getValues = function getValues() { 21267 var _this3 = this; 21268 21269 return reduce(selectConfigs, function (accum, config, key) { 21270 var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); 21271 21272 if (value !== undefined) { 21273 accum[key] = value; 21274 } 21275 21276 return accum; 21277 }, {}); 21278 } 21279 /** 21280 * Sets text track settings from an object of values. 21281 * 21282 * @param {Object} values 21283 * An object with config values parsed from the DOM or localStorage. 21284 */ 21285 ; 21286 21287 _proto.setValues = function setValues(values) { 21288 var _this4 = this; 21289 21290 each(selectConfigs, function (config, key) { 21291 setSelectedOption(_this4.$(config.selector), values[key], config.parser); 21292 }); 21293 } 21294 /** 21295 * Sets all `<select>` elements to their default values. 21296 */ 21297 ; 21298 21299 _proto.setDefaults = function setDefaults() { 21300 var _this5 = this; 21301 21302 each(selectConfigs, function (config) { 21303 var index = config.hasOwnProperty('default') ? config["default"] : 0; 21304 _this5.$(config.selector).selectedIndex = index; 21305 }); 21306 } 21307 /** 21308 * Restore texttrack settings from localStorage 21309 */ 21310 ; 21311 21312 _proto.restoreSettings = function restoreSettings() { 21313 var values; 21314
21315 try { 21316 values = JSON.parse(window_1.localStorage.getItem(LOCAL_STORAGE_KEY$1)); 21317 } catch (err) { 21318 log$1.warn(err); 21319 } 21320 21321 if (values) { 21322 this.setValues(values); 21323 } 21324 } 21325 /** 21326 * Save text track settings to localStorage 21327 */ 21328 ; 21329 21330 _proto.saveSettings = function saveSettings() { 21331 if (!this.options_.persistTextTrackSettings) { 21332 return; 21333 } 21334 21335 var values = this.getValues(); 21336 21337 try { 21338 if (Object.keys(values).length) { 21339 window_1.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(values)); 21340 } else { 21341 window_1.localStorage.removeItem(LOCAL_STORAGE_KEY$1); 21342 } 21343 } catch (err) { 21344 log$1.warn(err); 21345 } 21346 } 21347 /** 21348 * Update display of text track settings 21349 */ 21350 ; 21351 21352 _proto.updateDisplay = function updateDisplay() { 21353 var ttDisplay = this.player_.getChild('textTrackDisplay'); 21354 21355 if (ttDisplay) { 21356 ttDisplay.updateDisplay(); 21357 } 21358 } 21359 /** 21360 * conditionally blur the element and refocus the captions button 21361 * 21362 * @private 21363 */ 21364 ; 21365 21366 _proto.conditionalBlur_ = function conditionalBlur_() { 21367 this.previouslyActiveEl_ = null; 21368 var cb = this.player_.controlBar; 21369 var subsCapsBtn = cb && cb.subsCapsButton; 21370 var ccBtn = cb && cb.captionsButton; 21371 21372 if (subsCapsBtn) { 21373 subsCapsBtn.focus(); 21374 } else if (ccBtn) { 21375 ccBtn.focus(); 21376 } 21377 }; 21378 21379 return TextTrackSettings; 21380 }(ModalDialog); 21381 21382 Component$1.registerComponent('TextTrackSettings', TextTrackSettings); 21383 21384 /** 21385 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions. 21386 * 21387 * 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}. 21388 * 21389 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option. 21390 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below. 21391 * @example <caption>How to disable the resize manager</caption> 21392 * const player = videojs('#vid', { 21393 * resizeManager: false 21394 * }); 21395 * 21396 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification} 21397 * 21398 * @extends Component 21399 */ 21400 21401 var ResizeManager = /*#__PURE__*/function (_Component) { 21402 inheritsLoose(ResizeManager, _Component); 21403 21404 /** 21405 * Create the ResizeManager. 21406 * 21407 * @param {Object} player 21408 * The `Player` that this class should be attached to. 21409 * 21410 * @param {Object} [options] 21411 * The key/value store of ResizeManager options. 21412 * 21413 * @param {Object} [options.ResizeObserver] 21414 * A polyfill for ResizeObserver can be passed in here. 21415 * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback. 21416 */ 21417 function ResizeManager(player, options) { 21418 var _this; 21419 21420 var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window_1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver 21421 21422 if (options.ResizeObserver === null) { 21423 RESIZE_OBSERVER_AVAILABLE = false; 21424 } // Only create an element when ResizeObserver isn't available 21425 21426 21427 var options_ = mergeOptions$3({ 21428 createEl: !RESIZE_OBSERVER_AVAILABLE, 21429 reportTouchActivity: false 21430 }, options); 21431 _this = _Component.call(this, player, options_) || this; 21432 _this.ResizeObserver = options.ResizeObserver || window_1.ResizeObserver; 21433 _this.loadListener_ = null; 21434 _this.resizeObserver_ = null; 21435 _this.debouncedHandler_ = debounce(function () { 21436 _this.resizeHandler(); 21437 }, 100, false, assertThisInitialized(_this)); 21438 21439 if (RESIZE_OBSERVER_AVAILABLE) { 21440 _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_); 21441
21442 _this.resizeObserver_.observe(player.el()); 21443 } else { 21444 _this.loadListener_ = function () { 21445 if (!_this.el_ || !_this.el_.contentWindow) { 21446 return; 21447 } 21448 21449 var debouncedHandler_ = _this.debouncedHandler_; 21450 21451 var unloadListener_ = _this.unloadListener_ = function () { 21452 off(this, 'resize', debouncedHandler_); 21453 off(this, 'unload', unloadListener_); 21454 unloadListener_ = null; 21455 }; // safari and edge can unload the iframe before resizemanager dispose 21456 // we have to dispose of event handlers correctly before that happens 21457 21458 21459 on(_this.el_.contentWindow, 'unload', unloadListener_); 21460 on(_this.el_.contentWindow, 'resize', debouncedHandler_); 21461 }; 21462 21463 _this.one('load', _this.loadListener_); 21464 } 21465 21466 return _this; 21467 } 21468 21469 var _proto = ResizeManager.prototype; 21470 21471 _proto.createEl = function createEl() { 21472 return _Component.prototype.createEl.call(this, 'iframe', { 21473 className: 'vjs-resize-manager', 21474 tabIndex: -1 21475 }, { 21476 'aria-hidden': 'true' 21477 }); 21478 } 21479 /** 21480 * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver 21481 * 21482 * @fires Player#playerresize 21483 */ 21484 ; 21485 21486 _proto.resizeHandler = function resizeHandler() { 21487 /** 21488 * Called when the player size has changed 21489 * 21490 * @event Player#playerresize 21491 * @type {EventTarget~Event} 21492 */ 21493 // make sure player is still around to trigger 21494 // prevents this from causing an error after dispose 21495 if (!this.player_ || !this.player_.trigger) { 21496 return; 21497 } 21498 21499 this.player_.trigger('playerresize'); 21500 }; 21501 21502 _proto.dispose = function dispose() { 21503 if (this.debouncedHandler_) { 21504 this.debouncedHandler_.cancel(); 21505 } 21506 21507 if (this.resizeObserver_) { 21508 if (this.player_.el()) { 21509 this.resizeObserver_.unobserve(this.player_.el()); 21510 } 21511 21512 this.resizeObserver_.disconnect(); 21513 } 21514 21515 if (this.loadListener_) { 21516 this.off('load', this.loadListener_); 21517 } 21518 21519 if (this.el_ && this.el_.contentWindow && this.unloadListener_) { 21520 this.unloadListener_.call(this.el_.contentWindow); 21521 } 21522 21523 this.ResizeObserver = null; 21524 this.resizeObserver = null; 21525 this.debouncedHandler_ = null; 21526 this.loadListener_ = null; 21527 21528 _Component.prototype.dispose.call(this); 21529 }; 21530 21531 return ResizeManager; 21532 }(Component$1); 21533 21534 Component$1.registerComponent('ResizeManager', ResizeManager); 21535 21536 var defaults = { 21537 trackingThreshold: 30, 21538 liveTolerance: 15 21539 }; 21540 /* 21541 track when we are at the live edge, and other helpers for live playback */ 21542 21543 /** 21544 * A class for checking live current time and determining when the player 21545 * is at or behind the live edge. 21546 */ 21547 21548 var LiveTracker = /*#__PURE__*/function (_Component) { 21549 inheritsLoose(LiveTracker, _Component); 21550 21551 /** 21552 * Creates an instance of this class. 21553 * 21554 * @param {Player} player 21555 * The `Player` that this class should be attached to. 21556 * 21557 * @param {Object} [options] 21558 * The key/value store of player options. 21559 * 21560 * @param {number} [options.trackingThreshold=30] 21561 * Number of seconds of live window (seekableEnd - seekableStart) that 21562 * media needs to have before the liveui will be shown. 21563 * 21564 * @param {number} [options.liveTolerance=15] 21565 * Number of seconds behind live that we have to be 21566 * before we will be considered non-live. Note that this will only 21567 * be used when playing at the live edge. This allows large seekable end 21568 * changes to not effect wether we are live or not. 21569 */ 21570 function LiveTracker(player, options) { 21571 var _this; 21572 21573 // LiveTracker does not need an element 21574 var options_ = mergeOptions$3(defaults, options, { 21575 createEl: false 21576 }); 21577 _this = _Component.call(this, player, options_) || this; 21578 21579 _this.handleVisibilityChange_ = function (e) { 21580 return _this.handleVisibilityChange(e); 21581 }; 21582 21583 _this.trackLiveHandler_ = function () { 21584 return _this.trackLive_(); 21585 }; 21586 21587 _this.handlePlay_ = function (e) { 21588 return _this.handlePlay(e); 21589 }; 21590 21591 _this.handleFirstTimeupdate_ = function (e) { 21592 return _this.handleFirstTimeupdate(e); 21593 }; 21594 21595 _this.handleSeeked_ = function (e) { 21596 return _this.handleSeeked(e); 21597 }; 21598 21599 _this.seekToLiveEdge_ = function (e) { 21600 return _this.seekToLiveEdge(e); 21601 }; 21602 21603 _this.reset_(); 21604 21605 _this.on(_this.player_, 'durationchange', function (e) { 21606 return _this.handleDurationchange(e); 21607 }); // we should try to toggle tracking on canplay as native playback engines, like Safari 21608 // may not have the proper values for things like seekableEnd until then 21609 21610 21611 _this.one(_this.player_, 'canplay', function () { 21612 return _this.toggleTracking(); 21613 }); // we don't need to track live playback if the document is hidden, 21614 // also, tracking when the document is hidden can 21615 // cause the CPU to spike and eventually crash the page on IE11. 21616 21617 21618 if (IE_VERSION && 'hidden' in document_1 && 'visibilityState' in document_1) { 21619 _this.on(document_1, 'visibilitychange', _this.handleVisibilityChange_); 21620 } 21621 21622 return _this; 21623 } 21624 /** 21625 * toggle tracking based on document visiblility 21626 */ 21627 21628 21629 var _proto = LiveTracker.prototype; 21630 21631 _proto.handleVisibilityChange = function handleVisibilityChange() { 21632 if (this.player_.duration() !== Infinity) { 21633 return; 21634 } 21635 21636 if (document_1.hidden) { 21637 this.stopTracking(); 21638 } else { 21639 this.startTracking(); 21640 } 21641 } 21642 /** 21643 * all the functionality for tracking when seek end changes 21644 * and for tracking how far past seek end we should be 21645 */ 21646 ; 21647 21648 _proto.trackLive_ = function trackLive_() { 21649 var seekable = this.player_.seekable();
21649 // skip undefined seekable 21650 21651 if (!seekable || !seekable.length) { 21652 return; 21653 } 21654 21655 var newTime = Number(window_1.performance.now().toFixed(4)); 21656 var deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000; 21657 this.lastTime_ = newTime; 21658 this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime; 21659 var liveCurrentTime = this.liveCurrentTime(); 21660 var currentTime = this.player_.currentTime(); // we are behind live if any are true 21661 // 1. the player is paused 21662 // 2. the user seeked to a location 2 seconds away from live 21663 // 3. the difference between live and current time is greater 21664 // liveTolerance which defaults to 15s 21665 21666 var isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance; // we cannot be behind if 21667 // 1. until we have not seen a timeupdate yet 21668 // 2. liveCurrentTime is Infinity, which happens on Android and Native Safari 21669 21670 if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) { 21671 isBehind = false; 21672 } 21673 21674 if (isBehind !== this.behindLiveEdge_) { 21675 this.behindLiveEdge_ = isBehind; 21676 this.trigger('liveedgechange'); 21677 } 21678 } 21679 /** 21680 * handle a durationchange event on the player 21681 * and start/stop tracking accordingly. 21682 */ 21683 ; 21684 21685 _proto.handleDurationchange = function handleDurationchange() { 21686 this.toggleTracking(); 21687 } 21688 /** 21689 * start/stop tracking 21690 */ 21691 ; 21692 21693 _proto.toggleTracking = function toggleTracking() { 21694 if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) { 21695 if (this.player_.options_.liveui) { 21696 this.player_.addClass('vjs-liveui'); 21697 } 21698 21699 this.startTracking(); 21700 } else { 21701 this.player_.removeClass('vjs-liveui'); 21702 this.stopTracking(); 21703 } 21704 } 21705 /** 21706 * start tracking live playback 21707 */ 21708 ; 21709 21710 _proto.startTracking = function startTracking() { 21711 if (this.isTracking()) { 21712 return; 21713 } // If we haven't seen a timeupdate, we need to check whether playback 21714 // began before this component started tracking. This can happen commonly 21715 // when using autoplay. 21716 21717 21718 if (!this.timeupdateSeen_) { 21719 this.timeupdateSeen_ = this.player_.hasStarted(); 21720 } 21721 21722 this.trackingInterval_ = this.setInterval(this.trackLiveHandler_, UPDATE_REFRESH_INTERVAL); 21723 this.trackLive_(); 21724 this.on(this.player_, ['play', 'pause'], this.trackLiveHandler_); 21725 21726 if (!this.timeupdateSeen_) { 21727 this.one(this.player_, 'play', this.handlePlay_); 21728 this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate_); 21729 } else { 21730 this.on(this.player_, 'seeked', this.handleSeeked_); 21731 } 21732 } 21733 /** 21734 * handle the first timeupdate on the player if it wasn't already playing 21735 * when live tracker started tracking. 21736 */ 21737 ; 21738 21739 _proto.handleFirstTimeupdate = function handleFirstTimeupdate() { 21740 this.timeupdateSeen_ = true; 21741 this.on(this.player_, 'seeked', this.handleSeeked_); 21742 } 21743 /** 21744 * Keep track of what time a seek starts, and listen for seeked 21745 * to find where a seek ends. 21746 */ 21747 ; 21748 21749 _proto.handleSeeked = function handleSeeked() { 21750 var timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime()); 21751 this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2; 21752 this.nextSeekedFromUser_ = false; 21753 this.trackLive_(); 21754 } 21755 /** 21756 * handle the first play on the player, and make sure that we seek 21757 * right to the live edge. 21758 */ 21759 ; 21760 21761 _proto.handlePlay = function handlePlay() { 21762 this.one(this.player_, 'timeupdate', this.seekToLiveEdge_); 21763 } 21764 /** 21765 * Stop tracking, and set all internal variables to 21766 * their initial value. 21767 */ 21768 ; 21769 21770 _proto.reset_ = function reset_() { 21771 this.lastTime_ = -1; 21772 this.pastSeekEnd_ = 0; 21773 this.lastSeekEnd_ = -1; 21774 this.behindLiveEdge_ = true; 21775 this.timeupdateSeen_ = false; 21776 this.seekedBehindLive_ = false; 21777 this.nextSeekedFromUser_ = false; 21778 this.clearInterval(this.trackingInterval_); 21779 this.trackingInterval_ = null; 21780 this.off(this.player_, ['play', 'pause'], this.trackLiveHandler_); 21781 this.off(this.player_, 'seeked', this.handleSeeked_); 21782 this.off(this.player_, 'play', this.handlePlay_); 21783 this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate_); 21784 this.off(this.player_, 'timeupdate', this.seekToLiveEdge_); 21785 } 21786 /** 21787 * The next seeked event is from the user. Meaning that any seek 21788 * > 2s behind live will be considered behind live for real and 21789 * liveTolerance will be ignored. 21790 */ 21791 ; 21792 21793 _proto.nextSeekedFromUser = function nextSeekedFromUser() { 21794 this.nextSeekedFromUser_ = true; 21795 } 21796 /** 21797 * stop tracking live playback 21798 */ 21799 ; 21800 21801 _proto.stopTracking = function stopTracking() { 21802 if (!this.isTracking()) { 21803 return; 21804 } 21805 21806 this.reset_(); 21807 this.trigger('liveedgechange'); 21808 } 21809 /** 21810 * A helper to get the player seekable end 21811 * so that we don't have to null check everywhere 21812 * 21813 * @return {number} 21814 * The furthest seekable end or Infinity. 21815 */ 21816 ; 21817 21818 _proto.seekableEnd = function seekableEnd() { 21819 var seekable = this.player_.seekable(); 21820 var seekableEnds = []; 21821 var i = seekable ? seekable.length : 0; 21822 21823 while (i--) { 21824 seekableEnds.push(seekable.end(i)); 21825 } // grab the furthest seekable end after sorting, or if there are none 21826 // default to Infinity 21827 21828 21829 return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity; 21830 } 21831 /** 21832 * A helper to get the player seekable start 21833 * so that we don't have to null check everywhere 21834 * 21835 * @return {number} 21836 * The earliest seekable start or 0. 21837 */ 21838 ; 21839 21840 _proto.seekableStart = function seekableStart() { 21841 var seekable = this.player_.seekable(); 21842 var seekableStarts = []; 21843 var i = seekable ? seekable.length : 0; 21844 21845 while (i--) { 21846 seekableStarts.push(seekable.start(i)); 21847 } // grab the first seekable start after sorting, or if there are none 21848 // default to 0 21849 21850 21851 return seekableStarts.length ? seekableStarts.sort()[0] : 0; 21852 } 21853 /** 21854 * Get the live time window aka 21855 * the amount of time between seekable start and 21856 * live current time. 21857 * 21858 * @return {number} 21859 * The amount of seconds that are seekable in 21860 * the live video. 21861 */ 21862 ; 21863 21864 _proto.liveWindow = function liveWindow() { 21865 var liveCurrentTime = this.liveCurrentTime(); // if liveCurrenTime is Infinity then we don't have a liveWindow at all 21866 21867 if (liveCurrentTime === Infinity) { 21868 return 0; 21869 } 21870 21871 return liveCurrentTime - this.seekableStart(); 21872 } 21873 /** 21874 * Determines if the player is live, only checks if this component 21875 * is tracking live playback or not 21876 * 21877 * @return {boolean} 21878 * Wether liveTracker is tracking 21879 */ 21880 ; 21881 21882 _proto.isLive = function isLive() { 21883 return this.isTracking(); 21884 } 21885 /** 21886 * Determines if currentTime is at the live edge and won't fall behind 21887 * on each seekableendchange 21888 * 21889 * @return {boolean} 21890 * Wether playback is at the live edge 21891 */ 21892 ; 21893 21894 _proto.atLiveEdge = function atLiveEdge() { 21895 return !this.behindLiveEdge(); 21896 } 21897 /** 21898 * get what we expect the live current time to be 21899 * 21900 * @return {number} 21901 * The expected live current time 21902 */ 21903 ; 21904 21905 _proto.liveCurrentTime = function liveCurrentTime() { 21906 return this.pastSeekEnd() + this.seekableEnd(); 21907 } 21908 /** 21909 * The number of seconds that have occured after seekable end 21910 * changed. This will be reset to 0 once seekable end changes. 21911 * 21912 * @return {number} 21913 * Seconds past the current seekable end 21914 */ 21915 ; 21916 21917 _proto.pastSeekEnd = function pastSeekEnd() { 21918 var seekableEnd = this.seekableEnd(); 21919 21920 if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) { 21921 this.pastSeekEnd_ = 0; 21922 } 21923 21924 this.lastSeekEnd_ = seekableEnd; 21925 return this.pastSeekEnd_; 21926 } 21927 /** 21928 * If we are currently behind the live edge, aka currentTime will be 21929 * behind on a seekableendchange 21930 * 21931 * @return {boolean} 21932 * If we are behind the live edge 21933 */ 21934 ; 21935 21936 _proto.behindLiveEdge = function behindLiveEdge() { 21937 return this.behindLiveEdge_; 21938 } 21939 /** 21940 * Wether live tracker is currently tracking or not. 21941 */ 21942 ; 21943 21944 _proto.isTracking = function isTracking() { 21945 return typeof this.trackingInterval_ === 'number'; 21946 } 21947 /** 21948 * Seek to the live edge if we are behind the live edge 21949 */ 21950 ; 21951 21952 _proto.seekToLiveEdge = function seekToLiveEdge() { 21953 this.seekedBehindLive_ = false; 21954 21955 if (this.atLiveEdge()) { 21956 return; 21957 } 21958 21959 this.nextSeekedFromUser_ = false; 21960 this.player_.currentTime(this.liveCurrentTime()); 21961 } 21962 /**
vendor: 6,466 bytes, lines 21963-22153
21963 * Dispose of liveTracker 21964 */ 21965 ; 21966 21967 _proto.dispose = function dispose() { 21968 this.off(document_1, 'visibilitychange', this.handleVisibilityChange_); 21969 this.stopTracking(); 21970 21971 _Component.prototype.dispose.call(this); 21972 }; 21973 21974 return LiveTracker; 21975 }(Component$1); 21976 21977 Component$1.registerComponent('LiveTracker', LiveTracker); 21978 21979 /** 21980 * This function is used to fire a sourceset when there is something 21981 * similar to `mediaEl.load()` being called. It will try to find the source via 21982 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset` 21983 * with the source that was found or empty string if we cannot know. If it cannot 21984 * find a source then `sourceset` will not be fired. 21985 * 21986 * @param {Html5} tech 21987 * The tech object that sourceset was setup on 21988 * 21989 * @return {boolean} 21990 * returns false if the sourceset was not fired and true otherwise. 21991 */ 21992 21993 var sourcesetLoad = function sourcesetLoad(tech) { 21994 var el = tech.el(); // if `el.src` is set, that source will be loaded. 21995 21996 if (el.hasAttribute('src')) { 21997 tech.triggerSourceset(el.src); 21998 return true; 21999 } 22000 /** 22001 * Since there isn't a src property on the media element, source elements will be used for 22002 * implementing the source selection algorithm. This happens asynchronously and 22003 * for most cases were there is more than one source we cannot tell what source will 22004 * be loaded, without re-implementing the source selection algorithm. At this time we are not 22005 * going to do that. There are three special cases that we do handle here though: 22006 * 22007 * 1. If there are no sources, do not fire `sourceset`. 22008 * 2. If there is only one `<source>` with a `src` property/attribute that is our `src` 22009 * 3. If there is more than one `<source>` but all of them have the same `src` url. 22010 * That will be our src. 22011 */ 22012 22013 22014 var sources = tech.$$('source'); 22015 var srcUrls = []; 22016 var src = ''; // if there are no sources, do not fire sourceset 22017 22018 if (!sources.length) { 22019 return false; 22020 } // only count valid/non-duplicate source elements 22021 22022 22023 for (var i = 0; i < sources.length; i++) { 22024 var url = sources[i].src; 22025 22026 if (url && srcUrls.indexOf(url) === -1) { 22027 srcUrls.push(url); 22028 } 22029 } // there were no valid sources 22030 22031 22032 if (!srcUrls.length) { 22033 return false; 22034 } // there is only one valid source element url 22035 // use that 22036 22037 22038 if (srcUrls.length === 1) { 22039 src = srcUrls[0]; 22040 } 22041 22042 tech.triggerSourceset(src); 22043 return true; 22044 }; 22045 /** 22046 * our implementation of an `innerHTML` descriptor for browsers 22047 * that do not have one. 22048 */ 22049 22050 22051 var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', { 22052 get: function get() { 22053 return this.cloneNode(true).innerHTML; 22054 }, 22055 set: function set(v) { 22056 // make a dummy node to use innerHTML on 22057 var dummy = document_1.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided 22058 22059 dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy 22060 22061 var docFrag = document_1.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy 22062 // to the document fragment 22063 22064 while (dummy.childNodes.length) { 22065 docFrag.appendChild(dummy.childNodes[0]); 22066 } // remove content 22067 22068 22069 this.innerText = ''; // now we add all of that html in one by appending the 22070 // document fragment. This is how innerHTML does it. 22071 22072 window_1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would 22073 22074 return this.innerHTML; 22075 } 22076 }); 22077 /** 22078 * Get a property descriptor given a list of priorities and the 22079 * property to get. 22080 */ 22081 22082 var getDescriptor = function getDescriptor(priority, prop) { 22083 var descriptor = {}; 22084 22085 for (var i = 0; i < priority.length; i++) { 22086 descriptor = Object.getOwnPropertyDescriptor(priority[i], prop); 22087 22088 if (descriptor && descriptor.set && descriptor.get) { 22089 break; 22090 } 22091 } 22092 22093 descriptor.enumerable = true; 22094 descriptor.configurable = true; 22095 return descriptor; 22096 }; 22097 22098 var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) { 22099 return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, window_1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML'); 22100 }; 22101 /** 22102 * Patches browser internal functions so that we can tell synchronously 22103 * if a `<source>` was appended to the media element. For some reason this 22104 * causes a `sourceset` if the the media element is ready and has no source. 22105 * This happens when: 22106 * - The page has just loaded and the media element does not have a source. 22107 * - The media element was emptied of all sources, then `load()` was called. 22108 * 22109 * It does this by patching the following functions/properties when they are supported: 22110 * 22111 * - `append()` - can be used to add a `<source>` element to the media element 22112 * - `appendChild()` - can be used to add a `<source>` element to the media element 22113 * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element 22114 * - `innerHTML` - can be used to add a `<source>` element to the media element 22115 * 22116 * @param {Html5} tech 22117 * The tech object that sourceset is being setup on. 22118 */ 22119 22120 22121 var firstSourceWatch = function firstSourceWatch(tech) { 22122 var el = tech.el(); // make sure firstSourceWatch isn't setup twice. 22123 22124 if (el.resetSourceWatch_) { 22125 return; 22126 } 22127 22128 var old = {}; 22129 var innerDescriptor = getInnerHTMLDescriptor(tech); 22130 22131 var appendWrapper = function appendWrapper(appendFn) { 22132 return function () { 22133 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 22134 args[_key] = arguments[_key]; 22135 } 22136 22137 var retval = appendFn.apply(el, args); 22138 sourcesetLoad(tech); 22139 return retval; 22140 }; 22141 }; 22142 22143 ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) { 22144 if (!el[k]) { 22145 return; 22146 } // store the old function 22147 22148 22149 old[k] = el[k]; // call the old function with a sourceset if a source 22150 // was loaded 22151 22152 el[k] = appendWrapper(old[k]); 22153 });
22154 Object.defineProperty(el, 'innerHTML', mergeOptions$3(innerDescriptor, { 22155 set: appendWrapper(innerDescriptor.set) 22156 })); 22157 22158 el.resetSourceWatch_ = function () { 22159 el.resetSourceWatch_ = null; 22160 Object.keys(old).forEach(function (k) { 22161 el[k] = old[k]; 22162 }); 22163 Object.defineProperty(el, 'innerHTML', innerDescriptor); 22164 }; // on the first sourceset, we need to revert our changes 22165 22166 22167 tech.one('sourceset', el.resetSourceWatch_); 22168 }; 22169 /** 22170 * our implementation of a `src` descriptor for browsers 22171 * that do not have one. 22172 */ 22173 22174 22175 var srcDescriptorPolyfill = Object.defineProperty({}, 'src', { 22176 get: function get() { 22177 if (this.hasAttribute('src')) { 22178 return getAbsoluteURL(window_1.Element.prototype.getAttribute.call(this, 'src')); 22179 } 22180 22181 return ''; 22182 }, 22183 set: function set(v) { 22184 window_1.Element.prototype.setAttribute.call(this, 'src', v); 22185 return v; 22186 } 22187 }); 22188 22189 var getSrcDescriptor = function getSrcDescriptor(tech) { 22190 return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src'); 22191 }; 22192 /** 22193 * setup `sourceset` handling on the `Html5` tech. This function 22194 * patches the following element properties/functions: 22195 * 22196 * - `src` - to determine when `src` is set 22197 * - `setAttribute()` - to determine when `src` is set 22198 * - `load()` - this re-triggers the source selection algorithm, and can 22199 * cause a sourceset. 22200 * 22201 * If there is no source when we are adding `sourceset` support or during a `load()` 22202 * we also patch the functions listed in `firstSourceWatch`. 22203 * 22204 * @param {Html5} tech 22205 * The tech to patch 22206 */ 22207 22208 22209 var setupSourceset = function setupSourceset(tech) { 22210 if (!tech.featuresSourceset) { 22211 return; 22212 } 22213 22214 var el = tech.el(); // make sure sourceset isn't setup twice. 22215 22216 if (el.resetSourceset_) { 22217 return; 22218 } 22219 22220 var srcDescriptor = getSrcDescriptor(tech); 22221 var oldSetAttribute = el.setAttribute; 22222 var oldLoad = el.load; 22223 Object.defineProperty(el, 'src', mergeOptions$3(srcDescriptor, { 22224 set: function set(v) { 22225 var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src 22226 22227 tech.triggerSourceset(el.src); 22228 return retval; 22229 } 22230 })); 22231 22232 el.setAttribute = function (n, v) { 22233 var retval = oldSetAttribute.call(el, n, v); 22234 22235 if (/src/i.test(n)) { 22236 tech.triggerSourceset(el.src); 22237 } 22238 22239 return retval; 22240 }; 22241 22242 el.load = function () { 22243 var retval = oldLoad.call(el); // if load was called, but there was no source to fire 22244 // sourceset on. We have to watch for a source append 22245 // as that can trigger a `sourceset` when the media element 22246 // has no source 22247 22248 if (!sourcesetLoad(tech)) { 22249 tech.triggerSourceset(''); 22250 firstSourceWatch(tech); 22251 } 22252 22253 return retval; 22254 }; 22255 22256 if (el.currentSrc) { 22257 tech.triggerSourceset(el.currentSrc); 22258 } else if (!sourcesetLoad(tech)) { 22259 firstSourceWatch(tech); 22260 } 22261 22262 el.resetSourceset_ = function () { 22263 el.resetSourceset_ = null; 22264 el.load = oldLoad; 22265 el.setAttribute = oldSetAttribute; 22266 Object.defineProperty(el, 'src', srcDescriptor); 22267 22268 if (el.resetSourceWatch_) { 22269 el.resetSourceWatch_(); 22270 } 22271 }; 22272 }; 22273 22274 /** 22275 * Object.defineProperty but "lazy", which means that the value is only set after 22276 * it retrieved the first time, rather than being set right away. 22277 * 22278 * @param {Object} obj the object to set the property on 22279 * @param {string} key the key for the property to set 22280 * @param {Function} getValue the function used to get the value when it is needed. 22281 * @param {boolean} setter wether a setter shoould be allowed or not 22282 */ 22283 var defineLazyProperty = function defineLazyProperty(obj, key, getValue, setter) { 22284 if (setter === void 0) { 22285 setter = true; 22286 } 22287 22288 var set = function set(value) { 22289 return Object.defineProperty(obj, key, { 22290 value: value, 22291 enumerable: true, 22292 writable: true 22293 }); 22294 }; 22295 22296 var options = { 22297 configurable: true, 22298 enumerable: true, 22299 get: function get() { 22300 var value = getValue(); 22301 set(value); 22302 return value; 22303 } 22304 }; 22305 22306 if (setter) { 22307 options.set = set; 22308 } 22309 22310 return Object.defineProperty(obj, key, options); 22311 }; 22312 22313 /** 22314 * HTML5 Media Controller - Wrapper for HTML5 Media API 22315 * 22316 * @mixes Tech~SourceHandlerAdditions 22317 * @extends Tech 22318 */ 22319 22320 var Html5 = /*#__PURE__*/function (_Tech) { 22321 inheritsLoose(Html5, _Tech); 22322 22323 /** 22324 * Create an instance of this Tech. 22325 * 22326 * @param {Object} [options] 22327 * The key/value store of player options. 22328 * 22329 * @param {Component~ReadyCallback} ready 22330 * Callback function to call when the `HTML5` Tech is ready. 22331 */ 22332 function Html5(options, ready) { 22333 var _this; 22334 22335 _this = _Tech.call(this, options, ready) || this; 22336 var source = options.source; 22337 var crossoriginTracks = false; // Set the source if one is provided 22338 // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) 22339 // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source 22340 // anyway so the error gets fired. 22341 22342 if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.
vendor: 7,352 bytes, lines 22342-22531
22342initNetworkState_ === 3)) { 22343 _this.setSource(source); 22344 } else { 22345 _this.handleLateInit_(_this.el_); 22346 } // setup sourceset after late sourceset/init 22347 22348 22349 if (options.enableSourceset) { 22350 _this.setupSourcesetHandling_(); 22351 } 22352 22353 _this.isScrubbing_ = false; 22354 22355 if (_this.el_.hasChildNodes()) { 22356 var nodes = _this.el_.childNodes; 22357 var nodesLength = nodes.length; 22358 var removeNodes = []; 22359 22360 while (nodesLength--) { 22361 var node = nodes[nodesLength]; 22362 var nodeName = node.nodeName.toLowerCase(); 22363 22364 if (nodeName === 'track') { 22365 if (!_this.featuresNativeTextTracks) { 22366 // Empty video tag tracks so the built-in player doesn't use them also. 22367 // This may not be fast enough to stop HTML5 browsers from reading the tags 22368 // so we'll need to turn off any default tracks if we're manually doing 22369 // captions and subtitles. videoElement.textTracks 22370 removeNodes.push(node); 22371 } else { 22372 // store HTMLTrackElement and TextTrack to remote list 22373 _this.remoteTextTrackEls().addTrackElement_(node); 22374 22375 _this.remoteTextTracks().addTrack(node.track); 22376 22377 _this.textTracks().addTrack(node.track); 22378 22379 if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) { 22380 crossoriginTracks = true; 22381 } 22382 } 22383 } 22384 } 22385 22386 for (var i = 0; i < removeNodes.length; i++) { 22387 _this.el_.removeChild(removeNodes[i]); 22388 } 22389 } 22390 22391 _this.proxyNativeTracks_(); 22392 22393 if (_this.featuresNativeTextTracks && crossoriginTracks) { 22394 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.'); 22395 } // prevent iOS Safari from disabling metadata text tracks during native playback 22396 22397 22398 _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used 22399 // Our goal should be to get the custom controls on mobile solid everywhere 22400 // so we can remove this all together. Right now this will block custom 22401 // controls on touch enabled laptops like the Chrome Pixel 22402 22403 22404 if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { 22405 _this.setControls(true); 22406 } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` 22407 // into a `fullscreenchange` event 22408 22409 22410 _this.proxyWebkitFullscreen_(); 22411 22412 _this.triggerReady(); 22413 22414 return _this; 22415 } 22416 /** 22417 * Dispose of `HTML5` media element and remove all tracks. 22418 */ 22419 22420 22421 var _proto = Html5.prototype; 22422 22423 _proto.dispose = function dispose() { 22424 if (this.el_ && this.el_.resetSourceset_) { 22425 this.el_.resetSourceset_(); 22426 } 22427 22428 Html5.disposeMediaElement(this.el_); 22429 this.options_ = null; // tech will handle clearing of the emulated track list 22430 22431 _Tech.prototype.dispose.call(this); 22432 } 22433 /** 22434 * Modify the media element so that we can detect when 22435 * the source is changed. Fires `sourceset` just after the source has changed 22436 */ 22437 ; 22438 22439 _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() { 22440 setupSourceset(this); 22441 } 22442 /** 22443 * When a captions track is enabled in the iOS Safari native player, all other 22444 * tracks are disabled (including metadata tracks), which nulls all of their 22445 * associated cue points. This will restore metadata tracks to their pre-fullscreen 22446 * state in those cases so that cue points are not needlessly lost. 22447 * 22448 * @private 22449 */ 22450 ; 22451 22452 _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() { 22453 var textTracks = this.textTracks(); 22454 var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state 22455 22456 var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() { 22457 metadataTracksPreFullscreenState = []; 22458 22459 for (var i = 0; i < textTracks.length; i++) { 22460 var track = textTracks[i]; 22461 22462 if (track.kind === 'metadata') { 22463 metadataTracksPreFullscreenState.push({ 22464 track: track, 22465 storedMode: track.mode 22466 }); 22467 } 22468 } 22469 }; // snapshot each metadata track's initial state, and update the snapshot 22470 // each time there is a track 'change' event 22471 22472 22473 takeMetadataTrackSnapshot(); 22474 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 22475 this.on('dispose', function () { 22476 return textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 22477 }); 22478 22479 var restoreTrackMode = function restoreTrackMode() { 22480 for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) { 22481 var storedTrack = metadataTracksPreFullscreenState[i]; 22482 22483 if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) { 22484 storedTrack.track.mode = storedTrack.storedMode; 22485 } 22486 } // we only want this handler to be executed on the first 'change' event 22487 22488 22489 textTracks.removeEventListener('change', restoreTrackMode); 22490 }; // when we enter fullscreen playback, stop updating the snapshot and 22491 // restore all track modes to their pre-fullscreen state 22492 22493 22494 this.on('webkitbeginfullscreen', function () { 22495 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed 22496 22497 textTracks.removeEventListener('change', restoreTrackMode); 22498 textTracks.addEventListener('change', restoreTrackMode); 22499 }); // start updating the snapshot again after leaving fullscreen 22500 22501 this.on('webkitendfullscreen', function () { 22502 // remove the listener before adding it just in case it wasn't previously removed 22503 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 22504 textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback 22505 22506 textTracks.removeEventListener('change', restoreTrackMode); 22507 }); 22508 } 22509 /** 22510 * Attempt to force override of tracks for the given type 22511 * 22512 * @param {string} type - Track type to override, possible values include 'Audio', 22513 * 'Video', and 'Text'. 22514 * @param {boolean} override - If set to true native audio/video will be overridden, 22515 * otherwise native audio/video will potentially be used. 22516 * @private 22517 */ 22518 ; 22519 22520 _proto.overrideNative_ = function overrideNative_(type, override) { 22521 var _this2 = this; 22522 22523 // If there is no behavioral change don't add/remove listeners 22524 if (override !== this["featuresNative" + type + "Tracks"]) { 22525 return; 22526 } 22527 22528 var lowerCaseType = type.toLowerCase(); 22529 22530 if (this[lowerCaseType + "TracksListeners_"]) { 22531 Object.keys(this[lowerCaseType + "TracksListeners_"]).forEac
22531h(function (eventName) { 22532 var elTracks = _this2.el()[lowerCaseType + "Tracks"]; 22533 22534 elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]); 22535 }); 22536 } 22537 22538 this["featuresNative" + type + "Tracks"] = !override; 22539 this[lowerCaseType + "TracksListeners_"] = null; 22540 this.proxyNativeTracksForType_(lowerCaseType); 22541 } 22542 /** 22543 * Attempt to force override of native audio tracks. 22544 * 22545 * @param {boolean} override - If set to true native audio will be overridden, 22546 * otherwise native audio will potentially be used. 22547 */ 22548 ; 22549 22550 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) { 22551 this.overrideNative_('Audio', override); 22552 } 22553 /** 22554 * Attempt to force override of native video tracks. 22555 * 22556 * @param {boolean} override - If set to true native video will be overridden, 22557 * otherwise native video will potentially be used. 22558 */ 22559 ; 22560 22561 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) { 22562 this.overrideNative_('Video', override); 22563 } 22564 /** 22565 * Proxy native track list events for the given type to our track 22566 * lists if the browser we are playing in supports that type of track list. 22567 * 22568 * @param {string} name - Track type; values include 'audio', 'video', and 'text' 22569 * @private 22570 */ 22571 ; 22572 22573 _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) { 22574 var _this3 = this; 22575 22576 var props = NORMAL[name]; 22577 var elTracks = this.el()[props.getterName]; 22578 var techTracks = this[props.getterName](); 22579 22580 if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) { 22581 return; 22582 } 22583 22584 var listeners = { 22585 change: function change(e) { 22586 var event = { 22587 type: 'change', 22588 target: techTracks, 22589 currentTarget: techTracks, 22590 srcElement: techTracks 22591 }; 22592 techTracks.trigger(event); // if we are a text track change event, we should also notify the 22593 // remote text track list. This can potentially cause a false positive 22594 // if we were to get a change event on a non-remote track and 22595 // we triggered the event on the remote text track list which doesn't 22596 // contain that track. However, best practices mean looping through the 22597 // list of tracks and searching for the appropriate mode value, so, 22598 // this shouldn't pose an issue 22599 22600 if (name === 'text') { 22601 _this3[REMOTE.remoteText.getterName]().trigger(event); 22602 } 22603 }, 22604 addtrack: function addtrack(e) { 22605 techTracks.addTrack(e.track); 22606 }, 22607 removetrack: function removetrack(e) { 22608 techTracks.removeTrack(e.track); 22609 } 22610 }; 22611 22612 var removeOldTracks = function removeOldTracks() { 22613 var removeTracks = []; 22614 22615 for (var i = 0; i < techTracks.length; i++) { 22616 var found = false; 22617 22618 for (var j = 0; j < elTracks.length; j++) { 22619 if (elTracks[j] === techTracks[i]) { 22620 found = true; 22621 break; 22622 } 22623 } 22624 22625 if (!found) { 22626 removeTracks.push(techTracks[i]); 22627 } 22628 } 22629 22630 while (removeTracks.length) { 22631 techTracks.removeTrack(removeTracks.shift()); 22632 } 22633 }; 22634 22635 this[props.getterName + 'Listeners_'] = listeners; 22636 Object.keys(listeners).forEach(function (eventName) { 22637 var listener = listeners[eventName]; 22638 elTracks.addEventListener(eventName, listener); 22639 22640 _this3.on('dispose', function (e) { 22641 return elTracks.removeEventListener(eventName, listener); 22642 }); 22643 }); // Remove (native) tracks that are not used anymore 22644 22645 this.on('loadstart', removeOldTracks); 22646 this.on('dispose', function (e) { 22647 return _this3.off('loadstart', removeOldTracks); 22648 }); 22649 } 22650 /** 22651 * Proxy all native track list events to our track lists if the browser we are playing 22652 * in supports that type of track list. 22653 * 22654 * @private 22655 */ 22656 ; 22657 22658 _proto.proxyNativeTracks_ = function proxyNativeTracks_() { 22659 var _this4 = this; 22660
22661 NORMAL.names.forEach(function (name) { 22662 _this4.proxyNativeTracksForType_(name); 22663 }); 22664 } 22665 /** 22666 * Create the `Html5` Tech's DOM element. 22667 * 22668 * @return {Element} 22669 * The element that gets created. 22670 */ 22671 ; 22672 22673 _proto.createEl = function createEl() { 22674 var el = this.options_.tag; // Check if this browser supports moving the element into the box. 22675 // On the iPhone video will break if you move the element, 22676 // So we have to create a brand new element. 22677 // If we ingested the player div, we do not need to move the media element. 22678 22679 if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { 22680 // If the original tag is still there, clone and remove it. 22681 if (el) { 22682 var clone = el.cloneNode(true); 22683 22684 if (el.parentNode) { 22685 el.parentNode.insertBefore(clone, el); 22686 } 22687 22688 Html5.disposeMediaElement(el); 22689 el = clone; 22690 } else { 22691 el = document_1.createElement('video'); // determine if native controls should be used 22692 22693 var tagAttributes = this.options_.tag && getAttributes(this.options_.tag); 22694 var attributes = mergeOptions$3({}, tagAttributes); 22695 22696 if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { 22697 delete attributes.controls; 22698 } 22699 22700 setAttributes(el, assign(attributes, { 22701 id: this.options_.techId, 22702 "class": 'vjs-tech' 22703 })); 22704 } 22705 22706 el.playerId = this.options_.playerId; 22707 } 22708 22709 if (typeof this.options_.preload !== 'undefined') { 22710 setAttribute(el, 'preload', this.options_.preload); 22711 } 22712 22713 if (this.options_.disablePictureInPicture !== undefined) { 22714 el.disablePictureInPicture = this.options_.disablePictureInPicture; 22715 } // Update specific tag settings, in case they were overridden 22716 // `autoplay` has to be *last* so that `muted` and `playsinline` are present 22717 // when iOS/Safari or other browsers attempt to autoplay. 22718 22719 22720 var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay']; 22721 22722 for (var i = 0; i < settingsAttrs.length; i++) { 22723 var attr = settingsAttrs[i]; 22724 var value = this.options_[attr]; 22725 22726 if (typeof value !== 'undefined') { 22727 if (value) { 22728 setAttribute(el, attr, attr); 22729 } else { 22730 removeAttribute(el, attr); 22731 } 22732 22733 el[attr] = value; 22734 } 22735 } 22736 22737 return el; 22738 } 22739 /** 22740 * This will be triggered if the loadstart event has already fired, before videojs was 22741 * ready. Two known examples of when this can happen are: 22742 * 1. If we're loading the playback object after it has started loading 22743 * 2. The media is already playing the (often with autoplay on) then 22744 * 22745 * This function will fire another loadstart so that videojs can catchup. 22746 * 22747 * @fires Tech#loadstart 22748 * 22749 * @return {undefined} 22750 * returns nothing. 22751 */ 22752 ; 22753 22754 _proto.handleLateInit_ = function handleLateInit_(el) { 22755 if (el.networkState === 0 || el.networkState === 3) { 22756 // The video element hasn't started loading the source yet 22757 // or didn't find a source 22758 return; 22759 } 22760 22761 if (el.readyState === 0) { 22762 // NetworkState is set synchronously BUT loadstart is fired at the 22763 // end of the current stack, usually before setInterval(fn, 0). 22764 // So at this point we know loadstart may have already fired or is 22765 // about to fire, and either way the player hasn't seen it yet. 22766 // We don't want to fire loadstart prematurely here and cause a 22767 // double loadstart so we'll wait and see if it happens between now 22768 // and the next loop, and fire it if not. 22769 // HOWEVER, we also want to make sure it fires before loadedmetadata 22770 // which could also happen between now and the next loop, so we'll 22771 // watch for that also. 22772 var loadstartFired = false; 22773
22774 var setLoadstartFired = function setLoadstartFired() { 22775 loadstartFired = true; 22776 }; 22777 22778 this.on('loadstart', setLoadstartFired); 22779 22780 var triggerLoadstart = function triggerLoadstart() { 22781 // We did miss the original loadstart. Make sure the player 22782 // sees loadstart before loadedmetadata 22783 if (!loadstartFired) { 22784 this.trigger('loadstart'); 22785 } 22786 }; 22787 22788 this.on('loadedmetadata', triggerLoadstart); 22789 this.ready(function () { 22790 this.off('loadstart', setLoadstartFired); 22791 this.off('loadedmetadata', triggerLoadstart); 22792 22793 if (!loadstartFired) { 22794 // We did miss the original native loadstart. Fire it now. 22795 this.trigger('loadstart'); 22796 } 22797 }); 22798 return; 22799 } // From here on we know that loadstart already fired and we missed it. 22800 // The other readyState events aren't as much of a problem if we double 22801 // them, so not going to go to as much trouble as loadstart to prevent 22802 // that unless we find reason to. 22803 22804 22805 var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater 22806 22807 eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater 22808 22809 if (el.readyState >= 2) { 22810 eventsToTrigger.push('loadeddata'); 22811 } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater 22812 22813 22814 if (el.readyState >= 3) { 22815 eventsToTrigger.push('canplay'); 22816 } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) 22817 22818 22819 if (el.readyState >= 4) { 22820 eventsToTrigger.push('canplaythrough'); 22821 } // We still need to give the player time to add event listeners 22822 22823 22824 this.ready(function () { 22825 eventsToTrigger.forEach(function (type) { 22826 this.trigger(type); 22827 }, this); 22828 }); 22829 } 22830 /** 22831 * Set whether we are scrubbing or not. 22832 * This is used to decide whether we should use `fastSeek` or not. 22833 * `fastSeek` is used to provide trick play on Safari browsers. 22834 * 22835 * @param {boolean} isScrubbing 22836 * - true for we are currently scrubbing 22837 * - false for we are no longer scrubbing 22838 */ 22839 ; 22840 22841 _proto.setScrubbing = function setScrubbing(isScrubbing) { 22842 this.isScrubbing_ = isScrubbing; 22843 } 22844 /** 22845 * Get whether we are scrubbing or not. 22846 * 22847 * @return {boolean} isScrubbing 22848 * - true for we are currently scrubbing 22849 * - false for we are no longer scrubbing 22850 */ 22851 ; 22852 22853 _proto.scrubbing = function scrubbing() { 22854 return this.isScrubbing_; 22855 } 22856 /** 22857 * Set current time for the `HTML5` tech. 22858 * 22859 * @param {number} seconds 22860 * Set the current time of the media to this. 22861 */ 22862 ; 22863 22864 _proto.setCurrentTime = function setCurrentTime(seconds) { 22865 try { 22866 if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) { 22867 this.el_.fastSeek(seconds); 22868 } else { 22869 this.el_.currentTime = seconds; 22870 } 22871 } catch (e) { 22872 log$1(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady); 22873 } 22874 } 22875 /** 22876 * Get the current duration of the HTML5 media element. 22877 * 22878 * @return {number} 22879 * The duration of the media or 0 if there is no duration. 22880 */ 22881 ; 22882 22883 _proto.duration = function duration() { 22884 var _this5 = this; 22885 22886 // Android Chrome will report duration as Infinity for VOD HLS until after 22887 // playback has started, which triggers the live display erroneously. 22888 // Return NaN if playback has not started and trigger a durationupdate once 22889 // the duration can be reliably known. 22890 if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) { 22891 // Wait for the first `timeupdate` with currentTime > 0 - there may be 22892 // several with 0 22893 var checkProgress = function checkProgress() { 22894 if (_this5.el_.currentTime > 0) { 22895 // Trigger durationchange for genuinely live video 22896 if (_this5.el_.duration === Infinity) { 22897 _this5.trigger('durationchange'); 22898 } 22899 22900 _this5.off('timeupdate', checkProgress); 22901 } 22902 }; 22903 22904 this.on('timeupdate', checkProgress); 22905 return NaN; 22906 } 22907 22908 return this.el_.duration || NaN; 22909 } 22910 /** 22911 * Get the current width of the HTML5 media element. 22912 * 22913 * @return {number} 22914 * The width of the HTML5 media element. 22915 */ 22916 ; 22917 22918 _proto.width = function width() { 22919 return this.el_.offsetWidth; 22920 } 22921 /** 22922 * Get the current height of the HTML5 media element. 22923 * 22924 * @return {number} 22925 * The height of the HTML5 media element. 22926 */ 22927 ; 22928 22929 _proto.height = function height() { 22930 return this.el_.offsetHeight; 22931 } 22932 /** 22933 * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into 22934 * `fullscreenchange` event. 22935 * 22936 * @private 22937 * @fires fullscreenchange 22938 * @listens webkitendfullscreen 22939 * @listens webkitbeginfullscreen 22940 * @listens webkitbeginfullscreen 22941 */ 22942 ; 22943 22944 _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { 22945 var _this6 = this; 22946 22947 if (!('webkitDisplayingFullscreen' in this.el_)) { 22948 return; 22949 } 22950 22951 var endFn = function endFn() { 22952 this.trigger('fullscreenchange', { 22953 isFullscreen: false 22954 }); 22955 }; 22956 22957 var beginFn = function beginFn() { 22958 if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') { 22959 this.one('webkitendfullscreen', endFn); 22960 this.trigger('fullscreenchange', { 22961 isFullscreen: true, 22962 // set a flag in case another tech triggers fullscreenchange 22963 nativeIOSFullscreen: true 22964 }); 22965 } 22966 }; 22967 22968 this.on('webkitbeginfullscreen', beginFn); 22969 this.on('dispose', function () { 22970 _this6.off('webkitbeginfullscreen', beginFn); 22971 22972 _this6.off('webkitendfullscreen', endFn); 22973 }); 22974 } 22975 /** 22976 * Check if fullscreen is supported on the current playback device. 22977 * 22978 * @return {boolean} 22979 * - True if fullscreen is supported. 22980 * - False if fullscreen is not supported. 22981 */ 22982 ; 22983 22984 _proto.supportsFullScreen = function supportsFullScreen() { 22985 if (typeof this.el_.webkitEnterFullScreen === 'function') { 22986 var userAgent = window_1.navigator && window_1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard 22987 22988 if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { 22989 return true; 22990 } 22991 } 22992 22993 return false; 22994 } 22995 /** 22996 * Request that the `HTML5` Tech enter fullscreen. 22997 */ 22998 ; 22999 23000 _proto.enterFullScreen = function enterFullScreen() { 23001 var video = this.el_; 23002 23003 if (video.paused && video.networkState <= video.HAVE_METADATA) { 23004 // attempt to prime the video element for programmatic access 23005 // this isn't necessary on the desktop but shouldn't hurt 23006 silencePromise(this.el_.play()); // playing and pausing synchronously during the transition to fullscreen 23007 // can get iOS ~6.1 devices into a play/pause loop 23008 23009 this.setTimeout(function () { 23010 video.pause(); 23011 23012 try { 23013 video.webkitEnterFullScreen(); 23014 } catch (e) { 23015 this.trigger('fullscreenerror', e); 23016 } 23017 }, 0); 23018 } else { 23019 try { 23020 video.webkitEnterFullScreen(); 23021 } catch (e) { 23022 this.trigger('fullscreenerror', e); 23023 } 23024 } 23025 } 23026 /** 23027 * Request that the `HTML5` Tech exit fullscreen. 23028 */ 23029 ; 23030 23031 _proto.exitFullScreen = function exitFullScreen() { 23032 if (!this.el_.webkitDisplayingFullscreen) { 23033 this.trigger('fullscreenerror', new Error('The video is not fullscreen')); 23034 return; 23035 } 23036 23037 this.el_.webkitExitFullScreen(); 23038 } 23039 /** 23040 * Create a floating video window always on top of other windows so that users may 23041 * continue consuming media while they interact with other content sites, or 23042 * applications on their device. 23043 * 23044 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 23045 * 23046 * @return {Promise} 23047 * A promise with a Picture-in-Picture window. 23048 */ 23049 ; 23050 23051 _proto.requestPictureInPicture = function requestPictureInPicture() { 23052 return this.el_.requestPictureInPicture(); 23053 } 23054 /** 23055 * A getter/setter for the `Html5` Tech's source object. 23056 * > Note: Please use {@link Html5#setSource} 23057 * 23058 * @param {Tech~SourceObject} [src] 23059 * The source object you want to set on the `HTML5` techs element. 23060 * 23061 * @return {Tech~SourceObject|undefined} 23062 * - The current source object when a source is not passed in. 23063 * - undefined when setting 23064 * 23065 * @deprecated Since version 5. 23066 */ 23067 ; 23068 23069 _proto.src = function src(_src) { 23070 if (_src === undefined) { 23071 return this.el_.src; 23072 } // Setting src through `src` instead of `setSrc` will be deprecated 23073 23074 23075 this.setSrc(_src); 23076 } 23077 /** 23078 * Reset the tech by removing all sources and then calling 23079 * {@link Html5.resetMediaElement}. 23080 */ 23081 ; 23082 23083 _proto.reset = function reset() { 23084 Html5.resetMediaElement(this.el_); 23085 } 23086 /** 23087 * Get the current source on the `HTML5` Tech. Falls back to returning the source from 23088 * the HTML5 media element. 23089 * 23090 * @return {Tech~SourceObject} 23091 * The current source object from the HTML5 tech. With a fallback to the 23092 * elements source. 23093 */ 23094 ; 23095 23096 _proto.currentSrc = function currentSrc() { 23097 if (this.currentSource_) { 23098 return this.currentSource_.src; 23099 } 23100 23101 return this.el_.currentSrc; 23102 } 23103 /** 23104 * Set controls attribute for the HTML5 media Element. 23105 * 23106 * @param {string} val 23107 * Value to set the controls attribute to 23108 */ 23109 ; 23110 23111 _proto.setControls = function setControls(val) { 23112 this.el_.controls = !!val; 23113 } 23114 /** 23115 * Create and returns a remote {@link TextTrack} object. 23116 * 23117 * @param {string} kind 23118 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 23119 * 23120 * @param {string} [label] 23121 * Label to identify the text track 23122 * 23123 * @param {string} [language] 23124 * Two letter language abbreviation 23125 * 23126 * @return {TextTrack} 23127 * The TextTrack that gets created. 23128 */ 23129 ; 23130 23131 _proto.addTextTrack = function addTextTrack(kind, label, language) { 23132 if (!this.featuresNativeTextTracks) { 23133 return _Tech.prototype.addTextTrack.call(this, kind, label, language); 23134 } 23135 23136 return this.el_.addTextTrack(kind, label, language); 23137 } 23138 /** 23139 * Creates either native TextTrack or an emulated TextTrack depending 23140 * on the value of `featuresNativeTextTracks` 23141 * 23142 * @param {Object} options 23143 * The object should contain the options to initialize the TextTrack with. 23144 * 23145 * @param {string} [options.kind] 23146 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 23147 * 23148 * @param {string} [options.label] 23149 * Label to identify the text track 23150 * 23151 * @param {string} [options.language] 23152 * Two letter language abbreviation. 23153 * 23154 * @param {boolean} [options.default] 23155 * Default this track to on. 23156 * 23157 * @param {string} [options.id] 23158 * The internal id to assign this track. 23159 * 23160 * @param {string} [options.src] 23161 * A source url for the track. 23162 * 23163 * @return {HTMLTrackElement} 23164 * The track element that gets created. 23165 */ 23166 ; 23167 23168 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 23169 if (!this.featuresNativeTextTracks) { 23170 return _Tech.prototype.createRemoteTextTrack.call(this, options); 23171 } 23172 23173 var htmlTrackElement = document_1.createElement('track'); 23174 23175 if (options.kind) { 23176 htmlTrackElement.kind = options.kind; 23177 } 23178 23179 if (options.label) { 23180 htmlTrackElement.label = options.label; 23181 } 23182 23183 if (options.language || options.srclang) { 23184 htmlTrackElement.srclang = options.language || options.srclang; 23185 } 23186 23187 if (options["default"]) { 23188 htmlTrackElement["default"] = options["default"]; 23189 } 23190 23191 if (options.id) { 23192 htmlTrackElement.id = options.id; 23193 } 23194 23195 if (options.src) { 23196 htmlTrackElement.src = options.src; 23197 } 23198 23199 return htmlTrackElement; 23200 } 23201 /** 23202 * Creates a remote text track object and returns an html track element. 23203 * 23204 * @param {Object} options The object should contain values for 23205 * kind, language, label, and src (location of the WebVTT file) 23206 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 23207 * automatically removed from the video element whenever the source changes 23208 * @return {HTMLTrackElement} An Html Track Element. 23209 * This can be an emulated {@link HTMLTrackElement} or a native one. 23210 * @deprecated The default value of the "manualCleanup" parameter will default 23211 * to "false" in upcoming versions of Video.js 23212 */ 23213 ; 23214 23215 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 23216 var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); 23217 23218 if (this.featuresNativeTextTracks) { 23219 this.el().appendChild(htmlTrackElement); 23220 } 23221 23222 return htmlTrackElement; 23223 } 23224 /** 23225 * Remove remote `TextTrack` from `TextTrackList` object 23226 * 23227 * @param {TextTrack} track 23228 * `TextTrack` object to remove 23229 */ 23230 ; 23231 23232 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 23233 _Tech.prototype.removeRemoteTextTrack.call(this, track); 23234 23235 if (this.featuresNativeTextTracks) { 23236 var tracks = this.$$('track'); 23237 var i = tracks.length; 23238 23239 while (i--) { 23240 if (track === tracks[i] || track === tracks[i].track) { 23241 this.el().removeChild(tracks[i]); 23242 } 23243 } 23244 } 23245 } 23246 /** 23247 * Gets available media playback quality metrics as specified by the W3C's Media 23248 * Playback Quality API. 23249 * 23250 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 23251 * 23252 * @return {Object} 23253 * An object with supported media playback quality metrics 23254 */ 23255 ; 23256 23257 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 23258 if (typeof this.el().getVideoPlaybackQuality === 'function') { 23259 return this.el().getVideoPlaybackQuality(); 23260 } 23261 23262 var videoPlaybackQuality = {}; 23263 23264 if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { 23265 videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; 23266 videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; 23267 } 23268 23269 if (window_1.performance && typeof window_1.performance.now === 'function') { 23270 videoPlaybackQuality.creationTime = window_1.performance.now(); 23271 } else if (window_1.performance && window_1.performance.timing && typeof window_1.performance.timing.navigationStart === 'number') { 23272 videoPlaybackQuality.creationTime = window_1.Date.now() - window_1.performance.timing.navigationStart; 23273 } 23274 23275 return videoPlaybackQuality;
23276 }; 23277 23278 return Html5; 23279 }(Tech); 23280 /* HTML5 Support Testing ---------------------------------------------------- */ 23281 23282 /** 23283 * Element for testing browser HTML5 media capabilities 23284 * 23285 * @type {Element} 23286 * @constant 23287 * @private 23288 */ 23289 23290 23291 defineLazyProperty(Html5, 'TEST_VID', function () { 23292 if (!isReal()) { 23293 return; 23294 } 23295 23296 var video = document_1.createElement('video'); 23297 var track = document_1.createElement('track'); 23298 track.kind = 'captions'; 23299 track.srclang = 'en'; 23300 track.label = 'English'; 23301 video.appendChild(track); 23302 return video; 23303 }); 23304 /** 23305 * Check if HTML5 media is supported by this browser/device. 23306 * 23307 * @return {boolean} 23308 * - True if HTML5 media is supported. 23309 * - False if HTML5 media is not supported. 23310 */ 23311 23312 Html5.isSupported = function () { 23313 // IE with no Media Player is a LIAR! (#984) 23314 try { 23315 Html5.TEST_VID.volume = 0.5; 23316 } catch (e) { 23317 return false; 23318 } 23319 23320 return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); 23321 }; 23322 /** 23323 * Check if the tech can support the given type 23324 * 23325 * @param {string} type 23326 * The mimetype to check 23327 * @return {string} 'probably', 'maybe', or '' (empty string) 23328 */ 23329 23330 23331 Html5.canPlayType = function (type) { 23332 return Html5.TEST_VID.canPlayType(type); 23333 }; 23334 /** 23335 * Check if the tech can support the given source 23336 * 23337 * @param {Object} srcObj 23338 * The source object 23339 * @param {Object} options 23340 * The options passed to the tech 23341 * @return {string} 'probably', 'maybe', or '' (empty string) 23342 */ 23343 23344 23345 Html5.canPlaySource = function (srcObj, options) { 23346 return Html5.canPlayType(srcObj.type); 23347 }; 23348 /** 23349 * Check if the volume can be changed in this browser/device. 23350 * Volume cannot be changed in a lot of mobile devices. 23351 * Specifically, it can't be changed from 1 on iOS. 23352 * 23353 * @return {boolean} 23354 * - True if volume can be controlled 23355 * - False otherwise 23356 */ 23357 23358 23359 Html5.canControlVolume = function () { 23360 // IE will error if Windows Media Player not installed #3315 23361 try { 23362 var volume = Html5.TEST_VID.volume; 23363 Html5.TEST_VID.volume = volume / 2 + 0.1; 23364 return volume !== Html5.TEST_VID.volume; 23365 } catch (e) { 23366 return false; 23367 } 23368 }; 23369 /** 23370 * Check if the volume can be muted in this browser/device. 23371 * Some devices, e.g. iOS, don't allow changing volume 23372 * but permits muting/unmuting. 23373 * 23374 * @return {bolean} 23375 * - True if volume can be muted 23376 * - False otherwise 23377 */ 23378 23379 23380 Html5.canMuteVolume = function () { 23381 try { 23382 var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always 23383 // work, so we want to set both property and attribute 23384 23385 Html5.TEST_VID.muted = !muted; 23386 23387 if (Html5.TEST_VID.muted) { 23388 setAttribute(Html5.TEST_VID, 'muted', 'muted'); 23389 } else { 23390 removeAttribute(Html5.TEST_VID, 'muted', 'muted'); 23391 } 23392 23393 return muted !== Html5.TEST_VID.muted; 23394 } catch (e) { 23395 return false; 23396 } 23397 }; 23398 /** 23399 * Check if the playback rate can be changed in this browser/device. 23400 * 23401 * @return {boolean} 23402 * - True if playback rate can be controlled 23403 * - False otherwise 23404 */ 23405 23406 23407 Html5.canControlPlaybackRate = function () { 23408 // Playback rate API is implemented in Android Chrome, but doesn't do anything 23409 // https://github.com/videojs/video.js/issues/3180 23410 if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) { 23411 return false; 23412 } // IE will error if Windows Media Player not installed #3315 23413 23414 23415 try { 23416 var playbackRate = Html5.TEST_VID.playbackRate; 23417 Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; 23418 return playbackRate !== Html5.TEST_VID.playbackRate; 23419 } catch (e) { 23420 return false; 23421 } 23422 }; 23423 /** 23424 * Check if we can override a video/audio elements attributes, with 23425 * Object.defineProperty. 23426 * 23427 * @return {boolean} 23428 * - True if builtin attributes can be overridden 23429 * - False otherwise 23430 */ 23431 23432 23433 Html5.canOverrideAttributes = function () {
23434 // if we cannot overwrite the src/innerHTML property, there is no support 23435 // iOS 7 safari for instance cannot do this. 23436 try { 23437 var noop = function noop() {}; 23438 23439 Object.defineProperty(document_1.createElement('video'), 'src', { 23440 get: noop, 23441 set: noop 23442 }); 23443 Object.defineProperty(document_1.createElement('audio'), 'src', { 23444 get: noop, 23445 set: noop 23446 }); 23447 Object.defineProperty(document_1.createElement('video'), 'innerHTML', { 23448 get: noop, 23449 set: noop 23450 }); 23451 Object.defineProperty(document_1.createElement('audio'), 'innerHTML', { 23452 get: noop, 23453 set: noop 23454 }); 23455 } catch (e) { 23456 return false; 23457 } 23458 23459 return true; 23460 }; 23461 /** 23462 * Check to see if native `TextTrack`s are supported by this browser/device. 23463 * 23464 * @return {boolean} 23465 * - True if native `TextTrack`s are supported. 23466 * - False otherwise 23467 */ 23468 23469 23470 Html5.supportsNativeTextTracks = function () { 23471 return IS_ANY_SAFARI || IS_IOS && IS_CHROME; 23472 }; 23473 /** 23474 * Check to see if native `VideoTrack`s are supported by this browser/device 23475 * 23476 * @return {boolean} 23477 * - True if native `VideoTrack`s are supported. 23478 * - False otherwise 23479 */ 23480 23481 23482 Html5.supportsNativeVideoTracks = function () { 23483 return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); 23484 }; 23485 /** 23486 * Check to see if native `AudioTrack`s are supported by this browser/device 23487 * 23488 * @return {boolean} 23489 * - True if native `AudioTrack`s are supported. 23490 * - False otherwise 23491 */ 23492 23493 23494 Html5.supportsNativeAudioTracks = function () { 23495 return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); 23496 }; 23497 /** 23498 * An array of events available on the Html5 tech. 23499 * 23500 * @private 23501 * @type {Array} 23502 */ 23503 23504 23505 Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange']; 23506 /** 23507 * Boolean indicating whether the `Tech` supports volume control. 23508 * 23509 * @type {boolean} 23510 * @default {@link Html5.canControlVolume} 23511 */ 23512 23513 /** 23514 * Boolean indicating whether the `Tech` supports muting volume. 23515 * 23516 * @type {bolean} 23517 * @default {@link Html5.canMuteVolume} 23518 */ 23519 23520 /** 23521 * Boolean indicating whether the `Tech` supports changing the speed at which the media 23522 * plays. Examples: 23523 * - Set player to play 2x (twice) as fast 23524 * - Set player to play 0.5x (half) as fast 23525 * 23526 * @type {boolean} 23527 * @default {@link Html5.canControlPlaybackRate} 23528 */ 23529 23530 /** 23531 * Boolean indicating whether the `Tech` supports the `sourceset` event. 23532 * 23533 * @type {boolean} 23534 * @default 23535 */ 23536 23537 /** 23538 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. 23539 * 23540 * @type {boolean} 23541 * @default {@link Html5.supportsNativeTextTracks} 23542 */ 23543 23544 /** 23545 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. 23546 * 23547 * @type {boolean} 23548 * @default {@link Html5.supportsNativeVideoTracks} 23549 */ 23550 23551 /** 23552 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. 23553 * 23554 * @type {boolean} 23555 * @default {@link Html5.supportsNativeAudioTracks} 23556 */ 23557 23558 [['featuresVolumeControl', 'canControlVolume'], ['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) { 23559 var key = _ref[0], 23560 fn = _ref[1]; 23561 defineLazyProperty(Html5.prototype, key, function () { 23562 return Html5[fn](); 23563 }, true); 23564 }); 23565 /** 23566 * Boolean indicating whether the `HTML5` tech currently supports the media element 23567 * moving in the DOM. iOS breaks if you move the media element, so this is set this to 23568 * false there. Everywhere else this should be true. 23569 * 23570 * @type {boolean} 23571 * @default 23572 */ 23573 23574 Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed. 23575 // Is this true? 23576 23577 /** 23578 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize 23579 * when going into fullscreen. 23580 * 23581 * @type {boolean} 23582 * @default 23583 */ 23584 23585 Html5.prototype.featuresFullscreenResize = true; 23586 /** 23587 * Boolean indicating whether the `HTML5` tech currently supports the progress event. 23588 * If this is false, manual `progress` events will be triggered instead. 23589 * 23590 * @type {boolean} 23591 * @default 23592 */ 23593 23594 Html5.prototype.featuresProgressEvents = true; 23595 /** 23596 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. 23597 * If this is false, manual `timeupdate` events will be triggered instead. 23598 * 23599 * @default 23600 */ 23601 23602 Html5.prototype.featuresTimeupdateEvents = true; // HTML5 Feature detection and Device Fixes --------------------------------- // 23603 23604 var canPlayType; 23605 23606 Html5.patchCanPlayType = function () { 23607 // Android 4.0 and above can play HLS to some extent but it reports being unable to do so 23608 // Firefox and Chrome report correctly 23609 if (ANDROID_VERSION >
vendor: 14,852 bytes, lines 23609-24001
23609= 4.0 && !IS_FIREFOX && !IS_CHROME) { 23610 canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; 23611 23612 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 23613 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 23614 23615 if (type && mpegurlRE.test(type)) { 23616 return 'maybe'; 23617 } 23618 23619 return canPlayType.call(this, type); 23620 }; 23621 } 23622 }; 23623 23624 Html5.unpatchCanPlayType = function () { 23625 var r = Html5.TEST_VID.constructor.prototype.canPlayType; 23626 23627 if (canPlayType) { 23628 Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; 23629 } 23630 23631 return r; 23632 }; // by default, patch the media element 23633 23634 23635 Html5.patchCanPlayType(); 23636 23637 Html5.disposeMediaElement = function (el) { 23638 if (!el) { 23639 return; 23640 } 23641 23642 if (el.parentNode) { 23643 el.parentNode.removeChild(el); 23644 } // remove any child track or source nodes to prevent their loading 23645 23646 23647 while (el.hasChildNodes()) { 23648 el.removeChild(el.firstChild); 23649 } // remove any src reference. not setting `src=''` because that causes a warning 23650 // in firefox 23651 23652 23653 el.removeAttribute('src'); // force the media element to update its loading state by calling load() 23654 // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) 23655 23656 if (typeof el.load === 'function') { 23657 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 23658 (function () { 23659 try { 23660 el.load(); 23661 } catch (e) {// not supported 23662 } 23663 })(); 23664 } 23665 }; 23666 23667 Html5.resetMediaElement = function (el) { 23668 if (!el) { 23669 return; 23670 } 23671 23672 var sources = el.querySelectorAll('source'); 23673 var i = sources.length; 23674 23675 while (i--) { 23676 el.removeChild(sources[i]); 23677 } // remove any src reference. 23678 // not setting `src=''` because that throws an error 23679 23680 23681 el.removeAttribute('src'); 23682 23683 if (typeof el.load === 'function') { 23684 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 23685 (function () { 23686 try { 23687 el.load(); 23688 } catch (e) {// satisfy linter 23689 } 23690 })(); 23691 } 23692 }; 23693 /* Native HTML5 element property wrapping ----------------------------------- */ 23694 // Wrap native boolean attributes with getters that check both property and attribute 23695 // The list is as followed: 23696 // muted, defaultMuted, autoplay, controls, loop, playsinline 23697 23698 23699 [ 23700 /** 23701 * Get the value of `muted` from the media element. `muted` indicates 23702 * that the volume for the media should be set to silent. This does not actually change 23703 * the `volume` attribute. 23704 * 23705 * @method Html5#muted 23706 * @return {boolean} 23707 * - True if the value of `volume` should be ignored and the audio set to silent. 23708 * - False if the value of `volume` should be used. 23709 * 23710 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 23711 */ 23712 'muted', 23713 /** 23714 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates 23715 * whether the media should start muted or not. Only changes the default state of the 23716 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the 23717 * current state. 23718 * 23719 * @method Html5#defaultMuted 23720 * @return {boolean} 23721 * - The value of `defaultMuted` from the media element. 23722 * - True indicates that the media should start muted. 23723 * - False indicates that the media should not start muted 23724 * 23725 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 23726 */ 23727 'defaultMuted', 23728 /** 23729 * Get the value of `autoplay` from the media element. `autoplay` indicates 23730 * that the media should start to play as soon as the page is ready. 23731 * 23732 * @method Html5#autoplay 23733 * @return {boolean} 23734 * - The value of `autoplay` from the media element. 23735 * - True indicates that the media should start as soon as the page loads. 23736 * - False indicates that the media should not start as soon as the page loads. 23737 * 23738 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 23739 */ 23740 'autoplay', 23741 /** 23742 * Get the value of `controls` from the media element. `controls` indicates 23743 * whether the native media controls should be shown or hidden. 23744 * 23745 * @method Html5#controls 23746 * @return {boolean} 23747 * - The value of `controls` from the media element. 23748 * - True indicates that native controls should be showing. 23749 * - False indicates that native controls should be hidden. 23750 * 23751 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} 23752 */ 23753 'controls', 23754 /** 23755 * Get the value of `loop` from the media element. `loop` indicates 23756 * that the media should return to the start of the media and continue playing once 23757 * it reaches the end. 23758 * 23759 * @method Html5#loop 23760 * @return {boolean} 23761 * - The value of `loop` from the media element. 23762 * - True indicates that playback should seek back to start once 23763 * the end of a media is reached. 23764 * - False indicates that playback should not loop back to the start when the 23765 * end of the media is reached. 23766 * 23767 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 23768 */ 23769 'loop', 23770 /** 23771 * Get the value of `playsinline` from the media element. `playsinline` indicates 23772 * to the browser that non-fullscreen playback is preferred when fullscreen 23773 * playback is the native default, such as in iOS Safari. 23774 * 23775 * @method Html5#playsinline 23776 * @return {boolean} 23777 * - The value of `playsinline` from the media element. 23778 * - True indicates that the media should play inline. 23779 * - False indicates that the media should not play inline. 23780 * 23781 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 23782 */ 23783 'playsinline'].forEach(function (prop) { 23784 Html5.prototype[prop] = function () { 23785 return this.el_[prop] || this.el_.hasAttribute(prop); 23786 }; 23787 }); // Wrap native boolean attributes with setters that set both property and attribute 23788 // The list is as followed: 23789 // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline 23790 // setControls is special-cased above 23791 23792 [ 23793 /** 23794 * Set the value of `muted` on the media element. `muted` indicates that the current 23795 * audio level should be silent. 23796 * 23797 * @method Html5#setMuted 23798 * @param {boolean} muted 23799 * - True if the audio should be set to silent 23800 * - False otherwise 23801 * 23802 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 23803 */ 23804 'muted', 23805 /** 23806 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current 23807 * audio level should be silent, but will only effect the muted level on initial playback.. 23808 * 23809 * @method Html5.prototype.setDefaultMuted 23810 * @param {boolean} defaultMuted 23811 * - True if the audio should be set to silent 23812 * - False otherwise 23813 * 23814 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 23815 */ 23816 'defaultMuted', 23817 /** 23818 * Set the value of `autoplay` on the media element. `autoplay` indicates 23819 * that the media should start to play as soon as the page is ready. 23820 * 23821 * @method Html5#setAutoplay 23822 * @param {boolean} autoplay 23823 * - True indicates that the media should start as soon as the page loads. 23824 * - False indicates that the media should not start as soon as the page loads. 23825 * 23826 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 23827 */ 23828 'autoplay', 23829 /** 23830 * Set the value of `loop` on the media element. `loop` indicates 23831 * that the media should return to the start of the media and continue playing once 23832 * it reaches the end. 23833 * 23834 * @method Html5#setLoop 23835 * @param {boolean} loop 23836 * - True indicates that playback should seek back to start once 23837 * the end of a media is reached. 23838 * - False indicates that playback should not loop back to the start when the 23839 * end of the media is reached. 23840 * 23841 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 23842 */ 23843 'loop', 23844 /** 23845 * Set the value of `playsinline` from the media element. `playsinline` indicates 23846 * to the browser that non-fullscreen playback is preferred when fullscreen 23847 * playback is the native default, such as in iOS Safari. 23848 * 23849 * @method Html5#setPlaysinline 23850 * @param {boolean} playsinline 23851 * - True indicates that the media should play inline. 23852 * - False indicates that the media should not play inline. 23853 * 23854 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 23855 */ 23856 'playsinline'].forEach(function (prop) { 23857 Html5.prototype['set' + toTitleCase$1(prop)] = function (v) { 23858 this.el_[prop] = v; 23859 23860 if (v) { 23861 this.el_.setAttribute(prop, prop); 23862 } else { 23863 this.el_.removeAttribute(prop); 23864 } 23865 }; 23866 }); // Wrap native properties with a getter 23867 // The list is as followed 23868 // paused, currentTime, buffered, volume, poster, preload, error, seeking 23869 // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture 23870 // played, networkState, readyState, videoWidth, videoHeight, crossOrigin 23871 23872 [ 23873 /** 23874 * Get the value of `paused` from the media element. `paused` indicates whether the media element 23875 * is currently paused or not. 23876 * 23877 * @method Html5#paused 23878 * @return {boolean} 23879 * The value of `paused` from the media element. 23880 * 23881 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} 23882 */ 23883 'paused', 23884 /** 23885 * Get the value of `currentTime` from the media element. `currentTime` indicates 23886 * the current second that the media is at in playback. 23887 * 23888 * @method Html5#currentTime 23889 * @return {number} 23890 * The value of `currentTime` from the media element. 23891 * 23892 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} 23893 */ 23894 'currentTime', 23895 /** 23896 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` 23897 * object that represents the parts of the media that are already downloaded and 23898 * available for playback. 23899 * 23900 * @method Html5#buffered 23901 * @return {TimeRange} 23902 * The value of `buffered` from the media element. 23903 * 23904 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} 23905 */ 23906 'buffered', 23907 /** 23908 * Get the value of `volume` from the media element. `volume` indicates 23909 * the current playback volume of audio for a media. `volume` will be a value from 0 23910 * (silent) to 1 (loudest and default). 23911 * 23912 * @method Html5#volume 23913 * @return {number} 23914 * The value of `volume` from the media element. Value will be between 0-1. 23915 * 23916 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 23917 */ 23918 'volume', 23919 /** 23920 * Get the value of `poster` from the media element. `poster` indicates 23921 * that the url of an image file that can/will be shown when no media data is available. 23922 * 23923 * @method Html5#poster 23924 * @return {string} 23925 * The value of `poster` from the media element. Value will be a url to an 23926 * image. 23927 * 23928 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} 23929 */ 23930 'poster', 23931 /** 23932 * Get the value of `preload` from the media element. `preload` indicates 23933 * what should download before the media is interacted with. It can have the following 23934 * values: 23935 * - none: nothing should be downloaded 23936 * - metadata: poster and the first few frames of the media may be downloaded to get 23937 * media dimensions and other metadata 23938 * - auto: allow the media and metadata for the media to be downloaded before 23939 * interaction 23940 * 23941 * @method Html5#preload 23942 * @return {string} 23943 * The value of `preload` from the media element. Will be 'none', 'metadata', 23944 * or 'auto'. 23945 * 23946 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 23947 */ 23948 'preload', 23949 /** 23950 * Get the value of the `error` from the media element. `error` indicates any 23951 * MediaError that may have occurred during playback. If error returns null there is no 23952 * current error. 23953 * 23954 * @method Html5#error 23955 * @return {MediaError|null} 23956 * The value of `error` from the media element. Will be `MediaError` if there 23957 * is a current error and null otherwise. 23958 * 23959 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} 23960 */ 23961 'error', 23962 /** 23963 * Get the value of `seeking` from the media element. `seeking` indicates whether the 23964 * media is currently seeking to a new position or not. 23965 * 23966 * @method Html5#seeking 23967 * @return {boolean} 23968 * - The value of `seeking` from the media element. 23969 * - True indicates that the media is currently seeking to a new position. 23970 * - False indicates that the media is not seeking to a new position at this time. 23971 * 23972 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} 23973 */ 23974 'seeking', 23975 /** 23976 * Get the value of `seekable` from the media element. `seekable` returns a 23977 * `TimeRange` object indicating ranges of time that can currently be `seeked` to. 23978 * 23979 * @method Html5#seekable 23980 * @return {TimeRange} 23981 * The value of `seekable` from the media element. A `TimeRange` object 23982 * indicating the current ranges of time that can be seeked to. 23983 * 23984 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} 23985 */ 23986 'seekable', 23987 /** 23988 * Get the value of `ended` from the media element. `ended` indicates whether 23989 * the media has reached the end or not. 23990 * 23991 * @method Html5#ended 23992 * @return {boolean} 23993 * - The value of `ended` from the media element. 23994 * - True indicates that the media has ended. 23995 * - False indicates that the media has not ended. 23996 * 23997 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} 23998 */ 23999 'ended', 24000 /** 24001 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
24002 * the rate at which the media is currently playing back. Examples: 24003 * - if playbackRate is set to 2, media will play twice as fast. 24004 * - if playbackRate is set to 0.5, media will play half as fast. 24005 * 24006 * @method Html5#playbackRate 24007 * @return {number} 24008 * The value of `playbackRate` from the media element. A number indicating 24009 * the current playback speed of the media, where 1 is normal speed. 24010 * 24011 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 24012 */ 24013 'playbackRate', 24014 /** 24015 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates 24016 * the rate at which the media is currently playing back. This value will not indicate the current 24017 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. 24018 * 24019 * Examples: 24020 * - if defaultPlaybackRate is set to 2, media will play twice as fast. 24021 * - if defaultPlaybackRate is set to 0.5, media will play half as fast. 24022 * 24023 * @method Html5.prototype.defaultPlaybackRate 24024 * @return {number} 24025 * The value of `defaultPlaybackRate` from the media element. A number indicating 24026 * the current playback speed of the media, where 1 is normal speed. 24027 * 24028 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 24029 */ 24030 'defaultPlaybackRate', 24031 /** 24032 * Get the value of 'disablePictureInPicture' from the video element. 24033 * 24034 * @method Html5#disablePictureInPicture 24035 * @return {boolean} value 24036 * - The value of `disablePictureInPicture` from the video element. 24037 * - True indicates that the video can't be played in Picture-In-Picture mode 24038 * - False indicates that the video can be played in Picture-In-Picture mode 24039 * 24040 * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip} 24041 */ 24042 'disablePictureInPicture', 24043 /** 24044 * Get the value of `played` from the media element. `played` returns a `TimeRange` 24045 * object representing points in the media timeline that have been played. 24046 * 24047 * @method Html5#played 24048 * @return {TimeRange} 24049 * The value of `played` from the media element. A `TimeRange` object indicating 24050 * the ranges of time that have been played. 24051 * 24052 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} 24053 */ 24054 'played', 24055 /** 24056 * Get the value of `networkState` from the media element. `networkState` indicates 24057 * the current network state. It returns an enumeration from the following list: 24058 * - 0: NETWORK_EMPTY 24059 * - 1: NETWORK_IDLE 24060 * - 2: NETWORK_LOADING 24061 * - 3: NETWORK_NO_SOURCE 24062 * 24063 * @method Html5#networkState 24064 * @return {number} 24065 * The value of `networkState` from the media element. This will be a number 24066 * from the list in the description. 24067 * 24068 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} 24069 */ 24070 'networkState', 24071 /** 24072 * Get the value of `readyState` from the media element. `readyState` indicates 24073 * the current state of the media element. It returns an enumeration from the 24074 * following list: 24075 * - 0: HAVE_NOTHING 24076 * - 1: HAVE_METADATA 24077 * - 2: HAVE_CURRENT_DATA 24078 * - 3: HAVE_FUTURE_DATA 24079 * - 4: HAVE_ENOUGH_DATA 24080 * 24081 * @method Html5#readyState 24082 * @return {number} 24083 * The value of `readyState` from the media element. This will be a number 24084 * from the list in the description. 24085 * 24086 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} 24087 */ 24088 'readyState', 24089 /** 24090 * Get the value of `videoWidth` from the video element. `videoWidth` indicates 24091 * the current width of the video in css pixels. 24092 * 24093 * @method Html5#videoWidth 24094 * @return {number} 24095 * The value of `videoWidth` from the video element. This will be a number 24096 * in css pixels. 24097 * 24098 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 24099 */ 24100 'videoWidth', 24101 /** 24102 * Get the value of `videoHeight` from the video element. `videoHeight` indicates 24103 * the current height of the video in css pixels. 24104 * 24105 * @method Html5#videoHeight 24106 * @return {number} 24107 * The value of `videoHeight` from the video element. This will be a number 24108 * in css pixels. 24109 * 24110 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 24111 */ 24112 'videoHeight', 24113 /** 24114 * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates 24115 * to the browser that should sent the cookies along with the requests for the 24116 * different assets/playlists 24117 * 24118 * @method Html5#crossOrigin 24119 * @return {string} 24120 * - anonymous indicates that the media should not sent cookies. 24121 * - use-credentials indicates that the media should sent cookies along the requests. 24122 * 24123 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin} 24124 */ 24125 'crossOrigin'].forEach(function (prop) { 24126 Html5.prototype[prop] = function () { 24127 return this.el_[prop]; 24128 }; 24129 }); // Wrap native properties with a setter in this format: 24130 // set + toTitleCase(name) 24131 // The list is as follows: 24132 // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate, 24133 // setDisablePictureInPicture, setCrossOrigin 24134 24135 [ 24136 /** 24137 * Set the value of `volume` on the media element. `volume` indicates the current 24138 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and 24139 * so on. 24140 * 24141 * @method Html5#setVolume 24142 * @param {number} percentAsDecimal 24143 * The volume percent as a decimal. Valid range is from 0-1. 24144 * 24145 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 24146 */ 24147 'volume', 24148 /** 24149 * Set the value of `src` on the media element. `src` indicates the current 24150 * {@link Tech~SourceObject} for the media. 24151 * 24152 * @method Html5#setSrc 24153 * @param {Tech~SourceObject} src 24154 * The source object to set as the current source. 24155 * 24156 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} 24157 */ 24158 'src', 24159 /** 24160 * Set the value of `poster` on the media element. `poster` is the url to 24161 * an image file that can/will be shown when no media data is available. 24162 * 24163 * @method Html5#setPoster 24164 * @param {string} poster 24165 * The url to an image that should be used as the `poster` for the media 24166 * element. 24167 * 24168 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} 24169 */ 24170 'poster', 24171 /** 24172 * Set the value of `preload` on the media element. `preload` indicates 24173 * what should download before the media is interacted with. It can have the following 24174 * values: 24175 * - none: nothing should be downloaded 24176 * - metadata: poster and the first few frames of the media may be downloaded to get 24177 * media dimensions and other metadata 24178 * - auto: allow the media and metadata for the media to be downloaded before 24179 * interaction 24180 * 24181 * @method Html5#setPreload 24182 * @param {string} preload 24183 * The value of `preload` to set on the media element. Must be 'none', 'metadata', 24184 * or 'auto'. 24185 * 24186 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 24187 */ 24188 'preload', 24189 /** 24190 * Set the value of `playbackRate` on the media element. `playbackRate` indicates 24191 * the rate at which the media should play back. Examples: 24192 * - if playbackRate is set to 2, media will play twice as fast. 24193 * - if playbackRate is set to 0.5, media will play half as fast. 24194 * 24195 * @method Html5#setPlaybackRate 24196 * @return {number} 24197 * The value of `playbackRate` from the media element. A number indicating 24198 * the current playback speed of the media, where 1 is normal speed. 24199 * 24200 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 24201 */ 24202 'playbackRate', 24203 /** 24204 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates 24205 * the rate at which the media should play back upon initial startup. Changing this value 24206 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. 24207 * 24208 * Example Values: 24209 * - if playbackRate is set to 2, media will play twice as fast. 24210 * - if playbackRate is set to 0.5, media will play half as fast. 24211 * 24212 * @method Html5.prototype.setDefaultPlaybackRate 24213 * @return {number} 24214 * The value of `defaultPlaybackRate` from the media element. A number indicating 24215 * the current playback speed of the media, where 1 is normal speed. 24216 * 24217 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} 24218 */ 24219 'defaultPlaybackRate', 24220 /** 24221 * Prevents the browser from suggesting a Picture-in-Picture context menu 24222 * or to request Picture-in-Picture automatically in some cases. 24223 * 24224 * @method Html5#setDisablePictureInPicture 24225 * @param {boolean} value 24226 * The true value will disable Picture-in-Picture mode. 24227 * 24228 * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip} 24229 */ 24230 'disablePictureInPicture', 24231 /** 24232 * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates 24233 * to the browser that should sent the cookies along with the requests for the 24234 * different assets/playlists 24235 * 24236 * @method Html5#setCrossOrigin 24237 * @param {string} crossOrigin 24238 * - anonymous indicates that the media should not sent cookies. 24239 * - use-credentials indicates that the media should sent cookies along the requests. 24240 * 24241 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin} 24242 */ 24243 'crossOrigin'].forEach(function (prop) { 24244 Html5.prototype['set' + toTitleCase$1(prop)] = function (v) { 24245 this.el_[prop] = v; 24246 }; 24247 }); // wrap native functions with a function 24248 // The list is as follows: 24249 // pause, load, play 24250 24251 [ 24252 /** 24253 * A wrapper around the media elements `pause` function. This will call the `HTML5` 24254 * media elements `pause` function. 24255 * 24256 * @method Html5#pause 24257 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} 24258 */ 24259 'pause', 24260 /** 24261 * A wrapper around the media elements `load` function. This will call the `HTML5`s 24262 * media element `load` function. 24263 * 24264 * @method Html5#load 24265 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} 24266 */ 24267 'load', 24268 /** 24269 * A wrapper around the media elements `play` function. This will call the `HTML5`s 24270 * media element `play` function. 24271 * 24272 * @method Html5#play 24273 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} 24274 */ 24275 'play'].forEach(function (prop) { 24276 Html5.prototype[prop] = function () { 24277 return this.el_[prop](); 24278 }; 24279 }); 24280 Tech.withSourceHandlers(Html5); 24281 /**
24282 * Native source handler for Html5, simply passes the source to the media element. 24283 * 24284 * @property {Tech~SourceObject} source 24285 * The source object 24286 * 24287 * @property {Html5} tech 24288 * The instance of the HTML5 tech. 24289 */ 24290 24291 Html5.nativeSourceHandler = {}; 24292 /** 24293 * Check if the media element can play the given mime type. 24294 * 24295 * @param {string} type 24296 * The mimetype to check 24297 * 24298 * @return {string} 24299 * 'probably', 'maybe', or '' (empty string) 24300 */ 24301 24302 Html5.nativeSourceHandler.canPlayType = function (type) { 24303 // IE without MediaPlayer throws an error (#519) 24304 try { 24305 return Html5.TEST_VID.canPlayType(type); 24306 } catch (e) { 24307 return ''; 24308 } 24309 }; 24310 /** 24311 * Check if the media element can handle a source natively. 24312 * 24313 * @param {Tech~SourceObject} source 24314 * The source object 24315 * 24316 * @param {Object} [options] 24317 * Options to be passed to the tech. 24318 * 24319 * @return {string} 24320 * 'probably', 'maybe', or '' (empty string). 24321 */ 24322 24323 24324 Html5.nativeSourceHandler.canHandleSource = function (source, options) { 24325 // If a type was provided we should rely on that 24326 if (source.type) { 24327 return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]' 24328 } else if (source.src) { 24329 var ext = getFileExtension(source.src); 24330 return Html5.nativeSourceHandler.canPlayType("video/" + ext); 24331 } 24332 24333 return ''; 24334 }; 24335 /** 24336 * Pass the source to the native media element. 24337 * 24338 * @param {Tech~SourceObject} source 24339 * The source object 24340 * 24341 * @param {Html5} tech 24342 * The instance of the Html5 tech 24343 * 24344 * @param {Object} [options] 24345 * The options to pass to the source 24346 */ 24347 24348 24349 Html5.nativeSourceHandler.handleSource = function (source, tech, options) { 24350 tech.setSrc(source.src); 24351 }; 24352 /** 24353 * A noop for the native dispose function, as cleanup is not needed. 24354 */ 24355 24356 24357 Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler 24358 24359 24360 Html5.registerSourceHandler(Html5.nativeSourceHandler); 24361 Tech.registerTech('Html5', Html5); 24362 24363 // on the player when they happen 24364 24365 var TECH_EVENTS_RETRIGGER = [ 24366 /** 24367 * Fired while the user agent is downloading media data. 24368 * 24369 * @event Player#progress 24370 * @type {EventTarget~Event} 24371 */ 24372 24373 /** 24374 * Retrigger the `progress` event that was triggered by the {@link Tech}. 24375 * 24376 * @private 24377 * @method Player#handleTechProgress_ 24378 * @fires Player#progress 24379 * @listens Tech#progress 24380 */ 24381 'progress', 24382 /** 24383 * Fires when the loading of an audio/video is aborted. 24384 * 24385 * @event Player#abort 24386 * @type {EventTarget~Event} 24387 */ 24388 24389 /** 24390 * Retrigger the `abort` event that was triggered by the {@link Tech}. 24391 * 24392 * @private 24393 * @method Player#handleTechAbort_ 24394 * @fires Player#abort 24395 * @listens Tech#abort 24396 */ 24397 'abort', 24398 /** 24399 * Fires when the browser is intentionally not getting media data. 24400 * 24401 * @event Player#suspend 24402 * @type {EventTarget~Event} 24403 */ 24404 24405 /** 24406 * Retrigger the `suspend` event that was triggered by the {@link Tech}. 24407 * 24408 * @private 24409 * @method Player#handleTechSuspend_ 24410 * @fires Player#suspend 24411 * @listens Tech#suspend 24412 */ 24413 'suspend', 24414 /** 24415 * Fires when the current playlist is empty. 24416 * 24417 * @event Player#emptied 24418 * @type {EventTarget~Event} 24419 */ 24420 24421 /** 24422 * Retrigger the `emptied` event that was triggered by the {@link Tech}. 24423 * 24424 * @private 24425 * @method Player#handleTechEmptied_ 24426 * @fires Player#emptied 24427 * @listens Tech#emptied 24428 */ 24429 'emptied', 24430 /** 24431 * Fires when the browser is trying to get media data, but data is not available. 24432 * 24433 * @event Player#stalled 24434 * @type {EventTarget~Event} 24435 */ 24436 24437 /** 24438 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 24439 * 24440 * @private 24441 * @method Player#handleTechStalled_ 24442 * @fires Player#stalled 24443 * @listens Tech#stalled 24444 */ 24445 'stalled', 24446 /** 24447 * Fires when the browser has loaded meta data for the audio/video. 24448 * 24449 * @event Player#loadedmetadata 24450 * @type {EventTarget~Event} 24451 */ 24452 24453 /** 24454 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 24455 * 24456 * @private 24457 * @method Player#handleTechLoadedmetadata_ 24458 * @fires Player#loadedmetadata 24459 * @listens Tech#loadedmetadata 24460 */ 24461 'loadedmetadata', 24462 /** 24463 * Fires when the browser has loaded the current frame of the audio/video. 24464 * 24465 * @event Player#loadeddata 24466 * @type {event} 24467 */ 24468 24469 /** 24470 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. 24471 * 24472 * @private 24473 * @method Player#handleTechLoaddeddata_ 24474 * @fires Player#loadeddata 24475 * @listens Tech#loadeddata 24476 */ 24477 'loadeddata', 24478 /** 24479 * Fires when the current playback position has changed. 24480 * 24481 * @event Player#timeupdate 24482 * @type {event} 24483 */ 24484 24485 /** 24486 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. 24487 * 24488 * @private 24489 * @method Player#handleTechTimeUpdate_ 24490 * @fires Player#timeupdate 24491 * @listens Tech#timeupdate 24492 */ 24493 'timeupdate', 24494 /** 24495 * Fires when the video's intrinsic dimensions change 24496 * 24497 * @event Player#resize 24498 * @type {event} 24499 */ 24500 24501 /** 24502 * Retrigger the `resize` event that was triggered by the {@link Tech}. 24503 * 24504 * @private 24505 * @method Player#handleTechResize_ 24506 * @fires Player#resize 24507 * @listens Tech#resize 24508 */ 24509 'resize',
24510 /** 24511 * Fires when the volume has been changed 24512 * 24513 * @event Player#volumechange 24514 * @type {event} 24515 */ 24516 24517 /** 24518 * Retrigger the `volumechange` event that was triggered by the {@link Tech}. 24519 * 24520 * @private 24521 * @method Player#handleTechVolumechange_ 24522 * @fires Player#volumechange 24523 * @listens Tech#volumechange 24524 */ 24525 'volumechange', 24526 /** 24527 * Fires when the text track has been changed 24528 * 24529 * @event Player#texttrackchange 24530 * @type {event} 24531 */ 24532 24533 /** 24534 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. 24535 * 24536 * @private 24537 * @method Player#handleTechTexttrackchange_ 24538 * @fires Player#texttrackchange 24539 * @listens Tech#texttrackchange 24540 */ 24541 'texttrackchange']; // events to queue when playback rate is zero 24542 // this is a hash for the sole purpose of mapping non-camel-cased event names 24543 // to camel-cased function names 24544 24545 var TECH_EVENTS_QUEUE = { 24546 canplay: 'CanPlay', 24547 canplaythrough: 'CanPlayThrough', 24548 playing: 'Playing', 24549 seeked: 'Seeked' 24550 }; 24551 var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge']; 24552 var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny 24553 // grep: vjs-layout-x-small 24554 // grep: vjs-layout-small 24555 // grep: vjs-layout-medium 24556 // grep: vjs-layout-large 24557 // grep: vjs-layout-x-large 24558 // grep: vjs-layout-huge 24559 24560 BREAKPOINT_ORDER.forEach(function (k) { 24561 var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k; 24562 BREAKPOINT_CLASSES[k] = "vjs-layout-" + v; 24563 }); 24564 var DEFAULT_BREAKPOINTS = { 24565 tiny: 210, 24566 xsmall: 320, 24567 small: 425, 24568 medium: 768, 24569 large: 1440, 24570 xlarge: 2560, 24571 huge: Infinity 24572 }; 24573 /** 24574 * An instance of the `Player` class is created when any of the Video.js setup methods 24575 * are used to initialize a video. 24576 * 24577 * After an instance has been created it can be accessed globally in two ways: 24578 * 1. By calling `videojs('example_video_1');` 24579 * 2. By using it directly via `videojs.players.example_video_1;` 24580 * 24581 * @extends Component 24582 */ 24583 24584 var Player = /*#__PURE__*/function (_Component) { 24585 inheritsLoose(Player, _Component); 24586 24587 /** 24588 * Create an instance of this class. 24589 * 24590 * @param {Element} tag 24591 * The original video DOM element used for configuring options. 24592 * 24593 * @param {Object} [options] 24594 * Object of option names and values. 24595 * 24596 * @param {Component~ReadyCallback} [ready] 24597 * Ready callback function. 24598 */ 24599 function Player(tag, options, ready) { 24600 var _this; 24601 24602 // Make sure tag ID exists 24603 tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options 24604 // The options argument overrides options set in the video tag 24605 // which overrides globally set options. 24606 // This latter part coincides with the load order 24607 // (tag must exist before Player) 24608 24609 options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up 24610 // player properties first, and can't use `this` before `super()` 24611 24612 options.initChildren = false; // Same with creating the element 24613 24614 options.createEl = false; // don't auto mixin the evented mixin 24615 24616 options.evented = false; // we don't want the player to report touch activity on itself 24617 // see enableTouchActivity in Component 24618 24619 options.reportTouchActivity = false; // If language is not set, get the closest lang attribute 24620 24621 if (!options.language) { 24622 if (typeof tag.closest === 'function') { 24623 var closest = tag.closest('[lang]'); 24624 24625 if (closest && closest.getAttribute) { 24626 options.language = closest.getAttribute('lang'); 24627 } 24628 } else { 24629 var element = tag; 24630 24631 while (element && element.nodeType === 1) { 24632 if (getAttributes(element).hasOwnProperty('lang')) { 24633 options.language = element.getAttribute('lang'); 24634 break; 24635 } 24636 24637 element = element.parentNode; 24638 } 24639 } 24640 } // Run base component initializing with new options 24641 24642 24643 _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners. 24644 24645 _this.boundDocumentFullscreenChange_ = function (e) { 24646 return _this.documentFullscreenChange_(e); 24647 }; 24648 24649 _this.boundFullWindowOnEscKey_ = function (e) { 24650 return _this.fullWindowOnEscKey(e); 24651 }; 24652 24653 _this.boundUpdateStyleEl_ = function (e) { 24654 return _this.updateStyleEl_(e); 24655 }; 24656 24657 _this.boundApplyInitTime_ = function (e) { 24658 return _this.applyInitTime_(e); 24659 }; 24660 24661 _this.boundUpdateCurrentBreakpoint_ = function (e) { 24662 return _this.updateCurrentBreakpoint_(e); 24663 }; 24664 24665 _this.boundHandleTechClick_ = function (e) { 24666 return _this.handleTechClick_(e); 24667 }; 24668 24669 _this.boundHandleTechDoubleClick_ = function (e) { 24670 return _this.handleTechDoubleClick_(e); 24671 }; 24672 24673 _this.boundHandleTechTouchStart_ = function (e) { 24674 return _this.handleTechTouchStart_(e); 24675 }; 24676 24677 _this.boundHandleTechTouchMove_ = function (e) { 24678 return _this.handleTechTouchMove_(e); 24679 }; 24680 24681 _this.boundHandleTechTouchEnd_ = function (e) { 24682 return _this.handleTechTouchEnd_(e); 24683 }; 24684 24685 _this.boundHandleTechTap_ = function (e) { 24686 return _this.handleTechTap_(e); 24687 }; // default isFullscreen_ to false 24688 24689 24690 _this.isFullscreen_ = false; // create logger 24691 24692 _this.log = createLogger(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests 24693 24694 _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster 24695 24696 _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero 24697 // and a seek is happening 24698 24699 _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously 24700 24701 _this.isReady_ = false; // Init state hasStarted_ 24702 24703 _this.hasStarted_ = false; // Init state userActive_ 24704 24705 _this.userActive_ = false; // Init debugEnabled_ 24706 24707 _this.debugEnabled_ = false; // if the global option object was accidentally blown away by 24708 // someone, bail early with an informative error 24709 24710 if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { 24711 throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); 24712 } // Store the original tag used to set options 24713 24714 24715 _this.tag = tag; // Store the tag attributes used to restore html5 element 24716 24717 _this.tagAttributes = tag && getAttributes(tag); // Update current language 24718 24719 _this.language(_this.options_.language); // Update Supported Languages 24720 24721 24722 if (options.languages) { 24723 // Normalise player option languages to lowercase 24724 var languagesToLower = {};
24725 Object.getOwnPropertyNames(options.languages).forEach(function (name) { 24726 languagesToLower[name.toLowerCase()] = options.languages[name]; 24727 }); 24728 _this.languages_ = languagesToLower; 24729 } else { 24730 _this.languages_ = Player.prototype.options_.languages; 24731 } 24732 24733 _this.resetCache_(); // Set poster 24734 24735 24736 _this.poster_ = options.poster || ''; // Set controls 24737 24738 _this.controls_ = !!options.controls; // Original tag settings stored in options 24739 // now remove immediately so native controls don't flash. 24740 // May be turned back on by HTML5 tech if nativeControlsForTouch is true 24741 24742 tag.controls = false; 24743 tag.removeAttribute('controls'); 24744 _this.changingSrc_ = false; 24745 _this.playCallbacks_ = []; 24746 _this.playTerminatedQueue_ = []; // the attribute overrides the option 24747 24748 if (tag.hasAttribute('autoplay')) { 24749 _this.autoplay(true); 24750 } else { 24751 // otherwise use the setter to validate and 24752 // set the correct value. 24753 _this.autoplay(_this.options_.autoplay); 24754 } // check plugins 24755 24756 24757 if (options.plugins) { 24758 Object.keys(options.plugins).forEach(function (name) { 24759 if (typeof _this[name] !== 'function') { 24760 throw new Error("plugin \"" + name + "\" does not exist"); 24761 } 24762 }); 24763 } 24764 /* 24765 * Store the internal state of scrubbing 24766 * 24767 * @private 24768 * @return {Boolean} True if the user is scrubbing 24769 */ 24770 24771 24772 _this.scrubbing_ = false; 24773 _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus. 24774 24775 evented(assertThisInitialized(_this), { 24776 eventBusKey: 'el_' 24777 }); // listen to document and player fullscreenchange handlers so we receive those events 24778 // before a user can receive them so we can update isFullscreen appropriately. 24779 // make sure that we listen to fullscreenchange events before everything else to make sure that 24780 // our isFullscreen method is updated properly for internal components as well as external. 24781 24782 if (_this.fsApi_.requestFullscreen) { 24783 on(document_1, _this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_); 24784 24785 _this.on(_this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_); 24786 } 24787 24788 if (_this.fluid_) { 24789 _this.on(['playerreset', 'resize'], _this.boundUpdateStyleEl_); 24790 } // We also want to pass the original player options to each component and plugin 24791 // as well so they don't need to reach back into the player for options later. 24792 // We also need to do another copy of this.options_ so we don't end up with 24793 // an infinite loop. 24794 24795 24796 var playerOptionsCopy = mergeOptions$3(_this.options_); // Load plugins 24797 24798 if (options.plugins) { 24799 Object.keys(options.plugins).forEach(function (name) { 24800 _this[name](options.plugins[name]); 24801 }); 24802 } // Enable debug mode to fire debugon event for all plugins. 24803 24804 24805 if (options.debug) { 24806 _this.debug(true); 24807 } 24808 24809 _this.options_.playerOptions = playerOptionsCopy; 24810 _this.middleware_ = []; 24811 24812 _this.playbackRates(options.playbackRates); 24813 24814 _this.initChildren(); // Set isAudio based on whether or not an audio tag was used 24815 24816 24817 _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially 24818 // set because the element doesn't exist yet. 24819 24820 24821 if (_this.controls()) { 24822 _this.addClass('vjs-controls-enabled'); 24823 } else { 24824 _this.addClass('vjs-controls-disabled'); 24825 } // Set ARIA label and region role depending on player type 24826 24827 24828 _this.el_.setAttribute('role', 'region'); 24829 24830 if (_this.isAudio()) { 24831 _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); 24832 } else { 24833 _this.el_.setAttribute('aria-label', _this.localize('Video Player')); 24834 } 24835 24836 if (_this.isAudio()) { 24837 _this.addClass('vjs-audio'); 24838 } 24839 24840 if (_this.flexNotSupported_()) { 24841 _this.addClass('vjs-no-flex'); 24842 } // TODO: Make this smarter. Toggle user state between touching/mousing 24843 // using events, since devices can have both touch and mouse events. 24844 // TODO: Make this check be performed again when the window switches between monitors 24845 // (See https://github.com/videojs/video.js/issues/5683) 24846 24847 24848 if (TOUCH_ENABLED) { 24849 _this.addClass('vjs-touch-enabled'); 24850 } // iOS Safari has broken hover handling 24851 24852 24853 if (!IS_IOS) { 24854 _this.addClass('vjs-workinghover'); 24855 } // Make player easily findable by ID 24856 24857 24858 Player.players[_this.id_] = assertThisInitialized(_this); // Add a major version class to aid css in plugins 24859 24860 var majorVersion = version$5.split('.')[0]; 24861 24862 _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components 24863 // like the control bar show themselves if needed 24864 24865 24866 _this.userActive(true); 24867 24868 _this.reportUserActivity(); 24869 24870 _this.one('play', function (e) { 24871 return _this.listenForUserActivity_(e); 24872 }); 24873 24874 _this.on('stageclick', function (e) { 24875 return _this.handleStageClick_(e); 24876 }); 24877 24878 _this.on('keydown', function (e) { 24879 return _this.handleKeyDown(e); 24880 }); 24881 24882 _this.on('languagechange', function (e) { 24883 return _this.handleLanguagechange(e); 24884 }); 24885 24886 _this.breakpoints(_this.options_.breakpoints); 24887 24888 _this.responsive(_this.options_.responsive); 24889 24890 return _this; 24891 } 24892 /** 24893 * Destroys the video player and does any necessary cleanup. 24894 * 24895 * This is especially helpful if you are dynamically adding and removing videos 24896 * to/from the DOM. 24897 * 24898 * @fires Player#dispose 24899 */ 24900 24901 24902 var _proto = Player.prototype; 24903 24904 _proto.dispose = function dispose() { 24905 var _this2 = this; 24906 24907 /** 24908 * Called when the player is being disposed of. 24909 * 24910 * @event Player#dispose 24911 * @type {EventTarget~Event} 24912 */ 24913 this.trigger('dispose'); // prevent dispose from being called twice 24914 24915 this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is 24916 24917 off(document_1, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_); 24918 off(document_1, 'keydown', this.boundFullWindowOnEscKey_); 24919 24920 if (this.styleEl_ && this.styleEl_.parentNode) { 24921 this.styleEl_.parentNode.removeChild(this.styleEl_); 24922 this.styleEl_ = null; 24923 } // Kill reference to this player 24924 24925 24926 Player.players[this.id_] = null; 24927 24928 if (this.tag && this.tag.player) { 24929 this.tag.player = null; 24930 } 24931 24932 if (this.el_ && this.el_.player) { 24933 this.el_.player = null; 24934 } 24935 24936 if (this.tech_) { 24937 this.tech_.dispose(); 24938 this.isPosterFromTech_ = false; 24939 this.poster_ = ''; 24940 } 24941 24942 if (this.playerElIngest_) { 24943 this.playerElIngest_ = null; 24944 } 24945 24946 if (this.tag) { 24947 this.tag = null; 24948 } 24949 24950 clearCacheForPlayer(this); // remove all event handlers for track lists 24951 // all tracks and track listeners are removed on 24952 // tech dispose 24953
24954 ALL.names.forEach(function (name) { 24955 var props = ALL[name]; 24956 24957 var list = _this2[props.getterName](); // if it is not a native list 24958 // we have to manually remove event listeners 24959 24960 24961 if (list && list.off) { 24962 list.off(); 24963 } 24964 }); // the actual .el_ is removed here 24965 24966 _Component.prototype.dispose.call(this); 24967 } 24968 /** 24969 * Create the `Player`'s DOM element. 24970 * 24971 * @return {Element} 24972 * The DOM element that gets created. 24973 */ 24974 ; 24975 24976 _proto.createEl = function createEl() { 24977 var tag = this.tag; 24978 var el; 24979 var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); 24980 var divEmbed = this.tag.tagName.toLowerCase() === 'video-js'; 24981 24982 if (playerElIngest) { 24983 el = this.el_ = tag.parentNode; 24984 } else if (!divEmbed) { 24985 el = this.el_ = _Component.prototype.createEl.call(this, 'div'); 24986 } // Copy over all the attributes from the tag, including ID and class 24987 // ID will now reference player box, not the video tag 24988 24989 24990 var attrs = getAttributes(tag); 24991 24992 if (divEmbed) { 24993 el = this.el_ = tag; 24994 tag = this.tag = document_1.createElement('video'); 24995 24996 while (el.children.length) { 24997 tag.appendChild(el.firstChild); 24998 } 24999 25000 if (!hasClass(el, 'video-js')) { 25001 addClass(el, 'video-js'); 25002 } 25003 25004 el.appendChild(tag); 25005 playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element 25006 // to our new `video` element. This will move things like 25007 // `src` or `controls` that were set via js before the player 25008 // was initialized. 25009 25010 Object.keys(el).forEach(function (k) { 25011 try { 25012 tag[k] = el[k]; 25013 } catch (e) {// we got a a property like outerHTML which we can't actually copy, ignore it 25014 } 25015 }); 25016 } // set tabindex to -1 to remove the video element from the focus order 25017 25018 25019 tag.setAttribute('tabindex', '-1'); 25020 attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and 25021 // for the same issue with Chrome (on Windows) with JAWS. 25022 // See https://github.com/FreedomScientific/VFO-standards-support/issues/78 25023 // Note that we can't detect if JAWS is being used, but this ARIA attribute 25024 // doesn't change behavior of IE11 or Chrome if JAWS is not being used 25025 25026 if (IE_VERSION || IS_CHROME && IS_WINDOWS) { 25027 tag.setAttribute('role', 'application'); 25028 attrs.role = 'application'; 25029 } // Remove width/height attrs from tag so CSS can make it 100% width/height 25030 25031 25032 tag.removeAttribute('width'); 25033 tag.removeAttribute('height'); 25034 25035 if ('width' in attrs) { 25036 delete attrs.width; 25037 } 25038 25039 if ('height' in attrs) { 25040 delete attrs.height; 25041 } 25042 25043 Object.getOwnPropertyNames(attrs).forEach(function (attr) { 25044 // don't copy over the class attribute to the player element when we're in a div embed 25045 // the class is already set up properly in the divEmbed case 25046 // and we want to make sure that the `video-js` class doesn't get lost 25047 if (!(divEmbed && attr === 'class')) { 25048 el.setAttribute(attr, attrs[attr]); 25049 } 25050 25051 if (divEmbed) { 25052 tag.setAttribute(attr, attrs[attr]); 25053 } 25054 }); // Update tag id/class for use as HTML5 playback tech 25055 // Might think we should do this after embedding in container so .vjs-tech class 25056 // doesn't flash 100% width/height, but class only applies with .video-js parent 25057 25058 tag.playerId = tag.id; 25059 tag.id += '_html5_api'; 25060 tag.className = 'vjs-tech'; // Make player findable on elements 25061 25062 tag.player = el.player = this; // Default state of video is paused 25063 25064 this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height 25065 // of the player in a way that's still overrideable by CSS, just like the 25066 // video element 25067 25068 if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true) { 25069 this.styleEl_ = createStyleElement('vjs-styles-dimensions'); 25070 var defaultsStyleEl = $('.vjs-styles-defaults'); 25071 var head = $('head'); 25072 head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); 25073 } 25074 25075 this.fill_ = false; 25076 this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el 25077 25078 this.width(this.options_.width); 25079 this.height(this.options_.height); 25080 this.fill(this.options_.fill); 25081 this.fluid(this.options_.fluid); 25082 this.aspectRatio(this.options_.aspectRatio); // support both crossOrigin and crossorigin to reduce confusion and issues around the name 25083 25084 this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin); // Hide any links within the video/audio tag, 25085 // because IE doesn't hide them completely from screen readers. 25086 25087 var links = tag.getElementsByTagName('a'); 25088 25089 for (var i = 0; i < links.length; i++) { 25090 var linkEl = links.item(i); 25091 addClass(linkEl, 'vjs-hidden'); 25092 linkEl.setAttribute('hidden', 'hidden'); 25093 } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so 25094 // keep track of the original for later so we can know if the source originally failed 25095 25096 25097 tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container 25098 25099 if (tag.parentNode && !playerElIngest) { 25100 tag.parentNode.insertBefore(el, tag); 25101 } // insert the tag as the first child of the player element 25102 // then manually add it to the children array so that this.addChild 25103 // will work properly for other components 25104 // 25105 // Breaks iPhone, fixed in HTML5 setup. 25106 25107 25108 prependTo(tag, el); 25109 this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player 25110 // if it's been set to something different to the doc 25111 25112 this.el_.setAttribute('lang', this.language_); 25113 this.el_ = el; 25114 return el; 25115 } 25116 /** 25117 * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this 25118 * sets the `crossOrigin` property on the `<video>` tag to control the CORS 25119 * behavior. 25120 * 25121 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin} 25122 * 25123 * @param {string} [value] 25124 * The value to set the `Player`'s crossOrigin to. If an argument is 25125 * given, must be one of `anonymous` or `use-credentials`. 25126 * 25127 * @return {string|undefined} 25128 * - The current crossOrigin value of the `Player` when getting. 25129 * - undefined when setting 25130 */ 25131 ; 25132 25133 _proto.crossOrigin = function crossOrigin(value) { 25134 if (!value) { 25135 return this.techGet_('crossOrigin'); 25136 } 25137 25138 if (value !== 'anonymous' && value !== 'use-credentials') { 25139 log$1.warn("crossOrigin must be \"anonymous\" or \"use-credentials\", given \"" + value + "\""); 25140 return; 25141 } 25142 25143 this.techCall_('setCrossOrigin', value); 25144 return; 25145 } 25146 /** 25147 * A getter/setter for the `Player`'s width. Returns the player'
25147s configured value. 25148 * To get the current width use `currentWidth()`. 25149 * 25150 * @param {number} [value] 25151 * The value to set the `Player`'s width to. 25152 * 25153 * @return {number} 25154 * The current width of the `Player` when getting. 25155 */ 25156 ; 25157 25158 _proto.width = function width(value) { 25159 return this.dimension('width', value); 25160 } 25161 /** 25162 * A getter/setter for the `Player`'s height. Returns the player's configured value. 25163 * To get the current height use `currentheight()`. 25164 * 25165 * @param {number} [value] 25166 * The value to set the `Player`'s heigth to. 25167 * 25168 * @return {number} 25169 * The current height of the `Player` when getting. 25170 */ 25171 ; 25172 25173 _proto.height = function height(value) { 25174 return this.dimension('height', value); 25175 } 25176 /** 25177 * A getter/setter for the `Player`'s width & height. 25178 * 25179 * @param {string} dimension 25180 * This string can be: 25181 * - 'width' 25182 * - 'height' 25183 * 25184 * @param {number} [value] 25185 * Value for dimension specified in the first argument. 25186 * 25187 * @return {number} 25188 * The dimension arguments value when getting (width/height). 25189 */ 25190 ; 25191 25192 _proto.dimension = function dimension(_dimension, value) { 25193 var privDimension = _dimension + '_'; 25194 25195 if (value === undefined) { 25196 return this[privDimension] || 0; 25197 } 25198 25199 if (value === '' || value === 'auto') { 25200 // If an empty string is given, reset the dimension to be automatic 25201 this[privDimension] = undefined; 25202 this.updateStyleEl_(); 25203 return; 25204 } 25205 25206 var parsedVal = parseFloat(value); 25207 25208 if (isNaN(parsedVal)) { 25209 log$1.error("Improper value \"" + value + "\" supplied for for " + _dimension); 25210 return; 25211 } 25212 25213 this[privDimension] = parsedVal; 25214 this.updateStyleEl_(); 25215 } 25216 /** 25217 * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. 25218 * 25219 * Turning this on will turn off fill mode. 25220 * 25221 * @param {boolean} [bool] 25222 * - A value of true adds the class. 25223 * - A value of false removes the class. 25224 * - No value will be a getter. 25225 * 25226 * @return {boolean|undefined} 25227 * - The value of fluid when getting. 25228 * - `undefined` when setting. 25229 */ 25230 ; 25231 25232 _proto.fluid = function fluid(bool) { 25233 var _this3 = this; 25234 25235 if (bool === undefined) { 25236 return !!this.fluid_; 25237 } 25238 25239 this.fluid_ = !!bool; 25240 25241 if (isEvented(this)) { 25242 this.off(['playerreset', 'resize'], this.boundUpdateStyleEl_); 25243 } 25244 25245 if (bool) { 25246 this.addClass('vjs-fluid'); 25247 this.fill(false); 25248 addEventedCallback(this, function () { 25249 _this3.on(['playerreset', 'resize'], _this3.boundUpdateStyleEl_); 25250 }); 25251 } else { 25252 this.removeClass('vjs-fluid'); 25253 } 25254 25255 this.updateStyleEl_(); 25256 } 25257 /** 25258 * A getter/setter/toggler for the vjs-fill `className` on the `Player`. 25259 * 25260 * Turning this on will turn off fluid mode. 25261 * 25262 * @param {boolean} [bool] 25263 * - A value of true adds the class. 25264 * - A value of false removes the class. 25265 * - No value will be a getter. 25266 * 25267 * @return {boolean|undefined} 25268 * - The value of fluid when getting. 25269 * - `undefined` when setting. 25270 */ 25271 ; 25272 25273 _proto.fill = function fill(bool) { 25274 if (bool === undefined) { 25275 return !!this.fill_; 25276 } 25277 25278 this.fill_ = !!bool; 25279 25280 if (bool) { 25281 this.addClass('vjs-fill'); 25282 this.fluid(false); 25283 } else { 25284 this.removeClass('vjs-fill'); 25285 } 25286 } 25287 /** 25288 * Get/Set the aspect ratio 25289 * 25290 * @param {string} [ratio] 25291 * Aspect ratio for player 25292 * 25293 * @return {string|undefined} 25294 * returns the current aspect ratio when getting 25295 */ 25296 25297 /** 25298 * A getter/setter for the `Player`'s aspect ratio. 25299 * 25300 * @param {string} [ratio] 25301 * The value to set the `Player`'s aspect ratio to. 25302 * 25303 * @return {string|undefined} 25304 * - The current aspect ratio of the `Player` when getting. 25305 * - undefined when setting 25306 */ 25307 ; 25308 25309 _proto.aspectRatio = function aspectRatio(ratio) { 25310 if (ratio === undefined) { 25311 return this.aspectRatio_; 25312 } // Check for width:height format 25313 25314 25315 if (!/^\d+\:\d+$/.test(ratio)) { 25316 throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); 25317 } 25318 25319 this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode, 25320 // because in fixed mode you could calculate width and height yourself. 25321 25322 this.fluid(true); 25323 this.updateStyleEl_(); 25324 } 25325 /** 25326 * Update styles of the `Player` element (height, width and aspect ratio). 25327 * 25328 * @private 25329 * @listens Tech#loadedmetadata 25330 */ 25331 ; 25332 25333 _proto.updateStyleEl_ = function updateStyleEl_() { 25334 if (window_1.VIDEOJS_NO_DYNAMIC_STYLE === true) { 25335 var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; 25336 25337 var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; 25338 25339 var techEl = this.tech_ && this.tech_.el(); 25340 25341 if (techEl) { 25342 if (_width >= 0) { 25343 techEl.width = _width; 25344 } 25345 25346 if (_height >= 0) {
25347 techEl.height = _height; 25348 } 25349 } 25350 25351 return; 25352 } 25353 25354 var width; 25355 var height; 25356 var aspectRatio; 25357 var idClass; // The aspect ratio is either used directly or to calculate width and height. 25358 25359 if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { 25360 // Use any aspectRatio that's been specifically set 25361 aspectRatio = this.aspectRatio_; 25362 } else if (this.videoWidth() > 0) { 25363 // Otherwise try to get the aspect ratio from the video metadata 25364 aspectRatio = this.videoWidth() + ':' + this.videoHeight(); 25365 } else { 25366 // Or use a default. The video element's is 2:1, but 16:9 is more common. 25367 aspectRatio = '16:9'; 25368 } // Get the ratio as a decimal we can use to calculate dimensions 25369 25370 25371 var ratioParts = aspectRatio.split(':'); 25372 var ratioMultiplier = ratioParts[1] / ratioParts[0]; 25373 25374 if (this.width_ !== undefined) { 25375 // Use any width that's been specifically set 25376 width = this.width_; 25377 } else if (this.height_ !== undefined) { 25378 // Or calulate the width from the aspect ratio if a height has been set 25379 width = this.height_ / ratioMultiplier; 25380 } else { 25381 // Or use the video's metadata, or use the video el's default of 300 25382 width = this.videoWidth() || 300; 25383 } 25384 25385 if (this.height_ !== undefined) { 25386 // Use any height that's been specifically set 25387 height = this.height_; 25388 } else { 25389 // Otherwise calculate the height from the ratio and the width 25390 height = width * ratioMultiplier; 25391 } // Ensure the CSS class is valid by starting with an alpha character 25392 25393 25394 if (/^[^a-zA-Z]/.test(this.id())) { 25395 idClass = 'dimensions-' + this.id(); 25396 } else { 25397 idClass = this.id() + '-dimensions'; 25398 } // Ensure the right class is still on the player for the style element 25399 25400 25401 this.addClass(idClass); 25402 setTextContent(this.styleEl_, "\n ." + idClass + " {\n width: " + width + "px;\n height: " + height + "px;\n }\n\n ." + idClass + ".vjs-fluid {\n padding-top: " + ratioMultiplier * 100 + "%;\n }\n "); 25403 } 25404 /** 25405 * Load/Create an instance of playback {@link Tech} including element 25406 * and API methods. Then append the `Tech` element in `Player` as a child. 25407 * 25408 * @param {string} techName 25409 * name of the playback technology 25410 * 25411 * @param {string} source 25412 * video source 25413 * 25414 * @private 25415 */ 25416 ; 25417 25418 _proto.loadTech_ = function loadTech_(techName, source) { 25419 var _this4 = this; 25420 25421 // Pause and remove current playback technology 25422 if (this.tech_) { 25423 this.unloadTech_(); 25424 } 25425 25426 var titleTechName = toTitleCase$1(techName); 25427 var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech 25428 25429 if (titleTechName !== 'Html5' && this.tag) { 25430 Tech.getTech('Html5').disposeMediaElement(this.tag); 25431 this.tag.player = null; 25432 this.tag = null; 25433 } 25434 25435 this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously 25436 25437 this.isReady_ = false; 25438 var autoplay = this.autoplay(); // if autoplay is a string (or `true` with normalizeAutoplay: true) we pass false to the tech 25439 // because the player is going to handle autoplay on `loadstart` 25440 25441 if (typeof this.autoplay() === 'string' || this.autoplay() === true && this.options_.normalizeAutoplay) { 25442 autoplay = false; 25443 } // Grab tech-specific options from player options and add source and parent element to use. 25444 25445 25446 var techOptions = { 25447 source: source, 25448 autoplay: autoplay, 25449 'nativeControlsForTouch': this.options_.nativeControlsForTouch, 25450 'playerId': this.id(), 25451 'techId': this.id() + "_" + camelTechName + "_api", 25452 'playsinline': this.options_.playsinline, 25453 'preload': this.options_.preload, 25454 'loop': this.options_.loop, 25455 'disablePictureInPicture': this.options_.disablePictureInPicture, 25456 'muted': this.options_.muted, 25457 'poster': this.poster(), 25458 'language': this.language(), 25459 'playerElIngest': this.playerElIngest_ || false, 25460 'vtt.js': this.options_['vtt.js'], 25461 'canOverridePoster': !!this.options_.techCanOverridePoster, 25462 'enableSourceset': this.options_.enableSourceset, 25463 'Promise': this.options_.Promise 25464 };
25465 ALL.names.forEach(function (name) { 25466 var props = ALL[name]; 25467 techOptions[props.getterName] = _this4[props.privateName]; 25468 }); 25469 assign(techOptions, this.options_[titleTechName]); 25470 assign(techOptions, this.options_[camelTechName]); 25471 assign(techOptions, this.options_[techName.toLowerCase()]); 25472 25473 if (this.tag) { 25474 techOptions.tag = this.tag; 25475 } 25476 25477 if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { 25478 techOptions.startTime = this.cache_.currentTime; 25479 } // Initialize tech instance 25480 25481 25482 var TechClass = Tech.getTech(techName); 25483 25484 if (!TechClass) { 25485 throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'"); 25486 } 25487 25488 this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async 25489 25490 this.tech_.ready(bind(this, this.handleTechReady_), true); 25491 textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player 25492 25493 TECH_EVENTS_RETRIGGER.forEach(function (event) { 25494 _this4.on(_this4.tech_, event, function (e) { 25495 return _this4["handleTech" + toTitleCase$1(event) + "_"](e); 25496 }); 25497 }); 25498 Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) { 25499 _this4.on(_this4.tech_, event, function (eventObj) { 25500 if (_this4.tech_.playbackRate() === 0 && _this4.tech_.seeking()) { 25501 _this4.queuedCallbacks_.push({ 25502 callback: _this4["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this4), 25503 event: eventObj 25504 }); 25505 25506 return; 25507 } 25508 25509 _this4["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj); 25510 }); 25511 }); 25512 this.on(this.tech_, 'loadstart', function (e) { 25513 return _this4.handleTechLoadStart_(e); 25514 }); 25515 this.on(this.tech_, 'sourceset', function (e) { 25516 return _this4.handleTechSourceset_(e); 25517 }); 25518 this.on(this.tech_, 'waiting', function (e) { 25519 return _this4.handleTechWaiting_(e); 25520 }); 25521 this.on(this.tech_, 'ended', function (e) { 25522 return _this4.handleTechEnded_(e); 25523 }); 25524 this.on(this.tech_, 'seeking', function (e) { 25525 return _this4.handleTechSeeking_(e); 25526 }); 25527 this.on(this.tech_, 'play', function (e) { 25528 return _this4.handleTechPlay_(e); 25529 }); 25530 this.on(this.tech_, 'firstplay', function (e) { 25531 return _this4.handleTechFirstPlay_(e); 25532 }); 25533 this.on(this.tech_, 'pause', function (e) { 25534 return _this4.handleTechPause_(e); 25535 }); 25536 this.on(this.tech_, 'durationchange', function (e) { 25537 return _this4.handleTechDurationChange_(e); 25538 }); 25539 this.on(this.tech_, 'fullscreenchange', function (e, data) { 25540 return _this4.handleTechFullscreenChange_(e, data); 25541 }); 25542 this.on(this.tech_, 'fullscreenerror', function (e, err) { 25543 return _this4.handleTechFullscreenError_(e, err); 25544 }); 25545 this.on(this.tech_, 'enterpictureinpicture', function (e) { 25546 return _this4.handleTechEnterPictureInPicture_(e); 25547 }); 25548 this.on(this.tech_, 'leavepictureinpicture', function (e) { 25549 return _this4.handleTechLeavePictureInPicture_(e); 25550 }); 25551 this.on(this.tech_, 'error', function (e) { 25552 return _this4.handleTechError_(e); 25553 }); 25554 this.on(this.tech_, 'posterchange', function (e) { 25555 return _this4.handleTechPosterChange_(e); 25556 }); 25557 this.on(this.tech_, 'textdata', function (e) { 25558 return _this4.handleTechTextData_(e); 25559 }); 25560 this.on(this.tech_, 'ratechange', function (e) { 25561 return _this4.handleTechRateChange_(e); 25562 }); 25563 this.on(this.tech_, 'loadedmetadata', this.boundUpdateStyleEl_); 25564 this.usingNativeControls(this.techGet_('controls')); 25565 25566 if (this.controls() && !this.usingNativeControls()) {
vendor: 1,850 bytes, lines 25567-25624
25567 this.addTechControlsListeners_(); 25568 } // Add the tech element in the DOM if it was not already there 25569 // Make sure to not insert the original video element if using Html5 25570 25571 25572 if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { 25573 prependTo(this.tech_.el(), this.el()); 25574 } // Get rid of the original video tag reference after the first tech is loaded 25575 25576 25577 if (this.tag) { 25578 this.tag.player = null; 25579 this.tag = null; 25580 } 25581 } 25582 /** 25583 * Unload and dispose of the current playback {@link Tech}. 25584 * 25585 * @private 25586 */ 25587 ; 25588 25589 _proto.unloadTech_ = function unloadTech_() { 25590 var _this5 = this; 25591 25592 // Save the current text tracks so that we can reuse the same text tracks with the next tech 25593 ALL.names.forEach(function (name) { 25594 var props = ALL[name]; 25595 _this5[props.privateName] = _this5[props.getterName](); 25596 }); 25597 this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_); 25598 this.isReady_ = false; 25599 this.tech_.dispose(); 25600 this.tech_ = false; 25601 25602 if (this.isPosterFromTech_) { 25603 this.poster_ = ''; 25604 this.trigger('posterchange'); 25605 } 25606 25607 this.isPosterFromTech_ = false; 25608 } 25609 /** 25610 * Return a reference to the current {@link Tech}. 25611 * It will print a warning by default about the danger of using the tech directly 25612 * but any argument that is passed in will silence the warning. 25613 * 25614 * @param {*} [safety] 25615 * Anything passed in to silence the warning 25616 * 25617 * @return {Tech} 25618 * The Tech 25619 */ 25620 ; 25621 25622 _proto.tech = function tech(safety) { 25623 if (safety === undefined) { 25624 log$1.warn('Using the tech directly can be dangerous. I hope you know
25624what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n'); 25625 } 25626 25627 return this.tech_; 25628 } 25629 /** 25630 * Set up click and touch listeners for the playback element 25631 * 25632 * - On desktops: a click on the video itself will toggle playback 25633 * - On mobile devices: a click on the video toggles controls 25634 * which is done by toggling the user state between active and 25635 * inactive 25636 * - A tap can signal that a user has become active or has become inactive 25637 * e.g. a quick tap on an iPhone movie should reveal the controls. Another 25638 * quick tap should hide them again (signaling the user is in an inactive 25639 * viewing state) 25640 * - In addition to this, we still want the user to be considered inactive after 25641 * a few seconds of inactivity. 25642 * 25643 * > Note: the only part of iOS interaction we can't mimic with this setup 25644 * is a touch and hold on the video element counting as activity in order to 25645 * keep the controls showing, but that shouldn't be an issue. A touch and hold 25646 * on any controls will still keep the user active 25647 * 25648 * @private 25649 */ 25650 ; 25651 25652 _proto.addTechControlsListeners_ = function addTechControlsListeners_() { 25653 // Make sure to remove all the previous listeners in case we are called multiple times. 25654 this.removeTechControlsListeners_(); 25655 this.on(this.tech_, 'click', this.boundHandleTechClick_); 25656 this.on(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event 25657 // so we'll check if the controls were already showing before reporting user 25658 // activity 25659 25660 this.on(this.tech_, 'touchstart', this.boundHandleTechTouchStart_); 25661 this.on(this.tech_, 'touchmove', this.boundHandleTechTouchMove_); 25662 this.on(this.tech_, 'touchend', this.boundHandleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap 25663 // listener cancels out any reportedUserActivity when setting userActive(false) 25664 25665 this.on(this.tech_, 'tap', this.boundHandleTechTap_); 25666 } 25667 /** 25668 * Remove the listeners used for click and tap controls. This is needed for 25669 * toggling to controls disabled, where a tap/touch should do nothing. 25670 * 25671 * @private 25672 */ 25673 ; 25674 25675 _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() { 25676 // We don't want to just use `this.off()` because there might be other needed 25677 // listeners added by techs that extend this. 25678 this.off(this.tech_, 'tap', this.boundHandleTechTap_); 25679 this.off(this.tech_, 'touchstart', this.boundHandleTechTouchStart_); 25680 this.off(this.tech_, 'touchmove', this.boundHandleTechTouchMove_); 25681 this.off(this.tech_, 'touchend', this.boundHandleTechTouchEnd_); 25682 this.off(this.tech_, 'click', this.boundHandleTechClick_); 25683 this.off(this.tech_, 'dblclick', this.boundHandleTechDoubleClick_); 25684 } 25685 /** 25686 * Player waits for the tech to be ready 25687 * 25688 * @private 25689 */ 25690 ; 25691 25692 _proto.handleTechReady_ = function handleTechReady_() { 25693 this.triggerReady(); // Keep the same volume as before 25694 25695 if (this.cache_.volume) { 25696 this.techCall_('setVolume', this.cache_.volume); 25697 } // Look if the tech found a higher resolution poster while loading 25698 25699 25700 this.handleTechPosterChange_(); // Update the duration if available 25701 25702 this.handleTechDurationChange_(); 25703 } 25704 /** 25705 * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This 25706 * function will also trigger {@link Player#firstplay} if it is the first loadstart 25707 * for a video. 25708 * 25709 * @fires Player#loadstart 25710 * @fires Player#firstplay 25711 * @listens Tech#loadstart 25712 * @private 25713 */ 25714 ; 25715 25716 _proto.handleTechLoadStart_ = function handleTechLoadStart_() { 25717 // TODO: Update to use `emptied` event instead. See #1277. 25718 this.removeClass('vjs-ended'); 25719 this.removeClass('vjs-seeking'); // reset the error state 25720 25721 this.error(null); // Update the duration 25722 25723 this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now. 25724 // The firstplay event relies on both the play and loadstart events 25725 // which can happen in any order for a new source 25726 25727 if (!this.paused()) { 25728 /** 25729 * Fired when the user agent begins looking for media data 25730 * 25731 * @event Player#loadstart 25732 * @type {EventTarget~Event} 25733 */ 25734 this.trigger('loadstart'); 25735 this.trigger('firstplay'); 25736 } else { 25737 // reset the hasStarted state 25738 this.hasStarted(false); 25739 this.trigger('loadstart'); 25740 } // autoplay happens after loadstart for the browser,
25741 // so we mimic that behavior 25742 25743 25744 this.manualAutoplay_(this.autoplay() === true && this.options_.normalizeAutoplay ? 'play' : this.autoplay()); 25745 } 25746 /** 25747 * Handle autoplay string values, rather than the typical boolean 25748 * values that should be handled by the tech. Note that this is not 25749 * part of any specification. Valid values and what they do can be 25750 * found on the autoplay getter at Player#autoplay() 25751 */ 25752 ; 25753 25754 _proto.manualAutoplay_ = function manualAutoplay_(type) { 25755 var _this6 = this; 25756 25757 if (!this.tech_ || typeof type !== 'string') { 25758 return; 25759 } // Save original muted() value, set muted to true, and attempt to play(). 25760 // On promise rejection, restore muted from saved value 25761 25762 25763 var resolveMuted = function resolveMuted() { 25764 var previouslyMuted = _this6.muted(); 25765 25766 _this6.muted(true); 25767 25768 var restoreMuted = function restoreMuted() { 25769 _this6.muted(previouslyMuted); 25770 }; // restore muted on play terminatation 25771 25772 25773 _this6.playTerminatedQueue_.push(restoreMuted); 25774 25775 var mutedPromise = _this6.play(); 25776 25777 if (!isPromise(mutedPromise)) { 25778 return; 25779 } 25780 25781 return mutedPromise["catch"](function (err) { 25782 restoreMuted(); 25783 throw new Error("Rejection at manualAutoplay. Restoring muted value. " + (err ? err : '')); 25784 }); 25785 }; 25786 25787 var promise; // if muted defaults to true 25788 // the only thing we can do is call play 25789 25790 if (type === 'any' && !this.muted()) { 25791 promise = this.play(); 25792 25793 if (isPromise(promise)) { 25794 promise = promise["catch"](resolveMuted); 25795 } 25796 } else if (type === 'muted' && !this.muted()) { 25797 promise = resolveMuted(); 25798 } else { 25799 promise = this.play(); 25800 } 25801 25802 if (!isPromise(promise)) { 25803 return; 25804 } 25805 25806 return promise.then(function () { 25807 _this6.trigger({ 25808 type: 'autoplay-success', 25809 autoplay: type 25810 }); 25811 })["catch"](function () { 25812 _this6.trigger({ 25813 type: 'autoplay-failure', 25814 autoplay: type 25815 }); 25816 }); 25817 } 25818 /** 25819 * Update the internal source caches so that we return the correct source from 25820 * `src()`, `currentSource()`, and `currentSources()`. 25821 * 25822 * > Note: `currentSources` will not be updated if the source that is passed in exists 25823 * in the current `currentSources` cache. 25824 * 25825 * 25826 * @param {Tech~SourceObject} srcObj 25827 * A string or object source to update our caches to. 25828 */ 25829 ; 25830 25831 _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) { 25832 if (srcObj === void 0) { 25833 srcObj = ''; 25834 } 25835 25836 var src = srcObj; 25837 var type = ''; 25838 25839 if (typeof src !== 'string') { 25840 src = srcObj.src; 25841 type = srcObj.type; 25842 } // make sure all the caches are set to default values 25843 // to prevent null checking 25844 25845 25846 this.cache_.source = this.cache_.source || {}; 25847 this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in 25848 25849 if (src && !type) { 25850 type = findMimetype(this, src); 25851 } // update `currentSource` cache always 25852 25853 25854 this.cache_.source = mergeOptions$3({}, srcObj, { 25855 src: src, 25856 type: type 25857 }); 25858 var matchingSources = this.cache_.sources.filter(function (s) { 25859 return s.src && s.src === src; 25860 }); 25861 var sourceElSources = []; 25862 var sourceEls = this.$$('source'); 25863 var matchingSourceEls = []; 25864 25865 for (var i = 0; i < sourceEls.length; i++) { 25866 var sourceObj = getAttributes(sourceEls[i]); 25867 sourceElSources.push(sourceObj); 25868 25869 if (sourceObj.src && sourceObj.src === src) { 25870 matchingSourceEls.push(sourceObj.src); 25871 } 25872 } // if we have matching source els but not matching sources 25873 // the current source cache is not up to date 25874 25875 25876 if (matchingSourceEls.length && !matchingSources.length) { 25877 this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the 25878 // sources cache to the `currentSource` cache 25879 } else if (!matchingSources.length) { 25880 this.cache_.sources = [this.cache_.source]; 25881 } // update the tech `src` cache 25882 25883 25884 this.cache_.src = src; 25885 } 25886 /**
25887 * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech} 25888 * causing the media element to reload. 25889 * 25890 * It will fire for the initial source and each subsequent source. 25891 * This event is a custom event from Video.js and is triggered by the {@link Tech}. 25892 * 25893 * The event object for this event contains a `src` property that will contain the source 25894 * that was available when the event was triggered. This is generally only necessary if Video.js 25895 * is switching techs while the source was being changed. 25896 * 25897 * It is also fired when `load` is called on the player (or media element) 25898 * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`} 25899 * says that the resource selection algorithm needs to be aborted and restarted. 25900 * In this case, it is very likely that the `src` property will be set to the 25901 * empty string `""` to indicate we do not know what the source will be but 25902 * that it is changing. 25903 * 25904 * *This event is currently still experimental and may change in minor releases.* 25905 * __To use this, pass `enableSourceset` option to the player.__ 25906 * 25907 * @event Player#sourceset 25908 * @type {EventTarget~Event} 25909 * @prop {string} src 25910 * The source url available when the `sourceset` was triggered. 25911 * It will be an empty string if we cannot know what the source is 25912 * but know that the source will change. 25913 */ 25914 25915 /** 25916 * Retrigger the `sourceset` event that was triggered by the {@link Tech}. 25917 * 25918 * @fires Player#sourceset 25919 * @listens Tech#sourceset 25920 * @private 25921 */ 25922 ; 25923 25924 _proto.handleTechSourceset_ = function handleTechSourceset_(event) { 25925 var _this7 = this; 25926 25927 // only update the source cache when the source 25928 // was not updated using the player api 25929 if (!this.changingSrc_) { 25930 var updateSourceCaches = function updateSourceCaches(src) { 25931 return _this7.updateSourceCaches_(src); 25932 }; 25933 25934 var playerSrc = this.currentSource().src; 25935 var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob 25936 25937 if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) { 25938 // if both the tech source and the player source were updated we assume 25939 // something like @videojs/http-streaming did the sourceset and skip updating the source cache. 25940 if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) { 25941 updateSourceCaches = function updateSourceCaches() {}; 25942 } 25943 } // update the source to the initial source right away 25944 // in some cases this will be empty string 25945 25946 25947 updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string 25948 // wait for a `loadstart` to update the cache to `currentSrc`. 25949 // If a sourceset happens before a `loadstart`, we reset the state 25950 25951 if (!event.src) { 25952 this.tech_.any(['sourceset', 'loadstart'], function (e) { 25953 // if a sourceset happens before a `loadstart` there 25954 // is nothing to do as this `handleTechSourceset_` 25955 // will be called again and this will be handled there. 25956 if (e.type === 'sourceset') { 25957 return; 25958 } 25959 25960 var techSrc = _this7.techGet('currentSrc'); 25961 25962 _this7.lastSource_.tech = techSrc; 25963 25964 _this7.updateSourceCaches_(techSrc); 25965 }); 25966 } 25967 } 25968 25969 this.lastSource_ = { 25970 player: this.currentSource().src, 25971 tech: event.src 25972 }; 25973 this.trigger({ 25974 src: event.src, 25975 type: 'sourceset' 25976 }); 25977 } 25978 /** 25979 * Add/remove the vjs-has-started class 25980 * 25981 * @fires Player#firstplay 25982 * 25983 * @param {boolean} request 25984 * - true: adds the class 25985 * - false: remove the class 25986 * 25987 * @return {boolean} 25988 * the boolean value of hasStarted_ 25989 */ 25990 ; 25991 25992 _proto.hasStarted = function hasStarted(request) { 25993 if (request === undefined) { 25994 // act as getter, if we have no request to change 25995 return this.hasStarted_; 25996 } 25997 25998 if (request === this.hasStarted_) { 25999 return; 26000 } 26001 26002 this.hasStarted_ = request; 26003 26004 if (this.hasStarted_) { 26005 this.addClass('vjs-has-started'); 26006 this.trigger('firstplay'); 26007 } else { 26008 this.removeClass('vjs-has-started'); 26009 } 26010 } 26011 /** 26012 * Fired whenever the media begins or resumes playback 26013 * 26014 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} 26015 * @fires Player#play 26016 * @listens Tech#play 26017 * @private 26018 */ 26019 ; 26020 26021 _proto.handleTechPlay_ = function handleTechPlay_() { 26022 this.removeClass('vjs-ended'); 26023 this.removeClass('vjs-paused'); 26024 this.addClass('vjs-playing'); // hide the poster when the user hits play 26025 26026 this.hasStarted(true); 26027 /** 26028 * Triggered whenever an {@link Tech#play} event happens. Indicates that 26029 * playback has started or resumed. 26030 * 26031 * @event Player#play 26032 * @type {EventTarget~Event} 26033 */ 26034 26035 this.trigger('play'); 26036 } 26037 /** 26038 * Retrigger the `ratechange` event that was triggered by the {@link Tech}. 26039 * 26040 * If there were any events queued while the playback rate was zero, fire 26041 * those events now. 26042 * 26043 * @private 26044 * @method Player#handleTechRateChange_ 26045 * @fires Player#ratechange 26046 * @listens Tech#ratechange 26047 */ 26048 ; 26049 26050 _proto.handleTechRateChange_ = function handleTechRateChange_() { 26051 if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) { 26052 this.queuedCallbacks_.forEach(function (queued) { 26053 return queued.callback(queued.event); 26054 }); 26055 this.queuedCallbacks_ = []; 26056 } 26057 26058 this.cache_.lastPlaybackRate = this.tech_.playbackRate(); 26059 /** 26060 * Fires when the playing speed of the audio/video is changed 26061 * 26062 * @event Player#ratechange 26063 * @type {event} 26064 */ 26065 26066 this.trigger('ratechange'); 26067 } 26068 /** 26069 * Retrigger the `waiting` event that was triggered by the {@link Tech}. 26070 * 26071 * @fires Player#waiting 26072 * @listens Tech#waiting 26073 * @private 26074 */ 26075 ; 26076 26077 _proto.handleTechWaiting_ = function handleTechWaiting_() { 26078 var _this8 = this; 26079 26080 this.addClass('vjs-waiting'); 26081 /** 26082 * A readyState change on the DOM element has caused playback to stop. 26083 * 26084 * @event Player#waiting 26085 * @type {EventTarget~Event} 26086 */ 26087 26088 this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent 26089 // premature removal of the waiting class, wait for the time to change. 26090 26091 var timeWhenWaiting = this.currentTime(); 26092 26093 var timeUpdateListener = function timeUpdateListener() { 26094 if (timeWhenWaiting !== _this8.currentTime()) { 26095 _this8.removeClass('vjs-waiting'); 26096 26097 _this8.off('timeupdate', timeUpdateListener); 26098 } 26099 }; 26100 26101 this.on('timeupdate', timeUpdateListener); 26102 } 26103 /** 26104 * Retrigger the `canplay` event that was triggered by the {@link Tech}. 26105 * > Note: This is not consistent between browsers. See #1351 26106 * 26107 * @fires Player#canplay 26108 * @listens Tech#canplay 26109 * @private 26110 */ 26111 ; 26112 26113 _proto.handleTechCanPlay_ = function handleTechCanPlay_() { 26114 this.removeClass('vjs-waiting'); 26115 /** 26116 * The media has a readyState of HAVE_FUTURE_DATA or greater. 26117 * 26118 * @event Player#canplay 26119 * @type {EventTarget~Event} 26120 */ 26121 26122 this.trigger('canplay'); 26123 } 26124 /** 26125 * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. 26126 * 26127 * @fires Player#canplaythrough 26128 * @listens Tech#canplaythrough 26129 * @private 26130 */ 26131 ; 26132 26133 _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { 26134 this.removeClass('vjs-waiting'); 26135 /** 26136 * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the 26137 * entire media file can be played without buffering. 26138 * 26139 * @event Player#canplaythrough 26140 * @type {EventTarget~Event} 26141 */ 26142 26143 this.trigger('canplaythrough'); 26144 } 26145 /** 26146 * Retrigger the `playing` event that was triggered by the {@link Tech}. 26147 * 26148 * @fires Player#playing 26149 * @listens Tech#playing 26150 * @private 26151 */ 26152 ; 26153 26154 _proto.handleTechPlaying_ = function handleTechPlaying_() { 26155 this.removeClass('vjs-waiting'); 26156 /** 26157 * The media is no longer blocked from playback, and has started playing. 26158 * 26159 * @event Player#playing 26160 * @type {EventTarget~Event} 26161 */ 26162 26163 this.trigger('playing'); 26164 } 26165 /** 26166 * Retrigger the `seeking` event that was triggered by the {@link Tech}. 26167 * 26168 * @fires Player#seeking 26169 * @listens Tech#seeking 26170 * @private 26171 */ 26172 ; 26173 26174 _proto.handleTechSeeking_ = function handleTechSeeking_() { 26175 this.addClass('vjs-seeking'); 26176 /** 26177 * Fired whenever the player is jumping to a new time 26178 * 26179 * @event Player#seeking 26180 * @type {EventTarget~Event} 26181 */ 26182 26183 this.trigger('seeking'); 26184 } 26185 /** 26186 * Retrigger the `seeked` event that was triggered by the {@link Tech}. 26187 * 26188 * @fires Player#seeked 26189 * @listens Tech#seeked 26190 * @private 26191 */ 26192 ; 26193 26194 _proto.handleTechSeeked_ = function handleTechSeeked_() { 26195 this.removeClass('vjs-seeking'); 26196 this.removeClass('vjs-ended'); 26197 /** 26198 * Fired when the player has finished jumping to a new time 26199 * 26200 * @event Player#seeked 26201 * @type {EventTarget~Event} 26202 */ 26203 26204 this.trigger('seeked'); 26205 } 26206 /** 26207 * Retrigger the `firstplay` event that was triggered by the {@link Tech}. 26208 * 26209 * @fires Player#firstplay 26210 * @listens Tech#firstplay 26211 * @deprecated As of 6.0 firstplay event is deprecated. 26212 * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. 26213 * @private 26214 */ 26215 ; 26216 26217 _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() { 26218 // If the first starttime attribute is specified 26219 // then we will start at the given offset in seconds 26220 if (this.options_.starttime) { 26221 log$1.warn('Passing the `starttime` option to the player will be deprecated
26221in 6.0'); 26222 this.currentTime(this.options_.starttime); 26223 } 26224 26225 this.addClass('vjs-has-started'); 26226 /** 26227 * Fired the first time a video is played. Not part of the HLS spec, and this is 26228 * probably not the best implementation yet, so use sparingly. If you don't have a 26229 * reason to prevent playback, use `myPlayer.one('play');` instead. 26230 * 26231 * @event Player#firstplay 26232 * @deprecated As of 6.0 firstplay event is deprecated. 26233 * @type {EventTarget~Event} 26234 */ 26235 26236 this.trigger('firstplay'); 26237 } 26238 /** 26239 * Retrigger the `pause` event that was triggered by the {@link Tech}. 26240 * 26241 * @fires Player#pause 26242 * @listens Tech#pause 26243 * @private 26244 */ 26245 ; 26246 26247 _proto.handleTechPause_ = function handleTechPause_() { 26248 this.removeClass('vjs-playing'); 26249 this.addClass('vjs-paused'); 26250 /** 26251 * Fired whenever the media has been paused 26252 * 26253 * @event Player#pause 26254 * @type {EventTarget~Event} 26255 */ 26256 26257 this.trigger('pause'); 26258 } 26259 /** 26260 * Retrigger the `ended` event that was triggered by the {@link Tech}. 26261 * 26262 * @fires Player#ended 26263 * @listens Tech#ended 26264 * @private 26265 */ 26266 ; 26267 26268 _proto.handleTechEnded_ = function handleTechEnded_() { 26269 this.addClass('vjs-ended'); 26270 this.removeClass('vjs-waiting'); 26271 26272 if (this.options_.loop) { 26273 this.currentTime(0); 26274 this.play(); 26275 } else if (!this.paused()) { 26276 this.pause(); 26277 } 26278 /** 26279 * Fired when the end of the media resource is reached (currentTime == duration) 26280 * 26281 * @event Player#ended 26282 * @type {EventTarget~Event} 26283 */ 26284 26285 26286 this.trigger('ended'); 26287 } 26288 /** 26289 * Fired when the duration of the media resource is first known or changed 26290 * 26291 * @listens Tech#durationchange 26292 * @private 26293 */ 26294 ; 26295 26296 _proto.handleTechDurationChange_ = function handleTechDurationChange_() { 26297 this.duration(this.techGet_('duration')); 26298 } 26299 /** 26300 * Handle a click on the media element to play/pause 26301 * 26302 * @param {EventTarget~Event} event 26303 * the event that caused this function to trigger 26304 * 26305 * @listens Tech#click 26306 * @private 26307 */ 26308 ; 26309 26310 _proto.handleTechClick_ = function handleTechClick_(event) { 26311 // When controls are disabled a click should not toggle playback because 26312 // the click is considered a control 26313 if (!this.controls_) { 26314 return; 26315 } 26316 26317 if (this.paused()) { 26318 silencePromise(this.play()); 26319 } else { 26320 this.pause(); 26321 } 26322 } 26323 /** 26324 * Handle a double-click on the media element to enter/exit fullscreen 26325 * 26326 * @param {EventTarget~Event} event 26327 * the event that caused this function to trigger 26328 * 26329 * @listens Tech#dblclick 26330 * @private 26331 */ 26332 ; 26333 26334 _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) { 26335 if (!this.controls_) { 26336 return; 26337 } // we do not want to toggle fullscreen state 26338 // when double-clicking inside a control bar or a modal 26339 26340 26341 var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) { 26342 return el.contains(event.target); 26343 }); 26344 26345 if (!inAllowedEls) { 26346 /* 26347 * options.userActions.doubleClick 26348 * 26349 * If `undefined` or `true`, double-click toggles fullscreen if controls are present 26350 * Set to `false` to disable double-click handling 26351 * Set to a function to substitute an external double-click handler 26352 */ 26353 if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) { 26354 if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') { 26355 this.options_.userActions.doubleClick.call(this, event); 26356 } else if (this.isFullscreen()) { 26357 this.exitFullscreen(); 26358 } else { 26359 this.requestFullscreen(); 26360 } 26361 } 26362 } 26363 } 26364 /** 26365 * Handle a tap on the media element. It will toggle the user 26366 * activity state, which hides and shows the controls. 26367 * 26368 * @listens Tech#tap 26369 * @private 26370 */ 26371 ; 26372 26373 _proto.handleTechTap_ = function handleTechTap_() { 26374 this.userActive(!this.userActive()); 26375 } 26376 /** 26377 * Handle touch to start 26378 * 26379 * @listens Tech#touchstart 26380 * @private 26381 */ 26382 ; 26383 26384 _proto.handleTechTouchStart_ = function handleTechTouchStart_() { 26385 this.userWasActive = this.userActive(); 26386 } 26387 /** 26388 * Handle touch to move 26389 * 26390 * @listens Tech#touchmove 26391 * @private 26392 */ 26393 ; 26394 26395 _proto.handleTechTouchMove_ = function handleTechTouchMove_() { 26396 if (this.userWasActive) { 26397 this.reportUserActivity(); 26398 } 26399 } 26400 /** 26401 * Handle touch to end 26402 * 26403 * @param {EventTarget~Event} event 26404 * the touchend event that triggered 26405 * this function 26406 * 26407 * @listens Tech#touchend 26408 * @private 26409 */ 26410 ; 26411 26412 _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { 26413 // Stop the mouse events from also happening 26414 if (event.cancelable) { 26415 event.preventDefault(); 26416 } 26417 } 26418 /** 26419 * native click events on the SWF aren't triggered on IE11, Win8.1RT 26420 * use stageclick events triggered from inside the SWF instead 26421 * 26422 * @private 26423 * @listens stageclick 26424 */ 26425 ; 26426 26427 _proto.handleStageClick_ = function handleStageClick_() { 26428 this.reportUserActivity(); 26429 } 26430 /** 26431 * @private 26432 */ 26433 ; 26434 26435 _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() { 26436 if (this.isFullscreen()) { 26437 this.addClass('vjs-fullscreen'); 26438 } else { 26439 this.removeClass('vjs-fullscreen'); 26440 } 26441 } 26442 /** 26443 * when the document fschange event triggers it calls this 26444 */ 26445 ; 26446 26447 _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) { 26448 var targetPlayer = e.target.player; // if another player was fullscreen 26449 // do a null check for targetPlayer because older firefox's would put document as e.target 26450 26451 if (targetPlayer && targetPlayer !== this) { 26452 return; 26453 } 26454 26455 var el = this.el(); 26456 var isFs = document_1[this.fsApi_.fullscreenElement] === el; 26457 26458 if (!isFs && el.matches) { 26459 isFs = el.matches(':' + this.fsApi_.fullscreen); 26460 } else if (!isFs && el.msMatchesSelector) { 26461 isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen); 26462 } 26463 26464 this.isFullscreen(isFs); 26465 } 26466 /** 26467 * Handle Tech Fullscreen Change 26468 * 26469 * @param {EventTarget~Event} event 26470 * the fullscreenchange event that triggered this function 26471 * 26472 * @param {Object} data 26473 * the data that was sent with the event 26474 * 26475 * @private 26476 * @listens Tech#fullscreenchange 26477 * @fires Player#fullscreenchange 26478 */ 26479 ; 26480 26481 _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { 26482 if (data) { 26483 if (data.nativeIOSFullscreen) { 26484 this.toggleClass('vjs-ios-native-fs'); 26485 } 26486 26487 this.isFullscreen(data.isFullscreen); 26488 } 26489 }; 26490 26491 _proto.handleTechFullscreenError_ = function handleTechFullscreenError_(event, err) { 26492 this.trigger('fullscreenerror', err); 26493 } 26494 /** 26495 * @private 26496 */ 26497 ; 26498 26499 _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() { 26500 if (this.isInPictureInPicture()) { 26501 this.addClass('vjs-picture-in-picture'); 26502 } else { 26503 this.removeClass('vjs-picture-in-picture'); 26504 } 26505 } 26506 /** 26507 * Handle Tech Enter Picture-in-Picture. 26508 * 26509 * @param {EventTarget~Event} event 26510 * the enterpictureinpicture event that triggered this function 26511 * 26512 * @private 26513 * @listens Tech#enterpictureinpicture 26514 */ 26515 ; 26516 26517 _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) { 26518 this.isInPictureInPicture(true); 26519 } 26520 /** 26521 * Handle Tech Leave Picture-in-Picture. 26522 * 26523 * @param {EventTarget~Event} event 26524 * the leavepictureinpicture event that triggered this function 26525 * 26526 * @private 26527 * @listens Tech#leavepictureinpicture 26528 */ 26529 ; 26530 26531 _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) { 26532 this.isInPictureInPicture(false); 26533 } 26534 /** 26535 * Fires when an error occurred during the loading of an audio/video. 26536 * 26537 * @private 26538 * @listens Tech#error 26539 */ 26540 ; 26541 26542 _proto.handleTechError_ = function handleTechError_() { 26543 var error = this.tech_.error(); 26544 this.error(error); 26545 } 26546 /** 26547 * Retrigger the `textdata` event that was triggered by the {@link Tech}. 26548 * 26549 * @fires Player#textdata 26550 * @listens Tech#textdata 26551 * @private 26552 */ 26553 ; 26554 26555 _proto.handleTechTextData_ = function handleTechTextData_() { 26556 var data = null; 26557 26558 if (arguments.length > 1) { 26559 data = arguments[1]; 26560 } 26561 /** 26562 * Fires when we get a textdata event from tech 26563 * 26564 * @event Player#textdata 26565 * @type {EventTarget~Event} 26566 */ 26567 26568 26569 this.trigger('textdata', data); 26570 } 26571 /** 26572 * Get object for cached values. 26573 * 26574 * @return {Object} 26575 * get the current object cache 26576 */ 26577 ; 26578 26579 _proto.getCache = function getCache() { 26580 return this.cache_; 26581 } 26582 /**
26583 * Resets the internal cache object. 26584 * 26585 * Using this function outside the player constructor or reset method may 26586 * have unintended side-effects. 26587 * 26588 * @private 26589 */ 26590 ; 26591 26592 _proto.resetCache_ = function resetCache_() { 26593 this.cache_ = { 26594 // Right now, the currentTime is not _really_ cached because it is always 26595 // retrieved from the tech (see: currentTime). However, for completeness, 26596 // we set it to zero here to ensure that if we do start actually caching 26597 // it, we reset it along with everything else. 26598 currentTime: 0, 26599 initTime: 0, 26600 inactivityTimeout: this.options_.inactivityTimeout, 26601 duration: NaN, 26602 lastVolume: 1, 26603 lastPlaybackRate: this.defaultPlaybackRate(), 26604 media: null, 26605 src: '', 26606 source: {}, 26607 sources: [], 26608 playbackRates: [], 26609 volume: 1 26610 }; 26611 } 26612 /** 26613 * Pass values to the playback tech 26614 * 26615 * @param {string} [method] 26616 * the method to call 26617 * 26618 * @param {Object} arg 26619 * the argument to pass 26620 * 26621 * @private 26622 */ 26623 ; 26624 26625 _proto.techCall_ = function techCall_(method, arg) { 26626 // If it's not ready yet, call method when it is 26627 this.ready(function () { 26628 if (method in allowedSetters) { 26629 return set(this.middleware_, this.tech_, method, arg); 26630 } else if (method in allowedMediators) { 26631 return mediate(this.middleware_, this.tech_, method, arg); 26632 } 26633 26634 try { 26635 if (this.tech_) { 26636 this.tech_[method](arg); 26637 } 26638 } catch (e) { 26639 log$1(e); 26640 throw e; 26641 } 26642 }, true); 26643 } 26644 /** 26645 * Get calls can't wait for the tech, and sometimes don't need to. 26646 * 26647 * @param {string} method 26648 * Tech method 26649 * 26650 * @return {Function|undefined} 26651 * the method or undefined 26652 * 26653 * @private 26654 */ 26655 ; 26656 26657 _proto.techGet_ = function techGet_(method) { 26658 if (!this.tech_ || !this.tech_.isReady_) { 26659 return; 26660 } 26661 26662 if (method in allowedGetters) { 26663 return get(this.middleware_, this.tech_, method); 26664 } else if (method in allowedMediators) { 26665 return mediate(this.middleware_, this.tech_, method); 26666 } // Flash likes to die and reload when you hide or reposition it. 26667 // In these cases the object methods go away and we get errors. 26668 // TODO: Is this needed for techs other than Flash? 26669 // When that happens we'll catch the errors and inform tech that it's not ready any more. 26670 26671 26672 try { 26673 return this.tech_[method](); 26674 } catch (e) { 26675 // When building additional tech libs, an expected method may not be defined yet 26676 if (this.tech_[method] === undefined) { 26677 log$1("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e); 26678 throw e; 26679 } // When a method isn't available on the object it throws a TypeError 26680 26681 26682 if (e.name === 'TypeError') { 26683 log$1("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e); 26684 this.tech_.isReady_ = false; 26685 throw e; 26686 } // If error unknown, just log and throw 26687 26688 26689 log$1(e); 26690 throw e; 26691 } 26692 } 26693 /** 26694 * Attempt to begin playback at the first opportunity. 26695 * 26696 * @return {Promise|undefined} 26697 * Returns a promise if the browser supports Promises (or one 26698 * was passed in as an option). This promise will be resolved on 26699 * the return value of play. If this is undefined it will fulfill the 26700 * promise chain otherwise the promise chain will be fulfilled when 26701 * the promise from play is fulfilled. 26702 */ 26703 ; 26704 26705 _proto.play = function play() { 26706 var _this9 = this; 26707 26708 var PromiseClass = this.options_.Promise || window_1.Promise; 26709 26710 if (PromiseClass) { 26711 return new PromiseClass(function (resolve) { 26712 _this9.play_(resolve); 26713 }); 26714 } 26715 26716 return this.play_(); 26717 } 26718 /** 26719 * The actual logic for play, takes a callback that will be resolved on the 26720 * return value of play. This allows us to resolve to the play promise if there 26721 * is one on modern browsers. 26722 * 26723 * @private 26724 * @param {Function} [callback] 26725 * The callback that should be called when the techs play is actually called 26726 */ 26727 ; 26728 26729 _proto.play_ = function play_(callback) { 26730 var _this10 = this; 26731 26732 if (callback === void 0) { 26733 callback = silencePromise; 26734 } 26735 26736 this.playCallbacks_.push(callback);
26737 var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function 26738 26739 if (this.waitToPlay_) { 26740 this.off(['ready', 'loadstart'], this.waitToPlay_); 26741 this.waitToPlay_ = null; 26742 } // if the player/tech is not ready or the src itself is not ready 26743 // queue up a call to play on `ready` or `loadstart` 26744 26745 26746 if (!this.isReady_ || !isSrcReady) { 26747 this.waitToPlay_ = function (e) { 26748 _this10.play_(); 26749 }; 26750 26751 this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod 26752 // in that case, we need to prime the video element by calling load so it'll be ready in time 26753 26754 if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) { 26755 this.load(); 26756 } 26757 26758 return; 26759 } // If the player/tech is ready and we have a source, we can attempt playback. 26760 26761 26762 var val = this.techGet_('play'); // play was terminated if the returned value is null 26763 26764 if (val === null) { 26765 this.runPlayTerminatedQueue_(); 26766 } else { 26767 this.runPlayCallbacks_(val); 26768 } 26769 } 26770 /** 26771 * These functions will be run when if play is terminated. If play 26772 * runPlayCallbacks_ is run these function will not be run. This allows us 26773 * to differenciate between a terminated play and an actual call to play. 26774 */ 26775 ; 26776 26777 _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() { 26778 var queue = this.playTerminatedQueue_.slice(0); 26779 this.playTerminatedQueue_ = []; 26780 queue.forEach(function (q) { 26781 q(); 26782 }); 26783 } 26784 /** 26785 * When a callback to play is delayed we have to run these 26786 * callbacks when play is actually called on the tech. This function 26787 * runs the callbacks that were delayed and accepts the return value 26788 * from the tech. 26789 * 26790 * @param {undefined|Promise} val 26791 * The return value from the tech. 26792 */ 26793 ; 26794 26795 _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) { 26796 var callbacks = this.playCallbacks_.slice(0); 26797 this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play 26798 26799 this.playTerminatedQueue_ = []; 26800 callbacks.forEach(function (cb) { 26801 cb(val); 26802 }); 26803 } 26804 /** 26805 * Pause the video playback 26806 * 26807 * @return {Player} 26808 * A reference to the player object this function was called on 26809 */ 26810 ; 26811 26812 _proto.pause = function pause() { 26813 this.techCall_('pause'); 26814 } 26815 /** 26816 * Check if the player is paused or has yet to play 26817 * 26818 * @return {boolean} 26819 * - false: if the media is currently playing 26820 * - true: if media is not currently playing 26821 */ 26822 ; 26823 26824 _proto.paused = function paused() { 26825 // The initial state of paused should be true (in Safari it's actually false) 26826 return this.techGet_('paused') === false ? false : true; 26827 } 26828 /** 26829 * Get a TimeRange object representing the current ranges of time that the user 26830 * has played. 26831 * 26832 * @return {TimeRange} 26833 * A time range object that represents all the increments of time that have 26834 * been played. 26835 */ 26836 ; 26837 26838 _proto.played = function played() { 26839 return this.techGet_('played') || createTimeRanges(0, 0); 26840 } 26841 /** 26842 * Returns whether or not the user is "scrubbing". Scrubbing is 26843 * when the user has clicked the progress bar handle and is 26844 * dragging it along the progress bar. 26845 * 26846 * @param {boolean} [isScrubbing] 26847 * whether the user is or is not scrubbing 26848 * 26849 * @return {boolean} 26850 * The value of scrubbing when getting 26851 */ 26852 ; 26853 26854 _proto.scrubbing = function scrubbing(isScrubbing) { 26855 if (typeof isScrubbing === 'undefined') { 26856 return this.scrubbing_; 26857 } 26858 26859 this.scrubbing_ = !!isScrubbing; 26860 this.techCall_('setScrubbing', this.scrubbing_); 26861 26862 if (isScrubbing) { 26863 this.addClass('vjs-scrubbing'); 26864 } else { 26865 this.removeClass('vjs-scrubbing'); 26866 } 26867 } 26868 /** 26869 * Get or set the current time (in seconds) 26870 * 26871 * @param {number|string} [seconds] 26872 * The time to seek to in seconds 26873 * 26874 * @return {number} 26875 * - the current time in seconds when getting 26876 */ 26877 ; 26878 26879 _proto.currentTime = function currentTime(seconds) { 26880 if (typeof seconds !== 'undefined') { 26881 if (seconds < 0) { 26882 seconds = 0; 26883 } 26884 26885 if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) { 26886 this.cache_.initTime = seconds; 26887 this.off('canplay', this.boundApplyInitTime_); 26888 this.one('canplay', this.boundApplyInitTime_); 26889 return; 26890 } 26891 26892 this.techCall_('setCurrentTime', seconds); 26893 this.cache_.initTime = 0; 26894 return; 26895 } // cache last currentTime and return. default to 0 seconds 26896 // 26897 // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
26898 // currentTime when scrubbing, but may not provide much performance benefit afterall. 26899 // Should be tested. Also something has to read the actual current time or the cache will 26900 // never get updated. 26901 26902 26903 this.cache_.currentTime = this.techGet_('currentTime') || 0; 26904 return this.cache_.currentTime; 26905 } 26906 /** 26907 * Apply the value of initTime stored in cache as currentTime. 26908 * 26909 * @private 26910 */ 26911 ; 26912 26913 _proto.applyInitTime_ = function applyInitTime_() { 26914 this.currentTime(this.cache_.initTime); 26915 } 26916 /** 26917 * Normally gets the length in time of the video in seconds; 26918 * in all but the rarest use cases an argument will NOT be passed to the method 26919 * 26920 * > **NOTE**: The video must have started loading before the duration can be 26921 * known, and depending on preload behaviour may not be known until the video starts 26922 * playing. 26923 * 26924 * @fires Player#durationchange 26925 * 26926 * @param {number} [seconds] 26927 * The duration of the video to set in seconds 26928 * 26929 * @return {number} 26930 * - The duration of the video in seconds when getting 26931 */ 26932 ; 26933 26934 _proto.duration = function duration(seconds) { 26935 if (seconds === undefined) { 26936 // return NaN if the duration is not known 26937 return this.cache_.duration !== undefined ? this.cache_.duration : NaN; 26938 } 26939 26940 seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live 26941 26942 if (seconds < 0) { 26943 seconds = Infinity; 26944 } 26945 26946 if (seconds !== this.cache_.duration) { 26947 // Cache the last set value for optimized scrubbing (esp. Flash) 26948 // TODO: Required for techs other than Flash? 26949 this.cache_.duration = seconds; 26950 26951 if (seconds === Infinity) { 26952 this.addClass('vjs-live'); 26953 } else { 26954 this.removeClass('vjs-live'); 26955 } 26956 26957 if (!isNaN(seconds)) { 26958 // Do not fire durationchange unless the duration value is known. 26959 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 26960 26961 /** 26962 * @event Player#durationchange 26963 * @type {EventTarget~Event} 26964 */ 26965 this.trigger('durationchange'); 26966 } 26967 } 26968 } 26969 /** 26970 * Calculates how much time is left in the video. Not part 26971 * of the native video API. 26972 * 26973 * @return {number} 26974 * The time remaining in seconds 26975 */ 26976 ; 26977 26978 _proto.remainingTime = function remainingTime() { 26979 return this.duration() - this.currentTime(); 26980 } 26981 /** 26982 * A remaining time function that is intented to be used when 26983 * the time is to be displayed directly to the user. 26984 * 26985 * @return {number} 26986 * The rounded time remaining in seconds 26987 */ 26988 ; 26989 26990 _proto.remainingTimeDisplay = function remainingTimeDisplay() { 26991 return Math.floor(this.duration()) - Math.floor(this.currentTime()); 26992 } // 26993 // Kind of like an array of portions of the video that have been downloaded. 26994 26995 /** 26996 * Get a TimeRange object with an array of the times of the video 26997 * that have been downloaded. If you just want the percent of the 26998 * video that's been downloaded, use bufferedPercent. 26999 * 27000 * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} 27001 * 27002 * @return {TimeRange} 27003 * A mock TimeRange object (following HTML spec) 27004 */ 27005 ; 27006 27007 _proto.buffered = function buffered() { 27008 var buffered = this.techGet_('buffered'); 27009 27010 if (!buffered || !buffered.length) { 27011 buffered = createTimeRanges(0, 0); 27012 } 27013 27014 return buffered; 27015 } 27016 /** 27017 * Get the percent (as a decimal) of the video that's been downloaded. 27018 * This method is not a part of the native HTML video API. 27019 * 27020 * @return {number} 27021 * A decimal between 0 and 1 representing the percent 27022 * that is buffered 0 being 0% and 1 being 100% 27023 */ 27024 ; 27025 27026 _proto.bufferedPercent = function bufferedPercent$1() { 27027 return bufferedPercent(this.buffered(), this.duration()); 27028 } 27029 /** 27030 * Get the ending time of the last buffered time range 27031 * This is used in the progress bar to encapsulate all time ranges. 27032 * 27033 * @return {number} 27034 * The end of the last buffered time range 27035 */ 27036 ; 27037 27038 _proto.bufferedEnd = function bufferedEnd() { 27039 var buffered = this.buffered(); 27040 var duration = this.duration(); 27041 var end = buffered.end(buffered.length - 1); 27042 27043 if (end > duration) { 27044 end = duration; 27045 } 27046 27047 return end; 27048 } 27049 /** 27050 * Get or set the current volume of the media 27051 * 27052 * @param {number} [percentAsDecimal] 27053 * The new volume as a decimal percent: 27054 * - 0 is muted/0%/off 27055 * - 1.0 is 100%/full 27056 * - 0.5 is half volume or 50% 27057 * 27058 * @return {number} 27059 * The current volume as a percent when getting 27060 */ 27061 ; 27062 27063 _proto.volume = function volume(percentAsDecimal) { 27064 var vol; 27065 27066 if (percentAsDecimal !== undefined) { 27067 // Force value to between 0 and 1 27068 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); 27069 this.cache_.volume = vol; 27070 this.techCall_('setVolume', vol); 27071 27072 if (vol > 0) { 27073 this.lastVolume_(vol); 27074 } 27075 27076 return; 27077 } // Default to 1 when returning current volume. 27078 27079 27080 vol = parseFloat(this.techGet_('volume'));
27081 return isNaN(vol) ? 1 : vol; 27082 } 27083 /** 27084 * Get the current muted state, or turn mute on or off 27085 * 27086 * @param {boolean} [muted] 27087 * - true to mute 27088 * - false to unmute 27089 * 27090 * @return {boolean} 27091 * - true if mute is on and getting 27092 * - false if mute is off and getting 27093 */ 27094 ; 27095 27096 _proto.muted = function muted(_muted) { 27097 if (_muted !== undefined) { 27098 this.techCall_('setMuted', _muted); 27099 return; 27100 } 27101 27102 return this.techGet_('muted') || false; 27103 } 27104 /** 27105 * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted 27106 * indicates the state of muted on initial playback. 27107 * 27108 * ```js 27109 * var myPlayer = videojs('some-player-id'); 27110 * 27111 * myPlayer.src("http://www.example.com/path/to/video.mp4"); 27112 * 27113 * // get, should be false 27114 * console.log(myPlayer.defaultMuted()); 27115 * // set to true 27116 * myPlayer.defaultMuted(true); 27117 * // get should be true 27118 * console.log(myPlayer.defaultMuted()); 27119 * ``` 27120 * 27121 * @param {boolean} [defaultMuted] 27122 * - true to mute 27123 * - false to unmute 27124 * 27125 * @return {boolean|Player} 27126 * - true if defaultMuted is on and getting 27127 * - false if defaultMuted is off and getting 27128 * - A reference to the current player when setting 27129 */ 27130 ; 27131 27132 _proto.defaultMuted = function defaultMuted(_defaultMuted) { 27133 if (_defaultMuted !== undefined) { 27134 return this.techCall_('setDefaultMuted', _defaultMuted); 27135 } 27136 27137 return this.techGet_('defaultMuted') || false; 27138 } 27139 /** 27140 * Get the last volume, or set it 27141 * 27142 * @param {number} [percentAsDecimal] 27143 * The new last volume as a decimal percent: 27144 * - 0 is muted/0%/off 27145 * - 1.0 is 100%/full 27146 * - 0.5 is half volume or 50% 27147 * 27148 * @return {number} 27149 * the current value of lastVolume as a percent when getting 27150 * 27151 * @private 27152 */ 27153 ; 27154 27155 _proto.lastVolume_ = function lastVolume_(percentAsDecimal) { 27156 if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { 27157 this.cache_.lastVolume = percentAsDecimal; 27158 return; 27159 } 27160 27161 return this.cache_.lastVolume; 27162 } 27163 /** 27164 * Check if current tech can support native fullscreen 27165 * (e.g. with built in controls like iOS) 27166 * 27167 * @return {boolean} 27168 * if native fullscreen is supported 27169 */ 27170 ; 27171 27172 _proto.supportsFullScreen = function supportsFullScreen() { 27173 return this.techGet_('supportsFullScreen') || false; 27174 } 27175 /** 27176 * Check if the player is in fullscreen mode or tell the player that it 27177 * is or is not in fullscreen mode. 27178 * 27179 * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official 27180 * property and instead document.fullscreenElement is used. But isFullscreen is 27181 * still a valuable property for internal player workings. 27182 * 27183 * @param {boolean} [isFS] 27184 * Set the players current fullscreen state 27185 * 27186 * @return {boolean} 27187 * - true if fullscreen is on and getting 27188 * - false if fullscreen is off and getting 27189 */ 27190 ; 27191 27192 _proto.isFullscreen = function isFullscreen(isFS) { 27193 if (isFS !== undefined) { 27194 var oldValue = this.isFullscreen_; 27195 this.isFullscreen_ = Boolean(isFS); // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange 27196 // this is the only place where we trigger fullscreenchange events for older browsers 27197 // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well 27198 27199 if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) { 27200 /** 27201 * @event Player#fullscreenchange 27202 * @type {EventTarget~Event} 27203 */ 27204 this.trigger('fullscreenchange'); 27205 } 27206 27207 this.toggleFullscreenClass_(); 27208 return; 27209 } 27210 27211 return this.isFullscreen_; 27212 } 27213 /** 27214 * Increase the size of the video to full screen
27215 * In some browsers, full screen is not supported natively, so it enters 27216 * "full window mode", where the video fills the browser window. 27217 * In browsers and devices that support native full screen, sometimes the 27218 * browser's default controls will be shown, and not the Video.js custom skin. 27219 * This includes most mobile devices (iOS, Android) and older versions of 27220 * Safari. 27221 * 27222 * @param {Object} [fullscreenOptions] 27223 * Override the player fullscreen options 27224 * 27225 * @fires Player#fullscreenchange 27226 */ 27227 ; 27228 27229 _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) { 27230 var PromiseClass = this.options_.Promise || window_1.Promise; 27231 27232 if (PromiseClass) { 27233 var self = this; 27234 return new PromiseClass(function (resolve, reject) { 27235 function offHandler() { 27236 self.off('fullscreenerror', errorHandler); 27237 self.off('fullscreenchange', changeHandler); 27238 } 27239 27240 function changeHandler() { 27241 offHandler(); 27242 resolve(); 27243 } 27244 27245 function errorHandler(e, err) { 27246 offHandler(); 27247 reject(err); 27248 } 27249 27250 self.one('fullscreenchange', changeHandler); 27251 self.one('fullscreenerror', errorHandler); 27252 var promise = self.requestFullscreenHelper_(fullscreenOptions); 27253 27254 if (promise) { 27255 promise.then(offHandler, offHandler); 27256 promise.then(resolve, reject); 27257 } 27258 }); 27259 } 27260 27261 return this.requestFullscreenHelper_(); 27262 }; 27263 27264 _proto.requestFullscreenHelper_ = function requestFullscreenHelper_(fullscreenOptions) { 27265 var _this11 = this; 27266 27267 var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers. 27268 // Use defaults or player configured option unless passed directly to this method. 27269 27270 if (!this.fsApi_.prefixed) { 27271 fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {}; 27272 27273 if (fullscreenOptions !== undefined) { 27274 fsOptions = fullscreenOptions; 27275 } 27276 } // This method works as follows: 27277 // 1. if a fullscreen api is available, use it 27278 // 1. call requestFullscreen with potential options 27279 // 2. if we got a promise from above, use it to update isFullscreen() 27280 // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it. 27281 // This is particularly used for iPhone, older iPads, and non-safari browser on iOS. 27282 // 3. otherwise, use "fullWindow" mode 27283 27284 27285 if (this.fsApi_.requestFullscreen) { 27286 var promise = this.el_[this.fsApi_.requestFullscreen](fsOptions); 27287 27288 if (promise) { 27289 promise.then(function () { 27290 return _this11.isFullscreen(true); 27291 }, function () { 27292 return _this11.isFullscreen(false); 27293 }); 27294 } 27295 27296 return promise; 27297 } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) { 27298 // we can't take the video.js controls fullscreen but we can go fullscreen 27299 // with native controls 27300 this.techCall_('enterFullScreen'); 27301 } else { 27302 // fullscreen isn't supported so we'll just stretch the video element to 27303 // fill the viewport 27304 this.enterFullWindow(); 27305 } 27306 } 27307 /** 27308 * Return the video to its normal size after having been in full screen mode 27309 * 27310 * @fires Player#fullscreenchange 27311 */ 27312 ; 27313 27314 _proto.exitFullscreen = function exitFullscreen() { 27315 var PromiseClass = this.options_.Promise || window_1.Promise; 27316 27317 if (PromiseClass) { 27318 var self = this; 27319 return new PromiseClass(function (resolve, reject) { 27320 function offHandler() { 27321 self.off('fullscreenerror', errorHandler); 27322 self.off('fullscreenchange', changeHandler); 27323 } 27324 27325 function changeHandler() { 27326 offHandler(); 27327 resolve(); 27328 } 27329 27330 function errorHandler(e, err) { 27331 offHandler(); 27332 reject(err); 27333 } 27334 27335 self.one('fullscreenchange', changeHandler); 27336 self.one('fullscreenerror', errorHandler); 27337 var promise = self.exitFullscreenHelper_(); 27338 27339 if (promise) { 27340 promise.then(offHandler, offHandler); // map the promise to our resolve/reject methods 27341 27342 promise.then(resolve, reject); 27343 } 27344 }); 27345 } 27346 27347 return this.exitFullscreenHelper_(); 27348 }; 27349 27350 _proto.exitFullscreenHelper_ = function exitFullscreenHelper_() { 27351 var _this12 = this; 27352 27353 if (this.fsApi_.requestFullscreen) { 27354 var promise = document_1[this.fsApi_.exitFullscreen]();
27355 27356 if (promise) { 27357 // we're splitting the promise here, so, we want to catch the 27358 // potential error so that this chain doesn't have unhandled errors 27359 silencePromise(promise.then(function () { 27360 return _this12.isFullscreen(false); 27361 })); 27362 } 27363 27364 return promise; 27365 } else if (this.tech_.supportsFullScreen() && !this.options_.preferFullWindow === true) { 27366 this.techCall_('exitFullScreen'); 27367 } else { 27368 this.exitFullWindow(); 27369 } 27370 } 27371 /** 27372 * When fullscreen isn't supported we can stretch the 27373 * video container to as wide as the browser will let us. 27374 * 27375 * @fires Player#enterFullWindow 27376 */ 27377 ; 27378 27379 _proto.enterFullWindow = function enterFullWindow() { 27380 this.isFullscreen(true); 27381 this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off 27382 27383 this.docOrigOverflow = document_1.documentElement.style.overflow; // Add listener for esc key to exit fullscreen 27384 27385 on(document_1, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars 27386 27387 document_1.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles 27388 27389 addClass(document_1.body, 'vjs-full-window'); 27390 /** 27391 * @event Player#enterFullWindow 27392 * @type {EventTarget~Event} 27393 */ 27394 27395 this.trigger('enterFullWindow'); 27396 } 27397 /** 27398 * Check for call to either exit full window or 27399 * full screen on ESC key 27400 * 27401 * @param {string} event 27402 * Event to check for key press 27403 */ 27404 ; 27405 27406 _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) { 27407 if (keycode.isEventKey(event, 'Esc')) { 27408 if (this.isFullscreen() === true) { 27409 if (!this.isFullWindow) { 27410 this.exitFullscreen(); 27411 } else { 27412 this.exitFullWindow(); 27413 } 27414 } 27415 } 27416 } 27417 /** 27418 * Exit full window 27419 * 27420 * @fires Player#exitFullWindow 27421 */ 27422 ; 27423 27424 _proto.exitFullWindow = function exitFullWindow() { 27425 this.isFullscreen(false); 27426 this.isFullWindow = false; 27427 off(document_1, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars. 27428 27429 document_1.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles 27430 27431 removeClass(document_1.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes 27432 // this.positionAll(); 27433 27434 /** 27435 * @event Player#exitFullWindow 27436 * @type {EventTarget~Event} 27437 */ 27438 27439 this.trigger('exitFullWindow'); 27440 } 27441 /** 27442 * Disable Picture-in-Picture mode. 27443 * 27444 * @param {boolean} value 27445 * - true will disable Picture-in-Picture mode 27446 * - false will enable Picture-in-Picture mode 27447 */ 27448 ; 27449 27450 _proto.disablePictureInPicture = function disablePictureInPicture(value) { 27451 if (value === undefined) { 27452 return this.techGet_('disablePictureInPicture'); 27453 } 27454 27455 this.techCall_('setDisablePictureInPicture', value); 27456 this.options_.disablePictureInPicture = value; 27457 this.trigger('disablepictureinpicturechanged'); 27458 } 27459 /** 27460 * Check if the player is in Picture-in-Picture mode or tell the player that it 27461 * is or is not in Picture-in-Picture mode. 27462 * 27463 * @param {boolean} [isPiP] 27464 * Set the players current Picture-in-Picture state 27465 * 27466 * @return {boolean} 27467 * - true if Picture-in-Picture is on and getting 27468 * - false if Picture-in-Picture is off and getting 27469 */ 27470 ; 27471 27472 _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) { 27473 if (isPiP !== undefined) { 27474 this.isInPictureInPicture_ = !!isPiP; 27475 this.togglePictureInPictureClass_(); 27476 return; 27477 } 27478 27479 return !!this.isInPictureInPicture_; 27480 } 27481 /** 27482 * Create a floating video window always on top of other windows so that users may 27483 * continue consuming media while they interact with other content sites, or 27484 * applications on their device. 27485 * 27486 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 27487 * 27488 * @fires Player#enterpictureinpicture 27489 * 27490 * @return {Promise} 27491 * A promise with a Picture-in-Picture window. 27492 */ 27493 ; 27494 27495 _proto.requestPictureInPicture = function requestPictureInPicture() { 27496 if ('pictureInPictureEnabled' in document_1 && this.disablePictureInPicture() === false) { 27497 /** 27498 * This event fires when the player enters picture in picture mode 27499 * 27500 * @event Player#enterpictureinpicture 27501 * @type {EventTarget~Event} 27502 */ 27503 return this.techGet_('requestPictureInPicture'); 27504 } 27505 } 27506 /** 27507 * Exit Picture-in-Picture mode. 27508 * 27509 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 27510 * 27511 * @fires Player#leavepictureinpicture 27512 * 27513 * @return {Promise} 27514 * A promise. 27515 */ 27516 ; 27517 27518 _proto.exitPictureInPicture = function exitPictureInPicture() { 27519 if ('pictureInPictureEnabled' in document_1) { 27520 /** 27521 * This event fires when the player leaves picture in picture mode 27522 * 27523 * @event Player#leavepictureinpicture 27524 * @type {EventTarget~Event} 27525 */ 27526 return document_1.exitPictureInPicture(); 27527 } 27528 } 27529 /** 27530 * Called when this Player has focus and a key gets pressed down, or when 27531 * any Component of this player receives a key press that it doesn't handle. 27532 * This allows player-wide hotkeys (either as defined below, or optionally 27533 * by an external function). 27534 * 27535 * @param {EventTarget~Event} event
27536 * The `keydown` event that caused this function to be called. 27537 * 27538 * @listens keydown 27539 */ 27540 ; 27541 27542 _proto.handleKeyDown = function handleKeyDown(event) { 27543 var userActions = this.options_.userActions; // Bail out if hotkeys are not configured. 27544 27545 if (!userActions || !userActions.hotkeys) { 27546 return; 27547 } // Function that determines whether or not to exclude an element from 27548 // hotkeys handling. 27549 27550 27551 var excludeElement = function excludeElement(el) { 27552 var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements. 27553 27554 if (el.isContentEditable) { 27555 return true; 27556 } // Inputs matching these types will still trigger hotkey handling as 27557 // they are not text inputs. 27558 27559 27560 var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit']; 27561 27562 if (tagName === 'input') { 27563 return allowedInputTypes.indexOf(el.type) === -1; 27564 } // The final test is by tag name. These tags will be excluded entirely. 27565 27566 27567 var excludedTags = ['textarea']; 27568 return excludedTags.indexOf(tagName) !== -1; 27569 }; // Bail out if the user is focused on an interactive form element. 27570 27571 27572 if (excludeElement(this.el_.ownerDocument.activeElement)) { 27573 return; 27574 } 27575 27576 if (typeof userActions.hotkeys === 'function') { 27577 userActions.hotkeys.call(this, event); 27578 } else { 27579 this.handleHotkeys(event); 27580 } 27581 } 27582 /** 27583 * Called when this Player receives a hotkey keydown event. 27584 * Supported player-wide hotkeys are: 27585 * 27586 * f - toggle fullscreen 27587 * m - toggle mute 27588 * k or Space - toggle play/pause 27589 * 27590 * @param {EventTarget~Event} event 27591 * The `keydown` event that caused this function to be called. 27592 */ 27593 ; 27594 27595 _proto.handleHotkeys = function handleHotkeys(event) { 27596 var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set 27597 27598 var _hotkeys$fullscreenKe = hotkeys.fullscreenKey, 27599 fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) { 27600 return keycode.isEventKey(keydownEvent, 'f'); 27601 } : _hotkeys$fullscreenKe, 27602 _hotkeys$muteKey = hotkeys.muteKey, 27603 muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) { 27604 return keycode.isEventKey(keydownEvent, 'm'); 27605 } : _hotkeys$muteKey, 27606 _hotkeys$playPauseKey = hotkeys.playPauseKey, 27607 playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) { 27608 return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'); 27609 } : _hotkeys$playPauseKey; 27610 27611 if (fullscreenKey.call(this, event)) { 27612 event.preventDefault(); 27613 event.stopPropagation(); 27614 var FSToggle = Component$1.getComponent('FullscreenToggle'); 27615 27616 if (document_1[this.fsApi_.fullscreenEnabled] !== false) { 27617 FSToggle.prototype.handleClick.call(this, event); 27618 } 27619 } else if (muteKey.call(this, event)) { 27620 event.preventDefault(); 27621 event.stopPropagation(); 27622 var MuteToggle = Component$1.getComponent('MuteToggle'); 27623 MuteToggle.prototype.handleClick.call(this, event); 27624 } else if (playPauseKey.call(this, event)) { 27625 event.preventDefault(); 27626 event.stopPropagation(); 27627 var PlayToggle = Component$1.getComponent('PlayToggle'); 27628 PlayToggle.prototype.handleClick.call(this, event); 27629 } 27630 } 27631 /** 27632 * Check whether the player can play a given mimetype 27633 * 27634 * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
27635 * 27636 * @param {string} type 27637 * The mimetype to check 27638 * 27639 * @return {string} 27640 * 'probably', 'maybe', or '' (empty string) 27641 */ 27642 ; 27643 27644 _proto.canPlayType = function canPlayType(type) { 27645 var can; // Loop through each playback technology in the options order 27646 27647 for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { 27648 var techName = j[i]; 27649 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 27650 // Remove once that deprecated behavior is removed. 27651 27652 if (!tech) { 27653 tech = Component$1.getComponent(techName); 27654 } // Check if the current tech is defined before continuing 27655 27656 27657 if (!tech) { 27658 log$1.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 27659 continue; 27660 } // Check if the browser supports this technology 27661 27662 27663 if (tech.isSupported()) { 27664 can = tech.canPlayType(type); 27665 27666 if (can) { 27667 return can; 27668 } 27669 } 27670 } 27671 27672 return ''; 27673 } 27674 /** 27675 * Select source based on tech-order or source-order 27676 * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, 27677 * defaults to tech-order selection 27678 * 27679 * @param {Array} sources 27680 * The sources for a media asset 27681 * 27682 * @return {Object|boolean} 27683 * Object of source and tech order or false 27684 */ 27685 ; 27686 27687 _proto.selectSource = function selectSource(sources) { 27688 var _this13 = this; 27689 27690 // Get only the techs specified in `techOrder` that exist and are supported by the 27691 // current platform 27692 var techs = this.options_.techOrder.map(function (techName) { 27693 return [techName, Tech.getTech(techName)]; 27694 }).filter(function (_ref) { 27695 var techName = _ref[0], 27696 tech = _ref[1]; 27697 27698 // Check if the current tech is defined before continuing 27699 if (tech) { 27700 // Check if the browser supports this technology 27701 return tech.isSupported(); 27702 } 27703 27704 log$1.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 27705 return false; 27706 }); // Iterate over each `innerArray` element once per `outerArray` element and execute 27707 // `tester` with both. If `tester` returns a non-falsy value, exit early and return 27708 // that value. 27709 27710 var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { 27711 var found; 27712 outerArray.some(function (outerChoice) { 27713 return innerArray.some(function (innerChoice) { 27714 found = tester(outerChoice, innerChoice); 27715 27716 if (found) { 27717 return true; 27718 } 27719 }); 27720 }); 27721 return found; 27722 }; 27723 27724 var foundSourceAndTech; 27725 27726 var flip = function flip(fn) { 27727 return function (a, b) { 27728 return fn(b, a); 27729 }; 27730 }; 27731 27732 var finder = function finder(_ref2, source) { 27733 var techName = _ref2[0], 27734 tech = _ref2[1]; 27735 27736 if (tech.canPlaySource(source, _this13.options_[techName.toLowerCase()])) { 27737 return { 27738 source: source, 27739 tech: techName 27740 }; 27741 } 27742 }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources 27743 // to select from them based on their priority. 27744 27745 27746 if (this.options_.sourceOrder) { 27747 // Source-first ordering 27748 foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); 27749 } else { 27750 // Tech-first ordering 27751 foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); 27752 } 27753 27754 return foundSourceAndTech || false; 27755 } 27756 /** 27757 * Executes source setting and getting logic 27758 * 27759 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 27760 * A SourceObject, an array of SourceObjects, or a string referencing
27761 * a URL to a media source. It is _highly recommended_ that an object 27762 * or array of objects is used here, so that source selection 27763 * algorithms can take the `type` into account. 27764 * 27765 * If not provided, this method acts as a getter. 27766 * @param {boolean} isRetry 27767 * Indicates whether this is being called internally as a result of a retry 27768 * 27769 * @return {string|undefined} 27770 * If the `source` argument is missing, returns the current source 27771 * URL. Otherwise, returns nothing/undefined. 27772 */ 27773 ; 27774 27775 _proto.handleSrc_ = function handleSrc_(source, isRetry) { 27776 var _this14 = this; 27777 27778 // getter usage 27779 if (typeof source === 'undefined') { 27780 return this.cache_.src || ''; 27781 } // Reset retry behavior for new source 27782 27783 27784 if (this.resetRetryOnError_) { 27785 this.resetRetryOnError_(); 27786 } // filter out invalid sources and turn our source into 27787 // an array of source objects 27788 27789 27790 var sources = filterSource(source); // if a source was passed in then it is invalid because 27791 // it was filtered to a zero length Array. So we have to 27792 // show an error 27793 27794 if (!sources.length) { 27795 this.setTimeout(function () { 27796 this.error({ 27797 code: 4, 27798 message: this.localize(this.options_.notSupportedMessage) 27799 }); 27800 }, 0); 27801 return; 27802 } // initial sources 27803 27804 27805 this.changingSrc_ = true; // Only update the cached source list if we are not retrying a new source after error, 27806 // since in that case we want to include the failed source(s) in the cache 27807 27808 if (!isRetry) { 27809 this.cache_.sources = sources; 27810 } 27811 27812 this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware 27813 27814 setSource(this, sources[0], function (middlewareSource, mws) { 27815 _this14.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since 27816 // the source that is selected could be out of order from the cache update above this callback. 27817 27818 if (!isRetry) { 27819 _this14.cache_.sources = sources; 27820 } 27821 27822 _this14.updateSourceCaches_(middlewareSource); 27823 27824 var err = _this14.src_(middlewareSource); 27825 27826 if (err) { 27827 if (sources.length > 1) { 27828 return _this14.handleSrc_(sources.slice(1)); 27829 } 27830 27831 _this14.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers 27832 27833 _this14.setTimeout(function () { 27834 this.error({ 27835 code: 4, 27836 message: this.localize(this.options_.notSupportedMessage) 27837 }); 27838 }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it 27839 // this needs a better comment about why this is needed 27840 27841 27842 _this14.triggerReady(); 27843 27844 return; 27845 } 27846 27847 setTech(mws, _this14.tech_); 27848 }); // Try another available source if this one fails before playback. 27849 27850 if (this.options_.retryOnError && sources.length > 1) { 27851 var retry = function retry() { 27852 // Remove the error modal 27853 _this14.error(null); 27854 27855 _this14.handleSrc_(sources.slice(1), true); 27856 }; 27857 27858 var stopListeningForErrors = function stopListeningForErrors() { 27859 _this14.off('error', retry); 27860 }; 27861 27862 this.one('error', retry); 27863 this.one('playing', stopListeningForErrors); 27864 27865 this.resetRetryOnError_ = function () { 27866 _this14.off('error', retry); 27867 27868 _this14.off('playing', stopListeningForErrors); 27869 }; 27870 } 27871 } 27872 /** 27873 * Get or set the video source. 27874 * 27875 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 27876 * A SourceObject, an array of SourceObjects, or a string referencing 27877 * a URL to a media source. It is _highly recommended_ that an object 27878 * or array of objects is used here, so that source selection 27879 * algorithms can take the `type` into account. 27880 * 27881 * If not provided, this method acts as a getter. 27882 * 27883 * @return {string|undefined} 27884 * If the `source` argument is missing, returns the current source 27885 * URL. Otherwise, returns nothing/undefined. 27886 */ 27887 ; 27888 27889 _proto.src = function src(source) { 27890 return this.handleSrc_(source, false); 27891 } 27892 /** 27893 * Set the source object on the tech, returns a boolean that indicates whether 27894 * there is a tech that can play the source or not 27895 * 27896 * @param {Tech~SourceObject} source 27897 * The source object to set on the Tech 27898 * 27899 * @return {boolean} 27900 * - True if there is no Tech to playback this source 27901 * - False otherwise 27902 * 27903 * @private 27904 */ 27905 ; 27906 27907 _proto.src_ = function src_(source) { 27908 var _this15 = this; 27909 27910 var sourceTech = this.selectSource([source]); 27911 27912 if (!sourceTech) { 27913 return true; 27914 } 27915 27916 if (!titleCaseEquals(sourceTech.tech, this.techName_)) { 27917 this.changingSrc_ = true; // load this technology with the chosen source 27918 27919 this.loadTech_(sourceTech.tech, sourceTech.source); 27920 this.tech_.ready(function () { 27921 _this15.changingSrc_ = false; 27922 }); 27923 return false; 27924 } // wait until the tech is ready to set the source 27925 // and set it synchronously if possible (#2326) 27926 27927 27928 this.ready(function () { 27929 // The setSource tech method was added with source handlers 27930 // so older techs won't support it 27931 // We need to check the direct prototype for the case where subclasses 27932 // of the tech do not support source handlers 27933 if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { 27934 this.techCall_('setSource', source); 27935 } else { 27936 this.techCall_('src', source.src); 27937 } 27938 27939 this.changingSrc_ = false; 27940 }, true); 27941 return false; 27942 } 27943 /** 27944 * Begin loading the src data. 27945 */ 27946 ; 27947 27948 _proto.load = function load() { 27949 this.techCall_('load'); 27950 } 27951 /** 27952 * Reset the player. Loads the first tech in the techOrder,
27953 * removes all the text tracks in the existing `tech`, 27954 * and calls `reset` on the `tech`. 27955 */ 27956 ; 27957 27958 _proto.reset = function reset() { 27959 var _this16 = this; 27960 27961 var PromiseClass = this.options_.Promise || window_1.Promise; 27962 27963 if (this.paused() || !PromiseClass) { 27964 this.doReset_(); 27965 } else { 27966 var playPromise = this.play(); 27967 silencePromise(playPromise.then(function () { 27968 return _this16.doReset_(); 27969 })); 27970 } 27971 }; 27972 27973 _proto.doReset_ = function doReset_() { 27974 if (this.tech_) { 27975 this.tech_.clearTracks('text'); 27976 } 27977 27978 this.resetCache_(); 27979 this.poster(''); 27980 this.loadTech_(this.options_.techOrder[0], null); 27981 this.techCall_('reset'); 27982 this.resetControlBarUI_(); 27983 27984 if (isEvented(this)) { 27985 this.trigger('playerreset'); 27986 } 27987 } 27988 /** 27989 * Reset Control Bar's UI by calling sub-methods that reset 27990 * all of Control Bar's components 27991 */ 27992 ; 27993 27994 _proto.resetControlBarUI_ = function resetControlBarUI_() { 27995 this.resetProgressBar_(); 27996 this.resetPlaybackRate_(); 27997 this.resetVolumeBar_(); 27998 } 27999 /** 28000 * Reset tech's progress so progress bar is reset in the UI 28001 */ 28002 ; 28003 28004 _proto.resetProgressBar_ = function resetProgressBar_() { 28005 this.currentTime(0); 28006 var _this$controlBar = this.controlBar, 28007 durationDisplay = _this$controlBar.durationDisplay, 28008 remainingTimeDisplay = _this$controlBar.remainingTimeDisplay; 28009 28010 if (durationDisplay) { 28011 durationDisplay.updateContent(); 28012 } 28013 28014 if (remainingTimeDisplay) { 28015 remainingTimeDisplay.updateContent(); 28016 } 28017 } 28018 /** 28019 * Reset Playback ratio 28020 */ 28021 ; 28022 28023 _proto.resetPlaybackRate_ = function resetPlaybackRate_() { 28024 this.playbackRate(this.defaultPlaybackRate()); 28025 this.handleTechRateChange_(); 28026 } 28027 /** 28028 * Reset Volume bar 28029 */ 28030 ; 28031 28032 _proto.resetVolumeBar_ = function resetVolumeBar_() { 28033 this.volume(1.0); 28034 this.trigger('volumechange'); 28035 } 28036 /** 28037 * Returns all of the current source objects. 28038 * 28039 * @return {Tech~SourceObject[]} 28040 * The current source objects 28041 */ 28042 ; 28043 28044 _proto.currentSources = function currentSources() { 28045 var source = this.currentSource(); 28046 var sources = []; // assume `{}` or `{ src }` 28047 28048 if (Object.keys(source).length !== 0) { 28049 sources.push(source); 28050 } 28051 28052 return this.cache_.sources || sources; 28053 } 28054 /** 28055 * Returns the current source object. 28056 * 28057 * @return {Tech~SourceObject} 28058 * The current source object 28059 */ 28060 ; 28061 28062 _proto.currentSource = function currentSource() { 28063 return this.cache_.source || {}; 28064 } 28065 /** 28066 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 28067 * Can be used in conjunction with `currentType` to assist in rebuilding the current source object. 28068 * 28069 * @return {string} 28070 * The current source 28071 */ 28072 ; 28073 28074 _proto.currentSrc = function currentSrc() { 28075 return this.currentSource() && this.currentSource().src || ''; 28076 } 28077 /** 28078 * Get the current source type e.g. video/mp4 28079 * This can allow you rebuild the current source object so that you could load the same 28080 * source and tech later 28081 * 28082 * @return {string} 28083 * The source MIME type 28084 */ 28085 ; 28086 28087 _proto.currentType = function currentType() { 28088 return this.currentSource() && this.currentSource().type || ''; 28089 } 28090 /** 28091 * Get or set the preload attribute 28092 * 28093 * @param {boolean} [value] 28094 * - true means that we should preload 28095 * - false means that we should not preload 28096 * 28097 * @return {string} 28098 * The preload attribute value when getting 28099 */ 28100 ; 28101 28102 _proto.preload = function preload(value) { 28103 if (value !== undefined) { 28104 this.techCall_('setPreload', value); 28105 this.options_.preload = value; 28106 return; 28107 } 28108 28109 return this.techGet_('preload'); 28110 } 28111 /** 28112 * Get or set the autoplay option. When this is a boolean it will 28113 * modify the attribute on the tech. When this is a string the attribute on 28114 * the tech will be removed and `Player` will handle autoplay on loadstarts. 28115 * 28116 * @param {boolean|string} [value] 28117 * - true: autoplay using the browser behavior 28118 * - false: do not autoplay 28119 * - 'play': call play() on every loadstart 28120 * - 'muted': call muted() then play() on every loadstart 28121 * - 'any': call play() on every loadstart. if that fails call muted() then play(). 28122 * - *: values other than those listed here will be set `autoplay` to true 28123 * 28124 * @return {boolean|string} 28125 * The current value of autoplay when getting 28126 */ 28127 ; 28128 28129 _proto.autoplay = function autoplay(value) { 28130 // getter usage 28131 if (value === undefined) { 28132 return this.options_.autoplay || false; 28133 } 28134 28135 var techAutoplay; // if the value is a valid string set it to that, or normalize `true` to 'play', if need be 28136 28137 if (typeof value === 'string' && /(any|play|muted)/.test(value) || value === true && this.options_.normalizeAutoplay) { 28138 this.options_.autoplay = value; 28139 this.manualAutoplay_(typeof value === 'string' ? value : 'play'); 28140 techAutoplay = false;
28140 // any falsy value sets autoplay to false in the browser, 28141 // lets do the same 28142 } else if (!value) { 28143 this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true 28144 } else { 28145 this.options_.autoplay = true; 28146 } 28147 28148 techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up 28149 // a setAutoplay call on tech ready. We do this because the 28150 // autoplay option will be passed in the constructor and we 28151 // do not need to set it twice 28152 28153 if (this.tech_) { 28154 this.techCall_('setAutoplay', techAutoplay); 28155 } 28156 } 28157 /** 28158 * Set or unset the playsinline attribute. 28159 * Playsinline tells the browser that non-fullscreen playback is preferred. 28160 * 28161 * @param {boolean} [value] 28162 * - true means that we should try to play inline by default 28163 * - false means that we should use the browser's default playback mode, 28164 * which in most cases is inline. iOS Safari is a notable exception 28165 * and plays fullscreen by default. 28166 * 28167 * @return {string|Player} 28168 * - the current value of playsinline 28169 * - the player when setting 28170 * 28171 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 28172 */ 28173 ; 28174 28175 _proto.playsinline = function playsinline(value) { 28176 if (value !== undefined) { 28177 this.techCall_('setPlaysinline', value); 28178 this.options_.playsinline = value; 28179 return this; 28180 } 28181 28182 return this.techGet_('playsinline'); 28183 } 28184 /** 28185 * Get or set the loop attribute on the video element. 28186 * 28187 * @param {boolean} [value] 28188 * - true means that we should loop the video 28189 * - false means that we should not loop the video 28190 * 28191 * @return {boolean} 28192 * The current value of loop when getting 28193 */ 28194 ; 28195 28196 _proto.loop = function loop(value) { 28197 if (value !== undefined) { 28198 this.techCall_('setLoop', value); 28199 this.options_.loop = value; 28200 return; 28201 } 28202 28203 return this.techGet_('loop'); 28204 } 28205 /** 28206 * Get or set the poster image source url 28207 * 28208 * @fires Player#posterchange 28209 * 28210 * @param {string} [src] 28211 * Poster image source URL 28212 * 28213 * @return {string} 28214 * The current value of poster when getting 28215 */ 28216 ; 28217 28218 _proto.poster = function poster(src) { 28219 if (src === undefined) { 28220 return this.poster_; 28221 } // The correct way to remove a poster is to set as an empty string 28222 // other falsey values will throw errors 28223 28224 28225 if (!src) { 28226 src = ''; 28227 } 28228 28229 if (src === this.poster_) { 28230 return; 28231 } // update the internal poster variable 28232 28233 28234 this.poster_ = src; // update the tech's poster 28235 28236 this.techCall_('setPoster', src); 28237 this.isPosterFromTech_ = false; // alert components that the poster has been set 28238 28239 /** 28240 * This event fires when the poster image is changed on the player. 28241 * 28242 * @event Player#posterchange 28243 * @type {EventTarget~Event} 28244 */ 28245 28246 this.trigger('posterchange'); 28247 } 28248 /** 28249 * Some techs (e.g. YouTube) can provide a poster source in an 28250 * asynchronous way. We want the poster component to use this 28251 * poster source so that it covers up the tech's controls. 28252 * (YouTube's play button). However we only want to use this 28253 * source if the player user hasn't set a poster through 28254 * the normal APIs. 28255 * 28256 * @fires Player#posterchange 28257 * @listens Tech#posterchange 28258 * @private 28259 */ 28260 ; 28261 28262 _proto.handleTechPosterChange_ = function handleTechPosterChange_() { 28263 if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) { 28264 var newPoster = this.tech_.poster() || ''; 28265 28266 if (newPoster !== this.poster_) { 28267 this.poster_ = newPoster; 28268 this.isPosterFromTech_ = true; // Let components know the poster has changed 28269 28270 this.trigger('posterchange'); 28271 } 28272 } 28273 } 28274 /** 28275 * Get or set whether or not the controls are showing. 28276 * 28277 * @fires Player#controlsenabled 28278 * 28279 * @param {boolean} [bool] 28280 * - true to turn controls on 28281 * - false to turn controls off 28282 * 28283 * @return {boolean} 28284 * The current value of controls when getting 28285 */ 28286 ; 28287 28288 _proto.controls = function controls(bool) { 28289 if (bool === undefined) { 28290 return !!this.controls_; 28291 } 28292 28293 bool = !!bool; // Don't trigger a change event unless it actually changed 28294 28295 if (this.controls_ === bool) { 28296 return; 28297 } 28298 28299 this.controls_ = bool; 28300 28301 if (this.usingNativeControls()) { 28302 this.techCall_('setControls', bool); 28303 } 28304 28305 if (this.controls_) { 28306 this.removeClass('vjs-controls-disabled'); 28307 this.addClass('vjs-controls-enabled'); 28308 /** 28309 * @event Player#controlsenabled 28310 * @type {EventTarget~Event} 28311 */ 28312 28313 this.trigger('controlsenabled'); 28314 28315 if (!this.usingNativeControls()) {
28316 this.addTechControlsListeners_(); 28317 } 28318 } else { 28319 this.removeClass('vjs-controls-enabled'); 28320 this.addClass('vjs-controls-disabled'); 28321 /** 28322 * @event Player#controlsdisabled 28323 * @type {EventTarget~Event} 28324 */ 28325 28326 this.trigger('controlsdisabled'); 28327 28328 if (!this.usingNativeControls()) { 28329 this.removeTechControlsListeners_(); 28330 } 28331 } 28332 } 28333 /** 28334 * Toggle native controls on/off. Native controls are the controls built into 28335 * devices (e.g. default iPhone controls) or other techs 28336 * (e.g. Vimeo Controls) 28337 * **This should only be set by the current tech, because only the tech knows 28338 * if it can support native controls** 28339 * 28340 * @fires Player#usingnativecontrols 28341 * @fires Player#usingcustomcontrols 28342 * 28343 * @param {boolean} [bool] 28344 * - true to turn native controls on 28345 * - false to turn native controls off 28346 * 28347 * @return {boolean} 28348 * The current value of native controls when getting 28349 */ 28350 ; 28351 28352 _proto.usingNativeControls = function usingNativeControls(bool) { 28353 if (bool === undefined) { 28354 return !!this.usingNativeControls_; 28355 } 28356 28357 bool = !!bool; // Don't trigger a change event unless it actually changed 28358 28359 if (this.usingNativeControls_ === bool) { 28360 return; 28361 } 28362 28363 this.usingNativeControls_ = bool; 28364 28365 if (this.usingNativeControls_) { 28366 this.addClass('vjs-using-native-controls'); 28367 /** 28368 * player is using the native device controls 28369 * 28370 * @event Player#usingnativecontrols 28371 * @type {EventTarget~Event} 28372 */ 28373 28374 this.trigger('usingnativecontrols'); 28375 } else { 28376 this.removeClass('vjs-using-native-controls'); 28377 /** 28378 * player is using the custom HTML controls 28379 * 28380 * @event Player#usingcustomcontrols 28381 * @type {EventTarget~Event} 28382 */ 28383 28384 this.trigger('usingcustomcontrols'); 28385 } 28386 } 28387 /** 28388 * Set or get the current MediaError 28389 * 28390 * @fires Player#error 28391 * 28392 * @param {MediaError|string|number} [err] 28393 * A MediaError or a string/number to be turned 28394 * into a MediaError 28395 * 28396 * @return {MediaError|null} 28397 * The current MediaError when getting (or null) 28398 */ 28399 ; 28400 28401 _proto.error = function error(err) { 28402 var _this17 = this; 28403 28404 if (err === undefined) { 28405 return this.error_ || null; 28406 } // allow hooks to modify error object 28407 28408 28409 hooks('beforeerror').forEach(function (hookFunction) { 28410 var newErr = hookFunction(_this17, err); 28411 28412 if (!(isObject$1(newErr) && !Array.isArray(newErr) || typeof newErr === 'string' || typeof newErr === 'number' || newErr === null)) { 28413 _this17.log.error('please return a value that MediaError expects in beforeerror hooks'); 28414 28415 return; 28416 } 28417 28418 err = newErr; 28419 }); // Suppress the first error message for no compatible source until 28420 // user interaction 28421 28422 if (this.options_.suppressNotSupportedError && err && err.code === 4) { 28423 var triggerSuppressedError = function triggerSuppressedError() { 28424 this.error(err); 28425 }; 28426 28427 this.options_.suppressNotSupportedError = false; 28428 this.any(['click', 'touchstart'], triggerSuppressedError); 28429 this.one('loadstart', function () { 28430 this.off(['click', 'touchstart'], triggerSuppressedError); 28431 }); 28432 return; 28433 } // restoring to default 28434 28435 28436 if (err === null) { 28437 this.error_ = err; 28438 this.removeClass('vjs-error'); 28439 28440 if (this.errorDisplay) { 28441 this.errorDisplay.close(); 28442 } 28443 28444 return; 28445 } 28446 28447 this.error_ = new MediaError(err); // add the vjs-error classname to the player 28448 28449 if(this.error_.code == '2') { 28450 28451 var endEl = this.el_.parentNode.parentNode.querySelector('.video__guide.video__end'); 28452 if(endEl && endEl.getAttribute('style')) { 28453 var confirmEl = this.el_.parentNode.parentNode.querySelector('.video__guide.video__data-use'); 28454 confirmEl.setAttribute('style','display:none'); 28455 28456 var refreshEl = this.el_.parentNode.parentNode.querySelector('#video-player-reload'); 28457 refreshEl.removeAttribute('style'); 28458 if(initMotion) initMotion(); 28459 //this.el_.parentNode.removeChild(this.el_); 28460 } 28461
28462 log$1.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_); 28463 return; 28464 28465 } else { 28466 28467 this.addClass('vjs-error'); // log the name of the error type and any message 28468 // IE11 logs "[object object]" and required you to expand message to see error object 28469 28470 log$1.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_); 28471 } 28472 28473 /** 28474 * @event Player#error 28475 * @type {EventTarget~Event} 28476 */ 28477 28478 this.trigger('error'); // notify hooks of the per player error 28479 28480 hooks('error').forEach(function (hookFunction) { 28481 return hookFunction(_this17, _this17.error_); 28482 }); 28483 return; 28484 } 28485 /** 28486 * Report user activity 28487 * 28488 * @param {Object} event 28489 * Event object 28490 */ 28491 ; 28492 28493 _proto.reportUserActivity = function reportUserActivity(event) { 28494 this.userActivity_ = true; 28495 } 28496 /** 28497 * Get/set if user is active 28498 * 28499 * @fires Player#useractive 28500 * @fires Player#userinactive 28501 * 28502 * @param {boolean} [bool] 28503 * - true if the user is active 28504 * - false if the user is inactive 28505 * 28506 * @return {boolean} 28507 * The current value of userActive when getting 28508 */ 28509 ; 28510 28511 _proto.userActive = function userActive(bool) { 28512 if (bool === undefined) { 28513 return this.userActive_; 28514 } 28515 28516 bool = !!bool; 28517 28518 if (bool === this.userActive_) { 28519 return; 28520 } 28521 28522 this.userActive_ = bool; 28523 28524 if (this.userActive_) { 28525 this.userActivity_ = true; 28526 this.removeClass('vjs-user-inactive'); 28527 this.addClass('vjs-user-active'); 28528 /** 28529 * @event Player#useractive 28530 * @type {EventTarget~Event} 28531 */ 28532 28533 this.trigger('useractive'); 28534 return; 28535 } // Chrome/Safari/IE have bugs where when you change the cursor it can 28536 // trigger a mousemove event. This causes an issue when you're hiding 28537 // the cursor when the user is inactive, and a mousemove signals user 28538 // activity. Making it impossible to go into inactive mode. Specifically 28539 // this happens in fullscreen when we really need to hide the cursor. 28540 // 28541 // When this gets resolved in ALL browsers it can be removed 28542 // https://code.google.com/p/chromium/issues/detail?id=103041 28543 28544 28545 if (this.tech_) { 28546 this.tech_.one('mousemove', function (e) { 28547 e.stopPropagation(); 28548 e.preventDefault(); 28549 }); 28550 } 28551 28552 this.userActivity_ = false; 28553 this.removeClass('vjs-user-active'); 28554 this.addClass('vjs-user-inactive'); 28555 /** 28556 * @event Player#userinactive 28557 * @type {EventTarget~Event} 28558 */ 28559 28560 this.trigger('userinactive'); 28561 } 28562 /** 28563 * Listen for user activity based on timeout value 28564 * 28565 * @private 28566 */ 28567 ; 28568 28569 _proto.listenForUserActivity_ = function listenForUserActivity_() { 28570 var mouseInProgress; 28571 var lastMoveX; 28572 var lastMoveY; 28573 var handleActivity = bind(this, this.reportUserActivity); 28574 28575 var handleMouseMove = function handleMouseMove(e) { 28576 // #1068 - Prevent mousemove spamming 28577 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 28578 if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { 28579 lastMoveX = e.screenX; 28580 lastMoveY = e.screenY; 28581 handleActivity(); 28582 } 28583 }; 28584 28585 var handleMouseDown = function handleMouseDown() { 28586 handleActivity(); // For as long as the they are touching the device or have their mouse down, 28587 // we consider them active even if they're not moving their finger or mouse. 28588 // So we want to continue to update that they are active 28589 28590 this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time 28591 // as the activityCheck interval (250) should ensure we never miss the 28592 // next activityCheck 28593 28594 mouseInProgress = this.setInterval(handleActivity, 250); 28595 }; 28596 28597 var handleMouseUpAndMouseLeave = function handleMouseUpAndMouseLeave(event) { 28598 handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down 28599 28600 this.clearInterval(mouseInProgress); 28601 }; // Any mouse movement will be considered user activity 28602 28603 28604 this.on('mousedown', handleMouseDown);
28605 this.on('mousemove', handleMouseMove); 28606 this.on('mouseup', handleMouseUpAndMouseLeave); 28607 this.on('mouseleave', handleMouseUpAndMouseLeave); 28608 var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed) 28609 // controlBar would no longer be hidden by default timeout. 28610 28611 if (controlBar && !IS_IOS && !IS_ANDROID) { 28612 controlBar.on('mouseenter', function (event) { 28613 if (this.player().options_.inactivityTimeout !== 0) { 28614 this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout; 28615 } 28616 28617 this.player().options_.inactivityTimeout = 0; 28618 }); 28619 controlBar.on('mouseleave', function (event) { 28620 this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout; 28621 }); 28622 } // Listen for keyboard navigation 28623 // Shouldn't need to use inProgress interval because of key repeat 28624 28625 28626 this.on('keydown', handleActivity); 28627 this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into 28628 // the mousemove/touchmove function itself, to prevent performance degradation. 28629 // `this.reportUserActivity` simply sets this.userActivity_ to true, which 28630 // then gets picked up by this loop 28631 // http://ejohn.org/blog/learning-from-twitter/ 28632 28633 var inactivityTimeout; 28634 this.setInterval(function () { 28635 // Check to see if mouse/touch activity has happened 28636 if (!this.userActivity_) { 28637 return; 28638 } // Reset the activity tracker 28639 28640 28641 this.userActivity_ = false; // If the user state was inactive, set the state to active 28642 28643 this.userActive(true); // Clear any existing inactivity timeout to start the timer over 28644 28645 this.clearTimeout(inactivityTimeout); 28646 var timeout = this.options_.inactivityTimeout; 28647 28648 if (timeout <= 0) { 28649 return; 28650 } // In <timeout> milliseconds, if no more activity has occurred the 28651 // user will be considered inactive 28652 28653 28654 inactivityTimeout = this.setTimeout(function () { 28655 // Protect against the case where the inactivityTimeout can trigger just 28656 // before the next user activity is picked up by the activity check loop 28657 // causing a flicker 28658 if (!this.userActivity_) { 28659 this.userActive(false); 28660 } 28661 }, timeout); 28662 }, 250); 28663 } 28664 /** 28665 * Gets or sets the current playback rate. A playback rate of 28666 * 1.0 represents normal speed and 0.5 would indicate half-speed 28667 * playback, for instance. 28668 * 28669 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate 28670 * 28671 * @param {number} [rate] 28672 * New playback rate to set. 28673 * 28674 * @return {number} 28675 * The current playback rate when getting or 1.0 28676 */ 28677 ; 28678 28679 _proto.playbackRate = function playbackRate(rate) { 28680 if (rate !== undefined) { 28681 // NOTE: this.cache_.lastPlaybackRate is set from the tech handler 28682 // that is registered above 28683 this.techCall_('setPlaybackRate', rate); 28684 return; 28685 } 28686 28687 if (this.tech_ && this.tech_.featuresPlaybackRate) { 28688 return this.cache_.lastPlaybackRate || this.techGet_('playbackRate'); 28689 } 28690 28691 return 1.0; 28692 } 28693 /** 28694 * Gets or sets the current default playback rate. A default playback rate of 28695 * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. 28696 * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not 28697 * not the current playbackRate. 28698 * 28699 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate 28700 * 28701 * @param {number} [rate] 28702 * New default playback rate to set. 28703 * 28704 * @return {number|Player} 28705 * - The default playback rate when getting or 1.0 28706 * - the player when setting 28707 */ 28708 ; 28709 28710 _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) { 28711 if (rate !== undefined) { 28712 return this.techCall_('setDefaultPlaybackRate', rate); 28713 } 28714 28715 if (this.tech_ && this.tech_.featuresPlaybackRate) { 28716 return this.techGet_('defaultPlaybackRate'); 28717 } 28718 28719 return 1.0; 28720 } 28721 /** 28722 * Gets or sets the audio flag 28723 * 28724 * @param {boolean} bool 28725 * - true signals that this is an audio player 28726 * - false signals that this is not an audio player 28727 * 28728 * @return {boolean} 28729 * The current value of isAudio when getting 28730 */ 28731 ; 28732 28733 _proto.isAudio = function isAudio(bool) { 28734 if (bool !== undefined) { 28735 this.isAudio_ = !!bool; 28736 return; 28737 } 28738 28739 return !!this.isAudio_; 28740 } 28741 /** 28742 * A helper method for adding a {@link TextTrack} to our 28743 * {@link TextTrackList}. 28744 * 28745 * In addition to the W3C settings we allow adding additional info through options. 28746 * 28747 * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack 28748 * 28749 * @param {string} [kind] 28750 * the kind of TextTrack you are adding 28751 * 28752 * @param {string} [label] 28753 * the label to give the TextTrack label 28754 * 28755 * @param {string} [language] 28756 * the language to set on the TextTrack 28757 * 28758 * @return {TextTrack|undefined} 28759 * the TextTrack that was added or undefined 28760 * if there is no tech 28761 */ 28762 ; 28763 28764 _proto.addTextTrack = function addTextTrack(kind, label, language) { 28765 if (this.tech_) { 28766 return this.tech_.addTextTrack(kind, label, language); 28767 } 28768 } 28769 /** 28770 * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. 28771 * When manualCleanup is set to false, the track will be automatically removed 28772 * on source changes. 28773 * 28774 * @param {Object} options 28775 * Options to pass to {@link HTMLTrackElement} during creation. See 28776 * {@link HTMLTrackElement} for object properties that you should use. 28777 * 28778 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 28779 * removed on a source change 28780 * 28781 * @return {HtmlTrackElement} 28782 * the HTMLTrackElement that was created and added 28783 * to the HtmlTrackElementList and the remote 28784 * TextTrackList 28785 * 28786 * @deprecated The default value of the "manualCleanup" parameter will default
28787 * to "false" in upcoming versions of Video.js 28788 */ 28789 ; 28790 28791 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 28792 if (this.tech_) { 28793 return this.tech_.addRemoteTextTrack(options, manualCleanup); 28794 } 28795 } 28796 /** 28797 * Remove a remote {@link TextTrack} from the respective 28798 * {@link TextTrackList} and {@link HtmlTrackElementList}. 28799 * 28800 * @param {Object} track 28801 * Remote {@link TextTrack} to remove 28802 * 28803 * @return {undefined} 28804 * does not return anything 28805 */ 28806 ; 28807 28808 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) { 28809 if (obj === void 0) { 28810 obj = {}; 28811 } 28812 28813 var _obj = obj, 28814 track = _obj.track; 28815 28816 if (!track) { 28817 track = obj; 28818 } // destructure the input into an object with a track argument, defaulting to arguments[0] 28819 // default the whole argument to an empty object if nothing was passed in 28820 28821 28822 if (this.tech_) { 28823 return this.tech_.removeRemoteTextTrack(track); 28824 } 28825 } 28826 /** 28827 * Gets available media playback quality metrics as specified by the W3C's Media 28828 * Playback Quality API. 28829 * 28830 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 28831 * 28832 * @return {Object|undefined} 28833 * An object with supported media playback quality metrics or undefined if there 28834 * is no tech or the tech does not support it. 28835 */ 28836 ; 28837 28838 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 28839 return this.techGet_('getVideoPlaybackQuality'); 28840 } 28841 /** 28842 * Get video width 28843 * 28844 * @return {number} 28845 * current video width 28846 */ 28847 ; 28848 28849 _proto.videoWidth = function videoWidth() { 28850 return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; 28851 } 28852 /** 28853 * Get video height 28854 * 28855 * @return {number} 28856 * current video height 28857 */ 28858 ; 28859 28860 _proto.videoHeight = function videoHeight() { 28861 return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; 28862 } 28863 /** 28864 * The player's language code. 28865 * 28866 * Changing the langauge will trigger 28867 * [languagechange]{@link Player#event:languagechange} 28868 * which Components can use to update control text. 28869 * ClickableComponent will update its control text by default on 28870 * [languagechange]{@link Player#event:languagechange}. 28871 * 28872 * @fires Player#languagechange 28873 * 28874 * @param {string} [code] 28875 * the language code to set the player to 28876 * 28877 * @return {string} 28878 * The current language code when getting 28879 */ 28880 ; 28881 28882 _proto.language = function language(code) { 28883 if (code === undefined) { 28884 return this.language_; 28885 } 28886 28887 if (this.language_ !== String(code).toLowerCase()) { 28888 this.language_ = String(code).toLowerCase(); // during first init, it's possible some things won't be evented 28889 28890 if (isEvented(this)) { 28891 /** 28892 * fires when the player language change 28893 * 28894 * @event Player#languagechange 28895 * @type {EventTarget~Event} 28896 */ 28897 this.trigger('languagechange'); 28898 } 28899 } 28900 } 28901 /** 28902 * Get the player's language dictionary 28903 * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time 28904 * Languages specified directly in the player options have precedence 28905 * 28906 * @return {Array} 28907 * An array of of supported languages 28908 */ 28909 ; 28910 28911 _proto.languages = function languages() { 28912 return mergeOptions$3(Player.prototype.options_.languages, this.languages_); 28913 } 28914 /** 28915 * returns a JavaScript object reperesenting the current track 28916 * information. **DOES not return it as JSON** 28917 * 28918 * @return {Object} 28919 * Object representing the current of track info 28920 */ 28921 ; 28922 28923 _proto.toJSON = function toJSON() { 28924 var options = mergeOptions$3(this.options_); 28925 var tracks = options.tracks; 28926 options.tracks = []; 28927 28928 for (var i = 0; i < tracks.length; i++) { 28929 var track = tracks[i]; // deep merge tracks and null out player so no circular references 28930 28931 track = mergeOptions$3(track); 28932 track.player = undefined; 28933 options.tracks[i] = track; 28934 } 28935 28936 return options; 28937 } 28938 /** 28939 * Creates a simple modal dialog (an instance of the {@link ModalDialog} 28940 * component) that immediately overlays the player with arbitrary 28941 * content and removes itself when closed. 28942 * 28943 * @param {string|Function|Element|Array|null} content 28944 * Same as {@link ModalDialog#content}'s param of the same name. 28945 * The most straight-forward usage is to provide a string or DOM 28946 * element. 28947 * 28948 * @param {Object} [options] 28949 * Extra options which will be passed on to the {@link ModalDialog}. 28950 * 28951 * @return {ModalDialog} 28952 * the {@link ModalDialog} that was created 28953 */ 28954 ; 28955 28956 _proto.createModal = function createModal(content, options) { 28957 var _this18 = this; 28958 28959 options = options || {}; 28960 options.content = content || ''; 28961 var modal = new ModalDialog(this, options); 28962 this.addChild(modal); 28963 modal.on('dispose', function () { 28964 _this18.removeChild(modal); 28965 }); 28966 modal.open(); 28967 return modal; 28968 } 28969 /** 28970 * Change breakpoint classes when the player resizes. 28971 * 28972 * @private 28973 */ 28974 ; 28975 28976 _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() { 28977 if (!this.responsive()) { 28978 return; 28979 } 28980 28981 var currentBreakpoint = this.currentBreakpoint(); 28982 var currentWidth = this.currentWidth(); 28983 28984 for (var i = 0; i < BREAKPOINT_ORDER.length; i++) { 28985 var candidateBreakpoint = BREAKPOINT_ORDER[i]; 28986 var maxWidth = this.breakpoints_[candidateBreakpoint]; 28987 28988 if (currentWidth <= maxWidth) {
28989 // The current breakpoint did not change, nothing to do. 28990 if (currentBreakpoint === candidateBreakpoint) { 28991 return; 28992 } // Only remove a class if there is a current breakpoint. 28993 28994 28995 if (currentBreakpoint) { 28996 this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]); 28997 } 28998 28999 this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]); 29000 this.breakpoint_ = candidateBreakpoint; 29001 break; 29002 } 29003 } 29004 } 29005 /** 29006 * Removes the current breakpoint. 29007 * 29008 * @private 29009 */ 29010 ; 29011 29012 _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() { 29013 var className = this.currentBreakpointClass(); 29014 this.breakpoint_ = ''; 29015 29016 if (className) { 29017 this.removeClass(className); 29018 } 29019 } 29020 /** 29021 * Get or set breakpoints on the player. 29022 * 29023 * Calling this method with an object or `true` will remove any previous 29024 * custom breakpoints and start from the defaults again. 29025 * 29026 * @param {Object|boolean} [breakpoints] 29027 * If an object is given, it can be used to provide custom 29028 * breakpoints. If `true` is given, will set default breakpoints. 29029 * If this argument is not given, will simply return the current 29030 * breakpoints. 29031 * 29032 * @param {number} [breakpoints.tiny] 29033 * The maximum width for the "vjs-layout-tiny" class. 29034 * 29035 * @param {number} [breakpoints.xsmall] 29036 * The maximum width for the "vjs-layout-x-small" class. 29037 * 29038 * @param {number} [breakpoints.small] 29039 * The maximum width for the "vjs-layout-small" class. 29040 * 29041 * @param {number} [breakpoints.medium] 29042 * The maximum width for the "vjs-layout-medium" class. 29043 * 29044 * @param {number} [breakpoints.large] 29045 * The maximum width for the "vjs-layout-large" class. 29046 * 29047 * @param {number} [breakpoints.xlarge] 29048 * The maximum width for the "vjs-layout-x-large" class. 29049 * 29050 * @param {number} [breakpoints.huge] 29051 * The maximum width for the "vjs-layout-huge" class. 29052 * 29053 * @return {Object} 29054 * An object mapping breakpoint names to maximum width values. 29055 */ 29056 ; 29057 29058 _proto.breakpoints = function breakpoints(_breakpoints) { 29059 // Used as a getter. 29060 if (_breakpoints === undefined) { 29061 return assign(this.breakpoints_); 29062 } 29063 29064 this.breakpoint_ = ''; 29065 this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently 29066 // selected breakpoint. 29067 29068 this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning. 29069 29070 return assign(this.breakpoints_); 29071 } 29072 /** 29073 * Get or set a flag indicating whether or not this player should adjust 29074 * its UI based on its dimensions. 29075 * 29076 * @param {boolean} value 29077 * Should be `true` if the player should adjust its UI based on its 29078 * dimensions; otherwise, should be `false`. 29079 * 29080 * @return {boolean} 29081 * Will be `true` if this player should adjust its UI based on its 29082 * dimensions; otherwise, will be `false`. 29083 */ 29084 ; 29085 29086 _proto.responsive = function responsive(value) { 29087 // Used as a getter. 29088 if (value === undefined) { 29089 return this.responsive_; 29090 } 29091 29092 value = Boolean(value); 29093 var current = this.responsive_; // Nothing changed. 29094 29095 if (value === current) { 29096 return; 29097 } // The value actually changed, set it. 29098 29099 29100 this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the 29101 // player is now responsive. 29102 29103 if (value) { 29104 this.on('playerresize', this.boundUpdateCurrentBreakpoint_); 29105 this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive. 29106 } else { 29107 this.off('playerresize', this.boundUpdateCurrentBreakpoint_); 29108 this.removeCurrentBreakpoint_(); 29109 } 29110 29111 return value; 29112 } 29113 /** 29114 * Get current breakpoint name, if any. 29115 * 29116 * @return {string} 29117 * If there is currently a breakpoint set, returns a the key from the 29118 * breakpoints object matching it. Otherwise, returns an empty string. 29119 */ 29120 ; 29121 29122 _proto.currentBreakpoint = function currentBreakpoint() { 29123 return this.breakpoint_; 29124 } 29125 /** 29126 * Get the current breakpoint class name. 29127 * 29128 * @return {string} 29129 * The matching class name (e.g. `"vjs-layout-tiny"` or 29130 * `"vjs-layout-large"`) for the current breakpoint. Empty string if 29131 * there is no current breakpoint. 29132 */ 29133 ; 29134 29135 _proto.currentBreakpointClass = function currentBreakpointClass() { 29136 return BREAKPOINT_CLASSES[this.breakpoint_] || ''; 29137 } 29138 /** 29139 * An object that describes a single piece of media. 29140 * 29141 * Properties that are not part of this type description will be retained; so, 29142 * this can be viewed as a generic metadata storage mechanism as well. 29143 * 29144 * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface} 29145 * @typedef {Object} Player~MediaObject 29146 * 29147 * @property {string} [album] 29148 * Unused, except if this object is passed to the `MediaSession` 29149 * API. 29150 * 29151 * @property {string} [artist] 29152 * Unused, except if this object is passed to the `MediaSession` 29153 * API. 29154 * 29155 * @property {Object[]} [artwork] 29156 * Unused, except if this object is passed to the `MediaSession` 29157 * API. If not specified, will be populated via the `poster`, if 29158 * available. 29159 * 29160 * @property {string} [poster] 29161 * URL to an image that will display before playback. 29162 * 29163 * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src] 29164 * A single source object, an array of source objects, or a string
29165 * referencing a URL to a media source. It is _highly recommended_ 29166 * that an object or array of objects is used here, so that source 29167 * selection algorithms can take the `type` into account. 29168 * 29169 * @property {string} [title] 29170 * Unused, except if this object is passed to the `MediaSession` 29171 * API. 29172 * 29173 * @property {Object[]} [textTracks] 29174 * An array of objects to be used to create text tracks, following 29175 * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}. 29176 * For ease of removal, these will be created as "remote" text 29177 * tracks and set to automatically clean up on source changes. 29178 * 29179 * These objects may have properties like `src`, `kind`, `label`, 29180 * and `language`, see {@link Tech#createRemoteTextTrack}. 29181 */ 29182 29183 /** 29184 * Populate the player using a {@link Player~MediaObject|MediaObject}. 29185 * 29186 * @param {Player~MediaObject} media 29187 * A media object. 29188 * 29189 * @param {Function} ready 29190 * A callback to be called when the player is ready. 29191 */ 29192 ; 29193 29194 _proto.loadMedia = function loadMedia(media, ready) { 29195 var _this19 = this; 29196 29197 if (!media || typeof media !== 'object') { 29198 return; 29199 } 29200 29201 this.reset(); // Clone the media object so it cannot be mutated from outside. 29202 29203 this.cache_.media = mergeOptions$3(media); 29204 var _this$cache_$media = this.cache_.media, 29205 artwork = _this$cache_$media.artwork, 29206 poster = _this$cache_$media.poster, 29207 src = _this$cache_$media.src, 29208 textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`. 29209 29210 if (!artwork && poster) { 29211 this.cache_.media.artwork = [{ 29212 src: poster, 29213 type: getMimetype(poster) 29214 }]; 29215 } 29216 29217 if (src) { 29218 this.src(src); 29219 } 29220 29221 if (poster) { 29222 this.poster(poster); 29223 } 29224 29225 if (Array.isArray(textTracks)) { 29226 textTracks.forEach(function (tt) { 29227 return _this19.addRemoteTextTrack(tt, false); 29228 }); 29229 } 29230 29231 this.ready(ready); 29232 } 29233 /** 29234 * Get a clone of the current {@link Player~MediaObject} for this player. 29235 * 29236 * If the `loadMedia` method has not been used, will attempt to return a 29237 * {@link Player~MediaObject} based on the current state of the player. 29238 * 29239 * @return {Player~MediaObject} 29240 */ 29241 ; 29242 29243 _proto.getMedia = function getMedia() { 29244 if (!this.cache_.media) { 29245 var poster = this.poster(); 29246 var src = this.currentSources(); 29247 var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) { 29248 return { 29249 kind: tt.kind, 29250 label: tt.label, 29251 language: tt.language, 29252 src: tt.src 29253 }; 29254 }); 29255 var media = { 29256 src: src, 29257 textTracks: textTracks 29258 }; 29259 29260 if (poster) { 29261 media.poster = poster; 29262 media.artwork = [{ 29263 src: media.poster, 29264 type: getMimetype(media.poster) 29265 }]; 29266 } 29267 29268 return media; 29269 } 29270 29271 return mergeOptions$3(this.cache_.media); 29272 } 29273 /** 29274 * Gets tag settings 29275 * 29276 * @param {Element} tag 29277 * The player tag 29278 * 29279 * @return {Object} 29280 * An object containing all of the settings 29281 * for a player tag 29282 */ 29283 ; 29284 29285 Player.getTagSettings = function getTagSettings(tag) { 29286 var baseOptions = { 29287 sources: [], 29288 tracks: [] 29289 }; 29290 var tagOptions = getAttributes(tag); 29291 var dataSetup = tagOptions['data-setup']; 29292 29293 if (hasClass(tag, 'vjs-fill')) { 29294 tagOptions.fill = true; 29295 } 29296 29297 if (hasClass(tag, 'vjs-fluid')) { 29298 tagOptions.fluid = true; 29299 } // Check if data-setup attr exists. 29300 29301 29302 if (dataSetup !== null) { 29303 // Parse options JSON 29304 // If empty string, make it a parsable json object. 29305 var _safeParseTuple = tuple(dataSetup || '{}'), 29306 err = _safeParseTuple[0], 29307 data = _safeParseTuple[1];
29308 29309 if (err) { 29310 log$1.error(err); 29311 } 29312 29313 assign(tagOptions, data); 29314 } 29315 29316 assign(baseOptions, tagOptions); // Get tag children settings 29317 29318 if (tag.hasChildNodes()) { 29319 var children = tag.childNodes; 29320 29321 for (var i = 0, j = children.length; i < j; i++) { 29322 var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ 29323 29324 var childName = child.nodeName.toLowerCase(); 29325 29326 if (childName === 'source') { 29327 baseOptions.sources.push(getAttributes(child)); 29328 } else if (childName === 'track') { 29329 baseOptions.tracks.push(getAttributes(child)); 29330 } 29331 } 29332 } 29333 29334 return baseOptions; 29335 } 29336 /** 29337 * Determine whether or not flexbox is supported 29338 * 29339 * @return {boolean} 29340 * - true if flexbox is supported 29341 * - false if flexbox is not supported 29342 */ 29343 ; 29344 29345 _proto.flexNotSupported_ = function flexNotSupported_() { 29346 var elem = document_1.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more 29347 // common flex features that we can rely on when checking for flex support. 29348 29349 return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || // IE10-specific (2012 flex spec), available for completeness 29350 'msFlexOrder' in elem.style); 29351 } 29352 /** 29353 * Set debug mode to enable/disable logs at info level. 29354 * 29355 * @param {boolean} enabled 29356 * @fires Player#debugon 29357 * @fires Player#debugoff 29358 */ 29359 ; 29360 29361 _proto.debug = function debug(enabled) { 29362 if (enabled === undefined) { 29363 return this.debugEnabled_; 29364 } 29365 29366 if (enabled) { 29367 this.trigger('debugon'); 29368 this.previousLogLevel_ = this.log.level; 29369 this.log.level('debug'); 29370 this.debugEnabled_ = true; 29371 } else { 29372 this.trigger('debugoff'); 29373 this.log.level(this.previousLogLevel_); 29374 this.previousLogLevel_ = undefined; 29375 this.debugEnabled_ = false; 29376 } 29377 } 29378 /** 29379 * Set or get current playback rates. 29380 * Takes an array and updates the playback rates menu with the new items. 29381 * Pass in an empty array to hide the menu. 29382 * Values other than arrays are ignored. 29383 * 29384 * @fires Player#playbackrateschange 29385 * @param {number[]} newRates 29386 * The new rates that the playback rates menu should update to. 29387 * An empty array will hide the menu 29388 * @return {number[]} When used as a getter will return the current playback rates 29389 */ 29390 ; 29391 29392 _proto.playbackRates = function playbackRates(newRates) { 29393 if (newRates === undefined) { 29394 return this.cache_.playbackRates; 29395 } // ignore any value that isn't an array 29396 29397 29398 if (!Array.isArray(newRates)) { 29399 return; 29400 } // ignore any arrays that don't only contain numbers 29401 29402 29403 if (!newRates.every(function (rate) { 29404 return typeof rate === 'number'; 29405 })) { 29406 return; 29407 } 29408 29409 this.cache_.playbackRates = newRates; 29410 /** 29411 * fires when the playback rates in a player are changed 29412 * 29413 * @event Player#playbackrateschange 29414 * @type {EventTarget~Event} 29415 */ 29416 29417 this.trigger('playbackrateschange'); 29418 }; 29419 29420 return Player; 29421 }(Component$1); 29422 /** 29423 * Get the {@link VideoTrackList} 29424 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist 29425 * 29426 * @return {VideoTrackList} 29427 * the current video track list 29428 * 29429 * @method Player.prototype.videoTracks 29430 */ 29431 29432 /** 29433 * Get the {@link AudioTrackList} 29434 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist 29435 * 29436 * @return {AudioTrackList} 29437 * the current audio track list 29438 * 29439 * @method Player.prototype.audioTracks 29440 */ 29441 29442 /** 29443 * Get the {@link TextTrackList} 29444 * 29445 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks 29446 * 29447 * @return {TextTrackList} 29448 * the current text track list 29449 * 29450 * @method Player.prototype.textTracks 29451 */ 29452 29453 /** 29454 * Get the remote {@link TextTrackList} 29455 * 29456 * @return {TextTrackList} 29457 * The current remote text track list 29458 * 29459 * @method Player.prototype.remoteTextTracks 29460 */ 29461 29462 /** 29463 * Get the remote {@link HtmlTrackElementList} tracks. 29464 * 29465 * @return {HtmlTrackElementList} 29466 * The current remote text track element list 29467 * 29468 * @method Player.prototype.remoteTextTrackEls 29469 */ 29470 29471
29472 ALL.names.forEach(function (name) { 29473 var props = ALL[name]; 29474 29475 Player.prototype[props.getterName] = function () { 29476 if (this.tech_) { 29477 return this.tech_[props.getterName](); 29478 } // if we have not yet loadTech_, we create {video,audio,text}Tracks_ 29479 // these will be passed to the tech during loading 29480 29481 29482 this[props.privateName] = this[props.privateName] || new props.ListClass(); 29483 return this[props.privateName]; 29484 }; 29485 }); 29486 /** 29487 * Get or set the `Player`'s crossorigin option. For the HTML5 player, this 29488 * sets the `crossOrigin` property on the `<video>` tag to control the CORS 29489 * behavior. 29490 * 29491 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin} 29492 * 29493 * @param {string} [value] 29494 * The value to set the `Player`'s crossorigin to. If an argument is 29495 * given, must be one of `anonymous` or `use-credentials`. 29496 * 29497 * @return {string|undefined} 29498 * - The current crossorigin value of the `Player` when getting. 29499 * - undefined when setting 29500 */ 29501 29502 Player.prototype.crossorigin = Player.prototype.crossOrigin; 29503 /** 29504 * Global enumeration of players. 29505 * 29506 * The keys are the player IDs and the values are either the {@link Player} 29507 * instance or `null` for disposed players. 29508 * 29509 * @type {Object} 29510 */ 29511 29512 Player.players = {}; 29513 var navigator = window_1.navigator; 29514 /* 29515 * Player instance options, surfaced using options 29516 * options = Player.prototype.options_ 29517 * Make changes in options, not here. 29518 * 29519 * @type {Object} 29520 * @private 29521 */ 29522 29523 Player.prototype.options_ = { 29524 // Default order of fallback technology 29525 techOrder: Tech.defaultTechOrder_, 29526 html5: {}, 29527 // default inactivity timeout 29528 inactivityTimeout: 2000, 29529 // default playback rates 29530 playbackRates: [], 29531 // Add playback rate selection by adding rates 29532 // 'playbackRates': [0.5, 1, 1.5, 2], 29533 liveui: false, 29534 // Included control sets 29535 children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'], 29536 language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', 29537 // locales and their language translations 29538 languages: {}, 29539 // Default message to show when a video cannot be played. 29540 notSupportedMessage: 'No compatible source was found for this media.', 29541 normalizeAutoplay: false, 29542 fullscreen: { 29543 options: { 29544 navigationUI: 'hide' 29545 } 29546 }, 29547 breakpoints: {}, 29548 responsive: false 29549 }; 29550 [ 29551 /** 29552 * Returns whether or not the player is in the "ended" state. 29553 * 29554 * @return {Boolean} True if the player is in the ended state, false if not. 29555 * @method Player#ended 29556 */ 29557 'ended', 29558 /** 29559 * Returns whether or not the player is in the "seeking" state. 29560 * 29561 * @return {Boolean} True if the player is in the seeking state, false if not. 29562 * @method Player#seeking 29563 */ 29564 'seeking', 29565 /** 29566 * Returns the TimeRanges of the media that are currently available 29567 * for seeking to. 29568 * 29569 * @return {TimeRanges} the seekable intervals of the media timeline 29570 * @method Player#seekable 29571 */ 29572 'seekable', 29573 /** 29574 * Returns the current state of network activity for the element, from 29575 * the codes in the list below. 29576 * - NETWORK_EMPTY (numeric value 0) 29577 * The element has not yet been initialised. All attributes are in 29578 * their initial states. 29579 * - NETWORK_IDLE (numeric value 1) 29580 * The element's resource selection algorithm is active and has 29581 * selected a resource, but it is not actually using the network at 29582 * this time. 29583 * - NETWORK_LOADING (numeric value 2) 29584 * The user agent is actively trying to download data. 29585 * - NETWORK_NO_SOURCE (numeric value 3) 29586 * The element's resource selection algorithm is active, but it has 29587 * not yet found a resource to use. 29588 * 29589 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states 29590 * @return {number} the current network activity state 29591 * @method Player#networkState 29592 */ 29593 'networkState', 29594 /** 29595 * Returns a value that expresses the current state of the element 29596 * with respect to rendering the current playback position, from the 29597 * codes in the list below. 29598 * - HAVE_NOTHING (numeric value 0) 29599 * No information regarding the media resource is available. 29600 * - HAVE_METADATA (numeric value 1) 29601 * Enough of the resource has been obtained that the duration of the 29602 * resource is available. 29603 * - HAVE_CURRENT_DATA (numeric value 2) 29604 * Data for the immediate current playback position is available. 29605 * - HAVE_FUTURE_DATA (numeric value 3) 29606 * Data for the immediate current playback position is available, as 29607 * well as enough data for the user agent to advance the current 29608 * playback position in the direction of playback. 29609 * - HAVE_ENOUGH_DATA (numeric value 4) 29610 * The user agent estimates that enough data is available for 29611 * playback to proceed uninterrupted. 29612 * 29613 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate 29614 * @return {number} the current playback rendering state 29615 * @method Player#readyState 29616 */ 29617 'readyState'].forEach(function (fn) { 29618 Player.prototype[fn] = function () { 29619 return this.techGet_(fn); 29620 }; 29621 });
29622 TECH_EVENTS_RETRIGGER.forEach(function (event) { 29623 Player.prototype["handleTech" + toTitleCase$1(event) + "_"] = function () { 29624 return this.trigger(event); 29625 }; 29626 }); 29627 /** 29628 * Fired when the player has initial duration and dimension information 29629 * 29630 * @event Player#loadedmetadata 29631 * @type {EventTarget~Event} 29632 */ 29633 29634 /** 29635 * Fired when the player has downloaded data at the current playback position 29636 * 29637 * @event Player#loadeddata 29638 * @type {EventTarget~Event} 29639 */ 29640 29641 /** 29642 * Fired when the current playback position has changed * 29643 * During playback this is fired every 15-250 milliseconds, depending on the 29644 * playback technology in use. 29645 * 29646 * @event Player#timeupdate 29647 * @type {EventTarget~Event} 29648 */ 29649 29650 /** 29651 * Fired when the volume changes 29652 * 29653 * @event Player#volumechange 29654 * @type {EventTarget~Event} 29655 */ 29656 29657 /** 29658 * Reports whether or not a player has a plugin available. 29659 * 29660 * This does not report whether or not the plugin has ever been initialized 29661 * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. 29662 * 29663 * @method Player#hasPlugin 29664 * @param {string} name 29665 * The name of a plugin. 29666 * 29667 * @return {boolean} 29668 * Whether or not this player has the requested plugin available. 29669 */ 29670 29671 /** 29672 * Reports whether or not a player is using a plugin by name. 29673 * 29674 * For basic plugins, this only reports whether the plugin has _ever_ been 29675 * initialized on this player. 29676 * 29677 * @method Player#usingPlugin 29678 * @param {string} name 29679 * The name of a plugin. 29680 * 29681 * @return {boolean} 29682 * Whether or not this player is using the requested plugin. 29683 */ 29684 29685 Component$1.registerComponent('Player', Player); 29686 29687 var setPrototypeOf = createCommonjsModule(function (module) { 29688 function _setPrototypeOf(o, p) { 29689 module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 29690 o.__proto__ = p; 29691 return o; 29692 }; 29693 29694 return _setPrototypeOf(o, p); 29695 } 29696 29697 module.exports = _setPrototypeOf; 29698 }); 29699 29700 function _isNativeReflectConstruct() { 29701 if (typeof Reflect === "undefined" || !Reflect.construct) return false; 29702 if (Reflect.construct.sham) return false; 29703 if (typeof Proxy === "function") return true; 29704 29705 try { 29706 Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 29707 return true; 29708 } catch (e) { 29709 return false; 29710 } 29711 } 29712 29713 var isNativeReflectConstruct = _isNativeReflectConstruct; 29714 29715 var construct = createCommonjsModule(function (module) { 29716 function _construct(Parent, args, Class) { 29717 if (isNativeReflectConstruct()) { 29718 module.exports = _construct = Reflect.construct; 29719 } else { 29720 module.exports = _construct = function _construct(Parent, args, Class) { 29721 var a = [null]; 29722 a.push.apply(a, args); 29723 var Constructor = Function.bind.apply(Parent, a); 29724 var instance = new Constructor(); 29725 if (Class) setPrototypeOf(instance, Class.prototype); 29726 return instance; 29727 }; 29728 } 29729 29730 return _construct.apply(null, arguments); 29731 } 29732 29733 module.exports = _construct; 29734 }); 29735 29736 /** 29737 * The base plugin name. 29738 * 29739 * @private 29740 * @constant 29741 * @type {string} 29742 */ 29743 29744 var BASE_PLUGIN_NAME = 'plugin'; 29745 /** 29746 * The key on which a player's active plugins cache is stored. 29747 * 29748 * @private 29749 * @constant 29750 * @type {string} 29751 */ 29752 29753 var PLUGIN_CACHE_KEY = 'activePlugins_'; 29754 /** 29755 * Stores registered plugins in a private space. 29756 * 29757 * @private 29758 * @type {Object} 29759 */ 29760 29761 var pluginStorage = {}; 29762 /** 29763 * Reports whether or not a plugin has been registered. 29764 * 29765 * @private 29766 * @param {string} name 29767 * The name of a plugin. 29768 * 29769 * @return {boolean} 29770 * Whether or not the plugin has been registered. 29771 */ 29772 29773 var pluginExists = function pluginExists(name) { 29774 return pluginStorage.hasOwnProperty(name); 29775 }; 29776 /** 29777 * Get a single registered plugin by name. 29778 * 29779 * @private 29780 * @param {string} name 29781 * The name of a plugin. 29782 * 29783 * @return {Function|undefined} 29784 * The plugin (or undefined). 29785 */ 29786 29787 29788 var getPlugin = function getPlugin(name) { 29789 return pluginExists(name) ? pluginStorage[name] : undefined; 29790 }; 29791 /** 29792 * Marks a plugin as "active" on a player. 29793 * 29794 * Also, ensures that the player has an object for tracking active plugins. 29795 * 29796 * @private 29797 * @param {Player} player 29798 * A Video.js player instance. 29799 * 29800 * @param {string} name 29801 * The name of a plugin. 29802 */ 29803 29804 29805 var markPluginAsActive = function markPluginAsActive(player, name) { 29806 player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; 29807 player[PLUGIN_CACHE_KEY][name] = true; 29808 }; 29809 /** 29810 * Triggers a pair of plugin setup events. 29811 * 29812 * @private 29813 * @param {Player} player 29814 * A Video.js player instance. 29815 * 29816 * @param {Plugin~PluginEventHash} hash 29817 * A plugin event hash. 29818 * 29819 * @param {boolean} [before] 29820 * If true, prefixes the event name with "before". In other words, 29821 * use this to trigger "beforepluginsetup" instead of "pluginsetup". 29822 */ 29823 29824 29825 var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { 29826 var eventName = (before ? 'before' : '') + 'pluginsetup'; 29827 player.trigger(eventName, hash); 29828 player.trigger(eventName + ':' + hash.name, hash); 29829 }; 29830 /** 29831 * Takes a basic plugin function and returns a wrapper function which marks 29832 * on the player that the plugin has been activated. 29833 * 29834 * @private 29835 * @param {string} name 29836 * The name of the plugin. 29837 * 29838 * @param {Function} plugin 29839 * The basic plugin. 29840 * 29841 * @return {Function} 29842 * A wrapper function for the given plugin. 29843 */ 29844 29845 29846 var createBasicPlugin = function createBasicPlugin(name, plugin) { 29847 var basicPluginWrapper = function basicPluginWrapper() { 29848 // We trigger the "beforepluginsetup" and "pluginsetup" events on the player 29849 // regardless, but we want the hash to be consistent with the hash provided 29850 // for advanced plugins. 29851 // 29852 // The only potentially counter-intuitive thing here is the `instance` in 29853 // the "pluginsetup" event is the value returned by the `plugin` function. 29854 triggerSetupEvent(this, { 29855 name: name, 29856 plugin: plugin, 29857 instance: null 29858 }, true); 29859 var instance = plugin.apply(this, arguments); 29860 markPluginAsActive(this, name); 29861 triggerSetupEvent(this, { 29862 name: name, 29863 plugin: plugin, 29864 instance: instance 29865 }); 29866 return instance; 29867 }; 29868
29869 Object.keys(plugin).forEach(function (prop) { 29870 basicPluginWrapper[prop] = plugin[prop]; 29871 }); 29872 return basicPluginWrapper; 29873 }; 29874 /** 29875 * Takes a plugin sub-class and returns a factory function for generating 29876 * instances of it. 29877 * 29878 * This factory function will replace itself with an instance of the requested 29879 * sub-class of Plugin. 29880 * 29881 * @private 29882 * @param {string} name 29883 * The name of the plugin. 29884 * 29885 * @param {Plugin} PluginSubClass 29886 * The advanced plugin. 29887 * 29888 * @return {Function} 29889 */ 29890 29891 29892 var createPluginFactory = function createPluginFactory(name, PluginSubClass) { 29893 // Add a `name` property to the plugin prototype so that each plugin can 29894 // refer to itself by name. 29895 PluginSubClass.prototype.name = name; 29896 return function () { 29897 triggerSetupEvent(this, { 29898 name: name, 29899 plugin: PluginSubClass, 29900 instance: null 29901 }, true); 29902 29903 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 29904 args[_key] = arguments[_key]; 29905 } 29906 29907 var instance = construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance. 29908 29909 29910 this[name] = function () { 29911 return instance; 29912 }; 29913 29914 triggerSetupEvent(this, instance.getEventHash()); 29915 return instance; 29916 }; 29917 }; 29918 /** 29919 * Parent class for all advanced plugins. 29920 * 29921 * @mixes module:evented~EventedMixin 29922 * @mixes module:stateful~StatefulMixin 29923 * @fires Player#beforepluginsetup 29924 * @fires Player#beforepluginsetup:$name 29925 * @fires Player#pluginsetup 29926 * @fires Player#pluginsetup:$name 29927 * @listens Player#dispose 29928 * @throws {Error} 29929 * If attempting to instantiate the base {@link Plugin} class 29930 * directly instead of via a sub-class. 29931 */ 29932 29933 29934 var Plugin = /*#__PURE__*/function () { 29935 /** 29936 * Creates an instance of this class. 29937 * 29938 * Sub-classes should call `super` to ensure plugins are properly initialized. 29939 * 29940 * @param {Player} player 29941 * A Video.js player instance. 29942 */ 29943 function Plugin(player) { 29944 if (this.constructor === Plugin) { 29945 throw new Error('Plugin must be sub-classed; not directly instantiated.'); 29946 } 29947 29948 this.player = player; 29949 29950 if (!this.log) { 29951 this.log = this.player.log.createLogger(this.name); 29952 } // Make this object evented, but remove the added `trigger` method so we 29953 // use the prototype version instead. 29954 29955 29956 evented(this); 29957 delete this.trigger; 29958 stateful(this, this.constructor.defaultState); 29959 markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind 29960 // it later easily. 29961 29962 this.dispose = this.dispose.bind(this); // If the player is disposed, dispose the plugin. 29963 29964 player.on('dispose', this.dispose); 29965 } 29966 /** 29967 * Get the version of the plugin that was set on <pluginName>.VERSION 29968 */ 29969 29970 29971 var _proto = Plugin.prototype; 29972 29973 _proto.version = function version() { 29974 return this.constructor.VERSION; 29975 } 29976 /** 29977 * Each event triggered by plugins includes a hash of additional data with 29978 * conventional properties. 29979 * 29980 * This returns that object or mutates an existing hash. 29981 * 29982 * @param {Object} [hash={}] 29983 * An object to be used as event an event hash. 29984 * 29985 * @return {Plugin~PluginEventHash} 29986 * An event hash object with provided properties mixed-in. 29987 */ 29988 ; 29989 29990 _proto.getEventHash = function getEventHash(hash) { 29991 if (hash === void 0) { 29992 hash = {}; 29993 } 29994 29995 hash.name = this.name; 29996 hash.plugin = this.constructor; 29997 hash.instance = this; 29998 return hash; 29999 } 30000 /** 30001 * Triggers an event on the plugin object and overrides 30002 * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. 30003 * 30004 * @param {string|Object} event 30005 * An event type or an object with a type property. 30006 * 30007 * @param {Object} [hash={}] 30008 * Additional data hash to merge with a 30009 * {@link Plugin~PluginEventHash|PluginEventHash}. 30010 * 30011 * @return {boolean} 30012 * Whether or not default was prevented. 30013 */ 30014 ; 30015 30016 _proto.trigger = function trigger$1(event, hash) { 30017 if (hash === void 0) { 30018 hash = {}; 30019 } 30020 30021 return trigger(this.eventBusEl_, event, this.getEventHash(hash)); 30022 } 30023 /** 30024 * Handles "statechanged" events on the plugin. No-op by default, override by 30025 * subclassing. 30026 * 30027 * @abstract 30028 * @param {Event} e 30029 * An event object provided by a "statechanged" event. 30030 * 30031 * @param {Object} e.changes 30032 * An object describing changes that occurred with the "statechanged" 30033 * event. 30034 */ 30035 ; 30036 30037 _proto.handleStateChanged = function handleStateChanged(e) {} 30038 /**
30039 * Disposes a plugin. 30040 * 30041 * Subclasses can override this if they want, but for the sake of safety, 30042 * it's probably best to subscribe the "dispose" event. 30043 * 30044 * @fires Plugin#dispose 30045 */ 30046 ; 30047 30048 _proto.dispose = function dispose() { 30049 var name = this.name, 30050 player = this.player; 30051 /** 30052 * Signals that a advanced plugin is about to be disposed. 30053 * 30054 * @event Plugin#dispose 30055 * @type {EventTarget~Event} 30056 */ 30057 30058 this.trigger('dispose'); 30059 this.off(); 30060 player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up 30061 // references between the player and the plugin instance and nulling out 30062 // the plugin's state and replacing methods with a function that throws. 30063 30064 player[PLUGIN_CACHE_KEY][name] = false; 30065 this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory 30066 // function, so that the plugin is ready to be set up again. 30067 30068 player[name] = createPluginFactory(name, pluginStorage[name]); 30069 } 30070 /** 30071 * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). 30072 * 30073 * @param {string|Function} plugin 30074 * If a string, matches the name of a plugin. If a function, will be 30075 * tested directly. 30076 * 30077 * @return {boolean} 30078 * Whether or not a plugin is a basic plugin. 30079 */ 30080 ; 30081 30082 Plugin.isBasic = function isBasic(plugin) { 30083 var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; 30084 return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); 30085 } 30086 /** 30087 * Register a Video.js plugin. 30088 * 30089 * @param {string} name 30090 * The name of the plugin to be registered. Must be a string and 30091 * must not match an existing plugin or a method on the `Player` 30092 * prototype. 30093 * 30094 * @param {Function} plugin 30095 * A sub-class of `Plugin` or a function for basic plugins. 30096 * 30097 * @return {Function} 30098 * For advanced plugins, a factory function for that plugin. For 30099 * basic plugins, a wrapper function that initializes the plugin. 30100 */ 30101 ; 30102 30103 Plugin.registerPlugin = function registerPlugin(name, plugin) { 30104 if (typeof name !== 'string') { 30105 throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + "."); 30106 } 30107 30108 if (pluginExists(name)) { 30109 log$1.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!"); 30110 } else if (Player.prototype.hasOwnProperty(name)) { 30111 throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!"); 30112 } 30113 30114 if (typeof plugin !== 'function') { 30115 throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + "."); 30116 } 30117 30118 pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for 30119 // the base Plugin class). 30120 30121 if (name !== BASE_PLUGIN_NAME) { 30122 if (Plugin.isBasic(plugin)) { 30123 Player.prototype[name] = createBasicPlugin(name, plugin); 30124 } else { 30125 Player.prototype[name] = createPluginFactory(name, plugin); 30126 } 30127 } 30128 30129 return plugin; 30130 } 30131 /** 30132 * De-register a Video.js plugin. 30133 * 30134 * @param {string} name 30135 * The name of the plugin to be de-registered. Must be a string that 30136 * matches an existing plugin. 30137 * 30138 * @throws {Error} 30139 * If an attempt is made to de-register the base plugin. 30140 */ 30141 ; 30142 30143 Plugin.deregisterPlugin = function deregisterPlugin(name) { 30144 if (name === BASE_PLUGIN_NAME) { 30145 throw new Error('Cannot de-register base plugin.'); 30146 } 30147 30148 if (pluginExists(name)) { 30149 delete pluginStorage[name]; 30150 delete Player.prototype[name]; 30151 } 30152 } 30153 /**
30154 * Gets an object containing multiple Video.js plugins. 30155 * 30156 * @param {Array} [names] 30157 * If provided, should be an array of plugin names. Defaults to _all_ 30158 * plugin names. 30159 * 30160 * @return {Object|undefined} 30161 * An object containing plugin(s) associated with their name(s) or 30162 * `undefined` if no matching plugins exist). 30163 */ 30164 ; 30165 30166 Plugin.getPlugins = function getPlugins(names) { 30167 if (names === void 0) { 30168 names = Object.keys(pluginStorage); 30169 } 30170 30171 var result; 30172 names.forEach(function (name) { 30173 var plugin = getPlugin(name); 30174 30175 if (plugin) { 30176 result = result || {}; 30177 result[name] = plugin; 30178 } 30179 }); 30180 return result; 30181 } 30182 /** 30183 * Gets a plugin's version, if available 30184 * 30185 * @param {string} name 30186 * The name of a plugin. 30187 * 30188 * @return {string} 30189 * The plugin's version or an empty string. 30190 */ 30191 ; 30192 30193 Plugin.getPluginVersion = function getPluginVersion(name) { 30194 var plugin = getPlugin(name); 30195 return plugin && plugin.VERSION || ''; 30196 }; 30197 30198 return Plugin; 30199 }(); 30200 /** 30201 * Gets a plugin by name if it exists. 30202 * 30203 * @static 30204 * @method getPlugin 30205 * @memberOf Plugin 30206 * @param {string} name 30207 * The name of a plugin. 30208 * 30209 * @returns {Function|undefined} 30210 * The plugin (or `undefined`). 30211 */ 30212 30213 30214 Plugin.getPlugin = getPlugin; 30215 /** 30216 * The name of the base plugin class as it is registered. 30217 * 30218 * @type {string} 30219 */ 30220 30221 Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; 30222 Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); 30223 /** 30224 * Documented in player.js 30225 * 30226 * @ignore 30227 */ 30228 30229 Player.prototype.usingPlugin = function (name) { 30230 return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; 30231 }; 30232 /** 30233 * Documented in player.js 30234 * 30235 * @ignore 30236 */ 30237 30238 30239 Player.prototype.hasPlugin = function (name) { 30240 return !!pluginExists(name); 30241 }; 30242 /** 30243 * Signals that a plugin is about to be set up on a player. 30244 * 30245 * @event Player#beforepluginsetup 30246 * @type {Plugin~PluginEventHash} 30247 */ 30248 30249 /** 30250 * Signals that a plugin is about to be set up on a player - by name. The name 30251 * is the name of the plugin. 30252 * 30253 * @event Player#beforepluginsetup:$name 30254 * @type {Plugin~PluginEventHash} 30255 */ 30256 30257 /** 30258 * Signals that a plugin has just been set up on a player. 30259 * 30260 * @event Player#pluginsetup 30261 * @type {Plugin~PluginEventHash} 30262 */ 30263 30264 /** 30265 * Signals that a plugin has just been set up on a player - by name. The name 30266 * is the name of the plugin. 30267 * 30268 * @event Player#pluginsetup:$name 30269 * @type {Plugin~PluginEventHash} 30270 */ 30271 30272 /** 30273 * @typedef {Object} Plugin~PluginEventHash 30274 * 30275 * @property {string} instance 30276 * For basic plugins, the return value of the plugin function. For 30277 * advanced plugins, the plugin instance on which the event is fired. 30278 * 30279 * @property {string} name 30280 * The name of the plugin. 30281 * 30282 * @property {string} plugin 30283 * For basic plugins, the plugin function. For advanced plugins, the 30284 * plugin class/constructor. 30285 */ 30286 30287 function _inherits(subClass, superClass) { 30288 if (typeof superClass !== "function" && superClass !== null) { 30289 throw new TypeError("Super expression must either be null or a function"); 30290 } 30291 30292 subClass.prototype = Object.create(superClass && superClass.prototype, { 30293 constructor: { 30294 value: subClass, 30295 writable: true, 30296 configurable: true 30297 } 30298 }); 30299 if (superClass) setPrototypeOf(subClass, superClass); 30300 } 30301 30302 var inherits = _inherits; 30303 30304 /** 30305 * @file extend.js 30306 * @module extend 30307 */ 30308 /** 30309 * Used to subclass an existing class by emulating ES subclassing using the 30310 * `extends` keyword. 30311 * 30312 * @function 30313 * @example 30314 * var MyComponent = videojs.extend(videojs.getComponent('Component'), { 30315 * myCustomMethod: function() { 30316 * // Do things in my method. 30317 * } 30318 * }); 30319 * 30320 * @param {Function} superClass 30321 * The class to inherit from 30322 * 30323 * @param {Object} [subClassMethods={}] 30324 * Methods of the new class 30325 * 30326 * @return {Function} 30327 * The new class with subClassMethods that inherited superClass. 30328 */ 30329 30330 var extend = function extend(superClass, subClassMethods) { 30331 if (subClassMethods === void 0) { 30332 subClassMethods = {}; 30333 } 30334 30335 var subClass = function subClass() { 30336 superClass.apply(this, arguments); 30337 }; 30338 30339 var methods = {}; 30340 30341 if (typeof subClassMethods === 'object') { 30342 if (subClassMethods.constructor !== Object.prototype.constructor) { 30343 subClass = subClassMethods.constructor; 30344 } 30345 30346 methods = subClassMethods; 30347 } else if (typeof subClassMethods === 'function') { 30348 subClass = subClassMethods; 30349 } 30350 30351 inherits(subClass, superClass); // this is needed for backward-compatibility and node compatibility. 30352 30353 30354 if (superClass) { 30355 subClass.super_ = superClass; 30356 } // Extend subObj's prototype with functions and other properties from props 30357 30358 30359 for (var name in methods) { 30360 if (methods.hasOwnProperty(name)) { 30361 subClass.prototype[name] = methods[name]; 30362 } 30363 } 30364 30365 return subClass; 30366 }; 30367 30368 /** 30369 * @file video.js 30370 * @module videojs 30371 */ 30372 /** 30373 * Normalize an `id` value by trimming off a leading `#` 30374 * 30375 * @private 30376 * @param {string} id 30377 * A string, maybe with a leading `#`. 30378 * 30379 * @return {string} 30380 * The string, without any leading `#`. 30381 */ 30382 30383 var normalizeId = function normalizeId(id) { 30384 return id.indexOf('#') === 0 ? id.slice(1) : id; 30385 }; 30386 /** 30387 * The `videojs()` function doubles as the main function for users to create a 30388 * {@link Player} instance as well as the main library namespace. 30389 * 30390 * It can also be used as a getter for a pre-existing {@link Player} instance. 30391 * However, we _strongly_ recommend using `videojs.getPlayer()` for this 30392 * purpose because it avoids any potential for unintended initialization. 30393 * 30394 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 30395 * of our JSDoc template, we cannot properly document this as both a function 30396 * and a namespace, so its function signature is documented here. 30397 * 30398 * #### Arguments 30399 * ##### id 30400 * string|Element, **required** 30401 * 30402 * Video element or video element ID. 30403 * 30404 * ##### options 30405 * Object, optional 30406 * 30407 * Options object for providing settings. 30408 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 30409 * 30410 * ##### ready 30411 * {@link Component~ReadyCallback}, optional 30412 * 30413 * A function to be called when the {@link Player} and {@link Tech} are ready. 30414 * 30415 * #### Return Value 30416 * 30417 * The `videojs()` function returns a {@link Player} instance. 30418 * 30419 * @namespace 30420 * 30421 * @borrows AudioTrack as AudioTrack 30422 * @borrows Component.getComponent as getComponent 30423 * @borrows module:computed-style~computedStyle as computedStyle 30424 * @borrows module:events.on as on 30425 * @borrows module:events.one as one 30426 * @borrows module:events.off as off 30427 * @borrows module:events.trigger as trigger 30428 * @borrows EventTarget as EventTarget 30429 * @borrows module:extend~extend as extend 30430 * @borrows module:fn.bind as bind 30431 * @borrows module:format-time.formatTime as formatTime 30432 * @borrows module:format-time.resetFormatTime as resetFormatTime 30433 * @borrows module:format-time.setFormatTime as setFormatTime 30434 * @borrows module:merge-options.mergeOptions as mergeOptions 30435 * @borrows module:middleware.use as use 30436 * @borrows Player.players as players 30437 * @borrows Plugin.registerPlugin as registerPlugin 30438 * @borrows Plugin.deregisterPlugin as deregisterPlugin 30439 * @borrows Plugin.getPlugins as getPlugins 30440 * @borrows Plugin.getPlugin as getPlugin 30441 * @borrows Plugin.getPluginVersion as getPluginVersion 30442 * @borrows Tech.getTech as getTech 30443 * @borrows Tech.registerTech as registerTech 30444 * @borrows TextTrack as TextTrack 30445 * @borrows module:time-ranges.createTimeRanges as createTimeRange 30446 * @borrows module:time-ranges.createTimeRanges as createTimeRanges 30447 * @borrows module:url.isCrossOrigin as isCrossOrigin 30448 * @borrows module:url.parseUrl as parseUrl 30449 * @borrows VideoTrack as VideoTrack 30450 * 30451 * @param {string|Element}
30451 id 30452 * Video element or video element ID. 30453 * 30454 * @param {Object} [options] 30455 * Options object for providing settings. 30456 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 30457 * 30458 * @param {Component~ReadyCallback} [ready] 30459 * A function to be called when the {@link Player} and {@link Tech} are 30460 * ready. 30461 * 30462 * @return {Player} 30463 * The `videojs()` function returns a {@link Player|Player} instance. 30464 */ 30465 30466 30467 function videojs(id, options, ready) { 30468 var player = videojs.getPlayer(id); 30469 30470 if (player) { 30471 if (options) { 30472 log$1.warn("Player \"" + id + "\" is already initialised. Options will not be applied."); 30473 } 30474 30475 if (ready) { 30476 player.ready(ready); 30477 } 30478 30479 return player; 30480 } 30481 30482 var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id; 30483 30484 if (!isEl(el)) { 30485 throw new TypeError('The element or ID supplied is not valid. (videojs)'); 30486 } // document.body.contains(el) will only check if el is contained within that one document. 30487 // This causes problems for elements in iframes. 30488 // Instead, use the element's ownerDocument instead of the global document. 30489 // This will make sure that the element is indeed in the dom of that document. 30490 // Additionally, check that the document in question has a default view. 30491 // If the document is no longer attached to the dom, the defaultView of the document will be null. 30492 30493 30494 if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) { 30495 log$1.warn('The element supplied is not included in the DOM'); 30496 } 30497 30498 options = options || {}; 30499 hooks('beforesetup').forEach(function (hookFunction) { 30500 var opts = hookFunction(el, mergeOptions$3(options)); 30501 30502 if (!isObject$1(opts) || Array.isArray(opts)) { 30503 log$1.error('please return an object in beforesetup hooks'); 30504 return; 30505 } 30506 30507 options = mergeOptions$3(options, opts); 30508 }); // We get the current "Player" component here in case an integration has 30509 // replaced it with a custom player. 30510 30511 var PlayerComponent = Component$1.getComponent('Player'); 30512 player = new PlayerComponent(el, options, ready); 30513 hooks('setup').forEach(function (hookFunction) { 30514 return hookFunction(player); 30515 }); 30516 return player; 30517 } 30518 30519 videojs.hooks_ = hooks_; 30520 videojs.hooks = hooks; 30521 videojs.hook = hook; 30522 videojs.hookOnce = hookOnce; 30523 videojs.removeHook = removeHook; // Add default styles 30524 30525 if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) { 30526 var style = $('.vjs-styles-defaults'); 30527 30528 if (!style) { 30529 style = createStyleElement('vjs-styles-defaults'); 30530 var head = $('head'); 30531 30532 if (head) { 30533 head.insertBefore(style, head.firstChild); 30534 } 30535 30536 setTextContent(style, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n "); 30537 } 30538 } // Run Auto-load players 30539 // You have to wait at least once in case this script is loaded after your 30540 // video in the DOM (weird behavior only with minified version) 30541 30542 30543 autoSetupTimeout(1, videojs); 30544 /** 30545 * Current Video.js version. Follows [semantic versioning](https://semver.org/). 30546 * 30547 * @type {string} 30548 */ 30549 30550 videojs.VERSION = version$5; 30551 /** 30552 * The global options object. These are the settings that take effect 30553 * if no overrides are specified when the player is created. 30554 * 30555 * @type {Object} 30556 */ 30557 30558 videojs.options = Player.prototype.options_; 30559 /** 30560 * Get an object with the currently created players, keyed by player ID 30561 * 30562 * @return {Object} 30563 * The created players 30564 */ 30565 30566 videojs.getPlayers = function () { 30567 return Player.players; 30568 }; 30569 /** 30570 * Get a single player based on an ID or DOM element. 30571 * 30572 * This is useful if you want to check if an element or ID has an associated 30573 * Video.js player, but not create one if it doesn't. 30574 * 30575 * @param {string|Element}
30575 id 30576 * An HTML element - `<video>`, `<audio>`, or `<video-js>` - 30577 * or a string matching the `id` of such an element. 30578 * 30579 * @return {Player|undefined} 30580 * A player instance or `undefined` if there is no player instance 30581 * matching the argument. 30582 */ 30583 30584 30585 videojs.getPlayer = function (id) { 30586 var players = Player.players; 30587 var tag; 30588 30589 if (typeof id === 'string') { 30590 var nId = normalizeId(id); 30591 var player = players[nId]; 30592 30593 if (player) { 30594 return player; 30595 } 30596 30597 tag = $('#' + nId); 30598 } else { 30599 tag = id; 30600 } 30601 30602 if (isEl(tag)) { 30603 var _tag = tag, 30604 _player = _tag.player, 30605 playerId = _tag.playerId; // Element may have a `player` property referring to an already created 30606 // player instance. If so, return that. 30607 30608 if (_player || players[playerId]) { 30609 return _player || players[playerId]; 30610 } 30611 } 30612 }; 30613 /** 30614 * Returns an array of all current players. 30615 * 30616 * @return {Array} 30617 * An array of all players. The array will be in the order that 30618 * `Object.keys` provides, which could potentially vary between 30619 * JavaScript engines. 30620 * 30621 */ 30622 30623 30624 videojs.getAllPlayers = function () { 30625 return (// Disposed players leave a key with a `null` value, so we need to make sure 30626 // we filter those out. 30627 Object.keys(Player.players).map(function (k) { 30628 return Player.players[k]; 30629 }).filter(Boolean) 30630 ); 30631 }; 30632 30633 videojs.players = Player.players; 30634 videojs.getComponent = Component$1.getComponent; 30635 /** 30636 * Register a component so it can referred to by name. Used when adding to other 30637 * components, either through addChild `component.addChild('myComponent')` or through 30638 * default children options `{ children: ['myComponent'] }`. 30639 * 30640 * > NOTE: You could also just initialize the component before adding. 30641 * `component.addChild(new MyComponent());` 30642 * 30643 * @param {string} name 30644 * The class name of the component 30645 * 30646 * @param {Component} comp 30647 * The component class 30648 * 30649 * @return {Component} 30650 * The newly registered component 30651 */ 30652 30653 videojs.registerComponent = function (name, comp) { 30654 if (Tech.isTech(comp)) { 30655 log$1.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)"); 30656 } 30657 30658 Component$1.registerComponent.call(Component$1, name, comp); 30659 }; 30660 30661 videojs.getTech = Tech.getTech; 30662 videojs.registerTech = Tech.registerTech; 30663 videojs.use = use; 30664 /** 30665 * An object that can be returned by a middleware to signify 30666 * that the middleware is being terminated. 30667 * 30668 * @type {object} 30669 * @property {object} middleware.TERMINATOR 30670 */ 30671 30672 Object.defineProperty(videojs, 'middleware', { 30673 value: {}, 30674 writeable: false, 30675 enumerable: true 30676 }); 30677 Object.defineProperty(videojs.middleware, 'TERMINATOR', { 30678 value: TERMINATOR, 30679 writeable: false, 30680 enumerable: true 30681 }); 30682 /** 30683 * A reference to the {@link module:browser|browser utility module} as an object. 30684 * 30685 * @type {Object} 30686 * @see {@link module:browser|browser} 30687 */ 30688 30689 videojs.browser = browser; 30690 /** 30691 * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only 30692 * included for backward-compatibility with 4.x. 30693 * 30694 * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead. 30695 * @type {boolean} 30696 */ 30697 30698 videojs.TOUCH_ENABLED = TOUCH_ENABLED; 30699 videojs.extend = extend; 30700 videojs.mergeOptions = mergeOptions$3; 30701 videojs.bind = bind; 30702 videojs.registerPlugin = Plugin.registerPlugin; 30703 videojs.deregisterPlugin = Plugin.deregisterPlugin; 30704 /** 30705 * Deprecated method to register a plugin with Video.js 30706 * 30707 * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead 30708 * 30709 * @param {string} name 30710 * The plugin name 30711 * 30712 * @param {Plugin|Function} plugin 30713 * The plugin sub-class or function 30714 */ 30715 30716 videojs.plugin = function (name, plugin) { 30717 log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead'); 30718 return Plugin.registerPlugin(name, plugin); 30719 }; 30720 30721 videojs.getPlugins = Plugin.getPlugins; 30722 videojs.getPlugin = Plugin.getPlugin; 30723 videojs.getPluginVersion = Plugin.getPluginVersion; 30724 /** 30725 * Adding languages so that they're available to all players. 30726 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });` 30727 * 30728 * @param {string} code 30729 * The language code or dictionary property 30730 * 30731 * @param {Object} data 30732 * The data values to be translated 30733 * 30734 * @return {Object} 30735 * The resulting language dictionary object 30736 */ 30737 30738 videojs.addLanguage = function (code, data) { 30739 var _mergeOptions; 30740 30741 code = ('' + code).toLowerCase(); 30742 videojs.options.languages = mergeOptions$3(videojs.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions)); 30743 return videojs.options.languages[code]; 30744 }; 30745 /** 30746 * A reference to the {@link module:log|log utility module} as an object. 30747 * 30748 * @type {Function} 30749 * @see {@link module:log|log} 30750 */ 30751 30752 30753 videojs.log = log$1; 30754 videojs.createLogger = createLogger; 30755 videojs.createTimeRange = videojs.createTimeRanges = createTimeRanges;
vendor: 6,525 bytes, lines 30756-30916
30756 videojs.formatTime = formatTime; 30757 videojs.setFormatTime = setFormatTime; 30758 videojs.resetFormatTime = resetFormatTime; 30759 videojs.parseUrl = parseUrl; 30760 videojs.isCrossOrigin = isCrossOrigin; 30761 videojs.EventTarget = EventTarget$2; 30762 videojs.on = on; 30763 videojs.one = one; 30764 videojs.off = off; 30765 videojs.trigger = trigger; 30766 /** 30767 * A cross-browser XMLHttpRequest wrapper. 30768 * 30769 * @function 30770 * @param {Object} options 30771 * Settings for the request. 30772 * 30773 * @return {XMLHttpRequest|XDomainRequest} 30774 * The request object. 30775 * 30776 * @see https://github.com/Raynos/xhr 30777 */ 30778 30779 videojs.xhr = lib; 30780 videojs.TextTrack = TextTrack; 30781 videojs.AudioTrack = AudioTrack; 30782 videojs.VideoTrack = VideoTrack; 30783 ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) { 30784 videojs[k] = function () { 30785 log$1.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead"); 30786 return Dom[k].apply(null, arguments); 30787 }; 30788 }); 30789 videojs.computedStyle = computedStyle; 30790 /** 30791 * A reference to the {@link module:dom|DOM utility module} as an object. 30792 * 30793 * @type {Object} 30794 * @see {@link module:dom|dom} 30795 */ 30796 30797 videojs.dom = Dom; 30798 /** 30799 * A reference to the {@link module:url|URL utility module} as an object. 30800 * 30801 * @type {Object} 30802 * @see {@link module:url|url} 30803 */ 30804 30805 videojs.url = Url; 30806 videojs.defineLazyProperty = defineLazyProperty; // Adding less ambiguous text for fullscreen button. 30807 // In a major update this could become the default text and key. 30808 30809 videojs.addLanguage('en', { 30810 'Non-Fullscreen': 'Exit Fullscreen' 30811 }); 30812 30813 var urlToolkit = createCommonjsModule(function (module, exports) { 30814 // see https://tools.ietf.org/html/rfc1808 30815 (function (root) { 30816 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/?#]*\/)*[^;?#]*)?(;[^?#]*)?(\?[^#]*)?(#[^]*)?$/; 30817 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)([^]*)$/; 30818 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 30819 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g; 30820 var URLToolkit = { 30821 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 30822 // E.g 30823 // With opts.alwaysNormalize = false (default, spec compliant) 30824 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 30825 // With opts.alwaysNormalize = true (not spec compliant) 30826 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 30827 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 30828 opts = opts || {}; // remove any remaining space and CRLF 30829 30830 baseURL = baseURL.trim(); 30831 relativeURL = relativeURL.trim(); 30832 30833 if (!relativeURL) { 30834 // 2a) If the embedded URL is entirely empty, it inherits the 30835 // entire base URL (i.e., is set equal to the base URL) 30836 // and we are done. 30837 if (!opts.alwaysNormalize) { 30838 return baseURL; 30839 } 30840 30841 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 30842 30843 if (!basePartsForNormalise) { 30844 throw new Error('Error trying to parse base URL.'); 30845 } 30846 30847 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 30848 return URLToolkit.buildURLFromParts(basePartsForNormalise); 30849 } 30850 30851 var relativeParts = URLToolkit.parseURL(relativeURL); 30852 30853 if (!relativeParts) { 30854 throw new Error('Error trying to parse relative URL.'); 30855 } 30856 30857 if (relativeParts.scheme) { 30858 // 2b) If the embedded URL starts with a scheme name, it is 30859 // interpreted as an absolute URL and we are done. 30860 if (!opts.alwaysNormalize) { 30861 return relativeURL; 30862 } 30863 30864 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 30865 return URLToolkit.buildURLFromParts(relativeParts); 30866 } 30867 30868 var baseParts = URLToolkit.parseURL(baseURL); 30869 30870 if (!baseParts) { 30871 throw new Error('Error trying to parse base URL.'); 30872 } 30873 30874 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 30875 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 30876 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 30877 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 30878 baseParts.netLoc = pathParts[1]; 30879 baseParts.path = pathParts[2]; 30880 } 30881 30882 if (baseParts.netLoc && !baseParts.path) { 30883 baseParts.path = '/'; 30884 } 30885 30886 var builtParts = { 30887 // 2c) Otherwise, the embedded URL inherits the scheme of 30888 // the base URL. 30889 scheme: baseParts.scheme, 30890 netLoc: relativeParts.netLoc, 30891 path: null, 30892 params: relativeParts.params, 30893 query: relativeParts.query, 30894 fragment: relativeParts.fragment 30895 }; 30896 30897 if (!relativeParts.netLoc) { 30898 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 30899 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 30900 // (if any) of the base URL. 30901 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 30902 // path is not relative and we skip to Step 7. 30903 30904 if (relativeParts.path[0] !== '/') { 30905 if (!relativeParts.path) { 30906 // 5) If the embedded URL path is empty (and not preceded by a 30907 // slash), then the embedded URL inherits the base URL path 30908 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 30909 // step 7; otherwise, it inherits the <params> of the base 30910 // URL (if any) and 30911 30912 if (!relativeParts.params) { 30913 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 30914 // step 7; otherwise, it inherits the <query> of the base 30915 // URL (if any) and we skip to step 7. 30916
30917 if (!relativeParts.query) { 30918 builtParts.query = baseParts.query; 30919 } 30920 } 30921 } else { 30922 // 6) The last segment of the base URL's path (anything 30923 // following the rightmost slash "/", or the entire path if no 30924 // slash is present) is removed and the embedded URL's path is 30925 // appended in its place. 30926 var baseURLPath = baseParts.path; 30927 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 30928 builtParts.path = URLToolkit.normalizePath(newPath); 30929 } 30930 } 30931 } 30932 30933 if (builtParts.path === null) { 30934 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 30935 } 30936 30937 return URLToolkit.buildURLFromParts(builtParts); 30938 }, 30939 parseURL: function parseURL(url) { 30940 var parts = URL_REGEX.exec(url); 30941 30942 if (!parts) { 30943 return null; 30944 } 30945 30946 return { 30947 scheme: parts[1] || '', 30948 netLoc: parts[2] || '', 30949 path: parts[3] || '', 30950 params: parts[4] || '', 30951 query: parts[5] || '', 30952 fragment: parts[6] || '' 30953 }; 30954 }, 30955 normalizePath: function normalizePath(path) { 30956 // The following operations are 30957 // then applied, in order, to the new path: 30958 // 6a) All occurrences of "./", where "." is a complete path 30959 // segment, are removed. 30960 // 6b) If the path ends with "." as a complete path segment, 30961 // that "." is removed. 30962 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 30963 // complete path segment not equal to "..", are removed. 30964 // Removal of these path segments is performed iteratively, 30965 // removing the leftmost matching pattern on each iteration, 30966 // until no matching pattern remains. 30967 // 6d) If the path ends with "<segment>/..", where <segment> is a 30968 // complete path segment not equal to "..", that 30969 // "<segment>/.." is removed. 30970 30971 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} 30972 30973 return path.split('').reverse().join(''); 30974 }, 30975 buildURLFromParts: function buildURLFromParts(parts) { 30976 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 30977 } 30978 }; 30979 module.exports = URLToolkit; 30980 })(); 30981 }); 30982 30983 var DEFAULT_LOCATION = 'http://example.com'; 30984 30985 var resolveUrl$1 = function resolveUrl(baseUrl, relativeUrl) { 30986 // return early if we don't need to resolve 30987 if (/^[a-z]+:/i.test(relativeUrl)) { 30988 return relativeUrl; 30989 } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location 30990 30991 30992 if (/^data:/.test(baseUrl)) { 30993 baseUrl = window_1.location && window_1.location.href || ''; 30994 } // IE11 supports URL but not the URL constructor 30995 // feature detect the behavior we want 30996 30997 30998 var nativeURL = typeof window_1.URL === 'function'; 30999 var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node) 31000 // and if baseUrl isn't an absolute url 31001 31002 var removeLocation = !window_1.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location 31003 31004 if (nativeURL) { 31005 baseUrl = new window_1.URL(baseUrl, window_1.location || DEFAULT_LOCATION); 31006 } else if (!/\/\//i.test(baseUrl)) { 31007 baseUrl = urlToolkit.buildAbsoluteURL(window_1.location && window_1.location.href || '', baseUrl); 31008 } 31009 31010 if (nativeURL) { 31011 var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol 31012 // and if we're location-less, remove the location 31013 // otherwise, return the url unmodified 31014 31015 if (removeLocation) { 31016 return newUrl.href.slice(DEFAULT_LOCATION.length); 31017 } else if (protocolLess) { 31018 return newUrl.href.slice(newUrl.protocol.length); 31019 } 31020 31021 return newUrl.href; 31022 } 31023 31024 return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl); 31025 }; 31026 31027 /** 31028 * @file stream.js 31029 */ 31030 31031 /** 31032 * A lightweight readable stream implemention that handles event dispatching. 31033 * 31034 * @class Stream 31035 */ 31036 var Stream = /*#__PURE__*/function () { 31037 function Stream() { 31038 this.listeners = {}; 31039 } 31040 /** 31041 * Add a listener for a specified event type. 31042 * 31043 * @param {string} type the event name 31044 * @param {Function} listener the callback to be invoked when an event of 31045 * the specified type occurs 31046 */ 31047 31048 31049 var _proto = Stream.prototype; 31050 31051 _proto.on = function on(type, listener) { 31052 if (!this.listeners[type]) { 31053 this.listeners[type] = []; 31054 } 31055 31056 this.listeners[type].push(listener); 31057 } 31058 /** 31059 * Remove a listener for a specified event type. 31060 * 31061 * @param {string} type the event name 31062 * @param {Function} listener a function previously registered for this 31063 * type of event through `on` 31064 * @return {boolean} if we could turn it off or not 31065 */ 31066 ; 31067 31068 _proto.off = function off(type, listener) { 31069 if (!this.listeners[type]) { 31070 return false; 31071 } 31072 31073 var index = this.listeners[type].indexOf(listener); // TODO: which is better? 31074 // In Video.js we slice listener functions 31075 // on trigger so that it does not mess up the order 31076 // while we loop through. 31077 // 31078 // Here we slice on off so that the loop in trigger 31079 // can continue using it's old reference to loop without 31080 // messing up the order. 31081 31082 this.listeners[type] = this.listeners[type].slice(0); 31083 this.listeners[type].splice(index, 1); 31084 return index > -1; 31085 } 31086 /** 31087 * Trigger an event of the specified type on this stream. Any additional 31088 * arguments to this function are passed as parameters to event listeners. 31089 * 31090 * @param {string} type the event name 31091 */ 31092 ; 31093 31094 _proto.trigger = function trigger(type) { 31095 var callbacks = this.listeners[type]; 31096 31097 if (!callbacks) { 31098 return; 31099 } // Slicing the arguments on every invocation of this method 31100 // can add a significant amount of overhead. Avoid the 31101 // intermediate object creation for the common case of a 31102 // single callback argument 31103 31104 31105 if (arguments.length === 2) { 31106 var length = callbacks.length; 31107 31108 for (var i = 0; i < length; ++i) { 31109 callbacks[i].call(this, arguments[1]); 31110 } 31111 } else { 31112 var args = Array.prototype.slice.call(arguments, 1); 31113 var _length = callbacks.length; 31114 31115 for (var _i = 0; _i < _length; ++_i) { 31116 callbacks[_i].apply(this, args); 31117 } 31118 } 31119 } 31120 /** 31121 * Destroys the stream and cleans up. 31122 */ 31123 ; 31124 31125 _proto.dispose = function dispose() { 31126 this.listeners = {}; 31127 } 31128 /** 31129 * Forwards all `data` events on this stream to the destination stream. The 31130 * destination stream should provide a method `push` to receive the data 31131 * events as they arrive. 31132 * 31133 * @param {Stream} destination the stream that will receive all `data` events 31134 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 31135 */ 31136 ; 31137 31138 _proto.pipe = function pipe(destination) { 31139 this.on('data', function (data) { 31140 destination.push(data); 31141 }); 31142 }; 31143 31144 return Stream; 31145 }(); 31146 31147 var atob = function atob(s) { 31148 return window_1.atob ? window_1.atob(s) : Buffer.from(s, 'base64').toString('binary'); 31149 }; 31150 31151 function decodeB64ToUint8Array(b64Text) { 31152 var decodedString = atob(b64Text); 31153 var array = new Uint8Array(decodedString.length); 31154 31155 for (var i = 0; i < decodedString.length; i++) { 31156 array[i] = decodedString.charCodeAt(i); 31157 } 31158 31159 return array; 31160 } 31161 31162 /*! @name m3u8-parser @version 4.7.0 @license Apache-2.0 */ 31163 /** 31164 * A stream that buffers string input and generates a `data` event for each 31165 * line. 31166 * 31167 * @class LineStream 31168 * @extends Stream 31169 */ 31170
31171 var LineStream = /*#__PURE__*/function (_Stream) { 31172 inheritsLoose(LineStream, _Stream); 31173 31174 function LineStream() { 31175 var _this; 31176 31177 _this = _Stream.call(this) || this; 31178 _this.buffer = ''; 31179 return _this; 31180 } 31181 /** 31182 * Add new data to be parsed. 31183 * 31184 * @param {string} data the text to process 31185 */ 31186 31187 31188 var _proto = LineStream.prototype; 31189 31190 _proto.push = function push(data) { 31191 var nextNewline; 31192 this.buffer += data; 31193 nextNewline = this.buffer.indexOf('\n'); 31194 31195 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 31196 this.trigger('data', this.buffer.substring(0, nextNewline)); 31197 this.buffer = this.buffer.substring(nextNewline + 1); 31198 } 31199 }; 31200 31201 return LineStream; 31202 }(Stream); 31203 31204 var TAB = String.fromCharCode(0x09); 31205 31206 var parseByterange = function parseByterange(byterangeString) { 31207 // optionally match and capture 0+ digits before `@` 31208 // optionally match and capture 0+ digits after `@` 31209 var match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || ''); 31210 var result = {}; 31211 31212 if (match[1]) { 31213 result.length = parseInt(match[1], 10); 31214 } 31215 31216 if (match[2]) { 31217 result.offset = parseInt(match[2], 10); 31218 } 31219 31220 return result; 31221 }; 31222 /** 31223 * "forgiving" attribute list psuedo-grammar: 31224 * attributes -> keyvalue (',' keyvalue)* 31225 * keyvalue -> key '=' value 31226 * key -> [^=]* 31227 * value -> '"' [^"]* '"' | [^,]* 31228 */ 31229 31230 31231 var attributeSeparator = function attributeSeparator() { 31232 var key = '[^=]*'; 31233 var value = '"[^"]*"|[^,]*'; 31234 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 31235 return new RegExp('(?:^|,)(' + keyvalue + ')'); 31236 }; 31237 /** 31238 * Parse attributes from a line given the separator 31239 * 31240 * @param {string} attributes the attribute line to parse 31241 */ 31242 31243 31244 var parseAttributes$1 = function parseAttributes(attributes) { 31245 // split the string using attributes as the separator 31246 var attrs = attributes.split(attributeSeparator()); 31247 var result = {}; 31248 var i = attrs.length; 31249 var attr; 31250 31251 while (i--) { 31252 // filter out unmatched portions of the string 31253 if (attrs[i] === '') { 31254 continue; 31255 } // split the key and value 31256 31257 31258 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value 31259 31260 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 31261 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 31262 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 31263 result[attr[0]] = attr[1]; 31264 } 31265 31266 return result; 31267 }; 31268 /** 31269 * A line-level M3U8 parser event stream. It expects to receive input one 31270 * line at a time and performs a context-free parse of its contents. A stream 31271 * interpretation of a manifest can be useful if the manifest is expected to 31272 * be too large to fit comfortably into memory or the entirety of the input 31273 * is not immediately available. Otherwise, it's probably much easier to work 31274 * with a regular `Parser` object. 31275 * 31276 * Produces `data` events with an object that captures the parser's 31277 * interpretation of the input. That object has a property `tag` that is one 31278 * of `uri`, `comment`, or `tag`. URIs only have a single additional 31279 * property, `line`, which captures the entirety of the input without 31280 * interpretation. Comments similarly have a single additional property 31281 * `text` which is the input without the leading `#`. 31282 * 31283 * Tags always have a property `tagType` which is the lower-cased version of 31284 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 31285 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 31286 * tags are given the tag type `unknown` and a single additional property 31287 * `data` with the remainder of the input. 31288 * 31289 * @class ParseStream 31290 * @extends Stream 31291 */ 31292 31293 31294 var ParseStream = /*#__PURE__*/function (_Stream) { 31295 inheritsLoose(ParseStream, _Stream); 31296 31297 function ParseStream() { 31298 var _this; 31299 31300 _this = _Stream.call(this) || this; 31301 _this.customParsers = []; 31302 _this.tagMappers = []; 31303 return _this; 31304 } 31305 /** 31306 * Parses an additional line of input. 31307 * 31308 * @param {string} line a single line of an M3U8 file to parse 31309 */ 31310 31311 31312 var _proto = ParseStream.prototype; 31313 31314 _proto.push = function push(line) { 31315 var _this2 = this; 31316 31317 var match; 31318 var event; // strip whitespace 31319 31320 line = line.trim(); 31321 31322 if (line.length === 0) { 31323 // ignore empty lines 31324 return; 31325 } // URIs 31326 31327 31328 if (line[0] !== '#') { 31329 this.trigger('data', { 31330 type: 'uri', 31331 uri: line 31332 }); 31333 return; 31334 } // map tags 31335 31336 31337 var newLines = this.tagMappers.reduce(function (acc, mapper) { 31338 var mappedLine = mapper(line);
31338 // skip if unchanged 31339 31340 if (mappedLine === line) { 31341 return acc; 31342 } 31343 31344 return acc.concat([mappedLine]); 31345 }, [line]); 31346 newLines.forEach(function (newLine) { 31347 for (var i = 0; i < _this2.customParsers.length; i++) { 31348 if (_this2.customParsers[i].call(_this2, newLine)) { 31349 return; 31350 } 31351 } // Comments 31352 31353 31354 if (newLine.indexOf('#EXT') !== 0) { 31355 _this2.trigger('data', { 31356 type: 'comment', 31357 text: newLine.slice(1) 31358 }); 31359 31360 return; 31361 } // strip off any carriage returns here so the regex matching 31362 // doesn't have to account for them. 31363 31364 31365 newLine = newLine.replace('\r', ''); // Tags 31366 31367 match = /^#EXTM3U/.exec(newLine); 31368 31369 if (match) { 31370 _this2.trigger('data', { 31371 type: 'tag', 31372 tagType: 'm3u' 31373 }); 31374 31375 return; 31376 } 31377 31378 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine); 31379 31380 if (match) { 31381 event = { 31382 type: 'tag', 31383 tagType: 'inf' 31384 }; 31385 31386 if (match[1]) { 31387 event.duration = parseFloat(match[1]); 31388 } 31389 31390 if (match[2]) { 31391 event.title = match[2]; 31392 } 31393 31394 _this2.trigger('data', event); 31395 31396 return; 31397 } 31398 31399 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine); 31400 31401 if (match) { 31402 event = { 31403 type: 'tag', 31404 tagType: 'targetduration' 31405 }; 31406 31407 if (match[1]) { 31408 event.duration = parseInt(match[1], 10); 31409 } 31410 31411 _this2.trigger('data', event); 31412 31413 return; 31414 } 31415 31416 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine); 31417 31418 if (match) { 31419 event = { 31420 type: 'tag', 31421 tagType: 'version' 31422 }; 31423 31424 if (match[1]) { 31425 event.version = parseInt(match[1], 10); 31426 } 31427 31428 _this2.trigger('data', event); 31429 31430 return; 31431 } 31432 31433 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 31434 31435 if (match) { 31436 event = { 31437 type: 'tag', 31438 tagType: 'media-sequence' 31439 }; 31440 31441 if (match[1]) { 31442 event.number = parseInt(match[1], 10); 31443 } 31444 31445 _this2.trigger('data', event); 31446 31447 return; 31448 } 31449 31450 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 31451 31452 if (match) { 31453 event = { 31454 type: 'tag', 31455 tagType: 'discontinuity-sequence' 31456 }; 31457 31458 if (match[1]) { 31459 event.number = parseInt(match[1], 10); 31460 } 31461 31462 _this2.trigger('data', event); 31463 31464 return; 31465 } 31466 31467 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine); 31468 31469 if (match) { 31470 event = { 31471 type: 'tag', 31472 tagType: 'playlist-type' 31473 }; 31474 31475 if (match[1]) { 31476 event.playlistType = match[1]; 31477 } 31478 31479 _this2.trigger('data', event); 31480 31481 return; 31482 } 31483 31484 match = /^#EXT-X-BYTERANGE:?(.*)?$/.exec(newLine); 31485 31486 if (match) { 31487 event = _extends_1(parseByterange(match[1]), { 31488 type: 'tag', 31489 tagType: 'byterange' 31490 }); 31491 31492 _this2.trigger('data', event); 31493 31494 return; 31495 } 31496 31497 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine); 31498 31499 if (match) { 31500 event = { 31501 type: 'tag', 31502 tagType: 'allow-cache' 31503 }; 31504 31505 if (match[1]) { 31506 event.allowed = !/NO/.test(match[1]); 31507 } 31508 31509 _this2.trigger('data', event); 31510 31511 return; 31512 } 31513 31514 match = /^#EXT-X-MAP:?(.*)$/.exec(newLine); 31515 31516 if (match) { 31517 event = { 31518 type: 'tag', 31519 tagType: 'map' 31520 }; 31521 31522 if (match[1]) { 31523 var attributes = parseAttributes$1(match[1]); 31524 31525 if (attributes.URI) { 31526 event.uri = attributes.URI; 31527 } 31528 31529 if (attributes.BYTERANGE) { 31530 event.byterange = parseByterange(attributes.BYTERANGE); 31531 } 31532 } 31533 31534 _this2.trigger('data', event); 31535 31536 return; 31537 } 31538 31539 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine); 31540 31541 if (match) { 31542 event = { 31543 type: 'tag', 31544 tagType: 'stream-inf' 31545 }; 31546 31547 if (match[1]) { 31548 event.attributes = parseAttributes$1(match[1]); 31549 31550 if (event.attributes.RESOLUTION) { 31551 var split = event.attributes.RESOLUTION.split('x'); 31552 var resolution = {}; 31553 31554 if (split[0]) { 31555 resolution.width = parseInt(split[0], 10); 31556 } 31557 31558 if (split[1]) { 31559 resolution.height = parseInt(split[1], 10); 31560 } 31561 31562 event.attributes.RESOLUTION = resolution; 31563 } 31564 31565 if (event.attributes.BANDWIDTH) { 31566 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 31567 } 31568 31569 if (event.attributes['PROGRAM-ID']) { 31570 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 31571 } 31572 } 31573 31574 _this2.trigger('data', event); 31575 31576 return; 31577 } 31578 31579 match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine); 31580 31581 if (match) { 31582 event = { 31583 type: 'tag', 31584 tagType: 'media' 31585 }; 31586 31587 if (match[1]) { 31588 event.attributes = parseAttributes$1(match[1]); 31589 } 31590 31591 _this2.trigger('data', event); 31592 31593 return; 31594 } 31595 31596 match = /^#EXT-X-ENDLIST/.exec(newLine); 31597 31598 if (match) { 31599 _this2.trigger('data', { 31600 type: 'tag', 31601 tagType: 'endlist' 31602 }); 31603 31604 return; 31605 } 31606 31607 match = /^#EXT-X-DISCONTINUITY/.exec(newLine); 31608 31609 if (match) { 31610 _this2.trigger('data', { 31611 type: 'tag', 31612 tagType: 'discontinuity' 31613 }); 31614 31615 return; 31616 } 31617 31618 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine); 31619 31620 if (match) { 31621 event = { 31622 type: 'tag', 31623 tagType: 'program-date-time' 31624 }; 31625 31626 if (match[1]) { 31627 event.dateTimeString = match[1]; 31628 event.dateTimeObject = new Date(match[1]); 31629 } 31630 31631 _this2.trigger('data', event); 31632 31633 return; 31634 } 31635 31636 match = /^#EXT-X-KEY:?(.*)$/.exec(newLine); 31637 31638 if (match) { 31639 event = { 31640 type: 'tag', 31641 tagType: 'key' 31642 }; 31643 31644 if (match[1]) { 31645 event.attributes = parseAttributes$1(match[1]); // parse the IV string into a Uint32Array 31646 31647 if (event.attributes.IV) { 31648 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 31649 event.attributes.IV = event.attributes.IV.substring(2); 31650 } 31651 31652 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 31653 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 31654 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 31655 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 31656 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 31657 event.attributes.IV = new Uint32Array(event.attributes.IV); 31658 } 31659 } 31660 31661 _this2.trigger('data', event); 31662 31663 return; 31664 } 31665 31666 match = /^#EXT-X-START:?(.*)$/.exec(newLine); 31667 31668 if (match) { 31669 event = { 31670 type: 'tag', 31671 tagType: 'start' 31672 }; 31673 31674 if (match[1]) { 31675 event.attributes = parseAttributes$1(match[1]); 31676 event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']); 31677 event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE); 31678 } 31679 31680 _this2.trigger('data', event); 31681 31682 return; 31683 } 31684 31685 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine); 31686 31687 if (match) { 31688 event = { 31689 type: 'tag', 31690 tagType: 'cue-out-cont' 31691 }; 31692 31693 if (match[1]) { 31694 event.data = match[1]; 31695 } else { 31696 event.data = ''; 31697 } 31698 31699 _this2.trigger('data', event); 31700 31701 return; 31702 } 31703 31704 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine); 31705 31706 if (match) { 31707 event = { 31708 type: 'tag', 31709 tagType: 'cue-out' 31710 }; 31711 31712 if (match[1]) { 31713 event.data = match[1]; 31714 } else { 31715 event.data = ''; 31716 } 31717 31718 _this2.trigger('data', event); 31719 31720 return; 31721 } 31722 31723 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine); 31724 31725 if (match) { 31726 event = { 31727 type: 'tag', 31728 tagType: 'cue-in' 31729 }; 31730 31731 if (match[1]) { 31732 event.data = match[1]; 31733 } else { 31734 event.data = ''; 31735 } 31736 31737 _this2.trigger('data', event); 31738 31739 return; 31740 } 31741 31742 match = /^#EXT-X-SKIP:(.*)$/.exec(newLine); 31743 31744 if (match && match[1]) { 31745 event = { 31746 type: 'tag', 31747 tagType: 'skip' 31748 }; 31749 event.attributes = parseAttributes$1(match[1]); 31750 31751 if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) { 31752 event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10); 31753 } 31754 31755 if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) { 31756 event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB); 31757 } 31758 31759 _this2.trigger('data', event); 31760 31761 return; 31762 } 31763
31764 match = /^#EXT-X-PART:(.*)$/.exec(newLine); 31765 31766 if (match && match[1]) { 31767 event = { 31768 type: 'tag', 31769 tagType: 'part' 31770 }; 31771 event.attributes = parseAttributes$1(match[1]); 31772 ['DURATION'].forEach(function (key) { 31773 if (event.attributes.hasOwnProperty(key)) { 31774 event.attributes[key] = parseFloat(event.attributes[key]); 31775 } 31776 }); 31777 ['INDEPENDENT', 'GAP'].forEach(function (key) { 31778 if (event.attributes.hasOwnProperty(key)) { 31779 event.attributes[key] = /YES/.test(event.attributes[key]); 31780 } 31781 }); 31782 31783 if (event.attributes.hasOwnProperty('BYTERANGE')) { 31784 event.attributes.byterange = parseByterange(event.attributes.BYTERANGE); 31785 } 31786 31787 _this2.trigger('data', event); 31788 31789 return; 31790 } 31791 31792 match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine); 31793 31794 if (match && match[1]) { 31795 event = { 31796 type: 'tag', 31797 tagType: 'server-control' 31798 }; 31799 event.attributes = parseAttributes$1(match[1]); 31800 ['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) { 31801 if (event.attributes.hasOwnProperty(key)) { 31802 event.attributes[key] = parseFloat(event.attributes[key]); 31803 } 31804 }); 31805 ['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) { 31806 if (event.attributes.hasOwnProperty(key)) { 31807 event.attributes[key] = /YES/.test(event.attributes[key]); 31808 } 31809 }); 31810 31811 _this2.trigger('data', event); 31812 31813 return; 31814 } 31815 31816 match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine); 31817 31818 if (match && match[1]) { 31819 event = { 31820 type: 'tag', 31821 tagType: 'part-inf' 31822 }; 31823 event.attributes = parseAttributes$1(match[1]); 31824 ['PART-TARGET'].forEach(function (key) { 31825 if (event.attributes.hasOwnProperty(key)) { 31826 event.attributes[key] = parseFloat(event.attributes[key]); 31827 } 31828 }); 31829 31830 _this2.trigger('data', event); 31831 31832 return; 31833 } 31834 31835 match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine); 31836 31837 if (match && match[1]) { 31838 event = { 31839 type: 'tag', 31840 tagType: 'preload-hint' 31841 }; 31842 event.attributes = parseAttributes$1(match[1]); 31843 ['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) { 31844 if (event.attributes.hasOwnProperty(key)) { 31845 event.attributes[key] = parseInt(event.attributes[key], 10); 31846 var subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset'; 31847 event.attributes.byterange = event.attributes.byterange || {}; 31848 event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object. 31849 31850 delete event.attributes[key]; 31851 } 31852 }); 31853 31854 _this2.trigger('data', event); 31855 31856 return; 31857 } 31858 31859 match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine); 31860 31861 if (match && match[1]) { 31862 event = { 31863 type: 'tag', 31864 tagType: 'rendition-report' 31865 }; 31866 event.attributes = parseAttributes$1(match[1]); 31867 ['LAST-MSN', 'LAST-PART'].forEach(function (key) { 31868 if (event.attributes.hasOwnProperty(key)) { 31869 event.attributes[key] = parseInt(event.attributes[key], 10); 31870 } 31871 }); 31872 31873 _this2.trigger('data', event); 31874 31875 return; 31876 } // unknown tag type 31877 31878 31879 _this2.trigger('data', { 31880 type: 'tag', 31881 data: newLine.slice(4) 31882 }); 31883 }); 31884 } 31885 /** 31886 * Add a parser for custom headers 31887 * 31888 * @param {Object} options a map of options for the added parser 31889 * @param {RegExp} options.expression a regular expression to match the custom header 31890 * @param {string} options.customType the custom type to register to the output 31891 * @param {Function} [options.dataParser] function to parse the line into an object 31892 * @param {boolean} [options.segment] should tag data be attached to the segment object 31893 */ 31894 ; 31895 31896 _proto.addParser = function addParser(_ref) { 31897 var _this3 = this; 31898 31899 var expression = _ref.expression, 31900 customType = _ref.customType, 31901 dataParser = _ref.dataParser, 31902 segment = _ref.segment; 31903 31904 if (typeof dataParser !== 'function') { 31905 dataParser = function dataParser(line) { 31906 return line; 31907 }; 31908 } 31909 31910 this.customParsers.push(function (line) { 31911 var match = expression.exec(line); 31912 31913 if (match) { 31914 _this3.trigger('data', { 31915 type: 'custom', 31916 data: dataParser(line), 31917 customType: customType, 31918 segment: segment 31919 }); 31920 31921 return true; 31922 } 31923 }); 31924 } 31925 /** 31926 * Add a custom header mapper 31927 * 31928 * @param {Object} options 31929 * @param {RegExp} options.expression a regular expression to match the custom header 31930 * @param {Function} options.map function to translate tag into a different tag 31931 */ 31932 ; 31933 31934 _proto.addTagMapper = function addTagMapper(_ref2) { 31935 var expression = _ref2.expression, 31936 map = _ref2.map; 31937 31938 var mapFn = function mapFn(line) { 31939 if (expression.test(line)) { 31940 return map(line); 31941 } 31942 31943 return line; 31944 }; 31945 31946 this.tagMappers.push(mapFn); 31947 }; 31948 31949 return ParseStream; 31950 }(Stream); 31951
31952 var camelCase = function camelCase(str) { 31953 return str.toLowerCase().replace(/-(\w)/g, function (a) { 31954 return a[1].toUpperCase(); 31955 }); 31956 }; 31957 31958 var camelCaseKeys = function camelCaseKeys(attributes) { 31959 var result = {}; 31960 Object.keys(attributes).forEach(function (key) { 31961 result[camelCase(key)] = attributes[key]; 31962 }); 31963 return result; 31964 }; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration 31965 // we need this helper because defaults are based upon targetDuration and 31966 // partTargetDuration being set, but they may not be if SERVER-CONTROL appears before 31967 // target durations are set. 31968 31969 31970 var setHoldBack = function setHoldBack(manifest) { 31971 var serverControl = manifest.serverControl, 31972 targetDuration = manifest.targetDuration, 31973 partTargetDuration = manifest.partTargetDuration; 31974 31975 if (!serverControl) { 31976 return; 31977 } 31978 31979 var tag = '#EXT-X-SERVER-CONTROL'; 31980 var hb = 'holdBack'; 31981 var phb = 'partHoldBack'; 31982 var minTargetDuration = targetDuration && targetDuration * 3; 31983 var minPartDuration = partTargetDuration && partTargetDuration * 2; 31984 31985 if (targetDuration && !serverControl.hasOwnProperty(hb)) { 31986 serverControl[hb] = minTargetDuration; 31987 this.trigger('info', { 31988 message: tag + " defaulting HOLD-BACK to targetDuration * 3 (" + minTargetDuration + ")." 31989 }); 31990 } 31991 31992 if (minTargetDuration && serverControl[hb] < minTargetDuration) { 31993 this.trigger('warn', { 31994 message: tag + " clamping HOLD-BACK (" + serverControl[hb] + ") to targetDuration * 3 (" + minTargetDuration + ")" 31995 }); 31996 serverControl[hb] = minTargetDuration; 31997 } // default no part hold back to part target duration * 3 31998 31999 32000 if (partTargetDuration && !serverControl.hasOwnProperty(phb)) { 32001 serverControl[phb] = partTargetDuration * 3; 32002 this.trigger('info', { 32003 message: tag + " defaulting PART-HOLD-BACK to partTargetDuration * 3 (" + serverControl[phb] + ")." 32004 }); 32005 } // if part hold back is too small default it to part target duration * 2 32006 32007 32008 if (partTargetDuration && serverControl[phb] < minPartDuration) { 32009 this.trigger('warn', { 32010 message: tag + " clamping PART-HOLD-BACK (" + serverControl[phb] + ") to partTargetDuration * 2 (" + minPartDuration + ")." 32011 }); 32012 serverControl[phb] = minPartDuration; 32013 } 32014 }; 32015 /** 32016 * A parser for M3U8 files. The current interpretation of the input is 32017 * exposed as a property `manifest` on parser objects. It's just two lines to 32018 * create and parse a manifest once you have the contents available as a string: 32019 * 32020 * ```js 32021 * var parser = new m3u8.Parser(); 32022 * parser.push(xhr.responseText); 32023 * ``` 32024 * 32025 * New input can later be applied to update the manifest object by calling 32026 * `push` again. 32027 * 32028 * The parser attempts to create a usable manifest object even if the 32029 * underlying input is somewhat nonsensical. It emits `info` and `warning` 32030 * events during the parse if it encounters input that seems invalid or 32031 * requires some property of the manifest object to be defaulted. 32032 * 32033 * @class Parser 32034 * @extends Stream 32035 */ 32036 32037 32038 var Parser = /*#__PURE__*/function (_Stream) { 32039 inheritsLoose(Parser, _Stream); 32040 32041 function Parser() { 32042 var _this; 32043 32044 _this = _Stream.call(this) || this; 32045 _this.lineStream = new LineStream(); 32046 _this.parseStream = new ParseStream(); 32047 32048 _this.lineStream.pipe(_this.parseStream); 32049 /* eslint-disable consistent-this */ 32050 32051 32052 var self = assertThisInitialized(_this); 32053 /* eslint-enable consistent-this */ 32054 32055 32056 var uris = []; 32057 var currentUri = {}; // if specified, the active EXT-X-MAP definition 32058 32059 var currentMap; // if specified, the active decryption key 32060 32061 var _key; 32062 32063 var hasParts = false; 32064 32065 var noop = function noop() {}; 32066 32067 var defaultMediaGroups = { 32068 'AUDIO': {}, 32069 'VIDEO': {}, 32070 'CLOSED-CAPTIONS': {}, 32071 'SUBTITLES': {} 32072 }; // This is the Widevine UUID from DASH IF IOP. The same exact string is 32073 // used in MPDs with Widevine encrypted streams. 32074 32075 var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities 32076 32077 var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data 32078 32079 _this.manifest = { 32080 allowCache: true, 32081 discontinuityStarts: [], 32082 segments: [] 32083 }; // keep track of the last seen segment's byte range end, as segments are not required 32084 // to provide the offset, in which case it defaults to the next byte after the 32085 // previous segment 32086 32087 var lastByterangeEnd = 0; // keep track of the last seen part's byte range end. 32088 32089 var lastPartByterangeEnd = 0; 32090 32091 _this.on('end', function () { 32092 // only add preloadSegment if we don't yet have a uri for it. 32093 // and we actually have parts/preloadHints 32094 if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) { 32095 return; 32096 } 32097 32098 if (!currentUri.map && currentMap) { 32099 currentUri.map = currentMap; 32100 } 32101 32102 if (!currentUri.key && _key) { 32103 currentUri.key = _key; 32104 } 32105 32106 if (!currentUri.timeline && typeof currentTimeline === 'number') { 32107 currentUri.timeline = currentTimeline; 32108 } 32109 32110 _this.manifest.preloadSegment = currentUri; 32111 }); // update the manifest with the m3u8 entry from the parse stream 32112 32113 32114 _this.parseStream.on('data', function (entry) { 32115 var mediaGroup; 32116 var rendition; 32117 ({ 32118 tag: function tag() { 32119 // switch based on the tag type 32120 (({ 32121 version: function version() { 32122 if (entry.version) { 32123 this.manifest.version = entry.version; 32124 } 32125 }, 32126 'allow-cache': function allowCache() { 32127 this.manifest.allowCache = entry.allowed; 32128 32129 if (!('allowed' in entry)) { 32130 this.trigger('info', { 32131 message: 'defaulting allowCache to YES' 32132 }); 32133 this.manifest.allowCache = true; 32134 } 32135 }, 32136 byterange: function byterange() { 32137 var byterange = {}; 32138 32139 if ('length' in entry) { 32140 currentUri.byterange = byterange; 32141 byterange.length = entry.length; 32142 32143 if (!('offset' in entry)) { 32144 /* 32145 * From the latest spec (as of this writing): 32146 * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2 32147 * 32148 * Same text since EXT-X-BYTERANGE's introduction in draft 7: 32149 * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1) 32150 * 32151 * "If o [offset] is not present, the sub-range begins at the next byte 32152 * following the sub-range of the previous media segment." 32153 */ 32154 entry.offset = lastByterangeEnd; 32155 } 32156 } 32157 32158 if ('offset' in entry) { 32159 currentUri.byterange = byterange; 32160 byterange.offset = entry.offset; 32161 } 32162 32163 lastByterangeEnd = byterange.offset + byterange.length; 32164 }, 32165 endlist: function endlist() { 32166 this.manifest.endList = true; 32167 }, 32168 inf: function inf() { 32169 if (!('mediaSequence' in this.manifest)) { 32170 this.manifest.mediaSequence = 0; 32171 this.trigger('info', { 32172 message: 'defaulting media sequence to zero' 32173 }); 32174 } 32175 32176 if (!('discontinuitySequence' in this.manifest)) { 32177 this.manifest.discontinuitySequence = 0; 32178 this.trigger('info', { 32179 message: 'defaulting discontinuity sequence to zero' 32180 }); 32181 } 32182 32183 if (entry.duration > 0) { 32184 currentUri.duration = entry.duration; 32185 } 32186 32187 if (entry.duration === 0) { 32188 currentUri.duration = 0.01; 32189 this.trigger('info', { 32190 message: 'updating zero segment duration to a small value' 32191 }); 32192 } 32193 32194 this.manifest.segments = uris; 32195 }, 32196 key: function key() { 32197 if (!entry.attributes) { 32198 this.trigger('warn', { 32199 message: 'ignoring key declaration without attribute list' 32200 }); 32201 return; 32202 } // clear the active encryption key 32203 32204 32205 if (entry.attributes.METHOD === 'NONE') { 32206 _key = null; 32207 return; 32208 } 32209 32210 if (!entry.attributes.URI) { 32211 this.trigger('warn', { 32212 message: 'ignoring key declaration without URI' 32213 }); 32214 return; 32215 } 32216 32217 if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') { 32218 this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this. 32219 32220 this.manifest.contentProtection['com.apple.fps.1_0'] = { 32221 attributes: entry.attributes 32222 }; 32223 return; 32224 } // check if the content is encrypted for Widevine
32225 // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf 32226 32227 32228 if (entry.attributes.KEYFORMAT === widevineUuid) { 32229 var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC']; 32230 32231 if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) { 32232 this.trigger('warn', { 32233 message: 'invalid key method provided for Widevine' 32234 }); 32235 return; 32236 } 32237 32238 if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') { 32239 this.trigger('warn', { 32240 message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead' 32241 }); 32242 } 32243 32244 if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') { 32245 this.trigger('warn', { 32246 message: 'invalid key URI provided for Widevine' 32247 }); 32248 return; 32249 } 32250 32251 if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) { 32252 this.trigger('warn', { 32253 message: 'invalid key ID provided for Widevine' 32254 }); 32255 return; 32256 } // if Widevine key attributes are valid, store them as `contentProtection` 32257 // on the manifest to emulate Widevine tag structure in a DASH mpd 32258 32259 32260 this.manifest.contentProtection = this.manifest.contentProtection || {}; 32261 this.manifest.contentProtection['com.widevine.alpha'] = { 32262 attributes: { 32263 schemeIdUri: entry.attributes.KEYFORMAT, 32264 // remove '0x' from the key id string 32265 keyId: entry.attributes.KEYID.substring(2) 32266 }, 32267 // decode the base64-encoded PSSH box 32268 pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1]) 32269 }; 32270 return; 32271 } 32272 32273 if (!entry.attributes.METHOD) { 32274 this.trigger('warn', { 32275 message: 'defaulting key method to AES-128' 32276 }); 32277 } // setup an encryption key for upcoming segments 32278 32279 32280 _key = { 32281 method: entry.attributes.METHOD || 'AES-128', 32282 uri: entry.attributes.URI 32283 }; 32284 32285 if (typeof entry.attributes.IV !== 'undefined') { 32286 _key.iv = entry.attributes.IV; 32287 } 32288 }, 32289 'media-sequence': function mediaSequence() { 32290 if (!isFinite(entry.number)) { 32291 this.trigger('warn', { 32292 message: 'ignoring invalid media sequence: ' + entry.number 32293 }); 32294 return; 32295 } 32296 32297 this.manifest.mediaSequence = entry.number; 32298 }, 32299 'discontinuity-sequence': function discontinuitySequence() { 32300 if (!isFinite(entry.number)) { 32301 this.trigger('warn', { 32302 message: 'ignoring invalid discontinuity sequence: ' + entry.number 32303 }); 32304 return; 32305 } 32306 32307 this.manifest.discontinuitySequence = entry.number; 32308 currentTimeline = entry.number; 32309 }, 32310 'playlist-type': function playlistType() { 32311 if (!/VOD|EVENT/.test(entry.playlistType)) { 32312 this.trigger('warn', { 32313 message: 'ignoring unknown playlist type: ' + entry.playlist 32314 }); 32315 return; 32316 } 32317 32318 this.manifest.playlistType = entry.playlistType; 32319 }, 32320 map: function map() { 32321 currentMap = {}; 32322 32323 if (entry.uri) { 32324 currentMap.uri = entry.uri; 32325 } 32326 32327 if (entry.byterange) { 32328 currentMap.byterange = entry.byterange; 32329 } 32330 32331 if (_key) { 32332 currentMap.key = _key; 32333 } 32334 }, 32335 'stream-inf': function streamInf() { 32336 this.manifest.playlists = uris; 32337 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 32338 32339 if (!entry.attributes) { 32340 this.trigger('warn', { 32341 message: 'ignoring empty stream-inf attributes' 32342 }); 32343 return; 32344 } 32345 32346 if (!currentUri.attributes) { 32347 currentUri.attributes = {}; 32348 } 32349 32350 _extends_1(currentUri.attributes, entry.attributes); 32351 }, 32352 media: function media() { 32353 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 32354 32355 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 32356 this.trigger('warn', { 32357 message: 'ignoring incomplete or missing media group' 32358 }); 32359 return; 32360 } // find the media group, creating defaults as necessary 32361 32362 32363 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 32364 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 32365 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata 32366 32367 rendition = { 32368 "default": /yes/i.test(entry.attributes.DEFAULT) 32369 }; 32370 32371 if (rendition["default"]) { 32372 rendition.autoselect = true; 32373 } else { 32374 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 32375 } 32376 32377 if (entry.attributes.LANGUAGE) { 32378 rendition.language = entry.attributes.LANGUAGE; 32379 } 32380 32381 if (entry.attributes.URI) { 32382 rendition.uri = entry.attributes.URI; 32383 } 32384 32385 if (entry.attributes['INSTREAM-ID']) { 32386 rendition.instreamId = entry.attributes['INSTREAM-ID']; 32387 } 32388 32389 if (entry.attributes.CHARACTERISTICS) { 32390 rendition.characteristics = entry.attributes.CHARACTERISTICS; 32391 } 32392 32393 if (entry.attributes.FORCED) { 32394 rendition.forced = /yes/i.test(entry.attributes.FORCED); 32395 } // insert the new rendition 32396 32397 32398 mediaGroup[entry.attributes.NAME] = rendition; 32399 }, 32400 discontinuity: function discontinuity() { 32401 currentTimeline += 1; 32402 currentUri.discontinuity = true; 32403 this.manifest.discontinuityStarts.push(uris.length); 32404 }, 32405 'program-date-time': function programDateTime() { 32406 if (typeof this.manifest.dateTimeString === 'undefined') { 32407 // PROGRAM-DATE-TIME is a media-segment tag, but for backwards 32408 // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag 32409 // to the manifest object 32410 // TODO: Consider removing this in future major version 32411 this.manifest.dateTimeString = entry.dateTimeString; 32412 this.manifest.dateTimeObject = entry.dateTimeObject; 32413 } 32414 32415 currentUri.dateTimeString = entry.dateTimeString; 32416 currentUri.dateTimeObject = entry.dateTimeObject; 32417 }, 32418 targetduration: function targetduration() { 32419 if (!isFinite(entry.duration) || entry.duration < 0) { 32420 this.trigger('warn', { 32421 message: 'ignoring invalid target duration: ' + entry.duration 32422 }); 32423 return; 32424 } 32425 32426 this.manifest.targetDuration = entry.duration; 32427 setHoldBack.call(this, this.manifest); 32428 }, 32429 start: function start() { 32430 if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) { 32431 this.trigger('warn', { 32432 message: 'ignoring start declaration without appropriate attribute list' 32433 }); 32434 return; 32435 } 32436 32437 this.manifest.start = { 32438 timeOffset: entry.attributes['TIME-OFFSET'], 32439 precise: entry.attributes.PRECISE 32440 }; 32441 }, 32442 'cue-out': function cueOut() { 32443 currentUri.cueOut = entry.data; 32444 }, 32445 'cue-out-cont': function cueOutCont() { 32446 currentUri.cueOutCont = entry.data; 32447 }, 32448 'cue-in': function cueIn() { 32449 currentUri.cueIn = entry.data; 32450 }, 32451 'skip': function skip() { 32452 this.manifest.skip = camelCaseKeys(entry.attributes); 32453 this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']); 32454 }, 32455 'part': function part() { 32456 var _this2 = this; 32457 32458 hasParts = true; // parts are always specifed before a segment 32459 32460 var segmentIndex = this.manifest.segments.length; 32461 var part = camelCaseKeys(entry.attributes); 32462 currentUri.parts = currentUri.parts || []; 32463 currentUri.parts.push(part); 32464 32465 if (part.byterange) { 32466 if (!part.byterange.hasOwnProperty('offset')) { 32467 part.byterange.offset = lastPartByterangeEnd; 32468 } 32469 32470 lastPartByterangeEnd = part.byterange.offset + part.byterange.length; 32471 } 32472 32473 var partIndex = currentUri.parts.length - 1; 32474 this.warnOnMissingAttributes_("#EXT-X-PART #" + partIndex + " for segment #" + segmentIndex, entry.attributes, ['URI', 'DURATION']); 32475 32476 if (this.manifest.renditionReports) {
32477 this.manifest.renditionReports.forEach(function (r, i) { 32478 if (!r.hasOwnProperty('lastPart')) { 32479 _this2.trigger('warn', { 32480 message: "#EXT-X-RENDITION-REPORT #" + i + " lacks required attribute(s): LAST-PART" 32481 }); 32482 } 32483 }); 32484 } 32485 }, 32486 'server-control': function serverControl() { 32487 var attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes); 32488 32489 if (!attrs.hasOwnProperty('canBlockReload')) { 32490 attrs.canBlockReload = false; 32491 this.trigger('info', { 32492 message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false' 32493 }); 32494 } 32495 32496 setHoldBack.call(this, this.manifest); 32497 32498 if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) { 32499 this.trigger('warn', { 32500 message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set' 32501 }); 32502 } 32503 }, 32504 'preload-hint': function preloadHint() { 32505 // parts are always specifed before a segment 32506 var segmentIndex = this.manifest.segments.length; 32507 var hint = camelCaseKeys(entry.attributes); 32508 var isPart = hint.type && hint.type === 'PART'; 32509 currentUri.preloadHints = currentUri.preloadHints || []; 32510 currentUri.preloadHints.push(hint); 32511 32512 if (hint.byterange) { 32513 if (!hint.byterange.hasOwnProperty('offset')) { 32514 // use last part byterange end or zero if not a part. 32515 hint.byterange.offset = isPart ? lastPartByterangeEnd : 0; 32516 32517 if (isPart) { 32518 lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length; 32519 } 32520 } 32521 } 32522 32523 var index = currentUri.preloadHints.length - 1; 32524 this.warnOnMissingAttributes_("#EXT-X-PRELOAD-HINT #" + index + " for segment #" + segmentIndex, entry.attributes, ['TYPE', 'URI']); 32525 32526 if (!hint.type) { 32527 return; 32528 } // search through all preload hints except for the current one for 32529 // a duplicate type. 32530 32531 32532 for (var i = 0; i < currentUri.preloadHints.length - 1; i++) { 32533 var otherHint = currentUri.preloadHints[i]; 32534 32535 if (!otherHint.type) { 32536 continue; 32537 } 32538 32539 if (otherHint.type === hint.type) { 32540 this.trigger('warn', { 32541 message: "#EXT-X-PRELOAD-HINT #" + index + " for segment #" + segmentIndex + " has the same TYPE " + hint.type + " as preload hint #" + i 32542 }); 32543 } 32544 } 32545 }, 32546 'rendition-report': function renditionReport() { 32547 var report = camelCaseKeys(entry.attributes); 32548 this.manifest.renditionReports = this.manifest.renditionReports || []; 32549 this.manifest.renditionReports.push(report); 32550 var index = this.manifest.renditionReports.length - 1; 32551 var required = ['LAST-MSN', 'URI']; 32552 32553 if (hasParts) { 32554 required.push('LAST-PART'); 32555 } 32556 32557 this.warnOnMissingAttributes_("#EXT-X-RENDITION-REPORT #" + index, entry.attributes, required); 32558 }, 32559 'part-inf': function partInf() { 32560 this.manifest.partInf = camelCaseKeys(entry.attributes); 32561 this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']); 32562 32563 if (this.manifest.partInf.partTarget) { 32564 this.manifest.partTargetDuration = this.manifest.partInf.partTarget; 32565 } 32566 32567 setHoldBack.call(this, this.manifest); 32568 } 32569 })[entry.tagType] || noop).call(self); 32570 }, 32571 uri: function uri() { 32572 currentUri.uri = entry.uri; 32573 uris.push(currentUri); // if no explicit duration was declared, use the target duration 32574 32575 if (this.manifest.targetDuration && !('duration' in currentUri)) { 32576 this.trigger('warn', { 32577 message: 'defaulting segment duration to the target duration' 32578 }); 32579 currentUri.duration = this.manifest.targetDuration; 32580 } // annotate with encryption information, if necessary 32581 32582 32583 if (_key) { 32584 currentUri.key = _key; 32585 } 32586 32587 currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary 32588 32589 if (currentMap) { 32590 currentUri.map = currentMap; 32591 }
32591 // reset the last byterange end as it needs to be 0 between parts 32592 32593 32594 lastPartByterangeEnd = 0; // prepare for the next URI 32595 32596 currentUri = {}; 32597 }, 32598 comment: function comment() {// comments are not important for playback 32599 }, 32600 custom: function custom() { 32601 // if this is segment-level data attach the output to the segment 32602 if (entry.segment) { 32603 currentUri.custom = currentUri.custom || {}; 32604 currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object 32605 } else { 32606 this.manifest.custom = this.manifest.custom || {}; 32607 this.manifest.custom[entry.customType] = entry.data; 32608 } 32609 } 32610 })[entry.type].call(self); 32611 }); 32612 32613 return _this; 32614 } 32615 32616 var _proto = Parser.prototype; 32617 32618 _proto.warnOnMissingAttributes_ = function warnOnMissingAttributes_(identifier, attributes, required) { 32619 var missing = []; 32620 required.forEach(function (key) { 32621 if (!attributes.hasOwnProperty(key)) { 32622 missing.push(key); 32623 } 32624 }); 32625 32626 if (missing.length) { 32627 this.trigger('warn', { 32628 message: identifier + " lacks required attribute(s): " + missing.join(', ') 32629 }); 32630 } 32631 } 32632 /** 32633 * Parse the input string and update the manifest object. 32634 * 32635 * @param {string} chunk a potentially incomplete portion of the manifest 32636 */ 32637 ; 32638 32639 _proto.push = function push(chunk) { 32640 this.lineStream.push(chunk); 32641 } 32642 /** 32643 * Flush any remaining input. This can be handy if the last line of an M3U8 32644 * manifest did not contain a trailing newline but the file has been 32645 * completely received. 32646 */ 32647 ; 32648 32649 _proto.end = function end() { 32650 // flush any buffered input 32651 this.lineStream.push('\n'); 32652 this.trigger('end'); 32653 } 32654 /** 32655 * Add an additional parser for non-standard tags 32656 * 32657 * @param {Object} options a map of options for the added parser 32658 * @param {RegExp} options.expression a regular expression to match the custom header 32659 * @param {string} options.type the type to register to the output 32660 * @param {Function} [options.dataParser] function to parse the line into an object 32661 * @param {boolean} [options.segment] should tag data be attached to the segment object 32662 */ 32663 ; 32664 32665 _proto.addParser = function addParser(options) { 32666 this.parseStream.addParser(options); 32667 } 32668 /** 32669 * Add a custom header mapper 32670 * 32671 * @param {Object} options 32672 * @param {RegExp} options.expression a regular expression to match the custom header 32673 * @param {Function} options.map function to translate tag into a different tag 32674 */ 32675 ; 32676 32677 _proto.addTagMapper = function addTagMapper(options) { 32678 this.parseStream.addTagMapper(options); 32679 }; 32680 32681 return Parser; 32682 }(Stream); 32683 32684 var regexs = { 32685 // to determine mime types 32686 mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/, 32687 webm: /^(vp0?[89]|av0?1|opus|vorbis)/, 32688 ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/, 32689 // to determine if a codec is audio or video 32690 video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/, 32691 audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/, 32692 text: /^(stpp.ttml.im1t)/, 32693 // mux.js support regex 32694 muxerVideo: /^(avc0?1)/, 32695 muxerAudio: /^(mp4a)/, 32696 // match nothing as muxer does not support text right now. 32697 // there cannot never be a character before the start of a string 32698 // so this matches nothing. 32699 muxerText: /a^/ 32700 }; 32701 var mediaTypes = ['video', 'audio', 'text']; 32702 var upperMediaTypes = ['Video', 'Audio', 'Text']; 32703 /** 32704 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 32705 * `avc1.<hhhhhh>` 32706 * 32707 * @param {string} codec 32708 * Codec string to translate 32709 * @return {string} 32710 * The translated codec string 32711 */ 32712 32713 var translateLegacyCodec = function translateLegacyCodec(codec) { 32714 if (!codec) { 32715 return codec; 32716 } 32717 32718 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 32719 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 32720 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 32721 return 'avc1.' + profileHex + '00' + avcLevelHex; 32722 });
vendor: 4,171 bytes, lines 32723-32868
32723 }; 32724 /** 32725 * @typedef {Object} ParsedCodecInfo 32726 * @property {number} codecCount 32727 * Number of codecs parsed 32728 * @property {string} [videoCodec] 32729 * Parsed video codec (if found) 32730 * @property {string} [videoObjectTypeIndicator] 32731 * Video object type indicator (if found) 32732 * @property {string|null} audioProfile 32733 * Audio profile 32734 */ 32735 32736 /** 32737 * Parses a codec string to retrieve the number of codecs specified, the video codec and 32738 * object type indicator, and the audio profile. 32739 * 32740 * @param {string} [codecString] 32741 * The codec string to parse 32742 * @return {ParsedCodecInfo} 32743 * Parsed codec info 32744 */ 32745 32746 var parseCodecs = function parseCodecs(codecString) { 32747 if (codecString === void 0) { 32748 codecString = ''; 32749 } 32750 32751 var codecs = codecString.split(','); 32752 var result = []; 32753 codecs.forEach(function (codec) { 32754 codec = codec.trim(); 32755 var codecType; 32756 mediaTypes.forEach(function (name) { 32757 var match = regexs[name].exec(codec.toLowerCase()); 32758 32759 if (!match || match.length <= 1) { 32760 return; 32761 } 32762 32763 codecType = name; // maintain codec case 32764 32765 var type = codec.substring(0, match[1].length); 32766 var details = codec.replace(type, ''); 32767 result.push({ 32768 type: type, 32769 details: details, 32770 mediaType: name 32771 }); 32772 }); 32773 32774 if (!codecType) { 32775 result.push({ 32776 type: codec, 32777 details: '', 32778 mediaType: 'unknown' 32779 }); 32780 } 32781 }); 32782 return result; 32783 }; 32784 /** 32785 * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is 32786 * a default alternate audio playlist for the provided audio group. 32787 * 32788 * @param {Object} master 32789 * The master playlist 32790 * @param {string} audioGroupId 32791 * ID of the audio group for which to find the default codec info 32792 * @return {ParsedCodecInfo} 32793 * Parsed codec info 32794 */ 32795 32796 var codecsFromDefault = function codecsFromDefault(master, audioGroupId) { 32797 if (!master.mediaGroups.AUDIO || !audioGroupId) { 32798 return null; 32799 } 32800 32801 var audioGroup = master.mediaGroups.AUDIO[audioGroupId]; 32802 32803 if (!audioGroup) { 32804 return null; 32805 } 32806 32807 for (var name in audioGroup) { 32808 var audioType = audioGroup[name]; 32809 32810 if (audioType["default"] && audioType.playlists) { 32811 // codec should be the same for all playlists within the audio type 32812 return parseCodecs(audioType.playlists[0].attributes.CODECS); 32813 } 32814 } 32815 32816 return null; 32817 }; 32818 var isAudioCodec = function isAudioCodec(codec) { 32819 if (codec === void 0) { 32820 codec = ''; 32821 } 32822 32823 return regexs.audio.test(codec.trim().toLowerCase()); 32824 }; 32825 var isTextCodec = function isTextCodec(codec) { 32826 if (codec === void 0) { 32827 codec = ''; 32828 } 32829 32830 return regexs.text.test(codec.trim().toLowerCase()); 32831 }; 32832 var getMimeForCodec = function getMimeForCodec(codecString) { 32833 if (!codecString || typeof codecString !== 'string') { 32834 return; 32835 } 32836 32837 var codecs = codecString.toLowerCase().split(',').map(function (c) { 32838 return translateLegacyCodec(c.trim()); 32839 }); // default to video type 32840 32841 var type = 'video'; // only change to audio type if the only codec we have is 32842 // audio 32843 32844 if (codecs.length === 1 && isAudioCodec(codecs[0])) { 32845 type = 'audio'; 32846 } else if (codecs.length === 1 && isTextCodec(codecs[0])) { 32847 // text uses application/<container> for now 32848 type = 'application'; 32849 } // default the container to mp4 32850 32851 32852 var container = 'mp4'; // every codec must be able to go into the container 32853 // for that container to be the correct one 32854 32855 if (codecs.every(function (c) { 32856 return regexs.mp4.test(c); 32857 })) { 32858 container = 'mp4'; 32859 } else if (codecs.every(function (c) { 32860 return regexs.webm.test(c); 32861 })) { 32862 container = 'webm'; 32863 } else if (codecs.every(function (c) { 32864 return regexs.ogg.test(c); 32865 })) { 32866 container = 'ogg'; 32867 } 32868
32869 return type + "/" + container + ";codecs=\"" + codecString + "\""; 32870 }; 32871 var browserSupportsCodec = function browserSupportsCodec(codecString) { 32872 if (codecString === void 0) { 32873 codecString = ''; 32874 } 32875 32876 return window_1.MediaSource && window_1.MediaSource.isTypeSupported && window_1.MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false; 32877 }; 32878 var muxerSupportsCodec = function muxerSupportsCodec(codecString) { 32879 if (codecString === void 0) { 32880 codecString = ''; 32881 } 32882 32883 return codecString.toLowerCase().split(',').every(function (codec) { 32884 codec = codec.trim(); // any match is supported. 32885 32886 for (var i = 0; i < upperMediaTypes.length; i++) { 32887 var type = upperMediaTypes[i]; 32888 32889 if (regexs["muxer" + type].test(codec)) { 32890 return true; 32891 } 32892 } 32893 32894 return false; 32895 }); 32896 }; 32897 var DEFAULT_AUDIO_CODEC = 'mp4a.40.2'; 32898 var DEFAULT_VIDEO_CODEC = 'avc1.4d400d'; 32899 32900 var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 32901 var DASH_REGEX = /^application\/dash\+xml/i; 32902 /** 32903 * Returns a string that describes the type of source based on a video source object's 32904 * media type. 32905 * 32906 * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type} 32907 * 32908 * @param {string} type 32909 * Video source object media type 32910 * @return {('hls'|'dash'|'vhs-json'|null)} 32911 * VHS source type string 32912 */ 32913 32914 var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) { 32915 if (MPEGURL_REGEX.test(type)) { 32916 return 'hls'; 32917 } 32918 32919 if (DASH_REGEX.test(type)) { 32920 return 'dash'; 32921 } // Denotes the special case of a manifest object passed to http-streaming instead of a 32922 // source URL. 32923 // 32924 // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types. 32925 // 32926 // In this case, vnd stands for vendor, video.js for the organization, VHS for this 32927 // project, and the +json suffix identifies the structure of the media type. 32928 32929 32930 if (type === 'application/vnd.videojs.vhs+json') { 32931 return 'vhs-json'; 32932 } 32933 32934 return null; 32935 }; 32936 32937 /** 32938 * "Shallow freezes" an object to render it immutable. 32939 * Uses `Object.freeze` if available, 32940 * otherwise the immutability is only in the type. 32941 * 32942 * Is used to create "enum like" objects. 32943 * 32944 * @template T 32945 * @param {T} object the object to freeze 32946 * @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default, 32947 * allows to inject custom object constructor for tests 32948 * @returns {Readonly<T>} 32949 * 32950 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze 32951 */ 32952 32953 function freeze(object, oc) { 32954 if (oc === undefined) { 32955 oc = Object; 32956 } 32957 32958 return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object; 32959 } 32960 /** 32961 * All mime types that are allowed as input to `DOMParser.parseFromString` 32962 * 32963 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN 32964 * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec 32965 * @see DOMParser.prototype.parseFromString 32966 */ 32967 32968 32969 var MIME_TYPE = freeze({ 32970 /** 32971 * `text/html`, the only mime type that triggers treating an XML document as HTML. 32972 * 32973 * @see DOMParser.SupportedType.isHTML 32974 * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration 32975 * @see https://en.wikipedia.org/wiki/HTML Wikipedia 32976 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN 32977 * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec 32978 */ 32979 HTML: 'text/html', 32980 32981 /** 32982 * Helper method to check a mime type if it indicates an HTML document 32983 * 32984 * @param {string} [value] 32985 * @returns {boolean} 32986 * 32987 * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration 32988 * @see https://en.wikipedia.org/wiki/HTML Wikipedia 32989 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN 32990 * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring */ 32991 isHTML: function isHTML(value) { 32992 return value === MIME_TYPE.HTML; 32993 }, 32994 32995 /** 32996 * `application/xml`, the standard mime type for XML documents. 32997 * 32998 * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration 32999 * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303 33000 * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia 33001 */ 33002 XML_APPLICATION: 'application/xml', 33003 33004 /** 33005 * `text/html`, an alias for `application/xml`. 33006 *
33007 * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303 33008 * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration 33009 * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia 33010 */ 33011 XML_TEXT: 'text/xml', 33012 33013 /** 33014 * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace, 33015 * but is parsed as an XML document. 33016 * 33017 * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration 33018 * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec 33019 * @see https://en.wikipedia.org/wiki/XHTML Wikipedia 33020 */ 33021 XML_XHTML_APPLICATION: 'application/xhtml+xml', 33022 33023 /** 33024 * `image/svg+xml`, 33025 * 33026 * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration 33027 * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1 33028 * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia 33029 */ 33030 XML_SVG_IMAGE: 'image/svg+xml' 33031 }); 33032 /** 33033 * Namespaces that are used in this code base. 33034 * 33035 * @see http://www.w3.org/TR/REC-xml-names 33036 */ 33037 33038 var NAMESPACE$2 = freeze({ 33039 /** 33040 * The XHTML namespace. 33041 * 33042 * @see http://www.w3.org/1999/xhtml 33043 */ 33044 HTML: 'http://www.w3.org/1999/xhtml', 33045 33046 /** 33047 * Checks if `uri` equals `NAMESPACE.HTML`. 33048 * 33049 * @param {string} [uri] 33050 * 33051 * @see NAMESPACE.HTML 33052 */ 33053 isHTML: function isHTML(uri) { 33054 return uri === NAMESPACE$2.HTML; 33055 }, 33056 33057 /** 33058 * The SVG namespace. 33059 * 33060 * @see http://www.w3.org/2000/svg 33061 */ 33062 SVG: 'http://www.w3.org/2000/svg', 33063 33064 /** 33065 * The `xml:` namespace. 33066 * 33067 * @see http://www.w3.org/XML/1998/namespace 33068 */ 33069 XML: 'http://www.w3.org/XML/1998/namespace', 33070 33071 /** 33072 * The `xmlns:` namespace 33073 * 33074 * @see https://www.w3.org/2000/xmlns/ 33075 */ 33076 XMLNS: 'http://www.w3.org/2000/xmlns/' 33077 }); 33078 var freeze_1 = freeze; 33079 var MIME_TYPE_1 = MIME_TYPE; 33080 var NAMESPACE_1 = NAMESPACE$2; 33081 var conventions = { 33082 freeze: freeze_1, 33083 MIME_TYPE: MIME_TYPE_1, 33084 NAMESPACE: NAMESPACE_1 33085 }; 33086 33087 var entities = createCommonjsModule(function (module, exports) { 33088 var freeze = conventions.freeze; 33089 /** 33090 * The entities that are predefined in every XML document. 33091 * 33092 * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1 33093 * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0 33094 * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia 33095 */ 33096 33097 exports.XML_ENTITIES = freeze({ 33098 amp: '&', 33099 apos: "'", 33100 gt: '>', 33101 lt: '<', 33102 quot: '"' 33103 }); 33104 /** 33105 * A map of currently 241 entities that are detected in an HTML document. 33106 * They contain all entries from `XML_ENTITIES`. 33107 * 33108 * @see XML_ENTITIES 33109 * @see DOMParser.parseFromString 33110 * @see DOMImplementation.prototype.createHTMLDocument 33111 * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec 33112 * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names 33113 * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML 33114 * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML) 33115 * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML) 33116 */ 33117 33118 exports.HTML_ENTITIES = freeze({ 33119 lt: '<', 33120 gt: '>', 33121 amp: '&', 33122 quot: '"', 33123 apos: "'", 33124 Agrave: "Ã", 33125 Aacute: "Ã", 33126 Acirc: "Ã", 33127 Atilde: "Ã", 33128 Auml: "Ã", 33129 Aring: "Ã ", 33130 AElig: "Ã", 33131 Ccedil: "Ã", 33132 Egrave: "Ã", 33133 Eacute: "Ã", 33134 Ecirc: "Ã", 33135 Euml: "Ã", 33136 Igrave: "Ã", 33137 Iacute: "Ã", 33138 Icirc: "Ã", 33139 Iuml: "Ã", 33140 ETH: "Ã", 33141 Ntilde: "Ã", 33142 Ograve: "Ã", 33143 Oacute: "Ã", 33144 Ocirc: "Ã", 33145 Otilde: "Ã", 33146 Ouml: "Ã",
33147 Oslash: "Ã", 33148 Ugrave: "Ã", 33149 Uacute: "Ã", 33150 Ucirc: "Ã", 33151 Uuml: "Ã", 33152 Yacute: "Ã", 33153 THORN: "Ã", 33154 szlig: "Ã", 33155 agrave: "à ", 33156 aacute: "á", 33157 acirc: "â", 33158 atilde: "ã", 33159 auml: "ä", 33160 aring: "Ã¥", 33161 aelig: "æ", 33162 ccedil: "ç", 33163 egrave: "è", 33164 eacute: "é", 33165 ecirc: "ê", 33166 euml: "ë", 33167 igrave: "ì", 33168 iacute: "Ã", 33169 icirc: "î", 33170 iuml: "ï", 33171 eth: "ð", 33172 ntilde: "ñ", 33173 ograve: "ò", 33174 oacute: "ó", 33175 ocirc: "ô", 33176 otilde: "õ", 33177 ouml: "ö", 33178 oslash: "ø", 33179 ugrave: "ù", 33180 uacute: "ú", 33181 ucirc: "û", 33182 uuml: "ü", 33183 yacute: "ý", 33184 thorn: "þ", 33185 yuml: "ÿ", 33186 nbsp: "\xA0", 33187 iexcl: "¡", 33188 cent: "¢", 33189 pound: "£", 33190 curren: "¤", 33191 yen: "Â¥", 33192 brvbar: "¦", 33193 sect: "§", 33194 uml: "¨", 33195 copy: "©", 33196 ordf: "ª", 33197 laquo: "«", 33198 not: "¬", 33199 shy: "ÂÂ", 33200 reg: "®", 33201 macr: "¯", 33202 deg: "°", 33203 plusmn: "±", 33204 sup2: "²", 33205 sup3: "³", 33206 acute: "´", 33207 micro: "µ", 33208 para: "¶", 33209 middot: "·", 33210 cedil: "¸", 33211 sup1: "¹", 33212 ordm: "º", 33213 raquo: "»", 33214 frac14: "¼", 33215 frac12: "½", 33216 frac34: "¾", 33217 iquest: "¿", 33218 times: "Ã", 33219 divide: "÷", 33220 forall: "â", 33221 part: "â", 33222 exist: "â", 33223 empty: "â ", 33224 nabla: "â", 33225 isin: "â", 33226 notin: "â", 33227 ni: "â", 33228 prod: "â", 33229 sum: "â", 33230 minus: "â", 33231 lowast: "â", 33232 radic: "â", 33233 prop: "â", 33234 infin: "â", 33235 ang: "â ", 33236 and: "â§", 33237 or: "â¨", 33238 cap: "â©", 33239 cup: "âª", 33240 'int': "â«", 33241 there4: "â´", 33242 sim: "â¼", 33243 cong: "â ", 33244 asymp: "â", 33245 ne: "â ", 33246 equiv: "â¡", 33247 le: "â¤", 33248 ge: "â¥", 33249 sub: "â", 33250 sup: "â", 33251 nsub: "â", 33252 sube: "â", 33253 supe: "â", 33254 oplus: "â", 33255 otimes: "â", 33256 perp: "â¥", 33257 sdot: "â ", 33258 Alpha: "Î", 33259 Beta: "Î", 33260 Gamma: "Î", 33261 Delta: "Î", 33262 Epsilon: "Î", 33263 Zeta: "Î", 33264 Eta: "Î", 33265 Theta: "Î", 33266 Iota: "Î", 33267 Kappa: "Î", 33268 Lambda: "Î", 33269 Mu: "Î", 33270 Nu: "Î", 33271 Xi: "Î", 33272 Omicron: "Î", 33273 Pi: "Î ", 33274 Rho: "Ρ", 33275 Sigma: "Σ", 33276 Tau: "Τ", 33277 Upsilon: "Î¥", 33278 Phi: "Φ", 33279 Chi: "Χ", 33280 Psi: "Ψ", 33281 Omega: "Ω", 33282 alpha: "α", 33283 beta: "β", 33284 gamma: "γ", 33285 delta: "δ", 33286 epsilon: "ε", 33287 zeta: "ζ", 33288 eta: "η", 33289 theta: "θ", 33290 iota: "ι", 33291 kappa: "κ", 33292 lambda: "λ", 33293 mu: "μ", 33294 nu: "ν", 33295 xi: "ξ", 33296 omicron: "ο", 33297 pi: "Ï", 33298 rho: "Ï", 33299 sigmaf: "Ï", 33300 sigma: "Ï", 33301 tau: "Ï", 33302 upsilon: "Ï ", 33303 phi: "Ï", 33304 chi: "Ï", 33305 psi: "Ï", 33306 omega: "Ï", 33307 thetasym: "Ï", 33308 upsih: "Ï", 33309 piv: "Ï", 33310 OElig: "Å", 33311 oelig: "Å", 33312 Scaron: "Å ", 33313 scaron: "Å¡", 33314 Yuml: "Ÿ", 33315 fnof: "Æ", 33316 circ: "Ë", 33317 tilde: "Ë", 33318 ensp: "â", 33319 emsp: "â", 33320 thinsp: "â", 33321 zwnj: "â", 33322 zwj: "â", 33323 lrm: "â", 33324 rlm: "â", 33325 ndash: "â", 33326 mdash: "â", 33327 lsquo: "â", 33328 rsquo: "â", 33329 sbquo: "â", 33330 ldquo: "â", 33331 rdquo: "â", 33332 bdquo: "â", 33333 dagger: "â ", 33334 Dagger: "â¡", 33335 bull: "â¢", 33336 hellip: "â¦", 33337 permil: "â°", 33338 prime: "â²", 33339 Prime: "â³", 33340 lsaquo: "â¹", 33341 rsaquo: "âº", 33342 oline: "â¾", 33343 euro: "â¬", 33344 trade: "â¢", 33345 larr: "â", 33346 uarr: "â", 33347 rarr: "â", 33348 darr: "â", 33349 harr: "â", 33350 crarr: "âµ", 33351 lceil: "â", 33352 rceil: "â", 33353 lfloor: "â", 33354 rfloor: "â", 33355 loz: "â", 33356 spades: "â ", 33357 clubs: "â£", 33358 hearts: "â¥", 33359 diams: "â¦" 33360 });
vendor: 4,817 bytes, lines 33361-33489
33361 /** 33362 * @deprecated use `HTML_ENTITIES` instead 33363 * @see HTML_ENTITIES 33364 */ 33365 33366 exports.entityMap = exports.HTML_ENTITIES; 33367 }); 33368 entities.XML_ENTITIES; 33369 entities.HTML_ENTITIES; 33370 entities.entityMap; 33371 33372 var NAMESPACE$1 = conventions.NAMESPACE; //[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] 33373 //[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] 33374 //[5] Name ::= NameStartChar (NameChar)* 33375 33376 var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/; //\u10000-\uEFFFF 33377 33378 var nameChar = new RegExp("[\\-\\.0-9" + nameStartChar.source.slice(1, -1) + "\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"); 33379 var tagNamePattern = new RegExp('^' + nameStartChar.source + nameChar.source + '*(?:\:' + nameStartChar.source + nameChar.source + '*)?$'); //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/ 33380 //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(',') 33381 //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE 33382 //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE 33383 33384 var S_TAG = 0; //tag name offerring 33385 33386 var S_ATTR = 1; //attr name offerring 33387 33388 var S_ATTR_SPACE = 2; //attr name end and space offer 33389 33390 var S_EQ = 3; //=space? 33391 33392 var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only) 33393 33394 var S_ATTR_END = 5; //attr value end and no space(quot end) 33395 33396 var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer) 33397 33398 var S_TAG_CLOSE = 7; //closed el<el /> 33399 33400 /** 33401 * Creates an error that will not be caught by XMLReader aka the SAX parser. 33402 * 33403 * @param {string} message 33404 * @param {any?} locator Optional, can provide details about the location in the source 33405 * @constructor 33406 */ 33407 33408 function ParseError(message, locator) { 33409 this.message = message; 33410 this.locator = locator; 33411 if (Error.captureStackTrace) Error.captureStackTrace(this, ParseError); 33412 } 33413 33414 ParseError.prototype = new Error(); 33415 ParseError.prototype.name = ParseError.name; 33416 33417 function XMLReader() {} 33418 33419 XMLReader.prototype = { 33420 parse: function parse(source, defaultNSMap, entityMap) { 33421 var domBuilder = this.domBuilder; 33422 domBuilder.startDocument(); 33423 33424 _copy(defaultNSMap, defaultNSMap = {}); 33425 33426 _parse(source, defaultNSMap, entityMap, domBuilder, this.errorHandler); 33427 33428 domBuilder.endDocument(); 33429 } 33430 }; 33431 33432 function _parse(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) { 33433 function fixedFromCharCode(code) { 33434 // String.prototype.fromCharCode does not supports 33435 // > 2 bytes unicode chars directly 33436 if (code > 0xffff) { 33437 code -= 0x10000; 33438 var surrogate1 = 0xd800 + (code >> 10), 33439 surrogate2 = 0xdc00 + (code & 0x3ff); 33440 return String.fromCharCode(surrogate1, surrogate2); 33441 } else { 33442 return String.fromCharCode(code); 33443 } 33444 } 33445 33446 function entityReplacer(a) { 33447 var k = a.slice(1, -1); 33448 33449 if (k in entityMap) { 33450 return entityMap[k]; 33451 } else if (k.charAt(0) === '#') { 33452 return fixedFromCharCode(parseInt(k.substr(1).replace('x', '0x'))); 33453 } else { 33454 errorHandler.error('entity not found:' + a); 33455 return a; 33456 } 33457 } 33458 33459 function appendText(end) { 33460 //has some bugs 33461 if (end > start) { 33462 var xt = source.substring(start, end).replace(/&#?\w+;/g, entityReplacer); 33463 locator && position(start); 33464 domBuilder.characters(xt, 0, end - start); 33465 start = end; 33466 } 33467 } 33468 33469 function position(p, m) { 33470 while (p >= lineEnd && (m = linePattern.exec(source))) { 33471 lineStart = m.index; 33472 lineEnd = lineStart + m[0].length; 33473 locator.lineNumber++; //console.log('line++:',locator,startPos,endPos) 33474 } 33475 33476 locator.columnNumber = p - lineStart + 1; 33477 } 33478 33479 var lineStart = 0; 33480 var lineEnd = 0; 33481 var linePattern = /.*(?:\r\n?|\n)|.*$/g; 33482 var locator = domBuilder.locator; 33483 var parseStack = [{ 33484 currentNSMap: defaultNSMapCopy 33485 }]; 33486 var closeMap = {}; 33487 var start = 0; 33488 33489 while (true) {
vendor: 16,124 bytes, lines 33490-34017
33490 try { 33491 var tagStart = source.indexOf('<', start); 33492 33493 if (tagStart < 0) { 33494 if (!source.substr(start).match(/^\s*$/)) { 33495 var doc = domBuilder.doc; 33496 var text = doc.createTextNode(source.substr(start)); 33497 doc.appendChild(text); 33498 domBuilder.currentElement = text; 33499 } 33500 33501 return; 33502 } 33503 33504 if (tagStart > start) { 33505 appendText(tagStart); 33506 } 33507 33508 switch (source.charAt(tagStart + 1)) { 33509 case '/': 33510 var end = source.indexOf('>', tagStart + 3); 33511 var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, ''); 33512 var config = parseStack.pop(); 33513 33514 if (end < 0) { 33515 tagName = source.substring(tagStart + 2).replace(/[\s<].*/, ''); 33516 errorHandler.error("end tag name: " + tagName + ' is not complete:' + config.tagName); 33517 end = tagStart + 1 + tagName.length; 33518 } else if (tagName.match(/\s</)) { 33519 tagName = tagName.replace(/[\s<].*/, ''); 33520 errorHandler.error("end tag name: " + tagName + ' maybe not complete'); 33521 end = tagStart + 1 + tagName.length; 33522 } 33523 33524 var localNSMap = config.localNSMap; 33525 var endMatch = config.tagName == tagName; 33526 var endIgnoreCaseMach = endMatch || config.tagName && config.tagName.toLowerCase() == tagName.toLowerCase(); 33527 33528 if (endIgnoreCaseMach) { 33529 domBuilder.endElement(config.uri, config.localName, tagName); 33530 33531 if (localNSMap) { 33532 for (var prefix in localNSMap) { 33533 domBuilder.endPrefixMapping(prefix); 33534 } 33535 } 33536 33537 if (!endMatch) { 33538 errorHandler.fatalError("end tag name: " + tagName + ' is not match the current start tagName:' + config.tagName); // No known test case 33539 } 33540 } else { 33541 parseStack.push(config); 33542 } 33543 33544 end++; 33545 break; 33546 // end elment 33547 33548 case '?': 33549 // <?...?> 33550 locator && position(tagStart); 33551 end = parseInstruction(source, tagStart, domBuilder); 33552 break; 33553 33554 case '!': 33555 // <!doctype,<![CDATA,<!-- 33556 locator && position(tagStart); 33557 end = parseDCC(source, tagStart, domBuilder, errorHandler); 33558 break; 33559 33560 default: 33561 locator && position(tagStart); 33562 var el = new ElementAttributes(); 33563 var currentNSMap = parseStack[parseStack.length - 1].currentNSMap; //elStartEnd 33564 33565 var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler); 33566 var len = el.length; 33567 33568 if (!el.closed && fixSelfClosed(source, end, el.tagName, closeMap)) { 33569 el.closed = true; 33570 33571 if (!entityMap.nbsp) { 33572 errorHandler.warning('unclosed xml attribute'); 33573 } 33574 } 33575 33576 if (locator && len) { 33577 var locator2 = copyLocator(locator, {}); //try{//attribute position fixed 33578 33579 for (var i = 0; i < len; i++) { 33580 var a = el[i]; 33581 position(a.offset); 33582 a.locator = copyLocator(locator, {}); 33583 } 33584 33585 domBuilder.locator = locator2; 33586 33587 if (appendElement(el, domBuilder, currentNSMap)) { 33588 parseStack.push(el); 33589 } 33590 33591 domBuilder.locator = locator; 33592 } else { 33593 if (appendElement(el, domBuilder, currentNSMap)) { 33594 parseStack.push(el); 33595 } 33596 } 33597 33598 if (NAMESPACE$1.isHTML(el.uri) && !el.closed) { 33599 end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder); 33600 } else { 33601 end++; 33602 } 33603 33604 } 33605 } catch (e) { 33606 if (e instanceof ParseError) { 33607 throw e; 33608 } 33609 33610 errorHandler.error('element parse error: ' + e); 33611 end = -1; 33612 } 33613 33614 if (end > start) { 33615 start = end; 33616 } else { 33617 //TODO: è¿éæå¯è½saxåéï¼æä½ç½®é误é£é© 33618 appendText(Math.max(tagStart, start) + 1); 33619 } 33620 } 33621 } 33622 33623 function copyLocator(f, t) { 33624 t.lineNumber = f.lineNumber; 33625 t.columnNumber = f.columnNumber; 33626 return t; 33627 } 33628 /** 33629 * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack); 33630 * @return end of the elementStartPart(end of elementEndPart for selfClosed el) 33631 */ 33632 33633 33634 function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler) { 33635 /** 33636 * @param {string} qname 33637 * @param {string} value 33638 * @param {number} startIndex 33639 */ 33640 function addAttribute(qname, value, startIndex) { 33641 if (qname in el.attributeNames) errorHandler.fatalError('Attribute ' + qname + ' redefined'); 33642 el.addValue(qname, value, startIndex); 33643 } 33644 33645 var attrName; 33646 var value; 33647 var p = ++start; 33648 var s = S_TAG; //status 33649 33650 while (true) { 33651 var c = source.charAt(p); 33652 33653 switch (c) { 33654 case '=': 33655 if (s === S_ATTR) { 33656 //attrName 33657 attrName = source.slice(start, p); 33658 s = S_EQ; 33659 } else if (s === S_ATTR_SPACE) { 33660 s = S_EQ; 33661 } else { 33662 //fatalError: equal must after attrName or space after attrName 33663 throw new Error('attribute equal must after attrName'); // No known test case 33664 } 33665 33666 break; 33667 33668 case '\'': 33669 case '"': 33670 if (s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE 33671 ) { 33672 //equal 33673 if (s === S_ATTR) { 33674 errorHandler.warning('attribute value must after "="'); 33675 attrName = source.slice(start, p); 33676 } 33677 33678 start = p + 1; 33679 p = source.indexOf(c, start); 33680 33681 if (p > 0) { 33682 value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); 33683 addAttribute(attrName, value, start - 1); 33684 s = S_ATTR_END; 33685 } else { 33686 //fatalError: no end quot match 33687 throw new Error('attribute value no end \'' + c + '\' match'); 33688 } 33689 } else if (s == S_ATTR_NOQUOT_VALUE) { 33690 value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); //console.log(attrName,value,start,p) 33691 33692 addAttribute(attrName, value, start); //console.dir(el) 33693 33694 errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!'); 33695 start = p + 1; 33696 s = S_ATTR_END; 33697 } else { 33698 //fatalError: no equal before 33699 throw new Error('attribute value must after "="'); // No known test case 33700 } 33701 33702 break; 33703 33704 case '/': 33705 switch (s) { 33706 case S_TAG: 33707 el.setTagName(source.slice(start, p)); 33708 33709 case S_ATTR_END: 33710 case S_TAG_SPACE: 33711 case S_TAG_CLOSE: 33712 s = S_TAG_CLOSE; 33713 el.closed = true; 33714 33715 case S_ATTR_NOQUOT_VALUE: 33716 case S_ATTR: 33717 case S_ATTR_SPACE: 33718 break; 33719 //case S_EQ: 33720 33721 default: 33722 throw new Error("attribute invalid close char('/')"); 33723 // No known test case 33724 } 33725 33726 break; 33727 33728 case '': 33729 //end document 33730 errorHandler.error('unexpected end of input'); 33731 33732 if (s == S_TAG) { 33733 el.setTagName(source.slice(start, p)); 33734 } 33735 33736 return p; 33737 33738 case '>': 33739 switch (s) { 33740 case S_TAG: 33741 el.setTagName(source.slice(start, p)); 33742 33743 case S_ATTR_END: 33744 case S_TAG_SPACE: 33745 case S_TAG_CLOSE: 33746 break; 33747 //normal 33748 33749 case S_ATTR_NOQUOT_VALUE: //Compatible state 33750 33751 case S_ATTR: 33752 value = source.slice(start, p); 33753 33754 if (value.slice(-1) === '/') { 33755 el.closed = true; 33756 value = value.slice(0, -1); 33757 } 33758 33759 case S_ATTR_SPACE: 33760 if (s === S_ATTR_SPACE) { 33761 value = attrName; 33762 } 33763 33764 if (s == S_ATTR_NOQUOT_VALUE) { 33765 errorHandler.warning('attribute "' + value + '" missed quot(")!'); 33766 addAttribute(attrName, value.replace(/&#?\w+;/g, entityReplacer), start); 33767 } else { 33768 if (!NAMESPACE$1.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)) { 33769 errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!'); 33770 } 33771 33772 addAttribute(value, value, start); 33773 } 33774 33775 break; 33776 33777 case S_EQ: 33778 throw new Error('attribute value missed!!'); 33779 } // console.log(tagName,tagNamePattern,tagNamePattern.test(tagName)) 33780 33781 33782 return p; 33783 33784 /*xml space '\x20' | #x9 | #xD | #xA; */ 33785 33786 case "\x80": 33787 c = ' '; 33788 33789 default: 33790 if (c <= ' ') { 33791 //space 33792 switch (s) { 33793 case S_TAG: 33794 el.setTagName(source.slice(start, p)); //tagName 33795 33796 s = S_TAG_SPACE; 33797 break; 33798 33799 case S_ATTR: 33800 attrName = source.slice(start, p); 33801 s = S_ATTR_SPACE; 33802 break; 33803 33804 case S_ATTR_NOQUOT_VALUE: 33805 var value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); 33806 errorHandler.warning('attribute "' + value + '" missed quot(")!!'); 33807 addAttribute(attrName, value, start); 33808 33809 case S_ATTR_END: 33810 s = S_TAG_SPACE; 33811 break; 33812 //case S_TAG_SPACE: 33813 //case S_EQ: 33814 //case S_ATTR_SPACE: 33815 // void();break; 33816 //case S_TAG_CLOSE: 33817 //ignore warning 33818 } 33819 } else { 33820 //not space 33821 //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE 33822 //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE 33823 switch (s) { 33824 //case S_TAG:void();break; 33825 //case S_ATTR:void();break; 33826 //case S_ATTR_NOQUOT_VALUE:void();break; 33827 case S_ATTR_SPACE: 33828 el.tagName; 33829 33830 if (!NAMESPACE$1.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) { 33831 errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!'); 33832 } 33833 33834 addAttribute(attrName, attrName, start); 33835 start = p; 33836 s = S_ATTR; 33837 break; 33838 33839 case S_ATTR_END: 33840 errorHandler.warning('attribute space is required"' + attrName + '"!!'); 33841 33842 case S_TAG_SPACE: 33843 s = S_ATTR; 33844 start = p; 33845 break; 33846 33847 case S_EQ: 33848 s = S_ATTR_NOQUOT_VALUE; 33849 start = p; 33850 break; 33851 33852 case S_TAG_CLOSE: 33853 throw new Error("elements closed character '/' and '>' must be connected to"); 33854 } 33855 } 33856 33857 } //end outer switch 33858 //console.log('p++',p) 33859 33860 33861 p++; 33862 } 33863 } 33864 /** 33865 * @return true if has new namespace define 33866 */ 33867 33868 33869 function appendElement(el, domBuilder, currentNSMap) { 33870 var tagName = el.tagName; 33871 var localNSMap = null; //var currentNSMap = parseStack[parseStack.length-1].currentNSMap; 33872 33873 var i = el.length; 33874 33875 while (i--) { 33876 var a = el[i]; 33877 var qName = a.qName; 33878 var value = a.value; 33879 var nsp = qName.indexOf(':'); 33880 33881 if (nsp > 0) { 33882 var prefix = a.prefix = qName.slice(0, nsp); 33883 var localName = qName.slice(nsp + 1); 33884 var nsPrefix = prefix === 'xmlns' && localName; 33885 } else { 33886 localName = qName; 33887 prefix = null; 33888 nsPrefix = qName === 'xmlns' && ''; 33889 } //can not set prefix,because prefix !== '' 33890 33891 33892 a.localName = localName; //prefix == null for no ns prefix attribute 33893 33894 if (nsPrefix !== false) { 33895 //hack!! 33896 if (localNSMap == null) { 33897 localNSMap = {}; //console.log(currentNSMap,0) 33898 33899 _copy(currentNSMap, currentNSMap = {}); //console.log(currentNSMap,1) 33900 33901 } 33902 33903 currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; 33904 a.uri = NAMESPACE$1.XMLNS; 33905 domBuilder.startPrefixMapping(nsPrefix, value); 33906 } 33907 } 33908 33909 var i = el.length; 33910 33911 while (i--) { 33912 a = el[i]; 33913 var prefix = a.prefix; 33914 33915 if (prefix) { 33916 //no prefix attribute has no namespace 33917 if (prefix === 'xml') { 33918 a.uri = NAMESPACE$1.XML; 33919 } 33920 33921 if (prefix !== 'xmlns') { 33922 a.uri = currentNSMap[prefix || '']; //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)} 33923 } 33924 } 33925 } 33926 33927 var nsp = tagName.indexOf(':'); 33928 33929 if (nsp > 0) { 33930 prefix = el.prefix = tagName.slice(0, nsp); 33931 localName = el.localName = tagName.slice(nsp + 1); 33932 } else { 33933 prefix = null; //important!! 33934 33935 localName = el.localName = tagName; 33936 } //no prefix element has default namespace 33937 33938 33939 var ns = el.uri = currentNSMap[prefix || '']; 33940 domBuilder.startElement(ns, localName, tagName, el); //endPrefixMapping and startPrefixMapping have not any help for dom builder 33941 //localNSMap = null 33942 33943 if (el.closed) { 33944 domBuilder.endElement(ns, localName, tagName); 33945 33946 if (localNSMap) { 33947 for (prefix in localNSMap) { 33948 domBuilder.endPrefixMapping(prefix); 33949 } 33950 } 33951 } else { 33952 el.currentNSMap = currentNSMap; 33953 el.localNSMap = localNSMap; //parseStack.push(el); 33954 33955 return true; 33956 } 33957 } 33958 33959 function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) { 33960 if (/^(?:script|textarea)$/i.test(tagName)) { 33961 var elEndStart = source.indexOf('</' + tagName + '>', elStartEnd); 33962 var text = source.substring(elStartEnd + 1, elEndStart); 33963 33964 if (/[&<]/.test(text)) { 33965 if (/^script$/i.test(tagName)) { 33966 //if(!/\]\]>/.test(text)){ 33967 //lexHandler.startCDATA(); 33968 domBuilder.characters(text, 0, text.length); //lexHandler.endCDATA(); 33969 33970 return elEndStart; //} 33971 } //}else{//text area 33972 33973 33974 text = text.replace(/&#?\w+;/g, entityReplacer); 33975 domBuilder.characters(text, 0, text.length); 33976 return elEndStart; //} 33977 } 33978 } 33979 33980 return elStartEnd + 1; 33981 } 33982 33983 function fixSelfClosed(source, elStartEnd, tagName, closeMap) { 33984 //if(tagName in closeMap){ 33985 var pos = closeMap[tagName]; 33986 33987 if (pos == null) { 33988 //console.log(tagName) 33989 pos = source.lastIndexOf('</' + tagName + '>'); 33990 33991 if (pos < elStartEnd) { 33992 //å¿è®°éå 33993 pos = source.lastIndexOf('</' + tagName); 33994 } 33995 33996 closeMap[tagName] = pos; 33997 } 33998 33999 return pos < elStartEnd; //} 34000 } 34001 34002 function _copy(source, target) { 34003 for (var n in source) { 34004 target[n] = source[n]; 34005 } 34006 } 34007 34008 function parseDCC(source, start, domBuilder, errorHandler) { 34009 //sure start with '<!' 34010 var next = source.charAt(start + 2); 34011 34012 switch (next) { 34013 case '-': 34014 if (source.charAt(start + 3) === '-') { 34015 var end = source.indexOf('-->', start + 4); //append comment source.substring(4,end)//<!-- 34016 34017 if (end > start) {
34018 domBuilder.comment(source, start + 4, end - start - 4); 34019 return end + 3; 34020 } else { 34021 errorHandler.error("Unclosed comment"); 34022 return -1; 34023 } 34024 } else { 34025 //error 34026 return -1; 34027 } 34028 34029 default: 34030 if (source.substr(start + 3, 6) == 'CDATA[') { 34031 var end = source.indexOf(']]>', start + 9); 34032 domBuilder.startCDATA(); 34033 domBuilder.characters(source, start + 9, end - start - 9); 34034 domBuilder.endCDATA(); 34035 return end + 3; 34036 } //<!DOCTYPE 34037 //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId) 34038 34039 34040 var matchs = split(source, start); 34041 var len = matchs.length; 34042 34043 if (len > 1 && /!doctype/i.test(matchs[0][0])) { 34044 var name = matchs[1][0]; 34045 var pubid = false; 34046 var sysid = false; 34047 34048 if (len > 3) { 34049 if (/^public$/i.test(matchs[2][0])) { 34050 pubid = matchs[3][0]; 34051 sysid = len > 4 && matchs[4][0]; 34052 } else if (/^system$/i.test(matchs[2][0])) { 34053 sysid = matchs[3][0]; 34054 } 34055 } 34056 34057 var lastMatch = matchs[len - 1]; 34058 domBuilder.startDTD(name, pubid, sysid); 34059 domBuilder.endDTD(); 34060 return lastMatch.index + lastMatch[0].length; 34061 } 34062 34063 } 34064 34065 return -1; 34066 } 34067 34068 function parseInstruction(source, start, domBuilder) { 34069 var end = source.indexOf('?>', start); 34070 34071 if (end) { 34072 var match = source.substring(start, end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/); 34073 34074 if (match) { 34075 match[0].length; 34076 domBuilder.processingInstruction(match[1], match[2]); 34077 return end + 2; 34078 } else { 34079 //error 34080 return -1; 34081 } 34082 } 34083 34084 return -1; 34085 } 34086 34087 function ElementAttributes() { 34088 this.attributeNames = {}; 34089 } 34090 34091 ElementAttributes.prototype = { 34092 setTagName: function setTagName(tagName) { 34093 if (!tagNamePattern.test(tagName)) { 34094 throw new Error('invalid tagName:' + tagName); 34095 } 34096 34097 this.tagName = tagName; 34098 }, 34099 addValue: function addValue(qName, value, offset) { 34100 if (!tagNamePattern.test(qName)) { 34101 throw new Error('invalid attribute:' + qName); 34102 } 34103 34104 this.attributeNames[qName] = this.length; 34105 this[this.length++] = { 34106 qName: qName, 34107 value: value, 34108 offset: offset 34109 }; 34110 }, 34111 length: 0, 34112 getLocalName: function getLocalName(i) { 34113 return this[i].localName; 34114 }, 34115 getLocator: function getLocator(i) { 34116 return this[i].locator; 34117 }, 34118 getQName: function getQName(i) { 34119 return this[i].qName; 34120 }, 34121 getURI: function getURI(i) { 34122 return this[i].uri; 34123 }, 34124 getValue: function getValue(i) { 34125 return this[i].value; 34126 } // ,getIndex:function(uri, localName)){ 34127 // if(localName){ 34128 // 34129 // }else{ 34130 // var qName = uri 34131 // } 34132 // }, 34133 // getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))}, 34134 // getType:function(uri,localName){} 34135 // getType:function(i){}, 34136 34137 }; 34138 34139 function split(source, start) { 34140 var match; 34141 var buf = []; 34142 var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g; 34143 reg.lastIndex = start; 34144 reg.exec(source); //skip < 34145 34146 while (match = reg.exec(source)) { 34147 buf.push(match); 34148 if (match[1]) return buf; 34149 } 34150 } 34151 34152 var XMLReader_1 = XMLReader; 34153 var ParseError_1 = ParseError; 34154 var sax = { 34155 XMLReader: XMLReader_1, 34156 ParseError: ParseError_1 34157 }; 34158 34159 var NAMESPACE = conventions.NAMESPACE; 34160 /** 34161 * A prerequisite for `[].filter`, to drop elements that are empty 34162 * @param {string} input 34163 * @returns {boolean} 34164 */ 34165 34166 function notEmptyString(input) { 34167 return input !== ''; 34168 } 34169 /** 34170 * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace 34171 * @see https://infra.spec.whatwg.org/#ascii-whitespace 34172 * 34173 * @param {string} input 34174 * @returns {string[]} (can be empty) 34175 */ 34176 34177 34178 function splitOnASCIIWhitespace(input) { 34179 // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE 34180 return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : []; 34181 } 34182 /** 34183 * Adds element as a key to current if it is not already present. 34184 * 34185 * @param {Record<string, boolean | undefined>} current 34186 * @param {string} element 34187 * @returns {Record<string, boolean | undefined>} 34188 */ 34189 34190 34191 function orderedSetReducer(current, element) { 34192 if (!current.hasOwnProperty(element)) { 34193 current[element] = true; 34194 } 34195 34196 return current; 34197 } 34198 /** 34199 * @see https://infra.spec.whatwg.org/#ordered-set 34200 * @param {string} input 34201 * @returns {string[]} 34202 */ 34203 34204 34205 function toOrderedSet(input) { 34206 if (!input) return []; 34207 var list = splitOnASCIIWhitespace(input); 34208 return Object.keys(list.reduce(orderedSetReducer, {})); 34209 } 34210 /** 34211 * Uses `list.indexOf` to implement something like `Array.prototype.includes`, 34212 * which we can not rely on being available. 34213 * 34214 * @param {any[]} list 34215 * @returns {function(any): boolean} 34216 */ 34217 34218 34219 function arrayIncludes(list) { 34220 return function (element) { 34221 return list && list.indexOf(element) !== -1; 34222 }; 34223 } 34224 34225 function copy(src, dest) { 34226 for (var p in src) { 34227 dest[p] = src[p]; 34228 } 34229 } 34230 /** 34231 ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));? 34232 ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));? 34233 */ 34234 34235 34236 function _extends(Class, Super) { 34237 var pt = Class.prototype; 34238 34239 if (!(pt instanceof Super)) { 34240 var t = function t() {}; 34241 t.prototype = Super.prototype; 34242 t = new t(); 34243 copy(pt, t); 34244 Class.prototype = pt = t; 34245 } 34246 34247 if (pt.constructor != Class) { 34248 if (typeof Class != 'function') { 34249 console.error("unknow Class:" + Class); 34250 } 34251 34252 pt.constructor = Class; 34253 } 34254 } // Node Types 34255 34256 34257 var NodeType = {};
34258 var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; 34259 var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; 34260 var TEXT_NODE = NodeType.TEXT_NODE = 3; 34261 var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; 34262 var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; 34263 var ENTITY_NODE = NodeType.ENTITY_NODE = 6; 34264 var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; 34265 var COMMENT_NODE = NodeType.COMMENT_NODE = 8; 34266 var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; 34267 var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; 34268 var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; 34269 var NOTATION_NODE = NodeType.NOTATION_NODE = 12; // ExceptionCode 34270 34271 var ExceptionCode = {}; 34272 var ExceptionMessage = {}; 34273 ExceptionCode.INDEX_SIZE_ERR = (ExceptionMessage[1] = "Index size error", 1); 34274 ExceptionCode.DOMSTRING_SIZE_ERR = (ExceptionMessage[2] = "DOMString size error", 2); 34275 var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = (ExceptionMessage[3] = "Hierarchy request error", 3); 34276 ExceptionCode.WRONG_DOCUMENT_ERR = (ExceptionMessage[4] = "Wrong document", 4); 34277 ExceptionCode.INVALID_CHARACTER_ERR = (ExceptionMessage[5] = "Invalid character", 5); 34278 ExceptionCode.NO_DATA_ALLOWED_ERR = (ExceptionMessage[6] = "No data allowed", 6); 34279 ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = (ExceptionMessage[7] = "No modification allowed", 7); 34280 var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = (ExceptionMessage[8] = "Not found", 8); 34281 ExceptionCode.NOT_SUPPORTED_ERR = (ExceptionMessage[9] = "Not supported", 9); 34282 var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = (ExceptionMessage[10] = "Attribute in use", 10); //level2 34283 34284 ExceptionCode.INVALID_STATE_ERR = (ExceptionMessage[11] = "Invalid state", 11); 34285 ExceptionCode.SYNTAX_ERR = (ExceptionMessage[12] = "Syntax error", 12); 34286 ExceptionCode.INVALID_MODIFICATION_ERR = (ExceptionMessage[13] = "Invalid modification", 13); 34287 ExceptionCode.NAMESPACE_ERR = (ExceptionMessage[14] = "Invalid namespace", 14); 34288 ExceptionCode.INVALID_ACCESS_ERR = (ExceptionMessage[15] = "Invalid access", 15); 34289 /** 34290 * DOM Level 2 34291 * Object DOMException 34292 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html 34293 * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html 34294 */ 34295 34296 function DOMException(code, message) { 34297 if (message instanceof Error) { 34298 var error = message; 34299 } else { 34300 error = this; 34301 Error.call(this, ExceptionMessage[code]); 34302 this.message = ExceptionMessage[code]; 34303 if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException); 34304 } 34305 34306 error.code = code; 34307 if (message) this.message = this.message + ": " + message; 34308 return error; 34309 } 34310 DOMException.prototype = Error.prototype; 34311 copy(ExceptionCode, DOMException); 34312 /** 34313 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 34314 * 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. 34315 * The items in the NodeList are accessible via an integral index, starting from 0. 34316 */ 34317 34318 function NodeList() {} 34319 NodeList.prototype = { 34320 /** 34321 * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive. 34322 * @standard level1 34323 */ 34324 length: 0, 34325 34326 /** 34327 * 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. 34328 * @standard level1 34329 * @param index unsigned long 34330 * Index into the collection. 34331 * @return Node 34332 * The node at the indexth position in the NodeList, or null if that is not a valid index. 34333 */ 34334 item: function item(index) { 34335 return this[index] || null; 34336 }, 34337 toString: function toString(isHTML, nodeFilter) { 34338 for (var buf = [], i = 0; i < this.length; i++) { 34339 serializeToString(this[i], buf, isHTML, nodeFilter); 34340 } 34341 34342 return buf.join(''); 34343 } 34344 }; 34345 34346 function LiveNodeList(node, refresh) { 34347 this._node = node; 34348 this._refresh = refresh; 34349 34350 _updateLiveList(this); 34351 } 34352 34353 function _updateLiveList(list) { 34354 var inc = list._node._inc || list._node.ownerDocument._inc; 34355 34356 if (list._inc != inc) { 34357 var ls = list._refresh(list._node); //console.log(ls.length) 34358 34359 34360 __set__(list, 'length', ls.length); 34361 34362 copy(ls, list); 34363 list._inc = inc; 34364 } 34365 } 34366 34367 LiveNodeList.prototype.item = function (i) { 34368 _updateLiveList(this); 34369 34370 return this[i]; 34371 }; 34372 34373 _extends(LiveNodeList, NodeList); 34374 /** 34375 * Objects implementing the NamedNodeMap interface are used 34376 * to represent collections of nodes that can be accessed by name. 34377 * Note that NamedNodeMap does not inherit from NodeList; 34378 * NamedNodeMaps are not maintained in any particular order. 34379 * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index, 34380 * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, 34381 * and does not imply that the DOM specifies an order to these Nodes. 34382 * NamedNodeMap objects in the DOM are live. 34383 * used for attributes or DocumentType entities 34384 */ 34385 34386 34387 function NamedNodeMap() {} 34388 34389 function _findNodeIndex(list, node) { 34390 var i = list.length; 34391 34392 while (i--) { 34393 if (list[i] === node) { 34394 return i; 34395 } 34396 } 34397 } 34398 34399 function _addNamedNode(el, list, newAttr, oldAttr) { 34400 if (oldAttr) { 34401 list[_findNodeIndex(list, oldAttr)] = newAttr; 34402 } else { 34403 list[list.length++] = newAttr; 34404 } 34405 34406 if (el) { 34407 newAttr.ownerElement = el; 34408 var doc = el.ownerDocument; 34409 34410 if (doc) { 34411 oldAttr && _onRemoveAttribute(doc, el, oldAttr); 34412 34413 _onAddAttribute(doc, el, newAttr); 34414 } 34415 } 34416 } 34417 34418 function _removeNamedNode(el, list, attr) { 34419 //console.log('remove attr:'+attr) 34420 var i = _findNodeIndex(list, attr); 34421 34422 if (i >= 0) { 34423 var lastIndex = list.length - 1; 34424 34425 while (i < lastIndex) { 34426 list[i] = list[++i]; 34427 } 34428 34429 list.length = lastIndex; 34430 34431 if (el) { 34432 var doc = el.ownerDocument; 34433 34434 if (doc) { 34435 _onRemoveAttribute(doc, el, attr); 34436 34437 attr.ownerElement = null; 34438 } 34439 } 34440 } else { 34441 throw DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr)); 34442 } 34443 } 34444 34445 NamedNodeMap.prototype = { 34446 length: 0, 34447 item: NodeList.prototype.item, 34448 getNamedItem: function getNamedItem(key) { 34449 // if(key.indexOf(':')>0 || key == 'xmlns'){ 34450 // return null; 34451 // } 34452 //console.log() 34453 var i = this.length; 34454 34455 while (i--) { 34456 var attr = this[i]; //console.log(attr.nodeName,key) 34457 34458 if (attr.nodeName == key) { 34459 return attr; 34460 } 34461 } 34462 }, 34463 setNamedItem: function setNamedItem(attr) { 34464 var el = attr.ownerElement; 34465 34466 if (el && el != this._ownerElement) { 34467 throw new DOMException(INUSE_ATTRIBUTE_ERR); 34468 } 34469 34470 var oldAttr = this.getNamedItem(attr.nodeName); 34471 34472 _addNamedNode(this._ownerElement, this, attr, oldAttr); 34473 34474 return oldAttr; 34475 }, 34476 34477 /* returns Node */ 34478 setNamedItemNS: function setNamedItemNS(attr) {
vendor: 4,998 bytes, lines 34479-34606
34479 // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR 34480 var el = attr.ownerElement, 34481 oldAttr; 34482 34483 if (el && el != this._ownerElement) { 34484 throw new DOMException(INUSE_ATTRIBUTE_ERR); 34485 } 34486 34487 oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName); 34488 34489 _addNamedNode(this._ownerElement, this, attr, oldAttr); 34490 34491 return oldAttr; 34492 }, 34493 34494 /* returns Node */ 34495 removeNamedItem: function removeNamedItem(key) { 34496 var attr = this.getNamedItem(key); 34497 34498 _removeNamedNode(this._ownerElement, this, attr); 34499 34500 return attr; 34501 }, 34502 // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR 34503 //for level2 34504 removeNamedItemNS: function removeNamedItemNS(namespaceURI, localName) { 34505 var attr = this.getNamedItemNS(namespaceURI, localName); 34506 34507 _removeNamedNode(this._ownerElement, this, attr); 34508 34509 return attr; 34510 }, 34511 getNamedItemNS: function getNamedItemNS(namespaceURI, localName) { 34512 var i = this.length; 34513 34514 while (i--) { 34515 var node = this[i]; 34516 34517 if (node.localName == localName && node.namespaceURI == namespaceURI) { 34518 return node; 34519 } 34520 } 34521 34522 return null; 34523 } 34524 }; 34525 /** 34526 * The DOMImplementation interface represents an object providing methods 34527 * which are not dependent on any particular document. 34528 * Such an object is returned by the `Document.implementation` property. 34529 * 34530 * __The individual methods describe the differences compared to the specs.__ 34531 * 34532 * @constructor 34533 * 34534 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN 34535 * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial) 34536 * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core 34537 * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core 34538 * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard 34539 */ 34540 34541 function DOMImplementation() {} 34542 34543 DOMImplementation.prototype = { 34544 /** 34545 * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported. 34546 * The different implementations fairly diverged in what kind of features were reported. 34547 * The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use. 34548 * 34549 * @deprecated It is deprecated and modern browsers return true in all cases. 34550 * 34551 * @param {string} feature 34552 * @param {string} [version] 34553 * @returns {boolean} always true 34554 * 34555 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN 34556 * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core 34557 * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard 34558 */ 34559 hasFeature: function hasFeature(feature, version) { 34560 return true; 34561 }, 34562 34563 /** 34564 * Creates an XML Document object of the specified type with its document element. 34565 * 34566 * __It behaves slightly different from the description in the living standard__: 34567 * - There is no interface/class `XMLDocument`, it returns a `Document` instance. 34568 * - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared. 34569 * - this implementation is not validating names or qualified names 34570 * (when parsing XML strings, the SAX parser takes care of that) 34571 * 34572 * @param {string|null} namespaceURI 34573 * @param {string} qualifiedName 34574 * @param {DocumentType=null} doctype 34575 * @returns {Document} 34576 * 34577 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN 34578 * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial) 34579 * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument DOM Level 2 Core 34580 * 34581 * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract 34582 * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names 34583 * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names 34584 */ 34585 createDocument: function createDocument(namespaceURI, qualifiedName, doctype) { 34586 var doc = new Document(); 34587 doc.implementation = this; 34588 doc.childNodes = new NodeList(); 34589 doc.doctype = doctype || null; 34590 34591 if (doctype) { 34592 doc.appendChild(doctype); 34593 } 34594 34595 if (qualifiedName) { 34596 var root = doc.createElementNS(namespaceURI, qualifiedName); 34597 doc.appendChild(root); 34598 } 34599 34600 return doc; 34601 }, 34602 34603 /** 34604 * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`. 34605 * 34606 * __This behavior is slightly different from the in the specs__:
34607 * - this implementation is not validating names or qualified names 34608 * (when parsing XML strings, the SAX parser takes care of that) 34609 * 34610 * @param {string} qualifiedName 34611 * @param {string} [publicId] 34612 * @param {string} [systemId] 34613 * @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation 34614 * or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()` 34615 * 34616 * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN 34617 * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core 34618 * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard 34619 * 34620 * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract 34621 * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names 34622 * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names 34623 */ 34624 createDocumentType: function createDocumentType(qualifiedName, publicId, systemId) { 34625 var node = new DocumentType(); 34626 node.name = qualifiedName; 34627 node.nodeName = qualifiedName; 34628 node.publicId = publicId || ''; 34629 node.systemId = systemId || ''; 34630 return node; 34631 } 34632 }; 34633 /** 34634 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 34635 */ 34636 34637 function Node() {} 34638 Node.prototype = { 34639 firstChild: null, 34640 lastChild: null, 34641 previousSibling: null, 34642 nextSibling: null, 34643 attributes: null, 34644 parentNode: null, 34645 childNodes: null, 34646 ownerDocument: null, 34647 nodeValue: null, 34648 namespaceURI: null, 34649 prefix: null, 34650 localName: null, 34651 // Modified in DOM Level 2: 34652 insertBefore: function insertBefore(newChild, refChild) { 34653 //raises 34654 return _insertBefore(this, newChild, refChild); 34655 }, 34656 replaceChild: function replaceChild(newChild, oldChild) { 34657 //raises 34658 this.insertBefore(newChild, oldChild); 34659 34660 if (oldChild) { 34661 this.removeChild(oldChild); 34662 } 34663 }, 34664 removeChild: function removeChild(oldChild) { 34665 return _removeChild(this, oldChild); 34666 }, 34667 appendChild: function appendChild(newChild) { 34668 return this.insertBefore(newChild, null); 34669 }, 34670 hasChildNodes: function hasChildNodes() { 34671 return this.firstChild != null; 34672 }, 34673 cloneNode: function cloneNode(deep) { 34674 return _cloneNode(this.ownerDocument || this, this, deep); 34675 }, 34676 // Modified in DOM Level 2: 34677 normalize: function normalize() { 34678 var child = this.firstChild; 34679 34680 while (child) { 34681 var next = child.nextSibling; 34682 34683 if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) { 34684 this.removeChild(next); 34685 child.appendData(next.data); 34686 } else { 34687 child.normalize(); 34688 child = next; 34689 } 34690 } 34691 }, 34692 // Introduced in DOM Level 2: 34693 isSupported: function isSupported(feature, version) { 34694 return this.ownerDocument.implementation.hasFeature(feature, version); 34695 }, 34696 // Introduced in DOM Level 2: 34697 hasAttributes: function hasAttributes() { 34698 return this.attributes.length > 0; 34699 }, 34700 lookupPrefix: function lookupPrefix(namespaceURI) { 34701 var el = this; 34702 34703 while (el) { 34704 var map = el._nsMap; //console.dir(map) 34705 34706 if (map) { 34707 for (var n in map) { 34708 if (map[n] == namespaceURI) { 34709 return n; 34710 } 34711 } 34712 } 34713 34714 el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode; 34715 } 34716 34717 return null; 34718 }, 34719 // Introduced in DOM Level 3: 34720 lookupNamespaceURI: function lookupNamespaceURI(prefix) { 34721 var el = this; 34722 34723 while (el) { 34724 var map = el._nsMap; //console.dir(map) 34725 34726 if (map) { 34727 if (prefix in map) { 34728 return map[prefix]; 34729 } 34730 } 34731 34732 el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode; 34733 } 34734 34735 return null; 34736 }, 34737 // Introduced in DOM Level 3: 34738 isDefaultNamespace: function isDefaultNamespace(namespaceURI) { 34739 var prefix = this.lookupPrefix(namespaceURI); 34740 return prefix == null; 34741 } 34742 }; 34743 34744 function _xmlEncoder(c) { 34745 return c == '<' && '<' || c == '>' && '>' || c == '&' && '&' || c == '"' && '"
34745' || '&#' + c.charCodeAt() + ';'; 34746 } 34747 34748 copy(NodeType, Node); 34749 copy(NodeType, Node.prototype); 34750 /** 34751 * @param callback return true for continue,false for break 34752 * @return boolean true: break visit; 34753 */ 34754 34755 function _visitNode(node, callback) { 34756 if (callback(node)) { 34757 return true; 34758 } 34759 34760 if (node = node.firstChild) { 34761 do { 34762 if (_visitNode(node, callback)) { 34763 return true; 34764 } 34765 } while (node = node.nextSibling); 34766 } 34767 } 34768 34769 function Document() {} 34770 34771 function _onAddAttribute(doc, el, newAttr) { 34772 doc && doc._inc++; 34773 var ns = newAttr.namespaceURI; 34774 34775 if (ns === NAMESPACE.XMLNS) { 34776 //update namespace 34777 el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value; 34778 } 34779 } 34780 34781 function _onRemoveAttribute(doc, el, newAttr, remove) { 34782 doc && doc._inc++; 34783 var ns = newAttr.namespaceURI; 34784 34785 if (ns === NAMESPACE.XMLNS) { 34786 //update namespace 34787 delete el._nsMap[newAttr.prefix ? newAttr.localName : '']; 34788 } 34789 } 34790 34791 function _onUpdateChild(doc, el, newChild) { 34792 if (doc && doc._inc) { 34793 doc._inc++; //update childNodes 34794 34795 var cs = el.childNodes; 34796 34797 if (newChild) { 34798 cs[cs.length++] = newChild; 34799 } else { 34800 //console.log(1) 34801 var child = el.firstChild; 34802 var i = 0; 34803 34804 while (child) { 34805 cs[i++] = child; 34806 child = child.nextSibling; 34807 } 34808 34809 cs.length = i; 34810 } 34811 } 34812 } 34813 /** 34814 * attributes; 34815 * children; 34816 * 34817 * writeable properties: 34818 * nodeValue,Attr:value,CharacterData:data 34819 * prefix 34820 */ 34821 34822 34823 function _removeChild(parentNode, child) { 34824 var previous = child.previousSibling; 34825 var next = child.nextSibling; 34826 34827 if (previous) { 34828 previous.nextSibling = next; 34829 } else { 34830 parentNode.firstChild = next; 34831 } 34832 34833 if (next) { 34834 next.previousSibling = previous; 34835 } else { 34836 parentNode.lastChild = previous; 34837 } 34838 34839 _onUpdateChild(parentNode.ownerDocument, parentNode); 34840 34841 return child; 34842 } 34843 /** 34844 * preformance key(refChild == null) 34845 */ 34846 34847 34848 function _insertBefore(parentNode, newChild, nextChild) { 34849 var cp = newChild.parentNode; 34850 34851 if (cp) { 34852 cp.removeChild(newChild); //remove and update 34853 } 34854 34855 if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) { 34856 var newFirst = newChild.firstChild; 34857 34858 if (newFirst == null) { 34859 return newChild; 34860 } 34861 34862 var newLast = newChild.lastChild; 34863 } else { 34864 newFirst = newLast = newChild; 34865 } 34866 34867 var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild; 34868 newFirst.previousSibling = pre; 34869 newLast.nextSibling = nextChild; 34870 34871 if (pre) { 34872 pre.nextSibling = newFirst; 34873 } else { 34874 parentNode.firstChild = newFirst; 34875 } 34876 34877 if (nextChild == null) { 34878 parentNode.lastChild = newLast; 34879 } else { 34880 nextChild.previousSibling = newLast; 34881 } 34882 34883 do { 34884 newFirst.parentNode = parentNode; 34885 } while (newFirst !== newLast && (newFirst = newFirst.nextSibling)); 34886 34887 _onUpdateChild(parentNode.ownerDocument || parentNode, parentNode); //console.log(parentNode.lastChild.nextSibling == null) 34888 34889 34890 if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) { 34891 newChild.firstChild = newChild.lastChild = null; 34892 } 34893 34894 return newChild; 34895 } 34896 34897 function _appendSingleChild(parentNode, newChild) { 34898 var cp = newChild.parentNode; 34899 34900 if (cp) { 34901 var pre = parentNode.lastChild; 34902 cp.removeChild(newChild); //remove and update 34903 34904 var pre = parentNode.lastChild; 34905 } 34906 34907 var pre = parentNode.lastChild; 34908 newChild.parentNode = parentNode; 34909 newChild.previousSibling = pre; 34910 newChild.nextSibling = null; 34911 34912 if (pre) { 34913 pre.nextSibling = newChild; 34914 } else { 34915 parentNode.firstChild = newChild; 34916 } 34917 34918 parentNode.lastChild = newChild; 34919 34920 _onUpdateChild(parentNode.ownerDocument, parentNode, newChild); 34921 34922 return newChild; //console.log("__aa",parentNode.lastChild.nextSibling == null) 34923 } 34924 34925 Document.prototype = { 34926 //implementation : null, 34927 nodeName: '#document', 34928 nodeType: DOCUMENT_NODE, 34929 doctype: null, 34930 documentElement: null, 34931 _inc: 1, 34932 insertBefore: function insertBefore(newChild, refChild) { 34933 //raises 34934 if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) { 34935 var child = newChild.firstChild; 34936 34937 while (child) { 34938 var next = child.nextSibling; 34939 this.insertBefore(child, refChild); 34940 child = next; 34941 } 34942 34943 return newChild; 34944 } 34945 34946 if (this.documentElement == null && newChild.nodeType == ELEMENT_NODE) { 34947 this.documentElement = newChild; 34948 } 34949 34950 return _insertBefore(this, newChild, refChild), newChild.ownerDocument = this, newChild; 34951 }, 34952 removeChild: function removeChild(oldChild) { 34953 if (this.documentElement == oldChild) { 34954 this.documentElement = null; 34955 } 34956 34957 return _removeChild(this, oldChild); 34958 }, 34959 // Introduced in DOM Level 2: 34960 importNode: function importNode(importedNode, deep) { 34961 return _importNode(this, importedNode, deep); 34962 }, 34963 // Introduced in DOM Level 2: 34964 getElementById: function getElementById(id) { 34965 var rtv = null; 34966 34967 _visitNode(this.documentElement, function (node) { 34968 if (node.nodeType == ELEMENT_NODE) { 34969 if (node.getAttribute('id') == id) { 34970 rtv = node; 34971 return true; 34972 } 34973 } 34974 }); 34975 34976 return rtv; 34977 }, 34978 34979 /** 34980 * The `getElementsByClassName` method of `Document` interface returns an array-like object 34981 * of all child elements which have **all** of the given class
34981name(s). 34982 * 34983 * Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters. 34984 * 34985 * 34986 * Warning: This is a live LiveNodeList. 34987 * Changes in the DOM will reflect in the array as the changes occur. 34988 * If an element selected by this array no longer qualifies for the selector, 34989 * it will automatically be removed. Be aware of this for iteration purposes. 34990 * 34991 * @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace 34992 * 34993 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName 34994 * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname 34995 */ 34996 getElementsByClassName: function getElementsByClassName(classNames) { 34997 var classNamesSet = toOrderedSet(classNames); 34998 return new LiveNodeList(this, function (base) { 34999 var ls = []; 35000 35001 if (classNamesSet.length > 0) { 35002 _visitNode(base.documentElement, function (node) { 35003 if (node !== base && node.nodeType === ELEMENT_NODE) { 35004 var nodeClassNames = node.getAttribute('class'); // can be null if the attribute does not exist 35005 35006 if (nodeClassNames) { 35007 // before splitting and iterating just compare them for the most common case 35008 var matches = classNames === nodeClassNames; 35009 35010 if (!matches) { 35011 var nodeClassNamesSet = toOrderedSet(nodeClassNames); 35012 matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet)); 35013 } 35014 35015 if (matches) { 35016 ls.push(node); 35017 } 35018 } 35019 } 35020 }); 35021 } 35022 35023 return ls; 35024 }); 35025 }, 35026 //document factory method: 35027 createElement: function createElement(tagName) { 35028 var node = new Element(); 35029 node.ownerDocument = this; 35030 node.nodeName = tagName; 35031 node.tagName = tagName; 35032 node.localName = tagName; 35033 node.childNodes = new NodeList(); 35034 var attrs = node.attributes = new NamedNodeMap(); 35035 attrs._ownerElement = node; 35036 return node; 35037 }, 35038 createDocumentFragment: function createDocumentFragment() { 35039 var node = new DocumentFragment(); 35040 node.ownerDocument = this; 35041 node.childNodes = new NodeList(); 35042 return node; 35043 }, 35044 createTextNode: function createTextNode(data) { 35045 var node = new Text(); 35046 node.ownerDocument = this; 35047 node.appendData(data); 35048 return node; 35049 }, 35050 createComment: function createComment(data) { 35051 var node = new Comment(); 35052 node.ownerDocument = this; 35053 node.appendData(data); 35054 return node; 35055 }, 35056 createCDATASection: function createCDATASection(data) { 35057 var node = new CDATASection(); 35058 node.ownerDocument = this; 35059 node.appendData(data); 35060 return node; 35061 }, 35062 createProcessingInstruction: function createProcessingInstruction(target, data) { 35063 var node = new ProcessingInstruction(); 35064 node.ownerDocument = this; 35065 node.tagName = node.target = target; 35066 node.nodeValue = node.data = data; 35067 return node; 35068 }, 35069 createAttribute: function createAttribute(name) { 35070 var node = new Attr(); 35071 node.ownerDocument = this; 35072 node.name = name; 35073 node.nodeName = name; 35074 node.localName = name; 35075 node.specified = true; 35076 return node; 35077 }, 35078 createEntityReference: function createEntityReference(name) { 35079 var node = new EntityReference(); 35080 node.ownerDocument = this; 35081 node.nodeName = name; 35082 return node; 35083 }, 35084 // Introduced in DOM Level 2: 35085 createElementNS: function createElementNS(namespaceURI, qualifiedName) { 35086 var node = new Element(); 35087 var pl = qualifiedName.split(':'); 35088 var attrs = node.attributes = new NamedNodeMap(); 35089 node.childNodes = new NodeList(); 35090 node.ownerDocument = this; 35091 node.nodeName = qualifiedName; 35092 node.tagName = qualifiedName; 35093 node.namespaceURI = namespaceURI; 35094 35095 if (pl.length == 2) { 35096 node.prefix = pl[0]; 35097 node.localName = pl[1]; 35098 } else { 35099 //el.prefix = null; 35100 node.localName = qualifiedName; 35101 } 35102 35103 attrs._ownerElement = node; 35104 return node; 35105 }, 35106 // Introduced in DOM Level 2: 35107 createAttributeNS: function createAttributeNS(namespaceURI, qualifiedName) { 35108 var node = new Attr(); 35109 var pl = qualifiedName.split(':'); 35110 node.ownerDocument = this; 35111 node.nodeName = qualifiedName; 35112 node.name = qualifiedName; 35113 node.namespaceURI = namespaceURI; 35114 node.specified = true; 35115 35116 if (pl.length == 2) { 35117 node.prefix = pl[0]; 35118 node.localName = pl[1]; 35119 } else { 35120 //el.prefix = null; 35121 node.localName = qualifiedName; 35122 } 35123 35124 return node; 35125 } 35126 }; 35127 35128 _extends(Document, Node); 35129 35130 function Element() { 35131 this._nsMap = {}; 35132 } 35133 Element.prototype = { 35134 nodeType: ELEMENT_NODE, 35135 hasAttribute: function hasAttribute(name) { 35136 return this.getAttributeNode(name) != null; 35137 }, 35138 getAttribute: function getAttribute(name) { 35139 var attr = this.getAttributeNode(name); 35140 return attr && attr.value || ''; 35141 }, 35142 getAttributeNode: function getAttributeNode(name) { 35143 return this.attributes.getNamedItem(name); 35144 }, 35145 setAttribute: function setAttribute(name, value) { 35146 var attr = this.ownerDocument.createAttribute(name); 35147 attr.value = attr.nodeValue = "" + value; 35148 this.setAttributeNode(attr); 35149 }, 35150 removeAttribute: function removeAttribute(name) { 35151 var attr = this.getAttributeNode(name); 35152 attr && this.removeAttributeNode(attr); 35153 }, 35154 //four real opeartion method 35155 appendChild: function appendChild(newChild) { 35156 if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) { 35157 return this.insertBefore(newChild, null); 35158 } else { 35159 return _appendSingleChild(this, newChild); 35160 } 35161 }, 35162 setAttributeNode: function setAttributeNode(newAttr) { 35163 return this.attributes.setNamedItem(newAttr); 35164 }, 35165 setAttributeNodeNS: function setAttributeNodeNS(newAttr) { 35166 return this.attributes.setNamedItemNS(newAttr); 35167 }, 35168 removeAttributeNode: function removeAttributeNode(oldAttr) { 35169 //console.log(this == oldAttr.ownerElement) 35170 return this.attributes.removeNamedItem(oldAttr.nodeName); 35171 }, 35172 //get real attribute name,and remove it by removeAttributeNode 35173 removeAttributeNS: function removeAttributeNS(namespaceURI, localName) { 35174 var old = this.getAttributeNodeNS(namespaceURI, localName); 35175 old && this.removeAttributeNode(old); 35176 }, 35177 hasAttributeNS: function hasAttributeNS(namespaceURI, localName) { 35178 return this.getAttributeNodeNS(namespaceURI, localName) != null; 35179 }, 35180 getAttributeNS: function getAttributeNS(namespaceURI, localName) { 35181 var attr = this.getAttributeNodeNS(namespaceURI, localName); 35182 return attr && attr.value || ''; 35183 }, 35184 setAttributeNS: function setAttributeNS(namespaceURI, qualifiedName, value) { 35185 var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); 35186 attr.value = attr.nodeValue = "" + value; 35187 this.setAttributeNode(attr); 35188 }, 35189 getAttributeNodeNS: function getAttributeNodeNS(namespaceURI, localName) { 35190 return this.attributes.getNamedItemNS(namespaceURI, localName); 35191 }, 35192 getElementsByTagName: function getElementsByTagName(tagName) { 35193 return new LiveNodeList(this, function (base) { 35194 var ls = []; 35195 35196 _visitNode(base, function (node) { 35197 if (node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)) { 35198 ls.push(node); 35199 } 35200 }); 35201 35202 return ls; 35203 }); 35204 }, 35205 getElementsByTagNameNS: function getElementsByTagNameNS(namespaceURI, localName) { 35206 return new LiveNodeList(this, function (base) { 35207 var ls = []; 35208 35209 _visitNode(base, function (node) { 35210 if (node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)) { 35211 ls.push(node); 35212 } 35213 }); 35214 35215 return ls; 35216 }); 35217 } 35218 }; 35219 Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; 35220 Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; 35221 35222 _extends(Element, Node); 35223 35224 function Attr() {} 35225 Attr.prototype.nodeType = ATTRIBUTE_NODE; 35226 35227 _extends(Attr, Node); 35228 35229 function CharacterData() {} 35230 CharacterData.prototype = { 35231 data: '', 35232 substringData: function substringData(offset, count) { 35233 return this.data.substring(offset, offset + count); 35234 }, 35235 appendData: function appendData(text) { 35236 text = this.data + text; 35237 this.nodeValue = this.data = text; 35238 this.length = text.length; 35239 }, 35240 insertData: function insertData(offset, text) { 35241 this.replaceData(offset, 0, text); 35242 }, 35243 appendChild: function appendChild(newChild) { 35244 throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]); 35245 }, 35246 deleteData: function deleteData(offset, count) { 35247 this.replaceData(offset, count, ""); 35248 }, 35249 replaceData: function replaceData(offset, count, text) { 35250 var start = this.data.substring(0, offset); 35251 var end = this.data.substring(offset + count);
vendor: 9,228 bytes, lines 35252-35552
35252 text = start + text + end; 35253 this.nodeValue = this.data = text; 35254 this.length = text.length; 35255 } 35256 }; 35257 35258 _extends(CharacterData, Node); 35259 35260 function Text() {} 35261 Text.prototype = { 35262 nodeName: "#text", 35263 nodeType: TEXT_NODE, 35264 splitText: function splitText(offset) { 35265 var text = this.data; 35266 var newText = text.substring(offset); 35267 text = text.substring(0, offset); 35268 this.data = this.nodeValue = text; 35269 this.length = text.length; 35270 var newNode = this.ownerDocument.createTextNode(newText); 35271 35272 if (this.parentNode) { 35273 this.parentNode.insertBefore(newNode, this.nextSibling); 35274 } 35275 35276 return newNode; 35277 } 35278 }; 35279 35280 _extends(Text, CharacterData); 35281 35282 function Comment() {} 35283 Comment.prototype = { 35284 nodeName: "#comment", 35285 nodeType: COMMENT_NODE 35286 }; 35287 35288 _extends(Comment, CharacterData); 35289 35290 function CDATASection() {} 35291 CDATASection.prototype = { 35292 nodeName: "#cdata-section", 35293 nodeType: CDATA_SECTION_NODE 35294 }; 35295 35296 _extends(CDATASection, CharacterData); 35297 35298 function DocumentType() {} 35299 DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; 35300 35301 _extends(DocumentType, Node); 35302 35303 function Notation() {} 35304 Notation.prototype.nodeType = NOTATION_NODE; 35305 35306 _extends(Notation, Node); 35307 35308 function Entity() {} 35309 Entity.prototype.nodeType = ENTITY_NODE; 35310 35311 _extends(Entity, Node); 35312 35313 function EntityReference() {} 35314 EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; 35315 35316 _extends(EntityReference, Node); 35317 35318 function DocumentFragment() {} 35319 DocumentFragment.prototype.nodeName = "#document-fragment"; 35320 DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; 35321 35322 _extends(DocumentFragment, Node); 35323 35324 function ProcessingInstruction() {} 35325 35326 ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; 35327 35328 _extends(ProcessingInstruction, Node); 35329 35330 function XMLSerializer() {} 35331 35332 XMLSerializer.prototype.serializeToString = function (node, isHtml, nodeFilter) { 35333 return nodeSerializeToString.call(node, isHtml, nodeFilter); 35334 }; 35335 35336 Node.prototype.toString = nodeSerializeToString; 35337 35338 function nodeSerializeToString(isHtml, nodeFilter) { 35339 var buf = []; 35340 var refNode = this.nodeType == 9 && this.documentElement || this; 35341 var prefix = refNode.prefix; 35342 var uri = refNode.namespaceURI; 35343 35344 if (uri && prefix == null) { 35345 //console.log(prefix) 35346 var prefix = refNode.lookupPrefix(uri); 35347 35348 if (prefix == null) { 35349 //isHTML = true; 35350 var visibleNamespaces = [{ 35351 namespace: uri, 35352 prefix: null 35353 } //{namespace:uri,prefix:''} 35354 ]; 35355 } 35356 } 35357 35358 serializeToString(this, buf, isHtml, nodeFilter, visibleNamespaces); //console.log('###',this.nodeType,uri,prefix,buf.join('')) 35359 35360 return buf.join(''); 35361 } 35362 35363 function needNamespaceDefine(node, isHTML, visibleNamespaces) { 35364 var prefix = node.prefix || ''; 35365 var uri = node.namespaceURI; // According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) , 35366 // and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl : 35367 // > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty. 35368 // in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using) 35369 // and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared : 35370 // > [...] Furthermore, the attribute value [...] must not be an empty string. 35371 // so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document. 35372 35373 if (!uri) { 35374 return false; 35375 } 35376 35377 if (prefix === "xml" && uri === NAMESPACE.XML || uri === NAMESPACE.XMLNS) { 35378 return false; 35379 } 35380 35381 var i = visibleNamespaces.length; 35382 35383 while (i--) { 35384 var ns = visibleNamespaces[i]; // get namespace prefix 35385 35386 if (ns.prefix === prefix) { 35387 return ns.namespace !== uri; 35388 } 35389 } 35390 35391 return true; 35392 } 35393 /** 35394 * Well-formed constraint: No < in Attribute Values 35395 * The replacement text of any entity referred to directly or indirectly in an attribute value must not contain a <. 35396 * @see https://www.w3.org/TR/xml/#CleanAttrVals 35397 * @see https://www.w3.org/TR/xml/#NT-AttValue 35398 */ 35399 35400 35401 function addSerializedAttribute(buf, qualifiedName, value) { 35402 buf.push(' ', qualifiedName, '="', value.replace(/[<&"]/g, _xmlEncoder), '"'); 35403 } 35404 35405 function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces) { 35406 if (!visibleNamespaces) { 35407 visibleNamespaces = []; 35408 } 35409 35410 if (nodeFilter) { 35411 node = nodeFilter(node); 35412 35413 if (node) { 35414 if (typeof node == 'string') { 35415 buf.push(node); 35416 return; 35417 } 35418 } else { 35419 return; 35420 } //buf.sort.apply(attrs, attributeSorter); 35421 35422 } 35423 35424 switch (node.nodeType) { 35425 case ELEMENT_NODE: 35426 var attrs = node.attributes; 35427 var len = attrs.length; 35428 var child = node.firstChild; 35429 var nodeName = node.tagName; 35430 isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML; 35431 var prefixedNodeName = nodeName; 35432 35433 if (!isHTML && !node.prefix && node.namespaceURI) { 35434 var defaultNS; 35435 35436 for (var ai = 0; ai < attrs.length; ai++) { 35437 if (attrs.item(ai).name === 'xmlns') { 35438 defaultNS = attrs.item(ai).value; 35439 break; 35440 } 35441 } 35442 35443 if (defaultNS !== node.namespaceURI) { 35444 for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) { 35445 var namespace = visibleNamespaces[nsi]; 35446 35447 if (namespace.namespace === node.namespaceURI) { 35448 if (namespace.prefix) { 35449 prefixedNodeName = namespace.prefix + ':' + nodeName; 35450 } 35451 35452 break; 35453 } 35454 } 35455 } 35456 } 35457 35458 buf.push('<', prefixedNodeName); 35459 35460 for (var i = 0; i < len; i++) { 35461 // add namespaces for attributes 35462 var attr = attrs.item(i); 35463 35464 if (attr.prefix == 'xmlns') { 35465 visibleNamespaces.push({ 35466 prefix: attr.localName, 35467 namespace: attr.value 35468 }); 35469 } else if (attr.nodeName == 'xmlns') { 35470 visibleNamespaces.push({ 35471 prefix: '', 35472 namespace: attr.value 35473 }); 35474 } 35475 } 35476 35477 for (var i = 0; i < len; i++) { 35478 var attr = attrs.item(i); 35479 35480 if (needNamespaceDefine(attr, isHTML, visibleNamespaces)) { 35481 var prefix = attr.prefix || ''; 35482 var uri = attr.namespaceURI; 35483 addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri); 35484 visibleNamespaces.push({ 35485 prefix: prefix, 35486 namespace: uri 35487 }); 35488 } 35489 35490 serializeToString(attr, buf, isHTML, nodeFilter, visibleNamespaces); 35491 } // add namespace for current node 35492 35493 35494 if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) { 35495 var prefix = node.prefix || ''; 35496 var uri = node.namespaceURI; 35497 addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri); 35498 visibleNamespaces.push({ 35499 prefix: prefix, 35500 namespace: uri 35501 }); 35502 } 35503 35504 if (child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) { 35505 buf.push('>'); //if is cdata child node 35506 35507 if (isHTML && /^script$/i.test(nodeName)) { 35508 while (child) { 35509 if (child.data) { 35510 buf.push(child.data); 35511 } else { 35512 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); 35513 } 35514 35515 child = child.nextSibling; 35516 } 35517 } else { 35518 while (child) { 35519 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); 35520 child = child.nextSibling; 35521 } 35522 } 35523 35524 buf.push('</', prefixedNodeName, '>'); 35525 } else { 35526 buf.push('/>'); 35527 } // remove added visible namespaces 35528 //visibleNamespaces.length = startVisibleNamespaces; 35529 35530 35531 return; 35532 35533 case DOCUMENT_NODE: 35534 case DOCUMENT_FRAGMENT_NODE: 35535 var child = node.firstChild; 35536 35537 while (child) { 35538 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); 35539 child = child.nextSibling; 35540 } 35541 35542 return; 35543 35544 case ATTRIBUTE_NODE: 35545 return addSerializedAttribute(buf, node.name, node.value); 35546 35547 case TEXT_NODE: 35548 /** 35549 * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form, 35550 * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section. 35551 * If they are needed elsewhere, they must be escaped using either numeric character references or the strings 35552 * `&` and `<` respectively.
35553 * The right angle bracket (>) may be represented using the string " > ", and must, for compatibility, 35554 * be escaped using either `>` or a character reference when it appears in the string `]]>` in content, 35555 * when that string is not marking the end of a CDATA section. 35556 * 35557 * In the content of elements, character data is any string of characters 35558 * which does not contain the start-delimiter of any markup 35559 * and does not include the CDATA-section-close delimiter, `]]>`. 35560 * 35561 * @see https://www.w3.org/TR/xml/#NT-CharData 35562 */ 35563 return buf.push(node.data.replace(/[<&]/g, _xmlEncoder).replace(/]]>/g, ']]>')); 35564 35565 case CDATA_SECTION_NODE: 35566 return buf.push('<![CDATA[', node.data, ']]>'); 35567 35568 case COMMENT_NODE: 35569 return buf.push("<!--", node.data, "-->"); 35570 35571 case DOCUMENT_TYPE_NODE: 35572 var pubid = node.publicId; 35573 var sysid = node.systemId; 35574 buf.push('<!DOCTYPE ', node.name); 35575 35576 if (pubid) { 35577 buf.push(' PUBLIC ', pubid); 35578 35579 if (sysid && sysid != '.') { 35580 buf.push(' ', sysid); 35581 } 35582 35583 buf.push('>'); 35584 } else if (sysid && sysid != '.') { 35585 buf.push(' SYSTEM ', sysid, '>'); 35586 } else { 35587 var sub = node.internalSubset; 35588 35589 if (sub) { 35590 buf.push(" [", sub, "]"); 35591 } 35592 35593 buf.push(">"); 35594 } 35595 35596 return; 35597 35598 case PROCESSING_INSTRUCTION_NODE: 35599 return buf.push("<?", node.target, " ", node.data, "?>"); 35600 35601 case ENTITY_REFERENCE_NODE: 35602 return buf.push('&', node.nodeName, ';'); 35603 //case ENTITY_NODE: 35604 //case NOTATION_NODE: 35605 35606 default: 35607 buf.push('??', node.nodeName); 35608 } 35609 } 35610 35611 function _importNode(doc, node, deep) { 35612 var node2; 35613 35614 switch (node.nodeType) { 35615 case ELEMENT_NODE: 35616 node2 = node.cloneNode(false); 35617 node2.ownerDocument = doc; 35618 //var attrs = node2.attributes; 35619 //var len = attrs.length; 35620 //for(var i=0;i<len;i++){ 35621 //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep)); 35622 //} 35623 35624 case DOCUMENT_FRAGMENT_NODE: 35625 break; 35626 35627 case ATTRIBUTE_NODE: 35628 deep = true; 35629 break; 35630 //case ENTITY_REFERENCE_NODE: 35631 //case PROCESSING_INSTRUCTION_NODE: 35632 ////case TEXT_NODE: 35633 //case CDATA_SECTION_NODE: 35634 //case COMMENT_NODE: 35635 // deep = false; 35636 // break; 35637 //case DOCUMENT_NODE: 35638 //case DOCUMENT_TYPE_NODE: 35639 //cannot be imported. 35640 //case ENTITY_NODE: 35641 //case NOTATION_NODEï¼ 35642 //can not hit in level3 35643 //default:throw e; 35644 } 35645 35646 if (!node2) { 35647 node2 = node.cloneNode(false); //false 35648 } 35649 35650 node2.ownerDocument = doc; 35651 node2.parentNode = null; 35652 35653 if (deep) { 35654 var child = node.firstChild; 35655 35656 while (child) { 35657 node2.appendChild(_importNode(doc, child, deep)); 35658 child = child.nextSibling; 35659 } 35660 } 35661 35662 return node2; 35663 } // 35664 //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1, 35665 // attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,}; 35666 35667 35668 function _cloneNode(doc, node, deep) { 35669 var node2 = new node.constructor(); 35670 35671 for (var n in node) { 35672 var v = node[n]; 35673 35674 if (typeof v != 'object') { 35675 if (v != node2[n]) { 35676 node2[n] = v; 35677 } 35678 } 35679 } 35680 35681 if (node.childNodes) { 35682 node2.childNodes = new NodeList(); 35683 } 35684 35685 node2.ownerDocument = doc; 35686 35687 switch (node2.nodeType) { 35688 case ELEMENT_NODE: 35689 var attrs = node.attributes; 35690 var attrs2 = node2.attributes = new NamedNodeMap(); 35691 var len = attrs.length; 35692 attrs2._ownerElement = node2; 35693 35694 for (var i = 0; i < len; i++) { 35695 node2.setAttributeNode(_cloneNode(doc, attrs.item(i), true)); 35696 } 35697 35698 break; 35699 35700 case ATTRIBUTE_NODE: 35701 deep = true; 35702 } 35703 35704 if (deep) { 35705 var child = node.firstChild; 35706 35707 while (child) { 35708 node2.appendChild(_cloneNode(doc, child, deep)); 35709 child = child.nextSibling; 35710 } 35711 } 35712 35713 return node2; 35714 } 35715 35716 function __set__(object, key, value) { 35717 object[key] = value; 35718 } //do dynamic 35719 35720 35721 try { 35722 if (Object.defineProperty) { 35723 var getTextContent = function getTextContent(node) { 35724 switch (node.nodeType) { 35725 case ELEMENT_NODE: 35726 case DOCUMENT_FRAGMENT_NODE: 35727 var buf = []; 35728 node = node.firstChild; 35729 35730 while (node) { 35731 if (node.nodeType !== 7 && node.nodeType !== 8) { 35732 buf.push(getTextContent(node));
35733 } 35734 35735 node = node.nextSibling; 35736 } 35737 35738 return buf.join(''); 35739 35740 default: 35741 return node.nodeValue; 35742 } 35743 }; 35744 35745 Object.defineProperty(LiveNodeList.prototype, 'length', { 35746 get: function get() { 35747 _updateLiveList(this); 35748 35749 return this.$$length; 35750 } 35751 }); 35752 Object.defineProperty(Node.prototype, 'textContent', { 35753 get: function get() { 35754 return getTextContent(this); 35755 }, 35756 set: function set(data) { 35757 switch (this.nodeType) { 35758 case ELEMENT_NODE: 35759 case DOCUMENT_FRAGMENT_NODE: 35760 while (this.firstChild) { 35761 this.removeChild(this.firstChild); 35762 } 35763 35764 if (data || String(data)) { 35765 this.appendChild(this.ownerDocument.createTextNode(data)); 35766 } 35767 35768 break; 35769 35770 default: 35771 this.data = data; 35772 this.value = data; 35773 this.nodeValue = data; 35774 } 35775 } 35776 }); 35777 35778 __set__ = function __set__(object, key, value) { 35779 //console.log(value) 35780 object['$$' + key] = value; 35781 }; 35782 } 35783 } catch (e) {//ie8 35784 } //if(typeof require == 'function'){ 35785 35786 35787 var DocumentType_1 = DocumentType; 35788 var DOMException_1 = DOMException; 35789 var DOMImplementation_1 = DOMImplementation; 35790 var Element_1 = Element; 35791 var Node_1 = Node; 35792 var NodeList_1 = NodeList; 35793 var XMLSerializer_1 = XMLSerializer; //} 35794 35795 var dom = { 35796 DocumentType: DocumentType_1, 35797 DOMException: DOMException_1, 35798 DOMImplementation: DOMImplementation_1, 35799 Element: Element_1, 35800 Node: Node_1, 35801 NodeList: NodeList_1, 35802 XMLSerializer: XMLSerializer_1 35803 }; 35804 35805 var domParser = createCommonjsModule(function (module, exports) { 35806 var NAMESPACE = conventions.NAMESPACE; 35807 35808 function DOMParser(options) { 35809 this.options = options || { 35810 locator: {} 35811 }; 35812 } 35813 35814 DOMParser.prototype.parseFromString = function (source, mimeType) { 35815 var options = this.options; 35816 var sax = new XMLReader(); 35817 var domBuilder = options.domBuilder || new DOMHandler(); //contentHandler and LexicalHandler 35818 35819 var errorHandler = options.errorHandler; 35820 var locator = options.locator; 35821 var defaultNSMap = options.xmlns || {}; 35822 var isHTML = /\/x?html?$/.test(mimeType); //mimeType.toLowerCase().indexOf('html') > -1; 35823 35824 var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES; 35825 35826 if (locator) { 35827 domBuilder.setDocumentLocator(locator); 35828 } 35829 35830 sax.errorHandler = buildErrorHandler(errorHandler, domBuilder, locator); 35831 sax.domBuilder = options.domBuilder || domBuilder; 35832 35833 if (isHTML) { 35834 defaultNSMap[''] = NAMESPACE.HTML; 35835 } 35836 35837 defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML; 35838 35839 if (source && typeof source === 'string') { 35840 sax.parse(source, defaultNSMap, entityMap); 35841 } else { 35842 sax.errorHandler.error("invalid doc source"); 35843 } 35844 35845 return domBuilder.doc; 35846 }; 35847 35848 function buildErrorHandler(errorImpl, domBuilder, locator) { 35849 if (!errorImpl) { 35850 if (domBuilder instanceof DOMHandler) { 35851 return domBuilder; 35852 } 35853 35854 errorImpl = domBuilder; 35855 } 35856 35857 var errorHandler = {}; 35858 var isCallback = errorImpl instanceof Function; 35859 locator = locator || {}; 35860 35861 function build(key) { 35862 var fn = errorImpl[key]; 35863 35864 if (!fn && isCallback) { 35865 fn = errorImpl.length == 2 ? function (msg) { 35866 errorImpl(key, msg); 35867 } : errorImpl; 35868 } 35869 35870 errorHandler[key] = fn && function (msg) { 35871 fn('[xmldom ' + key + ']\t' + msg + _locator(locator)); 35872 } || function () {}; 35873 } 35874 35875 build('warning'); 35876 build('error'); 35877 build('fatalError'); 35878 return errorHandler; 35879 } //console.log('#\n\n\n\n\n\n\n####') 35880 35881 /** 35882 * +ContentHandler+ErrorHandler 35883 * +LexicalHandler+EntityResolver2 35884 * -DeclHandler-DTDHandler 35885 * 35886 * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler 35887 * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 35888 * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html 35889 */ 35890 35891 35892 function DOMHandler() { 35893 this.cdata = false; 35894 } 35895 35896 function position(locator, node) { 35897 node.lineNumber = locator.lineNumber; 35898 node.columnNumber = locator.columnNumber; 35899 } 35900 /** 35901 * @see org.xml.sax.ContentHandler#startDocument 35902 * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html 35903 */ 35904 35905 35906 DOMHandler.prototype = { 35907 startDocument: function startDocument() { 35908 this.doc = new DOMImplementation().createDocument(null, null, null); 35909 35910 if (this.locator) { 35911 this.doc.documentURI = this.locator.systemId; 35912 } 35913 }, 35914 startElement: function startElement(namespaceURI, localName, qName, attrs) { 35915 var doc = this.doc; 35916 var el = doc.createElementNS(namespaceURI, qName || localName); 35917 var len = attrs.length; 35918 appendElement(this, el); 35919 this.currentElement = el; 35920 this.locator && position(this.locator, el); 35921 35922 for (var i = 0; i < len; i++) { 35923 var namespaceURI = attrs.getURI(i); 35924 var value = attrs.getValue(i); 35925 var qName = attrs.getQName(i); 35926 var attr = doc.createAttributeNS(namespaceURI, qName); 35927 this.locator && position(attrs.getLocator(i), attr); 35928 attr.value = attr.nodeValue = value; 35929 el.setAttributeNode(attr); 35930 } 35931 }, 35932 endElement: function endElement(namespaceURI, localName, qName) { 35933 var current = this.currentElement; 35934 current.tagName; 35935 this.currentElement = current.parentNode; 35936 }, 35937 startPrefixMapping: function startPrefixMapping(prefix, uri) {}, 35938 endPrefixMapping: function endPrefixMapping(prefix) {}, 35939 processingInstruction: function processingInstruction(target, data) { 35940 var ins = this.doc.createProcessingInstruction(target, data); 35941 this.locator && position(this.locator, ins);
35942 appendElement(this, ins); 35943 }, 35944 ignorableWhitespace: function ignorableWhitespace(ch, start, length) {}, 35945 characters: function characters(chars, start, length) { 35946 chars = _toString.apply(this, arguments); //console.log(chars) 35947 35948 if (chars) { 35949 if (this.cdata) { 35950 var charNode = this.doc.createCDATASection(chars); 35951 } else { 35952 var charNode = this.doc.createTextNode(chars); 35953 } 35954 35955 if (this.currentElement) { 35956 this.currentElement.appendChild(charNode); 35957 } else if (/^\s*$/.test(chars)) { 35958 this.doc.appendChild(charNode); //process xml 35959 } 35960 35961 this.locator && position(this.locator, charNode); 35962 } 35963 }, 35964 skippedEntity: function skippedEntity(name) {}, 35965 endDocument: function endDocument() { 35966 this.doc.normalize(); 35967 }, 35968 setDocumentLocator: function setDocumentLocator(locator) { 35969 if (this.locator = locator) { 35970 // && !('lineNumber' in locator)){ 35971 locator.lineNumber = 0; 35972 } 35973 }, 35974 //LexicalHandler 35975 comment: function comment(chars, start, length) { 35976 chars = _toString.apply(this, arguments); 35977 var comm = this.doc.createComment(chars); 35978 this.locator && position(this.locator, comm); 35979 appendElement(this, comm); 35980 }, 35981 startCDATA: function startCDATA() { 35982 //used in characters() methods 35983 this.cdata = true; 35984 }, 35985 endCDATA: function endCDATA() { 35986 this.cdata = false; 35987 }, 35988 startDTD: function startDTD(name, publicId, systemId) { 35989 var impl = this.doc.implementation; 35990 35991 if (impl && impl.createDocumentType) { 35992 var dt = impl.createDocumentType(name, publicId, systemId); 35993 this.locator && position(this.locator, dt); 35994 appendElement(this, dt); 35995 } 35996 }, 35997 35998 /** 35999 * @see org.xml.sax.ErrorHandler 36000 * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html 36001 */ 36002 warning: function warning(error) { 36003 console.warn('[xmldom warning]\t' + error, _locator(this.locator)); 36004 }, 36005 error: function error(_error) { 36006 console.error('[xmldom error]\t' + _error, _locator(this.locator)); 36007 }, 36008 fatalError: function fatalError(error) { 36009 throw new ParseError(error, this.locator); 36010 } 36011 }; 36012 36013 function _locator(l) { 36014 if (l) { 36015 return '\n@' + (l.systemId || '') + '#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']'; 36016 } 36017 } 36018 36019 function _toString(chars, start, length) { 36020 if (typeof chars == 'string') { 36021 return chars.substr(start, length); 36022 } else { 36023 //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") 36024 if (chars.length >= start + length || start) { 36025 return new java.lang.String(chars, start, length) + ''; 36026 } 36027 36028 return chars; 36029 } 36030 } 36031 /* 36032 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html 36033 * used method of org.xml.sax.ext.LexicalHandler: 36034 * #comment(chars, start, length) 36035 * #startCDATA() 36036 * #endCDATA() 36037 * #startDTD(name, publicId, systemId) 36038 * 36039 * 36040 * IGNORED method of org.xml.sax.ext.LexicalHandler: 36041 * #endDTD() 36042 * #startEntity(name) 36043 * #endEntity(name) 36044 * 36045 * 36046 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html 36047 * IGNORED method of org.xml.sax.ext.DeclHandler 36048 * #attributeDecl(eName, aName, type, mode, value) 36049 * #elementDecl(name, model) 36050 * #externalEntityDecl(name, publicId, systemId) 36051 * #internalEntityDecl(name, value) 36052 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html 36053 * IGNORED method of org.xml.sax.EntityResolver2 36054 * #resolveEntity(String name,String publicId,String baseURI,String systemId) 36055 * #resolveEntity(publicId, systemId) 36056 * #getExternalSubset(name, baseURI) 36057 * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html 36058 * IGNORED method of org.xml.sax.DTDHandler 36059 * #notationDecl(name, publicId, systemId) {}; 36060 * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; 36061 */ 36062 36063 36064 "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g, function (key) { 36065 DOMHandler.prototype[key] = function () { 36066 return null; 36067 }; 36068 });
36069 /* 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 */ 36070 36071 function appendElement(hander, node) { 36072 if (!hander.currentElement) { 36073 hander.doc.appendChild(node); 36074 } else { 36075 hander.currentElement.appendChild(node); 36076 } 36077 } //appendChild and setAttributeNS are preformance key 36078 //if(typeof require == 'function'){ 36079 36080 36081 var XMLReader = sax.XMLReader; 36082 var ParseError = sax.ParseError; 36083 var DOMImplementation = exports.DOMImplementation = dom.DOMImplementation; 36084 exports.XMLSerializer = dom.XMLSerializer; 36085 exports.DOMParser = DOMParser; 36086 exports.__DOMHandler = DOMHandler; //} 36087 }); 36088 domParser.DOMImplementation; 36089 domParser.XMLSerializer; 36090 var domParser_3 = domParser.DOMParser; 36091 domParser.__DOMHandler; 36092 36093 /*! @name mpd-parser @version 0.19.0 @license Apache-2.0 */ 36094 36095 var isObject = function isObject(obj) { 36096 return !!obj && typeof obj === 'object'; 36097 }; 36098 36099 var merge = function merge() { 36100 for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) { 36101 objects[_key] = arguments[_key]; 36102 } 36103 36104 return objects.reduce(function (result, source) { 36105 if (typeof source !== 'object') { 36106 return result; 36107 } 36108 36109 Object.keys(source).forEach(function (key) { 36110 if (Array.isArray(result[key]) && Array.isArray(source[key])) { 36111 result[key] = result[key].concat(source[key]); 36112 } else if (isObject(result[key]) && isObject(source[key])) { 36113 result[key] = merge(result[key], source[key]); 36114 } else { 36115 result[key] = source[key]; 36116 } 36117 }); 36118 return result; 36119 }, {}); 36120 }; 36121 36122 var values = function values(o) { 36123 return Object.keys(o).map(function (k) { 36124 return o[k]; 36125 }); 36126 }; 36127 36128 var range = function range(start, end) { 36129 var result = []; 36130 36131 for (var i = start; i < end; i++) { 36132 result.push(i); 36133 } 36134 36135 return result; 36136 }; 36137 36138 var flatten = function flatten(lists) { 36139 return lists.reduce(function (x, y) { 36140 return x.concat(y); 36141 }, []); 36142 }; 36143 36144 var from = function from(list) { 36145 if (!list.length) { 36146 return []; 36147 } 36148 36149 var result = []; 36150 36151 for (var i = 0; i < list.length; i++) { 36152 result.push(list[i]); 36153 } 36154 36155 return result; 36156 }; 36157 36158 var findIndexes = function findIndexes(l, key) { 36159 return l.reduce(function (a, e, i) { 36160 if (e[key]) { 36161 a.push(i); 36162 } 36163 36164 return a; 36165 }, []); 36166 }; 36167 36168 var errors = { 36169 INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD', 36170 DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST', 36171 DASH_INVALID_XML: 'DASH_INVALID_XML', 36172 NO_BASE_URL: 'NO_BASE_URL', 36173 MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', 36174 SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED', 36175 UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME' 36176 }; 36177 /** 36178 * @typedef {Object} SingleUri 36179 * @property {string} uri - relative location of segment 36180 * @property {string} resolvedUri - resolved location of segment 36181 * @property {Object} byterange - Object containing information on how to make byte range 36182 * requests following byte-range-spec per RFC2616. 36183 * @property {String} byterange.length - length of range request 36184 * @property {String} byterange.offset - byte offset of range request 36185 * 36186 * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 36187 */ 36188 36189 /** 36190 * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object 36191 * that conforms to how m3u8-parser is structured 36192 * 36193 * @see https://github.com/videojs/m3u8-parser 36194 * 36195 * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes 36196 * @param {string} source - source url for segment 36197 * @param {string} range - optional range used for range calls, 36198 * follows RFC 2616, Clause 14.35.1 36199 * @return {SingleUri} full segment information transformed into a format similar 36200 * to m3u8-parser 36201 */ 36202 36203 var urlTypeToSegment = function urlTypeToSegment(_ref) { 36204 var _ref$baseUrl = _ref.baseUrl, 36205 baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl, 36206 _ref$source = _ref.source, 36207 source = _ref$source === void 0 ? '' : _ref$source, 36208 _ref$range = _ref.range, 36209 range = _ref$range === void 0 ? '' : _ref$range, 36210 _ref$indexRange = _ref.indexRange, 36211 indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange; 36212 var segment = { 36213 uri: source, 36214 resolvedUri: resolveUrl$1(baseUrl || '', source) 36215 }; 36216 36217 if (range || indexRange) { 36218 var rangeStr = range ? range : indexRange; 36219 var ranges = rangeStr.split('-'); 36220 var startRange = parseInt(ranges[0], 10); 36221 var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to 36222 // RFC 2616, Clause 14.35.1 36223 36224 segment.byterange = { 36225 length: endRange - startRange + 1, 36226 offset: startRange 36227 }; 36228 } 36229 36230 return segment; 36231 }; 36232 36233 var byteRangeToString = function byteRangeToString(byterange) { 36234 // `endRange` is one less than `offset + length` because the HTTP range 36235 // header uses inclusive ranges 36236 var endRange = byterange.offset + byterange.length - 1; 36237 return byterange.offset + "-" + endRange; 36238 }; 36239 /** 36240 * parse the end number attribue that can be a string 36241 * number, or undefined. 36242 * 36243 * @param {string|number|undefined} endNumber 36244 * The end number attribute. 36245 * 36246 * @return {number|null} 36247 * The result of parsing the end number. 36248 */ 36249 36250 36251 var parseEndNumber = function parseEndNumber(endNumber) { 36252 if (endNumber && typeof endNumber !== 'number') { 36253 endNumber = parseInt(endNumber, 10); 36254 } 36255
36256 if (isNaN(endNumber)) { 36257 return null; 36258 } 36259 36260 return endNumber; 36261 }; 36262 /** 36263 * Functions for calculating the range of available segments in static and dynamic 36264 * manifests. 36265 */ 36266 36267 36268 var segmentRange = { 36269 /** 36270 * Returns the entire range of available segments for a static MPD 36271 * 36272 * @param {Object} attributes 36273 * Inheritied MPD attributes 36274 * @return {{ start: number, end: number }} 36275 * The start and end numbers for available segments 36276 */ 36277 "static": function _static(attributes) { 36278 var duration = attributes.duration, 36279 _attributes$timescale = attributes.timescale, 36280 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 36281 sourceDuration = attributes.sourceDuration, 36282 periodDuration = attributes.periodDuration; 36283 var endNumber = parseEndNumber(attributes.endNumber); 36284 var segmentDuration = duration / timescale; 36285 36286 if (typeof endNumber === 'number') { 36287 return { 36288 start: 0, 36289 end: endNumber 36290 }; 36291 } 36292 36293 if (typeof periodDuration === 'number') { 36294 return { 36295 start: 0, 36296 end: periodDuration / segmentDuration 36297 }; 36298 } 36299 36300 return { 36301 start: 0, 36302 end: sourceDuration / segmentDuration 36303 }; 36304 }, 36305 36306 /** 36307 * Returns the current live window range of available segments for a dynamic MPD 36308 * 36309 * @param {Object} attributes 36310 * Inheritied MPD attributes 36311 * @return {{ start: number, end: number }} 36312 * The start and end numbers for available segments 36313 */ 36314 dynamic: function dynamic(attributes) { 36315 var NOW = attributes.NOW, 36316 clientOffset = attributes.clientOffset, 36317 availabilityStartTime = attributes.availabilityStartTime, 36318 _attributes$timescale2 = attributes.timescale, 36319 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 36320 duration = attributes.duration, 36321 _attributes$start = attributes.start, 36322 start = _attributes$start === void 0 ? 0 : _attributes$start, 36323 _attributes$minimumUp = attributes.minimumUpdatePeriod, 36324 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp, 36325 _attributes$timeShift = attributes.timeShiftBufferDepth, 36326 timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift; 36327 var endNumber = parseEndNumber(attributes.endNumber); 36328 var now = (NOW + clientOffset) / 1000; 36329 var periodStartWC = availabilityStartTime + start; 36330 var periodEndWC = now + minimumUpdatePeriod; 36331 var periodDuration = periodEndWC - periodStartWC; 36332 var segmentCount = Math.ceil(periodDuration * timescale / duration); 36333 var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration); 36334 var availableEnd = Math.floor((now - periodStartWC) * timescale / duration); 36335 return { 36336 start: Math.max(0, availableStart), 36337 end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd) 36338 }; 36339 } 36340 }; 36341 /** 36342 * Maps a range of numbers to objects with information needed to build the corresponding 36343 * segment list 36344 * 36345 * @name toSegmentsCallback 36346 * @function 36347 * @param {number} number 36348 * Number of the segment 36349 * @param {number} index 36350 * Index of the number in the range list 36351 * @return {{ number: Number, duration: Number, timeline: Number, time: Number }} 36352 * Object with segment timing and duration info 36353 */ 36354 36355 /** 36356 * Returns a callback for Array.prototype.map for mapping a range of numbers to 36357 * information needed to build the segment list. 36358 * 36359 * @param {Object} attributes 36360 * Inherited MPD attributes 36361 * @return {toSegmentsCallback} 36362 * Callback map function 36363 */ 36364 36365 var toSegments = function toSegments(attributes) { 36366 return function (number, index) { 36367 var duration = attributes.duration, 36368 _attributes$timescale3 = attributes.timescale, 36369 timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3, 36370 periodIndex = attributes.periodIndex, 36371 _attributes$startNumb = attributes.startNumber, 36372 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb; 36373 return { 36374 number: startNumber + number, 36375 duration: duration / timescale, 36376 timeline: periodIndex, 36377 time: index * duration 36378 }; 36379 }; 36380 }; 36381 /** 36382 * Returns a list of objects containing segment timing and duration info used for 36383 * building the list of segments. This uses the @duration attribute specified 36384 * in the MPD manifest to derive the range of segments. 36385 * 36386 * @param {Object} attributes 36387 * Inherited MPD attributes 36388 * @return {{number: number, duration: number, time: number, timeline: number}[]} 36389 * List of Objects with segment timing and duration info 36390 */ 36391 36392 36393 var parseByDuration = function parseByDuration(attributes) { 36394 var type = attributes.type, 36395 duration = attributes.duration, 36396 _attributes$timescale4 = attributes.timescale, 36397 timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4, 36398 periodDuration = attributes.periodDuration, 36399 sourceDuration = attributes.sourceDuration; 36400 36401 var _segmentRange$type = segmentRange[type](attributes), 36402 start = _segmentRange$type.start, 36403 end = _segmentRange$type.end; 36404 36405 var segments = range(start, end).map(toSegments(attributes)); 36406 36407 if (type === 'static') { 36408 var index = segments.length - 1; // section is either a period or the full source 36409 36410 var sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration 36411 36412 segments[index].duration = sectionDuration - duration / timescale * index; 36413 } 36414 36415 return segments; 36416 }; 36417 /** 36418 * Translates SegmentBase into a set of segments. 36419 * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 36420 * node should be translated into segment. 36421 * 36422 * @param {Object} attributes 36423 * Object containing all inherited attributes from parent elements with attribute 36424 * names as keys 36425 * @return {Object.<Array>} list of segments 36426 */ 36427 36428 36429 var segmentsFromBase = function segmentsFromBase(attributes) { 36430 var baseUrl = attributes.baseUrl, 36431 _attributes$initializ = attributes.initialization, 36432 initialization = _attributes$initializ === void 0 ? {}
36432 : _attributes$initializ, 36433 sourceDuration = attributes.sourceDuration, 36434 _attributes$indexRang = attributes.indexRange, 36435 indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang, 36436 duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) 36437 36438 if (!baseUrl) { 36439 throw new Error(errors.NO_BASE_URL); 36440 } 36441 36442 var initSegment = urlTypeToSegment({ 36443 baseUrl: baseUrl, 36444 source: initialization.sourceURL, 36445 range: initialization.range 36446 }); 36447 var segment = urlTypeToSegment({ 36448 baseUrl: baseUrl, 36449 source: baseUrl, 36450 indexRange: indexRange 36451 }); 36452 segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source 36453 // (since SegmentBase is only for one total segment) 36454 36455 if (duration) { 36456 var segmentTimeInfo = parseByDuration(attributes); 36457 36458 if (segmentTimeInfo.length) { 36459 segment.duration = segmentTimeInfo[0].duration; 36460 segment.timeline = segmentTimeInfo[0].timeline; 36461 } 36462 } else if (sourceDuration) { 36463 segment.duration = sourceDuration; 36464 segment.timeline = 0; 36465 } // This is used for mediaSequence 36466 36467 36468 segment.number = 0; 36469 return [segment]; 36470 }; 36471 /** 36472 * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist 36473 * according to the sidx information given. 36474 * 36475 * playlist.sidx has metadadata about the sidx where-as the sidx param 36476 * is the parsed sidx box itself. 36477 * 36478 * @param {Object} playlist the playlist to update the sidx information for 36479 * @param {Object} sidx the parsed sidx box 36480 * @return {Object} the playlist object with the updated sidx information 36481 */ 36482 36483 36484 var addSidxSegmentsToPlaylist = function addSidxSegmentsToPlaylist(playlist, sidx, baseUrl) { 36485 // Retain init segment information 36486 var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing 36487 36488 var sourceDuration = playlist.sidx.duration; // Retain source timeline 36489 36490 var timeline = playlist.timeline || 0; 36491 var sidxByteRange = playlist.sidx.byterange; 36492 var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx 36493 36494 var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes 36495 36496 var mediaReferences = sidx.references.filter(function (r) { 36497 return r.referenceType !== 1; 36498 }); 36499 var segments = []; 36500 var type = playlist.endList ? 'static' : 'dynamic'; // firstOffset is the offset from the end of the sidx box 36501 36502 var startIndex = sidxEnd + sidx.firstOffset; 36503 36504 for (var i = 0; i < mediaReferences.length; i++) { 36505 var reference = sidx.references[i]; // size of the referenced (sub)segment 36506 36507 var size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale 36508 // this will be converted to seconds when generating segments 36509 36510 var duration = reference.subsegmentDuration; // should be an inclusive range 36511 36512 var endIndex = startIndex + size - 1; 36513 var indexRange = startIndex + "-" + endIndex; 36514 var attributes = { 36515 baseUrl: baseUrl, 36516 timescale: timescale, 36517 timeline: timeline, 36518 // this is used in parseByDuration 36519 periodIndex: timeline, 36520 duration: duration, 36521 sourceDuration: sourceDuration, 36522 indexRange: indexRange, 36523 type: type 36524 }; 36525 var segment = segmentsFromBase(attributes)[0]; 36526 36527 if (initSegment) { 36528 segment.map = initSegment; 36529 } 36530 36531 segments.push(segment); 36532 startIndex += size; 36533 } 36534 36535 playlist.segments = segments; 36536 return playlist; 36537 }; 36538 36539 var generateSidxKey = function generateSidxKey(sidx) { 36540 return sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange); 36541 }; 36542 36543 var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) { 36544 var mergedPlaylists = values(playlists.reduce(function (acc, playlist) { 36545 // assuming playlist IDs are the same across periods 36546 // TODO: handle multiperiod where representation sets are not the same 36547 // across periods 36548 var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first 36549 36550 if (acc[name]) { 36551 var _acc$name$segments; // first segment of subsequent periods signal a discontinuity 36552 36553 36554 if (playlist.segments[0]) { 36555 playlist.segments[0].discontinuity = true; 36556 } 36557 36558 (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content 36559 // has the same contentProtection 36560 36561 36562 if (playlist.attributes.contentProtection) { 36563 acc[name].attributes.contentProtection = playlist.attributes.contentProtection; 36564 } 36565 } else { 36566 // first Period 36567 acc[name] = playlist; 36568 } 36569 36570 return acc; 36571 }, {})); 36572 return mergedPlaylists.map(function (playlist) { 36573 playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity'); 36574 return playlist; 36575 });
36576 }; 36577 36578 var addSidxSegmentsToPlaylist$1 = function addSidxSegmentsToPlaylist$1(playlist, sidxMapping) { 36579 var sidxKey = generateSidxKey(playlist.sidx); 36580 var sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx; 36581 36582 if (sidxMatch) { 36583 addSidxSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri); 36584 } 36585 36586 return playlist; 36587 }; 36588 36589 var addSidxSegmentsToPlaylists = function addSidxSegmentsToPlaylists(playlists, sidxMapping) { 36590 if (sidxMapping === void 0) { 36591 sidxMapping = {}; 36592 } 36593 36594 if (!Object.keys(sidxMapping).length) { 36595 return playlists; 36596 } 36597 36598 for (var i in playlists) { 36599 playlists[i] = addSidxSegmentsToPlaylist$1(playlists[i], sidxMapping); 36600 } 36601 36602 return playlists; 36603 }; 36604 36605 var formatAudioPlaylist = function formatAudioPlaylist(_ref, isAudioOnly) { 36606 var _attributes; 36607 36608 var attributes = _ref.attributes, 36609 segments = _ref.segments, 36610 sidx = _ref.sidx; 36611 var playlist = { 36612 attributes: (_attributes = { 36613 NAME: attributes.id, 36614 BANDWIDTH: attributes.bandwidth, 36615 CODECS: attributes.codecs 36616 }, _attributes['PROGRAM-ID'] = 1, _attributes), 36617 uri: '', 36618 endList: attributes.type === 'static', 36619 timeline: attributes.periodIndex, 36620 resolvedUri: '', 36621 targetDuration: attributes.duration, 36622 segments: segments, 36623 mediaSequence: segments.length ? segments[0].number : 1 36624 }; 36625 36626 if (attributes.contentProtection) { 36627 playlist.contentProtection = attributes.contentProtection; 36628 } 36629 36630 if (sidx) { 36631 playlist.sidx = sidx; 36632 } 36633 36634 if (isAudioOnly) { 36635 playlist.attributes.AUDIO = 'audio'; 36636 playlist.attributes.SUBTITLES = 'subs'; 36637 } 36638 36639 return playlist; 36640 }; 36641 36642 var formatVttPlaylist = function formatVttPlaylist(_ref2) { 36643 var _m3u8Attributes; 36644 36645 var attributes = _ref2.attributes, 36646 segments = _ref2.segments; 36647 36648 if (typeof segments === 'undefined') { 36649 // vtt tracks may use single file in BaseURL 36650 segments = [{ 36651 uri: attributes.baseUrl, 36652 timeline: attributes.periodIndex, 36653 resolvedUri: attributes.baseUrl || '', 36654 duration: attributes.sourceDuration, 36655 number: 0 36656 }]; // targetDuration should be the same duration as the only segment 36657 36658 attributes.duration = attributes.sourceDuration; 36659 } 36660 36661 var m3u8Attributes = (_m3u8Attributes = { 36662 NAME: attributes.id, 36663 BANDWIDTH: attributes.bandwidth 36664 }, _m3u8Attributes['PROGRAM-ID'] = 1, _m3u8Attributes); 36665 36666 if (attributes.codecs) { 36667 m3u8Attributes.CODECS = attributes.codecs; 36668 } 36669 36670 return { 36671 attributes: m3u8Attributes, 36672 uri: '', 36673 endList: attributes.type === 'static', 36674 timeline: attributes.periodIndex, 36675 resolvedUri: attributes.baseUrl || '', 36676 targetDuration: attributes.duration, 36677 segments: segments, 36678 mediaSequence: segments.length ? segments[0].number : 1 36679 }; 36680 }; 36681 36682 var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping, isAudioOnly) { 36683 if (sidxMapping === void 0) { 36684 sidxMapping = {}; 36685 } 36686 36687 if (isAudioOnly === void 0) { 36688 isAudioOnly = false; 36689 } 36690 36691 var mainPlaylist; 36692 var formattedPlaylists = playlists.reduce(function (a, playlist) { 36693 var role = playlist.attributes.role && playlist.attributes.role.value || ''; 36694 var language = playlist.attributes.lang || ''; 36695 var label = playlist.attributes.label || 'main'; 36696 36697 if (language && !playlist.attributes.label) { 36698 var roleLabel = role ? " (" + role + ")" : ''; 36699 label = "" + playlist.attributes.lang + roleLabel; 36700 } 36701 36702 if (!a[label]) { 36703 a[label] = { 36704 language: language, 36705 autoselect: true, 36706 "default": role === 'main', 36707 playlists: [], 36708 uri: '' 36709 }; 36710 } 36711 36712 var formatted = addSidxSegmentsToPlaylist$1(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping); 36713 a[label].playlists.push(formatted); 36714 36715 if (typeof mainPlaylist === 'undefined' && role === 'main') { 36716 mainPlaylist = playlist; 36717 mainPlaylist["default"] = true; 36718 } 36719 36720 return a; 36721 }, {}); // if no playlists have role "main", mark the first as main 36722 36723 if (!mainPlaylist) { 36724 var firstLabel = Object.keys(formattedPlaylists)[0]; 36725 formattedPlaylists[firstLabel]["default"] = true; 36726 } 36727 36728 return formattedPlaylists; 36729 }; 36730 36731 var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) { 36732 if (sidxMapping === void 0) { 36733 sidxMapping = {}; 36734 } 36735 36736 return playlists.reduce(function (a, playlist) { 36737 var label = playlist.attributes.lang || 'text'; 36738 36739 if (!a[label]) { 36740 a[label] = { 36741 language: label, 36742 "default": false, 36743 autoselect: false, 36744 playlists: [], 36745 uri: '' 36746 }; 36747 } 36748 36749 a[label].playlists.push(addSidxSegmentsToPlaylist$1(formatVttPlaylist(playlist), sidxMapping)); 36750 return a; 36751 }, {}); 36752 }; 36753 36754 var organizeCaptionServices = function organizeCaptionServices(captionServices) { 36755 return captionServices.reduce(function (svcObj, svc) { 36756 if (!svc) { 36757 return svcObj; 36758 } 36759
36760 svc.forEach(function (service) { 36761 var channel = service.channel, 36762 language = service.language; 36763 svcObj[language] = { 36764 autoselect: false, 36765 "default": false, 36766 instreamId: channel, 36767 language: language 36768 }; 36769 36770 if (service.hasOwnProperty('aspectRatio')) { 36771 svcObj[language].aspectRatio = service.aspectRatio; 36772 } 36773 36774 if (service.hasOwnProperty('easyReader')) { 36775 svcObj[language].easyReader = service.easyReader; 36776 } 36777 36778 if (service.hasOwnProperty('3D')) { 36779 svcObj[language]['3D'] = service['3D']; 36780 } 36781 }); 36782 return svcObj; 36783 }, {}); 36784 }; 36785 36786 var formatVideoPlaylist = function formatVideoPlaylist(_ref3) { 36787 var _attributes2; 36788 36789 var attributes = _ref3.attributes, 36790 segments = _ref3.segments, 36791 sidx = _ref3.sidx; 36792 var playlist = { 36793 attributes: (_attributes2 = { 36794 NAME: attributes.id, 36795 AUDIO: 'audio', 36796 SUBTITLES: 'subs', 36797 RESOLUTION: { 36798 width: attributes.width, 36799 height: attributes.height 36800 }, 36801 CODECS: attributes.codecs, 36802 BANDWIDTH: attributes.bandwidth 36803 }, _attributes2['PROGRAM-ID'] = 1, _attributes2), 36804 uri: '', 36805 endList: attributes.type === 'static', 36806 timeline: attributes.periodIndex, 36807 resolvedUri: '', 36808 targetDuration: attributes.duration, 36809 segments: segments, 36810 mediaSequence: segments.length ? segments[0].number : 1 36811 }; 36812 36813 if (attributes.contentProtection) { 36814 playlist.contentProtection = attributes.contentProtection; 36815 } 36816 36817 if (sidx) { 36818 playlist.sidx = sidx; 36819 } 36820 36821 return playlist; 36822 }; 36823 36824 var videoOnly = function videoOnly(_ref4) { 36825 var attributes = _ref4.attributes; 36826 return attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video'; 36827 }; 36828 36829 var audioOnly = function audioOnly(_ref5) { 36830 var attributes = _ref5.attributes; 36831 return attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio'; 36832 }; 36833 36834 var vttOnly = function vttOnly(_ref6) { 36835 var attributes = _ref6.attributes; 36836 return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text'; 36837 }; 36838 36839 var toM3u8 = function toM3u8(dashPlaylists, locations, sidxMapping) { 36840 var _mediaGroups; 36841 36842 if (sidxMapping === void 0) { 36843 sidxMapping = {}; 36844 } 36845 36846 if (!dashPlaylists.length) { 36847 return {}; 36848 } // grab all main manifest attributes 36849 36850 36851 var _dashPlaylists$0$attr = dashPlaylists[0].attributes, 36852 duration = _dashPlaylists$0$attr.sourceDuration, 36853 type = _dashPlaylists$0$attr.type, 36854 suggestedPresentationDelay = _dashPlaylists$0$attr.suggestedPresentationDelay, 36855 minimumUpdatePeriod = _dashPlaylists$0$attr.minimumUpdatePeriod; 36856 var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist); 36857 var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly)); 36858 var vttPlaylists = dashPlaylists.filter(vttOnly); 36859 var captions = dashPlaylists.map(function (playlist) { 36860 return playlist.attributes.captionServices; 36861 }).filter(Boolean); 36862 var manifest = { 36863 allowCache: true, 36864 discontinuityStarts: [], 36865 segments: [], 36866 endList: true, 36867 mediaGroups: (_mediaGroups = { 36868 AUDIO: {}, 36869 VIDEO: {} 36870 }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups), 36871 uri: '', 36872 duration: duration, 36873 playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping) 36874 }; 36875 36876 if (minimumUpdatePeriod >= 0) { 36877 manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000; 36878 } 36879 36880 if (locations) { 36881 manifest.locations = locations; 36882 } 36883 36884 if (type === 'dynamic') { 36885 manifest.suggestedPresentationDelay = suggestedPresentationDelay; 36886 } 36887 36888 var isAudioOnly = manifest.playlists.length === 0; 36889 36890 if (audioPlaylists.length) { 36891 manifest.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly); 36892 } 36893 36894 if (vttPlaylists.length) { 36895 manifest.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping); 36896 } 36897 36898 if (captions.length) { 36899 manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions); 36900 } 36901 36902 return manifest; 36903 }; 36904 /** 36905 * Calculates the R (repetition) value for a live stream (for the final segment 36906 * in a manifest where the r value is negative 1) 36907 * 36908 * @param {Object} attributes 36909 * Object containing all inherited attributes from parent elements with attribute 36910 * names as keys 36911 * @param {number} time 36912 * current time (typically the total time up until the final segment) 36913 * @param {number} duration 36914 * duration property for the given <S /> 36915 * 36916 * @return {number} 36917 * R value to reach the end of the given period 36918 */ 36919 36920 36921 var getLiveRValue = function getLiveRValue(attributes, time, duration) { 36922 var NOW = attributes.NOW, 36923 clientOffset = attributes.clientOffset, 36924 availabilityStartTime = attributes.availabilityStartTime, 36925 _attributes$timescale = attributes.timescale, 36926 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 36927 _attributes$start = attributes.start, 36928 start = _attributes$start === void 0 ? 0 : _attributes$start, 36929 _attributes$minimumUp = attributes.minimumUpdatePeriod, 36930 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp; 36931 var now = (NOW + clientOffset) / 1000; 36932 var periodStartWC = availabilityStartTime + start; 36933 var periodEndWC = now + minimumUpdatePeriod; 36934 var periodDuration = periodEndWC - periodStartWC; 36935 return Math.ceil((periodDuration * timescale - time) / duration); 36936 }; 36937 /** 36938 * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment 36939 * timing and duration 36940 * 36941 * @param {Object} attributes 36942 * Object containing all inherited attributes from parent elements with attribute 36943 * names as keys 36944 * @param {Object[]} segmentTimeline 36945 * List of objects representing the attributes of each S element contained within 36946 * 36947 * @return {{number: number, duration: number, time: number, timeline: number}[]} 36948 * List of Objects with segment timing and duration info 36949 */ 36950 36951 36952 var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) { 36953 var type = attributes.type, 36954 _attributes$minimumUp2 = attributes.minimumUpdatePeriod, 36955 minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2, 36956 _attributes$media = attributes.media, 36957 media = _attributes$media === void 0 ? '' : _attributes$media, 36958 sourceDuration = attributes.sourceDuration, 36959 _attributes$timescale2 = attributes.timescale, 36960 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 36961 _attributes$startNumb = attributes.startNumber, 36962 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb, 36963 timeline = attributes.periodIndex; 36964 var segments = []; 36965 var time = -1; 36966 36967 for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { 36968 var S = segmentTimeline[sIndex]; 36969 var duration = S.d; 36970 var repeat = S.r || 0; 36971 var segmentTime = S.t || 0; 36972 36973 if (time < 0) { 36974 // first segment 36975 time = segmentTime; 36976 } 36977 36978 if (segmentTime && segmentTime > time) { 36979 // discontinuity 36980 // TODO: How to handle this type of discontinuity 36981 // timeline++ here would treat it like HLS discontuity and content would 36982 // get appended without gap 36983 // E.G. 36984 // <S t="0" d="1" /> 36985 // <S d="1" /> 36986 // <S d="1" /> 36987 // <S t="5" d="1" /> 36988 // would have $Time$ values of [0, 1, 2, 5] 36989 // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) 36990 // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) 36991 // does the value of sourceDuration consider this when calculating arbitrary 36992 // negative @r repeat value? 36993 // E.G. Same elements as above with this added at the end 36994 // <S d="1" r="-1" /> 36995 // with a sourceDuration of 10 36996 // Would the 2 gaps be included in the time duration calculations resulting in 36997 // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments 36998 // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? 36999 time = segmentTime; 37000 } 37001 37002 var count = void 0; 37003 37004 if (repeat < 0) { 37005 var nextS = sIndex + 1; 37006 37007 if (nextS === segmentTimeline.length) { 37008 // last segment 37009 if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) { 37010 count = getLiveRValue(attributes, time, duration); 37011 } else { 37012 // TODO: This may be incorrect depending on conclusion of TODO above 37013 count = (sourceDuration * timescale - time) / duration; 37014 } 37015 } else { 37016 count = (segmentTimeline[nextS].t - time) / duration; 37017 } 37018 } else { 37019 count = repeat + 1; 37020 } 37021 37022 var end = startNumber + segments.length + count; 37023 var number = startNumber + segments.length; 37024 37025 while (number < end) { 37026 segments.push({ 37027 number: number, 37028 duration: duration / timescale, 37029 time: time, 37030 timeline: timeline 37031 }); 37032 time += duration; 37033 number++; 37034 } 37035 } 37036 37037 return segments; 37038 }; 37039 37040 var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; 37041 /** 37042 * Replaces template identifiers with corresponding values. To be used as the callback 37043 * for String.prototype.replace 37044 * 37045 * @name replaceCallback 37046 * @function 37047 * @param {string} match 37048 * Entire match of identifier 37049 * @param {string} identifier 37050 * Name of matched identifier 37051 * @param {string} format 37052 * Format tag string. Its presence indicates that padding is expected 37053 * @param {string} width 37054 * Desired length of the replaced value. Values less than this width shall be left 37055 * zero padded 37056 * @return {string} 37057 * Replacement for the matched identifier 37058 */ 37059 37060 /** 37061 * Returns a function to be used as a callback for String.prototype.replace to replace 37062 * template identifiers 37063 * 37064 * @param {Obect} values 37065 * Object containing values that shall be used to replace known identifiers 37066 * @param {number} values.RepresentationID 37067 * Value of the Representation@id attribute 37068 * @param {number} values.Number 37069 * Number of the corresponding segment 37070 * @param {number} values.Bandwidth 37071 * Value of the Representation@bandwidth attribute. 37072 * @param {number} values.Time 37073 * Timestamp value of the corresponding segment 37074 * @return {replaceCallback} 37075 * Callback to be used with String.prototype.replace to replace identifiers 37076 */ 37077 37078 var identifierReplacement = function identifierReplacement(values) { 37079 return function (match, identifier, format, width) { 37080 if (match === '$$') { 37081 // escape sequence 37082 return '$'; 37083 } 37084 37085 if (typeof values[identifier] === 'undefined') { 37086 return match; 37087 } 37088 37089 var value = '' + values[identifier]; 37090 37091 if (identifier === 'RepresentationID') { 37092 // Format tag shall not be present with RepresentationID 37093 return value; 37094 } 37095 37096 if (!format) { 37097 width = 1; 37098 } else { 37099 width = parseInt(width, 10); 37100 } 37101 37102 if (value.length >= width) { 37103 return value; 37104 } 37105 37106 return "" + new Array(width - value.length + 1).join('0') + value; 37107 }; 37108 }; 37109 /** 37110 * Constructs a segment url from a template string 37111 * 37112 * @param {string} url 37113 * Template string to construct url from 37114 * @param {Obect} values 37115 * Object containing values that shall be used to replace known identifiers 37116 * @param {number} values.RepresentationID 37117 * Value of the Representation@id attribute 37118 * @param {number} values.Number 37119 * Number of the corresponding segment 37120 * @param {number} values.Bandwidth 37121 * Value of the Representation@bandwidth attribute. 37122 * @param {number} values.Time 37123 * Timestamp value of the corresponding segment 37124 * @return {string} 37125 * Segment url with identifiers replaced 37126 */ 37127 37128 37129 var constructTemplateUrl = function constructTemplateUrl(url, values) { 37130 return url.replace(identifierPattern, identifierReplacement(values)); 37131 }; 37132 /** 37133 * Generates a list of objects containing timing and duration information about each 37134 * segment needed to generate segment uris and the complete segment object 37135 * 37136 * @param {Object} attributes 37137 * Object containing all inherited attributes from parent elements with attribute 37138 * names as keys 37139 * @param {Object[]|undefined} segmentTimeline 37140 * List of objects representing the attributes of each S element contained within 37141 * the SegmentTimeline element 37142 * @return {{number: number, duration: number, time: number, timeline: number}[]} 37143 * List of Objects with segment timing and duration info 37144 */ 37145 37146 37147 var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) { 37148 if (!attributes.duration && !segmentTimeline) { 37149 // if neither @duration or SegmentTimeline are present, then there shall be exactly 37150 // one media segment 37151 return [{ 37152 number: attributes.startNumber || 1, 37153 duration: attributes.sourceDuration, 37154 time: 0, 37155 timeline: attributes.periodIndex 37156 }]; 37157 } 37158 37159 if (attributes.duration) { 37160 return parseByDuration(attributes); 37161 } 37162 37163 return parseByTimeline(attributes, segmentTimeline); 37164 }; 37165 /** 37166 * Generates a list of segments using information provided by the SegmentTemplate element 37167 * 37168 * @param {Object} attributes 37169 * Object containing all inherited attributes from parent elements with attribute 37170 * names as keys 37171 * @param {Object[]|undefined} segmentTimeline 37172 * List of objects representing the attributes of each S element contained within 37173 * the SegmentTimeline element 37174 * @return {Object[]} 37175 * List of segment objects 37176 */ 37177 37178 37179 var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) { 37180 var templateValues = { 37181 RepresentationID: attributes.id, 37182 Bandwidth: attributes.bandwidth || 0 37183 }; 37184 var _attributes$initializ = attributes.initialization, 37185 initialization = _attributes$initializ === void 0 ? { 37186 sourceURL: '', 37187 range: '' 37188 } : _attributes$initializ; 37189 var mapSegment = urlTypeToSegment({ 37190 baseUrl: attributes.baseUrl, 37191 source: constructTemplateUrl(initialization.sourceURL, templateValues), 37192 range: initialization.range 37193 });
37194 var segments = parseTemplateInfo(attributes, segmentTimeline); 37195 return segments.map(function (segment) { 37196 templateValues.Number = segment.number; 37197 templateValues.Time = segment.time; 37198 var uri = constructTemplateUrl(attributes.media || '', templateValues); // See DASH spec section 5.3.9.2.2 37199 // - if timescale isn't present on any level, default to 1. 37200 37201 var timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0 37202 37203 var presentationTimeOffset = attributes.presentationTimeOffset || 0; 37204 var presentationTime = // Even if the @t attribute is not specified for the segment, segment.time is 37205 // calculated in mpd-parser prior to this, so it's assumed to be available. 37206 attributes.periodStart + (segment.time - presentationTimeOffset) / timescale; 37207 var map = { 37208 uri: uri, 37209 timeline: segment.timeline, 37210 duration: segment.duration, 37211 resolvedUri: resolveUrl$1(attributes.baseUrl || '', uri), 37212 map: mapSegment, 37213 number: segment.number, 37214 presentationTime: presentationTime 37215 }; 37216 return map; 37217 }); 37218 }; 37219 /** 37220 * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14) 37221 * to an object that matches the output of a segment in videojs/mpd-parser 37222 * 37223 * @param {Object} attributes 37224 * Object containing all inherited attributes from parent elements with attribute 37225 * names as keys 37226 * @param {Object} segmentUrl 37227 * <SegmentURL> node to translate into a segment object 37228 * @return {Object} translated segment object 37229 */ 37230 37231 37232 var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) { 37233 var baseUrl = attributes.baseUrl, 37234 _attributes$initializ = attributes.initialization, 37235 initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ; 37236 var initSegment = urlTypeToSegment({ 37237 baseUrl: baseUrl, 37238 source: initialization.sourceURL, 37239 range: initialization.range 37240 }); 37241 var segment = urlTypeToSegment({ 37242 baseUrl: baseUrl, 37243 source: segmentUrl.media, 37244 range: segmentUrl.mediaRange 37245 }); 37246 segment.map = initSegment; 37247 return segment; 37248 }; 37249 /** 37250 * Generates a list of segments using information provided by the SegmentList element 37251 * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 37252 * node should be translated into segment. 37253 * 37254 * @param {Object} attributes 37255 * Object containing all inherited attributes from parent elements with attribute 37256 * names as keys 37257 * @param {Object[]|undefined} segmentTimeline 37258 * List of objects representing the attributes of each S element contained within 37259 * the SegmentTimeline element 37260 * @return {Object.<Array>} list of segments 37261 */ 37262 37263
37264 var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) { 37265 var duration = attributes.duration, 37266 _attributes$segmentUr = attributes.segmentUrls, 37267 segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr, 37268 periodStart = attributes.periodStart; // Per spec (5.3.9.2.1) no way to determine segment duration OR 37269 // if both SegmentTimeline and @duration are defined, it is outside of spec. 37270 37271 if (!duration && !segmentTimeline || duration && segmentTimeline) { 37272 throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); 37273 } 37274 37275 var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) { 37276 return SegmentURLToSegmentObject(attributes, segmentUrlObject); 37277 }); 37278 var segmentTimeInfo; 37279 37280 if (duration) { 37281 segmentTimeInfo = parseByDuration(attributes); 37282 } 37283 37284 if (segmentTimeline) { 37285 segmentTimeInfo = parseByTimeline(attributes, segmentTimeline); 37286 } 37287 37288 var segments = segmentTimeInfo.map(function (segmentTime, index) { 37289 if (segmentUrlMap[index]) { 37290 var segment = segmentUrlMap[index]; // See DASH spec section 5.3.9.2.2 37291 // - if timescale isn't present on any level, default to 1. 37292 37293 var timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0 37294 37295 var presentationTimeOffset = attributes.presentationTimeOffset || 0; 37296 segment.timeline = segmentTime.timeline; 37297 segment.duration = segmentTime.duration; 37298 segment.number = segmentTime.number; 37299 segment.presentationTime = periodStart + (segmentTime.time - presentationTimeOffset) / timescale; 37300 return segment; 37301 } // Since we're mapping we should get rid of any blank segments (in case 37302 // the given SegmentTimeline is handling for more elements than we have 37303 // SegmentURLs for). 37304 37305 }).filter(function (segment) { 37306 return segment; 37307 }); 37308 return segments; 37309 }; 37310 37311 var generateSegments = function generateSegments(_ref) { 37312 var attributes = _ref.attributes, 37313 segmentInfo = _ref.segmentInfo; 37314 var segmentAttributes; 37315 var segmentsFn; 37316 37317 if (segmentInfo.template) { 37318 segmentsFn = segmentsFromTemplate; 37319 segmentAttributes = merge(attributes, segmentInfo.template); 37320 } else if (segmentInfo.base) { 37321 segmentsFn = segmentsFromBase; 37322 segmentAttributes = merge(attributes, segmentInfo.base); 37323 } else if (segmentInfo.list) { 37324 segmentsFn = segmentsFromList; 37325 segmentAttributes = merge(attributes, segmentInfo.list); 37326 } 37327 37328 var segmentsInfo = { 37329 attributes: attributes 37330 }; 37331 37332 if (!segmentsFn) { 37333 return segmentsInfo; 37334 } 37335 37336 var segments = segmentsFn(segmentAttributes, segmentInfo.segmentTimeline); // The @duration attribute will be used to determin the playlist's targetDuration which 37337 // must be in seconds. Since we've generated the segment list, we no longer need 37338 // @duration to be in @timescale units, so we can convert it here. 37339 37340 if (segmentAttributes.duration) { 37341 var _segmentAttributes = segmentAttributes, 37342 duration = _segmentAttributes.duration, 37343 _segmentAttributes$ti = _segmentAttributes.timescale, 37344 timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti; 37345 segmentAttributes.duration = duration / timescale; 37346 } else if (segments.length) { 37347 // if there is no @duration attribute, use the largest segment duration as 37348 // as target duration 37349 segmentAttributes.duration = segments.reduce(function (max, segment) { 37350 return Math.max(max, Math.ceil(segment.duration)); 37351 }, 0); 37352 } else { 37353 segmentAttributes.duration = 0; 37354 } 37355 37356 segmentsInfo.attributes = segmentAttributes; 37357 segmentsInfo.segments = segments; // This is a sidx box without actual segment information 37358 37359 if (segmentInfo.base && segmentAttributes.indexRange) { 37360 segmentsInfo.sidx = segments[0]; 37361 segmentsInfo.segments = []; 37362 } 37363 37364 return segmentsInfo; 37365 }; 37366 37367 var toPlaylists = function toPlaylists(representations) { 37368 return representations.map(generateSegments); 37369 }; 37370
vendor: 14,298 bytes, lines 37371-37795
37371 var findChildren = function findChildren(element, name) { 37372 return from(element.childNodes).filter(function (_ref) { 37373 var tagName = _ref.tagName; 37374 return tagName === name; 37375 }); 37376 }; 37377 37378 var getContent = function getContent(element) { 37379 return element.textContent.trim(); 37380 }; 37381 37382 var parseDuration = function parseDuration(str) { 37383 var SECONDS_IN_YEAR = 365 * 24 * 60 * 60; 37384 var SECONDS_IN_MONTH = 30 * 24 * 60 * 60; 37385 var SECONDS_IN_DAY = 24 * 60 * 60; 37386 var SECONDS_IN_HOUR = 60 * 60; 37387 var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S 37388 37389 var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/; 37390 var match = durationRegex.exec(str); 37391 37392 if (!match) { 37393 return 0; 37394 } 37395 37396 var _match$slice = match.slice(1), 37397 year = _match$slice[0], 37398 month = _match$slice[1], 37399 day = _match$slice[2], 37400 hour = _match$slice[3], 37401 minute = _match$slice[4], 37402 second = _match$slice[5]; 37403 37404 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); 37405 }; 37406 37407 var parseDate = function parseDate(str) { 37408 // Date format without timezone according to ISO 8601 37409 // YYY-MM-DDThh:mm:ss.ssssss 37410 var dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is 37411 // expressed by ending with 'Z' 37412 37413 if (dateRegex.test(str)) { 37414 str += 'Z'; 37415 } 37416 37417 return Date.parse(str); 37418 }; 37419 37420 var parsers = { 37421 /** 37422 * Specifies the duration of the entire Media Presentation. Format is a duration string 37423 * as specified in ISO 8601 37424 * 37425 * @param {string} value 37426 * value of attribute as a string 37427 * @return {number} 37428 * The duration in seconds 37429 */ 37430 mediaPresentationDuration: function mediaPresentationDuration(value) { 37431 return parseDuration(value); 37432 }, 37433 37434 /** 37435 * Specifies the Segment availability start time for all Segments referred to in this 37436 * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability 37437 * time. Format is a date string as specified in ISO 8601 37438 * 37439 * @param {string} value 37440 * value of attribute as a string 37441 * @return {number} 37442 * The date as seconds from unix epoch 37443 */ 37444 availabilityStartTime: function availabilityStartTime(value) { 37445 return parseDate(value) / 1000; 37446 }, 37447 37448 /** 37449 * Specifies the smallest period between potential changes to the MPD. Format is a 37450 * duration string as specified in ISO 8601 37451 * 37452 * @param {string} value 37453 * value of attribute as a string 37454 * @return {number} 37455 * The duration in seconds 37456 */ 37457 minimumUpdatePeriod: function minimumUpdatePeriod(value) { 37458 return parseDuration(value); 37459 }, 37460 37461 /** 37462 * Specifies the suggested presentation delay. Format is a 37463 * duration string as specified in ISO 8601 37464 * 37465 * @param {string} value 37466 * value of attribute as a string 37467 * @return {number} 37468 * The duration in seconds 37469 */ 37470 suggestedPresentationDelay: function suggestedPresentationDelay(value) { 37471 return parseDuration(value); 37472 }, 37473 37474 /** 37475 * specifices the type of mpd. Can be either "static" or "dynamic" 37476 * 37477 * @param {string} value 37478 * value of attribute as a string 37479 * 37480 * @return {string} 37481 * The type as a string 37482 */ 37483 type: function type(value) { 37484 return value; 37485 }, 37486 37487 /** 37488 * Specifies the duration of the smallest time shifting buffer for any Representation 37489 * in the MPD. Format is a duration string as specified in ISO 8601 37490 * 37491 * @param {string} value 37492 * value of attribute as a string 37493 * @return {number} 37494 * The duration in seconds 37495 */ 37496 timeShiftBufferDepth: function timeShiftBufferDepth(value) { 37497 return parseDuration(value); 37498 }, 37499 37500 /** 37501 * Specifies the PeriodStart time of the Period relative to the availabilityStarttime. 37502 * Format is a duration string as specified in ISO 8601 37503 * 37504 * @param {string} value 37505 * value of attribute as a string 37506 * @return {number} 37507 * The duration in seconds 37508 */ 37509 start: function start(value) { 37510 return parseDuration(value); 37511 }, 37512 37513 /** 37514 * Specifies the width of the visual presentation 37515 * 37516 * @param {string} value 37517 * value of attribute as a string 37518 * @return {number} 37519 * The parsed width 37520 */ 37521 width: function width(value) { 37522 return parseInt(value, 10); 37523 }, 37524 37525 /** 37526 * Specifies the height of the visual presentation 37527 * 37528 * @param {string} value 37529 * value of attribute as a string 37530 * @return {number} 37531 * The parsed height 37532 */ 37533 height: function height(value) { 37534 return parseInt(value, 10); 37535 }, 37536 37537 /** 37538 * Specifies the bitrate of the representation 37539 * 37540 * @param {string} value 37541 * value of attribute as a string 37542 * @return {number} 37543 * The parsed bandwidth 37544 */ 37545 bandwidth: function bandwidth(value) { 37546 return parseInt(value, 10); 37547 }, 37548 37549 /** 37550 * Specifies the number of the first Media Segment in this Representation in the Period 37551 * 37552 * @param {string} value 37553 * value of attribute as a string 37554 * @return {number} 37555 * The parsed number 37556 */ 37557 startNumber: function startNumber(value) { 37558 return parseInt(value, 10); 37559 }, 37560 37561 /** 37562 * Specifies the timescale in units per seconds 37563 * 37564 * @param {string} value 37565 * value of attribute as a string 37566 * @return {number} 37567 * The parsed timescale 37568 */ 37569 timescale: function timescale(value) { 37570 return parseInt(value, 10); 37571 }, 37572 37573 /** 37574 * Specifies the presentationTimeOffset. 37575 * 37576 * @param {string} value 37577 * value of the attribute as a string 37578 * 37579 * @return {number} 37580 * The parsed presentationTimeOffset 37581 */ 37582 presentationTimeOffset: function presentationTimeOffset(value) { 37583 return parseInt(value, 10); 37584 }, 37585 37586 /** 37587 * Specifies the constant approximate Segment duration 37588 * NOTE: The <Period> element also contains an @duration attribute. This duration 37589 * specifies the duration of the Period. This attribute is currently not 37590 * supported by the rest of the parser, however we still check for it to prevent 37591 * errors. 37592 * 37593 * @param {string} value 37594 * value of attribute as a string 37595 * @return {number} 37596 * The parsed duration 37597 */ 37598 duration: function duration(value) { 37599 var parsedValue = parseInt(value, 10); 37600 37601 if (isNaN(parsedValue)) { 37602 return parseDuration(value); 37603 } 37604 37605 return parsedValue; 37606 }, 37607 37608 /** 37609 * Specifies the Segment duration, in units of the value of the @timescale. 37610 * 37611 * @param {string} value 37612 * value of attribute as a string 37613 * @return {number} 37614 * The parsed duration 37615 */ 37616 d: function d(value) { 37617 return parseInt(value, 10); 37618 }, 37619 37620 /** 37621 * Specifies the MPD start time, in @timescale units, the first Segment in the series 37622 * starts relative to the beginning of the Period 37623 * 37624 * @param {string} value 37625 * value of attribute as a string 37626 * @return {number} 37627 * The parsed time 37628 */ 37629 t: function t(value) { 37630 return parseInt(value, 10); 37631 }, 37632 37633 /** 37634 * Specifies the repeat count of the number of following contiguous Segments with the 37635 * same duration expressed by the value of @d 37636 * 37637 * @param {string} value 37638 * value of attribute as a string 37639 * @return {number} 37640 * The parsed number 37641 */ 37642 r: function r(value) { 37643 return parseInt(value, 10); 37644 }, 37645 37646 /** 37647 * Default parser for all other attributes. Acts as a no-op and just returns the value 37648 * as a string 37649 * 37650 * @param {string} value 37651 * value of attribute as a string 37652 * @return {string} 37653 * Unparsed value 37654 */ 37655 DEFAULT: function DEFAULT(value) { 37656 return value; 37657 } 37658 }; 37659 /** 37660 * Gets all the attributes and values of the provided node, parses attributes with known 37661 * types, and returns an object with attribute names mapped to values. 37662 * 37663 * @param {Node} el 37664 * The node to parse attributes from 37665 * @return {Object} 37666 * Object with all attributes of el parsed 37667 */ 37668 37669 var parseAttributes = function parseAttributes(el) { 37670 if (!(el && el.attributes)) { 37671 return {}; 37672 } 37673 37674 return from(el.attributes).reduce(function (a, e) { 37675 var parseFn = parsers[e.name] || parsers.DEFAULT; 37676 a[e.name] = parseFn(e.value); 37677 return a; 37678 }, {}); 37679 }; 37680 37681 var keySystemsMap = { 37682 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', 37683 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', 37684 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready', 37685 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime' 37686 }; 37687 /** 37688 * Builds a list of urls that is the product of the reference urls and BaseURL values 37689 * 37690 * @param {string[]} referenceUrls 37691 * List of reference urls to resolve to 37692 * @param {Node[]} baseUrlElements 37693 * List of BaseURL nodes from the mpd 37694 * @return {string[]} 37695 * List of resolved urls 37696 */ 37697 37698 var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) { 37699 if (!baseUrlElements.length) { 37700 return referenceUrls; 37701 } 37702 37703 return flatten(referenceUrls.map(function (reference) { 37704 return baseUrlElements.map(function (baseUrlElement) { 37705 return resolveUrl$1(reference, getContent(baseUrlElement)); 37706 }); 37707 })); 37708 }; 37709 /** 37710 * Contains all Segment information for its containing AdaptationSet 37711 * 37712 * @typedef {Object} SegmentInformation 37713 * @property {Object|undefined} template 37714 * Contains the attributes for the SegmentTemplate node 37715 * @property {Object[]|undefined} segmentTimeline 37716 * Contains a list of atrributes for each S node within the SegmentTimeline node 37717 * @property {Object|undefined} list 37718 * Contains the attributes for the SegmentList node 37719 * @property {Object|undefined} base 37720 * Contains the attributes for the SegmentBase node 37721 */ 37722 37723 /** 37724 * Returns all available Segment information contained within the AdaptationSet node 37725 * 37726 * @param {Node} adaptationSet 37727 * The AdaptationSet node to get Segment information from 37728 * @return {SegmentInformation} 37729 * The Segment information contained within the provided AdaptationSet 37730 */ 37731 37732 37733 var getSegmentInformation = function getSegmentInformation(adaptationSet) { 37734 var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; 37735 var segmentList = findChildren(adaptationSet, 'SegmentList')[0]; 37736 var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) { 37737 return merge({ 37738 tag: 'SegmentURL' 37739 }, parseAttributes(s)); 37740 }); 37741 var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; 37742 var segmentTimelineParentNode = segmentList || segmentTemplate; 37743 var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0]; 37744 var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate; 37745 var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both 37746 // @initialization and an <Initialization> node. @initialization can be templated, 37747 // while the node can have a url and range specified. If the <SegmentTemplate> has 37748 // both @initialization and an <Initialization> subelement we opt to override with 37749 // the node, as this interaction is not defined in the spec. 37750 37751 var template = segmentTemplate && parseAttributes(segmentTemplate); 37752 37753 if (template && segmentInitialization) { 37754 template.initialization = segmentInitialization && parseAttributes(segmentInitialization); 37755 } else if (template && template.initialization) { 37756 // If it is @initialization we convert it to an object since this is the format that 37757 // later functions will rely on for the initialization segment. This is only valid 37758 // for <SegmentTemplate> 37759 template.initialization = { 37760 sourceURL: template.initialization 37761 }; 37762 } 37763 37764 var segmentInfo = { 37765 template: template, 37766 segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) { 37767 return parseAttributes(s); 37768 }), 37769 list: segmentList && merge(parseAttributes(segmentList), { 37770 segmentUrls: segmentUrls, 37771 initialization: parseAttributes(segmentInitialization) 37772 }), 37773 base: segmentBase && merge(parseAttributes(segmentBase), { 37774 initialization: parseAttributes(segmentInitialization) 37775 }) 37776 }; 37777 Object.keys(segmentInfo).forEach(function (key) { 37778 if (!segmentInfo[key]) { 37779 delete segmentInfo[key]; 37780 } 37781 }); 37782 return segmentInfo; 37783 }; 37784 /** 37785 * Contains Segment information and attributes needed to construct a Playlist object 37786 * from a Representation 37787 * 37788 * @typedef {Object} RepresentationInformation 37789 * @property {SegmentInformation} segmentInfo 37790 * Segment information for this Representation 37791 * @property {Object} attributes 37792 * Inherited attributes for this Representation 37793 */ 37794 37795 /**
37796 * Maps a Representation node to an object containing Segment information and attributes 37797 * 37798 * @name inheritBaseUrlsCallback 37799 * @function 37800 * @param {Node} representation 37801 * Representation node from the mpd 37802 * @return {RepresentationInformation} 37803 * Representation information needed to construct a Playlist object 37804 */ 37805 37806 /** 37807 * Returns a callback for Array.prototype.map for mapping Representation nodes to 37808 * Segment information and attributes using inherited BaseURL nodes. 37809 * 37810 * @param {Object} adaptationSetAttributes 37811 * Contains attributes inherited by the AdaptationSet 37812 * @param {string[]} adaptationSetBaseUrls 37813 * Contains list of resolved base urls inherited by the AdaptationSet 37814 * @param {SegmentInformation} adaptationSetSegmentInfo 37815 * Contains Segment information for the AdaptationSet 37816 * @return {inheritBaseUrlsCallback} 37817 * Callback map function 37818 */ 37819 37820 37821 var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) { 37822 return function (representation) { 37823 var repBaseUrlElements = findChildren(representation, 'BaseURL'); 37824 var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements); 37825 var attributes = merge(adaptationSetAttributes, parseAttributes(representation)); 37826 var representationSegmentInfo = getSegmentInformation(representation); 37827 return repBaseUrls.map(function (baseUrl) { 37828 return { 37829 segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo), 37830 attributes: merge(attributes, { 37831 baseUrl: baseUrl 37832 }) 37833 }; 37834 }); 37835 }; 37836 }; 37837 /** 37838 * Tranforms a series of content protection nodes to
37839 * an object containing pssh data by key system 37840 * 37841 * @param {Node[]} contentProtectionNodes 37842 * Content protection nodes 37843 * @return {Object} 37844 * Object containing pssh data by key system 37845 */ 37846 37847 37848 var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) { 37849 return contentProtectionNodes.reduce(function (acc, node) { 37850 var attributes = parseAttributes(node); 37851 var keySystem = keySystemsMap[attributes.schemeIdUri]; 37852 37853 if (keySystem) { 37854 acc[keySystem] = { 37855 attributes: attributes 37856 }; 37857 var psshNode = findChildren(node, 'cenc:pssh')[0]; 37858 37859 if (psshNode) { 37860 var pssh = getContent(psshNode); 37861 var psshBuffer = pssh && decodeB64ToUint8Array(pssh); 37862 acc[keySystem].pssh = psshBuffer; 37863 } 37864 } 37865 37866 return acc; 37867 }, {}); 37868 }; // defined in ANSI_SCTE 214-1 2016 37869 37870 37871 var parseCaptionServiceMetadata = function parseCaptionServiceMetadata(service) { 37872 // 608 captions 37873 if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') { 37874 var values = service.value.split(';'); 37875 return values.map(function (value) { 37876 var channel; 37877 var language; // default language to value 37878 37879 language = value; 37880 37881 if (/^CC\d=/.test(value)) { 37882 var _value$split = value.split('='); 37883 37884 channel = _value$split[0]; 37885 language = _value$split[1]; 37886 } else if (/^CC\d$/.test(value)) { 37887 channel = value; 37888 } 37889 37890 return { 37891 channel: channel, 37892 language: language 37893 }; 37894 }); 37895 } else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') { 37896 var _values = service.value.split(';'); 37897 37898 return _values.map(function (value) { 37899 var flags = { 37900 // service or channel number 1-63 37901 'channel': undefined, 37902 // language is a 3ALPHA per ISO 639.2/B 37903 // field is required 37904 'language': undefined, 37905 // BIT 1/0 or ? 37906 // default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown 37907 'aspectRatio': 1, 37908 // BIT 1/0 37909 // easy reader flag indicated the text is tailed to the needs of beginning readers 37910 // default 0, or off 37911 'easyReader': 0, 37912 // BIT 1/0 37913 // If 3d metadata is present (CEA-708.1) then 1 37914 // default 0 37915 '3D': 0 37916 }; 37917 37918 if (/=/.test(value)) { 37919 var _value$split2 = value.split('='), 37920 channel = _value$split2[0], 37921 _value$split2$ = _value$split2[1], 37922 opts = _value$split2$ === void 0 ? '' : _value$split2$; 37923 37924 flags.channel = channel; 37925 flags.language = value; 37926 opts.split(',').forEach(function (opt) { 37927 var _opt$split = opt.split(':'), 37928 name = _opt$split[0], 37929 val = _opt$split[1]; 37930 37931 if (name === 'lang') { 37932 flags.language = val; // er for easyReadery 37933 } else if (name === 'er') { 37934 flags.easyReader = Number(val); // war for wide aspect ratio 37935 } else if (name === 'war') { 37936 flags.aspectRatio = Number(val); 37937 } else if (name === '3D') { 37938 flags['3D'] = Number(val); 37939 } 37940 }); 37941 } else { 37942 flags.language = value; 37943 } 37944 37945 if (flags.channel) { 37946 flags.channel = 'SERVICE' + flags.channel; 37947 } 37948 37949 return flags; 37950 }); 37951 } 37952 }; 37953 /** 37954 * Maps an AdaptationSet node to a list of Representation information objects 37955 * 37956 * @name toRepresentationsCallback 37957 * @function 37958 * @param {Node} adaptationSet 37959 * AdaptationSet node from the mpd 37960 * @return {RepresentationInformation[]} 37961 * List of objects containing Representaion information 37962 */ 37963 37964 /** 37965 * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of 37966 * Representation information objects 37967 * 37968 * @param {Object} periodAttributes 37969 * Contains attributes inherited by the Period 37970 * @param {string[]} periodBaseUrls 37971 * Contains list of resolved base urls inherited by the Period 37972 * @param {string[]} periodSegmentInfo
37973 * Contains Segment Information at the period level 37974 * @return {toRepresentationsCallback} 37975 * Callback map function 37976 */ 37977 37978 37979 var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) { 37980 return function (adaptationSet) { 37981 var adaptationSetAttributes = parseAttributes(adaptationSet); 37982 var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL')); 37983 var role = findChildren(adaptationSet, 'Role')[0]; 37984 var roleAttributes = { 37985 role: parseAttributes(role) 37986 }; 37987 var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes); 37988 var accessibility = findChildren(adaptationSet, 'Accessibility')[0]; 37989 var captionServices = parseCaptionServiceMetadata(parseAttributes(accessibility)); 37990 37991 if (captionServices) { 37992 attrs = merge(attrs, { 37993 captionServices: captionServices 37994 }); 37995 } 37996 37997 var label = findChildren(adaptationSet, 'Label')[0]; 37998 37999 if (label && label.childNodes.length) { 38000 var labelVal = label.childNodes[0].nodeValue.trim(); 38001 attrs = merge(attrs, { 38002 label: labelVal 38003 }); 38004 } 38005 38006 var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection')); 38007 38008 if (Object.keys(contentProtection).length) { 38009 attrs = merge(attrs, { 38010 contentProtection: contentProtection 38011 }); 38012 } 38013 38014 var segmentInfo = getSegmentInformation(adaptationSet); 38015 var representations = findChildren(adaptationSet, 'Representation'); 38016 var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo); 38017 return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo))); 38018 }; 38019 }; 38020 /** 38021 * Contains all period information for mapping nodes onto adaptation sets. 38022 * 38023 * @typedef {Object} PeriodInformation 38024 * @property {Node} period.node 38025 * Period node from the mpd 38026 * @property {Object} period.attributes 38027 * Parsed period attributes from node plus any added 38028 */ 38029 38030 /** 38031 * Maps a PeriodInformation object to a list of Representation information objects for all 38032 * AdaptationSet nodes contained within the Period. 38033 * 38034 * @name toAdaptationSetsCallback 38035 * @function 38036 * @param {PeriodInformation} period 38037 * Period object containing necessary period information 38038 * @param {number} periodIndex 38039 * Index of the Period within the mpd 38040 * @return {RepresentationInformation[]} 38041 * List of objects containing Representaion information 38042 */ 38043 38044 /** 38045 * Returns a callback for Array.prototype.map for mapping Period nodes to a list of 38046 * Representation information objects 38047 * 38048 * @param {Object} mpdAttributes 38049 * Contains attributes inherited by the mpd 38050 * @param {string[]} mpdBaseUrls 38051 * Contains list of resolved base urls inherited by the mpd 38052 * @return {toAdaptationSetsCallback} 38053 * Callback map function 38054 */ 38055 38056 38057 var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) { 38058 return function (period, index) { 38059 var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL')); 38060 var parsedPeriodId = parseInt(period.attributes.id, 10); // fallback to mapping index if Period@id is not a number 38061 38062 var periodIndex = window_1.isNaN(parsedPeriodId) ? index : parsedPeriodId; 38063 var periodAttributes = merge(mpdAttributes, { 38064 periodIndex: periodIndex, 38065 periodStart: period.attributes.start 38066 }); 38067 38068 if (typeof period.attributes.duration === 'number') { 38069 periodAttributes.periodDuration = period.attributes.duration; 38070 } 38071 38072 var adaptationSets = findChildren(period.node, 'AdaptationSet'); 38073 var periodSegmentInfo = getSegmentInformation(period.node); 38074 return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo))); 38075 }; 38076 }; 38077 /**
38078 * Gets Period@start property for a given period. 38079 * 38080 * @param {Object} options 38081 * Options object 38082 * @param {Object} options.attributes 38083 * Period attributes 38084 * @param {Object} [options.priorPeriodAttributes] 38085 * Prior period attributes (if prior period is available) 38086 * @param {string} options.mpdType 38087 * The MPD@type these periods came from 38088 * @return {number|null} 38089 * The period start, or null if it's an early available period or error 38090 */ 38091 38092 38093 var getPeriodStart = function getPeriodStart(_ref) { 38094 var attributes = _ref.attributes, 38095 priorPeriodAttributes = _ref.priorPeriodAttributes, 38096 mpdType = _ref.mpdType; // Summary of period start time calculation from DASH spec section 5.3.2.1 38097 // 38098 // A period's start is the first period's start + time elapsed after playing all 38099 // prior periods to this one. Periods continue one after the other in time (without 38100 // gaps) until the end of the presentation. 38101 // 38102 // The value of Period@start should be: 38103 // 1. if Period@start is present: value of Period@start 38104 // 2. if previous period exists and it has @duration: previous Period@start + 38105 // previous Period@duration 38106 // 3. if this is first period and MPD@type is 'static': 0 38107 // 4. in all other cases, consider the period an "early available period" (note: not 38108 // currently supported) 38109 // (1) 38110 38111 if (typeof attributes.start === 'number') { 38112 return attributes.start; 38113 } // (2) 38114 38115 38116 if (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') { 38117 return priorPeriodAttributes.start + priorPeriodAttributes.duration; 38118 } // (3) 38119 38120 38121 if (!priorPeriodAttributes && mpdType === 'static') { 38122 return 0; 38123 } // (4) 38124 // There is currently no logic for calculating the Period@start value if there is
38125 // no Period@start or prior Period@start and Period@duration available. This is not made 38126 // explicit by the DASH interop guidelines or the DASH spec, however, since there's 38127 // nothing about any other resolution strategies, it's implied. Thus, this case should 38128 // be considered an early available period, or error, and null should suffice for both 38129 // of those cases. 38130 38131 38132 return null; 38133 }; 38134 /** 38135 * Traverses the mpd xml tree to generate a list of Representation information objects 38136 * that have inherited attributes from parent nodes 38137 * 38138 * @param {Node} mpd 38139 * The root node of the mpd 38140 * @param {Object} options 38141 * Available options for inheritAttributes 38142 * @param {string} options.manifestUri 38143 * The uri source of the mpd 38144 * @param {number} options.NOW 38145 * Current time per DASH IOP. Default is current time in ms since epoch 38146 * @param {number} options.clientOffset 38147 * Client time difference from NOW (in milliseconds) 38148 * @return {RepresentationInformation[]} 38149 * List of objects containing Representation information 38150 */ 38151 38152 38153 var inheritAttributes = function inheritAttributes(mpd, options) { 38154 if (options === void 0) { 38155 options = {}; 38156 } 38157 38158 var _options = options, 38159 _options$manifestUri = _options.manifestUri, 38160 manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri, 38161 _options$NOW = _options.NOW, 38162 NOW = _options$NOW === void 0 ? Date.now() : _options$NOW, 38163 _options$clientOffset = _options.clientOffset, 38164 clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset; 38165 var periodNodes = findChildren(mpd, 'Period'); 38166 38167 if (!periodNodes.length) { 38168 throw new Error(errors.INVALID_NUMBER_OF_PERIOD); 38169 } 38170 38171 var locations = findChildren(mpd, 'Location'); 38172 var mpdAttributes = parseAttributes(mpd); 38173 var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'. 38174 38175 mpdAttributes.type = mpdAttributes.type || 'static'; 38176 mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0; 38177 mpdAttributes.NOW = NOW; 38178 mpdAttributes.clientOffset = clientOffset; 38179 38180 if (locations.length) { 38181 mpdAttributes.locations = locations.map(getContent); 38182 } 38183 38184 var periods = []; // Since toAdaptationSets acts on individual periods right now, the simplest approach to 38185 // adding properties that require looking at prior periods is to parse attributes and add 38186 // missing ones before toAdaptationSets is called. If more such properties are added, it 38187 // may be better to refactor toAdaptationSets. 38188 38189 periodNodes.forEach(function (node, index) { 38190 var attributes = parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary 38191 // for this period. 38192 38193 var priorPeriod = periods[index - 1]; 38194 attributes.start = getPeriodStart({ 38195 attributes: attributes, 38196 priorPeriodAttributes: priorPeriod ? priorPeriod.attributes : null, 38197 mpdType: mpdAttributes.type 38198 }); 38199 periods.push({ 38200 node: node, 38201 attributes: attributes 38202 }); 38203 }); 38204 return { 38205 locations: mpdAttributes.locations, 38206 representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))) 38207 }; 38208 }; 38209 38210 var stringToMpdXml = function stringToMpdXml(manifestString) { 38211 if (manifestString === '') { 38212 throw new Error(errors.DASH_EMPTY_MANIFEST); 38213 } 38214
38215 var parser = new domParser_3(); 38216 var xml; 38217 var mpd; 38218 38219 try { 38220 xml = parser.parseFromString(manifestString, 'application/xml'); 38221 mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null; 38222 } catch (e) {// ie 11 throwsw on invalid xml 38223 } 38224 38225 if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) { 38226 throw new Error(errors.DASH_INVALID_XML); 38227 } 38228 38229 return mpd; 38230 }; 38231 /** 38232 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 38233 * 38234 * @param {string} mpd 38235 * XML string of the MPD manifest 38236 * @return {Object|null} 38237 * Attributes of UTCTiming node specified in the manifest. Null if none found 38238 */ 38239 38240 38241 var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) { 38242 var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0]; 38243 38244 if (!UTCTimingNode) { 38245 return null; 38246 } 38247 38248 var attributes = parseAttributes(UTCTimingNode); 38249 38250 switch (attributes.schemeIdUri) { 38251 case 'urn:mpeg:dash:utc:http-head:2014': 38252 case 'urn:mpeg:dash:utc:http-head:2012': 38253 attributes.method = 'HEAD'; 38254 break; 38255 38256 case 'urn:mpeg:dash:utc:http-xsdate:2014': 38257 case 'urn:mpeg:dash:utc:http-iso:2014': 38258 case 'urn:mpeg:dash:utc:http-xsdate:2012': 38259 case 'urn:mpeg:dash:utc:http-iso:2012': 38260 attributes.method = 'GET'; 38261 break; 38262 38263 case 'urn:mpeg:dash:utc:direct:2014': 38264 case 'urn:mpeg:dash:utc:direct:2012': 38265 attributes.method = 'DIRECT'; 38266 attributes.value = Date.parse(attributes.value); 38267 break; 38268 38269 case 'urn:mpeg:dash:utc:http-ntp:2014': 38270 case 'urn:mpeg:dash:utc:ntp:2014': 38271 case 'urn:mpeg:dash:utc:sntp:2014': 38272 default: 38273 throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME); 38274 } 38275 38276 return attributes; 38277 }; 38278 38279 var parse = function parse(manifestString, options) { 38280 if (options === void 0) { 38281 options = {}; 38282 } 38283 38284 var parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options); 38285 var playlists = toPlaylists(parsedManifestInfo.representationInfo); 38286 return toM3u8(playlists, parsedManifestInfo.locations, options.sidxMapping); 38287 }; 38288 /** 38289 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 38290 * 38291 * @param {string} manifestString 38292 * XML string of the MPD manifest 38293 * @return {Object|null} 38294 * Attributes of UTCTiming node specified in the manifest. Null if none found 38295 */ 38296 38297 38298 var parseUTCTiming = function parseUTCTiming(manifestString) { 38299 return parseUTCTimingScheme(stringToMpdXml(manifestString)); 38300 }; 38301 38302 var MAX_UINT32 = Math.pow(2, 32); 38303 38304 var parseSidx = function parseSidx(data) { 38305 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 38306 result = { 38307 version: data[0], 38308 flags: new Uint8Array(data.subarray(1, 4)), 38309 references: [], 38310 referenceId: view.getUint32(4), 38311 timescale: view.getUint32(8) 38312 }, 38313 i = 12; 38314 38315 if (result.version === 0) { 38316 result.earliestPresentationTime = view.getUint32(i); 38317 result.firstOffset = view.getUint32(i + 4); 38318 i += 8; 38319 } else { 38320 // read 64 bits 38321 result.earliestPresentationTime = view.getUint32(i) * MAX_UINT32 + view.getUint32(i + 4); 38322 result.firstOffset = view.getUint32(i + 8) * MAX_UINT32 + view.getUint32(i + 12); 38323 i += 16; 38324 } 38325 38326 i += 2; // reserved 38327 38328 var referenceCount = view.getUint16(i); 38329 i += 2; // start of references 38330 38331 for (; referenceCount > 0; i += 12, referenceCount--) { 38332 result.references.push({ 38333 referenceType: (data[i] & 0x80) >>> 7, 38334 referencedSize: view.getUint32(i) & 0x7FFFFFFF, 38335 subsegmentDuration: view.getUint32(i + 4), 38336 startsWithSap: !!(data[i + 8] & 0x80), 38337 sapType: (data[i + 8] & 0x70) >>> 4, 38338 sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF 38339 }); 38340 } 38341 38342 return result; 38343 }; 38344 38345 var parseSidx_1 = parseSidx; 38346 38347 // we used to do this with log2 but BigInt does not support builtin math
vendor: 22,355 bytes, lines 38348-39064
38348 // Math.ceil(log2(x)); 38349 38350 38351 var countBits = function countBits(x) { 38352 return x.toString(2).length; 38353 }; // count the number of whole bytes it would take to represent a number 38354 38355 var countBytes = function countBytes(x) { 38356 return Math.ceil(countBits(x) / 8); 38357 }; 38358 var isTypedArray = function isTypedArray(obj) { 38359 return ArrayBuffer.isView(obj); 38360 }; 38361 var toUint8 = function toUint8(bytes) { 38362 if (bytes instanceof Uint8Array) { 38363 return bytes; 38364 } 38365 38366 if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) { 38367 // any non-number or NaN leads to empty uint8array 38368 // eslint-disable-next-line 38369 if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) { 38370 bytes = 0; 38371 } else { 38372 bytes = [bytes]; 38373 } 38374 } 38375 38376 return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0); 38377 }; 38378 var BigInt = window_1.BigInt || Number; 38379 var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')]; 38380 var bytesToNumber = function bytesToNumber(bytes, _temp) { 38381 var _ref = _temp === void 0 ? {} : _temp, 38382 _ref$signed = _ref.signed, 38383 signed = _ref$signed === void 0 ? false : _ref$signed, 38384 _ref$le = _ref.le, 38385 le = _ref$le === void 0 ? false : _ref$le; 38386 38387 bytes = toUint8(bytes); 38388 var fn = le ? 'reduce' : 'reduceRight'; 38389 var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn]; 38390 var number = obj.call(bytes, function (total, _byte, i) { 38391 var exponent = le ? i : Math.abs(i + 1 - bytes.length); 38392 return total + BigInt(_byte) * BYTE_TABLE[exponent]; 38393 }, BigInt(0)); 38394 38395 if (signed) { 38396 var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1); 38397 number = BigInt(number); 38398 38399 if (number > max) { 38400 number -= max; 38401 number -= max; 38402 number -= BigInt(2); 38403 } 38404 } 38405 38406 return Number(number); 38407 }; 38408 var numberToBytes = function numberToBytes(number, _temp2) { 38409 var _ref2 = _temp2 === void 0 ? {} : _temp2, 38410 _ref2$le = _ref2.le, 38411 le = _ref2$le === void 0 ? false : _ref2$le; // eslint-disable-next-line 38412 38413 38414 if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) { 38415 number = 0; 38416 } 38417 38418 number = BigInt(number); 38419 var byteCount = countBytes(number); 38420 var bytes = new Uint8Array(new ArrayBuffer(byteCount)); 38421 38422 for (var i = 0; i < byteCount; i++) { 38423 var byteIndex = le ? i : Math.abs(i + 1 - bytes.length); 38424 bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF)); 38425 38426 if (number < 0) { 38427 bytes[byteIndex] = Math.abs(~bytes[byteIndex]); 38428 bytes[byteIndex] -= i === 0 ? 1 : 2; 38429 } 38430 } 38431 38432 return bytes; 38433 }; 38434 var stringToBytes = function stringToBytes(string, stringIsBytes) { 38435 if (typeof string !== 'string' && string && typeof string.toString === 'function') { 38436 string = string.toString(); 38437 } 38438 38439 if (typeof string !== 'string') { 38440 return new Uint8Array(); 38441 } // If the string already is bytes, we don't have to do this 38442 // otherwise we do this so that we split multi length characters 38443 // into individual bytes 38444 38445 38446 if (!stringIsBytes) { 38447 string = unescape(encodeURIComponent(string)); 38448 } 38449 38450 var view = new Uint8Array(string.length); 38451 38452 for (var i = 0; i < string.length; i++) { 38453 view[i] = string.charCodeAt(i); 38454 } 38455 38456 return view; 38457 }; 38458 var concatTypedArrays = function concatTypedArrays() { 38459 for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) { 38460 buffers[_key] = arguments[_key]; 38461 } 38462 38463 buffers = buffers.filter(function (b) { 38464 return b && (b.byteLength || b.length) && typeof b !== 'string'; 38465 }); 38466 38467 if (buffers.length <= 1) { 38468 // for 0 length we will return empty uint8 38469 // for 1 length we return the first uint8 38470 return toUint8(buffers[0]); 38471 } 38472 38473 var totalLen = buffers.reduce(function (total, buf, i) { 38474 return total + (buf.byteLength || buf.length); 38475 }, 0); 38476 var tempBuffer = new Uint8Array(totalLen); 38477 var offset = 0; 38478 buffers.forEach(function (buf) { 38479 buf = toUint8(buf); 38480 tempBuffer.set(buf, offset); 38481 offset += buf.byteLength; 38482 }); 38483 return tempBuffer; 38484 }; 38485 /** 38486 * Check if the bytes "b" are contained within bytes "a". 38487 * 38488 * @param {Uint8Array|Array} a 38489 * Bytes to check in 38490 * 38491 * @param {Uint8Array|Array} b 38492 * Bytes to check for 38493 * 38494 * @param {Object} options 38495 * options 38496 * 38497 * @param {Array|Uint8Array} [offset=0] 38498 * offset to use when looking at bytes in a 38499 * 38500 * @param {Array|Uint8Array} [mask=[]] 38501 * mask to use on bytes before comparison. 38502 * 38503 * @return {boolean} 38504 * If all bytes in b are inside of a, taking into account 38505 * bit masks. 38506 */ 38507 38508 var bytesMatch = function bytesMatch(a, b, _temp3) { 38509 var _ref3 = _temp3 === void 0 ? {} : _temp3, 38510 _ref3$offset = _ref3.offset, 38511 offset = _ref3$offset === void 0 ? 0 : _ref3$offset, 38512 _ref3$mask = _ref3.mask, 38513 mask = _ref3$mask === void 0 ? [] : _ref3$mask; 38514 38515 a = toUint8(a); 38516 b = toUint8(b); // ie 11 does not support uint8 every 38517 38518 var fn = b.every ? b.every : Array.prototype.every; 38519 return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8 38520 fn.call(b, function (bByte, i) { 38521 var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i]; 38522 return bByte === aByte; 38523 }); 38524 }; 38525 38526 var ID3 = toUint8([0x49, 0x44, 0x33]); 38527 var getId3Size = function getId3Size(bytes, offset) { 38528 if (offset === void 0) { 38529 offset = 0; 38530 } 38531 38532 bytes = toUint8(bytes); 38533 var flags = bytes[offset + 5]; 38534 var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9]; 38535 var footerPresent = (flags & 16) >> 4; 38536 38537 if (footerPresent) { 38538 return returnSize + 20; 38539 } 38540 38541 return returnSize + 10; 38542 }; 38543 var getId3Offset = function getId3Offset(bytes, offset) { 38544 if (offset === void 0) { 38545 offset = 0; 38546 } 38547 38548 bytes = toUint8(bytes); 38549 38550 if (bytes.length - offset < 10 || !bytesMatch(bytes, ID3, { 38551 offset: offset 38552 })) { 38553 return offset; 38554 } 38555 38556 offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files 38557 // have multiple ID3 tag sections even though 38558 // they should not. 38559 38560 return getId3Offset(bytes, offset); 38561 }; 38562 38563 var normalizePath$1 = function normalizePath(path) { 38564 if (typeof path === 'string') { 38565 return stringToBytes(path); 38566 } 38567 38568 if (typeof path === 'number') { 38569 return path; 38570 } 38571 38572 return path; 38573 }; 38574 38575 var normalizePaths$1 = function normalizePaths(paths) { 38576 if (!Array.isArray(paths)) { 38577 return [normalizePath$1(paths)]; 38578 } 38579 38580 return paths.map(function (p) { 38581 return normalizePath$1(p); 38582 }); 38583 }; 38584 /** 38585 * find any number of boxes by name given a path to it in an iso bmff 38586 * such as mp4. 38587 * 38588 * @param {TypedArray} bytes 38589 * bytes for the iso bmff to search for boxes in 38590 * 38591 * @param {Uint8Array[]|string[]|string|Uint8Array} name 38592 * An array of paths or a single path representing the name 38593 * of boxes to search through in bytes. Paths may be 38594 * uint8 (character codes) or strings. 38595 * 38596 * @param {boolean} [complete=false] 38597 * Should we search only for complete boxes on the final path. 38598 * This is very useful when you do not want to get back partial boxes 38599 * in the case of streaming files. 38600 * 38601 * @return {Uint8Array[]} 38602 * An array of the end paths that we found. 38603 */ 38604 38605 var findBox = function findBox(bytes, paths, complete) { 38606 if (complete === void 0) { 38607 complete = false; 38608 } 38609 38610 paths = normalizePaths$1(paths); 38611 bytes = toUint8(bytes); 38612 var results = []; 38613 38614 if (!paths.length) { 38615 // short-circuit the search for empty paths 38616 return results; 38617 } 38618 38619 var i = 0; 38620 38621 while (i < bytes.length) { 38622 var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0; 38623 var type = bytes.subarray(i + 4, i + 8); // invalid box format. 38624 38625 if (size === 0) { 38626 break; 38627 } 38628 38629 var end = i + size; 38630 38631 if (end > bytes.length) { 38632 // this box is bigger than the number of bytes we have 38633 // and complete is set, we cannot find any more boxes. 38634 if (complete) { 38635 break; 38636 } 38637 38638 end = bytes.length; 38639 } 38640 38641 var data = bytes.subarray(i + 8, end); 38642 38643 if (bytesMatch(type, paths[0])) { 38644 if (paths.length === 1) { 38645 // this is the end of the path and we've found the box we were 38646 // looking for 38647 results.push(data); 38648 } else { 38649 // recursively search for the next box along the path 38650 results.push.apply(results, findBox(data, paths.slice(1), complete)); 38651 } 38652 } 38653 38654 i = end; 38655 } // we've finished searching all of bytes 38656 38657 38658 return results; 38659 }; 38660 38661 // https://matroska-org.github.io/libebml/specs.html 38662 // https://www.matroska.org/technical/elements.html 38663 // https://www.webmproject.org/docs/container/ 38664 38665 var EBML_TAGS = { 38666 EBML: toUint8([0x1A, 0x45, 0xDF, 0xA3]), 38667 DocType: toUint8([0x42, 0x82]), 38668 Segment: toUint8([0x18, 0x53, 0x80, 0x67]), 38669 SegmentInfo: toUint8([0x15, 0x49, 0xA9, 0x66]), 38670 Tracks: toUint8([0x16, 0x54, 0xAE, 0x6B]), 38671 Track: toUint8([0xAE]), 38672 TrackNumber: toUint8([0xd7]), 38673 DefaultDuration: toUint8([0x23, 0xe3, 0x83]), 38674 TrackEntry: toUint8([0xAE]), 38675 TrackType: toUint8([0x83]), 38676 FlagDefault: toUint8([0x88]), 38677 CodecID: toUint8([0x86]), 38678 CodecPrivate: toUint8([0x63, 0xA2]), 38679 VideoTrack: toUint8([0xe0]), 38680 AudioTrack: toUint8([0xe1]), 38681 // Not used yet, but will be used for live webm/mkv 38682 // see https://www.matroska.org/technical/basics.html#block-structure 38683 // see https://www.matroska.org/technical/basics.html#simpleblock-structure 38684 Cluster: toUint8([0x1F, 0x43, 0xB6, 0x75]), 38685 Timestamp: toUint8([0xE7]), 38686 TimestampScale: toUint8([0x2A, 0xD7, 0xB1]), 38687 BlockGroup: toUint8([0xA0]), 38688 BlockDuration: toUint8([0x9B]), 38689 Block: toUint8([0xA1]), 38690 SimpleBlock: toUint8([0xA3]) 38691 }; 38692 /** 38693 * This is a simple table to determine the length 38694 * of things in ebml. The length is one based (starts at 1, 38695 * rather than zero) and for every zero bit before a one bit 38696 * we add one to length. We also need this table because in some 38697 * case we have to xor all the length bits from another value. 38698 */ 38699 38700 var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1]; 38701 38702 var getLength = function getLength(_byte) { 38703 var len = 1; 38704 38705 for (var i = 0; i < LENGTH_TABLE.length; i++) { 38706 if (_byte & LENGTH_TABLE[i]) { 38707 break; 38708 } 38709 38710 len++; 38711 } 38712 38713 return len; 38714 }; // length in ebml is stored in the first 4 to 8 bits 38715 // of the first byte. 4 for the id length and 8 for the 38716 // data size length. Length is measured by converting the number to binary 38717 // then 1 + the number of zeros before a 1 is encountered starting 38718 // from the left. 38719 38720 38721 var getvint = function getvint(bytes, offset, removeLength, signed) { 38722 if (removeLength === void 0) { 38723 removeLength = true; 38724 } 38725 38726 if (signed === void 0) { 38727 signed = false; 38728 } 38729 38730 var length = getLength(bytes[offset]); 38731 var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes 38732 // as they will be modified below to remove the dataSizeLen bits and we do not 38733 // want to modify the original data. normally we could just call slice on 38734 // uint8array but ie 11 does not support that... 38735 38736 if (removeLength) { 38737 valueBytes = Array.prototype.slice.call(bytes, offset, offset + length); 38738 valueBytes[0] ^= LENGTH_TABLE[length - 1]; 38739 } 38740 38741 return { 38742 length: length, 38743 value: bytesToNumber(valueBytes, { 38744 signed: signed 38745 }), 38746 bytes: valueBytes 38747 }; 38748 }; 38749 38750 var normalizePath = function normalizePath(path) { 38751 if (typeof path === 'string') { 38752 return path.match(/.{1,2}/g).map(function (p) { 38753 return normalizePath(p); 38754 }); 38755 } 38756 38757 if (typeof path === 'number') { 38758 return numberToBytes(path); 38759 } 38760 38761 return path; 38762 }; 38763 38764 var normalizePaths = function normalizePaths(paths) { 38765 if (!Array.isArray(paths)) { 38766 return [normalizePath(paths)]; 38767 } 38768 38769 return paths.map(function (p) { 38770 return normalizePath(p); 38771 }); 38772 }; 38773 38774 var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) { 38775 if (offset >= bytes.length) { 38776 return bytes.length; 38777 } 38778 38779 var innerid = getvint(bytes, offset, false); 38780 38781 if (bytesMatch(id.bytes, innerid.bytes)) { 38782 return offset; 38783 } 38784 38785 var dataHeader = getvint(bytes, offset + innerid.length); 38786 return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length); 38787 }; 38788 /** 38789 * Notes on the EBLM format. 38790 * 38791 * EBLM uses "vints" tags. Every vint tag contains 38792 * two parts 38793 * 38794 * 1. The length from the first byte. You get this by 38795 * converting the byte to binary and counting the zeros 38796 * before a 1. Then you add 1 to that. Examples 38797 * 00011111 = length 4 because there are 3 zeros before a 1. 38798 * 00100000 = length 3 because there are 2 zeros before a 1. 38799 * 00000011 = length 7 because there are 6 zeros before a 1. 38800 * 38801 * 2. The bits used for length are removed from the first byte 38802 * Then all the bytes are merged into a value. NOTE: this 38803 * is not the case for id ebml tags as there id includes 38804 * length bits. 38805 * 38806 */ 38807 38808 38809 var findEbml = function findEbml(bytes, paths) { 38810 paths = normalizePaths(paths); 38811 bytes = toUint8(bytes); 38812 var results = []; 38813 38814 if (!paths.length) { 38815 return results; 38816 } 38817 38818 var i = 0; 38819 38820 while (i < bytes.length) { 38821 var id = getvint(bytes, i, false); 38822 var dataHeader = getvint(bytes, i + id.length); 38823 var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream 38824 38825 if (dataHeader.value === 0x7f) { 38826 dataHeader.value = getInfinityDataSize(id, bytes, dataStart); 38827 38828 if (dataHeader.value !== bytes.length) { 38829 dataHeader.value -= dataStart; 38830 } 38831 } 38832 38833 var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value; 38834 var data = bytes.subarray(dataStart, dataEnd); 38835 38836 if (bytesMatch(paths[0], id.bytes)) { 38837 if (paths.length === 1) { 38838 // this is the end of the paths and we've found the tag we were 38839 // looking for 38840 results.push(data); 38841 } else { 38842 // recursively search for the next tag inside of the data 38843 // of this one 38844 results = results.concat(findEbml(data, paths.slice(1))); 38845 } 38846 } 38847 38848 var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it 38849 38850 i += totalLength; 38851 } 38852 38853 return results; 38854 }; // see https://www.matroska.org/technical/basics.html#block-structure 38855 38856 var NAL_TYPE_ONE = toUint8([0x00, 0x00, 0x00, 0x01]); 38857 var NAL_TYPE_TWO = toUint8([0x00, 0x00, 0x01]); 38858 var EMULATION_PREVENTION = toUint8([0x00, 0x00, 0x03]); 38859 /** 38860 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 38861 * Sequence Payload" 38862 * 38863 * @param data {Uint8Array} the bytes of a RBSP from a NAL 38864 * unit 38865 * @return {Uint8Array} the RBSP without any Emulation 38866 * Prevention Bytes 38867 */ 38868 38869 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(bytes) { 38870 var positions = []; 38871 var i = 1; // Find all `Emulation Prevention Bytes` 38872 38873 while (i < bytes.length - 2) { 38874 if (bytesMatch(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) { 38875 positions.push(i + 2); 38876 i++; 38877 } 38878 38879 i++; 38880 } // If no Emulation Prevention Bytes were found just return the original 38881 // array 38882 38883 38884 if (positions.length === 0) { 38885 return bytes; 38886 } // Create a new array to hold the NAL unit data 38887 38888 38889 var newLength = bytes.length - positions.length; 38890 var newData = new Uint8Array(newLength); 38891 var sourceIndex = 0; 38892 38893 for (i = 0; i < newLength; sourceIndex++, i++) { 38894 if (sourceIndex === positions[0]) { 38895 // Skip this byte 38896 sourceIndex++; // Remove this position index 38897 38898 positions.shift(); 38899 } 38900 38901 newData[i] = bytes[sourceIndex]; 38902 } 38903 38904 return newData; 38905 }; 38906 var findNal = function findNal(bytes, dataType, types, nalLimit) { 38907 if (nalLimit === void 0) { 38908 nalLimit = Infinity; 38909 } 38910 38911 bytes = toUint8(bytes); 38912 types = [].concat(types); 38913 var i = 0; 38914 var nalStart; 38915 var nalsFound = 0; // keep searching until: 38916 // we reach the end of bytes 38917 // we reach the maximum number of nals they want to seach 38918 // NOTE: that we disregard nalLimit when we have found the start 38919 // of the nal we want so that we can find the end of the nal we want. 38920 38921 while (i < bytes.length && (nalsFound < nalLimit || nalStart)) { 38922 var nalOffset = void 0; 38923 38924 if (bytesMatch(bytes.subarray(i), NAL_TYPE_ONE)) { 38925 nalOffset = 4; 38926 } else if (bytesMatch(bytes.subarray(i), NAL_TYPE_TWO)) { 38927 nalOffset = 3; 38928 } // we are unsynced, 38929 // find the next nal unit 38930 38931 38932 if (!nalOffset) { 38933 i++; 38934 continue; 38935 } 38936 38937 nalsFound++; 38938 38939 if (nalStart) { 38940 return discardEmulationPreventionBytes(bytes.subarray(nalStart, i)); 38941 } 38942 38943 var nalType = void 0; 38944 38945 if (dataType === 'h264') { 38946 nalType = bytes[i + nalOffset] & 0x1f; 38947 } else if (dataType === 'h265') { 38948 nalType = bytes[i + nalOffset] >> 1 & 0x3f; 38949 } 38950 38951 if (types.indexOf(nalType) !== -1) { 38952 nalStart = i + nalOffset; 38953 } // nal header is 1 length for h264, and 2 for h265 38954 38955 38956 i += nalOffset + (dataType === 'h264' ? 1 : 2); 38957 } 38958 38959 return bytes.subarray(0, 0); 38960 }; 38961 var findH264Nal = function findH264Nal(bytes, type, nalLimit) { 38962 return findNal(bytes, 'h264', type, nalLimit); 38963 }; 38964 var findH265Nal = function findH265Nal(bytes, type, nalLimit) { 38965 return findNal(bytes, 'h265', type, nalLimit); 38966 }; 38967 38968 var CONSTANTS = { 38969 // "webm" string literal in hex 38970 'webm': toUint8([0x77, 0x65, 0x62, 0x6d]), 38971 // "matroska" string literal in hex 38972 'matroska': toUint8([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]), 38973 // "fLaC" string literal in hex 38974 'flac': toUint8([0x66, 0x4c, 0x61, 0x43]), 38975 // "OggS" string literal in hex 38976 'ogg': toUint8([0x4f, 0x67, 0x67, 0x53]), 38977 // ac-3 sync byte, also works for ec-3 as that is simply a codec 38978 // of ac-3 38979 'ac3': toUint8([0x0b, 0x77]), 38980 // "RIFF" string literal in hex used for wav and avi 38981 'riff': toUint8([0x52, 0x49, 0x46, 0x46]), 38982 // "AVI" string literal in hex 38983 'avi': toUint8([0x41, 0x56, 0x49]), 38984 // "WAVE" string literal in hex 38985 'wav': toUint8([0x57, 0x41, 0x56, 0x45]), 38986 // "ftyp3g" string literal in hex 38987 '3gp': toUint8([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]), 38988 // "ftyp" string literal in hex 38989 'mp4': toUint8([0x66, 0x74, 0x79, 0x70]), 38990 // "styp" string literal in hex 38991 'fmp4': toUint8([0x73, 0x74, 0x79, 0x70]), 38992 // "ftypqt" string literal in hex 38993 'mov': toUint8([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]), 38994 // moov string literal in hex 38995 'moov': toUint8([0x6D, 0x6F, 0x6F, 0x76]), 38996 // moof string literal in hex 38997 'moof': toUint8([0x6D, 0x6F, 0x6F, 0x66]) 38998 }; 38999 var _isLikely = { 39000 aac: function aac(bytes) { 39001 var offset = getId3Offset(bytes); 39002 return bytesMatch(bytes, [0xFF, 0x10], { 39003 offset: offset, 39004 mask: [0xFF, 0x16] 39005 }); 39006 }, 39007 mp3: function mp3(bytes) { 39008 var offset = getId3Offset(bytes); 39009 return bytesMatch(bytes, [0xFF, 0x02], { 39010 offset: offset, 39011 mask: [0xFF, 0x06] 39012 }); 39013 }, 39014 webm: function webm(bytes) { 39015 var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm 39016 39017 return bytesMatch(docType, CONSTANTS.webm); 39018 }, 39019 mkv: function mkv(bytes) { 39020 var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska 39021 39022 return bytesMatch(docType, CONSTANTS.matroska); 39023 }, 39024 mp4: function mp4(bytes) { 39025 // if this file is another base media file format, it is not mp4 39026 if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) { 39027 return false; 39028 } // if this file starts with a ftyp or styp box its mp4 39029 39030 39031 if (bytesMatch(bytes, CONSTANTS.mp4, { 39032 offset: 4 39033 }) || bytesMatch(bytes, CONSTANTS.fmp4, { 39034 offset: 4 39035 })) { 39036 return true; 39037 } // if this file starts with a moof/moov box its mp4 39038 39039 39040 if (bytesMatch(bytes, CONSTANTS.moof, { 39041 offset: 4 39042 }) || bytesMatch(bytes, CONSTANTS.moov, { 39043 offset: 4 39044 })) { 39045 return true; 39046 } 39047 }, 39048 mov: function mov(bytes) { 39049 return bytesMatch(bytes, CONSTANTS.mov, { 39050 offset: 4 39051 }); 39052 }, 39053 '3gp': function gp(bytes) { 39054 return bytesMatch(bytes, CONSTANTS['3gp'], { 39055 offset: 4 39056 }); 39057 }, 39058 ac3: function ac3(bytes) { 39059 var offset = getId3Offset(bytes); 39060 return bytesMatch(bytes, CONSTANTS.ac3, { 39061 offset: offset 39062 }); 39063 }, 39064 ts: function ts(bytes) {
39065 if (bytes.length < 189 && bytes.length >= 1) { 39066 return bytes[0] === 0x47; 39067 } 39068 39069 var i = 0; // check the first 376 bytes for two matching sync bytes 39070 39071 while (i + 188 < bytes.length && i < 188) { 39072 if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) { 39073 return true; 39074 } 39075 39076 i += 1; 39077 } 39078 39079 return false; 39080 }, 39081 flac: function flac(bytes) { 39082 var offset = getId3Offset(bytes); 39083 return bytesMatch(bytes, CONSTANTS.flac, { 39084 offset: offset 39085 }); 39086 }, 39087 ogg: function ogg(bytes) { 39088 return bytesMatch(bytes, CONSTANTS.ogg); 39089 }, 39090 avi: function avi(bytes) { 39091 return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.avi, { 39092 offset: 8 39093 }); 39094 }, 39095 wav: function wav(bytes) { 39096 return bytesMatch(bytes, CONSTANTS.riff) && bytesMatch(bytes, CONSTANTS.wav, { 39097 offset: 8 39098 }); 39099 }, 39100 'h264': function h264(bytes) { 39101 // find seq_parameter_set_rbsp 39102 return findH264Nal(bytes, 7, 3).length; 39103 }, 39104 'h265': function h265(bytes) { 39105 // find video_parameter_set_rbsp or seq_parameter_set_rbsp 39106 return findH265Nal(bytes, [32, 33], 3).length; 39107 } 39108 }; // get all the isLikely functions 39109 // but make sure 'ts' is above h264 and h265 39110 // but below everything else as it is the least specific 39111 39112 var isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265 39113 .filter(function (t) { 39114 return t !== 'ts' && t !== 'h264' && t !== 'h265'; 39115 }) // add it back to the bottom 39116 .concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data. 39117 39118 isLikelyTypes.forEach(function (type) { 39119 var isLikelyFn = _isLikely[type]; 39120 39121 _isLikely[type] = function (bytes) { 39122 return isLikelyFn(toUint8(bytes)); 39123 }; 39124 }); // export after wrapping 39125 39126 var isLikely = _isLikely; // A useful list of file signatures can be found here 39127 // https://en.wikipedia.org/wiki/List_of_file_signatures 39128 39129 var detectContainerForBytes = function detectContainerForBytes(bytes) { 39130 bytes = toUint8(bytes); 39131 39132 for (var i = 0; i < isLikelyTypes.length; i++) { 39133 var type = isLikelyTypes[i]; 39134 39135 if (isLikely[type](bytes)) { 39136 return type; 39137 } 39138 } 39139 39140 return ''; 39141 }; // fmp4 is not a container 39142 39143 var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) { 39144 return findBox(bytes, ['moof']).length > 0; 39145 }; 39146 39147 /** 39148 * mux.js 39149 * 39150 * Copyright (c) Brightcove 39151 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 39152 */ 39153 var ONE_SECOND_IN_TS = 90000, 39154 // 90kHz clock 39155 secondsToVideoTs, 39156 secondsToAudioTs, 39157 videoTsToSeconds, 39158 audioTsToSeconds, 39159 audioTsToVideoTs, 39160 videoTsToAudioTs, 39161 metadataTsToSeconds; 39162 39163 secondsToVideoTs = function secondsToVideoTs(seconds) { 39164 return seconds * ONE_SECOND_IN_TS; 39165 }; 39166 39167 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 39168 return seconds * sampleRate; 39169 }; 39170 39171 videoTsToSeconds = function videoTsToSeconds(timestamp) { 39172 return timestamp / ONE_SECOND_IN_TS; 39173 }; 39174 39175 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 39176 return timestamp / sampleRate; 39177 }; 39178 39179 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 39180 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 39181 }; 39182 39183 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 39184 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 39185 }; 39186 /** 39187 * Adjust ID3 tag or caption timing information by the timeline pts values 39188 * (if keepOriginalTimestamps is false) and convert to seconds 39189 */ 39190 39191 39192 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) { 39193 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 39194 }; 39195 39196 var clock = { 39197 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 39198 secondsToVideoTs: secondsToVideoTs, 39199 secondsToAudioTs: secondsToAudioTs, 39200 videoTsToSeconds: videoTsToSeconds, 39201 audioTsToSeconds: audioTsToSeconds, 39202 audioTsToVideoTs: audioTsToVideoTs, 39203 videoTsToAudioTs: videoTsToAudioTs, 39204 metadataTsToSeconds: metadataTsToSeconds 39205 }; 39206 var clock_1 = clock.ONE_SECOND_IN_TS;
39207 39208 /*! @name @videojs/http-streaming @version 2.10.2 @license Apache-2.0 */ 39209 /** 39210 * @file resolve-url.js - Handling how URLs are resolved and manipulated 39211 */ 39212 39213 var resolveUrl = resolveUrl$1; 39214 /** 39215 * Checks whether xhr request was redirected and returns correct url depending 39216 * on `handleManifestRedirects` option 39217 * 39218 * @api private 39219 * 39220 * @param {string} url - an url being requested 39221 * @param {XMLHttpRequest} req - xhr request result 39222 * 39223 * @return {string} 39224 */ 39225 39226 var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) { 39227 // To understand how the responseURL below is set and generated: 39228 // - https://fetch.spec.whatwg.org/#concept-response-url 39229 // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling 39230 if (handleManifestRedirect && req && req.responseURL && url !== req.responseURL) { 39231 return req.responseURL; 39232 } 39233 39234 return url; 39235 }; 39236 39237 var logger = function logger(source) { 39238 if (videojs.log.debug) { 39239 return videojs.log.debug.bind(videojs, 'VHS:', source + " >"); 39240 } 39241 39242 return function () {}; 39243 }; 39244 /** 39245 * ranges 39246 * 39247 * Utilities for working with TimeRanges. 39248 * 39249 */ 39250 39251 39252 var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range 39253 // can be misleading because of precision differences or when the current media has poorly 39254 // aligned audio and video, which can cause values to be slightly off from what you would 39255 // expect. This value is what we consider to be safe to use in such comparisons to account 39256 // for these scenarios. 39257 39258 var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3; 39259 39260 var filterRanges = function filterRanges(timeRanges, predicate) { 39261 var results = []; 39262 var i; 39263 39264 if (timeRanges && timeRanges.length) { 39265 // Search for ranges that match the predicate 39266 for (i = 0; i < timeRanges.length; i++) { 39267 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 39268 results.push([timeRanges.start(i), timeRanges.end(i)]); 39269 } 39270 } 39271 } 39272 39273 return videojs.createTimeRanges(results); 39274 }; 39275 /** 39276 * Attempts to find the buffered TimeRange that contains the specified 39277 * time. 39278 * 39279 * @param {TimeRanges} buffered - the TimeRanges object to query 39280 * @param {number} time - the time to filter on. 39281 * @return {TimeRanges} a new TimeRanges object 39282 */ 39283 39284 39285 var findRange = function findRange(buffered, time) { 39286 return filterRanges(buffered, function (start, end) { 39287 return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time; 39288 }); 39289 }; 39290 /** 39291 * Returns the TimeRanges that begin later than the specified time. 39292 * 39293 * @param {TimeRanges} timeRanges - the TimeRanges object to query 39294 * @param {number} time - the time to filter on. 39295 * @return {TimeRanges} a new TimeRanges object. 39296 */ 39297 39298 39299 var findNextRange = function findNextRange(timeRanges, time) { 39300 return filterRanges(timeRanges, function (start) { 39301 return start - TIME_FUDGE_FACTOR >= time; 39302 }); 39303 }; 39304 /** 39305 * Returns gaps within a list of TimeRanges 39306 * 39307 * @param {TimeRanges} buffered - the TimeRanges object 39308 * @return {TimeRanges} a TimeRanges object of gaps 39309 */ 39310 39311 39312 var findGaps = function findGaps(buffered) { 39313 if (buffered.length < 2) { 39314 return videojs.createTimeRanges(); 39315 } 39316 39317 var ranges = []; 39318 39319 for (var i = 1; i < buffered.length; i++) { 39320 var start = buffered.end(i - 1); 39321 var end = buffered.start(i); 39322 ranges.push([start, end]); 39323 } 39324 39325 return videojs.createTimeRanges(ranges); 39326 }; 39327 /** 39328 * Calculate the intersection of two TimeRanges 39329 * 39330 * @param {TimeRanges} bufferA 39331 * @param {TimeRanges} bufferB 39332 * @return {TimeRanges} The interesection of `bufferA` with `bufferB` 39333 */ 39334 39335 39336 var bufferIntersection = function bufferIntersection(bufferA, bufferB) { 39337 var start = null; 39338 var end = null; 39339 var arity = 0; 39340 var extents = []; 39341 var ranges = []; 39342 39343 if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) { 39344 return videojs.createTimeRange(); 39345 } // Handle the case where we have both buffers and create an 39346 // intersection of the two 39347 39348 39349 var count = bufferA.length; // A) Gather up all start and end times 39350 39351 while (count--) { 39352 extents.push({ 39353 time: bufferA.start(count), 39354 type: 'start' 39355 }); 39356 extents.push({ 39357 time: bufferA.end(count), 39358 type: 'end' 39359 }); 39360 } 39361 39362 count = bufferB.length; 39363 39364 while (count--) { 39365 extents.push({ 39366 time: bufferB.start(count), 39367 type: 'start' 39368 }); 39369 extents.push({ 39370 time: bufferB.end(count), 39371 type: 'end' 39372 }); 39373 } // B) Sort them by time 39374 39375 39376 extents.sort(function (a, b) { 39377 return a.time - b.time; 39378 }); // C) Go along one by one incrementing arity for start and decrementing 39379 // arity for ends 39380 39381 for (count = 0; count < extents.length; count++) { 39382 if (extents[count].type === 'start') { 39383 arity++; // D) If arity is ever incremented to 2 we are entering an 39384 // overlapping range 39385 39386 if (arity === 2) { 39387 start = extents[count].time; 39388 } 39389 } else if (extents[count].type === 'end') { 39390 arity--;
39390 // E) If arity is ever decremented to 1 we leaving an 39391 // overlapping range 39392 39393 if (arity === 1) { 39394 end = extents[count].time; 39395 } 39396 } // F) Record overlapping ranges 39397 39398 39399 if (start !== null && end !== null) { 39400 ranges.push([start, end]); 39401 start = null; 39402 end = null; 39403 } 39404 } 39405 39406 return videojs.createTimeRanges(ranges); 39407 }; 39408 /** 39409 * Gets a human readable string for a TimeRange 39410 * 39411 * @param {TimeRange} range 39412 * @return {string} a human readable string 39413 */ 39414 39415 39416 var printableRange = function printableRange(range) { 39417 var strArr = []; 39418 39419 if (!range || !range.length) { 39420 return ''; 39421 } 39422 39423 for (var i = 0; i < range.length; i++) { 39424 strArr.push(range.start(i) + ' => ' + range.end(i)); 39425 } 39426 39427 return strArr.join(', '); 39428 }; 39429 /** 39430 * Calculates the amount of time left in seconds until the player hits the end of the 39431 * buffer and causes a rebuffer 39432 * 39433 * @param {TimeRange} buffered 39434 * The state of the buffer 39435 * @param {Numnber} currentTime 39436 * The current time of the player 39437 * @param {number} playbackRate 39438 * The current playback rate of the player. Defaults to 1. 39439 * @return {number} 39440 * Time until the player has to start rebuffering in seconds. 39441 * @function timeUntilRebuffer 39442 */ 39443 39444 39445 var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime, playbackRate) { 39446 if (playbackRate === void 0) { 39447 playbackRate = 1; 39448 } 39449 39450 var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0; 39451 return (bufferedEnd - currentTime) / playbackRate; 39452 }; 39453 /** 39454 * Converts a TimeRanges object into an array representation 39455 * 39456 * @param {TimeRanges} timeRanges 39457 * @return {Array} 39458 */ 39459 39460 39461 var timeRangesToArray = function timeRangesToArray(timeRanges) { 39462 var timeRangesList = []; 39463 39464 for (var i = 0; i < timeRanges.length; i++) { 39465 timeRangesList.push({ 39466 start: timeRanges.start(i), 39467 end: timeRanges.end(i) 39468 }); 39469 } 39470 39471 return timeRangesList; 39472 }; 39473 /** 39474 * Determines if two time range objects are different. 39475 * 39476 * @param {TimeRange} a 39477 * the first time range object to check 39478 * 39479 * @param {TimeRange} b 39480 * the second time range object to check 39481 * 39482 * @return {Boolean} 39483 * Whether the time range objects differ 39484 */ 39485 39486 39487 var isRangeDifferent = function isRangeDifferent(a, b) { 39488 // same object 39489 if (a === b) { 39490 return false; 39491 } // one or the other is undefined 39492 39493 39494 if (!a && b || !b && a) { 39495 return true; 39496 } // length is different 39497 39498 39499 if (a.length !== b.length) { 39500 return true; 39501 } // see if any start/end pair is different 39502 39503 39504 for (var i = 0; i < a.length; i++) { 39505 if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) { 39506 return true; 39507 } 39508 } // if the length and every pair is the same 39509 // this is the same time range 39510 39511 39512 return false; 39513 }; 39514 39515 var lastBufferedEnd = function lastBufferedEnd(a) { 39516 if (!a || !a.length || !a.end) { 39517 return; 39518 } 39519 39520 return a.end(a.length - 1); 39521 }; 39522 /** 39523 * @file playlist.js 39524 * 39525 * Playlist related utilities. 39526 */ 39527 39528 39529 var createTimeRange = videojs.createTimeRange; 39530 /** 39531 * A function to get a combined list of parts and segments with durations 39532 * and indexes. 39533 * 39534 * @param {Playlist} playlist the playlist to get the list for. 39535 * 39536 * @return {Array} The part/segment list. 39537 */ 39538 39539 var getPartsAndSegments = function getPartsAndSegments(playlist) { 39540 return (playlist.segments || []).reduce(function (acc, segment, si) { 39541 if (segment.parts) { 39542 segment.parts.forEach(function (part, pi) { 39543 acc.push({ 39544 duration: part.duration, 39545 segmentIndex: si, 39546 partIndex: pi, 39547 part: part, 39548 segment: segment 39549 }); 39550 }); 39551 } else { 39552 acc.push({ 39553 duration: segment.duration, 39554 segmentIndex: si, 39555 partIndex: null, 39556 segment: segment, 39557 part: null 39558 }); 39559 } 39560 39561 return acc; 39562 }, []); 39563 }; 39564 39565 var getLastParts = function getLastParts(media) { 39566 var lastSegment = media.segments && media.segments.length && media.segments[media.segments.length - 1]; 39567 return lastSegment && lastSegment.parts || []; 39568 }; 39569 39570 var getKnownPartCount = function getKnownPartCount(_ref) { 39571 var preloadSegment = _ref.preloadSegment; 39572 39573 if (!preloadSegment) { 39574 return; 39575 } 39576 39577 var parts = preloadSegment.parts, 39578 preloadHints = preloadSegment.preloadHints; 39579 var partCount = (preloadHints || []).reduce(function (count, hint) { 39580 return count + (hint.type === 'PART' ? 1 : 0); 39581 }, 0); 39582 partCount += parts && parts.length ? parts.length : 0; 39583 return partCount; 39584 }; 39585 /** 39586 * Get the number of seconds to delay from the end of a 39587 * live playlist. 39588 * 39589 * @param {Playlist} master the master playlist 39590 * @param {Playlist} media the media playlist 39591 * @return {number} the hold back in seconds. 39592 */ 39593 39594 39595 var liveEdgeDelay = function liveEdgeDelay(master, media) { 39596 if (media.endList) { 39597 return 0; 39598 } // dash suggestedPresentationDelay trumps everything 39599 39600 39601 if (master && master.suggestedPresentationDelay) { 39602 return master.suggestedPresentationDelay; 39603 } 39604 39605 var hasParts = getLastParts(media).length > 0; // look for "part" delays from ll-hls first 39606 39607 if (hasParts && media.serverControl && media.serverControl.partHoldBack) { 39608 return media.serverControl.partHoldBack; 39609 } else if (hasParts && media.partTargetDuration) { 39610 return media.partTargetDuration * 3; // finally look for full segment delays 39611 } else if (media.serverControl && media.serverControl.holdBack) { 39612 return media.serverControl.holdBack; 39613 } else if (media.targetDuration) { 39614 return media.targetDuration * 3; 39615 } 39616 39617 return 0; 39618 }; 39619 /** 39620 * walk backward until we find a duration we can use 39621 * or return a failure 39622 * 39623 * @param {Playlist} playlist the playlist to walk through 39624 * @param {Number} endSequence the mediaSequence to stop walking on 39625 */ 39626 39627 39628 var backwardDuration = function backwardDuration(playlist, endSequence) { 39629 var result = 0; 39630 var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following 39631 // the interval, use it 39632 39633 var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline 39634 // information that is earlier than endSequence 39635 39636 if (segment) { 39637 if (typeof segment.start !== 'undefined') { 39638 return { 39639 result: segment.start, 39640 precise: true 39641 }; 39642 } 39643 39644 if (typeof segment.end !== 'undefined') { 39645 return { 39646 result: segment.end - segment.duration, 39647 precise: true 39648 }; 39649 } 39650 } 39651 39652 while (i--) { 39653 segment = playlist.segments[i]; 39654 39655 if (typeof segment.end !== 'undefined') { 39656 return { 39657 result: result + segment.end, 39658 precise: true 39659 }; 39660 } 39661 39662 result += segment.duration; 39663 39664 if (typeof segment.start !== 'undefined') { 39665 return { 39666 result: result + segment.start, 39667 precise: true 39668 }; 39669 } 39670 } 39671 39672 return { 39673 result: result, 39674 precise: false 39675 }; 39676 }; 39677 /** 39678 * walk forward until we find a duration we can use 39679 * or return a failure 39680 * 39681 * @param {Playlist} playlist the playlist to walk through 39682 * @param {number} endSequence the mediaSequence to stop walking on 39683 */ 39684 39685 39686 var forwardDuration = function forwardDuration(playlist, endSequence) { 39687 var result = 0; 39688 var segment; 39689 var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline 39690 // information 39691 39692 for (; i < playlist.segments.length; i++) { 39693 segment = playlist.segments[i]; 39694 39695 if (typeof segment.start !== 'undefined') { 39696 return { 39697 result: segment.start - result, 39698 precise: true 39699 }; 39700 } 39701 39702 result += segment.duration; 39703 39704 if (typeof segment.end !== 'undefined') { 39705 return { 39706 result: segment.end - result, 39707 precise: true 39708 }; 39709 } 39710 } // indicate we didn't find a useful duration estimate 39711 39712 39713 return {
39714 result: -1, 39715 precise: false 39716 }; 39717 }; 39718 /** 39719 * Calculate the media duration from the segments associated with a 39720 * playlist. The duration of a subinterval of the available segments 39721 * may be calculated by specifying an end index. 39722 * 39723 * @param {Object} playlist a media playlist object 39724 * @param {number=} endSequence an exclusive upper boundary 39725 * for the playlist. Defaults to playlist length. 39726 * @param {number} expired the amount of time that has dropped 39727 * off the front of the playlist in a live scenario 39728 * @return {number} the duration between the first available segment 39729 * and end index. 39730 */ 39731 39732 39733 var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 39734 if (typeof endSequence === 'undefined') { 39735 endSequence = playlist.mediaSequence + playlist.segments.length; 39736 } 39737 39738 if (endSequence < playlist.mediaSequence) { 39739 return 0; 39740 } // do a backward walk to estimate the duration 39741 39742 39743 var backward = backwardDuration(playlist, endSequence); 39744 39745 if (backward.precise) { 39746 // if we were able to base our duration estimate on timing 39747 // information provided directly from the Media Source, return 39748 // it 39749 return backward.result; 39750 } // walk forward to see if a precise duration estimate can be made 39751 // that way 39752 39753 39754 var forward = forwardDuration(playlist, endSequence); 39755 39756 if (forward.precise) { 39757 // we found a segment that has been buffered and so it's 39758 // position is known precisely 39759 return forward.result; 39760 } // return the less-precise, playlist-based duration estimate 39761 39762 39763 return backward.result + expired; 39764 }; 39765 /** 39766 * Calculates the duration of a playlist. If a start and end index 39767 * are specified, the duration will be for the subset of the media 39768 * timeline between those two indices. The total duration for live 39769 * playlists is always Infinity. 39770 * 39771 * @param {Object} playlist a media playlist object 39772 * @param {number=} endSequence an exclusive upper 39773 * boundary for the playlist. Defaults to the playlist media 39774 * sequence number plus its length. 39775 * @param {number=} expired the amount of time that has 39776 * dropped off the front of the playlist in a live scenario 39777 * @return {number} the duration between the start index and end 39778 * index. 39779 */ 39780 39781 39782 var duration = function duration(playlist, endSequence, expired) { 39783 if (!playlist) { 39784 return 0; 39785 } 39786 39787 if (typeof expired !== 'number') { 39788 expired = 0; 39789 } // if a slice of the total duration is not requested, use 39790 // playlist-level duration indicators when they're present 39791 39792 39793 if (typeof endSequence === 'undefined') { 39794 // if present, use the duration specified in the playlist 39795 if (playlist.totalDuration) { 39796 return playlist.totalDuration; 39797 } // duration should be Infinity for live playlists 39798 39799 39800 if (!playlist.endList) { 39801 return window_1.Infinity; 39802 } 39803 } // calculate the total duration based on the segment durations 39804 39805 39806 return intervalDuration(playlist, endSequence, expired); 39807 }; 39808 /** 39809 * Calculate the time between two indexes in the current playlist 39810 * neight the start- nor the end-index need to be within the current 39811 * playlist in which case, the targetDuration of the playlist is used 39812 * to approximate the durations of the segments 39813 * 39814 * @param {Array} options.durationList list to iterate over for durations. 39815 * @param {number} options.defaultDuration duration to use for elements before or after the durationList 39816 * @param {number} options.startIndex partsAndSegments index to start 39817 * @param {number} options.endIndex partsAndSegments index to end. 39818 * @return {number} the number of seconds between startIndex and endIndex 39819 */ 39820 39821 39822 var sumDurations = function sumDurations(_ref2) { 39823 var defaultDuration = _ref2.defaultDuration, 39824 durationList = _ref2.durationList, 39825 startIndex = _ref2.startIndex, 39826 endIndex = _ref2.endIndex; 39827 var durations = 0; 39828 39829 if (startIndex > endIndex) { 39830 var _ref3 = [endIndex, startIndex]; 39831 startIndex = _ref3[0];
39832 endIndex = _ref3[1]; 39833 } 39834 39835 if (startIndex < 0) { 39836 for (var i = startIndex; i < Math.min(0, endIndex); i++) { 39837 durations += defaultDuration; 39838 } 39839 39840 startIndex = 0; 39841 } 39842 39843 for (var _i = startIndex; _i < endIndex; _i++) { 39844 durations += durationList[_i].duration; 39845 } 39846 39847 return durations; 39848 }; 39849 /** 39850 * Calculates the playlist end time 39851 * 39852 * @param {Object} playlist a media playlist object 39853 * @param {number=} expired the amount of time that has 39854 * dropped off the front of the playlist in a live scenario 39855 * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the 39856 * playlist end calculation should consider the safe live end 39857 * (truncate the playlist end by three segments). This is normally 39858 * used for calculating the end of the playlist's seekable range. 39859 * This takes into account the value of liveEdgePadding. 39860 * Setting liveEdgePadding to 0 is equivalent to setting this to false. 39861 * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds. 39862 * If this is provided, it is used in the safe live end calculation. 39863 * Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent. 39864 * Corresponds to suggestedPresentationDelay in DASH manifests. 39865 * @return {number} the end time of playlist 39866 * @function playlistEnd 39867 */ 39868 39869 39870 var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding) { 39871 if (!playlist || !playlist.segments) { 39872 return null; 39873 } 39874 39875 if (playlist.endList) { 39876 return duration(playlist); 39877 } 39878 39879 if (expired === null) { 39880 return null; 39881 } 39882 39883 expired = expired || 0; 39884 var lastSegmentTime = intervalDuration(playlist, playlist.mediaSequence + playlist.segments.length, expired); 39885 39886 if (useSafeLiveEnd) { 39887 liveEdgePadding = typeof liveEdgePadding === 'number' ? liveEdgePadding : liveEdgeDelay(null, playlist); 39888 lastSegmentTime -= liveEdgePadding; 39889 } // don't return a time less than zero 39890 39891 39892 return Math.max(0, lastSegmentTime); 39893 }; 39894 /** 39895 * Calculates the interval of time that is currently seekable in a 39896 * playlist. The returned time ranges are relative to the earliest 39897 * moment in the specified playlist that is still available. A full 39898 * seekable implementation for live streams would need to offset 39899 * these values by the duration of content that has expired from the 39900 * stream. 39901 * 39902 * @param {Object} playlist a media playlist object 39903 * dropped off the front of the playlist in a live scenario 39904 * @param {number=} expired the amount of time that has 39905 * dropped off the front of the playlist in a live scenario 39906 * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds. 39907 * Corresponds to suggestedPresentationDelay in DASH manifests. 39908 * @return {TimeRanges} the periods of time that are valid targets 39909 * for seeking 39910 */ 39911 39912 39913 var seekable = function seekable(playlist, expired, liveEdgePadding) { 39914 var useSafeLiveEnd = true; 39915 var seekableStart = expired || 0; 39916 var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding); 39917 39918 if (seekableEnd === null) { 39919 return createTimeRange(); 39920 } 39921 39922 return createTimeRange(seekableStart, seekableEnd); 39923 }; 39924 /** 39925 * Determine the index and estimated starting time of the segment that 39926 * contains a specified playback position in a media playlist. 39927 * 39928 * @param {Object} options.playlist the media playlist to query 39929 * @param {number} options.currentTime The number of seconds since the earliest 39930 * possible position to determine the containing segment for 39931 * @param {number} options.startTime the time when the segment/part starts 39932 * @param {number} options.startingSegmentIndex the segment index to start looking at. 39933 * @param {number?} [options.startingPartIndex] the part index to look at within the segment. 39934 * 39935 * @return {Object} an object with partIndex, segmentIndex, and startTime. 39936 */ 39937 39938 39939 var getMediaInfoForTime = function getMediaInfoForTime(_ref4) { 39940 var playlist = _ref4.playlist, 39941 currentTime = _ref4.currentTime, 39942 startingSegmentIndex = _ref4.startingSegmentIndex, 39943 startingPartIndex = _ref4.startingPartIndex, 39944 startTime = _ref4.startTime, 39945 experimentalExactManifestTimings = _ref4.experimentalExactManifestTimings; 39946 var time = currentTime - startTime; 39947 var partsAndSegments = getPartsAndSegments(playlist); 39948 var startIndex = 0; 39949 39950 for (var i = 0; i < partsAndSegments.length; i++) { 39951 var partAndSegment = partsAndSegments[i]; 39952 39953 if (startingSegmentIndex !== partAndSegment.segmentIndex) { 39954 continue; 39955 } // skip this if part index does not match. 39956 39957 39958 if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) { 39959 continue; 39960 } 39961 39962 startIndex = i; 39963 break; 39964 } 39965 39966 if (time < 0) { 39967 // Walk backward from startIndex in the playlist, adding durations 39968 // until we find a segment that contains `time` and return it 39969 if (startIndex > 0) { 39970 for (var _i2 = startIndex - 1; _i2 >= 0; _i2--) { 39971 var _partAndSegment = partsAndSegments[_i2]; 39972 time += _partAndSegment.duration; 39973 39974 if (experimentalExactManifestTimings) { 39975 if (time < 0) { 39976 continue; 39977 } 39978 } else if (time + TIME_FUDGE_FACTOR <= 0) { 39979 continue; 39980 } 39981 39982 return { 39983 partIndex: _partAndSegment.partIndex, 39984 segmentIndex: _partAndSegment.segmentIndex, 39985 startTime: startTime - sumDurations({ 39986 defaultDuration: playlist.targetDuration, 39987 durationList: partsAndSegments, 39988 startIndex: startIndex, 39989 endIndex: _i2 39990 }) 39991 }; 39992 } 39993 } // We were unable to find a good segment within the playlist 39994 // so select the first segment 39995 39996 39997 return { 39998 partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null, 39999 segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0, 40000 startTime: currentTime 40001 }; 40002 } // When startIndex is negative, we first walk forward to first segment 40003 // adding target durations. If we "run out of time" before getting to 40004 // the first segment, return the first segment 40005 40006 40007 if (startIndex < 0) { 40008 for (var _i3 = startIndex; _i3 < 0; _i3++) { 40009 time -= playlist.targetDuration; 40010 40011 if (time < 0) { 40012 return { 40013 partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null, 40014 segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0, 40015 startTime: currentTime 40016 }; 40017 } 40018 } 40019 40020 startIndex = 0; 40021 } // Walk forward from startIndex in the playlist, subtracting durations 40022 // until we find a segment that contains `time` and return it 40023 40024 40025 for (var _i4 = startIndex; _i4 < partsAndSegments.length; _i4++) { 40026 var _partAndSegment2 = partsAndSegments[_i4]; 40027 time -= _partAndSegment2.duration; 40028 40029 if (experimentalExactManifestTimings) { 40030 if (time > 0) { 40031 continue; 40032 } 40033 } else if (time - TIME_FUDGE_FACTOR >= 0) { 40034 continue; 40035 } 40036 40037 return { 40038 partIndex: _partAndSegment2.partIndex, 40039 segmentIndex: _partAndSegment2.segmentIndex, 40040 startTime: startTime + sumDurations({ 40041 defaultDuration: playlist.targetDuration, 40042 durationList: partsAndSegments, 40043 startIndex: startIndex, 40044 endIndex: _i4 40045 }) 40046 }; 40047 } // We are out of possible candidates so load the last one... 40048 40049 40050 return { 40051 segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex, 40052 partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex, 40053 startTime: currentTime 40054 }; 40055 }; 40056 /** 40057 * Check whether the playlist is blacklisted or not. 40058 * 40059 * @param {Object} playlist the media playlist object 40060 * @return {boolean} whether the playlist is blacklisted or not 40061 * @function isBlacklisted 40062 */ 40063 40064 40065 var isBlacklisted = function isBlacklisted(playlist) { 40066 return playlist.excludeUntil && playlist.excludeUntil > Date.now(); 40067 }; 40068 /** 40069 * Check whether the playlist is compatible with current playback configuration or has 40070 * been blacklisted permanently for being incompatible. 40071 * 40072 * @param {Object} playlist the media playlist object 40073 * @return {boolean} whether the playlist is incompatible or not 40074 * @function isIncompatible 40075 */ 40076 40077 40078 var isIncompatible = function isIncompatible(playlist) { 40079 return playlist.excludeUntil && playlist.excludeUntil === Infinity; 40080 }; 40081 /** 40082 * Check whether the playlist is enabled or not. 40083 * 40084 * @param {Object} playlist the media playlist object 40085 * @return {boolean} whether the playlist is enabled or not 40086 * @function isEnabled 40087 */ 40088 40089 40090 var isEnabled = function isEnabled(playlist) { 40091 var blacklisted = isBlacklisted(playlist); 40092 return !playlist.disabled && !blacklisted; 40093 }; 40094 /** 40095 * Check whether the playlist has been manually disabled through the representations api. 40096 * 40097 * @param {Object} playlist the media playlist object 40098 * @return {boolean} whether the playlist is disabled manually or not 40099 * @function isDisabled 40100 */ 40101 40102 40103 var isDisabled = function isDisabled(playlist) { 40104 return playlist.disabled; 40105 }; 40106 /** 40107 * Returns whether the current playlist is an AES encrypted HLS stream 40108 * 40109 * @return {boolean} true if it's an AES encrypted HLS stream 40110 */ 40111 40112 40113 var isAes = function isAes(media) { 40114 for (var i = 0; i < media.segments.length; i++) { 40115 if (media.segments[i].key) { 40116 return true; 40117 } 40118 } 40119 40120 return false; 40121 }; 40122 /** 40123 * Checks if the playlist has a value for the specified attribute 40124 * 40125 * @param {string} attr 40126 * Attribute to check for 40127 * @param {Object} playlist 40128 * The media playlist object 40129 * @return {boolean} 40130 * Whether the playlist contains a value for the attribute or not 40131 * @function hasAttribute 40132 */ 40133 40134 40135 var hasAttribute = function hasAttribute(attr, playlist) { 40136 return playlist.attributes && playlist.attributes[attr]; 40137 }; 40138 /** 40139 * Estimates the time required to complete a segment download from the specified playlist 40140 * 40141 * @param {number} segmentDuration 40142 * Duration of requested segment 40143 * @param {number} bandwidth 40144 * Current measured bandwidth of the player 40145 * @param {Object} playlist 40146 * The media playlist object 40147 * @param {number=} bytesReceived 40148 * Number of bytes already received for the request. Defaults to 0 40149 * @return {number|NaN} 40150 * The estimated time to request the segment. NaN if bandwidth information for 40151 * the given playlist is unavailable 40152 * @function estimateSegmentRequestTime 40153 */ 40154 40155 40156 var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist, bytesReceived) { 40157 if (bytesReceived === void 0) { 40158 bytesReceived = 0; 40159 } 40160 40161 if (!hasAttribute('BANDWIDTH', playlist)) { 40162 return NaN; 40163 } 40164 40165 var size = segmentDuration * playlist.attributes.BANDWIDTH; 40166 return (size - bytesReceived * 8) / bandwidth; 40167 }; 40168 /* 40169 * Returns whether the current playlist is the lowest rendition 40170 * 40171 * @return {Boolean} true if on lowest rendition 40172 */ 40173 40174 40175 var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) { 40176 if (master.playlists.length === 1) { 40177 return true; 40178 } 40179 40180 var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; 40181 return master.playlists.filter(function (playlist) { 40182 if (!isEnabled(playlist)) { 40183 return false; 40184 } 40185 40186 return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth; 40187 }).length === 0; 40188 }; 40189 40190 var playlistMatch = function playlistMatch(a, b) { 40191 // both playlits are null 40192 // or only one playlist is non-null 40193 // no match 40194 if (!a && !b || !a && b || a && !b) { 40195 return false; 40196 } // playlist objects are the same, match 40197 40198 40199 if (a === b) { 40200 return true; 40201 } // first try to use id as it should be the most 40202 // accurate 40203 40204 40205 if (a.id && b.id && a.id === b.id) { 40206 return true; 40207 } // next try to use reslovedUri as it should be the 40208 // second most accurate. 40209 40210 40211 if (a.resolvedUri && b.resolvedUri && a.resolvedUri === b.resolvedUri) { 40212 return true; 40213 } // finally try to use uri as it should be accurate 40214 // but might miss a few cases for relative uris 40215 40216 40217 if (a.uri && b.uri && a.uri === b.uri) { 40218 return true; 40219 } 40220 40221 return false; 40222 }; 40223 40224 var someAudioVariant = function someAudioVariant(master, callback) { 40225 var AUDIO = master && master.mediaGroups && master.mediaGroups.AUDIO || {}; 40226 var found = false; 40227 40228 for (var groupName in AUDIO) { 40229 for (var label in AUDIO[groupName]) { 40230 found = callback(AUDIO[groupName][label]); 40231 40232 if (found) { 40233 break; 40234 } 40235 } 40236 40237 if (found) { 40238 break; 40239 } 40240 } 40241 40242 return !!found; 40243 }; 40244 40245 var isAudioOnly = function isAudioOnly(master) { 40246 // we are audio only if we have no main playlists but do 40247 // have media group playlists. 40248 if (!master || !master.playlists || !master.playlists.length) { 40249 // without audio variants or playlists this 40250 // is not an audio only master. 40251 var found = someAudioVariant(master, function (variant) { 40252 return variant.playlists && variant.playlists.length || variant.uri; 40253 }); 40254 return found; 40255 } // if every playlist has only an audio codec it is audio only 40256 40257 40258 var _loop = function _loop(i) { 40259 var playlist = master.playlists[i]; 40260 var CODECS = playlist.attributes && playlist.attributes.CODECS; // all codecs are audio, this is an audio playlist. 40261 40262 if (CODECS && CODECS.split(',').every(function (c) { 40263 return isAudioCodec(c); 40264 })) { 40265 return "continue"; 40266 } // playlist is in an audio group it is audio only 40267 40268 40269 var found = someAudioVariant(master, function (variant) { 40270 return playlistMatch(playlist, variant); 40271 }); 40272 40273 if (found) { 40274 return "continue"; 40275 } // if we make it here this playlist isn't audio and we 40276 // are not audio only 40277 40278 40279 return { 40280 v: false 40281 }; 40282 }; 40283 40284 for (var i = 0; i < master.playlists.length; i++) { 40285 var _ret = _loop(i); 40286 40287 if (_ret === "continue") continue; 40288 if (typeof _ret === "object") return _ret.v; 40289 } // if we make it past every playlist without returning, then 40290 // this is an audio only playlist. 40291 40292 40293 return true; 40294 }; // exports 40295 40296 40297 var Playlist = { 40298 liveEdgeDelay: liveEdgeDelay, 40299 duration: duration, 40300 seekable: seekable, 40301 getMediaInfoForTime: getMediaInfoForTime, 40302 isEnabled: isEnabled, 40303 isDisabled: isDisabled, 40304 isBlacklisted: isBlacklisted, 40305 isIncompatible: isIncompatible, 40306 playlistEnd: playlistEnd,
40307 isAes: isAes, 40308 hasAttribute: hasAttribute, 40309 estimateSegmentRequestTime: estimateSegmentRequestTime, 40310 isLowestEnabledRendition: isLowestEnabledRendition, 40311 isAudioOnly: isAudioOnly, 40312 playlistMatch: playlistMatch 40313 }; 40314 var log = videojs.log; 40315 40316 var createPlaylistID = function createPlaylistID(index, uri) { 40317 return index + "-" + uri; 40318 }; 40319 /** 40320 * Parses a given m3u8 playlist 40321 * 40322 * @param {Function} [onwarn] 40323 * a function to call when the parser triggers a warning event. 40324 * @param {Function} [oninfo] 40325 * a function to call when the parser triggers an info event. 40326 * @param {string} manifestString 40327 * The downloaded manifest string 40328 * @param {Object[]} [customTagParsers] 40329 * An array of custom tag parsers for the m3u8-parser instance 40330 * @param {Object[]} [customTagMappers] 40331 * An array of custom tag mappers for the m3u8-parser instance 40332 * @param {boolean} [experimentalLLHLS=false] 40333 * Whether to keep ll-hls features in the manifest after parsing. 40334 * @return {Object} 40335 * The manifest object 40336 */ 40337 40338 40339 var parseManifest = function parseManifest(_ref) { 40340 var onwarn = _ref.onwarn, 40341 oninfo = _ref.oninfo, 40342 manifestString = _ref.manifestString, 40343 _ref$customTagParsers = _ref.customTagParsers, 40344 customTagParsers = _ref$customTagParsers === void 0 ? [] : _ref$customTagParsers, 40345 _ref$customTagMappers = _ref.customTagMappers, 40346 customTagMappers = _ref$customTagMappers === void 0 ? [] : _ref$customTagMappers, 40347 experimentalLLHLS = _ref.experimentalLLHLS; 40348 var parser = new Parser(); 40349 40350 if (onwarn) { 40351 parser.on('warn', onwarn); 40352 } 40353 40354 if (oninfo) { 40355 parser.on('info', oninfo); 40356 } 40357 40358 customTagParsers.forEach(function (customParser) { 40359 return parser.addParser(customParser); 40360 }); 40361 customTagMappers.forEach(function (mapper) { 40362 return parser.addTagMapper(mapper); 40363 }); 40364 parser.push(manifestString); 40365 parser.end(); 40366 var manifest = parser.manifest; // remove llhls features from the parsed manifest 40367 // if we don't want llhls support. 40368 40369 if (!experimentalLLHLS) { 40370 ['preloadSegment', 'skip', 'serverControl', 'renditionReports', 'partInf', 'partTargetDuration'].forEach(function (k) { 40371 if (manifest.hasOwnProperty(k)) { 40372 delete manifest[k]; 40373 } 40374 }); 40375 40376 if (manifest.segments) { 40377 manifest.segments.forEach(function (segment) { 40378 ['parts', 'preloadHints'].forEach(function (k) { 40379 if (segment.hasOwnProperty(k)) { 40380 delete segment[k]; 40381 } 40382 }); 40383 }); 40384 } 40385 } 40386 40387 if (!manifest.targetDuration) { 40388 var targetDuration = 10; 40389 40390 if (manifest.segments && manifest.segments.length) { 40391 targetDuration = manifest.segments.reduce(function (acc, s) { 40392 return Math.max(acc, s.duration); 40393 }, 0); 40394 } 40395 40396 if (onwarn) { 40397 onwarn("manifest has no targetDuration defaulting to " + targetDuration); 40398 } 40399 40400 manifest.targetDuration = targetDuration; 40401 } 40402 40403 var parts = getLastParts(manifest); 40404 40405 if (parts.length && !manifest.partTargetDuration) { 40406 var partTargetDuration = parts.reduce(function (acc, p) { 40407 return Math.max(acc, p.duration); 40408 }, 0); 40409 40410 if (onwarn) { 40411 onwarn("manifest has no partTargetDuration defaulting to " + partTargetDuration); 40412 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.'); 40413 } 40414 40415 manifest.partTargetDuration = partTargetDuration; 40416 } 40417 40418 return manifest; 40419 }; 40420 /** 40421 * Loops through all supported media groups in master and calls the provided 40422 * callback for each group 40423 * 40424 * @param {Object} master 40425 * The parsed master manifest object 40426 * @param {Function} callback 40427 * Callback to call for each media group 40428 */ 40429 40430
40431 var forEachMediaGroup = function forEachMediaGroup(master, callback) { 40432 if (!master.mediaGroups) { 40433 return; 40434 } 40435 40436 ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) { 40437 if (!master.mediaGroups[mediaType]) { 40438 return; 40439 } 40440 40441 for (var groupKey in master.mediaGroups[mediaType]) { 40442 for (var labelKey in master.mediaGroups[mediaType][groupKey]) { 40443 var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey]; 40444 callback(mediaProperties, mediaType, groupKey, labelKey); 40445 } 40446 } 40447 }); 40448 }; 40449 /** 40450 * Adds properties and attributes to the playlist to keep consistent functionality for 40451 * playlists throughout VHS. 40452 * 40453 * @param {Object} config 40454 * Arguments object 40455 * @param {Object} config.playlist 40456 * The media playlist 40457 * @param {string} [config.uri] 40458 * The uri to the media playlist (if media playlist is not from within a master 40459 * playlist) 40460 * @param {string} id 40461 * ID to use for the playlist 40462 */ 40463 40464 40465 var setupMediaPlaylist = function setupMediaPlaylist(_ref2) { 40466 var playlist = _ref2.playlist, 40467 uri = _ref2.uri, 40468 id = _ref2.id; 40469 playlist.id = id; 40470 playlist.playlistErrors_ = 0; 40471 40472 if (uri) { 40473 // For media playlists, m3u8-parser does not have access to a URI, as HLS media 40474 // playlists do not contain their own source URI, but one is needed for c
40474onsistency in 40475 // VHS. 40476 playlist.uri = uri; 40477 } // For HLS master playlists, even though certain attributes MUST be defined, the 40478 // stream may still be played without them. 40479 // For HLS media playlists, m3u8-parser does not attach an attributes object to the 40480 // manifest. 40481 // 40482 // To avoid undefined reference errors through the project, and make the code easier 40483 // to write/read, add an empty attributes object for these cases. 40484 40485 40486 playlist.attributes = playlist.attributes || {}; 40487 }; 40488 /** 40489 * Adds ID, resolvedUri, and attributes properties to each playlist of the master, where 40490 * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to 40491 * playlist references to the playlists array. 40492 * 40493 * @param {Object} master 40494 * The master playlist 40495 */ 40496 40497 40498 var setupMediaPlaylists = function setupMediaPlaylists(master) { 40499 var i = master.playlists.length; 40500 40501 while (i--) { 40502 var playlist = master.playlists[i]; 40503 setupMediaPlaylist({ 40504 playlist: playlist, 40505 id: createPlaylistID(i, playlist.uri) 40506 }); 40507 playlist.resolvedUri = resolveUrl(master.uri, playlist.uri); 40508 master.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility 40509 40510 master.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute, 40511 // the stream can be played without it. Although an attributes property may have been 40512 // added to the playlist to prevent undefined references, issue a warning to fix the 40513 // manifest. 40514 40515 if (!playlist.attributes.BANDWIDTH) { 40516 log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.'); 40517 } 40518 } 40519 }; 40520 /** 40521 * Adds resolvedUri properties to each media group. 40522 * 40523 * @param {Object} master 40524 * The master playlist 40525 */ 40526 40527 40528 var resolveMediaGroupUris = function resolveMediaGroupUris(master) { 40529 forEachMediaGroup(master, function (properties) { 40530 if (properties.uri) { 40531 properties.resolvedUri = resolveUrl(master.uri, properties.uri); 40532 } 40533 }); 40534 }; 40535 /** 40536 * Creates a master playlist wrapper to insert a sole media playlist into. 40537 * 40538 * @param {Object} media 40539 * Media playlist 40540 * @param {string} uri 40541 * The media URI 40542 * 40543 * @return {Object} 40544 * Master playlist 40545 */ 40546 40547 40548 var masterForMedia = function masterForMedia(media, uri) { 40549 var id = createPlaylistID(0, uri); 40550 var master = { 40551 mediaGroups: { 40552 'AUDIO': {}, 40553 'VIDEO': {}, 40554 'CLOSED-CAPTIONS': {}, 40555 'SUBTITLES': {} 40556 }, 40557 uri: window_1.location.href, 40558 resolvedUri: window_1.location.href, 40559 playlists: [{ 40560 uri: uri, 40561 id: id, 40562 resolvedUri: uri, 40563 // m3u8-parser does not attach an attributes property to media playlists so make 40564 // sure that the property is attached to avoid undefined reference errors 40565 attributes: {} 40566 }] 40567 }; // set up ID reference 40568 40569 master.playlists[id] = master.playlists[0]; // URI reference added for backwards compatibility 40570 40571 master.playlists[uri] = master.playlists[0]; 40572 return master; 40573 }; 40574 /** 40575 * Does an in-place update of the master manifest to add updated playlist URI references 40576 * as well as other properties needed by VHS that aren't included by the parser. 40577 * 40578 * @param {Object} master 40579 * Master manifest object 40580 * @param {string} uri 40581 * The source URI 40582 */ 40583 40584 40585 var addPropertiesToMaster = function addPropertiesToMaster(master, uri) { 40586 master.uri = uri; 40587 40588 for (var i = 0; i < master.playlists.length; i++) { 40589 if (!master.playlists[i].uri) { 40590 // Set up phony URIs for the playlists since playlists are referenced by their URIs 40591 // throughout VHS, but some formats (e.g., DASH) don't have external URIs 40592 // TODO: consider adding dummy URIs in mpd-parser 40593 var phonyUri = "placeholder-uri-" + i; 40594 master.playlists[i].uri = phonyUri; 40595 } 40596 } 40597 40598 var audioOnlyMaster = isAudioOnly(master);
40599 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 40600 var groupId = "placeholder-uri-" + mediaType + "-" + groupKey + "-" + labelKey; // add a playlist array under properties 40601 40602 if (!properties.playlists || !properties.playlists.length) { 40603 // If the manifest is audio only and this media group does not have a uri, check 40604 // if the media group is located in the main list of playlists. If it is, don't add 40605 // placeholder properties as it shouldn't be considered an alternate audio track. 40606 if (audioOnlyMaster && mediaType === 'AUDIO' && !properties.uri) { 40607 for (var _i = 0; _i < master.playlists.length; _i++) { 40608 var p = master.playlists[_i]; 40609 40610 if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) { 40611 return; 40612 } 40613 } 40614 } 40615 40616 properties.playlists = [_extends_1({}, properties)]; 40617 } 40618 40619 properties.playlists.forEach(function (p, i) { 40620 var id = createPlaylistID(i, groupId); 40621 40622 if (p.uri) { 40623 p.resolvedUri = p.resolvedUri || resolveUrl(master.uri, p.uri); 40624 } else { 40625 // DEPRECATED, this has been added to prevent a breaking change. 40626 // previously we only ever had a single media group playlist, so 40627 // we mark the first playlist uri without prepending the index as we used to 40628 // ideally we would do all of the playlists the same way. 40629 p.uri = i === 0 ? groupId : id; // don't resolve a placeholder uri to an absolute url, just use 40630 // the placeholder again 40631 40632 p.resolvedUri = p.uri; 40633 } 40634 40635 p.id = p.id || id; // add an empty attributes object, all playlists are 40636 // expected to have this. 40637 40638 p.attributes = p.attributes || {}; // setup ID and URI references (URI for backwards compatibility) 40639 40640 master.playlists[p.id] = p; 40641 master.playlists[p.uri] = p; 40642 }); 40643 }); 40644 setupMediaPlaylists(master); 40645 resolveMediaGroupUris(master); 40646 }; 40647 40648 var mergeOptions$2 = videojs.mergeOptions, 40649 EventTarget$1 = videojs.EventTarget; 40650 40651 var addLLHLSQueryDirectives = function addLLHLSQueryDirectives(uri, media) { 40652 if (media.endList) { 40653 return uri; 40654 } 40655 40656 var query = []; 40657 40658 if (media.serverControl && media.serverControl.canBlockReload) { 40659 var preloadSegment = media.preloadSegment; // next msn is a zero based value, length is not. 40660 40661 var nextMSN = media.mediaSequence + media.segments.length; // If preload segment has parts then it is likely 40662 // that we are going to request a part of that preload segment. 40663 // the logic below is used to determine that. 40664 40665 if (preloadSegment) { 40666 var parts = preloadSegment.parts || []; // _HLS_part is a zero based index 40667 40668 var nextPart = getKnownPartCount(media) - 1; // if nextPart is > -1 and not equal to just the 40669 // length of parts, then we know we had part preload hints 40670 // and we need to add the _HLS_part= query 40671 40672 if (nextPart > -1 && nextPart !== parts.length - 1) { 40673 // add existing parts to our preload hints 40674 query.push("_HLS_part=" + nextPart); 40675 } // this if statement makes sure that we request the msn 40676 // of the preload segment if: 40677 // 1. the preload segment had parts (and was not yet a full segment) 40678 // but was added to our segments array 40679 // 2. the preload segment had preload hints for parts that are not in 40680 // the manifest yet. 40681 // in all other cases we want the segment after the preload segment 40682 // which will be given by using media.segments.length because it is 1 based 40683 // rather than 0 based. 40684 40685 40686 if (nextPart > -1 || parts.length) { 40687 nextMSN--; 40688 } 40689 } // add _HLS_msn= in front of any _HLS_part query 40690 40691 40692 query.unshift("_HLS_msn=" + nextMSN); 40693 } 40694 40695 if (media.serverControl && media.serverControl.canSkipUntil) { 40696 // add _HLS_skip= infront of all other queries. 40697 query.unshift('_HLS_skip=' + (media.serverControl.canSkipDateranges ? 'v2' : 'YES')); 40698 } 40699
40700 query.forEach(function (str, i) { 40701 var symbol = i === 0 ? '?' : '&'; 40702 uri += "" + symbol + str; 40703 }); 40704 return uri; 40705 }; 40706 /** 40707 * Returns a new segment object with properties and 40708 * the parts array merged. 40709 * 40710 * @param {Object} a the old segment 40711 * @param {Object} b the new segment 40712 * 40713 * @return {Object} the merged segment 40714 */ 40715 40716 40717 var updateSegment = function updateSegment(a, b) { 40718 if (!a) { 40719 return b; 40720 } 40721 40722 var result = mergeOptions$2(a, b); // if only the old segment has preload hints 40723 // and the new one does not, remove preload hints. 40724 40725 if (a.preloadHints && !b.preloadHints) { 40726 delete result.preloadHints; 40727 } // if only the old segment has parts 40728 // then the parts are no longer valid 40729 40730 40731 if (a.parts && !b.parts) { 40732 delete result.parts; // if both segments have parts 40733 // copy part propeties from the old segment 40734 // to the new one. 40735 } else if (a.parts && b.parts) { 40736 for (var i = 0; i < b.parts.length; i++) { 40737 if (a.parts && a.parts[i]) { 40738 result.parts[i] = mergeOptions$2(a.parts[i], b.parts[i]); 40739 } 40740 } 40741 } // set skipped to false for segments that have 40742 // have had information merged from the old segment. 40743 40744 40745 if (!a.skipped && b.skipped) { 40746 result.skipped = false; 40747 } // set preload to false for segments that have 40748 // had information added in the new segment. 40749 40750 40751 if (a.preload && !b.preload) { 40752 result.preload = false; 40753 } 40754 40755 return result; 40756 }; 40757 /** 40758 * Returns a new array of segments that is the result of merging 40759 * properties from an older list of segments onto an updated 40760 * list. No properties on the updated playlist will be ovewritten. 40761 * 40762 * @param {Array} original the outdated list of segments 40763 * @param {Array} update the updated list of segments 40764 * @param {number=} offset the index of the first update 40765 * segment in the original segment list. For non-live playlists, 40766 * this should always be zero and does not need to be 40767 * specified. For live playlists, it should be the difference 40768 * between the media sequence numbers in the original and updated 40769 * playlists. 40770 * @return {Array} a list of merged segment objects 40771 */ 40772 40773 40774 var updateSegments = function updateSegments(original, update, offset) { 40775 var oldSegments = original.slice(); 40776 var newSegments = update.slice(); 40777 offset = offset || 0; 40778 var result = []; 40779 var currentMap; 40780 40781 for (var newIndex = 0; newIndex < newSegments.length; newIndex++) { 40782 var oldSegment = oldSegments[newIndex + offset]; 40783 var newSegment = newSegments[newIndex]; 40784 40785 if (oldSegment) { 40786 currentMap = oldSegment.map || currentMap; 40787 result.push(updateSegment(oldSegment, newSegment)); 40788 } else { 40789 // carry over map to new segment if it is missing 40790 if (currentMap && !newSegment.map) { 40791 newSegment.map = currentMap; 40792 } 40793 40794 result.push(newSegment); 40795 } 40796 } 40797 40798 return result; 40799 }; 40800 40801 var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) { 40802 // preloadSegment will not have a uri at all 40803 // as the segment isn't actually in the manifest yet, only parts 40804 if (!segment.resolvedUri && segment.uri) { 40805 segment.resolvedUri = resolveUrl(baseUri, segment.uri); 40806 } 40807 40808 if (segment.key && !segment.key.resolvedUri) { 40809 segment.key.resolvedUri = resolveUrl(baseUri, segment.key.uri); 40810 } 40811 40812 if (segment.map && !segment.map.resolvedUri) { 40813 segment.map.resolvedUri = resolveUrl(baseUri, segment.map.uri); 40814 } 40815 40816 if (segment.map && segment.map.key && !segment.map.key.resolvedUri) { 40817 segment.map.key.resolvedUri = resolveUrl(baseUri, segment.map.key.uri); 40818 } 40819 40820 if (segment.parts && segment.parts.length) { 40821 segment.parts.forEach(function (p) { 40822 if (p.resolvedUri) { 40823 return; 40824 } 40825 40826 p.resolvedUri = resolveUrl(baseUri, p.uri); 40827 }); 40828 } 40829 40830 if (segment.preloadHints && segment.preloadHints.length) {
40831 segment.preloadHints.forEach(function (p) { 40832 if (p.resolvedUri) { 40833 return; 40834 } 40835 40836 p.resolvedUri = resolveUrl(baseUri, p.uri); 40837 }); 40838 } 40839 }; 40840 40841 var getAllSegments = function getAllSegments(media) { 40842 var segments = media.segments || []; 40843 var preloadSegment = media.preloadSegment; // a preloadSegment with only preloadHints is not currently 40844 // a usable segment, only include a preloadSegment that has 40845 // parts. 40846 40847 if (preloadSegment && preloadSegment.parts && preloadSegment.parts.length) { 40848 // if preloadHints has a MAP that means that the 40849 // init segment is going to change. We cannot use any of the parts 40850 // from this preload segment. 40851 if (preloadSegment.preloadHints) { 40852 for (var i = 0; i < preloadSegment.preloadHints.length; i++) { 40853 if (preloadSegment.preloadHints[i].type === 'MAP') { 40854 return segments; 40855 } 40856 } 40857 } // set the duration for our preload segment to target duration. 40858 40859 40860 preloadSegment.duration = media.targetDuration; 40861 preloadSegment.preload = true; 40862 segments.push(preloadSegment); 40863 } 40864 40865 return segments; 40866 }; // consider the playlist unchanged if the playlist object is the same or 40867 // the number of segments is equal, the media sequence number is unchanged, 40868 // and this playlist hasn't become the end of the playlist 40869 40870 40871 var isPlaylistUnchanged = function isPlaylistUnchanged(a, b) { 40872 return a === b || a.segments && b.segments && a.segments.length === b.segments.length && a.endList === b.endList && a.mediaSequence === b.mediaSequence; 40873 }; 40874 /** 40875 * Returns a new master playlist that is the result of merging an 40876 * updated media playlist into the original version. If the 40877 * updated media playlist does not match any of the playlist 40878 * entries in the original master playlist, null is returned. 40879 * 40880 * @param {Object} master a parsed master M3U8 object 40881 * @param {Object} media a parsed media M3U8 object 40882 * @return {Object} a new object that represents the original 40883 * master playlist with the updated media playlist merged in, or 40884 * null if the merge produced no change. 40885 */ 40886 40887 40888 var updateMaster$1 = function updateMaster(master, newMedia, unchangedCheck) { 40889 if (unchangedCheck === void 0) { 40890 unchangedCheck = isPlaylistUnchanged; 40891 } 40892 40893 var result = mergeOptions$2(master, {}); 40894 var oldMedia = result.playlists[newMedia.id]; 40895 40896 if (!oldMedia) { 40897 return null; 40898 } 40899 40900 if (unchangedCheck(oldMedia, newMedia)) { 40901 return null; 40902 } 40903 40904 newMedia.segments = getAllSegments(newMedia); 40905 var mergedPlaylist = mergeOptions$2(oldMedia, newMedia); // always use the new media's preload segment 40906 40907 if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) { 40908 delete mergedPlaylist.preloadSegment; 40909 } // if the update could overlap existing segment information, merge the two segment lists 40910 40911 40912 if (oldMedia.segments) { 40913 if (newMedia.skip) { 40914 newMedia.segments = newMedia.segments || []; // add back in objects for skipped segments, so that we merge 40915 // old properties into the new segments 40916 40917 for (var i = 0; i < newMedia.skip.skippedSegments; i++) { 40918 newMedia.segments.unshift({ 40919 skipped: true 40920 }); 40921 } 40922 } 40923 40924 mergedPlaylist.segments = updateSegments(oldMedia.segments, newMedia.segments, newMedia.mediaSequence - oldMedia.mediaSequence); 40925 } // resolve any segment URIs to prevent us from having to do it later 40926 40927 40928 mergedPlaylist.segments.forEach(function (segment) { 40929 resolveSegmentUris(segment, mergedPlaylist.resolvedUri); 40930 }); // TODO Right now in the playlists array there are two references to each playlist, one 40931 // that is referenced by index, and one by URI. The index reference may no longer be 40932 // necessary. 40933 40934 for (var _i = 0; _i < result.playlists.length; _i++) { 40935 if (result.playlists[_i].id === newMedia.id) { 40936 result.playlists[_i] = mergedPlaylist; 40937 } 40938 } 40939 40940 result.playlists[newMedia.id] = mergedPlaylist; // URI reference added for backwards compatibility 40941 40942 result.playlists[newMedia.uri] = mergedPlaylist; // update media group playlist references. 40943
40944 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 40945 if (!properties.playlists) { 40946 return; 40947 } 40948 40949 for (var _i2 = 0; _i2 < properties.playlists.length; _i2++) { 40950 if (newMedia.id === properties.playlists[_i2].id) { 40951 properties.playlists[_i2] = newMedia; 40952 } 40953 } 40954 }); 40955 return result; 40956 }; 40957 /** 40958 * Calculates the time to wait before refreshing a live playlist 40959 * 40960 * @param {Object} media 40961 * The current media 40962 * @param {boolean} update 40963 * True if there were any updates from the last refresh, false otherwise 40964 * @return {number} 40965 * The time in ms to wait before refreshing the live playlist 40966 */ 40967 40968 40969 var refreshDelay = function refreshDelay(media, update) { 40970 var lastSegment = media.segments[media.segments.length - 1]; 40971 var lastPart = lastSegment && lastSegment.parts && lastSegment.parts[lastSegment.parts.length - 1]; 40972 var lastDuration = lastPart && lastPart.duration || lastSegment && lastSegment.duration; 40973 40974 if (update && lastDuration) { 40975 return lastDuration * 1000; 40976 } // if the playlist is unchanged since the last reload or last segment duration 40977 // cannot be determined, try again after half the target duration 40978 40979 40980 return (media.partTargetDuration || media.targetDuration || 10) * 500; 40981 }; 40982 /** 40983 * Load a playlist from a remote location 40984 * 40985 * @class PlaylistLoader 40986 * @extends Stream 40987 * @param {string|Object} src url or object of manifest 40988 * @param {boolean} withCredentials the withCredentials xhr option 40989 * @class 40990 */ 40991 40992 40993 var PlaylistLoader = /*#__PURE__*/function (_EventTarget) { 40994 inheritsLoose(PlaylistLoader, _EventTarget); 40995 40996 function PlaylistLoader(src, vhs, options) { 40997 var _this; 40998 40999 if (options === void 0) { 41000 options = {}; 41001 } 41002 41003 _this = _EventTarget.call(this) || this; 41004 41005 if (!src) { 41006 throw new Error('A non-empty playlist URL or object is required'); 41007 } 41008 41009 _this.logger_ = logger('PlaylistLoader'); 41010 var _options = options, 41011 _options$withCredenti = _options.withCredentials, 41012 withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti, 41013 _options$handleManife = _options.handleManifestRedirects, 41014 handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife; 41015 _this.src = src; 41016 _this.vhs_ = vhs; 41017 _this.withCredentials = withCredentials; 41018 _this.handleManifestRedirects = handleManifestRedirects; 41019 var vhsOptions = vhs.options_; 41020 _this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || []; 41021 _this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || []; 41022 _this.experimentalLLHLS = vhsOptions && vhsOptions.experimentalLLHLS || false; // initialize the loader state 41023 41024 _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout 41025 41026 _this.handleMediaupdatetimeout_ = _this.handleMediaupdatetimeout_.bind(assertThisInitialized(_this)); 41027 41028 _this.on('mediaupdatetimeout', _this.handleMediaupdatetimeout_); 41029 41030 return _this; 41031 } 41032 41033 var _proto = PlaylistLoader.prototype; 41034 41035 _proto.handleMediaupdatetimeout_ = function handleMediaupdatetimeout_() { 41036 var _this2 = this; 41037 41038 if (this.state !== 'HAVE_METADATA') { 41039 // only refresh the media playlist if no other activity is going on 41040 return; 41041 } 41042 41043 var media = this.media(); 41044 var uri = resolveUrl(this.master.uri, media.uri); 41045 41046 if (this.experimentalLLHLS) { 41047 uri = addLLHLSQueryDirectives(uri, media); 41048 } 41049 41050 this.state = 'HAVE_CURRENT_METADATA'; 41051 this.request = this.vhs_.xhr({ 41052 uri: uri, 41053 withCredentials: this.withCredentials 41054 }, function (error, req) { 41055 // disposed 41056 if (!_this2.request) { 41057 return; 41058 } 41059 41060 if (error) { 41061 return _this2.playlistRequestError(_this2.request, _this2.media(), 'HAVE_METADATA'); 41062 } 41063 41064 _this2.haveMetadata({ 41065 playlistString: _this2.request.responseText, 41066 url: _this2.media().uri, 41067 id: _this2.media().id 41068 }); 41069 }); 41070 }; 41071 41072 _proto.playlistRequestError = function playlistRequestError(xhr, playlist, startingState) { 41073 var uri = playlist.uri, 41074 id = playlist.id; // any in-flight request is now finished 41075 41076 this.request = null; 41077 41078 if (startingState) { 41079 this.state = startingState; 41080 } 41081 41082 this.error = { 41083 playlist: this.master.playlists[id], 41084 status: xhr.status, 41085 message: "HLS playlist request error at URL: " + uri + ".", 41086 responseText: xhr.responseText, 41087 code: xhr.status >= 500 ? 4 : 2 41088 }; 41089 this.trigger('error'); 41090 }; 41091 41092 _proto.parseManifest_ = function parseManifest_(_ref) { 41093 var _this3 = this; 41094 41095 var url = _ref.url, 41096 manifestString = _ref.manifestString; 41097 return parseManifest({ 41098 onwarn: function onwarn(_ref2) { 41099 var message = _ref2.message; 41100 return _this3.logger_("m3u8-parser warn for " + url + ": " + message); 41101 }, 41102 oninfo: function oninfo(_ref3) { 41103 var message = _ref3.message; 41104 return _this3.logger_("m3u8-parser info for " + url + ": " + message); 41105 }, 41106 manifestString: manifestString, 41107 customTagParsers: this.customTagParsers, 41108 customTagMappers: this.customTagMappers, 41109 experimentalLLHLS: this.experimentalLLHLS 41110 }); 41111 } 41112 /** 41113 * Update the playlist loader's state in response to a new or updated playlist. 41114 * 41115 * @param {string} [playlistString]
41116 * Playlist string (if playlistObject is not provided) 41117 * @param {Object} [playlistObject] 41118 * Playlist object (if playlistString is not provided) 41119 * @param {string} url 41120 * URL of playlist 41121 * @param {string} id 41122 * ID to use for playlist 41123 */ 41124 ; 41125 41126 _proto.haveMetadata = function haveMetadata(_ref4) { 41127 var playlistString = _ref4.playlistString, 41128 playlistObject = _ref4.playlistObject, 41129 url = _ref4.url, 41130 id = _ref4.id; // any in-flight request is now finished 41131 41132 this.request = null; 41133 this.state = 'HAVE_METADATA'; 41134 var playlist = playlistObject || this.parseManifest_({ 41135 url: url, 41136 manifestString: playlistString 41137 }); 41138 playlist.lastRequest = Date.now(); 41139 setupMediaPlaylist({ 41140 playlist: playlist, 41141 uri: url, 41142 id: id 41143 }); // merge this playlist into the master 41144 41145 var update = updateMaster$1(this.master, playlist); 41146 this.targetDuration = playlist.partTargetDuration || playlist.targetDuration; 41147 41148 if (update) { 41149 this.master = update; 41150 this.media_ = this.master.playlists[id]; 41151 } else { 41152 this.trigger('playlistunchanged'); 41153 } 41154 41155 this.updateMediaUpdateTimeout_(refreshDelay(this.media(), !!update)); 41156 this.trigger('loadedplaylist'); 41157 } 41158 /** 41159 * Abort any outstanding work and clean up. 41160 */ 41161 ; 41162 41163 _proto.dispose = function dispose() { 41164 this.trigger('dispose'); 41165 this.stopRequest(); 41166 window_1.clearTimeout(this.mediaUpdateTimeout); 41167 window_1.clearTimeout(this.finalRenditionTimeout); 41168 this.off(); 41169 }; 41170 41171 _proto.stopRequest = function stopRequest() { 41172 if (this.request) { 41173 var oldRequest = this.request; 41174 this.request = null; 41175 oldRequest.onreadystatechange = null; 41176 oldRequest.abort(); 41177 } 41178 } 41179 /** 41180 * When called without any arguments, returns the currently 41181 * active media playlist. When called with a single argument, 41182 * triggers the playlist loader to asynchronously switch to the 41183 * specified media playlist. Calling this method while the 41184 * loader is in the HAVE_NOTHING causes an error to be emitted 41185 * but otherwise has no effect. 41186 * 41187 * @param {Object=} playlist the parsed media playlist 41188 * object to switch to 41189 * @param {boolean=} shouldDelay whether we should delay the request by half target duration 41190 * 41191 * @return {Playlist} the current loaded media 41192 */ 41193 ; 41194 41195 _proto.media = function media(playlist, shouldDelay) { 41196 var _this4 = this; // getter 41197 41198 41199 if (!playlist) { 41200 return this.media_; 41201 } // setter 41202 41203 41204 if (this.state === 'HAVE_NOTHING') { 41205 throw new Error('Cannot switch media playlist from ' + this.state); 41206 } // find the playlist object if the target playlist has been 41207 // specified by URI 41208 41209 41210 if (typeof playlist === 'string') { 41211 if (!this.master.playlists[playlist]) { 41212 throw new Error('Unknown playlist URI: ' + playlist); 41213 } 41214 41215 playlist = this.master.playlists[playlist]; 41216 } 41217 41218 window_1.clearTimeout(this.finalRenditionTimeout); 41219 41220 if (shouldDelay) { 41221 var delay = (playlist.partTargetDuration || playlist.targetDuration) / 2 * 1000 || 5 * 1000; 41222 this.finalRenditionTimeout = window_1.setTimeout(this.media.bind(this, playlist, false), delay); 41223 return; 41224 } 41225 41226 var startingState = this.state; 41227 var mediaChange = !this.media_ || playlist.id !== this.media_.id; 41228 var masterPlaylistRef = this.master.playlists[playlist.id]; // switch to fully loaded playlists immediately 41229 41230 if (masterPlaylistRef && masterPlaylistRef.endList || // handle the case of a playlist object (e.g., if using vhs-json with a resolved 41231 // media playlist or, for the case of demuxed audio, a resolved audio media group) 41232 playlist.endList && playlist.segments.length) { 41233 // abort outstanding playlist requests 41234 if (this.request) { 41235 this.request.onreadystatechange = null; 41236 this.request.abort(); 41237 this.request = null; 41238 } 41239 41240 this.state = 'HAVE_METADATA'; 41241 this.media_ = playlist; // trigger media change if the active media has been updated 41242 41243 if (mediaChange) { 41244 this.trigger('mediachanging'); 41245 41246 if (startingState === 'HAVE_MASTER') { 41247 // The initial playlist was a master manifest, and the first media selected was 41248 // also provided (in the form of a resolved playlist object) as part of the 41249 // source object (rather than just a URL). Therefore, since the media playlist 41250 // doesn't need to be requested, loadedmetadata won't trigger as part of the 41251 // normal flow, and needs an explicit trigger here. 41252 this.trigger('loadedmetadata'); 41253 } else { 41254 this.trigger('mediachange'); 41255 } 41256 } 41257 41258 return; 41259 } // We update/set the timeout here so that live playlists 41260 // that are not a media change will "start" the loader as expected. 41261 // We expect that this function will start the media update timeout 41262 // cycle again. This also prevents a playlist switch failure from 41263 // causing us to stall during live. 41264 41265 41266 this.updateMediaUpdateTimeout_(refreshDelay(playlist, true)); // switching to the active playlist is a no-op 41267 41268 if (!mediaChange) { 41269 return; 41270 } 41271 41272 this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request 41273 41274 if (this.request) { 41275 if (playlist.resolvedUri === this.request.url) { 41276 // requesting to switch to the same playlist multiple times 41277 // has no effect after the first 41278 return; 41279 } 41280 41281 this.request.onreadystatechange = null; 41282 this.request.abort(); 41283 this.request = null; 41284 } // request the new playlist 41285 41286 41287 if (this.media_) { 41288 this.trigger('mediachanging'); 41289 } 41290 41291 this.request = this.vhs_.xhr({ 41292 uri: playlist.resolvedUri, 41293 withCredentials: this.withCredentials 41294 }, function (error, req) { 41295 // disposed 41296 if (!_this4.request) { 41297 return; 41298 } 41299 41300 playlist.lastRequest = Date.now(); 41301 playlist.resolvedUri = resolveManifestRedirect(_this4.handleManifestRedirects, playlist.resolvedUri, req); 41302 41303 if (error) { 41304 return _this4.playlistRequestError(_this4.request, playlist, startingState); 41305 } 41306 41307 _this4.haveMetadata({ 41308 playlistString: req.responseText, 41309 url: playlist.uri, 41310 id: playlist.id 41311 }); // fire loadedmetadata the first time a media playlist is loaded 41312 41313 41314 if (startingState === 'HAVE_MASTER') { 41315 _this4.trigger('loadedmetadata'); 41316 } else { 41317 _this4.trigger('mediachange'); 41318 } 41319 }); 41320 } 41321 /** 41322 * pause loading of the playlist 41323 */ 41324 ; 41325 41326 _proto.pause = function pause() { 41327 if (this.mediaUpdateTimeout) { 41328 window_1.clearTimeout(this.mediaUpdateTimeout); 41329 this.mediaUpdateTimeout = null; 41330 } 41331 41332 this.stopRequest(); 41333 41334 if (this.state === 'HAVE_NOTHING') { 41335 // If we pause the loader before any data has been retrieved, its as if we never 41336 // started, so reset to an unstarted state. 41337 this.started = false; 41338 } // Need to restore state now that no activity is happening 41339 41340 41341 if (this.state === 'SWITCHING_MEDIA') { 41342 // if the loader was in the process of switching media, it should either return to 41343 // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media 41344 // playlist yet. This is determined by the existence of loader.media_ 41345 if (this.media_) { 41346 this.state = 'HAVE_METADATA'; 41347 } else { 41348 this.state = 'HAVE_MASTER'; 41349 } 41350 } else if (this.state === 'HAVE_CURRENT_METADATA') { 41351 this.state = 'HAVE_METADATA'; 41352 } 41353 } 41354 /** 41355 * start loading of the playlist 41356 */ 41357 ; 41358 41359 _proto.load = function load(shouldDelay) { 41360 var _this5 = this; 41361 41362 if (this.mediaUpdateTimeout) { 41363 window_1.clearTimeout(this.mediaUpdateTimeout); 41364 this.mediaUpdateTimeout = null; 41365 } 41366 41367 var media = this.media(); 41368 41369 if (shouldDelay) {
41370 var delay = media ? (media.partTargetDuration || media.targetDuration) / 2 * 1000 : 5 * 1000; 41371 this.mediaUpdateTimeout = window_1.setTimeout(function () { 41372 _this5.mediaUpdateTimeout = null; 41373 41374 _this5.load(); 41375 }, delay); 41376 return; 41377 } 41378 41379 if (!this.started) { 41380 this.start(); 41381 return; 41382 } 41383 41384 if (media && !media.endList) { 41385 this.trigger('mediaupdatetimeout'); 41386 } else { 41387 this.trigger('loadedplaylist'); 41388 } 41389 }; 41390 41391 _proto.updateMediaUpdateTimeout_ = function updateMediaUpdateTimeout_(delay) { 41392 var _this6 = this; 41393 41394 if (this.mediaUpdateTimeout) { 41395 window_1.clearTimeout(this.mediaUpdateTimeout); 41396 this.mediaUpdateTimeout = null; 41397 } // we only have use mediaupdatetimeout for live playlists. 41398 41399 41400 if (!this.media() || this.media().endList) { 41401 return; 41402 } 41403 41404 this.mediaUpdateTimeout = window_1.setTimeout(function () { 41405 _this6.mediaUpdateTimeout = null; 41406 41407 _this6.trigger('mediaupdatetimeout'); 41408 41409 _this6.updateMediaUpdateTimeout_(delay); 41410 }, delay); 41411 } 41412 /** 41413 * start loading of the playlist 41414 */ 41415 ; 41416 41417 _proto.start = function start() { 41418 var _this7 = this; 41419 41420 this.started = true; 41421 41422 if (typeof this.src === 'object') { 41423 // in the case of an entirely constructed manifest object (meaning there's no actual 41424 // manifest on a server), default the uri to the page's href 41425 if (!this.src.uri) { 41426 this.src.uri = window_1.location.href; 41427 } // resolvedUri is added on internally after the initial request. Since there's no 41428 // request for pre-resolved manifests, add on resolvedUri here. 41429 41430 41431 this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first 41432 // request can be skipped (since the top level of the manifest, at a minimum, is 41433 // already available as a parsed manifest object). However, if the manifest object 41434 // represents a master playlist, some media playlists may need to be resolved before 41435 // the starting segment list is available. Therefore, go directly to setup of the 41436 // initial playlist, and let the normal flow continue from there. 41437 // 41438 // Note that the call to setup is asynchronous, as other sections of VHS may assume 41439 // that the first request is asynchronous. 41440 41441 setTimeout(function () { 41442 _this7.setupInitialPlaylist(_this7.src); 41443 }, 0); 41444 return; 41445 } // request the specified URL 41446 41447 41448 this.request = this.vhs_.xhr({ 41449 uri: this.src, 41450 withCredentials: this.withCredentials 41451 }, function (error, req) { 41452 // disposed 41453 if (!_this7.request) { 41454 return; 41455 } // clear the loader's request reference 41456 41457 41458 _this7.request = null; 41459 41460 if (error) { 41461 _this7.error = { 41462 status: req.status, 41463 message: "HLS playlist request error at URL: " + _this7.src + ".", 41464 responseText: req.responseText, 41465 // MEDIA_ERR_NETWORK 41466 code: 2 41467 }; 41468 41469 if (_this7.state === 'HAVE_NOTHING') { 41470 _this7.started = false; 41471 } 41472 41473 return _this7.trigger('error'); 41474 } 41475 41476 _this7.src = resolveManifestRedirect(_this7.handleManifestRedirects, _this7.src, req); 41477 41478 var manifest = _this7.parseManifest_({ 41479 manifestString: req.responseText, 41480 url: _this7.src 41481 }); 41482 41483 _this7.setupInitialPlaylist(manifest); 41484 }); 41485 }; 41486 41487 _proto.srcUri = function srcUri() { 41488 return typeof this.src === 'string' ? this.src : this.src.uri; 41489 } 41490 /** 41491 * Given a manifest object that's either a master or media playlist, trigger the proper 41492 * events and set the state of the playlist loader. 41493 * 41494 * If the manifest object represents a master playlist, `loadedplaylist` will be 41495 * triggered to allow listeners to select a playlist. If none is selected, the loader 41496 * will default to the first one in the playlists array. 41497 * 41498 * If the manifest object represents a media playlist, `loadedplaylist` will be 41499 * triggered followed by `loadedmetadata`, as the only available playlist is loaded. 41500 * 41501 * In the case of a media playlist, a master playlist object wrapper with one playlist 41502 * will be created so that all logic can handle playlists in the same fashion (as an 41503 * assumed manifest object schema). 41504 * 41505 * @param {Object} manifest 41506 * The parsed manifest object 41507 */ 41508 ; 41509 41510 _proto.setupInitialPlaylist = function setupInitialPlaylist(manifest) { 41511 this.state = 'HAVE_MASTER'; 41512 41513 if (manifest.playlists) { 41514 this.master = manifest; 41515 addPropertiesToMaster(this.master, this.srcUri()); // If the initial master playlist has playlists wtih segments already resolved, 41516 // then resolve URIs in advance, as they are usually done after a playlist request, 41517 // which may not happen if the playlist is resolved. 41518
41519 manifest.playlists.forEach(function (playlist) { 41520 playlist.segments = getAllSegments(playlist); 41521 playlist.segments.forEach(function (segment) { 41522 resolveSegmentUris(segment, playlist.resolvedUri); 41523 }); 41524 }); 41525 this.trigger('loadedplaylist'); 41526 41527 if (!this.request) { 41528 // no media playlist was specifically selected so start 41529 // from the first listed one 41530 this.media(this.master.playlists[0]); 41531 } 41532 41533 return; 41534 } // In order to support media playlists passed in as vhs-json, the case where the uri 41535 // is not provided as part of the manifest should be considered, and an appropriate 41536 // default used. 41537 41538 41539 var uri = this.srcUri() || window_1.location.href; 41540 this.master = masterForMedia(manifest, uri); 41541 this.haveMetadata({ 41542 playlistObject: manifest, 41543 url: uri, 41544 id: this.master.playlists[0].id 41545 }); 41546 this.trigger('loadedmetadata'); 41547 }; 41548 41549 return PlaylistLoader; 41550 }(EventTarget$1); 41551 /** 41552 * @file xhr.js 41553 */ 41554 41555 41556 var videojsXHR = videojs.xhr, 41557 mergeOptions$1 = videojs.mergeOptions; 41558 41559 var callbackWrapper = function callbackWrapper(request, error, response, callback) { 41560 var reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText; 41561 41562 if (!error && reqResponse) { 41563 request.responseTime = Date.now(); 41564 request.roundTripTime = request.responseTime - request.requestTime; 41565 request.bytesReceived = reqResponse.byteLength || reqResponse.length; 41566 41567 if (!request.bandwidth) { 41568 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 41569 } 41570 } 41571 41572 if (response.headers) { 41573 request.responseHeaders = response.headers; 41574 } // videojs.xhr now uses a specific code on the error 41575 // object to signal that a request has timed out instead 41576 // of setting a boolean on the request object 41577 41578 41579 if (error && error.code === 'ETIMEDOUT') { 41580 request.timedout = true; 41581 } // videojs.xhr no longer considers status codes outside of 200 and 0 41582 // (for file uris) to be errors, but the old XHR did, so emulate that 41583 // behavior. Status 206 may be used in response to byterange requests. 41584 41585 41586 if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 41587 error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))); 41588 } 41589 41590 callback(error, request); 41591 }; 41592 41593 var xhrFactory = function xhrFactory() { 41594 var xhr = function XhrFunction(options, callback) { 41595 // Add a default timeout 41596 options = mergeOptions$1({ 41597 timeout: 45e3 41598 }, options); // Allow an optional user-specified function to modify the option 41599 // object before we construct the xhr request 41600 41601 var beforeRequest = XhrFunction.beforeRequest || videojs.Vhs.xhr.beforeRequest; 41602 41603 if (beforeRequest && typeof beforeRequest === 'function') { 41604 var newOptions = beforeRequest(options); 41605 41606 if (newOptions) { 41607 options = newOptions; 41608 } 41609 } // Use the standard videojs.xhr() method unless `videojs.Vhs.xhr` has been overriden 41610 // TODO: switch back to videojs.Vhs.xhr.name === 'XhrFunction' when we drop IE11 41611 41612 41613 var xhrMethod = videojs.Vhs.xhr.original === true ? videojsXHR : videojs.Vhs.xhr; 41614 var request = xhrMethod(options, function (error, response) { 41615 return callbackWrapper(request, error, response, callback); 41616 }); 41617 var originalAbort = request.abort; 41618 41619 request.abort = function () { 41620 request.aborted = true; 41621 return originalAbort.apply(request, arguments); 41622 }; 41623 41624 request.uri = options.uri; 41625 request.requestTime = Date.now(); 41626 return request; 41627 }; 41628 41629 xhr.original = true; 41630 return xhr; 41631 }; 41632 /** 41633 * Turns segment byterange into a string suitable for use in 41634 * HTTP Range requests 41635 * 41636 * @param {Object} byterange - an object with two values defining the start and end 41637 * of a byte-range 41638 */ 41639 41640 41641 var byterangeStr = function byterangeStr(byterange) { 41642 // `byterangeEnd` is one less than `offset + length` because the HTTP range 41643 // header uses inclusive ranges 41644 var byterangeEnd = byterange.offset + byterange.length - 1; 41645 var byterangeStart = byterange.offset; 41646 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 41647 }; 41648 /** 41649 * Defines headers for use in the xhr request for a particular segment. 41650 * 41651 * @param {Object} segment - a simplified copy of the segmentInfo object 41652 * from SegmentLoader 41653 */ 41654 41655 41656 var segmentXhrHeaders = function segmentXhrHeaders(segment) { 41657 var headers = {}; 41658 41659 if (segment.byterange) { 41660 headers.Range = byterangeStr(segment.byterange); 41661 } 41662 41663 return headers; 41664 }; 41665 /** 41666 * @file bin-utils.js 41667 */ 41668 41669 /** 41670 * convert a TimeRange to text 41671 * 41672 * @param {TimeRange} range the timerange to use for conversion 41673 * @param {number} i the iterator on the range to convert 41674 * @return {string} the range in string format 41675 */ 41676 41677 41678 var textRange = function textRange(range, i) { 41679 return range.start(i) + '-' + range.end(i); 41680 }; 41681 /** 41682 * format a number as hex string 41683 * 41684 * @param {number} e The number 41685 * @param {number} i the iterator 41686 * @return {string} the hex formatted number as a string 41687 */ 41688 41689 41690 var formatHexString = function formatHexString(e, i) { 41691 var value = e.toString(16); 41692 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 41693 }; 41694 41695 var formatAsciiString = function formatAsciiString(e) { 41696 if (e >= 0x20 && e < 0x7e) { 41697 return String.fromCharCode(e); 41698 } 41699 41700 return '.'; 41701 }; 41702 /** 41703 * Creates an object for sending to a web worker modifying properties that are TypedArrays 41704 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 41705 * 41706 * @param {Object} message 41707 * Object of properties and values to send to the web worker 41708 * @return {Object} 41709 * Modified message with TypedArray values expanded 41710 * @function createTransferableMessage 41711 */ 41712 41713 41714 var createTransferableMessage = function createTransferableMessage(message) { 41715 var transferable = {}; 41716 Object.keys(message).forEach(function (key) { 41717 var value = message[key]; 41718 41719 if (ArrayBuffer.isView(value)) { 41720 transferable[key] = { 41721 bytes: value.buffer, 41722 byteOffset: value.byteOffset, 41723 byteLength: value.byteLength 41724 }; 41725 } else { 41726 transferable[key] = value; 41727 } 41728 });
41729 return transferable; 41730 }; 41731 /** 41732 * Returns a unique string identifier for a media initialization 41733 * segment. 41734 * 41735 * @param {Object} initSegment 41736 * the init segment object. 41737 * 41738 * @return {string} the generated init segment id 41739 */ 41740 41741 41742 var initSegmentId = function initSegmentId(initSegment) { 41743 var byterange = initSegment.byterange || { 41744 length: Infinity, 41745 offset: 0 41746 }; 41747 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 41748 }; 41749 /** 41750 * Returns a unique string identifier for a media segment key. 41751 * 41752 * @param {Object} key the encryption key 41753 * @return {string} the unique id for the media segment key. 41754 */ 41755 41756 41757 var segmentKeyId = function segmentKeyId(key) { 41758 return key.resolvedUri; 41759 }; 41760 /** 41761 * utils to help dump binary data to the console 41762 * 41763 * @param {Array|TypedArray} data 41764 * data to dump to a string 41765 * 41766 * @return {string} the data as a hex string. 41767 */ 41768 41769 41770 var hexDump = function hexDump(data) { 41771 var bytes = Array.prototype.slice.call(data); 41772 var step = 16; 41773 var result = ''; 41774 var hex; 41775 var ascii; 41776 41777 for (var j = 0; j < bytes.length / step; j++) { 41778 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 41779 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 41780 result += hex + ' ' + ascii + '\n'; 41781 } 41782 41783 return result; 41784 }; 41785 41786 var tagDump = function tagDump(_ref) { 41787 var bytes = _ref.bytes; 41788 return hexDump(bytes); 41789 }; 41790 41791 var textRanges = function textRanges(ranges) { 41792 var result = ''; 41793 var i; 41794 41795 for (i = 0; i < ranges.length; i++) { 41796 result += textRange(ranges, i) + ' '; 41797 } 41798 41799 return result; 41800 }; 41801 41802 var utils = /*#__PURE__*/Object.freeze({ 41803 __proto__: null, 41804 createTransferableMessage: createTransferableMessage, 41805 initSegmentId: initSegmentId, 41806 segmentKeyId: segmentKeyId, 41807 hexDump: hexDump, 41808 tagDump: tagDump, 41809 textRanges: textRanges 41810 }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux 41811 // 25% was arbitrarily chosen, and may need to be refined over time. 41812 41813 var SEGMENT_END_FUDGE_PERCENT = 0.25; 41814 /** 41815 * Converts a player time (any time that can be gotten/set from player.currentTime(), 41816 * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a 41817 * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)). 41818 * 41819 * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor 41820 * point" (a point where we have a mapping from program time to player time, with player 41821 * time being the post transmux start of the segment). 41822 * 41823 * For more details, see [this doc](../../docs/program-time-from-player-time.md). 41824 * 41825 * @param {number} playerTime the player time 41826 * @param {Object} segment the segment which contains the player time 41827 * @return {Date} program time 41828 */ 41829 41830 var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) { 41831 if (!segment.dateTimeObject) { 41832 // Can't convert without an "anchor point" for the program time (i.e., a time that can 41833 // be used to map the start of a segment with a real world time). 41834 return null; 41835 } 41836 41837 var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds; 41838 var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended 41839 41840 var startOfSegment = transmuxedStart + transmuxerPrependedSeconds; 41841 var offsetFromSegmentStart = playerTime - startOfSegment; 41842 return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000); 41843 }; 41844 41845 var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) { 41846 return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds; 41847 }; 41848 /** 41849 * Finds a segment that contains the time requested given as an ISO-8601 string. The 41850 * returned segment might be an estimate or an accurate match. 41851 * 41852 * @param {string} programTime The ISO-8601 programTime to find a match for 41853 * @param {Object} playlist A playlist object to search within 41854 */ 41855 41856 41857 var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) { 41858 // Assumptions: 41859 // - verifyProgramDateTimeTags has already been run 41860 // - live streams have been started 41861 var dateTimeObject; 41862 41863 try { 41864 dateTimeObject = new Date(programTime); 41865 } catch (e) { 41866 return null; 41867 } 41868 41869 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 41870 return null; 41871 } 41872 41873 var segment = playlist.segments[0]; 41874 41875 if (dateTimeObject < segment.dateTimeObject) { 41876 // Requested time is before stream start. 41877 return null; 41878 } 41879 41880 for (var i = 0; i < playlist.segments.length - 1; i++) { 41881 segment = playlist.segments[i]; 41882 var nextSegmentStart = playlist.segments[i + 1].dateTimeObject; 41883 41884 if (dateTimeObject < nextSegmentStart) { 41885 break; 41886 } 41887 } 41888 41889 var lastSegment = playlist.segments[playlist.segments.length - 1]; 41890 var lastSegmentStart = lastSegment.dateTimeObject; 41891 var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT; 41892 var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000); 41893 41894 if (dateTimeObject > lastSegmentEnd) { 41895 // Beyond the end of the stream, or our best guess of the end of the stream. 41896 return null; 41897 } 41898 41899 if (dateTimeObject > lastSegmentStart) { 41900 segment = lastSegment; 41901 } 41902 41903 return { 41904 segment: segment, 41905 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)), 41906 // Although, given that all segments have accurate date time objects, the segment 41907 // selected should be accurate, unless the video has been transmuxed at some point 41908 // (determined by the presence of the videoTimingInfo object), the segment's "player 41909 // time" (the start time in the player) can't be considered accurate. 41910 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 41911 }; 41912 }; 41913 /** 41914 * Finds a segment that contains the given player time(in seconds). 41915 * 41916 * @param {number} time The player time to find a match for 41917 * @param {Object} playlist A playlist object to search within 41918 */ 41919 41920 41921 var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) { 41922 // Assumptions: 41923 // - there will always be a segment.duration 41924 // - we can start from zero
41925 // - segments are in time order 41926 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 41927 return null; 41928 } 41929 41930 var segmentEnd = 0; 41931 var segment; 41932 41933 for (var i = 0; i < playlist.segments.length; i++) { 41934 segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and 41935 // should contain the most accurate values we have for the segment's player times. 41936 // 41937 // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall 41938 // back to an estimate based on the manifest derived (inaccurate) segment.duration, to 41939 // calculate an end value. 41940 41941 segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration; 41942 41943 if (time <= segmentEnd) { 41944 break; 41945 } 41946 } 41947 41948 var lastSegment = playlist.segments[playlist.segments.length - 1]; 41949 41950 if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) { 41951 // The time requested is beyond the stream end. 41952 return null; 41953 } 41954 41955 if (time > segmentEnd) { 41956 // The time is within or beyond the last segment. 41957 // 41958 // Check to see if the time is beyond a reasonable guess of the end of the stream. 41959 if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) { 41960 // Technically, because the duration value is only an estimate, the time may still 41961 // exist in the last segment, however, there isn't enough information to make even 41962 // a reasonable estimate. 41963 return null; 41964 } 41965 41966 segment = lastSegment; 41967 } 41968 41969 return { 41970 segment: segment, 41971 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration, 41972 // Because videoTimingInfo is only set after transmux, it is the only way to get 41973 // accurate timing values. 41974 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 41975 }; 41976 }; 41977 /** 41978 * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds. 41979 * If the offset returned is positive, the programTime occurs after the 41980 * comparisonTimestamp. 41981 * If the offset is negative, the programTime occurs before the comparisonTimestamp. 41982 * 41983 * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against 41984 * @param {string} programTime The programTime as an ISO-8601 string 41985 * @return {number} offset 41986 */ 41987 41988 41989 var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) { 41990 var segmentDateTime; 41991 var programDateTime; 41992 41993 try { 41994 segmentDateTime = new Date(comparisonTimeStamp); 41995 programDateTime = new Date(programTime); 41996 } catch (e) {// TODO handle error 41997 } 41998 41999 var segmentTimeEpoch = segmentDateTime.getTime(); 42000 var programTimeEpoch = programDateTime.getTime(); 42001 return (programTimeEpoch - segmentTimeEpoch) / 1000; 42002 }; 42003 /** 42004 * Checks that all segments in this playlist have programDateTime tags. 42005 * 42006 * @param {Object} playlist A playlist object 42007 */ 42008 42009 42010 var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) { 42011 if (!playlist.segments || playlist.segments.length === 0) { 42012 return false; 42013 } 42014 42015 for (var i = 0; i < playlist.segments.length; i++) { 42016 var segment = playlist.segments[i]; 42017 42018 if (!segment.dateTimeObject) { 42019 return false; 42020 } 42021 } 42022 42023 return true; 42024 }; 42025 /** 42026 * Returns the programTime of the media given a playlist and a playerTime. 42027 * The playlist must have programDateTime tags for a programDateTime tag to be returned. 42028 * If the segments containing the time requested have not been buffered yet, an estimate 42029 * may be returned to the callback. 42030 * 42031 * @param {Object} args 42032 * @param {Object} args.playlist A playlist object to search within 42033 * @param {number} time A playerTime in seconds 42034 * @param {Function} callback(err, programTime) 42035 * @return {string} err.message A detailed error message 42036 * @return {Object} programTime 42037 * @return {number} programTime.mediaSeconds The streamTime in seconds 42038 * @return {string} programTime.programDateTime The programTime as an ISO-8601 String 42039 */ 42040 42041 42042 var getProgramTime = function getProgramTime(_ref) { 42043 var playlist = _ref.playlist, 42044 _ref$time = _ref.time, 42045 time = _ref$time === void 0 ? undefined : _ref$time, 42046 callback = _ref.callback; 42047 42048 if (!callback) { 42049 throw new Error('getProgramTime: callback must be provided'); 42050 } 42051 42052 if (!playlist || time === undefined) { 42053 return callback({ 42054 message: 'getProgramTime: playlist and time must be provided' 42055 }); 42056 } 42057 42058 var matchedSegment = findSegmentForPlayerTime(time, playlist); 42059 42060 if (!matchedSegment) { 42061 return callback({ 42062 message: 'valid programTime was not found' 42063 }); 42064 } 42065 42066 if (matchedSegment.type === 'estimate') { 42067 return callback({ 42068 message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again', 42069 seekTime: matchedSegment.estimatedStart 42070 }); 42071 } 42072 42073 var programTimeObject = { 42074 mediaSeconds: time 42075 }; 42076 var programTime = playerTimeToProgramTime(time, matchedSegment.segment); 42077 42078 if (programTime) { 42079 programTimeObject.programDateTime = programTime.toISOString(); 42080 } 42081 42082 return callback(null, programTimeObject); 42083 }; 42084 /** 42085 * Seeks in the player to a time that matches the given programTime ISO-8601 string. 42086 * 42087 * @param {Object} args 42088 * @param {string} args.programTime A programTime to seek to as an ISO-8601 String 42089 * @param {Object} args.playlist A playlist to look within 42090 * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2. 42091 * @param {Function} args.seekTo A method to perform a seek 42092 * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true. 42093 * @param {Object} args.tech The tech to seek on 42094 * @param {Function} args.callback(err, newTime) A callback to return the new time to 42095 * @return {string} err.message A detailed error message 42096 * @return {number} newTime The exact time that was seeked to in seconds 42097 */ 42098 42099 42100 var seekToProgramTime = function seekToProgramTime(_ref2) { 42101 var programTime = _ref2.programTime, 42102 playlist = _ref2.playlist, 42103 _ref2$retryCount = _ref2.retryCount, 42104 retryCount = _ref2$retryCount === void 0 ? 2 : _ref2$retryCount, 42105 seekTo = _ref2.seekTo, 42106 _ref2$pauseAfterSeek = _ref2.pauseAfterSeek, 42107 pauseAfterSeek = _ref2$pauseAfterSeek === void 0 ? true : _ref2$pauseAfterSeek, 42108 tech = _ref2.tech, 42109 callback = _ref2.callback; 42110 42111 if (!callback) { 42112 throw new Error('seekToProgramTime: callback must be provided'); 42113 } 42114 42115 if (typeof programTime === 'undefined' || !playlist || !seekTo) { 42116 return callback({ 42117 message: 'seekToProgramTime: programTime, seekTo and playlist must be provided' 42118 }); 42119 } 42120 42121 if (!playlist.endList && !tech.hasStarted_) { 42122 return callback({ 42123 message: 'player must be playing a live stream to start buffering' 42124 }); 42125 } 42126 42127 if (!verifyProgramDateTimeTags(playlist)) { 42128 return callback({ 42129 message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri 42130 }); 42131 } 42132 42133 var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match 42134 42135 if (!matchedSegment) { 42136 return callback({ 42137 message: programTime + " was not found in the stream" 42138 }); 42139 } 42140 42141 var segment = matchedSegment.segment; 42142 var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime); 42143 42144 if (matchedSegment.type === 'estimate') { 42145 // we've run out of retries 42146 if (retryCount === 0) { 42147 return callback({ 42148 message: programTime + " is not buffered yet. Try again" 42149 }); 42150 } 42151 42152 seekTo(matchedSegment.estimatedStart + mediaOffset); 42153 tech.one('seeked', function () { 42154 seekToProgramTime({ 42155 programTime: programTime, 42156 playlist: playlist, 42157 retryCount: retryCount - 1, 42158 seekTo: seekTo, 42159 pauseAfterSeek: pauseAfterSeek, 42160 tech: tech, 42161 callback: callback 42162 }); 42163 }); 42164 return; 42165 } // Since the segment.start value is determined from the buffered end or ending time 42166 // of the prior segment, the seekToTime doesn't need to account for any transmuxer 42167 // modifications. 42168 42169 42170 var seekToTime = segment.start + mediaOffset; 42171 42172 var seekedCallback = function seekedCallback() { 42173 return callback(null, tech.currentTime()); 42174 }; // listen for seeked event 42175 42176 42177 tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state 42178 42179 if (pauseAfterSeek) { 42180 tech.pause(); 42181 } 42182 42183 seekTo(seekToTime); 42184 }; // which will only happen if the request is complete. 42185 42186 42187 var callbackOnCompleted = function callbackOnCompleted(request, cb) { 42188 if (request.readyState === 4) { 42189 return cb(); 42190 } 42191 42192 return; 42193 }; 42194
42195 var containerRequest = function containerRequest(uri, xhr, cb) { 42196 var bytes = []; 42197 var id3Offset; 42198 var finished = false; 42199 42200 var endRequestAndCallback = function endRequestAndCallback(err, req, type, _bytes) { 42201 req.abort(); 42202 finished = true; 42203 return cb(err, req, type, _bytes); 42204 }; 42205 42206 var progressListener = function progressListener(error, request) { 42207 if (finished) { 42208 return; 42209 } 42210 42211 if (error) { 42212 return endRequestAndCallback(error, request, '', bytes); 42213 } // grap the new part of content that was just downloaded 42214 42215 42216 var newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes 42217 42218 bytes = concatTypedArrays(bytes, stringToBytes(newPart, true)); 42219 id3Offset = id3Offset || getId3Offset(bytes); // we need at least 10 bytes to determine a type 42220 // or we need at least two bytes after an id3Offset 42221 42222 if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) { 42223 return callbackOnCompleted(request, function () { 42224 return endRequestAndCallback(error, request, '', bytes); 42225 }); 42226 } 42227 42228 var type = detectContainerForBytes(bytes); // if this looks like a ts segment but we don't have enough data 42229 // to see the second sync byte, wait until we have enough data 42230 // before declaring it ts 42231 42232 if (type === 'ts' && bytes.length < 188) { 42233 return callbackOnCompleted(request, function () { 42234 return endRequestAndCallback(error, request, '', bytes); 42235 }); 42236 } // this may be an unsynced ts segment 42237 // wait for 376 bytes before detecting no container 42238 42239 42240 if (!type && bytes.length < 376) { 42241 return callbackOnCompleted(request, function () { 42242 return endRequestAndCallback(error, request, '', bytes); 42243 }); 42244 } 42245 42246 return endRequestAndCallback(null, request, type, bytes); 42247 }; 42248 42249 var options = { 42250 uri: uri, 42251 beforeSend: function beforeSend(request) { 42252 // this forces the browser to pass the bytes to us unprocessed 42253 request.overrideMimeType('text/plain; charset=x-user-defined'); 42254 request.addEventListener('progress', function (_ref) { 42255 _ref.total; 42256 _ref.loaded; 42257 return callbackWrapper(request, null, { 42258 statusCode: request.status 42259 }, progressListener); 42260 }); 42261 } 42262 }; 42263 var request = xhr(options, function (error, response) { 42264 return callbackWrapper(request, error, response, progressListener); 42265 }); 42266 return request; 42267 }; 42268 42269 var EventTarget = videojs.EventTarget, 42270 mergeOptions = videojs.mergeOptions; 42271 42272 var dashPlaylistUnchanged = function dashPlaylistUnchanged(a, b) { 42273 if (!isPlaylistUnchanged(a, b)) { 42274 return false; 42275 } // for dash the above check will often return true in scenarios where 42276 // the playlist actually has changed because mediaSequence isn't a 42277 // dash thing, and we often set it to 1. So if the playlists have the same amount 42278 // of segments we return true. 42279 // So for dash we need to make sure that the underlying segments are different. 42280 // if sidx changed then the playlists are different. 42281 42282 42283 if (a.sidx && b.sidx && (a.sidx.offset !== b.sidx.offset || a.sidx.length !== b.sidx.length)) { 42284 return false; 42285 } else if (!a.sidx && b.sidx || a.sidx && !b.sidx) { 42286 return false; 42287 } // one or the other does not have segments 42288 // there was a change. 42289 42290 42291 if (a.segments && !b.segments || !a.segments && b.segments) { 42292 return false; 42293 } // neither has segments nothing changed 42294 42295 42296 if (!a.segments && !b.segments) { 42297 return true; 42298 } // check segments themselves 42299 42300 42301 for (var i = 0; i < a.segments.length; i++) { 42302 var aSegment = a.segments[i]; 42303 var bSegment = b.segments[i]; // if uris are different between segments there was a change 42304 42305 if (aSegment.uri !== bSegment.uri) { 42306 return false; 42307 } // neither segment has a byterange, there will be no byterange change. 42308 42309 42310 if (!aSegment.byterange && !bSegment.byterange) { 42311 continue; 42312 } 42313 42314 var aByterange = aSegment.byterange; 42315 var bByterange = bSegment.byterange; // if byterange only exists on one of the segments, there was a change. 42316 42317 if (aByterange && !bByterange || !aByterange && bByterange) { 42318 return false; 42319 } // if both segments have byterange with different offsets, there was a change. 42320 42321 42322 if (aByterange.offset !== bByterange.offset || aByterange.length !== bByterange.length) { 42323 return false; 42324 } 42325 } // if everything was the same with segments, this is the same playlist. 42326 42327 42328 return true; 42329 }; 42330 /** 42331 * Parses the master XML string and updates playlist URI references. 42332 * 42333 * @param {Object} config 42334 * Object of arguments 42335 * @param {string} config.masterXml 42336 * The mpd XML 42337 * @param {string} config.srcUrl 42338 * The mpd URL 42339 * @param {Date} config.clientOffset 42340 * A time difference between server and client 42341 * @param {Object} config.sidxMapping 42342 * SIDX mappings for moof/mdat URIs and byte ranges 42343 * @return {Object} 42344 * The parsed mpd manifest object 42345 */ 42346 42347 42348 var parseMasterXml = function parseMasterXml(_ref) { 42349 var masterXml = _ref.masterXml, 42350 srcUrl = _ref.srcUrl, 42351 clientOffset = _ref.clientOffset, 42352 sidxMapping = _ref.sidxMapping; 42353 var master = parse(masterXml, { 42354 manifestUri: srcUrl, 42355 clientOffset: clientOffset, 42356 sidxMapping: sidxMapping 42357 });
42358 addPropertiesToMaster(master, srcUrl); 42359 return master; 42360 }; 42361 /** 42362 * Returns a new master manifest that is the result of merging an updated master manifest 42363 * into the original version. 42364 * 42365 * @param {Object} oldMaster 42366 * The old parsed mpd object 42367 * @param {Object} newMaster 42368 * The updated parsed mpd object 42369 * @return {Object} 42370 * A new object representing the original master manifest with the updated media 42371 * playlists merged in 42372 */ 42373 42374 42375 var updateMaster = function updateMaster(oldMaster, newMaster, sidxMapping) { 42376 var noChanges = true; 42377 var update = mergeOptions(oldMaster, { 42378 // These are top level properties that can be updated 42379 duration: newMaster.duration, 42380 minimumUpdatePeriod: newMaster.minimumUpdatePeriod 42381 }); // First update the playlists in playlist list 42382 42383 for (var i = 0; i < newMaster.playlists.length; i++) { 42384 var playlist = newMaster.playlists[i]; 42385 42386 if (playlist.sidx) { 42387 var sidxKey = generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already 42388 42389 if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) { 42390 addSidxSegmentsToPlaylist(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri); 42391 } 42392 } 42393 42394 var playlistUpdate = updateMaster$1(update, playlist, dashPlaylistUnchanged); 42395 42396 if (playlistUpdate) { 42397 update = playlistUpdate; 42398 noChanges = false; 42399 } 42400 } // Then update media group playlists 42401 42402 42403 forEachMediaGroup(newMaster, function (properties, type, group, label) { 42404 if (properties.playlists && properties.playlists.length) { 42405 var id = properties.playlists[0].id; 42406 42407 var _playlistUpdate = updateMaster$1(update, properties.playlists[0], dashPlaylistUnchanged); 42408 42409 if (_playlistUpdate) { 42410 update = _playlistUpdate; // update the playlist reference within media groups 42411 42412 update.mediaGroups[type][group][label].playlists[0] = update.playlists[id]; 42413 noChanges = false; 42414 } 42415 } 42416 }); 42417 42418 if (newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) { 42419 noChanges = false; 42420 } 42421 42422 if (noChanges) { 42423 return null; 42424 } 42425 42426 return update; 42427 }; // SIDX should be equivalent if the URI and byteranges of the SIDX match. 42428 // If the SIDXs have maps, the two maps should match, 42429 // both `a` and `b` missing SIDXs is considered matching. 42430 // If `a` or `b` but not both have a map, they aren't matching. 42431 42432 42433 var equivalentSidx = function equivalentSidx(a, b) { 42434 var neitherMap = Boolean(!a.map && !b.map); 42435 var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length); 42436 return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length; 42437 }; // exported for testing 42438 42439 42440 var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) { 42441 var newSidxMapping = {}; 42442 42443 for (var id in playlists) { 42444 var playlist = playlists[id]; 42445 var currentSidxInfo = playlist.sidx; 42446 42447 if (currentSidxInfo) { 42448 var key = generateSidxKey(currentSidxInfo); 42449 42450 if (!oldSidxMapping[key]) { 42451 break; 42452 } 42453 42454 var savedSidxInfo = oldSidxMapping[key].sidxInfo; 42455 42456 if (equivalentSidx(savedSidxInfo, currentSidxInfo)) { 42457 newSidxMapping[key] = oldSidxMapping[key]; 42458 } 42459 } 42460 } 42461 42462 return newSidxMapping; 42463 }; 42464 /** 42465 * A function that filters out changed items as they need to be requested separately. 42466 * 42467 * The method is exported for testing 42468 * 42469 * @param {Object} master the parsed mpd XML returned via mpd-parser 42470 * @param {Object} oldSidxMapping the SIDX to compare against 42471 */ 42472 42473 42474 var filterChangedSidxMappings = function filterChangedSidxMappings(master, oldSidxMapping) { 42475 var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping); 42476 var mediaGroupSidx = videoSidx;
42477 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 42478 if (properties.playlists && properties.playlists.length) { 42479 var playlists = properties.playlists; 42480 mediaGroupSidx = mergeOptions(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping)); 42481 } 42482 }); 42483 return mediaGroupSidx; 42484 }; 42485 42486 var DashPlaylistLoader = /*#__PURE__*/function (_EventTarget) { 42487 inheritsLoose(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent 42488 // playlist loader setups from media groups will expect to be able to pass a playlist 42489 // (since there aren't external URLs to media playlists with DASH) 42490 42491 42492 function DashPlaylistLoader(srcUrlOrPlaylist, vhs, options, masterPlaylistLoader) { 42493 var _this; 42494 42495 if (options === void 0) { 42496 options = {}; 42497 } 42498 42499 _this = _EventTarget.call(this) || this; 42500 _this.masterPlaylistLoader_ = masterPlaylistLoader || assertThisInitialized(_this); 42501 42502 if (!masterPlaylistLoader) { 42503 _this.isMaster_ = true; 42504 } 42505 42506 var _options = options, 42507 _options$withCredenti = _options.withCredentials, 42508 withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti, 42509 _options$handleManife = _options.handleManifestRedirects, 42510 handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife; 42511 _this.vhs_ = vhs; 42512 _this.withCredentials = withCredentials; 42513 _this.handleManifestRedirects = handleManifestRedirects; 42514 42515 if (!srcUrlOrPlaylist) { 42516 throw new Error('A non-empty playlist URL or object is required'); 42517 } // event naming? 42518 42519 42520 _this.on('minimumUpdatePeriod', function () { 42521 _this.refreshXml_(); 42522 }); // live playlist staleness timeout 42523 42524 42525 _this.on('mediaupdatetimeout', function () { 42526 _this.refreshMedia_(_this.media().id); 42527 }); 42528 42529 _this.state = 'HAVE_NOTHING'; 42530 _this.loadedPlaylists_ = {}; 42531 _this.logger_ = logger('DashPlaylistLoader'); // initialize the loader state 42532 // The masterPlaylistLoader will be created with a string 42533 42534 if (_this.isMaster_) { 42535 _this.masterPlaylistLoader_.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes 42536 // once multi-period is refactored 42537 42538 _this.masterPlaylistLoader_.sidxMapping_ = {}; 42539 } else { 42540 _this.childPlaylist_ = srcUrlOrPlaylist; 42541 } 42542 42543 return _this; 42544 } 42545 42546 var _proto = DashPlaylistLoader.prototype; 42547 42548 _proto.requestErrored_ = function requestErrored_(err, request, startingState) { 42549 // disposed 42550 if (!this.request) { 42551 return true; 42552 } // pending request is cleared 42553 42554 42555 this.request = null; 42556 42557 if (err) { 42558 // use the provided error object or create one 42559 // based on the request/response 42560 this.error = typeof err === 'object' && !(err instanceof Error) ? err : { 42561 status: request.status, 42562 message: 'DASH request error at URL: ' + request.uri, 42563 response: request.response, 42564 // MEDIA_ERR_NETWORK 42565 code: 2 42566 }; 42567 42568 if (startingState) { 42569 this.state = startingState; 42570 } 42571 42572 this.trigger('error'); 42573 return true; 42574 } 42575 } 42576 /** 42577 * Verify that the container of the sidx segment can be parsed 42578 * and if it can, get and parse that segment. 42579 */ 42580 ; 42581 42582 _proto.addSidxSegments_ = function addSidxSegments_(playlist, startingState, cb) { 42583 var _this2 = this; 42584 42585 var sidxKey = playlist.sidx && generateSidxKey(playlist.sidx); // playlist lacks sidx or sidx segments were added to this playlist already. 42586 42587 if (!playlist.sidx || !sidxKey || this.masterPlaylistLoader_.sidxMapping_[sidxKey]) { 42588 // keep this function async 42589 this.mediaRequest_ = window_1.setTimeout(function () { 42590 return cb(false); 42591 }, 0); 42592 return; 42593 } // resolve the segment URL relative to the playlist 42594 42595 42596 var uri = resolveManifestRedirect(this.handleManifestRedirects, playlist.sidx.resolvedUri); 42597 42598 var fin = function fin(err, request) { 42599 if (_this2.requestErrored_(err, request, startingState)) { 42600 return; 42601 } 42602 42603 var sidxMapping = _this2.masterPlaylistLoader_.sidxMapping_; 42604 var sidx; 42605 42606 try { 42607 sidx = parseSidx_1(toUint8(request.response).subarray(8)); 42608 } catch (e) { 42609 // sidx parsing failed. 42610 _this2.requestErrored_(e, request, startingState); 42611 42612 return; 42613 } 42614 42615 sidxMapping[sidxKey] = { 42616 sidxInfo: playlist.sidx, 42617 sidx: sidx 42618 }; 42619 addSidxSegmentsToPlaylist(playlist, sidx, playlist.sidx.resolvedUri); 42620 return cb(true); 42621 }; 42622
42623 this.request = containerRequest(uri, this.vhs_.xhr, function (err, request, container, bytes) { 42624 if (err) { 42625 return fin(err, request); 42626 } 42627 42628 if (!container || container !== 'mp4') { 42629 return fin({ 42630 status: request.status, 42631 message: "Unsupported " + (container || 'unknown') + " container type for sidx segment at URL: " + uri, 42632 // response is just bytes in this case 42633 // but we really don't want to return that. 42634 response: '', 42635 playlist: playlist, 42636 internal: true, 42637 blacklistDuration: Infinity, 42638 // MEDIA_ERR_NETWORK 42639 code: 2 42640 }, request); 42641 } // if we already downloaded the sidx bytes in the container request, use them 42642 42643 42644 var _playlist$sidx$bytera = playlist.sidx.byterange, 42645 offset = _playlist$sidx$bytera.offset, 42646 length = _playlist$sidx$bytera.length; 42647 42648 if (bytes.length >= length + offset) { 42649 return fin(err, { 42650 response: bytes.subarray(offset, offset + length), 42651 status: request.status, 42652 uri: request.uri 42653 }); 42654 } // otherwise request sidx bytes 42655 42656 42657 _this2.request = _this2.vhs_.xhr({ 42658 uri: uri, 42659 responseType: 'arraybuffer', 42660 headers: segmentXhrHeaders({ 42661 byterange: playlist.sidx.byterange 42662 }) 42663 }, fin); 42664 }); 42665 }; 42666 42667 _proto.dispose = function dispose() { 42668 this.trigger('dispose'); 42669 this.stopRequest(); 42670 this.loadedPlaylists_ = {}; 42671 window_1.clearTimeout(this.minimumUpdatePeriodTimeout_); 42672 window_1.clearTimeout(this.mediaRequest_); 42673 window_1.clearTimeout(this.mediaUpdateTimeout); 42674 this.mediaUpdateTimeout = null; 42675 this.mediaRequest_ = null; 42676 this.minimumUpdatePeriodTimeout_ = null; 42677 42678 if (this.masterPlaylistLoader_.createMupOnMedia_) { 42679 this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_); 42680 this.masterPlaylistLoader_.createMupOnMedia_ = null; 42681 } 42682 42683 this.off(); 42684 }; 42685 42686 _proto.hasPendingRequest = function hasPendingRequest() { 42687 return this.request || this.mediaRequest_; 42688 }; 42689 42690 _proto.stopRequest = function stopRequest() { 42691 if (this.request) { 42692 var oldRequest = this.request; 42693 this.request = null; 42694 oldRequest.onreadystatechange = null; 42695 oldRequest.abort(); 42696 } 42697 }; 42698 42699 _proto.media = function media(playlist) { 42700 var _this3 = this; // getter 42701 42702 42703 if (!playlist) { 42704 return this.media_; 42705 } // setter 42706 42707 42708 if (this.state === 'HAVE_NOTHING') { 42709 throw new Error('Cannot switch media playlist from ' + this.state); 42710 } 42711 42712 var startingState = this.state; // find the playlist object if the target playlist has been specified by URI 42713 42714 if (typeof playlist === 'string') { 42715 if (!this.masterPlaylistLoader_.master.playlists[playlist]) { 42716 throw new Error('Unknown playlist URI: ' + playlist); 42717 } 42718 42719 playlist = this.masterPlaylistLoader_.master.playlists[playlist]; 42720 } 42721 42722 var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately 42723 42724 if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) { 42725 this.state = 'HAVE_METADATA'; 42726 this.media_ = playlist; // trigger media change if the active media has been updated 42727 42728 if (mediaChange) { 42729 this.trigger('mediachanging'); 42730 this.trigger('mediachange'); 42731 } 42732 42733 return; 42734 } // switching to the active playlist is a no-op 42735 42736 42737 if (!mediaChange) { 42738 return; 42739 } // switching from an already loaded playlist 42740 42741 42742 if (this.media_) { 42743 this.trigger('mediachanging'); 42744 } 42745 42746 this.addSidxSegments_(playlist, startingState, function (sidxChanged) { 42747 // everything is ready just continue to haveMetadata 42748 _this3.haveMetadata({ 42749 startingState: startingState, 42750 playlist: playlist 42751 }); 42752 }); 42753 }; 42754 42755 _proto.haveMetadata = function haveMetadata(_ref2) { 42756 var startingState = _ref2.startingState, 42757 playlist = _ref2.playlist; 42758 this.state = 'HAVE_METADATA'; 42759 this.loadedPlaylists_[playlist.id] = playlist; 42760 this.mediaRequest_ = null; // This will trigger loadedplaylist 42761 42762 this.refreshMedia_(playlist.id);
42762 // fire loadedmetadata the first time a media playlist is loaded 42763 // to resolve setup of media groups 42764 42765 if (startingState === 'HAVE_MASTER') { 42766 this.trigger('loadedmetadata'); 42767 } else { 42768 // trigger media change if the active media has been updated 42769 this.trigger('mediachange'); 42770 } 42771 }; 42772 42773 _proto.pause = function pause() { 42774 if (this.masterPlaylistLoader_.createMupOnMedia_) { 42775 this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_); 42776 this.masterPlaylistLoader_.createMupOnMedia_ = null; 42777 } 42778 42779 this.stopRequest(); 42780 window_1.clearTimeout(this.mediaUpdateTimeout); 42781 this.mediaUpdateTimeout = null; 42782 42783 if (this.isMaster_) { 42784 window_1.clearTimeout(this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_); 42785 this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_ = null; 42786 } 42787 42788 if (this.state === 'HAVE_NOTHING') { 42789 // If we pause the loader before any data has been retrieved, its as if we never 42790 // started, so reset to an unstarted state. 42791 this.started = false; 42792 } 42793 }; 42794 42795 _proto.load = function load(isFinalRendition) { 42796 var _this4 = this; 42797 42798 window_1.clearTimeout(this.mediaUpdateTimeout); 42799 this.mediaUpdateTimeout = null; 42800 var media = this.media(); 42801 42802 if (isFinalRendition) { 42803 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 42804 this.mediaUpdateTimeout = window_1.setTimeout(function () { 42805 return _this4.load(); 42806 }, delay); 42807 return; 42808 } // because the playlists are internal to the manifest, load should either load the 42809 // main manifest, or do nothing but trigger an event 42810 42811 42812 if (!this.started) { 42813 this.start(); 42814 return; 42815 } 42816 42817 if (media && !media.endList) { 42818 // Check to see if this is the master loader and the MUP was cleared (this happens 42819 // when the loader was paused). `media` should be set at this point since one is always 42820 // set during `start()`. 42821 if (this.isMaster_ && !this.minimumUpdatePeriodTimeout_) { 42822 // Trigger minimumUpdatePeriod to refresh the master manifest 42823 this.trigger('minimumUpdatePeriod'); // Since there was no prior minimumUpdatePeriodTimeout it should be recreated 42824 42825 this.updateMinimumUpdatePeriodTimeout_(); 42826 } 42827 42828 this.trigger('mediaupdatetimeout'); 42829 } else { 42830 this.trigger('loadedplaylist'); 42831 } 42832 }; 42833 42834 _proto.start = function start() { 42835 var _this5 = this; 42836 42837 this.started = true; // We don't need to request the master manifest again 42838 // Call this asynchronously to match the xhr request behavior below 42839 42840 if (!this.isMaster_) { 42841 this.mediaRequest_ = window_1.setTimeout(function () { 42842 return _this5.haveMaster_(); 42843 }, 0); 42844 return; 42845 } 42846 42847 this.requestMaster_(function (req, masterChanged) { 42848 _this5.haveMaster_(); 42849 42850 if (!_this5.hasPendingRequest() && !_this5.media_) { 42851 _this5.media(_this5.masterPlaylistLoader_.master.playlists[0]); 42852 } 42853 }); 42854 }; 42855 42856 _proto.requestMaster_ = function requestMaster_(cb) { 42857 var _this6 = this; 42858 42859 this.request = this.vhs_.xhr({ 42860 uri: this.masterPlaylistLoader_.srcUrl, 42861 withCredentials: this.withCredentials 42862 }, function (error, req) { 42863 if (_this6.requestErrored_(error, req)) { 42864 if (_this6.state === 'HAVE_NOTHING') { 42865 _this6.started = false; 42866 } 42867 42868 return; 42869 } 42870 42871 var masterChanged = req.responseText !== _this6.masterPlaylistLoader_.masterXml_; 42872 _this6.masterPlaylistLoader_.masterXml_ = req.responseText; 42873 42874 if (req.responseHeaders && req.responseHeaders.date) { 42875 _this6.masterLoaded_ = Date.parse(req.responseHeaders.date); 42876 } else { 42877 _this6.masterLoaded_ = Date.now(); 42878 } 42879 42880 _this6.masterPlaylistLoader_.srcUrl = resolveManifestRedirect(_this6.handleManifestRedirects, _this6.masterPlaylistLoader_.srcUrl, req); 42881 42882 if (masterChanged) { 42883 _this6.handleMaster_(); 42884
42885 _this6.syncClientServerClock_(function () { 42886 return cb(req, masterChanged); 42887 }); 42888 42889 return; 42890 } 42891 42892 return cb(req, masterChanged); 42893 }); 42894 } 42895 /** 42896 * Parses the master xml for UTCTiming node to sync the client clock to the server 42897 * clock. If the UTCTiming node requires a HEAD or GET request, that request is made. 42898 * 42899 * @param {Function} done 42900 * Function to call when clock sync has completed 42901 */ 42902 ; 42903 42904 _proto.syncClientServerClock_ = function syncClientServerClock_(done) { 42905 var _this7 = this; 42906 42907 var utcTiming = parseUTCTiming(this.masterPlaylistLoader_.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the 42908 // server clock 42909 42910 if (utcTiming === null) { 42911 this.masterPlaylistLoader_.clientOffset_ = this.masterLoaded_ - Date.now(); 42912 return done(); 42913 } 42914 42915 if (utcTiming.method === 'DIRECT') { 42916 this.masterPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now(); 42917 return done(); 42918 } 42919 42920 this.request = this.vhs_.xhr({ 42921 uri: resolveUrl(this.masterPlaylistLoader_.srcUrl, utcTiming.value), 42922 method: utcTiming.method, 42923 withCredentials: this.withCredentials 42924 }, function (error, req) { 42925 // disposed 42926 if (!_this7.request) { 42927 return; 42928 } 42929 42930 if (error) { 42931 // sync request failed, fall back to using date header from mpd 42932 // TODO: log warning 42933 _this7.masterPlaylistLoader_.clientOffset_ = _this7.masterLoaded_ - Date.now(); 42934 return done(); 42935 } 42936 42937 var serverTime; 42938 42939 if (utcTiming.method === 'HEAD') { 42940 if (!req.responseHeaders || !req.responseHeaders.date) { 42941 // expected date header not preset, fall back to using date header from mpd 42942 // TODO: log warning 42943 serverTime = _this7.masterLoaded_; 42944 } else { 42945 serverTime = Date.parse(req.responseHeaders.date); 42946 } 42947 } else { 42948 serverTime = Date.parse(req.responseText); 42949 } 42950 42951 _this7.masterPlaylistLoader_.clientOffset_ = serverTime - Date.now(); 42952 done(); 42953 }); 42954 }; 42955 42956 _proto.haveMaster_ = function haveMaster_() { 42957 this.state = 'HAVE_MASTER'; 42958 42959 if (this.isMaster_) { 42960 // We have the master playlist at this point, so 42961 // trigger this to allow MasterPlaylistController 42962 // to make an initial playlist selection 42963 this.trigger('loadedplaylist'); 42964 } else if (!this.media_) { 42965 // no media playlist was specifically selected so select 42966 // the one the child playlist loader was created with 42967 this.media(this.childPlaylist_); 42968 } 42969 }; 42970 42971 _proto.handleMaster_ = function handleMaster_() { 42972 // clear media request 42973 this.mediaRequest_ = null; 42974 var newMaster = parseMasterXml({ 42975 masterXml: this.masterPlaylistLoader_.masterXml_, 42976 srcUrl: this.masterPlaylistLoader_.srcUrl, 42977 clientOffset: this.masterPlaylistLoader_.clientOffset_, 42978 sidxMapping: this.masterPlaylistLoader_.sidxMapping_ 42979 }); 42980 var oldMaster = this.masterPlaylistLoader_.master; // if we have an old master to compare the new master against 42981 42982 if (oldMaster) { 42983 newMaster = updateMaster(oldMaster, newMaster, this.masterPlaylistLoader_.sidxMapping_); 42984 } // only update master if we have a new master 42985 42986 42987 this.masterPlaylistLoader_.master = newMaster ? newMaster : oldMaster; 42988 var location = this.masterPlaylistLoader_.master.locations && this.masterPlaylistLoader_.master.locations[0]; 42989 42990 if (location && location !== this.masterPlaylistLoader_.srcUrl) { 42991 this.masterPlaylistLoader_.srcUrl = location; 42992 } 42993 42994 if (!oldMaster || newMaster && newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) { 42995 this.updateMinimumUpdatePeriodTimeout_(); 42996 } 42997 42998 return Boolean(newMaster); 42999 }; 43000 43001 _proto.updateMinimumUpdatePeriodTimeout_ = function updateMinimumUpdatePeriodTimeout_() { 43002 var mpl = this.masterPlaylistLoader_;
43002 // cancel any pending creation of mup on media 43003 // a new one will be added if needed. 43004 43005 if (mpl.createMupOnMedia_) { 43006 mpl.off('loadedmetadata', mpl.createMupOnMedia_); 43007 mpl.createMupOnMedia_ = null; 43008 } // clear any pending timeouts 43009 43010 43011 if (mpl.minimumUpdatePeriodTimeout_) { 43012 window_1.clearTimeout(mpl.minimumUpdatePeriodTimeout_); 43013 mpl.minimumUpdatePeriodTimeout_ = null; 43014 } 43015 43016 var mup = mpl.master && mpl.master.minimumUpdatePeriod; // If the minimumUpdatePeriod has a value of 0, that indicates that the current 43017 // MPD has no future validity, so a new one will need to be acquired when new 43018 // media segments are to be made available. Thus, we use the target duration 43019 // in this case 43020 43021 if (mup === 0) { 43022 if (mpl.media()) { 43023 mup = mpl.media().targetDuration * 1000; 43024 } else { 43025 mpl.createMupOnMedia_ = mpl.updateMinimumUpdatePeriodTimeout_; 43026 mpl.one('loadedmetadata', mpl.createMupOnMedia_); 43027 } 43028 } // if minimumUpdatePeriod is invalid or <= zero, which 43029 // can happen when a live video becomes VOD. skip timeout 43030 // creation. 43031 43032 43033 if (typeof mup !== 'number' || mup <= 0) { 43034 if (mup < 0) { 43035 this.logger_("found invalid minimumUpdatePeriod of " + mup + ", not setting a timeout"); 43036 } 43037 43038 return; 43039 } 43040 43041 this.createMUPTimeout_(mup); 43042 }; 43043 43044 _proto.createMUPTimeout_ = function createMUPTimeout_(mup) { 43045 var mpl = this.masterPlaylistLoader_; 43046 mpl.minimumUpdatePeriodTimeout_ = window_1.setTimeout(function () { 43047 mpl.minimumUpdatePeriodTimeout_ = null; 43048 mpl.trigger('minimumUpdatePeriod'); 43049 mpl.createMUPTimeout_(mup); 43050 }, mup); 43051 } 43052 /** 43053 * Sends request to refresh the master xml and updates the parsed master manifest 43054 */ 43055 ; 43056 43057 _proto.refreshXml_ = function refreshXml_() { 43058 var _this8 = this; 43059 43060 this.requestMaster_(function (req, masterChanged) { 43061 if (!masterChanged) { 43062 return; 43063 } 43064 43065 if (_this8.media_) { 43066 _this8.media_ = _this8.masterPlaylistLoader_.master.playlists[_this8.media_.id]; 43067 } // This will filter out updated sidx info from the mapping 43068 43069 43070 _this8.masterPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(_this8.masterPlaylistLoader_.master, _this8.masterPlaylistLoader_.sidxMapping_); 43071 43072 _this8.addSidxSegments_(_this8.media(), _this8.state, function (sidxChanged) { 43073 // TODO: do we need to reload the current playlist? 43074 _this8.refreshMedia_(_this8.media().id); 43075 }); 43076 }); 43077 } 43078 /** 43079 * Refreshes the media playlist by re-parsing the master xml and updating playlist 43080 * references. If this is an alternate loader, the updated parsed manifest is retrieved 43081 * from the master loader. 43082 */ 43083 ; 43084 43085 _proto.refreshMedia_ = function refreshMedia_(mediaID) { 43086 var _this9 = this; 43087 43088 if (!mediaID) { 43089 throw new Error('refreshMedia_ must take a media id'); 43090 } // for master we have to reparse the master xml 43091 // to re-create segments based on current timing values 43092 // which may change media. We only skip updating master 43093 // if this is the first time this.media_ is being set. 43094 // as master was just parsed in that case. 43095 43096 43097 if (this.media_ && this.isMaster_) { 43098 this.handleMaster_(); 43099 } 43100 43101 var playlists = this.masterPlaylistLoader_.master.playlists; 43102 var mediaChanged = !this.media_ || this.media_ !== playlists[mediaID]; 43103 43104 if (mediaChanged) { 43105 this.media_ = playlists[mediaID]; 43106 } else { 43107 this.trigger('playlistunchanged'); 43108 } 43109 43110 if (!this.mediaUpdateTimeout) { 43111 var createMediaUpdateTimeout = function createMediaUpdateTimeout() { 43112 if (_this9.media().endList) { 43113 return; 43114 } 43115 43116 _this9.mediaUpdateTimeout = window_1.setTimeout(function () { 43117 _this9.trigger('mediaupdatetimeout'); 43118 43119 createMediaUpdateTimeout(); 43120 }, refreshDelay(_this9.media(), Boolean(mediaChanged))); 43121 }; 43122 43123 createMediaUpdateTimeout(); 43124 } 43125 43126 this.trigger('loadedplaylist'); 43127 }; 43128 43129 return DashPlaylistLoader; 43130 }(EventTarget); 43131 43132 var Config = { 43133 GOAL_BUFFER_LENGTH: 30, 43134 MAX_GOAL_BUFFER_LENGTH: 60, 43135 BACK_BUFFER_LENGTH: 30, 43136 GOAL_BUFFER_LENGTH_RATE: 1, 43137 // 0.5 MB/s 43138 INITIAL_BANDWIDTH: 4194304, 43139 // A fudge factor to apply to advertised playlist bitrates to account for 43140 // temporary flucations in client bandwidth 43141 BANDWIDTH_VARIANCE: 1.2, 43142 // How much of the buffer must be filled before we consider upswitching 43143 BUFFER_LOW_WATER_LINE: 0, 43144 MAX_BUFFER_LOW_WATER_LINE: 30, 43145 // TODO: Remove this when experimentalBufferBasedABR is removed 43146 EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE: 16, 43147 BUFFER_LOW_WATER_LINE_RATE: 1, 43148 // If the buffer is greater than the high water line, we won't switch down 43149 BUFFER_HIGH_WATER_LINE: 30 43150 }; 43151 43152 var stringToArrayBuffer = function stringToArrayBuffer(string) { 43153 var view = new Uint8Array(new ArrayBuffer(string.length)); 43154 43155 for (var i = 0; i < string.length; i++) { 43156 view[i] = string.charCodeAt(i); 43157 } 43158 43159 return view.buffer; 43160 }; 43161 /* global Blob, BlobBuilder, Worker */ 43162 // unify worker interface 43163 43164 43165 var browserWorkerPolyFill = function browserWorkerPolyFill(workerObj) { 43166 // node only supports on/off 43167 workerObj.on = workerObj.addEventListener; 43168 workerObj.off = workerObj.removeEventListener; 43169 return workerObj; 43170 }; 43171 43172 var createObjectURL = function createObjectURL(str) { 43173 try { 43174 return URL.createObjectURL(new Blob([str], { 43175 type: 'application/javascript' 43176 })); 43177 } catch (e) { 43178 var blob = new BlobBuilder(); 43179 blob.append(str); 43180 return URL.createObjectURL(blob.getBlob()); 43181 } 43182 }; 43183 43184 var factory = function factory(code) { 43185 return function () { 43186 var objectUrl = createObjectURL(code); 43187 var worker = browserWorkerPolyFill(new Worker(objectUrl)); 43188 worker.objURL = objectUrl; 43189 var terminate = worker.terminate; 43190 worker.on = worker.addEventListener; 43191 worker.off = worker.removeEventListener; 43192 43193 worker.terminate = function () { 43194 URL.revokeObjectURL(objectUrl); 43195 return terminate.call(this); 43196 }; 43197 43198 return worker; 43199 }; 43200 }; 43201 43202 var transform = function transform(code) { 43203 return "var browserWorkerPolyFill = " + browserWorkerPolyFill.toString() + ";\n" + 'browserWorkerPolyFill(self);\n' + code; 43204 }; 43205 43206 var getWorkerString = function getWorkerString(fn) { 43207 return fn.toString().replace(/^function.+?{/, '').slice(0, -1); 43208 }; 43209 /* rollup-plugin-worker-factory start for worker!/Users/bcasey/Projects/videojs-http-streaming/src/transmuxer-worker.js */ 43210 43211 43212 var workerCode$1 = transform(getWorkerString(function () { 43213 /** 43214 * mux.js 43215 * 43216 * Copyright (c) Brightcove 43217 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43218 * 43219 * A lightweight readable stream implemention that handles event dispatching. 43220 * Objects that inherit from streams should call init in their constructors. 43221 */ 43222 var Stream = function Stream() { 43223 this.init = function () { 43224 var listeners = {}; 43225 /** 43226 * Add a listener for a specified event type. 43227 * @param type {string} the event name 43228 * @param listener {function} the callback to be invoked when an event of 43229 * the specified type occurs 43230 */ 43231 43232 this.on = function (type, listener) { 43233 if (!listeners[type]) { 43234 listeners[type] = []; 43235 } 43236 43237 listeners[type] = listeners[type].concat(listener); 43238 }; 43239 /** 43240 * Remove a listener for a specified event type. 43241 * @param type {string} the event name 43242 * @param listener {function} a function previously registered for this 43243 * type of event through `on` 43244 */ 43245 43246 43247 this.off = function (type, listener) { 43248 var index; 43249 43250 if (!listeners[type]) { 43251 return false; 43252 } 43253 43254 index = listeners[type].indexOf(listener); 43255 listeners[type] = listeners[type].slice(); 43256 listeners[type].splice(index, 1); 43257 return index > -1; 43258 }; 43259 /** 43260 * Trigger an event of the specified type on this stream. Any additional 43261 * arguments to this function are passed as parameters to event listeners. 43262 * @param type {string} the event name 43263 */ 43264 43265 43266 this.trigger = function (type) { 43267 var callbacks, i, length, args; 43268 callbacks = listeners[type]; 43269 43270 if (!callbacks) { 43271 return; 43272 } // Slicing the arguments on every invocation of this method 43273 // can add a significant amount of overhead. Avoid the 43274 // intermediate object creation for the common case of a 43275 // single callback argument 43276 43277 43278 if (arguments.length === 2) { 43279 length = callbacks.length; 43280 43281 for (i = 0; i < length; ++i) { 43282 callbacks[i].call(this, arguments[1]); 43283 } 43284 } else { 43285 args = []; 43286 i = arguments.length; 43287 43288 for (i = 1; i < arguments.length; ++i) { 43289 args.push(arguments[i]); 43290 } 43291 43292 length = callbacks.length; 43293 43294 for (i = 0; i < length; ++i) { 43295 callbacks[i].apply(this, args); 43296 } 43297 } 43298 }; 43299 /** 43300 * Destroys the stream and cleans up. 43301 */ 43302 43303 43304 this.dispose = function () { 43305 listeners = {}; 43306 }; 43307 }; 43308 }; 43309 /** 43310 * Forwards all `data` events on this stream to the destination stream. The 43311 * destination stream should provide a method `push` to receive the data 43312 * events as they arrive. 43313 * @param destination {stream} the stream that will receive all `data` events 43314 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 43315 * when the current stream emits a 'done' event 43316 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 43317 */ 43318 43319 43320 Stream.prototype.pipe = function (destination) { 43321 this.on('data', function (data) { 43322 destination.push(data); 43323 }); 43324 this.on('done', function (flushSource) { 43325 destination.flush(flushSource); 43326 }); 43327 this.on('partialdone', function (flushSource) { 43328 destination.partialFlush(flushSource); 43329 }); 43330 this.on('endedtimeline', function (flushSource) { 43331 destination.endTimeline(flushSource); 43332 }); 43333 this.on('reset', function (flushSource) { 43334 destination.reset(flushSource); 43335 }); 43336 return destination; 43337 }; // Default stream functions that are expected to be overridden to perform 43338 // actual work. These are provided by the prototype as a sort of no-op 43339 // implementation so that we don't have to check for their existence in the 43340 // `pipe` function above. 43341 43342 43343 Stream.prototype.push = function (data) { 43344 this.trigger('data', data); 43345 }; 43346 43347 Stream.prototype.flush = function (flushSource) { 43348 this.trigger('done', flushSource); 43349 }; 43350 43351 Stream.prototype.partialFlush = function (flushSource) { 43352 this.trigger('partialdone', flushSource); 43353 }; 43354 43355 Stream.prototype.endTimeline = function (flushSource) { 43356 this.trigger('endedtimeline', flushSource); 43357 }; 43358 43359 Stream.prototype.reset = function (flushSource) { 43360 this.trigger('reset', flushSource); 43361 }; 43362 43363 var stream = Stream; 43364 /** 43365 * mux.js 43366 * 43367 * Copyright (c) Brightcove 43368 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43369 * 43370 * Functions that generate fragmented MP4s suitable for use with Media 43371 * Source Extensions. 43372 */ 43373 43374 var UINT32_MAX = Math.pow(2, 32) - 1; 43375 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 43376 43377 (function () { 43378 var i; 43379 types = { 43380 avc1: [], 43381 // codingname 43382 avcC: [], 43383 btrt: [], 43384 dinf: [], 43385 dref: [], 43386 esds: [], 43387 ftyp: [], 43388 hdlr: [], 43389 mdat: [], 43390 mdhd: [], 43391 mdia: [], 43392 mfhd: [], 43393 minf: [], 43394 moof: [], 43395 moov: [], 43396 mp4a: [], 43397 // codingname 43398 mvex: [], 43399 mvhd: [], 43400 pasp: [], 43401 sdtp: [], 43402 smhd: [], 43403 stbl: [], 43404 stco: [], 43405 stsc: [], 43406 stsd: [], 43407 stsz: [], 43408 stts: [], 43409 styp: [], 43410 tfdt: [], 43411 tfhd: [], 43412 traf: [], 43413 trak: [], 43414 trun: [], 43415 trex: [], 43416 tkhd: [], 43417 vmhd: [] 43418 }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 43419 // don't throw an error 43420 43421 if (typeof Uint8Array === 'undefined') { 43422 return; 43423 } 43424 43425 for (i in types) { 43426 if (types.hasOwnProperty(i)) {
vendor: 56,075 bytes, lines 43427-44921
43427 types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; 43428 } 43429 } 43430 43431 MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]); 43432 AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]); 43433 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 43434 VIDEO_HDLR = new Uint8Array([0x00, // version 0 43435 0x00, 0x00, 0x00, // flags 43436 0x00, 0x00, 0x00, 0x00, // pre_defined 43437 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 43438 0x00, 0x00, 0x00, 0x00, // reserved 43439 0x00, 0x00, 0x00, 0x00, // reserved 43440 0x00, 0x00, 0x00, 0x00, // reserved 43441 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 43442 ]); 43443 AUDIO_HDLR = new Uint8Array([0x00, // version 0 43444 0x00, 0x00, 0x00, // flags 43445 0x00, 0x00, 0x00, 0x00, // pre_defined 43446 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 43447 0x00, 0x00, 0x00, 0x00, // reserved 43448 0x00, 0x00, 0x00, 0x00, // reserved 43449 0x00, 0x00, 0x00, 0x00, // reserved 43450 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 43451 ]); 43452 HDLR_TYPES = { 43453 video: VIDEO_HDLR, 43454 audio: AUDIO_HDLR 43455 }; 43456 DREF = new Uint8Array([0x00, // version 0 43457 0x00, 0x00, 0x00, // flags 43458 0x00, 0x00, 0x00, 0x01, // entry_count 43459 0x00, 0x00, 0x00, 0x0c, // entry_size 43460 0x75, 0x72, 0x6c, 0x20, // 'url' type 43461 0x00, // version 0 43462 0x00, 0x00, 0x01 // entry_flags 43463 ]); 43464 SMHD = new Uint8Array([0x00, // version 43465 0x00, 0x00, 0x00, // flags 43466 0x00, 0x00, // balance, 0 means centered 43467 0x00, 0x00 // reserved 43468 ]); 43469 STCO = new Uint8Array([0x00, // version 43470 0x00, 0x00, 0x00, // flags 43471 0x00, 0x00, 0x00, 0x00 // entry_count 43472 ]); 43473 STSC = STCO; 43474 STSZ = new Uint8Array([0x00, // version 43475 0x00, 0x00, 0x00, // flags 43476 0x00, 0x00, 0x00, 0x00, // sample_size 43477 0x00, 0x00, 0x00, 0x00 // sample_count 43478 ]); 43479 STTS = STCO; 43480 VMHD = new Uint8Array([0x00, // version 43481 0x00, 0x00, 0x01, // flags 43482 0x00, 0x00, // graphicsmode 43483 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor 43484 ]); 43485 })(); 43486 43487 box = function box(type) { 43488 var payload = [], 43489 size = 0, 43490 i, 43491 result, 43492 view; 43493 43494 for (i = 1; i < arguments.length; i++) { 43495 payload.push(arguments[i]); 43496 } 43497 43498 i = payload.length; // calculate the total size we need to allocate 43499 43500 while (i--) { 43501 size += payload[i].byteLength; 43502 } 43503 43504 result = new Uint8Array(size + 8); 43505 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 43506 view.setUint32(0, result.byteLength); 43507 result.set(type, 4); // copy the payload into the result 43508 43509 for (i = 0, size = 8; i < payload.length; i++) { 43510 result.set(payload[i], size); 43511 size += payload[i].byteLength; 43512 } 43513 43514 return result; 43515 }; 43516 43517 dinf = function dinf() { 43518 return box(types.dinf, box(types.dref, DREF)); 43519 }; 43520 43521 esds = function esds(track) { 43522 return box(types.esds, new Uint8Array([0x00, // version 43523 0x00, 0x00, 0x00, // flags 43524 // ES_Descriptor 43525 0x03, // tag, ES_DescrTag 43526 0x19, // length 43527 0x00, 0x00, // ES_ID 43528 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 43529 // DecoderConfigDescriptor 43530 0x04, // tag, DecoderConfigDescrTag 43531 0x11, // length 43532 0x40, // object type 43533 0x15, // streamType 43534 0x00, 0x06, 0x00, // bufferSizeDB 43535 0x00, 0x00, 0xda, 0xc0, // maxBitrate 43536 0x00, 0x00, 0xda, 0xc0, // avgBitrate 43537 // DecoderSpecificInfo 43538 0x05, // tag, DecoderSpecificInfoTag 43539 0x02, // length 43540 // ISO/IEC 14496-3, AudioSpecificConfig 43541 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 43542 track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig 43543 ])); 43544 }; 43545 43546 ftyp = function ftyp() { 43547 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 43548 }; 43549 43550 hdlr = function hdlr(type) { 43551 return box(types.hdlr, HDLR_TYPES[type]); 43552 }; 43553 43554 mdat = function mdat(data) { 43555 return box(types.mdat, data); 43556 }; 43557 43558 mdhd = function mdhd(track) { 43559 var result = new Uint8Array([0x00, // version 0 43560 0x00, 0x00, 0x00, // flags 43561 0x00, 0x00, 0x00, 0x02, // creation_time 43562 0x00, 0x00, 0x00, 0x03, // modification_time 43563 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 43564 track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration 43565 0x55, 0xc4, // 'und' language (undetermined) 43566 0x00, 0x00]); // Use the sample rate from the track metadata, when it is 43567 // defined. The sample rate can be parsed out of an ADTS header, for 43568 // instance. 43569 43570 if (track.samplerate) { 43571 result[12] = track.samplerate >>> 24 & 0xFF; 43572 result[13] = track.samplerate >>> 16 & 0xFF; 43573 result[14] = track.samplerate >>> 8 & 0xFF; 43574 result[15] = track.samplerate & 0xFF; 43575 } 43576 43577 return box(types.mdhd, result); 43578 }; 43579 43580 mdia = function mdia(track) { 43581 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 43582 }; 43583 43584 mfhd = function mfhd(sequenceNumber) { 43585 return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags 43586 (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number 43587 ])); 43588 }; 43589 43590 minf = function minf(track) { 43591 return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track)); 43592 }; 43593 43594 moof = function moof(sequenceNumber, tracks) { 43595 var trackFragments = [], 43596 i = tracks.length; // build traf boxes for each track fragment 43597 43598 while (i--) { 43599 trackFragments[i] = traf(tracks[i]); 43600 } 43601 43602 return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments)); 43603 }; 43604 /** 43605 * Returns a movie box. 43606 * @param tracks {array} the tracks associated with this movie 43607 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 43608 */ 43609 43610 43611 moov = function moov(tracks) { 43612 var i = tracks.length, 43613 boxes = []; 43614 43615 while (i--) { 43616 boxes[i] = trak(tracks[i]); 43617 } 43618 43619 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 43620 }; 43621 43622 mvex = function mvex(tracks) { 43623 var i = tracks.length, 43624 boxes = []; 43625 43626 while (i--) { 43627 boxes[i] = trex(tracks[i]); 43628 } 43629 43630 return box.apply(null, [types.mvex].concat(boxes)); 43631 }; 43632 43633 mvhd = function mvhd(duration) { 43634 var bytes = new Uint8Array([0x00, // version 0 43635 0x00, 0x00, 0x00, // flags 43636 0x00, 0x00, 0x00, 0x01, // creation_time 43637 0x00, 0x00, 0x00, 0x02, // modification_time 43638 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 43639 (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration 43640 0x00, 0x01, 0x00, 0x00, // 1.0 rate 43641 0x01, 0x00, // 1.0 volume 43642 0x00, 0x00, // reserved 43643 0x00, 0x00, 0x00, 0x00, // reserved 43644 0x00, 0x00, 0x00, 0x00, // reserved 43645 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 43646 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 43647 0xff, 0xff, 0xff, 0xff // next_track_ID 43648 ]); 43649 return box(types.mvhd, bytes); 43650 }; 43651 43652 sdtp = function sdtp(track) { 43653 var samples = track.samples || [], 43654 bytes = new Uint8Array(4 + samples.length), 43655 flags, 43656 i; // leave the full box header (4 bytes) all zero 43657 // write the sample table 43658 43659 for (i = 0; i < samples.length; i++) { 43660 flags = samples[i].flags; 43661 bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; 43662 } 43663 43664 return box(types.sdtp, bytes); 43665 }; 43666 43667 stbl = function stbl(track) { 43668 return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO)); 43669 }; 43670 43671 (function () { 43672 var videoSample, audioSample; 43673 43674 stsd = function stsd(track) { 43675 return box(types.stsd, new Uint8Array([0x00, // version 0 43676 0x00, 0x00, 0x00, // flags 43677 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track)); 43678 }; 43679 43680 videoSample = function videoSample(track) { 43681 var sps = track.sps || [], 43682 pps = track.pps || [], 43683 sequenceParameterSets = [], 43684 pictureParameterSets = [], 43685 i, 43686 avc1Box; // assemble the SPSs 43687 43688 for (i = 0; i < sps.length; i++) { 43689 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 43690 sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength 43691 43692 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 43693 } // assemble the PPSs 43694 43695 43696 for (i = 0; i < pps.length; i++) { 43697 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 43698 pictureParameterSets.push(pps[i].byteLength & 0xFF); 43699 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 43700 } 43701 43702 avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 43703 0x00, 0x01, // data_reference_index 43704 0x00, 0x00, // pre_defined 43705 0x00, 0x00, // reserved 43706 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 43707 (track.width & 0xff00) >> 8, track.width & 0xff, // width 43708 (track.height & 0xff00) >> 8, track.height & 0xff, // height 43709 0x00, 0x48, 0x00, 0x00, // horizresolution 43710 0x00, 0x48, 0x00, 0x00, // vertresolution 43711 0x00, 0x00, 0x00, 0x00, // reserved 43712 0x00, 0x01, // frame_count 43713 0x13, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6a, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2d, 0x68, 0x6c, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname 43714 0x00, 0x18, // depth = 24 43715 0x11, 0x11 // pre_defined = -1 43716 ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion 43717 track.profileIdc, // AVCProfileIndication 43718 track.profileCompatibility, // profile_compatibility 43719 track.levelIdc, // AVCLevelIndication 43720 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 43721 ].concat([sps.length], // numOfSequenceParameterSets 43722 sequenceParameterSets, // "SPS" 43723 [pps.length], // numOfPictureParameterSets 43724 pictureParameterSets // "PPS" 43725 ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 43726 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 43727 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate 43728 ]))]; 43729 43730 if (track.sarRatio) { 43731 var hSpacing = track.sarRatio[0], 43732 vSpacing = track.sarRatio[1]; 43733 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]))); 43734 } 43735 43736 return box.apply(null, avc1Box); 43737 }; 43738 43739 audioSample = function audioSample(track) { 43740 return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12 43741 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 43742 0x00, 0x01, // data_reference_index 43743 // AudioSampleEntry, ISO/IEC 14496-12 43744 0x00, 0x00, 0x00, 0x00, // reserved 43745 0x00, 0x00, 0x00, 0x00, // reserved 43746 (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount 43747 (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize 43748 0x00, 0x00, // pre_defined 43749 0x00, 0x00, // reserved 43750 (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16 43751 // MP4AudioSampleEntry, ISO/IEC 14496-14 43752 ]), esds(track)); 43753 }; 43754 })(); 43755 43756 tkhd = function tkhd(track) { 43757 var result = new Uint8Array([0x00, // version 0 43758 0x00, 0x00, 0x07, // flags 43759 0x00, 0x00, 0x00, 0x00, // creation_time 43760 0x00, 0x00, 0x00, 0x00, // modification_time 43761 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 43762 0x00, 0x00, 0x00, 0x00, // reserved 43763 (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration 43764 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 43765 0x00, 0x00, // layer 43766 0x00, 0x00, // alternate_group 43767 0x01, 0x00, // non-audio track volume 43768 0x00, 0x00, // reserved 43769 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 43770 (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width 43771 (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height 43772 ]); 43773 return box(types.tkhd, result); 43774 }; 43775 /** 43776 * Generate a track fragment (traf) box. A traf box collects metadata 43777 * about tracks in a movie fragment (moof) box. 43778 */ 43779 43780 43781 traf = function traf(track) { 43782 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 43783 trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0 43784 0x00, 0x00, 0x3a, // flags 43785 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 43786 0x00, 0x00, 0x00, 0x01, // sample_description_index 43787 0x00, 0x00, 0x00, 0x00, // default_sample_duration 43788 0x00, 0x00, 0x00, 0x00, // default_sample_size 43789 0x00, 0x00, 0x00, 0x00 // default_sample_flags 43790 ])); 43791 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 43792 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 43793 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1 43794 0x00, 0x00, 0x00, // flags 43795 // baseMediaDecodeTime 43796 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 43797 // the containing moof to the first payload byte of the associated 43798 // mdat 43799 43800 dataOffset = 32 + // tfhd 43801 20 + // tfdt 43802 8 + // traf header 43803 16 + // mfhd 43804 8 + // moof header 43805 8; // mdat header 43806 // audio tracks require less metadata 43807 43808 if (track.type === 'audio') { 43809 trackFragmentRun = trun$1(track, dataOffset); 43810 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun); 43811 } // video tracks should contain an independent and disposable samples 43812 // box (sdtp) 43813 // generate one and adjust offsets to match 43814 43815 43816 sampleDependencyTable = sdtp(track); 43817 trackFragmentRun = trun$1(track, sampleDependencyTable.length + dataOffset); 43818 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable); 43819 }; 43820 /** 43821 * Generate a track box. 43822 * @param track {object} a track definition 43823 * @return {Uint8Array} the track box 43824 */ 43825 43826 43827 trak = function trak(track) { 43828 track.duration = track.duration || 0xffffffff; 43829 return box(types.trak, tkhd(track), mdia(track)); 43830 }; 43831 43832 trex = function trex(track) { 43833 var result = new Uint8Array([0x00, // version 0 43834 0x00, 0x00, 0x00, // flags 43835 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 43836 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 43837 0x00, 0x00, 0x00, 0x00, // default_sample_duration 43838 0x00, 0x00, 0x00, 0x00, // default_sample_size 43839 0x00, 0x01, 0x00, 0x01 // default_sample_flags 43840 ]); // the last two bytes of default_sample_flags is the sample 43841 // degradation priority, a hint about the importance of this sample 43842 // relative to others. Lower the degradation priority for all sample 43843 // types other than video. 43844 43845 if (track.type !== 'video') { 43846 result[result.length - 1] = 0x00; 43847 } 43848 43849 return box(types.trex, result); 43850 }; 43851 43852 (function () { 43853 var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a 43854 // duration is present for the first sample, it will be present for 43855 // all subsequent samples. 43856 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 43857 43858 trunHeader = function trunHeader(samples, offset) { 43859 var durationPresent = 0, 43860 sizePresent = 0, 43861 flagsPresent = 0, 43862 compositionTimeOffset = 0; // trun flag constants 43863 43864 if (samples.length) { 43865 if (samples[0].duration !== undefined) { 43866 durationPresent = 0x1; 43867 } 43868 43869 if (samples[0].size !== undefined) { 43870 sizePresent = 0x2; 43871 } 43872 43873 if (samples[0].flags !== undefined) { 43874 flagsPresent = 0x4; 43875 } 43876 43877 if (samples[0].compositionTimeOffset !== undefined) { 43878 compositionTimeOffset = 0x8; 43879 } 43880 } 43881 43882 return [0x00, // version 0 43883 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags 43884 (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count 43885 (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset 43886 ]; 43887 }; 43888 43889 videoTrun = function videoTrun(track, offset) { 43890 var bytesOffest, bytes, header, samples, sample, i; 43891 samples = track.samples || []; 43892 offset += 8 + 12 + 16 * samples.length; 43893 header = trunHeader(samples, offset); 43894 bytes = new Uint8Array(header.length + samples.length * 16); 43895 bytes.set(header); 43896 bytesOffest = header.length; 43897 43898 for (i = 0; i < samples.length; i++) { 43899 sample = samples[i]; 43900 bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24; 43901 bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16; 43902 bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8; 43903 bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration 43904 43905 bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24; 43906 bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16; 43907 bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8; 43908 bytes[bytesOffest++] = sample.size & 0xFF; // sample_size 43909 43910 bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn; 43911 bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample; 43912 bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8; 43913 bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags 43914 43915 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24; 43916 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16; 43917 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8; 43918 bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset 43919 } 43920 43921 return box(types.trun, bytes); 43922 }; 43923 43924 audioTrun = function audioTrun(track, offset) { 43925 var bytes, bytesOffest, header, samples, sample, i; 43926 samples = track.samples || []; 43927 offset += 8 + 12 + 8 * samples.length; 43928 header = trunHeader(samples, offset); 43929 bytes = new Uint8Array(header.length + samples.length * 8); 43930 bytes.set(header); 43931 bytesOffest = header.length; 43932 43933 for (i = 0; i < samples.length; i++) { 43934 sample = samples[i]; 43935 bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24; 43936 bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16; 43937 bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8; 43938 bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration 43939 43940 bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24; 43941 bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16; 43942 bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8; 43943 bytes[bytesOffest++] = sample.size & 0xFF; // sample_size 43944 } 43945 43946 return box(types.trun, bytes); 43947 }; 43948 43949 trun$1 = function trun(track, offset) { 43950 if (track.type === 'audio') { 43951 return audioTrun(track, offset); 43952 } 43953 43954 return videoTrun(track, offset); 43955 }; 43956 })(); 43957 43958 var mp4Generator = { 43959 ftyp: ftyp, 43960 mdat: mdat, 43961 moof: moof, 43962 moov: moov, 43963 initSegment: function initSegment(tracks) { 43964 var fileType = ftyp(), 43965 movie = moov(tracks), 43966 result; 43967 result = new Uint8Array(fileType.byteLength + movie.byteLength); 43968 result.set(fileType); 43969 result.set(movie, fileType.byteLength); 43970 return result; 43971 } 43972 }; 43973 /** 43974 * mux.js 43975 * 43976 * Copyright (c) Brightcove 43977 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43978 */ 43979 // Convert an array of nal units into an array of frames with each frame being 43980 // composed of the nal units that make up that frame 43981 // Also keep track of cummulative data about the frame from the nal units such 43982 // as the frame duration, starting pts, etc. 43983 43984 var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) { 43985 var i, 43986 currentNal, 43987 currentFrame = [], 43988 frames = []; // TODO added for LHLS, make sure this is OK 43989 43990 frames.byteLength = 0; 43991 frames.nalCount = 0; 43992 frames.duration = 0; 43993 currentFrame.byteLength = 0; 43994 43995 for (i = 0; i < nalUnits.length; i++) { 43996 currentNal = nalUnits[i]; // Split on 'aud'-type nal units 43997 43998 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 43999 // Since the very first nal unit is expected to be an AUD 44000 // only push to the frames array when currentFrame is not empty 44001 if (currentFrame.length) { 44002 currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK 44003 44004 frames.byteLength += currentFrame.byteLength; 44005 frames.nalCount += currentFrame.length; 44006 frames.duration += currentFrame.duration; 44007 frames.push(currentFrame); 44008 } 44009 44010 currentFrame = [currentNal]; 44011 currentFrame.byteLength = currentNal.data.byteLength; 44012 currentFrame.pts = currentNal.pts; 44013 currentFrame.dts = currentNal.dts; 44014 } else { 44015 // Specifically flag key frames for ease of use later 44016 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 44017 currentFrame.keyFrame = true; 44018 } 44019 44020 currentFrame.duration = currentNal.dts - currentFrame.dts; 44021 currentFrame.byteLength += currentNal.data.byteLength; 44022 currentFrame.push(currentNal); 44023 } 44024 } // For the last frame, use the duration of the previous frame if we 44025 // have nothing better to go on 44026 44027 44028 if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) { 44029 currentFrame.duration = frames[frames.length - 1].duration; 44030 } // Push the final frame 44031 // TODO added for LHLS, make sure this is OK 44032 44033 44034 frames.byteLength += currentFrame.byteLength; 44035 frames.nalCount += currentFrame.length; 44036 frames.duration += currentFrame.duration; 44037 frames.push(currentFrame); 44038 return frames; 44039 }; // Convert an array of frames into an array of Gop with each Gop being composed 44040 // of the frames that make up that Gop 44041 // Also keep track of cummulative data about the Gop from the frames such as the 44042 // Gop duration, starting pts, etc. 44043 44044 44045 var groupFramesIntoGops = function groupFramesIntoGops(frames) { 44046 var i, 44047 currentFrame, 44048 currentGop = [], 44049 gops = []; // We must pre-set some of the values on the Gop since we 44050 // keep running totals of these values 44051 44052 currentGop.byteLength = 0; 44053 currentGop.nalCount = 0; 44054 currentGop.duration = 0; 44055 currentGop.pts = frames[0].pts; 44056 currentGop.dts = frames[0].dts; // store some metadata about all the Gops 44057 44058 gops.byteLength = 0; 44059 gops.nalCount = 0; 44060 gops.duration = 0; 44061 gops.pts = frames[0].pts; 44062 gops.dts = frames[0].dts; 44063 44064 for (i = 0; i < frames.length; i++) { 44065 currentFrame = frames[i]; 44066 44067 if (currentFrame.keyFrame) { 44068 // Since the very first frame is expected to be an keyframe 44069 // only push to the gops array when currentGop is not empty 44070 if (currentGop.length) { 44071 gops.push(currentGop); 44072 gops.byteLength += currentGop.byteLength; 44073 gops.nalCount += currentGop.nalCount; 44074 gops.duration += currentGop.duration; 44075 } 44076 44077 currentGop = [currentFrame]; 44078 currentGop.nalCount = currentFrame.length; 44079 currentGop.byteLength = currentFrame.byteLength; 44080 currentGop.pts = currentFrame.pts; 44081 currentGop.dts = currentFrame.dts; 44082 currentGop.duration = currentFrame.duration; 44083 } else { 44084 currentGop.duration += currentFrame.duration; 44085 currentGop.nalCount += currentFrame.length; 44086 currentGop.byteLength += currentFrame.byteLength; 44087 currentGop.push(currentFrame); 44088 } 44089 } 44090 44091 if (gops.length && currentGop.duration <= 0) { 44092 currentGop.duration = gops[gops.length - 1].duration; 44093 } 44094 44095 gops.byteLength += currentGop.byteLength; 44096 gops.nalCount += currentGop.nalCount; 44097 gops.duration += currentGop.duration; // push the final Gop 44098 44099 gops.push(currentGop); 44100 return gops; 44101 }; 44102 /* 44103 * Search for the first keyframe in the GOPs and throw away all frames 44104 * until that keyframe. Then extend the duration of the pulled keyframe 44105 * and pull the PTS and DTS of the keyframe so that it covers the time 44106 * range of the frames that were disposed. 44107 * 44108 * @param {Array} gops video GOPs 44109 * @returns {Array} modified video GOPs 44110 */ 44111 44112 44113 var extendFirstKeyFrame = function extendFirstKeyFrame(gops) { 44114 var currentGop; 44115 44116 if (!gops[0][0].keyFrame && gops.length > 1) { 44117 // Remove the first GOP 44118 currentGop = gops.shift(); 44119 gops.byteLength -= currentGop.byteLength; 44120 gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the 44121 // first gop to cover the time period of the 44122 // frames we just removed 44123 44124 gops[0][0].dts = currentGop.dts; 44125 gops[0][0].pts = currentGop.pts; 44126 gops[0][0].duration += currentGop.duration; 44127 } 44128 44129 return gops; 44130 }; 44131 /** 44132 * Default sample object 44133 * see ISO/IEC 14496-12:2012, section 8.6.4.3 44134 */ 44135 44136 44137 var createDefaultSample = function createDefaultSample() { 44138 return { 44139 size: 0, 44140 flags: { 44141 isLeading: 0, 44142 dependsOn: 1, 44143 isDependedOn: 0, 44144 hasRedundancy: 0, 44145 degradationPriority: 0, 44146 isNonSyncSample: 1 44147 } 44148 }; 44149 }; 44150 /* 44151 * Collates information from a video frame into an object for eventual 44152 * entry into an MP4 sample table. 44153 * 44154 * @param {Object} frame the video frame 44155 * @param {Number} dataOffset the byte offset to position the sample 44156 * @return {Object} object containing sample table info for a frame 44157 */ 44158 44159 44160 var sampleForFrame = function sampleForFrame(frame, dataOffset) { 44161 var sample = createDefaultSample(); 44162 sample.dataOffset = dataOffset; 44163 sample.compositionTimeOffset = frame.pts - frame.dts; 44164 sample.duration = frame.duration; 44165 sample.size = 4 * frame.length; // Space for nal unit size 44166 44167 sample.size += frame.byteLength; 44168 44169 if (frame.keyFrame) { 44170 sample.flags.dependsOn = 2; 44171 sample.flags.isNonSyncSample = 0; 44172 } 44173 44174 return sample; 44175 }; // generate the track's sample table from an array of gops 44176 44177 44178 var generateSampleTable$1 = function generateSampleTable(gops, baseDataOffset) { 44179 var h, 44180 i, 44181 sample, 44182 currentGop, 44183 currentFrame, 44184 dataOffset = baseDataOffset || 0, 44185 samples = []; 44186 44187 for (h = 0; h < gops.length; h++) { 44188 currentGop = gops[h]; 44189 44190 for (i = 0; i < currentGop.length; i++) { 44191 currentFrame = currentGop[i]; 44192 sample = sampleForFrame(currentFrame, dataOffset); 44193 dataOffset += sample.size; 44194 samples.push(sample); 44195 } 44196 } 44197 44198 return samples; 44199 }; // generate the track's raw mdat data from an array of gops 44200 44201 44202 var concatenateNalData = function concatenateNalData(gops) { 44203 var h, 44204 i, 44205 j, 44206 currentGop, 44207 currentFrame, 44208 currentNal, 44209 dataOffset = 0, 44210 nalsByteLength = gops.byteLength, 44211 numberOfNals = gops.nalCount, 44212 totalByteLength = nalsByteLength + 4 * numberOfNals, 44213 data = new Uint8Array(totalByteLength), 44214 view = new DataView(data.buffer); // For each Gop.. 44215 44216 for (h = 0; h < gops.length; h++) { 44217 currentGop = gops[h]; // For each Frame.. 44218 44219 for (i = 0; i < currentGop.length; i++) { 44220 currentFrame = currentGop[i]; // For each NAL.. 44221 44222 for (j = 0; j < currentFrame.length; j++) { 44223 currentNal = currentFrame[j]; 44224 view.setUint32(dataOffset, currentNal.data.byteLength); 44225 dataOffset += 4; 44226 data.set(currentNal.data, dataOffset); 44227 dataOffset += currentNal.data.byteLength; 44228 } 44229 } 44230 } 44231 44232 return data; 44233 }; // generate the track's sample table from a frame 44234 44235 44236 var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) { 44237 var sample, 44238 dataOffset = baseDataOffset || 0, 44239 samples = []; 44240 sample = sampleForFrame(frame, dataOffset); 44241 samples.push(sample); 44242 return samples; 44243 }; // generate the track's raw mdat data from a frame 44244 44245 44246 var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) { 44247 var i, 44248 currentNal, 44249 dataOffset = 0, 44250 nalsByteLength = frame.byteLength, 44251 numberOfNals = frame.length, 44252 totalByteLength = nalsByteLength + 4 * numberOfNals, 44253 data = new Uint8Array(totalByteLength), 44254 view = new DataView(data.buffer); // For each NAL.. 44255 44256 for (i = 0; i < frame.length; i++) { 44257 currentNal = frame[i]; 44258 view.setUint32(dataOffset, currentNal.data.byteLength); 44259 dataOffset += 4; 44260 data.set(currentNal.data, dataOffset); 44261 dataOffset += currentNal.data.byteLength; 44262 } 44263 44264 return data; 44265 }; 44266 44267 var frameUtils = { 44268 groupNalsIntoFrames: groupNalsIntoFrames, 44269 groupFramesIntoGops: groupFramesIntoGops, 44270 extendFirstKeyFrame: extendFirstKeyFrame, 44271 generateSampleTable: generateSampleTable$1, 44272 concatenateNalData: concatenateNalData, 44273 generateSampleTableForFrame: generateSampleTableForFrame, 44274 concatenateNalDataForFrame: concatenateNalDataForFrame 44275 }; 44276 /** 44277 * mux.js 44278 * 44279 * Copyright (c) Brightcove 44280 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 44281 */ 44282 44283 var highPrefix = [33, 16, 5, 32, 164, 27]; 44284 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 44285 44286 var zeroFill = function zeroFill(count) { 44287 var a = []; 44288 44289 while (count--) { 44290 a.push(0); 44291 } 44292 44293 return a; 44294 }; 44295 44296 var makeTable = function makeTable(metaTable) { 44297 return Object.keys(metaTable).reduce(function (obj, key) { 44298 obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) { 44299 return arr.concat(part); 44300 }, [])); 44301 return obj; 44302 }, {}); 44303 }; 44304 44305 var silence; 44306 44307 var silence_1 = function silence_1() { 44308 if (!silence) { 44309 // Frames-of-silence to use for filling in missing AAC frames 44310 var coneOfSilence = { 44311 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 44312 88200: [highPrefix, [231], zeroFill(170), [56]], 44313 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 44314 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 44315 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 44316 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 44317 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 44318 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 44319 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]], 44320 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]], 44321 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 44322 }; 44323 silence = makeTable(coneOfSilence); 44324 } 44325 44326 return silence; 44327 }; 44328 /** 44329 * mux.js 44330 * 44331 * Copyright (c) Brightcove 44332 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 44333 */ 44334 44335 44336 var ONE_SECOND_IN_TS$4 = 90000, 44337 // 90kHz clock 44338 secondsToVideoTs, 44339 secondsToAudioTs, 44340 videoTsToSeconds, 44341 audioTsToSeconds, 44342 audioTsToVideoTs, 44343 videoTsToAudioTs, 44344 metadataTsToSeconds; 44345 44346 secondsToVideoTs = function secondsToVideoTs(seconds) { 44347 return seconds * ONE_SECOND_IN_TS$4; 44348 }; 44349 44350 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 44351 return seconds * sampleRate; 44352 }; 44353 44354 videoTsToSeconds = function videoTsToSeconds(timestamp) { 44355 return timestamp / ONE_SECOND_IN_TS$4; 44356 }; 44357 44358 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 44359 return timestamp / sampleRate; 44360 }; 44361 44362 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 44363 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 44364 }; 44365 44366 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 44367 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 44368 }; 44369 /** 44370 * Adjust ID3 tag or caption timing information by the timeline pts values 44371 * (if keepOriginalTimestamps is false) and convert to seconds 44372 */ 44373 44374 44375 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) { 44376 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 44377 }; 44378 44379 var clock = { 44380 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS$4, 44381 secondsToVideoTs: secondsToVideoTs, 44382 secondsToAudioTs: secondsToAudioTs, 44383 videoTsToSeconds: videoTsToSeconds, 44384 audioTsToSeconds: audioTsToSeconds, 44385 audioTsToVideoTs: audioTsToVideoTs, 44386 videoTsToAudioTs: videoTsToAudioTs, 44387 metadataTsToSeconds: metadataTsToSeconds 44388 }; 44389 /** 44390 * mux.js 44391 * 44392 * Copyright (c) Brightcove 44393 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 44394 */ 44395 44396 /** 44397 * Sum the `byteLength` properties of the data in each AAC frame 44398 */ 44399 44400 var sumFrameByteLengths = function sumFrameByteLengths(array) { 44401 var i, 44402 currentObj, 44403 sum = 0; // sum the byteLength's all each nal unit in the frame 44404 44405 for (i = 0; i < array.length; i++) { 44406 currentObj = array[i]; 44407 sum += currentObj.data.byteLength; 44408 } 44409 44410 return sum; 44411 }; // Possibly pad (prefix) the audio track with silence if appending this track 44412 // would lead to the introduction of a gap in the audio buffer 44413 44414 44415 var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) { 44416 var baseMediaDecodeTimeTs, 44417 frameDuration = 0, 44418 audioGapDuration = 0, 44419 audioFillFrameCount = 0, 44420 audioFillDuration = 0, 44421 silentFrame, 44422 i, 44423 firstFrame; 44424 44425 if (!frames.length) { 44426 return; 44427 } 44428 44429 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills 44430 44431 frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024)); 44432 44433 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 44434 // insert the shortest possible amount (audio gap or audio to video gap) 44435 audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap 44436 44437 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 44438 audioFillDuration = audioFillFrameCount * frameDuration; 44439 } // don't attempt to fill gaps smaller than a single frame or larger 44440 // than a half second 44441 44442 44443 if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) { 44444 return; 44445 } 44446 44447 silentFrame = silence_1()[track.samplerate]; 44448 44449 if (!silentFrame) { 44450 // we don't have a silent frame pregenerated for the sample rate, so use a frame 44451 // from the content instead 44452 silentFrame = frames[0].data; 44453 } 44454 44455 for (i = 0; i < audioFillFrameCount; i++) { 44456 firstFrame = frames[0]; 44457 frames.splice(0, 0, { 44458 data: silentFrame, 44459 dts: firstFrame.dts - frameDuration, 44460 pts: firstFrame.pts - frameDuration 44461 }); 44462 } 44463 44464 track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 44465 return audioFillDuration; 44466 }; // If the audio segment extends before the earliest allowed dts 44467 // value, remove AAC frames until starts at or after the earliest 44468 // allowed DTS so that we don't end up with a negative baseMedia- 44469 // DecodeTime for the audio track 44470 44471 44472 var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) { 44473 if (track.minSegmentDts >= earliestAllowedDts) { 44474 return adtsFrames; 44475 } // We will need to recalculate the earliest segment Dts 44476 44477 44478 track.minSegmentDts = Infinity; 44479 return adtsFrames.filter(function (currentFrame) { 44480 // If this is an allowed frame, keep it and record it's Dts 44481 if (currentFrame.dts >= earliestAllowedDts) { 44482 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 44483 track.minSegmentPts = track.minSegmentDts; 44484 return true; 44485 } // Otherwise, discard it 44486 44487 44488 return false; 44489 }); 44490 }; // generate the track's raw mdat data from an array of frames 44491 44492 44493 var generateSampleTable = function generateSampleTable(frames) { 44494 var i, 44495 currentFrame, 44496 samples = []; 44497 44498 for (i = 0; i < frames.length; i++) { 44499 currentFrame = frames[i]; 44500 samples.push({ 44501 size: currentFrame.data.byteLength, 44502 duration: 1024 // For AAC audio, all samples contain 1024 samples 44503 44504 }); 44505 } 44506 44507 return samples; 44508 }; // generate the track's sample table from an array of frames 44509 44510 44511 var concatenateFrameData = function concatenateFrameData(frames) { 44512 var i, 44513 currentFrame, 44514 dataOffset = 0, 44515 data = new Uint8Array(sumFrameByteLengths(frames)); 44516 44517 for (i = 0; i < frames.length; i++) { 44518 currentFrame = frames[i]; 44519 data.set(currentFrame.data, dataOffset); 44520 dataOffset += currentFrame.data.byteLength; 44521 } 44522 44523 return data; 44524 }; 44525 44526 var audioFrameUtils = { 44527 prefixWithSilence: prefixWithSilence, 44528 trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts, 44529 generateSampleTable: generateSampleTable, 44530 concatenateFrameData: concatenateFrameData 44531 }; 44532 /** 44533 * mux.js 44534 * 44535 * Copyright (c) Brightcove 44536 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 44537 */ 44538 44539 var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS; 44540 /** 44541 * Store information about the start and end of the track and the 44542 * duration for each frame/sample we process in order to calculate 44543 * the baseMediaDecodeTime 44544 */ 44545 44546 var collectDtsInfo = function collectDtsInfo(track, data) { 44547 if (typeof data.pts === 'number') { 44548 if (track.timelineStartInfo.pts === undefined) { 44549 track.timelineStartInfo.pts = data.pts; 44550 } 44551 44552 if (track.minSegmentPts === undefined) { 44553 track.minSegmentPts = data.pts; 44554 } else { 44555 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 44556 } 44557 44558 if (track.maxSegmentPts === undefined) { 44559 track.maxSegmentPts = data.pts; 44560 } else { 44561 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 44562 } 44563 } 44564 44565 if (typeof data.dts === 'number') { 44566 if (track.timelineStartInfo.dts === undefined) { 44567 track.timelineStartInfo.dts = data.dts; 44568 } 44569 44570 if (track.minSegmentDts === undefined) { 44571 track.minSegmentDts = data.dts; 44572 } else { 44573 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 44574 } 44575 44576 if (track.maxSegmentDts === undefined) { 44577 track.maxSegmentDts = data.dts; 44578 } else { 44579 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 44580 } 44581 } 44582 }; 44583 /** 44584 * Clear values used to calculate the baseMediaDecodeTime between 44585 * tracks 44586 */ 44587 44588 44589 var clearDtsInfo = function clearDtsInfo(track) { 44590 delete track.minSegmentDts; 44591 delete track.maxSegmentDts; 44592 delete track.minSegmentPts; 44593 delete track.maxSegmentPts; 44594 }; 44595 /** 44596 * Calculate the track's baseMediaDecodeTime based on the earliest 44597 * DTS the transmuxer has ever seen and the minimum DTS for the 44598 * current track 44599 * @param track {object} track metadata configuration 44600 * @param keepOriginalTimestamps {boolean} If true, keep the timestamps 44601 * in the source; false to adjust the first segment to start at 0. 44602 */ 44603 44604 44605 var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) { 44606 var baseMediaDecodeTime, 44607 scale, 44608 minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero. 44609 44610 if (!keepOriginalTimestamps) { 44611 minSegmentDts -= track.timelineStartInfo.dts; 44612 } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 44613 // we want the start of the first segment to be placed 44614 44615 44616 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first 44617 44618 baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative 44619 44620 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 44621 44622 if (track.type === 'audio') { 44623 // Audio has a different clock equal to the sampling_rate so we need to 44624 // scale the PTS values into the clock rate of the track 44625 scale = track.samplerate / ONE_SECOND_IN_TS$3; 44626 baseMediaDecodeTime *= scale; 44627 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 44628 } 44629 44630 return baseMediaDecodeTime; 44631 }; 44632 44633 var trackDecodeInfo = { 44634 clearDtsInfo: clearDtsInfo, 44635 calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime, 44636 collectDtsInfo: collectDtsInfo 44637 }; 44638 /** 44639 * mux.js 44640 * 44641 * Copyright (c) Brightcove 44642 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 44643 * 44644 * Reads in-band caption information from a video elementary 44645 * stream. Captions must follow the CEA-708 standard for injection 44646 * into an MPEG-2 transport streams. 44647 * @see https://en.wikipedia.org/wiki/CEA-708 44648 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 44649 */ 44650 // payload type field to indicate how they are to be 44651 // interpreted. CEAS-708 caption content is always transmitted with 44652 // payload type 0x04. 44653 44654 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 44655 RBSP_TRAILING_BITS = 128; 44656 /** 44657 * Parse a supplemental enhancement information (SEI) NAL unit. 44658 * Stops parsing once a message of type ITU T T35 has been found. 44659 * 44660 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 44661 * @return {object} the parsed SEI payload 44662 * @see Rec. ITU-T H.264, 7.3.2.3.1 44663 */ 44664 44665 var parseSei = function parseSei(bytes) { 44666 var i = 0, 44667 result = { 44668 payloadType: -1, 44669 payloadSize: 0 44670 }, 44671 payloadType = 0, 44672 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 44673 44674 while (i < bytes.byteLength) { 44675 // stop once we have hit the end of the sei_rbsp 44676 if (bytes[i] === RBSP_TRAILING_BITS) { 44677 break; 44678 } // Parse payload type 44679 44680 44681 while (bytes[i] === 0xFF) { 44682 payloadType += 255; 44683 i++; 44684 } 44685 44686 payloadType += bytes[i++]; // Parse payload size 44687 44688 while (bytes[i] === 0xFF) { 44689 payloadSize += 255; 44690 i++; 44691 } 44692 44693 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 44694 // there can only ever be one caption message in a frame's sei 44695 44696 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 44697 var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]); 44698 44699 if (userIdentifier === 'GA94') { 44700 result.payloadType = payloadType; 44701 result.payloadSize = payloadSize; 44702 result.payload = bytes.subarray(i, i + payloadSize); 44703 break; 44704 } else { 44705 result.payload = void 0; 44706 } 44707 } // skip the payload and parse the next message 44708 44709 44710 i += payloadSize; 44711 payloadType = 0; 44712 payloadSize = 0; 44713 } 44714 44715 return result; 44716 }; // see ANSI/SCTE 128-1 (2013), section 8.1 44717 44718 44719 var parseUserData = function parseUserData(sei) { 44720 // itu_t_t35_contry_code must be 181 (United States) for 44721 // captions 44722 if (sei.payload[0] !== 181) { 44723 return null; 44724 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 44725 44726 44727 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 44728 return null; 44729 } // the user_identifier should be "GA94" to indicate ATSC1 data 44730 44731 44732 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 44733 return null; 44734 } // finally, user_data_type_code should be 0x03 for caption data 44735 44736 44737 if (sei.payload[7] !== 0x03) { 44738 return null; 44739 } // return the user_data_type_structure and strip the trailing 44740 // marker bits 44741 44742 44743 return sei.payload.subarray(8, sei.payload.length - 1); 44744 }; // see CEA-708-D, section 4.4 44745 44746 44747 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 44748 var results = [], 44749 i, 44750 count, 44751 offset, 44752 data; // if this is just filler, return immediately 44753 44754 if (!(userData[0] & 0x40)) { 44755 return results; 44756 } // parse out the cc_data_1 and cc_data_2 fields 44757 44758 44759 count = userData[0] & 0x1f; 44760 44761 for (i = 0; i < count; i++) { 44762 offset = i * 3; 44763 data = { 44764 type: userData[offset + 2] & 0x03, 44765 pts: pts 44766 }; // capture cc data when cc_valid is 1 44767 44768 if (userData[offset + 2] & 0x04) { 44769 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 44770 results.push(data); 44771 } 44772 } 44773 44774 return results; 44775 }; 44776 44777 var discardEmulationPreventionBytes$1 = function discardEmulationPreventionBytes(data) { 44778 var length = data.byteLength, 44779 emulationPreventionBytesPositions = [], 44780 i = 1, 44781 newLength, 44782 newData; // Find all `Emulation Prevention Bytes` 44783 44784 while (i < length - 2) { 44785 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 44786 emulationPreventionBytesPositions.push(i + 2); 44787 i += 2; 44788 } else { 44789 i++; 44790 } 44791 } // If no Emulation Prevention Bytes were found just return the original 44792 // array 44793 44794 44795 if (emulationPreventionBytesPositions.length === 0) { 44796 return data; 44797 } // Create a new array to hold the NAL unit data 44798 44799 44800 newLength = length - emulationPreventionBytesPositions.length; 44801 newData = new Uint8Array(newLength); 44802 var sourceIndex = 0; 44803 44804 for (i = 0; i < newLength; sourceIndex++, i++) { 44805 if (sourceIndex === emulationPreventionBytesPositions[0]) { 44806 // Skip this byte 44807 sourceIndex++; // Remove this position index 44808 44809 emulationPreventionBytesPositions.shift(); 44810 } 44811 44812 newData[i] = data[sourceIndex]; 44813 } 44814 44815 return newData; 44816 }; // exports 44817 44818 44819 var captionPacketParser = { 44820 parseSei: parseSei, 44821 parseUserData: parseUserData, 44822 parseCaptionPackets: parseCaptionPackets, 44823 discardEmulationPreventionBytes: discardEmulationPreventionBytes$1, 44824 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 44825 }; // Link To Transport 44826 // ----------------- 44827 44828 var CaptionStream$1 = function CaptionStream(options) { 44829 options = options || {}; 44830 CaptionStream.prototype.init.call(this); // parse708captions flag, default to true 44831 44832 this.parse708captions_ = typeof options.parse708captions === 'boolean' ? options.parse708captions : true; 44833 this.captionPackets_ = []; 44834 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 44835 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 44836 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 44837 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 44838 ]; 44839 44840 if (this.parse708captions_) { 44841 this.cc708Stream_ = new Cea708Stream(); // eslint-disable-line no-use-before-define 44842 } 44843 44844 this.reset(); // forward data and done events from CCs to this CaptionStream 44845 44846 this.ccStreams_.forEach(function (cc) { 44847 cc.on('data', this.trigger.bind(this, 'data')); 44848 cc.on('partialdone', this.trigger.bind(this, 'partialdone')); 44849 cc.on('done', this.trigger.bind(this, 'done')); 44850 }, this); 44851 44852 if (this.parse708captions_) { 44853 this.cc708Stream_.on('data', this.trigger.bind(this, 'data')); 44854 this.cc708Stream_.on('partialdone', this.trigger.bind(this, 'partialdone')); 44855 this.cc708Stream_.on('done', this.trigger.bind(this, 'done')); 44856 } 44857 }; 44858 44859 CaptionStream$1.prototype = new stream(); 44860 44861 CaptionStream$1.prototype.push = function (event) { 44862 var sei, userData, newCaptionPackets; // only examine SEI NALs 44863 44864 if (event.nalUnitType !== 'sei_rbsp') { 44865 return; 44866 } // parse the sei 44867 44868 44869 sei = captionPacketParser.parseSei(event.escapedRBSP); // no payload data, skip 44870 44871 if (!sei.payload) { 44872 return; 44873 } // ignore everything but user_data_registered_itu_t_t35 44874 44875 44876 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 44877 return; 44878 } // parse out the user data payload 44879 44880 44881 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 44882 44883 if (!userData) { 44884 return; 44885 } // Sometimes, the same segment # will be downloaded twice. To stop the 44886 // caption data from being processed twice, we track the latest dts we've 44887 // received and ignore everything with a dts before that. However, since 44888 // data for a specific dts can be split across packets on either side of 44889 // a segment boundary, we need to make sure we *don't* ignore the packets 44890 // from the *next* segment that have dts === this.latestDts_. By constantly 44891 // tracking the number of packets received with dts === this.latestDts_, we 44892 // know how many should be ignored once we start receiving duplicates. 44893 44894 44895 if (event.dts < this.latestDts_) { 44896 // We've started getting older data, so set the flag. 44897 this.ignoreNextEqualDts_ = true; 44898 return; 44899 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 44900 this.numSameDts_--; 44901 44902 if (!this.numSameDts_) { 44903 // We've received the last duplicate packet, time to start processing again 44904 this.ignoreNextEqualDts_ = false; 44905 } 44906 44907 return; 44908 } // parse out CC data packets and save them for later 44909 44910 44911 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 44912 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 44913 44914 if (this.latestDts_ !== event.dts) { 44915 this.numSameDts_ = 0; 44916 } 44917 44918 this.numSameDts_++; 44919 this.latestDts_ = event.dts; 44920 }; 44921
vendor: 5,500 bytes, lines 44922-45062
44922 CaptionStream$1.prototype.flushCCStreams = function (flushType) { 44923 this.ccStreams_.forEach(function (cc) { 44924 return flushType === 'flush' ? cc.flush() : cc.partialFlush(); 44925 }, this); 44926 }; 44927 44928 CaptionStream$1.prototype.flushStream = function (flushType) { 44929 // make sure we actually parsed captions before proceeding 44930 if (!this.captionPackets_.length) { 44931 this.flushCCStreams(flushType); 44932 return; 44933 } // In Chrome, the Array#sort function is not stable so add a 44934 // presortIndex that we can use to ensure we get a stable-sort 44935 44936 44937 this.captionPackets_.forEach(function (elem, idx) { 44938 elem.presortIndex = idx; 44939 }); // sort caption byte-pairs based on their PTS values 44940 44941 this.captionPackets_.sort(function (a, b) { 44942 if (a.pts === b.pts) { 44943 return a.presortIndex - b.presortIndex; 44944 } 44945 44946 return a.pts - b.pts; 44947 }); 44948 this.captionPackets_.forEach(function (packet) { 44949 if (packet.type < 2) { 44950 // Dispatch packet to the right Cea608Stream 44951 this.dispatchCea608Packet(packet); 44952 } else { 44953 // Dispatch packet to the Cea708Stream 44954 this.dispatchCea708Packet(packet); 44955 } 44956 }, this); 44957 this.captionPackets_.length = 0; 44958 this.flushCCStreams(flushType); 44959 }; 44960 44961 CaptionStream$1.prototype.flush = function () { 44962 return this.flushStream('flush'); 44963 }; // Only called if handling partial data 44964 44965 44966 CaptionStream$1.prototype.partialFlush = function () { 44967 return this.flushStream('partialFlush'); 44968 }; 44969 44970 CaptionStream$1.prototype.reset = function () { 44971 this.latestDts_ = null; 44972 this.ignoreNextEqualDts_ = false; 44973 this.numSameDts_ = 0; 44974 this.activeCea608Channel_ = [null, null]; 44975 this.ccStreams_.forEach(function (ccStream) { 44976 ccStream.reset(); 44977 }); 44978 }; // From the CEA-608 spec: 44979 44980 /* 44981 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed 44982 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is 44983 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair 44984 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the 44985 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) 44986 * to switch to captioning or Text. 44987 */ 44988 // With that in mind, we ignore any data between an XDS control code and a 44989 // subsequent closed-captioning control code. 44990 44991 44992 CaptionStream$1.prototype.dispatchCea608Packet = function (packet) { 44993 // NOTE: packet.type is the CEA608 field 44994 if (this.setsTextOrXDSActive(packet)) { 44995 this.activeCea608Channel_[packet.type] = null; 44996 } else if (this.setsChannel1Active(packet)) { 44997 this.activeCea608Channel_[packet.type] = 0; 44998 } else if (this.setsChannel2Active(packet)) { 44999 this.activeCea608Channel_[packet.type] = 1; 45000 } 45001 45002 if (this.activeCea608Channel_[packet.type] === null) { 45003 // If we haven't received anything to set the active channel, or the 45004 // packets are Text/XDS data, discard the data; we don't want jumbled 45005 // captions 45006 return; 45007 } 45008 45009 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 45010 }; 45011 45012 CaptionStream$1.prototype.setsChannel1Active = function (packet) { 45013 return (packet.ccData & 0x7800) === 0x1000; 45014 }; 45015 45016 CaptionStream$1.prototype.setsChannel2Active = function (packet) { 45017 return (packet.ccData & 0x7800) === 0x1800; 45018 }; 45019 45020 CaptionStream$1.prototype.setsTextOrXDSActive = function (packet) { 45021 return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a; 45022 }; 45023 45024 CaptionStream$1.prototype.dispatchCea708Packet = function (packet) { 45025 if (this.parse708captions_) { 45026 this.cc708Stream_.push(packet); 45027 } 45028 }; // ---------------------- 45029 // Session to Application 45030 // ---------------------- 45031 // This hash maps special and extended character codes to their 45032 // proper Unicode equivalent. The first one-byte key is just a 45033 // non-standard character code. The two-byte keys that follow are 45034 // the extended CEA708 character codes, along with the preceding 45035 // 0x10 extended character byte to distinguish these codes from 45036 // non-extended character codes. Every CEA708 character code that 45037 // is not in this object maps directly to a standard unicode 45038 // character code. 45039 // The transparent space and non-breaking transparent space are 45040 // technically not fully supported since there is no code to 45041 // make them transparent, so they have normal non-transparent 45042 // stand-ins. 45043 // The special closed caption (CC) character isn't a standard 45044 // unicode character, so a fairly similar unicode character was 45045 // chosen in it's place. 45046 45047 45048 var CHARACTER_TRANSLATION_708 = { 45049 0x7f: 0x266a, 45050 // ⪠45051 0x1020: 0x20, 45052 // Transparent Space 45053 0x1021: 0xa0, 45054 // Nob-breaking Transparent Space 45055 0x1025: 0x2026, 45056 // ⦠45057 0x102a: 0x0160, 45058 // Å 45059 0x102c: 0x0152, 45060 // Å 45061 0x1030: 0x2588, 45062 // â
vendor: 14,361 bytes, lines 45063-45552
45063 0x1031: 0x2018, 45064 // â 45065 0x1032: 0x2019, 45066 // â 45067 0x1033: 0x201c, 45068 // â 45069 0x1034: 0x201d, 45070 // â 45071 0x1035: 0x2022, 45072 // ⢠45073 0x1039: 0x2122, 45074 // ⢠45075 0x103a: 0x0161, 45076 // Å¡ 45077 0x103c: 0x0153, 45078 // Å 45079 0x103d: 0x2120, 45080 // â 45081 0x103f: 0x0178, 45082 // Ÿ 45083 0x1076: 0x215b, 45084 // â 45085 0x1077: 0x215c, 45086 // â 45087 0x1078: 0x215d, 45088 // â 45089 0x1079: 0x215e, 45090 // â 45091 0x107a: 0x23d0, 45092 // â 45093 0x107b: 0x23a4, 45094 // ⤠45095 0x107c: 0x23a3, 45096 // ⣠45097 0x107d: 0x23af, 45098 // ⯠45099 0x107e: 0x23a6, 45100 // ⦠45101 0x107f: 0x23a1, 45102 // â¡ 45103 0x10a0: 0x3138 // 㸠(CC char) 45104 45105 }; 45106 45107 var get708CharFromCode = function get708CharFromCode(code) { 45108 var newCode = CHARACTER_TRANSLATION_708[code] || code; 45109 45110 if (code & 0x1000 && code === newCode) { 45111 // Invalid extended code 45112 return ''; 45113 } 45114 45115 return String.fromCharCode(newCode); 45116 }; 45117 45118 var within708TextBlock = function within708TextBlock(b) { 45119 return 0x20 <= b && b <= 0x7f || 0xa0 <= b && b <= 0xff; 45120 }; 45121 45122 var Cea708Window = function Cea708Window(windowNum) { 45123 this.windowNum = windowNum; 45124 this.reset(); 45125 }; 45126 45127 Cea708Window.prototype.reset = function () { 45128 this.clearText(); 45129 this.pendingNewLine = false; 45130 this.winAttr = {}; 45131 this.penAttr = {}; 45132 this.penLoc = {}; 45133 this.penColor = {}; // These default values are arbitrary, 45134 // defineWindow will usually override them 45135 45136 this.visible = 0; 45137 this.rowLock = 0; 45138 this.columnLock = 0; 45139 this.priority = 0; 45140 this.relativePositioning = 0; 45141 this.anchorVertical = 0; 45142 this.anchorHorizontal = 0; 45143 this.anchorPoint = 0; 45144 this.rowCount = 1; 45145 this.virtualRowCount = this.rowCount + 1; 45146 this.columnCount = 41; 45147 this.windowStyle = 0; 45148 this.penStyle = 0; 45149 }; 45150 45151 Cea708Window.prototype.getText = function () { 45152 return this.rows.join('\n'); 45153 }; 45154 45155 Cea708Window.prototype.clearText = function () { 45156 this.rows = ['']; 45157 this.rowIdx = 0; 45158 }; 45159 45160 Cea708Window.prototype.newLine = function (pts) { 45161 if (this.rows.length >= this.virtualRowCount && typeof this.beforeRowOverflow === 'function') { 45162 this.beforeRowOverflow(pts); 45163 } 45164 45165 if (this.rows.length > 0) { 45166 this.rows.push(''); 45167 this.rowIdx++; 45168 } // Show all virtual rows since there's no visible scrolling 45169 45170 45171 while (this.rows.length > this.virtualRowCount) { 45172 this.rows.shift(); 45173 this.rowIdx--; 45174 } 45175 }; 45176 45177 Cea708Window.prototype.isEmpty = function () { 45178 if (this.rows.length === 0) { 45179 return true; 45180 } else if (this.rows.length === 1) { 45181 return this.rows[0] === ''; 45182 } 45183 45184 return false; 45185 }; 45186 45187 Cea708Window.prototype.addText = function (text) { 45188 this.rows[this.rowIdx] += text; 45189 }; 45190 45191 Cea708Window.prototype.backspace = function () { 45192 if (!this.isEmpty()) { 45193 var row = this.rows[this.rowIdx]; 45194 this.rows[this.rowIdx] = row.substr(0, row.length - 1); 45195 } 45196 }; 45197 45198 var Cea708Service = function Cea708Service(serviceNum) { 45199 this.serviceNum = serviceNum; 45200 this.text = ''; 45201 this.currentWindow = new Cea708Window(-1); 45202 this.windows = []; 45203 }; 45204 /** 45205 * Initialize service windows 45206 * Must be run before service use 45207 * 45208 * @param {Integer} pts PTS value 45209 * @param {Function} beforeRowOverflow Function to execute before row overflow of a window 45210 */ 45211 45212 45213 Cea708Service.prototype.init = function (pts, beforeRowOverflow) { 45214 this.startPts = pts; 45215 45216 for (var win = 0; win < 8; win++) { 45217 this.windows[win] = new Cea708Window(win); 45218 45219 if (typeof beforeRowOverflow === 'function') { 45220 this.windows[win].beforeRowOverflow = beforeRowOverflow; 45221 } 45222 } 45223 }; 45224 /** 45225 * Set current window of service to be affected by commands 45226 * 45227 * @param {Integer} windowNum Window number 45228 */ 45229 45230 45231 Cea708Service.prototype.setCurrentWindow = function (windowNum) { 45232 this.currentWindow = this.windows[windowNum]; 45233 }; 45234 45235 var Cea708Stream = function Cea708Stream() { 45236 Cea708Stream.prototype.init.call(this); 45237 var self = this; 45238 this.current708Packet = null; 45239 this.services = {}; 45240 45241 this.push = function (packet) { 45242 if (packet.type === 3) { 45243 // 708 packet start 45244 self.new708Packet(); 45245 self.add708Bytes(packet); 45246 } else { 45247 if (self.current708Packet === null) { 45248 // This should only happen at the start of a file if there's no packet start. 45249 self.new708Packet(); 45250 } 45251 45252 self.add708Bytes(packet); 45253 } 45254 }; 45255 }; 45256 45257 Cea708Stream.prototype = new stream(); 45258 /** 45259 * Push current 708 packet, create new 708 packet. 45260 */ 45261 45262 Cea708Stream.prototype.new708Packet = function () { 45263 if (this.current708Packet !== null) { 45264 this.push708Packet(); 45265 } 45266 45267 this.current708Packet = { 45268 data: [], 45269 ptsVals: [] 45270 }; 45271 }; 45272 /** 45273 * Add pts and both bytes from packet into current 708 packet. 45274 */ 45275 45276 45277 Cea708Stream.prototype.add708Bytes = function (packet) { 45278 var data = packet.ccData; 45279 var byte0 = data >>> 8; 45280 var byte1 = data & 0xff; // I would just keep a list of packets instead of bytes, but it isn't clear in the spec 45281 // that service blocks will always line up with byte pairs. 45282 45283 this.current708Packet.ptsVals.push(packet.pts); 45284 this.current708Packet.data.push(byte0); 45285 this.current708Packet.data.push(byte1); 45286 }; 45287 /** 45288 * Parse completed 708 packet into service blocks and push each service block. 45289 */ 45290 45291 45292 Cea708Stream.prototype.push708Packet = function () { 45293 var packet708 = this.current708Packet; 45294 var packetData = packet708.data; 45295 var serviceNum = null; 45296 var blockSize = null; 45297 var i = 0; 45298 var b = packetData[i++]; 45299 packet708.seq = b >> 6; 45300 packet708.sizeCode = b & 0x3f; // 0b00111111; 45301 45302 for (; i < packetData.length; i++) { 45303 b = packetData[i++]; 45304 serviceNum = b >> 5; 45305 blockSize = b & 0x1f; // 0b00011111 45306 45307 if (serviceNum === 7 && blockSize > 0) { 45308 // Extended service num 45309 b = packetData[i++]; 45310 serviceNum = b; 45311 } 45312 45313 this.pushServiceBlock(serviceNum, i, blockSize); 45314 45315 if (blockSize > 0) { 45316 i += blockSize - 1; 45317 } 45318 } 45319 }; 45320 /** 45321 * Parse service block, execute commands, read text. 45322 * 45323 * Note: While many of these commands serve important purposes, 45324 * many others just parse out the parameters or attributes, but 45325 * nothing is done with them because this is not a full and complete 45326 * implementation of the entire 708 spec. 45327 * 45328 * @param {Integer} serviceNum Service number 45329 * @param {Integer} start Start index of the 708 packet data 45330 * @param {Integer} size Block size 45331 */ 45332 45333 45334 Cea708Stream.prototype.pushServiceBlock = function (serviceNum, start, size) { 45335 var b; 45336 var i = start; 45337 var packetData = this.current708Packet.data; 45338 var service = this.services[serviceNum]; 45339 45340 if (!service) { 45341 service = this.initService(serviceNum, i); 45342 } 45343 45344 for (; i < start + size && i < packetData.length; i++) { 45345 b = packetData[i]; 45346 45347 if (within708TextBlock(b)) { 45348 i = this.handleText(i, service); 45349 } else if (b === 0x10) { 45350 i = this.extendedCommands(i, service); 45351 } else if (0x80 <= b && b <= 0x87) { 45352 i = this.setCurrentWindow(i, service); 45353 } else if (0x98 <= b && b <= 0x9f) { 45354 i = this.defineWindow(i, service); 45355 } else if (b === 0x88) { 45356 i = this.clearWindows(i, service); 45357 } else if (b === 0x8c) { 45358 i = this.deleteWindows(i, service); 45359 } else if (b === 0x89) { 45360 i = this.displayWindows(i, service); 45361 } else if (b === 0x8a) { 45362 i = this.hideWindows(i, service); 45363 } else if (b === 0x8b) { 45364 i = this.toggleWindows(i, service); 45365 } else if (b === 0x97) { 45366 i = this.setWindowAttributes(i, service); 45367 } else if (b === 0x90) { 45368 i = this.setPenAttributes(i, service); 45369 } else if (b === 0x91) { 45370 i = this.setPenColor(i, service); 45371 } else if (b === 0x92) { 45372 i = this.setPenLocation(i, service); 45373 } else if (b === 0x8f) { 45374 service = this.reset(i, service); 45375 } else if (b === 0x08) { 45376 // BS: Backspace 45377 service.currentWindow.backspace(); 45378 } else if (b === 0x0c) { 45379 // FF: Form feed 45380 service.currentWindow.clearText(); 45381 } else if (b === 0x0d) { 45382 // CR: Carriage return 45383 service.currentWindow.pendingNewLine = true; 45384 } else if (b === 0x0e) { 45385 // HCR: Horizontal carriage return 45386 service.currentWindow.clearText(); 45387 } else if (b === 0x8d) { 45388 // DLY: Delay, nothing to do 45389 i++; 45390 } else ; 45391 } 45392 }; 45393 /** 45394 * Execute an extended command 45395 * 45396 * @param {Integer} i Current index in the 708 packet 45397 * @param {Service} service The service object to be affected 45398 * @return {Integer} New index after parsing 45399 */ 45400 45401 45402 Cea708Stream.prototype.extendedCommands = function (i, service) { 45403 var packetData = this.current708Packet.data; 45404 var b = packetData[++i]; 45405 45406 if (within708TextBlock(b)) { 45407 i = this.handleText(i, service, true); 45408 } 45409 45410 return i; 45411 }; 45412 /** 45413 * Get PTS value of a given byte index 45414 * 45415 * @param {Integer} byteIndex Index of the byte 45416 * @return {Integer} PTS 45417 */ 45418 45419 45420 Cea708Stream.prototype.getPts = function (byteIndex) { 45421 // There's 1 pts value per 2 bytes 45422 return this.current708Packet.ptsVals[Math.floor(byteIndex / 2)]; 45423 }; 45424 /** 45425 * Initializes a service 45426 * 45427 * @param {Integer} serviceNum Service number 45428 * @return {Service} Initialized service object 45429 */ 45430 45431 45432 Cea708Stream.prototype.initService = function (serviceNum, i) { 45433 var self = this; 45434 this.services[serviceNum] = new Cea708Service(serviceNum); 45435 this.services[serviceNum].init(this.getPts(i), function (pts) { 45436 self.flushDisplayed(pts, self.services[serviceNum]); 45437 }); 45438 return this.services[serviceNum]; 45439 }; 45440 /** 45441 * Execute text writing to current window 45442 * 45443 * @param {Integer} i Current index in the 708 packet 45444 * @param {Service} service The service object to be affected 45445 * @return {Integer} New index after parsing 45446 */ 45447 45448 45449 Cea708Stream.prototype.handleText = function (i, service, isExtended) { 45450 var packetData = this.current708Packet.data; 45451 var b = packetData[i]; 45452 var extended = isExtended ? 0x1000 : 0x0000; 45453 45454 var _char = get708CharFromCode(extended | b); 45455 45456 var win = service.currentWindow; 45457 45458 if (win.pendingNewLine && !win.isEmpty()) { 45459 win.newLine(this.getPts(i)); 45460 } 45461 45462 win.pendingNewLine = false; 45463 win.addText(_char); 45464 return i; 45465 }; 45466 /** 45467 * Parse and execute the CW# command. 45468 * 45469 * Set the current window. 45470 * 45471 * @param {Integer} i Current index in the 708 packet 45472 * @param {Service} service The service object to be affected 45473 * @return {Integer} New index after parsing 45474 */ 45475 45476 45477 Cea708Stream.prototype.setCurrentWindow = function (i, service) { 45478 var packetData = this.current708Packet.data; 45479 var b = packetData[i]; 45480 var windowNum = b & 0x07; 45481 service.setCurrentWindow(windowNum); 45482 return i; 45483 }; 45484 /** 45485 * Parse and execute the DF# command. 45486 * 45487 * Define a window and set it as the current window. 45488 * 45489 * @param {Integer} i Current index in the 708 packet 45490 * @param {Service} service The service object to be affected 45491 * @return {Integer} New index after parsing 45492 */ 45493 45494 45495 Cea708Stream.prototype.defineWindow = function (i, service) { 45496 var packetData = this.current708Packet.data; 45497 var b = packetData[i]; 45498 var windowNum = b & 0x07; 45499 service.setCurrentWindow(windowNum); 45500 var win = service.currentWindow; 45501 b = packetData[++i]; 45502 win.visible = (b & 0x20) >> 5; // v 45503 45504 win.rowLock = (b & 0x10) >> 4; // rl 45505 45506 win.columnLock = (b & 0x08) >> 3; // cl 45507 45508 win.priority = b & 0x07; // p 45509 45510 b = packetData[++i]; 45511 win.relativePositioning = (b & 0x80) >> 7; // rp 45512 45513 win.anchorVertical = b & 0x7f; // av 45514 45515 b = packetData[++i]; 45516 win.anchorHorizontal = b; // ah 45517 45518 b = packetData[++i]; 45519 win.anchorPoint = (b & 0xf0) >> 4; // ap 45520 45521 win.rowCount = b & 0x0f; // rc 45522 45523 b = packetData[++i]; 45524 win.columnCount = b & 0x3f; // cc 45525 45526 b = packetData[++i]; 45527 win.windowStyle = (b & 0x38) >> 3; // ws 45528 45529 win.penStyle = b & 0x07; // ps 45530 // The spec says there are (rowCount+1) "virtual rows" 45531 45532 win.virtualRowCount = win.rowCount + 1; 45533 return i; 45534 }; 45535 /** 45536 * Parse and execute the SWA command. 45537 * 45538 * Set attributes of the current window. 45539 * 45540 * @param {Integer} i Current index in the 708 packet 45541 * @param {Service} service The service object to be affected 45542 * @return {Integer} New index after parsing 45543 */ 45544 45545 45546 Cea708Stream.prototype.setWindowAttributes = function (i, service) { 45547 var packetData = this.current708Packet.data; 45548 var b = packetData[i]; 45549 var winAttr = service.currentWindow.winAttr; 45550 b = packetData[++i]; 45551 winAttr.fillOpacity = (b & 0xc0) >> 6; // fo 45552
vendor: 10,444 bytes, lines 45553-45901
45553 winAttr.fillRed = (b & 0x30) >> 4; // fr 45554 45555 winAttr.fillGreen = (b & 0x0c) >> 2; // fg 45556 45557 winAttr.fillBlue = b & 0x03; // fb 45558 45559 b = packetData[++i]; 45560 winAttr.borderType = (b & 0xc0) >> 6; // bt 45561 45562 winAttr.borderRed = (b & 0x30) >> 4; // br 45563 45564 winAttr.borderGreen = (b & 0x0c) >> 2; // bg 45565 45566 winAttr.borderBlue = b & 0x03; // bb 45567 45568 b = packetData[++i]; 45569 winAttr.borderType += (b & 0x80) >> 5; // bt 45570 45571 winAttr.wordWrap = (b & 0x40) >> 6; // ww 45572 45573 winAttr.printDirection = (b & 0x30) >> 4; // pd 45574 45575 winAttr.scrollDirection = (b & 0x0c) >> 2; // sd 45576 45577 winAttr.justify = b & 0x03; // j 45578 45579 b = packetData[++i]; 45580 winAttr.effectSpeed = (b & 0xf0) >> 4; // es 45581 45582 winAttr.effectDirection = (b & 0x0c) >> 2; // ed 45583 45584 winAttr.displayEffect = b & 0x03; // de 45585 45586 return i; 45587 }; 45588 /** 45589 * Gather text from all displayed windows and push a caption to output. 45590 * 45591 * @param {Integer} i Current index in the 708 packet 45592 * @param {Service} service The service object to be affected 45593 */ 45594 45595 45596 Cea708Stream.prototype.flushDisplayed = function (pts, service) { 45597 var displayedText = []; // TODO: Positioning not supported, displaying multiple windows will not necessarily 45598 // display text in the correct order, but sample files so far have not shown any issue. 45599 45600 for (var winId = 0; winId < 8; winId++) { 45601 if (service.windows[winId].visible && !service.windows[winId].isEmpty()) { 45602 displayedText.push(service.windows[winId].getText()); 45603 } 45604 } 45605 45606 service.endPts = pts; 45607 service.text = displayedText.join('\n\n'); 45608 this.pushCaption(service); 45609 service.startPts = pts; 45610 }; 45611 /** 45612 * Push a caption to output if the caption contains text. 45613 * 45614 * @param {Service} service The service object to be affected 45615 */ 45616 45617 45618 Cea708Stream.prototype.pushCaption = function (service) { 45619 if (service.text !== '') { 45620 this.trigger('data', { 45621 startPts: service.startPts, 45622 endPts: service.endPts, 45623 text: service.text, 45624 stream: 'cc708_' + service.serviceNum 45625 }); 45626 service.text = ''; 45627 service.startPts = service.endPts; 45628 } 45629 }; 45630 /** 45631 * Parse and execute the DSW command. 45632 * 45633 * Set visible property of windows based on the parsed bitmask. 45634 * 45635 * @param {Integer} i Current index in the 708 packet 45636 * @param {Service} service The service object to be affected 45637 * @return {Integer} New index after parsing 45638 */ 45639 45640 45641 Cea708Stream.prototype.displayWindows = function (i, service) { 45642 var packetData = this.current708Packet.data; 45643 var b = packetData[++i]; 45644 var pts = this.getPts(i); 45645 this.flushDisplayed(pts, service); 45646 45647 for (var winId = 0; winId < 8; winId++) { 45648 if (b & 0x01 << winId) { 45649 service.windows[winId].visible = 1; 45650 } 45651 } 45652 45653 return i; 45654 }; 45655 /** 45656 * Parse and execute the HDW command. 45657 * 45658 * Set visible property of windows based on the parsed bitmask. 45659 * 45660 * @param {Integer} i Current index in the 708 packet 45661 * @param {Service} service The service object to be affected 45662 * @return {Integer} New index after parsing 45663 */ 45664 45665 45666 Cea708Stream.prototype.hideWindows = function (i, service) { 45667 var packetData = this.current708Packet.data; 45668 var b = packetData[++i]; 45669 var pts = this.getPts(i); 45670 this.flushDisplayed(pts, service); 45671 45672 for (var winId = 0; winId < 8; winId++) { 45673 if (b & 0x01 << winId) { 45674 service.windows[winId].visible = 0; 45675 } 45676 } 45677 45678 return i; 45679 }; 45680 /** 45681 * Parse and execute the TGW command. 45682 * 45683 * Set visible property of windows based on the parsed bitmask. 45684 * 45685 * @param {Integer} i Current index in the 708 packet 45686 * @param {Service} service The service object to be affected 45687 * @return {Integer} New index after parsing 45688 */ 45689 45690 45691 Cea708Stream.prototype.toggleWindows = function (i, service) { 45692 var packetData = this.current708Packet.data; 45693 var b = packetData[++i]; 45694 var pts = this.getPts(i); 45695 this.flushDisplayed(pts, service); 45696 45697 for (var winId = 0; winId < 8; winId++) { 45698 if (b & 0x01 << winId) { 45699 service.windows[winId].visible ^= 1; 45700 } 45701 } 45702 45703 return i; 45704 }; 45705 /** 45706 * Parse and execute the CLW command. 45707 * 45708 * Clear text of windows based on the parsed bitmask. 45709 * 45710 * @param {Integer} i Current index in the 708 packet 45711 * @param {Service} service The service object to be affected 45712 * @return {Integer} New index after parsing 45713 */ 45714 45715 45716 Cea708Stream.prototype.clearWindows = function (i, service) { 45717 var packetData = this.current708Packet.data; 45718 var b = packetData[++i]; 45719 var pts = this.getPts(i); 45720 this.flushDisplayed(pts, service); 45721 45722 for (var winId = 0; winId < 8; winId++) { 45723 if (b & 0x01 << winId) { 45724 service.windows[winId].clearText(); 45725 } 45726 } 45727 45728 return i; 45729 }; 45730 /** 45731 * Parse and execute the DLW command. 45732 * 45733 * Re-initialize windows based on the parsed bitmask. 45734 * 45735 * @param {Integer} i Current index in the 708 packet 45736 * @param {Service} service The service object to be affected 45737 * @return {Integer} New index after parsing 45738 */ 45739 45740 45741 Cea708Stream.prototype.deleteWindows = function (i, service) { 45742 var packetData = this.current708Packet.data; 45743 var b = packetData[++i]; 45744 var pts = this.getPts(i); 45745 this.flushDisplayed(pts, service); 45746 45747 for (var winId = 0; winId < 8; winId++) { 45748 if (b & 0x01 << winId) { 45749 service.windows[winId].reset(); 45750 } 45751 } 45752 45753 return i; 45754 }; 45755 /** 45756 * Parse and execute the SPA command. 45757 * 45758 * Set pen attributes of the current window. 45759 * 45760 * @param {Integer} i Current index in the 708 packet 45761 * @param {Service} service The service object to be affected 45762 * @return {Integer} New index after parsing 45763 */ 45764 45765 45766 Cea708Stream.prototype.setPenAttributes = function (i, service) { 45767 var packetData = this.current708Packet.data; 45768 var b = packetData[i]; 45769 var penAttr = service.currentWindow.penAttr; 45770 b = packetData[++i]; 45771 penAttr.textTag = (b & 0xf0) >> 4; // tt 45772 45773 penAttr.offset = (b & 0x0c) >> 2; // o 45774 45775 penAttr.penSize = b & 0x03; // s 45776 45777 b = packetData[++i]; 45778 penAttr.italics = (b & 0x80) >> 7; // i 45779 45780 penAttr.underline = (b & 0x40) >> 6; // u 45781 45782 penAttr.edgeType = (b & 0x38) >> 3; // et 45783 45784 penAttr.fontStyle = b & 0x07; // fs 45785 45786 return i; 45787 }; 45788 /** 45789 * Parse and execute the SPC command. 45790 * 45791 * Set pen color of the current window. 45792 * 45793 * @param {Integer} i Current index in the 708 packet 45794 * @param {Service} service The service object to be affected 45795 * @return {Integer} New index after parsing 45796 */ 45797 45798 45799 Cea708Stream.prototype.setPenColor = function (i, service) { 45800 var packetData = this.current708Packet.data; 45801 var b = packetData[i]; 45802 var penColor = service.currentWindow.penColor; 45803 b = packetData[++i]; 45804 penColor.fgOpacity = (b & 0xc0) >> 6; // fo 45805 45806 penColor.fgRed = (b & 0x30) >> 4; // fr 45807 45808 penColor.fgGreen = (b & 0x0c) >> 2; // fg 45809 45810 penColor.fgBlue = b & 0x03; // fb 45811 45812 b = packetData[++i]; 45813 penColor.bgOpacity = (b & 0xc0) >> 6; // bo 45814 45815 penColor.bgRed = (b & 0x30) >> 4; // br 45816 45817 penColor.bgGreen = (b & 0x0c) >> 2; // bg 45818 45819 penColor.bgBlue = b & 0x03; // bb 45820 45821 b = packetData[++i]; 45822 penColor.edgeRed = (b & 0x30) >> 4; // er 45823 45824 penColor.edgeGreen = (b & 0x0c) >> 2; // eg 45825 45826 penColor.edgeBlue = b & 0x03; // eb 45827 45828 return i; 45829 }; 45830 /** 45831 * Parse and execute the SPL command. 45832 * 45833 * Set pen location of the current window. 45834 * 45835 * @param {Integer} i Current index in the 708 packet 45836 * @param {Service} service The service object to be affected 45837 * @return {Integer} New index after parsing 45838 */ 45839 45840 45841 Cea708Stream.prototype.setPenLocation = function (i, service) { 45842 var packetData = this.current708Packet.data; 45843 var b = packetData[i]; 45844 var penLoc = service.currentWindow.penLoc; // Positioning isn't really supported at the moment, so this essentially just inserts a linebreak 45845 45846 service.currentWindow.pendingNewLine = true; 45847 b = packetData[++i]; 45848 penLoc.row = b & 0x0f; // r 45849 45850 b = packetData[++i]; 45851 penLoc.column = b & 0x3f; // c 45852 45853 return i; 45854 }; 45855 /** 45856 * Execute the RST command. 45857 * 45858 * Reset service to a clean slate. Re-initialize. 45859 * 45860 * @param {Integer} i Current index in the 708 packet 45861 * @param {Service} service The service object to be affected 45862 * @return {Service} Re-initialized service 45863 */ 45864 45865 45866 Cea708Stream.prototype.reset = function (i, service) { 45867 var pts = this.getPts(i); 45868 this.flushDisplayed(pts, service); 45869 return this.initService(service.serviceNum, i); 45870 }; // This hash maps non-ASCII, special, and extended character codes to their 45871 // proper Unicode equivalent. The first keys that are only a single byte 45872 // are the non-standard ASCII characters, which simply map the CEA608 byte 45873 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 45874 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 45875 // can be performed regardless of the field and data channel on which the 45876 // character code was received. 45877 45878 45879 var CHARACTER_TRANSLATION = { 45880 0x2a: 0xe1, 45881 // á 45882 0x5c: 0xe9, 45883 // é 45884 0x5e: 0xed, 45885 // à 45886 0x5f: 0xf3, 45887 // ó 45888 0x60: 0xfa, 45889 // ú 45890 0x7b: 0xe7, 45891 // ç 45892 0x7c: 0xf7, 45893 // ÷ 45894 0x7d: 0xd1, 45895 // à 45896 0x7e: 0xf1, 45897 // ñ 45898 0x7f: 0x2588, 45899 // â 45900 0x0130: 0xae, 45901 // ®
45902 0x0131: 0xb0, 45903 // ° 45904 0x0132: 0xbd, 45905 // ½ 45906 0x0133: 0xbf, 45907 // ¿ 45908 0x0134: 0x2122, 45909 // ⢠45910 0x0135: 0xa2, 45911 // ¢ 45912 0x0136: 0xa3, 45913 // £ 45914 0x0137: 0x266a, 45915 // ⪠45916 0x0138: 0xe0, 45917 // à 45918 0x0139: 0xa0, 45919 // 45920 0x013a: 0xe8, 45921 // è 45922 0x013b: 0xe2, 45923 // â 45924 0x013c: 0xea, 45925 // ê 45926 0x013d: 0xee, 45927 // î 45928 0x013e: 0xf4, 45929 // ô 45930 0x013f: 0xfb, 45931 // û 45932 0x0220: 0xc1, 45933 // à 45934 0x0221: 0xc9, 45935 // à 45936 0x0222: 0xd3, 45937 // à 45938 0x0223: 0xda, 45939 // à 45940 0x0224: 0xdc, 45941 // à 45942 0x0225: 0xfc, 45943 // ü 45944 0x0226: 0x2018, 45945 // â 45946 0x0227: 0xa1, 45947 // ¡ 45948 0x0228: 0x2a, 45949 // * 45950 0x0229: 0x27, 45951 // ' 45952 0x022a: 0x2014, 45953 // â 45954 0x022b: 0xa9, 45955 // © 45956 0x022c: 0x2120, 45957 // â 45958 0x022d: 0x2022, 45959 // ⢠45960 0x022e: 0x201c, 45961 // â 45962 0x022f: 0x201d, 45963 // â 45964 0x0230: 0xc0, 45965 // à 45966 0x0231: 0xc2, 45967 // à 45968 0x0232: 0xc7, 45969 // à 45970 0x0233: 0xc8, 45971 // à 45972 0x0234: 0xca, 45973 // à 45974 0x0235: 0xcb, 45975 // à 45976 0x0236: 0xeb, 45977 // ë 45978 0x0237: 0xce, 45979 // à 45980 0x0238: 0xcf, 45981 // à 45982 0x0239: 0xef, 45983 // ï 45984 0x023a: 0xd4, 45985 // à 45986 0x023b: 0xd9, 45987 // à 45988 0x023c: 0xf9, 45989 // ù 45990 0x023d: 0xdb, 45991 // à 45992 0x023e: 0xab, 45993 // « 45994 0x023f: 0xbb, 45995 // » 45996 0x0320: 0xc3, 45997 // à 45998 0x0321: 0xe3, 45999 // ã 46000 0x0322: 0xcd, 46001 // à 46002 0x0323: 0xcc, 46003 // à 46004 0x0324: 0xec, 46005 // ì 46006 0x0325: 0xd2, 46007 // à 46008 0x0326: 0xf2, 46009 // ò 46010 0x0327: 0xd5, 46011 // à 46012 0x0328: 0xf5, 46013 // õ 46014 0x0329: 0x7b, 46015 // { 46016 0x032a: 0x7d, 46017 // } 46018 0x032b: 0x5c, 46019 // \ 46020 0x032c: 0x5e, 46021 // ^ 46022 0x032d: 0x5f, 46023 // _ 46024 0x032e: 0x7c, 46025 // | 46026 0x032f: 0x7e, 46027 // ~ 46028 0x0330: 0xc4, 46029 // à 46030 0x0331: 0xe4,
vendor: 19,390 bytes, lines 46031-46532
46031 // ä 46032 0x0332: 0xd6, 46033 // à 46034 0x0333: 0xf6, 46035 // ö 46036 0x0334: 0xdf, 46037 // à 46038 0x0335: 0xa5, 46039 // Â¥ 46040 0x0336: 0xa4, 46041 // ¤ 46042 0x0337: 0x2502, 46043 // â 46044 0x0338: 0xc5, 46045 // à 46046 0x0339: 0xe5, 46047 // Ã¥ 46048 0x033a: 0xd8, 46049 // à 46050 0x033b: 0xf8, 46051 // ø 46052 0x033c: 0x250c, 46053 // â 46054 0x033d: 0x2510, 46055 // â 46056 0x033e: 0x2514, 46057 // â 46058 0x033f: 0x2518 // â 46059 46060 }; 46061 46062 var getCharFromCode = function getCharFromCode(code) { 46063 if (code === null) { 46064 return ''; 46065 } 46066 46067 code = CHARACTER_TRANSLATION[code] || code; 46068 return String.fromCharCode(code); 46069 }; // the index of the last row in a CEA-608 display buffer 46070 46071 46072 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 46073 // getting it through bit logic. 46074 46075 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 46076 // cells. The "bottom" row is the last element in the outer array. 46077 46078 var createDisplayBuffer = function createDisplayBuffer() { 46079 var result = [], 46080 i = BOTTOM_ROW + 1; 46081 46082 while (i--) { 46083 result.push(''); 46084 } 46085 46086 return result; 46087 }; 46088 46089 var Cea608Stream = function Cea608Stream(field, dataChannel) { 46090 Cea608Stream.prototype.init.call(this); 46091 this.field_ = field || 0; 46092 this.dataChannel_ = dataChannel || 0; 46093 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 46094 this.setConstants(); 46095 this.reset(); 46096 46097 this.push = function (packet) { 46098 var data, swap, char0, char1, text; // remove the parity bits 46099 46100 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 46101 46102 if (data === this.lastControlCode_) { 46103 this.lastControlCode_ = null; 46104 return; 46105 } // Store control codes 46106 46107 46108 if ((data & 0xf000) === 0x1000) { 46109 this.lastControlCode_ = data; 46110 } else if (data !== this.PADDING_) { 46111 this.lastControlCode_ = null; 46112 } 46113 46114 char0 = data >>> 8; 46115 char1 = data & 0xff; 46116 46117 if (data === this.PADDING_) { 46118 return; 46119 } else if (data === this.RESUME_CAPTION_LOADING_) { 46120 this.mode_ = 'popOn'; 46121 } else if (data === this.END_OF_CAPTION_) { 46122 // If an EOC is received while in paint-on mode, the displayed caption 46123 // text should be swapped to non-displayed memory as if it was a pop-on 46124 // caption. Because of that, we should explicitly switch back to pop-on 46125 // mode 46126 this.mode_ = 'popOn'; 46127 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 46128 46129 this.flushDisplayed(packet.pts); // flip memory 46130 46131 swap = this.displayed_; 46132 this.displayed_ = this.nonDisplayed_; 46133 this.nonDisplayed_ = swap; // start measuring the time to display the caption 46134 46135 this.startPts_ = packet.pts; 46136 } else if (data === this.ROLL_UP_2_ROWS_) { 46137 this.rollUpRows_ = 2; 46138 this.setRollUp(packet.pts); 46139 } else if (data === this.ROLL_UP_3_ROWS_) { 46140 this.rollUpRows_ = 3; 46141 this.setRollUp(packet.pts); 46142 } else if (data === this.ROLL_UP_4_ROWS_) { 46143 this.rollUpRows_ = 4; 46144 this.setRollUp(packet.pts); 46145 } else if (data === this.CARRIAGE_RETURN_) { 46146 this.clearFormatting(packet.pts); 46147 this.flushDisplayed(packet.pts); 46148 this.shiftRowsUp_(); 46149 this.startPts_ = packet.pts; 46150 } else if (data === this.BACKSPACE_) { 46151 if (this.mode_ === 'popOn') { 46152 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 46153 } else { 46154 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 46155 } 46156 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 46157 this.flushDisplayed(packet.pts); 46158 this.displayed_ = createDisplayBuffer(); 46159 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 46160 this.nonDisplayed_ = createDisplayBuffer(); 46161 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 46162 if (this.mode_ !== 'paintOn') { 46163 // NOTE: This should be removed when proper caption positioning is 46164 // implemented 46165 this.flushDisplayed(packet.pts); 46166 this.displayed_ = createDisplayBuffer(); 46167 } 46168 46169 this.mode_ = 'paintOn'; 46170 this.startPts_ = packet.pts; // Append special characters to caption text 46171 } else if (this.isSpecialCharacter(char0, char1)) { 46172 // Bitmask char0 so that we can apply character transformations 46173 // regardless of field and data channel. 46174 // Then byte-shift to the left and OR with char1 so we can pass the 46175 // entire character code to `getCharFromCode`. 46176 char0 = (char0 & 0x03) << 8; 46177 text = getCharFromCode(char0 | char1); 46178 this[this.mode_](packet.pts, text); 46179 this.column_++; // Append extended characters to caption text 46180 } else if (this.isExtCharacter(char0, char1)) { 46181 // Extended characters always follow their "non-extended" equivalents. 46182 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 46183 // decoders are supposed to drop the "è", while compliant decoders 46184 // backspace the "e" and insert "è". 46185 // Delete the previous character 46186 if (this.mode_ === 'popOn') { 46187 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 46188 } else { 46189 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 46190 } // Bitmask char0 so that we can apply character transformations 46191 // regardless of field and data channel. 46192 // Then byte-shift to the left and OR with char1 so we can pass the 46193 // entire character code to `getCharFromCode`. 46194 46195 46196 char0 = (char0 & 0x03) << 8; 46197 text = getCharFromCode(char0 | char1); 46198 this[this.mode_](packet.pts, text); 46199 this.column_++; // Process mid-row codes 46200 } else if (this.isMidRowCode(char0, char1)) { 46201 // Attributes are not additive, so clear all formatting 46202 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 46203 // should be replaced with spaces, so add one now 46204 46205 this[this.mode_](packet.pts, ' '); 46206 this.column_++; 46207 46208 if ((char1 & 0xe) === 0xe) { 46209 this.addFormatting(packet.pts, ['i']); 46210 } 46211 46212 if ((char1 & 0x1) === 0x1) { 46213 this.addFormatting(packet.pts, ['u']); 46214 } // Detect offset control codes and adjust cursor 46215 46216 } else if (this.isOffsetControlCode(char0, char1)) { 46217 // Cursor position is set by indent PAC (see below) in 4-column 46218 // increments, with an additional offset code of 1-3 to reach any 46219 // of the 32 columns specified by CEA-608. So all we need to do 46220 // here is increment the column cursor by the given offset. 46221 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 46222 } else if (this.isPAC(char0, char1)) { 46223 // There's no logic for PAC -> row mapping, so we have to just 46224 // find the row code in an array and use its index :( 46225 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 46226 46227 if (this.mode_ === 'rollUp') { 46228 // This implies that the base row is incorrectly set. 46229 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 46230 // of roll-up rows set. 46231 if (row - this.rollUpRows_ + 1 < 0) { 46232 row = this.rollUpRows_ - 1; 46233 } 46234 46235 this.setRollUp(packet.pts, row); 46236 } 46237 46238 if (row !== this.row_) { 46239 // formatting is only persistent for current row 46240 this.clearFormatting(packet.pts); 46241 this.row_ = row; 46242 } // All PACs can apply underline, so detect and apply 46243 // (All odd-numbered second bytes set underline) 46244 46245 46246 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 46247 this.addFormatting(packet.pts, ['u']); 46248 } 46249 46250 if ((data & 0x10) === 0x10) { 46251 // We've got an indent level code. Each successive even number 46252 // increments the column cursor by 4, so we can get the desired 46253 // column position by bit-shifting to the right (to get n/2) 46254 // and multiplying by 4. 46255 this.column_ = ((data & 0xe) >> 1) * 4; 46256 } 46257 46258 if (this.isColorPAC(char1)) { 46259 // it's a color code, though we only support white, which 46260 // can be either normal or italicized. white italics can be 46261 // either 0x4e or 0x6e depending on the row, so we just 46262 // bitwise-and with 0xe to see if italics should be turned on 46263 if ((char1 & 0xe) === 0xe) { 46264 this.addFormatting(packet.pts, ['i']); 46265 } 46266 } // We have a normal character in char0, and possibly one in char1 46267 46268 } else if (this.isNormalChar(char0)) { 46269 if (char1 === 0x00) { 46270 char1 = null; 46271 } 46272 46273 text = getCharFromCode(char0); 46274 text += getCharFromCode(char1); 46275 this[this.mode_](packet.pts, text); 46276 this.column_ += text.length; 46277 } // finish data processing 46278 46279 }; 46280 }; 46281 46282 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 46283 // display buffer 46284 46285 Cea608Stream.prototype.flushDisplayed = function (pts) { 46286 var content = this.displayed_ // remove spaces from the start and end of the string 46287 .map(function (row, index) { 46288 try { 46289 return row.trim(); 46290 } catch (e) { 46291 // Ordinarily, this shouldn't happen. However, caption 46292 // parsing errors should not throw exceptions and 46293 // break playback. 46294 this.trigger('log', { 46295 level: 'warn', 46296 message: 'Skipping a malformed 608 caption at index ' + index + '.' 46297 }); 46298 return ''; 46299 } 46300 }, this) // combine all text rows to display in one cue 46301 .join('\n') // and remove blank rows from the start and end, but not the middle 46302 .replace(/^\n+|\n+$/g, ''); 46303 46304 if (content.length) { 46305 this.trigger('data', { 46306 startPts: this.startPts_, 46307 endPts: pts, 46308 text: content, 46309 stream: this.name_ 46310 }); 46311 } 46312 }; 46313 /** 46314 * Zero out the data, used for startup and on seek 46315 */ 46316 46317 46318 Cea608Stream.prototype.reset = function () { 46319 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 46320 // actually display captions. If a caption is shifted to a row 46321 // with a lower index than this, it is cleared from the display 46322 // buffer 46323 46324 this.topRow_ = 0; 46325 this.startPts_ = 0; 46326 this.displayed_ = createDisplayBuffer(); 46327 this.nonDisplayed_ = createDisplayBuffer(); 46328 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 46329 46330 this.column_ = 0; 46331 this.row_ = BOTTOM_ROW; 46332 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 46333 46334 this.formatting_ = []; 46335 }; 46336 /** 46337 * Sets up control code and related constants for this instance 46338 */ 46339 46340 46341 Cea608Stream.prototype.setConstants = function () { 46342 // The following attributes have these uses: 46343 // ext_ : char0 for mid-row codes, and the base for extended 46344 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 46345 // extended codes) 46346 // control_: char0 for control codes, except byte-shifted to the 46347 // left so that we can do this.control_ | CONTROL_CODE 46348 // offset_: char0 for tab offset codes 46349 // 46350 // It's also worth noting that control codes, and _only_ control codes, 46351 // differ between field 1 and field2. Field 2 control codes are always 46352 // their field 1 value plus 1. That's why there's the "| field" on the 46353 // control value. 46354 if (this.dataChannel_ === 0) { 46355 this.BASE_ = 0x10; 46356 this.EXT_ = 0x11; 46357 this.CONTROL_ = (0x14 | this.field_) << 8; 46358 this.OFFSET_ = 0x17; 46359 } else if (this.dataChannel_ === 1) { 46360 this.BASE_ = 0x18; 46361 this.EXT_ = 0x19; 46362 this.CONTROL_ = (0x1c | this.field_) << 8; 46363 this.OFFSET_ = 0x1f; 46364 } // Constants for the LSByte command codes recognized by Cea608Stream. This 46365 // list is not exhaustive. For a more comprehensive listing and semantics see 46366 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 46367 // Padding 46368 46369 46370 this.PADDING_ = 0x0000; // Pop-on Mode 46371 46372 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 46373 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 46374 46375 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 46376 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 46377 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 46378 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 46379 46380 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 46381 46382 this.BACKSPACE_ = this.CONTROL_ | 0x21; 46383 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 46384 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 46385 }; 46386 /** 46387 * Detects if the 2-byte packet data is a special character 46388 * 46389 * Special characters have a second byte in the range 0x30 to 0x3f, 46390 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 46391 * data channel 2). 46392 * 46393 * @param {Integer} char0 The first byte 46394 * @param {Integer} char1 The second byte 46395 * @return {Boolean} Whether the 2 bytes are an special character 46396 */ 46397 46398 46399 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 46400 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 46401 }; 46402 /** 46403 * Detects if the 2-byte packet data is an extended character 46404 * 46405 * Extended characters have a second byte in the range 0x20 to 0x3f, 46406 * with the first byte being 0x12 or 0x13 (for data channel 1) or 46407 * 0x1a or 0x1b (for data channel 2). 46408 * 46409 * @param {Integer} char0 The first byte 46410 * @param {Integer} char1 The second byte 46411 * @return {Boolean} Whether the 2 bytes are an extended character 46412 */ 46413 46414 46415 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 46416 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 46417 }; 46418 /** 46419 * Detects if the 2-byte packet is a mid-row code 46420 * 46421 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 46422 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 46423 * channel 2). 46424 * 46425 * @param {Integer} char0 The first byte 46426 * @param {Integer} char1 The second byte 46427 * @return {Boolean} Whether the 2 bytes are a mid-row code 46428 */ 46429 46430 46431 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 46432 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 46433 }; 46434 /** 46435 * Detects if the 2-byte packet is an offset control code 46436 * 46437 * Offset control codes have a second byte in the range 0x21 to 0x23, 46438 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 46439 * data channel 2). 46440 * 46441 * @param {Integer} char0 The first byte 46442 * @param {Integer} char1 The second byte 46443 * @return {Boolean} Whether the 2 bytes are an offset control code 46444 */ 46445 46446 46447 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 46448 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 46449 }; 46450 /** 46451 * Detects if the 2-byte packet is a Preamble Address Code 46452 * 46453 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 46454 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 46455 * range 0x40 to 0x7f. 46456 * 46457 * @param {Integer} char0 The first byte 46458 * @param {Integer} char1 The second byte 46459 * @return {Boolean} Whether the 2 bytes are a PAC 46460 */ 46461 46462 46463 Cea608Stream.prototype.isPAC = function (char0, char1) { 46464 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 46465 }; 46466 /** 46467 * Detects if a packet's second byte is in the range of a PAC color code 46468 * 46469 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 46470 * 0x60 to 0x6f. 46471 * 46472 * @param {Integer} char1 The second byte 46473 * @return {Boolean} Whether the byte is a color PAC 46474 */ 46475 46476 46477 Cea608Stream.prototype.isColorPAC = function (char1) { 46478 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 46479 }; 46480 /** 46481 * Detects if a single byte is in the range of a normal character 46482 * 46483 * Normal text bytes are in the range 0x20 to 0x7f. 46484 * 46485 * @param {Integer} char The byte 46486 * @return {Boolean} Whether the byte is a normal character 46487 */ 46488 46489 46490 Cea608Stream.prototype.isNormalChar = function (_char2) { 46491 return _char2 >= 0x20 && _char2 <= 0x7f; 46492 }; 46493 /** 46494 * Configures roll-up 46495 * 46496 * @param {Integer} pts Current PTS 46497 * @param {Integer} newBaseRow Used by PACs to slide the current window to 46498 * a new position 46499 */ 46500 46501 46502 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 46503 // Reset the base row to the bottom row when switching modes 46504 if (this.mode_ !== 'rollUp') { 46505 this.row_ = BOTTOM_ROW; 46506 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 46507 46508 this.flushDisplayed(pts); 46509 this.nonDisplayed_ = createDisplayBuffer(); 46510 this.displayed_ = createDisplayBuffer(); 46511 } 46512 46513 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 46514 // move currently displayed captions (up or down) to the new base row 46515 for (var i = 0; i < this.rollUpRows_; i++) { 46516 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 46517 this.displayed_[this.row_ - i] = ''; 46518 } 46519 } 46520 46521 if (newBaseRow === undefined) { 46522 newBaseRow = this.row_; 46523 } 46524 46525 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 46526 }; // Adds the opening HTML tag for the passed character to the caption text, 46527 // and keeps track of it for later closing 46528 46529 46530 Cea608Stream.prototype.addFormatting = function (pts, format) { 46531 this.formatting_ = this.formatting_.concat(format); 46532 var text = format.reduce(function (text, format) {
vendor: 15,531 bytes, lines 46533-46977
46533 return text + '<' + format + '>'; 46534 }, ''); 46535 this[this.mode_](pts, text); 46536 }; // Adds HTML closing tags for current formatting to caption text and 46537 // clears remembered formatting 46538 46539 46540 Cea608Stream.prototype.clearFormatting = function (pts) { 46541 if (!this.formatting_.length) { 46542 return; 46543 } 46544 46545 var text = this.formatting_.reverse().reduce(function (text, format) { 46546 return text + '</' + format + '>'; 46547 }, ''); 46548 this.formatting_ = []; 46549 this[this.mode_](pts, text); 46550 }; // Mode Implementations 46551 46552 46553 Cea608Stream.prototype.popOn = function (pts, text) { 46554 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 46555 46556 baseRow += text; 46557 this.nonDisplayed_[this.row_] = baseRow; 46558 }; 46559 46560 Cea608Stream.prototype.rollUp = function (pts, text) { 46561 var baseRow = this.displayed_[this.row_]; 46562 baseRow += text; 46563 this.displayed_[this.row_] = baseRow; 46564 }; 46565 46566 Cea608Stream.prototype.shiftRowsUp_ = function () { 46567 var i; // clear out inactive rows 46568 46569 for (i = 0; i < this.topRow_; i++) { 46570 this.displayed_[i] = ''; 46571 } 46572 46573 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 46574 this.displayed_[i] = ''; 46575 } // shift displayed rows up 46576 46577 46578 for (i = this.topRow_; i < this.row_; i++) { 46579 this.displayed_[i] = this.displayed_[i + 1]; 46580 } // clear out the bottom row 46581 46582 46583 this.displayed_[this.row_] = ''; 46584 }; 46585 46586 Cea608Stream.prototype.paintOn = function (pts, text) { 46587 var baseRow = this.displayed_[this.row_]; 46588 baseRow += text; 46589 this.displayed_[this.row_] = baseRow; 46590 }; // exports 46591 46592 46593 var captionStream = { 46594 CaptionStream: CaptionStream$1, 46595 Cea608Stream: Cea608Stream, 46596 Cea708Stream: Cea708Stream 46597 }; 46598 /** 46599 * mux.js 46600 * 46601 * Copyright (c) Brightcove 46602 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 46603 */ 46604 46605 var streamTypes = { 46606 H264_STREAM_TYPE: 0x1B, 46607 ADTS_STREAM_TYPE: 0x0F, 46608 METADATA_STREAM_TYPE: 0x15 46609 }; 46610 var MAX_TS = 8589934592; 46611 var RO_THRESH = 4294967296; 46612 var TYPE_SHARED = 'shared'; 46613 46614 var handleRollover$1 = function handleRollover(value, reference) { 46615 var direction = 1; 46616 46617 if (value > reference) { 46618 // If the current timestamp value is greater than our reference timestamp and we detect a 46619 // timestamp rollover, this means the roll over is happening in the opposite direction. 46620 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 46621 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 46622 // rollover point. In loading this segment, the timestamp values will be very large, 46623 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 46624 // the time stamp to be `value - 2^33`. 46625 direction = -1; 46626 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 46627 // cause an incorrect adjustment. 46628 46629 46630 while (Math.abs(reference - value) > RO_THRESH) { 46631 value += direction * MAX_TS; 46632 } 46633 46634 return value; 46635 }; 46636 46637 var TimestampRolloverStream$1 = function TimestampRolloverStream(type) { 46638 var lastDTS, referenceDTS; 46639 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 46640 // video and audio. We could use `undefined` here, but having a string 46641 // makes debugging a little clearer. 46642 46643 this.type_ = type || TYPE_SHARED; 46644 46645 this.push = function (data) { 46646 // Any "shared" rollover streams will accept _all_ data. Otherwise, 46647 // streams will only accept data that matches their type. 46648 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 46649 return; 46650 } 46651 46652 if (referenceDTS === undefined) { 46653 referenceDTS = data.dts; 46654 } 46655 46656 data.dts = handleRollover$1(data.dts, referenceDTS); 46657 data.pts = handleRollover$1(data.pts, referenceDTS); 46658 lastDTS = data.dts; 46659 this.trigger('data', data); 46660 }; 46661 46662 this.flush = function () { 46663 referenceDTS = lastDTS; 46664 this.trigger('done'); 46665 }; 46666 46667 this.endTimeline = function () { 46668 this.flush(); 46669 this.trigger('endedtimeline'); 46670 }; 46671 46672 this.discontinuity = function () { 46673 referenceDTS = void 0; 46674 lastDTS = void 0; 46675 }; 46676 46677 this.reset = function () { 46678 this.discontinuity(); 46679 this.trigger('reset'); 46680 }; 46681 }; 46682 46683 TimestampRolloverStream$1.prototype = new stream(); 46684 var timestampRolloverStream = { 46685 TimestampRolloverStream: TimestampRolloverStream$1, 46686 handleRollover: handleRollover$1 46687 }; 46688 46689 var percentEncode$1 = function percentEncode(bytes, start, end) { 46690 var i, 46691 result = ''; 46692 46693 for (i = start; i < end; i++) { 46694 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 46695 } 46696 46697 return result; 46698 }, 46699 // return the string representation of the specified byte range, 46700 // interpreted as UTf-8. 46701 parseUtf8 = function parseUtf8(bytes, start, end) { 46702 return decodeURIComponent(percentEncode$1(bytes, start, end)); 46703 }, 46704 // return the string representation of the specified byte range, 46705 // interpreted as ISO-8859-1. 46706 parseIso88591$1 = function parseIso88591(bytes, start, end) { 46707 return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line 46708 }, 46709 parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) { 46710 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 46711 }, 46712 tagParsers = { 46713 TXXX: function TXXX(tag) { 46714 var i; 46715 46716 if (tag.data[0] !== 3) { 46717 // ignore frames with unrecognized character encodings 46718 return; 46719 } 46720 46721 for (i = 1; i < tag.data.length; i++) { 46722 if (tag.data[i] === 0) { 46723 // parse the text fields 46724 tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value 46725 46726 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 46727 break; 46728 } 46729 } 46730 46731 tag.data = tag.value; 46732 }, 46733 WXXX: function WXXX(tag) { 46734 var i; 46735 46736 if (tag.data[0] !== 3) { 46737 // ignore frames with unrecognized character encodings 46738 return; 46739 } 46740 46741 for (i = 1; i < tag.data.length; i++) { 46742 if (tag.data[i] === 0) { 46743 // parse the description and URL fields 46744 tag.description = parseUtf8(tag.data, 1, i); 46745 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 46746 break; 46747 } 46748 } 46749 }, 46750 PRIV: function PRIV(tag) { 46751 var i; 46752 46753 for (i = 0; i < tag.data.length; i++) { 46754 if (tag.data[i] === 0) { 46755 // parse the description and URL fields 46756 tag.owner = parseIso88591$1(tag.data, 0, i); 46757 break; 46758 } 46759 } 46760 46761 tag.privateData = tag.data.subarray(i + 1); 46762 tag.data = tag.privateData; 46763 } 46764 }, 46765 _MetadataStream; 46766 46767 _MetadataStream = function MetadataStream(options) { 46768 var settings = { 46769 // the bytes of the program-level descriptor field in MP2T 46770 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 46771 // program element descriptors" 46772 descriptor: options && options.descriptor 46773 }, 46774 // the total size in bytes of the ID3 tag being parsed 46775 tagSize = 0, 46776 // tag data that is not complete enough to be parsed 46777 buffer = [], 46778 // the total number of bytes currently in the buffer 46779 bufferSize = 0, 46780 i; 46781 46782 _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type 46783 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 46784 46785 46786 this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16); 46787 46788 if (settings.descriptor) { 46789 for (i = 0; i < settings.descriptor.length; i++) { 46790 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 46791 } 46792 } 46793 46794 this.push = function (chunk) { 46795 var tag, frameStart, frameSize, frame, i, frameHeader; 46796 46797 if (chunk.type !== 'timed-metadata') { 46798 return; 46799 } // if data_alignment_indicator is set in the PES header, 46800 // we must have the start of a new ID3 tag. Assume anything 46801 // remaining in the buffer was malformed and throw it out 46802 46803 46804 if (chunk.dataAlignmentIndicator) { 46805 bufferSize = 0; 46806 buffer.length = 0; 46807 } // ignore events that don't look like ID3 data 46808 46809 46810 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))) { 46811 this.trigger('log', { 46812 level: 'warn', 46813 message: 'Skipping unrecognized metadata packet' 46814 }); 46815 return; 46816 } // add this chunk to the data we've collected so far 46817 46818 46819 buffer.push(chunk); 46820 bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header 46821 46822 if (buffer.length === 1) { 46823 // the frame size is transmitted as a 28-bit integer in the 46824 // last four bytes of the ID3 header. 46825 // The most significant bit of each byte is dropped and the 46826 // results concatenated to recover the actual value. 46827 tagSize = parseSyncSafeInteger$1(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more 46828 // convenient for our comparisons to include it 46829 46830 tagSize += 10; 46831 } // if the entire frame has not arrived, wait for more data 46832 46833 46834 if (bufferSize < tagSize) { 46835 return; 46836 } // collect the entire frame so it can be parsed 46837 46838 46839 tag = { 46840 data: new Uint8Array(tagSize), 46841 frames: [], 46842 pts: buffer[0].pts, 46843 dts: buffer[0].dts 46844 }; 46845 46846 for (i = 0; i < tagSize;) { 46847 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 46848 i += buffer[0].data.byteLength; 46849 bufferSize -= buffer[0].data.byteLength; 46850 buffer.shift(); 46851 } // find the start of the first frame and the end of the tag 46852 46853 46854 frameStart = 10; 46855 46856 if (tag.data[5] & 0x40) { 46857 // advance the frame start past the extended header 46858 frameStart += 4; // header size field 46859 46860 frameStart += parseSyncSafeInteger$1(tag.data.subarray(10, 14)); // clip any padding off the end 46861 46862 tagSize -= parseSyncSafeInteger$1(tag.data.subarray(16, 20)); 46863 } // parse one or more ID3 frames 46864 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 46865 46866 46867 do { 46868 // determine the number of bytes in this frame 46869 frameSize = parseSyncSafeInteger$1(tag.data.subarray(frameStart + 4, frameStart + 8)); 46870 46871 if (frameSize < 1) { 46872 this.trigger('log', { 46873 level: 'warn', 46874 message: 'Malformed ID3 frame encountered. Skipping metadata parsing.' 46875 }); 46876 return; 46877 } 46878 46879 frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]); 46880 frame = { 46881 id: frameHeader, 46882 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 46883 }; 46884 frame.key = frame.id; 46885 46886 if (tagParsers[frame.id]) { 46887 tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start 46888 // time for raw AAC data 46889 46890 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 46891 var d = frame.data, 46892 size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 46893 size *= 4; 46894 size += d[7] & 0x03; 46895 frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based 46896 // on the value of this frame 46897 // we couldn't have known the appropriate pts and dts before 46898 // parsing this ID3 tag so set those values now 46899 46900 if (tag.pts === undefined && tag.dts === undefined) { 46901 tag.pts = frame.timeStamp; 46902 tag.dts = frame.timeStamp; 46903 } 46904 46905 this.trigger('timestamp', frame); 46906 } 46907 } 46908 46909 tag.frames.push(frame); 46910 frameStart += 10; // advance past the frame header 46911 46912 frameStart += frameSize; // advance past the frame body 46913 } while (frameStart < tagSize); 46914 46915 this.trigger('data', tag); 46916 }; 46917 }; 46918 46919 _MetadataStream.prototype = new stream(); 46920 var metadataStream = _MetadataStream; 46921 var TimestampRolloverStream = timestampRolloverStream.TimestampRolloverStream; // object types 46922 46923 var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants 46924 46925 46926 var MP2T_PACKET_LENGTH$1 = 188, 46927 // bytes 46928 SYNC_BYTE$1 = 0x47; 46929 /** 46930 * Splits an incoming stream of binary data into MPEG-2 Transport 46931 * Stream packets. 46932 */ 46933 46934 _TransportPacketStream = function TransportPacketStream() { 46935 var buffer = new Uint8Array(MP2T_PACKET_LENGTH$1), 46936 bytesInBuffer = 0; 46937 46938 _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream. 46939 46940 /** 46941 * Split a stream of data into M2TS packets 46942 **/ 46943 46944 46945 this.push = function (bytes) { 46946 var startIndex = 0, 46947 endIndex = MP2T_PACKET_LENGTH$1, 46948 everything; // If there are bytes remaining from the last segment, prepend them to the 46949 // bytes that were pushed in 46950 46951 if (bytesInBuffer) { 46952 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 46953 everything.set(buffer.subarray(0, bytesInBuffer)); 46954 everything.set(bytes, bytesInBuffer); 46955 bytesInBuffer = 0; 46956 } else { 46957 everything = bytes; 46958 } // While we have enough data for a packet 46959 46960 46961 while (endIndex < everything.byteLength) { 46962 // Look for a pair of start and end sync bytes in the data.. 46963 if (everything[startIndex] === SYNC_BYTE$1 && everything[endIndex] === SYNC_BYTE$1) { 46964 // We found a packet so emit it and jump one whole packet forward in 46965 // the stream 46966 this.trigger('data', everything.subarray(startIndex, endIndex)); 46967 startIndex += MP2T_PACKET_LENGTH$1; 46968 endIndex += MP2T_PACKET_LENGTH$1; 46969 continue; 46970 } // If we get here, we have somehow become de-synchronized and we need to step 46971 // forward one byte at a time until we find a pair of sync bytes that denote 46972 // a packet 46973 46974 46975 startIndex++; 46976 endIndex++; 46977 } // If there was some data left over at the end of the segment that couldn't
vendor: 17,820 bytes, lines 46978-47459
46978 // possibly be a whole packet, keep it because it might be the start of a packet 46979 // that continues in the next segment 46980 46981 46982 if (startIndex < everything.byteLength) { 46983 buffer.set(everything.subarray(startIndex), 0); 46984 bytesInBuffer = everything.byteLength - startIndex; 46985 } 46986 }; 46987 /** 46988 * Passes identified M2TS packets to the TransportParseStream to be parsed 46989 **/ 46990 46991 46992 this.flush = function () { 46993 // If the buffer contains a whole packet when we are being flushed, emit it 46994 // and empty the buffer. Otherwise hold onto the data because it may be 46995 // important for decoding the next segment 46996 if (bytesInBuffer === MP2T_PACKET_LENGTH$1 && buffer[0] === SYNC_BYTE$1) { 46997 this.trigger('data', buffer); 46998 bytesInBuffer = 0; 46999 } 47000 47001 this.trigger('done'); 47002 }; 47003 47004 this.endTimeline = function () { 47005 this.flush(); 47006 this.trigger('endedtimeline'); 47007 }; 47008 47009 this.reset = function () { 47010 bytesInBuffer = 0; 47011 this.trigger('reset'); 47012 }; 47013 }; 47014 47015 _TransportPacketStream.prototype = new stream(); 47016 /** 47017 * Accepts an MP2T TransportPacketStream and emits data events with parsed 47018 * forms of the individual transport stream packets. 47019 */ 47020 47021 _TransportParseStream = function TransportParseStream() { 47022 var parsePsi, parsePat, parsePmt, self; 47023 47024 _TransportParseStream.prototype.init.call(this); 47025 47026 self = this; 47027 this.packetsWaitingForPmt = []; 47028 this.programMapTable = undefined; 47029 47030 parsePsi = function parsePsi(payload, psi) { 47031 var offset = 0; // PSI packets may be split into multiple sections and those 47032 // sections may be split into multiple packets. If a PSI 47033 // section starts in this packet, the payload_unit_start_indicator 47034 // will be true and the first byte of the payload will indicate 47035 // the offset from the current position to the start of the 47036 // section. 47037 47038 if (psi.payloadUnitStartIndicator) { 47039 offset += payload[offset] + 1; 47040 } 47041 47042 if (psi.type === 'pat') { 47043 parsePat(payload.subarray(offset), psi); 47044 } else { 47045 parsePmt(payload.subarray(offset), psi); 47046 } 47047 }; 47048 47049 parsePat = function parsePat(payload, pat) { 47050 pat.section_number = payload[7]; // eslint-disable-line camelcase 47051 47052 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 47053 // skip the PSI header and parse the first PMT entry 47054 47055 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 47056 pat.pmtPid = self.pmtPid; 47057 }; 47058 /** 47059 * Parse out the relevant fields of a Program Map Table (PMT). 47060 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 47061 * packet. The first byte in this array should be the table_id 47062 * field. 47063 * @param pmt {object} the object that should be decorated with 47064 * fields parsed from the PMT. 47065 */ 47066 47067 47068 parsePmt = function parsePmt(payload, pmt) { 47069 var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually 47070 // take effect. We don't believe this should ever be the case 47071 // for HLS but we'll ignore "forward" PMT declarations if we see 47072 // them. Future PMT declarations have the current_next_indicator 47073 // set to zero. 47074 47075 if (!(payload[5] & 0x01)) { 47076 return; 47077 } // overwrite any existing program map table 47078 47079 47080 self.programMapTable = { 47081 video: null, 47082 audio: null, 47083 'timed-metadata': {} 47084 }; // the mapping table ends at the end of the current section 47085 47086 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 47087 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 47088 // long the program info descriptors are 47089 47090 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table 47091 47092 offset = 12 + programInfoLength; 47093 47094 while (offset < tableEnd) { 47095 var streamType = payload[offset]; 47096 var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types 47097 // TODO: should this be done for metadata too? for now maintain behavior of 47098 // multiple metadata streams 47099 47100 if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) { 47101 self.programMapTable.video = pid; 47102 } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) { 47103 self.programMapTable.audio = pid; 47104 } else if (streamType === streamTypes.METADATA_STREAM_TYPE) { 47105 // map pid to stream type for metadata streams 47106 self.programMapTable['timed-metadata'][pid] = streamType; 47107 } // move to the next table entry 47108 // skip past the elementary stream descriptors, if present 47109 47110 47111 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 47112 } // record the map on the packet as well 47113 47114 47115 pmt.programMapTable = self.programMapTable; 47116 }; 47117 /** 47118 * Deliver a new MP2T packet to the next stream in the pipeline. 47119 */ 47120 47121 47122 this.push = function (packet) { 47123 var result = {}, 47124 offset = 4; 47125 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1] 47126 47127 result.pid = packet[1] & 0x1f; 47128 result.pid <<= 8; 47129 result.pid |= packet[2]; // if an adaption field is present, its length is specified by the 47130 // fifth byte of the TS packet header. The adaptation field is 47131 // used to add stuffing to PES packets that don't fill a complete 47132 // TS packet, and to specify some forms of timing and control data 47133 // that we do not currently use. 47134 47135 if ((packet[3] & 0x30) >>> 4 > 0x01) { 47136 offset += packet[offset] + 1; 47137 } // parse the rest of the packet based on the type 47138 47139 47140 if (result.pid === 0) { 47141 result.type = 'pat'; 47142 parsePsi(packet.subarray(offset), result); 47143 this.trigger('data', result); 47144 } else if (result.pid === this.pmtPid) { 47145 result.type = 'pmt'; 47146 parsePsi(packet.subarray(offset), result); 47147 this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now 47148 47149 while (this.packetsWaitingForPmt.length) { 47150 this.processPes_.apply(this, this.packetsWaitingForPmt.shift()); 47151 } 47152 } else if (this.programMapTable === undefined) { 47153 // When we have not seen a PMT yet, defer further processing of 47154 // PES packets until one has been parsed 47155 this.packetsWaitingForPmt.push([packet, offset, result]); 47156 } else { 47157 this.processPes_(packet, offset, result); 47158 } 47159 }; 47160 47161 this.processPes_ = function (packet, offset, result) { 47162 // set the appropriate stream type 47163 if (result.pid === this.programMapTable.video) { 47164 result.streamType = streamTypes.H264_STREAM_TYPE; 47165 } else if (result.pid === this.programMapTable.audio) { 47166 result.streamType = streamTypes.ADTS_STREAM_TYPE; 47167 } else { 47168 // if not video or audio, it is timed-metadata or unknown 47169 // if unknown, streamType will be undefined 47170 result.streamType = this.programMapTable['timed-metadata'][result.pid]; 47171 } 47172 47173 result.type = 'pes'; 47174 result.data = packet.subarray(offset); 47175 this.trigger('data', result); 47176 }; 47177 }; 47178 47179 _TransportParseStream.prototype = new stream(); 47180 _TransportParseStream.STREAM_TYPES = { 47181 h264: 0x1b, 47182 adts: 0x0f 47183 }; 47184 /** 47185 * Reconsistutes program elementary stream (PES) packets from parsed 47186 * transport stream packets. That is, if you pipe an 47187 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 47188 * events will be events which capture the bytes for individual PES 47189 * packets plus relevant metadata that has been extracted from the 47190 * container. 47191 */ 47192 47193 _ElementaryStream = function ElementaryStream() { 47194 var self = this, 47195 segmentHadPmt = false, 47196 // PES packet fragments 47197 video = { 47198 data: [], 47199 size: 0 47200 }, 47201 audio = { 47202 data: [], 47203 size: 0 47204 }, 47205 timedMetadata = { 47206 data: [], 47207 size: 0 47208 }, 47209 programMapTable, 47210 parsePes = function parsePes(payload, pes) { 47211 var ptsDtsFlags; 47212 var startPrefix = payload[0] << 16 | payload[1] << 8 | payload[2]; // default to an empty array 47213 47214 pes.data = new Uint8Array(); // In certain live streams, the start of a TS fragment has ts packets 47215 // that are frame data that is continuing from the previous fragment. This 47216 // is to check that the pes data is the start of a new pes payload 47217 47218 if (startPrefix !== 1) { 47219 return; 47220 } // get the packet length, this will be 0 for video 47221 47222 47223 pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe 47224 47225 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value 47226 // and a DTS value. Determine what combination of values is 47227 // available to work with. 47228 47229 ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 47230 // performs all bitwise operations on 32-bit integers but javascript 47231 // supports a much greater range (52-bits) of integer using standard 47232 // mathematical operations. 47233 // We construct a 31-bit value using bitwise operators over the 31 47234 // most significant bits and then multiply by 4 (equal to a left-shift 47235 // of 2) before we add the final 2 least significant bits of the 47236 // timestamp (equal to an OR.) 47237 47238 if (ptsDtsFlags & 0xC0) { 47239 // the PTS and DTS are not written out directly. For information 47240 // on how they are encoded, see 47241 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 47242 pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3; 47243 pes.pts *= 4; // Left shift by 2 47244 47245 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 47246 47247 pes.dts = pes.pts; 47248 47249 if (ptsDtsFlags & 0x40) { 47250 pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3; 47251 pes.dts *= 4; // Left shift by 2 47252 47253 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 47254 } 47255 } // the data section starts immediately after the PES header. 47256 // pes_header_data_length specifies the number of header bytes 47257 // that follow the last byte of the field. 47258 47259 47260 pes.data = payload.subarray(9 + payload[8]); 47261 }, 47262 47263 /** 47264 * Pass completely parsed PES packets to the next stream in the pipeline 47265 **/ 47266 flushStream = function flushStream(stream, type, forceFlush) { 47267 var packetData = new Uint8Array(stream.size), 47268 event = { 47269 type: type 47270 }, 47271 i = 0, 47272 offset = 0, 47273 packetFlushable = false, 47274 fragment; // do nothing if there is not enough buffered data for a complete 47275 // PES header 47276 47277 if (!stream.data.length || stream.size < 9) { 47278 return; 47279 } 47280 47281 event.trackId = stream.data[0].pid; // reassemble the packet 47282 47283 for (i = 0; i < stream.data.length; i++) { 47284 fragment = stream.data[i]; 47285 packetData.set(fragment.data, offset); 47286 offset += fragment.data.byteLength; 47287 } // parse assembled packet's PES header 47288 47289 47290 parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length 47291 // check that there is enough stream data to fill the packet 47292 47293 packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right 47294 47295 if (forceFlush || packetFlushable) { 47296 stream.size = 0; 47297 stream.data.length = 0; 47298 } // only emit packets that are complete. this is to avoid assembling 47299 // incomplete PES packets due to poor segmentation 47300 47301 47302 if (packetFlushable) { 47303 self.trigger('data', event); 47304 } 47305 }; 47306 47307 _ElementaryStream.prototype.init.call(this); 47308 /** 47309 * Identifies M2TS packet types and parses PES packets using metadata 47310 * parsed from the PMT 47311 **/ 47312 47313 47314 this.push = function (data) { 47315 ({ 47316 pat: function pat() {// we have to wait for the PMT to arrive as well before we 47317 // have any meaningful metadata 47318 }, 47319 pes: function pes() { 47320 var stream, streamType; 47321 47322 switch (data.streamType) { 47323 case streamTypes.H264_STREAM_TYPE: 47324 stream = video; 47325 streamType = 'video'; 47326 break; 47327 47328 case streamTypes.ADTS_STREAM_TYPE: 47329 stream = audio; 47330 streamType = 'audio'; 47331 break; 47332 47333 case streamTypes.METADATA_STREAM_TYPE: 47334 stream = timedMetadata; 47335 streamType = 'timed-metadata'; 47336 break; 47337 47338 default: 47339 // ignore unknown stream types 47340 return; 47341 } // if a new packet is starting, we can flush the completed 47342 // packet 47343 47344 47345 if (data.payloadUnitStartIndicator) { 47346 flushStream(stream, streamType, true); 47347 } // buffer this fragment until we are sure we've received the 47348 // complete payload 47349 47350 47351 stream.data.push(data); 47352 stream.size += data.data.byteLength; 47353 }, 47354 pmt: function pmt() { 47355 var event = { 47356 type: 'metadata', 47357 tracks: [] 47358 }; 47359 programMapTable = data.programMapTable; // translate audio and video streams to tracks 47360 47361 if (programMapTable.video !== null) { 47362 event.tracks.push({ 47363 timelineStartInfo: { 47364 baseMediaDecodeTime: 0 47365 }, 47366 id: +programMapTable.video, 47367 codec: 'avc', 47368 type: 'video' 47369 }); 47370 } 47371 47372 if (programMapTable.audio !== null) { 47373 event.tracks.push({ 47374 timelineStartInfo: { 47375 baseMediaDecodeTime: 0 47376 }, 47377 id: +programMapTable.audio, 47378 codec: 'adts', 47379 type: 'audio' 47380 }); 47381 } 47382 47383 segmentHadPmt = true; 47384 self.trigger('data', event); 47385 } 47386 })[data.type](); 47387 }; 47388 47389 this.reset = function () { 47390 video.size = 0; 47391 video.data.length = 0; 47392 audio.size = 0; 47393 audio.data.length = 0; 47394 this.trigger('reset'); 47395 }; 47396 /** 47397 * Flush any remaining input. Video PES packets may be of variable 47398 * length. Normally, the start of a new video packet can trigger the 47399 * finalization of the previous packet. That is not possible if no 47400 * more video is forthcoming, however. In that case, some other 47401 * mechanism (like the end of the file) has to be employed. When it is 47402 * clear that no additional data is forthcoming, calling this method 47403 * will flush the buffered packets. 47404 */ 47405 47406 47407 this.flushStreams_ = function () { 47408 // !!THIS ORDER IS IMPORTANT!! 47409 // video first then audio 47410 flushStream(video, 'video'); 47411 flushStream(audio, 'audio'); 47412 flushStream(timedMetadata, 'timed-metadata'); 47413 }; 47414 47415 this.flush = function () { 47416 // if on flush we haven't had a pmt emitted 47417 // and we have a pmt to emit. emit the pmt 47418 // so that we trigger a trackinfo downstream. 47419 if (!segmentHadPmt && programMapTable) { 47420 var pmt = { 47421 type: 'metadata', 47422 tracks: [] 47423 }; // translate audio and video streams to tracks 47424 47425 if (programMapTable.video !== null) { 47426 pmt.tracks.push({ 47427 timelineStartInfo: { 47428 baseMediaDecodeTime: 0 47429 }, 47430 id: +programMapTable.video, 47431 codec: 'avc', 47432 type: 'video' 47433 }); 47434 } 47435 47436 if (programMapTable.audio !== null) { 47437 pmt.tracks.push({ 47438 timelineStartInfo: { 47439 baseMediaDecodeTime: 0 47440 }, 47441 id: +programMapTable.audio, 47442 codec: 'adts', 47443 type: 'audio' 47444 }); 47445 } 47446 47447 self.trigger('data', pmt); 47448 } 47449 47450 segmentHadPmt = false; 47451 this.flushStreams_(); 47452 this.trigger('done'); 47453 }; 47454 }; 47455 47456 _ElementaryStream.prototype = new stream(); 47457 var m2ts = { 47458 PAT_PID: 0x0000, 47459 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH$1,
vendor: 4,324 bytes, lines 47460-47573
47460 TransportPacketStream: _TransportPacketStream, 47461 TransportParseStream: _TransportParseStream, 47462 ElementaryStream: _ElementaryStream, 47463 TimestampRolloverStream: TimestampRolloverStream, 47464 CaptionStream: captionStream.CaptionStream, 47465 Cea608Stream: captionStream.Cea608Stream, 47466 Cea708Stream: captionStream.Cea708Stream, 47467 MetadataStream: metadataStream 47468 }; 47469 47470 for (var type in streamTypes) { 47471 if (streamTypes.hasOwnProperty(type)) { 47472 m2ts[type] = streamTypes[type]; 47473 } 47474 } 47475 47476 var m2ts_1 = m2ts; 47477 var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS; 47478 47479 var _AdtsStream; 47480 47481 var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 47482 /* 47483 * Accepts a ElementaryStream and emits data events with parsed 47484 * AAC Audio Frames of the individual packets. Input audio in ADTS 47485 * format is unpacked and re-emitted as AAC frames. 47486 * 47487 * @see http://wiki.multimedia.cx/index.php?title=ADTS 47488 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 47489 */ 47490 47491 _AdtsStream = function AdtsStream(handlePartialSegments) { 47492 var buffer, 47493 frameNum = 0; 47494 47495 _AdtsStream.prototype.init.call(this); 47496 47497 this.skipWarn_ = function (start, end) { 47498 this.trigger('log', { 47499 level: 'warn', 47500 message: "adts skiping bytes " + start + " to " + end + " in frame " + frameNum + " outside syncword" 47501 }); 47502 }; 47503 47504 this.push = function (packet) { 47505 var i = 0, 47506 frameLength, 47507 protectionSkipBytes, 47508 oldBuffer, 47509 sampleCount, 47510 adtsFrameDuration; 47511 47512 if (!handlePartialSegments) { 47513 frameNum = 0; 47514 } 47515 47516 if (packet.type !== 'audio') { 47517 // ignore non-audio data 47518 return; 47519 } // Prepend any data in the buffer to the input data so that we can parse 47520 // aac frames the cross a PES packet boundary 47521 47522 47523 if (buffer && buffer.length) { 47524 oldBuffer = buffer; 47525 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 47526 buffer.set(oldBuffer); 47527 buffer.set(packet.data, oldBuffer.byteLength); 47528 } else { 47529 buffer = packet.data; 47530 } // unpack any ADTS frames which have been fully received 47531 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 47532 47533 47534 var skip; // We use i + 7 here because we want to be able to parse the entire header. 47535 // If we don't have enough bytes to do that, then we definitely won't have a full frame. 47536 47537 while (i + 7 < buffer.length) { 47538 // Look for the start of an ADTS header.. 47539 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 47540 if (typeof skip !== 'number') { 47541 skip = i; 47542 } // If a valid header was not found, jump one forward and attempt to 47543 // find a valid ADTS header starting at the next byte 47544 47545 47546 i++; 47547 continue; 47548 } 47549 47550 if (typeof skip === 'number') { 47551 this.skipWarn_(skip, i); 47552 skip = null; 47553 } // The protection skip bit tells us if we have 2 bytes of CRC data at the 47554 // end of the ADTS header 47555 47556 47557 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the 47558 // end of the sync sequence 47559 // NOTE: frame length includes the size of the header 47560 47561 frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5; 47562 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 47563 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, 47564 // then we have to wait for more data 47565 47566 if (buffer.byteLength - i < frameLength) { 47567 break; 47568 } // Otherwise, deliver the complete AAC frame 47569 47570 47571 this.trigger('data', { 47572 pts: packet.pts + frameNum * adtsFrameDuration, 47573 dts: packet.dts + frameNum * adtsFrameDuration,
vendor: 10,322 bytes, lines 47574-47928
47574 sampleCount: sampleCount, 47575 audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1, 47576 channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6, 47577 samplerate: ADTS_SAMPLING_FREQUENCIES$1[(buffer[i + 2] & 0x3c) >>> 2], 47578 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 47579 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 47580 samplesize: 16, 47581 // data is the frame without it's header 47582 data: buffer.subarray(i + 7 + protectionSkipBytes, i + frameLength) 47583 }); 47584 frameNum++; 47585 i += frameLength; 47586 } 47587 47588 if (typeof skip === 'number') { 47589 this.skipWarn_(skip, i); 47590 skip = null; 47591 } // remove processed bytes from the buffer. 47592 47593 47594 buffer = buffer.subarray(i); 47595 }; 47596 47597 this.flush = function () { 47598 frameNum = 0; 47599 this.trigger('done'); 47600 }; 47601 47602 this.reset = function () { 47603 buffer = void 0; 47604 this.trigger('reset'); 47605 }; 47606 47607 this.endTimeline = function () { 47608 buffer = void 0; 47609 this.trigger('endedtimeline'); 47610 }; 47611 }; 47612 47613 _AdtsStream.prototype = new stream(); 47614 var adts = _AdtsStream; 47615 /** 47616 * mux.js 47617 * 47618 * Copyright (c) Brightcove 47619 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 47620 */ 47621 47622 var ExpGolomb; 47623 /** 47624 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 47625 * scheme used by h264. 47626 */ 47627 47628 ExpGolomb = function ExpGolomb(workingData) { 47629 var // the number of bytes left to examine in workingData 47630 workingBytesAvailable = workingData.byteLength, 47631 // the current word being examined 47632 workingWord = 0, 47633 // :uint 47634 // the number of bits left to examine in the current word 47635 workingBitsAvailable = 0; // :uint; 47636 // ():uint 47637 47638 this.length = function () { 47639 return 8 * workingBytesAvailable; 47640 }; // ():uint 47641 47642 47643 this.bitsAvailable = function () { 47644 return 8 * workingBytesAvailable + workingBitsAvailable; 47645 }; // ():void 47646 47647 47648 this.loadWord = function () { 47649 var position = workingData.byteLength - workingBytesAvailable, 47650 workingBytes = new Uint8Array(4), 47651 availableBytes = Math.min(4, workingBytesAvailable); 47652 47653 if (availableBytes === 0) { 47654 throw new Error('no bytes available'); 47655 } 47656 47657 workingBytes.set(workingData.subarray(position, position + availableBytes)); 47658 workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed 47659 47660 workingBitsAvailable = availableBytes * 8; 47661 workingBytesAvailable -= availableBytes; 47662 }; // (count:int):void 47663 47664 47665 this.skipBits = function (count) { 47666 var skipBytes; // :int 47667 47668 if (workingBitsAvailable > count) { 47669 workingWord <<= count; 47670 workingBitsAvailable -= count; 47671 } else { 47672 count -= workingBitsAvailable; 47673 skipBytes = Math.floor(count / 8); 47674 count -= skipBytes * 8; 47675 workingBytesAvailable -= skipBytes; 47676 this.loadWord(); 47677 workingWord <<= count; 47678 workingBitsAvailable -= count; 47679 } 47680 }; // (size:int):uint 47681 47682 47683 this.readBits = function (size) { 47684 var bits = Math.min(workingBitsAvailable, size), 47685 // :uint 47686 valu = workingWord >>> 32 - bits; // :uint 47687 // if size > 31, handle error 47688 47689 workingBitsAvailable -= bits; 47690 47691 if (workingBitsAvailable > 0) { 47692 workingWord <<= bits; 47693 } else if (workingBytesAvailable > 0) { 47694 this.loadWord(); 47695 } 47696 47697 bits = size - bits; 47698 47699 if (bits > 0) { 47700 return valu << bits | this.readBits(bits); 47701 } 47702 47703 return valu; 47704 }; // ():uint 47705 47706 47707 this.skipLeadingZeros = function () { 47708 var leadingZeroCount; // :uint 47709 47710 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 47711 if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) { 47712 // the first bit of working word is 1 47713 workingWord <<= leadingZeroCount; 47714 workingBitsAvailable -= leadingZeroCount; 47715 return leadingZeroCount; 47716 } 47717 } // we exhausted workingWord and still have not found a 1 47718 47719 47720 this.loadWord(); 47721 return leadingZeroCount + this.skipLeadingZeros(); 47722 }; // ():void 47723 47724 47725 this.skipUnsignedExpGolomb = function () { 47726 this.skipBits(1 + this.skipLeadingZeros()); 47727 }; // ():void 47728 47729 47730 this.skipExpGolomb = function () { 47731 this.skipBits(1 + this.skipLeadingZeros()); 47732 }; // ():uint 47733 47734 47735 this.readUnsignedExpGolomb = function () { 47736 var clz = this.skipLeadingZeros(); // :uint 47737 47738 return this.readBits(clz + 1) - 1; 47739 }; // ():int 47740 47741 47742 this.readExpGolomb = function () { 47743 var valu = this.readUnsignedExpGolomb(); // :int 47744 47745 if (0x01 & valu) { 47746 // the number is odd if the low order bit is set 47747 return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 47748 } 47749 47750 return -1 * (valu >>> 1); // divide by two then make it negative 47751 }; // Some convenience functions 47752 // :Boolean 47753 47754 47755 this.readBoolean = function () { 47756 return this.readBits(1) === 1; 47757 }; // ():int 47758 47759 47760 this.readUnsignedByte = function () { 47761 return this.readBits(8); 47762 }; 47763 47764 this.loadWord(); 47765 }; 47766 47767 var expGolomb = ExpGolomb; 47768 47769 var _H264Stream, _NalByteStream; 47770 47771 var PROFILES_WITH_OPTIONAL_SPS_DATA; 47772 /** 47773 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 47774 */ 47775 47776 _NalByteStream = function NalByteStream() { 47777 var syncPoint = 0, 47778 i, 47779 buffer; 47780 47781 _NalByteStream.prototype.init.call(this); 47782 /* 47783 * Scans a byte stream and triggers a data event with the NAL units found. 47784 * @param {Object} data Event received from H264Stream 47785 * @param {Uint8Array} data.data The h264 byte stream to be scanned 47786 * 47787 * @see H264Stream.push 47788 */ 47789 47790 47791 this.push = function (data) { 47792 var swapBuffer; 47793 47794 if (!buffer) { 47795 buffer = data.data; 47796 } else { 47797 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 47798 swapBuffer.set(buffer); 47799 swapBuffer.set(data.data, buffer.byteLength); 47800 buffer = swapBuffer; 47801 } 47802 47803 var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B 47804 // scan for NAL unit boundaries 47805 // a match looks like this: 47806 // 0 0 1 .. NAL .. 0 0 1 47807 // ^ sync point ^ i 47808 // or this: 47809 // 0 0 1 .. NAL .. 0 0 0 47810 // ^ sync point ^ i 47811 // advance the sync point to a NAL start, if necessary 47812 47813 for (; syncPoint < len - 3; syncPoint++) { 47814 if (buffer[syncPoint + 2] === 1) { 47815 // the sync point is properly aligned 47816 i = syncPoint + 5; 47817 break; 47818 } 47819 } 47820 47821 while (i < len) { 47822 // look at the current byte to determine if we've hit the end of 47823 // a NAL unit boundary 47824 switch (buffer[i]) { 47825 case 0: 47826 // skip past non-sync sequences 47827 if (buffer[i - 1] !== 0) { 47828 i += 2; 47829 break; 47830 } else if (buffer[i - 2] !== 0) { 47831 i++; 47832 break; 47833 } // deliver the NAL unit if it isn't empty 47834 47835 47836 if (syncPoint + 3 !== i - 2) { 47837 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 47838 } // drop trailing zeroes 47839 47840 47841 do { 47842 i++; 47843 } while (buffer[i] !== 1 && i < len); 47844 47845 syncPoint = i - 2; 47846 i += 3; 47847 break; 47848 47849 case 1: 47850 // skip past non-sync sequences 47851 if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) { 47852 i += 3; 47853 break; 47854 } // deliver the NAL unit 47855 47856 47857 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 47858 syncPoint = i - 2; 47859 i += 3; 47860 break; 47861 47862 default: 47863 // the current byte isn't a one or zero, so it cannot be part 47864 // of a sync sequence 47865 i += 3; 47866 break; 47867 } 47868 } // filter out the NAL units that were delivered 47869 47870 47871 buffer = buffer.subarray(syncPoint); 47872 i -= syncPoint; 47873 syncPoint = 0; 47874 }; 47875 47876 this.reset = function () { 47877 buffer = null; 47878 syncPoint = 0; 47879 this.trigger('reset'); 47880 }; 47881 47882 this.flush = function () { 47883 // deliver the last buffered NAL unit 47884 if (buffer && buffer.byteLength > 3) { 47885 this.trigger('data', buffer.subarray(syncPoint + 3)); 47886 } // reset the stream state 47887 47888 47889 buffer = null; 47890 syncPoint = 0; 47891 this.trigger('done'); 47892 }; 47893 47894 this.endTimeline = function () { 47895 this.flush(); 47896 this.trigger('endedtimeline'); 47897 }; 47898 }; 47899 47900 _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS 47901 // see Recommendation ITU-T H.264 (4/2013), 47902 // 7.3.2.1.1 Sequence parameter set data syntax 47903 47904 PROFILES_WITH_OPTIONAL_SPS_DATA = { 47905 100: true, 47906 110: true, 47907 122: true, 47908 244: true, 47909 44: true, 47910 83: true, 47911 86: true, 47912 118: true, 47913 128: true, 47914 // TODO: the three profiles below don't 47915 // appear to have sps data in the specificiation anymore? 47916 138: true, 47917 139: true, 47918 134: true 47919 }; 47920 /** 47921 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 47922 * events. 47923 */ 47924 47925 _H264Stream = function H264Stream() { 47926 var nalByteStream = new _NalByteStream(), 47927 self, 47928 trackId,
47929 currentPts, 47930 currentDts, 47931 discardEmulationPreventionBytes, 47932 readSequenceParameterSet, 47933 skipScalingList; 47934 47935 _H264Stream.prototype.init.call(this); 47936 47937 self = this; 47938 /* 47939 * Pushes a packet from a stream onto the NalByteStream 47940 * 47941 * @param {Object} packet - A packet received from a stream 47942 * @param {Uint8Array} packet.data - The raw bytes of the packet 47943 * @param {Number} packet.dts - Decode timestamp of the packet 47944 * @param {Number} packet.pts - Presentation timestamp of the packet 47945 * @param {Number} packet.trackId - The id of the h264 track this packet came from 47946 * @param {('video'|'audio')} packet.type - The type of packet 47947 * 47948 */ 47949 47950 this.push = function (packet) { 47951 if (packet.type !== 'video') { 47952 return; 47953 } 47954 47955 trackId = packet.trackId; 47956 currentPts = packet.pts; 47957 currentDts = packet.dts; 47958 nalByteStream.push(packet); 47959 }; 47960 /* 47961 * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps 47962 * for the NALUs to the next stream component. 47963 * Also, preprocess caption and sequence parameter NALUs. 47964 * 47965 * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push` 47966 * @see NalByteStream.push 47967 */ 47968 47969 47970 nalByteStream.on('data', function (data) { 47971 var event = { 47972 trackId: trackId,
vendor: 28,582 bytes, lines 47973-48778
47973 pts: currentPts, 47974 dts: currentDts, 47975 data: data, 47976 nalUnitTypeCode: data[0] & 0x1f 47977 }; 47978 47979 switch (event.nalUnitTypeCode) { 47980 case 0x05: 47981 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 47982 break; 47983 47984 case 0x06: 47985 event.nalUnitType = 'sei_rbsp'; 47986 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 47987 break; 47988 47989 case 0x07: 47990 event.nalUnitType = 'seq_parameter_set_rbsp'; 47991 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 47992 event.config = readSequenceParameterSet(event.escapedRBSP); 47993 break; 47994 47995 case 0x08: 47996 event.nalUnitType = 'pic_parameter_set_rbsp'; 47997 break; 47998 47999 case 0x09: 48000 event.nalUnitType = 'access_unit_delimiter_rbsp'; 48001 break; 48002 } // This triggers data on the H264Stream 48003 48004 48005 self.trigger('data', event); 48006 }); 48007 nalByteStream.on('done', function () { 48008 self.trigger('done'); 48009 }); 48010 nalByteStream.on('partialdone', function () { 48011 self.trigger('partialdone'); 48012 }); 48013 nalByteStream.on('reset', function () { 48014 self.trigger('reset'); 48015 }); 48016 nalByteStream.on('endedtimeline', function () { 48017 self.trigger('endedtimeline'); 48018 }); 48019 48020 this.flush = function () { 48021 nalByteStream.flush(); 48022 }; 48023 48024 this.partialFlush = function () { 48025 nalByteStream.partialFlush(); 48026 }; 48027 48028 this.reset = function () { 48029 nalByteStream.reset(); 48030 }; 48031 48032 this.endTimeline = function () { 48033 nalByteStream.endTimeline(); 48034 }; 48035 /** 48036 * Advance the ExpGolomb decoder past a scaling list. The scaling 48037 * list is optionally transmitted as part of a sequence parameter 48038 * set and is not relevant to transmuxing. 48039 * @param count {number} the number of entries in this scaling list 48040 * @param expGolombDecoder {object} an ExpGolomb pointed to the 48041 * start of a scaling list 48042 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 48043 */ 48044 48045 48046 skipScalingList = function skipScalingList(count, expGolombDecoder) { 48047 var lastScale = 8, 48048 nextScale = 8, 48049 j, 48050 deltaScale; 48051 48052 for (j = 0; j < count; j++) { 48053 if (nextScale !== 0) { 48054 deltaScale = expGolombDecoder.readExpGolomb(); 48055 nextScale = (lastScale + deltaScale + 256) % 256; 48056 } 48057 48058 lastScale = nextScale === 0 ? lastScale : nextScale; 48059 } 48060 }; 48061 /** 48062 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 48063 * Sequence Payload" 48064 * @param data {Uint8Array} the bytes of a RBSP from a NAL 48065 * unit 48066 * @return {Uint8Array} the RBSP without any Emulation 48067 * Prevention Bytes 48068 */ 48069 48070 48071 discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 48072 var length = data.byteLength, 48073 emulationPreventionBytesPositions = [], 48074 i = 1, 48075 newLength, 48076 newData; // Find all `Emulation Prevention Bytes` 48077 48078 while (i < length - 2) { 48079 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 48080 emulationPreventionBytesPositions.push(i + 2); 48081 i += 2; 48082 } else { 48083 i++; 48084 } 48085 } // If no Emulation Prevention Bytes were found just return the original 48086 // array 48087 48088 48089 if (emulationPreventionBytesPositions.length === 0) { 48090 return data; 48091 } // Create a new array to hold the NAL unit data 48092 48093 48094 newLength = length - emulationPreventionBytesPositions.length; 48095 newData = new Uint8Array(newLength); 48096 var sourceIndex = 0; 48097 48098 for (i = 0; i < newLength; sourceIndex++, i++) { 48099 if (sourceIndex === emulationPreventionBytesPositions[0]) { 48100 // Skip this byte 48101 sourceIndex++; // Remove this position index 48102 48103 emulationPreventionBytesPositions.shift(); 48104 } 48105 48106 newData[i] = data[sourceIndex]; 48107 } 48108 48109 return newData; 48110 }; 48111 /** 48112 * Read a sequence parameter set and return some interesting video 48113 * properties. A sequence parameter set is the H264 metadata that 48114 * describes the properties of upcoming video frames. 48115 * @param data {Uint8Array} the bytes of a sequence parameter set 48116 * @return {object} an object with configuration parsed from the 48117 * sequence parameter set, including the dimensions of the 48118 * associated video frames. 48119 */ 48120 48121 48122 readSequenceParameterSet = function readSequenceParameterSet(data) { 48123 var frameCropLeftOffset = 0, 48124 frameCropRightOffset = 0, 48125 frameCropTopOffset = 0, 48126 frameCropBottomOffset = 0, 48127 expGolombDecoder, 48128 profileIdc, 48129 levelIdc, 48130 profileCompatibility, 48131 chromaFormatIdc, 48132 picOrderCntType, 48133 numRefFramesInPicOrderCntCycle, 48134 picWidthInMbsMinus1, 48135 picHeightInMapUnitsMinus1, 48136 frameMbsOnlyFlag, 48137 scalingListCount, 48138 sarRatio = [1, 1], 48139 aspectRatioIdc, 48140 i; 48141 expGolombDecoder = new expGolomb(data); 48142 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 48143 48144 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 48145 48146 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 48147 48148 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 48149 // some profiles have more optional data we don't need 48150 48151 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 48152 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 48153 48154 if (chromaFormatIdc === 3) { 48155 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 48156 } 48157 48158 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 48159 48160 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 48161 48162 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 48163 48164 if (expGolombDecoder.readBoolean()) { 48165 // seq_scaling_matrix_present_flag 48166 scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; 48167 48168 for (i = 0; i < scalingListCount; i++) { 48169 if (expGolombDecoder.readBoolean()) { 48170 // seq_scaling_list_present_flag[ i ] 48171 if (i < 6) { 48172 skipScalingList(16, expGolombDecoder); 48173 } else { 48174 skipScalingList(64, expGolombDecoder); 48175 } 48176 } 48177 } 48178 } 48179 } 48180 48181 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 48182 48183 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 48184 48185 if (picOrderCntType === 0) { 48186 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 48187 } else if (picOrderCntType === 1) { 48188 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 48189 48190 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 48191 48192 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 48193 48194 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 48195 48196 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 48197 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 48198 } 48199 } 48200 48201 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 48202 48203 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 48204 48205 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 48206 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 48207 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 48208 48209 if (frameMbsOnlyFlag === 0) { 48210 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 48211 } 48212 48213 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 48214 48215 if (expGolombDecoder.readBoolean()) { 48216 // frame_cropping_flag 48217 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 48218 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 48219 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 48220 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 48221 } 48222 48223 if (expGolombDecoder.readBoolean()) { 48224 // vui_parameters_present_flag 48225 if (expGolombDecoder.readBoolean()) { 48226 // aspect_ratio_info_present_flag 48227 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 48228 48229 switch (aspectRatioIdc) { 48230 case 1: 48231 sarRatio = [1, 1]; 48232 break; 48233 48234 case 2: 48235 sarRatio = [12, 11]; 48236 break; 48237 48238 case 3: 48239 sarRatio = [10, 11]; 48240 break; 48241 48242 case 4: 48243 sarRatio = [16, 11]; 48244 break; 48245 48246 case 5: 48247 sarRatio = [40, 33]; 48248 break; 48249 48250 case 6: 48251 sarRatio = [24, 11]; 48252 break; 48253 48254 case 7: 48255 sarRatio = [20, 11]; 48256 break; 48257 48258 case 8: 48259 sarRatio = [32, 11]; 48260 break; 48261 48262 case 9: 48263 sarRatio = [80, 33]; 48264 break; 48265 48266 case 10: 48267 sarRatio = [18, 11]; 48268 break; 48269 48270 case 11: 48271 sarRatio = [15, 11]; 48272 break; 48273 48274 case 12: 48275 sarRatio = [64, 33]; 48276 break; 48277 48278 case 13: 48279 sarRatio = [160, 99]; 48280 break; 48281 48282 case 14: 48283 sarRatio = [4, 3]; 48284 break; 48285 48286 case 15: 48287 sarRatio = [3, 2]; 48288 break; 48289 48290 case 16: 48291 sarRatio = [2, 1]; 48292 break; 48293 48294 case 255: 48295 { 48296 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()]; 48297 break; 48298 } 48299 } 48300 48301 if (sarRatio) { 48302 sarRatio[0] / sarRatio[1]; 48303 } 48304 } 48305 } 48306 48307 return { 48308 profileIdc: profileIdc, 48309 levelIdc: levelIdc, 48310 profileCompatibility: profileCompatibility, 48311 width: (picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2, 48312 height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2, 48313 // sar is sample aspect ratio 48314 sarRatio: sarRatio 48315 }; 48316 }; 48317 }; 48318 48319 _H264Stream.prototype = new stream(); 48320 var h264 = { 48321 H264Stream: _H264Stream, 48322 NalByteStream: _NalByteStream 48323 }; 48324 /** 48325 * mux.js 48326 * 48327 * Copyright (c) Brightcove 48328 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 48329 * 48330 * Utilities to detect basic properties and metadata about Aac data. 48331 */ 48332 48333 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 48334 48335 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 48336 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 48337 flags = header[byteIndex + 5], 48338 footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0 48339 48340 returnSize = returnSize >= 0 ? returnSize : 0; 48341 48342 if (footerPresent) { 48343 return returnSize + 20; 48344 } 48345 48346 return returnSize + 10; 48347 }; 48348 48349 var getId3Offset = function getId3Offset(data, offset) { 48350 if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) { 48351 return offset; 48352 } 48353 48354 offset += parseId3TagSize(data, offset); 48355 return getId3Offset(data, offset); 48356 }; // TODO: use vhs-utils 48357 48358 48359 var isLikelyAacData$1 = function isLikelyAacData(data) { 48360 var offset = getId3Offset(data, 0); 48361 return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 && // verify that the 2 layer bits are 0, aka this 48362 // is not mp3 data but aac data. 48363 (data[offset + 1] & 0x16) === 0x10; 48364 }; 48365 48366 var parseSyncSafeInteger = function parseSyncSafeInteger(data) { 48367 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 48368 }; // return a percent-encoded representation of the specified byte range 48369 // @see http://en.wikipedia.org/wiki/Percent-encoding 48370 48371 48372 var percentEncode = function percentEncode(bytes, start, end) { 48373 var i, 48374 result = ''; 48375 48376 for (i = start; i < end; i++) { 48377 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 48378 } 48379 48380 return result; 48381 }; // return the string representation of the specified byte range, 48382 // interpreted as ISO-8859-1. 48383 48384 48385 var parseIso88591 = function parseIso88591(bytes, start, end) { 48386 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 48387 }; 48388 48389 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 48390 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 48391 middle = header[byteIndex + 4] << 3, 48392 highTwo = header[byteIndex + 3] & 0x3 << 11; 48393 return highTwo | middle | lowThree; 48394 }; 48395 48396 var parseType$2 = function parseType(header, byteIndex) { 48397 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 48398 return 'timed-metadata'; 48399 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 48400 return 'audio'; 48401 } 48402 48403 return null; 48404 }; 48405 48406 var parseSampleRate = function parseSampleRate(packet) { 48407 var i = 0; 48408 48409 while (i + 5 < packet.length) { 48410 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 48411 // If a valid header was not found, jump one forward and attempt to 48412 // find a valid ADTS header starting at the next byte 48413 i++; 48414 continue; 48415 } 48416 48417 return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2]; 48418 } 48419 48420 return null; 48421 }; 48422 48423 var parseAacTimestamp = function parseAacTimestamp(packet) { 48424 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 48425 48426 frameStart = 10; 48427 48428 if (packet[5] & 0x40) { 48429 // advance the frame start past the extended header 48430 frameStart += 4; // header size field 48431 48432 frameStart += parseSyncSafeInteger(packet.subarray(10, 14)); 48433 } // parse one or more ID3 frames 48434 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 48435 48436 48437 do { 48438 // determine the number of bytes in this frame 48439 frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8)); 48440 48441 if (frameSize < 1) { 48442 return null; 48443 } 48444 48445 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 48446 48447 if (frameHeader === 'PRIV') { 48448 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 48449 48450 for (var i = 0; i < frame.byteLength; i++) { 48451 if (frame[i] === 0) { 48452 var owner = parseIso88591(frame, 0, i); 48453 48454 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 48455 var d = frame.subarray(i + 1); 48456 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 48457 size *= 4; 48458 size += d[7] & 0x03; 48459 return size; 48460 } 48461 48462 break; 48463 } 48464 } 48465 } 48466 48467 frameStart += 10; // advance past the frame header 48468 48469 frameStart += frameSize; // advance past the frame body 48470 } while (frameStart < packet.byteLength); 48471 48472 return null; 48473 }; 48474 48475 var utils = { 48476 isLikelyAacData: isLikelyAacData$1, 48477 parseId3TagSize: parseId3TagSize, 48478 parseAdtsSize: parseAdtsSize, 48479 parseType: parseType$2, 48480 parseSampleRate: parseSampleRate, 48481 parseAacTimestamp: parseAacTimestamp 48482 }; 48483 48484 var _AacStream; 48485 /** 48486 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 48487 */ 48488 48489 48490 _AacStream = function AacStream() { 48491 var everything = new Uint8Array(), 48492 timeStamp = 0; 48493 48494 _AacStream.prototype.init.call(this); 48495 48496 this.setTimestamp = function (timestamp) { 48497 timeStamp = timestamp; 48498 }; 48499 48500 this.push = function (bytes) { 48501 var frameSize = 0, 48502 byteIndex = 0, 48503 bytesLeft, 48504 chunk, 48505 packet, 48506 tempLength; // If there are bytes remaining from the last segment, prepend them to the 48507 // bytes that were pushed in 48508 48509 if (everything.length) { 48510 tempLength = everything.length; 48511 everything = new Uint8Array(bytes.byteLength + tempLength); 48512 everything.set(everything.subarray(0, tempLength)); 48513 everything.set(bytes, tempLength); 48514 } else { 48515 everything = bytes; 48516 } 48517 48518 while (everything.length - byteIndex >= 3) { 48519 if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) { 48520 // Exit early because we don't have enough to parse 48521 // the ID3 tag header 48522 if (everything.length - byteIndex < 10) { 48523 break; 48524 } // check framesize 48525 48526 48527 frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 48528 // to emit a full packet 48529 // Add to byteIndex to support multiple ID3 tags in sequence 48530 48531 if (byteIndex + frameSize > everything.length) { 48532 break; 48533 } 48534 48535 chunk = { 48536 type: 'timed-metadata', 48537 data: everything.subarray(byteIndex, byteIndex + frameSize) 48538 }; 48539 this.trigger('data', chunk); 48540 byteIndex += frameSize; 48541 continue; 48542 } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) { 48543 // Exit early because we don't have enough to parse 48544 // the ADTS frame header 48545 if (everything.length - byteIndex < 7) { 48546 break; 48547 } 48548 48549 frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 48550 // to emit a full packet 48551 48552 if (byteIndex + frameSize > everything.length) { 48553 break; 48554 } 48555 48556 packet = { 48557 type: 'audio', 48558 data: everything.subarray(byteIndex, byteIndex + frameSize), 48559 pts: timeStamp, 48560 dts: timeStamp 48561 }; 48562 this.trigger('data', packet); 48563 byteIndex += frameSize; 48564 continue; 48565 } 48566 48567 byteIndex++; 48568 } 48569 48570 bytesLeft = everything.length - byteIndex; 48571 48572 if (bytesLeft > 0) { 48573 everything = everything.subarray(byteIndex); 48574 } else { 48575 everything = new Uint8Array(); 48576 } 48577 }; 48578 48579 this.reset = function () { 48580 everything = new Uint8Array(); 48581 this.trigger('reset'); 48582 }; 48583 48584 this.endTimeline = function () { 48585 everything = new Uint8Array(); 48586 this.trigger('endedtimeline'); 48587 }; 48588 }; 48589 48590 _AacStream.prototype = new stream(); 48591 var aac = _AacStream; // constants 48592 48593 var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize']; 48594 var audioProperties = AUDIO_PROPERTIES; 48595 var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio']; 48596 var videoProperties = VIDEO_PROPERTIES; 48597 var H264Stream = h264.H264Stream; 48598 var isLikelyAacData = utils.isLikelyAacData; 48599 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; // object types 48600 48601 var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream; 48602 /** 48603 * Compare two arrays (even typed) for same-ness 48604 */ 48605 48606 48607 var arrayEquals = function arrayEquals(a, b) { 48608 var i; 48609 48610 if (a.length !== b.length) { 48611 return false; 48612 } // compare the value of each element in the array 48613 48614 48615 for (i = 0; i < a.length; i++) { 48616 if (a[i] !== b[i]) { 48617 return false; 48618 } 48619 } 48620 48621 return true; 48622 }; 48623 48624 var generateSegmentTimingInfo = function generateSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) { 48625 var ptsOffsetFromDts = startPts - startDts, 48626 decodeDuration = endDts - startDts, 48627 presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment, 48628 // however, the player time values will reflect a start from the baseMediaDecodeTime. 48629 // In order to provide relevant values for the player times, base timing info on the 48630 // baseMediaDecodeTime and the DTS and PTS durations of the segment. 48631 48632 return { 48633 start: { 48634 dts: baseMediaDecodeTime, 48635 pts: baseMediaDecodeTime + ptsOffsetFromDts 48636 }, 48637 end: { 48638 dts: baseMediaDecodeTime + decodeDuration, 48639 pts: baseMediaDecodeTime + presentationDuration 48640 }, 48641 prependedContentDuration: prependedContentDuration, 48642 baseMediaDecodeTime: baseMediaDecodeTime 48643 }; 48644 }; 48645 /** 48646 * Constructs a single-track, ISO BMFF media segment from AAC data 48647 * events. The output of this stream can be fed to a SourceBuffer 48648 * configured with a suitable initialization segment. 48649 * @param track {object} track metadata configuration 48650 * @param options {object} transmuxer options object 48651 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 48652 * in the source; false to adjust the first segment to start at 0. 48653 */ 48654 48655 48656 _AudioSegmentStream = function AudioSegmentStream(track, options) { 48657 var adtsFrames = [], 48658 sequenceNumber, 48659 earliestAllowedDts = 0, 48660 audioAppendStartTs = 0, 48661 videoBaseMediaDecodeTime = Infinity; 48662 options = options || {}; 48663 sequenceNumber = options.firstSequenceNumber || 0; 48664 48665 _AudioSegmentStream.prototype.init.call(this); 48666 48667 this.push = function (data) { 48668 trackDecodeInfo.collectDtsInfo(track, data); 48669 48670 if (track) { 48671 audioProperties.forEach(function (prop) { 48672 track[prop] = data[prop]; 48673 }); 48674 } // buffer audio data until end() is called 48675 48676 48677 adtsFrames.push(data); 48678 }; 48679 48680 this.setEarliestDts = function (earliestDts) { 48681 earliestAllowedDts = earliestDts; 48682 }; 48683 48684 this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) { 48685 videoBaseMediaDecodeTime = baseMediaDecodeTime; 48686 }; 48687 48688 this.setAudioAppendStart = function (timestamp) { 48689 audioAppendStartTs = timestamp; 48690 }; 48691 48692 this.flush = function () { 48693 var frames, moof, mdat, boxes, frameDuration, segmentDuration, videoClockCyclesOfSilencePrefixed; // return early if no audio data has been observed 48694 48695 if (adtsFrames.length === 0) { 48696 this.trigger('done', 'AudioSegmentStream'); 48697 return; 48698 } 48699 48700 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts); 48701 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); // amount of audio filled but the value is in video clock rather than audio clock 48702 48703 videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to 48704 // samples (that is, adts frames) in the audio data 48705 48706 track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat 48707 48708 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames)); 48709 adtsFrames = []; 48710 moof = mp4Generator.moof(sequenceNumber, [track]); 48711 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time 48712 48713 sequenceNumber++; 48714 boxes.set(moof); 48715 boxes.set(mdat, moof.byteLength); 48716 trackDecodeInfo.clearDtsInfo(track); 48717 frameDuration = Math.ceil(ONE_SECOND_IN_TS$1 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with 48718 // tests) on adding the timingInfo event. However, it seems unlikely that there's a 48719 // valid use-case where an init segment/data should be triggered without associated 48720 // frames. Leaving for now, but should be looked into. 48721 48722 if (frames.length) { 48723 segmentDuration = frames.length * frameDuration; 48724 this.trigger('segmentTimingInfo', generateSegmentTimingInfo( // The audio track's baseMediaDecodeTime is in audio clock cycles, but the 48725 // frame info is in video clock cycles. Convert to match expectation of 48726 // listeners (that all timestamps will be based on video clock cycles). 48727 clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate), // frame times are already in video clock, as is segment duration 48728 frames[0].dts, frames[0].pts, frames[0].dts + segmentDuration, frames[0].pts + segmentDuration, videoClockCyclesOfSilencePrefixed || 0)); 48729 this.trigger('timingInfo', { 48730 start: frames[0].pts, 48731 end: frames[0].pts + segmentDuration 48732 }); 48733 } 48734 48735 this.trigger('data', { 48736 track: track, 48737 boxes: boxes 48738 }); 48739 this.trigger('done', 'AudioSegmentStream'); 48740 }; 48741 48742 this.reset = function () { 48743 trackDecodeInfo.clearDtsInfo(track); 48744 adtsFrames = []; 48745 this.trigger('reset'); 48746 }; 48747 }; 48748 48749 _AudioSegmentStream.prototype = new stream(); 48750 /** 48751 * Constructs a single-track, ISO BMFF media segment from H264 data 48752 * events. The output of this stream can be fed to a SourceBuffer 48753 * configured with a suitable initialization segment. 48754 * @param track {object} track metadata configuration 48755 * @param options {object} transmuxer options object 48756 * @param options.alignGopsAtEnd {boolean} If true, start from the end of the 48757 * gopsToAlignWith list when attempting to align gop pts 48758 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 48759 * in the source; false to adjust the first segment to start at 0. 48760 */ 48761 48762 _VideoSegmentStream = function VideoSegmentStream(track, options) { 48763 var sequenceNumber, 48764 nalUnits = [], 48765 gopsToAlignWith = [], 48766 config, 48767 pps; 48768 options = options || {}; 48769 sequenceNumber = options.firstSequenceNumber || 0; 48770 48771 _VideoSegmentStream.prototype.init.call(this); 48772 48773 delete track.minPTS; 48774 this.gopCache_ = []; 48775 /** 48776 * Constructs a ISO BMFF segment given H264 nalUnits 48777 * @param {Object} nalUnit A data event representing a nalUnit 48778 * @param {String} nalUnit.nalUnitType
48779 * @param {Object} nalUnit.config Properties for a mp4 track 48780 * @param {Uint8Array} nalUnit.data The nalUnit bytes 48781 * @see lib/codecs/h264.js 48782 **/ 48783 48784 this.push = function (nalUnit) { 48785 trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config 48786 48787 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 48788 config = nalUnit.config; 48789 track.sps = [nalUnit.data]; 48790 videoProperties.forEach(function (prop) { 48791 track[prop] = config[prop]; 48792 }, this); 48793 } 48794 48795 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) { 48796 pps = nalUnit.data; 48797 track.pps = [nalUnit.data]; 48798 } // buffer video until flush() is called 48799 48800 48801 nalUnits.push(nalUnit); 48802 }; 48803 /** 48804 * Pass constructed ISO BMFF track and boxes on to the 48805 * next stream in the pipeline 48806 **/ 48807 48808 48809 this.flush = function () { 48810 var frames, 48811 gopForFusion, 48812 gops, 48813 moof, 48814 mdat, 48815 boxes, 48816 prependedContentDuration = 0, 48817 firstGop, 48818 lastGop; // Throw away nalUnits at the start of the byte stream until 48819 // we find the first AUD 48820 48821 while (nalUnits.length) { 48822 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 48823 break; 48824 } 48825 48826 nalUnits.shift(); 48827 } // Return early if no video data has been observed 48828 48829 48830 if (nalUnits.length === 0) { 48831 this.resetStream_(); 48832 this.trigger('done', 'VideoSegmentStream'); 48833 return; 48834 } // Organize the raw nal-units into arrays that represent 48835 // higher-level constructs such as frames and gops 48836 // (group-of-pictures) 48837 48838 48839 frames = frameUtils.groupNalsIntoFrames(nalUnits); 48840 gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have 48841 // a problem since MSE (on Chrome) requires a leading keyframe. 48842 // 48843 // We have two approaches to repairing this situation: 48844 // 1) GOP-FUSION: 48845 // This is where we keep track of the GOPS (group-of-pictures) 48846 // from previous fragments and attempt to find one that we can 48847 // prepend to the current fragment in order to create a valid 48848 // fragment. 48849 // 2) KEYFRAME-PULLING: 48850 // Here we search for the first keyframe in the fragment and 48851 // throw away all the frames between the start of the fragment 48852 // and that keyframe. We then extend the duration and pull the 48853 // PTS of the keyframe forward so that it covers the time range 48854 // of the frames that were disposed of. 48855 // 48856 // #1 is far prefereable over #2 which can cause "stuttering" but 48857 // requires more things to be just right. 48858 48859 if (!gops[0][0].keyFrame) { 48860 // Search for a gop for fusion from our gopCache 48861 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 48862 48863 if (gopForFusion) { 48864 // in order to provide more accurate timing information about the segment, save 48865 // the number of seconds prepended to the original segment due to GOP fusion 48866 prependedContentDuration = gopForFusion.duration; 48867 gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the 48868 // new gop at the beginning 48869 48870 gops.byteLength += gopForFusion.byteLength; 48871 gops.nalCount += gopForFusion.nalCount; 48872 gops.pts = gopForFusion.pts; 48873 gops.dts = gopForFusion.dts; 48874 gops.duration += gopForFusion.duration; 48875 } else { 48876 // If we didn't find a candidate gop fall back to keyframe-pulling 48877 gops = frameUtils.extendFirstKeyFrame(gops); 48878 } 48879 } // Trim gops to align with gopsToAlignWith 48880 48881 48882 if (gopsToAlignWith.length) { 48883 var alignedGops; 48884 48885 if (options.alignGopsAtEnd) { 48886 alignedGops = this.alignGopsAtEnd_(gops); 48887 } else { 48888 alignedGops = this.alignGopsAtStart_(gops); 48889 } 48890 48891 if (!alignedGops) { 48892 // save all the nals in the last GOP into the gop cache 48893 this.gopCache_.unshift({ 48894 gop: gops.pop(), 48895 pps: track.pps, 48896 sps: track.sps 48897 }); // Keep a maximum of 6 GOPs in the cache 48898 48899 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 48900 48901 nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith 48902
vendor: 7,515 bytes, lines 48903-49102
48903 this.resetStream_(); 48904 this.trigger('done', 'VideoSegmentStream'); 48905 return; 48906 } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct 48907 // when recalculated before sending off to CoalesceStream 48908 48909 48910 trackDecodeInfo.clearDtsInfo(track); 48911 gops = alignedGops; 48912 } 48913 48914 trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to 48915 // samples (that is, frames) in the video data 48916 48917 track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat 48918 48919 mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops)); 48920 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 48921 this.trigger('processedGopsInfo', gops.map(function (gop) { 48922 return { 48923 pts: gop.pts, 48924 dts: gop.dts, 48925 byteLength: gop.byteLength 48926 }; 48927 })); 48928 firstGop = gops[0]; 48929 lastGop = gops[gops.length - 1]; 48930 this.trigger('segmentTimingInfo', generateSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); 48931 this.trigger('timingInfo', { 48932 start: gops[0].pts, 48933 end: gops[gops.length - 1].pts + gops[gops.length - 1].duration 48934 }); // save all the nals in the last GOP into the gop cache 48935 48936 this.gopCache_.unshift({ 48937 gop: gops.pop(), 48938 pps: track.pps, 48939 sps: track.sps 48940 }); // Keep a maximum of 6 GOPs in the cache 48941 48942 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 48943 48944 nalUnits = []; 48945 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 48946 this.trigger('timelineStartInfo', track.timelineStartInfo); 48947 moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of 48948 // throwing away hundreds of media segment fragments 48949 48950 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time 48951 48952 sequenceNumber++; 48953 boxes.set(moof); 48954 boxes.set(mdat, moof.byteLength); 48955 this.trigger('data', { 48956 track: track, 48957 boxes: boxes 48958 }); 48959 this.resetStream_(); // Continue with the flush process now 48960 48961 this.trigger('done', 'VideoSegmentStream'); 48962 }; 48963 48964 this.reset = function () { 48965 this.resetStream_(); 48966 nalUnits = []; 48967 this.gopCache_.length = 0; 48968 gopsToAlignWith.length = 0; 48969 this.trigger('reset'); 48970 }; 48971 48972 this.resetStream_ = function () { 48973 trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments 48974 // for instance, when we are rendition switching 48975 48976 config = undefined; 48977 pps = undefined; 48978 }; // Search for a candidate Gop for gop-fusion from the gop cache and 48979 // return it or return null if no good candidate was found 48980 48981 48982 this.getGopForFusion_ = function (nalUnit) { 48983 var halfSecond = 45000, 48984 // Half-a-second in a 90khz clock 48985 allowableOverlap = 10000, 48986 // About 3 frames @ 30fps 48987 nearestDistance = Infinity, 48988 dtsDistance, 48989 nearestGopObj, 48990 currentGop, 48991 currentGopObj, 48992 i; // Search for the GOP nearest to the beginning of this nal unit 48993 48994 for (i = 0; i < this.gopCache_.length; i++) { 48995 currentGopObj = this.gopCache_[i]; 48996 currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS 48997 48998 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 48999 continue; 49000 } // Reject Gops that would require a negative baseMediaDecodeTime 49001 49002 49003 if (currentGop.dts < track.timelineStartInfo.dts) { 49004 continue; 49005 } // The distance between the end of the gop and the start of the nalUnit 49006 49007 49008 dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within 49009 // a half-second of the nal unit 49010 49011 if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) { 49012 // Always use the closest GOP we found if there is more than 49013 // one candidate 49014 if (!nearestGopObj || nearestDistance > dtsDistance) { 49015 nearestGopObj = currentGopObj; 49016 nearestDistance = dtsDistance; 49017 } 49018 } 49019 } 49020 49021 if (nearestGopObj) { 49022 return nearestGopObj.gop; 49023 } 49024 49025 return null; 49026 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 49027 // of gopsToAlignWith starting from the START of the list 49028 49029 49030 this.alignGopsAtStart_ = function (gops) { 49031 var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops; 49032 byteLength = gops.byteLength; 49033 nalCount = gops.nalCount; 49034 duration = gops.duration; 49035 alignIndex = gopIndex = 0; 49036 49037 while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) { 49038 align = gopsToAlignWith[alignIndex]; 49039 gop = gops[gopIndex]; 49040 49041 if (align.pts === gop.pts) { 49042 break; 49043 } 49044 49045 if (gop.pts > align.pts) { 49046 // this current gop starts after the current gop we want to align on, so increment 49047 // align index 49048 alignIndex++; 49049 continue; 49050 } // current gop starts before the current gop we want to align on. so increment gop 49051 // index 49052 49053 49054 gopIndex++; 49055 byteLength -= gop.byteLength; 49056 nalCount -= gop.nalCount; 49057 duration -= gop.duration; 49058 } 49059 49060 if (gopIndex === 0) { 49061 // no gops to trim 49062 return gops; 49063 } 49064 49065 if (gopIndex === gops.length) { 49066 // all gops trimmed, skip appending all gops 49067 return null; 49068 } 49069 49070 alignedGops = gops.slice(gopIndex); 49071 alignedGops.byteLength = byteLength; 49072 alignedGops.duration = duration; 49073 alignedGops.nalCount = nalCount; 49074 alignedGops.pts = alignedGops[0].pts; 49075 alignedGops.dts = alignedGops[0].dts; 49076 return alignedGops; 49077 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 49078 // of gopsToAlignWith starting from the END of the list 49079 49080 49081 this.alignGopsAtEnd_ = function (gops) { 49082 var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound; 49083 alignIndex = gopsToAlignWith.length - 1; 49084 gopIndex = gops.length - 1; 49085 alignEndIndex = null; 49086 matchFound = false; 49087 49088 while (alignIndex >= 0 && gopIndex >= 0) { 49089 align = gopsToAlignWith[alignIndex]; 49090 gop = gops[gopIndex]; 49091 49092 if (align.pts === gop.pts) { 49093 matchFound = true; 49094 break; 49095 } 49096 49097 if (align.pts > gop.pts) { 49098 alignIndex--; 49099 continue; 49100 } 49101 49102 if (alignIndex === gopsToAlignWith.length - 1) {
vendor: 4,348 bytes, lines 49103-49233
49103 // gop.pts is greater than the last alignment candidate. If no match is found 49104 // by the end of this loop, we still want to append gops that come after this 49105 // point 49106 alignEndIndex = gopIndex; 49107 } 49108 49109 gopIndex--; 49110 } 49111 49112 if (!matchFound && alignEndIndex === null) { 49113 return null; 49114 } 49115 49116 var trimIndex; 49117 49118 if (matchFound) { 49119 trimIndex = gopIndex; 49120 } else { 49121 trimIndex = alignEndIndex; 49122 } 49123 49124 if (trimIndex === 0) { 49125 return gops; 49126 } 49127 49128 var alignedGops = gops.slice(trimIndex); 49129 var metadata = alignedGops.reduce(function (total, gop) { 49130 total.byteLength += gop.byteLength; 49131 total.duration += gop.duration; 49132 total.nalCount += gop.nalCount; 49133 return total; 49134 }, { 49135 byteLength: 0, 49136 duration: 0, 49137 nalCount: 0 49138 }); 49139 alignedGops.byteLength = metadata.byteLength; 49140 alignedGops.duration = metadata.duration; 49141 alignedGops.nalCount = metadata.nalCount; 49142 alignedGops.pts = alignedGops[0].pts; 49143 alignedGops.dts = alignedGops[0].dts; 49144 return alignedGops; 49145 }; 49146 49147 this.alignGopsWith = function (newGopsToAlignWith) { 49148 gopsToAlignWith = newGopsToAlignWith; 49149 }; 49150 }; 49151 49152 _VideoSegmentStream.prototype = new stream(); 49153 /** 49154 * A Stream that can combine multiple streams (ie. audio & video) 49155 * into a single output segment for MSE. Also supports audio-only 49156 * and video-only streams. 49157 * @param options {object} transmuxer options object 49158 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 49159 * in the source; false to adjust the first segment to start at media timeline start. 49160 */ 49161 49162 _CoalesceStream = function CoalesceStream(options, metadataStream) { 49163 // Number of Tracks per output segment 49164 // If greater than 1, we combine multiple 49165 // tracks into a single segment 49166 this.numberOfTracks = 0; 49167 this.metadataStream = metadataStream; 49168 options = options || {}; 49169 49170 if (typeof options.remux !== 'undefined') { 49171 this.remuxTracks = !!options.remux; 49172 } else { 49173 this.remuxTracks = true; 49174 } 49175 49176 if (typeof options.keepOriginalTimestamps === 'boolean') { 49177 this.keepOriginalTimestamps = options.keepOriginalTimestamps; 49178 } else { 49179 this.keepOriginalTimestamps = false; 49180 } 49181 49182 this.pendingTracks = []; 49183 this.videoTrack = null; 49184 this.pendingBoxes = []; 49185 this.pendingCaptions = []; 49186 this.pendingMetadata = []; 49187 this.pendingBytes = 0; 49188 this.emittedTracks = 0; 49189 49190 _CoalesceStream.prototype.init.call(this); // Take output from multiple 49191 49192 49193 this.push = function (output) { 49194 // buffer incoming captions until the associated video segment 49195 // finishes 49196 if (output.text) { 49197 return this.pendingCaptions.push(output); 49198 } // buffer incoming id3 tags until the final flush 49199 49200 49201 if (output.frames) { 49202 return this.pendingMetadata.push(output); 49203 } // Add this track to the list of pending tracks and store 49204 // important information required for the construction of 49205 // the final segment 49206 49207 49208 this.pendingTracks.push(output.track); 49209 this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome? 49210 // We unshift audio and push video because 49211 // as of Chrome 75 when switching from 49212 // one init segment to another if the video 49213 // mdat does not appear after the audio mdat 49214 // only audio will play for the duration of our transmux. 49215 49216 if (output.track.type === 'video') { 49217 this.videoTrack = output.track; 49218 this.pendingBoxes.push(output.boxes); 49219 } 49220 49221 if (output.track.type === 'audio') { 49222 this.audioTrack = output.track; 49223 this.pendingBoxes.unshift(output.boxes); 49224 } 49225 }; 49226 }; 49227 49228 _CoalesceStream.prototype = new stream(); 49229 49230 _CoalesceStream.prototype.flush = function (flushSource) { 49231 var offset = 0, 49232 event = { 49233 captions: [],
vendor: 4,652 bytes, lines 49234-49339
49234 captionStreams: {}, 49235 metadata: [], 49236 info: {} 49237 }, 49238 caption, 49239 id3, 49240 initSegment, 49241 timelineStartPts = 0, 49242 i; 49243 49244 if (this.pendingTracks.length < this.numberOfTracks) { 49245 if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') { 49246 // Return because we haven't received a flush from a data-generating 49247 // portion of the segment (meaning that we have only recieved meta-data 49248 // or captions.) 49249 return; 49250 } else if (this.remuxTracks) { 49251 // Return until we have enough tracks from the pipeline to remux (if we 49252 // are remuxing audio and video into a single MP4) 49253 return; 49254 } else if (this.pendingTracks.length === 0) { 49255 // In the case where we receive a flush without any data having been 49256 // received we consider it an emitted track for the purposes of coalescing 49257 // `done` events. 49258 // We do this for the case where there is an audio and video track in the 49259 // segment but no audio data. (seen in several playlists with alternate 49260 // audio tracks and no audio present in the main TS segments.) 49261 this.emittedTracks++; 49262 49263 if (this.emittedTracks >= this.numberOfTracks) { 49264 this.trigger('done'); 49265 this.emittedTracks = 0; 49266 } 49267 49268 return; 49269 } 49270 } 49271 49272 if (this.videoTrack) { 49273 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 49274 videoProperties.forEach(function (prop) { 49275 event.info[prop] = this.videoTrack[prop]; 49276 }, this); 49277 } else if (this.audioTrack) { 49278 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 49279 audioProperties.forEach(function (prop) { 49280 event.info[prop] = this.audioTrack[prop]; 49281 }, this); 49282 } 49283 49284 if (this.videoTrack || this.audioTrack) { 49285 if (this.pendingTracks.length === 1) { 49286 event.type = this.pendingTracks[0].type; 49287 } else { 49288 event.type = 'combined'; 49289 } 49290 49291 this.emittedTracks += this.pendingTracks.length; 49292 initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment 49293 49294 event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov 49295 // and track definitions 49296 49297 event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats 49298 49299 event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together 49300 49301 for (i = 0; i < this.pendingBoxes.length; i++) { 49302 event.data.set(this.pendingBoxes[i], offset); 49303 offset += this.pendingBoxes[i].byteLength; 49304 } // Translate caption PTS times into second offsets to match the 49305 // video timeline for the segment, and add track info 49306 49307 49308 for (i = 0; i < this.pendingCaptions.length; i++) { 49309 caption = this.pendingCaptions[i]; 49310 caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps); 49311 caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps); 49312 event.captionStreams[caption.stream] = true; 49313 event.captions.push(caption); 49314 } // Translate ID3 frame PTS times into second offsets to match the 49315 // video timeline for the segment 49316 49317 49318 for (i = 0; i < this.pendingMetadata.length; i++) { 49319 id3 = this.pendingMetadata[i]; 49320 id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps); 49321 event.metadata.push(id3); 49322 } // We add this to every single emitted segment even though we only need 49323 // it for the first 49324 49325 49326 event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state 49327 49328 this.pendingTracks.length = 0; 49329 this.videoTrack = null; 49330 this.pendingBoxes.length = 0; 49331 this.pendingCaptions.length = 0; 49332 this.pendingBytes = 0; 49333 this.pendingMetadata.length = 0; // Emit the built segment 49334 // We include captions and ID3 tags for backwards compatibility, 49335 // ideally we should send only video and audio in the data event 49336 49337 this.trigger('data', event); // Emit each caption to the outside world 49338 // Ideally, this would happen immediately on parsing captions, 49339 // but we need to ensure that video data is sent back first
vendor: 18,819 bytes, lines 49340-49814
49340 // so that caption timing can be adjusted to match video timing 49341 49342 for (i = 0; i < event.captions.length; i++) { 49343 caption = event.captions[i]; 49344 this.trigger('caption', caption); 49345 } // Emit each id3 tag to the outside world 49346 // Ideally, this would happen immediately on parsing the tag, 49347 // but we need to ensure that video data is sent back first 49348 // so that ID3 frame timing can be adjusted to match video timing 49349 49350 49351 for (i = 0; i < event.metadata.length; i++) { 49352 id3 = event.metadata[i]; 49353 this.trigger('id3Frame', id3); 49354 } 49355 } // Only emit `done` if all tracks have been flushed and emitted 49356 49357 49358 if (this.emittedTracks >= this.numberOfTracks) { 49359 this.trigger('done'); 49360 this.emittedTracks = 0; 49361 } 49362 }; 49363 49364 _CoalesceStream.prototype.setRemux = function (val) { 49365 this.remuxTracks = val; 49366 }; 49367 /** 49368 * A Stream that expects MP2T binary data as input and produces 49369 * corresponding media segments, suitable for use with Media Source 49370 * Extension (MSE) implementations that support the ISO BMFF byte 49371 * stream format, like Chrome. 49372 */ 49373 49374 49375 _Transmuxer = function Transmuxer(options) { 49376 var self = this, 49377 hasFlushed = true, 49378 videoTrack, 49379 audioTrack; 49380 49381 _Transmuxer.prototype.init.call(this); 49382 49383 options = options || {}; 49384 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 49385 this.transmuxPipeline_ = {}; 49386 49387 this.setupAacPipeline = function () { 49388 var pipeline = {}; 49389 this.transmuxPipeline_ = pipeline; 49390 pipeline.type = 'aac'; 49391 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 49392 49393 pipeline.aacStream = new aac(); 49394 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 49395 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 49396 pipeline.adtsStream = new adts(); 49397 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 49398 pipeline.headOfPipeline = pipeline.aacStream; 49399 pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 49400 pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); 49401 pipeline.metadataStream.on('timestamp', function (frame) { 49402 pipeline.aacStream.setTimestamp(frame.timeStamp); 49403 }); 49404 pipeline.aacStream.on('data', function (data) { 49405 if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) { 49406 return; 49407 } 49408 49409 audioTrack = audioTrack || { 49410 timelineStartInfo: { 49411 baseMediaDecodeTime: self.baseMediaDecodeTime 49412 }, 49413 codec: 'adts', 49414 type: 'audio' 49415 }; // hook up the audio segment stream to the first track with aac data 49416 49417 pipeline.coalesceStream.numberOfTracks++; 49418 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 49419 pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream')); 49420 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 49421 49422 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info 49423 49424 self.trigger('trackinfo', { 49425 hasAudio: !!audioTrack, 49426 hasVideo: !!videoTrack 49427 }); 49428 }); // Re-emit any data coming from the coalesce stream to the outside world 49429 49430 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 49431 49432 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 49433 }; 49434 49435 this.setupTsPipeline = function () { 49436 var pipeline = {}; 49437 this.transmuxPipeline_ = pipeline; 49438 pipeline.type = 'ts'; 49439 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 49440 49441 pipeline.packetStream = new m2ts_1.TransportPacketStream(); 49442 pipeline.parseStream = new m2ts_1.TransportParseStream(); 49443 pipeline.elementaryStream = new m2ts_1.ElementaryStream(); 49444 pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream(); 49445 pipeline.adtsStream = new adts(); 49446 pipeline.h264Stream = new H264Stream(); 49447 pipeline.captionStream = new m2ts_1.CaptionStream(options); 49448 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 49449 pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams 49450 49451 pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!! 49452 // demux the streams 49453 49454 pipeline.timestampRolloverStream.pipe(pipeline.h264Stream); 49455 pipeline.timestampRolloverStream.pipe(pipeline.adtsStream); 49456 pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream 49457 49458 pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream); 49459 pipeline.elementaryStream.on('data', function (data) { 49460 var i; 49461 49462 if (data.type === 'metadata') { 49463 i = data.tracks.length; // scan the tracks listed in the metadata 49464 49465 while (i--) { 49466 if (!videoTrack && data.tracks[i].type === 'video') { 49467 videoTrack = data.tracks[i]; 49468 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 49469 } else if (!audioTrack && data.tracks[i].type === 'audio') { 49470 audioTrack = data.tracks[i]; 49471 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 49472 } 49473 } // hook up the video segment stream to the first track with h264 data 49474 49475 49476 if (videoTrack && !pipeline.videoSegmentStream) { 49477 pipeline.coalesceStream.numberOfTracks++; 49478 pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options); 49479 pipeline.videoSegmentStream.on('log', self.getLogTrigger_('videoSegmentStream')); 49480 pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) { 49481 // When video emits timelineStartInfo data after a flush, we forward that 49482 // info to the AudioSegmentStream, if it exists, because video timeline 49483 // data takes precedence. Do not do this if keepOriginalTimestamps is set, 49484 // because this is a particularly subtle form of timestamp alteration. 49485 if (audioTrack && !options.keepOriginalTimestamps) { 49486 audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the 49487 // very earliest DTS we have seen in video because Chrome will 49488 // interpret any video track with a baseMediaDecodeTime that is 49489 // non-zero as a gap. 49490 49491 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime); 49492 } 49493 }); 49494 pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo')); 49495 pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo')); 49496 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) { 49497 if (audioTrack) { 49498 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 49499 } 49500 }); 49501 pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline 49502 49503 pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream); 49504 } 49505 49506 if (audioTrack && !pipeline.audioSegmentStream) { 49507 // hook up the audio segment stream to the first track with aac data 49508 pipeline.coalesceStream.numberOfTracks++; 49509 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 49510 pipeline.audioSegmentStream.on('log', self.getLogTrigger_('audioSegmentStream')); 49511 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); 49512 pipeline.audioSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'audioSegmentTimingInfo')); // Set up the final part of the audio pipeline 49513 49514 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 49515 } // emit pmt info 49516 49517 49518 self.trigger('trackinfo', { 49519 hasAudio: !!audioTrack, 49520 hasVideo: !!videoTrack 49521 }); 49522 } 49523 }); // Re-emit any data coming from the coalesce stream to the outside world 49524 49525 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 49526 pipeline.coalesceStream.on('id3Frame', function (id3Frame) { 49527 id3Frame.dispatchType = pipeline.metadataStream.dispatchType; 49528 self.trigger('id3Frame', id3Frame); 49529 }); 49530 pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline 49531 49532 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 49533 }; // hook up the segment streams once track metadata is delivered 49534 49535 49536 this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { 49537 var pipeline = this.transmuxPipeline_; 49538 49539 if (!options.keepOriginalTimestamps) { 49540 this.baseMediaDecodeTime = baseMediaDecodeTime; 49541 } 49542 49543 if (audioTrack) { 49544 audioTrack.timelineStartInfo.dts = undefined; 49545 audioTrack.timelineStartInfo.pts = undefined; 49546 trackDecodeInfo.clearDtsInfo(audioTrack); 49547 49548 if (pipeline.audioTimestampRolloverStream) { 49549 pipeline.audioTimestampRolloverStream.discontinuity(); 49550 } 49551 } 49552 49553 if (videoTrack) { 49554 if (pipeline.videoSegmentStream) { 49555 pipeline.videoSegmentStream.gopCache_ = []; 49556 } 49557 49558 videoTrack.timelineStartInfo.dts = undefined; 49559 videoTrack.timelineStartInfo.pts = undefined; 49560 trackDecodeInfo.clearDtsInfo(videoTrack); 49561 pipeline.captionStream.reset(); 49562 } 49563 49564 if (pipeline.timestampRolloverStream) { 49565 pipeline.timestampRolloverStream.discontinuity(); 49566 } 49567 }; 49568 49569 this.setAudioAppendStart = function (timestamp) { 49570 if (audioTrack) { 49571 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 49572 } 49573 }; 49574 49575 this.setRemux = function (val) { 49576 var pipeline = this.transmuxPipeline_; 49577 options.remux = val; 49578 49579 if (pipeline && pipeline.coalesceStream) { 49580 pipeline.coalesceStream.setRemux(val); 49581 } 49582 }; 49583 49584 this.alignGopsWith = function (gopsToAlignWith) { 49585 if (videoTrack && this.transmuxPipeline_.videoSegmentStream) { 49586 this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith); 49587 } 49588 }; 49589 49590 this.getLogTrigger_ = function (key) { 49591 var self = this; 49592 return function (event) { 49593 event.stream = key; 49594 self.trigger('log', event); 49595 }; 49596 }; // feed incoming data to the front of the parsing pipeline 49597 49598 49599 this.push = function (data) { 49600 if (hasFlushed) { 49601 var isAac = isLikelyAacData(data); 49602 49603 if (isAac && this.transmuxPipeline_.type !== 'aac') { 49604 this.setupAacPipeline(); 49605 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 49606 this.setupTsPipeline(); 49607 } 49608 49609 if (this.transmuxPipeline_) { 49610 var keys = Object.keys(this.transmuxPipeline_); 49611 49612 for (var i = 0; i < keys.length; i++) { 49613 var key = keys[i]; // skip non-stream keys and headOfPipeline 49614 // which is just a duplicate 49615 49616 if (key === 'headOfPipeline' || !this.transmuxPipeline_[key].on) { 49617 continue; 49618 } 49619 49620 this.transmuxPipeline_[key].on('log', this.getLogTrigger_(key)); 49621 } 49622 } 49623 49624 hasFlushed = false; 49625 } 49626 49627 this.transmuxPipeline_.headOfPipeline.push(data); 49628 }; // flush any buffered data 49629 49630 49631 this.flush = function () { 49632 hasFlushed = true; // Start at the top of the pipeline and flush all pending work 49633 49634 this.transmuxPipeline_.headOfPipeline.flush(); 49635 }; 49636 49637 this.endTimeline = function () { 49638 this.transmuxPipeline_.headOfPipeline.endTimeline(); 49639 }; 49640 49641 this.reset = function () { 49642 if (this.transmuxPipeline_.headOfPipeline) { 49643 this.transmuxPipeline_.headOfPipeline.reset(); 49644 } 49645 }; // Caption data has to be reset when seeking outside buffered range 49646 49647 49648 this.resetCaptions = function () { 49649 if (this.transmuxPipeline_.captionStream) { 49650 this.transmuxPipeline_.captionStream.reset(); 49651 } 49652 }; 49653 }; 49654 49655 _Transmuxer.prototype = new stream(); 49656 var transmuxer = { 49657 Transmuxer: _Transmuxer, 49658 VideoSegmentStream: _VideoSegmentStream, 49659 AudioSegmentStream: _AudioSegmentStream, 49660 AUDIO_PROPERTIES: audioProperties, 49661 VIDEO_PROPERTIES: videoProperties, 49662 // exported for testing 49663 generateSegmentTimingInfo: generateSegmentTimingInfo 49664 }; 49665 /** 49666 * mux.js 49667 * 49668 * Copyright (c) Brightcove 49669 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 49670 */ 49671 49672 var toUnsigned$3 = function toUnsigned(value) { 49673 return value >>> 0; 49674 }; 49675 49676 var toHexString$1 = function toHexString(value) { 49677 return ('00' + value.toString(16)).slice(-2); 49678 }; 49679 49680 var bin = { 49681 toUnsigned: toUnsigned$3, 49682 toHexString: toHexString$1 49683 }; 49684 49685 var parseType$1 = function parseType(buffer) { 49686 var result = ''; 49687 result += String.fromCharCode(buffer[0]); 49688 result += String.fromCharCode(buffer[1]); 49689 result += String.fromCharCode(buffer[2]); 49690 result += String.fromCharCode(buffer[3]); 49691 return result; 49692 }; 49693 49694 var parseType_1 = parseType$1; 49695 var toUnsigned$2 = bin.toUnsigned; 49696 49697 var findBox = function findBox(data, path) { 49698 var results = [], 49699 i, 49700 size, 49701 type, 49702 end, 49703 subresults; 49704 49705 if (!path.length) { 49706 // short-circuit the search for empty paths 49707 return null; 49708 } 49709 49710 for (i = 0; i < data.byteLength;) { 49711 size = toUnsigned$2(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]); 49712 type = parseType_1(data.subarray(i + 4, i + 8)); 49713 end = size > 1 ? i + size : data.byteLength; 49714 49715 if (type === path[0]) { 49716 if (path.length === 1) { 49717 // this is the end of the path and we've found the box we were 49718 // looking for 49719 results.push(data.subarray(i + 8, end)); 49720 } else { 49721 // recursively search for the next box along the path 49722 subresults = findBox(data.subarray(i + 8, end), path.slice(1)); 49723 49724 if (subresults.length) { 49725 results = results.concat(subresults); 49726 } 49727 } 49728 } 49729 49730 i = end; 49731 } // we've finished searching all of data 49732 49733 49734 return results; 49735 }; 49736 49737 var findBox_1 = findBox; 49738 var toUnsigned$1 = bin.toUnsigned; 49739 49740 var tfdt = function tfdt(data) { 49741 var result = { 49742 version: data[0], 49743 flags: new Uint8Array(data.subarray(1, 4)), 49744 baseMediaDecodeTime: toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]) 49745 }; 49746 49747 if (result.version === 1) { 49748 result.baseMediaDecodeTime *= Math.pow(2, 32); 49749 result.baseMediaDecodeTime += toUnsigned$1(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]); 49750 } 49751 49752 return result; 49753 }; 49754 49755 var parseTfdt = tfdt; 49756 49757 var parseSampleFlags = function parseSampleFlags(flags) { 49758 return { 49759 isLeading: (flags[0] & 0x0c) >>> 2, 49760 dependsOn: flags[0] & 0x03, 49761 isDependedOn: (flags[1] & 0xc0) >>> 6, 49762 hasRedundancy: (flags[1] & 0x30) >>> 4, 49763 paddingValue: (flags[1] & 0x0e) >>> 1, 49764 isNonSyncSample: flags[1] & 0x01, 49765 degradationPriority: flags[2] << 8 | flags[3] 49766 }; 49767 }; 49768 49769 var parseSampleFlags_1 = parseSampleFlags; 49770 49771 var trun = function trun(data) { 49772 var result = { 49773 version: data[0], 49774 flags: new Uint8Array(data.subarray(1, 4)), 49775 samples: [] 49776 }, 49777 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 49778 // Flag interpretation 49779 dataOffsetPresent = result.flags[2] & 0x01, 49780 // compare with 2nd byte of 0x1 49781 firstSampleFlagsPresent = result.flags[2] & 0x04, 49782 // compare with 2nd byte of 0x4 49783 sampleDurationPresent = result.flags[1] & 0x01, 49784 // compare with 2nd byte of 0x100 49785 sampleSizePresent = result.flags[1] & 0x02, 49786 // compare with 2nd byte of 0x200 49787 sampleFlagsPresent = result.flags[1] & 0x04, 49788 // compare with 2nd byte of 0x400 49789 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, 49790 // compare with 2nd byte of 0x800 49791 sampleCount = view.getUint32(4), 49792 offset = 8, 49793 sample; 49794 49795 if (dataOffsetPresent) { 49796 // 32 bit signed integer 49797 result.dataOffset = view.getInt32(offset); 49798 offset += 4; 49799 } // Overrides the flags for the first sample only. The order of 49800 // optional values will be: duration, size, compositionTimeOffset 49801 49802 49803 if (firstSampleFlagsPresent && sampleCount) { 49804 sample = { 49805 flags: parseSampleFlags_1(data.subarray(offset, offset + 4)) 49806 }; 49807 offset += 4; 49808 49809 if (sampleDurationPresent) { 49810 sample.duration = view.getUint32(offset); 49811 offset += 4; 49812 } 49813 49814 if (sampleSizePresent) {
vendor: 4,340 bytes, lines 49815-49960
49815 sample.size = view.getUint32(offset); 49816 offset += 4; 49817 } 49818 49819 if (sampleCompositionTimeOffsetPresent) { 49820 if (result.version === 1) { 49821 sample.compositionTimeOffset = view.getInt32(offset); 49822 } else { 49823 sample.compositionTimeOffset = view.getUint32(offset); 49824 } 49825 49826 offset += 4; 49827 } 49828 49829 result.samples.push(sample); 49830 sampleCount--; 49831 } 49832 49833 while (sampleCount--) { 49834 sample = {}; 49835 49836 if (sampleDurationPresent) { 49837 sample.duration = view.getUint32(offset); 49838 offset += 4; 49839 } 49840 49841 if (sampleSizePresent) { 49842 sample.size = view.getUint32(offset); 49843 offset += 4; 49844 } 49845 49846 if (sampleFlagsPresent) { 49847 sample.flags = parseSampleFlags_1(data.subarray(offset, offset + 4)); 49848 offset += 4; 49849 } 49850 49851 if (sampleCompositionTimeOffsetPresent) { 49852 if (result.version === 1) { 49853 sample.compositionTimeOffset = view.getInt32(offset); 49854 } else { 49855 sample.compositionTimeOffset = view.getUint32(offset); 49856 } 49857 49858 offset += 4; 49859 } 49860 49861 result.samples.push(sample); 49862 } 49863 49864 return result; 49865 }; 49866 49867 var parseTrun = trun; 49868 49869 var tfhd = function tfhd(data) { 49870 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 49871 result = { 49872 version: data[0], 49873 flags: new Uint8Array(data.subarray(1, 4)), 49874 trackId: view.getUint32(4) 49875 }, 49876 baseDataOffsetPresent = result.flags[2] & 0x01, 49877 sampleDescriptionIndexPresent = result.flags[2] & 0x02, 49878 defaultSampleDurationPresent = result.flags[2] & 0x08, 49879 defaultSampleSizePresent = result.flags[2] & 0x10, 49880 defaultSampleFlagsPresent = result.flags[2] & 0x20, 49881 durationIsEmpty = result.flags[0] & 0x010000, 49882 defaultBaseIsMoof = result.flags[0] & 0x020000, 49883 i; 49884 i = 8; 49885 49886 if (baseDataOffsetPresent) { 49887 i += 4; // truncate top 4 bytes 49888 // FIXME: should we read the full 64 bits? 49889 49890 result.baseDataOffset = view.getUint32(12); 49891 i += 4; 49892 } 49893 49894 if (sampleDescriptionIndexPresent) { 49895 result.sampleDescriptionIndex = view.getUint32(i); 49896 i += 4; 49897 } 49898 49899 if (defaultSampleDurationPresent) { 49900 result.defaultSampleDuration = view.getUint32(i); 49901 i += 4; 49902 } 49903 49904 if (defaultSampleSizePresent) { 49905 result.defaultSampleSize = view.getUint32(i); 49906 i += 4; 49907 } 49908 49909 if (defaultSampleFlagsPresent) { 49910 result.defaultSampleFlags = view.getUint32(i); 49911 } 49912 49913 if (durationIsEmpty) { 49914 result.durationIsEmpty = true; 49915 } 49916 49917 if (!baseDataOffsetPresent && defaultBaseIsMoof) { 49918 result.baseDataOffsetIsMoof = true; 49919 } 49920 49921 return result; 49922 }; 49923 49924 var parseTfhd = tfhd; 49925 var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes; 49926 var CaptionStream = captionStream.CaptionStream; 49927 /** 49928 * Maps an offset in the mdat to a sample based on the the size of the samples. 49929 * Assumes that `parseSamples` has been called first. 49930 * 49931 * @param {Number} offset - The offset into the mdat 49932 * @param {Object[]} samples - An array of samples, parsed using `parseSamples` 49933 * @return {?Object} The matching sample, or null if no match was found. 49934 * 49935 * @see ISO-BMFF-12/2015, Section 8.8.8 49936 **/ 49937 49938 var mapToSample = function mapToSample(offset, samples) { 49939 var approximateOffset = offset; 49940 49941 for (var i = 0; i < samples.length; i++) { 49942 var sample = samples[i]; 49943 49944 if (approximateOffset < sample.size) { 49945 return sample; 49946 } 49947 49948 approximateOffset -= sample.size; 49949 } 49950 49951 return null; 49952 }; 49953 /** 49954 * Finds SEI nal units contained in a Media Data Box. 49955 * Assumes that `parseSamples` has been called first. 49956 * 49957 * @param {Uint8Array} avcStream - The bytes of the mdat 49958 * @param {Object[]} samples - The samples parsed out by `parseSamples` 49959 * @param {Number} trackId - The trackId of this video track 49960 * @return {Object[]}
49960 seiNals - the parsed SEI NALUs found. 49961 * The contents of the seiNal should match what is expected by 49962 * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts) 49963 * 49964 * @see ISO-BMFF-12/2015, Section 8.1.1 49965 * @see Rec. ITU-T H.264, 7.3.2.3.1 49966 **/ 49967 49968 49969 var findSeiNals = function findSeiNals(avcStream, samples, trackId) { 49970 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 49971 result = { 49972 logs: [], 49973 seiNals: [] 49974 }, 49975 seiNal, 49976 i, 49977 length, 49978 lastMatchedSample; 49979 49980 for (i = 0; i + 4 < avcStream.length; i += length) { 49981 length = avcView.getUint32(i); 49982 i += 4; // Bail if this doesn't appear to be an H264 stream 49983 49984 if (length <= 0) { 49985 continue; 49986 } 49987 49988 switch (avcStream[i] & 0x1F) { 49989 case 0x06: 49990 var data = avcStream.subarray(i + 1, i + 1 + length); 49991 var matchingSample = mapToSample(i, samples); 49992 seiNal = { 49993 nalUnitType: 'sei_rbsp', 49994 size: length, 49995 data: data, 49996 escapedRBSP: discardEmulationPreventionBytes(data), 49997 trackId: trackId 49998 }; 49999 50000 if (matchingSample) { 50001 seiNal.pts = matchingSample.pts; 50002 seiNal.dts = matchingSample.dts; 50003 lastMatchedSample = matchingSample; 50004 } else if (lastMatchedSample) { 50005 // If a matching sample cannot be found, use the last 50006 // sample's values as they should be as close as possible 50007 seiNal.pts = lastMatchedSample.pts; 50008 seiNal.dts = lastMatchedSample.dts; 50009 } else { 50010 result.logs.push({ 50011 level: 'warn', 50012 message: 'We\'ve encountered a nal unit without data at ' + i + ' for trackId ' + trackId + '. See mux.js#223.' 50013 }); 50014 break; 50015 } 50016 50017 result.seiNals.push(seiNal); 50018 break; 50019 } 50020 } 50021 50022 return result; 50023 }; 50024 /** 50025 * Parses sample information out of Track Run Boxes and calculates 50026 * the absolute presentation and decode timestamps of each sample. 50027 * 50028 * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed 50029 * @param {Number} baseMediaDecodeTime - base media decode time from tfdt 50030 @see ISO-BMFF-12/2015, Section 8.8.12 50031 * @param {Object} tfhd - The parsed Track Fragment Header 50032 * @see inspect.parseTfhd 50033 * @return {Object[]} the parsed samples 50034 * 50035 * @see ISO-BMFF-12/2015, Section 8.8.8 50036 **/ 50037 50038 50039 var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) { 50040 var currentDts = baseMediaDecodeTime; 50041 var defaultSampleDuration = tfhd.defaultSampleDuration || 0; 50042 var defaultSampleSize = tfhd.defaultSampleSize || 0; 50043 var trackId = tfhd.trackId; 50044 var allSamples = []; 50045 truns.forEach(function (trun) { 50046 // Note: We currently do not parse the sample table as well 50047 // as the trun. It's possible some sources will require this. 50048 // moov > trak > mdia > minf > stbl 50049 var trackRun = parseTrun(trun); 50050 var samples = trackRun.samples; 50051 samples.forEach(function (sample) { 50052 if (sample.duration === undefined) { 50053 sample.duration = defaultSampleDuration; 50054 } 50055 50056 if (sample.size === undefined) { 50057 sample.size = defaultSampleSize; 50058 } 50059 50060 sample.trackId = trackId; 50061 sample.dts = currentDts; 50062 50063 if (sample.compositionTimeOffset === undefined) { 50064 sample.compositionTimeOffset = 0; 50065 } 50066 50067 sample.pts = currentDts + sample.compositionTimeOffset; 50068 currentDts += sample.duration; 50069 }); 50070 allSamples = allSamples.concat(samples); 50071 }); 50072 return allSamples; 50073 }; 50074 /** 50075 * Parses out caption nals from an FMP4 segment's video tracks. 50076 * 50077 * @param {Uint8Array} segment - The bytes of a single segment 50078 * @param {Number} videoTrackId - The trackId of a video track in the segment 50079 * @return {Object.<Number, Object[]>} A mapping of video trackId to 50080 * a list of seiNals found in that track 50081 **/ 50082 50083 50084 var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) { 50085 // To get the samples 50086 var trafs = findBox_1(segment, ['moof', 'traf']); // To get SEI NAL units 50087 50088 var mdats = findBox_1(segment, ['mdat']); 50089 var captionNals = {}; 50090 var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs 50091
vendor: 4,161 bytes, lines 50092-50200
50092 mdats.forEach(function (mdat, index) { 50093 var matchingTraf = trafs[index]; 50094 mdatTrafPairs.push({ 50095 mdat: mdat, 50096 traf: matchingTraf 50097 }); 50098 }); 50099 mdatTrafPairs.forEach(function (pair) { 50100 var mdat = pair.mdat; 50101 var traf = pair.traf; 50102 var tfhd = findBox_1(traf, ['tfhd']); // Exactly 1 tfhd per traf 50103 50104 var headerInfo = parseTfhd(tfhd[0]); 50105 var trackId = headerInfo.trackId; 50106 var tfdt = findBox_1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf 50107 50108 var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt(tfdt[0]).baseMediaDecodeTime : 0; 50109 var truns = findBox_1(traf, ['trun']); 50110 var samples; 50111 var result; // Only parse video data for the chosen video track 50112 50113 if (videoTrackId === trackId && truns.length > 0) { 50114 samples = parseSamples(truns, baseMediaDecodeTime, headerInfo); 50115 result = findSeiNals(mdat, samples, trackId); 50116 50117 if (!captionNals[trackId]) { 50118 captionNals[trackId] = { 50119 seiNals: [], 50120 logs: [] 50121 }; 50122 } 50123 50124 captionNals[trackId].seiNals = captionNals[trackId].seiNals.concat(result.seiNals); 50125 captionNals[trackId].logs = captionNals[trackId].logs.concat(result.logs); 50126 } 50127 }); 50128 return captionNals; 50129 }; 50130 /** 50131 * Parses out inband captions from an MP4 container and returns 50132 * caption objects that can be used by WebVTT and the TextTrack API. 50133 * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue 50134 * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack 50135 * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first 50136 * 50137 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 50138 * @param {Number} trackId - The id of the video track to parse 50139 * @param {Number} timescale - The timescale for the video track from the init segment 50140 * 50141 * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks 50142 * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds 50143 * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds 50144 * @return {String} parsedCaptions[].text - The visible content of the caption 50145 **/ 50146 50147 50148 var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) { 50149 var captionNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 50150 50151 if (trackId === null) { 50152 return null; 50153 } 50154 50155 captionNals = parseCaptionNals(segment, trackId); 50156 var trackNals = captionNals[trackId] || {}; 50157 return { 50158 seiNals: trackNals.seiNals, 50159 logs: trackNals.logs, 50160 timescale: timescale 50161 }; 50162 }; 50163 /** 50164 * Converts SEI NALUs into captions that can be used by video.js 50165 **/ 50166 50167 50168 var CaptionParser = function CaptionParser() { 50169 var isInitialized = false; 50170 var captionStream; // Stores segments seen before trackId and timescale are set 50171 50172 var segmentCache; // Stores video track ID of the track being parsed 50173 50174 var trackId; // Stores the timescale of the track being parsed 50175 50176 var timescale; // Stores captions parsed so far 50177 50178 var parsedCaptions; // Stores whether we are receiving partial data or not 50179 50180 var parsingPartial; 50181 /** 50182 * A method to indicate whether a CaptionParser has been initalized 50183 * @returns {Boolean} 50184 **/ 50185 50186 this.isInitialized = function () { 50187 return isInitialized; 50188 }; 50189 /** 50190 * Initializes the underlying CaptionStream, SEI NAL parsing 50191 * and management, and caption collection 50192 **/ 50193 50194 50195 this.init = function (options) { 50196 captionStream = new CaptionStream(); 50197 isInitialized = true; 50198 parsingPartial = options ? options.isPartial : false; // Collect dispatched captions 50199 50200 captionStream.on('data', function (event) {
vendor: 4,120 bytes, lines 50201-50316
50201 // Convert to seconds in the source's timescale 50202 event.startTime = event.startPts / timescale; 50203 event.endTime = event.endPts / timescale; 50204 parsedCaptions.captions.push(event); 50205 parsedCaptions.captionStreams[event.stream] = true; 50206 }); 50207 captionStream.on('log', function (log) { 50208 parsedCaptions.logs.push(log); 50209 }); 50210 }; 50211 /** 50212 * Determines if a new video track will be selected 50213 * or if the timescale changed 50214 * @return {Boolean} 50215 **/ 50216 50217 50218 this.isNewInit = function (videoTrackIds, timescales) { 50219 if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) { 50220 return false; 50221 } 50222 50223 return trackId !== videoTrackIds[0] || timescale !== timescales[trackId]; 50224 }; 50225 /** 50226 * Parses out SEI captions and interacts with underlying 50227 * CaptionStream to return dispatched captions 50228 * 50229 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 50230 * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment 50231 * @param {Object.<Number, Number>} timescales - The timescales found in the init segment 50232 * @see parseEmbeddedCaptions 50233 * @see m2ts/caption-stream.js 50234 **/ 50235 50236 50237 this.parse = function (segment, videoTrackIds, timescales) { 50238 var parsedData; 50239 50240 if (!this.isInitialized()) { 50241 return null; // This is not likely to be a video segment 50242 } else if (!videoTrackIds || !timescales) { 50243 return null; 50244 } else if (this.isNewInit(videoTrackIds, timescales)) { 50245 // Use the first video track only as there is no 50246 // mechanism to switch to other video tracks 50247 trackId = videoTrackIds[0]; 50248 timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment 50249 // data until we have one. 50250 // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 50251 } else if (trackId === null || !timescale) { 50252 segmentCache.push(segment); 50253 return null; 50254 } // Now that a timescale and trackId is set, parse cached segments 50255 50256 50257 while (segmentCache.length > 0) { 50258 var cachedSegment = segmentCache.shift(); 50259 this.parse(cachedSegment, videoTrackIds, timescales); 50260 } 50261 50262 parsedData = parseEmbeddedCaptions(segment, trackId, timescale); 50263 50264 if (parsedData && parsedData.logs) { 50265 parsedCaptions.logs = parsedCaptions.logs.concat(parsedData.logs); 50266 } 50267 50268 if (parsedData === null || !parsedData.seiNals) { 50269 if (parsedCaptions.logs.length) { 50270 return { 50271 logs: parsedCaptions.logs, 50272 captions: [], 50273 captionStreams: [] 50274 }; 50275 } 50276 50277 return null; 50278 } 50279 50280 this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched 50281 50282 this.flushStream(); 50283 return parsedCaptions; 50284 }; 50285 /** 50286 * Pushes SEI NALUs onto CaptionStream 50287 * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals` 50288 * Assumes that `parseCaptionNals` has been called first 50289 * @see m2ts/caption-stream.js 50290 **/ 50291 50292 50293 this.pushNals = function (nals) { 50294 if (!this.isInitialized() || !nals || nals.length === 0) { 50295 return null; 50296 } 50297 50298 nals.forEach(function (nal) { 50299 captionStream.push(nal); 50300 }); 50301 }; 50302 /** 50303 * Flushes underlying CaptionStream to dispatch processed, displayable captions 50304 * @see m2ts/caption-stream.js 50305 **/ 50306 50307 50308 this.flushStream = function () { 50309 if (!this.isInitialized()) { 50310 return null; 50311 } 50312 50313 if (!parsingPartial) { 50314 captionStream.flush(); 50315 } else { 50316 captionStream.partialFlush();
50317 } 50318 }; 50319 /** 50320 * Reset caption buckets for new data 50321 **/ 50322 50323 50324 this.clearParsedCaptions = function () { 50325 parsedCaptions.captions = []; 50326 parsedCaptions.captionStreams = {}; 50327 parsedCaptions.logs = []; 50328 }; 50329 /** 50330 * Resets underlying CaptionStream 50331 * @see m2ts/caption-stream.js 50332 **/ 50333 50334 50335 this.resetCaptionStream = function () { 50336 if (!this.isInitialized()) { 50337 return null; 50338 } 50339 50340 captionStream.reset(); 50341 }; 50342 /** 50343 * Convenience method to clear all captions flushed from the 50344 * CaptionStream and still being parsed 50345 * @see m2ts/caption-stream.js 50346 **/ 50347 50348 50349 this.clearAllCaptions = function () { 50350 this.clearParsedCaptions(); 50351 this.resetCaptionStream(); 50352 }; 50353 /** 50354 * Reset caption parser 50355 **/ 50356 50357 50358 this.reset = function () { 50359 segmentCache = []; 50360 trackId = null; 50361 timescale = null; 50362 50363 if (!parsedCaptions) { 50364 parsedCaptions = { 50365 captions: [], 50366 // CC1, CC2, CC3, CC4 50367 captionStreams: {}, 50368 logs: [] 50369 }; 50370 } else { 50371 this.clearParsedCaptions(); 50372 } 50373 50374 this.resetCaptionStream(); 50375 }; 50376 50377 this.reset(); 50378 }; 50379 50380 var captionParser = CaptionParser; 50381 var toUnsigned = bin.toUnsigned; 50382 var toHexString = bin.toHexString; 50383 var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader; 50384 /** 50385 * Parses an MP4 initialization segment and extracts the timescale 50386 * values for any declared tracks. Timescale values indicate the 50387 * number of clock ticks per second to assume for time-based values 50388 * elsewhere in the MP4. 50389 * 50390 * To determine the start time of an MP4, you need two pieces of 50391 * information: the timescale unit and the earliest base media decode 50392 * time. Multiple timescales can be specified within an MP4 but the 50393 * base media decode time is always expressed in the timescale from 50394 * the media header box for the track: 50395 * ``` 50396 * moov > trak > mdia > mdhd.timescale 50397 * ``` 50398 * @param init {Uint8Array} the bytes of the init segment 50399 * @return {object} a hash of track ids to timescale values or null if 50400 * the init segment is malformed. 50401 */ 50402 50403 timescale = function timescale(init) { 50404 var result = {}, 50405 traks = findBox_1(init, ['moov', 'trak']); // mdhd timescale 50406 50407 return traks.reduce(function (result, trak) { 50408 var tkhd, version, index, id, mdhd; 50409 tkhd = findBox_1(trak, ['tkhd'])[0]; 50410 50411 if (!tkhd) { 50412 return null; 50413 } 50414 50415 version = tkhd[0]; 50416 index = version === 0 ? 12 : 20; 50417 id = toUnsigned(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]); 50418 mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0]; 50419 50420 if (!mdhd) { 50421 return null; 50422 } 50423 50424 version = mdhd[0]; 50425 index = version === 0 ? 12 : 20; 50426 result[id] = toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 50427 return result; 50428 }, result); 50429 }; 50430 /** 50431 * Determine the base media decode start time, in seconds, for an MP4 50432 * fragment. If multiple fragments are specified, the earliest time is 50433 * returned. 50434 * 50435 * The base media decode time can be parsed from track fragment 50436 * metadata: 50437 * ``` 50438 * moof > traf > tfdt.baseMediaDecodeTime 50439 * ``` 50440 * It requires the timescale value from the mdhd to interpret. 50441 * 50442 * @param timescale {object} a hash of track ids to timescale values. 50443 * @return {number} the earliest base media decode start time for the 50444 * fragment, in seconds 50445 */ 50446 50447 50448 startTime = function startTime(timescale, fragment) { 50449 var trafs, baseTimes, result; // we need info from two childrend of each track fragment box 50450 50451 trafs = findBox_1(fragment, ['moof', 'traf']); // determine the start times for each track 50452 50453 baseTimes = [].concat.apply([], trafs.map(function (traf) { 50454 return findBox_1(traf, ['tfhd']).map(function (tfhd) { 50455 var id, scale, baseTime; // get the track id from the tfhd 50456 50457 id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified 50458 50459 scale = timescale[id] || 90e3; // get the base media decode time from the tfdt 50460 50461 baseTime = findBox_1(traf, ['tfdt']).map(function (tfdt) { 50462 var version, result; 50463 version = tfdt[0]; 50464 result = toUnsigned(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]); 50465 50466 if (version === 1) { 50467 result *= Math.pow(2, 32); 50468 result += toUnsigned(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]); 50469 } 50470 50471 return result; 50472 })[0]; 50473 baseTime = typeof baseTime === 'number' && !isNaN(baseTime) ? baseTime : Infinity; // convert base time to seconds 50474 50475 return baseTime / scale; 50476 }); 50477 })); // return the minimum 50478 50479 result = Math.min.apply(null, baseTimes); 50480 return isFinite(result) ? result : 0; 50481 }; 50482 /** 50483 * Determine the composition start, in seconds, for an MP4 50484 * fragment. 50485 * 50486 * The composition start time of a fragment can be calculated using the base 50487 * media decode time, composition time offset, and timescale, as follows: 50488 * 50489 * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale 50490 * 50491 * All of the aforementioned information is contained within a media fragment's 50492 * `traf` box, except for timescale info, which comes from the initialization 50493 * segment, so a track id (also contained within a `traf`) is also necessary to 50494 * associate it with a timescale 50495 * 50496 * 50497 * @param timescales {object} - a hash of track ids to timescale values. 50498 * @param fragment {Unit8Array} - the bytes of a media segment 50499 * @return {number} the composition start time for the fragment, in seconds 50500 **/ 50501 50502 50503 compositionStartTime = function compositionStartTime(timescales, fragment) { 50504 var trafBoxes = findBox_1(fragment, ['moof', 'traf']); 50505 var baseMediaDecodeTime = 0; 50506 var compositionTimeOffset = 0; 50507 var trackId; 50508 50509 if (trafBoxes && trafBoxes.length) { 50510 // The spec states that track run samples contained within a `traf` box are contiguous, but 50511 // it does not explicitly state whether the `traf` boxes themselves are contiguous. 50512 // We will assume that they are, so we only need the first to calculate start time. 50513 var tfhd = findBox_1(trafBoxes[0], ['tfhd'])[0]; 50514 var trun = findBox_1(trafBoxes[0], ['trun'])[0]; 50515 var tfdt = findBox_1(trafBoxes[0], ['tfdt'])[0]; 50516 50517 if (tfhd) { 50518 var parsedTfhd = parseTfhd(tfhd); 50519 trackId = parsedTfhd.trackId; 50520 } 50521 50522 if (tfdt) { 50523 var parsedTfdt = parseTfdt(tfdt); 50524 baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime; 50525 } 50526 50527 if (trun) { 50528 var parsedTrun = parseTrun(trun); 50529 50530 if (parsedTrun.samples && parsedTrun.samples.length) { 50531 compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0; 50532 } 50533 } 50534 } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was 50535 // specified. 50536 50537 50538 var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds 50539 50540 return (baseMediaDecodeTime + compositionTimeOffset) / timescale; 50541 }; 50542 /** 50543 * Find the trackIds of the video tracks in this source. 50544 * Found by parsing the Handler Reference and Track Header Boxes: 50545 * moov > trak > mdia > hdlr 50546 * moov > trak > tkhd 50547 * 50548 * @param {Uint8Array} init - The bytes of the init segment for this source 50549 * @return {Number[]} A list of trackIds 50550 * 50551 * @see ISO-BMFF-12/2015, Section 8.4.3 50552 **/ 50553 50554 50555 getVideoTrackIds = function getVideoTrackIds(init) { 50556 var traks = findBox_1(init, ['moov', 'trak']); 50557 var videoTrackIds = []; 50558 traks.forEach(function (trak) { 50559 var hdlrs = findBox_1(trak, ['mdia', 'hdlr']); 50560 var tkhds = findBox_1(trak, ['tkhd']);
vendor: 29,447 bytes, lines 50561-51417
50561 hdlrs.forEach(function (hdlr, index) { 50562 var handlerType = parseType_1(hdlr.subarray(8, 12)); 50563 var tkhd = tkhds[index]; 50564 var view; 50565 var version; 50566 var trackId; 50567 50568 if (handlerType === 'vide') { 50569 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 50570 version = view.getUint8(0); 50571 trackId = version === 0 ? view.getUint32(12) : view.getUint32(20); 50572 videoTrackIds.push(trackId); 50573 } 50574 }); 50575 }); 50576 return videoTrackIds; 50577 }; 50578 50579 getTimescaleFromMediaHeader = function getTimescaleFromMediaHeader(mdhd) { 50580 // mdhd is a FullBox, meaning it will have its own version as the first byte 50581 var version = mdhd[0]; 50582 var index = version === 0 ? 12 : 20; 50583 return toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 50584 }; 50585 /** 50586 * Get all the video, audio, and hint tracks from a non fragmented 50587 * mp4 segment 50588 */ 50589 50590 50591 getTracks = function getTracks(init) { 50592 var traks = findBox_1(init, ['moov', 'trak']); 50593 var tracks = []; 50594 traks.forEach(function (trak) { 50595 var track = {}; 50596 var tkhd = findBox_1(trak, ['tkhd'])[0]; 50597 var view, tkhdVersion; // id 50598 50599 if (tkhd) { 50600 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 50601 tkhdVersion = view.getUint8(0); 50602 track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20); 50603 } 50604 50605 var hdlr = findBox_1(trak, ['mdia', 'hdlr'])[0]; // type 50606 50607 if (hdlr) { 50608 var type = parseType_1(hdlr.subarray(8, 12)); 50609 50610 if (type === 'vide') { 50611 track.type = 'video'; 50612 } else if (type === 'soun') { 50613 track.type = 'audio'; 50614 } else { 50615 track.type = type; 50616 } 50617 } // codec 50618 50619 50620 var stsd = findBox_1(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; 50621 50622 if (stsd) { 50623 var sampleDescriptions = stsd.subarray(8); // gives the codec type string 50624 50625 track.codec = parseType_1(sampleDescriptions.subarray(4, 8)); 50626 var codecBox = findBox_1(sampleDescriptions, [track.codec])[0]; 50627 var codecConfig, codecConfigType; 50628 50629 if (codecBox) { 50630 // https://tools.ietf.org/html/rfc6381#section-3.3 50631 if (/^[a-z]vc[1-9]$/i.test(track.codec)) { 50632 // we don't need anything but the "config" parameter of the 50633 // avc1 codecBox 50634 codecConfig = codecBox.subarray(78); 50635 codecConfigType = parseType_1(codecConfig.subarray(4, 8)); 50636 50637 if (codecConfigType === 'avcC' && codecConfig.length > 11) { 50638 track.codec += '.'; // left padded with zeroes for single digit hex 50639 // profile idc 50640 50641 track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags 50642 50643 track.codec += toHexString(codecConfig[10]); // level idc 50644 50645 track.codec += toHexString(codecConfig[11]); 50646 } else { 50647 // TODO: show a warning that we couldn't parse the codec 50648 // and are using the default 50649 track.codec = 'avc1.4d400d'; 50650 } 50651 } else if (/^mp4[a,v]$/i.test(track.codec)) { 50652 // we do not need anything but the streamDescriptor of the mp4a codecBox 50653 codecConfig = codecBox.subarray(28); 50654 codecConfigType = parseType_1(codecConfig.subarray(4, 8)); 50655 50656 if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) { 50657 track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit 50658 50659 track.codec += '.' + toHexString(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, ''); 50660 } else { 50661 // TODO: show a warning that we couldn't parse the codec 50662 // and are using the default 50663 track.codec = 'mp4a.40.2'; 50664 } 50665 } else { 50666 // flac, opus, etc 50667 track.codec = track.codec.toLowerCase(); 50668 } 50669 } 50670 } 50671 50672 var mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0]; 50673 50674 if (mdhd) { 50675 track.timescale = getTimescaleFromMediaHeader(mdhd); 50676 } 50677 50678 tracks.push(track); 50679 }); 50680 return tracks; 50681 }; 50682 50683 var probe$2 = { 50684 // export mp4 inspector's findBox and parseType for backwards compatibility 50685 findBox: findBox_1, 50686 parseType: parseType_1, 50687 timescale: timescale, 50688 startTime: startTime, 50689 compositionStartTime: compositionStartTime, 50690 videoTrackIds: getVideoTrackIds, 50691 tracks: getTracks, 50692 getTimescaleFromMediaHeader: getTimescaleFromMediaHeader 50693 }; 50694 50695 var parsePid = function parsePid(packet) { 50696 var pid = packet[1] & 0x1f; 50697 pid <<= 8; 50698 pid |= packet[2]; 50699 return pid; 50700 }; 50701 50702 var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) { 50703 return !!(packet[1] & 0x40); 50704 }; 50705 50706 var parseAdaptionField = function parseAdaptionField(packet) { 50707 var offset = 0; // if an adaption field is present, its length is specified by the 50708 // fifth byte of the TS packet header. The adaptation field is 50709 // used to add stuffing to PES packets that don't fill a complete 50710 // TS packet, and to specify some forms of timing and control data 50711 // that we do not currently use. 50712 50713 if ((packet[3] & 0x30) >>> 4 > 0x01) { 50714 offset += packet[4] + 1; 50715 } 50716 50717 return offset; 50718 }; 50719 50720 var parseType = function parseType(packet, pmtPid) { 50721 var pid = parsePid(packet); 50722 50723 if (pid === 0) { 50724 return 'pat'; 50725 } else if (pid === pmtPid) { 50726 return 'pmt'; 50727 } else if (pmtPid) { 50728 return 'pes'; 50729 } 50730 50731 return null; 50732 }; 50733 50734 var parsePat = function parsePat(packet) { 50735 var pusi = parsePayloadUnitStartIndicator(packet); 50736 var offset = 4 + parseAdaptionField(packet); 50737 50738 if (pusi) { 50739 offset += packet[offset] + 1; 50740 } 50741 50742 return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; 50743 }; 50744 50745 var parsePmt = function parsePmt(packet) { 50746 var programMapTable = {}; 50747 var pusi = parsePayloadUnitStartIndicator(packet); 50748 var payloadOffset = 4 + parseAdaptionField(packet); 50749 50750 if (pusi) { 50751 payloadOffset += packet[payloadOffset] + 1; 50752 } // PMTs can be sent ahead of the time when they should actually 50753 // take effect. We don't believe this should ever be the case 50754 // for HLS but we'll ignore "forward" PMT declarations if we see 50755 // them. Future PMT declarations have the current_next_indicator 50756 // set to zero. 50757 50758 50759 if (!(packet[payloadOffset + 5] & 0x01)) { 50760 return; 50761 } 50762 50763 var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section 50764 50765 sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; 50766 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 50767 // long the program info descriptors are 50768 50769 programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table 50770 50771 var offset = 12 + programInfoLength; 50772 50773 while (offset < tableEnd) { 50774 var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type 50775 50776 programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry 50777 // skip past the elementary stream descriptors, if present 50778 50779 offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; 50780 } 50781 50782 return programMapTable; 50783 }; 50784 50785 var parsePesType = function parsePesType(packet, programMapTable) { 50786 var pid = parsePid(packet); 50787 var type = programMapTable[pid]; 50788 50789 switch (type) { 50790 case streamTypes.H264_STREAM_TYPE: 50791 return 'video'; 50792 50793 case streamTypes.ADTS_STREAM_TYPE: 50794 return 'audio'; 50795 50796 case streamTypes.METADATA_STREAM_TYPE: 50797 return 'timed-metadata'; 50798 50799 default: 50800 return null; 50801 } 50802 }; 50803 50804 var parsePesTime = function parsePesTime(packet) { 50805 var pusi = parsePayloadUnitStartIndicator(packet); 50806 50807 if (!pusi) { 50808 return null; 50809 } 50810 50811 var offset = 4 + parseAdaptionField(packet); 50812 50813 if (offset >= packet.byteLength) { 50814 // From the H 222.0 MPEG-TS spec 50815 // "For transport stream packets carrying PES packets, stuffing is needed when there 50816 // is insufficient PES packet data to completely fill the transport stream packet 50817 // payload bytes. Stuffing is accomplished by defining an adaptation field longer than 50818 // the sum of the lengths of the data elements in it, so that the payload bytes 50819 // remaining after the adaptation field exactly accommodates the available PES packet 50820 // data." 50821 // 50822 // If the offset is >= the length of the packet, then the packet contains no data 50823 // and instead is just adaption field stuffing bytes 50824 return null; 50825 } 50826 50827 var pes = null; 50828 var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value 50829 // and a DTS value. Determine what combination of values is 50830 // available to work with. 50831 50832 ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 50833 // performs all bitwise operations on 32-bit integers but javascript 50834 // supports a much greater range (52-bits) of integer using standard 50835 // mathematical operations. 50836 // We construct a 31-bit value using bitwise operators over the 31 50837 // most significant bits and then multiply by 4 (equal to a left-shift 50838 // of 2) before we add the final 2 least significant bits of the 50839 // timestamp (equal to an OR.) 50840 50841 if (ptsDtsFlags & 0xC0) { 50842 pes = {}; // the PTS and DTS are not written out directly. For information 50843 // on how they are encoded, see 50844 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 50845 50846 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; 50847 pes.pts *= 4; // Left shift by 2 50848 50849 pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs 50850 50851 pes.dts = pes.pts; 50852 50853 if (ptsDtsFlags & 0x40) { 50854 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; 50855 pes.dts *= 4; // Left shift by 2 50856 50857 pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs 50858 } 50859 } 50860 50861 return pes; 50862 }; 50863 50864 var parseNalUnitType = function parseNalUnitType(type) { 50865 switch (type) { 50866 case 0x05: 50867 return 'slice_layer_without_partitioning_rbsp_idr'; 50868 50869 case 0x06: 50870 return 'sei_rbsp'; 50871 50872 case 0x07: 50873 return 'seq_parameter_set_rbsp'; 50874 50875 case 0x08: 50876 return 'pic_parameter_set_rbsp'; 50877 50878 case 0x09: 50879 return 'access_unit_delimiter_rbsp'; 50880 50881 default: 50882 return null; 50883 } 50884 }; 50885 50886 var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) { 50887 var offset = 4 + parseAdaptionField(packet); 50888 var frameBuffer = packet.subarray(offset); 50889 var frameI = 0; 50890 var frameSyncPoint = 0; 50891 var foundKeyFrame = false; 50892 var nalType; // advance the sync point to a NAL start, if necessary 50893 50894 for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { 50895 if (frameBuffer[frameSyncPoint + 2] === 1) { 50896 // the sync point is properly aligned 50897 frameI = frameSyncPoint + 5; 50898 break; 50899 } 50900 } 50901 50902 while (frameI < frameBuffer.byteLength) { 50903 // look at the current byte to determine if we've hit the end of 50904 // a NAL unit boundary 50905 switch (frameBuffer[frameI]) { 50906 case 0: 50907 // skip past non-sync sequences 50908 if (frameBuffer[frameI - 1] !== 0) { 50909 frameI += 2; 50910 break; 50911 } else if (frameBuffer[frameI - 2] !== 0) { 50912 frameI++; 50913 break; 50914 } 50915 50916 if (frameSyncPoint + 3 !== frameI - 2) { 50917 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 50918 50919 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 50920 foundKeyFrame = true; 50921 } 50922 } // drop trailing zeroes 50923 50924 50925 do { 50926 frameI++; 50927 } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); 50928 50929 frameSyncPoint = frameI - 2; 50930 frameI += 3; 50931 break; 50932 50933 case 1: 50934 // skip past non-sync sequences 50935 if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) { 50936 frameI += 3; 50937 break; 50938 } 50939 50940 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 50941 50942 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 50943 foundKeyFrame = true; 50944 } 50945 50946 frameSyncPoint = frameI - 2; 50947 frameI += 3; 50948 break; 50949 50950 default: 50951 // the current byte isn't a one or zero, so it cannot be part 50952 // of a sync sequence 50953 frameI += 3; 50954 break; 50955 } 50956 } 50957 50958 frameBuffer = frameBuffer.subarray(frameSyncPoint); 50959 frameI -= frameSyncPoint; 50960 frameSyncPoint = 0; // parse the final nal 50961 50962 if (frameBuffer && frameBuffer.byteLength > 3) { 50963 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 50964 50965 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 50966 foundKeyFrame = true; 50967 } 50968 } 50969 50970 return foundKeyFrame; 50971 }; 50972 50973 var probe$1 = { 50974 parseType: parseType, 50975 parsePat: parsePat, 50976 parsePmt: parsePmt, 50977 parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, 50978 parsePesType: parsePesType, 50979 parsePesTime: parsePesTime, 50980 videoPacketContainsKeyFrame: videoPacketContainsKeyFrame 50981 }; 50982 var handleRollover = timestampRolloverStream.handleRollover; 50983 var probe = {}; 50984 probe.ts = probe$1; 50985 probe.aac = utils; 50986 var ONE_SECOND_IN_TS = clock.ONE_SECOND_IN_TS; 50987 var MP2T_PACKET_LENGTH = 188, 50988 // bytes 50989 SYNC_BYTE = 0x47; 50990 /** 50991 * walks through segment data looking for pat and pmt packets to parse out 50992 * program map table information 50993 */ 50994 50995 var parsePsi_ = function parsePsi_(bytes, pmt) { 50996 var startIndex = 0, 50997 endIndex = MP2T_PACKET_LENGTH, 50998 packet, 50999 type; 51000 51001 while (endIndex < bytes.byteLength) { 51002 // Look for a pair of start and end sync bytes in the data.. 51003 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 51004 // We found a packet 51005 packet = bytes.subarray(startIndex, endIndex); 51006 type = probe.ts.parseType(packet, pmt.pid); 51007 51008 switch (type) { 51009 case 'pat': 51010 pmt.pid = probe.ts.parsePat(packet); 51011 break; 51012 51013 case 'pmt': 51014 var table = probe.ts.parsePmt(packet); 51015 pmt.table = pmt.table || {}; 51016 Object.keys(table).forEach(function (key) { 51017 pmt.table[key] = table[key]; 51018 }); 51019 break; 51020 } 51021 51022 startIndex += MP2T_PACKET_LENGTH; 51023 endIndex += MP2T_PACKET_LENGTH; 51024 continue; 51025 } // If we get here, we have somehow become de-synchronized and we need to step 51026 // forward one byte at a time until we find a pair of sync bytes that denote 51027 // a packet 51028 51029 51030 startIndex++; 51031 endIndex++; 51032 } 51033 }; 51034 /** 51035 * walks through the segment data from the start and end to get timing information 51036 * for the first and last audio pes packets 51037 */ 51038 51039 51040 var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) { 51041 var startIndex = 0, 51042 endIndex = MP2T_PACKET_LENGTH, 51043 packet, 51044 type, 51045 pesType, 51046 pusi, 51047 parsed; 51048 var endLoop = false; // Start walking from start of segment to get first audio packet 51049 51050 while (endIndex <= bytes.byteLength) { 51051 // Look for a pair of start and end sync bytes in the data.. 51052 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 51053 // We found a packet 51054 packet = bytes.subarray(startIndex, endIndex); 51055 type = probe.ts.parseType(packet, pmt.pid); 51056 51057 switch (type) { 51058 case 'pes': 51059 pesType = probe.ts.parsePesType(packet, pmt.table); 51060 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 51061 51062 if (pesType === 'audio' && pusi) { 51063 parsed = probe.ts.parsePesTime(packet); 51064 51065 if (parsed) { 51066 parsed.type = 'audio'; 51067 result.audio.push(parsed); 51068 endLoop = true; 51069 } 51070 } 51071 51072 break; 51073 } 51074 51075 if (endLoop) { 51076 break; 51077 } 51078 51079 startIndex += MP2T_PACKET_LENGTH; 51080 endIndex += MP2T_PACKET_LENGTH; 51081 continue; 51082 } // If we get here, we have somehow become de-synchronized and we need to step 51083 // forward one byte at a time until we find a pair of sync bytes that denote 51084 // a packet 51085 51086 51087 startIndex++; 51088 endIndex++; 51089 } // Start walking from end of segment to get last audio packet 51090 51091 51092 endIndex = bytes.byteLength; 51093 startIndex = endIndex - MP2T_PACKET_LENGTH; 51094 endLoop = false; 51095 51096 while (startIndex >= 0) { 51097 // Look for a pair of start and end sync bytes in the data.. 51098 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 51099 // We found a packet 51100 packet = bytes.subarray(startIndex, endIndex); 51101 type = probe.ts.parseType(packet, pmt.pid); 51102 51103 switch (type) { 51104 case 'pes': 51105 pesType = probe.ts.parsePesType(packet, pmt.table); 51106 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 51107 51108 if (pesType === 'audio' && pusi) { 51109 parsed = probe.ts.parsePesTime(packet); 51110 51111 if (parsed) { 51112 parsed.type = 'audio'; 51113 result.audio.push(parsed); 51114 endLoop = true; 51115 } 51116 } 51117 51118 break; 51119 } 51120 51121 if (endLoop) { 51122 break; 51123 } 51124 51125 startIndex -= MP2T_PACKET_LENGTH; 51126 endIndex -= MP2T_PACKET_LENGTH; 51127 continue; 51128 } // If we get here, we have somehow become de-synchronized and we need to step 51129 // forward one byte at a time until we find a pair of sync bytes that denote 51130 // a packet 51131 51132 51133 startIndex--; 51134 endIndex--; 51135 } 51136 }; 51137 /** 51138 * walks through the segment data from the start and end to get timing information 51139 * for the first and last video pes packets as well as timing information for the first 51140 * key frame. 51141 */ 51142 51143 51144 var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) { 51145 var startIndex = 0, 51146 endIndex = MP2T_PACKET_LENGTH, 51147 packet, 51148 type, 51149 pesType, 51150 pusi, 51151 parsed, 51152 frame, 51153 i, 51154 pes; 51155 var endLoop = false; 51156 var currentFrame = { 51157 data: [], 51158 size: 0 51159 }; // Start walking from start of segment to get first video packet 51160 51161 while (endIndex < bytes.byteLength) { 51162 // Look for a pair of start and end sync bytes in the data.. 51163 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 51164 // We found a packet 51165 packet = bytes.subarray(startIndex, endIndex); 51166 type = probe.ts.parseType(packet, pmt.pid); 51167 51168 switch (type) { 51169 case 'pes': 51170 pesType = probe.ts.parsePesType(packet, pmt.table); 51171 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 51172 51173 if (pesType === 'video') { 51174 if (pusi && !endLoop) { 51175 parsed = probe.ts.parsePesTime(packet); 51176 51177 if (parsed) { 51178 parsed.type = 'video'; 51179 result.video.push(parsed); 51180 endLoop = true; 51181 } 51182 } 51183 51184 if (!result.firstKeyFrame) { 51185 if (pusi) { 51186 if (currentFrame.size !== 0) { 51187 frame = new Uint8Array(currentFrame.size); 51188 i = 0; 51189 51190 while (currentFrame.data.length) { 51191 pes = currentFrame.data.shift(); 51192 frame.set(pes, i); 51193 i += pes.byteLength; 51194 } 51195 51196 if (probe.ts.videoPacketContainsKeyFrame(frame)) { 51197 var firstKeyFrame = probe.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting 51198 // the keyframe seems to work fine with HLS playback 51199 // and definitely preferable to a crash with TypeError... 51200 51201 if (firstKeyFrame) { 51202 result.firstKeyFrame = firstKeyFrame; 51203 result.firstKeyFrame.type = 'video'; 51204 } else { 51205 // eslint-disable-next-line 51206 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.'); 51207 } 51208 } 51209 51210 currentFrame.size = 0; 51211 } 51212 } 51213 51214 currentFrame.data.push(packet); 51215 currentFrame.size += packet.byteLength; 51216 } 51217 } 51218 51219 break; 51220 } 51221 51222 if (endLoop && result.firstKeyFrame) { 51223 break; 51224 } 51225 51226 startIndex += MP2T_PACKET_LENGTH; 51227 endIndex += MP2T_PACKET_LENGTH; 51228 continue; 51229 } // If we get here, we have somehow become de-synchronized and we need to step 51230 // forward one byte at a time until we find a pair of sync bytes that denote 51231 // a packet 51232 51233 51234 startIndex++; 51235 endIndex++; 51236 } // Start walking from end of segment to get last video packet 51237 51238 51239 endIndex = bytes.byteLength; 51240 startIndex = endIndex - MP2T_PACKET_LENGTH; 51241 endLoop = false; 51242 51243 while (startIndex >= 0) { 51244 // Look for a pair of start and end sync bytes in the data.. 51245 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 51246 // We found a packet 51247 packet = bytes.subarray(startIndex, endIndex); 51248 type = probe.ts.parseType(packet, pmt.pid); 51249 51250 switch (type) { 51251 case 'pes': 51252 pesType = probe.ts.parsePesType(packet, pmt.table); 51253 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 51254 51255 if (pesType === 'video' && pusi) { 51256 parsed = probe.ts.parsePesTime(packet); 51257 51258 if (parsed) { 51259 parsed.type = 'video'; 51260 result.video.push(parsed); 51261 endLoop = true; 51262 } 51263 } 51264 51265 break; 51266 } 51267 51268 if (endLoop) { 51269 break; 51270 } 51271 51272 startIndex -= MP2T_PACKET_LENGTH; 51273 endIndex -= MP2T_PACKET_LENGTH; 51274 continue; 51275 } // If we get here, we have somehow become de-synchronized and we need to step 51276 // forward one byte at a time until we find a pair of sync bytes that denote 51277 // a packet 51278 51279 51280 startIndex--; 51281 endIndex--; 51282 } 51283 }; 51284 /** 51285 * Adjusts the timestamp information for the segment to account for 51286 * rollover and convert to seconds based on pes packet timescale (90khz clock) 51287 */ 51288 51289 51290 var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) { 51291 if (segmentInfo.audio && segmentInfo.audio.length) { 51292 var audioBaseTimestamp = baseTimestamp; 51293 51294 if (typeof audioBaseTimestamp === 'undefined' || isNaN(audioBaseTimestamp)) { 51295 audioBaseTimestamp = segmentInfo.audio[0].dts; 51296 } 51297 51298 segmentInfo.audio.forEach(function (info) { 51299 info.dts = handleRollover(info.dts, audioBaseTimestamp); 51300 info.pts = handleRollover(info.pts, audioBaseTimestamp); // time in seconds 51301 51302 info.dtsTime = info.dts / ONE_SECOND_IN_TS; 51303 info.ptsTime = info.pts / ONE_SECOND_IN_TS; 51304 }); 51305 } 51306 51307 if (segmentInfo.video && segmentInfo.video.length) { 51308 var videoBaseTimestamp = baseTimestamp; 51309 51310 if (typeof videoBaseTimestamp === 'undefined' || isNaN(videoBaseTimestamp)) { 51311 videoBaseTimestamp = segmentInfo.video[0].dts; 51312 } 51313 51314 segmentInfo.video.forEach(function (info) { 51315 info.dts = handleRollover(info.dts, videoBaseTimestamp); 51316 info.pts = handleRollover(info.pts, videoBaseTimestamp); // time in seconds 51317 51318 info.dtsTime = info.dts / ONE_SECOND_IN_TS; 51319 info.ptsTime = info.pts / ONE_SECOND_IN_TS; 51320 }); 51321 51322 if (segmentInfo.firstKeyFrame) { 51323 var frame = segmentInfo.firstKeyFrame; 51324 frame.dts = handleRollover(frame.dts, videoBaseTimestamp); 51325 frame.pts = handleRollover(frame.pts, videoBaseTimestamp); // time in seconds 51326 51327 frame.dtsTime = frame.dts / ONE_SECOND_IN_TS; 51328 frame.ptsTime = frame.pts / ONE_SECOND_IN_TS; 51329 } 51330 } 51331 }; 51332 /** 51333 * inspects the aac data stream for start and end time information 51334 */ 51335 51336 51337 var inspectAac_ = function inspectAac_(bytes) { 51338 var endLoop = false, 51339 audioCount = 0, 51340 sampleRate = null, 51341 timestamp = null, 51342 frameSize = 0, 51343 byteIndex = 0, 51344 packet; 51345 51346 while (bytes.length - byteIndex >= 3) { 51347 var type = probe.aac.parseType(bytes, byteIndex); 51348 51349 switch (type) { 51350 case 'timed-metadata': 51351 // Exit early because we don't have enough to parse 51352 // the ID3 tag header 51353 if (bytes.length - byteIndex < 10) { 51354 endLoop = true; 51355 break; 51356 } 51357 51358 frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 51359 // to emit a full packet 51360 51361 if (frameSize > bytes.length) { 51362 endLoop = true; 51363 break; 51364 } 51365 51366 if (timestamp === null) { 51367 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 51368 timestamp = probe.aac.parseAacTimestamp(packet); 51369 } 51370 51371 byteIndex += frameSize; 51372 break; 51373 51374 case 'audio': 51375 // Exit early because we don't have enough to parse 51376 // the ADTS frame header 51377 if (bytes.length - byteIndex < 7) { 51378 endLoop = true; 51379 break; 51380 } 51381 51382 frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 51383 // to emit a full packet 51384 51385 if (frameSize > bytes.length) { 51386 endLoop = true; 51387 break; 51388 } 51389 51390 if (sampleRate === null) { 51391 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 51392 sampleRate = probe.aac.parseSampleRate(packet); 51393 } 51394 51395 audioCount++; 51396 byteIndex += frameSize; 51397 break; 51398 51399 default: 51400 byteIndex++; 51401 break; 51402 } 51403 51404 if (endLoop) { 51405 return null; 51406 } 51407 } 51408 51409 if (sampleRate === null || timestamp === null) { 51410 return null; 51411 } 51412 51413 var audioTimescale = ONE_SECOND_IN_TS / sampleRate; 51414 var result = { 51415 audio: [{ 51416 type: 'audio', 51417 dts: timestamp,
vendor: 7,740 bytes, lines 51418-51656
51418 pts: timestamp 51419 }, { 51420 type: 'audio', 51421 dts: timestamp + audioCount * 1024 * audioTimescale, 51422 pts: timestamp + audioCount * 1024 * audioTimescale 51423 }] 51424 }; 51425 return result; 51426 }; 51427 /** 51428 * inspects the transport stream segment data for start and end time information 51429 * of the audio and video tracks (when present) as well as the first key frame's 51430 * start time. 51431 */ 51432 51433 51434 var inspectTs_ = function inspectTs_(bytes) { 51435 var pmt = { 51436 pid: null, 51437 table: null 51438 }; 51439 var result = {}; 51440 parsePsi_(bytes, pmt); 51441 51442 for (var pid in pmt.table) { 51443 if (pmt.table.hasOwnProperty(pid)) { 51444 var type = pmt.table[pid]; 51445 51446 switch (type) { 51447 case streamTypes.H264_STREAM_TYPE: 51448 result.video = []; 51449 parseVideoPes_(bytes, pmt, result); 51450 51451 if (result.video.length === 0) { 51452 delete result.video; 51453 } 51454 51455 break; 51456 51457 case streamTypes.ADTS_STREAM_TYPE: 51458 result.audio = []; 51459 parseAudioPes_(bytes, pmt, result); 51460 51461 if (result.audio.length === 0) { 51462 delete result.audio; 51463 } 51464 51465 break; 51466 } 51467 } 51468 } 51469 51470 return result; 51471 }; 51472 /** 51473 * Inspects segment byte data and returns an object with start and end timing information 51474 * 51475 * @param {Uint8Array} bytes The segment byte data 51476 * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame 51477 * timestamps for rollover. This value must be in 90khz clock. 51478 * @return {Object} Object containing start and end frame timing info of segment. 51479 */ 51480 51481 51482 var inspect = function inspect(bytes, baseTimestamp) { 51483 var isAacData = probe.aac.isLikelyAacData(bytes); 51484 var result; 51485 51486 if (isAacData) { 51487 result = inspectAac_(bytes); 51488 } else { 51489 result = inspectTs_(bytes); 51490 } 51491 51492 if (!result || !result.audio && !result.video) { 51493 return null; 51494 } 51495 51496 adjustTimestamp_(result, baseTimestamp); 51497 return result; 51498 }; 51499 51500 var tsInspector = { 51501 inspect: inspect, 51502 parseAudioPes_: parseAudioPes_ 51503 }; 51504 /* global self */ 51505 51506 /** 51507 * Re-emits transmuxer events by converting them into messages to the 51508 * world outside the worker. 51509 * 51510 * @param {Object} transmuxer the transmuxer to wire events on 51511 * @private 51512 */ 51513 51514 var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer) { 51515 transmuxer.on('data', function (segment) { 51516 // transfer ownership of the underlying ArrayBuffer 51517 // instead of doing a copy to save memory 51518 // ArrayBuffers are transferable but generic TypedArrays are not 51519 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 51520 var initArray = segment.initSegment; 51521 segment.initSegment = { 51522 data: initArray.buffer, 51523 byteOffset: initArray.byteOffset, 51524 byteLength: initArray.byteLength 51525 }; 51526 var typedArray = segment.data; 51527 segment.data = typedArray.buffer; 51528 self.postMessage({ 51529 action: 'data', 51530 segment: segment, 51531 byteOffset: typedArray.byteOffset, 51532 byteLength: typedArray.byteLength 51533 }, [segment.data]); 51534 }); 51535 transmuxer.on('done', function (data) { 51536 self.postMessage({ 51537 action: 'done' 51538 }); 51539 }); 51540 transmuxer.on('gopInfo', function (gopInfo) { 51541 self.postMessage({ 51542 action: 'gopInfo', 51543 gopInfo: gopInfo 51544 }); 51545 }); 51546 transmuxer.on('videoSegmentTimingInfo', function (timingInfo) { 51547 var videoSegmentTimingInfo = { 51548 start: { 51549 decode: clock.videoTsToSeconds(timingInfo.start.dts), 51550 presentation: clock.videoTsToSeconds(timingInfo.start.pts) 51551 }, 51552 end: { 51553 decode: clock.videoTsToSeconds(timingInfo.end.dts), 51554 presentation: clock.videoTsToSeconds(timingInfo.end.pts) 51555 }, 51556 baseMediaDecodeTime: clock.videoTsToSeconds(timingInfo.baseMediaDecodeTime) 51557 }; 51558 51559 if (timingInfo.prependedContentDuration) { 51560 videoSegmentTimingInfo.prependedContentDuration = clock.videoTsToSeconds(timingInfo.prependedContentDuration); 51561 } 51562 51563 self.postMessage({ 51564 action: 'videoSegmentTimingInfo', 51565 videoSegmentTimingInfo: videoSegmentTimingInfo 51566 }); 51567 }); 51568 transmuxer.on('audioSegmentTimingInfo', function (timingInfo) { 51569 // Note that all times for [audio/video]SegmentTimingInfo events are in video clock 51570 var audioSegmentTimingInfo = { 51571 start: { 51572 decode: clock.videoTsToSeconds(timingInfo.start.dts), 51573 presentation: clock.videoTsToSeconds(timingInfo.start.pts) 51574 }, 51575 end: { 51576 decode: clock.videoTsToSeconds(timingInfo.end.dts), 51577 presentation: clock.videoTsToSeconds(timingInfo.end.pts) 51578 }, 51579 baseMediaDecodeTime: clock.videoTsToSeconds(timingInfo.baseMediaDecodeTime) 51580 }; 51581 51582 if (timingInfo.prependedContentDuration) { 51583 audioSegmentTimingInfo.prependedContentDuration = clock.videoTsToSeconds(timingInfo.prependedContentDuration); 51584 } 51585 51586 self.postMessage({ 51587 action: 'audioSegmentTimingInfo', 51588 audioSegmentTimingInfo: audioSegmentTimingInfo 51589 }); 51590 }); 51591 transmuxer.on('id3Frame', function (id3Frame) { 51592 self.postMessage({ 51593 action: 'id3Frame', 51594 id3Frame: id3Frame 51595 }); 51596 }); 51597 transmuxer.on('caption', function (caption) { 51598 self.postMessage({ 51599 action: 'caption', 51600 caption: caption 51601 }); 51602 }); 51603 transmuxer.on('trackinfo', function (trackInfo) { 51604 self.postMessage({ 51605 action: 'trackinfo', 51606 trackInfo: trackInfo 51607 }); 51608 }); 51609 transmuxer.on('audioTimingInfo', function (audioTimingInfo) { 51610 // convert to video TS since we prioritize video time over audio 51611 self.postMessage({ 51612 action: 'audioTimingInfo', 51613 audioTimingInfo: { 51614 start: clock.videoTsToSeconds(audioTimingInfo.start), 51615 end: clock.videoTsToSeconds(audioTimingInfo.end) 51616 } 51617 }); 51618 }); 51619 transmuxer.on('videoTimingInfo', function (videoTimingInfo) { 51620 self.postMessage({ 51621 action: 'videoTimingInfo', 51622 videoTimingInfo: { 51623 start: clock.videoTsToSeconds(videoTimingInfo.start), 51624 end: clock.videoTsToSeconds(videoTimingInfo.end) 51625 } 51626 }); 51627 }); 51628 transmuxer.on('log', function (log) { 51629 self.postMessage({ 51630 action: 'log', 51631 log: log 51632 }); 51633 }); 51634 }; 51635 /** 51636 * All incoming messages route through this hash. If no function exists 51637 * to handle an incoming message, then we ignore the message. 51638 * 51639 * @class MessageHandlers 51640 * @param {Object} options the options to initialize with 51641 */ 51642 51643 51644 var MessageHandlers = /*#__PURE__*/function () { 51645 function MessageHandlers(self, options) { 51646 this.options = options || {}; 51647 this.self = self; 51648 this.init(); 51649 } 51650 /** 51651 * initialize our web worker and wire all the events. 51652 */ 51653 51654 51655 var _proto = MessageHandlers.prototype; 51656
51657 _proto.init = function init() { 51658 if (this.transmuxer) { 51659 this.transmuxer.dispose(); 51660 } 51661 51662 this.transmuxer = new transmuxer.Transmuxer(this.options); 51663 wireTransmuxerEvents(this.self, this.transmuxer); 51664 }; 51665 51666 _proto.pushMp4Captions = function pushMp4Captions(data) { 51667 if (!this.captionParser) { 51668 this.captionParser = new captionParser(); 51669 this.captionParser.init(); 51670 } 51671 51672 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 51673 var parsed = this.captionParser.parse(segment, data.trackIds, data.timescales); 51674 this.self.postMessage({ 51675 action: 'mp4Captions', 51676 captions: parsed && parsed.captions || [], 51677 logs: parsed && parsed.logs || [], 51678 data: segment.buffer 51679 }, [segment.buffer]); 51680 }; 51681 51682 _proto.probeMp4StartTime = function probeMp4StartTime(_ref) { 51683 var timescales = _ref.timescales, 51684 data = _ref.data; 51685 var startTime = probe$2.startTime(timescales, data); 51686 this.self.postMessage({ 51687 action: 'probeMp4StartTime', 51688 startTime: startTime, 51689 data: data 51690 }, [data.buffer]); 51691 }; 51692 51693 _proto.probeMp4Tracks = function probeMp4Tracks(_ref2) { 51694 var data = _ref2.data; 51695 var tracks = probe$2.tracks(data); 51696 this.self.postMessage({ 51697 action: 'probeMp4Tracks', 51698 tracks: tracks, 51699 data: data 51700 }, [data.buffer]); 51701 } 51702 /** 51703 * Probe an mpeg2-ts segment to determine the start time of the segment in it's 51704 * internal "media time," as well as whether it contains video and/or audio. 51705 * 51706 * @private 51707 * @param {Uint8Array} bytes - segment bytes 51708 * @param {number} baseStartTime 51709 * Relative reference timestamp used when adjusting frame timestamps for rollover. 51710 * This value should be in seconds, as it's converted to a 90khz clock within the 51711 * function body. 51712 * @return {Object} The start time of the current segment in "media time" as well as 51713 * whether it contains video and/or audio 51714 */ 51715 ; 51716 51717 _proto.probeTs = function probeTs(_ref3) { 51718 var data = _ref3.data, 51719 baseStartTime = _ref3.baseStartTime; 51720 var tsStartTime = typeof baseStartTime === 'number' && !isNaN(baseStartTime) ? baseStartTime * clock.ONE_SECOND_IN_TS : void 0; 51721 var timeInfo = tsInspector.inspect(data, tsStartTime); 51722 var result = null; 51723 51724 if (timeInfo) { 51725 result = { 51726 // each type's time info comes back as an array of 2 times, start and end 51727 hasVideo: timeInfo.video && timeInfo.video.length === 2 || false, 51728 hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false 51729 }; 51730 51731 if (result.hasVideo) { 51732 result.videoStart = timeInfo.video[0].ptsTime; 51733 } 51734 51735 if (result.hasAudio) { 51736 result.audioStart = timeInfo.audio[0].ptsTime; 51737 } 51738 } 51739 51740 this.self.postMessage({ 51741 action: 'probeTs', 51742 result: result, 51743 data: data 51744 }, [data.buffer]); 51745 }; 51746 51747 _proto.clearAllMp4Captions = function clearAllMp4Captions() { 51748 if (this.captionParser) { 51749 this.captionParser.clearAllCaptions(); 51750 } 51751 }; 51752 51753 _proto.clearParsedMp4Captions = function clearParsedMp4Captions() { 51754 if (this.captionParser) { 51755 this.captionParser.clearParsedCaptions(); 51756 } 51757 } 51758 /** 51759 * Adds data (a ts segment) to the start of the transmuxer pipeline for 51760 * processing. 51761 * 51762 * @param {ArrayBuffer} data data to push into the muxer 51763 */ 51764 ; 51765 51766 _proto.push = function push(data) { 51767 // Cast array buffer to correct type for transmuxer 51768 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 51769 this.transmuxer.push(segment); 51770 } 51771 /** 51772 * Recreate the transmuxer so that the next segment added via `push` 51773 * start with a fresh transmuxer. 51774 */ 51775 ; 51776 51777 _proto.reset = function reset() { 51778 this.transmuxer.reset(); 51779 } 51780 /** 51781 * Set the value that will be used as the `baseMediaDecodeTime` time for the 51782 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 51783 * set relative to the first based on the PTS values. 51784 * 51785 * @param {Object} data used to set the timestamp offset in the muxer 51786 */ 51787 ; 51788 51789 _proto.setTimestampOffset = function setTimestampOffset(data) { 51790 var timestampOffset = data.timestampOffset || 0; 51791 this.transmuxer.setBaseMediaDecodeTime(Math.round(clock.secondsToVideoTs(timestampOffset))); 51792 }; 51793 51794 _proto.setAudioAppendStart = function setAudioAppendStart(data) { 51795 this.transmuxer.setAudioAppendStart(Math.ceil(clock.secondsToVideoTs(data.appendStart))); 51796 }; 51797 51798 _proto.setRemux = function setRemux(data) { 51799 this.transmuxer.setRemux(data.remux); 51800 } 51801 /** 51802 * Forces the pipeline to finish processing the last segment and emit it's 51803 * results. 51804 * 51805 * @param {Object} data event data, not really used 51806 */ 51807 ; 51808 51809 _proto.flush = function flush(data) { 51810 this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed 51811 51812 self.postMessage({ 51813 action: 'done', 51814 type: 'transmuxed' 51815 }); 51816 }; 51817 51818 _proto.endTimeline = function endTimeline() { 51819 this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their 51820 // timelines 51821 51822 self.postMessage({ 51823 action: 'endedtimeline', 51824 type: 'transmuxed' 51825 }); 51826 }; 51827 51828 _proto.alignGopsWith = function alignGopsWith(data) { 51829 this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice()); 51830 }; 51831 51832 return MessageHandlers; 51833 }(); 51834 /** 51835 * Our web worker interface so that things can talk to mux.js 51836 * that will be running in a web worker. the scope is passed to this by 51837 * webworkify. 51838 * 51839 * @param {Object} self the scope for the web worker 51840 */ 51841 51842 51843 self.onmessage = function (event) {
51844 if (event.data.action === 'init' && event.data.options) { 51845 this.messageHandlers = new MessageHandlers(self, event.data.options); 51846 return; 51847 } 51848 51849 if (!this.messageHandlers) { 51850 this.messageHandlers = new MessageHandlers(self); 51851 } 51852 51853 if (event.data && event.data.action && event.data.action !== 'init') { 51854 if (this.messageHandlers[event.data.action]) { 51855 this.messageHandlers[event.data.action](event.data); 51856 } 51857 } 51858 }; 51859 })); 51860 var TransmuxWorker = factory(workerCode$1); 51861 /* rollup-plugin-worker-factory end for worker!/Users/bcasey/Projects/videojs-http-streaming/src/transmuxer-worker.js */ 51862 51863 var handleData_ = function handleData_(event, transmuxedData, callback) { 51864 var _event$data$segment = event.data.segment, 51865 type = _event$data$segment.type, 51866 initSegment = _event$data$segment.initSegment, 51867 captions = _event$data$segment.captions, 51868 captionStreams = _event$data$segment.captionStreams, 51869 metadata = _event$data$segment.metadata, 51870 videoFrameDtsTime = _event$data$segment.videoFrameDtsTime, 51871 videoFramePtsTime = _event$data$segment.videoFramePtsTime; 51872 transmuxedData.buffer.push({ 51873 captions: captions, 51874 captionStreams: captionStreams, 51875 metadata: metadata 51876 }); 51877 var boxes = event.data.segment.boxes || { 51878 data: event.data.segment.data 51879 }; 51880 var result = { 51881 type: type, 51882 // cast ArrayBuffer to TypedArray 51883 data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength), 51884 initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength) 51885 }; 51886 51887 if (typeof videoFrameDtsTime !== 'undefined') { 51888 result.videoFrameDtsTime = videoFrameDtsTime; 51889 } 51890 51891 if (typeof videoFramePtsTime !== 'undefined') { 51892 result.videoFramePtsTime = videoFramePtsTime; 51893 } 51894 51895 callback(result); 51896 }; 51897 51898 var handleDone_ = function handleDone_(_ref) { 51899 var transmuxedData = _ref.transmuxedData, 51900 callback = _ref.callback; // Previously we only returned data on data events, 51901 // not on done events. Clear out the buffer to keep that consistent. 51902 51903 transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we 51904 // have received 51905 51906 callback(transmuxedData); 51907 }; 51908 51909 var handleGopInfo_ = function handleGopInfo_(event, transmuxedData) { 51910 transmuxedData.gopInfo = event.data.gopInfo; 51911 }; 51912 51913 var processTransmux = function processTransmux(options) { 51914 var transmuxer = options.transmuxer, 51915 bytes = options.bytes, 51916 audioAppendStart = options.audioAppendStart, 51917 gopsToAlignWith = options.gopsToAlignWith, 51918 remux = options.remux, 51919 onData = options.onData, 51920 onTrackInfo = options.onTrackInfo, 51921 onAudioTimingInfo = options.onAudioTimingInfo, 51922 onVideoTimingInfo = options.onVideoTimingInfo, 51923 onVideoSegmentTimingInfo = options.onVideoSegmentTimingInfo, 51924 onAudioSegmentTimingInfo = options.onAudioSegmentTimingInfo, 51925 onId3 = options.onId3, 51926 onCaptions = options.onCaptions, 51927 onDone = options.onDone, 51928 onEndedTimeline = options.onEndedTimeline, 51929 onTransmuxerLog = options.onTransmuxerLog, 51930 isEndOfTimeline = options.isEndOfTimeline; 51931 var transmuxedData = { 51932 buffer: [] 51933 }; 51934 var waitForEndedTimelineEvent = isEndOfTimeline; 51935 51936 var handleMessage = function handleMessage(event) { 51937 if (transmuxer.currentTransmux !== options) { 51938 // disposed 51939 return; 51940 } 51941 51942 if (event.data.action === 'data') { 51943 handleData_(event, transmuxedData, onData); 51944 } 51945 51946 if (event.data.action === 'trackinfo') { 51947 onTrackInfo(event.data.trackInfo); 51948 } 51949 51950 if (event.data.action === 'gopInfo') { 51951 handleGopInfo_(event, transmuxedData); 51952 } 51953 51954 if (event.data.action === 'audioTimingInfo') { 51955 onAudioTimingInfo(event.data.audioTimingInfo); 51956 } 51957 51958 if (event.data.action === 'videoTimingInfo') { 51959 onVideoTimingInfo(event.data.videoTimingInfo); 51960 } 51961 51962 if (event.data.action === 'videoSegmentTimingInfo') { 51963 onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo); 51964 } 51965 51966 if (event.data.action === 'audioSegmentTimingInfo') { 51967 onAudioSegmentTimingInfo(event.data.audioSegmentTimingInfo); 51968 } 51969
51970 if (event.data.action === 'id3Frame') { 51971 onId3([event.data.id3Frame], event.data.id3Frame.dispatchType); 51972 } 51973 51974 if (event.data.action === 'caption') { 51975 onCaptions(event.data.caption); 51976 } 51977 51978 if (event.data.action === 'endedtimeline') { 51979 waitForEndedTimelineEvent = false; 51980 onEndedTimeline(); 51981 } 51982 51983 if (event.data.action === 'log') { 51984 onTransmuxerLog(event.data.log); 51985 } // wait for the transmuxed event since we may have audio and video 51986 51987 51988 if (event.data.type !== 'transmuxed') { 51989 return; 51990 } // If the "endedtimeline" event has not yet fired, and this segment represents the end 51991 // of a timeline, that means there may still be data events before the segment 51992 // processing can be considerred complete. In that case, the final event should be 51993 // an "endedtimeline" event with the type "transmuxed." 51994 51995 51996 if (waitForEndedTimelineEvent) { 51997 return; 51998 } 51999 52000 transmuxer.onmessage = null; 52001 handleDone_({ 52002 transmuxedData: transmuxedData, 52003 callback: onDone 52004 }); 52005 /* eslint-disable no-use-before-define */ 52006 52007 dequeue(transmuxer); 52008 /* eslint-enable */ 52009 }; 52010 52011 transmuxer.onmessage = handleMessage; 52012 52013 if (audioAppendStart) { 52014 transmuxer.postMessage({ 52015 action: 'setAudioAppendStart', 52016 appendStart: audioAppendStart 52017 }); 52018 } // allow empty arrays to be passed to clear out GOPs 52019 52020 52021 if (Array.isArray(gopsToAlignWith)) { 52022 transmuxer.postMessage({ 52023 action: 'alignGopsWith', 52024 gopsToAlignWith: gopsToAlignWith 52025 }); 52026 } 52027 52028 if (typeof remux !== 'undefined') { 52029 transmuxer.postMessage({ 52030 action: 'setRemux', 52031 remux: remux 52032 }); 52033 } 52034 52035 if (bytes.byteLength) { 52036 var buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer; 52037 var byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset; 52038 transmuxer.postMessage({ 52039 action: 'push', 52040 // Send the typed-array of data as an ArrayBuffer so that 52041 // it can be sent as a "Transferable" and avoid the costly 52042 // memory copy 52043 data: buffer, 52044 // To recreate the original typed-array, we need information 52045 // about what portion of the ArrayBuffer it was a view into 52046 byteOffset: byteOffset, 52047 byteLength: bytes.byteLength 52048 }, [buffer]); 52049 } 52050 52051 if (isEndOfTimeline) { 52052 transmuxer.postMessage({ 52053 action: 'endTimeline' 52054 }); 52055 } // even if we didn't push any bytes, we have to make sure we flush in case we reached 52056 // the end of the segment 52057 52058 52059 transmuxer.postMessage({ 52060 action: 'flush' 52061 }); 52062 }; 52063 52064 var dequeue = function dequeue(transmuxer) { 52065 transmuxer.currentTransmux = null; 52066 52067 if (transmuxer.transmuxQueue.length) { 52068 transmuxer.currentTransmux = transmuxer.transmuxQueue.shift(); 52069 52070 if (typeof transmuxer.currentTransmux === 'function') { 52071 transmuxer.currentTransmux(); 52072 } else { 52073 processTransmux(transmuxer.currentTransmux); 52074 } 52075 } 52076 }; 52077 52078 var processAction = function processAction(transmuxer, action) { 52079 transmuxer.postMessage({ 52080 action: action 52081 }); 52082 dequeue(transmuxer); 52083 }; 52084 52085 var enqueueAction = function enqueueAction(action, transmuxer) { 52086 if (!transmuxer.currentTransmux) { 52087 transmuxer.currentTransmux = action; 52088 processAction(transmuxer, action); 52089 return; 52090 } 52091 52092 transmuxer.transmuxQueue.push(processAction.bind(null, transmuxer, action)); 52093 }; 52094 52095 var reset = function reset(transmuxer) { 52096 enqueueAction('reset', transmuxer); 52097 }; 52098 52099 var endTimeline = function endTimeline(transmuxer) { 52100 enqueueAction('endTimeline', transmuxer); 52101 }; 52102 52103 var transmux = function transmux(options) { 52104 if (!options.transmuxer.currentTransmux) { 52105 options.transmuxer.currentTransmux = options; 52106 processTransmux(options); 52107 return; 52108 } 52109 52110 options.transmuxer.transmuxQueue.push(options); 52111 }; 52112 52113 var createTransmuxer = function createTransmuxer(options) { 52114 var transmuxer = new TransmuxWorker(); 52115 transmuxer.currentTransmux = null; 52116 transmuxer.transmuxQueue = []; 52117 var term = transmuxer.terminate; 52118 52119 transmuxer.terminate = function () { 52120 transmuxer.currentTransmux = null; 52121 transmuxer.transmuxQueue.length = 0; 52122 return term.call(transmuxer); 52123 }; 52124 52125 transmuxer.postMessage({ 52126 action: 'init', 52127 options: options 52128 });
52129 return transmuxer; 52130 }; 52131 52132 var segmentTransmuxer = { 52133 reset: reset, 52134 endTimeline: endTimeline, 52135 transmux: transmux, 52136 createTransmuxer: createTransmuxer 52137 }; 52138 52139 var workerCallback = function workerCallback(options) { 52140 var transmuxer = options.transmuxer; 52141 var endAction = options.endAction || options.action; 52142 var callback = options.callback; 52143 52144 var message = _extends_1({}, options, { 52145 endAction: null, 52146 transmuxer: null, 52147 callback: null 52148 }); 52149 52150 var listenForEndEvent = function listenForEndEvent(event) { 52151 if (event.data.action !== endAction) { 52152 return; 52153 } 52154 52155 transmuxer.removeEventListener('message', listenForEndEvent); // transfer ownership of bytes back to us. 52156 52157 if (event.data.data) { 52158 event.data.data = new Uint8Array(event.data.data, options.byteOffset || 0, options.byteLength || event.data.data.byteLength); 52159 52160 if (options.data) { 52161 options.data = event.data.data; 52162 } 52163 } 52164 52165 callback(event.data); 52166 }; 52167 52168 transmuxer.addEventListener('message', listenForEndEvent); 52169 52170 if (options.data) { 52171 var isArrayBuffer = options.data instanceof ArrayBuffer; 52172 message.byteOffset = isArrayBuffer ? 0 : options.data.byteOffset; 52173 message.byteLength = options.data.byteLength; 52174 var transfers = [isArrayBuffer ? options.data : options.data.buffer]; 52175 transmuxer.postMessage(message, transfers); 52176 } else { 52177 transmuxer.postMessage(message); 52178 } 52179 }; 52180 52181 var REQUEST_ERRORS = { 52182 FAILURE: 2, 52183 TIMEOUT: -101, 52184 ABORTED: -102 52185 }; 52186 /** 52187 * Abort all requests 52188 * 52189 * @param {Object} activeXhrs - an object that tracks all XHR requests 52190 */ 52191 52192 var abortAll = function abortAll(activeXhrs) { 52193 activeXhrs.forEach(function (xhr) { 52194 xhr.abort(); 52195 }); 52196 }; 52197 /** 52198 * Gather important bandwidth stats once a request has completed 52199 * 52200 * @param {Object} request - the XHR request from which to gather stats 52201 */ 52202 52203 52204 var getRequestStats = function getRequestStats(request) { 52205 return { 52206 bandwidth: request.bandwidth, 52207 bytesReceived: request.bytesReceived || 0, 52208 roundTripTime: request.roundTripTime || 0 52209 }; 52210 }; 52211 /** 52212 * If possible gather bandwidth stats as a request is in 52213 * progress 52214 * 52215 * @param {Event} progressEvent - an event object from an XHR's progress event 52216 */ 52217 52218 52219 var getProgressStats = function getProgressStats(progressEvent) { 52220 var request = progressEvent.target; 52221 var roundTripTime = Date.now() - request.requestTime; 52222 var stats = { 52223 bandwidth: Infinity, 52224 bytesReceived: 0, 52225 roundTripTime: roundTripTime || 0 52226 }; 52227 stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok 52228 // because we should only use bandwidth stats on progress to determine when 52229 // abort a request early due to insufficient bandwidth 52230 52231 stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000); 52232 return stats; 52233 }; 52234 /** 52235 * Handle all error conditions in one place and return an object 52236 * with all the information 52237 * 52238 * @param {Error|null} error - if non-null signals an error occured with the XHR 52239 * @param {Object} request - the XHR request that possibly generated the error 52240 */ 52241 52242 52243 var handleErrors = function handleErrors(error, request) { 52244 if (request.timedout) { 52245 return { 52246 status: request.status, 52247 message: 'HLS request timed-out at URL: ' + request.uri, 52248 code: REQUEST_ERRORS.TIMEOUT, 52249 xhr: request 52250 }; 52251 } 52252 52253 if (request.aborted) { 52254 return { 52255 status: request.status, 52256 message: 'HLS request aborted at URL: ' + request.uri, 52257 code: REQUEST_ERRORS.ABORTED, 52258 xhr: request 52259 }; 52260 } 52261 52262 if (error) { 52263 return { 52264 status: request.status, 52265 message: 'HLS request errored at URL: ' + request.uri, 52266 code: REQUEST_ERRORS.FAILURE, 52267 xhr: request 52268 }; 52269 } 52270 52271 if (request.responseType === 'arraybuffer' && request.response.byteLength === 0) { 52272 return { 52273 status: request.status, 52274 message: 'Empty HLS response at URL: ' + request.uri, 52275 code: REQUEST_ERRORS.FAILURE, 52276 xhr: request 52277 }; 52278 } 52279 52280 return null; 52281 }; 52282 /** 52283 * Handle responses for key data and convert the key data to the correct format 52284 * for the decryption step later 52285 * 52286 * @param {Object} segment - a simplified copy of the segmentInfo object 52287 * from SegmentLoader 52288 * @param {Array} objects - objects to add the key bytes to. 52289 * @param {Function} finishProcessingFn - a callback to execute to continue processing 52290 * this request 52291 */ 52292 52293 52294 var handleKeyResponse = function handleKeyResponse(segment, objects, finishProcessingFn) { 52295 return function (error, request) { 52296 var response = request.response; 52297 var errorObj = handleErrors(error, request); 52298 52299 if (errorObj) { 52300 return finishProcessingFn(errorObj, segment); 52301 } 52302 52303 if (response.byteLength !== 16) { 52304 return finishProcessingFn({ 52305 status: request.status, 52306 message: 'Invalid HLS key at URL: ' + request.uri, 52307 code: REQUEST_ERRORS.FAILURE, 52308 xhr: request 52309 }, segment); 52310 } 52311 52312 var view = new DataView(response); 52313 var bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 52314 52315 for (var i = 0; i < objects.length; i++) { 52316 objects[i].bytes = bytes; 52317 } 52318 52319 return finishProcessingFn(null, segment); 52320 }; 52321 }; 52322 52323 var parseInitSegment = function parseInitSegment(segment, _callback) { 52324 var type = detectContainerForBytes(segment.map.bytes); // TODO: We should also handle ts init segments here, but we 52325 // only know how to parse mp4 init segments at the moment 52326 52327 if (type !== 'mp4') { 52328 var uri = segment.map.resolvedUri || segment.map.uri; 52329 return _callback({ 52330 internal: true, 52331 message: "Found unsupported " + (type || 'unknown') + " container for initialization segment at URL: " + uri, 52332 code: REQUEST_ERRORS.FAILURE 52333 }); 52334 } 52335 52336 workerCallback({ 52337 action: 'probeMp4Tracks', 52338 data: segment.map.bytes, 52339 transmuxer: segment.transmuxer, 52340 callback: function callback(_ref) { 52341 var tracks = _ref.tracks, 52342 data = _ref.data; // transfer bytes back to us 52343 52344 segment.map.bytes = data; 52345 tracks.forEach(function (track) { 52346 segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now 52347 52348 if (segment.map.tracks[track.type]) { 52349 return; 52350 } 52351 52352 segment.map.tracks[track.type] = track; 52353 52354 if (typeof track.id === 'number' && track.timescale) { 52355 segment.map.timescales = segment.map.timescales || {}; 52356 segment.map.timescales[track.id] = track.timescale; 52357 } 52358 }); 52359 return _callback(null); 52360 } 52361 });
52362 }; 52363 /** 52364 * Handle init-segment responses 52365 * 52366 * @param {Object} segment - a simplified copy of the segmentInfo object 52367 * from SegmentLoader 52368 * @param {Function} finishProcessingFn - a callback to execute to continue processing 52369 * this request 52370 */ 52371 52372 52373 var handleInitSegmentResponse = function handleInitSegmentResponse(_ref2) { 52374 var segment = _ref2.segment, 52375 finishProcessingFn = _ref2.finishProcessingFn; 52376 return function (error, request) { 52377 var errorObj = handleErrors(error, request); 52378 52379 if (errorObj) { 52380 return finishProcessingFn(errorObj, segment); 52381 } 52382 52383 var bytes = new Uint8Array(request.response); // init segment is encypted, we will have to wait 52384 // until the key request is done to decrypt. 52385 52386 if (segment.map.key) { 52387 segment.map.encryptedBytes = bytes; 52388 return finishProcessingFn(null, segment); 52389 } 52390 52391 segment.map.bytes = bytes; 52392 parseInitSegment(segment, function (parseError) { 52393 if (parseError) { 52394 parseError.xhr = request; 52395 parseError.status = request.status; 52396 return finishProcessingFn(parseError, segment); 52397 } 52398 52399 finishProcessingFn(null, segment); 52400 }); 52401 }; 52402 }; 52403 /** 52404 * Response handler for segment-requests being sure to set the correct 52405 * property depending on whether the segment is encryped or not 52406 * Also records and keeps track of stats that are used for ABR purposes 52407 * 52408 * @param {Object} segment - a simplified copy of the segmentInfo object 52409 * from SegmentLoader 52410 * @param {Function} finishProcessingFn - a callback to execute to continue processing 52411 * this request 52412 */ 52413 52414 52415 var handleSegmentResponse = function handleSegmentResponse(_ref3) { 52416 var segment = _ref3.segment, 52417 finishProcessingFn = _ref3.finishProcessingFn, 52418 responseType = _ref3.responseType; 52419 return function (error, request) { 52420 var errorObj = handleErrors(error, request); 52421 52422 if (errorObj) { 52423 return finishProcessingFn(errorObj, segment); 52424 } 52425 52426 var newBytes = // although responseText "should" exist, this guard serves to prevent an error being 52427 // thrown for two primary cases: 52428 // 1. the mime type override stops working, or is not implemented for a specific 52429 // browser 52430 // 2. when using mock XHR libraries like sinon that do not allow the override behavior 52431 responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0)); 52432 segment.stats = getRequestStats(request); 52433 52434 if (segment.key) { 52435 segment.encryptedBytes = new Uint8Array(newBytes); 52436 } else { 52437 segment.bytes = new Uint8Array(newBytes); 52438 } 52439 52440 return finishProcessingFn(null, segment); 52441 }; 52442 }; 52443 52444 var transmuxAndNotify = function transmuxAndNotify(_ref4) { 52445 var segment = _ref4.segment, 52446 bytes = _ref4.bytes, 52447 trackInfoFn = _ref4.trackInfoFn, 52448 timingInfoFn = _ref4.timingInfoFn, 52449 videoSegmentTimingInfoFn = _ref4.videoSegmentTimingInfoFn, 52450 audioSegmentTimingInfoFn = _ref4.audioSegmentTimingInfoFn, 52451 id3Fn = _ref4.id3Fn, 52452 captionsFn = _ref4.captionsFn, 52453 isEndOfTimeline = _ref4.isEndOfTimeline, 52454 endedTimelineFn = _ref4.endedTimelineFn, 52455 dataFn = _ref4.dataFn, 52456 doneFn = _ref4.doneFn, 52457 onTransmuxerLog = _ref4.onTransmuxerLog; 52458 var fmp4Tracks = segment.map && segment.map.tracks || {}; 52459 var isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them. 52460 // One reason for this is that in the case of full segments, we want to trust start 52461 // times from the probe, rather than the transmuxer. 52462 52463 var audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start'); 52464 var audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end'); 52465 var videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start'); 52466 var videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end'); 52467 52468 var finish = function finish() { 52469 return transmux({ 52470 bytes: bytes, 52471 transmuxer: segment.transmuxer, 52472 audioAppendStart: segment.audioAppendStart, 52473 gopsToAlignWith: segment.gopsToAlignWith, 52474 remux: isMuxed, 52475 onData: function onData(result) { 52476 result.type = result.type === 'combined' ? 'video' : result.type; 52477 dataFn(segment, result); 52478 }, 52479 onTrackInfo: function onTrackInfo(trackInfo) { 52480 if (trackInfoFn) { 52481 if (isMuxed) { 52482 trackInfo.isMuxed = true; 52483 } 52484 52485 trackInfoFn(segment, trackInfo); 52486 } 52487 }, 52488 onAudioTimingInfo: function onAudioTimingInfo(audioTimingInfo) { 52489 // we only want the first start value we encounter 52490 if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') { 52491 audioStartFn(audioTimingInfo.start); 52492 audioStartFn = null; 52493 } // we want to continually update the end time 52494 52495 52496 if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') { 52497 audioEndFn(audioTimingInfo.end); 52498 } 52499 }, 52500 onVideoTimingInfo: function onVideoTimingInfo(videoTimingInfo) { 52501 // we only want the first start value we encounter 52502 if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') { 52503 videoStartFn(videoTimingInfo.start); 52504 videoStartFn = null; 52505 } // we want to continually update the end time 52506 52507 52508 if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') { 52509 videoEndFn(videoTimingInfo.end); 52510 } 52511 }, 52512 onVideoSegmentTimingInfo: function onVideoSegmentTimingInfo(videoSegmentTimingInfo) { 52513 videoSegmentTimingInfoFn(videoSegmentTimingInfo); 52514 }, 52515 onAudioSegmentTimingInfo: function onAudioSegmentTimingInfo(audioSegmentTimingInfo) { 52516 audioSegmentTimingInfoFn(audioSegmentTimingInfo); 52517 }, 52518 onId3: function onId3(id3Frames, dispatchType) { 52519 id3Fn(segment, id3Frames, dispatchType); 52520 }, 52521 onCaptions: function onCaptions(captions) { 52522 captionsFn(segment, [captions]); 52523 }, 52524 isEndOfTimeline: isEndOfTimeline, 52525 onEndedTimeline: function onEndedTimeline() { 52526 endedTimelineFn(); 52527 }, 52528 onTransmuxerLog: onTransmuxerLog, 52529 onDone: function onDone(result) { 52530 if (!doneFn) { 52531 return; 52532 } 52533 52534 result.type = result.type === 'combined' ? 'video' : result.type; 52535 doneFn(null, segment, result); 52536 } 52537 }); 52538 }; // In the transmuxer, we don't yet have the ability to extract a "proper" start time. 52539 // Meaning cached frame data may corrupt our notion of where this segment 52540 // really starts. To get around this, probe for the info needed. 52541 52542 52543 workerCallback({ 52544 action: 'probeTs', 52545 transmuxer: segment.transmuxer, 52546 data: bytes, 52547 baseStartTime: segment.baseStartTime, 52548 callback: function callback(data) { 52549 segment.bytes = bytes = data.data; 52550 var probeResult = data.result; 52551 52552 if (probeResult) { 52553 trackInfoFn(segment, { 52554 hasAudio: probeResult.hasAudio, 52555 hasVideo: probeResult.hasVideo, 52556 isMuxed: isMuxed 52557 }); 52558 trackInfoFn = null; 52559 52560 if (probeResult.hasAudio && !isMuxed) { 52561 audioStartFn(probeResult.audioStart); 52562 } 52563 52564 if (probeResult.hasVideo) { 52565 videoStartFn(probeResult.videoStart); 52566 } 52567 52568 audioStartFn = null; 52569 videoStartFn = null; 52570 } 52571 52572 finish(); 52573 } 52574 });
52575 }; 52576 52577 var handleSegmentBytes = function handleSegmentBytes(_ref5) { 52578 var segment = _ref5.segment, 52579 bytes = _ref5.bytes, 52580 trackInfoFn = _ref5.trackInfoFn, 52581 timingInfoFn = _ref5.timingInfoFn, 52582 videoSegmentTimingInfoFn = _ref5.videoSegmentTimingInfoFn, 52583 audioSegmentTimingInfoFn = _ref5.audioSegmentTimingInfoFn, 52584 id3Fn = _ref5.id3Fn, 52585 captionsFn = _ref5.captionsFn, 52586 isEndOfTimeline = _ref5.isEndOfTimeline, 52587 endedTimelineFn = _ref5.endedTimelineFn, 52588 dataFn = _ref5.dataFn, 52589 doneFn = _ref5.doneFn, 52590 onTransmuxerLog = _ref5.onTransmuxerLog; 52591 var bytesAsUint8Array = new Uint8Array(bytes); // TODO: 52592 // We should have a handler that fetches the number of bytes required 52593 // to check if something is fmp4. This will allow us to save bandwidth 52594 // because we can only blacklist a playlist and abort requests 52595 // by codec after trackinfo triggers. 52596 52597 if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) { 52598 segment.isFmp4 = true; 52599 var tracks = segment.map.tracks; 52600 var trackInfo = { 52601 isFmp4: true, 52602 hasVideo: !!tracks.video, 52603 hasAudio: !!tracks.audio 52604 }; // if we have a audio track, with a codec that is not set to 52605 // encrypted audio 52606 52607 if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') { 52608 trackInfo.audioCodec = tracks.audio.codec; 52609 } // if we have a video track, with a codec that is not set to 52610 // encrypted video 52611 52612 52613 if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') { 52614 trackInfo.videoCodec = tracks.video.codec; 52615 } 52616 52617 if (tracks.video && tracks.audio) { 52618 trackInfo.isMuxed = true; 52619 } // since we don't support appending fmp4 data on progress, we know we have the full 52620 // segment here 52621 52622 52623 trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start 52624 // time. The end time can be roughly calculated by the receiver using the duration. 52625 // 52626 // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as 52627 // that is the true start of the segment (where the playback engine should begin 52628 // decoding). 52629 52630 var finishLoading = function finishLoading(captions) { 52631 // if the track still has audio at this point it is only possible 52632 // for it to be audio only. See `tracks.video && tracks.audio` if statement 52633 // above. 52634 // we make sure to use segment.bytes here as that 52635 dataFn(segment, { 52636 data: bytesAsUint8Array, 52637 type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video' 52638 }); 52639 52640 if (captions && captions.length) { 52641 captionsFn(segment, captions); 52642 } 52643 52644 doneFn(null, segment, {}); 52645 }; 52646 52647 workerCallback({ 52648 action: 'probeMp4StartTime', 52649 timescales: segment.map.timescales, 52650 data: bytesAsUint8Array, 52651 transmuxer: segment.transmuxer, 52652 callback: function callback(_ref6) { 52653 var data = _ref6.data, 52654 startTime = _ref6.startTime; // transfer bytes back to us 52655 52656 bytes = data.buffer; 52657 segment.bytes = bytesAsUint8Array = data; 52658 52659 if (trackInfo.hasAudio && !trackInfo.isMuxed) { 52660 timingInfoFn(segment, 'audio', 'start', startTime); 52661 } 52662 52663 if (trackInfo.hasVideo) { 52664 timingInfoFn(segment, 'video', 'start', startTime); 52665 } // Run through the CaptionParser in case there are captions. 52666 // Initialize CaptionParser if it hasn't been yet 52667 52668 52669 if (!tracks.video || !data.byteLength || !segment.transmuxer) { 52670 finishLoading(); 52671 return; 52672 } 52673 52674 workerCallback({ 52675 action: 'pushMp4Captions', 52676 endAction: 'mp4Captions', 52677 transmuxer: segment.transmuxer, 52678 data: bytesAsUint8Array, 52679 timescales: segment.map.timescales, 52680 trackIds: [tracks.video.id], 52681 callback: function callback(message) { 52682 // transfer bytes back to us 52683 bytes = message.data.buffer; 52684 segment.bytes = bytesAsUint8Array = message.data;
52685 message.logs.forEach(function (log) { 52686 onTransmuxerLog(videojs.mergeOptions(log, { 52687 stream: 'mp4CaptionParser' 52688 })); 52689 }); 52690 finishLoading(message.captions); 52691 } 52692 }); 52693 } 52694 }); 52695 return; 52696 } // VTT or other segments that don't need processing 52697 52698 52699 if (!segment.transmuxer) { 52700 doneFn(null, segment, {}); 52701 return; 52702 } 52703 52704 if (typeof segment.container === 'undefined') { 52705 segment.container = detectContainerForBytes(bytesAsUint8Array); 52706 } 52707 52708 if (segment.container !== 'ts' && segment.container !== 'aac') { 52709 trackInfoFn(segment, { 52710 hasAudio: false, 52711 hasVideo: false 52712 }); 52713 doneFn(null, segment, {}); 52714 return; 52715 } // ts or aac 52716 52717 52718 transmuxAndNotify({ 52719 segment: segment, 52720 bytes: bytes, 52721 trackInfoFn: trackInfoFn, 52722 timingInfoFn: timingInfoFn, 52723 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 52724 audioSegmentTimingInfoFn: audioSegmentTimingInfoFn, 52725 id3Fn: id3Fn, 52726 captionsFn: captionsFn, 52727 isEndOfTimeline: isEndOfTimeline, 52728 endedTimelineFn: endedTimelineFn, 52729 dataFn: dataFn, 52730 doneFn: doneFn, 52731 onTransmuxerLog: onTransmuxerLog 52732 }); 52733 }; 52734 52735 var decrypt = function decrypt(_ref7, callback) { 52736 var id = _ref7.id, 52737 key = _ref7.key, 52738 encryptedBytes = _ref7.encryptedBytes, 52739 decryptionWorker = _ref7.decryptionWorker; 52740 52741 var decryptionHandler = function decryptionHandler(event) { 52742 if (event.data.source === id) { 52743 decryptionWorker.removeEventListener('message', decryptionHandler); 52744 var decrypted = event.data.decrypted; 52745 callback(new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength)); 52746 } 52747 }; 52748 52749 decryptionWorker.addEventListener('message', decryptionHandler); 52750 var keyBytes; 52751 52752 if (key.bytes.slice) { 52753 keyBytes = key.bytes.slice(); 52754 } else { 52755 keyBytes = new Uint32Array(Array.prototype.slice.call(key.bytes)); 52756 } // incrementally decrypt the bytes 52757 52758 52759 decryptionWorker.postMessage(createTransferableMessage({ 52760 source: id, 52761 encrypted: encryptedBytes, 52762 key: keyBytes, 52763 iv: key.iv 52764 }), [encryptedBytes.buffer, keyBytes.buffer]); 52765 }; 52766 /** 52767 * Decrypt the segment via the decryption web worker 52768 * 52769 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption 52770 * routines 52771 * @param {Object} segment - a simplified copy of the segmentInfo object 52772 * from SegmentLoader 52773 * @param {Function} trackInfoFn - a callback that receives track info 52774 * @param {Function} timingInfoFn - a callback that receives timing info 52775 * @param {Function} videoSegmentTimingInfoFn 52776 * a callback that receives video timing info based on media times and 52777 * any adjustments made by the transmuxer 52778 * @param {Function} audioSegmentTimingInfoFn 52779 * a callback that receives audio timing info based on media times and 52780 * any adjustments made by the transmuxer 52781 * @param {boolean} isEndOfTimeline 52782 * true if this segment represents the last segment in a timeline 52783 * @param {Function} endedTimelineFn 52784 * a callback made when a timeline is ended, will only be called if 52785 * isEndOfTimeline is true 52786 * @param {Function} dataFn - a callback that is executed when segment bytes are available 52787 * and ready to use 52788 * @param {Function} doneFn - a callback that is executed after decryption has completed 52789 */ 52790 52791 52792 var decryptSegment = function decryptSegment(_ref8) { 52793 var decryptionWorker = _ref8.decryptionWorker, 52794 segment = _ref8.segment, 52795 trackInfoFn = _ref8.trackInfoFn, 52796 timingInfoFn = _ref8.timingInfoFn, 52797 videoSegmentTimingInfoFn = _ref8.videoSegmentTimingInfoFn, 52798 audioSegmentTimingInfoFn = _ref8.audioSegmentTimingInfoFn, 52799 id3Fn = _ref8.id3Fn, 52800 captionsFn = _ref8.captionsFn, 52801 isEndOfTimeline = _ref8.isEndOfTimeline,
52802 endedTimelineFn = _ref8.endedTimelineFn, 52803 dataFn = _ref8.dataFn, 52804 doneFn = _ref8.doneFn, 52805 onTransmuxerLog = _ref8.onTransmuxerLog; 52806 decrypt({ 52807 id: segment.requestId, 52808 key: segment.key, 52809 encryptedBytes: segment.encryptedBytes, 52810 decryptionWorker: decryptionWorker 52811 }, function (decryptedBytes) { 52812 segment.bytes = decryptedBytes; 52813 handleSegmentBytes({ 52814 segment: segment, 52815 bytes: segment.bytes, 52816 trackInfoFn: trackInfoFn, 52817 timingInfoFn: timingInfoFn, 52818 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 52819 audioSegmentTimingInfoFn: audioSegmentTimingInfoFn, 52820 id3Fn: id3Fn, 52821 captionsFn: captionsFn, 52822 isEndOfTimeline: isEndOfTimeline, 52823 endedTimelineFn: endedTimelineFn, 52824 dataFn: dataFn, 52825 doneFn: doneFn, 52826 onTransmuxerLog: onTransmuxerLog 52827 }); 52828 }); 52829 }; 52830 /** 52831 * This function waits for all XHRs to finish (with either success or failure) 52832 * before continueing processing via it's callback. The function gathers errors 52833 * from each request into a single errors array so that the error status for 52834 * each request can be examined later. 52835 * 52836 * @param {Object} activeXhrs - an object that tracks all XHR requests 52837 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption 52838 * routines 52839 * @param {Function} trackInfoFn - a callback that receives track info 52840 * @param {Function} timingInfoFn - a callback that receives timing info 52841 * @param {Function} videoSegmentTimingInfoFn 52842 * a callback that receives video timing info based on media times and 52843 * any adjustments made by the transmuxer 52844 * @param {Function} audioSegmentTimingInfoFn 52845 * a callback that receives audio timing info based on media times and 52846 * any adjustments made by the transmuxer 52847 * @param {Function} id3Fn - a callback that receives ID3 metadata 52848 * @param {Function} captionsFn - a callback that receives captions 52849 * @param {boolean} isEndOfTimeline 52850 * true if this segment represents the last segment in a timeline 52851 * @param {Function} endedTimelineFn 52852 * a callback made when a timeline is ended, will only be called if 52853 * isEndOfTimeline is true 52854 * @param {Function} dataFn - a callback that is executed when segment bytes are available 52855 * and ready to use 52856 * @param {Function} doneFn - a callback that is executed after all resources have been 52857 * downloaded and any decryption completed 52858 */ 52859 52860 52861 var waitForCompletion = function waitForCompletion(_ref9) { 52862 var activeXhrs = _ref9.activeXhrs, 52863 decryptionWorker = _ref9.decryptionWorker, 52864 trackInfoFn = _ref9.trackInfoFn, 52865 timingInfoFn = _ref9.timingInfoFn, 52866 videoSegmentTimingInfoFn = _ref9.videoSegmentTimingInfoFn, 52867 audioSegmentTimingInfoFn = _ref9.audioSegmentTimingInfoFn, 52868 id3Fn = _ref9.id3Fn, 52869 captionsFn = _ref9.captionsFn, 52870 isEndOfTimeline = _ref9.isEndOfTimeline, 52871 endedTimelineFn = _ref9.endedTimelineFn, 52872 dataFn = _ref9.dataFn, 52873 doneFn = _ref9.doneFn, 52874 onTransmuxerLog = _ref9.onTransmuxerLog; 52875 var count = 0; 52876 var didError = false; 52877 return function (error, segment) { 52878 if (didError) { 52879 return; 52880 } 52881 52882 if (error) { 52883 didError = true; // If there are errors, we have to abort any outstanding requests 52884 52885 abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we 52886 // handle the aborted events from those requests, there are some cases where we may 52887 // never get an aborted event. For instance, if the network connection is lost and 52888 // there were two requests, the first may have triggered an error immediately, while 52889 // the second request remains unsent. In that case, the aborted algorithm will not 52890 // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method 52891 // 52892 // We also can't rely on the ready state of the XHR, since the request that
52893 // triggered the connection error may also show as a ready state of 0 (unsent). 52894 // Therefore, we have to finish this group of requests immediately after the first 52895 // seen error. 52896 52897 return doneFn(error, segment); 52898 } 52899 52900 count += 1; 52901 52902 if (count === activeXhrs.length) { 52903 var segmentFinish = function segmentFinish() { 52904 if (segment.encryptedBytes) { 52905 return decryptSegment({ 52906 decryptionWorker: decryptionWorker, 52907 segment: segment, 52908 trackInfoFn: trackInfoFn, 52909 timingInfoFn: timingInfoFn, 52910 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 52911 audioSegmentTimingInfoFn: audioSegmentTimingInfoFn, 52912 id3Fn: id3Fn, 52913 captionsFn: captionsFn, 52914 isEndOfTimeline: isEndOfTimeline, 52915 endedTimelineFn: endedTimelineFn, 52916 dataFn: dataFn, 52917 doneFn: doneFn, 52918 onTransmuxerLog: onTransmuxerLog 52919 }); 52920 } // Otherwise, everything is ready just continue 52921 52922 52923 handleSegmentBytes({ 52924 segment: segment, 52925 bytes: segment.bytes, 52926 trackInfoFn: trackInfoFn, 52927 timingInfoFn: timingInfoFn, 52928 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 52929 audioSegmentTimingInfoFn: audioSegmentTimingInfoFn, 52930 id3Fn: id3Fn, 52931 captionsFn: captionsFn, 52932 isEndOfTimeline: isEndOfTimeline, 52933 endedTimelineFn: endedTimelineFn, 52934 dataFn: dataFn, 52935 doneFn: doneFn, 52936 onTransmuxerLog: onTransmuxerLog 52937 }); 52938 }; // Keep track of when *all* of the requests have completed 52939 52940 52941 segment.endOfAllRequests = Date.now(); 52942 52943 if (segment.map && segment.map.encryptedBytes && !segment.map.bytes) { 52944 return decrypt({ 52945 decryptionWorker: decryptionWorker, 52946 // add -init to the "id" to differentiate between segment 52947 // and init segment decryption, just in case they happen 52948 // at the same time at some point in the future. 52949 id: segment.requestId + '-init', 52950 encryptedBytes: segment.map.encryptedBytes, 52951 key: segment.map.key 52952 }, function (decryptedBytes) { 52953 segment.map.bytes = decryptedBytes; 52954 parseInitSegment(segment, function (parseError) { 52955 if (parseError) { 52956 abortAll(activeXhrs); 52957 return doneFn(parseError, segment); 52958 } 52959 52960 segmentFinish(); 52961 }); 52962 }); 52963 } 52964 52965 segmentFinish(); 52966 } 52967 }; 52968 }; 52969 /** 52970 * Calls the abort callback if any request within the batch was aborted. Will only call 52971 * the callback once per batch of requests, even if multiple were aborted. 52972 * 52973 * @param {Object} loadendState - state to check to see if the abort function was called 52974 * @param {Function} abortFn - callback to call for abort 52975 */ 52976 52977 52978 var handleLoadEnd = function handleLoadEnd(_ref10) { 52979 var loadendState = _ref10.loadendState, 52980 abortFn = _ref10.abortFn; 52981 return function (event) { 52982 var request = event.target; 52983 52984 if (request.aborted && abortFn && !loadendState.calledAbortFn) { 52985 abortFn(); 52986 loadendState.calledAbortFn = true; 52987 } 52988 }; 52989 }; 52990 /** 52991 * Simple progress event callback handler that gathers some stats before 52992 * executing a provided callback with the `segment` object 52993 * 52994 * @param {Object} segment - a simplified copy of the segmentInfo object 52995 * from SegmentLoader 52996 * @param {Function} progressFn - a callback that is executed each time a progress event 52997 * is received 52998 * @param {Function} trackInfoFn - a callback that receives track info 52999 * @param {Function} timingInfoFn - a callback that receives timing info 53000 * @param {Function} videoSegmentTimingInfoFn 53001 * a callback that receives video timing info based on media times and 53002 * any adjustments made by the transmuxer 53003 * @param {Function} audioSegmentTimingInfoFn 53004 * a callback that receives audio timing info based on media times and 53005 * any adjustments made by the transmuxer 53006 * @param {boolean} isEndOfTimeline 53007 * true if this segment represents the last segment in a timeline 53008 * @param {Function} endedTimelineFn 53009 * a callback made when a timeline is ended, will only be called if 53010 * isEndOfTimeline is true 53011 * @param {Function} dataFn - a callback that is executed when segment bytes are available 53012 * and ready to use 53013 * @param {Event} event - the progress event object from XMLHttpRequest 53014 */ 53015 53016 53017 var handleProgress = function handleProgress(_ref11) { 53018 var segment = _ref11.segment, 53019 progressFn = _ref11.progressFn; 53020 _ref11.trackInfoFn; 53021 _ref11.timingInfoFn; 53022 _ref11.videoSegmentTimingInfoFn; 53023 _ref11.audioSegmentTimingInfoFn; 53024 _ref11.id3Fn; 53025 _ref11.captionsFn; 53026 _ref11.isEndOfTimeline; 53027 _ref11.endedTimelineFn; 53028 _ref11.dataFn; 53029 return function (event) { 53030 var request = event.target; 53031 53032 if (request.aborted) { 53033 return; 53034 } 53035 53036 segment.stats = videojs.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data 53037 53038 if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) { 53039 segment.stats.firstBytesReceivedAt = Date.now(); 53040 } 53041 53042 return progressFn(event, segment); 53043 }; 53044 }; 53045 /** 53046 * Load all resources and does any processing necessary for a media-segment 53047 * 53048 * Features: 53049 * decrypts the media-segment if it has a key uri and an iv 53050 * aborts *all* requests if *any* one request fails 53051 * 53052 * The segment object, at minimum, has the following format: 53053 * { 53054 * resolvedUri: String, 53055 * [transmuxer]: Object, 53056 * [byterange]: { 53057 * offset: Number, 53058 * length: Number 53059 * }, 53060 * [key]: { 53061 * resolvedUri: String 53062 * [byterange]: { 53063 * offset: Number, 53064 * length: Number 53065 * }, 53066 * iv: { 53067 * bytes: Uint32Array 53068 * } 53069 * }, 53070 * [map]: { 53071 * resolvedUri: String, 53072 * [byterange]: { 53073 * offset: Number, 53074 * length: Number 53075 * }, 53076 * [bytes]: Uint8Array 53077 * } 53078 * } 53079 * ...where [name] denotes optional properties 53080 * 53081 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js 53082 * @param {Object} xhrOptions - the base options to provide to all xhr requests 53083 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 53084 * decryption routines 53085 * @param {Object} segment - a simplified copy of the segmentInfo object 53086 * from SegmentLoader 53087 * @param {Function} abortFn - a callback called (only once) if any piece of a request was 53088 * aborted 53089 * @param {Function} progressFn - a callback that receives progress events from the main 53090 * segment's xhr request 53091 * @param {Function} trackInfoFn - a callback that receives track info 53092 * @param {Function} timingInfoFn - a callback that receives timing info 53093 * @param {Function} videoSegmentTimingInfoFn 53094 * a callback that receives video timing info based on media times and 53095 * any adjustments made by the transmuxer 53096 * @param {Function} audioSegmentTimingInfoFn 53097 * a callback that receives audio timing info based on media times and 53098 * any adjustments made by the transmuxer 53099 * @param {Function} id3Fn - a callback that receives ID3 metadata 53100 * @param {Function} captionsFn - a callback that receives captions 53101 * @param {boolean} isEndOfTimeline 53102 * true if this segment represents the last segment in a timeline 53103 * @param {Function} endedTimelineFn 53104 * a callback made when a timeline is ended, will only be called if 53105 * isEndOfTimeline is true 53106 * @param {Function} dataFn - a callback that receives data from the main segment's xhr 53107 * request, transmuxed if needed 53108 * @param {Function} doneFn - a callback that is executed only once all requests have 53109 * succeeded or failed 53110 * @return {Function} a function that, when invoked, immediately aborts all 53111 * outstanding requests 53112 */ 53113 53114 53115 var mediaSegmentRequest = function mediaSegmentRequest(_ref12) { 53116 var xhr = _ref12.xhr, 53117 xhrOptions = _ref12.xhrOptions, 53118 decryptionWorker = _ref12.decryptionWorker, 53119 segment = _ref12.segment, 53120 abortFn = _ref12.abortFn, 53121 progressFn = _ref12.progressFn, 53122 trackInfoFn = _ref12.trackInfoFn, 53123 timingInfoFn = _ref12.timingInfoFn, 53124 videoSegmentTimingInfoFn = _ref12.videoSegmentTimingInfoFn, 53125 audioSegmentTimingInfoFn = _ref12.audioSegmentTimingInfoFn,
53126 id3Fn = _ref12.id3Fn, 53127 captionsFn = _ref12.captionsFn, 53128 isEndOfTimeline = _ref12.isEndOfTimeline, 53129 endedTimelineFn = _ref12.endedTimelineFn, 53130 dataFn = _ref12.dataFn, 53131 doneFn = _ref12.doneFn, 53132 onTransmuxerLog = _ref12.onTransmuxerLog; 53133 var activeXhrs = []; 53134 var finishProcessingFn = waitForCompletion({ 53135 activeXhrs: activeXhrs, 53136 decryptionWorker: decryptionWorker, 53137 trackInfoFn: trackInfoFn, 53138 timingInfoFn: timingInfoFn, 53139 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 53140 audioSegmentTimingInfoFn: audioSegmentTimingInfoFn, 53141 id3Fn: id3Fn, 53142 captionsFn: captionsFn, 53143 isEndOfTimeline: isEndOfTimeline, 53144 endedTimelineFn: endedTimelineFn, 53145 dataFn: dataFn, 53146 doneFn: doneFn, 53147 onTransmuxerLog: onTransmuxerLog 53148 }); // optionally, request the decryption key 53149 53150 if (segment.key && !segment.key.bytes) { 53151 var objects = [segment.key]; 53152 53153 if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) { 53154 objects.push(segment.map.key); 53155 } 53156 53157 var keyRequestOptions = videojs.mergeOptions(xhrOptions, { 53158 uri: segment.key.resolvedUri, 53159 responseType: 'arraybuffer' 53160 }); 53161 var keyRequestCallback = handleKeyResponse(segment, objects, finishProcessingFn); 53162 var keyXhr = xhr(keyRequestOptions, keyRequestCallback); 53163 activeXhrs.push(keyXhr); 53164 } // optionally, request the associated media init segment 53165 53166 53167 if (segment.map && !segment.map.bytes) { 53168 var differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri); 53169 53170 if (differentMapKey) { 53171 var mapKeyRequestOptions = videojs.mergeOptions(xhrOptions, { 53172 uri: segment.map.key.resolvedUri, 53173 responseType: 'arraybuffer' 53174 }); 53175 var mapKeyRequestCallback = handleKeyResponse(segment, [segment.map.key], finishProcessingFn); 53176 var mapKeyXhr = xhr(mapKeyRequestOptions, mapKeyRequestCallback); 53177 activeXhrs.push(mapKeyXhr); 53178 } 53179 53180 var initSegmentOptions = videojs.mergeOptions(xhrOptions, { 53181 uri: segment.map.resolvedUri, 53182 responseType: 'arraybuffer', 53183 headers: segmentXhrHeaders(segment.map) 53184 }); 53185 var initSegmentRequestCallback = handleInitSegmentResponse({ 53186 segment: segment, 53187 finishProcessingFn: finishProcessingFn 53188 }); 53189 var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); 53190 activeXhrs.push(initSegmentXhr); 53191 } 53192 53193 var segmentRequestOptions = videojs.mergeOptions(xhrOptions, { 53194 uri: segment.part && segment.part.resolvedUri || segment.resolvedUri, 53195 responseType: 'arraybuffer', 53196 headers: segmentXhrHeaders(segment) 53197 }); 53198 var segmentRequestCallback = handleSegmentResponse({ 53199 segment: segment, 53200 finishProcessingFn: finishProcessingFn, 53201 responseType: segmentRequestOptions.responseType 53202 }); 53203 var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); 53204 segmentXhr.addEventListener('progress', handleProgress({ 53205 segment: segment, 53206 progressFn: progressFn, 53207 trackInfoFn: trackInfoFn, 53208 timingInfoFn: timingInfoFn, 53209 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 53210 audioSegmentTimingInfoFn: audioSegmentTimingInfoFn, 53211 id3Fn: id3Fn, 53212 captionsFn: captionsFn, 53213 isEndOfTimeline: isEndOfTimeline, 53214 endedTimelineFn: endedTimelineFn, 53215 dataFn: dataFn 53216 })); 53217 activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks 53218 // multiple times, provide a shared state object 53219 53220 var loadendState = {}; 53221 activeXhrs.forEach(function (activeXhr) { 53222 activeXhr.addEventListener('loadend', handleLoadEnd({ 53223 loadendState: loadendState, 53224 abortFn: abortFn 53225 })); 53226 }); 53227 return function () { 53228 return abortAll(activeXhrs); 53229 }; 53230 }; 53231 /** 53232 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to 53233 * codec strings, or translating codec strings into objects that can be examined. 53234 */ 53235 53236 53237 var logFn$1 = logger('CodecUtils'); 53238 /** 53239 * Returns a set of codec strings parsed from the playlist or the default 53240 * codec strings if no codecs were specified in the playlist 53241 * 53242 * @param {Playlist} media the current media playlist 53243 * @return {Object} an object with the video and audio codecs 53244 */ 53245 53246 var getCodecs = function getCodecs(media) { 53247 // if the codecs were explicitly specified, use them instead of the 53248 // defaults 53249 var mediaAttributes = media.attributes || {}; 53250 53251 if (mediaAttributes.CODECS) { 53252 return parseCodecs(mediaAttributes.CODECS); 53253 } 53254 }; 53255 53256 var isMaat = function isMaat(master, media) { 53257 var mediaAttributes = media.attributes || {}; 53258 return master && master.mediaGroups && master.mediaGroups.AUDIO && mediaAttributes.AUDIO && master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; 53259 }; 53260 53261 var isMuxed = function isMuxed(master, media) { 53262 if (!isMaat(master, media)) { 53263 return true; 53264 } 53265 53266 var mediaAttributes = media.attributes || {}; 53267 var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; 53268 53269 for (var groupId in audioGroup) { 53270 // If an audio group has a URI (the case for HLS, as HLS will use external playlists), 53271 // or there are listed playlists (the case for DASH, as the manifest will have already 53272 // provided all of the details necessary to generate the audio playlist, as opposed to 53273 // HLS' externally requested playlists), then the content is demuxed. 53274 if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) { 53275 return true; 53276 } 53277 } 53278 53279 return false; 53280 }; 53281 53282 var unwrapCodecList = function unwrapCodecList(codecList) { 53283 var codecs = {}; 53284 codecList.forEach(function (_ref) { 53285 var mediaType = _ref.mediaType, 53286 type = _ref.type, 53287 details = _ref.details; 53288 codecs[mediaType] = codecs[mediaType] || []; 53289 codecs[mediaType].push(translateLegacyCodec("" + type + details)); 53290 });
53291 Object.keys(codecs).forEach(function (mediaType) { 53292 if (codecs[mediaType].length > 1) { 53293 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."); 53294 codecs[mediaType] = null; 53295 return; 53296 } 53297 53298 codecs[mediaType] = codecs[mediaType][0]; 53299 }); 53300 return codecs; 53301 }; 53302 53303 var codecCount = function codecCount(codecObj) { 53304 var count = 0; 53305 53306 if (codecObj.audio) { 53307 count++; 53308 } 53309 53310 if (codecObj.video) { 53311 count++; 53312 } 53313 53314 return count; 53315 }; 53316 /** 53317 * Calculates the codec strings for a working configuration of 53318 * SourceBuffers to play variant streams in a master playlist. If 53319 * there is no possible working configuration, an empty object will be 53320 * returned. 53321 * 53322 * @param master {Object} the m3u8 object for the master playlist 53323 * @param media {Object} the m3u8 object for the variant playlist 53324 * @return {Object} the codec strings. 53325 * 53326 * @private 53327 */ 53328 53329 53330 var codecsForPlaylist = function codecsForPlaylist(master, media) { 53331 var mediaAttributes = media.attributes || {}; 53332 var codecInfo = unwrapCodecList(getCodecs(media) || []); // HLS with multiple-audio tracks must always get an audio codec. 53333 // Put another way, there is no way to have a video-only multiple-audio HLS! 53334 53335 if (isMaat(master, media) && !codecInfo.audio) { 53336 if (!isMuxed(master, media)) { 53337 // It is possible for codecs to be specified on the audio media group playlist but 53338 // not on the rendition playlist. This is mostly the case for DASH, where audio and 53339 // video are always separate (and separately specified). 53340 var defaultCodecs = unwrapCodecList(codecsFromDefault(master, mediaAttributes.AUDIO) || []); 53341 53342 if (defaultCodecs.audio) { 53343 codecInfo.audio = defaultCodecs.audio; 53344 } 53345 } 53346 } 53347 53348 return codecInfo; 53349 }; 53350 53351 var logFn = logger('PlaylistSelector'); 53352 53353 var representationToString = function representationToString(representation) { 53354 if (!representation || !representation.playlist) { 53355 return; 53356 } 53357 53358 var playlist = representation.playlist; 53359 return JSON.stringify({ 53360 id: playlist.id, 53361 bandwidth: representation.bandwidth, 53362 width: representation.width, 53363 height: representation.height, 53364 codecs: playlist.attributes && playlist.attributes.CODECS || '' 53365 }); 53366 }; // Utilities 53367 53368 /** 53369 * Returns the CSS value for the specified property on an element 53370 * using `getComputedStyle`. Firefox has a long-standing issue where 53371 * getComputedStyle() may return null when running in an iframe with 53372 * `display: none`. 53373 * 53374 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 53375 * @param {HTMLElement} el the htmlelement to work on 53376 * @param {string} the proprety to get the style for 53377 */ 53378 53379 53380 var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 53381 if (!el) { 53382 return ''; 53383 } 53384 53385 var result = window_1.getComputedStyle(el); 53386 53387 if (!result) { 53388 return ''; 53389 } 53390 53391 return result[property]; 53392 }; 53393 /** 53394 * Resuable stable sort function 53395 * 53396 * @param {Playlists} array 53397 * @param {Function} sortFn Different comparators 53398 * @function stableSort 53399 */ 53400 53401 53402 var stableSort = function stableSort(array, sortFn) { 53403 var newArray = array.slice(); 53404 array.sort(function (left, right) { 53405 var cmp = sortFn(left, right); 53406 53407 if (cmp === 0) { 53408 return newArray.indexOf(left) - newArray.indexOf(right); 53409 } 53410 53411 return cmp; 53412 }); 53413 }; 53414 /** 53415 * A comparator function to sort two playlist object by bandwidth. 53416 * 53417 * @param {Object} left a media playlist object 53418 * @param {Object} right a media playlist object 53419 * @return {number} Greater than zero if the bandwidth attribute of 53420 * left is greater than the corresponding attribute of right. Less 53421 * than zero if the bandwidth of right is greater than left and 53422 * exactly zero if the two are equal. 53423 */ 53424 53425 53426 var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) { 53427 var leftBandwidth; 53428 var rightBandwidth; 53429 53430 if (left.attributes.BANDWIDTH) { 53431 leftBandwidth = left.attributes.BANDWIDTH; 53432 } 53433 53434 leftBandwidth = leftBandwidth || window_1.Number.MAX_VALUE; 53435 53436 if (right.attributes.BANDWIDTH) { 53437 rightBandwidth = right.attributes.BANDWIDTH; 53438 } 53439 53440 rightBandwidth = rightBandwidth || window_1.Number.MAX_VALUE; 53441 return leftBandwidth - rightBandwidth; 53442 }; 53443 /** 53444 * A comparator function to sort two playlist object by resolution (width). 53445 * 53446 * @param {Object} left a media playlist object 53447 * @param {Object} right a media playlist object 53448 * @return {number} Greater than zero if the resolution.width attribute of 53449 * left is greater than the corresponding attribute of right. Less 53450 * than zero if the resolution.width of right is greater than left and 53451 * exactly zero if the two are equal. 53452 */ 53453 53454 53455 var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
53456 var leftWidth; 53457 var rightWidth; 53458 53459 if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 53460 leftWidth = left.attributes.RESOLUTION.width; 53461 } 53462 53463 leftWidth = leftWidth || window_1.Number.MAX_VALUE; 53464 53465 if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 53466 rightWidth = right.attributes.RESOLUTION.width; 53467 } 53468 53469 rightWidth = rightWidth || window_1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 53470 // have the same media dimensions/ resolution 53471 53472 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 53473 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 53474 } 53475 53476 return leftWidth - rightWidth; 53477 }; 53478 /** 53479 * Chooses the appropriate media playlist based on bandwidth and player size 53480 * 53481 * @param {Object} master 53482 * Object representation of the master manifest 53483 * @param {number} playerBandwidth 53484 * Current calculated bandwidth of the player 53485 * @param {number} playerWidth 53486 * Current width of the player element (should account for the device pixel ratio) 53487 * @param {number} playerHeight 53488 * Current height of the player element (should account for the device pixel ratio) 53489 * @param {boolean} limitRenditionByPlayerDimensions 53490 * True if the player width and height should be used during the selection, false otherwise 53491 * @param {Object} masterPlaylistController 53492 * the current masterPlaylistController object 53493 * @return {Playlist} the highest bitrate playlist less than the 53494 * currently detected bandwidth, accounting for some amount of 53495 * bandwidth variance 53496 */ 53497 53498 53499 var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions, masterPlaylistController) { 53500 // If we end up getting called before `master` is available, exit early 53501 if (!master) { 53502 return; 53503 } 53504 53505 var options = { 53506 bandwidth: playerBandwidth, 53507 width: playerWidth,
53508 height: playerHeight, 53509 limitRenditionByPlayerDimensions: limitRenditionByPlayerDimensions 53510 }; 53511 var playlists = master.playlists; // if playlist is audio only, select between currently active audio group playlists. 53512 53513 if (Playlist.isAudioOnly(master)) { 53514 playlists = masterPlaylistController.getAudioTrackPlaylists_(); // add audioOnly to options so that we log audioOnly: true 53515 // at the buttom of this function for debugging. 53516 53517 options.audioOnly = true; 53518 } // convert the playlists to an intermediary representation to make comparisons easier 53519 53520 53521 var sortedPlaylistReps = playlists.map(function (playlist) { 53522 var bandwidth; 53523 var width = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width; 53524 var height = playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height; 53525 bandwidth = playlist.attributes && playlist.attributes.BANDWIDTH; 53526 bandwidth = bandwidth || window_1.Number.MAX_VALUE; 53527 return { 53528 bandwidth: bandwidth, 53529 width: width, 53530 height: height, 53531 playlist: playlist 53532 }; 53533 }); 53534 stableSort(sortedPlaylistReps, function (left, right) { 53535 return left.bandwidth - right.bandwidth; 53536 }); // filter out any playlists that have been excluded due to 53537 // incompatible configurations 53538 53539 sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) { 53540 return !Playlist.isIncompatible(rep.playlist); 53541 }); // filter out any playlists that have been disabled manually through the representations 53542 // api or blacklisted temporarily due to playback errors. 53543 53544 var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 53545 return Playlist.isEnabled(rep.playlist); 53546 }); 53547 53548 if (!enabledPlaylistReps.length) { 53549 // if there are no enabled playlists, then they have all been blacklisted or disabled 53550 // by the user through the representations api. In this case, ignore blacklisting and 53551 // fallback to what the user wants by using playlists the user has not disabled. 53552 enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 53553 return !Playlist.isDisabled(rep.playlist); 53554 }); 53555 } // filter out any variant that has greater effective bitrate 53556 // than the current estimated bandwidth 53557 53558 53559 var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) { 53560 return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth; 53561 }); 53562 var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth 53563 // and then taking the very first element 53564 53565 var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) { 53566 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 53567 })[0]; // if we're not going to limit renditions by player size, make an early decision. 53568 53569 if (limitRenditionByPlayerDimensions === false) { 53570 var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 53571 53572 if (_chosenRep && _chosenRep.playlist) { 53573 var type = 'sortedPlaylistReps'; 53574 53575 if (bandwidthBestRep) { 53576 type = 'bandwidthBestRep'; 53577 } 53578 53579 if (enabledPlaylistReps[0]) { 53580 type = 'enabledPlaylistReps'; 53581 } 53582 53583 logFn("choosing " + representationToString(_chosenRep) + " using " + type + " with options", options); 53584 return _chosenRep.playlist; 53585 } 53586 53587 logFn('could not choose a playlist with options', options); 53588 return null; 53589 } // filter out playlists without resolution information 53590 53591 53592 var haveResolution = bandwidthPlaylistReps.filter(function (rep) { 53593 return rep.width && rep.height; 53594 }); // sort variants by resolution 53595 53596 stableSort(haveResolution, function (left, right) { 53597 return left.width - right.width; 53598 }); // if we have the exact resolution as the player use it 53599 53600 var resolutionBestRepList = haveResolution.filter(function (rep) { 53601 return rep.width === playerWidth && rep.height === playerHe
53601ight; 53602 }); 53603 highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution 53604 53605 var resolutionBestRep = resolutionBestRepList.filter(function (rep) { 53606 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 53607 })[0]; 53608 var resolutionPlusOneList; 53609 var resolutionPlusOneSmallest; 53610 var resolutionPlusOneRep; // find the smallest variant that is larger than the player 53611 // if there is no match of exact resolution 53612 53613 if (!resolutionBestRep) { 53614 resolutionPlusOneList = haveResolution.filter(function (rep) { 53615 return rep.width > playerWidth || rep.height > playerHeight; 53616 }); // find all the variants have the same smallest resolution 53617 53618 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) { 53619 return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height; 53620 }); // ensure that we also pick the highest bandwidth variant that 53621 // is just-larger-than the video player 53622 53623 highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1]; 53624 resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) { 53625 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 53626 })[0]; 53627 } 53628 53629 var leastPixelDiffRep; // If this selector proves to be better than others, 53630 // resolutionPlusOneRep and resolutionBestRep and all 53631 // the code involving them should be removed. 53632 53633 if (masterPlaylistController.experimentalLeastPixelDiffSelector) { 53634 // find the variant that is closest to the player's pixel size 53635 var leastPixelDiffList = haveResolution.map(function (rep) { 53636 rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight); 53637 return rep; 53638 }); // get the highest bandwidth, closest resolution playlist 53639 53640 stableSort(leastPixelDiffList, function (left, right) { 53641 // sort by highest bandwidth if pixelDiff is the same 53642 if (left.pixelDiff === right.pixelDiff) { 53643 return right.bandwidth - left.bandwidth; 53644 } 53645 53646 return left.pixelDiff - right.pixelDiff; 53647 }); 53648 leastPixelDiffRep = leastPixelDiffList[0]; 53649 } // fallback chain of variants 53650 53651 53652 var chosenRep = leastPixelDiffRep || resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 53653 53654 if (chosenRep && chosenRep.playlist) { 53655 var _type = 'sortedPlaylistReps'; 53656 53657 if (leastPixelDiffRep) { 53658 _type = 'leastPixelDiffRep'; 53659 } else if (resolutionPlusOneRep) { 53660 _type = 'resolutionPlusOneRep'; 53661 } else if (resolutionBestRep) { 53662 _type = 'resolutionBestRep'; 53663 } else if (bandwidthBestRep) { 53664 _type = 'bandwidthBestRep'; 53665 } else if (enabledPlaylistReps[0]) { 53666 _type = 'enabledPlaylistReps'; 53667 } 53668 53669 logFn("choosing " + representationToString(chosenRep) + " using " + _type + " with options", options); 53670 return chosenRep.playlist; 53671 } 53672 53673 logFn('could not choose a playlist with options', options); 53674 return null; 53675 }; 53676 /** 53677 * Chooses the appropriate media playlist based on the most recent 53678 * bandwidth estimate and the player size. 53679 * 53680 * Expects to be called within the context of an instance of VhsHandler 53681 * 53682 * @return {Playlist} the highest bitrate playlist less than the 53683 * currently detected bandwidth, accounting for some amount of 53684 * bandwidth variance 53685 */ 53686 53687 53688 var lastBandwidthSelector = function lastBandwidthSelector() { 53689 var pixelRatio = this.useDevicePixelRatio ? window_1.devicePixelRatio || 1 : 1; 53690 return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.masterPlaylistController_); 53691 }; 53692 /** 53693 * Chooses the appropriate media playlist based on an 53694 * exponential-weighted moving average of the bandwidth after 53695 * filtering for player size. 53696 * 53697 * Expects to be called within the context of an instance of VhsHandler 53698 * 53699 * @param {number} decay - a number between 0 and 1. Higher values of 53700 * this parameter will cause previous bandwidth estimates to lose 53701 * significance more quickly. 53702 * @return {Function} a function which can be invoked to create a new 53703 * playlist selector function. 53704 * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average 53705 */ 53706 53707 53708 var movingAverageBandwidthSelector = function movingAverageBandwidthSelector(decay) { 53709 var average = -1; 53710 var lastSystemBandwidth = -1; 53711 53712 if (decay < 0 || decay > 1) { 53713 throw new Error('Moving average bandwidth decay must be between 0 and 1.'); 53714 } 53715 53716 return function () { 53717 var pixelRatio = this.useDevicePixelRatio ? window_1.devicePixelRatio || 1 : 1; 53718 53719 if (average < 0) { 53720 average = this.systemBandwidth; 53721 lastSystemBandwidth = this.systemBandwidth; 53722 } // stop the average value from decaying for every 250ms 53723 // when the systemBandwidth is constant 53724 // and 53725 // stop average from setting to a very low value when the 53726 // systemBandwidth becomes 0 in case of chunk cancellation 53727 53728 53729 if (this.systemBandwidth > 0 && this.systemBandwidth !== lastSystemBandwidth) { 53730 average = decay * this.systemBandwidth + (1 - decay) * average; 53731 lastSystemBandwidth = this.systemBandwidth; 53732 } 53733 53734 return simpleSelector(this.playlists.master, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, this.limitRenditionByPlayerDimensions, this.masterPlaylistController_); 53735 }; 53736 }; 53737 /** 53738 * Chooses the appropriate media playlist based on the potential to rebuffer 53739 * 53740 * @param {Object} settings 53741 * Object of information required to use this selector 53742 * @param {Object} settings.master 53743 * Object representation of the master manifest 53744 * @param {number} settings.currentTime 53745 * The current time of the player 53746 * @param {number} settings.bandwidth 53747 * Current measured bandwidth 53748 * @param {number} settings.duration 53749 * Duration of the media 53750 * @param {number} settings.segmentDuration 53751 * Segment duration to be used in round trip time calculations 53752 * @param {number} settings.timeUntilRebuffer 53753 * Time left in seconds until the player has to rebuffer 53754 * @param {number} settings.currentTimeline 53755 * The current timeline segments are being loaded from 53756 * @param {SyncController} settings.syncController 53757 * SyncController for determining if we have a sync point for a given playlist 53758 * @return {Object|null} 53759 * {Object} return.playlist 53760 * The highest bandwidth playlist with the least amount of rebuffering 53761 * {Number} return.rebufferingImpact 53762 * The amount of time in seconds switching to this playlist will rebuffer. A 53763 * negative value means that switching will cause zero rebuffering. 53764 */ 53765 53766 53767 var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) { 53768 var master = settings.master, 53769 currentTime = settings.currentTime, 53770 bandwidth = settings.bandwidth, 53771 duration = settings.duration, 53772 segmentDuration = settings.segmentDuration, 53773 timeUntilRebuffer = settings.timeUntilRebuffer, 53774 currentTimeline = settings.currentTimeline, 53775 syncController = settings.syncController; // filter out any playlists that have been excluded due to 53776 // incompatible configurations 53777 53778 var compatiblePlaylists = master.playlists.filter(function (playlist) { 53779 return !Playlist.isIncompatible(playlist); 53780 }); // filter out any playlists that have been disabled manually through the representations 53781 // api or blacklisted temporarily due to playback errors. 53782 53783 var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled); 53784 53785 if (!enabledPlaylists.length) { 53786 // if there are no enabled playlists, then they have all been blacklisted or disabled 53787 // by the user through the representations api. In this case, ignore blacklisting and 53788 // fallback to what the user wants by using playlists the user has not disabled. 53789 enabledPlaylists = compatiblePlaylists.filter(function (playlist) { 53790 return !Playlist.isDisabled(playlist); 53791 }); 53792 } 53793 53794 var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH')); 53795 var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) { 53796 var syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a 53797 // sync request first. This will double the request time 53798 53799 var numRequests = syncPoint ? 1 : 2; 53800 var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist); 53801 var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer; 53802 return { 53803 playlist: playlist, 53804 rebufferingImpact: rebufferingImpact 53805 }; 53806 });
53807 var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) { 53808 return estimate.rebufferingImpact <= 0; 53809 }); // Sort by bandwidth DESC 53810 53811 stableSort(noRebufferingPlaylists, function (a, b) { 53812 return comparePlaylistBandwidth(b.playlist, a.playlist); 53813 }); 53814 53815 if (noRebufferingPlaylists.length) { 53816 return noRebufferingPlaylists[0]; 53817 } 53818 53819 stableSort(rebufferingEstimates, function (a, b) { 53820 return a.rebufferingImpact - b.rebufferingImpact; 53821 }); 53822 return rebufferingEstimates[0] || null; 53823 }; 53824 /** 53825 * Chooses the appropriate media playlist, which in this case is the lowest bitrate 53826 * one with video. If no renditions with video exist, return the lowest audio rendition. 53827 * 53828 * Expects to be called within the context of an instance of VhsHandler 53829 * 53830 * @return {Object|null} 53831 * {Object} return.playlist 53832 * The lowest bitrate playlist that contains a video codec. If no such rendition 53833 * exists pick the lowest audio rendition. 53834 */ 53835 53836 53837 var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() { 53838 var _this = this; // filter out any playlists that have been excluded due to 53839 // incompatible configurations or playback errors 53840 53841 53842 var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate 53843 53844 stableSort(playlists, function (a, b) { 53845 return comparePlaylistBandwidth(a, b); 53846 }); // Parse and assume that playlists with no video codec have no video 53847 // (this is not necessarily true, although it is generally true). 53848 // 53849 // If an entire manifest has no valid videos everything will get filtered 53850 // out. 53851 53852 var playlistsWithVideo = playlists.filter(function (playlist) { 53853 return !!codecsForPlaylist(_this.playlists.master, playlist).video; 53854 }); 53855 return playlistsWithVideo[0] || null; 53856 }; 53857 /** 53858 * Combine all segments into a single Uint8Array 53859 * 53860 * @param {Object} segmentObj 53861 * @return {Uint8Array} concatenated bytes 53862 * @private 53863 */ 53864 53865 53866 var concatSegments = function concatSegments(segmentObj) { 53867 var offset = 0; 53868 var tempBuffer; 53869 53870 if (segmentObj.bytes) { 53871 tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array 53872 53873 segmentObj.segments.forEach(function (segment) { 53874 tempBuffer.set(segment, offset); 53875 offset += segment.byteLength; 53876 }); 53877 } 53878 53879 return tempBuffer; 53880 }; 53881 /** 53882 * @file text-tracks.js 53883 */ 53884 53885 /** 53886 * Create captions text tracks on video.js if they do not exist 53887 * 53888 * @param {Object} inbandTextTracks a reference to current inbandTextTracks 53889 * @param {Object} tech the video.js tech 53890 * @param {Object} captionStream the caption stream to create 53891 * @private 53892 */ 53893 53894 53895 var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStream) { 53896 if (!inbandTextTracks[captionStream]) { 53897 tech.trigger({ 53898 type: 'usage', 53899 name: 'vhs-608' 53900 }); 53901 tech.trigger({ 53902 type: 'usage', 53903 name: 'hls-608' 53904 }); 53905 var instreamId = captionStream; // we need to translate SERVICEn for 708 to how mux.js currently labels them 53906 53907 if (/^cc708_/.test(captionStream)) { 53908 instreamId = 'SERVICE' + captionStream.split('_')[1]; 53909 } 53910 53911 var track = tech.textTracks().getTrackById(instreamId); 53912 53913 if (track) { 53914 // Resuse an existing track with a CC# id because this was 53915 // very likely created by videojs-contrib-hls from information 53916 // in the m3u8 for us to use 53917 inbandTextTracks[captionStream] = track; 53918 } else { 53919 // This section gets called when we have caption services that aren't specified in the manifest. 53920 // Manifest level caption services are handled in media-groups.js under CLOSED-CAPTIONS. 53921 var captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {}; 53922 var label = captionStream; 53923 var language = captionStream; 53924 var def = false; 53925 var captionService = captionServices[instreamId]; 53926 53927 if (captionService) { 53928 label = captionService.label; 53929 language = captionService.language; 53930 def = captionService["default"]; 53931 } // Otherwise, create a track with the default `CC#` label and 53932 // without a language 53933 53934 53935 inbandTextTracks[captionStream] = tech.addRemoteTextTrack({ 53936 kind: 'captions', 53937 id: instreamId, 53938 // TODO: investigate why this doesn't seem to turn the caption on by default 53939 "default": def, 53940 label: label, 53941 language: language 53942 }, false).track; 53943 } 53944 } 53945 }; 53946 /** 53947 * Add caption text track data to a source handler given an array of captions 53948 * 53949 * @param {Object} 53950 * @param {Object} inbandTextTracks the inband text tracks 53951 * @param {number} timestampOffset the timestamp offset of the source buffer 53952 * @param {Array} captionArray an array of caption data 53953 * @private 53954 */ 53955 53956 53957 var addCaptionData = function addCaptionData(_ref) { 53958 var inbandTextTracks = _ref.inbandTextTracks, 53959 captionArray = _ref.captionArray, 53960 timestampOffset = _ref.timestampOffset; 53961 53962 if (!captionArray) { 53963 return; 53964 } 53965 53966 var Cue = window_1.WebKitDataCue || window_1.VTTCue; 53967 captionArray.forEach(function (caption) { 53968 var track = caption.stream; 53969 inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text)); 53970 });
53971 }; 53972 /** 53973 * Define properties on a cue for backwards compatability, 53974 * but warn the user that the way that they are using it 53975 * is depricated and will be removed at a later date. 53976 * 53977 * @param {Cue} cue the cue to add the properties on 53978 * @private 53979 */ 53980 53981 53982 var deprecateOldCue = function deprecateOldCue(cue) { 53983 Object.defineProperties(cue.frame, { 53984 id: { 53985 get: function get() { 53986 videojs.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 53987 return cue.value.key; 53988 } 53989 }, 53990 value: { 53991 get: function get() { 53992 videojs.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 53993 return cue.value.data; 53994 } 53995 }, 53996 privateData: { 53997 get: function get() { 53998 videojs.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 53999 return cue.value.data; 54000 } 54001 } 54002 }); 54003 }; 54004 /** 54005 * Add metadata text track data to a source handler given an array of metadata 54006 * 54007 * @param {Object} 54008 * @param {Object} inbandTextTracks the inband text tracks 54009 * @param {Array} metadataArray an array of meta data 54010 * @param {number} timestampOffset the timestamp offset of the source buffer 54011 * @param {number} videoDuration the duration of the video 54012 * @private 54013 */ 54014 54015 54016 var addMetadata = function addMetadata(_ref2) { 54017 var inbandTextTracks = _ref2.inbandTextTracks, 54018 metadataArray = _ref2.metadataArray, 54019 timestampOffset = _ref2.timestampOffset, 54020 videoDuration = _ref2.videoDuration; 54021 54022 if (!metadataArray) { 54023 return; 54024 } 54025 54026 var Cue = window_1.WebKitDataCue || window_1.VTTCue; 54027 var metadataTrack = inbandTextTracks.metadataTrack_; 54028 54029 if (!metadataTrack) { 54030 return; 54031 } 54032 54033 metadataArray.forEach(function (metadata) { 54034 var time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN, 54035 // ignore this bit of metadata. 54036 // This likely occurs when you have an non-timed ID3 tag like TIT2, 54037 // which is the "Title/Songname/Content description" frame 54038 54039 if (typeof time !== 'number' || window_1.isNaN(time) || time < 0 || !(time < Infinity)) { 54040 return; 54041 } 54042 54043 metadata.frames.forEach(function (frame) { 54044 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 54045 cue.frame = frame; 54046 cue.value = frame; 54047 deprecateOldCue(cue); 54048 metadataTrack.addCue(cue); 54049 }); 54050 }); 54051 54052 if (!metadataTrack.cues || !metadataTrack.cues.length) { 54053 return; 54054 } // Updating the metadeta cues so that 54055 // the endTime of each cue is the startTime of the next cue 54056 // the endTime of last cue is the duration of the video 54057 54058 54059 var cues = metadataTrack.cues; 54060 var cuesArray = []; // Create a copy of the TextTrackCueList... 54061 // ...disregarding cues with a falsey value 54062 54063 for (var i = 0; i < cues.length; i++) { 54064 if (cues[i]) { 54065 cuesArray.push(cues[i]); 54066 } 54067 } // Group cues by their startTime value 54068 54069 54070 var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) { 54071 var timeSlot = obj[cue.startTime] || []; 54072 timeSlot.push(cue); 54073 obj[cue.startTime] = timeSlot; 54074 return obj; 54075 }, {}); // Sort startTimes by ascending order 54076 54077 var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) { 54078 return Number(a) - Number(b); 54079 }); // Map each cue group's endTime to the next group's startTime 54080 54081 sortedStartTimes.forEach(function (startTime, idx) { 54082 var cueGroup = cuesGroupedByStartTime[startTime]; 54083 var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime 54084 54085 cueGroup.forEach(function (cue) { 54086 cue.endTime = nextTime; 54087 }); 54088 }); 54089 }; 54090 /** 54091 * Create metadata text track on video.js if it does not exist 54092 * 54093 * @param {Object} inbandTextTracks a reference to current inbandTextTracks 54094 * @param {string} dispatchType the inband metadata track dispatch type 54095 * @param {Object} tech the video.js tech 54096 * @private 54097 */ 54098 54099 54100 var createMetadataTrackIfNotExists = function createMetadataTrackIfNotExists(inbandTextTracks, dispatchType, tech) { 54101 if (inbandTextTracks.metadataTrack_) { 54102 return; 54103 } 54104 54105 inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({ 54106 kind: 'metadata', 54107 label: 'Timed Metadata' 54108 }, false).track; 54109 inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType; 54110 }; 54111 /** 54112 * Remove cues from a track on video.js. 54113 * 54114 * @param {Double} start start of where we should remove the cue 54115 * @param {Double} end end of where the we should remove the cue 54116 * @param {Object} track the text track to remove the cues from 54117 * @private 54118 */ 54119 54120 54121 var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 54122 var i; 54123 var cue; 54124 54125 if (!track) { 54126 return; 54127 } 54128 54129 if (!track.cues) { 54130 return; 54131 } 54132 54133 i = track.cues.length; 54134 54135 while (i--) { 54136 cue = track.cues[i]; // Remove any cue within the provided start and end time 54137 54138 if (cue.startTime >= start && cue.endTime <= end) { 54139 track.removeCue(cue); 54140 } 54141 } 54142 }; 54143 /** 54144 * Remove duplicate cues from a track on video.js (a cue is considered a 54145 * duplicate if it has the same time interval and text as another) 54146 * 54147 * @param {Object} track the text track to remove the duplicate cues from 54148 * @private 54149 */ 54150 54151 54152 var removeDuplicateCuesFromTrack = function removeDuplicateCuesFromTrack(track) { 54153 var cues = track.cues; 54154 54155 if (!cues) { 54156 return; 54157 } 54158 54159 for (var i = 0; i < cues.length; i++) { 54160 var duplicates = []; 54161 var occurrences = 0; 54162 54163 for (var j = 0; j < cues.length; j++) { 54164 if (cues[i].startTime === cues[j].startTime && cues[i].endTime === cues[j].endTime && cues[i].text === cues[j].text) { 54165 occurrences++; 54166 54167 if (occurrences > 1) { 54168 duplicates.push(cues[j]); 54169 } 54170 } 54171 } 54172 54173 if (duplicates.length) {
54174 duplicates.forEach(function (dupe) { 54175 return track.removeCue(dupe); 54176 }); 54177 } 54178 } 54179 }; 54180 /** 54181 * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in 54182 * front of current time. 54183 * 54184 * @param {Array} buffer 54185 * The current buffer of gop information 54186 * @param {number} currentTime 54187 * The current time 54188 * @param {Double} mapping 54189 * Offset to map display time to stream presentation time 54190 * @return {Array} 54191 * List of gops considered safe to append over 54192 */ 54193 54194 54195 var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) { 54196 if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) { 54197 return []; 54198 } // pts value for current time + 3 seconds to give a bit more wiggle room 54199 54200 54201 var currentTimePts = Math.ceil((currentTime - mapping + 3) * clock_1); 54202 var i; 54203 54204 for (i = 0; i < buffer.length; i++) { 54205 if (buffer[i].pts > currentTimePts) { 54206 break; 54207 } 54208 } 54209 54210 return buffer.slice(i); 54211 }; 54212 /** 54213 * Appends gop information (timing and byteLength) received by the transmuxer for the 54214 * gops appended in the last call to appendBuffer 54215 * 54216 * @param {Array} buffer 54217 * The current buffer of gop information 54218 * @param {Array} gops 54219 * List of new gop information 54220 * @param {boolean} replace 54221 * If true, replace the buffer with the new gop information. If false, append the 54222 * new gop information to the buffer in the right location of time. 54223 * @return {Array} 54224 * Updated list of gop information 54225 */ 54226 54227 54228 var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) { 54229 if (!gops.length) { 54230 return buffer; 54231 } 54232 54233 if (replace) { 54234 // If we are in safe append mode, then completely overwrite the gop buffer 54235 // with the most recent appeneded data. This will make sure that when appending 54236 // future segments, we only try to align with gops that are both ahead of current 54237 // time and in the last segment appended. 54238 return gops.slice(); 54239 } 54240 54241 var start = gops[0].pts; 54242 var i = 0; 54243 54244 for (i; i < buffer.length; i++) { 54245 if (buffer[i].pts >= start) { 54246 break; 54247 } 54248 } 54249 54250 return buffer.slice(0, i).concat(gops); 54251 }; 54252 /**
54253 * Removes gop information in buffer that overlaps with provided start and end 54254 * 54255 * @param {Array} buffer 54256 * The current buffer of gop information 54257 * @param {Double} start 54258 * position to start the remove at 54259 * @param {Double} end 54260 * position to end the remove at 54261 * @param {Double} mapping 54262 * Offset to map display time to stream presentation time 54263 */ 54264 54265 54266 var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) { 54267 var startPts = Math.ceil((start - mapping) * clock_1); 54268 var endPts = Math.ceil((end - mapping) * clock_1); 54269 var updatedBuffer = buffer.slice(); 54270 var i = buffer.length; 54271 54272 while (i--) { 54273 if (buffer[i].pts <= endPts) { 54274 break; 54275 } 54276 } 54277 54278 if (i === -1) { 54279 // no removal because end of remove range is before start of buffer 54280 return updatedBuffer; 54281 } 54282 54283 var j = i + 1; 54284 54285 while (j--) { 54286 if (buffer[j].pts <= startPts) { 54287 break; 54288 } 54289 } // clamp remove range start to 0 index 54290 54291 54292 j = Math.max(j, 0); 54293 updatedBuffer.splice(j, i - j + 1); 54294 return updatedBuffer; 54295 }; 54296 54297 var shallowEqual = function shallowEqual(a, b) { 54298 // if both are undefined 54299 // or one or the other is undefined 54300 // they are not equal 54301 if (!a && !b || !a && b || a && !b) { 54302 return false; 54303 } // they are the same object and thus, equal 54304 54305 54306 if (a === b) { 54307 return true; 54308 } // sort keys so we can make sure they have 54309 // all the same keys later. 54310 54311 54312 var akeys = Object.keys(a).sort(); 54313 var bkeys = Object.keys(b).sort(); // different number of keys, not equal 54314 54315 if (akeys.length !== bkeys.length) { 54316 return false; 54317 } 54318 54319 for (var i = 0; i < akeys.length; i++) { 54320 var key = akeys[i]; // different sorted keys, not equal 54321 54322 if (key !== bkeys[i]) { 54323 return false; 54324 } // different values, not equal 54325 54326 54327 if (a[key] !== b[key]) { 54328 return false; 54329 } 54330 } 54331 54332 return true; 54333 }; // https://www.w3.org/TR/WebIDL-1/#quotaexceedederror 54334 54335 54336 var QUOTA_EXCEEDED_ERR = 22; 54337 /** 54338 * The segment loader has no recourse except to fetch a segment in the 54339 * current playlist and use the internal timestamps in that segment to 54340 * generate a syncPoint. This function returns a good candidate index 54341 * for that process. 54342 * 54343 * @param {Array} segments - the segments array from a playlist. 54344 * @return {number} An index of a segment from the playlist to load 54345 */ 54346 54347 var getSyncSegmentCandidate = function getSyncSegmentCandidate(currentTimeline, segments, targetTime) { 54348 segments = segments || []; 54349 var timelineSegments = []; 54350 var time = 0; 54351 54352 for (var i = 0; i < segments.length; i++) { 54353 var segment = segments[i]; 54354 54355 if (currentTimeline === segment.timeline) { 54356 timelineSegments.push(i); 54357 time += segment.duration; 54358 54359 if (time > targetTime) { 54360 return i; 54361 } 54362 } 54363 } 54364 54365 if (timelineSegments.length === 0) { 54366 return 0; 54367 } // default to the last timeline segment 54368 54369 54370 return timelineSegments[timelineSegments.length - 1]; 54371 }; // In the event of a quota exceeded error, keep at least one second of back buffer. This 54372 // number was arbitrarily chosen and may be updated in the future, but seemed reasonable 54373 // as a start to prevent any potential issues with removing content too close to the 54374 // playhead. 54375 54376 54377 var MIN_BACK_BUFFER = 1; // in ms 54378 54379 var CHECK_BUFFER_DELAY = 500; 54380 54381 var finite = function finite(num) { 54382 return typeof num === 'number' && isFinite(num); 54383 }; // With most content hovering around 30fps, if a segment has a duration less than a half 54384 // frame at 30fps or one frame at 60fps, the bandwidth and throughput calculations will 54385 // not accurately reflect the rest of the content. 54386 54387 54388 var MIN_SEGMENT_DURATION_TO_SAVE_STATS = 1 / 60; 54389 54390 var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, trackInfo) { 54391 // Although these checks should most likely cover non 'main' types, for now it narrows 54392 // the scope of our checks. 54393 if (loaderType !== 'main' || !startingMedia || !trackInfo) { 54394 return null; 54395 } 54396 54397 if (!trackInfo.hasAudio && !trackInfo.hasVideo) {
54398 return 'Neither audio nor video found in segment.'; 54399 } 54400 54401 if (startingMedia.hasVideo && !trackInfo.hasVideo) { 54402 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.'; 54403 } 54404 54405 if (!startingMedia.hasVideo && trackInfo.hasVideo) { 54406 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.'; 54407 } 54408 54409 return null; 54410 }; 54411 /** 54412 * Calculates a time value that is safe to remove from the back buffer without interrupting 54413 * playback. 54414 * 54415 * @param {TimeRange} seekable 54416 * The current seekable range 54417 * @param {number} currentTime 54418 * The current time of the player 54419 * @param {number} targetDuration 54420 * The target duration of the current playlist 54421 * @return {number} 54422 * Time that is safe to remove from the back buffer without interrupting playback 54423 */ 54424 54425 54426 var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable, currentTime, targetDuration) { 54427 // 30 seconds before the playhead provides a safe default for trimming. 54428 // 54429 // Choosing a reasonable default is particularly important for high bitrate content and 54430 // VOD videos/live streams with large windows, as the buffer may end up overfilled and 54431 // throw an APPEND_BUFFER_ERR. 54432 var trimTime = currentTime - Config.BACK_BUFFER_LENGTH; 54433 54434 if (seekable.length) { 54435 // Some live playlists may have a shorter window of content than the full allowed back 54436 // buffer. For these playlists, don't save content that's no longer within the window. 54437 trimTime = Math.max(trimTime, seekable.start(0)); 54438 } // Don't remove within target duration of the current time to avoid the possibility of 54439 // removing the GOP currently being played, as removing it can cause playback stalls. 54440 54441 54442 var maxTrimTime = currentTime - targetDuration; 54443 return Math.min(maxTrimTime, trimTime); 54444 }; 54445 54446 var segmentInfoString = function segmentInfoString(segmentInfo) { 54447 var startOfSegment = segmentInfo.startOfSegment, 54448 duration = segmentInfo.duration, 54449 segment = segmentInfo.segment, 54450 part = segmentInfo.part, 54451 _segmentInfo$playlist = segmentInfo.playlist, 54452 seq = _segmentInfo$playlist.mediaSequence, 54453 id = _segmentInfo$playlist.id, 54454 _segmentInfo$playlist2 = _segmentInfo$playlist.segments, 54455 segments = _segmentInfo$playlist2 === void 0 ? [] : _segmentInfo$playlist2, 54456 index = segmentInfo.mediaIndex, 54457 partIndex = segmentInfo.partIndex, 54458 timeline = segmentInfo.timeline; 54459 var segmentLen = segments.length - 1; 54460 var selection = 'mediaIndex/partIndex increment'; 54461 54462 if (segmentInfo.getMediaInfoForTime) { 54463 selection = "getMediaInfoForTime (" + segmentInfo.getMediaInfoForTime + ")"; 54464 } else if (segmentInfo.isSyncRequest) { 54465 selection = 'getSyncSegmentCandidate (isSyncRequest)'; 54466 } 54467 54468 var hasPartIndex = typeof partIndex === 'number'; 54469 var name = segmentInfo.segment.uri ? 'segment' : 'pre-segment'; 54470 var zeroBasedPartCount = hasPartIndex ? getKnownPartCount({ 54471 preloadSegment: segment 54472 }) - 1 : 0; 54473 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 + "]"); 54474 }; 54475 54476 var timingInfoPropertyForMedia = function timingInfoPropertyForMedia(mediaType) { 54477 return mediaType + "TimingInfo"; 54478 }; 54479 /** 54480 * Returns the timestamp offset to use for the segment. 54481 * 54482 * @param {number} segmentTimeline 54483 * The timeline of the segment 54484 * @param {number} currentTimeline 54485 * The timeline currently being followed by the loader 54486 * @param {number} startOfSegment 54487 * The estimated segment start 54488 * @param {TimeRange[]} buffered 54489 * The loader's buffer 54490 * @param {boolean} overrideCheck 54491 * If true, no checks are made to see if the timestamp offset value should be set, 54492 * but sets it directly to a value. 54493 * 54494 * @return {number|null} 54495 * Either a number representing a new timestamp offset, or null if the segment is 54496 * part of the same timeline 54497 */ 54498 54499 54500 var timestampOffsetForSegment = function timestampOffsetForSegment(_ref) { 54501 var segmentTimeline = _ref.segmentTimeline, 54502 currentTimeline = _ref.currentTimeline, 54503 startOfSegment = _ref.startOfSegment, 54504 buffered = _ref.buffered, 54505 overrideCheck = _ref.overrideCheck; // Check to see if we are crossing a discontinuity to see if we need to set the 54506 // timestamp offset on the transmuxer and source buffer. 54507 // 54508 // Previously, we changed the timestampOffset if the start of this segment was less than 54509 // the currently set timestampOffset, but this isn't desirable as it can produce bad 54510 // behavior, especially around long running live streams. 54511 54512 if (!overrideCheck && segmentTimeline === currentTimeline) { 54513 return null; 54514 } // When changing renditions, it's possible to request a segment on an older timeline. For 54515 // instance, given two renditions with the following: 54516 // 54517 // #EXTINF:10 54518 // segment1 54519 // #EXT-X-DISCONTINUITY 54520 // #EXTINF:10 54521 // segment2 54522 // #EXTINF:10 54523 // segment3 54524 // 54525 // And the current player state: 54526 // 54527 // current time: 8 54528 // buffer: 0 => 20 54529 // 54530 // The next segment on the current rendition would be segment3, filling the buffer from 54531 // 20s onwards. However, if a rendition switch happens after segment2 was requested, 54532 // then the next segment to be requested will be segment1 from the new rendition in 54533 // order to fill time 8 and onwards. Using the buffered end would result in repeated 54534 // content (since it would position segment1 of the new rendition starting at 20s). This 54535 // case can be identified when the new segment's timeline is a prior value. Instead of 54536 // using the buffered end, the startOfSegment can be used, which, hopefully, will be 54537 // more accurate to the actual start time of the segment. 54538 54539 54540 if (segmentTimeline < currentTimeline) { 54541 return startOfSegment; 54542 } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that 54543 // value uses the end of the last segment if it is available. While this value 54544 // should often be correct, it's better to rely on the buffered end, as the new 54545 // content post discontinuity should line up with the buffered end as if it were 54546 // time 0 for the new content. 54547 54548 54549 return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment; 54550 }; 54551 /** 54552 * Returns whether or not the loader should wait for a timeline change from the timeline 54553 * change controller before processing the segment. 54554 * 54555 * Primary timing in VHS goes by video. This is different from most media players, as 54556 * audio is more often used as the primary timing source. For the foreseeable future, VHS 54557 * will continue to use video as the primary timing source, due to the current logic and 54558 * expectations built around it. 54559 54560 * Since the timing follows video, in order to maintain sync, the video loader is 54561 * responsible for setting both audio and video source buffer timestamp offsets. 54562 * 54563 * Setting different values for audio and video source buffers could lead to 54564 * desyncing. The following examples demonstrate some of the situations where this 54565 * distinction is important. Note that all of these cases involve demuxed content. When 54566 * content is muxed, the audio and video are packaged together, therefore syncing 54567 * separate media playlists is not an issue. 54568 * 54569 * CASE 1: Audio prepares to load a new timeline before video: 54570 * 54571 * Timeline: 0 1
54572 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 54573 * Audio Loader: ^ 54574 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 54575 * Video Loader ^ 54576 * 54577 * In the above example, the audio loader is preparing to load the 6th segment, the first 54578 * after a discontinuity, while the video loader is still loading the 5th segment, before 54579 * the discontinuity. 54580 * 54581 * If the audio loader goes ahead and loads and appends the 6th segment before the video 54582 * loader crosses the discontinuity, then when appended, the 6th audio segment will use 54583 * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition, 54584 * the audio loader must provide the audioAppendStart value to trim the content in the 54585 * transmuxer, and that value relies on the audio timestamp offset. Since the audio 54586 * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the 54587 * segment until that value is provided. 54588 * 54589 * CASE 2: Video prepares to load a new timeline before audio: 54590 * 54591 * Timeline: 0 1 54592 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 54593 * Audio Loader: ^ 54594 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 54595 * Video Loader ^ 54596 * 54597 * In the above example, the video loader is preparing to load the 6th segment, the first 54598 * after a discontinuity, while the audio loader is still loading the 5th segment, before 54599 * the discontinuity. 54600 * 54601 * If the video loader goes ahead and loads and appends the 6th segment, then once the 54602 * segment is loaded and processed, both the video and audio timestamp offsets will be 54603 * set, since video is used as the primary timing source. This is to ensure content lines 54604 * up appropriately, as any modifications to the video timing are reflected by audio when 54605 * the video loader sets the audio and video timestamp offsets to the same value. However, 54606 * setting the timestamp offset for audio before audio has had a chance to change 54607 * timelines will likely lead to desyncing, as the audio loader will append segment 5 with 54608 * a timestamp intended to apply to segments from timeline 1 rather than timeline 0. 54609 * 54610 * CASE 3: When seeking, audio prepares to load a new timeline before video 54611 * 54612 * Timeline: 0 1 54613 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 54614 * Audio Loader: ^ 54615 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 54616 * Video Loader ^ 54617 * 54618 * In the above example, both audio and video loaders are loading segments from timeline 54619 * 0, but imagine that the seek originated from timeline 1. 54620 * 54621 * When seeking to a new timeline, the timestamp offset will be set based on the expected 54622 * segment start of the loaded video segment. In order to maintain sync, the audio loader 54623 * must wait for the video loader to load its segment and update both the audio and video 54624 * timestamp offsets before it may load and append its own segment. This is the case 54625 * whether the seek results in a mismatched segment request (e.g., the audio loader 54626 * chooses to load segment 3 and the video loader chooses to load segment 4) or the 54627 * loaders choose to load the same segment index from each playlist, as the segments may 54628 * not be aligned perfectly, even for matching segment indexes. 54629 * 54630 * @param {Object} timelinechangeController 54631 * @param {number} currentTimeline 54632 * The timeline currently being followed by the loader 54633 * @param {number} segmentTimeline 54634 * The timeline of the segment being loaded 54635 * @param {('main'|'audio')} loaderType 54636 * The loader type 54637 * @param {boolean} audioDisabled 54638 * Whether the audio is disabled for the loader. This should only be true when the 54639 * loader may have muxed audio in its segment, but should not append it, e.g., for 54640 * the main loader when an alternate audio playlist is active. 54641 * 54642 * @return {boolean} 54643 * Whether the loader should wait for a timeline change from the timeline change 54644 * controller before processing the segment 54645 */ 54646 54647 54648 var shouldWaitForTimelineChange = function shouldWaitForTimelineChange(_ref2) { 54649 var timelineChangeController = _ref2.timelineChangeController, 54650 currentTimeline = _ref2.currentTimeline, 54651 segmentTimeline = _ref2.segmentTimeline, 54652 loaderType = _ref2.loaderType, 54653 audioDisabled = _ref2.audioDisabled; 54654 54655 if (currentTimeline === segmentTimeline) { 54656 return false; 54657 } 54658 54659 if (loaderType === 'audio') { 54660 var lastMainTimelineChange = timelineChangeController.lastTimelineChange({ 54661 type: 'main' 54662 }); // Audio loader should wait if: 54663 // 54664 // * main hasn't had a timeline change yet (thus has not loaded its first segment) 54665 // * main hasn't yet changed to the timeline audio is looking to load 54666 54667 return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline; 54668 } // The main loader only needs to wait for timeline changes if there's demuxed audio. 54669 // Otherwise, there's nothing to wait for, since audio would be muxed into the main 54670 // loader's segments (or the content is audio/video only and handled by the main 54671 // loader). 54672 54673 54674 if (loaderType === 'main' && audioDisabled) { 54675 var pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({ 54676 type: 'audio' 54677 }); // Main loader should wait for the audio loader if audio is not pe
54677nding a timeline 54678 // change to the current timeline. 54679 // 54680 // Since the main loader is responsible for setting the timestamp offset for both 54681 // audio and video, the main loader must wait for audio to be about to change to its 54682 // timeline before setting the offset, otherwise, if audio is behind in loading, 54683 // segments from the previous timeline would be adjusted by the new timestamp offset. 54684 // 54685 // This requirement means that video will not cross a timeline until the audio is 54686 // about to cross to it, so that way audio and video will always cross the timeline 54687 // together. 54688 // 54689 // In addition to normal timeline changes, these rules also apply to the start of a 54690 // stream (going from a non-existent timeline, -1, to timeline 0). It's important 54691 // that these rules apply to the first timeline change because if they did not, it's 54692 // possible that the main loader will cross two timelines before the audio loader has 54693 // crossed one. Logic may be implemented to handle the startup as a special case, but 54694 // it's easier to simply treat all timeline changes the same. 54695 54696 if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) { 54697 return false; 54698 } 54699 54700 return true; 54701 } 54702 54703 return false; 54704 }; 54705 54706 var mediaDuration = function mediaDuration(audioTimingInfo, videoTimingInfo) { 54707 var audioDuration = audioTimingInfo && typeof audioTimingInfo.start === 'number' && typeof audioTimingInfo.end === 'number' ? audioTimingInfo.end - audioTimingInfo.start : 0; 54708 var videoDuration = videoTimingInfo && typeof videoTimingInfo.start === 'number' && typeof videoTimingInfo.end === 'number' ? videoTimingInfo.end - videoTimingInfo.start : 0; 54709 return Math.max(audioDuration, videoDuration); 54710 }; 54711 54712 var segmentTooLong = function segmentTooLong(_ref3) { 54713 var segmentDuration = _ref3.segmentDuration, 54714 maxDuration = _ref3.maxDuration; // 0 duration segments are most likely due to metadata only segments or a lack of 54715 // information. 54716 54717 if (!segmentDuration) { 54718 return false; 54719 } // For HLS: 54720 // 54721 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.3.1 54722 // The EXTINF duration of each Media Segment in the Playlist 54723 // file, when rounded to the nearest integer, MUST be less than or equal 54724 // to the target duration; longer segments can trigger playback stalls 54725 // or other errors. 54726 // 54727 // For DASH, the mpd-parser uses the largest reported segment duration as the target 54728 // duration. Although that reported duration is occasionally approximate (i.e., not 54729 // exact), a strict check may report that a segment is too long more often in DASH. 54730 54731 54732 return Math.round(segmentDuration) > maxDuration + TIME_FUDGE_FACTOR; 54733 }; 54734 54735 var getTroublesomeSegmentDurationMessage = function getTroublesomeSegmentDurationMessage(segmentInfo, sourceType) { 54736 // Right now we aren't following DASH's timing model exactly, so only perform 54737 // this check for HLS content. 54738 if (sourceType !== 'hls') { 54739 return null; 54740 } 54741 54742 var segmentDuration = mediaDuration(segmentInfo.audioTimingInfo, segmentInfo.videoTimingInfo); // Don't report if we lack information. 54743 // 54744 // If the segment has a duration of 0 it is either a lack of information or a 54745 // metadata only segment and shouldn't be reported here. 54746 54747 if (!segmentDuration) { 54748 return null; 54749 } 54750 54751 var targetDuration = segmentInfo.playlist.targetDuration; 54752 var isSegmentWayTooLong = segmentTooLong({ 54753 segmentDuration: segmentDuration, 54754 maxDuration: targetDuration * 2 54755 }); 54756 var isSegmentSlightlyTooLong = segmentTooLong({ 54757 segmentDuration: segmentDuration, 54758 maxDuration: targetDuration 54759 }); 54760 var 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'; 54761 54762 if (isSegmentWayTooLong || isSegmentSlightlyTooLong) { 54763 return { 54764 severity: isSegmentWayTooLong ? 'warn' : 'info', 54765 message: segmentTooLongMessage 54766 }; 54767 } 54768 54769 return null; 54770 }; 54771 /** 54772 * An object that manages segment loading and appending. 54773 * 54774 * @class SegmentLoader 54775 * @param {Object} options required and optional options 54776 * @extends videojs.EventTarget 54777 */ 54778 54779 54780 var SegmentLoader = /*#__PURE__*/function (_videojs$EventTarget) { 54781 inheritsLoose(SegmentLoader, _videojs$EventTarget); 54782 54783 function SegmentLoader(settings, options) { 54784 var _this; 54785 54786 _this = _videojs$EventTarget.call(this) || this; // check pre-conditions 54787 54788 if (!settings) { 54789 throw new TypeError('Initialization settings are required'); 54790 } 54791 54792 if (typeof settings.currentTime !== 'function') { 54793 throw new TypeError('No currentTime getter specified'); 54794 } 54795 54796 if (!settings.mediaSource) { 54797 throw new TypeError('No MediaSource specified'); 54798 } // public properties 54799 54800 54801 _this.bandwidth = settings.bandwidth; 54802 _this.throughput = { 54803 rate: 0, 54804 count: 0 54805 }; 54806 _this.roundTrip = NaN; 54807 54808 _this.resetStats_(); 54809 54810 _this.mediaIndex = null; 54811 _this.partIndex = null; // private settings 54812 54813 _this.hasPlayed_ = settings.hasPlayed; 54814 _this.currentTime_ = settings.currentTime; 54815 _this.seekable_ = settings.seekable; 54816 _this.seeking_ = settings.seeking; 54817 _this.duration_ = settings.duration; 54818 _this.mediaSource_ = settings.mediaSource; 54819 _this.vhs_ = settings.vhs; 54820 _this.loaderType_ = settings.loaderType; 54821 _this.currentMediaInfo_ = void 0; 54822 _this.startingMediaInfo_ = void 0; 54823 _this.segmentMetadataTrack_ = settings.segmentMetadataTrack; 54824 _this.goalBufferLength_ = settings.goalBufferLength; 54825 _this.sourceType_ = settings.sourceType; 54826 _this.sourceUpdater_ = settings.sourceUpdater; 54827 _this.inbandTextTracks_ = settings.inbandTextTracks; 54828 _this.state_ = 'INIT'; 54829 _this.timelineChangeController_ = settings.timelineChangeController; 54830 _this.shouldSaveSegmentTimingInfo_ = true; 54831 _this.parse708captions_ = settings.parse708captions; 54832 _this.experimentalExactManifestTimings = settings.experimentalExactManifestTimings; // private instance variables 54833 54834 _this.checkBufferTimeout_ = null; 54835 _this.error_ = void 0; 54836 _this.currentTimeline_ = -1; 54837 _this.pendingSegment_ = null; 54838 _this.xhrOptions_ = null; 54839 _this.pendingSegments_ = []; 54840 _this.audioDisabled_ = false; 54841 _this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller 54842 54843 _this.gopBuffer_ = []; 54844 _this.timeMapping_ = 0; 54845 _this.safeAppend_ = videojs.browser.IE_VERSION >= 11; 54846 _this.appendInitSegment_ = { 54847 audio: true, 54848 video: true 54849 }; 54850 _this.playlistOfLastInitSegment_ = { 54851 audio: null, 54852 video: null 54853 }; 54854 _this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough 54855 // information yet to start the loading process (e.g., if the audio loader wants to 54856 // load a segment from the next timeline but the main loader hasn't yet crossed that 54857 // timeline), then the load call will be added to the queue until it is ready to be 54858 // processed. 54859 54860 _this.loadQueue_ = []; 54861 _this.metadataQueue_ = { 54862 id3: [], 54863 caption: [] 54864 }; 54865 _this.waitingOnRemove_ = false; 54866 _this.quotaExceededErrorRetryTimeout_ = null; // Fragmented mp4 playback 54867 54868 _this.activeInitSegmentId_ = null; 54869 _this.initSegments_ = {}; // HLSe playback 54870 54871 _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys; 54872 _this.keyCache_ = {}; 54873 _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings 54874 // between a time in the display time and a segment index within 54875 // a playlist 54876 54877 _this.syncController_ = settings.syncController; 54878 _this.syncPoint_ = { 54879 segmentIndex: 0, 54880 time: 0 54881 }; 54882 _this.transmuxer_ = _this.createTransmuxer_(); 54883 54884 _this.triggerSyncInfoUpdate_ = function () { 54885 return _this.trigger('syncinfoupdate'); 54886 }; 54887 54888 _this.syncController_.on('syncinfoupdate', _this.triggerSyncInfoUpdate_); 54889 54890 _this.mediaSource_.addEventListener('sourceopen', function () {
54891 if (!_this.isEndOfStream_()) { 54892 _this.ended_ = false; 54893 } 54894 }); // ...for determining the fetch location 54895 54896 54897 _this.fetchAtBuffer_ = false; 54898 _this.logger_ = logger("SegmentLoader[" + _this.loaderType_ + "]"); 54899 Object.defineProperty(assertThisInitialized(_this), 'state', { 54900 get: function get() { 54901 return this.state_; 54902 }, 54903 set: function set(newState) { 54904 if (newState !== this.state_) { 54905 this.logger_(this.state_ + " -> " + newState); 54906 this.state_ = newState; 54907 this.trigger('statechange'); 54908 } 54909 } 54910 }); 54911 54912 _this.sourceUpdater_.on('ready', function () { 54913 if (_this.hasEnoughInfoToAppend_()) { 54914 _this.processCallQueue_(); 54915 } 54916 }); // Only the main loader needs to listen for pending timeline changes, as the main 54917 // loader should wait for audio to be ready to change its timeline so that both main 54918 // and audio timelines change together. For more details, see the 54919 // shouldWaitForTimelineChange function. 54920 54921 54922 if (_this.loaderType_ === 'main') { 54923 _this.timelineChangeController_.on('pendingtimelinechange', function () { 54924 if (_this.hasEnoughInfoToAppend_()) { 54925 _this.processCallQueue_(); 54926 } 54927 }); 54928 } // The main loader only listens on pending timeline changes, but the audio loader, 54929 // since its loads follow main, needs to listen on timeline changes. For more details, 54930 // see the shouldWaitForTimelineChange function. 54931 54932 54933 if (_this.loaderType_ === 'audio') { 54934 _this.timelineChangeController_.on('timelinechange', function () { 54935 if (_this.hasEnoughInfoToLoad_()) { 54936 _this.processLoadQueue_(); 54937 } 54938 54939 if (_this.hasEnoughInfoToAppend_()) { 54940 _this.processCallQueue_(); 54941 } 54942 }); 54943 } 54944 54945 return _this; 54946 } 54947 54948 var _proto = SegmentLoader.prototype; 54949 54950 _proto.createTransmuxer_ = function createTransmuxer_() { 54951 return segmentTransmuxer.createTransmuxer({ 54952 remux: false, 54953 alignGopsAtEnd: this.safeAppend_, 54954 keepOriginalTimestamps: true, 54955 parse708captions: this.parse708captions_ 54956 }); 54957 } 54958 /** 54959 * reset all of our media stats 54960 * 54961 * @private 54962 */ 54963 ; 54964 54965 _proto.resetStats_ = function resetStats_() { 54966 this.mediaBytesTransferred = 0; 54967 this.mediaRequests = 0; 54968 this.mediaRequestsAborted = 0; 54969 this.mediaRequestsTimedout = 0; 54970 this.mediaRequestsErrored = 0; 54971 this.mediaTransferDuration = 0; 54972 this.mediaSecondsLoaded = 0; 54973 this.mediaAppends = 0; 54974 } 54975 /** 54976 * dispose of the SegmentLoader and reset to the default state 54977 */ 54978 ; 54979 54980 _proto.dispose = function dispose() { 54981 this.trigger('dispose'); 54982 this.state = 'DISPOSED'; 54983 this.pause(); 54984 this.abort_(); 54985 54986 if (this.transmuxer_) { 54987 this.transmuxer_.terminate(); 54988 } 54989 54990 this.resetStats_(); 54991 54992 if (this.checkBufferTimeout_) { 54993 window_1.clearTimeout(this.checkBufferTimeout_); 54994 } 54995 54996 if (this.syncController_ && this.triggerSyncInfoUpdate_) { 54997 this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_); 54998 } 54999 55000 this.off(); 55001 }; 55002 55003 _proto.setAudio = function setAudio(enable) { 55004 this.audioDisabled_ = !enable; 55005 55006 if (enable) { 55007 this.appendInitSegment_.audio = true; 55008 } else { 55009 // remove current track audio if it gets disabled 55010 this.sourceUpdater_.removeAudio(0, this.duration_()); 55011 } 55012 } 55013 /** 55014 * abort anything that is currently doing on with the SegmentLoader 55015 * and reset to a default state 55016 */ 55017 ; 55018 55019 _proto.abort = function abort() { 55020 if (this.state !== 'WAITING') { 55021 if (this.pendingSegment_) { 55022 this.pendingSegment_ = null; 55023 } 55024 55025 return; 55026 } 55027 55028 this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY 55029 // since we are no longer "waiting" on any requests. XHR callback is not always run 55030 // when the request is aborted. This will prevent the loader from being stuck in the 55031 // WAITING state indefinitely. 55032 55033 this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the 55034 // next segment 55035 55036 if (!this.paused()) { 55037 this.monitorBuffer_(); 55038 } 55039 } 55040 /**
55041 * abort all pending xhr requests and null any pending segements 55042 * 55043 * @private 55044 */ 55045 ; 55046 55047 _proto.abort_ = function abort_() { 55048 if (this.pendingSegment_ && this.pendingSegment_.abortRequests) { 55049 this.pendingSegment_.abortRequests(); 55050 } // clear out the segment being processed 55051 55052 55053 this.pendingSegment_ = null; 55054 this.callQueue_ = []; 55055 this.loadQueue_ = []; 55056 this.metadataQueue_.id3 = []; 55057 this.metadataQueue_.caption = []; 55058 this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_); 55059 this.waitingOnRemove_ = false; 55060 window_1.clearTimeout(this.quotaExceededErrorRetryTimeout_); 55061 this.quotaExceededErrorRetryTimeout_ = null; 55062 }; 55063 55064 _proto.checkForAbort_ = function checkForAbort_(requestId) { 55065 // If the state is APPENDING, then aborts will not modify the state, meaning the first 55066 // callback that happens should reset the state to READY so that loading can continue. 55067 if (this.state === 'APPENDING' && !this.pendingSegment_) { 55068 this.state = 'READY'; 55069 return true; 55070 } 55071 55072 if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) { 55073 return true; 55074 } 55075 55076 return false; 55077 } 55078 /** 55079 * set an error on the segment loader and null out any pending segements 55080 * 55081 * @param {Error} error the error to set on the SegmentLoader 55082 * @return {Error} the error that was set or that is currently set 55083 */ 55084 ; 55085 55086 _proto.error = function error(_error) { 55087 if (typeof _error !== 'undefined') { 55088 this.logger_('error occurred:', _error); 55089 this.error_ = _error; 55090 } 55091 55092 this.pendingSegment_ = null; 55093 return this.error_; 55094 }; 55095 55096 _proto.endOfStream = function endOfStream() { 55097 this.ended_ = true; 55098 55099 if (this.transmuxer_) { 55100 // need to clear out any cached data to prepare for the new segment 55101 segmentTransmuxer.reset(this.transmuxer_); 55102 } 55103 55104 this.gopBuffer_.length = 0; 55105 this.pause(); 55106 this.trigger('ended'); 55107 } 55108 /** 55109 * Indicates which time ranges are buffered 55110 * 55111 * @return {TimeRange} 55112 * TimeRange object representing the current buffered ranges 55113 */ 55114 ; 55115 55116 _proto.buffered_ = function buffered_() { 55117 var trackInfo = this.getMediaInfo_(); 55118 55119 if (!this.sourceUpdater_ || !trackInfo) { 55120 return videojs.createTimeRanges(); 55121 } 55122 55123 if (this.loaderType_ === 'main') { 55124 var hasAudio = trackInfo.hasAudio, 55125 hasVideo = trackInfo.hasVideo, 55126 isMuxed = trackInfo.isMuxed; 55127 55128 if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) { 55129 return this.sourceUpdater_.buffered(); 55130 } 55131 55132 if (hasVideo) { 55133 return this.sourceUpdater_.videoBuffered(); 55134 } 55135 } // One case that can be ignored for now is audio only with alt audio, 55136 // as we don't yet have proper support for that. 55137 55138 55139 return this.sourceUpdater_.audioBuffered(); 55140 } 55141 /** 55142 * Gets and sets init segment for the provided map 55143 * 55144 * @param {Object} map 55145 * The map object representing the init segment to get or set 55146 * @param {boolean=} set 55147 * If true, the init segment for the provided map should be saved 55148 * @return {Object} 55149 * map object for desired init segment 55150 */ 55151 ; 55152 55153 _proto.initSegmentForMap = function initSegmentForMap(map, set) { 55154 if (set === void 0) { 55155 set = false; 55156 } 55157 55158 if (!map) { 55159 return null; 55160 } 55161 55162 var id = initSegmentId(map); 55163 var storedMap = this.initSegments_[id]; 55164 55165 if (set && !storedMap && map.bytes) { 55166 this.initSegments_[id] = storedMap = { 55167 resolvedUri: map.resolvedUri, 55168 byterange: map.byterange, 55169 bytes: map.bytes, 55170 tracks: map.tracks, 55171 timescales: map.timescales 55172 }; 55173 } 55174 55175 return storedMap || map; 55176 } 55177 /** 55178 * Gets and sets key for the provided key 55179 * 55180 * @param {Object} key 55181 * The key object representing the key to get or set 55182 * @param {boolean=} set 55183 * If true, the key for the provided key should be saved 55184 * @return {Object} 55185 * Key object for desired key 55186 */ 55187 ; 55188 55189 _proto.segmentKey = function segmentKey(key, set) { 55190 if (set === void 0) { 55191 set = false; 55192 } 55193 55194 if (!key) { 55195 return null; 55196 } 55197 55198 var id = segmentKeyId(key); 55199 var storedKey = this.keyCache_[id];
55199 // TODO: We should use the HTTP Expires header to invalidate our cache per 55200 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3 55201 55202 if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) { 55203 this.keyCache_[id] = storedKey = { 55204 resolvedUri: key.resolvedUri, 55205 bytes: key.bytes 55206 }; 55207 } 55208 55209 var result = { 55210 resolvedUri: (storedKey || key).resolvedUri 55211 }; 55212 55213 if (storedKey) { 55214 result.bytes = storedKey.bytes; 55215 } 55216 55217 return result; 55218 } 55219 /** 55220 * Returns true if all configuration required for loading is present, otherwise false. 55221 * 55222 * @return {boolean} True if the all configuration is ready for loading 55223 * @private 55224 */ 55225 ; 55226 55227 _proto.couldBeginLoading_ = function couldBeginLoading_() { 55228 return this.playlist_ && !this.paused(); 55229 } 55230 /** 55231 * load a playlist and start to fill the buffer 55232 */ 55233 ; 55234 55235 _proto.load = function load() { 55236 // un-pause 55237 this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be 55238 // specified 55239 55240 if (!this.playlist_) { 55241 return; 55242 } // if all the configuration is ready, initialize and begin loading 55243 55244 55245 if (this.state === 'INIT' && this.couldBeginLoading_()) { 55246 return this.init_(); 55247 } // if we're in the middle of processing a segment already, don't 55248 // kick off an additional segment request 55249 55250 55251 if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') { 55252 return; 55253 } 55254 55255 this.state = 'READY'; 55256 } 55257 /** 55258 * Once all the starting parameters have been specified, begin 55259 * operation. This method should only be invoked from the INIT 55260 * state. 55261 * 55262 * @private 55263 */ 55264 ; 55265 55266 _proto.init_ = function init_() { 55267 this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old 55268 // audio data from the muxed content should be removed 55269 55270 this.resetEverything(); 55271 return this.monitorBuffer_(); 55272 } 55273 /** 55274 * set a playlist on the segment loader 55275 * 55276 * @param {PlaylistLoader} media the playlist to set on the segment loader 55277 */ 55278 ; 55279 55280 _proto.playlist = function playlist(newPlaylist, options) { 55281 if (options === void 0) { 55282 options = {}; 55283 } 55284 55285 if (!newPlaylist) { 55286 return; 55287 } 55288 55289 var oldPlaylist = this.playlist_; 55290 var segmentInfo = this.pendingSegment_; 55291 this.playlist_ = newPlaylist; 55292 this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist 55293 // is always our zero-time so force a sync update each time the playlist 55294 // is refreshed from the server 55295 // 55296 // Use the INIT state to determine if playback has started, as the playlist sync info 55297 // should be fixed once requests begin (as sync points are generated based on sync 55298 // info), but not before then. 55299 55300 if (this.state === 'INIT') { 55301 newPlaylist.syncInfo = { 55302 mediaSequence: newPlaylist.mediaSequence, 55303 time: 0 55304 }; // Setting the date time mapping means mapping the program date time (if available) 55305 // to time 0 on the player's timeline. The playlist's syncInfo serves a similar 55306 // purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can 55307 // be updated as the playlist is refreshed before the loader starts loading, the 55308 // program date time mapping needs to be updated as well. 55309 // 55310 // This mapping is only done for the main loader because a program date time should 55311 // map equivalently between playlists. 55312 55313 if (this.loaderType_ === 'main') { 55314 this.syncController_.setDateTimeMappingForStart(newPlaylist); 55315 } 55316 } 55317 55318 var oldId = null; 55319 55320 if (oldPlaylist) { 55321 if (oldPlaylist.id) { 55322 oldId = oldPlaylist.id; 55323 } else if (oldPlaylist.uri) { 55324 oldId = oldPlaylist.uri; 55325 } 55326 } 55327 55328 this.logger_("playlist update [" + oldId + " =>
55328 " + (newPlaylist.id || newPlaylist.uri) + "]"); // in VOD, this is always a rendition switch (or we updated our syncInfo above) 55329 // in LIVE, we always want to update with new playlists (including refreshes) 55330 55331 this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start 55332 // buffering now 55333 55334 if (this.state === 'INIT' && this.couldBeginLoading_()) { 55335 return this.init_(); 55336 } 55337 55338 if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) { 55339 if (this.mediaIndex !== null) { 55340 // we must "resync" the segment loader when we switch renditions and 55341 // the segment loader is already synced to the previous rendition 55342 this.resyncLoader(); 55343 } 55344 55345 this.currentMediaInfo_ = void 0; 55346 this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined 55347 55348 return; 55349 } // we reloaded the same playlist so we are in a live scenario 55350 // and we will likely need to adjust the mediaIndex 55351 55352 55353 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 55354 this.logger_("live window shift [" + mediaSequenceDiff + "]"); // update the mediaIndex on the SegmentLoader 55355 // this is important because we can abort a request and this value must be 55356 // equal to the last appended mediaIndex 55357 55358 if (this.mediaIndex !== null) { 55359 this.mediaIndex -= mediaSequenceDiff; // this can happen if we are going to load the first segment, but get a playlist 55360 // update during that. mediaIndex would go from 0 to -1 if mediaSequence in the 55361 // new playlist was incremented by 1. 55362 55363 if (this.mediaIndex < 0) { 55364 this.mediaIndex = null; 55365 this.partIndex = null; 55366 } else { 55367 var segment = this.playlist_.segments[this.mediaIndex]; // partIndex should remain the same for the same segment 55368 // unless parts fell off of the playlist for this segment. 55369 // In that case we need to reset partIndex and resync 55370 55371 if (this.partIndex && (!segment.parts || !segment.parts.length || !segment.parts[this.partIndex])) { 55372 var mediaIndex = this.mediaIndex; 55373 this.logger_("currently processing part (index " + this.partIndex + ") no longer exists."); 55374 this.resetLoader(); // We want to throw away the partIndex and the data associated with it, 55375 // as the part was dropped from our current playlists segment. 55376 // The mediaIndex will still be valid so keep that around. 55377 55378 this.mediaIndex = mediaIndex; 55379 } 55380 } 55381 } // update the mediaIndex on the SegmentInfo object 55382 // this is important because we will update this.mediaIndex with this value 55383 // in `handleAppendsDone_` after the segment has been successfully appended 55384 55385 55386 if (segmentInfo) { 55387 segmentInfo.mediaIndex -= mediaSequenceDiff; 55388 55389 if (segmentInfo.mediaIndex < 0) { 55390 segmentInfo.mediaIndex = null; 55391 segmentInfo.partIndex = null; 55392 } else { 55393 // we need to update the referenced segment so that timing information is 55394 // saved for the new playlist's segment, however, if the segment fell off the 55395 // playlist, we can leave the old reference and just lose the timing info 55396 if (segmentInfo.mediaIndex >= 0) { 55397 segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex]; 55398 } 55399 55400 if (segmentInfo.partIndex >= 0 && segmentInfo.segment.parts) { 55401 segmentInfo.part = segmentInfo.segment.parts[segmentInfo.partIndex]; 55402 } 55403 } 55404 } 55405 55406 this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist); 55407 } 55408 /** 55409 * Prevent the loader from fetching additional segments. If there 55410 * is a segment request outstanding, it will finish processing 55411 * before the loader halts. A segment loader can be unpaused by 55412 * calling load(). 55413 */ 55414 ; 55415 55416 _proto.pause = function pause() { 55417 if (this.checkBufferTimeout_) { 55418 window_1.clearTimeout(this.checkBufferTimeout_); 55419 this.checkBufferTimeout_ = null; 55420 } 55421 } 55422 /** 55423 * Returns whether the segment loader is fetching additional 55424 * segments when given the opportunity. This property can be 55425 * modified through calls to pause() and load(). 55426 */ 55427 ; 55428 55429 _proto.paused = function paused() { 55430 return this.checkBufferTimeout_ === null; 55431 } 55432 /** 55433 * Delete all the buffered data and reset the SegmentLoader 55434 * 55435 * @param {Function} [done] an optional callback to be executed when the remove 55436 * operation is complete 55437 */ 55438 ; 55439 55440 _proto.resetEverything = function resetEverything(done) { 55441 this.ended_ = false; 55442 this.appendInitSegment_ = { 55443 audio: true, 55444 video: true 55445 }; 55446 this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything. 55447 // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader, 55448 // we then clamp the value to duration if necessary. 55449 55450 this.remove(0, Infinity, done); // clears fmp4 captions 55451 55452 if (this.transmuxer_) { 55453 this.transmuxer_.postMessage({ 55454 action: 'clearAllMp4Captions' 55455 }); 55456 } 55457 } 55458 /** 55459 * Force the SegmentLoader to resync and start loading around the currentTime instead 55460 * of starting at the end of the buffer 55461 * 55462 * Useful for fast quality changes 55463 */ 55464 ; 55465 55466 _proto.resetLoader = function resetLoader() { 55467 this.fetchAtBuffer_ = false; 55468 this.resyncLoader(); 55469 } 55470 /** 55471 * Force the SegmentLoader to restart synchronization and make a conservative guess 55472 * before returning to the simple walk-forward method 55473 */ 55474 ; 55475 55476 _proto.resyncLoader = function resyncLoader() { 55477 if (this.transmuxer_) { 55478 // need to clear out any cached data to prepare for the new segment 55479 segmentTransmuxer.reset(this.transmuxer_); 55480 } 55481 55482 this.mediaIndex = null; 55483 this.partIndex = null; 55484 this.syncPoint_ = null; 55485 this.isPendingTimestampOffset_ = false; 55486 this.callQueue_ = []; 55487 this.loadQueue_ = []; 55488 this.metadataQueue_.id3 = []; 55489 this.metadataQueue_.caption = []; 55490 this.abort(); 55491 55492 if (this.transmuxer_) { 55493 this.transmuxer_.postMessage({ 55494 action: 'clearParsedMp4Captions' 55495 }); 55496 } 55497 } 55498 /** 55499 * Remove any data in the source buffer between start and end times 55500 * 55501 * @param {number} start - the start time of the region to remove from the buffer 55502 * @param {number} end - the end time of the region to remove from the buffer 55503 * @param {Function} [done] - an optional callback to be executed when the remove 55504 * @param {boolean} force - force all remove operations to happen 55505 * operation is complete 55506 */ 55507 ; 55508 55509 _proto.remove = function remove(start, end, done, force) { 55510 if (done === void 0) { 55511 done = function done() {}; 55512 } 55513 55514 if (force === void 0) { 55515 force = false; 55516 } // clamp end to duration if we need to remove everything. 55517 // This is due to a browser bug that causes issues if we remove to Infinity. 55518 // videojs/videojs-contrib-hls#1225 55519 55520 55521 if (end === Infinity) { 55522 end = this.duration_(); 55523 } // skip removes that would throw an error 55524 // commonly happens during a rendition switch at the start of a video 55525 // from start 0 to end 0 55526 55527 55528 if (end <= start) { 55529 this.logger_('skipping remove because end ${end} is <= start ${start}'); 55530 return; 55531 } 55532 55533 if (!this.sourceUpdater_ || !this.getMediaInfo_()) { 55534 this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media 55535 55536 return; 55537 } // set it to one to complete this function's removes 55538 55539 55540 var removesRemaining = 1; 55541 55542 var removeFinished = function removeFinished() { 55543 removesRemaining--; 55544 55545 if (removesRemaining === 0) { 55546 done(); 55547 } 55548 }; 55549 55550 if (force || !this.audioDisabled_) { 55551 removesRemaining++; 55552 this.sourceUpdater_.removeAudio(start, end, removeFinished); 55553 } // While it would be better to only remove video if the main loader has video, this 55554 // should be safe with audio only as removeVideo will call back even if there's no 55555 // video buffer. 55556 // 55557 // In theory we can check to see if there's video before calling the remove, but in 55558 // the event that we're switching between renditions and from video to audio only
55559 // (when we add support for that), we may need to clear the video contents despite 55560 // what the new media will contain. 55561 55562 55563 if (force || this.loaderType_ === 'main') { 55564 this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_); 55565 removesRemaining++; 55566 this.sourceUpdater_.removeVideo(start, end, removeFinished); 55567 } // remove any captions and ID3 tags 55568 55569 55570 for (var track in this.inbandTextTracks_) { 55571 removeCuesFromTrack(start, end, this.inbandTextTracks_[track]); 55572 } 55573 55574 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes 55575 55576 removeFinished(); 55577 } 55578 /** 55579 * (re-)schedule monitorBufferTick_ to run as soon as possible 55580 * 55581 * @private 55582 */ 55583 ; 55584 55585 _proto.monitorBuffer_ = function monitorBuffer_() { 55586 if (this.checkBufferTimeout_) { 55587 window_1.clearTimeout(this.checkBufferTimeout_); 55588 } 55589 55590 this.checkBufferTimeout_ = window_1.setTimeout(this.monitorBufferTick_.bind(this), 1); 55591 } 55592 /** 55593 * As long as the SegmentLoader is in the READY state, periodically 55594 * invoke fillBuffer_(). 55595 * 55596 * @private 55597 */ 55598 ; 55599 55600 _proto.monitorBufferTick_ = function monitorBufferTick_() { 55601 if (this.state === 'READY') { 55602 this.fillBuffer_(); 55603 } 55604 55605 if (this.checkBufferTimeout_) { 55606 window_1.clearTimeout(this.checkBufferTimeout_); 55607 } 55608 55609 this.checkBufferTimeout_ = window_1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY); 55610 } 55611 /** 55612 * fill the buffer with segements unless the sourceBuffers are 55613 * currently updating 55614 * 55615 * Note: this function should only ever be called by monitorBuffer_ 55616 * and never directly 55617 * 55618 * @private 55619 */ 55620 ; 55621 55622 _proto.fillBuffer_ = function fillBuffer_() { 55623 // TODO since the source buffer maintains a queue, and we shouldn't call this function 55624 // except when we're ready for the next segment, this check can most likely be removed 55625 if (this.sourceUpdater_.updating()) { 55626 return; 55627 } // see if we need to begin loading immediately 55628 55629 55630 var segmentInfo = this.chooseNextRequest_(); 55631 55632 if (!segmentInfo) { 55633 return; 55634 } 55635 55636 if (typeof segmentInfo.timestampOffset === 'number') { 55637 this.isPendingTimestampOffset_ = false; 55638 this.timelineChangeController_.pendingTimelineChange({ 55639 type: this.loaderType_, 55640 from: this.currentTimeline_, 55641 to: segmentInfo.timeline 55642 }); 55643 } 55644 55645 this.loadSegment_(segmentInfo); 55646 } 55647 /** 55648 * Determines if we should call endOfStream on the media source based 55649 * on the state of the buffer or if appened segment was the final 55650 * segment in the playlist. 55651 * 55652 * @param {number} [mediaIndex] the media index of segment we last appended 55653 * @param {Object} [playlist] a media playlist object 55654 * @return {boolean} do we need to call endOfStream on the MediaSource 55655 */ 55656 ; 55657 55658 _proto.isEndOfStream_ = function isEndOfStream_(mediaIndex, playlist, partIndex) { 55659 if (mediaIndex === void 0) { 55660 mediaIndex = this.mediaIndex; 55661 } 55662 55663 if (playlist === void 0) { 55664 playlist = this.playlist_; 55665 } 55666 55667 if (partIndex === void 0) { 55668 partIndex = this.partIndex; 55669 } 55670 55671 if (!playlist || !this.mediaSource_) { 55672 return false; 55673 } 55674 55675 var segment = typeof mediaIndex === 'number' && playlist.segments[mediaIndex]; // mediaIndex is zero based but length is 1 based 55676 55677 var appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // true if there are no parts, or this is the last part. 55678 55679 var appendedLastPart = !segment || !segment.parts || partIndex + 1 === segment.parts.length; // if we've buffered to the end of the video, we need to call endOfStream 55680 // so that MediaSources can trigger the `ended` event when it runs out of 55681 // buffered data instead of waiting for me 55682 55683 return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment && appendedLastPart; 55684 } 55685 /** 55686 * Determines what request should be made given current segment loader state. 55687 * 55688 * @return {Object} a request object that describes the segment/part to load 55689 */ 55690 ; 55691 55692 _proto.chooseNextRequest_ = function chooseNextRequest_() { 55693 var bufferedEnd = lastBufferedEnd(this.buffered_()) || 0; 55694 var bufferedTime = Math.max(0, bufferedEnd - this.currentTime_()); 55695 var preloaded = !this.hasPlayed_() && bufferedTime >= 1; 55696 var haveEnoughBuffer = bufferedTime >= this.goalBufferLength_(); 55697 var segments = this.playlist_.segments; // return no segment if: 55698 // 1. we don't have segments 55699 // 2. The video has not yet played and we already downloaded a segment 55700 // 3. we already have enough buffered time 55701 55702 if (!segments.length || preloaded || haveEnoughBuffer) { 55703 return null; 55704 } 55705 55706 this.syncPoint_ = this.syncPoint_ || this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 55707 var next = { 55708 partIndex: null, 55709 mediaIndex: null, 55710 startOfSegment: null, 55711 playlist: this.playlist_, 55712 isSyncRequest: Boolean(!this.syncPoint_) 55713 }; 55714 55715 if (next.isSyncRequest) { 55716 next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd); 55717 } else if (this.mediaIndex !== null) { 55718 var segment = segments[this.mediaIndex]; 55719 var partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1; 55720 next.startOfSegment = segment.end ? segment.end : bufferedEnd; 55721 55722 if (segment.parts && segment.parts[partIndex + 1]) { 55723 next.mediaIndex = this.mediaIndex; 55724 next.partIndex = partIndex + 1; 55725 } else { 55726 next.mediaIndex = this.mediaIndex + 1; 55727 } 55728 } else { 55729 // Find the segment containing the end of the buffer or current time. 55730 var _Playlist$getMediaInf = Playlist.getMediaInfoForTime({ 55731 experimentalExactManifestTimings: this.experimentalExactManifestTimings, 55732 playlist: this.playlist_, 55733 currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(), 55734 startingPartIndex: this.syncPoint_.partIndex, 55735 startingSegmentIndex: this.syncPoint_.segmentIndex, 55736 startTime: this.syncPoint_.time 55737 }), 55738 segmentIndex = _Playlist$getMediaInf.segmentIndex, 55739 startTime = _Playlist$getMediaInf.startTime, 55740 _partIndex = _Playlist$getMediaInf.partIndex; 55741 55742 next.getMediaInfoForTime = this.fetchAtBuffer_ ? 'bufferedEnd' : 'currentTime'; 55743 next.mediaIndex = segmentIndex; 55744 next.startOfSegment = startTime; 55745 next.partIndex = _partIndex; 55746 } 55747 55748 var nextSegment = segments[next.mediaIndex]; 55749 var nextPart = nextSegment && typeof next.partIndex === 'number' && nextSegment.parts && nextSegment.parts[next.partIndex];
55749 // if the next segment index is invalid or 55750 // the next partIndex is invalid do not choose a next segment. 55751 55752 if (!nextSegment || typeof next.partIndex === 'number' && !nextPart) { 55753 return null; 55754 } // if the next segment has parts, and we don't have a partIndex. 55755 // Set partIndex to 0 55756 55757 55758 if (typeof next.partIndex !== 'number' && nextSegment.parts) { 55759 next.partIndex = 0; 55760 } 55761 55762 var ended = this.mediaSource_ && this.mediaSource_.readyState === 'ended'; // do not choose a next segment if all of the following: 55763 // 1. this is the last segment in the playlist 55764 // 2. end of stream has been called on the media source already 55765 // 3. the player is not seeking 55766 55767 if (next.mediaIndex >= segments.length - 1 && ended && !this.seeking_()) { 55768 return null; 55769 } 55770 55771 return this.generateSegmentInfo_(next); 55772 }; 55773 55774 _proto.generateSegmentInfo_ = function generateSegmentInfo_(options) { 55775 var playlist = options.playlist, 55776 mediaIndex = options.mediaIndex, 55777 startOfSegment = options.startOfSegment, 55778 isSyncRequest = options.isSyncRequest, 55779 partIndex = options.partIndex, 55780 forceTimestampOffset = options.forceTimestampOffset, 55781 getMediaInfoForTime = options.getMediaInfoForTime; 55782 var segment = playlist.segments[mediaIndex]; 55783 var part = typeof partIndex === 'number' && segment.parts[partIndex]; 55784 var segmentInfo = { 55785 requestId: 'segment-loader-' + Math.random(), 55786 // resolve the segment URL relative to the playlist 55787 uri: part && part.resolvedUri || segment.resolvedUri, 55788 // the segment's mediaIndex at the time it was requested 55789 mediaIndex: mediaIndex, 55790 partIndex: part ? partIndex : null, 55791 // whether or not to update the SegmentLoader's state with this 55792 // segment's mediaIndex 55793 isSyncRequest: isSyncRequest, 55794 startOfSegment: startOfSegment, 55795 // the segment's playlist 55796 playlist: playlist, 55797 // unencrypted bytes of the segment 55798 bytes: null, 55799 // when a key is defined for this segment, the encrypted bytes 55800 encryptedBytes: null, 55801 // The target timestampOffset for this segment when we append it 55802 // to the source buffer 55803 timestampOffset: null, 55804 // The timeline that the segment is in 55805 timeline: segment.timeline, 55806 // The expected duration of the segment in seconds 55807 duration: part && part.duration || segment.duration, 55808 // retain the segment in case the playlist updates while doing an async process 55809 segment: segment, 55810 part: part, 55811 byteLength: 0, 55812 transmuxer: this.transmuxer_, 55813 // type of getMediaInfoForTime that was used to get this segment 55814 getMediaInfoForTime: getMediaInfoForTime 55815 }; 55816 var overrideCheck = typeof forceTimestampOffset !== 'undefined' ? forceTimestampOffset : this.isPendingTimestampOffset_; 55817 segmentInfo.timestampOffset = this.timestampOffsetForSegment_({ 55818 segmentTimeline: segment.timeline, 55819 currentTimeline: this.currentTimeline_, 55820 startOfSegment: startOfSegment, 55821 buffered: this.buffered_(), 55822 overrideCheck: overrideCheck 55823 }); 55824 var audioBufferedEnd = lastBufferedEnd(this.sourceUpdater_.audioBuffered()); 55825 55826 if (typeof audioBufferedEnd === 'number') { 55827 // since the transmuxer is using the actual timing values, but the buffer is 55828 // adjusted by the timestamp offset, we must adjust the value here 55829 segmentInfo.audioAppendStart = audioBufferedEnd - this.sourceUpdater_.audioTimestampOffset(); 55830 } 55831 55832 if (this.sourceUpdater_.videoBuffered().length) { 55833 segmentInfo.gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_, // since the transmuxer is using the actual timing values, but the time is 55834 // adjusted by the timestmap offset, we must adjust the value here 55835 this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_); 55836 } 55837 55838 return segmentInfo; 55839 } // get the timestampoffset for a segment, 55840 // added so that vtt segment loader can override and prevent 55841 // adding timestamp offsets. 55842 ; 55843 55844 _proto.timestampOffsetForSegment_ = function timestampOffsetForSegment_(options) { 55845 return timestampOffsetForSegment(options); 55846 } 55847 /** 55848 * Determines if the network has enough bandwidth to complete the current segment 55849 * request in a timely manner. If not, the request will be aborted early and bandwidth 55850 * updated to trigger a playlist switch. 55851 * 55852 * @param {Object} stats 55853 * Object containing stats about the request timing and size 55854 * @private 55855 */ 55856 ; 55857 55858 _proto.earlyAbortWhenNeeded_ = function earlyAbortWhenNeeded_(stats) { 55859 if (this.vhs_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition 55860 // TODO: Replace using timeout with a boolean indicating whether this playlist is 55861 // the lowestEnabledRendition. 55862 !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes 55863 !this.playlist_.attributes.BANDWIDTH) { 55864 return; 55865 } // Wait at least 1 second since the first byte of data has been received before 55866 // using the calculated bandwidth from the progress event to allow the bitrate 55867 // to stabilize 55868 55869 55870 if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) { 55871 return; 55872 } 55873 55874 var currentTime = this.currentTime_(); 55875 var measuredBandwidth = stats.bandwidth; 55876 var segmentDuration = this.pendingSegment_.duration; 55877 var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort 55878 // if we are only left with less than 1 second when the request completes. 55879 // A negative timeUntilRebuffering indicates we are already rebuffering 55880 55881 var timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download 55882 // is larger than the estimated time until the player runs out of forward buffer 55883 55884 if (requestTimeRemaining <= timeUntilRebuffer$1) { 55885 return; 55886 } 55887 55888 var switchCandidate = minRebufferMaxBandwidthSelector({ 55889 master: this.vhs_.playlists.master, 55890 currentTime: currentTime, 55891 bandwidth: measuredBandwidth, 55892 duration: this.duration_(), 55893 segmentDuration: segmentDuration, 55894 timeUntilRebuffer: timeUntilRebuffer$1, 55895 currentTimeline: this.currentTimeline_, 55896 syncController: this.syncController_ 55897 }); 55898 55899 if (!switchCandidate) { 55900 return; 55901 } 55902 55903 var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1;
55904 var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact; 55905 var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the 55906 // potential round trip time of the new request so that we are not too aggressive 55907 // with switching to a playlist that might save us a fraction of a second. 55908 55909 if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) { 55910 minimumTimeSaving = 1; 55911 } 55912 55913 if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) { 55914 return; 55915 } // set the bandwidth to that of the desired playlist being sure to scale by 55916 // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it 55917 // don't trigger a bandwidthupdate as the bandwidth is artifial 55918 55919 55920 this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1; 55921 this.trigger('earlyabort'); 55922 }; 55923 55924 _proto.handleAbort_ = function handleAbort_(segmentInfo) { 55925 this.logger_("Aborting " + segmentInfoString(segmentInfo)); 55926 this.mediaRequestsAborted += 1; 55927 } 55928 /** 55929 * XHR `progress` event handler 55930 * 55931 * @param {Event} 55932 * The XHR `progress` event 55933 * @param {Object} simpleSegment 55934 * A simplified segment object copy 55935 * @private 55936 */ 55937 ; 55938 55939 _proto.handleProgress_ = function handleProgress_(event, simpleSegment) { 55940 this.earlyAbortWhenNeeded_(simpleSegment.stats); 55941 55942 if (this.checkForAbort_(simpleSegment.requestId)) { 55943 return; 55944 } 55945 55946 this.trigger('progress'); 55947 }; 55948 55949 _proto.handleTrackInfo_ = function handleTrackInfo_(simpleSegment, trackInfo) { 55950 this.earlyAbortWhenNeeded_(simpleSegment.stats); 55951 55952 if (this.checkForAbort_(simpleSegment.requestId)) { 55953 return; 55954 } 55955 55956 if (this.checkForIllegalMediaSwitch(trackInfo)) { 55957 return; 55958 } 55959 55960 trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with. 55961 // Guard against cases where we're not getting track info at all until we are 55962 // certain that all streams will provide it. 55963 55964 if (!shallowEqual(this.currentMediaInfo_, trackInfo)) { 55965 this.appendInitSegment_ = { 55966 audio: true, 55967 video: true 55968 }; 55969 this.startingMediaInfo_ = trackInfo; 55970 this.currentMediaInfo_ = trackInfo; 55971 this.logger_('trackinfo update', trackInfo); 55972 this.trigger('trackinfo'); 55973 } // trackinfo may cause an abort if the trackinfo 55974 // causes a codec change to an unsupported codec. 55975 55976 55977 if (this.checkForAbort_(simpleSegment.requestId)) { 55978 return; 55979 } // set trackinfo on the pending segment so that 55980 // it can append. 55981 55982 55983 this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info 55984 55985 if (this.hasEnoughInfoToAppend_()) { 55986 this.processCallQueue_(); 55987 } 55988 }; 55989 55990 _proto.handleTimingInfo_ = function handleTimingInfo_(simpleSegment, mediaType, timeType, time) { 55991 this.earlyAbortWhenNeeded_(simpleSegment.stats); 55992 55993 if (this.checkForAbort_(simpleSegment.requestId)) { 55994 return; 55995 } 55996 55997 var segmentInfo = this.pendingSegment_; 55998 var timingInfoProperty = timingInfoPropertyForMedia(mediaType); 55999 segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {}; 56000 segmentInfo[timingInfoProperty][timeType] = time; 56001 this.logger_("timinginfo: " + mediaType + " - " + timeType + " - " + time); // check if any calls were waiting on the timing info 56002 56003 if (this.hasEnoughInfoToAppend_()) { 56004 this.processCallQueue_(); 56005 } 56006 }; 56007 56008 _proto.handleCaptions_ = function handleCaptions_(simpleSegment, captionData) { 56009 var _this2 = this; 56010 56011 this.earlyAbortWhenNeeded_(simpleSegment.stats); 56012 56013 if (this.checkForAbort_(simpleSegment.requestId)) { 56014 return; 56015 } // This could only happen with fmp4 segments, but 56016 // should still not happen in general 56017 56018 56019 if (captionData.length === 0) { 56020 this.logger_('SegmentLoader received no captions from a caption event'); 56021 return; 56022 } 56023 56024 var segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing
56025 // can be adjusted by the timestamp offset 56026 56027 if (!segmentInfo.hasAppendedData_) { 56028 this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData)); 56029 return; 56030 } 56031 56032 var timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset(); 56033 var captionTracks = {}; // get total start/end and captions for each track/stream 56034 56035 captionData.forEach(function (caption) { 56036 // caption.stream is actually a track name... 56037 // set to the existing values in tracks or default values 56038 captionTracks[caption.stream] = captionTracks[caption.stream] || { 56039 // Infinity, as any other value will be less than this 56040 startTime: Infinity, 56041 captions: [], 56042 // 0 as an other value will be more than this 56043 endTime: 0 56044 }; 56045 var captionTrack = captionTracks[caption.stream]; 56046 captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset); 56047 captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset); 56048 captionTrack.captions.push(caption); 56049 });
56050 Object.keys(captionTracks).forEach(function (trackName) { 56051 var _captionTracks$trackN = captionTracks[trackName], 56052 startTime = _captionTracks$trackN.startTime, 56053 endTime = _captionTracks$trackN.endTime, 56054 captions = _captionTracks$trackN.captions; 56055 var inbandTextTracks = _this2.inbandTextTracks_; 56056 56057 _this2.logger_("adding cues from " + startTime + " -> " + endTime + " for " + trackName); 56058 56059 createCaptionsTrackIfNotExists(inbandTextTracks, _this2.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track. 56060 // We do this because a rendition change that also changes the timescale for captions 56061 // will result in captions being re-parsed for certain segments. If we add them again 56062 // without clearing we will have two of the same captions visible. 56063 56064 removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]); 56065 addCaptionData({ 56066 captionArray: captions, 56067 inbandTextTracks: inbandTextTracks, 56068 timestampOffset: timestampOffset 56069 }); 56070 }); // Reset stored captions since we added parsed 56071 // captions to a text track at this point 56072 56073 if (this.transmuxer_) { 56074 this.transmuxer_.postMessage({ 56075 action: 'clearParsedMp4Captions' 56076 }); 56077 } 56078 }; 56079 56080 _proto.handleId3_ = function handleId3_(simpleSegment, id3Frames, dispatchType) { 56081 this.earlyAbortWhenNeeded_(simpleSegment.stats); 56082 56083 if (this.checkForAbort_(simpleSegment.requestId)) { 56084 return; 56085 } 56086 56087 var segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set 56088 56089 if (!segmentInfo.hasAppendedData_) { 56090 this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType)); 56091 return; 56092 } 56093 56094 var timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset(); // There's potentially an issue where we could double add metadata if there's a muxed 56095 // audio/video source with a metadata track, and an alt audio with a metadata track. 56096 // However, this probably won't happen, and if it does it can be handled then. 56097 56098 createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.vhs_.tech_); 56099 addMetadata({ 56100 inbandTextTracks: this.inbandTextTracks_, 56101 metadataArray: id3Frames, 56102 timestampOffset: timestampOffset, 56103 videoDuration: this.duration_() 56104 }); 56105 }; 56106 56107 _proto.processMetadataQueue_ = function processMetadataQueue_() { 56108 this.metadataQueue_.id3.forEach(function (fn) { 56109 return fn(); 56110 }); 56111 this.metadataQueue_.caption.forEach(function (fn) { 56112 return fn(); 56113 }); 56114 this.metadataQueue_.id3 = []; 56115 this.metadataQueue_.caption = []; 56116 }; 56117 56118 _proto.processCallQueue_ = function processCallQueue_() { 56119 var callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the 56120 // functions may check the length of the load queue and default to pushing themselves 56121 // back onto the queue. 56122 56123 this.callQueue_ = []; 56124 callQueue.forEach(function (fun) { 56125 return fun(); 56126 }); 56127 }; 56128 56129 _proto.processLoadQueue_ = function processLoadQueue_() { 56130 var loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the 56131 // functions may check the length of the load queue and default to pushing themselves 56132 // back onto the queue. 56133 56134 this.loadQueue_ = []; 56135 loadQueue.forEach(function (fun) { 56136 return fun(); 56137 }); 56138 } 56139 /** 56140 * Determines whether the loader has enough info to load the next segment. 56141 * 56142 * @return {boolean} 56143 * Whether or not the loader has enough info to load the next segment 56144 */ 56145 ; 56146 56147 _proto.hasEnoughInfoToLoad_ = function hasEnoughInfoToLoad_() { 56148 // Since primary timing goes by video, only the audio loader potentially needs to wait 56149 // to load. 56150 if (this.loaderType_ !== 'audio') { 56151 return true; 56152 } 56153 56154 var segmentInfo = this.pendingSegment_;
56154 // A fill buffer must have already run to establish a pending segment before there's 56155 // enough info to load. 56156 56157 if (!segmentInfo) { 56158 return false; 56159 } // The first segment can and should be loaded immediately so that source buffers are 56160 // created together (before appending). Source buffer creation uses the presence of 56161 // audio and video data to determine whether to create audio/video source buffers, and 56162 // uses processed (transmuxed or parsed) media to determine the types required. 56163 56164 56165 if (!this.getCurrentMediaInfo_()) { 56166 return true; 56167 } 56168 56169 if ( // Technically, instead of waiting to load a segment on timeline changes, a segment 56170 // can be requested and downloaded and only wait before it is transmuxed or parsed. 56171 // But in practice, there are a few reasons why it is better to wait until a loader 56172 // is ready to append that segment before requesting and downloading: 56173 // 56174 // 1. Because audio and main loaders cross discontinuities together, if this loader 56175 // is waiting for the other to catch up, then instead of requesting another 56176 // segment and using up more bandwidth, by not yet loading, more bandwidth is 56177 // allotted to the loader currently behind. 56178 // 2. media-segment-request doesn't have to have logic to consider whether a segment 56179 // is ready to be processed or not, isolating the queueing behavior to the loader. 56180 // 3. The audio loader bases some of its segment properties on timing information 56181 // provided by the main loader, meaning that, if the logic for waiting on 56182 // processing was in media-segment-request, then it would also need to know how 56183 // to re-generate the segment information after the main loader caught up. 56184 shouldWaitForTimelineChange({ 56185 timelineChangeController: this.timelineChangeController_, 56186 currentTimeline: this.currentTimeline_, 56187 segmentTimeline: segmentInfo.timeline, 56188 loaderType: this.loaderType_, 56189 audioDisabled: this.audioDisabled_ 56190 })) { 56191 return false; 56192 } 56193 56194 return true; 56195 }; 56196 56197 _proto.getCurrentMediaInfo_ = function getCurrentMediaInfo_(segmentInfo) { 56198 if (segmentInfo === void 0) { 56199 segmentInfo = this.pendingSegment_; 56200 } 56201 56202 return segmentInfo && segmentInfo.trackInfo || this.currentMediaInfo_; 56203 }; 56204 56205 _proto.getMediaInfo_ = function getMediaInfo_(segmentInfo) { 56206 if (segmentInfo === void 0) { 56207 segmentInfo = this.pendingSegment_; 56208 } 56209 56210 return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_; 56211 }; 56212 56213 _proto.hasEnoughInfoToAppend_ = function hasEnoughInfoToAppend_() { 56214 if (!this.sourceUpdater_.ready()) { 56215 return false; 56216 } // If content needs to be removed or the loader is waiting on an append reattempt, 56217 // then no additional content should be appended until the prior append is resolved. 56218 56219 56220 if (this.waitingOnRemove_ || this.quotaExceededErrorRetryTimeout_) { 56221 return false; 56222 } 56223 56224 var segmentInfo = this.pendingSegment_; 56225 var trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or 56226 // we do not have information on this specific 56227 // segment yet 56228 56229 if (!segmentInfo || !trackInfo) { 56230 return false; 56231 } 56232 56233 var hasAudio = trackInfo.hasAudio, 56234 hasVideo = trackInfo.hasVideo, 56235 isMuxed = trackInfo.isMuxed; 56236 56237 if (hasVideo && !segmentInfo.videoTimingInfo) { 56238 return false; 56239 } // muxed content only relies on video timing information for now. 56240 56241 56242 if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) { 56243 return false; 56244 } 56245 56246 if (shouldWaitForTimelineChange({ 56247 timelineChangeController: this.timelineChangeController_, 56248 currentTimeline: this.currentTimeline_, 56249 segmentTimeline: segmentInfo.timeline, 56250 loaderType: this.loaderType_, 56251 audioDisabled: this.audioDisabled_ 56252 })) { 56253 return false; 56254 } 56255 56256 return true; 56257 }; 56258 56259 _proto.handleData_ = function handleData_(simpleSegment, result) { 56260 this.earlyAbortWhenNeeded_(simpleSegment.stats); 56261 56262 if (this.checkForAbort_(simpleSegment.requestId)) { 56263 return; 56264 } // If there's anything in the call queue, then this data came later and should be 56265 // executed after the calls currently queued. 56266 56267 56268 if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) { 56269 this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result)); 56270 return; 56271 } 56272 56273 var segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time 56274 56275 this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats 56276 56277 this.updateMediaSecondsLoaded_(segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort
56278 // logic may change behavior depending on the state, and changing state too early may 56279 // inflate our estimates of bandwidth. In the future this should be re-examined to 56280 // note more granular states. 56281 // don't process and append data if the mediaSource is closed 56282 56283 if (this.mediaSource_.readyState === 'closed') { 56284 return; 56285 } // if this request included an initialization segment, save that data 56286 // to the initSegment cache 56287 56288 56289 if (simpleSegment.map) { 56290 simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request 56291 56292 segmentInfo.segment.map = simpleSegment.map; 56293 } // if this request included a segment key, save that data in the cache 56294 56295 56296 if (simpleSegment.key) { 56297 this.segmentKey(simpleSegment.key, true); 56298 } 56299 56300 segmentInfo.isFmp4 = simpleSegment.isFmp4; 56301 segmentInfo.timingInfo = segmentInfo.timingInfo || {}; 56302 56303 if (segmentInfo.isFmp4) { 56304 this.trigger('fmp4'); 56305 segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start; 56306 } else { 56307 var trackInfo = this.getCurrentMediaInfo_(); 56308 var useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo; 56309 var firstVideoFrameTimeForData; 56310 56311 if (useVideoTimingInfo) { 56312 firstVideoFrameTimeForData = segmentInfo.videoTimingInfo.start; 56313 } // Segment loader knows more about segment timing than the transmuxer (in certain 56314 // aspects), so make any changes required for a more accurate start time. 56315 // Don't set the end time yet, as the segment may not be finished processing. 56316 56317 56318 segmentInfo.timingInfo.start = this.trueSegmentStart_({ 56319 currentStart: segmentInfo.timingInfo.start, 56320 playlist: segmentInfo.playlist, 56321 mediaIndex: segmentInfo.mediaIndex, 56322 currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(), 56323 useVideoTimingInfo: useVideoTimingInfo, 56324 firstVideoFrameTimeForData: firstVideoFrameTimeForData, 56325 videoTimingInfo: segmentInfo.videoTimingInfo, 56326 audioTimingInfo: segmentInfo.audioTimingInfo 56327 }); 56328 } // Init segments for audio and video only need to be appended in certain cases. Now 56329 // that data is about to be appended, we can check the final cases to determine 56330 // whether we should append an init segment. 56331 56332 56333 this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info, 56334 // as we use the start of the segment to offset the best guess (playlist provided) 56335 // timestamp offset. 56336 56337 this.updateSourceBufferTimestampOffset_(segmentInfo); // if this is a sync request we need to determine whether it should 56338 // be appended or not. 56339 56340 if (segmentInfo.isSyncRequest) { 56341 // first save/update our timing info for this segment. 56342 // this is what allows us to choose an accurate segment 56343 // and the main reason we make a sync request. 56344 this.updateTimingInfoEnd_(segmentInfo); 56345 this.syncController_.saveSegmentTimingInfo({ 56346 segmentInfo: segmentInfo, 56347 shouldSaveTimelineMapping: this.loaderType_ === 'main' 56348 }); 56349 var next = this.chooseNextRequest_(); // If the sync request isn't the segment that would be requested next 56350 // after taking into account its timing info, do not append it. 56351 56352 if (next.mediaIndex !== segmentInfo.mediaIndex || next.partIndex !== segmentInfo.partIndex) { 56353 this.logger_('sync segment was incorrect, not appending'); 56354 return; 56355 } // otherwise append it like any other segment as our guess was correct. 56356 56357 56358 this.logger_('sync segment was correct, appending'); 56359 } // Save some state so that in the future anything waiting on first append (and/or 56360 // timestamp offset(s)) can process immediately. While the extra state isn't optimal, 56361 // we need some notion of whether the timestamp offset or other relevant information 56362 // has had a chance to be set. 56363 56364 56365 segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags. 56366 56367 this.processMetadataQueue_(); 56368 this.appendData_(segmentInfo, result); 56369 }; 56370 56371 _proto.updateAppendInitSegmentStatus = function updateAppendInitSegmentStatus(segmentInfo, type) { 56372 // alt audio doesn't manage timestamp offset 56373 if (this.loaderType_ === 'main' && typeof segmentInfo.timestampOffset === 'number' && // in the case that we're handling partial data, we don't want to append an init 56374 // segment for each chunk 56375 !segmentInfo.changedTimestampOffset) { 56376 // if the timestamp offset changed, the timeline may have changed, so we have to re- 56377 // append init segments 56378 this.appendInitSegment_ = { 56379 audio: true, 56380 video: true 56381 }; 56382 } 56383 56384 if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) { 56385 // make sure we append init segment on playlist changes, in case the media config 56386 // changed 56387 this.appendInitSegment_[type] = true; 56388 } 56389 }; 56390 56391 _proto.getInitSegmentAndUpdateState_ = function getInitSegmentAndUpdateState_(_ref4) { 56392 var type = _ref4.type, 56393 initSegment = _ref4.initSegment, 56394 map = _ref4.map, 56395 playlist = _ref4.playlist; // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section 56396 // (Section 3) required to parse the applicable Media Segments. It applies to every 56397 // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag 56398 // or until the end of the playlist." 56399 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5 56400 56401 if (map) { 56402 var id = initSegmentId(map); 56403 56404 if (this.activeInitSegmentId_ === id) { 56405 // don't need to re-append the init segment if the ID matches 56406 return null; 56407 } // a map-specified init segment takes priority over any transmuxed (or otherwise 56408 // obtained) init segment 56409 // 56410 // this also caches the init segment for later use 56411 56412 56413 initSegment = this.initSegmentForMap(map, true).bytes; 56414 this.activeInitSegmentId_ = id; 56415 }
56415 // We used to always prepend init segments for video, however, that shouldn't be 56416 // necessary. Instead, we should only append on changes, similar to what we've always 56417 // done for audio. This is more important (though may not be that important) for 56418 // frame-by-frame appending for LHLS, simply because of the increased quantity of 56419 // appends. 56420 56421 56422 if (initSegment && this.appendInitSegment_[type]) { 56423 // Make sure we track the playlist that we last used for the init segment, so that 56424 // we can re-append the init segment in the event that we get data from a new 56425 // playlist. Discontinuities and track changes are handled in other sections. 56426 this.playlistOfLastInitSegment_[type] = playlist; // Disable future init segment appends for this type. Until a change is necessary. 56427 56428 this.appendInitSegment_[type] = false; // we need to clear out the fmp4 active init segment id, since 56429 // we are appending the muxer init segment 56430 56431 this.activeInitSegmentId_ = null; 56432 return initSegment; 56433 } 56434 56435 return null; 56436 }; 56437 56438 _proto.handleQuotaExceededError_ = function handleQuotaExceededError_(_ref5, error) { 56439 var _this3 = this; 56440 56441 var segmentInfo = _ref5.segmentInfo, 56442 type = _ref5.type, 56443 bytes = _ref5.bytes; 56444 var audioBuffered = this.sourceUpdater_.audioBuffered(); 56445 var videoBuffered = this.sourceUpdater_.videoBuffered(); // For now we're ignoring any notion of gaps in the buffer, but they, in theory, 56446 // should be cleared out during the buffer removals. However, log in case it helps 56447 // debug. 56448 56449 if (audioBuffered.length > 1) { 56450 this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the audio buffer: ' + timeRangesToArray(audioBuffered).join(', ')); 56451 } 56452 56453 if (videoBuffered.length > 1) { 56454 this.logger_('On QUOTA_EXCEEDED_ERR, found gaps in the video buffer: ' + timeRangesToArray(videoBuffered).join(', ')); 56455 } 56456 56457 var audioBufferStart = audioBuffered.length ? audioBuffered.start(0) : 0; 56458 var audioBufferEnd = audioBuffered.length ? audioBuffered.end(audioBuffered.length - 1) : 0; 56459 var videoBufferStart = videoBuffered.length ? videoBuffered.start(0) : 0; 56460 var videoBufferEnd = videoBuffered.length ? videoBuffered.end(videoBuffered.length - 1) : 0; 56461 56462 if (audioBufferEnd - audioBufferStart <= MIN_BACK_BUFFER && videoBufferEnd - videoBufferStart <= MIN_BACK_BUFFER) { 56463 // Can't remove enough buffer to make room for new segment (or the browser doesn't 56464 // allow for appends of segments this size). In the future, it may be possible to 56465 // split up the segment and append in pieces, but for now, error out this playlist 56466 // in an attempt to switch to a more manageable rendition. 56467 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(', ') + ", ")); 56468 this.error({ 56469 message: 'Quota exceeded error with append of a single segment of content', 56470 excludeUntil: Infinity 56471 }); 56472 this.trigger('error'); 56473 return; 56474 } // To try to resolve the quota exceeded error, clear back buffer and retry. This means 56475 // that the segment-loader should block on future events until this one is handled, so 56476 // that it doesn't keep moving onto further segments. Adding the call to the call 56477 // queue will prevent further appends until waitingOnRemove_ and 56478 // quotaExceededErrorRetryTimeout_ are cleared. 56479 // 56480 // Note that this will only block the current loader. In the case of demuxed content, 56481 // the other load may keep filling as fast as possible. In practice, this should be 56482 // OK, as it is a rare case when either audio has a high enough bitrate to fill up a 56483 // source buffer, or video fills without enough room for audio to append (and without 56484 // the availability of clearing out seconds of back buffer to make room for audio). 56485 // But it might still be good to handle this case in the future as a TODO. 56486 56487 56488 this.waitingOnRemove_ = true; 56489 this.callQueue_.push(this.appendToSourceBuffer_.bind(this, { 56490 segmentInfo: segmentInfo, 56491 type: type, 56492 bytes: bytes 56493 })); 56494 var currentTime = this.currentTime_(); // Try to remove as much audio and video as possible to make room for new content 56495 // before retrying. 56496 56497 var timeToRemoveUntil = currentTime - MIN_BACK_BUFFER; 56498 this.logger_("On QUOTA_EXCEEDED_ERR, removing audio/video from 0 to " + timeToRemoveUntil); 56499 this.remove(0, timeToRemoveUntil, function () { 56500 _this3.logger_("On QUOTA_EXCEEDED_ERR, retrying append in " + MIN_BACK_BUFFER + "s"); 56501 56502 _this3.waitingOnRemove_ = false; // wait the length of time alotted in the back buffer to prevent wasted 56503 // attempts (since we can't clear less than the minimum) 56504 56505 _this3.quotaExceededErrorRetryTimeout_ = window_1.setTimeout(function () { 56506 _this3.logger_('On QUOTA_EXCEEDED_ERR, re-processing call queue'); 56507 56508 _this3.quotaExceededErrorRetryTimeout_ = null; 56509 56510 _this3.processCallQueue_(); 56511 }, MIN_BACK_BUFFER * 1000); 56512 }, true); 56513 }; 56514 56515 _proto.handleAppendError_ = function handleAppendError_(_ref6, error) { 56516 var segmentInfo = _ref6.segmentInfo, 56517 type = _ref6.type, 56518 bytes = _ref6.bytes; // if there's no error, nothing to do 56519 56520 if (!error) { 56521 return; 56522 } 56523 56524 if (error.code === QUOTA_EXCEEDED_ERR) { 56525 this.handleQuotaExceededError_({ 56526 segmentInfo: segmentInfo, 56527 type: type, 56528 bytes: bytes 56529 }); // A quota exceeded error should be recoverable with a future re-append, so no need 56530 // to trigger an append error. 56531 56532 return; 56533 } 56534 56535 this.logger_('Received non QUOTA_EXCEEDED_ERR on append', error); 56536 this.error(type + " append of " + bytes.length + "b failed for segment " + ("#" + segmentInfo.mediaIndex + " in playlist " + segmentInfo.playlist.id)); // If an append errors, we often can't recover. 56537 // (see https://w3c.github.io/media-source/#sourcebuffer-append-error). 56538 // 56539 // Trigger a special error so that it can be handled separately from normal, 56540 // recoverable errors. 56541 56542 this.trigger('appenderror'); 56543 }; 56544 56545 _proto.appendToSourceBuffer_ = function appendToSourceBuffer_(_ref7) { 56546 var segmentInfo = _ref7.segmentInfo, 56547 type = _ref7.type, 56548 initSegment = _ref7.initSegment, 56549 data = _ref7.data, 56550 bytes = _ref7.bytes; // If this is a re-append, bytes were already created and don't need to be recreated 56551 56552 if (!bytes) { 56553 var segments = [data]; 56554 var byteLength = data.byteLength; 56555 56556 if (initSegment) { 56557 // if the media initialization segment is changing, append it before the content 56558 // segment 56559 segments.unshift(initSegment); 56560 byteLength += initSegment.byteLength; 56561 } // Technically we should be OK appending the init segment separately, however, we
56562 // haven't yet tested that, and prepending is how we have always done things. 56563 56564 56565 bytes = concatSegments({ 56566 bytes: byteLength, 56567 segments: segments 56568 }); 56569 } 56570 56571 this.sourceUpdater_.appendBuffer({ 56572 segmentInfo: segmentInfo, 56573 type: type, 56574 bytes: bytes 56575 }, this.handleAppendError_.bind(this, { 56576 segmentInfo: segmentInfo, 56577 type: type, 56578 bytes: bytes 56579 })); 56580 }; 56581 56582 _proto.handleSegmentTimingInfo_ = function handleSegmentTimingInfo_(type, requestId, segmentTimingInfo) { 56583 if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) { 56584 return; 56585 } 56586 56587 var segment = this.pendingSegment_.segment; 56588 var timingInfoProperty = type + "TimingInfo"; 56589 56590 if (!segment[timingInfoProperty]) { 56591 segment[timingInfoProperty] = {}; 56592 } 56593 56594 segment[timingInfoProperty].transmuxerPrependedSeconds = segmentTimingInfo.prependedContentDuration || 0; 56595 segment[timingInfoProperty].transmuxedPresentationStart = segmentTimingInfo.start.presentation; 56596 segment[timingInfoProperty].transmuxedDecodeStart = segmentTimingInfo.start.decode; 56597 segment[timingInfoProperty].transmuxedPresentationEnd = segmentTimingInfo.end.presentation; 56598 segment[timingInfoProperty].transmuxedDecodeEnd = segmentTimingInfo.end.decode; // mainly used as a reference for debugging 56599 56600 segment[timingInfoProperty].baseMediaDecodeTime = segmentTimingInfo.baseMediaDecodeTime; 56601 }; 56602 56603 _proto.appendData_ = function appendData_(segmentInfo, result) { 56604 var type = result.type, 56605 data = result.data; 56606 56607 if (!data || !data.byteLength) { 56608 return; 56609 } 56610 56611 if (type === 'audio' && this.audioDisabled_) { 56612 return; 56613 } 56614 56615 var initSegment = this.getInitSegmentAndUpdateState_({ 56616 type: type, 56617 initSegment: result.initSegment, 56618 playlist: segmentInfo.playlist, 56619 map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null 56620 }); 56621 this.appendToSourceBuffer_({ 56622 segmentInfo: segmentInfo, 56623 type: type, 56624 initSegment: initSegment, 56625 data: data 56626 }); 56627 } 56628 /** 56629 * load a specific segment from a request into the buffer 56630 * 56631 * @private 56632 */ 56633 ; 56634 56635 _proto.loadSegment_ = function loadSegment_(segmentInfo) { 56636 var _this4 = this; 56637 56638 this.state = 'WAITING'; 56639 this.pendingSegment_ = segmentInfo; 56640 this.trimBackBuffer_(segmentInfo); 56641 56642 if (typeof segmentInfo.timestampOffset === 'number') { 56643 if (this.transmuxer_) { 56644 this.transmuxer_.postMessage({ 56645 action: 'clearAllMp4Captions' 56646 }); 56647 } 56648 } 56649 56650 if (!this.hasEnoughInfoToLoad_()) { 56651 this.loadQueue_.push(function () { 56652 // regenerate the audioAppendStart, timestampOffset, etc as they 56653 // may have changed since this function was added to the queue. 56654 var options = _extends_1({}, segmentInfo, { 56655 forceTimestampOffset: true 56656 }); 56657 56658 _extends_1(segmentInfo, _this4.generateSegmentInfo_(options)); 56659 56660 _this4.isPendingTimestampOffset_ = false; 56661 56662 _this4.updateTransmuxerAndRequestSegment_(segmentInfo); 56663 }); 56664 return; 56665 } 56666 56667 this.updateTransmuxerAndRequestSegment_(segmentInfo); 56668 }; 56669 56670 _proto.updateTransmuxerAndRequestSegment_ = function updateTransmuxerAndRequestSegment_(segmentInfo) { 56671 var _this5 = this; // We'll update the source buffer's timestamp offset once we have transmuxed data, but 56672 // the transmuxer still needs to be updated before then. 56673 // 56674 // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp 56675 // offset must be passed to the transmuxer for stream correcting adjustments. 56676 56677 56678 if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) { 56679 this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared 56680 56681 segmentInfo.gopsToAlignWith = []; 56682 this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh 56683 56684 this.transmuxer_.postMessage({ 56685 action: 'reset' 56686 }); 56687 this.transmuxer_.postMessage({ 56688 action: 'setTimestampOffset', 56689 timestampOffset: segmentInfo.timestampOffset 56690 }); 56691 } 56692 56693 var simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
56694 var isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex); 56695 var isWalkingForward = this.mediaIndex !== null; 56696 var isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ && // currentTimeline starts at -1, so we shouldn't end the timeline switching to 0, 56697 // the first timeline 56698 segmentInfo.timeline > 0; 56699 var isEndOfTimeline = isEndOfStream || isWalkingForward && isDiscontinuity; 56700 this.logger_("Requesting " + segmentInfoString(segmentInfo)); // If there's an init segment associated with this segment, but it is not cached (identified by a lack of bytes), 56701 // then this init segment has never been seen before and should be appended. 56702 // 56703 // At this point the content type (audio/video or both) is not yet known, but it should be safe to set 56704 // both to true and leave the decision of whether to append the init segment to append time. 56705 56706 if (simpleSegment.map && !simpleSegment.map.bytes) { 56707 this.logger_('going to request init segment.'); 56708 this.appendInitSegment_ = { 56709 video: true, 56710 audio: true 56711 }; 56712 } 56713 56714 segmentInfo.abortRequests = mediaSegmentRequest({ 56715 xhr: this.vhs_.xhr, 56716 xhrOptions: this.xhrOptions_, 56717 decryptionWorker: this.decrypter_, 56718 segment: simpleSegment, 56719 abortFn: this.handleAbort_.bind(this, segmentInfo), 56720 progressFn: this.handleProgress_.bind(this), 56721 trackInfoFn: this.handleTrackInfo_.bind(this), 56722 timingInfoFn: this.handleTimingInfo_.bind(this), 56723 videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'video', segmentInfo.requestId), 56724 audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'audio', segmentInfo.requestId), 56725 captionsFn: this.handleCaptions_.bind(this), 56726 isEndOfTimeline: isEndOfTimeline, 56727 endedTimelineFn: function endedTimelineFn() { 56728 _this5.logger_('received endedtimeline callback'); 56729 }, 56730 id3Fn: this.handleId3_.bind(this), 56731 dataFn: this.handleData_.bind(this), 56732 doneFn: this.segmentRequestFinished_.bind(this), 56733 onTransmuxerLog: function onTransmuxerLog(_ref8) { 56734 var message = _ref8.message, 56735 level = _ref8.level, 56736 stream = _ref8.stream; 56737 56738 _this5.logger_(segmentInfoString(segmentInfo) + " logged from transmuxer stream " + stream + " as a " + level + ": " + message); 56739 } 56740 }); 56741 } 56742 /** 56743 * trim the back buffer so that we don't have too much data 56744 * in the source buffer 56745 * 56746 * @private 56747 * 56748 * @param {Object} segmentInfo - the current segment 56749 */ 56750 ; 56751 56752 _proto.trimBackBuffer_ = function trimBackBuffer_(segmentInfo) { 56753 var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of 56754 // buffer and a very conservative "garbage collector" 56755 // We manually clear out the old buffer to ensure 56756 // we don't trigger the QuotaExceeded error 56757 // on the source buffer during subsequent appends 56758 56759 if (removeToTime > 0) { 56760 this.remove(0, removeToTime); 56761 } 56762 } 56763 /** 56764 * created a simplified copy of the segment object with just the 56765 * information necessary to perform the XHR and decryption 56766 * 56767 * @private 56768 * 56769 * @param {Object} segmentInfo - the current segment 56770 * @return {Object} a simplified segment object copy 56771 */ 56772 ; 56773 56774 _proto.createSimplifiedSegmentObj_ = function createSimplifiedSegmentObj_(segmentInfo) { 56775 var segment = segmentInfo.segment; 56776 var part = segmentInfo.part; 56777 var simpleSegment = { 56778 resolvedUri: part ? part.resolvedUri : segment.resolvedUri, 56779 byterange: part ? part.byterange : segment.byterange, 56780 requestId: segmentInfo.requestId, 56781 transmuxer: segmentInfo.transmuxer, 56782 audioAppendStart: segmentInfo.audioAppendStart, 56783 gopsToAlignWith: segmentInfo.gopsToAlignWith, 56784 part: segmentInfo.part 56785 }; 56786 var previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex - 1]; 56787 56788 if (previousSegment && previousSegment.timeline === segment.timeline) { 56789 // The baseStartTime of a segment is used to handle rollover when probing the TS 56790 // segment to retrieve timing information. Since the probe only looks at the media's 56791 // times (e.g., PTS and DTS values of the segment), and doesn't consider the 56792 // player's time (e.g., player.currentTime()), baseStartTime should reflect the 56793 // media time as well. transmuxedDecodeEnd represents the end time of a segment, in 56794 // seconds of media time, so should be used here. The previous segment is used since 56795 // the end of the previous segment should represent the beginning of the current 56796 // segment, so long as they are on the same timeline. 56797 if (previousSegment.videoTimingInfo) { 56798 simpleSegment.baseStartTime = previousSegment.videoTimingInfo.transmuxedDecodeEnd; 56799 } else if (previousSegment.audioTimingInfo) { 56800 simpleSegment.baseStartTime = previousSegment.audioTimingInfo.transmuxedDecodeEnd; 56801 } 56802 } 56803 56804 if (segment.key) { 56805 // if the media sequence is greater than 2^32, the IV will be incorrect 56806 // assuming 10s segments, that would be about 1300 years 56807 var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 56808 simpleSegment.key = this.segmentKey(segment.key); 56809 simpleSegment.key.iv = iv; 56810 } 56811 56812 if (segment.map) { 56813 simpleSegment.map = this.initSegmentForMap(segment.map); 56814 } 56815 56816 return simpleSegment; 56817 }; 56818 56819 _proto.saveTransferStats_ = function saveTransferStats_(stats) { 56820 // every request counts as a media request even if it has been aborted 56821 // or canceled due to a timeout 56822 this.mediaRequests += 1; 56823 56824 if (stats) { 56825 this.mediaBytesTransferred += stats.bytesReceived; 56826 this.mediaTransferDuration += stats.roundTripTime; 56827 } 56828 }; 56829 56830 _proto.saveBandwidthRelatedStats_ = function saveBandwidthRelatedStats_(duration, stats) { 56831 // byteLength will be used for throughput, and should be based on bytes receieved, 56832 // which we only know at the end of the request and should reflect total bytes 56833 // downloaded rather than just bytes processed from components of the segment 56834 this.pendingSegment_.byteLength = stats.bytesReceived; 56835 56836 if (duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) { 56837 this.logger_("Ignoring segment's bandwidth because its duration of " + duration + (" is less than the min to record " + MIN_SEGMENT_DURATION_TO_SAVE_STATS)); 56838 return; 56839 } 56840 56841 this.bandwidth = stats.bandwidth; 56842 this.roundTrip = stats.roundTripTime; 56843 }; 56844 56845 _proto.handleTimeout_ = function handleTimeout_() { 56846 // although the VTT segment loader bandwidth isn't really used, it's good to 56847 // maintain functinality between segment loaders 56848 this.mediaRequestsTimedout += 1; 56849 this.bandwidth = 1; 56850 this.roundTrip = NaN; 56851 this.trigger('bandwidthupdate'); 56852 } 56853 /** 56854 * Handle the callback from the segmentRequest function and set the 56855 * associated SegmentLoader state and errors if necessary 56856 * 56857 * @private 56858 */ 56859 ; 56860 56861 _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) { 56862 // TODO handle special cases, e.g., muxed audio/video but only audio in the segment 56863 // check the call queue directly since this function doesn't need to deal with any 56864 // data, and can continue even if the source buffers are not set up and we didn't get 56865 // any data from the segment 56866 if (this.callQueue_.length) { 56867 this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result)); 56868 return; 56869 } 56870 56871 this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset 56872 56873 if (!this.pendingSegment_) { 56874 return; 56875 } // the request was aborted and the SegmentLoader has already started 56876 // another request. this can happen when the timeout for an aborted 56877 // request triggers due to a limitation in the XHR library 56878 // do not count this as any sort of request or we risk double-counting 56879 56880 56881 if (simpleSegment.requestId !== this.pendingSegment_.requestId) { 56882 return; 56883 } // an error occurred from the active pendingSegment_ so reset everything 56884 56885 56886 if (error) { 56887 this.pendingSegment_ = null; 56888 this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done 56889 56890 if (error.code === REQUEST_ERRORS.ABORTED) { 56891 return; 56892 } 56893 56894 this.pause(); // the error is really just that at least one of the requests timed-out 56895 // set the bandwidth to a very low value and trigger an ABR switch to 56896 // take emergency action 56897 56898 if (error.code === REQUEST_ERRORS.TIMEOUT) { 56899 this.handleTimeout_(); 56900 return; 56901 } // if control-flow has arrived here, then the error is real 56902 // emit an error event to blacklist the current playlist 56903 56904 56905 this.mediaRequestsErrored += 1;
56906 this.error(error); 56907 this.trigger('error'); 56908 return; 56909 } 56910 56911 var segmentInfo = this.pendingSegment_; // the response was a success so set any bandwidth stats the request 56912 // generated for ABR purposes 56913 56914 this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats); 56915 segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; 56916 56917 if (result.gopInfo) { 56918 this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_); 56919 } // Although we may have already started appending on progress, we shouldn't switch the 56920 // state away from loading until we are officially done loading the segment data. 56921 56922 56923 this.state = 'APPENDING'; // used for testing 56924 56925 this.trigger('appending'); 56926 this.waitForAppendsToComplete_(segmentInfo); 56927 }; 56928 56929 _proto.setTimeMapping_ = function setTimeMapping_(timeline) { 56930 var timelineMapping = this.syncController_.mappingForTimeline(timeline); 56931 56932 if (timelineMapping !== null) { 56933 this.timeMapping_ = timelineMapping; 56934 } 56935 }; 56936 56937 _proto.updateMediaSecondsLoaded_ = function updateMediaSecondsLoaded_(segment) { 56938 if (typeof segment.start === 'number' && typeof segment.end === 'number') { 56939 this.mediaSecondsLoaded += segment.end - segment.start; 56940 } else { 56941 this.mediaSecondsLoaded += segment.duration; 56942 } 56943 }; 56944 56945 _proto.shouldUpdateTransmuxerTimestampOffset_ = function shouldUpdateTransmuxerTimestampOffset_(timestampOffset) { 56946 if (timestampOffset === null) { 56947 return false; 56948 } // note that we're potentially using the same timestamp offset for both video and 56949 // audio 56950 56951 56952 if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) { 56953 return true; 56954 } 56955 56956 if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) { 56957 return true; 56958 } 56959 56960 return false; 56961 }; 56962 56963 _proto.trueSegmentStart_ = function trueSegmentStart_(_ref9) { 56964 var currentStart = _ref9.currentStart, 56965 playlist = _ref9.playlist, 56966 mediaIndex = _ref9.mediaIndex, 56967 firstVideoFrameTimeForData = _ref9.firstVideoFrameTimeForData, 56968 currentVideoTimestampOffset = _ref9.currentVideoTimestampOffset, 56969 useVideoTimingInfo = _ref9.useVideoTimingInfo, 56970 videoTimingInfo = _ref9.videoTimingInfo, 56971 audioTimingInfo = _ref9.audioTimingInfo; 56972 56973 if (typeof currentStart !== 'undefined') { 56974 // if start was set once, keep using it 56975 return currentStart; 56976 } 56977 56978 if (!useVideoTimingInfo) { 56979 return audioTimingInfo.start; 56980 } 56981 56982 var previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame c
56982ontained 56983 // within that segment. Since the transmuxer maintains a cache of incomplete data 56984 // from and/or the last frame seen, the start time may reflect a frame that starts 56985 // in the previous segment. Check for that case and ensure the start time is 56986 // accurate for the segment. 56987 56988 if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) { 56989 return firstVideoFrameTimeForData; 56990 } 56991 56992 return videoTimingInfo.start; 56993 }; 56994 56995 _proto.waitForAppendsToComplete_ = function waitForAppendsToComplete_(segmentInfo) { 56996 var trackInfo = this.getCurrentMediaInfo_(segmentInfo); 56997 56998 if (!trackInfo) { 56999 this.error({ 57000 message: 'No starting media returned, likely due to an unsupported media format.', 57001 blacklistDuration: Infinity 57002 }); 57003 this.trigger('error'); 57004 return; 57005 } // Although transmuxing is done, appends may not yet be finished. Throw a marker 57006 // on each queue this loader is responsible for to ensure that the appends are 57007 // complete. 57008 57009 57010 var hasAudio = trackInfo.hasAudio, 57011 hasVideo = trackInfo.hasVideo, 57012 isMuxed = trackInfo.isMuxed; 57013 var waitForVideo = this.loaderType_ === 'main' && hasVideo; 57014 var waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed; 57015 segmentInfo.waitingOnAppends = 0; // segments with no data 57016 57017 if (!segmentInfo.hasAppendedData_) { 57018 if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') { 57019 // When there's no audio or video data in the segment, there's no audio or video 57020 // timing information. 57021 // 57022 // If there's no audio or video timing information, then the timestamp offset 57023 // can't be adjusted to the appropriate value for the transmuxer and source 57024 // buffers. 57025 // 57026 // Therefore, the next segment should be used to set the timestamp offset. 57027 this.isPendingTimestampOffset_ = true; 57028 } // override settings for metadata only segments 57029 57030 57031 segmentInfo.timingInfo = { 57032 start: 0 57033 }; 57034 segmentInfo.waitingOnAppends++; 57035 57036 if (!this.isPendingTimestampOffset_) { 57037 // update the timestampoffset 57038 this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have 57039 // no video/audio data. 57040 57041 this.processMetadataQueue_(); 57042 } // append is "done" instantly with no data. 57043 57044 57045 this.checkAppendsDone_(segmentInfo); 57046 return; 57047 } // Since source updater could call back synchronously, do the increments first. 57048 57049 57050 if (waitForVideo) { 57051 segmentInfo.waitingOnAppends++; 57052 } 57053 57054 if (waitForAudio) { 57055 segmentInfo.waitingOnAppends++; 57056 } 57057 57058 if (waitForVideo) { 57059 this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo)); 57060 } 57061 57062 if (waitForAudio) { 57063 this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo)); 57064 } 57065 }; 57066 57067 _proto.checkAppendsDone_ = function checkAppendsDone_(segmentInfo) { 57068 if (this.checkForAbort_(segmentInfo.requestId)) { 57069 return; 57070 } 57071 57072 segmentInfo.waitingOnAppends--; 57073 57074 if (segmentInfo.waitingOnAppends === 0) { 57075 this.handleAppendsDone_(); 57076 } 57077 }; 57078 57079 _proto.checkForIllegalMediaSwitch = function checkForIllegalMediaSwitch(trackInfo) { 57080 var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.getCurrentMediaInfo_(), trackInfo); 57081 57082 if (illegalMediaSwitchError) { 57083 this.error({ 57084 message: illegalMediaSwitchError, 57085 blacklistDuration: Infinity 57086 }); 57087 this.trigger('error'); 57088 return true; 57089 } 57090 57091 return false; 57092 }; 57093 57094 _proto.updateSourceBufferTimestampOffset_ = function updateSourceBufferTimestampOffset_(segmentInfo) { 57095 if (segmentInfo.timestampOffset === null || // we don't yet have the start for whatever media type (video or audio) has 57096 // priority, timing-wise, so we must wait 57097 typeof segmentInfo.timingInfo.start !== 'number' || // already updated the timestamp offset for this segment 57098 segmentInfo.changedTimestampOffset || // the alt audio loader should not be responsible for setting the timestamp offset 57099 this.loaderType_ !== 'main') { 57100 return; 57101 } 57102 57103 var didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that 57104 // the timing info here comes from video. In the event that the audio is longer than 57105 // the video, this will trim the start of the audio.
57106 // This also trims any offset from 0 at the beginning of the media 57107 57108 segmentInfo.timestampOffset -= segmentInfo.timingInfo.start; // In the event that there are part segment downloads, each will try to update the 57109 // timestamp offset. Retaining this bit of state prevents us from updating in the 57110 // future (within the same segment), however, there may be a better way to handle it. 57111 57112 segmentInfo.changedTimestampOffset = true; 57113 57114 if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) { 57115 this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset); 57116 didChange = true; 57117 } 57118 57119 if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) { 57120 this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset); 57121 didChange = true; 57122 } 57123 57124 if (didChange) { 57125 this.trigger('timestampoffset'); 57126 } 57127 }; 57128 57129 _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_(segmentInfo) { 57130 segmentInfo.timingInfo = segmentInfo.timingInfo || {}; 57131 var trackInfo = this.getMediaInfo_(); 57132 var useVideoTimingInfo = this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo; 57133 var prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo; 57134 57135 if (!prioritizedTimingInfo) { 57136 return; 57137 } 57138 57139 segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ? // End time may not exist in a case where we aren't parsing the full segment (one 57140 // current example is the case of fmp4), so use the rough duration to calculate an 57141 // end time. 57142 prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration; 57143 } 57144 /** 57145 * callback to run when appendBuffer is finished. detects if we are 57146 * in a good state to do things with the data we got, or if we need 57147 * to wait for more 57148 * 57149 * @private 57150 */ 57151 ; 57152 57153 _proto.handleAppendsDone_ = function handleAppendsDone_() { 57154 // appendsdone can cause an abort 57155 if (this.pendingSegment_) { 57156 this.trigger('appendsdone'); 57157 } 57158 57159 if (!this.pendingSegment_) { 57160 this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in 57161 // all appending cases? 57162 57163 if (!this.paused()) { 57164 this.monitorBuffer_(); 57165 } 57166 57167 return; 57168 } 57169 57170 var segmentInfo = this.pendingSegment_; // Now that the end of the segment has been reached, we can set the end time. It's 57171 // best to wait until all appends are done so we're sure that the primary media is 57172 // finished (and we have its end time). 57173 57174 this.updateTimingInfoEnd_(segmentInfo); 57175 57176 if (this.shouldSaveSegmentTimingInfo_) { 57177 // Timeline mappings should only be saved for the main loader. This is for multiple 57178 // reasons: 57179 // 57180 // 1) Only one mapping is saved per timeline, meaning that if both the audio loader 57181 // and the main loader try to save the timeline mapping, whichever comes later 57182 // will overwrite the first. In theory this is OK, as the mappings should be the 57183 // same, however, it breaks for (2) 57184 // 2) In the event of a live stream, the initial live point will make for a somewhat 57185 // arbitrary mapping. If audio and video streams are not perfectly in-sync, then 57186 // the mapping will be off for one of the streams, dependent on which one was 57187 // first saved (see (1)). 57188 // 3) Primary timing goes by video in VHS, so the mapping should be video. 57189 // 57190 // Since the audio loader will wait for the main loader to load the first segment, 57191 // the main loader will save the first timeline mapping, and ensure that there won't 57192 // be a case where audio loads two segments without saving a mapping (thus leading 57193 // to missing segment timing info). 57194 this.syncController_.saveSegmentTimingInfo({ 57195 segmentInfo: segmentInfo, 57196 shouldSaveTimelineMapping: this.loaderType_ === 'main' 57197 }); 57198 } 57199 57200 var segmentDurationMessage = getTroublesomeSegmentDurationMessage(segmentInfo, this.sourceType_); 57201 57202 if (segmentDurationMessage) { 57203 if (segmentDurationMessage.severity === 'warn') { 57204 videojs.log.warn(segmentDurationMessage.message); 57205 } else { 57206 this.logger_(segmentDurationMessage.message); 57207 } 57208 } 57209 57210 this.recordThroughput_(segmentInfo); 57211 this.pendingSegment_ = null; 57212 this.state = 'READY'; 57213 57214 if (segmentInfo.isSyncRequest) { 57215 this.trigger('syncinfoupdate'); // if the sync request was not appended 57216 // then it was not the correct segment. 57217 // throw it away and use the data it gave us 57218 // to get the correct one. 57219 57220 if (!segmentInfo.hasAppendedData_) { 57221 this.logger_("Throwing away un-appended sync request " + segmentInfoString(segmentInfo)); 57222 return; 57223 } 57224 } 57225 57226 this.logger_("Appended " + segmentInfoString(segmentInfo)); 57227 this.addSegmentMetadataCue_(segmentInfo); 57228 this.fetchAtBuffer_ = true; 57229 57230 if (this.currentTimeline_ !== segmentInfo.timeline) { 57231 this.timelineChangeController_.lastTimelineChange({ 57232 type: this.loaderType_, 57233 from: this.currentTimeline_,
57234 to: segmentInfo.timeline 57235 }); // If audio is not disabled, the main segment loader is responsible for updating 57236 // the audio timeline as well. If the content is video only, this won't have any 57237 // impact. 57238 57239 if (this.loaderType_ === 'main' && !this.audioDisabled_) { 57240 this.timelineChangeController_.lastTimelineChange({ 57241 type: 'audio', 57242 from: this.currentTimeline_, 57243 to: segmentInfo.timeline 57244 }); 57245 } 57246 } 57247 57248 this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before 57249 // the following conditional otherwise it may consider this a bad "guess" 57250 // and attempt to resync when the post-update seekable window and live 57251 // point would mean that this was the perfect segment to fetch 57252 57253 this.trigger('syncinfoupdate'); 57254 var segment = segmentInfo.segment; // If we previously appended a segment that ends more than 3 targetDurations before 57255 // the currentTime_ that means that our conservative guess was too conservative. 57256 // In that case, reset the loader state so that we try to use any information gained 57257 // from the previous request to create a new, more accurate, sync-point. 57258 57259 if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) { 57260 this.resetEverything(); 57261 return; 57262 } 57263 57264 var isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment 57265 // and conservatively guess 57266 57267 if (isWalkingForward) { 57268 this.trigger('bandwidthupdate'); 57269 } 57270 57271 this.trigger('progress'); 57272 this.mediaIndex = segmentInfo.mediaIndex; 57273 this.partIndex = segmentInfo.partIndex; // any time an update finishes and the last segment is in the 57274 // buffer, end the stream. this ensures the "ended" event will 57275 // fire if playback reaches that point. 57276 57277 if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist, segmentInfo.partIndex)) { 57278 this.endOfStream(); 57279 } // used for testing 57280 57281 57282 this.trigger('appended'); 57283 57284 if (segmentInfo.hasAppendedData_) { 57285 this.mediaAppends++; 57286 } 57287 57288 if (!this.paused()) { 57289 this.monitorBuffer_(); 57290 } 57291 } 57292 /** 57293 * Records the current throughput of the decrypt, transmux, and append 57294 * portion of the semgment pipeline. `throughput.rate` is a the cumulative 57295 * moving average of the throughput. `throughput.count` is the number of 57296 * data points in the average. 57297 * 57298 * @private 57299 * @param {Object} segmentInfo the object returned by loadSegment 57300 */ 57301 ; 57302 57303 _proto.recordThroughput_ = function recordThroughput_(segmentInfo) { 57304 if (segmentInfo.duration < MIN_SEGMENT_DURATION_TO_SAVE_STATS) { 57305 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)); 57306 return; 57307 } 57308 57309 var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide 57310 // by zero in the case where the throughput is ridiculously high 57311 57312 var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second 57313 57314 var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation: 57315 // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1) 57316 57317 this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count; 57318 } 57319 /** 57320 * Adds a cue to the segment-metadata track with some metadata information about the 57321 * segment 57322 * 57323 * @private 57324 * @param {Object} segmentInfo 57325 * the object returned by loadSegment 57326 * @method addSegmentMetadataCue_ 57327 */ 57328 ; 57329 57330 _proto.addSegmentMetadataCue_ = function addSegmentMetadataCue_(segmentInfo) { 57331 if (!this.segmentMetadataTrack_) { 57332 return; 57333 } 57334 57335 var segment = segmentInfo.segment; 57336 var start = segment.start; 57337 var end = segment.end;
57337 // Do not try adding the cue if the start and end times are invalid. 57338 57339 if (!finite(start) || !finite(end)) { 57340 return; 57341 } 57342 57343 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 57344 var Cue = window_1.WebKitDataCue || window_1.VTTCue; 57345 var value = { 57346 custom: segment.custom, 57347 dateTimeObject: segment.dateTimeObject, 57348 dateTimeString: segment.dateTimeString, 57349 bandwidth: segmentInfo.playlist.attributes.BANDWIDTH, 57350 resolution: segmentInfo.playlist.attributes.RESOLUTION, 57351 codecs: segmentInfo.playlist.attributes.CODECS, 57352 byteLength: segmentInfo.byteLength, 57353 uri: segmentInfo.uri, 57354 timeline: segmentInfo.timeline, 57355 playlist: segmentInfo.playlist.id, 57356 start: start, 57357 end: end 57358 }; 57359 var data = JSON.stringify(value); 57360 var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between 57361 // the differences of WebKitDataCue in safari and VTTCue in other browsers 57362 57363 cue.value = value; 57364 this.segmentMetadataTrack_.addCue(cue); 57365 }; 57366 57367 return SegmentLoader; 57368 }(videojs.EventTarget); 57369 57370 function noop() {} 57371 57372 var toTitleCase = function toTitleCase(string) { 57373 if (typeof string !== 'string') { 57374 return string; 57375 } 57376 57377 return string.replace(/./, function (w) { 57378 return w.toUpperCase(); 57379 }); 57380 }; 57381 57382 var bufferTypes = ['video', 'audio']; 57383 57384 var _updating = function updating(type, sourceUpdater) { 57385 var sourceBuffer = sourceUpdater[type + "Buffer"]; 57386 return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type]; 57387 }; 57388 57389 var nextQueueIndexOfType = function nextQueueIndexOfType(type, queue) { 57390 for (var i = 0; i < queue.length; i++) { 57391 var queueEntry = queue[i]; 57392 57393 if (queueEntry.type === 'mediaSource') { 57394 // If the next entry is a media source entry (uses multiple source buffers), block 57395 // processing to allow it to go through first. 57396 return null; 57397 } 57398 57399 if (queueEntry.type === type) { 57400 return i; 57401 } 57402 } 57403 57404 return null; 57405 }; 57406 57407 var shiftQueue = function shiftQueue(type, sourceUpdater) { 57408 if (sourceUpdater.queue.length === 0) { 57409 return; 57410 } 57411 57412 var queueIndex = 0; 57413 var queueEntry = sourceUpdater.queue[queueIndex]; 57414 57415 if (queueEntry.type === 'mediaSource') { 57416 if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') { 57417 sourceUpdater.queue.shift(); 57418 queueEntry.action(sourceUpdater); 57419 57420 if (queueEntry.doneFn) { 57421 queueEntry.doneFn(); 57422 } // Only specific source buffer actions must wait for async updateend events. Media 57423 // Source actions process synchronously. Therefore, both audio and video source 57424 // buffers are now clear to process the next queue entries. 57425 57426 57427 shiftQueue('audio', sourceUpdater); 57428 shiftQueue('video', sourceUpdater); 57429 } // Media Source actions require both source buffers, so if the media source action 57430 // couldn't process yet (because one or both source buffers are busy), block other 57431 // queue actions until both are available and the media source action can process. 57432 57433 57434 return; 57435 } 57436 57437 if (type === 'mediaSource') { 57438 // If the queue was shifted by a media source action (this happens when pushing a 57439 // media source action onto the queue), then it wasn't from an updateend event from an 57440 // audio or video source buffer, so there's no change from previous state, and no 57441 // processing should be done. 57442 return; 57443 } // Media source queue entries don't need to consider whether the source updater is 57444 // started (i.e., source buffers are created) as they don't need the source buffers, but 57445 // source buffer queue entries do. 57446 57447 57448 if (!sourceUpdater.ready() || sourceUpdater.mediaSource.readyState === 'closed' || _updating(type, sourceUpdater)) { 57449 return; 57450 } 57451 57452 if (queueEntry.type !== type) { 57453 queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue); 57454 57455 if (queueIndex === null) {
57456 // Either there's no queue entry that uses this source buffer type in the queue, or 57457 // there's a media source queue entry before the next entry of this type, in which 57458 // case wait for that action to process first. 57459 return; 57460 } 57461 57462 queueEntry = sourceUpdater.queue[queueIndex]; 57463 } 57464 57465 sourceUpdater.queue.splice(queueIndex, 1); // Keep a record that this source buffer type is in use. 57466 // 57467 // The queue pending operation must be set before the action is performed in the event 57468 // that the action results in a synchronous event that is acted upon. For instance, if 57469 // an exception is thrown that can be handled, it's possible that new actions will be 57470 // appended to an empty queue and immediately executed, but would not have the correct 57471 // pending information if this property was set after the action was performed. 57472 57473 sourceUpdater.queuePending[type] = queueEntry; 57474 queueEntry.action(type, sourceUpdater); 57475 57476 if (!queueEntry.doneFn) { 57477 // synchronous operation, process next entry 57478 sourceUpdater.queuePending[type] = null; 57479 shiftQueue(type, sourceUpdater); 57480 return; 57481 } 57482 }; 57483 57484 var cleanupBuffer = function cleanupBuffer(type, sourceUpdater) { 57485 var buffer = sourceUpdater[type + "Buffer"]; 57486 var titleType = toTitleCase(type); 57487 57488 if (!buffer) { 57489 return; 57490 } 57491 57492 buffer.removeEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]); 57493 buffer.removeEventListener('error', sourceUpdater["on" + titleType + "Error_"]); 57494 sourceUpdater.codecs[type] = null; 57495 sourceUpdater[type + "Buffer"] = null; 57496 }; 57497 57498 var inSourceBuffers = function inSourceBuffers(mediaSource, sourceBuffer) { 57499 return mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1; 57500 }; 57501 57502 var actions = { 57503 appendBuffer: function appendBuffer(bytes, segmentInfo, onError) { 57504 return function (type, sourceUpdater) { 57505 var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null 57506 // or the media source does not contain this source buffer. 57507 57508 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 57509 return; 57510 } 57511 57512 sourceUpdater.logger_("Appending segment " + segmentInfo.mediaIndex + "'s " + bytes.length + " bytes to " + type + "Buffer"); 57513 57514 try { 57515 sourceBuffer.appendBuffer(bytes); 57516 } catch (e) { 57517 sourceUpdater.logger_("Error with code " + e.code + " " + (e.code === QUOTA_EXCEEDED_ERR ? '(QUOTA_EXCEEDED_ERR) ' : '') + ("when appending segment " + segmentInfo.mediaIndex + " to " + type + "Buffer")); 57518 sourceUpdater.queuePending[type] = null; 57519 onError(e); 57520 } 57521 }; 57522 }, 57523 remove: function remove(start, end) { 57524 return function (type, sourceUpdater) { 57525 var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null 57526 // or the media source does not contain this source buffer. 57527 57528 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 57529 return; 57530 } 57531 57532 sourceUpdater.logger_("Removing " + start + " to " + end + " from " + type + "Buffer"); 57533 57534 try { 57535 sourceBuffer.remove(start, end); 57536 } catch (e) { 57537 sourceUpdater.logger_("Remove " + start + " to " + end + " from " + type + "Buffer failed"); 57538 } 57539 }; 57540 }, 57541 timestampOffset: function timestampOffset(offset) { 57542 return function (type, sourceUpdater) { 57543 var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null 57544 // or the media source does not contain this source buffer. 57545 57546 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 57547 return; 57548 } 57549 57550 sourceUpdater.logger_("Setting " + type + "timestampOffset to " + offset); 57551 sourceBuffer.timestampOffset = offset; 57552 }; 57553 }, 57554 callback: function callback(_callback) { 57555 return function (type, sourceUpdater) { 57556 _callback(); 57557 }; 57558 },
57559 endOfStream: function endOfStream(error) { 57560 return function (sourceUpdater) { 57561 if (sourceUpdater.mediaSource.readyState !== 'open') { 57562 return; 57563 } 57564 57565 sourceUpdater.logger_("Calling mediaSource endOfStream(" + (error || '') + ")"); 57566 57567 try { 57568 sourceUpdater.mediaSource.endOfStream(error); 57569 } catch (e) { 57570 videojs.log.warn('Failed to call media source endOfStream', e); 57571 } 57572 }; 57573 }, 57574 duration: function duration(_duration) { 57575 return function (sourceUpdater) { 57576 sourceUpdater.logger_("Setting mediaSource duration to " + _duration); 57577 57578 try { 57579 sourceUpdater.mediaSource.duration = _duration; 57580 } catch (e) { 57581 videojs.log.warn('Failed to set media source duration', e); 57582 } 57583 }; 57584 }, 57585 abort: function abort() { 57586 return function (type, sourceUpdater) { 57587 if (sourceUpdater.mediaSource.readyState !== 'open') { 57588 return; 57589 } 57590 57591 var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null 57592 // or the media source does not contain this source buffer. 57593 57594 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 57595 return; 57596 } 57597 57598 sourceUpdater.logger_("calling abort on " + type + "Buffer"); 57599 57600 try { 57601 sourceBuffer.abort(); 57602 } catch (e) { 57603 videojs.log.warn("Failed to abort on " + type + "Buffer", e); 57604 } 57605 }; 57606 }, 57607 addSourceBuffer: function addSourceBuffer(type, codec) { 57608 return function (sourceUpdater) { 57609 var titleType = toTitleCase(type); 57610 var mime = getMimeForCodec(codec); 57611 sourceUpdater.logger_("Adding " + type + "Buffer with codec " + codec + " to mediaSource"); 57612 var sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime); 57613 sourceBuffer.addEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]); 57614 sourceBuffer.addEventListener('error', sourceUpdater["on" + titleType + "Error_"]); 57615 sourceUpdater.codecs[type] = codec; 57616 sourceUpdater[type + "Buffer"] = sourceBuffer; 57617 }; 57618 }, 57619 removeSourceBuffer: function removeSourceBuffer(type) { 57620 return function (sourceUpdater) { 57621 var sourceBuffer = sourceUpdater[type + "Buffer"]; 57622 cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null 57623 // or the media source does not contain this source buffer. 57624 57625 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 57626 return; 57627 } 57628 57629 sourceUpdater.logger_("Removing " + type + "Buffer with codec " + sourceUpdater.codecs[type] + " from mediaSource"); 57630 57631 try { 57632 sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer); 57633 } catch (e) { 57634 videojs.log.warn("Failed to removeSourceBuffer " + type + "Buffer", e); 57635 } 57636 }; 57637 }, 57638 changeType: function changeType(codec) { 57639 return function (type, sourceUpdater) { 57640 var sourceBuffer = sourceUpdater[type + "Buffer"]; 57641 var mime = getMimeForCodec(codec); // can't do anything if the media source / source buffer is null 57642 // or the media source does not contain this source buffer. 57643 57644 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 57645 return; 57646 } // do not update codec if we don't need to. 57647 57648 57649 if (sourceUpdater.codecs[type] === codec) { 57650 return; 57651 } 57652 57653 sourceUpdater.logger_("changing " + type + "Buffer codec from " + sourceUpdater.codecs[type] + " to " + codec); 57654 sourceBuffer.changeType(mime); 57655 sourceUpdater.codecs[type] = codec; 57656 }; 57657 } 57658 }; 57659 57660 var pushQueue = function pushQueue(_ref) { 57661 var type = _ref.type, 57662 sourceUpdater = _ref.sourceUpdater, 57663 action = _ref.action, 57664 doneFn = _ref.doneFn, 57665 name = _ref.name; 57666 sourceUpdater.queue.push({ 57667 type: type, 57668 action: action, 57669 doneFn: doneFn, 57670 name: name 57671 });
57672 shiftQueue(type, sourceUpdater); 57673 }; 57674 57675 var onUpdateend = function onUpdateend(type, sourceUpdater) { 57676 return function (e) { 57677 // Although there should, in theory, be a pending action for any updateend receieved, 57678 // there are some actions that may trigger updateend events without set definitions in 57679 // the w3c spec. For instance, setting the duration on the media source may trigger 57680 // updateend events on source buffers. This does not appear to be in the spec. As such, 57681 // if we encounter an updateend without a corresponding pending action from our queue 57682 // for that source buffer type, process the next action. 57683 if (sourceUpdater.queuePending[type]) { 57684 var doneFn = sourceUpdater.queuePending[type].doneFn; 57685 sourceUpdater.queuePending[type] = null; 57686 57687 if (doneFn) { 57688 // if there's an error, report it 57689 doneFn(sourceUpdater[type + "Error_"]); 57690 } 57691 } 57692 57693 shiftQueue(type, sourceUpdater); 57694 }; 57695 }; 57696 /** 57697 * A queue of callbacks to be serialized and applied when a 57698 * MediaSource and its associated SourceBuffers are not in the 57699 * updating state. It is used by the segment loader to update the 57700 * underlying SourceBuffers when new data is loaded, for instance. 57701 * 57702 * @class SourceUpdater 57703 * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from 57704 * @param {string} mimeType the desired MIME type of the underlying SourceBuffer 57705 */ 57706 57707 57708 var SourceUpdater = /*#__PURE__*/function (_videojs$EventTarget) { 57709 inheritsLoose(SourceUpdater, _videojs$EventTarget); 57710 57711 function SourceUpdater(mediaSource) { 57712 var _this; 57713 57714 _this = _videojs$EventTarget.call(this) || this; 57715 _this.mediaSource = mediaSource; 57716 57717 _this.sourceopenListener_ = function () { 57718 return shiftQueue('mediaSource', assertThisInitialized(_this)); 57719 }; 57720 57721 _this.mediaSource.addEventListener('sourceopen', _this.sourceopenListener_); 57722 57723 _this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0 57724 57725 _this.audioTimestampOffset_ = 0; 57726 _this.videoTimestampOffset_ = 0; 57727 _this.queue = []; 57728 _this.queuePending = { 57729 audio: null, 57730 video: null 57731 }; 57732 _this.delayedAudioAppendQueue_ = []; 57733 _this.videoAppendQueued_ = false; 57734 _this.codecs = {}; 57735 _this.onVideoUpdateEnd_ = onUpdateend('video', assertThisInitialized(_this)); 57736 _this.onAudioUpdateEnd_ = onUpdateend('audio', assertThisInitialized(_this)); 57737 57738 _this.onVideoError_ = function (e) { 57739 // used for debugging 57740 _this.videoError_ = e; 57741 }; 57742 57743 _this.onAudioError_ = function (e) { 57744 // used for debugging 57745 _this.audioError_ = e; 57746 }; 57747 57748 _this.createdSourceBuffers_ = false; 57749 _this.initializedEme_ = false; 57750 _this.triggeredReady_ = false; 57751 return _this; 57752 } 57753 57754 var _proto = SourceUpdater.prototype; 57755 57756 _proto.initializedEme = function initializedEme() { 57757 this.initializedEme_ = true; 57758 this.triggerReady(); 57759 }; 57760 57761 _proto.hasCreatedSourceBuffers = function hasCreatedSourceBuffers() { 57762 // if false, likely waiting on one of the segment loaders to get enough data to create 57763 // source buffers 57764 return this.createdSourceBuffers_; 57765 }; 57766 57767 _proto.hasInitializedAnyEme = function hasInitializedAnyEme() { 57768 return this.initializedEme_; 57769 }; 57770 57771 _proto.ready = function ready() { 57772 return this.hasCreatedSourceBuffers() && this.hasInitializedAnyEme(); 57773 }; 57774 57775 _proto.createSourceBuffers = function createSourceBuffers(codecs) { 57776 if (this.hasCreatedSourceBuffers()) { 57777 // already created them before 57778 return; 57779 } // the intial addOrChangeSourceBuffers will always be 57780 // two add buffers. 57781 57782 57783 this.addOrChangeSourceBuffers(codecs); 57784 this.createdSourceBuffers_ = true; 57785 this.trigger('createdsourcebuffers'); 57786 this.triggerReady(); 57787 }; 57788 57789 _proto.triggerReady = function triggerReady() { 57790 // only allow ready to be triggered once, this prevents the case 57791 // where: 57792 // 1. we trigger createdsourcebuffers
57793 // 2. ie 11 synchronously initializates eme 57794 // 3. the synchronous initialization causes us to trigger ready 57795 // 4. We go back to the ready check in createSourceBuffers and ready is triggered again. 57796 if (this.ready() && !this.triggeredReady_) { 57797 this.triggeredReady_ = true; 57798 this.trigger('ready'); 57799 } 57800 } 57801 /** 57802 * Add a type of source buffer to the media source. 57803 * 57804 * @param {string} type 57805 * The type of source buffer to add. 57806 * 57807 * @param {string} codec 57808 * The codec to add the source buffer with. 57809 */ 57810 ; 57811 57812 _proto.addSourceBuffer = function addSourceBuffer(type, codec) { 57813 pushQueue({ 57814 type: 'mediaSource', 57815 sourceUpdater: this, 57816 action: actions.addSourceBuffer(type, codec), 57817 name: 'addSourceBuffer' 57818 }); 57819 } 57820 /** 57821 * call abort on a source buffer. 57822 * 57823 * @param {string} type 57824 * The type of source buffer to call abort on. 57825 */ 57826 ; 57827 57828 _proto.abort = function abort(type) { 57829 pushQueue({ 57830 type: type, 57831 sourceUpdater: this, 57832 action: actions.abort(type), 57833 name: 'abort' 57834 }); 57835 } 57836 /** 57837 * Call removeSourceBuffer and remove a specific type 57838 * of source buffer on the mediaSource. 57839 * 57840 * @param {string} type 57841 * The type of source buffer to remove. 57842 */ 57843 ; 57844 57845 _proto.removeSourceBuffer = function removeSourceBuffer(type) { 57846 if (!this.canRemoveSourceBuffer()) { 57847 videojs.log.error('removeSourceBuffer is not supported!'); 57848 return; 57849 } 57850 57851 pushQueue({ 57852 type: 'mediaSource', 57853 sourceUpdater: this, 57854 action: actions.removeSourceBuffer(type), 57855 name: 'removeSourceBuffer' 57856 }); 57857 } 57858 /** 57859 * Whether or not the removeSourceBuffer function is supported 57860 * on the mediaSource. 57861 * 57862 * @return {boolean} 57863 * if removeSourceBuffer can be called. 57864 */ 57865 ; 57866 57867 _proto.canRemoveSourceBuffer = function canRemoveSourceBuffer() { 57868 // IE reports that it supports removeSourceBuffer, but often throws 57869 // errors when attempting to use the function. So we report that it 57870 // does not support removeSourceBuffer. As of Firefox 83 removeSourceBuffer 57871 // throws errors, so we report that it does not support this as well. 57872 return !videojs.browser.IE_VERSION && !videojs.browser.IS_FIREFOX && window_1.MediaSource && window_1.MediaSource.prototype && typeof window_1.MediaSource.prototype.removeSourceBuffer === 'function'; 57873 } 57874 /** 57875 * Whether or not the changeType function is supported 57876 * on our SourceBuffers. 57877 * 57878 * @return {boolean} 57879 * if changeType can be called. 57880 */ 57881 ; 57882 57883 SourceUpdater.canChangeType = function canChangeType() { 57884 return window_1.SourceBuffer && window_1.SourceBuffer.prototype && typeof window_1.SourceBuffer.prototype.changeType === 'function'; 57885 } 57886 /** 57887 * Whether or not the changeType function is supported 57888 * on our SourceBuffers. 57889 * 57890 * @return {boolean} 57891 * if changeType can be called. 57892 */ 57893 ; 57894 57895 _proto.canChangeType = function canChangeType() { 57896 return this.constructor.canChangeType(); 57897 } 57898 /** 57899 * Call the changeType function on a source buffer, given the code and type. 57900 * 57901 * @param {string} type 57902 * The type of source buffer to call changeType on. 57903 * 57904 * @param {string} codec 57905 * The codec string to change type with on the source buffer. 57906 */ 57907 ; 57908 57909 _proto.changeType = function changeType(type, codec) { 57910 if (!this.canChangeType()) { 57911 videojs.log.error('changeType is not supported!'); 57912 return; 57913 } 57914 57915 pushQueue({ 57916 type: type, 57917 sourceUpdater: this, 57918 action: actions.changeType(codec), 57919 name: 'changeType' 57920 }); 57921 } 57922 /** 57923 * Add source buffers with a codec or, if they are already created, 57924 * call changeType on source buffers using changeType. 57925 * 57926 * @param {Object} codecs 57927 * Codecs to switch to 57928 */ 57929 ; 57930 57931 _proto.addOrChangeSourceBuffers = function addOrChangeSourceBuffers(codecs) { 57932 var _this2 = this; 57933 57934 if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) { 57935 throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs'); 57936 } 57937
57938 Object.keys(codecs).forEach(function (type) { 57939 var codec = codecs[type]; 57940 57941 if (!_this2.hasCreatedSourceBuffers()) { 57942 return _this2.addSourceBuffer(type, codec); 57943 } 57944 57945 if (_this2.canChangeType()) { 57946 _this2.changeType(type, codec); 57947 } 57948 }); 57949 } 57950 /** 57951 * Queue an update to append an ArrayBuffer. 57952 * 57953 * @param {MediaObject} object containing audioBytes and/or videoBytes 57954 * @param {Function} done the function to call when done 57955 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 57956 */ 57957 ; 57958 57959 _proto.appendBuffer = function appendBuffer(options, doneFn) { 57960 var _this3 = this; 57961 57962 var segmentInfo = options.segmentInfo, 57963 type = options.type, 57964 bytes = options.bytes; 57965 this.processedAppend_ = true; 57966 57967 if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) { 57968 this.delayedAudioAppendQueue_.push([options, doneFn]); 57969 this.logger_("delayed audio append of " + bytes.length + " until video append"); 57970 return; 57971 } // In the case of certain errors, for instance, QUOTA_EXCEEDED_ERR, updateend will 57972 // not be fired. This means that the queue will be blocked until the next action 57973 // taken by the segment-loader. Provide a mechanism for segment-loader to handle 57974 // these errors by calling the doneFn with the specific error. 57975 57976 57977 var onError = doneFn; 57978 pushQueue({ 57979 type: type, 57980 sourceUpdater: this, 57981 action: actions.appendBuffer(bytes, segmentInfo || { 57982 mediaIndex: -1 57983 }, onError), 57984 doneFn: doneFn, 57985 name: 'appendBuffer' 57986 }); 57987 57988 if (type === 'video') { 57989 this.videoAppendQueued_ = true; 57990 57991 if (!this.delayedAudioAppendQueue_.length) { 57992 return; 57993 } 57994 57995 var queue = this.delayedAudioAppendQueue_.slice(); 57996 this.logger_("queuing delayed audio " + queue.length + " appendBuffers"); 57997 this.delayedAudioAppendQueue_.length = 0; 57998 queue.forEach(function (que) { 57999 _this3.appendBuffer.apply(_this3, que); 58000 }); 58001 } 58002 } 58003 /** 58004 * Get the audio buffer's buffered timerange. 58005 * 58006 * @return {TimeRange} 58007 * The audio buffer's buffered time range 58008 */ 58009 ; 58010 58011 _proto.audioBuffered = function audioBuffered() { 58012 // no media source/source buffer or it isn't in the media sources 58013 // source buffer list 58014 if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) { 58015 return videojs.createTimeRange(); 58016 } 58017 58018 return this.audioBuffer.buffered ? this.audioBuffer.buffered : videojs.createTimeRange(); 58019 } 58020 /** 58021 * Get the video buffer's buffered timerange. 58022 * 58023 * @return {TimeRange} 58024 * The video buffer's buffered time range 58025 */ 58026 ; 58027 58028 _proto.videoBuffered = function videoBuffered() { 58029 // no media source/source buffer or it isn't in the media sources 58030 // source buffer list 58031 if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) { 58032 return videojs.createTimeRange(); 58033 } 58034 58035 return this.videoBuffer.buffered ? this.videoBuffer.buffered : videojs.createTimeRange(); 58036 } 58037 /** 58038 * Get a combined video/audio buffer's buffered timerange. 58039 * 58040 * @return {TimeRange} 58041 * the combined time range 58042 */ 58043 ; 58044 58045 _proto.buffered = function buffered() { 58046 var video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null; 58047 var audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null; 58048 58049 if (audio && !video) { 58050 return this.audioBuffered(); 58051 } 58052 58053 if (video && !audio) { 58054 return this.videoBuffered(); 58055 } 58056 58057 return bufferIntersection(this.audioBuffered(), this.videoBuffered()); 58058 } 58059 /** 58060 * Add a callback to the queue that will set duration on the mediaSource. 58061 * 58062 * @param {number} duration 58063 * The duration to set 58064 * 58065 * @param {Function} [doneFn] 58066 * function to run after duration has been set. 58067 */ 58068 ; 58069 58070 _proto.setDuration = function setDuration(duration, doneFn) { 58071 if (doneFn === void 0) { 58072 doneFn = noop; 58073 } // In order to set the duration on the media source, it's necessary to wait for all 58074 // source buffers to no longer be updating. "If the updating attribute equals true on 58075 // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and 58076 // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes). 58077 58078 58079 pushQueue({ 58080 type: 'mediaSource', 58081 sourceUpdater: this, 58082 action: actions.duration(duration), 58083 name: 'duration', 58084 doneFn: doneFn 58085 }); 58086 } 58087 /** 58088 * Add a mediaSource endOfStream call to the queue 58089 * 58090 * @param {Error} [error] 58091 * Call endOfStream with an error 58092 * 58093 * @param {Function} [doneFn] 58094 * A function that should be called when the 58095 * endOfStream call has finished. 58096 */ 58097 ; 58098 58099 _proto.endOfStream = function endOfStream(error, doneFn) { 58100 if (error === void 0) { 58101 error = null; 58102 } 58103 58104 if (doneFn === void 0) { 58105 doneFn = noop; 58106 } 58107 58108 if (typeof error !== 'string') { 58109 error = undefined; 58110 } // In order to set the duration on the media source, it's necessary to wait for all 58111 // source buffers to no longer be updating. "If the updating attribute equals true on 58112 // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and 58113 // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes). 58114 58115 58116 pushQueue({ 58117 type: 'mediaSource', 58118 sourceUpdater: this, 58119 action: actions.endOfStream(error), 58120 name: 'endOfStream', 58121 doneFn: doneFn 58122 }); 58123 } 58124 /** 58125 * Queue an update to remove a time range from the buffer. 58126 * 58127 * @param {number} start where to start the removal 58128 * @param {number} end where to end the removal 58129 * @param {Function} [done=noop] optional callback to be executed when the remove 58130 * operation is complete 58131 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 58132 */ 58133 ; 58134 58135 _proto.removeAudio = function removeAudio(start, end, done) { 58136 if (done === void 0) { 58137 done = noop; 58138 } 58139 58140 if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) { 58141 done(); 58142 return; 58143 } 58144 58145 pushQueue({ 58146 type: 'audio', 58147 sourceUpdater: this, 58148 action: actions.remove(start, end), 58149 doneFn: done, 58150 name: 'remove' 58151 }); 58152 } 58153 /** 58154 * Queue an update to remove a time range from the buffer. 58155 * 58156 * @param {number} start where to start the removal 58157 * @param {number} end where to end the removal 58158 * @param {Function} [done=noop] optional callback to be executed when the remove 58159 * operation is complete 58160 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 58161 */ 58162 ; 58163 58164 _proto.removeVideo = function removeVideo(start, end, done) { 58165 if (done === void 0) { 58166 done = noop; 58167 } 58168 58169 if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) { 58170 done(); 58171 return; 58172 } 58173 58174 pushQueue({ 58175 type: 'video', 58176 sourceUpdater: this, 58177 action: actions.remove(start, end), 58178 doneFn: done, 58179 name: 'remove' 58180 }); 58181 } 58182 /** 58183 * Whether the underlying sourceBuffer is updating or not 58184 * 58185 * @return {boolean} the updating status of the SourceBuffer 58186 */ 58187 ; 58188 58189 _proto.updating = function updating() { 58190 // the audio/video source buffer is updating 58191 if (_updating('audio', this) || _updating('video', this)) { 58192 return true; 58193 } 58194 58195 return false; 58196 } 58197 /** 58198 * Set/get the timestampoffset on the audio SourceBuffer 58199 * 58200 * @return {number} the timestamp offset 58201 */ 58202 ; 58203 58204 _proto.audioTimestampOffset = function audioTimestampOffset(offset) { 58205 if (typeof offset !== 'undefined' && this.audioBuffer && // no point in updating if it's the same 58206 this.audioTimestampOffset_ !== offset) { 58207 pushQueue({ 58208 type: 'audio', 58209 sourceUpdater: this, 58210 action: actions.timestampOffset(offset), 58211 name: 'timestampOffset' 58212 }); 58213 this.audioTimestampOffset_ = offset; 58214 } 58215 58216 return this.audioTimestampOffset_; 58217 } 58218 /** 58219 * Set/get the timestampoffset on the video SourceBuffer 58220 * 58221 * @return {number} the timestamp offset 58222 */ 58223 ; 58224 58225 _proto.videoTimestampOffset = function videoTimestampOffset(offset) { 58226 if (typeof offset !== 'undefined' && this.videoBuffer && // no point in updating if it's the same 58227 this.videoTimestampOffset !== offset) { 58228 pushQueue({ 58229 type: 'video', 58230 sourceUpdater: this, 58231 action: actions.timestampOffset(offset), 58232 name: 'timestampOffset' 58233 }); 58234 this.videoTimestampOffset_ = offset; 58235 } 58236 58237 return this.videoTimestampOffset_; 58238 } 58239 /** 58240 * Add a function to the queue that will be called 58241 * when it is its turn to run in the audio queue. 58242 * 58243 * @param {Function} callback 58244 * The callback to queue. 58245 */ 58246 ; 58247 58248 _proto.audioQueueCallback = function audioQueueCallback(callback) { 58249 if (!this.audioBuffer) { 58250 return; 58251 } 58252 58253 pushQueue({ 58254 type: 'audio', 58255 sourceUpdater: this, 58256 action: actions.callback(callback), 58257 name: 'callback' 58258 }); 58259 } 58260 /** 58261 * Add a function to the queue that will be called 58262 * when it is its turn to run in the video queue. 58263 * 58264 * @param {Function} callback 58265 * The callback to queue. 58266 */ 58267 ; 58268 58269 _proto.videoQueueCallback = function videoQueueCallback(callback) { 58270 if (!this.videoBuffer) { 58271 return; 58272 } 58273 58274 pushQueue({ 58275 type: 'video', 58276 sourceUpdater: this, 58277 action: actions.callback(callback), 58278 name: 'callback' 58279 }); 58280 } 58281 /** 58282 * dispose of the source updater and the underlying sourceBuffer 58283 */ 58284 ; 58285 58286 _proto.dispose = function dispose() { 58287 var _this4 = this; 58288 58289 this.trigger('dispose');
58290 bufferTypes.forEach(function (type) { 58291 _this4.abort(type); 58292 58293 if (_this4.canRemoveSourceBuffer()) { 58294 _this4.removeSourceBuffer(type); 58295 } else { 58296 _this4[type + "QueueCallback"](function () { 58297 return cleanupBuffer(type, _this4); 58298 }); 58299 } 58300 }); 58301 this.videoAppendQueued_ = false; 58302 this.delayedAudioAppendQueue_.length = 0; 58303 58304 if (this.sourceopenListener_) { 58305 this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_); 58306 } 58307 58308 this.off(); 58309 }; 58310 58311 return SourceUpdater; 58312 }(videojs.EventTarget); 58313 58314 var uint8ToUtf8 = function uint8ToUtf8(uintArray) { 58315 return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray))); 58316 }; 58317 58318 var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char3) { 58319 return _char3.charCodeAt(0); 58320 })); 58321 /** 58322 * An object that manages segment loading and appending. 58323 * 58324 * @class VTTSegmentLoader 58325 * @param {Object} options required and optional options 58326 * @extends videojs.EventTarget 58327 */ 58328 58329 var VTTSegmentLoader = /*#__PURE__*/function (_SegmentLoader) { 58330 inheritsLoose(VTTSegmentLoader, _SegmentLoader); 58331 58332 function VTTSegmentLoader(settings, options) { 58333 var _this; 58334 58335 if (options === void 0) { 58336 options = {}; 58337 } 58338 58339 _this = _SegmentLoader.call(this, settings, options) || this; // SegmentLoader requires a MediaSource be specified or it will throw an error; 58340 // however, VTTSegmentLoader has no need of a media source, so delete the reference 58341 58342 _this.mediaSource_ = null; 58343 _this.subtitlesTrack_ = null; 58344 _this.loaderType_ = 'subtitle'; 58345 _this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks; // The VTT segment will have its own time mappings. Saving VTT segment timing info in 58346 // the sync controller leads to improper behavior. 58347 58348 _this.shouldSaveSegmentTimingInfo_ = false; 58349 return _this; 58350 } 58351 58352 var _proto = VTTSegmentLoader.prototype; 58353 58354 _proto.createTransmuxer_ = function createTransmuxer_() { 58355 // don't need to transmux any subtitles 58356 return null; 58357 } 58358 /** 58359 * Indicates which time ranges are buffered 58360 * 58361 * @return {TimeRange} 58362 * TimeRange object representing the current buffered ranges 58363 */ 58364 ; 58365 58366 _proto.buffered_ = function buffered_() { 58367 if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) { 58368 return videojs.createTimeRanges(); 58369 } 58370 58371 var cues = this.subtitlesTrack_.cues; 58372 var start = cues[0].startTime; 58373 var end = cues[cues.length - 1].startTime; 58374 return videojs.createTimeRanges([[start, end]]); 58375 } 58376 /** 58377 * Gets and sets init segment for the provided map 58378 * 58379 * @param {Object} map 58380 * The map object representing the init segment to get or set 58381 * @param {boolean=} set 58382 * If true, the init segment for the provided map should be saved 58383 * @return {Object} 58384 * map object for desired init segment 58385 */ 58386 ; 58387 58388 _proto.initSegmentForMap = function initSegmentForMap(map, set) { 58389 if (set === void 0) { 58390 set = false; 58391 } 58392 58393 if (!map) { 58394 return null; 58395 } 58396 58397 var id = initSegmentId(map); 58398 var storedMap = this.initSegments_[id]; 58399 58400 if (set && !storedMap && map.bytes) { 58401 // append WebVTT line terminators to the media initialization segment if it exists 58402 // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that 58403 // requires two or more WebVTT line terminators between the WebVTT header and the 58404 // rest of the file 58405 var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength; 58406 var combinedSegment = new Uint8Array(combinedByteLength); 58407 combinedSegment.set(map.bytes); 58408 combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength); 58409 this.initSegments_[id] = storedMap = { 58410 resolvedUri: map.resolvedUri, 58411 byterange: map.byterange, 58412 bytes: combinedSegment 58413 }; 58414 } 58415 58416 return storedMap || map; 58417 } 58418 /**
58419 * Returns true if all configuration required for loading is present, otherwise false. 58420 * 58421 * @return {boolean} True if the all configuration is ready for loading 58422 * @private 58423 */ 58424 ; 58425 58426 _proto.couldBeginLoading_ = function couldBeginLoading_() { 58427 return this.playlist_ && this.subtitlesTrack_ && !this.paused(); 58428 } 58429 /** 58430 * Once all the starting parameters have been specified, begin 58431 * operation. This method should only be invoked from the INIT 58432 * state. 58433 * 58434 * @private 58435 */ 58436 ; 58437 58438 _proto.init_ = function init_() { 58439 this.state = 'READY'; 58440 this.resetEverything(); 58441 return this.monitorBuffer_(); 58442 } 58443 /** 58444 * Set a subtitle track on the segment loader to add subtitles to 58445 * 58446 * @param {TextTrack=} track 58447 * The text track to add loaded subtitles to 58448 * @return {TextTrack} 58449 * Returns the subtitles track 58450 */ 58451 ; 58452 58453 _proto.track = function track(_track) { 58454 if (typeof _track === 'undefined') { 58455 return this.subtitlesTrack_; 58456 } 58457 58458 this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start 58459 // buffering now 58460 58461 if (this.state === 'INIT' && this.couldBeginLoading_()) { 58462 this.init_(); 58463 } 58464 58465 return this.subtitlesTrack_; 58466 } 58467 /** 58468 * Remove any data in the source buffer between start and end times 58469 * 58470 * @param {number} start - the start time of the region to remove from the buffer 58471 * @param {number} end - the end time of the region to remove from the buffer 58472 */ 58473 ; 58474 58475 _proto.remove = function remove(start, end) { 58476 removeCuesFromTrack(start, end, this.subtitlesTrack_); 58477 } 58478 /** 58479 * fill the buffer with segements unless the sourceBuffers are 58480 * currently updating 58481 * 58482 * Note: this function should only ever be called by monitorBuffer_ 58483 * and never directly 58484 * 58485 * @private 58486 */ 58487 ; 58488 58489 _proto.fillBuffer_ = function fillBuffer_() { 58490 var _this2 = this; // see if we need to begin loading immediately 58491 58492 58493 var segmentInfo = this.chooseNextRequest_(); 58494 58495 if (!segmentInfo) { 58496 return; 58497 } 58498 58499 if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) { 58500 // We don't have the timestamp offset that we need to sync subtitles. 58501 // Rerun on a timestamp offset or user interaction. 58502 var checkTimestampOffset = function checkTimestampOffset() { 58503 _this2.state = 'READY'; 58504 58505 if (!_this2.paused()) { 58506 // if not paused, queue a buffer check as soon as possible 58507 _this2.monitorBuffer_(); 58508 } 58509 }; 58510 58511 this.syncController_.one('timestampoffset', checkTimestampOffset); 58512 this.state = 'WAITING_ON_TIMELINE'; 58513 return; 58514 } 58515 58516 this.loadSegment_(segmentInfo); 58517 } // never set a timestamp offset for vtt segments. 58518 ; 58519 58520 _proto.timestampOffsetForSegment_ = function timestampOffsetForSegment_() { 58521 return null; 58522 }; 58523 58524 _proto.chooseNextRequest_ = function chooseNextRequest_() { 58525 return this.skipEmptySegments_(_SegmentLoader.prototype.chooseNextRequest_.call(this)); 58526 } 58527 /** 58528 * Prevents the segment loader from requesting segments we know contain no subtitles 58529 * by walking forward until we find the next segment that we don't know whether it is 58530 * empty or not. 58531 * 58532 * @param {Object} segmentInfo 58533 * a segment info object that describes the current segment 58534 * @return {Object} 58535 * a segment info object that describes the current segment 58536 */ 58537 ; 58538 58539 _proto.skipEmptySegments_ = function skipEmptySegments_(segmentInfo) { 58540 while (segmentInfo && segmentInfo.segment.empty) { 58541 // stop at the last possible segmentInfo 58542 if (segmentInfo.mediaIndex + 1 >= segmentInfo.playlist.segments.length) { 58543 segmentInfo = null; 58544 break; 58545 } 58546 58547 segmentInfo = this.generateSegmentInfo_({ 58548 playlist: segmentInfo.playlist, 58549 mediaIndex: segmentInfo.mediaIndex + 1, 58550 startOfSegment: segmentInfo.startOfSegment + segmentInfo.duration, 58551 isSyncRequest: segmentInfo.isSyncRequest 58552 }); 58553 } 58554 58555 return segmentInfo; 58556 }; 58557 58558 _proto.stopForError = function stopForError(error) { 58559 this.error(error); 58560 this.state = 'READY'; 58561 this.pause(); 58562 this.trigger('error'); 58563 } 58564 /** 58565 * append a decrypted segement to the SourceBuffer through a SourceUpdater 58566 * 58567 * @private 58568 */ 58569 ; 58570 58571 _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) { 58572 var _this3 = this; 58573 58574 if (!this.subtitlesTrack_) { 58575 this.state = 'READY'; 58576 return; 58577 } 58578 58579 this.saveTransferStats_(simpleSegment.stats); // the request was aborted 58580 58581 if (!this.pendingSegment_) { 58582 this.state = 'READY'; 58583 this.mediaRequestsAborted += 1;
58584 return; 58585 } 58586 58587 if (error) { 58588 if (error.code === REQUEST_ERRORS.TIMEOUT) { 58589 this.handleTimeout_(); 58590 } 58591 58592 if (error.code === REQUEST_ERRORS.ABORTED) { 58593 this.mediaRequestsAborted += 1; 58594 } else { 58595 this.mediaRequestsErrored += 1; 58596 } 58597 58598 this.stopForError(error); 58599 return; 58600 } 58601 58602 var segmentInfo = this.pendingSegment_; // although the VTT segment loader bandwidth isn't really used, it's good to 58603 // maintain functionality between segment loaders 58604 58605 this.saveBandwidthRelatedStats_(segmentInfo.duration, simpleSegment.stats); 58606 this.state = 'APPENDING'; // used for tests 58607 58608 this.trigger('appending'); 58609 var segment = segmentInfo.segment; 58610 58611 if (segment.map) { 58612 segment.map.bytes = simpleSegment.map.bytes; 58613 } 58614 58615 segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 58616 58617 if (typeof window_1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) { 58618 var loadHandler; 58619 58620 var errorHandler = function errorHandler() { 58621 _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler); 58622 58623 _this3.stopForError({ 58624 message: 'Error loading vtt.js' 58625 }); 58626 58627 return; 58628 }; 58629 58630 loadHandler = function loadHandler() { 58631 _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler); 58632 58633 _this3.segmentRequestFinished_(error, simpleSegment, result); 58634 }; 58635 58636 this.state = 'WAITING_ON_VTTJS'; 58637 this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler); 58638 this.subtitlesTrack_.tech_.one('vttjserror', errorHandler); 58639 return; 58640 } 58641 58642 segment.requested = true; 58643 58644 try { 58645 this.parseVTTCues_(segmentInfo); 58646 } catch (e) { 58647 this.stopForError({ 58648 message: e.message 58649 }); 58650 return; 58651 } 58652 58653 this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_); 58654 58655 if (segmentInfo.cues.length) { 58656 segmentInfo.timingInfo = { 58657 start: segmentInfo.cues[0].startTime, 58658 end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime 58659 }; 58660 } else { 58661 segmentInfo.timingInfo = { 58662 start: segmentInfo.startOfSegment, 58663 end: segmentInfo.startOfSegment + segmentInfo.duration 58664 }; 58665 } 58666 58667 if (segmentInfo.isSyncRequest) { 58668 this.trigger('syncinfoupdate'); 58669 this.pendingSegment_ = null; 58670 this.state = 'READY'; 58671 return; 58672 } 58673 58674 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 58675 this.mediaSecondsLoaded += segment.duration; // Create VTTCue instances for each cue in the new segment and add them to 58676 // the subtitle track 58677 58678 segmentInfo.cues.forEach(function (cue) { 58679 _this3.subtitlesTrack_.addCue(_this3.featuresNativeTextTracks_ ? new window_1.VTTCue(cue.startTime, cue.endTime, cue.text) : cue); 58680 }); // Remove any duplicate cues from the subtitle track. The WebVTT spec allows 58681 // cues to have identical time-intervals, but if the text is also identical 58682 // we can safely assume it is a duplicate that can be removed (ex. when a cue 58683 // "overlaps" VTT segments) 58684 58685 removeDuplicateCuesFromTrack(this.subtitlesTrack_); 58686 this.handleAppendsDone_(); 58687 }; 58688 58689 _proto.handleData_ = function handleData_() {// noop as we shouldn't be getting video/audio data captions 58690 // that we do not support here. 58691 }; 58692 58693 _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_() {// noop 58694 } 58695 /** 58696 * Uses the WebVTT parser to parse the segment response 58697 * 58698 * @param {Object} segmentInfo 58699 * a segment info object that describes the current segment 58700 * @private 58701 */ 58702 ; 58703 58704 _proto.parseVTTCues_ = function parseVTTCues_(segmentInfo) { 58705 var decoder; 58706 var decodeBytesToString = false; 58707 58708 if (typeof window_1.TextDecoder === 'function') { 58709 decoder = new window_1.TextDecoder('utf8'); 58710 } else { 58711 decoder = window_1.WebVTT.StringDecoder(); 58712 decodeBytesToString = true; 58713 } 58714 58715 var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, decoder); 58716 segmentInfo.cues = []; 58717 segmentInfo.timestampmap = { 58718 MPEGTS: 0, 58719 LOCAL: 0 58720 }; 58721 parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues); 58722 58723 parser.ontimestampmap = function (map) { 58724 segmentInfo.timestampmap = map; 58725 }; 58726 58727 parser.onparsingerror = function (error) { 58728 videojs.log.warn('Error encountered when parsing cues: ' + error.message); 58729 }; 58730 58731 if (segmentInfo.segment.map) { 58732 var mapData = segmentInfo.segment.map.bytes; 58733 58734 if (decodeBytesToString) { 58735 mapData = uint8ToUtf8(mapData); 58736 } 58737 58738 parser.parse(mapData); 58739 } 58740 58741 var segmentData = segmentInfo.bytes; 58742 58743 if (decodeBytesToString) { 58744 segmentData = uint8ToUtf8(segmentData); 58745 } 58746 58747 parser.parse(segmentData); 58748 parser.flush(); 58749 } 58750 /** 58751 * Updates the start and end times of any cues parsed by the WebVTT parser using 58752 * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping 58753 * from the SyncController 58754 * 58755 * @param {Object} segmentInfo 58756 * a segment info object that describes the current segment 58757 * @param {Object} mappingObj
58758 * object containing a mapping from TS to media time 58759 * @param {Object} playlist 58760 * the playlist object containing the segment 58761 * @private 58762 */ 58763 ; 58764 58765 _proto.updateTimeMapping_ = function updateTimeMapping_(segmentInfo, mappingObj, playlist) { 58766 var segment = segmentInfo.segment; 58767 58768 if (!mappingObj) { 58769 // If the sync controller does not have a mapping of TS to Media Time for the 58770 // timeline, then we don't have enough information to update the cue 58771 // start/end times 58772 return; 58773 } 58774 58775 if (!segmentInfo.cues.length) { 58776 // If there are no cues, we also do not have enough information to figure out 58777 // segment timing. Mark that the segment contains no cues so we don't re-request 58778 // an empty segment. 58779 segment.empty = true; 58780 return; 58781 } 58782 58783 var timestampmap = segmentInfo.timestampmap; 58784 var diff = timestampmap.MPEGTS / clock_1 - timestampmap.LOCAL + mappingObj.mapping; 58785 segmentInfo.cues.forEach(function (cue) { 58786 // First convert cue time to TS time using the timestamp-map provided within the vtt 58787 cue.startTime += diff; 58788 cue.endTime += diff; 58789 }); 58790 58791 if (!playlist.syncInfo) { 58792 var firstStart = segmentInfo.cues[0].startTime; 58793 var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; 58794 playlist.syncInfo = { 58795 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 58796 time: Math.min(firstStart, lastStart - segment.duration) 58797 }; 58798 } 58799 }; 58800 58801 return VTTSegmentLoader; 58802 }(SegmentLoader); 58803 /** 58804 * @file ad-cue-tags.js 58805 */ 58806 58807 /** 58808 * Searches for an ad cue that overlaps with the given mediaTime 58809 * 58810 * @param {Object} track 58811 * the track to find the cue for 58812 * 58813 * @param {number} mediaTime 58814 * the time to find the cue at 58815 * 58816 * @return {Object|null} 58817 * the found cue or null 58818 */ 58819 58820 58821 var findAdCue = function findAdCue(track, mediaTime) { 58822 var cues = track.cues; 58823 58824 for (var i = 0; i < cues.length; i++) { 58825 var cue = cues[i]; 58826 58827 if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) { 58828 return cue; 58829 } 58830 } 58831 58832 return null; 58833 }; 58834 58835 var updateAdCues = function updateAdCues(media, track, offset) { 58836 if (offset === void 0) { 58837 offset = 0; 58838 } 58839 58840 if (!media.segments) { 58841 return; 58842 } 58843 58844 var mediaTime = offset; 58845 var cue; 58846 58847 for (var i = 0; i < media.segments.length; i++) { 58848 var segment = media.segments[i]; 58849 58850 if (!cue) { 58851 // Since the cues will span for at least the segment duration, adding a fudge 58852 // factor of half segment duration will prevent duplicate cues from being 58853 // created when timing info is not exact (e.g. cue start time initialized 58854 // at 10.006677, but next call mediaTime is 10.003332 ) 58855 cue = findAdCue(track, mediaTime + segment.duration / 2); 58856 } 58857 58858 if (cue) { 58859 if ('cueIn' in segment) { 58860 // Found a CUE-IN so end the cue 58861 cue.endTime = mediaTime; 58862 cue.adEndTime = mediaTime; 58863 mediaTime += segment.duration; 58864 cue = null; 58865 continue; 58866 } 58867 58868 if (mediaTime < cue.endTime) { 58869 // Already processed this mediaTime for this cue 58870 mediaTime += segment.duration; 58871 continue; 58872 } // otherwise extend cue until a CUE-IN is found 58873 58874 58875 cue.endTime += segment.duration; 58876 } else { 58877 if ('cueOut' in segment) { 58878 cue = new window_1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut); 58879 cue.adStartTime = mediaTime; // Assumes tag format to be 58880 // #EXT-X-CUE-OUT:30 58881 58882 cue.adEndTime = mediaTime + parseFloat(segment.cueOut); 58883 track.addCue(cue); 58884 } 58885 58886 if ('cueOutCont' in segment) { 58887 // Entered into the middle of an ad cue 58888 // Assumes tag formate to be 58889 // #EXT-X-CUE-OUT-CONT:10/30 58890 var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat), 58891 adOffset = _segment$cueOutCont$s[0], 58892 adTotal = _segment$cueOutCont$s[1]; 58893 58894 cue = new window_1.VTTCue(mediaTime, mediaTime + segment.duration, ''); 58895 cue.adStartTime = mediaTime - adOffset; 58896 cue.adEndTime = cue.adStartTime + adTotal; 58897 track.addCue(cue); 58898 } 58899 } 58900 58901 mediaTime += segment.duration; 58902 } 58903 }; 58904 58905 var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always* 58906 // the equivalence display-time 0 === segment-index 0 58907 { 58908 name: 'VOD', 58909 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 58910 if (duration !== Infinity) { 58911 var syncPoint = { 58912 time: 0, 58913 segmentIndex: 0, 58914 partIndex: null 58915 }; 58916 return syncPoint; 58917 } 58918 58919 return null; 58920 } 58921 }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist 58922 { 58923 name: 'ProgramDateTime', 58924 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 58925 if (!Object.keys(syncController.timelineToDatetimeMappings).length) { 58926 return null; 58927 } 58928 58929 var syncPoint = null; 58930 var lastDistance = null; 58931 var partsAndSegments = getPartsAndSegments(playlist); 58932 currentTime = currentTime || 0; 58933 58934 for (var i = 0; i < partsAndSegments.length; i++) { 58935 // start from the end and loop backwards for live 58936 // or start from the front and loop forwards for non-live 58937 var index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1); 58938 var partAndSegment = partsAndSegments[index]; 58939 var segment = partAndSegment.segment; 58940 var datetimeMapping = syncController.timelineToDatetimeMappings[segment.timeline]; 58941 58942 if (!datetimeMapping) { 58943 continue; 58944 } 58945 58946 if (segment.dateTimeObject) { 58947 var segmentTime = segment.dateTimeObject.getTime() / 1000; 58948 var start = segmentTime + datetimeMapping; // take part duration into account. 58949 58950 if (segment.parts && typeof partAndSegment.partIndex === 'number') { 58951 for (var z = 0; z < partAndSegment.partIndex; z++) { 58952 start += segment.parts[z].duration; 58953 } 58954 } 58955 58956 var distance = Math.abs(currentTime - start); // Once the distance begins to increase, or if distance is 0, we have passed 58957 // currentTime and can stop looking for better candidates 58958 58959 if (lastDistance !== null && (distance === 0 || lastDistance < distance)) { 58960 break; 58961 } 58962 58963 lastDistance = distance; 58964 syncPoint = { 58965 time: start, 58966 segmentIndex: partAndSegment.segmentIndex, 58967 partIndex: partAndSegment.partIndex 58968 }; 58969 } 58970 } 58971 58972 return syncPoint; 58973 } 58974 }, // Stategy "Segment": We have a known time mapping for a timeline and a 58975 // segment in the current timeline with timing data 58976 { 58977 name: 'Segment', 58978 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 58979 var syncPoint = null; 58980 var lastDistance = null; 58981 currentTime = currentTime || 0; 58982 var partsAndSegments = getPartsAndSegments(playlist); 58983 58984 for (var i = 0; i < partsAndSegments.length; i++) { 58985 // start from the end and loop backwards for live 58986 // or start from the front and loop forwards for non-live 58987 var index = playlist.endList || currentTime === 0 ? i : partsAndSegments.length - (i + 1); 58988 var partAndSegment = partsAndSegments[index]; 58989 var segment = partAndSegment.segment; 58990 var start = partAndSegment.part && partAndSegment.part.start || segment && segment.start; 58991 58992 if (segment.timeline === currentTimeline && typeof start !== 'undefined') { 58993 var distance = Math.abs(currentTime - start); // Once the distance begins to increase, we have passed 58994 // currentTime and can stop looking for better candidates 58995 58996 if (lastDistance !== null && lastDistance < distance) { 58997 break; 58998 } 58999 59000 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 59001 lastDistance = distance; 59002 syncPoint = { 59003 time: start, 59004 segmentIndex: partAndSegment.segmentIndex, 59005 partIndex: partAndSegment.partIndex 59006 }; 59007 } 59008 } 59009 } 59010 59011 return syncPoint; 59012 } 59013 }, // Stategy "Discontinuity": We have a discontinuity with a known 59014 // display-time 59015 { 59016 name: 'Discontinuity', 59017 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 59018 var syncPoint = null; 59019 currentTime = currentTime || 0; 59020 59021 if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 59022 var lastDistance = null; 59023 59024 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 59025 var segmentIndex = playlist.discontinuityStarts[i];
59026 var discontinuity = playlist.discontinuitySequence + i + 1; 59027 var discontinuitySync = syncController.discontinuities[discontinuity]; 59028 59029 if (discontinuitySync) { 59030 var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed 59031 // currentTime and can stop looking for better candidates 59032 59033 if (lastDistance !== null && lastDistance < distance) { 59034 break; 59035 } 59036 59037 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 59038 lastDistance = distance; 59039 syncPoint = { 59040 time: discontinuitySync.time, 59041 segmentIndex: segmentIndex, 59042 partIndex: null 59043 }; 59044 } 59045 } 59046 } 59047 } 59048 59049 return syncPoint; 59050 } 59051 }, // Stategy "Playlist": We have a playlist with a known mapping of 59052 // segment index to display time 59053 { 59054 name: 'Playlist', 59055 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 59056 if (playlist.syncInfo) { 59057 var syncPoint = { 59058 time: playlist.syncInfo.time, 59059 segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence, 59060 partIndex: null 59061 }; 59062 return syncPoint; 59063 } 59064 59065 return null; 59066 } 59067 }]; 59068 59069 var SyncController = /*#__PURE__*/function (_videojs$EventTarget) { 59070 inheritsLoose(SyncController, _videojs$EventTarget); 59071 59072 function SyncController(options) { 59073 var _this; 59074 59075 _this = _videojs$EventTarget.call(this) || this; // ...for synching across variants 59076 59077 _this.timelines = []; 59078 _this.discontinuities = []; 59079 _this.timelineToDatetimeMappings = {}; 59080 _this.logger_ = logger('SyncController'); 59081 return _this; 59082 } 59083 /** 59084 * Find a sync-point for the playlist specified 59085 * 59086 * A sync-point is defined as a known mapping from display-time to 59087 * a segment-index in the current playlist. 59088 * 59089 * @param {Playlist} playlist 59090 * The playlist that needs a sync-point 59091 * @param {number} duration 59092 * Duration of the MediaSource (Infinite if playing a live source) 59093 * @param {number} currentTimeline 59094 * The last timeline from which a segment was loaded 59095 * @return {Object} 59096 * A sync-point object 59097 */ 59098 59099 59100 var _proto = SyncController.prototype; 59101 59102 _proto.getSyncPoint = function getSyncPoint(playlist, duration, currentTimeline, currentTime) { 59103 var syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime); 59104 59105 if (!syncPoints.length) { 59106 // Signal that we need to attempt to get a sync-point manually 59107 // by fetching a segment in the playlist and constructing 59108 // a sync-point from that information 59109 return null; 59110 } // Now find the sync-point that is closest to the currentTime because 59111 // that should result in the most accurate guess about which segment 59112 // to fetch 59113 59114 59115 return this.selectSyncPoint_(syncPoints, { 59116 key: 'time', 59117 value: currentTime 59118 }); 59119 } 59120 /** 59121 * Calculate the amount of time that has expired off the playlist during playback 59122 * 59123 * @param {Playlist} playlist 59124 * Playlist object to calculate expired from 59125 * @param {number} duration 59126 * Duration of the MediaSource (Infinity if playling a live source) 59127 * @return {number|null} 59128 * The amount of time that has expired off the playlist during playback. Null 59129 * if no sync-points for the playlist can be found. 59130 */ 59131 ; 59132 59133 _proto.getExpiredTime = function getExpiredTime(playlist, duration) { 59134 if (!playlist || !playlist.segments) { 59135 return null; 59136 } 59137 59138 var syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time 59139 59140 if (!syncPoints.length) { 59141 return null; 59142 } 59143 59144 var syncPoint = this.selectSyncPoint_(syncPoints, { 59145 key: 'segmentIndex', 59146 value: 0 59147 }); // If the sync-point is beyond the start of the playlist, we want to subtract the 59148 // duration from index 0 to syncPoint.segmentIndex instead of adding. 59149 59150 if (syncPoint.segmentIndex > 0) { 59151 syncPoint.time *= -1; 59152 } 59153 59154 return Math.abs(syncPoint.time + sumDurations({ 59155 defaultDuration: playlist.targetDuration, 59156 durationList: playlist.segments, 59157 startIndex: syncPoint.segmentIndex, 59158 endIndex: 0 59159 })); 59160 } 59161 /** 59162 * Runs each sync-point strategy and returns a list of sync-points returned by the 59163 * strategies 59164 * 59165 * @private 59166 * @param {Playlist} playlist 59167 * The playlist that needs a sync-point 59168 * @param {number} duration 59169 * Duration of the MediaSource (Infinity if playing a live source) 59170 * @param {number} currentTimeline 59171 * The last timeline from which a segment was loaded 59172 * @return {Array} 59173 * A list of sync-point objects 59174 */ 59175 ; 59176 59177 _proto.runStrategies_ = function runStrategies_(playlist, duration, currentTimeline, currentTime) { 59178 var syncPoints = []; // Try to find a sync-point in by utilizing various strategies... 59179 59180 for (var i = 0; i < syncPointStrategies.length; i++) { 59181 var strategy = syncPointStrategies[i];
59182 var syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime); 59183 59184 if (syncPoint) { 59185 syncPoint.strategy = strategy.name; 59186 syncPoints.push({ 59187 strategy: strategy.name, 59188 syncPoint: syncPoint 59189 }); 59190 } 59191 } 59192 59193 return syncPoints; 59194 } 59195 /** 59196 * Selects the sync-point nearest the specified target 59197 * 59198 * @private 59199 * @param {Array} syncPoints 59200 * List of sync-points to select from 59201 * @param {Object} target 59202 * Object specifying the property and value we are targeting 59203 * @param {string} target.key 59204 * Specifies the property to target. Must be either 'time' or 'segmentIndex' 59205 * @param {number} target.value 59206 * The value to target for the specified key. 59207 * @return {Object} 59208 * The sync-point nearest the target 59209 */ 59210 ; 59211 59212 _proto.selectSyncPoint_ = function selectSyncPoint_(syncPoints, target) { 59213 var bestSyncPoint = syncPoints[0].syncPoint; 59214 var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value); 59215 var bestStrategy = syncPoints[0].strategy; 59216 59217 for (var i = 1; i < syncPoints.length; i++) { 59218 var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value); 59219 59220 if (newDistance < bestDistance) { 59221 bestDistance = newDistance; 59222 bestSyncPoint = syncPoints[i].syncPoint; 59223 bestStrategy = syncPoints[i].strategy; 59224 } 59225 } 59226 59227 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 : '') + ']'); 59228 return bestSyncPoint; 59229 } 59230 /** 59231 * Save any meta-data present on the segments when segments leave 59232 * the live window to the playlist to allow for synchronization at the 59233 * playlist level later. 59234 * 59235 * @param {Playlist} oldPlaylist - The previous active playlist 59236 * @param {Playlist} newPlaylist - The updated and most current playlist 59237 */ 59238 ; 59239 59240 _proto.saveExpiredSegmentInfo = function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) { 59241 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time 59242 // save that information as a possible sync-point reference in future 59243 59244 for (var i = mediaSequenceDiff - 1; i >= 0; i--) { 59245 var lastRemovedSegment = oldPlaylist.segments[i]; 59246 59247 if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') { 59248 newPlaylist.syncInfo = { 59249 mediaSequence: oldPlaylist.mediaSequence + i, 59250 time: lastRemovedSegment.start 59251 }; 59252 this.logger_("playlist refresh sync: [time:" + newPlaylist.syncInfo.time + "," + (" mediaSequence: " + newPlaylist.syncInfo.mediaSequence + "]")); 59253 this.trigger('syncinfoupdate'); 59254 break; 59255 } 59256 } 59257 } 59258 /** 59259 * Save the mapping from playlist's ProgramDateTime to display. This should only happen 59260 * before segments start to load. 59261 * 59262 * @param {Playlist} playlist - The currently active playlist 59263 */ 59264 ; 59265 59266 _proto.setDateTimeMappingForStart = function setDateTimeMappingForStart(playlist) { 59267 // It's possible for the playlist to be updated before playback starts, meaning time 59268 // zero is not yet set. If, during these playlist refreshes, a discontinuity is 59269 // crossed, then the old time zero mapping (for the prior timeline) would be retained 59270 // unless the mappings are cleared. 59271 this.timelineToDatetimeMappings = {}; 59272 59273 if (playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) { 59274 var firstSegment = playlist.segments[0]; 59275 var playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000; 59276 this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp; 59277 } 59278 } 59279 /** 59280 * Calculates and saves timeline mappings, playlist sync info, and segment timing values 59281 * based on the latest timing information. 59282 * 59283 * @param {Object} options 59284 * Options object 59285 * @param {SegmentInfo} options.segmentInfo 59286 * The current active request information 59287 * @param {boolean} options.shouldSaveTimelineMapping 59288 * If there's a timeline change, determines if the timeline mapping should be 59289 * saved for timeline mapping and program date time mappings. 59290 */ 59291 ; 59292 59293 _proto.saveSegmentTimingInfo = function saveSegmentTimingInfo(_ref) { 59294 var segmentInfo = _ref.segmentInfo, 59295 shouldSaveTimelineMapping = _ref.shouldSaveTimelineMapping; 59296 var didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping); 59297 var segment = segmentInfo.segment; 59298 59299 if (didCalculateSegmentTimeMapping) { 59300 this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information 59301 // now with segment timing information 59302 59303 if (!segmentInfo.playlist.syncInfo) { 59304 segmentInfo.playlist.syncInfo = { 59305 mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex, 59306 time: segment.start 59307 }; 59308 } 59309 } 59310 59311 var dateTime = segment.dateTimeObject; 59312 59313 if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) { 59314 this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000); 59315 } 59316 }; 59317 59318 _proto.timestampOffsetForTimeline = function timestampOffsetForTimeline(timeline) { 59319 if (typeof this.timelines[timeline] === 'undefined') { 59320 return null; 59321 } 59322 59323 return this.timelines[timeline].time; 59324 }; 59325 59326 _proto.mappingForTimeline = function mappingForTimeline(timeline) { 59327 if (typeof this.timelines[timeline] === 'undefined') { 59328 return null; 59329 } 59330 59331 return this.timelines[timeline].mapping; 59332 } 59333 /** 59334 * Use the "media time" for a segment to generate a mapping to "display time" and 59335 * save that display time to the segment. 59336 * 59337 * @private 59338 * @param {SegmentInfo} segmentInfo 59339 * The current active request information 59340 * @param {Object} timingInfo 59341 * The start and end time of the current segment in "media time" 59342 * @param {boolean} shouldSaveTimelineMapping 59343 * If there's a timeline change, determines if the timeline mapping should be 59344 * saved in timelines. 59345 * @return {boolean} 59346 * Returns false if segment time mapping could not be calculated 59347 */ 59348 ; 59349 59350 _proto.calculateSegmentTimeMapping_ = function calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) { 59351 // TODO: remove side effects 59352 var segment = segmentInfo.segment; 59353 var part = segmentInfo.part; 59354 var mappingObj = this.timelines[segmentInfo.timeline]; 59355 var start; 59356 var end; 59357 59358 if (typeof segmentInfo.timestampOffset === 'number') { 59359 mappingObj = { 59360 time: segmentInfo.startOfSegment, 59361 mapping: segmentInfo.startOfSegment - timingInfo.start 59362 }; 59363 59364 if (shouldSaveTimelineMapping) { 59365 this.timelines[segmentInfo.timeline] = mappingObj; 59366 this.trigger('timestampoffset'); 59367 this.logger_("time mapping for timeline " + segmentInfo.timeline + ": " + ("[time: " + mappingObj.time + "] [mapping: " + mappingObj.mapping + "]")); 59368 } 59369 59370 start = segmentInfo.startOfSegment; 59371 end = timingInfo.end + mappingObj.mapping; 59372 } else if (mappingObj) { 59373 start = timingInfo.start + mappingObj.mapping; 59374 end = timingInfo.end + mappingObj.mapping; 59375 } else { 59376 return false; 59377 } 59378 59379 if (part) { 59380 part.start = start; 59381 part.end = end; 59382 } // If we don't have a segment start yet or the start value we got
59383 // is less than our current segment.start value, save a new start value. 59384 // We have to do this because parts will have segment timing info saved 59385 // multiple times and we want segment start to be the earliest part start 59386 // value for that segment. 59387 59388 59389 if (!segment.start || start < segment.start) { 59390 segment.start = start; 59391 } 59392 59393 segment.end = end; 59394 return true; 59395 } 59396 /** 59397 * Each time we have discontinuity in the playlist, attempt to calculate the location 59398 * in display of the start of the discontinuity and save that. We also save an accuracy 59399 * value so that we save values with the most accuracy (closest to 0.) 59400 * 59401 * @private 59402 * @param {SegmentInfo} segmentInfo - The current active request information 59403 */ 59404 ; 59405 59406 _proto.saveDiscontinuitySyncInfo_ = function saveDiscontinuitySyncInfo_(segmentInfo) { 59407 var playlist = segmentInfo.playlist; 59408 var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where 59409 // the start of the range and it's accuracy is 0 (greater accuracy values 59410 // mean more approximation) 59411 59412 if (segment.discontinuity) { 59413 this.discontinuities[segment.timeline] = { 59414 time: segment.start, 59415 accuracy: 0 59416 }; 59417 } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 59418 // Search for future discontinuities that we can provide better timing 59419 // information for and save that information for sync purposes 59420 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 59421 var segmentIndex = playlist.discontinuityStarts[i]; 59422 var discontinuity = playlist.discontinuitySequence + i + 1; 59423 var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex; 59424 var accuracy = Math.abs(mediaIndexDiff); 59425 59426 if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) { 59427 var time = void 0; 59428 59429 if (mediaIndexDiff < 0) { 59430 time = segment.start - sumDurations({ 59431 defaultDuration: playlist.targetDuration, 59432 durationList: playlist.segments, 59433 startIndex: segmentInfo.mediaIndex, 59434 endIndex: segmentIndex 59435 }); 59436 } else { 59437 time = segment.end + sumDurations({ 59438 defaultDuration: playlist.targetDuration, 59439 durationList: playlist.segments, 59440 startIndex: segmentInfo.mediaIndex + 1, 59441 endIndex: segmentIndex 59442 }); 59443 } 59444 59445 this.discontinuities[discontinuity] = { 59446 time: time, 59447 accuracy: accuracy 59448 }; 59449 } 59450 } 59451 } 59452 }; 59453 59454 _proto.dispose = function dispose() { 59455 this.trigger('dispose'); 59456 this.off(); 59457 }; 59458 59459 return SyncController; 59460 }(videojs.EventTarget); 59461 /** 59462 * The TimelineChangeController acts as a source for segment loaders to listen for and 59463 * keep track of latest and pending timeline changes. This is useful to ensure proper 59464 * sync, as each loader may need to make a consideration for what timeline the other 59465 * loader is on before making changes which could impact the other loader's media. 59466 * 59467 * @class TimelineChangeController 59468 * @extends videojs.EventTarget 59469 */ 59470 59471 59472 var TimelineChangeController = /*#__PURE__*/function (_videojs$EventTarget) { 59473 inheritsLoose(TimelineChangeController, _videojs$EventTarget); 59474 59475 function TimelineChangeController() { 59476 var _this; 59477 59478 _this = _videojs$EventTarget.call(this) || this; 59479 _this.pendingTimelineChanges_ = {}; 59480 _this.lastTimelineChanges_ = {}; 59481 return _this; 59482 } 59483 59484 var _proto = TimelineChangeController.prototype; 59485 59486 _proto.clearPendingTimelineChange = function clearPendingTimelineChange(type) { 59487 this.pendingTimelineChanges_[type] = null; 59488 this.trigger('pendingtimelinechange'); 59489 }; 59490 59491 _proto.pendingTimelineChange = function pendingTimelineChange(_ref) { 59492 var type = _ref.type, 59493 from = _ref.from, 59494 to = _ref.to; 59495 59496 if (typeof from === 'number' && typeof to === 'number') { 59497 this.pendingTimelineChanges_[type] = { 59498 type: type, 59499 from: from, 59500 to: to 59501 }; 59502 this.trigger('pendingtimelinechange'); 59503 } 59504 59505 return this.pendingTimelineChanges_[type]; 59506 }; 59507 59508 _proto.lastTimelineChange = function lastTimelineChange(_ref2) { 59509 var type = _ref2.type, 59510 from = _ref2.from, 59511 to = _ref2.to; 59512 59513 if (typeof from === 'number' && typeof to === 'number') { 59514 this.lastTimelineChanges_[type] = { 59515 type: type, 59516 from: from, 59517 to: to 59518 }; 59519 delete this.pendingTimelineChanges_[type]; 59520 this.trigger('timelinechange'); 59521 } 59522 59523 return this.lastTimelineChanges_[type]; 59524 }; 59525 59526 _proto.dispose = function dispose() { 59527 this.trigger('dispose'); 59528 this.pendingTimelineChanges_ = {}; 59529 this.lastTimelineChanges_ = {}; 59530 this.off(); 59531 }; 59532 59533 return TimelineChangeController; 59534 }(videojs.EventTarget); 59535 /* rollup-plugin-worker-factory start for worker!/Users/bcasey/Projects/videojs-http-streaming/src/decrypter-worker.js */ 59536 59537 59538 var workerCode = transform(getWorkerString(function () { 59539 function createCommonjsModule(fn, basedir, module) { 59540 return module = { 59541 path: basedir, 59542 exports: {}, 59543 require: function require(path, base) { 59544 return commonjsRequire(path, base === undefined || base === null ? module.path : base); 59545 } 59546 }, fn(module, module.exports), module.exports; 59547 } 59548 59549 function commonjsRequire() { 59550 throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs'); 59551 } 59552 59553 var createClass = createCommonjsModule(function (module) { 59554 function _defineProperties(target, props) { 59555 for (var i = 0; i < props.length; i++) { 59556 var descriptor = props[i]; 59557 descriptor.enumerable = descriptor.enumerable || false; 59558 descriptor.configurable = true; 59559 if ("value" in descriptor) descriptor.writable = true; 59560 Object.defineProperty(target, descriptor.key, descriptor); 59561 } 59562 } 59563 59564 function _createClass(Constructor, protoProps, staticProps) { 59565 if (protoProps) _defineProperties(Constructor.prototype, protoProps); 59566 if (staticProps) _defineProperties(Constructor, staticProps); 59567 return Constructor; 59568 } 59569 59570 module.exports = _createClass; 59571 module.exports["default"] = module.exports, module.exports.__esModule = true; 59572 });
59573 var setPrototypeOf = createCommonjsModule(function (module) { 59574 function _setPrototypeOf(o, p) { 59575 module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 59576 o.__proto__ = p; 59577 return o; 59578 }; 59579 59580 module.exports["default"] = module.exports, module.exports.__esModule = true; 59581 return _setPrototypeOf(o, p); 59582 } 59583 59584 module.exports = _setPrototypeOf; 59585 module.exports["default"] = module.exports, module.exports.__esModule = true; 59586 }); 59587 var inheritsLoose = createCommonjsModule(function (module) { 59588 function _inheritsLoose(subClass, superClass) { 59589 subClass.prototype = Object.create(superClass.prototype); 59590 subClass.prototype.constructor = subClass; 59591 setPrototypeOf(subClass, superClass); 59592 } 59593 59594 module.exports = _inheritsLoose; 59595 module.exports["default"] = module.exports, module.exports.__esModule = true; 59596 }); 59597 /** 59598 * @file stream.js 59599 */ 59600 59601 /** 59602 * A lightweight readable stream implemention that handles event dispatching. 59603 * 59604 * @class Stream 59605 */ 59606 59607 var Stream = /*#__PURE__*/function () { 59608 function Stream() { 59609 this.listeners = {}; 59610 } 59611 /** 59612 * Add a listener for a specified event type. 59613 * 59614 * @param {string} type the event name 59615 * @param {Function} listener the callback to be invoked when an event of 59616 * the specified type occurs 59617 */ 59618 59619 59620 var _proto = Stream.prototype; 59621 59622 _proto.on = function on(type, listener) { 59623 if (!this.listeners[type]) { 59624 this.listeners[type] = []; 59625 } 59626 59627 this.listeners[type].push(listener); 59628 } 59629 /** 59630 * Remove a listener for a specified event type. 59631 * 59632 * @param {string} type the event name 59633 * @param {Function} listener a function previously registered for this 59634 * type of event through `on` 59635 * @return {boolean} if we could turn it off or not 59636 */ 59637 ; 59638 59639 _proto.off = function off(type, listener) { 59640 if (!this.listeners[type]) { 59641 return false; 59642 } 59643 59644 var index = this.listeners[type].indexOf(listener); // TODO: which is better? 59645 // In Video.js we slice listener functions 59646 // on trigger so that it does not mess up the order 59647 // while we loop through. 59648 // 59649 // Here we slice on off so that the loop in trigger 59650 // can continue using it's old reference to loop without 59651 // messing up the order. 59652 59653 this.listeners[type] = this.listeners[type].slice(0); 59654 this.listeners[type].splice(index, 1); 59655 return index > -1; 59656 } 59657 /** 59658 * Trigger an event of the specified type on this stream. Any additional 59659 * arguments to this function are passed as parameters to event listeners. 59660 * 59661 * @param {string} type the event name 59662 */ 59663 ; 59664 59665 _proto.trigger = function trigger(type) { 59666 var callbacks = this.listeners[type]; 59667 59668 if (!callbacks) { 59669 return; 59670 } // Slicing the arguments on every invocation of this method 59671 // can add a significant amount of overhead. Avoid the 59672 // intermediate object creation for the common case of a 59673 // single callback argument 59674 59675 59676 if (arguments.length === 2) { 59677 var length = callbacks.length; 59678 59679 for (var i = 0; i < length; ++i) { 59680 callbacks[i].call(this, arguments[1]); 59681 } 59682 } else { 59683 var args = Array.prototype.slice.call(arguments, 1); 59684 var _length = callbacks.length; 59685 59686 for (var _i = 0; _i < _length; ++_i) { 59687 callbacks[_i].apply(this, args); 59688 } 59689 } 59690 } 59691 /** 59692 * Destroys the stream and cleans up. 59693 */ 59694 ; 59695 59696 _proto.dispose = function dispose() { 59697 this.listeners = {}; 59698 } 59699 /** 59700 * Forwards all `data` events on this stream to the destination stream. The 59701 * destination stream should provide a method `push` to receive the data 59702 * events as they arrive. 59703 * 59704 * @param {Stream} destination the stream that will receive all `data` events 59705 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 59706 */ 59707 ; 59708 59709 _proto.pipe = function pipe(destination) { 59710 this.on('data', function (data) { 59711 destination.push(data); 59712 }); 59713 }; 59714 59715 return Stream; 59716 }(); 59717 /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */ 59718 59719 /** 59720 * Returns the subarray of a Uint8Array without PKCS#7 padding. 59721 * 59722 * @param padded {Uint8Array} unencrypted bytes that have been padded 59723 * @return {Uint8Array} the unpadded bytes 59724 * @see http://tools.ietf.org/html/rfc5652 59725 */ 59726 59727 59728 function unpad(padded) { 59729 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 59730 } 59731 /*! @name aes-decrypter @version 3.1.2 @license Apache-2.0 */ 59732 59733 /** 59734 * @file aes.js 59735 * 59736 * This file contains an adaptation of the AES decryption algorithm 59737 * from the Standford Javascript Cryptography Library. That work is 59738 * covered by the following copyright and permissions notice: 59739 * 59740 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 59741 * All rights reserved. 59742 * 59743 * Redistribution and use in source and binary forms, with or without 59744 * modification, are permitted provided that the following conditions are 59745 * met: 59746 * 59747 * 1. Redistributions of source code must retain the above copyright 59748 * notice, this list of conditions and the following disclaimer. 59749 * 59750 * 2. Redistributions in binary form must reproduce the above 59751 * copyright notice, this list of conditions and the following 59752 * disclaimer in the documentation and/or other materials provided 59753 * with the distribution. 59754 * 59755 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 59756 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 59757 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 59758 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 59759 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
59760 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 59761 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 59762 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 59763 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 59764 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 59765 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 59766 * 59767 * The views and conclusions contained in the software and documentation 59768 * are those of the authors and should not be interpreted as representing 59769 * official policies, either expressed or implied, of the authors. 59770 */ 59771 59772 /** 59773 * Expand the S-box tables. 59774 * 59775 * @private 59776 */ 59777 59778 59779 var precompute = function precompute() { 59780 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 59781 var encTable = tables[0]; 59782 var decTable = tables[1]; 59783 var sbox = encTable[4]; 59784 var sboxInv = decTable[4]; 59785 var i; 59786 var x; 59787 var xInv; 59788 var d = []; 59789 var th = []; 59790 var x2; 59791 var x4; 59792 var x8; 59793 var s; 59794 var tEnc; 59795 var tDec; // Compute double and third tables 59796 59797 for (i = 0; i < 256; i++) { 59798 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 59799 } 59800 59801 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 59802 // Compute sbox 59803 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 59804 s = s >> 8 ^ s & 255 ^ 99; 59805 sbox[x] = s; 59806 sboxInv[s] = x; // Compute MixColumns 59807 59808 x8 = d[x4 = d[x2 = d[x]]]; 59809 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 59810 tEnc = d[s] * 0x101 ^ s * 0x1010100; 59811 59812 for (i = 0; i < 4; i++) { 59813 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 59814 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 59815 } 59816 } // Compactify. Considerable speedup on Firefox. 59817 59818 59819 for (i = 0; i < 5; i++) { 59820 encTable[i] = encTable[i].slice(0); 59821 decTable[i] = decTable[i].slice(0); 59822 } 59823 59824 return tables; 59825 }; 59826 59827 var aesTables = null; 59828 /** 59829 * Schedule out an AES key for both encryption and decryption. This 59830 * is a low-level class. Use a cipher mode to do bulk encryption. 59831 * 59832 * @class AES 59833 * @param key {Array} The key as an array of 4, 6 or 8 words. 59834 */ 59835 59836 var AES = /*#__PURE__*/function () { 59837 function AES(key) { 59838 /** 59839 * The expanded S-box and inverse S-box tables. These will be computed 59840 * on the client so that we don't have to send them down the wire. 59841 * 59842 * There are two tables, _tables[0] is for encryption and 59843 * _tables[1] is for decryption. 59844 * 59845 * The first 4 sub-tables are the expanded S-box with MixColumns. The 59846 * last (_tables[01][4]) is the S-box itself. 59847 * 59848 * @private 59849 */ 59850 // if we have yet to precompute the S-box tables 59851 // do so now 59852 if (!aesTables) { 59853 aesTables = precompute(); 59854 } // then make a copy of that object for use 59855 59856 59857 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()]];
59858 var i; 59859 var j; 59860 var tmp; 59861 var sbox = this._tables[0][4]; 59862 var decTable = this._tables[1]; 59863 var keyLen = key.length; 59864 var rcon = 1; 59865 59866 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 59867 throw new Error('Invalid aes key size'); 59868 } 59869 59870 var encKey = key.slice(0); 59871 var decKey = []; 59872 this._key = [encKey, decKey]; // schedule encryption keys 59873 59874 for (i = keyLen; i < 4 * keyLen + 28; i++) { 59875 tmp = encKey[i - 1]; // apply sbox 59876 59877 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 59878 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 59879 59880 if (i % keyLen === 0) { 59881 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 59882 rcon = rcon << 1 ^ (rcon >> 7) * 283; 59883 } 59884 } 59885 59886 encKey[i] = encKey[i - keyLen] ^ tmp; 59887 } // schedule decryption keys 59888 59889 59890 for (j = 0; i; j++, i--) { 59891 tmp = encKey[j & 3 ? i : i - 4]; 59892 59893 if (i <= 4 || j < 4) { 59894 decKey[j] = tmp; 59895 } else { 59896 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 59897 } 59898 } 59899 } 59900 /** 59901 * Decrypt 16 bytes, specified as four 32-bit words. 59902 * 59903 * @param {number} encrypted0 the first word to decrypt 59904 * @param {number} encrypted1 the second word to decrypt 59905 * @param {number} encrypted2 the third word to decrypt 59906 * @param {number} encrypted3 the fourth word to decrypt 59907 * @param {Int32Array} out the array to write the decrypted words 59908 * into 59909 * @param {number} offset the offset into the output array to start 59910 * writing results 59911 * @return {Array} The plaintext. 59912 */ 59913 59914 59915 var _proto = AES.prototype; 59916 59917 _proto.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 59918 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 59919 59920 var a = encrypted0 ^ key[0]; 59921 var b = encrypted3 ^ key[1]; 59922 var c = encrypted2 ^ key[2]; 59923 var d = encrypted1 ^ key[3]; 59924 var a2; 59925 var b2; 59926 var c2; // key.length === 2 ? 59927 59928 var nInnerRounds = key.length / 4 - 2; 59929 var i; 59930 var kIndex = 4; 59931 var table = this._tables[1]; // load up the tables 59932 59933 var table0 = table[0]; 59934 var table1 = table[1]; 59935 var table2 = table[2]; 59936 var table3 = table[3]; 59937 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 59938 59939 for (i = 0; i < nInnerRounds; i++) { 59940 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 59941 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 59942 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 59943 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 59944 kIndex += 4; 59945 a = a2; 59946 b = b2; 59947 c = c2; 59948 } // Last round. 59949 59950 59951 for (i = 0; i < 4; i++) { 59952 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 59953 a2 = a; 59954 a = b; 59955 b = c; 59956 c = d; 59957 d = a2; 59958 } 59959 }; 59960 59961 return AES; 59962 }(); 59963 /** 59964 * A wrapper around the Stream class to use setTimeout 59965 * and run stream "jobs" Asynchronously 59966 * 59967 * @class AsyncStream 59968 * @extends Stream 59969 */ 59970 59971 59972 var AsyncStream = /*#__PURE__*/function (_Stream) { 59973 inheritsLoose(AsyncStream, _Stream); 59974 59975 function AsyncStream() { 59976 var _this; 59977 59978 _this = _Stream.call(this, Stream) || this; 59979 _this.jobs = []; 59980 _this.delay = 1; 59981 _this.timeout_ = null; 59982 return _this; 59983 } 59984 /** 59985 * process an async job 59986 * 59987 * @private 59988 */ 59989 59990 59991 var _proto = AsyncStream.prototype; 59992 59993 _proto.processJob_ = function processJob_() { 59994 this.jobs.shift()(); 59995 59996 if (this.jobs.length) { 59997 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 59998 } else { 59999 this.timeout_ = null; 60000 } 60001 } 60002 /** 60003 * push a job into the stream 60004 * 60005 * @param {Function} job the job to push into the stream 60006 */ 60007 ; 60008 60009 _proto.push = function push(job) { 60010 this.jobs.push(job); 60011 60012 if (!this.timeout_) { 60013 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 60014 } 60015 }; 60016 60017 return AsyncStream; 60018 }(Stream); 60019 /** 60020 * Convert network-order (big-endian) bytes into their little-endian 60021 * representation. 60022 */ 60023 60024 60025 var ntoh = function ntoh(word) { 60026 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 60027 }; 60028 /** 60029 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 60030 * 60031 * @param {Uint8Array} encrypted the encrypted bytes 60032 * @param {Uint32Array} key the bytes of the decryption key 60033 * @param {Uint32Array} initVector the initialization vector (IV) to
vendor: 6,719 bytes, lines 60034-60213
60034 * use for the first round of CBC. 60035 * @return {Uint8Array} the decrypted bytes 60036 * 60037 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 60038 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 60039 * @see https://tools.ietf.org/html/rfc2315 60040 */ 60041 60042 60043 var decrypt = function decrypt(encrypted, key, initVector) { 60044 // word-level access to the encrypted bytes 60045 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 60046 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 60047 60048 var decrypted = new Uint8Array(encrypted.byteLength); 60049 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 60050 // decrypted data 60051 60052 var init0; 60053 var init1; 60054 var init2; 60055 var init3; 60056 var encrypted0; 60057 var encrypted1; 60058 var encrypted2; 60059 var encrypted3; // iteration variable 60060 60061 var wordIx; // pull out the words of the IV to ensure we don't modify the 60062 // passed-in reference and easier access 60063 60064 init0 = initVector[0]; 60065 init1 = initVector[1]; 60066 init2 = initVector[2]; 60067 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 60068 // to each decrypted block 60069 60070 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 60071 // convert big-endian (network order) words into little-endian 60072 // (javascript order) 60073 encrypted0 = ntoh(encrypted32[wordIx]); 60074 encrypted1 = ntoh(encrypted32[wordIx + 1]); 60075 encrypted2 = ntoh(encrypted32[wordIx + 2]); 60076 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 60077 60078 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 60079 // plaintext 60080 60081 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 60082 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 60083 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 60084 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 60085 60086 init0 = encrypted0; 60087 init1 = encrypted1; 60088 init2 = encrypted2; 60089 init3 = encrypted3; 60090 } 60091 60092 return decrypted; 60093 }; 60094 /** 60095 * The `Decrypter` class that manages decryption of AES 60096 * data through `AsyncStream` objects and the `decrypt` 60097 * function 60098 * 60099 * @param {Uint8Array} encrypted the encrypted bytes 60100 * @param {Uint32Array} key the bytes of the decryption key 60101 * @param {Uint32Array} initVector the initialization vector (IV) to 60102 * @param {Function} done the function to run when done 60103 * @class Decrypter 60104 */ 60105 60106 60107 var Decrypter = /*#__PURE__*/function () { 60108 function Decrypter(encrypted, key, initVector, done) { 60109 var step = Decrypter.STEP; 60110 var encrypted32 = new Int32Array(encrypted.buffer); 60111 var decrypted = new Uint8Array(encrypted.byteLength); 60112 var i = 0; 60113 this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously 60114 60115 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 60116 60117 for (i = step; i < encrypted32.length; i += step) { 60118 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 60119 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 60120 } // invoke the done() callback when everything is finished 60121 60122 60123 this.asyncStream_.push(function () { 60124 // remove pkcs#7 padding from the decrypted bytes 60125 done(null, unpad(decrypted)); 60126 }); 60127 } 60128 /** 60129 * a getter for step the maximum number of bytes to process at one time 60130 * 60131 * @return {number} the value of step 32000 60132 */ 60133 60134 60135 var _proto = Decrypter.prototype; 60136 /** 60137 * @private 60138 */ 60139 60140 _proto.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 60141 return function () { 60142 var bytes = decrypt(encrypted, key, initVector); 60143 decrypted.set(bytes, encrypted.byteOffset); 60144 }; 60145 }; 60146 60147 createClass(Decrypter, null, [{ 60148 key: "STEP", 60149 get: function get() { 60150 // 4 * 8000; 60151 return 32000; 60152 } 60153 }]); 60154 return Decrypter; 60155 }(); 60156 /** 60157 * @file bin-utils.js 60158 */ 60159 60160 /** 60161 * Creates an object for sending to a web worker modifying properties that are TypedArrays 60162 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 60163 * 60164 * @param {Object} message 60165 * Object of properties and values to send to the web worker 60166 * @return {Object} 60167 * Modified message with TypedArray values expanded 60168 * @function createTransferableMessage 60169 */ 60170 60171 60172 var createTransferableMessage = function createTransferableMessage(message) { 60173 var transferable = {}; 60174 Object.keys(message).forEach(function (key) { 60175 var value = message[key]; 60176 60177 if (ArrayBuffer.isView(value)) { 60178 transferable[key] = { 60179 bytes: value.buffer, 60180 byteOffset: value.byteOffset, 60181 byteLength: value.byteLength 60182 }; 60183 } else { 60184 transferable[key] = value; 60185 } 60186 }); 60187 return transferable; 60188 }; 60189 /* global self */ 60190 60191 /** 60192 * Our web worker interface so that things can talk to aes-decrypter 60193 * that will be running in a web worker. the scope is passed to this by 60194 * webworkify. 60195 */ 60196 60197 60198 self.onmessage = function (event) { 60199 var data = event.data; 60200 var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength); 60201 var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4); 60202 var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4); 60203 /* eslint-disable no-new, handle-callback-err */ 60204 60205 new Decrypter(encrypted, key, iv, function (err, bytes) { 60206 self.postMessage(createTransferableMessage({ 60207 source: data.source, 60208 decrypted: bytes 60209 }), [bytes.buffer]); 60210 }); 60211 /* eslint-enable */ 60212 }; 60213 }));
60214 var Decrypter = factory(workerCode); 60215 /* rollup-plugin-worker-factory end for worker!/Users/bcasey/Projects/videojs-http-streaming/src/decrypter-worker.js */ 60216 60217 /** 60218 * Convert the properties of an HLS track into an audioTrackKind. 60219 * 60220 * @private 60221 */ 60222 60223 var audioTrackKind_ = function audioTrackKind_(properties) { 60224 var kind = properties["default"] ? 'main' : 'alternative'; 60225 60226 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) { 60227 kind = 'main-desc'; 60228 } 60229 60230 return kind; 60231 }; 60232 /** 60233 * Pause provided segment loader and playlist loader if active 60234 * 60235 * @param {SegmentLoader} segmentLoader 60236 * SegmentLoader to pause 60237 * @param {Object} mediaType 60238 * Active media type 60239 * @function stopLoaders 60240 */ 60241 60242 60243 var stopLoaders = function stopLoaders(segmentLoader, mediaType) { 60244 segmentLoader.abort(); 60245 segmentLoader.pause(); 60246 60247 if (mediaType && mediaType.activePlaylistLoader) { 60248 mediaType.activePlaylistLoader.pause(); 60249 mediaType.activePlaylistLoader = null; 60250 } 60251 }; 60252 /** 60253 * Start loading provided segment loader and playlist loader 60254 * 60255 * @param {PlaylistLoader} playlistLoader 60256 * PlaylistLoader to start loading 60257 * @param {Object} mediaType 60258 * Active media type 60259 * @function startLoaders 60260 */ 60261 60262 60263 var startLoaders = function startLoaders(playlistLoader, mediaType) { 60264 // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the 60265 // playlist loader 60266 mediaType.activePlaylistLoader = playlistLoader; 60267 playlistLoader.load(); 60268 }; 60269 /** 60270 * Returns a function to be called when the media group changes. It performs a 60271 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a 60272 * change of group is merely a rendition switch of the same content at another encoding, 60273 * rather than a change of content, such as switching audio from English to Spanish. 60274 * 60275 * @param {string} type 60276 * MediaGroup type 60277 * @param {Object} settings 60278 * Object containing required information for media groups 60279 * @return {Function} 60280 * Handler for a non-destructive resync of SegmentLoader when the active media 60281 * group changes. 60282 * @function onGroupChanged 60283 */ 60284 60285 60286 var onGroupChanged = function onGroupChanged(type, settings) { 60287 return function () { 60288 var _settings$segmentLoad = settings.segmentLoaders, 60289 segmentLoader = _settings$segmentLoad[type], 60290 mainSegmentLoader = _settings$segmentLoad.main, 60291 mediaType = settings.mediaTypes[type]; 60292 var activeTrack = mediaType.activeTrack(); 60293 var activeGroup = mediaType.getActiveGroup(); 60294 var previousActiveLoader = mediaType.activePlaylistLoader; 60295 var lastGroup = mediaType.lastGroup_; // the group did not change do nothing 60296 60297 if (activeGroup && lastGroup && activeGroup.id === lastGroup.id) { 60298 return; 60299 } 60300 60301 mediaType.lastGroup_ = activeGroup; 60302 mediaType.lastTrack_ = activeTrack; 60303 stopLoaders(segmentLoader, mediaType); 60304 60305 if (!activeGroup || activeGroup.isMasterPlaylist) { 60306 // there is no group active or active group is a main playlist and won't change 60307 return; 60308 } 60309 60310 if (!activeGroup.playlistLoader) { 60311 if (previousActiveLoader) { 60312 // The previous group had a playlist loader but the new active group does not 60313 // this means we are switching from demuxed to muxed audio. In this case we want to 60314 // do a destructive reset of the main segment loader and not restart the audio 60315 // loaders. 60316 mainSegmentLoader.resetEverything(); 60317 } 60318 60319 return; 60320 } // Non-destructive resync 60321 60322 60323 segmentLoader.resyncLoader(); 60324 startLoaders(activeGroup.playlistLoader, mediaType); 60325 }; 60326 }; 60327 60328 var onGroupChanging = function onGroupChanging(type, settings) { 60329 return function () { 60330 var segmentLoader = settings.segmentLoaders[type], 60331 mediaType = settings.mediaTypes[type]; 60332 mediaType.lastGroup_ = null; 60333 segmentLoader.abort(); 60334 segmentLoader.pause(); 60335 }; 60336 }; 60337 /** 60338 * Returns a function to be called when the media track changes. It performs a 60339 * destructive reset of the SegmentLoader to ensure we start loading as close to 60340 * currentTime as possible. 60341 * 60342 * @param {string} type 60343 * MediaGroup type 60344 * @param {Object} settings 60345 * Object containing required information for media groups 60346 * @return {Function} 60347 * Handler for a destructive reset of SegmentLoader when the active media 60348 * track changes. 60349 * @function onTrackChanged 60350 */ 60351 60352 60353 var onTrackChanged = function onTrackChanged(type, settings) { 60354 return function () { 60355 var masterPlaylistLoader = settings.masterPlaylistLoader, 60356 _settings$segmentLoad2 = settings.segmentLoaders, 60357 segmentLoader = _settings$segmentLoad2[type], 60358 mainSegmentLoader = _settings$segmentLoad2.main, 60359 mediaType = settings.mediaTypes[type]; 60360 var activeTrack = mediaType.activeTrack(); 60361 var activeGroup = mediaType.getActiveGroup(); 60362 var previousActiveLoader = mediaType.activePlaylistLoader; 60363 var lastTrack = mediaType.lastTrack_; // track did not change, do nothing 60364 60365 if (lastTrack && activeTrack && lastTrack.id === activeTrack.id) { 60366 return; 60367 } 60368 60369 mediaType.lastGroup_ = activeGroup; 60370 mediaType.lastTrack_ = activeTrack; 60371 stopLoaders(segmentLoader, mediaType); 60372 60373 if (!activeGroup) { 60374 // there is no group active so we do not want to restart loaders 60375 return; 60376 } 60377 60378 if (activeGroup.isMasterPlaylist) { 60379 // track did not change, do nothing 60380 if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) { 60381 return; 60382 } 60383 60384 var mpc = settings.vhs.masterPlaylistController_; 60385 var newPlaylist = mpc.selectPlaylist(); // media will not change do nothing 60386 60387 if (mpc.media() === newPlaylist) { 60388 return; 60389 } 60390 60391 mediaType.logger_("track change. Switching master audio from " + lastTrack.id + " to " + activeTrack.id); 60392 masterPlaylistLoader.pause(); 60393 mainSegmentLoader.resetEverything(); 60394 mpc.fastQualityChange_(newPlaylist); 60395 return; 60396 } 60397 60398 if (type === 'AUDIO') { 60399 if (!activeGroup.playlistLoader) { 60400 // when switching from demuxed audio/video to muxed audio/video (noted by no 60401 // playlist loader for the audio group), we want to do a destructive reset of the
60402 // main segment loader and not restart the audio loaders 60403 mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since 60404 // it should be stopped 60405 60406 mainSegmentLoader.resetEverything(); 60407 return; 60408 } // although the segment loader is an audio segment loader, call the setAudio 60409 // function to ensure it is prepared to re-append the init segment (or handle other 60410 // config changes) 60411 60412 60413 segmentLoader.setAudio(true); 60414 mainSegmentLoader.setAudio(false); 60415 } 60416 60417 if (previousActiveLoader === activeGroup.playlistLoader) { 60418 // Nothing has actually changed. This can happen because track change events can fire 60419 // multiple times for a "single" change. One for enabling the new active track, and 60420 // one for disabling the track that was active 60421 startLoaders(activeGroup.playlistLoader, mediaType); 60422 return; 60423 } 60424 60425 if (segmentLoader.track) { 60426 // For WebVTT, set the new text track in the segmentloader 60427 segmentLoader.track(activeTrack); 60428 } // destructive reset 60429 60430 60431 segmentLoader.resetEverything(); 60432 startLoaders(activeGroup.playlistLoader, mediaType); 60433 }; 60434 }; 60435 60436 var onError = { 60437 /** 60438 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 60439 * an error. 60440 * 60441 * @param {string} type 60442 * MediaGroup type 60443 * @param {Object} settings 60444 * Object containing required information for media groups 60445 * @return {Function} 60446 * Error handler. Logs warning (or error if the playlist is blacklisted) to 60447 * console and switches back to default audio track. 60448 * @function onError.AUDIO 60449 */ 60450 AUDIO: function AUDIO(type, settings) { 60451 return function () { 60452 var segmentLoader = settings.segmentLoaders[type], 60453 mediaType = settings.mediaTypes[type], 60454 blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist; 60455 stopLoaders(segmentLoader, mediaType); // switch back to default audio track 60456 60457 var activeTrack = mediaType.activeTrack(); 60458 var activeGroup = mediaType.activeGroup(); 60459 var id = (activeGroup.filter(function (group) { 60460 return group["default"]; 60461 })[0] || activeGroup[0]).id; 60462 var defaultTrack = mediaType.tracks[id]; 60463 60464 if (activeTrack === defaultTrack) { 60465 // Default track encountered an error. All we can do now is blacklist the current 60466 // rendition and hope another will switch audio groups 60467 blacklistCurrentPlaylist({ 60468 message: 'Problem encountered loading the default audio track.' 60469 }); 60470 return; 60471 } 60472 60473 videojs.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.'); 60474 60475 for (var trackId in mediaType.tracks) { 60476 mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack; 60477 } 60478 60479 mediaType.onTrackChanged(); 60480 }; 60481 }, 60482 60483 /** 60484 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 60485 * an error. 60486 * 60487 * @param {string} type 60488 * MediaGroup type 60489 * @param {Object} settings 60490 * Object containing required information for media groups 60491 * @return {Function} 60492 * Error handler. Logs warning to console and disables the active subtitle track 60493 * @function onError.SUBTITLES 60494 */ 60495 SUBTITLES: function SUBTITLES(type, settings) { 60496 return function () { 60497 var segmentLoader = settings.segmentLoaders[type], 60498 mediaType = settings.mediaTypes[type]; 60499 videojs.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.'); 60500 stopLoaders(segmentLoader, mediaType); 60501 var track = mediaType.activeTrack(); 60502 60503 if (track) { 60504 track.mode = 'disabled'; 60505 } 60506 60507 mediaType.onTrackChanged(); 60508 }; 60509 } 60510 }; 60511 var setupListeners = { 60512 /** 60513 * Setup event listeners for audio playlist loader 60514 * 60515 * @param {string} type 60516 * MediaGroup type 60517 * @param {PlaylistLoader|null} playlistLoader 60518 * PlaylistLoader to register listeners on 60519 * @param {Object} settings 60520 * Object containing required information for media groups 60521 * @function setupListeners.AUDIO 60522 */ 60523 AUDIO: function AUDIO(type, playlistLoader, settings) { 60524 if (!playlistLoader) { 60525 // no playlist loader means audio will be muxed with the video 60526 return; 60527 } 60528 60529 var tech = settings.tech, 60530 requestOptions = settings.requestOptions, 60531 segmentLoader = settings.segmentLoaders[type]; 60532 playlistLoader.on('loadedmetadata', function () { 60533 var media = playlistLoader.media(); 60534 segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload 60535 // permits, start downloading segments 60536 60537 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 60538 segmentLoader.load(); 60539 } 60540 }); 60541 playlistLoader.on('loadedplaylist', function () { 60542 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 60543 60544 if (!tech.paused()) { 60545 segmentLoader.load(); 60546 } 60547 }); 60548 playlistLoader.on('error', onError[type](type, settings)); 60549 }, 60550 60551 /** 60552 * Setup event listeners for subtitle playlist loader 60553 * 60554 * @param {string} type 60555 * MediaGroup type 60556 * @param {PlaylistLoader|null} playlistLoader 60557 * PlaylistLoader to register listeners on 60558 * @param {Object} settings 60559 * Object containing required information for media groups 60560 * @function setupListeners.SUBTITLES 60561 */ 60562 SUBTITLES: function SUBTITLES(type, playlistLoader, settings) { 60563 var tech = settings.tech, 60564 requestOptions = settings.requestOptions, 60565 segmentLoader = settings.segmentLoaders[type], 60566 mediaType = settings.mediaTypes[type]; 60567 playlistLoader.on('loadedmetadata', function () { 60568 var media = playlistLoader.media(); 60569 segmentLoader.playlist(media, requestOptions); 60570 segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload 60571 // permits, start downloading segments 60572 60573 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 60574 segmentLoader.load(); 60575 } 60576 }); 60577 playlistLoader.on('loadedplaylist', function () { 60578 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 60579 60580 if (!tech.paused()) { 60581 segmentLoader.load(); 60582 } 60583 }); 60584 playlistLoader.on('error', onError[type](type, settings)); 60585 } 60586 }; 60587 var initialize = { 60588 /** 60589 * Setup PlaylistLoaders and AudioTracks for the audio groups 60590 * 60591 * @param {string} type 60592 * MediaGroup type 60593 * @param {Object} settings 60594 * Object containing required information for media groups 60595 * @function initialize.AUDIO 60596 */ 60597 'AUDIO': function AUDIO(type, settings) { 60598 var vhs = settings.vhs, 60599 sourceType = settings.sourceType, 60600 segmentLoader = settings.segmentLoaders[type], 60601 requestOptions = settings.requestOptions, 60602 mediaGroups = settings.master.mediaGroups, 60603 _settings$mediaTypes$ = settings.mediaTypes[type], 60604 groups = _settings$mediaTypes$.groups, 60605 tracks = _settings$mediaTypes$.tracks, 60606 logger_ = _settings$mediaTypes$.logger_, 60607 masterPlaylistLoader = settings.masterPlaylistLoader; 60608 var audioOnlyMaster = isAudioOnly(masterPlaylistLoader.master); // force a default if we have none 60609 60610 if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) { 60611 mediaGroups[type] = { 60612 main: { 60613 "default": { 60614 "default": true 60615 } 60616 } 60617 }; 60618 60619 if (audioOnlyMaster) { 60620 mediaGroups[type].main["default"].playlists = masterPlaylistLoader.master.playlists; 60621 } 60622 } 60623 60624 for (var groupId in mediaGroups[type]) { 60625 if (!groups[groupId]) { 60626 groups[groupId] = []; 60627 } 60628 60629 for (var variantLabel in mediaGroups[type][groupId]) { 60630 var properties = mediaGroups[type][groupId][variantLabel]; 60631 var playlistLoader = void 0; 60632 60633 if (audioOnlyMaster) { 60634 logger_("AUDIO group '" + groupId + "' label '" + variantLabel + "' is a master playlist"); 60635 properties.isMasterPlaylist = true; 60636 playlistLoader = null; // if vhs-json was provided as the source, and the media playlist was resolved, 60637 // use the resolved media playlist object 60638 } else if (sourceType === 'vhs-json' && properties.playlists) { 60639 playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions); 60640 } else if (properties.resolvedUri) { 60641 playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); // TODO: dash isn't the only type with properties.playlists 60642 // should we even have properties.playlists in this check. 60643 } else if (properties.playlists && sourceType === 'dash') { 60644 playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader); 60645 } else { 60646 // no resolvedUri means the audio is muxed with the video when using this 60647 // audio track 60648 playlistLoader = null; 60649 } 60650 60651 properties = videojs.mergeOptions({ 60652 id: variantLabel, 60653 playlistLoader: playlistLoader 60654 }, properties); 60655 setupListeners[type](type, properties.playlistLoader, settings); 60656 groups[groupId].push(properties); 60657 60658 if (typeof tracks[variantLabel] === 'undefined') { 60659 var track = new videojs.AudioTrack({ 60660 id: variantLabel, 60661 kind: audioTrackKind_(properties), 60662 enabled: false, 60663 language: properties.language, 60664 "default": properties["default"], 60665 label: variantLabel 60666 }); 60667 tracks[variantLabel] = track; 60668 } 60669 } 60670 } // setup single error event handler for the segment loader 60671 60672 60673 segmentLoader.on('error', onError[type](type, settings)); 60674 }, 60675 60676 /** 60677 * Setup PlaylistLoaders and TextTracks for the subtitle groups 60678 * 60679 * @param {string} type 60680 * MediaGroup type 60681 * @param {Object} settings 60682 * Object containing required information for media groups 60683 * @function initialize.SUBTITLES 60684 */ 60685 'SUBTITLES': function SUBTITLES(type, settings) { 60686 var tech = settings.tech, 60687 vhs = settings.vhs, 60688 sourceType = settings.sourceType, 60689 segmentLoader = settings.segmentLoaders[type], 60690 requestOptions = settings.requestOptions, 60691 mediaGroups = settings.master.mediaGroups, 60692 _settings$mediaTypes$2 = settings.mediaTypes[type], 60693 groups = _settings$mediaTypes$2.groups, 60694 tracks = _settings$mediaTypes$2.tracks, 60695 masterPlaylistLoader = settings.masterPlaylistLoader; 60696 60697 for (var groupId in mediaGroups[type]) { 60698 if (!groups[groupId]) { 60699 groups[groupId] = []; 60700 } 60701 60702 for (var variantLabel in mediaGroups[type][groupId]) { 60703 if (mediaGroups[type][groupId][variantLabel].forced) { 60704 // Subtitle playlists with the forced attribute are not selectable in Safari. 60705 // According to Apple's HLS Authoring Specification: 60706 // If content has forced subtitles and regular subtitles in a given language, 60707 // the regular subtitles track in that language MUST contain both the forced 60708 // subtitles and the regular subtitles for that language. 60709 // Because of this requirement and that Safari does not add forced subtitles, 60710 // forced subtitles are skipped here to maintain consistent experience across 60711 // all platforms 60712 continue; 60713 } 60714 60715 var properties = mediaGroups[type][groupId][variantLabel]; 60716 var playlistLoader = void 0; 60717 60718 if (sourceType === 'hls') { 60719 playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); 60720 } else if (sourceType === 'dash') { 60721 var playlists = properties.playlists.filter(function (p) { 60722 return p.excludeUntil !== Infinity; 60723 }); 60724 60725 if (!playlists.length) { 60726 return; 60727 } 60728 60729 playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader); 60730 } else if (sourceType === 'vhs-json') { 60731 playlistLoader = new PlaylistLoader( // if the vhs-json object included the media playlist, use the media playlist 60732 // as provided, otherwise use the resolved URI to load the playlist 60733 properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions); 60734 } 60735 60736 properties = videojs.mergeOptions({ 60737 id: variantLabel, 60738 playlistLoader: playlistLoader 60739 }, properties); 60740 setupListeners[type](type, properties.playlistLoader, settings); 60741 groups[groupId].push(properties); 60742 60743 if (typeof tracks[variantLabel] === 'undefined') { 60744 var track = tech.addRemoteTextTrack({ 60745 id: variantLabel, 60746 kind: 'subtitles', 60747 "default": properties["default"] && properties.autoselect, 60748 language: properties.language, 60749 label: variantLabel 60750 }, false).track; 60751 tracks[variantLabel] = track; 60752 } 60753 } 60754 } // setup single error event handler for the segment loader 60755 60756 60757 segmentLoader.on('error', onError[type](type, settings)); 60758 }, 60759 60760 /** 60761 * Setup TextTracks for the closed-caption groups 60762 * 60763 * @param {String} type 60764 * MediaGroup type 60765 * @param {Object} settings 60766 * Object containing required information for media groups 60767 * @function initialize['CLOSED-CAPTIONS'] 60768 */ 60769 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) { 60770 var tech = settings.tech, 60771 mediaGroups = settings.master.mediaGroups, 60772 _settings$mediaTypes$3 = settings.mediaTypes[type], 60773 groups = _settings$mediaTypes$3.groups, 60774 tracks = _settings$mediaTypes$3.tracks; 60775 60776 for (var groupId in mediaGroups[type]) { 60777 if (!groups[groupId]) { 60778 groups[groupId] = []; 60779 } 60780 60781 for (var variantLabel in mediaGroups[type][groupId]) { 60782 var properties = mediaGroups[type][groupId][variantLabel]; // Look for either 608 (CCn) or 708 (SERVICEn) caption services 60783 60784 if (!/^(?:CC|SERVICE)/.test(properties.instreamId)) { 60785 continue; 60786 } 60787 60788 var captionServices = tech.options_.vhs && tech.options_.vhs.captionServices || {}; 60789 var newProps = { 60790 label: variantLabel, 60791 language: properties.language, 60792 instreamId: properties.instreamId, 60793 "default": properties["default"] && properties.autoselect 60794 }; 60795 60796 if (captionServices[newProps.instreamId]) { 60797 newProps = videojs.mergeOptions(newProps, captionServices[newProps.instreamId]); 60798 } 60799 60800 if (newProps["default"] === undefined) { 60801 delete newProps["default"]; 60802 } // No PlaylistLoader is required for Closed-Captions because the captions are 60803 // embedded within the video stream 60804 60805 60806 groups[groupId].push(videojs.mergeOptions({�
60806 60807 id: variantLabel 60808 }, properties)); 60809 60810 if (typeof tracks[variantLabel] === 'undefined') { 60811 var track = tech.addRemoteTextTrack({ 60812 id: newProps.instreamId, 60813 kind: 'captions', 60814 "default": newProps["default"], 60815 language: newProps.language, 60816 label: newProps.label 60817 }, false).track; 60818 tracks[variantLabel] = track; 60819 } 60820 } 60821 } 60822 } 60823 }; 60824 60825 var groupMatch = function groupMatch(list, media) { 60826 for (var i = 0; i < list.length; i++) { 60827 if (playlistMatch(media, list[i])) { 60828 return true; 60829 } 60830 60831 if (list[i].playlists && groupMatch(list[i].playlists, media)) { 60832 return true; 60833 } 60834 } 60835 60836 return false; 60837 }; 60838 /** 60839 * Returns a function used to get the active group of the provided type 60840 * 60841 * @param {string} type 60842 * MediaGroup type 60843 * @param {Object} settings 60844 * Object containing required information for media groups 60845 * @return {Function} 60846 * Function that returns the active media group for the provided type. Takes an 60847 * optional parameter {TextTrack} track. If no track is provided, a list of all 60848 * variants in the group, otherwise the variant corresponding to the provided 60849 * track is returned. 60850 * @function activeGroup 60851 */ 60852 60853 60854 var activeGroup = function activeGroup(type, settings) { 60855 return function (track) { 60856 var masterPlaylistLoader = settings.masterPlaylistLoader, 60857 groups = settings.mediaTypes[type].groups; 60858 var media = masterPlaylistLoader.media(); 60859 60860 if (!media) { 60861 return null; 60862 } 60863 60864 var variants = null; // set to variants to main media active group 60865 60866 if (media.attributes[type]) { 60867 variants = groups[media.attributes[type]]; 60868 } 60869 60870 var groupKeys = Object.keys(groups); 60871 60872 if (!variants) { 60873 // find the masterPlaylistLoader media 60874 // that is in a media group if we are dealing 60875 // with audio only 60876 if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.master)) { 60877 for (var i = 0; i < groupKeys.length; i++) { 60878 var groupPropertyList = groups[groupKeys[i]]; 60879 60880 if (groupMatch(groupPropertyList, media)) { 60881 variants = groupPropertyList; 60882 break; 60883 } 60884 } // use the main group if it exists 60885 60886 } else if (groups.main) { 60887 variants = groups.main; // only one group, use that one 60888 } else if (groupKeys.length === 1) { 60889 variants = groups[groupKeys[0]]; 60890 } 60891 } 60892 60893 if (typeof track === 'undefined') { 60894 return variants; 60895 } 60896 60897 if (track === null || !variants) { 60898 // An active track was specified so a corresponding group is expected. track === null 60899 // means no track is currently active so there is no corresponding group 60900 return null; 60901 } 60902 60903 return variants.filter(function (props) { 60904 return props.id === track.id; 60905 })[0] || null; 60906 }; 60907 }; 60908 60909 var activeTrack = { 60910 /** 60911 * Returns a function used to get the active track of type provided 60912 * 60913 * @param {string} type 60914 * MediaGroup type 60915 * @param {Object} settings 60916 * Object containing required information for media groups 60917 * @return {Function} 60918 * Function that returns the active media track for the provided type. Returns 60919 * null if no track is active 60920 * @function activeTrack.AUDIO 60921 */ 60922 AUDIO: function AUDIO(type, settings) { 60923 return function () { 60924 var tracks = settings.mediaTypes[type].tracks; 60925 60926 for (var id in tracks) { 60927 if (tracks[id].enabled) { 60928 return tracks[id]; 60929 } 60930 } 60931 60932 return null; 60933 }; 60934 }, 60935 60936 /** 60937 * Returns a function used to get the active track of type provided 60938 * 60939 * @param {string} type 60940 * MediaGroup type 60941 * @param {Object} settings 60942 * Object containing required information for media groups 60943 * @return {Function} 60944 * Function that returns the active media track for the provided type. Returns 60945 * null if no track is active 60946 * @function activeTrack.SUBTITLES 60947 */ 60948 SUBTITLES: function SUBTITLES(type, settings) { 60949 return function () { 60950 var tracks = settings.mediaTypes[type].tracks; 60951 60952 for (var id in tracks) { 60953 if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') { 60954 return tracks[id]; 60955 } 60956 } 60957 60958 return null; 60959 }; 60960 } 60961 }; 60962 60963 var getActiveGroup = function getActiveGroup(type, _ref) { 60964 var mediaTypes = _ref.mediaTypes; 60965 return function () { 60966 var activeTrack_ = mediaTypes[type].activeTrack(); 60967 60968 if (!activeTrack_) { 60969 return null; 60970 } 60971 60972 return mediaTypes[type].activeGroup(activeTrack_); 60973 }; 60974 }; 60975 /** 60976 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles, 60977 * Closed-Captions) specified in the master manifest. 60978 * 60979 * @param {Object} settings 60980 * Object containing required information for setting up the media groups 60981 * @param {Tech} settings.tech 60982 * The tech of the player 60983 * @param {Object} settings.requestOptions 60984 * XHR request options used by the segment loaders 60985 * @param {PlaylistLoader} settings.masterPlaylistLoader 60986 * PlaylistLoader for the master source 60987 * @param {VhsHandler} settings.vhs 60988 * VHS SourceHandler 60989 * @param {Object} settings.master 60990 * The parsed master manifest 60991 * @param {Object} settings.mediaTypes 60992 * Object to store the loaders, tracks, and utility methods for each media type 60993 * @param {Function} settings.blacklistCurrentPlaylist 60994 * Blacklists the current rendition and forces a rendition switch. 60995 * @function setupMediaGroups 60996 */ 60997 60998 60999 var setupMediaGroups = function setupMediaGroups(settings) { 61000 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 61001 initialize[type](type, settings); 61002 });
61003 var mediaTypes = settings.mediaTypes, 61004 masterPlaylistLoader = settings.masterPlaylistLoader, 61005 tech = settings.tech, 61006 vhs = settings.vhs, 61007 _settings$segmentLoad3 = settings.segmentLoaders, 61008 audioSegmentLoader = _settings$segmentLoad3['AUDIO'], 61009 mainSegmentLoader = _settings$segmentLoad3.main; // setup active group and track getters and change event handlers 61010 61011 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 61012 mediaTypes[type].activeGroup = activeGroup(type, settings); 61013 mediaTypes[type].activeTrack = activeTrack[type](type, settings); 61014 mediaTypes[type].onGroupChanged = onGroupChanged(type, settings); 61015 mediaTypes[type].onGroupChanging = onGroupChanging(type, settings); 61016 mediaTypes[type].onTrackChanged = onTrackChanged(type, settings); 61017 mediaTypes[type].getActiveGroup = getActiveGroup(type, settings); 61018 }); // DO NOT enable the default subtitle or caption track. 61019 // DO enable the default audio track 61020 61021 var audioGroup = mediaTypes.AUDIO.activeGroup(); 61022 61023 if (audioGroup) { 61024 var groupId = (audioGroup.filter(function (group) { 61025 return group["default"]; 61026 })[0] || audioGroup[0]).id; 61027 mediaTypes.AUDIO.tracks[groupId].enabled = true; 61028 mediaTypes.AUDIO.onGroupChanged(); 61029 mediaTypes.AUDIO.onTrackChanged(); 61030 var activeAudioGroup = mediaTypes.AUDIO.getActiveGroup(); // a similar check for handling setAudio on each loader is run again each time the 61031 // track is changed, but needs to be handled here since the track may not be considered 61032 // changed on the first call to onTrackChanged 61033 61034 if (!activeAudioGroup.playlistLoader) { 61035 // either audio is muxed with video or the stream is audio only 61036 mainSegmentLoader.setAudio(true); 61037 } else { 61038 // audio is demuxed 61039 mainSegmentLoader.setAudio(false); 61040 audioSegmentLoader.setAudio(true); 61041 } 61042 } 61043 61044 masterPlaylistLoader.on('mediachange', function () { 61045 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 61046 return mediaTypes[type].onGroupChanged(); 61047 }); 61048 }); 61049 masterPlaylistLoader.on('mediachanging', function () { 61050 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 61051 return mediaTypes[type].onGroupChanging(); 61052 }); 61053 }); // custom audio track change event handler for usage event 61054 61055 var onAudioTrackChanged = function onAudioTrackChanged() { 61056 mediaTypes.AUDIO.onTrackChanged(); 61057 tech.trigger({ 61058 type: 'usage', 61059 name: 'vhs-audio-change' 61060 }); 61061 tech.trigger({ 61062 type: 'usage', 61063 name: 'hls-audio-change' 61064 }); 61065 }; 61066 61067 tech.audioTracks().addEventListener('change', onAudioTrackChanged); 61068 tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 61069 vhs.on('dispose', function () { 61070 tech.audioTracks().removeEventListener('change', onAudioTrackChanged); 61071 tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 61072 }); // clear existing audio tracks and add the ones we just created 61073 61074 tech.clearTracks('audio'); 61075 61076 for (var id in mediaTypes.AUDIO.tracks) { 61077 tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]); 61078 } 61079 }; 61080 /** 61081 * Creates skeleton object used to store the loaders, tracks, and utility methods for each 61082 * media type 61083 * 61084 * @return {Object} 61085 * Object to store the loaders, tracks, and utility methods for each media type 61086 * @function createMediaTypes 61087 */ 61088 61089 61090 var createMediaTypes = function createMediaTypes() { 61091 var mediaTypes = {}; 61092 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 61093 mediaTypes[type] = { 61094 groups: {}, 61095 tracks: {}, 61096 activePlaylistLoader: null, 61097 activeGroup: noop, 61098 activeTrack: noop, 61099 getActiveGroup: noop, 61100 onGroupChanged: noop, 61101 onTrackChanged: noop, 61102 lastTrack_: null, 61103 logger_: logger("MediaGroups[" + type + "]") 61104 }; 61105 });
61106 return mediaTypes; 61107 }; 61108 61109 var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2; 61110 var Vhs$1; // SegmentLoader stats that need to have each loader's 61111 // values summed to calculate the final value 61112 61113 var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred', 'mediaAppends']; 61114 61115 var sumLoaderStat = function sumLoaderStat(stat) { 61116 return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat]; 61117 }; 61118 61119 var shouldSwitchToMedia = function shouldSwitchToMedia(_ref) { 61120 var currentPlaylist = _ref.currentPlaylist, 61121 nextPlaylist = _ref.nextPlaylist, 61122 forwardBuffer = _ref.forwardBuffer, 61123 bufferLowWaterLine = _ref.bufferLowWaterLine, 61124 bufferHighWaterLine = _ref.bufferHighWaterLine, 61125 duration = _ref.duration, 61126 experimentalBufferBasedABR = _ref.experimentalBufferBasedABR, 61127 log = _ref.log; // we have no other playlist to switch to 61128 61129 if (!nextPlaylist) { 61130 videojs.log.warn('We received no playlist to switch to. Please check your stream.'); 61131 return false; 61132 } 61133 61134 var sharedLogLine = "allowing switch " + (currentPlaylist && currentPlaylist.id || 'null') + " -> " + nextPlaylist.id; 61135 61136 if (!currentPlaylist) { 61137 log(sharedLogLine + " as current playlist is not set"); 61138 return true; 61139 } // no need to switch if playlist is the same 61140 61141 61142 if (nextPlaylist.id === currentPlaylist.id) { 61143 return false; 61144 } // If the playlist is live, then we want to not take low water line into account. 61145 // This is because in LIVE, the player plays 3 segments from the end of the 61146 // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble 61147 // in those segments, a viewer will never experience a rendition upswitch. 61148 61149 61150 if (!currentPlaylist.endList) { 61151 log(sharedLogLine + " as current playlist is live"); 61152 return true; 61153 } 61154 61155 var maxBufferLowWaterLine = experimentalBufferBasedABR ? 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 61156 // duration is below the max potential low water line 61157 61158 if (duration < maxBufferLowWaterLine) { 61159 log(sharedLogLine + " as duration < max low water line (" + duration + " < " + maxBufferLowWaterLine + ")"); 61160 return true; 61161 } 61162 61163 var nextBandwidth = nextPlaylist.attributes.BANDWIDTH; 61164 var currBandwidth = currentPlaylist.attributes.BANDWIDTH; // when switching down, if our buffer is lower than the high water line, 61165 // we can switch down 61166 61167 if (nextBandwidth < currBandwidth && (!experimentalBufferBasedABR || forwardBuffer < bufferHighWaterLine)) { 61168 var logLine = sharedLogLine + " as next bandwidth < current bandwidth (" + nextBandwidth + " < " + currBandwidth + ")"; 61169 61170 if (experimentalBufferBasedABR) { 61171 logLine += " and forwardBuffer < bufferHighWaterLine (" + forwardBuffer + " < " + bufferHighWaterLine + ")"; 61172 } 61173 61174 log(logLine); 61175 return true; 61176 } // and if our buffer is higher than the low water line, 61177 // we can switch up 61178 61179 61180 if ((!experimentalBufferBasedABR || nextBandwidth > currBandwidth) && forwardBuffer >= bufferLowWaterLine) { 61181 var _logLine = sharedLogLine + " as forwardBuffer >= bufferLowWaterLine (" + forwardBuffer + " >= " + bufferLowWaterLine + ")"; 61182 61183 if (experimentalBufferBasedABR) { 61184 _logLine += " and next bandwidth > current bandwidth (" + nextBandwidth + " > " + currBandwidth + ")"; 61185 } 61186 61187 log(_logLine); 61188 return true; 61189 } 61190 61191 log("not " + sharedLogLine + " as no switching criteria met"); 61192 return false; 61193 }; 61194 /** 61195 * the master playlist controller controller all interactons 61196 * between playlists and segmentloaders. At this time this mainly 61197 * involves a master playlist and a series of audio playlists 61198 * if they are available 61199 * 61200 * @class MasterPlaylistController 61201 * @extends videojs.EventTarget 61202 */ 61203 61204 61205 var MasterPlaylistController = /*#__PURE__*/function (_videojs$EventTarget) { 61206 inheritsLoose(MasterPlaylistController, _videojs$EventTarget); 61207 61208 function MasterPlaylistController(options) { 61209 var _this; 61210 61211 _this = _videojs$EventTarget.call(this) || this; 61212 var src = options.src, 61213 handleManifestRedirects = options.handleManifestRedirects, 61214 withCredentials = options.withCredentials, 61215 tech = options.tech, 61216 bandwidth = options.bandwidth, 61217 externVhs = options.externVhs, 61218 useCueTags = options.useCueTags,
61219 blacklistDuration = options.blacklistDuration, 61220 enableLowInitialPlaylist = options.enableLowInitialPlaylist, 61221 sourceType = options.sourceType, 61222 cacheEncryptionKeys = options.cacheEncryptionKeys, 61223 experimentalBufferBasedABR = options.experimentalBufferBasedABR, 61224 experimentalLeastPixelDiffSelector = options.experimentalLeastPixelDiffSelector; 61225 61226 if (!src) { 61227 throw new Error('A non-empty playlist URL or JSON manifest string is required'); 61228 } 61229 61230 var maxPlaylistRetries = options.maxPlaylistRetries; 61231 61232 if (maxPlaylistRetries === null || typeof maxPlaylistRetries === 'undefined') { 61233 maxPlaylistRetries = Infinity; 61234 } 61235 61236 Vhs$1 = externVhs; 61237 _this.experimentalBufferBasedABR = Boolean(experimentalBufferBasedABR); 61238 _this.experimentalLeastPixelDiffSelector = Boolean(experimentalLeastPixelDiffSelector); 61239 _this.withCredentials = withCredentials; 61240 _this.tech_ = tech; 61241 _this.vhs_ = tech.vhs; 61242 _this.sourceType_ = sourceType; 61243 _this.useCueTags_ = useCueTags;
61244 _this.blacklistDuration = blacklistDuration; 61245 _this.maxPlaylistRetries = maxPlaylistRetries; 61246 _this.enableLowInitialPlaylist = enableLowInitialPlaylist; 61247 61248 if (_this.useCueTags_) { 61249 _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues'); 61250 _this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 61251 } 61252 61253 _this.requestOptions_ = { 61254 withCredentials: withCredentials, 61255 handleManifestRedirects: handleManifestRedirects, 61256 maxPlaylistRetries: maxPlaylistRetries, 61257 timeout: null 61258 }; 61259 61260 _this.on('error', _this.pauseLoading); 61261 61262 _this.mediaTypes_ = createMediaTypes(); 61263 _this.mediaSource = new window_1.MediaSource(); 61264 _this.handleDurationChange_ = _this.handleDurationChange_.bind(assertThisInitialized(_this)); 61265 _this.handleSourceOpen_ = _this.handleSourceOpen_.bind(assertThisInitialized(_this)); 61266 _this.handleSourceEnded_ = _this.handleSourceEnded_.bind(assertThisInitialized(_this)); 61267 61268 _this.mediaSource.addEventListener('durationchange', _this.handleDurationChange_); // load the media source into the player 61269 61270 61271 _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_); 61272 61273 _this.mediaSource.addEventListener('sourceended', _this.handleSourceEnded_); // we don't have to handle sourceclose since dispose will handle termination of 61274 // everything, and the MediaSource should not be detached without a proper disposal 61275 61276 61277 _this.seekable_ = videojs.createTimeRanges(); 61278 _this.hasPlayed_ = false; 61279 _this.syncController_ = new SyncController(options); 61280 _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({ 61281 kind: 'metadata', 61282 label: 'segment-metadata' 61283 }, false).track; 61284 _this.decrypter_ = new Decrypter(); 61285 _this.sourceUpdater_ = new SourceUpdater(_this.mediaSource); 61286 _this.inbandTextTracks_ = {}; 61287 _this.timelineChangeController_ = new TimelineChangeController(); 61288 var segmentLoaderSettings = { 61289 vhs: _this.vhs_, 61290 parse708captions: options.parse708captions, 61291 mediaSource: _this.mediaSource, 61292 currentTime: _this.tech_.currentTime.bind(_this.tech_), 61293 seekable: function seekable() { 61294 return _this.seekable(); 61295 }, 61296 seeking: function seeking() { 61297 return _this.tech_.seeking(); 61298 }, 61299 duration: function duration() { 61300 return _this.duration(); 61301 }, 61302 hasPlayed: function hasPlayed() { 61303 return _this.hasPlayed_; 61304 }, 61305 goalBufferLength: function goalBufferLength() { 61306 return _this.goalBufferLength(); 61307 }, 61308 bandwidth: bandwidth, 61309 syncController: _this.syncController_, 61310 decrypter: _this.decrypter_, 61311 sourceType: _this.sourceType_, 61312 inbandTextTracks: _this.inbandTextTracks_, 61313 cacheEncryptionKeys: cacheEncryptionKeys, 61314 sourceUpdater: _this.sourceUpdater_, 61315 timelineChangeController: _this.timelineChangeController_, 61316 experimentalExactManifestTimings: options.experimentalExactManifestTimings 61317 }; // The source type check not only determines whether a special DASH playlist loader 61318 // should be used, but also covers the case where the provided src is a vhs-json 61319 // manifest object (instead of a URL). In the case of vhs-json, the default 61320 // PlaylistLoader should be used. 61321 61322 _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, _this.vhs_, _this.requestOptions_) : new PlaylistLoader(src, _this.vhs_, _this.requestOptions_); 61323 61324 _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders 61325 // combined audio/video or just video when alternate audio track is selected 61326 61327 61328 _this.mainSegmentLoader_ = new SegmentLoader(videojs.mergeOptions(segmentLoaderSettings, { 61329 segmentMetadataTrack: _this.segmentMetadataTrack_, 61330 loaderType: 'main' 61331 }), options); // alternate audio track 61332 61333 _this.audioSegmentLoader_ = new SegmentLoader(videojs.mergeOptions(segmentLoaderSettings, { 61334 loaderType: 'audio' 61335 }), options); 61336 _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs.mergeOptions(segmentLoaderSettings, { 61337 loaderType: 'vtt', 61338 featuresNativeTextTracks: _this.tech_.featuresNativeTextTracks 61339 }), options); 61340 61341 _this.setupSegmentLoaderListeners_(); 61342 61343 if (_this.experimentalBufferBasedABR) { 61344 _this.masterPlaylistLoader_.one('loadedplaylist', function () { 61345 return _this.startABRTimer_(); 61346 }); 61347 61348 _this.tech_.on('pause', function () { 61349 return _this.stopABRTimer_(); 61350 }); 61351 61352 _this.tech_.on('play', function () { 61353 return _this.startABRTimer_(); 61354 }); 61355 } // Create SegmentLoader stat-getters 61356 // mediaRequests_ 61357 // mediaRequestsAborted_ 61358 // mediaRequestsTimedout_ 61359 // mediaRequestsErrored_ 61360 // mediaTransferDuration_ 61361 // mediaBytesTransferred_ 61362 // mediaAppends_ 61363 61364
61365 loaderStats.forEach(function (stat) { 61366 _this[stat + '_'] = sumLoaderStat.bind(assertThisInitialized(_this), stat); 61367 }); 61368 _this.logger_ = logger('MPC'); 61369 _this.triggeredFmp4Usage = false; 61370 61371 if (_this.tech_.preload() === 'none') { 61372 _this.loadOnPlay_ = function () { 61373 _this.loadOnPlay_ = null; 61374 61375 _this.masterPlaylistLoader_.load(); 61376 }; 61377 61378 _this.tech_.one('play', _this.loadOnPlay_); 61379 } else { 61380 _this.masterPlaylistLoader_.load(); 61381 } 61382 61383 _this.timeToLoadedData__ = -1; 61384 _this.mainAppendsToLoadedData__ = -1; 61385 _this.audioAppendsToLoadedData__ = -1; 61386 var event = _this.tech_.preload() === 'none' ? 'play' : 'loadstart'; // start the first frame timer on loadstart or play (for preload none) 61387 61388 _this.tech_.one(event, function () { 61389 var timeToLoadedDataStart = Date.now(); 61390 61391 _this.tech_.one('loadeddata', function () { 61392 _this.timeToLoadedData__ = Date.now() - timeToLoadedDataStart; 61393 _this.mainAppendsToLoadedData__ = _this.mainSegmentLoader_.mediaAppends; 61394 _this.audioAppendsToLoadedData__ = _this.audioSegmentLoader_.mediaAppends; 61395 }); 61396 }); 61397 61398 return _this; 61399 } 61400 61401 var _proto = MasterPlaylistController.prototype; 61402 61403 _proto.mainAppendsToLoadedData_ = function mainAppendsToLoadedData_() { 61404 return this.mainAppendsToLoadedData__; 61405 }; 61406 61407 _proto.audioAppendsToLoadedData_ = function audioAppendsToLoadedData_() { 61408 return this.audioAppendsToLoadedData__; 61409 }; 61410 61411 _proto.appendsToLoadedData_ = function appendsToLoadedData_() { 61412 var main = this.mainAppendsToLoadedData_(); 61413 var audio = this.audioAppendsToLoadedData_(); 61414 61415 if (main === -1 || audio === -1) { 61416 return -1; 61417 } 61418 61419 return main + audio; 61420 }; 61421 61422 _proto.timeToLoadedData_ = function timeToLoadedData_() { 61423 return this.timeToLoadedData__; 61424 } 61425 /** 61426 * Run selectPlaylist and switch to the new playlist if we should 61427 * 61428 * @private 61429 * 61430 */ 61431 ; 61432 61433 _proto.checkABR_ = function checkABR_() { 61434 var nextPlaylist = this.selectPlaylist(); 61435 61436 if (nextPlaylist && this.shouldSwitchToMedia_(nextPlaylist)) { 61437 this.switchMedia_(nextPlaylist, 'abr'); 61438 } 61439 }; 61440 61441 _proto.switchMedia_ = function switchMedia_(playlist, cause, delay) { 61442 var oldMedia = this.media(); 61443 var oldId = oldMedia && (oldMedia.id || oldMedia.uri); 61444 var newId = playlist.id || playlist.uri; 61445 61446 if (oldId && oldId !== newId) { 61447 this.logger_("switch media " + oldId + " -> " + newId + " from " + cause); 61448 this.tech_.trigger({ 61449 type: 'usage', 61450 name: "vhs-rendition-change-" + cause 61451 }); 61452 } 61453 61454 this.masterPlaylistLoader_.media(playlist, delay); 61455 } 61456 /** 61457 * Start a timer that periodically calls checkABR_ 61458 * 61459 * @private 61460 */ 61461 ; 61462 61463 _proto.startABRTimer_ = function startABRTimer_() { 61464 var _this2 = this; 61465 61466 this.stopABRTimer_(); 61467 this.abrTimer_ = window_1.setInterval(function () { 61468 return _this2.checkABR_(); 61469 }, 250); 61470 } 61471 /** 61472 * Stop the timer that periodically calls checkABR_ 61473 * 61474 * @private 61475 */ 61476 ; 61477 61478 _proto.stopABRTimer_ = function stopABRTimer_() { 61479 // if we're scrubbing, we don't need to pause. 61480 // This getter will be added to Video.js in version 7.11. 61481 if (this.tech_.scrubbing && this.tech_.scrubbing()) { 61482 return; 61483 } 61484 61485 window_1.clearInterval(this.abrTimer_); 61486 this.abrTimer_ = null; 61487 } 61488 /** 61489 * Get a list of playlists for the currently selected audio playlist 61490 * 61491 * @return {Array} the array of audio playlists 61492 */ 61493 ; 61494 61495 _proto.getAudioTrackPlaylists_ = function getAudioTrackPlaylists_() { 61496 var master = this.master(); 61497 var defaultPlaylists = master && master.playlists || []; // if we don't have any audio groups then we can only 61498 // assume that the audio tracks are contained in masters 61499 // playlist array, use that or an empty array. 61500 61501 if (!master || !master.mediaGroups || !master.mediaGroups.AUDIO) { 61502 return defaultPlaylists; 61503 } 61504 61505 var AUDIO = master.mediaGroups.AUDIO; 61506 var groupKeys = Object.keys(AUDIO); 61507 var track; // get the current active track 61508 61509 if (Object.keys(this.mediaTypes_.AUDIO.groups).length) { 61510 track = this.mediaTypes_.AUDIO.activeTrack(); // or get the default track from master if mediaTypes_ isn't setup yet 61511 } else { 61512 // default group is `main` or just the first group. 61513 var defaultGroup = AUDIO.main || groupKeys.length && AUDIO[groupKeys[0]];
61514 61515 for (var label in defaultGroup) { 61516 if (defaultGroup[label]["default"]) { 61517 track = { 61518 label: label 61519 }; 61520 break; 61521 } 61522 } 61523 } // no active track no playlists. 61524 61525 61526 if (!track) { 61527 return defaultPlaylists; 61528 } 61529 61530 var playlists = []; // get all of the playlists that are possible for the 61531 // active track. 61532 61533 for (var group in AUDIO) { 61534 if (AUDIO[group][track.label]) { 61535 var properties = AUDIO[group][track.label]; 61536 61537 if (properties.playlists && properties.playlists.length) { 61538 playlists.push.apply(playlists, properties.playlists); 61539 } else if (properties.uri) { 61540 playlists.push(properties); 61541 } else if (master.playlists.length) { 61542 // if an audio group does not have a uri 61543 // see if we have main playlists that use it as a group. 61544 // if we do then add those to the playlists list. 61545 for (var i = 0; i < master.playlists.length; i++) { 61546 var playlist = master.playlists[i]; 61547 61548 if (playlist.attributes && playlist.attributes.AUDIO && playlist.attributes.AUDIO === group) { 61549 playlists.push(playlist); 61550 } 61551 } 61552 } 61553 } 61554 } 61555 61556 if (!playlists.length) { 61557 return defaultPlaylists; 61558 } 61559 61560 return playlists; 61561 } 61562 /** 61563 * Register event handlers on the master playlist loader. A helper 61564 * function for construction time. 61565 * 61566 * @private 61567 */ 61568 ; 61569 61570 _proto.setupMasterPlaylistLoaderListeners_ = function setupMasterPlaylistLoaderListeners_() { 61571 var _this3 = this; 61572 61573 this.masterPlaylistLoader_.on('loadedmetadata', function () { 61574 var media = _this3.masterPlaylistLoader_.media(); 61575 61576 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 61577 // timeout the request. 61578 61579 if (isLowestEnabledRendition(_this3.masterPlaylistLoader_.master, _this3.masterPlaylistLoader_.media())) { 61580 _this3.requestOptions_.timeout = 0; 61581 } else { 61582 _this3.requestOptions_.timeout = requestTimeout; 61583 } // if this isn't a live video and preload permits, start 61584 // downloading segments 61585 61586 61587 if (media.endList && _this3.tech_.preload() !== 'none') { 61588 _this3.mainSegmentLoader_.playlist(media, _this3.requestOptions_); 61589 61590 _this3.mainSegmentLoader_.load(); 61591 } 61592 61593 setupMediaGroups({ 61594 sourceType: _this3.sourceType_, 61595 segmentLoaders: { 61596 AUDIO: _this3.audioSegmentLoader_, 61597 SUBTITLES: _this3.subtitleSegmentLoader_, 61598 main: _this3.mainSegmentLoader_ 61599 }, 61600 tech: _this3.tech_, 61601 requestOptions: _this3.requestOptions_, 61602 masterPlaylistLoader: _this3.masterPlaylistLoader_, 61603 vhs: _this3.vhs_, 61604 master: _this3.master(), 61605 mediaTypes: _this3.mediaTypes_, 61606 blacklistCurrentPlaylist: _this3.blacklistCurrentPlaylist.bind(_this3) 61607 }); 61608 61609 _this3.triggerPresenceUsage_(_this3.master(), media); 61610 61611 _this3.setupFirstPlay(); 61612 61613 if (!_this3.mediaTypes_.AUDIO.activePlaylistLoader || _this3.mediaTypes_.AUDIO.activePlaylistLoader.media()) { 61614 _this3.trigger('selectedinitialmedia'); 61615 } else { 61616 // We must wait for the active audio playlist loader to 61617 // finish setting up before triggering this event so the 61618 // representations API and EME setup is correct 61619 _this3.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () { 61620 _this3.trigger('selectedinitialmedia'); 61621 }); 61622 } 61623 }); 61624 this.masterPlaylistLoader_.on('loadedplaylist', function () { 61625 if (_this3.loadOnPlay_) { 61626 _this3.tech_.off('play', _this3.loadOnPlay_); 61627 } 61628 61629 var updatedPlaylist = _this3.masterPlaylistLoader_.media(); 61630 61631 if (!updatedPlaylist) { 61632 // exclude any variants that are not supported by the browser before selecting 61633 // an initial media as the playlist selectors do not consider browser support 61634 _this3.excludeUnsupportedVariants_(); 61635 61636 var selectedMedia; 61637 61638 if (_this3.enableLowInitialPlaylist) { 61639 selectedMedia = _this3.selectInitialPlaylist(); 61640 } 61641 61642 if (!selectedMedia) { 61643 selectedMedia = _this3.selectPlaylist(); 61644 } 61645 61646 if (!selectedMedia || !_this3.shouldSwitchToMedia_(selectedMedia)) { 61647 return; 61648 } 61649 61650 _this3.initialMedia_ = selectedMedia; 61651 61652 _this3.switchMedia_(_this3.initialMedia_, 'initial'); // Under the standard case where a source URL is provided, loadedplaylist will 61653 // fire again since the playlist will be requested. In the case of vhs-json 61654 // (where the manifest object is provided as the source), when the media 61655 // playlist's `segments` list is already available, a media playlist won't be 61656 // requested, and loadedplaylist won't fire again, so the playlist handler must be 61657 // called on its own here. 61658 61659 61660 var haveJsonSource = _this3.sourceType_ === 'vhs-json' && _this3.initialMedia_.segments; 61661 61662 if (!haveJsonSource) { 61663 return; 61664 } 61665 61666 updatedPlaylist = _this3.initialMedia_; 61667 } 61668 61669 _this3.handleUpdatedMediaPlaylist(updatedPlaylist); 61670 }); 61671 this.masterPlaylistLoader_.on('error', function () { 61672 _this3.blacklistCurrentPlaylist(_this3.masterPlaylistLoader_.error); 61673 }); 61674 this.masterPlaylistLoader_.on('mediachanging', function () { 61675 _this3.mainSegmentLoader_.abort(); 61676 61677 _this3.mainSegmentLoader_.pause(); 61678 }); 61679 this.masterPlaylistLoader_.on('mediachange', function () { 61680 var media = _this3.masterPlaylistLoader_.media(); 61681 61682 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 61683 // timeout the request. 61684 61685 if (isLowestEnabledRendition(_this3.masterPlaylistLoader_.master, _this3.masterPlaylistLoader_.media())) { 61686 _this3.requestOptions_.timeout = 0; 61687 } else { 61688 _this3.requestOptions_.timeout = requestTimeout; 61689 } // TODO: Create a new event on the PlaylistLoader that signals 61690 // that the segments have changed in some way and use that to 61691 // update the SegmentLoader instead of doing it twice here and 61692 // on `loadedplaylist` 61693 61694 61695 _this3.mainSegmentLoader_.playlist(media, _this3.requestOptions_); 61696 61697 _this3.mainSegmentLoader_.load(); 61698 61699 _this3.tech_.trigger({ 61700 type: 'mediachange', 61701 bubbles: true 61702 }); 61703 }); 61704 this.masterPlaylistLoader_.on('playlistunchanged', function () { 61705 var updatedPlaylist = _this3.masterPlaylistLoader_.media(); // ignore unchanged playlists that have already been 61706 // excluded for not-changing. We likely just have a really slowly updating 61707 // playlist. 61708 61709 61710 if (updatedPlaylist.lastExcludeReason_ === 'playlist-unchanged') { 61711 return; 61712 } 61713 61714 var playlistOutdated = _this3.stuckAtPlaylistEnd_(updatedPlaylist); 61715 61716 if (playlistOutdated) { 61717 // Playlist has stopped updating and we're stuck at its end. Try to 61718 // blacklist it and switch to another playlist in the hope that that 61719 // one is updating (and give the player a chance to re-adjust to the 61720 // safe live point). 61721 _this3.blacklistCurrentPlaylist({ 61722 message: 'Playlist no longer updating.', 61723 reason: 'playlist-unchanged' 61724 }); // useful for monitoring QoS 61725 61726 61727 _this3.tech_.trigger('playliststuck'); 61728 } 61729 }); 61730 this.masterPlaylistLoader_.on('renditiondisabled', function () { 61731 _this3.tech_.trigger({ 61732 type: 'usage', 61733 name: 'vhs-rendition-disabled' 61734 }); 61735 61736 _this3.tech_.trigger({ 61737 type: 'usage', 61738 name: 'hls-rendition-disabled' 61739 }); 61740 }); 61741 this.masterPlaylistLoader_.on('renditionenabled', function () { 61742 _this3.tech_.trigger({ 61743 type: 'usage', 61744 name: 'vhs-rendition-enabled' 61745 }); 61746 61747 _this3.tech_.trigger({ 61748 type: 'usage', 61749 name: 'hls-rendition-enabled' 61750 }); 61751 }); 61752 } 61753 /** 61754 * Given an updated media playlist (whether it was loaded for the first time, or 61755 * refreshed for live playlists), update any relevant properties and state to reflect 61756 * changes in the media that should be accounted for (e.g., cues and duration). 61757 * 61758 * @param {Object} updatedPlaylist the updated media playlist object 61759 * 61760 * @private 61761 */ 61762 ; 61763 61764 _proto.handleUpdatedMediaPlaylist = function handleUpdatedMediaPlaylist(updatedPlaylist) { 61765 if (this.useCueTags_) { 61766 this.updateAdCues_(updatedPlaylist); 61767 } // TODO: Create a new event on the PlaylistLoader that signals 61768 // that the segments have changed in some way and use that to 61769 // update the SegmentLoader instead of doing it twice here and 61770 // on `mediachange` 61771 61772 61773 this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_); 61774 this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running, 61775 // as it is possible that it was temporarily stopped while waiting for 61776 // a playlist (e.g., in case the playlist errored and we re-requested it). 61777 61778 if (!this.tech_.paused()) { 61779 this.mainSegmentLoader_.load(); 61780 61781 if (this.audioSegmentLoader_) { 61782 this.audioSegmentLoader_.load(); 61783 } 61784 } 61785 } 61786 /** 61787 * A helper function for triggerring presence usage events once per source 61788 * 61789 * @private 61790 */ 61791 ; 61792 61793 _proto.triggerPresenceUsage_ = function triggerPresenceUsage_(master, media) { 61794 var mediaGroups = master.mediaGroups || {}; 61795 var defaultDemuxed = true; 61796 var audioGroupKeys = Object.keys(mediaGroups.AUDIO); 61797 61798 for (var mediaGroup in mediaGroups.AUDIO) { 61799 for (var label in mediaGroups.AUDIO[mediaGroup]) { 61800 var properties = mediaGroups.AUDIO[mediaGroup][label]; 61801 61802 if (!properties.uri) { 61803 defaultDemuxed = false; 61804 } 61805 } 61806 } 61807 61808 if (defaultDemuxed) { 61809 this.tech_.trigger({ 61810 type: 'usage', 61811 name: 'vhs-demuxed' 61812 }); 61813 this.tech_.trigger({ 61814 type: 'usage', 61815 name: 'hls-demuxed' 61816 }); 61817 } 61818 61819 if (Object.keys(mediaGroups.SUBTITLES).length) { 61820 this.tech_.trigger({ 61821 type: 'usage', 61822 name: 'vhs-webvtt' 61823 }); 61824 this.tech_.trigger({ 61825 type: 'usage', 61826 name: 'hls-webvtt' 61827 }); 61828 } 61829
61830 if (Vhs$1.Playlist.isAes(media)) { 61831 this.tech_.trigger({ 61832 type: 'usage', 61833 name: 'vhs-aes' 61834 }); 61835 this.tech_.trigger({ 61836 type: 'usage', 61837 name: 'hls-aes' 61838 }); 61839 } 61840 61841 if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) { 61842 this.tech_.trigger({ 61843 type: 'usage', 61844 name: 'vhs-alternate-audio' 61845 }); 61846 this.tech_.trigger({ 61847 type: 'usage', 61848 name: 'hls-alternate-audio' 61849 }); 61850 } 61851 61852 if (this.useCueTags_) { 61853 this.tech_.trigger({ 61854 type: 'usage', 61855 name: 'vhs-playlist-cue-tags' 61856 }); 61857 this.tech_.trigger({ 61858 type: 'usage', 61859 name: 'hls-playlist-cue-tags' 61860 }); 61861 } 61862 }; 61863 61864 _proto.shouldSwitchToMedia_ = function shouldSwitchToMedia_(nextPlaylist) { 61865 var currentPlaylist = this.masterPlaylistLoader_.media(); 61866 var buffered = this.tech_.buffered(); 61867 var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - this.tech_.currentTime() : 0; 61868 var bufferLowWaterLine = this.bufferLowWaterLine(); 61869 var bufferHighWaterLine = this.bufferHighWaterLine(); 61870 return shouldSwitchToMedia({ 61871 currentPlaylist: currentPlaylist, 61872 nextPlaylist: nextPlaylist, 61873 forwardBuffer: forwardBuffer, 61874 bufferLowWaterLine: bufferLowWaterLine, 61875 bufferHighWaterLine: bufferHighWaterLine, 61876 duration: this.duration(), 61877 experimentalBufferBasedABR: this.experimentalBufferBasedABR, 61878 log: this.logger_ 61879 }); 61880 } 61881 /** 61882 * Register event handlers on the segment loaders. A helper function 61883 * for construction time. 61884 * 61885 * @private 61886 */ 61887 ; 61888 61889 _proto.setupSegmentLoaderListeners_ = function setupSegmentLoaderListeners_() { 61890 var _this4 = this; 61891 61892 if (!this.experimentalBufferBasedABR) { 61893 this.mainSegmentLoader_.on('bandwidthupdate', function () { 61894 var nextPlaylist = _this4.selectPlaylist(); 61895 61896 if (_this4.shouldSwitchToMedia_(nextPlaylist)) { 61897 _this4.switchMedia_(nextPlaylist, 'bandwidthupdate'); 61898 } 61899 61900 _this4.tech_.trigger('bandwidthupdate'); 61901 }); 61902 this.mainSegmentLoader_.on('progress', function () { 61903 _this4.trigger('progress'); 61904 }); 61905 } 61906 61907 this.mainSegmentLoader_.on('error', function () { 61908 _this4.blacklistCurrentPlaylist(_this4.mainSegmentLoader_.error()); 61909 }); 61910 this.mainSegmentLoader_.on('appenderror', function () { 61911 _this4.error = _this4.mainSegmentLoader_.error_; 61912 61913 _this4.trigger('error'); 61914 }); 61915 this.mainSegmentLoader_.on('syncinfoupdate', function () { 61916 _this4.onSyncInfoUpdate_(); 61917 }); 61918 this.mainSegmentLoader_.on('timestampoffset', function () { 61919 _this4.tech_.trigger({ 61920 type: 'usage', 61921 name: 'vhs-timestamp-offset' 61922 }); 61923 61924 _this4.tech_.trigger({ 61925 type: 'usage', 61926 name: 'hls-timestamp-offset' 61927 }); 61928 }); 61929 this.audioSegmentLoader_.on('syncinfoupdate', function () { 61930 _this4.onSyncInfoUpdate_(); 61931 }); 61932 this.audioSegmentLoader_.on('appenderror', function () { 61933 _this4.error = _this4.audioSegmentLoader_.error_; 61934 61935 _this4.trigger('error'); 61936 }); 61937 this.mainSegmentLoader_.on('ended', function () { 61938 _this4.logger_('main segment loader ended'); 61939 61940 _this4.onEndOfStream(); 61941 }); 61942 this.mainSegmentLoader_.on('earlyabort', function (event) { 61943 // never try to early abort with the new ABR algorithm 61944 if (_this4.experimentalBufferBasedABR) { 61945 return; 61946 } 61947 61948 _this4.delegateLoaders_('all', ['abort']); 61949 61950 _this4.blacklistCurrentPlaylist({ 61951 message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.' 61952 }, ABORT_EARLY_BLACKLIST_SECONDS); 61953 }); 61954 61955 var updateCodecs = function updateCodecs() { 61956 if (!_this4.sourceUpdater_.hasCreatedSourceBuffers()) { 61957 return _this4.tryToCreateSourceBuffers_(); 61958 } 61959 61960 var codecs = _this4.getCodecsOrExclude_(); // no codecs means that the playlist was excluded 61961 61962 61963 if (!codecs) { 61964 return; 61965 } 61966 61967 _this4.sourceUpdater_.addOrChangeSourceBuffers(codecs); 61968 }; 61969 61970 this.mainSegmentLoader_.on('trackinfo', updateCodecs); 61971 this.audioSegmentLoader_.on('trackinfo', updateCodecs); 61972 this.mainSegmentLoader_.on('fmp4', function () { 61973 if (!_this4.triggeredFmp4Usage) { 61974 _this4.tech_.trigger({ 61975 type: 'usage', 61976 name: 'vhs-fmp4' 61977 }); 61978 61979 _this4.tech_.trigger({ 61980 type: 'usage', 61981 name: 'hls-fmp4' 61982 }); 61983 61984 _this4.triggeredFmp4Usage = true; 61985 } 61986 }); 61987 this.audioSegmentLoader_.on('fmp4', function () { 61988 if (!_this4.triggeredFmp4Usage) { 61989 _this4.tech_.trigger({ 61990 type: 'usage', 61991 name: 'vhs-fmp4' 61992 }); 61993 61994 _this4.tech_.trigger({ 61995 type: 'usage', 61996 name: 'hls-fmp4' 61997 }); 61998 61999 _this4.triggeredFmp4Usage = true; 62000 } 62001 }); 62002 this.audioSegmentLoader_.on('ended', function () { 62003 _this4.logger_('audioSegmentLoader ended'); 62004 62005 _this4.onEndOfStream(); 62006 }); 62007 }; 62008 62009 _proto.mediaSecondsLoaded_ = function mediaSecondsLoaded_() { 62010 return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded); 62011 } 62012 /** 62013 * Call load on our SegmentLoaders 62014 */ 62015 ; 62016 62017 _proto.load = function load() { 62018 this.mainSegmentLoader_.load(); 62019 62020 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 62021 this.audioSegmentLoader_.load(); 62022 } 62023 62024 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 62025 this.subtitleSegmentLoader_.load(); 62026 } 62027 } 62028 /** 62029 * Re-tune playback quality level for the current player 62030 * conditions without performing destructive actions, like 62031 * removing already buffered content 62032 * 62033 * @private 62034 * @deprecated 62035 */ 62036 ; 62037 62038 _proto.smoothQualityChange_ = function smoothQualityChange_(media) { 62039 if (media === void 0) { 62040 media = this.selectPlaylist(); 62041 } 62042 62043 this.fastQualityChange_(media); 62044 } 62045 /** 62046 * Re-tune playback quality level for the current player 62047 * conditions. This method will perform destructive actions like remov
62047ing 62048 * already buffered content in order to readjust the currently active 62049 * playlist quickly. This is good for manual quality changes 62050 * 62051 * @private 62052 */ 62053 ; 62054 62055 _proto.fastQualityChange_ = function fastQualityChange_(media) { 62056 var _this5 = this; 62057 62058 if (media === void 0) { 62059 media = this.selectPlaylist(); 62060 } 62061 62062 if (media === this.masterPlaylistLoader_.media()) { 62063 this.logger_('skipping fastQualityChange because new media is same as old'); 62064 return; 62065 } 62066 62067 this.switchMedia_(media, 'fast-quality'); // Delete all buffered data to allow an immediate quality switch, then seek to give 62068 // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds 62069 // ahead is roughly the minimum that will accomplish this across a variety of content 62070 // in IE and Edge, but seeking in place is sufficient on all other browsers) 62071 // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/ 62072 // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904 62073 62074 this.mainSegmentLoader_.resetEverything(function () { 62075 // Since this is not a typical seek, we avoid the seekTo method which can cause segments 62076 // from the previously enabled rendition to load before the new playlist has finished loading 62077 if (videojs.browser.IE_VERSION || videojs.browser.IS_EDGE) { 62078 _this5.tech_.setCurrentTime(_this5.tech_.currentTime() + 0.04); 62079 } else { 62080 _this5.tech_.setCurrentTime(_this5.tech_.currentTime()); 62081 } 62082 }); // don't need to reset audio as it is reset when media changes 62083 } 62084 /** 62085 * Begin playback. 62086 */ 62087 ; 62088 62089 _proto.play = function play() { 62090 if (this.setupFirstPlay()) { 62091 return; 62092 } 62093 62094 if (this.tech_.ended()) { 62095 this.tech_.setCurrentTime(0); 62096 } 62097 62098 if (this.hasPlayed_) { 62099 this.load(); 62100 } 62101 62102 var seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window, 62103 // seek forward to the live point 62104 62105 if (this.tech_.duration() === Infinity) { 62106 if (this.tech_.currentTime() < seekable.start(0)) { 62107 return this.tech_.setCurrentTime(seekable.end(seekable.length - 1)); 62108 } 62109 } 62110 } 62111 /** 62112 * Seek to the latest media position if this is a live video and the 62113 * player and video are loaded and initialized. 62114 */ 62115 ; 62116 62117 _proto.setupFirstPlay = function setupFirstPlay() { 62118 var _this6 = this; 62119 62120 var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play 62121 // If 1) there is no active media 62122 // 2) the player is paused 62123 // 3) the first play has already been setup 62124 // then exit early 62125 62126 if (!media || this.tech_.paused() || this.hasPlayed_) { 62127 return false; 62128 } // when the video is a live stream 62129 62130 62131 if (!media.endList) { 62132 var seekable = this.seekable(); 62133 62134 if (!seekable.length) { 62135 // without a seekable range, the player cannot seek to begin buffering at the live 62136 // point 62137 return false; 62138 } 62139 62140 if (videojs.browser.IE_VERSION && this.tech_.readyState() === 0) { 62141 // IE11 throws an InvalidStateError if you try to set currentTime while the 62142 // readyState is 0, so it must be delayed until the tech fires loadedmetadata. 62143 this.tech_.one('loadedmetadata', function () { 62144 _this6.trigger('firstplay'); 62145 62146 _this6.tech_.setCurrentTime(seekable.end(0)); 62147 62148 _this6.hasPlayed_ = true; 62149 }); 62150 return false; 62151 } // trigger firstplay to inform the source handler to ignore the next seek event 62152 62153 62154 this.trigger('firstplay'); // seek to the live point 62155 62156 this.tech_.setCurrentTime(seekable.end(0)); 62157 } 62158 62159 this.hasPlayed_ = true; // we can begin loading now that everything is ready 62160 62161 this.load(); 62162 return true; 62163 } 62164 /** 62165 * handle the sourceopen event on the MediaSource 62166 * 62167 * @private 62168 */ 62169 ; 62170 62171 _proto.handleSourceOpen_ = function handleSourceOpen_() { 62172 // Only attempt to create the source buffer if none already exist. 62173 // handleSourceOpen is also called when we are "re-opening" a source buffer 62174 // after `endOfStream` has been called (in response to a seek for instance) 62175 this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of 62176 // code in video.js but is required because play() must be invoked 62177 // *after* the media source has opened. 62178 62179 if (this.tech_.autoplay()) {
62180 var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request 62181 // on browsers which return a promise 62182 62183 if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') { 62184 playPromise.then(null, function (e) {}); 62185 } 62186 } 62187 62188 this.trigger('sourceopen'); 62189 } 62190 /** 62191 * handle the sourceended event on the MediaSource 62192 * 62193 * @private 62194 */ 62195 ; 62196 62197 _proto.handleSourceEnded_ = function handleSourceEnded_() { 62198 if (!this.inbandTextTracks_.metadataTrack_) { 62199 return; 62200 } 62201 62202 var cues = this.inbandTextTracks_.metadataTrack_.cues; 62203 62204 if (!cues || !cues.length) { 62205 return; 62206 } 62207 62208 var duration = this.duration(); 62209 cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration; 62210 } 62211 /** 62212 * handle the durationchange event on the MediaSource 62213 * 62214 * @private 62215 */ 62216 ; 62217 62218 _proto.handleDurationChange_ = function handleDurationChange_() { 62219 this.tech_.trigger('durationchange'); 62220 } 62221 /** 62222 * Calls endOfStream on the media source when all active stream types have called 62223 * endOfStream 62224 * 62225 * @param {string} streamType 62226 * Stream type of the segment loader that called endOfStream 62227 * @private 62228 */ 62229 ; 62230 62231 _proto.onEndOfStream = function onEndOfStream() { 62232 var isEndOfStream = this.mainSegmentLoader_.ended_; 62233 62234 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 62235 var mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); // if the audio playlist loader exists, then alternate audio is active 62236 62237 if (!mainMediaInfo || mainMediaInfo.hasVideo) { 62238 // if we do not know if the main segment loader contains video yet or if we 62239 // definitively know the main segment loader contains video, then we need to wait 62240 // for both main and audio segment loaders to call endOfStream 62241 isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_; 62242 } else { 62243 // otherwise just rely on the audio loader 62244 isEndOfStream = this.audioSegmentLoader_.ended_; 62245 } 62246 } 62247 62248 if (!isEndOfStream) { 62249 return; 62250 } 62251 62252 this.stopABRTimer_(); 62253 this.sourceUpdater_.endOfStream(); 62254 } 62255 /** 62256 * Check if a playlist has stopped being updated 62257 * 62258 * @param {Object} playlist the media playlist object 62259 * @return {boolean} whether the playlist has stopped being updated or not 62260 */ 62261 ; 62262 62263 _proto.stuckAtPlaylistEnd_ = function stuckAtPlaylistEnd_(playlist) { 62264 var seekable = this.seekable(); 62265 62266 if (!seekable.length) { 62267 // playlist doesn't have enough information to determine whether we are stuck 62268 return false; 62269 } 62270 62271 var expired = this.syncController_.getExpiredTime(playlist, this.duration()); 62272 62273 if (expired === null) { 62274 return false; 62275 } // does not use the safe live end to calculate playlist end, since we 62276 // don't want to say we are stuck while there is still content 62277 62278 62279 var absolutePlaylistEnd = Vhs$1.Playlist.playlistEnd(playlist, expired); 62280 var currentTime = this.tech_.currentTime(); 62281 var buffered = this.tech_.buffered(); 62282 62283 if (!buffered.length) { 62284 // return true if the playhead reached the absolute end of the playlist 62285 return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA; 62286 } 62287 62288 var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute 62289 // end of playlist 62290 62291 return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA; 62292 } 62293 /** 62294 * Blacklists a playlist when an error occurs for a set amount of time 62295 * making it unavailable for selection by the rendition selection algorithm 62296 * and then forces a new playlist (rendition) selection. 62297 * 62298 * @param {Object=} error an optional error that may include the playlist 62299 * to blacklist 62300 * @param {number=} blacklistDuration an optional number of seconds to blacklist the 62301 * playlist 62302 */ 62303 ; 62304
62305 _proto.blacklistCurrentPlaylist = function blacklistCurrentPlaylist(error, blacklistDuration) { 62306 if (error === void 0) { 62307 error = {}; 62308 } // If the `error` was generated by the playlist loader, it will contain 62309 // the playlist we were trying to load (but failed) and that should be 62310 // blacklisted instead of the currently selected playlist which is likely 62311 // out-of-date in this scenario 62312 62313 62314 var currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 62315 blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were 62316 // trying to load the master OR while we were disposing of the tech 62317 62318 if (!currentPlaylist) { 62319 this.error = error; 62320 62321 if (this.mediaSource.readyState !== 'open') { 62322 this.trigger('error'); 62323 } else { 62324 this.sourceUpdater_.endOfStream('network'); 62325 } 62326 62327 return; 62328 } 62329 62330 currentPlaylist.playlistErrors_++; 62331 var playlists = this.masterPlaylistLoader_.master.playlists; 62332 var enabledPlaylists = playlists.filter(isEnabled); 62333 var isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === currentPlaylist; // Don't blacklist the only playlist unless it was blacklisted 62334 // forever 62335 62336 if (playlists.length === 1 && blacklistDuration !== Infinity) { 62337 videojs.log.warn("Problem encountered with playlist " + currentPlaylist.id + ". " + 'Trying again since it is the only playlist.'); 62338 this.tech_.trigger('retryplaylist'); // if this is a final rendition, we should delay 62339 62340 return this.masterPlaylistLoader_.load(isFinalRendition); 62341 } 62342 62343 if (isFinalRendition) { 62344 // Since we're on the final non-blacklisted playlist, and we're about to blacklist 62345 // it, instead of erring the player or retrying this playlist, clear out the current 62346 // blacklist. This allows other playlists to be attempted in case any have been 62347 // fixed. 62348 var reincluded = false;
62349 playlists.forEach(function (playlist) { 62350 // skip current playlist which is about to be blacklisted 62351 if (playlist === currentPlaylist) { 62352 return; 62353 } 62354 62355 var excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with. 62356 62357 if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) { 62358 reincluded = true; 62359 delete playlist.excludeUntil; 62360 } 62361 }); 62362 62363 if (reincluded) { 62364 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 62365 // playlist. This is needed for users relying on the retryplaylist event to catch a 62366 // case where the player might be stuck and looping through "dead" playlists. 62367 62368 this.tech_.trigger('retryplaylist'); 62369 } 62370 } // Blacklist this playlist 62371 62372 62373 var excludeUntil; 62374 62375 if (currentPlaylist.playlistErrors_ > this.maxPlaylistRetries) { 62376 excludeUntil = Infinity; 62377 } else { 62378 excludeUntil = Date.now() + blacklistDuration * 1000; 62379 } 62380 62381 currentPlaylist.excludeUntil = excludeUntil; 62382 62383 if (error.reason) { 62384 currentPlaylist.lastExcludeReason_ = error.reason; 62385 } 62386 62387 this.tech_.trigger('blacklistplaylist'); 62388 this.tech_.trigger({ 62389 type: 'usage', 62390 name: 'vhs-rendition-blacklisted' 62391 }); 62392 this.tech_.trigger({ 62393 type: 'usage', 62394 name: 'hls-rendition-blacklisted' 62395 }); // TODO: should we select a new playlist if this blacklist wasn't for the currentPlaylist? 62396 // Would be something like media().id !=== currentPlaylist.id and we would need something 62397 // like `pendingMedia` in playlist loaders to check against that too. This will prevent us 62398 // from loading a new playlist on any blacklist. 62399 // Select a new playlist 62400 62401 var nextPlaylist = this.selectPlaylist(); 62402 62403 if (!nextPlaylist) { 62404 this.error = 'Playback cannot continue. No available working or supported playlists.'; 62405 this.trigger('error'); 62406 return; 62407 } 62408 62409 var logFn = error.internal ? this.logger_ : videojs.log.warn; 62410 var errorMessage = error.message ? ' ' + error.message : ''; 62411 logFn((error.internal ? 'Internal problem' : 'Problem') + " encountered with playlist " + currentPlaylist.id + "." + (errorMessage + " Switching to playlist " + nextPlaylist.id + ".")); // if audio group changed reset audio loaders 62412 62413 if (nextPlaylist.attributes.AUDIO !== currentPlaylist.attributes.AUDIO) { 62414 this.delegateLoaders_('audio', ['abort', 'pause']); 62415 } // if subtitle group changed reset subtitle loaders 62416 62417 62418 if (nextPlaylist.attributes.SUBTITLES !== currentPlaylist.attributes.SUBTITLES) { 62419 this.delegateLoaders_('subtitle', ['abort', 'pause']); 62420 } 62421 62422 this.delegateLoaders_('main', ['abort', 'pause']); 62423 var delayDuration = nextPlaylist.targetDuration / 2 * 1000 || 5 * 1000; 62424 var 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 62425 62426 return this.switchMedia_(nextPlaylist, 'exclude', isFinalRendition || shouldDelay); 62427 } 62428 /** 62429 * Pause all segment/playlist loaders 62430 */ 62431 ; 62432 62433 _proto.pauseLoading = function pauseLoading() { 62434 this.delegateLoaders_('all', ['abort', 'pause']); 62435 this.stopABRTimer_(); 62436 } 62437 /** 62438 * Call a set of functions in order on playlist loaders, segment loaders, 62439 * or both types of loaders. 62440 * 62441 * @param {string} filter 62442 * Filter loaders that should call fnNames using a string. Can be: 62443 * * all - run on all loaders 62444 * * audio - run on all audio loaders 62445 * * subtitle - run on all subtitle loaders 62446 * * main - run on the main/master loaders 62447 * 62448 * @param {Array|string} fnNames 62449 * A string or array of function names to call. 62450 */ 62451 ; 62452 62453 _proto.delegateLoaders_ = function delegateLoaders_(filter, fnNames) { 62454 var _this7 = this; 62455 62456 var loaders = []; 62457 var dontFilterPlaylist = filter === 'all'; 62458 62459 if (dontFilterPlaylist || filter === 'main') { 62460 loaders.push(this.masterPlaylistLoader_); 62461 } 62462 62463 var mediaTypes = []; 62464 62465 if (dontFilterPlaylist || filter === 'audio') { 62466 mediaTypes.push('AUDIO'); 62467 } 62468 62469 if (dontFilterPlaylist || filter === 'subtitle') { 62470 mediaTypes.push('CLOSED-CAPTIONS'); 62471 mediaTypes.push('SUBTITLES'); 62472 } 62473
62474 mediaTypes.forEach(function (mediaType) { 62475 var loader = _this7.mediaTypes_[mediaType] && _this7.mediaTypes_[mediaType].activePlaylistLoader; 62476 62477 if (loader) { 62478 loaders.push(loader); 62479 } 62480 }); 62481 ['main', 'audio', 'subtitle'].forEach(function (name) { 62482 var loader = _this7[name + "SegmentLoader_"]; 62483 62484 if (loader && (filter === name || filter === 'all')) { 62485 loaders.push(loader); 62486 } 62487 }); 62488 loaders.forEach(function (loader) { 62489 return fnNames.forEach(function (fnName) { 62490 if (typeof loader[fnName] === 'function') { 62491 loader[fnName](); 62492 } 62493 }); 62494 }); 62495 } 62496 /** 62497 * set the current time on all segment loaders 62498 * 62499 * @param {TimeRange} currentTime the current time to set 62500 * @return {TimeRange} the current time 62501 */ 62502 ; 62503 62504 _proto.setCurrentTime = function setCurrentTime(currentTime) { 62505 var buffered = findRange(this.tech_.buffered(), currentTime); 62506 62507 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 62508 // return immediately if the metadata is not ready yet 62509 return 0; 62510 } // it's clearly an edge-case but don't thrown an error if asked to 62511 // seek within an empty playlist 62512 62513 62514 if (!this.masterPlaylistLoader_.media().segments) { 62515 return 0; 62516 } // if the seek location is already buffered, continue buffering as usual 62517 62518 62519 if (buffered && buffered.length) { 62520 return currentTime; 62521 } // cancel outstanding requests so we begin buffering at the new 62522 // location 62523 62524 62525 this.mainSegmentLoader_.resetEverything(); 62526 this.mainSegmentLoader_.abort(); 62527 62528 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 62529 this.audioSegmentLoader_.resetEverything(); 62530 this.audioSegmentLoader_.abort(); 62531 } 62532 62533 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 62534 this.subtitleSegmentLoader_.resetEverything(); 62535 this.subtitleSegmentLoader_.abort(); 62536 } // start segment loader loading in case they are paused 62537 62538 62539 this.load(); 62540 } 62541 /** 62542 * get the current duration 62543 * 62544 * @return {TimeRange} the duration 62545 */ 62546 ; 62547 62548 _proto.duration = function duration() { 62549 if (!this.masterPlaylistLoader_) { 62550 return 0; 62551 } 62552 62553 var media = this.masterPlaylistLoader_.media(); 62554 62555 if (!media) { 62556 // no playlists loaded yet, so can't determine a duration 62557 return 0; 62558 } // Don't rely on the media source for duration in the case of a live playlist since 62559 // setting the native MediaSource's duration to infinity ends up with consequences to 62560 // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details. 62561 //
62562 // This is resolved in the spec by https://github.com/w3c/media-source/pull/92, 62563 // however, few browsers have support for setLiveSeekableRange() 62564 // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange 62565 // 62566 // Until a time when the duration of the media source can be set to infinity, and a 62567 // seekable range specified across browsers, just return Infinity. 62568 62569 62570 if (!media.endList) { 62571 return Infinity; 62572 } // Since this is a VOD video, it is safe to rely on the media source's duration (if 62573 // available). If it's not available, fall back to a playlist-calculated estimate. 62574 62575 62576 if (this.mediaSource) { 62577 return this.mediaSource.duration; 62578 } 62579 62580 return Vhs$1.Playlist.duration(media); 62581 } 62582 /** 62583 * check the seekable range 62584 * 62585 * @return {TimeRange} the seekable range 62586 */ 62587 ; 62588 62589 _proto.seekable = function seekable() { 62590 return this.seekable_; 62591 }; 62592 62593 _proto.onSyncInfoUpdate_ = function onSyncInfoUpdate_() { 62594 var audioSeekable; 62595 62596 if (!this.masterPlaylistLoader_) { 62597 return; 62598 } 62599 62600 var media = this.masterPlaylistLoader_.media(); 62601 62602 if (!media) { 62603 return; 62604 } 62605 62606 var expired = this.syncController_.getExpiredTime(media, this.duration()); 62607 62608 if (expired === null) { 62609 // not enough information to update seekable 62610 return; 62611 } 62612 62613 var master = this.masterPlaylistLoader_.master; 62614 var mainSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(master, media)); 62615 62616 if (mainSeekable.length === 0) { 62617 return; 62618 } 62619 62620 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 62621 media = this.mediaTypes_.AUDIO.activePlaylistLoader.media(); 62622 expired = this.syncController_.getExpiredTime(media, this.duration()); 62623 62624 if (expired === null) { 62625 return; 62626 } 62627 62628 audioSeekable = Vhs$1.Playlist.seekable(media, expired, Vhs$1.Playlist.liveEdgeDelay(master, media)); 62629 62630 if (audioSeekable.length === 0) { 62631 return; 62632 } 62633 } 62634 62635 var oldEnd; 62636 var oldStart; 62637 62638 if (this.seekable_ && this.seekable_.length) { 62639 oldEnd = this.seekable_.end(0); 62640 oldStart = this.seekable_.start(0); 62641 } 62642 62643 if (!audioSeekable) { 62644 // seekable has been calculated based on buffering video data so it 62645 // can be returned directly 62646 this.seekable_ = mainSeekable; 62647 } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) { 62648 // seekables are pretty far off, rely on main 62649 this.seekable_ = mainSeekable; 62650 } else { 62651 this.seekable_ = videojs.createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]); 62652 } // seekable is the same as last time 62653 62654 62655 if (this.seekable_ && this.seekable_.length) { 62656 if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) { 62657 return; 62658 } 62659 } 62660 62661 this.logger_("seekable updated [" + printableRange(this.seekable_) + "]"); 62662 this.tech_.trigger('seekablechanged'); 62663 } 62664 /** 62665 * Update the player duration 62666 */ 62667 ; 62668 62669 _proto.updateDuration = function updateDuration(isLive) { 62670 if (this.updateDuration_) { 62671 this.mediaSource.removeEventListener('sourceopen', this.updateDuration_); 62672 this.updateDuration_ = null; 62673 } 62674 62675 if (this.mediaSource.readyState !== 'open') { 62676 this.updateDuration_ = this.updateDuration.bind(this, isLive); 62677 this.mediaSource.addEventListener('sourceopen', this.updateDuration_); 62678 return; 62679 } 62680 62681 if (isLive) { 62682 var seekable = this.seekable(); 62683 62684 if (!seekable.length) { 62685 return; 62686 } // Even in the case of a live playlist, the native MediaSource's duration should not 62687 // be set to Infinity (even though this would be expected for a live playlist), since 62688 // setting the native MediaSource's duration to infinity ends up with consequences to 62689 // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details. 62690 //
62691 // This is resolved in the spec by https://github.com/w3c/media-source/pull/92, 62692 // however, few browsers have support for setLiveSeekableRange() 62693 // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange 62694 // 62695 // Until a time when the duration of the media source can be set to infinity, and a 62696 // seekable range specified across browsers, the duration should be greater than or 62697 // equal to the last possible seekable value. 62698 // MediaSource duration starts as NaN 62699 // It is possible (and probable) that this case will never be reached for many 62700 // sources, since the MediaSource reports duration as the highest value without 62701 // accounting for timestamp offset. For example, if the timestamp offset is -100 and 62702 // we buffered times 0 to 100 with real times of 100 to 200, even though current 62703 // time will be between 0 and 100, the native media source may report the duration 62704 // as 200. However, since we report duration separate from the media source (as 62705 // Infinity), and as long as the native media source duration value is greater than 62706 // our reported seekable range, seeks will work as expected. The large number as 62707 // duration for live is actually a strategy used by some players to work around the 62708 // issue of live seekable ranges cited above. 62709 62710 62711 if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) { 62712 this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1)); 62713 } 62714 62715 return; 62716 } 62717 62718 var buffered = this.tech_.buffered(); 62719 var duration = Vhs$1.Playlist.duration(this.masterPlaylistLoader_.media()); 62720 62721 if (buffered.length > 0) { 62722 duration = Math.max(duration, buffered.end(buffered.length - 1)); 62723 } 62724 62725 if (this.mediaSource.duration !== duration) { 62726 this.sourceUpdater_.setDuration(duration); 62727 } 62728 } 62729 /** 62730 * dispose of the MasterPlaylistController and everything 62731 * that it controls 62732 */ 62733 ; 62734 62735 _proto.dispose = function dispose() { 62736 var _this8 = this; 62737 62738 this.trigger('dispose'); 62739 this.decrypter_.terminate(); 62740 this.masterPlaylistLoader_.dispose(); 62741 this.mainSegmentLoader_.dispose(); 62742 62743 if (this.loadOnPlay_) { 62744 this.tech_.off('play', this.loadOnPlay_); 62745 } 62746 62747 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 62748 var groups = _this8.mediaTypes_[type].groups; 62749 62750 for (var id in groups) { 62751 groups[id].forEach(function (group) { 62752 if (group.playlistLoader) { 62753 group.playlistLoader.dispose(); 62754 } 62755 }); 62756 } 62757 }); 62758 this.audioSegmentLoader_.dispose(); 62759 this.subtitleSegmentLoader_.dispose(); 62760 this.sourceUpdater_.dispose(); 62761 this.timelineChangeController_.dispose(); 62762 this.stopABRTimer_(); 62763 62764 if (this.updateDuration_) { 62765 this.mediaSource.removeEventListener('sourceopen', this.updateDuration_); 62766 } 62767 62768 this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player 62769 62770 this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_); 62771 this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_); 62772 this.off(); 62773 } 62774 /** 62775 * return the master playlist object if we have one 62776 * 62777 * @return {Object} the master playlist object that we parsed 62778 */ 62779 ; 62780 62781 _proto.master = function master() { 62782 return this.masterPlaylistLoader_.master; 62783 } 62784 /** 62785 * return the currently selected playlist 62786 * 62787 * @return {Object} the currently selected playlist object that we parsed 62788 */ 62789 ; 62790 62791 _proto.media = function media() { 62792 // playlist loader will not return media if it has not been fully loaded 62793 return this.masterPlaylistLoader_.media() || this.initialMedia_; 62794 }; 62795 62796 _proto.areMediaTypesKnown_ = function areMediaTypesKnown_() { 62797 var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader; 62798 var hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); // if we are not using an audio loader, then we have audio media info 62799 // otherwise check on the segment loader. 62800 62801 var hasAudioMediaInfo = !usingAudioLoader ? true : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs 62802 62803 if (!hasMainMediaInfo || !hasAudioMediaInfo) { 62804 return false; 62805 } 62806 62807 return true; 62808 }; 62809 62810 _proto.getCodecsOrExclude_ = function getCodecsOrExclude_() { 62811 var _this9 = this; 62812 62813 var media = { 62814 main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {}, 62815 audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {} 62816 }; // set "main" media equal to video 62817 62818 media.video = media.main; 62819 var playlistCodecs = codecsForPlaylist(this.master(), this.media()); 62820 var codecs = {}; 62821 var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader; 62822 62823 if (media.main.hasVideo) { 62824 codecs.video = playlistCodecs.video || media.main.videoCodec || DEFAULT_VIDEO_CODEC; 62825 } 62826 62827 if (media.main.isMuxed) { 62828 codecs.video += "," + (playlistCodecs.audio || media.main.audioCodec || DEFAULT_AUDIO_CODEC); 62829 } 62830 62831 if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) { 62832 codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || DEFAULT_AUDIO_CODEC; // set audio isFmp4 so we use the correct "supports" function below 62833 62834 media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4; 62835 } // no codecs, no playback. 62836 62837 62838 if (!codecs.audio && !codecs.video) {
62839 this.blacklistCurrentPlaylist({ 62840 playlist: this.media(), 62841 message: 'Could not determine codecs for playlist.', 62842 blacklistDuration: Infinity 62843 }); 62844 return; 62845 } // fmp4 relies on browser support, while ts relies on muxer support 62846 62847 62848 var supportFunction = function supportFunction(isFmp4, codec) { 62849 return isFmp4 ? browserSupportsCodec(codec) : muxerSupportsCodec(codec); 62850 }; 62851 62852 var unsupportedCodecs = {}; 62853 var unsupportedAudio; 62854 ['video', 'audio'].forEach(function (type) { 62855 if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) { 62856 var supporter = media[type].isFmp4 ? 'browser' : 'muxer'; 62857 unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || []; 62858 unsupportedCodecs[supporter].push(codecs[type]); 62859 62860 if (type === 'audio') { 62861 unsupportedAudio = supporter; 62862 } 62863 } 62864 }); 62865 62866 if (usingAudioLoader && unsupportedAudio && this.media().attributes.AUDIO) { 62867 var audioGroup = this.media().attributes.AUDIO;
62868 this.master().playlists.forEach(function (variant) { 62869 var variantAudioGroup = variant.attributes && variant.attributes.AUDIO; 62870 62871 if (variantAudioGroup === audioGroup && variant !== _this9.media()) { 62872 variant.excludeUntil = Infinity; 62873 } 62874 }); 62875 this.logger_("excluding audio group " + audioGroup + " as " + unsupportedAudio + " does not support codec(s): \"" + codecs.audio + "\""); 62876 } // if we have any unsupported codecs blacklist this playlist. 62877 62878 62879 if (Object.keys(unsupportedCodecs).length) { 62880 var message = Object.keys(unsupportedCodecs).reduce(function (acc, supporter) { 62881 if (acc) { 62882 acc += ', '; 62883 } 62884 62885 acc += supporter + " does not support codec(s): \"" + unsupportedCodecs[supporter].join(',') + "\""; 62886 return acc; 62887 }, '') + '.'; 62888 this.blacklistCurrentPlaylist({ 62889 playlist: this.media(), 62890 internal: true, 62891 message: message, 62892 blacklistDuration: Infinity 62893 }); 62894 return; 62895 } // check if codec switching is happening 62896 62897 62898 if (this.sourceUpdater_.hasCreatedSourceBuffers() && !this.sourceUpdater_.canChangeType()) { 62899 var switchMessages = []; 62900 ['video', 'audio'].forEach(function (type) { 62901 var newCodec = (parseCodecs(_this9.sourceUpdater_.codecs[type] || '')[0] || {}).type; 62902 var oldCodec = (parseCodecs(codecs[type] || '')[0] || {}).type; 62903 62904 if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) { 62905 switchMessages.push("\"" + _this9.sourceUpdater_.codecs[type] + "\" -> \"" + codecs[type] + "\""); 62906 } 62907 }); 62908 62909 if (switchMessages.length) {
62910 this.blacklistCurrentPlaylist({ 62911 playlist: this.media(), 62912 message: "Codec switching not supported: " + switchMessages.join(', ') + ".", 62913 blacklistDuration: Infinity, 62914 internal: true 62915 }); 62916 return; 62917 } 62918 } // TODO: when using the muxer shouldn't we just return 62919 // the codecs that the muxer outputs? 62920 62921 62922 return codecs; 62923 } 62924 /** 62925 * Create source buffers and exlude any incompatible renditions. 62926 * 62927 * @private 62928 */ 62929 ; 62930 62931 _proto.tryToCreateSourceBuffers_ = function tryToCreateSourceBuffers_() { 62932 // media source is not ready yet or sourceBuffers are already 62933 // created. 62934 if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.hasCreatedSourceBuffers()) { 62935 return; 62936 } 62937 62938 if (!this.areMediaTypesKnown_()) { 62939 return; 62940 } 62941 62942 var codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded 62943 62944 if (!codecs) { 62945 return; 62946 } 62947 62948 this.sourceUpdater_.createSourceBuffers(codecs); 62949 var codecString = [codecs.video, codecs.audio].filter(Boolean).join(','); 62950 this.excludeIncompatibleVariants_(codecString); 62951 } 62952 /** 62953 * Excludes playlists with codecs that are unsupported by the muxer and browser. 62954 */ 62955 ; 62956 62957 _proto.excludeUnsupportedVariants_ = function excludeUnsupportedVariants_() { 62958 var _this10 = this; 62959 62960 var playlists = this.master().playlists; 62961 var ids = []; // TODO: why don't we have a property to loop through all 62962 // playlist? Why did we ever mix indexes and keys? 62963 62964 Object.keys(playlists).forEach(function (key) { 62965 var variant = playlists[key]; // check if we already processed this playlist. 62966 62967 if (ids.indexOf(variant.id) !== -1) { 62968 return; 62969 } 62970 62971 ids.push(variant.id); 62972 var codecs = codecsForPlaylist(_this10.master, variant); 62973 var unsupported = []; 62974 62975 if (codecs.audio && !muxerSupportsCodec(codecs.audio) && !browserSupportsCodec(codecs.audio)) { 62976 unsupported.push("audio codec " + codecs.audio); 62977 } 62978 62979 if (codecs.video && !muxerSupportsCodec(codecs.video) && !browserSupportsCodec(codecs.video)) { 62980 unsupported.push("video codec " + codecs.video); 62981 } 62982 62983 if (codecs.text && codecs.text === 'stpp.ttml.im1t') { 62984 unsupported.push("text codec " + codecs.text); 62985 } 62986 62987 if (unsupported.length) { 62988 variant.excludeUntil = Infinity; 62989 62990 _this10.logger_("excluding " + variant.id + " for unsupported: " + unsupported.join(', ')); 62991 } 62992 }); 62993 } 62994 /** 62995 * Blacklist playlists that are known to be codec or 62996 * stream-incompatible with the SourceBuffer configuration. For 62997 * instance, Media Source Extensions would cause the video element to 62998 * stall waiting for video data if you switched from a variant with 62999 * video and audio to an audio-only one. 63000 * 63001 * @param {Object} media a media playlist compatible with the current 63002 * set of SourceBuffers. Variants in the current master playlist that 63003 * do not appear to have compatible codec or stream configurations 63004 * will be excluded from the default playlist selection algorithm 63005 * indefinitely. 63006 * @private 63007 */ 63008 ; 63009 63010 _proto.excludeIncompatibleVariants_ = function excludeIncompatibleVariants_(codecString) { 63011 var _this11 = this; 63012 63013 var ids = []; 63014 var playlists = this.master().playlists; 63015 var codecs = unwrapCodecList(parseCodecs(codecString)); 63016 var codecCount_ = codecCount(codecs); 63017 var videoDetails = codecs.video && parseCodecs(codecs.video)[0] || null; 63018 var audioDetails = codecs.audio && parseCodecs(codecs.audio)[0] || null; 63019 Object.keys(playlists).forEach(function (key) { 63020 var variant = playlists[key]; // check if we already processed this playlist. 63021 // or it if it is already excluded forever. 63022 63023 if (ids.indexOf(variant.id) !== -1 || variant.excludeUntil === Infinity) { 63024 return; 63025 } 63026 63027 ids.push(variant.id); 63028 var blacklistReasons = [];
63028 // get codecs from the playlist for this variant 63029 63030 var variantCodecs = codecsForPlaylist(_this11.masterPlaylistLoader_.master, variant); 63031 var variantCodecCount = codecCount(variantCodecs); // if no codecs are listed, we cannot determine that this 63032 // variant is incompatible. Wait for mux.js to probe 63033 63034 if (!variantCodecs.audio && !variantCodecs.video) { 63035 return; 63036 } // TODO: we can support this by removing the 63037 // old media source and creating a new one, but it will take some work. 63038 // The number of streams cannot change 63039 63040 63041 if (variantCodecCount !== codecCount_) { 63042 blacklistReasons.push("codec count \"" + variantCodecCount + "\" !== \"" + codecCount_ + "\""); 63043 } // only exclude playlists by codec change, if codecs cannot switch 63044 // during playback. 63045 63046 63047 if (!_this11.sourceUpdater_.canChangeType()) { 63048 var variantVideoDetails = variantCodecs.video && parseCodecs(variantCodecs.video)[0] || null; 63049 var variantAudioDetails = variantCodecs.audio && parseCodecs(variantCodecs.audio)[0] || null; // the video codec cannot change 63050 63051 if (variantVideoDetails && videoDetails && variantVideoDetails.type.toLowerCase() !== videoDetails.type.toLowerCase()) { 63052 blacklistReasons.push("video codec \"" + variantVideoDetails.type + "\" !== \"" + videoDetails.type + "\""); 63053 } // the audio codec cannot change 63054 63055 63056 if (variantAudioDetails && audioDetails && variantAudioDetails.type.toLowerCase() !== audioDetails.type.toLowerCase()) { 63057 blacklistReasons.push("audio codec \"" + variantAudioDetails.type + "\" !== \"" + audioDetails.type + "\""); 63058 } 63059 } 63060 63061 if (blacklistReasons.length) { 63062 variant.excludeUntil = Infinity; 63063 63064 _this11.logger_("blacklisting " + variant.id + ": " + blacklistReasons.join(' && ')); 63065 } 63066 }); 63067 }; 63068 63069 _proto.updateAdCues_ = function updateAdCues_(media) { 63070 var offset = 0; 63071 var seekable = this.seekable(); 63072 63073 if (seekable.length) { 63074 offset = seekable.start(0); 63075 } 63076 63077 updateAdCues(media, this.cueTagsTrack_, offset); 63078 } 63079 /** 63080 * Calculates the desired forward buffer length based on current time 63081 * 63082 * @return {number} Desired forward buffer length in seconds 63083 */ 63084 ; 63085 63086 _proto.goalBufferLength = function goalBufferLength() { 63087 var currentTime = this.tech_.currentTime(); 63088 var initial = Config.GOAL_BUFFER_LENGTH; 63089 var rate = Config.GOAL_BUFFER_LENGTH_RATE; 63090 var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH); 63091 return Math.min(initial + currentTime * rate, max); 63092 } 63093 /** 63094 * Calculates the desired buffer low water line based on current time 63095 * 63096 * @return {number} Desired buffer low water line in seconds 63097 */ 63098 ; 63099 63100 _proto.bufferLowWaterLine = function bufferLowWaterLine() { 63101 var currentTime = this.tech_.currentTime(); 63102 var initial = Config.BUFFER_LOW_WATER_LINE; 63103 var rate = Config.BUFFER_LOW_WATER_LINE_RATE; 63104 var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE); 63105 var newMax = Math.max(initial, Config.EXPERIMENTAL_MAX_BUFFER_LOW_WATER_LINE); 63106 return Math.min(initial + currentTime * rate, this.experimentalBufferBasedABR ? newMax : max); 63107 }; 63108 63109 _proto.bufferHighWaterLine = function bufferHighWaterLine() { 63110 return Config.BUFFER_HIGH_WATER_LINE; 63111 }; 63112 63113 return MasterPlaylistController; 63114 }(videojs.EventTarget); 63115 /** 63116 * Returns a function that acts as the Enable/disable playlist function. 63117 * 63118 * @param {PlaylistLoader} loader - The master playlist loader 63119 * @param {string} playlistID - id of the playlist 63120 * @param {Function} changePlaylistFn - A function to be called after a 63121 * playlist's enabled-state has been changed. Will NOT be called if a 63122 * playlist's enabled-state is unchanged 63123 * @param {boolean=} enable - Value to set the playlist enabled-state to 63124 * or if undefined returns the current enabled-state for the playlist 63125 * @return {Function} Function for setting/getting enabled 63126 */ 63127 63128 63129 var enableFunction = function enableFunction(loader, playlistID, changePlaylistFn) { 63130 return function (enable) { 63131 var playlist = loader.master.playlists[playlistID]; 63132 var incompatible = isIncompatible(playlist); 63133 var currentlyEnabled = isEnabled(playlist); 63134 63135 if (typeof enable === 'undefined') { 63136 return currentlyEnabled; 63137 } 63138 63139 if (enable) { 63140 delete playlist.disabled; 63141 } else { 63142 playlist.disabled = true; 63143 } 63144 63145 if (enable !== currentlyEnabled && !incompatible) { 63146 // Ensure the outside world knows about our changes 63147 changePlaylistFn(); 63148 63149 if (enable) { 63150 loader.trigger('renditionenabled'); 63151 } else { 63152 loader.trigger('renditiondisabled'); 63153 } 63154 } 63155 63156 return enable; 63157 }; 63158 }; 63159 /** 63160 * The representation object encapsulates the publicly visible information 63161 * in a media playlist along with a setter/getter-type function (enabled) 63162 * for changing the enabled-state of a particular playlist entry 63163 * 63164 * @class Representation 63165 */ 63166 63167 63168 var Representation = function Representation(vhsHandler, playlist, id) { 63169 var mpc = vhsHandler.masterPlaylistController_, 63170 smoothQualityChange = vhsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function 63171 63172 var changeType = smoothQualityChange ? 'smooth' : 'fast'; 63173 var qualityChangeFunction = mpc[changeType + "QualityChange_"].bind(mpc); // some playlist attributes are optional 63174 63175 if (playlist.attributes) { 63176 var resolution = playlist.attributes.RESOLUTION; 63177 this.width = resolution && resolution.width; 63178 this.height = resolution && resolution.height; 63179 this.bandwidth = playlist.attributes.BANDWIDTH; 63180 } 63181 63182 this.codecs = codecsForPlaylist(mpc.master(), playlist); 63183 this.playlist = playlist; // The id is simply the ordinality of the media playlist 63184 // within the master playlist 63185 63186 this.id = id; // Partially-apply the enableFunction to create a playlist- 63187 // specific variant 63188 63189 this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction); 63190 }; 63191 /** 63192 * A mixin function that adds the `representations` api to an instance 63193 * of the VhsHandler class 63194 * 63195 * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the 63196 * representation API into 63197 */ 63198 63199 63200 var renditionSelectionMixin = function renditionSelectionMixin(vhsHandler) { 63201 // Add a single API-specific function to the VhsHandler instance 63202 vhsHandler.representations = function () { 63203 var master = vhsHandler.masterPlaylistController_.master(); 63204 var playlists = isAudioOnly(master) ? vhsHandler.masterPlaylistController_.getAudioTrackPlaylists_() : master.playlists; 63205 63206 if (!playlists) { 63207 return []; 63208 } 63209 63210 return playlists.filter(function (media) { 63211 return !isIncompatible(media); 63212 }).map(function (e, i) { 63213 return new Representation(vhsHandler, e, e.id); 63214 }); 63215 }; 63216 }; 63217 /** 63218 * @file playback-watcher.js 63219 * 63220 * Playback starts, and now my watch begins. It shall not end until my death. I shall 63221 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns 63222 * and win no glory. I shall live and die at my post. I am the corrector of the underflow. 63223 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge 63224 * my life and honor to the Playback Watch, for this Player and all the Players to come. 63225 */ 63226 63227 63228 var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 63229 /** 63230 * Returns whether or not the current time should be considered close to buffered content, 63231 * taking into consideration whether there's enough buffered content for proper playback. 63232 * 63233 * @param {Object} options 63234 * Options object 63235 * @param {TimeRange} options.buffered 63236 * Current buffer 63237 * @param {number} options.targetDuration 63238 * The active playlist's target duration 63239 * @param {number} options.currentTime 63240 * The current time of the player 63241 * @return {boolean} 63242 * Whether the current time should be considered close to the buffer 63243 */ 63244 63245 var closeToBufferedContent = function closeToBufferedContent(_ref) { 63246 var buffered = _ref.buffered, 63247 targetDuration = _ref.targetDuration, 63248 currentTime = _ref.currentTime; 63249 63250 if (!buffered.length) { 63251 return false; 63252 } // At least two to three segments worth of content should be buffered before there's a 63253 // full enough buffer to consider taking any actions. 63254 63255 63256 if (buffered.end(0) - buffered.start(0) < targetDuration * 2) { 63257 return false; 63258 } // It's possible that, on seek, a remove hasn't completed and the buffered range is 63259 // somewhere past the current time. In that event, don't consider the buffered content 63260 // close.
63261 63262 63263 if (currentTime > buffered.start(0)) { 63264 return false; 63265 } // Since target duration generally represents the max (or close to max) duration of a 63266 // segment, if the buffer is within a segment of the current time, the gap probably 63267 // won't be closed, and current time should be considered close to buffered content. 63268 63269 63270 return buffered.start(0) - currentTime < targetDuration; 63271 }; 63272 /** 63273 * @class PlaybackWatcher 63274 */ 63275 63276 63277 var PlaybackWatcher = /*#__PURE__*/function () { 63278 /** 63279 * Represents an PlaybackWatcher object. 63280 * 63281 * @class 63282 * @param {Object} options an object that includes the tech and settings 63283 */ 63284 function PlaybackWatcher(options) { 63285 var _this = this; 63286 63287 this.masterPlaylistController_ = options.masterPlaylistController; 63288 this.tech_ = options.tech; 63289 this.seekable = options.seekable; 63290 this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow; 63291 this.liveRangeSafeTimeDelta = options.liveRangeSafeTimeDelta; 63292 this.media = options.media; 63293 this.consecutiveUpdates = 0; 63294 this.lastRecordedTime = null; 63295 this.timer_ = null; 63296 this.checkCurrentTimeTimeout_ = null; 63297 this.logger_ = logger('PlaybackWatcher'); 63298 this.logger_('initialize'); 63299 63300 var playHandler = function playHandler() { 63301 return _this.monitorCurrentTime_(); 63302 }; 63303 63304 var canPlayHandler = function canPlayHandler() { 63305 return _this.monitorCurrentTime_(); 63306 }; 63307 63308 var waitingHandler = function waitingHandler() { 63309 return _this.techWaiting_(); 63310 }; 63311 63312 var cancelTimerHandler = function cancelTimerHandler() { 63313 return _this.cancelTimer_(); 63314 }; 63315 63316 var fixesBadSeeksHandler = function fixesBadSeeksHandler() { 63317 return _this.fixesBadSeeks_(); 63318 }; 63319 63320 var mpc = this.masterPlaylistController_; 63321 var loaderTypes = ['main', 'subtitle', 'audio']; 63322 var loaderChecks = {}; 63323 loaderTypes.forEach(function (type) { 63324 loaderChecks[type] = { 63325 reset: function reset() { 63326 return _this.resetSegmentDownloads_(type); 63327 }, 63328 updateend: function updateend() { 63329 return _this.checkSegmentDownloads_(type); 63330 } 63331 }; 63332 mpc[type + "SegmentLoader_"].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer 63333 // isn't changing we want to reset. We cannot assume that the new rendition 63334 // will also be stalled, until after new appends. 63335 63336 mpc[type + "SegmentLoader_"].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking. 63337 // This prevents one segment playlists (single vtt or single segment content) 63338 // from being detected as stalling. As the buffer will not change in those cases, since 63339 // the buffer is the entire video duration. 63340 63341 _this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset); 63342 }); 63343 this.tech_.on('seekablechanged', fixesBadSeeksHandler); 63344 this.tech_.on('waiting', waitingHandler); 63345 this.tech_.on(timerCancelEvents, cancelTimerHandler); 63346 this.tech_.on('canplay', canPlayHandler); 63347 /* 63348 An edge case exists that results in gaps not being skipped when they exist at the beginning of a stream. This case 63349 is surfaced in one of two ways: 63350 1) The `waiting` event is fired before the player has buffered content, making it impossible 63351 to find or skip the gap. The `waiting` event is followed by a `play` event. On first play 63352 we can check if playback is stalled due to a gap, and skip the gap if necessary. 63353 2) A source with a gap at the beginning of the stream is loaded programatically while the player 63354 is in a playing state. To catch this case, it's important that our one-time play listener is setup 63355 even if the player is in a playing state 63356 */ 63357 63358 this.tech_.one('play', playHandler); // Define the dispose function to clean up our events 63359 63360 this.dispose = function () { 63361 _this.logger_('dispose'); 63362 63363 _this.tech_.off('seekablechanged', fixesBadSeeksHandler); 63364 63365 _this.tech_.off('waiting', waitingHandler); 63366 63367 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 63368 63369 _this.tech_.off('canplay', canPlayHandler); 63370 63371 _this.tech_.off('play', playHandler); 63372
63373 loaderTypes.forEach(function (type) { 63374 mpc[type + "SegmentLoader_"].off('appendsdone', loaderChecks[type].updateend); 63375 mpc[type + "SegmentLoader_"].off('playlistupdate', loaderChecks[type].reset); 63376 63377 _this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset); 63378 }); 63379 63380 if (_this.checkCurrentTimeTimeout_) { 63381 window_1.clearTimeout(_this.checkCurrentTimeTimeout_); 63382 } 63383 63384 _this.cancelTimer_(); 63385 }; 63386 } 63387 /** 63388 * Periodically check current time to see if playback stopped 63389 * 63390 * @private 63391 */ 63392 63393 63394 var _proto = PlaybackWatcher.prototype; 63395 63396 _proto.monitorCurrentTime_ = function monitorCurrentTime_() { 63397 this.checkCurrentTime_(); 63398 63399 if (this.checkCurrentTimeTimeout_) { 63400 window_1.clearTimeout(this.checkCurrentTimeTimeout_); 63401 } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 63402 63403 63404 this.checkCurrentTimeTimeout_ = window_1.setTimeout(this.monitorCurrentTime_.bind(this), 250); 63405 } 63406 /** 63407 * Reset stalled download stats for a specific type of loader 63408 * 63409 * @param {string} type 63410 * The segment loader type to check. 63411 * 63412 * @listens SegmentLoader#playlistupdate 63413 * @listens Tech#seeking 63414 * @listens Tech#seeked 63415 */ 63416 ; 63417 63418 _proto.resetSegmentDownloads_ = function resetSegmentDownloads_(type) { 63419 var loader = this.masterPlaylistController_[type + "SegmentLoader_"]; 63420 63421 if (this[type + "StalledDownloads_"] > 0) { 63422 this.logger_("resetting possible stalled download count for " + type + " loader"); 63423 } 63424 63425 this[type + "StalledDownloads_"] = 0; 63426 this[type + "Buffered_"] = loader.buffered_(); 63427 } 63428 /** 63429 * Checks on every segment `appendsdone` to see 63430 * if segment appends are making progress. If they are not 63431 * and we are still downloading bytes. We blacklist the playlist. 63432 * 63433 * @param {string} type 63434 * The segment loader type to check. 63435 * 63436 * @listens SegmentLoader#appendsdone 63437 */ 63438 ; 63439 63440 _proto.checkSegmentDownloads_ = function checkSegmentDownloads_(type) { 63441 var mpc = this.masterPlaylistController_; 63442 var loader = mpc[type + "SegmentLoader_"]; 63443 var buffered = loader.buffered_(); 63444 var isBufferedDifferent = isRangeDifferent(this[type + "Buffered_"], buffered); 63445 this[type + "Buffered_"] = buffered; // if another watcher is going to fix the issue or 63446 // the buffered value for this loader changed 63447 // appends are working 63448 63449 if (isBufferedDifferent) { 63450 this.resetSegmentDownloads_(type); 63451 return; 63452 } 63453 63454 this[type + "StalledDownloads_"]++; 63455 this.logger_("found #" + this[type + "StalledDownloads_"] + " " + type + " appends that did not increase buffer (possible stalled download)", { 63456 playlistId: loader.playlist_ && loader.playlist_.id, 63457 buffered: timeRangesToArray(buffered) 63458 }); // after 10 possibly stalled appends with no reset, exclude 63459 63460 if (this[type + "StalledDownloads_"] < 10) { 63461 return; 63462 } 63463 63464 this.logger_(type + " loader stalled download exclusion"); 63465 this.resetSegmentDownloads_(type); 63466 this.tech_.trigger({ 63467 type: 'usage', 63468 name: "vhs-" + type + "-download-exclusion" 63469 }); 63470 63471 if (type === 'subtitle') { 63472 return; 63473 } // TODO: should we exclude audio tracks rather than main tracks 63474 // when type is audio? 63475 63476 63477 mpc.blacklistCurrentPlaylist({ 63478 message: "Excessive " + type + " segment downloading detected." 63479 }, Infinity); 63480 } 63481 /** 63482 * The purpose of this function is to emulate the "waiting" event on 63483 * browsers that do not emit it when they are waiting for more 63484 * data to continue playback 63485 * 63486 * @private 63487 */ 63488 ; 63489 63490 _proto.checkCurrentTime_ = function checkCurrentTime_() { 63491 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 63492 this.consecutiveUpdates = 0; 63493 this.lastRecordedTime = this.tech_.currentTime(); 63494 return; 63495 } 63496 63497 if (this.tech_.paused() || this.tech_.seeking()) { 63498 return; 63499 } 63500 63501 var currentTime = this.tech_.currentTime(); 63502 var buffered = this.tech_.buffered(); 63503 63504 if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) { 63505 // If current time is at the end of the final buffered region, then any playback 63506 // stall is most likely caused by buffering in a low bandwidth environment. The tech 63507 // should fire a `waiting` event in this scenario, but due to browser and tech 63508 // inconsistencies. Calling `techWaiting_` here allows us to simulate 63509 // responding to a native `waiting` event when the tech fails to emit one. 63510 return this.techWaiting_(); 63511 } 63512 63513 if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) { 63514 this.consecutiveUpdates++; 63515 this.waiting_(); 63516 } else if (currentTime === this.lastRecordedTime) { 63517 this.consecutiveUpdates++; 63518 } else { 63519 this.consecutiveUpdates = 0; 63520 this.lastRecordedTime = currentTime; 63521 } 63522 } 63523 /** 63524 * Cancels any pending timers and resets the 'timeupdate' mechanism 63525 * designed to detect that we are stalled 63526 * 63527 * @private 63528 */ 63529 ; 63530 63531 _proto.cancelTimer_ = function cancelTimer_() { 63532 this.consecutiveUpdates = 0; 63533 63534 if (this.timer_) { 63535 this.logger_('cancelTimer_'); 63536 clearTimeout(this.timer_); 63537 } 63538 63539 this.timer_ = null; 63540 } 63541 /** 63542 * Fixes situations where there's a bad seek 63543 * 63544 * @return {boolean} whether an action was taken to fix the seek 63545 * @private 63546 */ 63547 ; 63548 63549 _proto.fixesBadSeeks_ = function fixesBadSeeks_() { 63550 var seeking = this.tech_.seeking(); 63551 63552 if (!seeking) { 63553 return false; 63554 } 63555 63556 var seekable = this.seekable(); 63557 var currentTime = this.tech_.currentTime(); 63558 var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow); 63559 var seekTo; 63560 63561 if (isAfterSeekableRange) { 63562 var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting) 63563 63564 seekTo = seekableEnd; 63565 } 63566 63567 if (this.beforeSeekableWindow_(seekable, currentTime)) { 63568 var seekableStart = seekable.start(0); // sync to the beginning of the live window 63569 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 63570 63571 seekTo = seekableStart + ( // if the playlist is too short and the seekable range is an exact time (can 63572 // happen in live with a 3 segment playlist), then don't use a time delta 63573 seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA); 63574 } 63575 63576 if (typeof seekTo !== 'undefined') { 63577 this.logger_("Trying to seek outside of seekable at time " + currentTime + " with " + ("seekable range " + printableRange(seekable) + ". Seeking to ") + (seekTo + ".")); 63578 this.tech_.setCurrentTime(seekTo); 63579 return true; 63580 } 63581 63582 var buffered = this.tech_.buffered(); 63583 63584 if (closeToBufferedContent({ 63585 buffered: buffered, 63586 targetDuration: this.media().targetDuration, 63587 currentTime: currentTime 63588 })) { 63589 seekTo = buffered.start(0) + SAFE_TIME_DELTA; 63590 this.logger_("Buffered region starts (" + buffered.start(0) + ") " + (" just beyond seek point (" + currentTime + "). Seeking to " + seekTo + ".")); 63591 this.tech_.setCurrentTime(seekTo); 63592 return true; 63593 } 63594 63595 return false; 63596 } 63597 /** 63598 * Handler for situations when we determine the player is waiting. 63599 * 63600 * @private 63601 */ 63602 ; 63603 63604 _proto.waiting_ = function waiting_() { 63605 if (this.techWaiting_()) { 63606 return; 63607 }
63607 // All tech waiting checks failed. Use last resort correction 63608 63609 63610 var currentTime = this.tech_.currentTime(); 63611 var buffered = this.tech_.buffered(); 63612 var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered 63613 // region with no indication that anything is amiss (seen in Firefox). Seeking to 63614 // currentTime is usually enough to kickstart the player. This checks that the player 63615 // is currently within a buffered region before attempting a corrective seek. 63616 // Chrome does not appear to continue `timeupdate` events after a `waiting` event 63617 // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also 63618 // make sure there is ~3 seconds of forward buffer before taking any corrective action 63619 // to avoid triggering an `unknownwaiting` event when the network is slow. 63620 63621 if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { 63622 this.cancelTimer_(); 63623 this.tech_.setCurrentTime(currentTime); 63624 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 63625 63626 this.tech_.trigger({ 63627 type: 'usage', 63628 name: 'vhs-unknown-waiting' 63629 }); 63630 this.tech_.trigger({ 63631 type: 'usage', 63632 name: 'hls-unknown-waiting' 63633 }); 63634 return; 63635 } 63636 } 63637 /** 63638 * Handler for situations when the tech fires a `waiting` event 63639 * 63640 * @return {boolean} 63641 * True if an action (or none) was needed to correct the waiting. False if no 63642 * checks passed 63643 * @private 63644 */ 63645 ; 63646 63647 _proto.techWaiting_ = function techWaiting_() { 63648 var seekable = this.seekable(); 63649 var currentTime = this.tech_.currentTime(); 63650 63651 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 63652 // Tech is seeking or bad seek fixed, no action needed 63653 return true; 63654 } 63655 63656 if (this.tech_.seeking() || this.timer_ !== null) { 63657 // Tech is seeking or already waiting on another action, no action needed 63658 return true; 63659 } 63660 63661 if (this.beforeSeekableWindow_(seekable, currentTime)) { 63662 var livePoint = seekable.end(seekable.length - 1); 63663 this.logger_("Fell out of live window at time " + currentTime + ". Seeking to " + ("live point (seekable end) " + livePoint)); 63664 this.cancelTimer_(); 63665 this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS 63666 63667 this.tech_.trigger({ 63668 type: 'usage', 63669 name: 'vhs-live-resync' 63670 }); 63671 this.tech_.trigger({ 63672 type: 'usage', 63673 name: 'hls-live-resync' 63674 }); 63675 return true; 63676 } 63677 63678 var sourceUpdater = this.tech_.vhs.masterPlaylistController_.sourceUpdater_; 63679 var buffered = this.tech_.buffered(); 63680 var videoUnderflow = this.videoUnderflow_({ 63681 audioBuffered: sourceUpdater.audioBuffered(), 63682 videoBuffered: sourceUpdater.videoBuffered(), 63683 currentTime: currentTime 63684 }); 63685 63686 if (videoUnderflow) { 63687 // Even though the video underflowed and was stuck in a gap, the audio overplayed 63688 // the gap, leading currentTime into a buffered range. Seeking to currentTime 63689 // allows the video to catch up to the audio position without losing any audio 63690 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 63691 this.cancelTimer_(); 63692 this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS 63693 63694 this.tech_.trigger({ 63695 type: 'usage', 63696 name: 'vhs-video-underflow' 63697 }); 63698 this.tech_.trigger({ 63699 type: 'usage', 63700 name: 'hls-video-underflow' 63701 }); 63702 return true; 63703 } 63704 63705 var nextRange = findNextRange(buffered, currentTime); // check for gap 63706 63707 if (nextRange.length > 0) { 63708 var difference = nextRange.start(0) - currentTime; 63709 this.logger_("Stopped at " + currentTime + ", setting timer for " + difference + ", seeking " + ("to " + nextRange.start(0))); 63710 this.cancelTimer_(); 63711 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 63712 return true; 63713 } // All checks failed. Returning false to indicate failure to correct waiting 63714 63715 63716 return false; 63717 }; 63718 63719 _proto.afterSeekableWindow_ = function afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow) { 63720 if (allowSeeksWithinUnsafeLiveWindow === void 0) { 63721 allowSeeksWithinUnsafeLiveWindow = false; 63722 } 63723 63724 if (!seekable.length) { 63725 // we can't make a solid case if there's no seekable, default to false 63726 return false; 63727 } 63728 63729 var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA; 63730 var isLive = !playlist.endList; 63731 63732 if (isLive && allowSeeksWithinUnsafeLiveWindow) { 63733 allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3; 63734 } 63735 63736 if (currentTime > allowedEnd) { 63737 return true; 63738 } 63739 63740 return false; 63741 }; 63742 63743 _proto.beforeSeekableWindow_ = function beforeSeekableWindow_(seekable, currentTime) { 63744 if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream 63745 seekable.start(0) > 0 && currentTime < seekable.start(0) - this.liveRangeSafeTimeDelta) { 63746 return true; 63747 } 63748 63749 return false; 63750 }; 63751 63752 _proto.videoUnderflow_ = function videoUnderflow_(_ref2) { 63753 var videoBuffered = _ref2.videoBuffered, 63754 audioBuffered = _ref2.audioBuffered, 63755 currentTime = _ref2.currentTime; // audio only content will not have video underflow :) 63756 63757 if (!videoBuffered) { 63758 return; 63759 } 63760 63761 var gap;
63761 // find a gap in demuxed content. 63762 63763 if (videoBuffered.length && audioBuffered.length) { 63764 // in Chrome audio will continue to play for ~3s when we run out of video 63765 // so we have to check that the video buffer did have some buffer in the 63766 // past. 63767 var lastVideoRange = findRange(videoBuffered, currentTime - 3); 63768 var videoRange = findRange(videoBuffered, currentTime); 63769 var audioRange = findRange(audioBuffered, currentTime); 63770 63771 if (audioRange.length && !videoRange.length && lastVideoRange.length) { 63772 gap = { 63773 start: lastVideoRange.end(0), 63774 end: audioRange.end(0) 63775 }; 63776 } // find a gap in muxed content. 63777 63778 } else { 63779 var nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are 63780 // stuck in a gap due to video underflow. 63781 63782 if (!nextRange.length) { 63783 gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime); 63784 } 63785 } 63786 63787 if (gap) { 63788 this.logger_("Encountered a gap in video from " + gap.start + " to " + gap.end + ". " + ("Seeking to current time " + currentTime)); 63789 return true; 63790 } 63791 63792 return false; 63793 } 63794 /** 63795 * Timer callback. If playback still has not proceeded, then we seek 63796 * to the start of the next buffered region. 63797 * 63798 * @private 63799 */ 63800 ; 63801 63802 _proto.skipTheGap_ = function skipTheGap_(scheduledCurrentTime) { 63803 var buffered = this.tech_.buffered(); 63804 var currentTime = this.tech_.currentTime(); 63805 var nextRange = findNextRange(buffered, currentTime); 63806 this.cancelTimer_(); 63807 63808 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 63809 return; 63810 } 63811 63812 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played 63813 63814 this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR); 63815 this.tech_.trigger({ 63816 type: 'usage', 63817 name: 'vhs-gap-skip' 63818 }); 63819 this.tech_.trigger({ 63820 type: 'usage', 63821 name: 'hls-gap-skip' 63822 }); 63823 }; 63824 63825 _proto.gapFromVideoUnderflow_ = function gapFromVideoUnderflow_(buffered, currentTime) { 63826 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 63827 // playing for ~3 seconds after the video gap starts. This is done to account for 63828 // video buffer underflow/underrun (note that this is not done when there is audio 63829 // buffer underflow/underrun -- in that case the video will stop as soon as it 63830 // encounters the gap, as audio stalls are more noticeable/jarring to a user than 63831 // video stalls). The player's time will reflect the playthrough of audio, so the 63832 // time will appear as if we are in a buffered region, even if we are stuck in a 63833 // "gap." 63834 // 63835 // Example: 63836 // video buffer: 0 => 10.1, 10.2 => 20 63837 // audio buffer: 0 => 20 63838 // overall buffer: 0 => 10.1, 10.2 => 20 63839 // current time: 13 63840 // 63841 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 63842 // however, the audio continued playing until it reached ~3 seconds past the gap 63843 // (13 seconds), at which point it stops as well. Since current time is past the 63844 // gap, findNextRange will return no ranges. 63845 // 63846 // To check for this issue, we see if there is a gap that starts somewhere within 63847 // a 3 second range (3 seconds +/- 1 second) back from our current time. 63848 var gaps = findGaps(buffered); 63849 63850 for (var i = 0; i < gaps.length; i++) { 63851 var start = gaps.start(i); 63852 var end = gaps.end(i); // gap is starts no more than 4 seconds back 63853 63854 if (currentTime - start < 4 && currentTime - start > 2) { 63855 return { 63856 start: start, 63857 end: end 63858 }; 63859 } 63860 } 63861 63862 return null; 63863 }; 63864 63865 return PlaybackWatcher; 63866 }(); 63867 63868 var defaultOptions = { 63869 errorInterval: 30, 63870 getSource: function getSource(next) { 63871 var tech = this.tech({ 63872 IWillNotUseThisInPlugins: true 63873 }); 63874 var sourceObj = tech.currentSource_ || this.currentSource(); 63875 return next(sourceObj); 63876 } 63877 }; 63878 /** 63879 * Main entry point for the plugin 63880 * 63881 * @param {Player} player a reference to a videojs Player instance 63882 * @param {Object} [options] an object with plugin options 63883 * @private 63884 */ 63885 63886 var initPlugin = function initPlugin(player, options) { 63887 var lastCalled = 0; 63888 var seekTo = 0; 63889 var localOptions = videojs.mergeOptions(defaultOptions, options); 63890 player.ready(function () { 63891 player.trigger({ 63892 type: 'usage', 63893 name: 'vhs-error-reload-initialized' 63894 }); 63895 player.trigger({ 63896 type: 'usage', 63897 name: 'hls-error-reload-initialized' 63898 }); 63899 });
63900 /** 63901 * Player modifications to perform that must wait until `loadedmetadata` 63902 * has been triggered 63903 * 63904 * @private 63905 */ 63906 63907 var loadedMetadataHandler = function loadedMetadataHandler() { 63908 if (seekTo) { 63909 player.currentTime(seekTo); 63910 } 63911 }; 63912 /** 63913 * Set the source on the player element, play, and seek if necessary 63914 * 63915 * @param {Object} sourceObj An object specifying the source url and mime-type to play 63916 * @private 63917 */ 63918 63919 63920 var setSource = function setSource(sourceObj) { 63921 if (sourceObj === null || sourceObj === undefined) { 63922 return; 63923 } 63924 63925 seekTo = player.duration() !== Infinity && player.currentTime() || 0; 63926 player.one('loadedmetadata', loadedMetadataHandler); 63927 player.src(sourceObj); 63928 player.trigger({ 63929 type: 'usage', 63930 name: 'vhs-error-reload' 63931 }); 63932 player.trigger({ 63933 type: 'usage', 63934 name: 'hls-error-reload' 63935 }); 63936 player.play(); 63937 }; 63938 /** 63939 * Attempt to get a source from either the built-in getSource function 63940 * or a custom function provided via the options 63941 * 63942 * @private 63943 */ 63944 63945 63946 var errorHandler = function errorHandler() { 63947 // Do not attempt to reload the source if a source-reload occurred before 63948 // 'errorInterval' time has elapsed since the last source-reload 63949 if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { 63950 player.trigger({ 63951 type: 'usage', 63952 name: 'vhs-error-reload-canceled' 63953 }); 63954 player.trigger({ 63955 type: 'usage', 63956 name: 'hls-error-reload-canceled' 63957 }); 63958 return; 63959 } 63960 63961 if (!localOptions.getSource || typeof localOptions.getSource !== 'function') { 63962 videojs.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!'); 63963 return; 63964 } 63965 63966 lastCalled = Date.now(); 63967 return localOptions.getSource.call(player, setSource); 63968 }; 63969 /** 63970 * Unbind any event handlers that were bound by the plugin 63971 * 63972 * @private 63973 */ 63974 63975 63976 var cleanupEvents = function cleanupEvents() { 63977 player.off('loadedmetadata', loadedMetadataHandler); 63978 player.off('error', errorHandler); 63979 player.off('dispose', cleanupEvents); 63980 }; 63981 /** 63982 * Cleanup before re-initializing the plugin 63983 * 63984 * @param {Object} [newOptions] an object with plugin options 63985 * @private 63986 */ 63987 63988 63989 var reinitPlugin = function reinitPlugin(newOptions) { 63990 cleanupEvents(); 63991 initPlugin(player, newOptions); 63992 }; 63993 63994 player.on('error', errorHandler); 63995 player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before 63996 // initializing the plugin 63997 63998 player.reloadSourceOnError = reinitPlugin; 63999 }; 64000 /** 64001 * Reload the source when an error is detected as long as there 64002 * wasn't an error previously within the last 30 seconds 64003 * 64004 * @param {Object} [options] an object with plugin options 64005 */ 64006 64007 64008 var reloadSourceOnError = function reloadSourceOnError(options) { 64009 initPlugin(this, options); 64010 }; 64011 64012 var version$4 = "2.10.2"; 64013 var version$3 = "5.13.0"; 64014 var version$2 = "0.19.0"; 64015 var version$1 = "4.7.0"; 64016 var version = "3.1.2"; 64017 var Vhs = { 64018 PlaylistLoader: PlaylistLoader, 64019 Playlist: Playlist, 64020 utils: utils, 64021 STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector, 64022 INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector, 64023 lastBandwidthSelector: lastBandwidthSelector, 64024 movingAverageBandwidthSelector: movingAverageBandwidthSelector, 64025 comparePlaylistBandwidth: comparePlaylistBandwidth, 64026 comparePlaylistResolution: comparePlaylistResolution, 64027 xhr: xhrFactory() 64028 }; // Define getter/setters for config properties 64029 64030 Object.keys(Config).forEach(function (prop) { 64031 Object.defineProperty(Vhs, prop, { 64032 get: function get() { 64033 videojs.log.warn("using Vhs." + prop + " is UNSAFE be sure you know
64033what you are doing"); 64034 return Config[prop]; 64035 }, 64036 set: function set(value) { 64037 videojs.log.warn("using Vhs." + prop + " is UNSAFE be sure you know what you are doing"); 64038 64039 if (typeof value !== 'number' || value < 0) { 64040 videojs.log.warn("value of Vhs." + prop + " must be greater than or equal to 0"); 64041 return; 64042 } 64043 64044 Config[prop] = value; 64045 } 64046 }); 64047 }); 64048 var LOCAL_STORAGE_KEY = 'videojs-vhs'; 64049 /** 64050 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs. 64051 * 64052 * @param {QualityLevelList} qualityLevels The QualityLevelList to update. 64053 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info. 64054 * @function handleVhsMediaChange 64055 */ 64056 64057 var handleVhsMediaChange = function handleVhsMediaChange(qualityLevels, playlistLoader) { 64058 var newPlaylist = playlistLoader.media(); 64059 var selectedIndex = -1; 64060 64061 for (var i = 0; i < qualityLevels.length; i++) { 64062 if (qualityLevels[i].id === newPlaylist.id) { 64063 selectedIndex = i; 64064 break; 64065 } 64066 } 64067 64068 qualityLevels.selectedIndex_ = selectedIndex; 64069 qualityLevels.trigger({ 64070 selectedIndex: selectedIndex, 64071 type: 'change' 64072 }); 64073 }; 64074 /** 64075 * Adds quality levels to list once playlist metadata is available 64076 * 64077 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to. 64078 * @param {Object} vhs Vhs object to listen to for media events. 64079 * @function handleVhsLoadedMetadata 64080 */ 64081 64082 64083 var handleVhsLoadedMetadata = function handleVhsLoadedMetadata(qualityLevels, vhs) { 64084 vhs.representations().forEach(function (rep) { 64085 qualityLevels.addQualityLevel(rep); 64086 }); 64087 handleVhsMediaChange(qualityLevels, vhs.playlists); 64088 }; // HLS is a source handler, not a tech. Make sure attempts to use it 64089 // as one do not cause exceptions. 64090 64091 64092 Vhs.canPlaySource = function () { 64093 return videojs.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 64094 }; 64095 64096 var emeKeySystems = function emeKeySystems(keySystemOptions, mainPlaylist, audioPlaylist) { 64097 if (!keySystemOptions) { 64098 return keySystemOptions; 64099 } 64100 64101 var codecs = {}; 64102 64103 if (mainPlaylist && mainPlaylist.attributes && mainPlaylist.attributes.CODECS) { 64104 codecs = unwrapCodecList(parseCodecs(mainPlaylist.attributes.CODECS)); 64105 } 64106 64107 if (audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS) { 64108 codecs.audio = audioPlaylist.attributes.CODECS; 64109 } 64110 64111 var videoContentType = getMimeForCodec(codecs.video); 64112 var audioContentType = getMimeForCodec(codecs.audio); // upsert the content types based on the selected playlist 64113 64114 var keySystemContentTypes = {}; 64115 64116 for (var keySystem in keySystemOptions) { 64117 keySystemContentTypes[keySystem] = {}; 64118 64119 if (audioContentType) { 64120 keySystemContentTypes[keySystem].audioContentType = audioContentType; 64121 } 64122 64123 if (videoContentType) { 64124 keySystemContentTypes[keySystem].videoContentType = videoContentType; 64125 } // Default to using the video playlist's PSSH even though they may be different, as 64126 // videojs-contrib-eme will only accept one in the options. 64127 // 64128 // This shouldn't be an issue for most cases as early intialization will handle all 64129 // unique PSSH values, and if they aren't, then encrypted events should have the 64130 // specific information needed for the unique license. 64131 64132 64133 if (mainPlaylist.contentProtection && mainPlaylist.contentProtection[keySystem] && mainPlaylist.contentProtection[keySystem].pssh) { 64134 keySystemContentTypes[keySystem].pssh = mainPlaylist.contentProtection[keySystem].pssh; 64135 } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url' 64136 // so we need to prevent overwriting the URL entirely 64137 64138 64139 if (typeof keySystemOptions[keySystem] === 'string') { 64140 keySystemContentTypes[keySystem].url = keySystemOptions[keySystem]; 64141 } 64142 } 64143 64144 return videojs.mergeOptions(keySystemOptions, keySystemContentTypes); 64145 }; 64146 /** 64147 * @typedef {Object} KeySystems 64148 * 64149 * keySystems configuration for https://github.com/videojs/videojs-contrib-eme 64150 * Note: not all options are listed here. 64151 * 64152 * @property {Uint8Array} [pssh] 64153 * Protection System Specific Header 64154 */ 64155 64156 /** 64157 * Goes through all the playlists and collects an array of KeySystems options objects 64158 * containing each playlist's keySystems and their pssh values, if available. 64159 * 64160 * @param {Object[]} playlists 64161 * The playlists to look through 64162 * @param {string[]} keySystems 64163 * The keySystems to collect pssh values for 64164 * 64165 * @return {KeySystems[]} 64166 * An array of KeySystems objects containing available key systems and their 64167 * pssh values 64168 */ 64169 64170 64171 var getAllPsshKeySystemsOptions = function getAllPsshKeySystemsOptions(playlists, keySystems) { 64172 return playlists.reduce(function (keySystemsArr, playlist) { 64173 if (!playlist.contentProtection) { 64174 return keySystemsArr; 64175 } 64176 64177 var keySystemsOptions = keySystems.reduce(function (keySystemsObj, keySystem) { 64178 var keySystemOptions = playlist.contentProtection[keySystem]; 64179 64180 if (keySystemOptions && keySystemOptions.pssh) { 64181 keySystemsObj[keySystem] = { 64182 pssh: keySystemOptions.pssh 64183 }; 64184 } 64185 64186 return keySystemsObj; 64187 }, {}); 64188 64189 if (Object.keys(keySystemsOptions).length) { 64190 keySystemsArr.push(keySystemsOptions); 64191 } 64192 64193 return keySystemsArr; 64194 }, []); 64195 }; 64196 /** 64197 * Returns a promise that waits for the 64198 * [eme plugin](https://github.com/videojs/videojs-contrib-eme) to create a key session. 64199 * 64200 * Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 in non-IE11 64201 * browsers. 64202 * 64203 * As per the above ticket, this is particularly important for Chrome, where, if 64204 * unencrypted content is appended before encrypted content and the key session has not 64205 * been created, a MEDIA_ERR_DECODE will be thrown once the encrypted content is reached 64206 * during playback. 64207 * 64208 * @param {Object} player 64209 * The player instance 64210 * @param {Object[]} sourceKeySystems 64211 * The key systems options from the player source 64212 * @param {Object} [audioMedia] 64213 * The active audio media playlist (optional) 64214 * @param {Object[]} mainPlaylists 64215 * The playlists found on the master playlist object 64216 * 64217 * @return {Object} 64218 * Promise that resolves when the key session has been created 64219 */ 64220 64221 64222 var waitForKeySessionCreation = function waitForKeySessionCreation(_ref) { 64223 var player = _ref.player, 64224 sourceKeySystems = _ref.sourceKeySystems, 64225 audioMedia = _ref.audioMedia, 64226 mainPlaylists = _ref.mainPlaylists; 64227 64228 if (!player.eme.initializeMediaKeys) { 64229 return Promise.resolve(); 64230 } // TODO should all audio PSSH values be initialized for DRM? 64231 // 64232 // All unique video rendition pssh values are initialized for DRM, but here only 64233 // the initial audio playlist license is initialized. In theory, an encrypted 64234 // event should be fired if the user switches to an alternative audio playlist 64235 // where a license is required, but this case hasn't yet been tested. In addition, there 64236 // may be many alternate audio playlists unlikely to be used (e.g., multiple different 64237 // languages). 64238 64239 64240 var playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists; 64241 var keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems));
64242 var initializationFinishedPromises = []; 64243 var keySessionCreatedPromises = []; // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The 64244 // only place where it should not be deduped is for ms-prefixed APIs, but the early 64245 // return for IE11 above, and the existence of modern EME APIs in addition to 64246 // ms-prefixed APIs on Edge should prevent this from being a concern. 64247 // initializeMediaKeys also won't use the webkit-prefixed APIs. 64248 64249 keySystemsOptionsArr.forEach(function (keySystemsOptions) { 64250 keySessionCreatedPromises.push(new Promise(function (resolve, reject) { 64251 player.tech_.one('keysessioncreated', resolve); 64252 })); 64253 initializationFinishedPromises.push(new Promise(function (resolve, reject) { 64254 player.eme.initializeMediaKeys({ 64255 keySystems: keySystemsOptions 64256 }, function (err) { 64257 if (err) { 64258 reject(err); 64259 return; 64260 } 64261 64262 resolve(); 64263 }); 64264 })); 64265 }); // The reasons Promise.race is chosen over Promise.any: 64266 // 64267 // * Promise.any is only available in Safari 14+. 64268 // * None of these promises are expected to reject. If they do reject, it might be 64269 // better here for the race to surface the rejection, rather than mask it by using 64270 // Promise.any. 64271 64272 return Promise.race([// If a session was previously created, these will all finish resolving without 64273 // creating a new session, otherwise it will take until the end of all license 64274 // requests, which is why the key session check is used (to make setup much faster). 64275 Promise.all(initializationFinishedPromises), // Once a single session is created, the browser knows DRM will be used. 64276 Promise.race(keySessionCreatedPromises)]); 64277 }; 64278 /** 64279 * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and 64280 * there are keySystems on the source, sets up source options to prepare the source for 64281 * eme. 64282 * 64283 * @param {Object} player 64284 * The player instance 64285 * @param {Object[]} sourceKeySystems 64286 * The key systems options from the player source 64287 * @param {Object} media 64288 * The active media playlist 64289 * @param {Object} [audioMedia] 64290 * The active audio media playlist (optional) 64291 * 64292 * @return {boolean} 64293 * Whether or not options were configured and EME is available 64294 */ 64295 64296 64297 var setupEmeOptions = function setupEmeOptions(_ref2) { 64298 var player = _ref2.player, 64299 sourceKeySystems = _ref2.sourceKeySystems, 64300 media = _ref2.media, 64301 audioMedia = _ref2.audioMedia; 64302 var sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia); 64303 64304 if (!sourceOptions) { 64305 return false; 64306 } 64307 64308 player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing 64309 // do nothing. 64310 64311 if (sourceOptions && !player.eme) { 64312 videojs.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin'); 64313 return false; 64314 } 64315 64316 return true; 64317 }; 64318 64319 var getVhsLocalStorage = function getVhsLocalStorage() { 64320 if (!window_1.localStorage) { 64321 return null; 64322 } 64323 64324 var storedObject = window_1.localStorage.getItem(LOCAL_STORAGE_KEY); 64325 64326 if (!storedObject) { 64327 return null; 64328 } 64329 64330 try { 64331 return JSON.parse(storedObject); 64332 } catch (e) { 64333 // someone may have tampered with the value 64334 return null; 64335 } 64336 }; 64337 64338 var updateVhsLocalStorage = function updateVhsLocalStorage(options) { 64339 if (!window_1.localStorage) { 64340 return false; 64341 } 64342 64343 var objectToStore = getVhsLocalStorage(); 64344 objectToStore = objectToStore ? videojs.mergeOptions(objectToStore, options) : options; 64345 64346 try { 64347 window_1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore)); 64348 } catch (e) { 64349 // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where 64350 // storage is set to 0). 64351 // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions 64352 // No need to perform any operation. 64353 return false; 64354 } 64355 64356 return objectToStore; 64357 }; 64358 /** 64359 * Parses VHS-supported media types from data URIs. See 64360 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs 64361 * for information on data URIs. 64362 * 64363 * @param {string} dataUri 64364 * The data URI 64365 * 64366 * @return {string|Object} 64367 * The parsed object/string, or the original string if no supported media type 64368 * was found 64369 */ 64370 64371 64372 var expandDataUri = function expandDataUri(dataUri) { 64373 if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) { 64374 return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1)); 64375 } // no known case for this data URI, return the string as-is 64376 64377 64378 return dataUri; 64379 }; 64380 /**
64381 * Whether the browser has built-in HLS support. 64382 */ 64383 64384 64385 Vhs.supportsNativeHls = function () { 64386 if (!document_1 || !document_1.createElement) { 64387 return false; 64388 } 64389 64390 var video = document_1.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't 64391 64392 if (!videojs.getTech('Html5').isSupported()) { 64393 return false; 64394 } // HLS manifests can go by many mime-types 64395 64396 64397 var canPlay = [// Apple santioned 64398 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility 64399 'audio/mpegurl', // Very common 64400 'audio/x-mpegurl', // Very common 64401 'application/x-mpegurl', // Included for completeness 64402 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 64403 return canPlay.some(function (canItPlay) { 64404 return /maybe|probably/i.test(video.canPlayType(canItPlay)); 64405 }); 64406 }(); 64407 64408 Vhs.supportsNativeDash = function () { 64409 if (!document_1 || !document_1.createElement || !videojs.getTech('Html5').isSupported()) { 64410 return false; 64411 } 64412 64413 return /maybe|probably/i.test(document_1.createElement('video').canPlayType('application/dash+xml')); 64414 }(); 64415 64416 Vhs.supportsTypeNatively = function (type) { 64417 if (type === 'hls') { 64418 return Vhs.supportsNativeHls; 64419 } 64420 64421 if (type === 'dash') { 64422 return Vhs.supportsNativeDash; 64423 } 64424 64425 return false; 64426 }; 64427 /** 64428 * HLS is a source handler, not a tech. Make sure attempts to use it 64429 * as one do not cause exceptions. 64430 */ 64431 64432 64433 Vhs.isSupported = function () { 64434 return videojs.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 64435 }; 64436 64437 var Component = videojs.getComponent('Component'); 64438 /** 64439 * The Vhs Handler object, where we orchestrate all of the parts 64440 * of HLS to interact with video.js 64441 * 64442 * @class VhsHandler 64443 * @extends videojs.Component 64444 * @param {Object} source the soruce object 64445 * @param {Tech} tech the parent tech object 64446 * @param {Object} options optional and required options 64447 */ 64448 64449 var VhsHandler = /*#__PURE__*/function (_Component) { 64450 inheritsLoose(VhsHandler, _Component); 64451 64452 function VhsHandler(source, tech, options) { 64453 var _this; 64454 64455 _this = _Component.call(this, tech, videojs.mergeOptions(options.hls, options.vhs)) || this; 64456 64457 if (options.hls && Object.keys(options.hls).length) { 64458 videojs.log.warn('Using hls options is deprecated. Use vhs instead.'); 64459 } // if a tech level `initialBandwidth` option was passed 64460 // use that over the VHS level `bandwidth` option 64461 64462 64463 if (typeof options.initialBandwidth === 'number') { 64464 _this.options_.bandwidth = options.initialBandwidth; 64465 } 64466 64467 _this.logger_ = logger('VhsHandler'); // tech.player() is deprecated but setup a reference to HLS for 64468 // backwards-compatibility 64469 64470 if (tech.options_ && tech.options_.playerId) { 64471 var _player = videojs(tech.options_.playerId); 64472 64473 if (!_player.hasOwnProperty('hls')) { 64474 Object.defineProperty(_player, 'hls', { 64475 get: function get() { 64476 videojs.log.warn('player.hls is deprecated. Use player.tech().vhs instead.'); 64477 tech.trigger({ 64478 type: 'usage', 64479 name: 'hls-player-access' 64480 }); 64481 return assertThisInitialized(_this); 64482 }, 64483 configurable: true 64484 }); 64485 } 64486 64487 if (!_player.hasOwnProperty('vhs')) { 64488 Object.defineProperty(_player, 'vhs', { 64489 get: function get() { 64490 videojs.log.warn('player.vhs is deprecated. Use player.tech().vhs instead.'); 64491 tech.trigger({ 64492 type: 'usage', 64493 name: 'vhs-player-access' 64494 }); 64495 return assertThisInitialized(_this); 64496 }, 64497 configurable: true 64498 }); 64499 } 64500 64501 if (!_player.hasOwnProperty('dash')) { 64502 Object.defineProperty(_player, 'dash', { 64503 get: function get() { 64504 videojs.log.warn('player.dash is deprecated. Use player.tech().vhs instead.'); 64505 return assertThisInitialized(_this); 64506 }, 64507 configurable: true 64508 }); 64509 } 64510 64511 _this.player_ = _player; 64512 } 64513 64514 _this.tech_ = tech; 64515 _this.source_ = source; 64516 _this.stats = {}; 64517 _this.ignoreNextSeekingEvent_ = false; 64518 64519 _this.setOptions_(); 64520 64521 if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) { 64522 tech.overrideNativeAudioTracks(true); 64523 tech.overrideNativeVideoTracks(true); 64524 } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { 64525 // overriding native HLS only works if audio tracks have been emulated 64526 // error early if we're misconfigured 64527 throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB'); 64528 } // listen for fullscreenchange events for this player so that we 64529 // can adjust our quality selection quickly 64530 64531 64532 _this.on(document_1, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 64533 var fullscreenElement = document_1.fullscreenElement || document_1.webkitFullscreenElement || document_1.mozFullScreenElement || document_1.msFullscreenElement; 64534 64535 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 64536 _this.masterPlaylistController_.fastQualityChange_(); 64537 } else { 64538 // When leaving fullscreen, since the in page pixel dimensions should be smaller 64539 // than full screen, see if there should be a rendition switch down to preserve 64540 // bandwidth. 64541 _this.masterPlaylistController_.checkABR_(); 64542 } 64543 }); 64544 64545 _this.on(_this.tech_, 'seeking', function () { 64546 if (this.ignoreNextSeekingEvent_) { 64547 this.ignoreNextSeekingEvent_ = false; 64548 return; 64549 } 64550 64551 this.setCurrentTime(this.tech_.currentTime()); 64552 }); 64553 64554 _this.on(_this.tech_, 'error', function () { 64555 // verify that the error was real and we are loaded 64556 // enough to have mpc loaded. 64557 if (this.tech_.error() && this.masterPlaylistController_) { 64558 this.masterPlaylistController_.pauseLoading(); 64559 } 64560 }); 64561 64562 _this.on(_this.tech_, 'play', _this.play); 64563 64564 return _this; 64565 } 64566 64567 var _proto = VhsHandler.prototype; 64568 64569 _proto.setOptions_ = function setOptions_() { 64570 var _this2 = this; // defaults 64571 64572 64573 this.options_.withCredentials = this.options_.withCredentials || false; 64574 this.options_.handleManifestRedirects = this.options_.handleManifestRedirects === false ? false : true; 64575 this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true; 64576 this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false; 64577 this.options_.smoothQualityChange = this.options_.smoothQualityChange || false; 64578 this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false; 64579 this.options_.customTagParsers = this.options_.customTagParsers || []; 64580 this.options_.customTagMappers = this.options_.customTagMappers || []; 64581 this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false; 64582 64583 if (typeof this.options_.blacklistDuration !== 'number') { 64584 this.options_.blacklistDuration = 5 * 60; 64585 } 64586 64587 if (typeof this.options_.bandwidth !== 'number') { 64588 if (this.options_.useBandwidthFromLocalStorage) { 64589 var storedObject = getVhsLocalStorage(); 64590 64591 if (storedObject && storedObject.bandwidth) { 64592 this.options_.bandwidth = storedObject.bandwidth; 64593 this.tech_.trigger({ 64594 type: 'usage', 64595 name: 'vhs-bandwidth-from-local-storage' 64596 }); 64597 this.tech_.trigger({ 64598 type: 'usage', 64599 name: 'hls-bandwidth-from-local-storage' 64600 }); 64601 } 64602 64603 if (storedObject && storedObject.throughput) { 64604 this.options_.throughput = storedObject.throughput; 64605 this.tech_.trigger({ 64606 type: 'usage', 64607 name: 'vhs-throughput-from-local-storage' 64608 }); 64609 this.tech_.trigger({ 64610 type: 'usage', 64611 name: 'hls-throughput-from-local-storage' 64612 }); 64613 } 64614 } 64615 } // if bandwidth was not set by options or pulled from local storage, start playlist 64616 // selection at a reasonable bandwidth 64617 64618 64619 if (typeof this.options_.bandwidth !== 'number') { 64620 this.options_.bandwidth = Config.INITIAL_BANDWIDTH; 64621 } // If the bandwidth number is unchanged from the initial setting 64622 // then this takes precedence over the enableLowInitialPlaylist option 64623 64624 64625 this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src 64626 64627 ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys', 'playlistSelector', 'initialPlaylistSelector', 'experimentalBufferBasedABR', 'liveRangeSafeTimeDelta', 'experimentalLLHLS', 'experimentalExactManifestTimings', 'experimentalLeastPixelDiffSelector'].forEach(function (option) { 64628 if (typeof _this2.source_[option] !== 'undefined') { 64629 _this2.options_[option] = _this2.source_[option]; 64630 } 64631 }); 64632 this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions; 64633 this.useDevicePixelRatio = this.options_.useDevicePixelRatio; 64634 } 64635 /** 64636 * called when player.src gets called, handle a new source 64637 * 64638 * @param {Object} src the source object to handle 64639 */ 64640 ; 64641 64642 _proto.src = function src(_src, type) { 64643 var _this3 = this; // do nothing if the src is falsey 64644 64645 64646 if (!_src) { 64647 return; 64648 } 64649 64650 this.setOptions_(); // add master playlist controller options 64651 64652 this.options_.src = expandDataUri(this.source_.src); 64653 this.options_.tech = this.tech_; 64654 this.options_.externVhs = Vhs; 64655 this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech 64656 64657 this.options_.seekTo = function (time) { 64658 _this3.tech_.setCurrentTime(time); 64659 }; 64660 64661 if (this.options_.smoothQualityChange) { 64662 videojs.log.warn('smoothQualityChange is deprecated and will be removed in the next major version'); 64663 } 64664 64665 this.masterPlaylistController_ = new MasterPlaylistController(this.options_); 64666 var playbackWatcherOptions = videojs.mergeOptions({ 64667 liveRangeSafeTimeDelta: SAFE_TIME_DELTA 64668 }, this.options_, { 64669 seekable: function seekable() { 64670 return _this3.seekable(); 64671 }, 64672 media: function media() { 64673 return _this3.masterPlaylistController_.media(); 64674 }, 64675 masterPlaylistController: this.masterPlaylistController_ 64676 }); 64677 this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions); 64678 this.masterPlaylistController_.on('error', function () { 64679 var player = videojs.players[_this3.tech_.options_.playerId]; 64680 var error = _this3.masterPlaylistController_.error; 64681 64682 if (typeof error === 'object' && !error.code) { 64683 error.code = 3; 64684 } else if (typeof error === 'string') { 64685 error = { 64686 message: error, 64687 code: 3 64688 }; 64689 } 64690 64691 player.error(error); 64692 }); 64693 var defaultSelector = this.options_.experimentalBufferBasedABR ? Vhs.mov
64693ingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR; // `this` in selectPlaylist should be the VhsHandler for backwards 64694 // compatibility with < v2 64695 64696 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : defaultSelector.bind(this); 64697 this.masterPlaylistController_.selectInitialPlaylist = Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2 64698 64699 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 64700 this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist 64701 // controller. Using a custom property for backwards compatibility 64702 // with < v2 64703 64704 Object.defineProperties(this, { 64705 selectPlaylist: { 64706 get: function get() { 64707 return this.masterPlaylistController_.selectPlaylist; 64708 }, 64709 set: function set(selectPlaylist) { 64710 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 64711 } 64712 }, 64713 throughput: { 64714 get: function get() { 64715 return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; 64716 }, 64717 set: function set(throughput) { 64718 this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value 64719 // for the cumulative average 64720 64721 this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; 64722 } 64723 }, 64724 bandwidth: { 64725 get: function get() { 64726 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 64727 }, 64728 set: function set(bandwidth) { 64729 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter 64730 // `count` is set to zero that current value of `rate` isn't included 64731 // in the cumulative average 64732 64733 this.masterPlaylistController_.mainSegmentLoader_.throughput = { 64734 rate: 0, 64735 count: 0 64736 }; 64737 } 64738 }, 64739 64740 /** 64741 * `systemBandwidth` is a combination of two serial processes bit-rates. The first 64742 * is the network bitrate provided by `bandwidth` and the second is the bitrate of 64743 * the entire process after that - decryption, transmuxing, and appending - provided 64744 * by `throughput`. 64745 * 64746 * Since the two process are serial, the overall system bandwidth is given by: 64747 * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput) 64748 */ 64749 systemBandwidth: { 64750 get: function get() { 64751 var invBandwidth = 1 / (this.bandwidth || 1); 64752 var invThroughput; 64753 64754 if (this.throughput > 0) { 64755 invThroughput = 1 / this.throughput; 64756 } else { 64757 invThroughput = 0; 64758 } 64759 64760 var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput)); 64761 return systemBitrate; 64762 }, 64763 set: function set() { 64764 videojs.log.error('The "systemBandwidth" property is read-only'); 64765 } 64766 } 64767 }); 64768 64769 if (this.options_.bandwidth) { 64770 this.bandwidth = this.options_.bandwidth; 64771 } 64772 64773 if (this.options_.throughput) { 64774 this.throughput = this.options_.throughput; 64775 } 64776 64777 Object.defineProperties(this.stats, { 64778 bandwidth: { 64779 get: function get() { 64780 return _this3.bandwidth || 0; 64781 }, 64782 enumerable: true 64783 }, 64784 mediaRequests: { 64785 get: function get() { 64786 return _this3.masterPlaylistController_.mediaRequests_() || 0; 64787 }, 64788 enumerable: true 64789 }, 64790 mediaRequestsAborted: { 64791 get: function get() { 64792 return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0; 64793 }, 64794 enumerable: true 64795 }, 64796 mediaRequestsTimedout: { 64797 get: function get() { 64798 return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0; 64799 }, 64800 enumerable: true 64801 }, 64802 mediaRequestsErrored: { 64803 get: function get() { 64804 return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0; 64805 }, 64806 enumerable: true 64807 }, 64808 mediaTransferDuration: { 64809 get: function get() { 64810 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 64811 }, 64812 enumerable: true 64813 }, 64814 mediaBytesTransferred: { 64815 get: function get() { 64816 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 64817 }, 64818 enumerable: true 64819 }, 64820 mediaSecondsLoaded: { 64821 get: function get() { 64822 return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0; 64823 }, 64824 enumerable: true 64825 }, 64826 mediaAppends: { 64827 get: function get() { 64828 return _this3.masterPlaylistController_.mediaAppends_() || 0; 64829 }, 64830 enumerable: true 64831 }, 64832 mainAppendsToLoadedData: { 64833 get: function get() { 64834 return _this3.masterPlaylistController_.mainAppendsToLoadedData_() || 0; 64835 }, 64836 enumerable: true 64837 }, 64838 audioAppendsToLoadedData: { 64839 get: function get() { 64840 return _this3.masterPlaylistController_.audioAppendsToLoadedData_() || 0; 64841 }, 64842 enumerable: true 64843 }, 64844 appendsToLoadedData: { 64845 get: function get() { 64846 return _this3.masterPlaylistController_.appendsToLoadedData_() || 0; 64847 }, 64848 enumerable: true 64849 }, 64850 timeToLoadedData: { 64851 get: function get() { 64852 return _this3.masterPlaylistController_.timeToLoadedData_() || 0; 64853 }, 64854 enumerable: true 64855 }, 64856 buffered: { 64857 get: function get() { 64858 return timeRangesToArray(_this3.tech_.buffered()); 64859 }, 64860 enumerable: true 64861 }, 64862 currentTime: { 64863 get: function get() { 64864 return _this3.tech_.currentTime(); 64865 }, 64866 enumerable: true 64867 }, 64868 currentSource: { 64869 get: function get() { 64870 return _this3.tech_.currentSource_; 64871 }, 64872 enumerable: true 64873 }, 64874 currentTech: { 64875 get: function get() { 64876 return _this3.tech_.name_; 64877 }, 64878 enumerable: true 64879 }, 64880 duration: { 64881 get: function get() { 64882 return _this3.tech_.duration(); 64883 }, 64884 enumerable: true 64885 }, 64886 master: { 64887 get: function get() { 64888 return _this3.playlists.master; 64889 }, 64890 enumerable: true 64891 }, 64892 playerDimensions: { 64893 get: function get() { 64894 return _this3.tech_.currentDimensions(); 64895 }, 64896 enumerable: true 64897 }, 64898 seekable: { 64899 get: function get() { 64900 return timeRangesToArray(_this3.tech_.seekable()); 64901 }, 64902 enumerable: true 64903 }, 64904 timestamp: { 64905 get: function get() { 64906 return Date.now(); 64907 }, 64908 enumerable: true 64909 }, 64910 videoPlaybackQuality: { 64911 get: function get() { 64912 return _this3.tech_.getVideoPlaybackQuality(); 64913 }, 64914 enumerable: true 64915 } 64916 }); 64917 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 64918 this.tech_.on('bandwidthupdate', function () { 64919 if (_this3.options_.useBandwidthFromLocalStorage) { 64920 updateVhsLocalStorage({ 64921 bandwidth: _this3.bandwidth, 64922 throughput: Math.round(_this3.throughput) 64923 }); 64924 } 64925 }); 64926 this.masterPlaylistController_.on('selectedinitialmedia', function () {
64927 // Add the manual rendition mix-in to VhsHandler 64928 renditionSelectionMixin(_this3); 64929 }); 64930 this.masterPlaylistController_.sourceUpdater_.on('createdsourcebuffers', function () { 64931 _this3.setupEme_(); 64932 }); // the bandwidth of the primary segment loader is our best 64933 // estimate of overall bandwidth 64934 64935 this.on(this.masterPlaylistController_, 'progress', function () { 64936 this.tech_.trigger('progress'); 64937 }); // In the live case, we need to ignore the very first `seeking` event since 64938 // that will be the result of the seek-to-live behavior 64939 64940 this.on(this.masterPlaylistController_, 'firstplay', function () { 64941 this.ignoreNextSeekingEvent_ = true; 64942 }); 64943 this.setupQualityLevels_(); // do nothing if the tech has been disposed already 64944 // this can occur if someone sets the src in player.ready(), for instance 64945 64946 if (!this.tech_.el()) { 64947 return; 64948 } 64949 64950 this.mediaSourceUrl_ = window_1.URL.createObjectURL(this.masterPlaylistController_.mediaSource); 64951 this.tech_.src(this.mediaSourceUrl_); 64952 } 64953 /** 64954 * If necessary and EME is available, sets up EME options and waits for key session 64955 * creation. 64956 * 64957 * This function also updates the source updater so taht it can be used, as for some 64958 * browsers, EME must be configured before content is appended (if appending unencrypted 64959 * content before encrypted content). 64960 */ 64961 ; 64962 64963 _proto.setupEme_ = function setupEme_() { 64964 var _this4 = this; 64965 64966 var audioPlaylistLoader = this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader; 64967 var didSetupEmeOptions = setupEmeOptions({ 64968 player: this.player_, 64969 sourceKeySystems: this.source_.keySystems, 64970 media: this.playlists.media(), 64971 audioMedia: audioPlaylistLoader && audioPlaylistLoader.media() 64972 }); 64973 this.player_.tech_.on('keystatuschange', function (e) { 64974 if (e.status === 'output-restricted') { 64975 _this4.masterPlaylistController_.blacklistCurrentPlaylist({ 64976 playlist: _this4.masterPlaylistController_.media(), 64977 message: "DRM keystatus changed to " + e.status + ". Playlist will fail to play. Check for HDCP content.", 64978 blacklistDuration: Infinity 64979 }); 64980 } 64981 }); // In IE11 this is too early to initialize media keys, and IE11 does not support 64982 // promises. 64983 64984 if (videojs.browser.IE_VERSION === 11 || !didSetupEmeOptions) { 64985 // If EME options were not set up, we've done all we could to initialize EME. 64986 this.masterPlaylistController_.sourceUpdater_.initializedEme(); 64987 return; 64988 } 64989 64990 this.logger_('waiting for EME key session creation'); 64991 waitForKeySessionCreation({ 64992 player: this.player_, 64993 sourceKeySystems: this.source_.keySystems, 64994 audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(), 64995 mainPlaylists: this.playlists.master.playlists 64996 }).then(function () { 64997 _this4.logger_('created EME key session'); 64998 64999 _this4.masterPlaylistController_.sourceUpdater_.initializedEme(); 65000 })["catch"](function (err) { 65001 _this4.logger_('error while creating EME key session', err); 65002 65003 _this4.player_.error({ 65004 message: 'Failed to initialize media keys for EME', 65005 code: 3 65006 }); 65007 }); 65008 } 65009 /** 65010 * Initializes the quality levels and sets listeners to update them. 65011 * 65012 * @method setupQualityLevels_ 65013 * @private 65014 */ 65015 ; 65016 65017 _proto.setupQualityLevels_ = function setupQualityLevels_() { 65018 var _this5 = this; 65019 65020 var player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin 65021 // or qualityLevels_ listeners have already been setup, do nothing. 65022 65023 if (!player || !player.qualityLevels || this.qualityLevels_) { 65024 return; 65025 } 65026 65027 this.qualityLevels_ = player.qualityLevels(); 65028 this.masterPlaylistController_.on('selectedinitialmedia', function () { 65029 handleVhsLoadedMetadata(_this5.qualityLevels_, _this5); 65030 }); 65031 this.playlists.on('mediachange', function () { 65032 handleVhsMediaChange(_this5.qualityLevels_, _this5.playlists); 65033 }); 65034 } 65035 /** 65036 * return the version 65037 */ 65038 ; 65039 65040 VhsHandler.version = function version$5() { 65041 return { 65042 '@videojs/http-streaming': version$4, 65043 'mux.js': version$3, 65044 'mpd-parser': version$2, 65045 'm3u8-parser': version$1, 65046 'aes-decrypter': version 65047 }; 65048 } 65049 /** 65050 * return the version 65051 */ 65052 ; 65053 65054 _proto.version = function version() { 65055 return this.constructor.version(); 65056 }; 65057 65058 _proto.canChangeType = function canChangeType() { 65059 return SourceUpdater.canChangeType(); 65060 } 65061 /** 65062 * Begin playing the video. 65063 */ 65064 ; 65065 65066 _proto.play = function play() { 65067 this.masterPlaylistController_.play(); 65068 } 65069 /** 65070 * a wrapper around the function in MasterPlaylistController 65071 */ 65072 ; 65073 65074 _proto.setCurrentTime = function setCurrentTime(currentTime) { 65075 this.masterPlaylistController_.setCurrentTime(currentTime); 65076 } 65077 /** 65078 * a wrapper around the function in MasterPlaylistController 65079 */ 65080 ; 65081 65082 _proto.duration = function duration() { 65083 return this.masterPlaylistController_.duration(); 65084 } 65085 /** 65086 * a wrapper around the function in MasterPlaylistController 65087 */ 65088 ; 65089 65090 _proto.seekable = function seekable() { 65091 return this.masterPlaylistController_.seekable(); 65092 } 65093 /** 65094 * Abort all outstanding work and cleanup. 65095 */ 65096 ; 65097 65098 _proto.dispose = function dispose() { 65099 if (this.playbackWatcher_) { 65100 this.playbackWatcher_.dispose(); 65101 } 65102 65103 if (this.masterPlaylistController_) { 65104 this.masterPlaylistController_.dispose(); 65105 } 65106
65107 if (this.qualityLevels_) { 65108 this.qualityLevels_.dispose(); 65109 } 65110 65111 if (this.player_) { 65112 delete this.player_.vhs; 65113 delete this.player_.dash; 65114 delete this.player_.hls; 65115 } 65116 65117 if (this.tech_ && this.tech_.vhs) { 65118 delete this.tech_.vhs; 65119 } // don't check this.tech_.hls as it will log a deprecated warning 65120 65121 65122 if (this.tech_) { 65123 delete this.tech_.hls; 65124 } 65125 65126 if (this.mediaSourceUrl_ && window_1.URL.revokeObjectURL) { 65127 window_1.URL.revokeObjectURL(this.mediaSourceUrl_); 65128 this.mediaSourceUrl_ = null; 65129 } 65130 65131 _Component.prototype.dispose.call(this); 65132 }; 65133 65134 _proto.convertToProgramTime = function convertToProgramTime(time, callback) { 65135 return getProgramTime({ 65136 playlist: this.masterPlaylistController_.media(), 65137 time: time, 65138 callback: callback 65139 }); 65140 } // the player must be playing before calling this 65141 ; 65142 65143 _proto.seekToProgramTime = function seekToProgramTime$1(programTime, callback, pauseAfterSeek, retryCount) { 65144 if (pauseAfterSeek === void 0) { 65145 pauseAfterSeek = true; 65146 } 65147 65148 if (retryCount === void 0) { 65149 retryCount = 2; 65150 } 65151 65152 return seekToProgramTime({ 65153 programTime: programTime, 65154 playlist: this.masterPlaylistController_.media(), 65155 retryCount: retryCount, 65156 pauseAfterSeek: pauseAfterSeek, 65157 seekTo: this.options_.seekTo, 65158 tech: this.options_.tech, 65159 callback: callback 65160 }); 65161 }; 65162 65163 return VhsHandler; 65164 }(Component); 65165 /** 65166 * The Source Handler object, which informs video.js what additional 65167 * MIME types are supported and sets up playback. It is registered 65168 * automatically to the appropriate tech based on the capabilities of 65169 * the browser it is running in. It is not necessary to use or modify 65170 * this object in normal usage. 65171 */ 65172 65173 65174 var VhsSourceHandler = { 65175 name: 'videojs-http-streaming', 65176 VERSION: version$4, 65177 canHandleSource: function canHandleSource(srcObj, options) { 65178 if (options === void 0) { 65179 options = {}; 65180 } 65181 65182 var localOptions = videojs.mergeOptions(videojs.options, options); 65183 return VhsSourceHandler.canPlayType(srcObj.type, localOptions); 65184 }, 65185 handleSource: function handleSource(source, tech, options) { 65186 if (options === void 0) { 65187 options = {}; 65188 } 65189 65190 var localOptions = videojs.mergeOptions(videojs.options, options); 65191 tech.vhs = new VhsHandler(source, tech, localOptions); 65192 65193 if (!videojs.hasOwnProperty('hls')) { 65194 Object.defineProperty(tech, 'hls', { 65195 get: function get() { 65196 videojs.log.warn('player.tech().hls is deprecated. Use player.tech().vhs instead.'); 65197 return tech.vhs; 65198 }, 65199 configurable: true 65200 }); 65201 } 65202 65203 tech.vhs.xhr = xhrFactory(); 65204 tech.vhs.src(source.src, source.type); 65205 return tech.vhs; 65206 }, 65207 canPlayType: function canPlayType(type, options) { 65208 if (options === void 0) { 65209 options = {}; 65210 } 65211 65212 var _videojs$mergeOptions = videojs.mergeOptions(videojs.options, options), 65213 _videojs$mergeOptions2 = _videojs$mergeOptions.vhs.overrideNative, 65214 overrideNative = _videojs$mergeOptions2 === void 0 ? !videojs.browser.IS_ANY_SAFARI : _videojs$mergeOptions2; 65215 65216 var supportedType = simpleTypeFromSourceType(type); 65217 var canUseMsePlayback = supportedType && (!Vhs.supportsTypeNatively(supportedType) || overrideNative); 65218 return canUseMsePlayback ? 'maybe' : ''; 65219 } 65220 }; 65221 /** 65222 * Check to see if the native MediaSource object exists and supports 65223 * an MP4 container with both H.264 video and AAC-LC audio. 65224 * 65225 * @return {boolean} if native media sources are supported 65226 */ 65227 65228 var supportsNativeMediaSources = function supportsNativeMediaSources() { 65229 return browserSupportsCodec('avc1.4d400d,mp4a.40.2'); 65230 }; // register source handlers with the appropriate techs 65231 65232 65233 if (supportsNativeMediaSources()) { 65234 videojs.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0); 65235 } 65236 65237 videojs.VhsHandler = VhsHandler; 65238 Object.defineProperty(videojs, 'HlsHandler', { 65239 get: function get() { 65240 videojs.log.warn('videojs.HlsHandler is deprecated. Use videojs.VhsHandler instead.'); 65241 return VhsHandler; 65242 }, 65243 configurable: true 65244 }); 65245 videojs.VhsSourceHandler = VhsSourceHandler; 65246 Object.defineProperty(videojs, 'HlsSourceHandler', { 65247 get: function get() { 65248 videojs.log.warn('videojs.HlsSourceHandler is deprecated. ' + 'Use videojs.VhsSourceHandler instead.'); 65249 return VhsSourceHandler; 65250 }, 65251 configurable: true 65252 }); 65253 videojs.Vhs = Vhs; 65254 Object.defineProperty(videojs, 'Hls', { 65255 get: function get() { 65256 videojs.log.warn('videojs.Hls is deprecated. Use videojs.Vhs instead.'); 65257 return Vhs; 65258 }, 65259 configurable: true 65260 }); 65261 65262 if (!videojs.use) { 65263 videojs.registerComponent('Hls', Vhs); 65264 videojs.registerComponent('Vhs', Vhs); 65265 } 65266 65267 videojs.options.vhs = videojs.options.vhs || {}; 65268 videojs.options.hls = videojs.options.hls || {}; 65269 65270 if (videojs.registerPlugin) { 65271 videojs.registerPlugin('reloadSourceOnError', reloadSourceOnError); 65272 } else { 65273 videojs.plugin('reloadSourceOnError', reloadSourceOnError); 65274 } 65275 65276 return videojs; 65277 65278})))};
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.