1/** 2 * @license 3 * Video.js 7.10.2 <http://videojs.com/> 4 * Copyright Brightcove, Inc. <https://www.brightcove.com/> 5 * Available under Apache License Version 2.0 6 * <https://github.com/videojs/video.js/blob/master/LICENSE> 7 * 8 * Includes vtt.js <https://github.com/mozilla/vtt.js> 9 * Available under Apache License Version 2.0 10 * <https://github.com/mozilla/vtt.js/blob/master/LICENSE> 11 */ 12 13(function (global, factory) { 14 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('global/window'), require('global/document')) : 15 typeof define === 'function' && define.amd ? define(['global/window', 'global/document'], factory) : 16 (global = global || self, global.videojs = factory(global.window, global.document)); 17}(this, (function (window$1, document) { 'use strict'; 18 19 window$1 = window$1 && Object.prototype.hasOwnProperty.call(window$1, 'default') ? window$1['default'] : window$1; 20 document = document && Object.prototype.hasOwnProperty.call(document, 'default') ? document['default'] : document; 21 22 var version = "7.10.2"; 23 24 /** 25 * @file create-logger.js 26 * @module create-logger 27 */ 28 29 var history = []; 30 /** 31 * Log messages to the console and history based on the type of message 32 * 33 * @private 34 * @param {string} type 35 * The name of the console method to use. 36 * 37 * @param {Array} args 38 * The arguments to be passed to the matching console method. 39 */ 40 41 var LogByTypeFactory = function LogByTypeFactory(name, log) { 42 return function (type, level, args) { 43 var lvl = log.levels[level]; 44 var lvlRegExp = new RegExp("^(" + lvl + ")$"); 45 46 if (type !== 'log') { 47 // Add the type to the front of the message when it's not "log". 48 args.unshift(type.toUpperCase() + ':'); 49 } // Add console prefix after adding to history. 50 51 52 args.unshift(name + ':'); // Add a clone of the args at this point to history. 53 54 if (history) { 55 history.push([].concat(args)); // only store 1000 history entries 56 57 var splice = history.length - 1000; 58 history.splice(0, splice > 0 ? splice : 0); 59 } // If there's no console then don't try to output messages, but they will 60 // still be stored in history. 61 62 63 if (!window$1.console) { 64 return; 65 } // Was setting these once outside of this function, but containing them 66 // in the function makes it easier to test cases where console doesn't exist 67 // when the module is executed. 68 69 70 var fn = window$1.console[type]; 71 72 if (!fn && type === 'debug') { 73 // Certain browsers don't have support for console.debug. For those, we 74 // should default to the closest comparable log. 75 fn = window$1.console.info || window$1.console.log; 76 } // Bail out if there's no console or if this type is not allowed by the 77 // current logging level. 78 79 80 if (!fn || !lvl || !lvlRegExp.test(type)) { 81 return; 82 } 83 84 fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args); 85 }; 86 }; 87 88 function createLogger(name) { 89 // This is the private tracking variable for logging level. 90 var level = 'info'; // the curried logByType bound to the specific log and history 91 92 var logByType; 93 /** 94 * Logs plain debug messages. Similar to `console.log`. 95 * 96 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 97 * of our JSDoc template, we cannot properly document this as both a function 98 * and a namespace, so its function signature is documented here. 99 * 100 * #### Arguments 101 * ##### *args 102 * Mixed[] 103 * 104 * Any combination of values that could be passed to `console.log()`. 105 * 106 * #### Return Value 107 * 108 * `undefined` 109 * 110 * @namespace 111 * @param {Mixed[]} args 112 * One or more messages or objects that should be logged. 113 */ 114 115 var log = function log() { 116 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 117 args[_key] = arguments[_key]; 118 } 119
vendor: 4,289 bytes, lines 120-271
120 logByType('log', level, args); 121 }; // This is the logByType helper that the logging methods below use 122 123 124 logByType = LogByTypeFactory(name, log); 125 /** 126 * Create a new sublogger which chains the old name to the new name. 127 * 128 * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following: 129 * ```js 130 * mylogger('foo'); 131 * // > VIDEOJS: player: foo 132 * ``` 133 * 134 * @param {string} name 135 * The name to add call the new logger 136 * @return {Object} 137 */ 138 139 log.createLogger = function (subname) { 140 return createLogger(name + ': ' + subname); 141 }; 142 /** 143 * Enumeration of available logging levels, where the keys are the level names 144 * and the values are `|`-separated strings containing logging methods allowed 145 * in that logging level. These strings are used to create a regular expression 146 * matching the function name being called. 147 * 148 * Levels provided by Video.js are: 149 * 150 * - `off`: Matches no calls. Any value that can be cast to `false` will have 151 * this effect. The most restrictive. 152 * - `all`: Matches only Video.js-provided functions (`debug`, `log`, 153 * `log.warn`, and `log.error`). 154 * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. 155 * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. 156 * - `warn`: Matches `log.warn` and `log.error` calls. 157 * - `error`: Matches only `log.error` calls. 158 * 159 * @type {Object} 160 */ 161 162 163 log.levels = { 164 all: 'debug|log|warn|error', 165 off: '', 166 debug: 'debug|log|warn|error', 167 info: 'log|warn|error', 168 warn: 'warn|error', 169 error: 'error', 170 DEFAULT: level 171 }; 172 /** 173 * Get or set the current logging level. 174 * 175 * If a string matching a key from {@link module:log.levels} is provided, acts 176 * as a setter. 177 * 178 * @param {string} [lvl] 179 * Pass a valid level to set a new logging level. 180 * 181 * @return {string} 182 * The current logging level. 183 */ 184 185 log.level = function (lvl) { 186 if (typeof lvl === 'string') { 187 if (!log.levels.hasOwnProperty(lvl)) { 188 throw new Error("\"" + lvl + "\" in not a valid log level"); 189 } 190 191 level = lvl; 192 } 193 194 return level; 195 }; 196 /** 197 * Returns an array containing everything that has been logged to the history. 198 * 199 * This array is a shallow clone of the internal history record. However, its 200 * contents are _not_ cloned; so, mutating objects inside this array will 201 * mutate them in history. 202 * 203 * @return {Array} 204 */ 205 206 207 log.history = function () { 208 return history ? [].concat(history) : []; 209 }; 210 /** 211 * Allows you to filter the history by the given logger name 212 * 213 * @param {string} fname 214 * The name to filter by 215 * 216 * @return {Array} 217 * The filtered list to return 218 */ 219 220 221 log.history.filter = function (fname) { 222 return (history || []).filter(function (historyItem) { 223 // if the first item in each historyItem includes `fname`, then it's a match 224 return new RegExp(".*" + fname + ".*").test(historyItem[0]); 225 }); 226 }; 227 /** 228 * Clears the internal history tracking, but does not prevent further history 229 * tracking. 230 */ 231 232 233 log.history.clear = function () { 234 if (history) { 235 history.length = 0; 236 } 237 }; 238 /** 239 * Disable history tracking if it is currently enabled. 240 */ 241 242 243 log.history.disable = function () { 244 if (history !== null) { 245 history.length = 0; 246 history = null; 247 } 248 }; 249 /** 250 * Enable history tracking if it is currently disabled. 251 */ 252 253 254 log.history.enable = function () { 255 if (history === null) { 256 history = []; 257 } 258 }; 259 /** 260 * Logs error messages. Similar to `console.error`. 261 * 262 * @param {Mixed[]} args 263 * One or more messages or objects that should be logged as an error 264 */ 265 266 267 log.error = function () { 268 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 269 args[_key2] = arguments[_key2]; 270 } 271
272 return logByType('error', level, args); 273 }; 274 /** 275 * Logs warning messages. Similar to `console.warn`. 276 * 277 * @param {Mixed[]} args 278 * One or more messages or objects that should be logged as a warning. 279 */ 280 281 282 log.warn = function () { 283 for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 284 args[_key3] = arguments[_key3]; 285 } 286 287 return logByType('warn', level, args); 288 }; 289 /** 290 * Logs debug messages. Similar to `console.debug`, but may also act as a comparable 291 * log if `console.debug` is not available 292 * 293 * @param {Mixed[]} args 294 * One or more messages or objects that should be logged as debug. 295 */ 296 297 298 log.debug = function () { 299 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 300 args[_key4] = arguments[_key4]; 301 } 302 303 return logByType('debug', level, args); 304 }; 305 306 return log; 307 } 308 309 /** 310 * @file log.js 311 * @module log 312 */ 313 var log = createLogger('VIDEOJS'); 314 var createLogger$1 = log.createLogger; 315 316 function unwrapExports (x) { 317 return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; 318 } 319 320 function createCommonjsModule(fn, module) { 321 return module = { exports: {} }, fn(module, module.exports), module.exports; 322 } 323 324 var _extends_1 = createCommonjsModule(function (module) { 325 function _extends() { 326 module.exports = _extends = Object.assign || function (target) { 327 for (var i = 1; i < arguments.length; i++) { 328 var source = arguments[i]; 329 330 for (var key in source) { 331 if (Object.prototype.hasOwnProperty.call(source, key)) { 332 target[key] = source[key]; 333 } 334 } 335 } 336 337 return target; 338 }; 339 340 return _extends.apply(this, arguments); 341 } 342 343 module.exports = _extends; 344 }); 345 346 /** 347 * @file obj.js 348 * @module obj 349 */ 350 351 /** 352 * @callback obj:EachCallback 353 * 354 * @param {Mixed} value 355 * The current key for the object that is being iterated over. 356 * 357 * @param {string} key 358 * The current key-value for object that is being iterated over 359 */ 360 361 /** 362 * @callback obj:ReduceCallback 363 * 364 * @param {Mixed} accum 365 * The value that is accumulating over the reduce loop. 366 * 367 * @param {Mixed} value 368 * The current key for the object that is being iterated over. 369 * 370 * @param {string} key 371 * The current key-value for object that is being iterated over 372 * 373 * @return {Mixed} 374 * The new accumulated value. 375 */ 376 var toString = Object.prototype.toString; 377 /** 378 * Get the keys of an Object 379 * 380 * @param {Object} 381 * The Object to get the keys from 382 * 383 * @return {string[]} 384 * An array of the keys from the object. Returns an empty array if the 385 * object passed in was invalid or had no keys. 386 * 387 * @private 388 */ 389 390 var keys = function keys(object) { 391 return isObject(object) ? Object.keys(object) : []; 392 }; 393 /** 394 * Array-like iteration for objects. 395 * 396 * @param {Object} object 397 * The object to iterate over 398 * 399 * @param {obj:EachCallback} fn 400 * The callback function which is called for each key in the object. 401 */ 402 403 404 function each(object, fn) { 405 keys(object).forEach(function (key) { 406 return fn(object[key], key); 407 }); 408 } 409 /** 410 * Array-like reduce for objects. 411 * 412 * @param {Object} object 413 * The Object that you want to reduce. 414 * 415 * @param {Function} fn 416 * A callback function which is called for each key in the object. It 417 * receives the accumulated value and the per-iteration value and key 418 * as arguments. 419 * 420 * @param {Mixed} [initial = 0] 421 * Starting value 422 * 423 * @return {Mixed} 424 * The final accumulated value. 425 */ 426 427 function reduce(object, fn, initial) { 428 if (initial === void 0) { 429 initial = 0; 430 } 431 432 return keys(object).reduce(function (accum, key) { 433 return fn(accum, object[key], key); 434 }, initial); 435 } 436 /** 437 * Object.assign-style object shallow merge/extend. 438 * 439 * @param {Object} target 440 * @param {Object} ...sources 441 * @return {Object} 442 */ 443 444 function assign(target) { 445 for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 446 sources[_key - 1] = arguments[_key]; 447 } 448 449 if (Object.assign) { 450 return _extends_1.apply(void 0, [target].concat(sources)); 451 } 452
vendor: 15,680 bytes, lines 453-1012
453 sources.forEach(function (source) { 454 if (!source) { 455 return; 456 } 457 458 each(source, function (value, key) { 459 target[key] = value; 460 }); 461 }); 462 return target; 463 } 464 /** 465 * Returns whether a value is an object of any kind - including DOM nodes, 466 * arrays, regular expressions, etc. Not functions, though. 467 * 468 * This avoids the gotcha where using `typeof` on a `null` value 469 * results in `'object'`. 470 * 471 * @param {Object} value 472 * @return {boolean} 473 */ 474 475 function isObject(value) { 476 return !!value && typeof value === 'object'; 477 } 478 /** 479 * Returns whether an object appears to be a "plain" object - that is, a 480 * direct instance of `Object`. 481 * 482 * @param {Object} value 483 * @return {boolean} 484 */ 485 486 function isPlain(value) { 487 return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object; 488 } 489 490 /** 491 * @file computed-style.js 492 * @module computed-style 493 */ 494 /** 495 * A safe getComputedStyle. 496 * 497 * This is needed because in Firefox, if the player is loaded in an iframe with 498 * `display:none`, then `getComputedStyle` returns `null`, so, we do a 499 * null-check to make sure that the player doesn't break in these cases. 500 * 501 * @function 502 * @param {Element} el 503 * The element you want the computed style of 504 * 505 * @param {string} prop 506 * The property name you want 507 * 508 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 509 */ 510 511 function computedStyle(el, prop) { 512 if (!el || !prop) { 513 return ''; 514 } 515 516 if (typeof window$1.getComputedStyle === 'function') { 517 var computedStyleValue = window$1.getComputedStyle(el); 518 return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : ''; 519 } 520 521 return ''; 522 } 523 524 /** 525 * @file dom.js 526 * @module dom 527 */ 528 /** 529 * Detect if a value is a string with any non-whitespace characters. 530 * 531 * @private 532 * @param {string} str 533 * The string to check 534 * 535 * @return {boolean} 536 * Will be `true` if the string is non-blank, `false` otherwise. 537 * 538 */ 539 540 function isNonBlankString(str) { 541 // we use str.trim as it will trim any whitespace characters 542 // from the front or back of non-whitespace characters. aka 543 // Any string that contains non-whitespace characters will 544 // still contain them after `trim` but whitespace only strings 545 // will have a length of 0, failing this check. 546 return typeof str === 'string' && Boolean(str.trim()); 547 } 548 /** 549 * Throws an error if the passed string has whitespace. This is used by 550 * class methods to be relatively consistent with the classList API. 551 * 552 * @private 553 * @param {string} str 554 * The string to check for whitespace. 555 * 556 * @throws {Error} 557 * Throws an error if there is whitespace in the string. 558 */ 559 560 561 function throwIfWhitespace(str) { 562 // str.indexOf instead of regex because str.indexOf is faster performance wise. 563 if (str.indexOf(' ') >= 0) { 564 throw new Error('class has illegal whitespace characters'); 565 } 566 } 567 /** 568 * Produce a regular expression for matching a className within an elements className. 569 * 570 * @private 571 * @param {string} className 572 * The className to generate the RegExp for. 573 * 574 * @return {RegExp} 575 * The RegExp that will check for a specific `className` in an elements 576 * className. 577 */ 578 579 580 function classRegExp(className) { 581 return new RegExp('(^|\\s)' + className + '($|\\s)'); 582 } 583 /** 584 * Whether the current DOM interface appears to be real (i.e. not simulated). 585 * 586 * @return {boolean} 587 * Will be `true` if the DOM appears to be real, `false` otherwise. 588 */ 589 590 591 function isReal() { 592 // Both document and window will never be undefined thanks to `global`. 593 return document === window$1.document; 594 } 595 /** 596 * Determines, via duck typing, whether or not a value is a DOM element. 597 * 598 * @param {Mixed} value 599 * The value to check. 600 * 601 * @return {boolean} 602 * Will be `true` if the value is a DOM element, `false` otherwise. 603 */ 604 605 function isEl(value) { 606 return isObject(value) && value.nodeType === 1; 607 } 608 /** 609 * Determines if the current DOM is embedded in an iframe. 610 * 611 * @return {boolean} 612 * Will be `true` if the DOM is embedded in an iframe, `false` 613 * otherwise. 614 */ 615 616 function isInFrame() { 617 // We need a try/catch here because Safari will throw errors when attempting 618 // to get either `parent` or `self` 619 try { 620 return window$1.parent !== window$1.self; 621 } catch (x) { 622 return true; 623 } 624 } 625 /** 626 * Creates functions to query the DOM using a given method. 627 * 628 * @private 629 * @param {string} method 630 * The method to create the query with. 631 * 632 * @return {Function} 633 * The query method 634 */ 635 636 function createQuerier(method) { 637 return function (selector, context) { 638 if (!isNonBlankString(selector)) { 639 return document[method](null); 640 } 641 642 if (isNonBlankString(context)) { 643 context = document.querySelector(context); 644 } 645 646 var ctx = isEl(context) ? context : document; 647 return ctx[method] && ctx[method](selector); 648 }; 649 } 650 /** 651 * Creates an element and applies properties, attributes, and inserts content. 652 * 653 * @param {string} [tagName='div'] 654 * Name of tag to be created. 655 * 656 * @param {Object} [properties={}] 657 * Element properties to be applied. 658 * 659 * @param {Object} [attributes={}] 660 * Element attributes to be applied. 661 * 662 * @param {module:dom~ContentDescriptor} content 663 * A content descriptor object. 664 * 665 * @return {Element} 666 * The element that was created. 667 */ 668 669 670 function createEl(tagName, properties, attributes, content) { 671 if (tagName === void 0) { 672 tagName = 'div'; 673 } 674 675 if (properties === void 0) { 676 properties = {}; 677 } 678 679 if (attributes === void 0) { 680 attributes = {}; 681 } 682 683 var el = document.createElement(tagName); 684 Object.getOwnPropertyNames(properties).forEach(function (propName) { 685 var val = properties[propName]; // See #2176 686 // We originally were accepting both properties and attributes in the 687 // same object, but that doesn't work so well. 688 689 if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { 690 log.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 + ".")); 691 el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a 692 // method for it. 693 } else if (propName === 'textContent') { 694 textContent(el, val); 695 } else if (el[propName] !== val) { 696 el[propName] = val; 697 } 698 }); 699 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 700 el.setAttribute(attrName, attributes[attrName]); 701 }); 702 703 if (content) { 704 appendContent(el, content); 705 } 706 707 return el; 708 } 709 /** 710 * Injects text into an element, replacing any existing contents entirely. 711 * 712 * @param {Element} el 713 * The element to add text content into 714 * 715 * @param {string} text 716 * The text content to add. 717 * 718 * @return {Element} 719 * The element with added text content. 720 */ 721 722 function textContent(el, text) { 723 if (typeof el.textContent === 'undefined') { 724 el.innerText = text; 725 } else { 726 el.textContent = text; 727 } 728 729 return el; 730 } 731 /** 732 * Insert an element as the first child node of another 733 * 734 * @param {Element} child 735 * Element to insert 736 * 737 * @param {Element} parent 738 * Element to insert child into 739 */ 740 741 function prependTo(child, parent) { 742 if (parent.firstChild) { 743 parent.insertBefore(child, parent.firstChild); 744 } else { 745 parent.appendChild(child); 746 } 747 } 748 /** 749 * Check if an element has a class name. 750 * 751 * @param {Element} element 752 * Element to check 753 * 754 * @param {string} classToCheck 755 * Class name to check for 756 * 757 * @return {boolean} 758 * Will be `true` if the element has a class, `false` otherwise. 759 * 760 * @throws {Error} 761 * Throws an error if `classToCheck` has white space. 762 */ 763 764 function hasClass(element, classToCheck) { 765 throwIfWhitespace(classToCheck); 766 767 if (element.classList) { 768 return element.classList.contains(classToCheck); 769 } 770 771 return classRegExp(classToCheck).test(element.className); 772 } 773 /** 774 * Add a class name to an element. 775 * 776 * @param {Element} element 777 * Element to add class name to. 778 * 779 * @param {string} classToAdd 780 * Class name to add. 781 * 782 * @return {Element} 783 * The DOM element with the added class name. 784 */ 785 786 function addClass(element, classToAdd) { 787 if (element.classList) { 788 element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it 789 // in the case of classList not being supported. 790 } else if (!hasClass(element, classToAdd)) { 791 element.className = (element.className + ' ' + classToAdd).trim(); 792 } 793 794 return element; 795 } 796 /** 797 * Remove a class name from an element. 798 * 799 * @param {Element} element 800 * Element to remove a class name from. 801 * 802 * @param {string} classToRemove 803 * Class name to remove 804 * 805 * @return {Element} 806 * The DOM element with class name removed. 807 */ 808 809 function removeClass(element, classToRemove) { 810 if (element.classList) { 811 element.classList.remove(classToRemove); 812 } else { 813 throwIfWhitespace(classToRemove); 814 element.className = element.className.split(/\s+/).filter(function (c) { 815 return c !== classToRemove; 816 }).join(' '); 817 } 818 819 return element; 820 } 821 /** 822 * The callback definition for toggleClass. 823 * 824 * @callback module:dom~PredicateCallback 825 * @param {Element} element 826 * The DOM element of the Component. 827 * 828 * @param {string} classToToggle 829 * The `className` that wants to be toggled 830 * 831 * @return {boolean|undefined} 832 * If `true` is returned, the `classToToggle` will be added to the 833 * `element`. If `false`, the `classToToggle` will be removed from 834 * the `element`. If `undefined`, the callback will be ignored. 835 */ 836 837 /** 838 * Adds or removes a class name to/from an element depending on an optional 839 * condition or the presence/absence of the class name. 840 * 841 * @param {Element} element 842 * The element to toggle a class name on. 843 * 844 * @param {string} classToToggle 845 * The class that should be toggled. 846 * 847 * @param {boolean|module:dom~PredicateCallback} [predicate] 848 * See the return value for {@link module:dom~PredicateCallback} 849 * 850 * @return {Element} 851 * The element with a class that has been toggled. 852 */ 853 854 function toggleClass(element, classToToggle, predicate) { 855 // This CANNOT use `classList` internally because IE11 does not support the 856 // second parameter to the `classList.toggle()` method! Which is fine because 857 // `classList` will be used by the add/remove functions. 858 var has = hasClass(element, classToToggle); 859 860 if (typeof predicate === 'function') { 861 predicate = predicate(element, classToToggle); 862 } 863 864 if (typeof predicate !== 'boolean') { 865 predicate = !has; 866 } // If the necessary class operation matches the current state of the 867 // element, no action is required. 868 869 870 if (predicate === has) { 871 return; 872 } 873 874 if (predicate) { 875 addClass(element, classToToggle); 876 } else { 877 removeClass(element, classToToggle); 878 } 879 880 return element; 881 } 882 /** 883 * Apply attributes to an HTML element. 884 * 885 * @param {Element} el 886 * Element to add attributes to. 887 * 888 * @param {Object} [attributes] 889 * Attributes to be applied. 890 */ 891 892 function setAttributes(el, attributes) { 893 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 894 var attrValue = attributes[attrName]; 895 896 if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { 897 el.removeAttribute(attrName); 898 } else { 899 el.setAttribute(attrName, attrValue === true ? '' : attrValue); 900 } 901 }); 902 } 903 /** 904 * Get an element's attribute values, as defined on the HTML tag. 905 * 906 * Attributes are not the same as properties. They're defined on the tag 907 * or with setAttribute. 908 * 909 * @param {Element} tag 910 * Element from which to get tag attributes. 911 * 912 * @return {Object} 913 * All attributes of the element. Boolean attributes will be `true` or 914 * `false`, others will be strings. 915 */ 916 917 function getAttributes(tag) { 918 var obj = {}; // known boolean attributes 919 // we can check for matching boolean properties, but not all browsers 920 // and not all tags know about these attributes, so, we still want to check them manually 921 922 var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; 923 924 if (tag && tag.attributes && tag.attributes.length > 0) { 925 var attrs = tag.attributes; 926 927 for (var i = attrs.length - 1; i >= 0; i--) { 928 var attrName = attrs[i].name; 929 var attrVal = attrs[i].value; // check for known booleans 930 // the matching element property will return a value for typeof 931 932 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { 933 // the value of an included boolean attribute is typically an empty 934 // string ('') which would equal false if we just check for a false value. 935 // we also don't want support bad code like autoplay='false' 936 attrVal = attrVal !== null ? true : false; 937 } 938 939 obj[attrName] = attrVal; 940 } 941 } 942 943 return obj; 944 } 945 /** 946 * Get the value of an element's attribute. 947 * 948 * @param {Element} el 949 * A DOM element. 950 * 951 * @param {string} attribute 952 * Attribute to get the value of. 953 * 954 * @return {string} 955 * The value of the attribute. 956 */ 957 958 function getAttribute(el, attribute) { 959 return el.getAttribute(attribute); 960 } 961 /** 962 * Set the value of an element's attribute. 963 * 964 * @param {Element} el 965 * A DOM element. 966 * 967 * @param {string} attribute 968 * Attribute to set. 969 * 970 * @param {string} value 971 * Value to set the attribute to. 972 */ 973 974 function setAttribute(el, attribute, value) { 975 el.setAttribute(attribute, value); 976 } 977 /** 978 * Remove an element's attribute. 979 * 980 * @param {Element} el 981 * A DOM element. 982 * 983 * @param {string} attribute 984 * Attribute to remove. 985 */ 986 987 function removeAttribute(el, attribute) { 988 el.removeAttribute(attribute); 989 } 990 /** 991 * Attempt to block the ability to select text. 992 */ 993 994 function blockTextSelection() { 995 document.body.focus(); 996 997 document.onselectstart = function () { 998 return false; 999 }; 1000 } 1001 /** 1002 * Turn off text selection blocking. 1003 */ 1004 1005 function unblockTextSelection() { 1006 document.onselectstart = function () { 1007 return true; 1008 }; 1009 } 1010 /** 1011 * Identical to the native `getBoundingClientRect` function, but ensures that 1012 * the method is supported at all (it is in all browsers we claim to supp
1012ort) 1013 * and that the element is in the DOM before continuing. 1014 * 1015 * This wrapper function also shims properties which are not provided by some 1016 * older browsers (namely, IE8). 1017 * 1018 * Additionally, some browsers do not support adding properties to a 1019 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard 1020 * properties (except `x` and `y` which are not widely supported). This helps 1021 * avoid implementations where keys are non-enumerable. 1022 * 1023 * @param {Element} el 1024 * Element whose `ClientRect` we want to calculate. 1025 * 1026 * @return {Object|undefined} 1027 * Always returns a plain object - or `undefined` if it cannot. 1028 */ 1029 1030 function getBoundingClientRect(el) { 1031 if (el && el.getBoundingClientRect && el.parentNode) { 1032 var rect = el.getBoundingClientRect(); 1033 var result = {}; 1034 ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { 1035 if (rect[k] !== undefined) { 1036 result[k] = rect[k]; 1037 } 1038 }); 1039 1040 if (!result.height) { 1041 result.height = parseFloat(computedStyle(el, 'height')); 1042 } 1043 1044 if (!result.width) { 1045 result.width = parseFloat(computedStyle(el, 'width')); 1046 } 1047 1048 return result; 1049 } 1050 } 1051 /** 1052 * Represents the position of a DOM element on the page. 1053 * 1054 * @typedef {Object} module:dom~Position 1055 * 1056 * @property {number} left 1057 * Pixels to the left. 1058 * 1059 * @property {number} top 1060 * Pixels from the top. 1061 */ 1062 1063 /** 1064 * Get the position of an element in the DOM. 1065 * 1066 * Uses `getBoundingClientRect` technique from John Resig. 1067 * 1068 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ 1069 * 1070 * @param {Element} el 1071 * Element from which to get offset. 1072 * 1073 * @return {module:dom~Position} 1074 * The position of the element that was passed in. 1075 */ 1076 1077 function findPosition(el) { 1078 if (!el || el && !el.offsetParent) { 1079 return { 1080 left: 0, 1081 top: 0, 1082 width: 0, 1083 height: 0 1084 }; 1085 } 1086 1087 var width = el.offsetWidth; 1088 var height = el.offsetHeight; 1089 var left = 0; 1090 var top = 0; 1091 1092 do { 1093 left += el.offsetLeft; 1094 top += el.offsetTop; 1095 el = el.offsetParent; 1096 } while (el); 1097 1098 return { 1099 left: left, 1100 top: top, 1101 width: width, 1102 height: height 1103 }; 1104 } 1105 /** 1106 * Represents x and y coordinates for a DOM element or mouse pointer. 1107 * 1108 * @typedef {Object} module:dom~Coordinates 1109 * 1110 * @property {number} x 1111 * x coordinate in pixels 1112 * 1113 * @property {number} y 1114 * y coordinate in pixels 1115 */ 1116 1117 /** 1118 * Get the pointer position within an element. 1119 * 1120 * The base on the coordinates are the bottom left of the element. 1121 * 1122 * @param {Element} el 1123 * Element on which to get the pointer position on. 1124 * 1125 * @param {EventTarget~Event} event 1126 * Event object. 1127 * 1128 * @return {module:dom~Coordinates} 1129 * A coordinates object corresponding to the mouse position. 1130 * 1131 */ 1132 1133 function getPointerPosition(el, event) { 1134 var position = {}; 1135 var boxTarget = findPosition(event.target); 1136 var box = findPosition(el); 1137 var boxW = box.width; 1138 var boxH = box.height; 1139 var offsetY = event.offsetY - (box.top - boxTarget.top); 1140 var offsetX = event.offsetX - (box.left - boxTarget.left); 1141 1142 if (event.changedTouches) { 1143 offsetX = event.changedTouches[0].pageX - box.left; 1144 offsetY = event.changedTouches[0].pageY + box.top; 1145 } 1146 1147 position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH)); 1148 position.x = Math.max(0, Math.min(1, offsetX / boxW)); 1149 return position; 1150 } 1151 /** 1152 * Determines, via duck typing, whether or not a value is a text node. 1153 * 1154 * @param {Mixed} value 1155 * Check if this value is a text node. 1156 * 1157 * @return {boolean} 1158 * Will be `true` if the value is a text node, `false` otherwise. 1159 */ 1160 1161 function isTextNode(value) { 1162 return isObject(value) && value.nodeType === 3; 1163 } 1164 /** 1165 * Empties the contents of an element. 1166 * 1167 * @param {Element} el 1168 * The element to empty children from 1169 * 1170 * @return {Element} 1171 * The element with no children 1172 */ 1173 1174 function emptyEl(el) { 1175 while (el.firstChild) { 1176 el.removeChild(el.firstChild); 1177 } 1178 1179 return el; 1180 } 1181 /** 1182 * This is a mixed value that describes content to be injected into the DOM 1183 * via some method. It can be of the following types: 1184 * 1185 * Type | Description 1186 * -----------|------------- 1187 * `string` | The value will be normalized into a text node. 1188 * `Element` | The value will be accepted as-is. 1189 * `TextNode` | The value will be accepted as-is.
1190 * `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). 1191 * `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. 1192 * 1193 * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor 1194 */ 1195 1196 /** 1197 * Normalizes content for eventual insertion into the DOM. 1198 * 1199 * This allows a wide range of content definition methods, but helps protect 1200 * from falling into the trap of simply writing to `innerHTML`, which could 1201 * be an XSS concern. 1202 * 1203 * The content for an element can be passed in multiple types and 1204 * combinations, whose behavior is as follows: 1205 * 1206 * @param {module:dom~ContentDescriptor} content 1207 * A content descriptor value. 1208 * 1209 * @return {Array} 1210 * All of the content that was passed in, normalized to an array of 1211 * elements or text nodes. 1212 */ 1213 1214 function normalizeContent(content) { 1215 // First, invoke content if it is a function. If it produces an array, 1216 // that needs to happen before normalization. 1217 if (typeof content === 'function') { 1218 content = content(); 1219 } // Next up, normalize to an array, so one or many items can be normalized, 1220 // filtered, and returned. 1221 1222 1223 return (Array.isArray(content) ? content : [content]).map(function (value) { 1224 // First, invoke value if it is a function to produce a new value, 1225 // which will be subsequently normalized to a Node of some kind. 1226 if (typeof value === 'function') { 1227 value = value(); 1228 } 1229 1230 if (isEl(value) || isTextNode(value)) { 1231 return value; 1232 } 1233 1234 if (typeof value === 'string' && /\S/.test(value)) { 1235 return document.createTextNode(value); 1236 } 1237 }).filter(function (value) { 1238 return value; 1239 }); 1240 } 1241 /** 1242 * Normalizes and appends content to an element. 1243 * 1244 * @param {Element} el 1245 * Element to append normalized content to. 1246 * 1247 * @param {module:dom~ContentDescriptor} content 1248 * A content descriptor value. 1249 * 1250 * @return {Element} 1251 * The element with appended normalized content. 1252 */ 1253 1254 function appendContent(el, content) { 1255 normalizeContent(content).forEach(function (node) { 1256 return el.appendChild(node); 1257 }); 1258 return el; 1259 } 1260 /** 1261 * Normalizes and inserts content into an element; this is identical to 1262 * `appendContent()`, except it empties the element first. 1263 * 1264 * @param {Element} el 1265 * Element to insert normalized content into. 1266 * 1267 * @param {module:dom~ContentDescriptor} content 1268 * A content descriptor value. 1269 * 1270 * @return {Element} 1271 * The element with inserted normalized content. 1272 */ 1273 1274 function insertContent(el, content) { 1275 return appendContent(emptyEl(el), content); 1276 } 1277 /** 1278 * Check if an event was a single left click. 1279 * 1280 * @param {EventTarget~Event} event 1281 * Event object. 1282 * 1283 * @return {boolean} 1284 * Will be `true` if a single left click, `false` otherwise. 1285 */ 1286 1287 function isSingleLeftClick(event) { 1288 // Note: if you create something draggable, be sure to 1289 // call it on both `mousedown` and `mousemove` event, 1290 // otherwise `mousedown` should be enough for a button 1291 if (event.button === undefined && event.buttons === undefined) { 1292 // Why do we need `buttons` ? 1293 // Because, middle mouse sometimes have this: 1294 // e.button === 0 and e.buttons === 4 1295 // Furthermore, we want to prevent combination click, something like 1296 // HOLD middlemouse then left click, that would be 1297 // e.button === 0, e.buttons === 5 1298 // just `button` is not gonna work 1299 // Alright, then what this block does ? 1300 // this is for chrome `simulate mobile devices`
vendor: 36,459 bytes, lines 1301-2568
1301 // I want to support this as well 1302 return true; 1303 } 1304 1305 if (event.button === 0 && event.buttons === undefined) { 1306 // Touch screen, sometimes on some specific device, `buttons` 1307 // doesn't have anything (safari on ios, blackberry...) 1308 return true; 1309 } // `mouseup` event on a single left click has 1310 // `button` and `buttons` equal to 0 1311 1312 1313 if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) { 1314 return true; 1315 } 1316 1317 if (event.button !== 0 || event.buttons !== 1) { 1318 // This is the reason we have those if else block above 1319 // if any special case we can catch and let it slide 1320 // we do it above, when get to here, this definitely 1321 // is-not-left-click 1322 return false; 1323 } 1324 1325 return true; 1326 } 1327 /** 1328 * Finds a single DOM element matching `selector` within the optional 1329 * `context` of another DOM element (defaulting to `document`). 1330 * 1331 * @param {string} selector 1332 * A valid CSS selector, which will be passed to `querySelector`. 1333 * 1334 * @param {Element|String} [context=document] 1335 * A DOM element within which to query. Can also be a selector 1336 * string in which case the first matching element will be used 1337 * as context. If missing (or no element matches selector), falls 1338 * back to `document`. 1339 * 1340 * @return {Element|null} 1341 * The element that was found or null. 1342 */ 1343 1344 var $ = createQuerier('querySelector'); 1345 /** 1346 * Finds a all DOM elements matching `selector` within the optional 1347 * `context` of another DOM element (defaulting to `document`). 1348 * 1349 * @param {string} selector 1350 * A valid CSS selector, which will be passed to `querySelectorAll`. 1351 * 1352 * @param {Element|String} [context=document] 1353 * A DOM element within which to query. Can also be a selector 1354 * string in which case the first matching element will be used 1355 * as context. If missing (or no element matches selector), falls 1356 * back to `document`. 1357 * 1358 * @return {NodeList} 1359 * A element list of elements that were found. Will be empty if none 1360 * were found. 1361 * 1362 */ 1363 1364 var $$ = createQuerier('querySelectorAll'); 1365 1366 var Dom = /*#__PURE__*/Object.freeze({ 1367 __proto__: null, 1368 isReal: isReal, 1369 isEl: isEl, 1370 isInFrame: isInFrame, 1371 createEl: createEl, 1372 textContent: textContent, 1373 prependTo: prependTo, 1374 hasClass: hasClass, 1375 addClass: addClass, 1376 removeClass: removeClass, 1377 toggleClass: toggleClass, 1378 setAttributes: setAttributes, 1379 getAttributes: getAttributes, 1380 getAttribute: getAttribute, 1381 setAttribute: setAttribute, 1382 removeAttribute: removeAttribute, 1383 blockTextSelection: blockTextSelection, 1384 unblockTextSelection: unblockTextSelection, 1385 getBoundingClientRect: getBoundingClientRect, 1386 findPosition: findPosition, 1387 getPointerPosition: getPointerPosition, 1388 isTextNode: isTextNode, 1389 emptyEl: emptyEl, 1390 normalizeContent: normalizeContent, 1391 appendContent: appendContent, 1392 insertContent: insertContent, 1393 isSingleLeftClick: isSingleLeftClick, 1394 $: $, 1395 $$: $$ 1396 }); 1397 1398 /** 1399 * @file setup.js - Functions for setting up a player without 1400 * user interaction based on the data-setup `attribute` of the video tag. 1401 * 1402 * @module setup 1403 */ 1404 var _windowLoaded = false; 1405 var videojs; 1406 /** 1407 * Set up any tags that have a data-setup `attribute` when the player is started. 1408 */ 1409 1410 var autoSetup = function autoSetup() { 1411 // Protect against breakage in non-browser environments and check global autoSetup option. 1412 if (!isReal() || videojs.options.autoSetup === false) { 1413 return; 1414 } 1415 1416 var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); 1417 var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); 1418 var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js')); 1419 var mediaEls = vids.concat(audios, divs); // Check if any media elements exist 1420 1421 if (mediaEls && mediaEls.length > 0) { 1422 for (var i = 0, e = mediaEls.length; i < e; i++) { 1423 var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. 1424 1425 if (mediaEl && mediaEl.getAttribute) { 1426 // Make sure this player hasn't already been set up. 1427 if (mediaEl.player === undefined) { 1428 var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. 1429 // We only auto-setup if they've added the data-setup attr. 1430 1431 if (options !== null) { 1432 // Create new video.js instance. 1433 videojs(mediaEl); 1434 } 1435 } // If getAttribute isn't defined, we need to wait for the DOM. 1436 1437 } else { 1438 autoSetupTimeout(1); 1439 break; 1440 } 1441 } // No videos were found, so keep looping unless page is finished loading. 1442 1443 } else if (!_windowLoaded) { 1444 autoSetupTimeout(1); 1445 } 1446 }; 1447 /** 1448 * Wait until the page is loaded before running autoSetup. This will be called in 1449 * autoSetup if `hasLoaded` returns false. 1450 * 1451 * @param {number} wait 1452 * How long to wait in ms 1453 * 1454 * @param {module:videojs} [vjs] 1455 * The videojs library function 1456 */ 1457 1458 1459 function autoSetupTimeout(wait, vjs) { 1460 if (vjs) { 1461 videojs = vjs; 1462 } 1463 1464 window$1.setTimeout(autoSetup, wait); 1465 } 1466 /** 1467 * Used to set the internal tracking of window loaded state to true. 1468 * 1469 * @private 1470 */ 1471 1472 1473 function setWindowLoaded() { 1474 _windowLoaded = true; 1475 window$1.removeEventListener('load', setWindowLoaded); 1476 } 1477 1478 if (isReal()) { 1479 if (document.readyState === 'complete') { 1480 setWindowLoaded(); 1481 } else { 1482 /** 1483 * Listen for the load event on window, and set _windowLoaded to true. 1484 * 1485 * We use a standard event listener here to avoid incrementing the GUID 1486 * before any players are created. 1487 * 1488 * @listens load 1489 */ 1490 window$1.addEventListener('load', setWindowLoaded); 1491 } 1492 } 1493 1494 /** 1495 * @file stylesheet.js 1496 * @module stylesheet 1497 */ 1498 /** 1499 * Create a DOM syle element given a className for it. 1500 * 1501 * @param {string} className 1502 * The className to add to the created style element. 1503 * 1504 * @return {Element} 1505 * The element that was created. 1506 */ 1507 1508 var createStyleElement = function createStyleElement(className) { 1509 var style = document.createElement('style'); 1510 style.className = className; 1511 return style; 1512 }; 1513 /** 1514 * Add text to a DOM element. 1515 * 1516 * @param {Element} el 1517 * The Element to add text content to. 1518 * 1519 * @param {string} content 1520 * The text to add to the element. 1521 */ 1522 1523 var setTextContent = function setTextContent(el, content) { 1524 if (el.styleSheet) { 1525 el.styleSheet.cssText = content; 1526 } else { 1527 el.textContent = content; 1528 } 1529 }; 1530 1531 /** 1532 * @file guid.js 1533 * @module guid 1534 */ 1535 // Default value for GUIDs. This allows us to reset the GUID counter in tests. 1536 // 1537 // The initial GUID is 3 because some users have come to rely on the first 1538 // default player ID ending up as `vjs_video_3`. 1539 // 1540 // See: https://github.com/videojs/video.js/pull/6216 1541 var _initialGuid = 3; 1542 /** 1543 * Unique ID for an element or function 1544 * 1545 * @type {Number} 1546 */ 1547 1548 var _guid = _initialGuid; 1549 /** 1550 * Get a unique auto-incrementing ID by number that has not been returned before. 1551 * 1552 * @return {number} 1553 * A new unique ID. 1554 */ 1555 1556 function newGUID() { 1557 return _guid++; 1558 } 1559 1560 /** 1561 * @file dom-data.js 1562 * @module dom-data 1563 */ 1564 var FakeWeakMap; 1565 1566 if (!window$1.WeakMap) { 1567 FakeWeakMap = /*#__PURE__*/function () { 1568 function FakeWeakMap() { 1569 this.vdata = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now()); 1570 this.data = {}; 1571 } 1572 1573 var _proto = FakeWeakMap.prototype; 1574 1575 _proto.set = function set(key, value) { 1576 var access = key[this.vdata] || newGUID(); 1577 1578 if (!key[this.vdata]) { 1579 key[this.vdata] = access; 1580 } 1581 1582 this.data[access] = value; 1583 return this; 1584 }; 1585 1586 _proto.get = function get(key) { 1587 var access = key[this.vdata]; // we have data, return it 1588 1589 if (access) { 1590 return this.data[access]; 1591 } // we don't have data, return nothing. 1592 // return undefined explicitly as that's the contract for this method 1593 1594 1595 log('We have no data for this element', key); 1596 return undefined; 1597 }; 1598 1599 _proto.has = function has(key) { 1600 var access = key[this.vdata]; 1601 return access in this.data; 1602 }; 1603 1604 _proto["delete"] = function _delete(key) { 1605 var access = key[this.vdata]; 1606 1607 if (access) { 1608 delete this.data[access]; 1609 delete key[this.vdata]; 1610 } 1611 }; 1612 1613 return FakeWeakMap; 1614 }(); 1615 } 1616 /** 1617 * Element Data Store. 1618 * 1619 * Allows for binding data to an element without putting it directly on the 1620 * element. Ex. Event listeners are stored here. 1621 * (also from jsninja.com, slightly modified and updated for closure compiler) 1622 * 1623 * @type {Object} 1624 * @private 1625 */ 1626 1627 1628 var DomData = window$1.WeakMap ? new WeakMap() : new FakeWeakMap(); 1629 1630 /** 1631 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) 1632 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) 1633 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as 1634 * robust as jquery's, so there's probably some differences. 1635 * 1636 * @file events.js 1637 * @module events 1638 */ 1639 /** 1640 * Clean up the listener cache and dispatchers 1641 * 1642 * @param {Element|Object} elem 1643 * Element to clean up 1644 * 1645 * @param {string} type 1646 * Type of event to clean up 1647 */ 1648 1649 function _cleanUpEvents(elem, type) { 1650 if (!DomData.has(elem)) { 1651 return; 1652 } 1653 1654 var data = DomData.get(elem); // Remove the events of a particular type if there are none left 1655 1656 if (data.handlers[type].length === 0) { 1657 delete data.handlers[type]; // data.handlers[type] = null; 1658 // Setting to null was causing an error with data.handlers 1659 // Remove the meta-handler from the element 1660 1661 if (elem.removeEventListener) { 1662 elem.removeEventListener(type, data.dispatcher, false); 1663 } else if (elem.detachEvent) { 1664 elem.detachEvent('on' + type, data.dispatcher); 1665 } 1666 } // Remove the events object if there are no types left 1667 1668 1669 if (Object.getOwnPropertyNames(data.handlers).length <= 0) { 1670 delete data.handlers; 1671 delete data.dispatcher; 1672 delete data.disabled; 1673 } // Finally remove the element data if there is no data left 1674 1675 1676 if (Object.getOwnPropertyNames(data).length === 0) { 1677 DomData["delete"](elem); 1678 } 1679 } 1680 /** 1681 * Loops through an array of event types and calls the requested method for each type. 1682 * 1683 * @param {Function} fn 1684 * The event method we want to use. 1685 * 1686 * @param {Element|Object} elem 1687 * Element or object to bind listeners to 1688 * 1689 * @param {string} type 1690 * Type of event to bind to. 1691 * 1692 * @param {EventTarget~EventListener} callback 1693 * Event listener. 1694 */ 1695 1696 1697 function _handleMultipleEvents(fn, elem, types, callback) { 1698 types.forEach(function (type) { 1699 // Call the event method for each one of the types 1700 fn(elem, type, callback); 1701 }); 1702 } 1703 /** 1704 * Fix a native event to have standard property values 1705 * 1706 * @param {Object} event 1707 * Event object to fix. 1708 * 1709 * @return {Object} 1710 * Fixed event object. 1711 */ 1712 1713 1714 function fixEvent(event) { 1715 if (event.fixed_) { 1716 return event; 1717 } 1718 1719 function returnTrue() { 1720 return true; 1721 } 1722 1723 function returnFalse() { 1724 return false; 1725 } // Test if fixing up is needed 1726 // Used to check if !event.stopPropagation instead of isPropagationStopped 1727 // But native events return true for stopPropagation, but don't have 1728 // other expected methods like isPropagationStopped. Seems to be a problem 1729 // with the Javascript Ninja code. So we're just overriding all events now. 1730 1731 1732 if (!event || !event.isPropagationStopped) { 1733 var old = event || window$1.event; 1734 event = {}; // Clone the old object so that we can modify the values event = {}; 1735 // IE8 Doesn't like when you mess with native event properties 1736 // Firefox returns false for event.hasOwnProperty('type') and other props 1737 // which makes copying more difficult. 1738 // TODO: Probably best to create a whitelist of event props 1739 1740 for (var key in old) { 1741 // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y 1742 // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation 1743 // and webkitMovementX/Y 1744 if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { 1745 // Chrome 32+ warns if you try to copy deprecated returnValue, but 1746 // we still want to if preventDefault isn't supported (IE8). 1747 if (!(key === 'returnValue' && old.preventDefault)) { 1748 event[key] = old[key]; 1749 } 1750 } 1751 } // The event occurred on this element 1752 1753 1754 if (!event.target) { 1755 event.target = event.srcElement || document; 1756 } // Handle which other element the event is related to 1757 1758 1759 if (!event.relatedTarget) { 1760 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 1761 } // Stop the default browser action 1762 1763 1764 event.preventDefault = function () { 1765 if (old.preventDefault) { 1766 old.preventDefault(); 1767 } 1768 1769 event.returnValue = false; 1770 old.returnValue = false; 1771 event.defaultPrevented = true; 1772 }; 1773 1774 event.defaultPrevented = false; // Stop the event from bubbling 1775 1776 event.stopPropagation = function () { 1777 if (old.stopPropagation) { 1778 old.stopPropagation(); 1779 } 1780 1781 event.cancelBubble = true; 1782 old.cancelBubble = true; 1783 event.isPropagationStopped = returnTrue; 1784 }; 1785 1786 event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers 1787 1788 event.stopImmediatePropagation = function () { 1789 if (old.stopImmediatePropagation) { 1790 old.stopImmediatePropagation(); 1791 } 1792 1793 event.isImmediatePropagationStopped = returnTrue; 1794 event.stopPropagation(); 1795 }; 1796 1797 event.isImmediatePropagationStopped = returnFalse; // Handle mouse position 1798 1799 if (event.clientX !== null && event.clientX !== undefined) { 1800 var doc = document.documentElement; 1801 var body = document.body; 1802 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 1803 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 1804 } // Handle key presses 1805 1806 1807 event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: 1808 // 0 == left; 1 == middle; 2 == right 1809 1810 if (event.button !== null && event.button !== undefined) { 1811 // The following is disabled because it does not pass videojs-standard 1812 // and... yikes. 1813 1814 /* eslint-disable */ 1815 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; 1816 /* eslint-enable */ 1817 } 1818 } 1819 1820 event.fixed_ = true; // Returns fixed-up instance 1821 1822 return event; 1823 } 1824 /** 1825 * Whether passive event listeners are supported 1826 */ 1827 1828 var _supportsPassive; 1829 1830 var supportsPassive = function supportsPassive() { 1831 if (typeof _supportsPassive !== 'boolean') { 1832 _supportsPassive = false; 1833 1834 try { 1835 var opts = Object.defineProperty({}, 'passive', { 1836 get: function get() { 1837 _supportsPassive = true; 1838 } 1839 }); 1840 window$1.addEventListener('test', null, opts); 1841 window$1.removeEventListener('test', null, opts); 1842 } catch (e) {// disregard 1843 } 1844 } 1845 1846 return _supportsPassive; 1847 }; 1848 /** 1849 * Touch events Chrome expects to be passive 1850 */ 1851 1852 1853 var passiveEvents = ['touchstart', 'touchmove']; 1854 /** 1855 * Add an event listener to element 1856 * It stores the handler function in a separate cache object 1857 * and adds a generic handler to the element's event, 1858 * along with a unique id (guid) to the element. 1859 * 1860 * @param {Element|Object} elem 1861 * Element or object to bind listeners to 1862 * 1863 * @param {string|string[]} type 1864 * Type of event to bind to. 1865 * 1866 * @param {EventTarget~EventListener} fn 1867 * Event listener. 1868 */ 1869 1870 function on(elem, type, fn) { 1871 if (Array.isArray(type)) { 1872 return _handleMultipleEvents(on, elem, type, fn); 1873 } 1874 1875 if (!DomData.has(elem)) { 1876 DomData.set(elem, {}); 1877 } 1878 1879 var data = DomData.get(elem); // We need a place to store all our handler data 1880 1881 if (!data.handlers) { 1882 data.handlers = {}; 1883 } 1884 1885 if (!data.handlers[type]) { 1886 data.handlers[type] = []; 1887 } 1888 1889 if (!fn.guid) { 1890 fn.guid = newGUID(); 1891 } 1892 1893 data.handlers[type].push(fn); 1894 1895 if (!data.dispatcher) { 1896 data.disabled = false; 1897 1898 data.dispatcher = function (event, hash) { 1899 if (data.disabled) { 1900 return; 1901 } 1902 1903 event = fixEvent(event); 1904 var handlers = data.handlers[event.type]; 1905 1906 if (handlers) { 1907 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 1908 var handlersCopy = handlers.slice(0); 1909 1910 for (var m = 0, n = handlersCopy.length; m < n; m++) { 1911 if (event.isImmediatePropagationStopped()) { 1912 break; 1913 } else { 1914 try { 1915 handlersCopy[m].call(elem, event, hash); 1916 } catch (e) { 1917 log.error(e); 1918 } 1919 } 1920 } 1921 } 1922 }; 1923 } 1924 1925 if (data.handlers[type].length === 1) { 1926 if (elem.addEventListener) { 1927 var options = false; 1928 1929 if (supportsPassive() && passiveEvents.indexOf(type) > -1) { 1930 options = { 1931 passive: true 1932 }; 1933 } 1934 1935 elem.addEventListener(type, data.dispatcher, options); 1936 } else if (elem.attachEvent) { 1937 elem.attachEvent('on' + type, data.dispatcher); 1938 } 1939 } 1940 } 1941 /** 1942 * Removes event listeners from an element 1943 * 1944 * @param {Element|Object} elem 1945 * Object to remove listeners from. 1946 * 1947 * @param {string|string[]} [type] 1948 * Type of listener to remove. Don't include to remove all events from element. 1949 * 1950 * @param {EventTarget~EventListener} [fn] 1951 * Specific listener to remove. Don't include to remove listeners for an event 1952 * type. 1953 */ 1954 1955 function off(elem, type, fn) { 1956 // Don't want to add a cache object through getElData if not needed 1957 if (!DomData.has(elem)) { 1958 return; 1959 } 1960 1961 var data = DomData.get(elem); // If no events exist, nothing to unbind 1962 1963 if (!data.handlers) { 1964 return; 1965 } 1966 1967 if (Array.isArray(type)) { 1968 return _handleMultipleEvents(off, elem, type, fn); 1969 } // Utility function 1970 1971 1972 var removeType = function removeType(el, t) { 1973 data.handlers[t] = []; 1974 1975 _cleanUpEvents(el, t); 1976 }; // Are we removing all bound events? 1977 1978 1979 if (type === undefined) { 1980 for (var t in data.handlers) { 1981 if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { 1982 removeType(elem, t); 1983 } 1984 } 1985 1986 return; 1987 } 1988 1989 var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind 1990 1991 if (!handlers) { 1992 return; 1993 } // If no listener was provided, remove all listeners for type 1994 1995 1996 if (!fn) { 1997 removeType(elem, type); 1998 return; 1999 } // We're only removing a single handler 2000 2001 2002 if (fn.guid) { 2003 for (var n = 0; n < handlers.length; n++) { 2004 if (handlers[n].guid === fn.guid) { 2005 handlers.splice(n--, 1); 2006 } 2007 } 2008 } 2009 2010 _cleanUpEvents(elem, type); 2011 } 2012 /** 2013 * Trigger an event for an element 2014 * 2015 * @param {Element|Object} elem 2016 * Element to trigger an event on 2017 * 2018 * @param {EventTarget~Event|string} event 2019 * A string (the type) or an event object with a type attribute 2020 * 2021 * @param {Object} [hash] 2022 * data hash to pass along with the event 2023 * 2024 * @return {boolean|undefined} 2025 * Returns the opposite of `defaultPrevented` if default was 2026 * prevented. Otherwise, returns `undefined` 2027 */ 2028 2029 function trigger(elem, event, hash) { 2030 // Fetches element data and a reference to the parent (for bubbling). 2031 // Don't want to add a data object to cache for every parent, 2032 // so checking hasElData first. 2033 var elemData = DomData.has(elem) ? DomData.get(elem) : {}; 2034 var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, 2035 // handler; 2036 // If an event name was passed as a string, creates an event out of it 2037 2038 if (typeof event === 'string') { 2039 event = { 2040 type: event, 2041 target: elem 2042 }; 2043 } else if (!event.target) { 2044 event.target = elem; 2045 } // Normalizes the event properties. 2046 2047 2048 event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. 2049 2050 if (elemData.dispatcher) { 2051 elemData.dispatcher.call(elem, event, hash); 2052 } // Unless explicitly stopped or the event does not bubble (e.g. media events) 2053 // recursively calls this function to bubble the event up the DOM. 2054 2055 2056 if (parent && !event.isPropagationStopped() && event.bubbles === true) { 2057 trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled. 2058 } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) { 2059 if (!DomData.has(event.target)) { 2060 DomData.set(event.target, {}); 2061 } 2062 2063 var targetData = DomData.get(event.target); // Checks if the target has a default action for this event. 2064 2065 if (event.target[event.type]) { 2066 // Temporarily disables event dispatching on the target as we have already executed the handler. 2067 targetData.disabled = true; // Executes the default action. 2068 2069 if (typeof event.target[event.type] === 'function') { 2070 event.target[event.type](); 2071 } // Re-enables event dispatching. 2072 2073 2074 targetData.disabled = false; 2075 } 2076 } // Inform the triggerer if the default was prevented by returning false 2077 2078 2079 return !event.defaultPrevented; 2080 } 2081 /** 2082 * Trigger a listener only once for an event. 2083 * 2084 * @param {Element|Object} elem 2085 * Element or object to bind to. 2086 * 2087 * @param {string|string[]} type 2088 * Name/type of event 2089 * 2090 * @param {Event~EventListener} fn 2091 * Event listener function 2092 */ 2093 2094 function one(elem, type, fn) { 2095 if (Array.isArray(type)) { 2096 return _handleMultipleEvents(one, elem, type, fn); 2097 } 2098 2099 var func = function func() { 2100 off(elem, type, func); 2101 fn.apply(this, arguments); 2102 }; // copy the guid to the new function so it can removed using the original function's ID 2103 2104 2105 func.guid = fn.guid = fn.guid || newGUID(); 2106 on(elem, type, func); 2107 } 2108 /** 2109 * Trigger a listener only once and then turn if off for all 2110 * configured events 2111 * 2112 * @param {Element|Object} elem 2113 * Element or object to bind to. 2114 * 2115 * @param {string|string[]} type 2116 * Name/type of event 2117 * 2118 * @param {Event~EventListener} fn 2119 * Event listener function 2120 */ 2121 2122 function any(elem, type, fn) { 2123 var func = function func() { 2124 off(elem, type, func); 2125 fn.apply(this, arguments); 2126 }; // copy the guid to the new function so it can removed using the original function's ID 2127 2128 2129 func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything 2130 2131 on(elem, type, func); 2132 } 2133 2134 var Events = /*#__PURE__*/Object.freeze({ 2135 __proto__: null, 2136 fixEvent: fixEvent, 2137 on: on, 2138 off: off, 2139 trigger: trigger, 2140 one: one, 2141 any: any 2142 }); 2143 2144 /** 2145 * @file fn.js 2146 * @module fn 2147 */ 2148 var UPDATE_REFRESH_INTERVAL = 30; 2149 /** 2150 * Bind (a.k.a proxy or context). A simple method for changing the context of 2151 * a function. 2152 * 2153 * It also stores a unique id on the function so it can be easily removed from 2154 * events. 2155 * 2156 * @function 2157 * @param {Mixed} context 2158 * The object to bind as scope. 2159 * 2160 * @param {Function} fn 2161 * The function to be bound to a scope. 2162 * 2163 * @param {number} [uid] 2164 * An optional unique ID for the function to be set 2165 * 2166 * @return {Function} 2167 * The new function that will be bound into the context given 2168 */ 2169 2170 var bind = function bind(context, fn, uid) { 2171 // Make sure the function has a unique ID 2172 if (!fn.guid) { 2173 fn.guid = newGUID(); 2174 } // Create the new function that changes the context 2175 2176 2177 var bound = fn.bind(context); // Allow for the ability to individualize this function 2178 // Needed in the case where multiple objects might share the same prototype 2179 // IF both items add an event listener with the same function, then you try to remove just one 2180 // it will remove both because they both have the same guid. 2181 // when using this, you need to use the bind method when you remove the listener as well. 2182 // currently used in text tracks 2183 2184 bound.guid = uid ? uid + '_' + fn.guid : fn.guid; 2185 return bound; 2186 }; 2187 /** 2188 * Wraps the given function, `fn`, with a new function that only invokes `fn` 2189 * at most once per every `wait` milliseconds. 2190 * 2191 * @function 2192 * @param {Function} fn 2193 * The function to be throttled. 2194 * 2195 * @param {number} wait 2196 * The number of milliseconds by which to throttle. 2197 * 2198 * @return {Function} 2199 */ 2200 2201 var throttle = function throttle(fn, wait) { 2202 var last = window$1.performance.now(); 2203 2204 var throttled = function throttled() { 2205 var now = window$1.performance.now(); 2206 2207 if (now - last >= wait) { 2208 fn.apply(void 0, arguments); 2209 last = now; 2210 } 2211 }; 2212 2213 return throttled; 2214 }; 2215 /** 2216 * Creates a debounced function that delays invoking `func` until after `wait` 2217 * milliseconds have elapsed since the last time the debounced function was 2218 * invoked. 2219 * 2220 * Inspired by lodash and underscore implementations. 2221 * 2222 * @function 2223 * @param {Function} func 2224 * The function to wrap with debounce behavior. 2225 * 2226 * @param {number} wait 2227 * The number of milliseconds to wait after the last invocation. 2228 * 2229 * @param {boolean} [immediate] 2230 * Whether or not to invoke the function immediately upon creation. 2231 * 2232 * @param {Object} [context=window] 2233 * The "context" in which the debounced function should debounce. For 2234 * example, if this function should be tied to a Video.js player, 2235 * the player can be passed here. Alternatively, defaults to the 2236 * global `window` object. 2237 * 2238 * @return {Function} 2239 * A debounced function. 2240 */ 2241 2242 var debounce = function debounce(func, wait, immediate, context) { 2243 if (context === void 0) { 2244 context = window$1; 2245 } 2246 2247 var timeout; 2248 2249 var cancel = function cancel() { 2250 context.clearTimeout(timeout); 2251 timeout = null; 2252 }; 2253 /* eslint-disable consistent-this */ 2254 2255 2256 var debounced = function debounced() { 2257 var self = this; 2258 var args = arguments; 2259 2260 var _later = function later() { 2261 timeout = null; 2262 _later = null; 2263 2264 if (!immediate) { 2265 func.apply(self, args); 2266 } 2267 }; 2268 2269 if (!timeout && immediate) { 2270 func.apply(self, args); 2271 } 2272 2273 context.clearTimeout(timeout); 2274 timeout = context.setTimeout(_later, wait); 2275 }; 2276 /* eslint-enable consistent-this */ 2277 2278 2279 debounced.cancel = cancel; 2280 return debounced; 2281 }; 2282 2283 /** 2284 * @file src/js/event-target.js 2285 */ 2286 /** 2287 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It 2288 * adds shorthand functions that wrap around lengthy functions. For example: 2289 * the `on` function is a wrapper around `addEventListener`. 2290 * 2291 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} 2292 * @class EventTarget 2293 */ 2294 2295 var EventTarget = function EventTarget() {}; 2296 /** 2297 * A Custom DOM event. 2298 * 2299 * @typedef {Object} EventTarget~Event 2300 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} 2301 */ 2302 2303 /** 2304 * All event listeners should follow the following format. 2305 * 2306 * @callback EventTarget~EventListener 2307 * @this {EventTarget} 2308 * 2309 * @param {EventTarget~Event} event 2310 * the event that triggered this function 2311 * 2312 * @param {Object} [hash] 2313 * hash of data sent during the event 2314 */ 2315 2316 /** 2317 * An object containing event names as keys and booleans as values. 2318 * 2319 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} 2320 * will have extra functionality. See that function for more information. 2321 * 2322 * @property EventTarget.prototype.allowedEvents_ 2323 * @private 2324 */ 2325 2326 2327 EventTarget.prototype.allowedEvents_ = {}; 2328 /** 2329 * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a 2330 * function that will get called when an event with a certain name gets triggered. 2331 * 2332 * @param {string|string[]} type 2333 * An event name or an array of event names. 2334 * 2335 * @param {EventTarget~EventListener} fn 2336 * The function to call with `EventTarget`s 2337 */ 2338 2339 EventTarget.prototype.on = function (type, fn) { 2340 // Remove the addEventListener alias before calling Events.on 2341 // so we don't get into an infinite type loop 2342 var ael = this.addEventListener; 2343 2344 this.addEventListener = function () {}; 2345 2346 on(this, type, fn); 2347 this.addEventListener = ael; 2348 }; 2349 /** 2350 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic 2351 * the standard DOM API. 2352 * 2353 * @function 2354 * @see {@link EventTarget#on} 2355 */ 2356 2357 2358 EventTarget.prototype.addEventListener = EventTarget.prototype.on; 2359 /** 2360 * Removes an `event listener` for a specific event from an instance of `EventTarget`. 2361 * This makes it so that the `event listener` will no longer get called when the 2362 * named event happens. 2363 * 2364 * @param {string|string[]} type 2365 * An event name or an array of event names. 2366 * 2367 * @param {EventTarget~EventListener} fn 2368 * The function to remove. 2369 */ 2370 2371 EventTarget.prototype.off = function (type, fn) { 2372 off(this, type, fn); 2373 }; 2374 /** 2375 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic 2376 * the standard DOM API. 2377 * 2378 * @function 2379 * @see {@link EventTarget#off} 2380 */ 2381 2382 2383 EventTarget.prototype.removeEventListener = EventTarget.prototype.off; 2384 /** 2385 * This function will add an `event listener` that gets triggered only once. After the 2386 * first trigger it will get removed. This is like adding an `event listener` 2387 * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. 2388 * 2389 * @param {string|string[]} type 2390 * An event name or an array of event names. 2391 * 2392 * @param {EventTarget~EventListener} fn 2393 * The function to be called once for each event name. 2394 */ 2395 2396 EventTarget.prototype.one = function (type, fn) { 2397 // Remove the addEventListener aliasing Events.on 2398 // so we don't get into an infinite type loop 2399 var ael = this.addEventListener; 2400 2401 this.addEventListener = function () {}; 2402 2403 one(this, type, fn); 2404 this.addEventListener = ael; 2405 }; 2406 2407 EventTarget.prototype.any = function (type, fn) { 2408 // Remove the addEventListener aliasing Events.on 2409 // so we don't get into an infinite type loop 2410 var ael = this.addEventListener; 2411 2412 this.addEventListener = function () {}; 2413 2414 any(this, type, fn); 2415 this.addEventListener = ael; 2416 }; 2417 /** 2418 * This function causes an event to happen. This will then cause any `event listeners` 2419 * that are waiting for that event, to get called. If there are no `event listeners` 2420 * for an event then nothing will happen. 2421 * 2422 * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. 2423 * Trigger will also call the `on` + `uppercaseEventName` function. 2424 * 2425 * Example: 2426 * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call 2427 * `onClick` if it exists. 2428 * 2429 * @param {string|EventTarget~Event|Object} event 2430 * The name of the event, an `Event`, or an object with a key of type set to 2431 * an event name. 2432 */ 2433 2434 2435 EventTarget.prototype.trigger = function (event) { 2436 var type = event.type || event; // deprecation 2437 // In a future version we should default target to `this` 2438 // similar to how we default the target to `elem` in 2439 // `Events.trigger`. Right now the default `target` will be 2440 // `document` due to the `Event.fixEvent` call. 2441 2442 if (typeof event === 'string') { 2443 event = { 2444 type: type 2445 }; 2446 } 2447 2448 event = fixEvent(event); 2449 2450 if (this.allowedEvents_[type] && this['on' + type]) { 2451 this['on' + type](event); 2452 } 2453 2454 trigger(this, event); 2455 }; 2456 /** 2457 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic 2458 * the standard DOM API. 2459 * 2460 * @function 2461 * @see {@link EventTarget#trigger} 2462 */ 2463 2464 2465 EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; 2466 var EVENT_MAP; 2467 2468 EventTarget.prototype.queueTrigger = function (event) { 2469 var _this = this; 2470 2471 // only set up EVENT_MAP if it'll be used 2472 if (!EVENT_MAP) { 2473 EVENT_MAP = new Map(); 2474 } 2475 2476 var type = event.type || event; 2477 var map = EVENT_MAP.get(this); 2478 2479 if (!map) { 2480 map = new Map(); 2481 EVENT_MAP.set(this, map); 2482 } 2483 2484 var oldTimeout = map.get(type); 2485 map["delete"](type); 2486 window$1.clearTimeout(oldTimeout); 2487 var timeout = window$1.setTimeout(function () { 2488 // if we cleared out all timeouts for the current target, delete its map 2489 if (map.size === 0) { 2490 map = null; 2491 EVENT_MAP["delete"](_this); 2492 } 2493 2494 _this.trigger(event); 2495 }, 0); 2496 map.set(type, timeout); 2497 }; 2498 2499 /** 2500 * @file mixins/evented.js 2501 * @module evented 2502 */ 2503 /** 2504 * Returns whether or not an object has had the evented mixin applied. 2505 * 2506 * @param {Object} object 2507 * An object to test. 2508 * 2509 * @return {boolean} 2510 * Whether or not the object appears to be evented. 2511 */ 2512 2513 var isEvented = function isEvented(object) { 2514 return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { 2515 return typeof object[k] === 'function'; 2516 }); 2517 }; 2518 /** 2519 * Adds a callback to run after the evented mixin applied. 2520 * 2521 * @param {Object} object 2522 * An object to Add 2523 * @param {Function} callback 2524 * The callback to run. 2525 */ 2526 2527 2528 var addEventedCallback = function addEventedCallback(target, callback) { 2529 if (isEvented(target)) { 2530 callback(); 2531 } else { 2532 if (!target.eventedCallbacks) { 2533 target.eventedCallbacks = []; 2534 } 2535 2536 target.eventedCallbacks.push(callback); 2537 } 2538 }; 2539 /** 2540 * Whether a value is a valid event type - non-empty string or array. 2541 * 2542 * @private 2543 * @param {string|Array} type 2544 * The type value to test. 2545 * 2546 * @return {boolean} 2547 * Whether or not the type is a valid event type. 2548 */ 2549 2550 2551 var isValidEventType = function isValidEventType(type) { 2552 return (// The regex here verifies that the `type` contains at least one non- 2553 // whitespace character. 2554 typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length 2555 ); 2556 }; 2557 /** 2558 * Validates a value to determine if it is a valid event target. Throws if not. 2559 * 2560 * @private 2561 * @throws {Error} 2562 * If the target does not appear to be a valid event target. 2563 * 2564 * @param {Object} target 2565 * The object to test. 2566 */ 2567 2568
vendor: 6,295 bytes, lines 2569-2762
2569 var validateTarget = function validateTarget(target) { 2570 if (!target.nodeName && !isEvented(target)) { 2571 throw new Error('Invalid target; must be a DOM node or evented object.'); 2572 } 2573 }; 2574 /** 2575 * Validates a value to determine if it is a valid event target. Throws if not. 2576 * 2577 * @private 2578 * @throws {Error} 2579 * If the type does not appear to be a valid event type. 2580 * 2581 * @param {string|Array} type 2582 * The type to test. 2583 */ 2584 2585 2586 var validateEventType = function validateEventType(type) { 2587 if (!isValidEventType(type)) { 2588 throw new Error('Invalid event type; must be a non-empty string or array.'); 2589 } 2590 }; 2591 /** 2592 * Validates a value to determine if it is a valid listener. Throws if not. 2593 * 2594 * @private 2595 * @throws {Error} 2596 * If the listener is not a function. 2597 * 2598 * @param {Function} listener 2599 * The listener to test. 2600 */ 2601 2602 2603 var validateListener = function validateListener(listener) { 2604 if (typeof listener !== 'function') { 2605 throw new Error('Invalid listener; must be a function.'); 2606 } 2607 }; 2608 /** 2609 * Takes an array of arguments given to `on()` or `one()`, validates them, and 2610 * normalizes them into an object. 2611 * 2612 * @private 2613 * @param {Object} self 2614 * The evented object on which `on()` or `one()` was called. This 2615 * object will be bound as the `this` value for the listener. 2616 * 2617 * @param {Array} args 2618 * An array of arguments passed to `on()` or `one()`. 2619 * 2620 * @return {Object} 2621 * An object containing useful values for `on()` or `one()` calls. 2622 */ 2623 2624 2625 var normalizeListenArgs = function normalizeListenArgs(self, args) { 2626 // If the number of arguments is less than 3, the target is always the 2627 // evented object itself. 2628 var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; 2629 var target; 2630 var type; 2631 var listener; 2632 2633 if (isTargetingSelf) { 2634 target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to 2635 // the evented object itself. 2636 2637 if (args.length >= 3) { 2638 args.shift(); 2639 } 2640 2641 type = args[0]; 2642 listener = args[1]; 2643 } else { 2644 target = args[0]; 2645 type = args[1]; 2646 listener = args[2]; 2647 } 2648 2649 validateTarget(target); 2650 validateEventType(type); 2651 validateListener(listener); 2652 listener = bind(self, listener); 2653 return { 2654 isTargetingSelf: isTargetingSelf, 2655 target: target, 2656 type: type, 2657 listener: listener 2658 }; 2659 }; 2660 /** 2661 * Adds the listener to the event type(s) on the target, normalizing for 2662 * the type of target. 2663 * 2664 * @private 2665 * @param {Element|Object} target 2666 * A DOM node or evented object. 2667 * 2668 * @param {string} method 2669 * The event binding method to use ("on" or "one"). 2670 * 2671 * @param {string|Array} type 2672 * One or more event type(s). 2673 * 2674 * @param {Function} listener 2675 * A listener function. 2676 */ 2677 2678 2679 var listen = function listen(target, method, type, listener) { 2680 validateTarget(target); 2681 2682 if (target.nodeName) { 2683 Events[method](target, type, listener); 2684 } else { 2685 target[method](type, listener); 2686 } 2687 }; 2688 /** 2689 * Contains methods that provide event capabilities to an object which is passed 2690 * to {@link module:evented|evented}. 2691 * 2692 * @mixin EventedMixin 2693 */ 2694 2695 2696 var EventedMixin = { 2697 /** 2698 * Add a listener to an event (or events) on this object or another evented 2699 * object. 2700 * 2701 * @param {string|Array|Element|Object} targetOrType 2702 * If this is a string or array, it represents the event type(s) 2703 * that will trigger the listener. 2704 * 2705 * Another evented object can be passed here instead, which will 2706 * cause the listener to listen for events on _that_ object. 2707 * 2708 * In either case, the listener's `this` value will be bound to 2709 * this object. 2710 * 2711 * @param {string|Array|Function} typeOrListener 2712 * If the first argument was a string or array, this should be the 2713 * listener function. Otherwise, this is a string or array of event 2714 * type(s). 2715 * 2716 * @param {Function} [listener] 2717 * If the first argument was another evented object, this will be 2718 * the listener function. 2719 */ 2720 on: function on() { 2721 var _this = this; 2722 2723 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 2724 args[_key] = arguments[_key]; 2725 } 2726 2727 var _normalizeListenArgs = normalizeListenArgs(this, args), 2728 isTargetingSelf = _normalizeListenArgs.isTargetingSelf, 2729 target = _normalizeListenArgs.target, 2730 type = _normalizeListenArgs.type, 2731 listener = _normalizeListenArgs.listener; 2732 2733 listen(target, 'on', type, listener); // If this object is listening to another evented object. 2734 2735 if (!isTargetingSelf) { 2736 // If this object is disposed, remove the listener. 2737 var removeListenerOnDispose = function removeListenerOnDispose() { 2738 return _this.off(target, type, listener); 2739 }; // Use the same function ID as the listener so we can remove it later it 2740 // using the ID of the original listener. 2741 2742 2743 removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures 2744 // that if the target is disposed BEFORE this object, we remove the 2745 // removal listener that was just added. Otherwise, we create a memory leak. 2746 2747 var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { 2748 return _this.off('dispose', removeListenerOnDispose); 2749 }; // Use the same function ID as the listener so we can remove it later 2750 // it using the ID of the original listener. 2751 2752 2753 removeRemoverOnTargetDispose.guid = listener.guid; 2754 listen(this, 'on', 'dispose', removeListenerOnDispose); 2755 listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); 2756 } 2757 }, 2758 2759 /** 2760 * Add a listener to an event (or events) on this object or another evented 2761 * object. The listener will be called once per event and then removed. 2762 *
2763 * @param {string|Array|Element|Object} targetOrType 2764 * If this is a string or array, it represents the event type(s) 2765 * that will trigger the listener. 2766 * 2767 * Another evented object can be passed here instead, which will 2768 * cause the listener to listen for events on _that_ object. 2769 * 2770 * In either case, the listener's `this` value will be bound to 2771 * this object. 2772 * 2773 * @param {string|Array|Function} typeOrListener 2774 * If the first argument was a string or array, this should be the 2775 * listener function. Otherwise, this is a string or array of event 2776 * type(s). 2777 * 2778 * @param {Function} [listener] 2779 * If the first argument was another evented object, this will be 2780 * the listener function. 2781 */ 2782 one: function one() { 2783 var _this2 = this; 2784 2785 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 2786 args[_key2] = arguments[_key2]; 2787 } 2788 2789 var _normalizeListenArgs2 = normalizeListenArgs(this, args), 2790 isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, 2791 target = _normalizeListenArgs2.target, 2792 type = _normalizeListenArgs2.type, 2793 listener = _normalizeListenArgs2.listener; // Targeting this evented object. 2794 2795 2796 if (isTargetingSelf) { 2797 listen(target, 'one', type, listener); // Targeting another evented object. 2798 } else { 2799 // TODO: This wrapper is incorrect! It should only 2800 // remove the wrapper for the event type that called it. 2801 // Instead all listners are removed on the first trigger! 2802 // see https://github.com/videojs/video.js/issues/5962 2803 var wrapper = function wrapper() { 2804 _this2.off(target, type, wrapper); 2805 2806 for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 2807 largs[_key3] = arguments[_key3]; 2808 } 2809 2810 listener.apply(null, largs); 2811 }; // Use the same function ID as the listener so we can remove it later 2812 // it using the ID of the original listener. 2813 2814 2815 wrapper.guid = listener.guid; 2816 listen(target, 'one', type, wrapper); 2817 } 2818 }, 2819 2820 /** 2821 * Add a listener to an event (or events) on this object or another evented 2822 * object. The listener will only be called once for the first event that is triggered 2823 * then removed. 2824 * 2825 * @param {string|Array|Element|Object} targetOrType 2826 * If this is a string or array, it represents the event type(s) 2827 * that will trigger the listener. 2828 * 2829 * Another evented object can be passed here instead, which will 2830 * cause the listener to listen for events on _that_ object. 2831 * 2832 * In either case, the listener's `this` value will be bound to 2833 * this object. 2834 * 2835 * @param {string|Array|Function} typeOrListener 2836 * If the first argument was a string or array, this should be the 2837 * listener function. Otherwise, this is a string or array of event 2838 * type(s). 2839 * 2840 * @param {Function} [listener] 2841 * If the first argument was another evented object, this will be 2842 * the listener function. 2843 */ 2844 any: function any() { 2845 var _this3 = this; 2846 2847 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 2848 args[_key4] = arguments[_key4]; 2849 } 2850 2851 var _normalizeListenArgs3 = normalizeListenArgs(this, args), 2852 isTargetingSelf = _normalizeListenArgs3.isTargetingSelf, 2853 target = _normalizeListenArgs3.target, 2854 type = _normalizeListenArgs3.type, 2855 listener = _normalizeListenArgs3.listener; // Targeting this evented object. 2856 2857 2858 if (isTargetingSelf) { 2859 listen(target, 'any', type, listener); // Targeting another evented object. 2860 } else { 2861 var wrapper = function wrapper() { 2862 _this3.off(target, type, wrapper); 2863 2864 for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 2865 largs[_key5] = arguments[_key5]; 2866 } 2867 2868 listener.apply(null, largs); 2869 }; // Use the same function ID as the listener so we can remove it later 2870 // it using the ID of the original listener. 2871 2872 2873 wrapper.guid = listener.guid; 2874 listen(target, 'any', type, wrapper); 2875 } 2876 }, 2877 2878 /** 2879 * Removes listener(s) from event(s) on an evented object. 2880 *
2881 * @param {string|Array|Element|Object} [targetOrType] 2882 * If this is a string or array, it represents the event type(s). 2883 * 2884 * Another evented object can be passed here instead, in which case 2885 * ALL 3 arguments are _required_. 2886 * 2887 * @param {string|Array|Function} [typeOrListener] 2888 * If the first argument was a string or array, this may be the 2889 * listener function. Otherwise, this is a string or array of event 2890 * type(s). 2891 * 2892 * @param {Function} [listener] 2893 * If the first argument was another evented object, this will be 2894 * the listener function; otherwise, _all_ listeners bound to the 2895 * event type(s) will be removed. 2896 */ 2897 off: function off$1(targetOrType, typeOrListener, listener) { 2898 // Targeting this evented object. 2899 if (!targetOrType || isValidEventType(targetOrType)) { 2900 off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object. 2901 } else { 2902 var target = targetOrType; 2903 var type = typeOrListener; // Fail fast and in a meaningful way! 2904 2905 validateTarget(target); 2906 validateEventType(type); 2907 validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used 2908 2909 listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given 2910 // the same guid as the event listener in on(). 2911 2912 this.off('dispose', listener); 2913 2914 if (target.nodeName) { 2915 off(target, type, listener); 2916 off(target, 'dispose', listener); 2917 } else if (isEvented(target)) { 2918 target.off(type, listener); 2919 target.off('dispose', listener); 2920 } 2921 } 2922 }, 2923 2924 /** 2925 * Fire an event on this evented object, causing its listeners to be called. 2926 * 2927 * @param {string|Object} event 2928 * An event type or an object with a type property. 2929 * 2930 * @param {Object} [hash] 2931 * An additional object to pass along to listeners. 2932 * 2933 * @return {boolean} 2934 * Whether or not the default behavior was prevented. 2935 */ 2936 trigger: function trigger$1(event, hash) { 2937 return trigger(this.eventBusEl_, event, hash); 2938 } 2939 }; 2940 /** 2941 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. 2942 * 2943 * @param {Object} target 2944 * The object to which to add event methods. 2945 * 2946 * @param {Object} [options={}] 2947 * Options for customizing the mixin behavior. 2948 * 2949 * @param {string} [options.eventBusKey] 2950 * By default, adds a `eventBusEl_` DOM element to the target object, 2951 * which is used as an event bus. If the target object already has a 2952 * DOM element that should be used, pass its key here. 2953 * 2954 * @return {Object} 2955 * The target object. 2956 */ 2957 2958 function evented(target, options) { 2959 if (options === void 0) { 2960 options = {}; 2961 } 2962 2963 var _options = options, 2964 eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_. 2965 2966 if (eventBusKey) { 2967 if (!target[eventBusKey].nodeName) { 2968 throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element."); 2969 } 2970 2971 target.eventBusEl_ = target[eventBusKey]; 2972 } else { 2973 target.eventBusEl_ = createEl('span', { 2974 className: 'vjs-event-bus' 2975 }); 2976 } 2977 2978 assign(target, EventedMixin); 2979 2980 if (target.eventedCallbacks) { 2981 target.eventedCallbacks.forEach(function (callback) { 2982 callback(); 2983 }); 2984 } // When any evented object is disposed, it removes all its listeners. 2985 2986 2987 target.on('dispose', function () { 2988 target.off(); 2989 window$1.setTimeout(function () { 2990 target.eventBusEl_ = null; 2991 }, 0); 2992 }); 2993 return target; 2994 } 2995 2996 /** 2997 * @file mixins/stateful.js 2998 * @module stateful 2999 */ 3000 /** 3001 * Contains methods that provide statefulness to an object which is passed 3002 * to {@link module:stateful}. 3003 * 3004 * @mixin StatefulMixin 3005 */ 3006 3007 var StatefulMixin = { 3008 /** 3009 * A hash containing arbitrary keys and values representing the state of 3010 * the object. 3011 * 3012 * @type {Object} 3013 */ 3014 state: {}, 3015 3016 /** 3017 * Set the state of an object by mutating its 3018 * {@link module:stateful~StatefulMixin.state|state} object in place. 3019 * 3020 * @fires module:stateful~StatefulMixin#statechanged 3021 * @param {Object|Function} stateUpdates 3022 * A new set of properties to shallow-merge into the plugin state. 3023 * Can be a plain object or a function returning a plain object. 3024 * 3025 * @return {Object|undefined}
vendor: 78,259 bytes, lines 3026-5541
3026 * An object containing changes that occurred. If no changes 3027 * occurred, returns `undefined`. 3028 */ 3029 setState: function setState(stateUpdates) { 3030 var _this = this; 3031 3032 // Support providing the `stateUpdates` state as a function. 3033 if (typeof stateUpdates === 'function') { 3034 stateUpdates = stateUpdates(); 3035 } 3036 3037 var changes; 3038 each(stateUpdates, function (value, key) { 3039 // Record the change if the value is different from what's in the 3040 // current state. 3041 if (_this.state[key] !== value) { 3042 changes = changes || {}; 3043 changes[key] = { 3044 from: _this.state[key], 3045 to: value 3046 }; 3047 } 3048 3049 _this.state[key] = value; 3050 }); // Only trigger "statechange" if there were changes AND we have a trigger 3051 // function. This allows us to not require that the target object be an 3052 // evented object. 3053 3054 if (changes && isEvented(this)) { 3055 /** 3056 * An event triggered on an object that is both 3057 * {@link module:stateful|stateful} and {@link module:evented|evented} 3058 * indicating that its state has changed. 3059 * 3060 * @event module:stateful~StatefulMixin#statechanged 3061 * @type {Object} 3062 * @property {Object} changes 3063 * A hash containing the properties that were changed and 3064 * the values they were changed `from` and `to`. 3065 */ 3066 this.trigger({ 3067 changes: changes, 3068 type: 'statechanged' 3069 }); 3070 } 3071 3072 return changes; 3073 } 3074 }; 3075 /** 3076 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target 3077 * object. 3078 * 3079 * If the target object is {@link module:evented|evented} and has a 3080 * `handleStateChanged` method, that method will be automatically bound to the 3081 * `statechanged` event on itself. 3082 * 3083 * @param {Object} target 3084 * The object to be made stateful. 3085 * 3086 * @param {Object} [defaultState] 3087 * A default set of properties to populate the newly-stateful object's 3088 * `state` property. 3089 * 3090 * @return {Object} 3091 * Returns the `target`. 3092 */ 3093 3094 function stateful(target, defaultState) { 3095 assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state` 3096 // added in that step. 3097 3098 target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists. 3099 3100 if (typeof target.handleStateChanged === 'function' && isEvented(target)) { 3101 target.on('statechanged', target.handleStateChanged); 3102 } 3103 3104 return target; 3105 } 3106 3107 /** 3108 * @file string-cases.js 3109 * @module to-lower-case 3110 */ 3111 3112 /** 3113 * Lowercase the first letter of a string. 3114 * 3115 * @param {string} string 3116 * String to be lowercased 3117 * 3118 * @return {string} 3119 * The string with a lowercased first letter 3120 */ 3121 var toLowerCase = function toLowerCase(string) { 3122 if (typeof string !== 'string') { 3123 return string; 3124 } 3125 3126 return string.replace(/./, function (w) { 3127 return w.toLowerCase(); 3128 }); 3129 }; 3130 /** 3131 * Uppercase the first letter of a string. 3132 * 3133 * @param {string} string 3134 * String to be uppercased 3135 * 3136 * @return {string} 3137 * The string with an uppercased first letter 3138 */ 3139 3140 var toTitleCase = function toTitleCase(string) { 3141 if (typeof string !== 'string') { 3142 return string; 3143 } 3144 3145 return string.replace(/./, function (w) { 3146 return w.toUpperCase(); 3147 }); 3148 }; 3149 /** 3150 * Compares the TitleCase versions of the two strings for equality. 3151 * 3152 * @param {string} str1 3153 * The first string to compare 3154 * 3155 * @param {string} str2 3156 * The second string to compare 3157 * 3158 * @return {boolean} 3159 * Whether the TitleCase versions of the strings are equal 3160 */ 3161 3162 var titleCaseEquals = function titleCaseEquals(str1, str2) { 3163 return toTitleCase(str1) === toTitleCase(str2); 3164 }; 3165 3166 /** 3167 * @file merge-options.js 3168 * @module merge-options 3169 */ 3170 /** 3171 * Merge two objects recursively. 3172 * 3173 * Performs a deep merge like 3174 * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges 3175 * plain objects (not arrays, elements, or anything else). 3176 * 3177 * Non-plain object values will be copied directly from the right-most 3178 * argument. 3179 * 3180 * @static 3181 * @param {Object[]} sources 3182 * One or more objects to merge into a new object. 3183 * 3184 * @return {Object} 3185 * A new object that is the merged result of all sources. 3186 */ 3187 3188 function mergeOptions() { 3189 var result = {}; 3190 3191 for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) { 3192 sources[_key] = arguments[_key]; 3193 } 3194 3195 sources.forEach(function (source) { 3196 if (!source) { 3197 return; 3198 } 3199 3200 each(source, function (value, key) { 3201 if (!isPlain(value)) { 3202 result[key] = value; 3203 return; 3204 } 3205 3206 if (!isPlain(result[key])) { 3207 result[key] = {}; 3208 } 3209 3210 result[key] = mergeOptions(result[key], value); 3211 }); 3212 }); 3213 return result; 3214 } 3215 3216 var MapSham = /*#__PURE__*/function () { 3217 function MapSham() { 3218 this.map_ = {}; 3219 } 3220 3221 var _proto = MapSham.prototype; 3222 3223 _proto.has = function has(key) { 3224 return key in this.map_; 3225 }; 3226 3227 _proto["delete"] = function _delete(key) { 3228 var has = this.has(key); 3229 delete this.map_[key]; 3230 return has; 3231 }; 3232 3233 _proto.set = function set(key, value) { 3234 this.set_[key] = value; 3235 return this; 3236 }; 3237 3238 _proto.forEach = function forEach(callback, thisArg) { 3239 for (var key in this.map_) { 3240 callback.call(thisArg, this.map_[key], key, this); 3241 } 3242 }; 3243 3244 return MapSham; 3245 }(); 3246 3247 var Map$1 = window$1.Map ? window$1.Map : MapSham; 3248 3249 var SetSham = /*#__PURE__*/function () { 3250 function SetSham() { 3251 this.set_ = {}; 3252 } 3253 3254 var _proto = SetSham.prototype; 3255 3256 _proto.has = function has(key) { 3257 return key in this.set_; 3258 }; 3259 3260 _proto["delete"] = function _delete(key) { 3261 var has = this.has(key); 3262 delete this.set_[key]; 3263 return has; 3264 }; 3265 3266 _proto.add = function add(key) { 3267 this.set_[key] = 1; 3268 return this; 3269 }; 3270 3271 _proto.forEach = function forEach(callback, thisArg) { 3272 for (var key in this.set_) { 3273 callback.call(thisArg, key, key, this); 3274 } 3275 }; 3276 3277 return SetSham; 3278 }(); 3279 3280 var Set = window$1.Set ? window$1.Set : SetSham; 3281 3282 /** 3283 * Player Component - Base class for all UI objects 3284 * 3285 * @file component.js 3286 */ 3287 /** 3288 * Base class for all UI Components. 3289 * Components are UI objects which represent both a javascript object and an element 3290 * in the DOM. They can be children of other components, and can have 3291 * children themselves. 3292 * 3293 * Components can also use methods from {@link EventTarget} 3294 */ 3295 3296 var Component = /*#__PURE__*/function () { 3297 /** 3298 * A callback that is called when a component is ready. Does not have any 3299 * paramters and any callback value will be ignored. 3300 * 3301 * @callback Component~ReadyCallback 3302 * @this Component 3303 */ 3304 3305 /** 3306 * Creates an instance of this class. 3307 * 3308 * @param {Player} player 3309 * The `Player` that this class should be attached to. 3310 * 3311 * @param {Object} [options] 3312 * The key/value store of player options. 3313 * 3314 * @param {Object[]} [options.children] 3315 * An array of children objects to intialize this component with. Children objects have 3316 * a name property that will be used if more than one component of the same type needs to be 3317 * added. 3318 * 3319 * @param {Component~ReadyCallback} [ready] 3320 * Function that gets called when the `Component` is ready. 3321 */ 3322 function Component(player, options, ready) { 3323 // The component might be the player itself and we can't pass `this` to super 3324 if (!player && this.play) { 3325 this.player_ = player = this; // eslint-disable-line 3326 } else { 3327 this.player_ = player; 3328 } 3329 3330 this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method 3331 3332 this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults 3333 3334 this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options 3335 3336 options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied 3337 3338 this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one 3339 3340 if (!this.id_) { 3341 // Don't require the player ID function in the case of mock players 3342 var id = player && player.id && player.id() || 'no_player'; 3343 this.id_ = id + "_component_" + newGUID(); 3344 } 3345 3346 this.name_ = options.name || null; // Create element if one wasn't provided in options 3347 3348 if (options.el) { 3349 this.el_ = options.el; 3350 } else if (options.createEl !== false) { 3351 this.el_ = this.createEl(); 3352 } // if evented is anything except false, we want to mixin in evented 3353 3354 3355 if (options.evented !== false) { 3356 // Make this an evented object and use `el_`, if available, as its event bus 3357 evented(this, { 3358 eventBusKey: this.el_ ? 'el_' : null 3359 }); 3360 } 3361 3362 stateful(this, this.constructor.defaultState); 3363 this.children_ = []; 3364 this.childIndex_ = {}; 3365 this.childNameIndex_ = {}; 3366 this.setTimeoutIds_ = new Set(); 3367 this.setIntervalIds_ = new Set(); 3368 this.rafIds_ = new Set(); 3369 this.namedRafs_ = new Map$1(); 3370 this.clearingTimersOnDispose_ = false; // Add any child components in options 3371 3372 if (options.initChildren !== false) { 3373 this.initChildren(); 3374 } 3375 3376 this.ready(ready); // Don't want to trigger ready here or it will before init is actually 3377 // finished for all children that run this constructor 3378 3379 if (options.reportTouchActivity !== false) { 3380 this.enableTouchActivity(); 3381 } 3382 } 3383 /** 3384 * Dispose of the `Component` and all child components. 3385 * 3386 * @fires Component#dispose 3387 */ 3388 3389 3390 var _proto = Component.prototype; 3391 3392 _proto.dispose = function dispose() { 3393 // Bail out if the component has already been disposed. 3394 if (this.isDisposed_) { 3395 return; 3396 } 3397 /** 3398 * Triggered when a `Component` is disposed. 3399 * 3400 * @event Component#dispose 3401 * @type {EventTarget~Event} 3402 * 3403 * @property {boolean} [bubbles=false] 3404 * set to false so that the dispose event does not 3405 * bubble up 3406 */ 3407 3408 3409 this.trigger({ 3410 type: 'dispose', 3411 bubbles: false 3412 }); 3413 this.isDisposed_ = true; // Dispose all children. 3414 3415 if (this.children_) { 3416 for (var i = this.children_.length - 1; i >= 0; i--) { 3417 if (this.children_[i].dispose) { 3418 this.children_[i].dispose(); 3419 } 3420 } 3421 } // Delete child references 3422 3423 3424 this.children_ = null; 3425 this.childIndex_ = null; 3426 this.childNameIndex_ = null; 3427 this.parentComponent_ = null; 3428 3429 if (this.el_) { 3430 // Remove element from DOM 3431 if (this.el_.parentNode) { 3432 this.el_.parentNode.removeChild(this.el_); 3433 } 3434 3435 if (DomData.has(this.el_)) { 3436 DomData["delete"](this.el_); 3437 } 3438 3439 this.el_ = null; 3440 } // remove reference to the player after disposing of the element 3441 3442 3443 this.player_ = null; 3444 } 3445 /** 3446 * Determine whether or not this component has been disposed. 3447 * 3448 * @return {boolean} 3449 * If the component has been disposed, will be `true`. Otherwise, `false`. 3450 */ 3451 ; 3452 3453 _proto.isDisposed = function isDisposed() { 3454 return Boolean(this.isDisposed_); 3455 } 3456 /** 3457 * Return the {@link Player} that the `Component` has attached to. 3458 * 3459 * @return {Player} 3460 * The player that this `Component` has attached to. 3461 */ 3462 ; 3463 3464 _proto.player = function player() { 3465 return this.player_; 3466 } 3467 /** 3468 * Deep merge of options objects with new options. 3469 * > Note: When both `obj` and `options` contain properties whose values are objects. 3470 * The two properties get merged using {@link module:mergeOptions} 3471 * 3472 * @param {Object} obj 3473 * The object that contains new options. 3474 * 3475 * @return {Object} 3476 * A new object of `this.options_` and `obj` merged together. 3477 */ 3478 ; 3479 3480 _proto.options = function options(obj) { 3481 if (!obj) { 3482 return this.options_; 3483 } 3484 3485 this.options_ = mergeOptions(this.options_, obj); 3486 return this.options_; 3487 } 3488 /** 3489 * Get the `Component`s DOM element 3490 * 3491 * @return {Element} 3492 * The DOM element for this `Component`. 3493 */ 3494 ; 3495 3496 _proto.el = function el() { 3497 return this.el_; 3498 } 3499 /** 3500 * Create the `Component`s DOM element. 3501 * 3502 * @param {string} [tagName] 3503 * Element's DOM node type. e.g. 'div' 3504 * 3505 * @param {Object} [properties] 3506 * An object of properties that should be set. 3507 * 3508 * @param {Object} [attributes] 3509 * An object of attributes that should be set. 3510 * 3511 * @return {Element} 3512 * The element that gets created. 3513 */ 3514 ; 3515 3516 _proto.createEl = function createEl$1(tagName, properties, attributes) { 3517 return createEl(tagName, properties, attributes); 3518 } 3519 /** 3520 * Localize a string given the string in english. 3521 * 3522 * If tokens are provided, it'll try and run a simple token replacement on the provided string. 3523 * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. 3524 * 3525 * If a `defaultValue` is provided, it'll use that over `string`, 3526 * if a value isn't found in provided language files. 3527 * This is useful if you want to have a descriptive key for token replacement 3528 * but have a succinct localized string and not require `en.json` to be included. 3529 * 3530 * Currently, it is used for the progress bar timing. 3531 * ```js 3532 * { 3533 * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" 3534 * } 3535 * ``` 3536 * It is then used like so: 3537 * ```js 3538 * this.localize('progress bar timing: currentTime={1} duration{2}', 3539 * [this.player_.currentTime(), this.player_.duration()], 3540 * '{1} of {2}'); 3541 * ``` 3542 * 3543 * Which outputs something like: `01:23 of 24:56`. 3544 * 3545 * 3546 * @param {string} string 3547 * The string to localize and the key to lookup in the language files. 3548 * @param {string[]} [tokens] 3549 * If the current item has token replacements, provide the tokens here. 3550 * @param {string} [defaultValue] 3551 * Defaults to `string`. Can be a default value to use for token replacement 3552 * if the lookup key is needed to be separate. 3553 * 3554 * @return {string} 3555 * The localized string or if no localization exists the english string. 3556 */ 3557 ; 3558 3559 _proto.localize = function localize(string, tokens, defaultValue) { 3560 if (defaultValue === void 0) { 3561 defaultValue = string; 3562 } 3563 3564 var code = this.player_.language && this.player_.language(); 3565 var languages = this.player_.languages && this.player_.languages(); 3566 var language = languages && languages[code]; 3567 var primaryCode = code && code.split('-')[0]; 3568 var primaryLang = languages && languages[primaryCode]; 3569 var localizedString = defaultValue; 3570 3571 if (language && language[string]) { 3572 localizedString = language[string]; 3573 } else if (primaryLang && primaryLang[string]) { 3574 localizedString = primaryLang[string]; 3575 } 3576 3577 if (tokens) { 3578 localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { 3579 var value = tokens[index - 1]; 3580 var ret = value; 3581 3582 if (typeof value === 'undefined') { 3583 ret = match; 3584 } 3585 3586 return ret; 3587 }); 3588 } 3589 3590 return localizedString; 3591 } 3592 /** 3593 * Return the `Component`s DOM element. This is where children get inserted. 3594 * This will usually be the the same as the element returned in {@link Component#el}. 3595 * 3596 * @return {Element} 3597 * The content element for this `Component`. 3598 */ 3599 ; 3600 3601 _proto.contentEl = function contentEl() { 3602 return this.contentEl_ || this.el_; 3603 } 3604 /** 3605 * Get this `Component`s ID 3606 * 3607 * @return {string} 3608 * The id of this `Component` 3609 */ 3610 ; 3611 3612 _proto.id = function id() { 3613 return this.id_; 3614 } 3615 /** 3616 * Get the `Component`s name. The name gets used to reference the `Component` 3617 * and is set during registration. 3618 * 3619 * @return {string} 3620 * The name of this `Component`. 3621 */ 3622 ; 3623 3624 _proto.name = function name() { 3625 return this.name_; 3626 } 3627 /** 3628 * Get an array of all child components 3629 * 3630 * @return {Array} 3631 * The children 3632 */ 3633 ; 3634 3635 _proto.children = function children() { 3636 return this.children_; 3637 } 3638 /** 3639 * Returns the child `Component` with the given `id`. 3640 * 3641 * @param {string} id 3642 * The id of the child `Component` to get. 3643 * 3644 * @return {Component|undefined} 3645 * The child `Component` with the given `id` or undefined. 3646 */ 3647 ; 3648 3649 _proto.getChildById = function getChildById(id) { 3650 return this.childIndex_[id]; 3651 } 3652 /** 3653 * Returns the child `Component` with the given `name`. 3654 * 3655 * @param {string} name 3656 * The name of the child `Component` to get. 3657 * 3658 * @return {Component|undefined} 3659 * The child `Component` with the given `name` or undefined. 3660 */ 3661 ; 3662 3663 _proto.getChild = function getChild(name) { 3664 if (!name) { 3665 return; 3666 } 3667 3668 return this.childNameIndex_[name]; 3669 } 3670 /** 3671 * Returns the descendant `Component` following the givent 3672 * descendant `names`. For instance ['foo', 'bar', 'baz'] would 3673 * try to get 'foo' on the current component, 'bar' on the 'foo' 3674 * component and 'baz' on the 'bar' component and return undefined 3675 * if any of those don't exist. 3676 * 3677 * @param {...string[]|...string} names 3678 * The name of the child `Component` to get. 3679 * 3680 * @return {Component|undefined} 3681 * The descendant `Component` following the given descendant 3682 * `names` or undefined. 3683 */ 3684 ; 3685 3686 _proto.getDescendant = function getDescendant() { 3687 for (var _len = arguments.length, names = new Array(_len), _key = 0; _key < _len; _key++) { 3688 names[_key] = arguments[_key]; 3689 } 3690 3691 // flatten array argument into the main array 3692 names = names.reduce(function (acc, n) { 3693 return acc.concat(n); 3694 }, []); 3695 var currentChild = this; 3696 3697 for (var i = 0; i < names.length; i++) { 3698 currentChild = currentChild.getChild(names[i]); 3699 3700 if (!currentChild || !currentChild.getChild) { 3701 return; 3702 } 3703 } 3704 3705 return currentChild; 3706 } 3707 /** 3708 * Add a child `Component` inside the current `Component`. 3709 * 3710 * 3711 * @param {string|Component} child 3712 * The name or instance of a child to add. 3713 * 3714 * @param {Object} [options={}] 3715 * The key/value store of options that will get passed to children of 3716 * the child. 3717 * 3718 * @param {number} [index=this.children_.length] 3719 * The index to attempt to add a child into. 3720 * 3721 * @return {Component} 3722 * The `Component` that gets added as a child. When using a string the 3723 * `Component` will get created by this process. 3724 */ 3725 ; 3726 3727 _proto.addChild = function addChild(child, options, index) { 3728 if (options === void 0) { 3729 options = {}; 3730 } 3731 3732 if (index === void 0) { 3733 index = this.children_.length; 3734 } 3735 3736 var component; 3737 var componentName; // If child is a string, create component with options 3738 3739 if (typeof child === 'string') { 3740 componentName = toTitleCase(child); 3741 var componentClassName = options.componentClass || componentName; // Set name through options 3742 3743 options.name = componentName; // Create a new object & element for this controls set 3744 // If there's no .player_, this is a player 3745 3746 var ComponentClass = Component.getComponent(componentClassName); 3747 3748 if (!ComponentClass) { 3749 throw new Error("Component " + componentClassName + " does not exist"); 3750 } // data stored directly on the videojs object may be 3751 // misidentified as a component to retain 3752 // backwards-compatibility with 4.x. check to make sure the 3753 // component class can be instantiated. 3754 3755 3756 if (typeof ComponentClass !== 'function') { 3757 return null; 3758 } 3759 3760 component = new ComponentClass(this.player_ || this, options); // child is a component instance 3761 } else { 3762 component = child; 3763 } 3764 3765 if (component.parentComponent_) { 3766 component.parentComponent_.removeChild(component); 3767 } 3768 3769 this.children_.splice(index, 0, component); 3770 component.parentComponent_ = this; 3771 3772 if (typeof component.id === 'function') { 3773 this.childIndex_[component.id()] = component; 3774 } // If a name wasn't used to create the component, check if we can use the 3775 // name function of the component 3776 3777 3778 componentName = componentName || component.name && toTitleCase(component.name()); 3779 3780 if (componentName) { 3781 this.childNameIndex_[componentName] = component; 3782 this.childNameIndex_[toLowerCase(componentName)] = component; 3783 } // Add the UI object's element to the container div (box) 3784 // Having an element is not required 3785 3786 3787 if (typeof component.el === 'function' && component.el()) { 3788 // If inserting before a component, insert before that component's element 3789 var refNode = null; 3790 3791 if (this.children_[index + 1]) { 3792 // Most children are components, but the video tech is an HTML element 3793 if (this.children_[index + 1].el_) { 3794 refNode = this.children_[index + 1].el_; 3795 } else if (isEl(this.children_[index + 1])) { 3796 refNode = this.children_[index + 1]; 3797 } 3798 } 3799 3800 this.contentEl().insertBefore(component.el(), refNode); 3801 } // Return so it can stored on parent object if desired. 3802 3803 3804 return component; 3805 } 3806 /** 3807 * Remove a child `Component` from this `Component`s list of children. Also removes 3808 * the child `Component`s element from this `Component`s element. 3809 * 3810 * @param {Component} component 3811 * The child `Component` to remove. 3812 */ 3813 ; 3814 3815 _proto.removeChild = function removeChild(component) { 3816 if (typeof component === 'string') { 3817 component = this.getChild(component); 3818 } 3819 3820 if (!component || !this.children_) { 3821 return; 3822 } 3823 3824 var childFound = false; 3825 3826 for (var i = this.children_.length - 1; i >= 0; i--) { 3827 if (this.children_[i] === component) { 3828 childFound = true; 3829 this.children_.splice(i, 1); 3830 break; 3831 } 3832 } 3833 3834 if (!childFound) { 3835 return; 3836 } 3837 3838 component.parentComponent_ = null; 3839 this.childIndex_[component.id()] = null; 3840 this.childNameIndex_[toTitleCase(component.name())] = null; 3841 this.childNameIndex_[toLowerCase(component.name())] = null; 3842 var compEl = component.el(); 3843 3844 if (compEl && compEl.parentNode === this.contentEl()) { 3845 this.contentEl().removeChild(component.el()); 3846 } 3847 } 3848 /** 3849 * Add and initialize default child `Component`s based upon options. 3850 */ 3851 ; 3852 3853 _proto.initChildren = function initChildren() { 3854 var _this = this; 3855 3856 var children = this.options_.children; 3857 3858 if (children) { 3859 // `this` is `parent` 3860 var parentOptions = this.options_; 3861 3862 var handleAdd = function handleAdd(child) { 3863 var name = child.name; 3864 var opts = child.opts; // Allow options for children to be set at the parent options 3865 // e.g. videojs(id, { controlBar: false }); 3866 // instead of videojs(id, { children: { controlBar: false }); 3867 3868 if (parentOptions[name] !== undefined) { 3869 opts = parentOptions[name]; 3870 } // Allow for disabling default components 3871 // e.g. options['children']['posterImage'] = false 3872 3873 3874 if (opts === false) { 3875 return; 3876 } // Allow options to be passed as a simple boolean if no configuration 3877 // is necessary. 3878 3879 3880 if (opts === true) { 3881 opts = {}; 3882 } // We also want to pass the original player options 3883 // to each component as well so they don't need to 3884 // reach back into the player for options later. 3885 3886 3887 opts.playerOptions = _this.options_.playerOptions; // Create and add the child component. 3888 // Add a direct reference to the child by name on the parent instance. 3889 // If two of the same component are used, different names should be supplied 3890 // for each 3891 3892 var newChild = _this.addChild(name, opts); 3893 3894 if (newChild) { 3895 _this[name] = newChild; 3896 } 3897 }; // Allow for an array of children details to passed in the options 3898 3899 3900 var workingChildren; 3901 var Tech = Component.getComponent('Tech'); 3902 3903 if (Array.isArray(children)) { 3904 workingChildren = children; 3905 } else { 3906 workingChildren = Object.keys(children); 3907 } 3908 3909 workingChildren // children that are in this.options_ but also in workingChildren would 3910 // give us extra children we do not want. So, we want to filter them out. 3911 .concat(Object.keys(this.options_).filter(function (child) { 3912 return !workingChildren.some(function (wchild) { 3913 if (typeof wchild === 'string') { 3914 return child === wchild; 3915 } 3916 3917 return child === wchild.name; 3918 }); 3919 })).map(function (child) { 3920 var name; 3921 var opts; 3922 3923 if (typeof child === 'string') { 3924 name = child; 3925 opts = children[name] || _this.options_[name] || {}; 3926 } else { 3927 name = child.name; 3928 opts = child; 3929 } 3930 3931 return { 3932 name: name, 3933 opts: opts 3934 }; 3935 }).filter(function (child) { 3936 // we have to make sure that child.name isn't in the techOrder since 3937 // techs are registerd as Components but can't aren't compatible 3938 // See https://github.com/videojs/video.js/issues/2772 3939 var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); 3940 return c && !Tech.isTech(c); 3941 }).forEach(handleAdd); 3942 } 3943 } 3944 /** 3945 * Builds the default DOM class name. Should be overriden by sub-components. 3946 * 3947 * @return {string} 3948 * The DOM class name for this object. 3949 * 3950 * @abstract 3951 */ 3952 ; 3953 3954 _proto.buildCSSClass = function buildCSSClass() { 3955 // Child classes can include a function that does: 3956 // return 'CLASS NAME' + this._super(); 3957 return ''; 3958 } 3959 /** 3960 * Bind a listener to the component's ready state. 3961 * Different from event listeners in that if the ready event has already happened 3962 * it will trigger the function immediately. 3963 * 3964 * @return {Component} 3965 * Returns itself; method can be chained. 3966 */ 3967 ; 3968 3969 _proto.ready = function ready(fn, sync) { 3970 if (sync === void 0) { 3971 sync = false; 3972 } 3973 3974 if (!fn) { 3975 return; 3976 } 3977 3978 if (!this.isReady_) { 3979 this.readyQueue_ = this.readyQueue_ || []; 3980 this.readyQueue_.push(fn); 3981 return; 3982 } 3983 3984 if (sync) { 3985 fn.call(this); 3986 } else { 3987 // Call the function asynchronously by default for consistency 3988 this.setTimeout(fn, 1); 3989 } 3990 } 3991 /** 3992 * Trigger all the ready listeners for this `Component`. 3993 * 3994 * @fires Component#ready 3995 */ 3996 ; 3997 3998 _proto.triggerReady = function triggerReady() { 3999 this.isReady_ = true; // Ensure ready is triggered asynchronously 4000 4001 this.setTimeout(function () { 4002 var readyQueue = this.readyQueue_; // Reset Ready Queue 4003 4004 this.readyQueue_ = []; 4005 4006 if (readyQueue && readyQueue.length > 0) { 4007 readyQueue.forEach(function (fn) { 4008 fn.call(this); 4009 }, this); 4010 } // Allow for using event listeners also 4011 4012 /** 4013 * Triggered when a `Component` is ready. 4014 * 4015 * @event Component#ready 4016 * @type {EventTarget~Event} 4017 */ 4018 4019 4020 this.trigger('ready'); 4021 }, 1); 4022 } 4023 /** 4024 * Find a single DOM element matching a `selector`. This can be within the `Component`s 4025 * `contentEl()` or another custom context. 4026 * 4027 * @param {string} selector 4028 * A valid CSS selector, which will be passed to `querySelector`. 4029 * 4030 * @param {Element|string} [context=this.contentEl()] 4031 * A DOM element within which to query. Can also be a selector string in 4032 * which case the first matching element will get used as context. If 4033 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 4034 * nothing it falls back to `document`. 4035 * 4036 * @return {Element|null} 4037 * the dom element that was found, or null 4038 * 4039 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 4040 */ 4041 ; 4042 4043 _proto.$ = function $$1(selector, context) { 4044 return $(selector, context || this.contentEl()); 4045 } 4046 /** 4047 * Finds all DOM element matching a `selector`. This can be within the `Component`s 4048 * `contentEl()` or another custom context. 4049 * 4050 * @param {string} selector 4051 * A valid CSS selector, which will be passed to `querySelectorAll`. 4052 * 4053 * @param {Element|string} [context=this.contentEl()] 4054 * A DOM element within which to query. Can also be a selector string in 4055 * which case the first matching element will get used as context. If 4056 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 4057 * nothing it falls back to `document`. 4058 * 4059 * @return {NodeList} 4060 * a list of dom elements that were found 4061 * 4062 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 4063 */ 4064 ; 4065 4066 _proto.$$ = function $$$1(selector, context) { 4067 return $$(selector, context || this.contentEl()); 4068 } 4069 /** 4070 * Check if a component's element has a CSS class name. 4071 * 4072 * @param {string} classToCheck 4073 * CSS class name to check. 4074 * 4075 * @return {boolean} 4076 * - True if the `Component` has the class. 4077 * - False if the `Component` does not have the class` 4078 */ 4079 ; 4080 4081 _proto.hasClass = function hasClass$1(classToCheck) { 4082 return hasClass(this.el_, classToCheck); 4083 } 4084 /** 4085 * Add a CSS class name to the `Component`s element. 4086 * 4087 * @param {string} classToAdd 4088 * CSS class name to add 4089 */ 4090 ; 4091 4092 _proto.addClass = function addClass$1(classToAdd) { 4093 addClass(this.el_, classToAdd); 4094 } 4095 /** 4096 * Remove a CSS class name from the `Component`s element. 4097 * 4098 * @param {string} classToRemove 4099 * CSS class name to remove 4100 */ 4101 ; 4102 4103 _proto.removeClass = function removeClass$1(classToRemove) { 4104 removeClass(this.el_, classToRemove); 4105 } 4106 /** 4107 * Add or remove a CSS class name from the component's element. 4108 * - `classToToggle` gets added when {@link Component#hasClass} would return false. 4109 * - `classToToggle` gets removed when {@link Component#hasClass} would return true. 4110 * 4111 * @param {string} classToToggle 4112 * The class to add or remove based on (@link Component#hasClass} 4113 * 4114 * @param {boolean|Dom~predicate} [predicate] 4115 * An {@link Dom~predicate} function or a boolean 4116 */ 4117 ; 4118 4119 _proto.toggleClass = function toggleClass$1(classToToggle, predicate) { 4120 toggleClass(this.el_, classToToggle, predicate); 4121 } 4122 /** 4123 * Show the `Component`s element if it is hidden by removing the 4124 * 'vjs-hidden' class name from it. 4125 */ 4126 ; 4127 4128 _proto.show = function show() { 4129 this.removeClass('vjs-hidden'); 4130 } 4131 /** 4132 * Hide the `Component`s element if it is currently showing by adding the 4133 * 'vjs-hidden` class name to it. 4134 */ 4135 ; 4136 4137 _proto.hide = function hide() { 4138 this.addClass('vjs-hidden'); 4139 } 4140 /** 4141 * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' 4142 * class name to it. Used during fadeIn/fadeOut. 4143 * 4144 * @private 4145 */ 4146 ; 4147 4148 _proto.lockShowing = function lockShowing() { 4149 this.addClass('vjs-lock-showing'); 4150 } 4151 /** 4152 * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' 4153 * class name from it. Used during fadeIn/fadeOut. 4154 * 4155 * @private 4156 */ 4157 ; 4158 4159 _proto.unlockShowing = function unlockShowing() { 4160 this.removeClass('vjs-lock-showing'); 4161 } 4162 /** 4163 * Get the value of an attribute on the `Component`s element. 4164 * 4165 * @param {string} attribute 4166 * Name of the attribute to get the value from. 4167 * 4168 * @return {string|null} 4169 * - The value of the attribute that was asked for. 4170 * - Can be an empty string on some browsers if the attribute does not exist 4171 * or has no value 4172 * - Most browsers will return null if the attibute does not exist or has 4173 * no value. 4174 * 4175 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} 4176 */ 4177 ; 4178 4179 _proto.getAttribute = function getAttribute$1(attribute) { 4180 return getAttribute(this.el_, attribute); 4181 } 4182 /** 4183 * Set the value of an attribute on the `Component`'s element 4184 * 4185 * @param {string} attribute 4186 * Name of the attribute to set. 4187 * 4188 * @param {string} value 4189 * Value to set the attribute to. 4190 * 4191 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} 4192 */ 4193 ; 4194 4195 _proto.setAttribute = function setAttribute$1(attribute, value) { 4196 setAttribute(this.el_, attribute, value); 4197 } 4198 /** 4199 * Remove an attribute from the `Component`s element. 4200 * 4201 * @param {string} attribute 4202 * Name of the attribute to remove. 4203 * 4204 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} 4205 */ 4206 ; 4207 4208 _proto.removeAttribute = function removeAttribute$1(attribute) { 4209 removeAttribute(this.el_, attribute); 4210 } 4211 /** 4212 * Get or set the width of the component based upon the CSS styles. 4213 * See {@link Component#dimension} for more detailed information. 4214 * 4215 * @param {number|string} [num] 4216 * The width that you want to set postfixed with '%', 'px' or nothing. 4217 * 4218 * @param {boolean} [skipListeners] 4219 * Skip the componentresize event trigger 4220 * 4221 * @return {number|string} 4222 * The width when getting, zero if there is no width. Can be a string 4223 * postpixed with '%' or 'px'. 4224 */ 4225 ; 4226 4227 _proto.width = function width(num, skipListeners) { 4228 return this.dimension('width', num, skipListeners); 4229 } 4230 /** 4231 * Get or set the height of the component based upon the CSS styles. 4232 * See {@link Component#dimension} for more detailed information. 4233 * 4234 * @param {number|string} [num] 4235 * The height that you want to set postfixed with '%', 'px' or nothing. 4236 * 4237 * @param {boolean} [skipListeners] 4238 * Skip the componentresize event trigger 4239 * 4240 * @return {number|string} 4241 * The width when getting, zero if there is no width. Can be a string 4242 * postpixed with '%' or 'px'. 4243 */ 4244 ; 4245 4246 _proto.height = function height(num, skipListeners) { 4247 return this.dimension('height', num, skipListeners); 4248 } 4249 /** 4250 * Set both the width and height of the `Component` element at the same time. 4251 * 4252 * @param {number|string} width 4253 * Width to set the `Component`s element to. 4254 * 4255 * @param {number|string} height 4256 * Height to set the `Component`s element to. 4257 */ 4258 ; 4259 4260 _proto.dimensions = function dimensions(width, height) { 4261 // Skip componentresize listeners on width for optimization 4262 this.width(width, true); 4263 this.height(height); 4264 } 4265 /** 4266 * Get or set width or height of the `Component` element. This is the shared code 4267 * for the {@link Component#width} and {@link Component#height}. 4268 * 4269 * Things to know: 4270 * - If the width or height in an number this will return the number postfixed with 'px'. 4271 * - If the width/height is a percent this will return the percent postfixed with '%' 4272 * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function 4273 * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. 4274 * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} 4275 * for more information 4276 * - If you want the computed style of the component, use {@link Component#currentWidth} 4277 * and {@link {Component#currentHeight} 4278 * 4279 * @fires Component#componentresize 4280 * 4281 * @param {string} widthOrHeight 4282 8 'width' or 'height' 4283 * 4284 * @param {number|string} [num] 4285 8 New dimension 4286 * 4287 * @param {boolean} [skipListeners] 4288 * Skip componentresize event trigger 4289 * 4290 * @return {number} 4291 * The dimension when getting or 0 if unset 4292 */ 4293 ; 4294 4295 _proto.dimension = function dimension(widthOrHeight, num, skipListeners) { 4296 if (num !== undefined) { 4297 // Set to zero if null or literally NaN (NaN !== NaN) 4298 if (num === null || num !== num) { 4299 num = 0; 4300 } // Check if using css width/height (% or px) and adjust 4301 4302 4303 if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { 4304 this.el_.style[widthOrHeight] = num; 4305 } else if (num === 'auto') { 4306 this.el_.style[widthOrHeight] = ''; 4307 } else { 4308 this.el_.style[widthOrHeight] = num + 'px'; 4309 } // skipListeners allows us to avoid triggering the resize event when setting both width and height 4310 4311 4312 if (!skipListeners) { 4313 /** 4314 * Triggered when a component is resized. 4315 * 4316 * @event Component#componentresize 4317 * @type {EventTarget~Event} 4318 */ 4319 this.trigger('componentresize'); 4320 } 4321 4322 return; 4323 } // Not setting a value, so getting it 4324 // Make sure element exists 4325 4326 4327 if (!this.el_) { 4328 return 0; 4329 } // Get dimension value from style 4330 4331 4332 var val = this.el_.style[widthOrHeight]; 4333 var pxIndex = val.indexOf('px'); 4334 4335 if (pxIndex !== -1) { 4336 // Return the pixel value with no 'px' 4337 return parseInt(val.slice(0, pxIndex), 10); 4338 } // No px so using % or no style was set, so falling back to offsetWidth/height 4339 // If component has display:none, offset will return 0 4340 // TODO: handle display:none and no dimension style using px 4341 4342 4343 return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); 4344 } 4345 /** 4346 * Get the computed width or the height of the component's element. 4347 * 4348 * Uses `window.getComputedStyle`. 4349 * 4350 * @param {string} widthOrHeight 4351 * A string containing 'width' or 'height'. Whichever one you want to get. 4352 * 4353 * @return {number} 4354 * The dimension that gets asked for or 0 if nothing was set 4355 * for that dimension. 4356 */ 4357 ; 4358 4359 _proto.currentDimension = function currentDimension(widthOrHeight) { 4360 var computedWidthOrHeight = 0; 4361 4362 if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { 4363 throw new Error('currentDimension only accepts width or height value'); 4364 } 4365 4366 computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer 4367 4368 computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying 4369 // and we want to check the offset values. 4370 // This code also runs wherever getComputedStyle doesn't exist. 4371 4372 if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) { 4373 var rule = "offset" + toTitleCase(widthOrHeight); 4374 computedWidthOrHeight = this.el_[rule]; 4375 } 4376 4377 return computedWidthOrHeight; 4378 } 4379 /** 4380 * An object that contains width and height values of the `Component`s 4381 * computed style. Uses `window.getComputedStyle`. 4382 * 4383 * @typedef {Object} Component~DimensionObject 4384 * 4385 * @property {number} width 4386 * The width of the `Component`s computed style. 4387 * 4388 * @property {number} height 4389 * The height of the `Component`s computed style. 4390 */ 4391 4392 /** 4393 * Get an object that contains computed width and height values of the 4394 * component's element. 4395 * 4396 * Uses `window.getComputedStyle`. 4397 * 4398 * @return {Component~DimensionObject} 4399 * The computed dimensions of the component's element. 4400 */ 4401 ; 4402 4403 _proto.currentDimensions = function currentDimensions() { 4404 return { 4405 width: this.currentDimension('width'), 4406 height: this.currentDimension('height') 4407 }; 4408 } 4409 /** 4410 * Get the computed width of the component's element. 4411 * 4412 * Uses `window.getComputedStyle`. 4413 * 4414 * @return {number} 4415 * The computed width of the component's element. 4416 */ 4417 ; 4418 4419 _proto.currentWidth = function currentWidth() { 4420 return this.currentDimension('width'); 4421 } 4422 /** 4423 * Get the computed height of the component's element. 4424 * 4425 * Uses `window.getComputedStyle`. 4426 * 4427 * @return {number} 4428 * The computed height of the component's element. 4429 */ 4430 ; 4431 4432 _proto.currentHeight = function currentHeight() { 4433 return this.currentDimension('height'); 4434 } 4435 /** 4436 * Set the focus to this component 4437 */ 4438 ; 4439 4440 _proto.focus = function focus() { 4441 this.el_.focus(); 4442 } 4443 /** 4444 * Remove the focus from this component 4445 */ 4446 ; 4447 4448 _proto.blur = function blur() { 4449 this.el_.blur(); 4450 } 4451 /** 4452 * When this Component receives a `keydown` event which it does not process, 4453 * it passes the event to the Player for handling. 4454 * 4455 * @param {EventTarget~Event} event 4456 * The `keydown` event that caused this function to be called. 4457 */ 4458 ; 4459 4460 _proto.handleKeyDown = function handleKeyDown(event) { 4461 if (this.player_) { 4462 // We only stop propagation here because we want unhandled events to fall 4463 // back to the browser. 4464 event.stopPropagation(); 4465 this.player_.handleKeyDown(event); 4466 } 4467 } 4468 /** 4469 * Many components used to have a `handleKeyPress` method, which was poorly 4470 * named because it listened to a `keydown` event. This method name now 4471 * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` 4472 * will not see their method calls stop working. 4473 * 4474 * @param {EventTarget~Event} event 4475 * The event that caused this function to be called. 4476 */ 4477 ; 4478 4479 _proto.handleKeyPress = function handleKeyPress(event) { 4480 this.handleKeyDown(event); 4481 } 4482 /** 4483 * Emit a 'tap' events when touch event support gets detected. This gets used to 4484 * support toggling the controls through a tap on the video. They get enabled 4485 * because every sub-component would have extra overhead otherwise. 4486 * 4487 * @private 4488 * @fires Component#tap 4489 * @listens Component#touchstart 4490 * @listens Component#touchmove 4491 * @listens Component#touchleave 4492 * @listens Component#touchcancel 4493 * @listens Component#touchend 4494 */ 4495 ; 4496 4497 _proto.emitTapEvents = function emitTapEvents() { 4498 // Track the start time so we can determine how long the touch lasted 4499 var touchStart = 0; 4500 var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap 4501 // Other popular libs use anywhere from 2 (hammer.js) to 15, 4502 // so 10 seems like a nice, round number. 4503 4504 var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap 4505 4506 var touchTimeThreshold = 200; 4507 var couldBeTap; 4508 this.on('touchstart', function (event) { 4509 // If more than one finger, don't consider treating this as a click 4510 if (event.touches.length === 1) { 4511 // Copy pageX/pageY from the object 4512 firstTouch = { 4513 pageX: event.touches[0].pageX, 4514 pageY: event.touches[0].pageY 4515 }; // Record start time so we can detect a tap vs. "touch and hold" 4516 4517 touchStart = window$1.performance.now(); // Reset couldBeTap tracking 4518 4519 couldBeTap = true; 4520 } 4521 }); 4522 this.on('touchmove', function (event) { 4523 // If more than one finger, don't consider treating this as a click 4524 if (event.touches.length > 1) { 4525 couldBeTap = false; 4526 } else if (firstTouch) { 4527 // Some devices will throw touchmoves for all but the slightest of taps. 4528 // So, if we moved only a small distance, this could still be a tap 4529 var xdiff = event.touches[0].pageX - firstTouch.pageX; 4530 var ydiff = event.touches[0].pageY - firstTouch.pageY; 4531 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); 4532 4533 if (touchDistance > tapMovementThreshold) { 4534 couldBeTap = false; 4535 } 4536 } 4537 }); 4538 4539 var noTap = function noTap() { 4540 couldBeTap = false; 4541 }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s 4542 4543 4544 this.on('touchleave', noTap); 4545 this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate 4546 // event 4547 4548 this.on('touchend', function (event) { 4549 firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen 4550 4551 if (couldBeTap === true) { 4552 // Measure how long the touch lasted 4553 var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap 4554 4555 if (touchTime < touchTimeThreshold) { 4556 // Don't let browser turn this into a click 4557 event.preventDefault(); 4558 /** 4559 * Triggered when a `Component` is tapped. 4560 * 4561 * @event Component#tap 4562 * @type {EventTarget~Event} 4563 */ 4564 4565 this.trigger('tap'); // It may be good to copy the touchend event object and change the 4566 // type to tap, if the other event properties aren't exact after 4567 // Events.fixEvent runs (e.g. event.target) 4568 } 4569 } 4570 }); 4571 } 4572 /** 4573 * This function reports user activity whenever touch events happen. This can get 4574 * turned off by any sub-components that wants touch events to act another way. 4575 * 4576 * Report user touch activity when touch events occur. User activity gets used to 4577 * determine when controls should show/hide. It is simple when it comes to mouse 4578 * events, because any mouse event should show the controls. So we capture mouse 4579 * events that bubble up to the player and report activity when that happens. 4580 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player 4581 * controls. So touch events can't help us at the player level either. 4582 * 4583 * User activity gets checked asynchronously. So what could happen is a tap event 4584 * on the video turns the controls off. Then the `touchend` event bubbles up to 4585 * the player. Which, if it reported user activity, would turn the controls right 4586 * back on. We also don't want to completely block touch events from bubbling up. 4587 * Furthermore a `touchmove` event and anything other than a tap, should not turn 4588 * controls back on. 4589 * 4590 * @listens Component#touchstart 4591 * @listens Component#touchmove 4592 * @listens Component#touchend 4593 * @listens Component#touchcancel 4594 */ 4595 ; 4596 4597 _proto.enableTouchActivity = function enableTouchActivity() { 4598 // Don't continue if the root player doesn't support reporting user activity 4599 if (!this.player() || !this.player().reportUserActivity) { 4600 return; 4601 } // listener for reporting that the user is active 4602 4603 4604 var report = bind(this.player(), this.player().reportUserActivity); 4605 var touchHolding; 4606 this.on('touchstart', function () { 4607 report(); // For as long as the they are touching the device or have their mouse down, 4608 // we consider them active even if they're not moving their finger or mouse. 4609 // So we want to continue to update that they are active 4610 4611 this.clearInterval(touchHolding); // report at the same interval as activityCheck 4612 4613 touchHolding = this.setInterval(report, 250); 4614 }); 4615 4616 var touchEnd = function touchEnd(event) { 4617 report(); // stop the interval that maintains activity if the touch is holding 4618 4619 this.clearInterval(touchHolding); 4620 }; 4621 4622 this.on('touchmove', report); 4623 this.on('touchend', touchEnd); 4624 this.on('touchcancel', touchEnd); 4625 } 4626 /** 4627 * A callback that has no parameters and is bound into `Component`s context. 4628 * 4629 * @callback Component~GenericCallback 4630 * @this Component 4631 */ 4632 4633 /** 4634 * Creates a function that runs after an `x` millisecond timeout. This function is a 4635 * wrapper around `window.setTimeout`. There are a few reasons to use this one 4636 * instead though: 4637 * 1. It gets cleared via {@link Component#clearTimeout} when 4638 * {@link Component#dispose} gets called. 4639 * 2. The function callback will gets turned into a {@link Component~GenericCallback} 4640 * 4641 * > Note: You can't use `window.clearTimeout` on the id returned by this function. This 4642 * will cause its dispose listener not to get cleaned up! Please use 4643 * {@link Component#clearTimeout} or {@link Component#dispose} instead. 4644 * 4645 * @param {Component~GenericCallback} fn 4646 * The function that will be run after `timeout`. 4647 * 4648 * @param {number} timeout 4649 * Timeout in milliseconds to delay before executing the specified function. 4650 * 4651 * @return {number} 4652 * Returns a timeout ID that gets used to identify the timeout. It can also 4653 * get used in {@link Component#clearTimeout} to clear the timeout that 4654 * was set. 4655 * 4656 * @listens Component#dispose 4657 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} 4658 */ 4659 ; 4660 4661 _proto.setTimeout = function setTimeout(fn, timeout) { 4662 var _this2 = this; 4663 4664 // declare as variables so they are properly available in timeout function 4665 // eslint-disable-next-line 4666 var timeoutId; 4667 fn = bind(this, fn); 4668 this.clearTimersOnDispose_(); 4669 timeoutId = window$1.setTimeout(function () { 4670 if (_this2.setTimeoutIds_.has(timeoutId)) { 4671 _this2.setTimeoutIds_["delete"](timeoutId); 4672 } 4673 4674 fn(); 4675 }, timeout); 4676 this.setTimeoutIds_.add(timeoutId); 4677 return timeoutId; 4678 } 4679 /** 4680 * Clears a timeout that gets created via `window.setTimeout` or 4681 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} 4682 * use this function instead of `window.clearTimout`. If you don't your dispose 4683 * listener will not get cleaned up until {@link Component#dispose}! 4684 * 4685 * @param {number} timeoutId 4686 * The id of the timeout to clear. The return value of 4687 * {@link Component#setTimeout} or `window.setTimeout`. 4688 * 4689 * @return {number} 4690 * Returns the timeout id that was cleared. 4691 * 4692 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} 4693 */ 4694 ; 4695 4696 _proto.clearTimeout = function clearTimeout(timeoutId) { 4697 if (this.setTimeoutIds_.has(timeoutId)) { 4698 this.setTimeoutIds_["delete"](timeoutId); 4699 window$1.clearTimeout(timeoutId); 4700 } 4701 4702 return timeoutId; 4703 } 4704 /** 4705 * Creates a function that gets run every `x` milliseconds. This function is a wrapper 4706 * around `window.setInterval`. There are a few reasons to use this one instead though. 4707 * 1. It gets cleared via {@link Component#clearInterval} when 4708 * {@link Component#dispose} gets called. 4709 * 2. The function callback will be a {@link Component~GenericCallback} 4710 * 4711 * @param {Component~GenericCallback} fn 4712 * The function to run every `x` seconds. 4713 * 4714 * @param {number} interval 4715 * Execute the specified function every `x` milliseconds. 4716 * 4717 * @return {number} 4718 * Returns an id that can be used to identify the interval. It can also be be used in 4719 * {@link Component#clearInterval} to clear the interval. 4720 * 4721 * @listens Component#dispose 4722 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} 4723 */ 4724 ; 4725 4726 _proto.setInterval = function setInterval(fn, interval) { 4727 fn = bind(this, fn); 4728 this.clearTimersOnDispose_(); 4729 var intervalId = window$1.setInterval(fn, interval); 4730 this.setIntervalIds_.add(intervalId); 4731 return intervalId; 4732 } 4733 /** 4734 * Clears an interval that gets created via `window.setInterval` or 4735 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} 4736 * use this function instead of `window.clearInterval`. If you don't your dispose 4737 * listener will not get cleaned up until {@link Component#dispose}! 4738 * 4739 * @param {number} intervalId 4740 * The id of the interval to clear. The return value of 4741 * {@link Component#setInterval} or `window.setInterval`. 4742 * 4743 * @return {number} 4744 * Returns the interval id that was cleared. 4745 * 4746 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} 4747 */ 4748 ; 4749 4750 _proto.clearInterval = function clearInterval(intervalId) { 4751 if (this.setIntervalIds_.has(intervalId)) { 4752 this.setIntervalIds_["delete"](intervalId); 4753 window$1.clearInterval(intervalId); 4754 } 4755 4756 return intervalId; 4757 } 4758 /** 4759 * Queues up a callback to be passed to requestAnimationFrame (rAF), but 4760 * with a few extra bonuses: 4761 * 4762 * - Supports browsers that do not support rAF by falling back to 4763 * {@link Component#setTimeout}. 4764 * 4765 * - The callback is turned into a {@link Component~GenericCallback} (i.e. 4766 * bound to the component). 4767 * 4768 * - Automatic cancellation of the rAF callback is handled if the component 4769 * is disposed before it is called. 4770 * 4771 * @param {Component~GenericCallback} fn 4772 * A function that will be bound to this component and executed just 4773 * before the browser's next repaint. 4774 * 4775 * @return {number} 4776 * Returns an rAF ID that gets used to identify the timeout. It can 4777 * also be used in {@link Component#cancelAnimationFrame} to cancel 4778 * the animation frame callback. 4779 * 4780 * @listens Component#dispose 4781 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} 4782 */ 4783 ; 4784 4785 _proto.requestAnimationFrame = function requestAnimationFrame(fn) { 4786 var _this3 = this; 4787 4788 // Fall back to using a timer. 4789 if (!this.supportsRaf_) { 4790 return this.setTimeout(fn, 1000 / 60); 4791 } 4792 4793 this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function 4794 // eslint-disable-next-line 4795 4796 var id; 4797 fn = bind(this, fn); 4798 id = window$1.requestAnimationFrame(function () { 4799 if (_this3.rafIds_.has(id)) { 4800 _this3.rafIds_["delete"](id); 4801 } 4802 4803 fn(); 4804 }); 4805 this.rafIds_.add(id); 4806 return id; 4807 } 4808 /** 4809 * Request an animation frame, but only one named animation 4810 * frame will be queued. Another will never be added until 4811 * the previous one finishes. 4812 * 4813 * @param {string} name 4814 * The name to give this requestAnimationFrame 4815 * 4816 * @param {Component~GenericCallback} fn 4817 * A function that will be bound to this component and executed just 4818 * before the browser's next repaint. 4819 */ 4820 ; 4821 4822 _proto.requestNamedAnimationFrame = function requestNamedAnimationFrame(name, fn) { 4823 var _this4 = this; 4824 4825 if (this.namedRafs_.has(name)) { 4826 return; 4827 } 4828 4829 this.clearTimersOnDispose_(); 4830 fn = bind(this, fn); 4831 var id = this.requestAnimationFrame(function () { 4832 fn(); 4833 4834 if (_this4.namedRafs_.has(name)) { 4835 _this4.namedRafs_["delete"](name); 4836 } 4837 }); 4838 this.namedRafs_.set(name, id); 4839 return name; 4840 } 4841 /** 4842 * Cancels a current named animation frame if it exists. 4843 * 4844 * @param {string} name 4845 * The name of the requestAnimationFrame to cancel. 4846 */ 4847 ; 4848 4849 _proto.cancelNamedAnimationFrame = function cancelNamedAnimationFrame(name) { 4850 if (!this.namedRafs_.has(name)) { 4851 return; 4852 } 4853 4854 this.cancelAnimationFrame(this.namedRafs_.get(name)); 4855 this.namedRafs_["delete"](name); 4856 } 4857 /** 4858 * Cancels a queued callback passed to {@link Component#requestAnimationFrame} 4859 * (rAF). 4860 * 4861 * If you queue an rAF callback via {@link Component#requestAnimationFrame}, 4862 * use this function instead of `window.cancelAnimationFrame`. If you don't, 4863 * your dispose listener will not get cleaned up until {@link Component#dispose}! 4864 * 4865 * @param {number} id 4866 * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. 4867 * 4868 * @return {number} 4869 * Returns the rAF ID that was cleared. 4870 * 4871 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} 4872 */ 4873 ; 4874 4875 _proto.cancelAnimationFrame = function cancelAnimationFrame(id) { 4876 // Fall back to using a timer. 4877 if (!this.supportsRaf_) { 4878 return this.clearTimeout(id); 4879 } 4880 4881 if (this.rafIds_.has(id)) { 4882 this.rafIds_["delete"](id); 4883 window$1.cancelAnimationFrame(id); 4884 } 4885 4886 return id; 4887 } 4888 /** 4889 * A function to setup `requestAnimationFrame`, `setTimeout`, 4890 * and `setInterval`, clearing on dispose. 4891 * 4892 * > Previously each timer added and removed dispose listeners on it's own. 4893 * For better performance it was decided to batch them all, and use `Set`s 4894 * to track outstanding timer ids. 4895 * 4896 * @private 4897 */ 4898 ; 4899 4900 _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() { 4901 var _this5 = this; 4902 4903 if (this.clearingTimersOnDispose_) { 4904 return; 4905 } 4906 4907 this.clearingTimersOnDispose_ = true; 4908 this.one('dispose', function () { 4909 [['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(function (_ref) { 4910 var idName = _ref[0], 4911 cancelName = _ref[1]; 4912 4913 // for a `Set` key will actually be the value again 4914 // so forEach((val, val) =>` but for maps we want to use 4915 // the key. 4916 _this5[idName].forEach(function (val, key) { 4917 return _this5[cancelName](key); 4918 }); 4919 }); 4920 _this5.clearingTimersOnDispose_ = false; 4921 }); 4922 } 4923 /** 4924 * Register a `Component` with `videojs` given the name and the component. 4925 * 4926 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s 4927 * should be registered using {@link Tech.registerTech} or 4928 * {@link videojs:videojs.registerTech}. 4929 * 4930 * > NOTE: This function can also be seen on videojs as 4931 * {@link videojs:videojs.registerComponent}. 4932 * 4933 * @param {string} name 4934 * The name of the `Component` to register. 4935 * 4936 * @param {Component} ComponentToRegister 4937 * The `Component` class to register. 4938 * 4939 * @return {Component} 4940 * The `Component` that was registered. 4941 */ 4942 ; 4943 4944 Component.registerComponent = function registerComponent(name, ComponentToRegister) { 4945 if (typeof name !== 'string' || !name) { 4946 throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string."); 4947 } 4948 4949 var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered. 4950 4951 var isTech = Tech && Tech.isTech(ComponentToRegister); 4952 var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); 4953 4954 if (isTech || !isComp) { 4955 var reason; 4956 4957 if (isTech) { 4958 reason = 'techs must be registered using Tech.registerTech()'; 4959 } else { 4960 reason = 'must be a Component subclass'; 4961 } 4962 4963 throw new Error("Illegal component, \"" + name + "\"; " + reason + "."); 4964 } 4965 4966 name = toTitleCase(name); 4967 4968 if (!Component.components_) { 4969 Component.components_ = {}; 4970 } 4971 4972 var Player = Component.getComponent('Player'); 4973 4974 if (name === 'Player' && Player && Player.players) { 4975 var players = Player.players; 4976 var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be 4977 // in Players.players. So, we must loop through and verify that the value 4978 // for each item is not null. This allows registration of the Player component 4979 // after all players have been disposed or before any were created. 4980 4981 if (players && playerNames.length > 0 && playerNames.map(function (pname) { 4982 return players[pname]; 4983 }).every(Boolean)) { 4984 throw new Error('Can not register Player component after player has been created.'); 4985 } 4986 } 4987 4988 Component.components_[name] = ComponentToRegister; 4989 Component.components_[toLowerCase(name)] = ComponentToRegister; 4990 return ComponentToRegister; 4991 } 4992 /** 4993 * Get a `Component` based on the name it was registered with. 4994 * 4995 * @param {string} name 4996 * The Name of the component to get. 4997 * 4998 * @return {Component} 4999 * The `Component` that got registered under the given name. 5000 * 5001 * @deprecated In `videojs` 6 this will not return `Component`s that were not 5002 * registered using {@link Component.registerComponent}. Currently we 5003 * check the global `videojs` object for a `Component` name and 5004 * return that if it exists. 5005 */ 5006 ; 5007 5008 Component.getComponent = function getComponent(name) { 5009 if (!name || !Component.components_) { 5010 return; 5011 } 5012 5013 return Component.components_[name]; 5014 }; 5015 5016 return Component; 5017 }(); 5018 /** 5019 * Whether or not this component supports `requestAnimationFrame`. 5020 * 5021 * This is exposed primarily for testing purposes. 5022 * 5023 * @private 5024 * @type {Boolean} 5025 */ 5026 5027 5028 Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function'; 5029 Component.registerComponent('Component', Component); 5030 5031 function _assertThisInitialized(self) { 5032 if (self === void 0) { 5033 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 5034 } 5035 5036 return self; 5037 } 5038 5039 var assertThisInitialized = _assertThisInitialized; 5040 5041 var _typeof_1 = createCommonjsModule(function (module) { 5042 function _typeof(obj) { 5043 "@babel/helpers - typeof"; 5044 5045 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 5046 module.exports = _typeof = function _typeof(obj) { 5047 return typeof obj; 5048 }; 5049 } else { 5050 module.exports = _typeof = function _typeof(obj) { 5051 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 5052 }; 5053 } 5054 5055 return _typeof(obj); 5056 } 5057 5058 module.exports = _typeof; 5059 }); 5060 5061 var getPrototypeOf = createCommonjsModule(function (module) { 5062 function _getPrototypeOf(o) { 5063 module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 5064 return o.__proto__ || Object.getPrototypeOf(o); 5065 }; 5066 return _getPrototypeOf(o); 5067 } 5068 5069 module.exports = _getPrototypeOf; 5070 }); 5071 5072 function _inheritsLoose(subClass, superClass) { 5073 subClass.prototype = Object.create(superClass.prototype); 5074 subClass.prototype.constructor = subClass; 5075 subClass.__proto__ = superClass; 5076 } 5077 5078 var inheritsLoose = _inheritsLoose; 5079 5080 /** 5081 * @file browser.js 5082 * @module browser 5083 */ 5084 var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || ''; 5085 var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); 5086 var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; 5087 /** 5088 * Whether or not this device is an iPod. 5089 * 5090 * @static 5091 * @const 5092 * @type {Boolean} 5093 */ 5094 5095 var IS_IPOD = /iPod/i.test(USER_AGENT); 5096 /** 5097 * The detected iOS version - or `null`. 5098 * 5099 * @static 5100 * @const 5101 * @type {string|null} 5102 */ 5103 5104 var IOS_VERSION = function () { 5105 var match = USER_AGENT.match(/OS (\d+)_/i); 5106 5107 if (match && match[1]) { 5108 return match[1]; 5109 } 5110 5111 return null; 5112 }(); 5113 /** 5114 * Whether or not this is an Android device. 5115 * 5116 * @static 5117 * @const 5118 * @type {Boolean} 5119 */ 5120 5121 var IS_ANDROID = /Android/i.test(USER_AGENT); 5122 /** 5123 * The detected Android version - or `null`. 5124 * 5125 * @static 5126 * @const 5127 * @type {number|string|null} 5128 */ 5129 5130 var ANDROID_VERSION = function () { 5131 // This matches Android Major.Minor.Patch versions 5132 // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned 5133 var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); 5134 5135 if (!match) { 5136 return null; 5137 } 5138 5139 var major = match[1] && parseFloat(match[1]); 5140 var minor = match[2] && parseFloat(match[2]); 5141 5142 if (major && minor) { 5143 return parseFloat(match[1] + '.' + match[2]); 5144 } else if (major) { 5145 return major; 5146 } 5147 5148 return null; 5149 }(); 5150 /** 5151 * Whether or not this is a native Android browser. 5152 * 5153 * @static 5154 * @const 5155 * @type {Boolean} 5156 */ 5157 5158 var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; 5159 /** 5160 * Whether or not this is Mozilla Firefox. 5161 * 5162 * @static 5163 * @const 5164 * @type {Boolean} 5165 */ 5166 5167 var IS_FIREFOX = /Firefox/i.test(USER_AGENT); 5168 /** 5169 * Whether or not this is Microsoft Edge. 5170 * 5171 * @static 5172 * @const 5173 * @type {Boolean} 5174 */ 5175 5176 var IS_EDGE = /Edg/i.test(USER_AGENT); 5177 /** 5178 * Whether or not this is Google Chrome. 5179 * 5180 * This will also be `true` for Chrome on iOS, which will have different support 5181 * as it is actually Safari under the hood. 5182 * 5183 * @static 5184 * @const 5185 * @type {Boolean} 5186 */ 5187 5188 var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); 5189 /** 5190 * The detected Google Chrome version - or `null`. 5191 * 5192 * @static 5193 * @const 5194 * @type {number|null} 5195 */ 5196 5197 var CHROME_VERSION = function () { 5198 var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); 5199 5200 if (match && match[2]) { 5201 return parseFloat(match[2]); 5202 } 5203 5204 return null; 5205 }(); 5206 /** 5207 * The detected Internet Explorer version - or `null`. 5208 * 5209 * @static 5210 * @const 5211 * @type {number|null} 5212 */ 5213 5214 var IE_VERSION = function () { 5215 var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); 5216 var version = result && parseFloat(result[1]); 5217 5218 if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { 5219 // IE 11 has a different user agent string than other IE versions 5220 version = 11.0; 5221 } 5222 5223 return version; 5224 }(); 5225 /** 5226 * Whether or not this is desktop Safari. 5227 * 5228 * @static 5229 * @const 5230 * @type {Boolean} 5231 */ 5232 5233 var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; 5234 /** 5235 * Whether or not this is a Windows machine. 5236 * 5237 * @static 5238 * @const 5239 * @type {Boolean} 5240 */ 5241 5242 var IS_WINDOWS = /Windows/i.test(USER_AGENT); 5243 /** 5244 * Whether or not this device is touch-enabled. 5245 * 5246 * @static 5247 * @const 5248 * @type {Boolean} 5249 */ 5250 5251 var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch); 5252 /** 5253 * Whether or not this device is an iPad. 5254 * 5255 * @static 5256 * @const 5257 * @type {Boolean} 5258 */ 5259 5260 var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT); 5261 /** 5262 * Whether or not this device is an iPhone. 5263 * 5264 * @static 5265 * @const 5266 * @type {Boolean} 5267 */ 5268 // The Facebook app's UIWebView identifies as both an iPhone and iPad, so 5269 // to identify iPhones, we need to exclude iPads. 5270 // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ 5271 5272 var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; 5273 /** 5274 * Whether or not this is an iOS device. 5275 * 5276 * @static 5277 * @const 5278 * @type {Boolean} 5279 */ 5280 5281 var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; 5282 /** 5283 * Whether or not this is any flavor of Safari - including iOS. 5284 * 5285 * @static 5286 * @const 5287 * @type {Boolean} 5288 */ 5289 5290 var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; 5291 5292 var browser = /*#__PURE__*/Object.freeze({ 5293 __proto__: null, 5294 IS_IPOD: IS_IPOD, 5295 IOS_VERSION: IOS_VERSION, 5296 IS_ANDROID: IS_ANDROID, 5297 ANDROID_VERSION: ANDROID_VERSION, 5298 IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, 5299 IS_FIREFOX: IS_FIREFOX, 5300 IS_EDGE: IS_EDGE, 5301 IS_CHROME: IS_CHROME, 5302 CHROME_VERSION: CHROME_VERSION, 5303 IE_VERSION: IE_VERSION, 5304 IS_SAFARI: IS_SAFARI, 5305 IS_WINDOWS: IS_WINDOWS, 5306 TOUCH_ENABLED: TOUCH_ENABLED, 5307 IS_IPAD: IS_IPAD, 5308 IS_IPHONE: IS_IPHONE, 5309 IS_IOS: IS_IOS, 5310 IS_ANY_SAFARI: IS_ANY_SAFARI 5311 }); 5312 5313 /** 5314 * @file time-ranges.js 5315 * @module time-ranges 5316 */ 5317 5318 /** 5319 * Returns the time for the specified index at the start or end 5320 * of a TimeRange object. 5321 * 5322 * @typedef {Function} TimeRangeIndex 5323 * 5324 * @param {number} [index=0] 5325 * The range number to return the time for. 5326 * 5327 * @return {number} 5328 * The time offset at the specified index. 5329 * 5330 * @deprecated The index argument must be provided. 5331 * In the future, leaving it out will throw an error. 5332 */ 5333 5334 /** 5335 * An object that contains ranges of time. 5336 * 5337 * @typedef {Object} TimeRange 5338 * 5339 * @property {number} length 5340 * The number of time ranges represented by this object. 5341 * 5342 * @property {module:time-ranges~TimeRangeIndex} start 5343 * Returns the time offset at which a specified time range begins. 5344 * 5345 * @property {module:time-ranges~TimeRangeIndex} end 5346 * Returns the time offset at which a specified time range ends. 5347 * 5348 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges 5349 */ 5350 5351 /** 5352 * Check if any of the time ranges are over the maximum index. 5353 * 5354 * @private 5355 * @param {string} fnName 5356 * The function name to use for logging 5357 * 5358 * @param {number} index 5359 * The index to check 5360 * 5361 * @param {number} maxIndex 5362 * The maximum possible index 5363 * 5364 * @throws {Error} if the timeRanges provided are over the maxIndex 5365 */ 5366 function rangeCheck(fnName, index, maxIndex) { 5367 if (typeof index !== 'number' || index < 0 || index > maxIndex) { 5368 throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ")."); 5369 } 5370 } 5371 /** 5372 * Get the time for the specified index at the start or end 5373 * of a TimeRange object. 5374 * 5375 * @private 5376 * @param {string} fnName 5377 * The function name to use for logging 5378 * 5379 * @param {string} valueIndex 5380 * The property that should be used to get the time. should be 5381 * 'start' or 'end' 5382 * 5383 * @param {Array} ranges 5384 * An array of time ranges 5385 * 5386 * @param {Array} [rangeIndex=0] 5387 * The index to start the search at 5388 * 5389 * @return {number} 5390 * The time that offset at the specified index. 5391 * 5392 * @deprecated rangeIndex must be set to a value, in the future this will throw an error. 5393 * @throws {Error} if rangeIndex is more than the length of ranges 5394 */ 5395 5396 5397 function getRange(fnName, valueIndex, ranges, rangeIndex) { 5398 rangeCheck(fnName, rangeIndex, ranges.length - 1); 5399 return ranges[rangeIndex][valueIndex]; 5400 } 5401 /** 5402 * Create a time range object given ranges of time. 5403 * 5404 * @private 5405 * @param {Array} [ranges] 5406 * An array of time ranges. 5407 */ 5408 5409 5410 function createTimeRangesObj(ranges) { 5411 if (ranges === undefined || ranges.length === 0) { 5412 return { 5413 length: 0, 5414 start: function start() { 5415 throw new Error('This TimeRanges object is empty'); 5416 }, 5417 end: function end() { 5418 throw new Error('This TimeRanges object is empty'); 5419 } 5420 }; 5421 } 5422 5423 return { 5424 length: ranges.length, 5425 start: getRange.bind(null, 'start', 0, ranges), 5426 end: getRange.bind(null, 'end', 1, ranges) 5427 }; 5428 } 5429 /** 5430 * Create a `TimeRange` object which mimics an 5431 * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. 5432 * 5433 * @param {number|Array[]} start 5434 * The start of a single range (a number) or an array of ranges (an 5435 * array of arrays of two numbers each). 5436 * 5437 * @param {number} end 5438 * The end of a single range. Cannot be used with the array form of 5439 * the `start` argument. 5440 */ 5441 5442 5443 function createTimeRanges(start, end) { 5444 if (Array.isArray(start)) { 5445 return createTimeRangesObj(start); 5446 } else if (start === undefined || end === undefined) { 5447 return createTimeRangesObj(); 5448 } 5449 5450 return createTimeRangesObj([[start, end]]); 5451 } 5452 5453 /** 5454 * @file buffer.js 5455 * @module buffer 5456 */ 5457 /** 5458 * Compute the percentage of the media that has been buffered. 5459 * 5460 * @param {TimeRange} buffered 5461 * The current `TimeRange` object representing buffered time ranges 5462 * 5463 * @param {number} duration 5464 * Total duration of the media 5465 * 5466 * @return {number} 5467 * Percent buffered of the total duration in decimal form. 5468 */ 5469 5470 function bufferedPercent(buffered, duration) { 5471 var bufferedDuration = 0; 5472 var start; 5473 var end; 5474 5475 if (!duration) { 5476 return 0; 5477 } 5478 5479 if (!buffered || !buffered.length) { 5480 buffered = createTimeRanges(0, 0); 5481 } 5482 5483 for (var i = 0; i < buffered.length; i++) { 5484 start = buffered.start(i); 5485 end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction 5486 5487 if (end > duration) { 5488 end = duration; 5489 } 5490 5491 bufferedDuration += end - start; 5492 } 5493 5494 return bufferedDuration / duration; 5495 } 5496 5497 /** 5498 * @file fullscreen-api.js 5499 * @module fullscreen-api 5500 * @private 5501 */ 5502 /** 5503 * Store the browser-specific methods for the fullscreen API. 5504 * 5505 * @type {Object} 5506 * @see [Specification]{@link https://fullscreen.spec.whatwg.org} 5507 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} 5508 */ 5509 5510 var FullscreenApi = { 5511 prefixed: true 5512 }; // browser API methods 5513 5514 var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit 5515 ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla 5516 ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft 5517 ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']]; 5518 var specApi = apiMap[0]; 5519 var browserApi; // determine the supported set of functions 5520 5521 for (var i = 0; i < apiMap.length; i++) { 5522 // check for exitFullscreen function 5523 if (apiMap[i][1] in document) { 5524 browserApi = apiMap[i]; 5525 break; 5526 } 5527 } // map the browser API names to the spec API names 5528 5529 5530 if (browserApi) { 5531 for (var _i = 0; _i < browserApi.length; _i++) { 5532 FullscreenApi[specApi[_i]] = browserApi[_i]; 5533 } 5534 5535 FullscreenApi.prefixed = browserApi[0] !== specApi[0]; 5536 } 5537 5538 /** 5539 * @file media-error.js 5540 */ 5541 /**
5542 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. 5543 * 5544 * @param {number|string|Object|MediaError} value 5545 * This can be of multiple types: 5546 * - number: should be a standard error code 5547 * - string: an error message (the code will be 0) 5548 * - Object: arbitrary properties 5549 * - `MediaError` (native): used to populate a video.js `MediaError` object 5550 * - `MediaError` (video.js): will return itself if it's already a 5551 * video.js `MediaError` object. 5552 * 5553 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} 5554 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} 5555 * 5556 * @class MediaError 5557 */ 5558 5559 function MediaError(value) { 5560 // Allow redundant calls to this constructor to avoid having `instanceof` 5561 // checks peppered around the code. 5562 if (value instanceof MediaError) { 5563 return value; 5564 } 5565 5566 if (typeof value === 'number') { 5567 this.code = value; 5568 } else if (typeof value === 'string') {
vendor: 9,266 bytes, lines 5569-5861
5569 // default code is zero, so this is a custom error 5570 this.message = value; 5571 } else if (isObject(value)) { 5572 // We assign the `code` property manually because native `MediaError` objects 5573 // do not expose it as an own/enumerable property of the object. 5574 if (typeof value.code === 'number') { 5575 this.code = value.code; 5576 } 5577 5578 assign(this, value); 5579 } 5580 5581 if (!this.message) { 5582 this.message = MediaError.defaultMessages[this.code] || ''; 5583 } 5584 } 5585 /** 5586 * The error code that refers two one of the defined `MediaError` types 5587 * 5588 * @type {Number} 5589 */ 5590 5591 5592 MediaError.prototype.code = 0; 5593 /** 5594 * An optional message that to show with the error. Message is not part of the HTML5 5595 * video spec but allows for more informative custom errors. 5596 * 5597 * @type {String} 5598 */ 5599 5600 MediaError.prototype.message = ''; 5601 /** 5602 * An optional status code that can be set by plugins to allow even more detail about 5603 * the error. For example a plugin might provide a specific HTTP status code and an 5604 * error message for that code. Then when the plugin gets that error this class will 5605 * know how to display an error message for it. This allows a custom message to show 5606 * up on the `Player` error overlay. 5607 * 5608 * @type {Array} 5609 */ 5610 5611 MediaError.prototype.status = null; 5612 /** 5613 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the 5614 * specification listed under {@link MediaError} for more information. 5615 * 5616 * @enum {array} 5617 * @readonly 5618 * @property {string} 0 - MEDIA_ERR_CUSTOM 5619 * @property {string} 1 - MEDIA_ERR_ABORTED 5620 * @property {string} 2 - MEDIA_ERR_NETWORK 5621 * @property {string} 3 - MEDIA_ERR_DECODE 5622 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED 5623 * @property {string} 5 - MEDIA_ERR_ENCRYPTED 5624 */ 5625 5626 MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; 5627 /** 5628 * The default `MediaError` messages based on the {@link MediaError.errorTypes}. 5629 * 5630 * @type {Array} 5631 * @constant 5632 */ 5633 5634 MediaError.defaultMessages = { 5635 1: 'You aborted the media playback', 5636 2: 'A network error caused the media download to fail part-way.', 5637 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 5638 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 5639 5: 'The media is encrypted and we do not have the keys to decrypt it.' 5640 }; // Add types as properties on MediaError 5641 // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 5642 5643 for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { 5644 MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance 5645 5646 MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; 5647 } // jsdocs for instance/static members added above 5648 5649 var tuple = SafeParseTuple; 5650 5651 function SafeParseTuple(obj, reviver) { 5652 var json; 5653 var error = null; 5654 5655 try { 5656 json = JSON.parse(obj, reviver); 5657 } catch (err) { 5658 error = err; 5659 } 5660 5661 return [error, json]; 5662 } 5663 5664 /** 5665 * Returns whether an object is `Promise`-like (i.e. has a `then` method). 5666 * 5667 * @param {Object} value 5668 * An object that may or may not be `Promise`-like. 5669 * 5670 * @return {boolean} 5671 * Whether or not the object is `Promise`-like. 5672 */ 5673 function isPromise(value) { 5674 return value !== undefined && value !== null && typeof value.then === 'function'; 5675 } 5676 /** 5677 * Silence a Promise-like object. 5678 * 5679 * This is useful for avoiding non-harmful, but potentially confusing "uncaught 5680 * play promise" rejection error messages. 5681 * 5682 * @param {Object} value 5683 * An object that may or may not be `Promise`-like. 5684 */ 5685 5686 function silencePromise(value) { 5687 if (isPromise(value)) { 5688 value.then(null, function (e) {}); 5689 } 5690 } 5691 5692 /** 5693 * @file text-track-list-converter.js Utilities for capturing text track state and 5694 * re-creating tracks based on a capture. 5695 * 5696 * @module text-track-list-converter 5697 */ 5698 5699 /** 5700 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that 5701 * represents the {@link TextTrack}'s state. 5702 * 5703 * @param {TextTrack} track 5704 * The text track to query. 5705 * 5706 * @return {Object} 5707 * A serializable javascript representation of the TextTrack. 5708 * @private 5709 */ 5710 var trackToJson_ = function trackToJson_(track) { 5711 var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { 5712 if (track[prop]) { 5713 acc[prop] = track[prop]; 5714 } 5715 5716 return acc; 5717 }, { 5718 cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { 5719 return { 5720 startTime: cue.startTime, 5721 endTime: cue.endTime, 5722 text: cue.text, 5723 id: cue.id 5724 }; 5725 }) 5726 }); 5727 return ret; 5728 }; 5729 /** 5730 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the 5731 * state of all {@link TextTrack}s currently configured. The return array is compatible with 5732 * {@link text-track-list-converter:jsonToTextTracks}. 5733 * 5734 * @param {Tech} tech 5735 * The tech object to query 5736 * 5737 * @return {Array} 5738 * A serializable javascript representation of the {@link Tech}s 5739 * {@link TextTrackList}. 5740 */ 5741 5742 5743 var textTracksToJson = function textTracksToJson(tech) { 5744 var trackEls = tech.$$('track'); 5745 var trackObjs = Array.prototype.map.call(trackEls, function (t) { 5746 return t.track; 5747 }); 5748 var tracks = Array.prototype.map.call(trackEls, function (trackEl) { 5749 var json = trackToJson_(trackEl.track); 5750 5751 if (trackEl.src) { 5752 json.src = trackEl.src; 5753 } 5754 5755 return json; 5756 }); 5757 return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { 5758 return trackObjs.indexOf(track) === -1; 5759 }).map(trackToJson_)); 5760 }; 5761 /** 5762 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript 5763 * object {@link TextTrack} representations. 5764 * 5765 * @param {Array} json 5766 * An array of `TextTrack` representation objects, like those that would be 5767 * produced by `textTracksToJson`. 5768 * 5769 * @param {Tech} tech 5770 * The `Tech` to create the `TextTrack`s on. 5771 */ 5772 5773 5774 var jsonToTextTracks = function jsonToTextTracks(json, tech) { 5775 json.forEach(function (track) { 5776 var addedTrack = tech.addRemoteTextTrack(track).track; 5777 5778 if (!track.src && track.cues) { 5779 track.cues.forEach(function (cue) { 5780 return addedTrack.addCue(cue); 5781 }); 5782 } 5783 }); 5784 return tech.textTracks(); 5785 }; 5786 5787 var textTrackConverter = { 5788 textTracksToJson: textTracksToJson, 5789 jsonToTextTracks: jsonToTextTracks, 5790 trackToJson_: trackToJson_ 5791 }; 5792 5793 var keycode = createCommonjsModule(function (module, exports) { 5794 // Source: http://jsfiddle.net/vWx8V/ 5795 // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes 5796 5797 /** 5798 * Conenience method returns corresponding value for given keyName or keyCode. 5799 * 5800 * @param {Mixed} keyCode {Number} or keyName {String} 5801 * @return {Mixed} 5802 * @api public 5803 */ 5804 function keyCode(searchInput) { 5805 // Keyboard Events 5806 if (searchInput && 'object' === typeof searchInput) { 5807 var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; 5808 if (hasKeyCode) searchInput = hasKeyCode; 5809 } // Numbers 5810 5811 5812 if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string) 5813 5814 var search = String(searchInput); // check codes 5815 5816 var foundNamedKey = codes[search.toLowerCase()]; 5817 if (foundNamedKey) return foundNamedKey; // check aliases 5818 5819 var foundNamedKey = aliases[search.toLowerCase()]; 5820 if (foundNamedKey) return foundNamedKey; // weird character? 5821 5822 if (search.length === 1) return search.charCodeAt(0); 5823 return undefined; 5824 } 5825 /** 5826 * Compares a keyboard event with a given keyCode or keyName. 5827 * 5828 * @param {Event} event Keyboard event that should be tested 5829 * @param {Mixed} keyCode {Number} or keyName {String} 5830 * @return {Boolean} 5831 * @api public 5832 */ 5833 5834 5835 keyCode.isEventKey = function isEventKey(event, nameOrCode) { 5836 if (event && 'object' === typeof event) { 5837 var keyCode = event.which || event.keyCode || event.charCode; 5838 5839 if (keyCode === null || keyCode === undefined) { 5840 return false; 5841 } 5842 5843 if (typeof nameOrCode === 'string') { 5844 // check codes 5845 var foundNamedKey = codes[nameOrCode.toLowerCase()]; 5846 5847 if (foundNamedKey) { 5848 return foundNamedKey === keyCode; 5849 } // check aliases 5850 5851 5852 var foundNamedKey = aliases[nameOrCode.toLowerCase()]; 5853 5854 if (foundNamedKey) { 5855 return foundNamedKey === keyCode; 5856 } 5857 } else if (typeof nameOrCode === 'number') { 5858 return nameOrCode === keyCode; 5859 } 5860 5861 return false;
5862 } 5863 }; 5864 5865 exports = module.exports = keyCode; 5866 /** 5867 * Get by name 5868 * 5869 * exports.code['enter'] // => 13 5870 */ 5871 5872 var codes = exports.code = exports.codes = { 5873 'backspace': 8, 5874 'tab': 9, 5875 'enter': 13, 5876 'shift': 16, 5877 'ctrl': 17, 5878 'alt': 18, 5879 'pause/break': 19, 5880 'caps lock': 20, 5881 'esc': 27, 5882 'space': 32, 5883 'page up': 33, 5884 'page down': 34, 5885 'end': 35, 5886 'home': 36, 5887 'left': 37, 5888 'up': 38, 5889 'right': 39, 5890 'down': 40, 5891 'insert': 45, 5892 'delete': 46, 5893 'command': 91, 5894 'left command': 91, 5895 'right command': 93, 5896 'numpad *': 106, 5897 'numpad +': 107, 5898 'numpad -': 109, 5899 'numpad .': 110, 5900 'numpad /': 111, 5901 'num lock': 144, 5902 'scroll lock': 145, 5903 'my computer': 182, 5904 'my calculator': 183, 5905 ';': 186, 5906 '=': 187, 5907 ',': 188, 5908 '-': 189, 5909 '.': 190, 5910 '/': 191, 5911 '`': 192, 5912 '[': 219, 5913 '\\': 220, 5914 ']': 221, 5915 "'": 222 5916 }; // Helper aliases 5917 5918 var aliases = exports.aliases = { 5919 'windows': 91, 5920 'â§': 16, 5921 'â¥': 18, 5922 'â': 17, 5923 'â': 91, 5924 'ctl': 17, 5925 'control': 17, 5926 'option': 18, 5927 'pause': 19, 5928 'break': 19, 5929 'caps': 20, 5930 'return': 13, 5931 'escape': 27, 5932 'spc': 32, 5933 'spacebar': 32, 5934 'pgup': 33, 5935 'pgdn': 34, 5936 'ins': 45, 5937 'del': 46, 5938 'cmd': 91 5939 }; 5940 /*! 5941 * Programatically add the following 5942 */ 5943 // lower case chars 5944 5945 for (i = 97; i < 123; i++) { 5946 codes[String.fromCharCode(i)] = i - 32; 5947 } // numbers 5948 5949 5950 for (var i = 48; i < 58; i++) { 5951 codes[i - 48] = i; 5952 } // function keys 5953 5954 5955 for (i = 1; i < 13; i++) { 5956 codes['f' + i] = i + 111; 5957 } // numpad keys 5958 5959 5960 for (i = 0; i < 10; i++) { 5961 codes['numpad ' + i] = i + 96; 5962 } 5963 /** 5964 * Get by code 5965 * 5966 * exports.name[13] // => 'Enter' 5967 */ 5968 5969 5970 var names = exports.names = exports.title = {}; // title for backward compat 5971 // Create reverse mapping 5972 5973 for (i in codes) { 5974 names[codes[i]] = i; 5975 } // Add aliases 5976 5977 5978 for (var alias in aliases) { 5979 codes[alias] = aliases[alias]; 5980 } 5981 }); 5982 var keycode_1 = keycode.code; 5983 var keycode_2 = keycode.codes; 5984 var keycode_3 = keycode.aliases; 5985 var keycode_4 = keycode.names; 5986 var keycode_5 = keycode.title; 5987 5988 var MODAL_CLASS_NAME = 'vjs-modal-dialog'; 5989 /** 5990 * The `ModalDialog` displays over the video and its controls, which blocks 5991 * interaction with the player until it is closed. 5992 * 5993 * Modal dialogs include a "Close" button and will close when that button 5994 * is activated - or when ESC is pressed anywhere. 5995 * 5996 * @extends Component 5997 */ 5998 5999 var ModalDialog = /*#__PURE__*/function (_Component) { 6000 inheritsLoose(ModalDialog, _Component); 6001 6002 /** 6003 * Create an instance of this class. 6004 * 6005 * @param {Player} player 6006 * The `Player` that this class should be attached to. 6007 * 6008 * @param {Object} [options] 6009 * The key/value store of player options. 6010 * 6011 * @param {Mixed} [options.content=undefined] 6012 * Provide customized content for this modal. 6013 * 6014 * @param {string} [options.description] 6015 * A text description for the modal, primarily for accessibility. 6016 * 6017 * @param {boolean} [options.fillAlways=false] 6018 * Normally, modals are automatically filled only the first time 6019 * they open. This tells the modal to refresh its content 6020 * every time it opens. 6021 * 6022 * @param {string} [options.label] 6023 * A text label for the modal, primarily for accessibility. 6024 * 6025 * @param {boolean} [options.pauseOnOpen=true] 6026 * If `true`, playback will will be paused if playing when 6027 * the modal opens, and resumed when it closes. 6028 * 6029 * @param {boolean} [options.temporary=true] 6030 * If `true`, the modal can only be opened once; it will be 6031 * disposed as soon as it's closed. 6032 * 6033 * @param {boolean} [options.uncloseable=false] 6034 * If `true`, the user will not be able to close the modal 6035 * through the UI in the normal ways. Programmatic closing is 6036 * still possible. 6037 */ 6038 function ModalDialog(player, options) { 6039 var _this; 6040 6041 _this = _Component.call(this, player, options) || this; 6042 _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
vendor: 72,018 bytes, lines 6043-8689
6043 6044 _this.closeable(!_this.options_.uncloseable); 6045 6046 _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized 6047 // because we only want the contents of the modal in the contentEl 6048 // (not the UI elements like the close button). 6049 6050 6051 _this.contentEl_ = createEl('div', { 6052 className: MODAL_CLASS_NAME + "-content" 6053 }, { 6054 role: 'document' 6055 }); 6056 _this.descEl_ = createEl('p', { 6057 className: MODAL_CLASS_NAME + "-description vjs-control-text", 6058 id: _this.el().getAttribute('aria-describedby') 6059 }); 6060 textContent(_this.descEl_, _this.description()); 6061 6062 _this.el_.appendChild(_this.descEl_); 6063 6064 _this.el_.appendChild(_this.contentEl_); 6065 6066 return _this; 6067 } 6068 /** 6069 * Create the `ModalDialog`'s DOM element 6070 * 6071 * @return {Element} 6072 * The DOM element that gets created. 6073 */ 6074 6075 6076 var _proto = ModalDialog.prototype; 6077 6078 _proto.createEl = function createEl() { 6079 return _Component.prototype.createEl.call(this, 'div', { 6080 className: this.buildCSSClass(), 6081 tabIndex: -1 6082 }, { 6083 'aria-describedby': this.id() + "_description", 6084 'aria-hidden': 'true', 6085 'aria-label': this.label(), 6086 'role': 'dialog' 6087 }); 6088 }; 6089 6090 _proto.dispose = function dispose() { 6091 this.contentEl_ = null; 6092 this.descEl_ = null; 6093 this.previouslyActiveEl_ = null; 6094 6095 _Component.prototype.dispose.call(this); 6096 } 6097 /** 6098 * Builds the default DOM `className`. 6099 * 6100 * @return {string} 6101 * The DOM `className` for this object. 6102 */ 6103 ; 6104 6105 _proto.buildCSSClass = function buildCSSClass() { 6106 return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this); 6107 } 6108 /** 6109 * Returns the label string for this modal. Primarily used for accessibility. 6110 * 6111 * @return {string} 6112 * the localized or raw label of this modal. 6113 */ 6114 ; 6115 6116 _proto.label = function label() { 6117 return this.localize(this.options_.label || 'Modal Window'); 6118 } 6119 /** 6120 * Returns the description string for this modal. Primarily used for 6121 * accessibility. 6122 * 6123 * @return {string} 6124 * The localized or raw description of this modal. 6125 */ 6126 ; 6127 6128 _proto.description = function description() { 6129 var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable. 6130 6131 if (this.closeable()) { 6132 desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); 6133 } 6134 6135 return desc; 6136 } 6137 /** 6138 * Opens the modal. 6139 * 6140 * @fires ModalDialog#beforemodalopen 6141 * @fires ModalDialog#modalopen 6142 */ 6143 ; 6144 6145 _proto.open = function open() { 6146 if (!this.opened_) { 6147 var player = this.player(); 6148 /** 6149 * Fired just before a `ModalDialog` is opened. 6150 * 6151 * @event ModalDialog#beforemodalopen 6152 * @type {EventTarget~Event} 6153 */ 6154 6155 this.trigger('beforemodalopen'); 6156 this.opened_ = true; // Fill content if the modal has never opened before and 6157 // never been filled. 6158 6159 if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { 6160 this.fill(); 6161 } // If the player was playing, pause it and take note of its previously 6162 // playing state. 6163 6164 6165 this.wasPlaying_ = !player.paused(); 6166 6167 if (this.options_.pauseOnOpen && this.wasPlaying_) { 6168 player.pause(); 6169 } 6170 6171 this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled. 6172 6173 this.hadControls_ = player.controls(); 6174 player.controls(false); 6175 this.show(); 6176 this.conditionalFocus_(); 6177 this.el().setAttribute('aria-hidden', 'false'); 6178 /** 6179 * Fired just after a `ModalDialog` is opened. 6180 * 6181 * @event ModalDialog#modalopen 6182 * @type {EventTarget~Event} 6183 */ 6184 6185 this.trigger('modalopen'); 6186 this.hasBeenOpened_ = true; 6187 } 6188 } 6189 /** 6190 * If the `ModalDialog` is currently open or closed. 6191 * 6192 * @param {boolean} [value] 6193 * If given, it will open (`true`) or close (`false`) the modal. 6194 * 6195 * @return {boolean} 6196 * the current open state of the modaldialog 6197 */ 6198 ; 6199 6200 _proto.opened = function opened(value) { 6201 if (typeof value === 'boolean') { 6202 this[value ? 'open' : 'close'](); 6203 } 6204 6205 return this.opened_; 6206 } 6207 /** 6208 * Closes the modal, does nothing if the `ModalDialog` is 6209 * not open. 6210 * 6211 * @fires ModalDialog#beforemodalclose 6212 * @fires ModalDialog#modalclose 6213 */ 6214 ; 6215 6216 _proto.close = function close() { 6217 if (!this.opened_) { 6218 return; 6219 } 6220 6221 var player = this.player(); 6222 /** 6223 * Fired just before a `ModalDialog` is closed. 6224 * 6225 * @event ModalDialog#beforemodalclose 6226 * @type {EventTarget~Event} 6227 */ 6228 6229 this.trigger('beforemodalclose'); 6230 this.opened_ = false; 6231 6232 if (this.wasPlaying_ && this.options_.pauseOnOpen) { 6233 player.play(); 6234 } 6235 6236 this.off('keydown', this.handleKeyDown); 6237 6238 if (this.hadControls_) { 6239 player.controls(true); 6240 } 6241 6242 this.hide(); 6243 this.el().setAttribute('aria-hidden', 'true'); 6244 /** 6245 * Fired just after a `ModalDialog` is closed. 6246 * 6247 * @event ModalDialog#modalclose 6248 * @type {EventTarget~Event} 6249 */ 6250 6251 this.trigger('modalclose'); 6252 this.conditionalBlur_(); 6253 6254 if (this.options_.temporary) { 6255 this.dispose(); 6256 } 6257 } 6258 /** 6259 * Check to see if the `ModalDialog` is closeable via the UI. 6260 * 6261 * @param {boolean} [value] 6262 * If given as a boolean, it will set the `closeable` option. 6263 * 6264 * @return {boolean} 6265 * Returns the final value of the closable option. 6266 */ 6267 ; 6268 6269 _proto.closeable = function closeable(value) { 6270 if (typeof value === 'boolean') { 6271 var closeable = this.closeable_ = !!value; 6272 var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one. 6273 6274 if (closeable && !close) { 6275 // The close button should be a child of the modal - not its 6276 // content element, so temporarily change the content element. 6277 var temp = this.contentEl_; 6278 this.contentEl_ = this.el_; 6279 close = this.addChild('closeButton', { 6280 controlText: 'Close Modal Dialog' 6281 }); 6282 this.contentEl_ = temp; 6283 this.on(close, 'close', this.close); 6284 } // If this is being made uncloseable and has a close button, remove it. 6285 6286 6287 if (!closeable && close) { 6288 this.off(close, 'close', this.close); 6289 this.removeChild(close); 6290 close.dispose(); 6291 } 6292 } 6293 6294 return this.closeable_; 6295 } 6296 /** 6297 * Fill the modal's content element with the modal's "content" option. 6298 * The content element will be emptied before this change takes place. 6299 */ 6300 ; 6301 6302 _proto.fill = function fill() { 6303 this.fillWith(this.content()); 6304 } 6305 /** 6306 * Fill the modal's content element with arbitrary content. 6307 * The content element will be emptied before this change takes place. 6308 * 6309 * @fires ModalDialog#beforemodalfill 6310 * @fires ModalDialog#modalfill 6311 * 6312 * @param {Mixed} [content] 6313 * The same rules apply to this as apply to the `content` option. 6314 */ 6315 ; 6316 6317 _proto.fillWith = function fillWith(content) { 6318 var contentEl = this.contentEl(); 6319 var parentEl = contentEl.parentNode; 6320 var nextSiblingEl = contentEl.nextSibling; 6321 /** 6322 * Fired just before a `ModalDialog` is filled with content. 6323 * 6324 * @event ModalDialog#beforemodalfill 6325 * @type {EventTarget~Event} 6326 */ 6327 6328 this.trigger('beforemodalfill'); 6329 this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing 6330 // manipulation to avoid modifying the live DOM multiple times. 6331 6332 parentEl.removeChild(contentEl); 6333 this.empty(); 6334 insertContent(contentEl, content); 6335 /** 6336 * Fired just after a `ModalDialog` is filled with content. 6337 * 6338 * @event ModalDialog#modalfill 6339 * @type {EventTarget~Event} 6340 */ 6341 6342 this.trigger('modalfill'); // Re-inject the re-filled content element. 6343 6344 if (nextSiblingEl) { 6345 parentEl.insertBefore(contentEl, nextSiblingEl); 6346 } else { 6347 parentEl.appendChild(contentEl); 6348 } // make sure that the close button is last in the dialog DOM 6349 6350 6351 var closeButton = this.getChild('closeButton'); 6352 6353 if (closeButton) { 6354 parentEl.appendChild(closeButton.el_); 6355 } 6356 } 6357 /** 6358 * Empties the content element. This happens anytime the modal is filled. 6359 * 6360 * @fires ModalDialog#beforemodalempty 6361 * @fires ModalDialog#modalempty 6362 */ 6363 ; 6364 6365 _proto.empty = function empty() { 6366 /** 6367 * Fired just before a `ModalDialog` is emptied. 6368 * 6369 * @event ModalDialog#beforemodalempty 6370 * @type {EventTarget~Event} 6371 */ 6372 this.trigger('beforemodalempty'); 6373 emptyEl(this.contentEl()); 6374 /** 6375 * Fired just after a `ModalDialog` is emptied. 6376 * 6377 * @event ModalDialog#modalempty 6378 * @type {EventTarget~Event} 6379 */ 6380 6381 this.trigger('modalempty'); 6382 } 6383 /** 6384 * Gets or sets the modal content, which gets normalized before being 6385 * rendered into the DOM. 6386 * 6387 * This does not update the DOM or fill the modal, but it is called during 6388 * that process. 6389 * 6390 * @param {Mixed} [value] 6391 * If defined, sets the internal content value to be used on the 6392 * next call(s) to `fill`. This value is normalized before being 6393 * inserted. To "clear" the internal content value, pass `null`. 6394 * 6395 * @return {Mixed} 6396 * The current content of the modal dialog 6397 */ 6398 ; 6399 6400 _proto.content = function content(value) { 6401 if (typeof value !== 'undefined') { 6402 this.content_ = value; 6403 } 6404 6405 return this.content_; 6406 } 6407 /** 6408 * conditionally focus the modal dialog if focus was previously on the player. 6409 * 6410 * @private 6411 */ 6412 ; 6413 6414 _proto.conditionalFocus_ = function conditionalFocus_() { 6415 var activeEl = document.activeElement; 6416 var playerEl = this.player_.el_; 6417 this.previouslyActiveEl_ = null; 6418 6419 if (playerEl.contains(activeEl) || playerEl === activeEl) { 6420 this.previouslyActiveEl_ = activeEl; 6421 this.focus(); 6422 } 6423 } 6424 /** 6425 * conditionally blur the element and refocus the last focused element 6426 * 6427 * @private 6428 */ 6429 ; 6430 6431 _proto.conditionalBlur_ = function conditionalBlur_() { 6432 if (this.previouslyActiveEl_) { 6433 this.previouslyActiveEl_.focus(); 6434 this.previouslyActiveEl_ = null; 6435 } 6436 } 6437 /** 6438 * Keydown handler. Attached when modal is focused. 6439 * 6440 * @listens keydown 6441 */ 6442 ; 6443 6444 _proto.handleKeyDown = function handleKeyDown(event) { 6445 // Do not allow keydowns to reach out of the modal dialog. 6446 event.stopPropagation(); 6447 6448 if (keycode.isEventKey(event, 'Escape') && this.closeable()) { 6449 event.preventDefault(); 6450 this.close(); 6451 return; 6452 } // exit early if it isn't a tab key 6453 6454 6455 if (!keycode.isEventKey(event, 'Tab')) { 6456 return; 6457 } 6458 6459 var focusableEls = this.focusableEls_(); 6460 var activeEl = this.el_.querySelector(':focus'); 6461 var focusIndex; 6462 6463 for (var i = 0; i < focusableEls.length; i++) { 6464 if (activeEl === focusableEls[i]) { 6465 focusIndex = i; 6466 break; 6467 } 6468 } 6469 6470 if (document.activeElement === this.el_) { 6471 focusIndex = 0; 6472 } 6473 6474 if (event.shiftKey && focusIndex === 0) { 6475 focusableEls[focusableEls.length - 1].focus(); 6476 event.preventDefault(); 6477 } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { 6478 focusableEls[0].focus(); 6479 event.preventDefault(); 6480 } 6481 } 6482 /** 6483 * get all focusable elements 6484 * 6485 * @private 6486 */ 6487 ; 6488 6489 _proto.focusableEls_ = function focusableEls_() { 6490 var allChildren = this.el_.querySelectorAll('*'); 6491 return Array.prototype.filter.call(allChildren, function (child) { 6492 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'); 6493 }); 6494 }; 6495 6496 return ModalDialog; 6497 }(Component); 6498 /** 6499 * Default options for `ModalDialog` default options. 6500 * 6501 * @type {Object} 6502 * @private 6503 */ 6504 6505 6506 ModalDialog.prototype.options_ = { 6507 pauseOnOpen: true, 6508 temporary: true 6509 }; 6510 Component.registerComponent('ModalDialog', ModalDialog); 6511 6512 /** 6513 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and 6514 * {@link VideoTrackList} 6515 * 6516 * @extends EventTarget 6517 */ 6518 6519 var TrackList = /*#__PURE__*/function (_EventTarget) { 6520 inheritsLoose(TrackList, _EventTarget); 6521 6522 /** 6523 * Create an instance of this class 6524 * 6525 * @param {Track[]} tracks 6526 * A list of tracks to initialize the list with. 6527 * 6528 * @abstract 6529 */ 6530 function TrackList(tracks) { 6531 var _this; 6532 6533 if (tracks === void 0) { 6534 tracks = []; 6535 } 6536 6537 _this = _EventTarget.call(this) || this; 6538 _this.tracks_ = []; 6539 /** 6540 * @memberof TrackList 6541 * @member {number} length 6542 * The current number of `Track`s in the this Trackist. 6543 * @instance 6544 */ 6545 6546 Object.defineProperty(assertThisInitialized(_this), 'length', { 6547 get: function get() { 6548 return this.tracks_.length; 6549 } 6550 }); 6551 6552 for (var i = 0; i < tracks.length; i++) { 6553 _this.addTrack(tracks[i]); 6554 } 6555 6556 return _this; 6557 } 6558 /** 6559 * Add a {@link Track} to the `TrackList` 6560 * 6561 * @param {Track} track 6562 * The audio, video, or text track to add to the list. 6563 * 6564 * @fires TrackList#addtrack 6565 */ 6566 6567 6568 var _proto = TrackList.prototype; 6569 6570 _proto.addTrack = function addTrack(track) { 6571 var index = this.tracks_.length; 6572 6573 if (!('' + index in this)) { 6574 Object.defineProperty(this, index, { 6575 get: function get() { 6576 return this.tracks_[index]; 6577 } 6578 }); 6579 } // Do not add duplicate tracks 6580 6581 6582 if (this.tracks_.indexOf(track) === -1) { 6583 this.tracks_.push(track); 6584 /** 6585 * Triggered when a track is added to a track list. 6586 * 6587 * @event TrackList#addtrack 6588 * @type {EventTarget~Event} 6589 * @property {Track} track 6590 * A reference to track that was added. 6591 */ 6592 6593 this.trigger({ 6594 track: track, 6595 type: 'addtrack', 6596 target: this 6597 }); 6598 } 6599 } 6600 /** 6601 * Remove a {@link Track} from the `TrackList` 6602 * 6603 * @param {Track} rtrack 6604 * The audio, video, or text track to remove from the list. 6605 * 6606 * @fires TrackList#removetrack 6607 */ 6608 ; 6609 6610 _proto.removeTrack = function removeTrack(rtrack) { 6611 var track; 6612 6613 for (var i = 0, l = this.length; i < l; i++) { 6614 if (this[i] === rtrack) { 6615 track = this[i]; 6616 6617 if (track.off) { 6618 track.off(); 6619 } 6620 6621 this.tracks_.splice(i, 1); 6622 break; 6623 } 6624 } 6625 6626 if (!track) { 6627 return; 6628 } 6629 /** 6630 * Triggered when a track is removed from track list. 6631 * 6632 * @event TrackList#removetrack 6633 * @type {EventTarget~Event} 6634 * @property {Track} track 6635 * A reference to track that was removed. 6636 */ 6637 6638 6639 this.trigger({ 6640 track: track, 6641 type: 'removetrack', 6642 target: this 6643 }); 6644 } 6645 /** 6646 * Get a Track from the TrackList by a tracks id 6647 * 6648 * @param {string} id - the id of the track to get 6649 * @method getTrackById 6650 * @return {Track} 6651 * @private 6652 */ 6653 ; 6654 6655 _proto.getTrackById = function getTrackById(id) { 6656 var result = null; 6657 6658 for (var i = 0, l = this.length; i < l; i++) { 6659 var track = this[i]; 6660 6661 if (track.id === id) { 6662 result = track; 6663 break; 6664 } 6665 } 6666 6667 return result; 6668 }; 6669 6670 return TrackList; 6671 }(EventTarget); 6672 /** 6673 * Triggered when a different track is selected/enabled. 6674 * 6675 * @event TrackList#change 6676 * @type {EventTarget~Event} 6677 */ 6678 6679 /** 6680 * Events that can be called with on + eventName. See {@link EventHandler}. 6681 * 6682 * @property {Object} TrackList#allowedEvents_ 6683 * @private 6684 */ 6685 6686 6687 TrackList.prototype.allowedEvents_ = { 6688 change: 'change', 6689 addtrack: 'addtrack', 6690 removetrack: 'removetrack' 6691 }; // emulate attribute EventHandler support to allow for feature detection 6692 6693 for (var event in TrackList.prototype.allowedEvents_) { 6694 TrackList.prototype['on' + event] = null; 6695 } 6696 6697 /** 6698 * Anywhere we call this function we diverge from the spec 6699 * as we only support one enabled audiotrack at a time 6700 * 6701 * @param {AudioTrackList} list 6702 * list to work on 6703 * 6704 * @param {AudioTrack} track 6705 * The track to skip 6706 * 6707 * @private 6708 */ 6709 6710 var disableOthers = function disableOthers(list, track) { 6711 for (var i = 0; i < list.length; i++) { 6712 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6713 continue; 6714 } // another audio track is enabled, disable it 6715 6716 6717 list[i].enabled = false; 6718 } 6719 }; 6720 /** 6721 * The current list of {@link AudioTrack} for a media file. 6722 * 6723 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} 6724 * @extends TrackList 6725 */ 6726 6727 6728 var AudioTrackList = /*#__PURE__*/function (_TrackList) { 6729 inheritsLoose(AudioTrackList, _TrackList); 6730 6731 /** 6732 * Create an instance of this class. 6733 * 6734 * @param {AudioTrack[]} [tracks=[]] 6735 * A list of `AudioTrack` to instantiate the list with. 6736 */ 6737 function AudioTrackList(tracks) { 6738 var _this; 6739 6740 if (tracks === void 0) { 6741 tracks = []; 6742 } 6743 6744 // make sure only 1 track is enabled 6745 // sorted from last index to first index 6746 for (var i = tracks.length - 1; i >= 0; i--) { 6747 if (tracks[i].enabled) { 6748 disableOthers(tracks, tracks[i]); 6749 break; 6750 } 6751 } 6752 6753 _this = _TrackList.call(this, tracks) || this; 6754 _this.changing_ = false; 6755 return _this; 6756 } 6757 /** 6758 * Add an {@link AudioTrack} to the `AudioTrackList`. 6759 * 6760 * @param {AudioTrack} track 6761 * The AudioTrack to add to the list 6762 * 6763 * @fires TrackList#addtrack 6764 */ 6765 6766 6767 var _proto = AudioTrackList.prototype; 6768 6769 _proto.addTrack = function addTrack(track) { 6770 var _this2 = this; 6771 6772 if (track.enabled) { 6773 disableOthers(this, track); 6774 } 6775 6776 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6777 6778 6779 if (!track.addEventListener) { 6780 return; 6781 } 6782 6783 track.enabledChange_ = function () { 6784 // when we are disabling other tracks (since we don't support 6785 // more than one track at a time) we will set changing_ 6786 // to true so that we don't trigger additional change events 6787 if (_this2.changing_) { 6788 return; 6789 } 6790 6791 _this2.changing_ = true; 6792 disableOthers(_this2, track); 6793 _this2.changing_ = false; 6794 6795 _this2.trigger('change'); 6796 }; 6797 /** 6798 * @listens AudioTrack#enabledchange 6799 * @fires TrackList#change 6800 */ 6801 6802 6803 track.addEventListener('enabledchange', track.enabledChange_); 6804 }; 6805 6806 _proto.removeTrack = function removeTrack(rtrack) { 6807 _TrackList.prototype.removeTrack.call(this, rtrack); 6808 6809 if (rtrack.removeEventListener && rtrack.enabledChange_) { 6810 rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); 6811 rtrack.enabledChange_ = null; 6812 } 6813 }; 6814 6815 return AudioTrackList; 6816 }(TrackList); 6817 6818 /** 6819 * Un-select all other {@link VideoTrack}s that are selected. 6820 * 6821 * @param {VideoTrackList} list 6822 * list to work on 6823 * 6824 * @param {VideoTrack} track 6825 * The track to skip 6826 * 6827 * @private 6828 */ 6829 6830 var disableOthers$1 = function disableOthers(list, track) { 6831 for (var i = 0; i < list.length; i++) { 6832 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6833 continue; 6834 } // another video track is enabled, disable it 6835 6836 6837 list[i].selected = false; 6838 } 6839 }; 6840 /** 6841 * The current list of {@link VideoTrack} for a video. 6842 * 6843 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} 6844 * @extends TrackList 6845 */ 6846 6847 6848 var VideoTrackList = /*#__PURE__*/function (_TrackList) { 6849 inheritsLoose(VideoTrackList, _TrackList); 6850 6851 /** 6852 * Create an instance of this class. 6853 * 6854 * @param {VideoTrack[]} [tracks=[]] 6855 * A list of `VideoTrack` to instantiate the list with. 6856 */ 6857 function VideoTrackList(tracks) { 6858 var _this; 6859 6860 if (tracks === void 0) { 6861 tracks = []; 6862 } 6863 6864 // make sure only 1 track is enabled 6865 // sorted from last index to first index 6866 for (var i = tracks.length - 1; i >= 0; i--) { 6867 if (tracks[i].selected) { 6868 disableOthers$1(tracks, tracks[i]); 6869 break; 6870 } 6871 } 6872 6873 _this = _TrackList.call(this, tracks) || this; 6874 _this.changing_ = false; 6875 /** 6876 * @member {number} VideoTrackList#selectedIndex 6877 * The current index of the selected {@link VideoTrack`}. 6878 */ 6879 6880 Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', { 6881 get: function get() { 6882 for (var _i = 0; _i < this.length; _i++) { 6883 if (this[_i].selected) { 6884 return _i; 6885 } 6886 } 6887 6888 return -1; 6889 }, 6890 set: function set() {} 6891 }); 6892 return _this; 6893 } 6894 /** 6895 * Add a {@link VideoTrack} to the `VideoTrackList`. 6896 * 6897 * @param {VideoTrack} track 6898 * The VideoTrack to add to the list 6899 * 6900 * @fires TrackList#addtrack 6901 */ 6902 6903 6904 var _proto = VideoTrackList.prototype; 6905 6906 _proto.addTrack = function addTrack(track) { 6907 var _this2 = this; 6908 6909 if (track.selected) { 6910 disableOthers$1(this, track); 6911 } 6912 6913 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6914 6915 6916 if (!track.addEventListener) { 6917 return; 6918 } 6919 6920 track.selectedChange_ = function () { 6921 if (_this2.changing_) { 6922 return; 6923 } 6924 6925 _this2.changing_ = true; 6926 disableOthers$1(_this2, track); 6927 _this2.changing_ = false; 6928 6929 _this2.trigger('change'); 6930 }; 6931 /** 6932 * @listens VideoTrack#selectedchange 6933 * @fires TrackList#change 6934 */ 6935 6936 6937 track.addEventListener('selectedchange', track.selectedChange_); 6938 }; 6939 6940 _proto.removeTrack = function removeTrack(rtrack) { 6941 _TrackList.prototype.removeTrack.call(this, rtrack); 6942 6943 if (rtrack.removeEventListener && rtrack.selectedChange_) { 6944 rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); 6945 rtrack.selectedChange_ = null; 6946 } 6947 }; 6948 6949 return VideoTrackList; 6950 }(TrackList); 6951 6952 /** 6953 * The current list of {@link TextTrack} for a media file. 6954 * 6955 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} 6956 * @extends TrackList 6957 */ 6958 6959 var TextTrackList = /*#__PURE__*/function (_TrackList) { 6960 inheritsLoose(TextTrackList, _TrackList); 6961 6962 function TextTrackList() { 6963 return _TrackList.apply(this, arguments) || this; 6964 } 6965 6966 var _proto = TextTrackList.prototype; 6967 6968 /** 6969 * Add a {@link TextTrack} to the `TextTrackList` 6970 * 6971 * @param {TextTrack} track 6972 * The text track to add to the list. 6973 * 6974 * @fires TrackList#addtrack 6975 */ 6976 _proto.addTrack = function addTrack(track) { 6977 var _this = this; 6978 6979 _TrackList.prototype.addTrack.call(this, track); 6980 6981 if (!this.queueChange_) { 6982 this.queueChange_ = function () { 6983 return _this.queueTrigger('change'); 6984 }; 6985 } 6986 6987 if (!this.triggerSelectedlanguagechange) { 6988 this.triggerSelectedlanguagechange_ = function () { 6989 return _this.trigger('selectedlanguagechange'); 6990 }; 6991 } 6992 /** 6993 * @listens TextTrack#modechange 6994 * @fires TrackList#change 6995 */ 6996 6997 6998 track.addEventListener('modechange', this.queueChange_); 6999 var nonLanguageTextTrackKind = ['metadata', 'chapters']; 7000 7001 if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { 7002 track.addEventListener('modechange', this.triggerSelectedlanguagechange_); 7003 } 7004 }; 7005 7006 _proto.removeTrack = function removeTrack(rtrack) { 7007 _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added 7008 7009 7010 if (rtrack.removeEventListener) { 7011 if (this.queueChange_) { 7012 rtrack.removeEventListener('modechange', this.queueChange_); 7013 } 7014 7015 if (this.selectedlanguagechange_) { 7016 rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); 7017 } 7018 } 7019 }; 7020 7021 return TextTrackList; 7022 }(TrackList); 7023 7024 /** 7025 * @file html-track-element-list.js 7026 */ 7027 7028 /** 7029 * The current list of {@link HtmlTrackElement}s. 7030 */ 7031 var HtmlTrackElementList = /*#__PURE__*/function () { 7032 /** 7033 * Create an instance of this class. 7034 * 7035 * @param {HtmlTrackElement[]} [tracks=[]] 7036 * A list of `HtmlTrackElement` to instantiate the list with. 7037 */ 7038 function HtmlTrackElementList(trackElements) { 7039 if (trackElements === void 0) { 7040 trackElements = []; 7041 } 7042 7043 this.trackElements_ = []; 7044 /** 7045 * @memberof HtmlTrackElementList 7046 * @member {number} length 7047 * The current number of `Track`s in the this Trackist. 7048 * @instance 7049 */ 7050 7051 Object.defineProperty(this, 'length', { 7052 get: function get() { 7053 return this.trackElements_.length; 7054 } 7055 }); 7056 7057 for (var i = 0, length = trackElements.length; i < length; i++) { 7058 this.addTrackElement_(trackElements[i]); 7059 } 7060 } 7061 /** 7062 * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` 7063 * 7064 * @param {HtmlTrackElement} trackElement 7065 * The track element to add to the list. 7066 * 7067 * @private 7068 */ 7069 7070 7071 var _proto = HtmlTrackElementList.prototype; 7072 7073 _proto.addTrackElement_ = function addTrackElement_(trackElement) { 7074 var index = this.trackElements_.length; 7075 7076 if (!('' + index in this)) { 7077 Object.defineProperty(this, index, { 7078 get: function get() { 7079 return this.trackElements_[index]; 7080 } 7081 }); 7082 } // Do not add duplicate elements 7083 7084 7085 if (this.trackElements_.indexOf(trackElement) === -1) { 7086 this.trackElements_.push(trackElement); 7087 } 7088 } 7089 /** 7090 * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an 7091 * {@link TextTrack}. 7092 * 7093 * @param {TextTrack} track 7094 * The track associated with a track element. 7095 * 7096 * @return {HtmlTrackElement|undefined} 7097 * The track element that was found or undefined. 7098 * 7099 * @private 7100 */ 7101 ; 7102 7103 _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { 7104 var trackElement_; 7105 7106 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 7107 if (track === this.trackElements_[i].track) { 7108 trackElement_ = this.trackElements_[i]; 7109 break; 7110 } 7111 } 7112 7113 return trackElement_; 7114 } 7115 /** 7116 * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` 7117 * 7118 * @param {HtmlTrackElement} trackElement 7119 * The track element to remove from the list. 7120 * 7121 * @private 7122 */ 7123 ; 7124 7125 _proto.removeTrackElement_ = function removeTrackElement_(trackElement) { 7126 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 7127 if (trackElement === this.trackElements_[i]) { 7128 if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { 7129 this.trackElements_[i].track.off(); 7130 } 7131 7132 if (typeof this.trackElements_[i].off === 'function') { 7133 this.trackElements_[i].off(); 7134 } 7135 7136 this.trackElements_.splice(i, 1); 7137 break; 7138 } 7139 } 7140 }; 7141 7142 return HtmlTrackElementList; 7143 }(); 7144 7145 /** 7146 * @file text-track-cue-list.js 7147 */ 7148 7149 /** 7150 * @typedef {Object} TextTrackCueList~TextTrackCue 7151 * 7152 * @property {string} id 7153 * The unique id for this text track cue 7154 * 7155 * @property {number} startTime 7156 * The start time for this text track cue 7157 * 7158 * @property {number} endTime 7159 * The end time for this text track cue 7160 * 7161 * @property {boolean} pauseOnExit 7162 * Pause when the end time is reached if true. 7163 * 7164 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} 7165 */ 7166 7167 /** 7168 * A List of TextTrackCues. 7169 * 7170 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} 7171 */ 7172 var TextTrackCueList = /*#__PURE__*/function () { 7173 /** 7174 * Create an instance of this class.. 7175 * 7176 * @param {Array} cues 7177 * A list of cues to be initialized with 7178 */ 7179 function TextTrackCueList(cues) { 7180 TextTrackCueList.prototype.setCues_.call(this, cues); 7181 /** 7182 * @memberof TextTrackCueList 7183 * @member {number} length 7184 * The current number of `TextTrackCue`s in the TextTrackCueList. 7185 * @instance 7186 */ 7187 7188 Object.defineProperty(this, 'length', { 7189 get: function get() { 7190 return this.length_; 7191 } 7192 }); 7193 } 7194 /** 7195 * A setter for cues in this list. Creates getters 7196 * an an index for the cues. 7197 * 7198 * @param {Array} cues 7199 * An array of cues to set 7200 * 7201 * @private 7202 */ 7203 7204 7205 var _proto = TextTrackCueList.prototype; 7206 7207 _proto.setCues_ = function setCues_(cues) { 7208 var oldLength = this.length || 0; 7209 var i = 0; 7210 var l = cues.length; 7211 this.cues_ = cues; 7212 this.length_ = cues.length; 7213 7214 var defineProp = function defineProp(index) { 7215 if (!('' + index in this)) { 7216 Object.defineProperty(this, '' + index, { 7217 get: function get() { 7218 return this.cues_[index]; 7219 } 7220 }); 7221 } 7222 }; 7223 7224 if (oldLength < l) { 7225 i = oldLength; 7226 7227 for (; i < l; i++) { 7228 defineProp.call(this, i); 7229 } 7230 } 7231 } 7232 /** 7233 * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. 7234 * 7235 * @param {string} id 7236 * The id of the cue that should be searched for. 7237 * 7238 * @return {TextTrackCueList~TextTrackCue|null} 7239 * A single cue or null if none was found. 7240 */ 7241 ; 7242 7243 _proto.getCueById = function getCueById(id) { 7244 var result = null; 7245 7246 for (var i = 0, l = this.length; i < l; i++) { 7247 var cue = this[i]; 7248 7249 if (cue.id === id) { 7250 result = cue; 7251 break; 7252 } 7253 } 7254 7255 return result; 7256 }; 7257 7258 return TextTrackCueList; 7259 }(); 7260 7261 /** 7262 * @file track-kinds.js 7263 */ 7264 7265 /** 7266 * All possible `VideoTrackKind`s 7267 * 7268 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind 7269 * @typedef VideoTrack~Kind 7270 * @enum 7271 */ 7272 var VideoTrackKind = { 7273 alternative: 'alternative', 7274 captions: 'captions', 7275 main: 'main', 7276 sign: 'sign', 7277 subtitles: 'subtitles', 7278 commentary: 'commentary' 7279 }; 7280 /** 7281 * All possible `AudioTrackKind`s 7282 * 7283 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind 7284 * @typedef AudioTrack~Kind 7285 * @enum 7286 */ 7287 7288 var AudioTrackKind = { 7289 'alternative': 'alternative', 7290 'descriptions': 'descriptions', 7291 'main': 'main', 7292 'main-desc': 'main-desc', 7293 'translation': 'translation', 7294 'commentary': 'commentary' 7295 }; 7296 /** 7297 * All possible `TextTrackKind`s 7298 * 7299 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind 7300 * @typedef TextTrack~Kind 7301 * @enum 7302 */ 7303 7304 var TextTrackKind = { 7305 subtitles: 'subtitles', 7306 captions: 'captions', 7307 descriptions: 'descriptions', 7308 chapters: 'chapters', 7309 metadata: 'metadata' 7310 }; 7311 /** 7312 * All possible `TextTrackMode`s 7313 * 7314 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode 7315 * @typedef TextTrack~Mode 7316 * @enum 7317 */ 7318 7319 var TextTrackMode = { 7320 disabled: 'disabled', 7321 hidden: 'hidden', 7322 showing: 'showing' 7323 }; 7324 7325 /** 7326 * A Track class that contains all of the common functionality for {@link AudioTrack}, 7327 * {@link VideoTrack}, and {@link TextTrack}. 7328 * 7329 * > Note: This class should not be used directly 7330 * 7331 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} 7332 * @extends EventTarget 7333 * @abstract 7334 */ 7335 7336 var Track = /*#__PURE__*/function (_EventTarget) { 7337 inheritsLoose(Track, _EventTarget); 7338 7339 /** 7340 * Create an instance of this class. 7341 * 7342 * @param {Object} [options={}] 7343 * Object of option names and values 7344 * 7345 * @param {string} [options.kind=''] 7346 * A valid kind for the track type you are creating. 7347 * 7348 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7349 * A unique id for this AudioTrack. 7350 * 7351 * @param {string} [options.label=''] 7352 * The menu label for this track. 7353 * 7354 * @param {string} [options.language=''] 7355 * A valid two character language code. 7356 * 7357 * @abstract 7358 */ 7359 function Track(options) { 7360 var _this; 7361 7362 if (options === void 0) { 7363 options = {}; 7364 } 7365 7366 _this = _EventTarget.call(this) || this; 7367 var trackProps = { 7368 id: options.id || 'vjs_track_' + newGUID(), 7369 kind: options.kind || '', 7370 label: options.label || '', 7371 language: options.language || '' 7372 }; 7373 /** 7374 * @memberof Track 7375 * @member {string} id 7376 * The id of this track. Cannot be changed after creation. 7377 * @instance 7378 * 7379 * @readonly 7380 */ 7381 7382 /** 7383 * @memberof Track 7384 * @member {string} kind 7385 * The kind of track that this is. Cannot be changed after creation. 7386 * @instance 7387 * 7388 * @readonly 7389 */ 7390 7391 /** 7392 * @memberof Track 7393 * @member {string} label 7394 * The label of this track. Cannot be changed after creation. 7395 * @instance 7396 * 7397 * @readonly 7398 */ 7399 7400 /** 7401 * @memberof Track 7402 * @member {string} language 7403 * The two letter language code for this track. Cannot be changed after 7404 * creation. 7405 * @instance 7406 * 7407 * @readonly 7408 */ 7409 7410 var _loop = function _loop(key) { 7411 Object.defineProperty(assertThisInitialized(_this), key, { 7412 get: function get() { 7413 return trackProps[key]; 7414 }, 7415 set: function set() {} 7416 }); 7417 }; 7418 7419 for (var key in trackProps) { 7420 _loop(key); 7421 } 7422 7423 return _this; 7424 } 7425 7426 return Track; 7427 }(EventTarget); 7428 7429 /** 7430 * @file url.js 7431 * @module url 7432 */ 7433 /** 7434 * @typedef {Object} url:URLObject 7435 * 7436 * @property {string} protocol 7437 * The protocol of the url that was parsed. 7438 * 7439 * @property {string} hostname 7440 * The hostname of the url that was parsed. 7441 * 7442 * @property {string} port 7443 * The port of the url that was parsed. 7444 * 7445 * @property {string} pathname 7446 * The pathname of the url that was parsed. 7447 * 7448 * @property {string} search 7449 * The search query of the url that was parsed. 7450 * 7451 * @property {string} hash 7452 * The hash of the url that was parsed. 7453 * 7454 * @property {string} host 7455 * The host of the url that was parsed. 7456 */ 7457 7458 /** 7459 * Resolve and parse the elements of a URL. 7460 * 7461 * @function 7462 * @param {String} url 7463 * The url to parse 7464 * 7465 * @return {url:URLObject} 7466 * An object of url details 7467 */ 7468 7469 var parseUrl = function parseUrl(url) { 7470 var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL 7471 7472 var a = document.createElement('a'); 7473 a.href = url; // IE8 (and 9?) Fix 7474 // ie8 doesn't parse the URL correctly until the anchor is actually 7475 // added to the body, and an innerHTML is needed to trigger the parsing 7476 7477 var addToBody = a.host === '' && a.protocol !== 'file:'; 7478 var div; 7479 7480 if (addToBody) { 7481 div = document.createElement('div'); 7482 div.innerHTML = "<a href=\"" + url + "\"></a>"; 7483 a = div.firstChild; // prevent the div from affecting layout 7484 7485 div.setAttribute('style', 'display:none; position:absolute;'); 7486 document.body.appendChild(div); 7487 } // Copy the specific URL properties to a new object 7488 // This is also needed for IE8 because the anchor loses its 7489 // properties when it's removed from the dom 7490 7491 7492 var details = {}; 7493 7494 for (var i = 0; i < props.length; i++) { 7495 details[props[i]] = a[props[i]]; 7496 } // IE9 adds the port to the host property unlike everyone else. If 7497 // a port identifier is added for standard ports, strip it. 7498 7499 7500 if (details.protocol === 'http:') { 7501 details.host = details.host.replace(/:80$/, ''); 7502 } 7503 7504 if (details.protocol === 'https:') { 7505 details.host = details.host.replace(/:443$/, ''); 7506 } 7507 7508 if (!details.protocol) { 7509 details.protocol = window$1.location.protocol; 7510 } 7511 7512 if (addToBody) { 7513 document.body.removeChild(div); 7514 } 7515 7516 return details; 7517 }; 7518 /** 7519 * Get absolute version of relative URL. Used to tell Flash the correct URL. 7520 * 7521 * @function 7522 * @param {string} url 7523 * URL to make absolute 7524 * 7525 * @return {string} 7526 * Absolute URL 7527 * 7528 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 7529 */ 7530 7531 var getAbsoluteURL = function getAbsoluteURL(url) { 7532 // Check if absolute URL 7533 if (!url.match(/^https?:\/\//)) { 7534 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 7535 var div = document.createElement('div'); 7536 div.innerHTML = "<a href=\"" + url + "\">x</a>"; 7537 url = div.firstChild.href; 7538 } 7539 7540 return url; 7541 }; 7542 /** 7543 * Returns the extension of the passed file name. It will return an empty string 7544 * if passed an invalid path. 7545 * 7546 * @function 7547 * @param {string} path 7548 * The fileName path like '/path/to/file.mp4' 7549 * 7550 * @return {string} 7551 * The extension in lower case or an empty string if no 7552 * extension could be found. 7553 */ 7554 7555 var getFileExtension = function getFileExtension(path) { 7556 if (typeof path === 'string') { 7557 var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/; 7558 var pathParts = splitPathRe.exec(path); 7559 7560 if (pathParts) { 7561 return pathParts.pop().toLowerCase(); 7562 } 7563 } 7564 7565 return ''; 7566 }; 7567 /** 7568 * Returns whether the url passed is a cross domain request or not. 7569 * 7570 * @function 7571 * @param {string} url 7572 * The url to check. 7573 * 7574 * @param {Object} [winLoc] 7575 * the domain to check the url against, defaults to window.location 7576 * 7577 * @param {string} [winLoc.protocol] 7578 * The window location protocol defaults to window.location.protocol 7579 * 7580 * @param {string} [winLoc.host] 7581 * The window location host defaults to window.location.host 7582 * 7583 * @return {boolean} 7584 * Whether it is a cross domain request or not. 7585 */ 7586 7587 var isCrossOrigin = function isCrossOrigin(url, winLoc) { 7588 if (winLoc === void 0) { 7589 winLoc = window$1.location; 7590 } 7591 7592 var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol 7593 7594 var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin 7595 // IE8 doesn't know location.origin, so we won't rely on it here 7596 7597 var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; 7598 return crossOrigin; 7599 }; 7600 7601 var Url = /*#__PURE__*/Object.freeze({ 7602 __proto__: null, 7603 parseUrl: parseUrl, 7604 getAbsoluteURL: getAbsoluteURL, 7605 getFileExtension: getFileExtension, 7606 isCrossOrigin: isCrossOrigin 7607 }); 7608 7609 var isFunction_1 = isFunction; 7610 var toString$1 = Object.prototype.toString; 7611 7612 function isFunction(fn) { 7613 var string = toString$1.call(fn); 7614 return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below 7615 fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt); 7616 } 7617 7618 /** 7619 * @license 7620 * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/> 7621 * Copyright (c) 2014 David Björklund 7622 * Available under the MIT license 7623 * <https://github.com/kesla/parse-headers/blob/master/LICENCE> 7624 */ 7625 7626 7627 var parseHeaders = function parseHeaders(headers) { 7628 var result = {}; 7629 7630 if (!headers) { 7631 return result; 7632 } 7633 7634 headers.trim().split('\n').forEach(function (row) { 7635 var index = row.indexOf(':'); 7636 var key = row.slice(0, index).trim().toLowerCase(); 7637 var value = row.slice(index + 1).trim(); 7638 7639 if (typeof result[key] === 'undefined') { 7640 result[key] = value; 7641 } else if (Array.isArray(result[key])) { 7642 result[key].push(value); 7643 } else { 7644 result[key] = [result[key], value]; 7645 } 7646 }); 7647 return result; 7648 }; 7649 7650 var xhr = createXHR; // Allow use of default import syntax in TypeScript 7651 7652 var default_1 = createXHR; 7653 createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop; 7654 createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest; 7655 forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) { 7656 createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) { 7657 options = initParams(uri, options, callback); 7658 options.method = method.toUpperCase(); 7659 return _createXHR(options); 7660 }; 7661 }); 7662 7663 function forEachArray(array, iterator) { 7664 for (var i = 0; i < array.length; i++) { 7665 iterator(array[i]); 7666 } 7667 } 7668 7669 function isEmpty(obj) { 7670 for (var i in obj) { 7671 if (obj.hasOwnProperty(i)) return false; 7672 } 7673 7674 return true; 7675 } 7676 7677 function initParams(uri, options, callback) { 7678 var params = uri; 7679 7680 if (isFunction_1(options)) { 7681 callback = options; 7682 7683 if (typeof uri === "string") { 7684 params = { 7685 uri: uri 7686 }; 7687 } 7688 } else { 7689 params = _extends_1({}, options, { 7690 uri: uri 7691 }); 7692 } 7693 7694 params.callback = callback; 7695 return params; 7696 } 7697 7698 function createXHR(uri, options, callback) { 7699 options = initParams(uri, options, callback); 7700 return _createXHR(options); 7701 } 7702 7703 function _createXHR(options) { 7704 if (typeof options.callback === "undefined") { 7705 throw new Error("callback argument missing"); 7706 } 7707 7708 var called = false; 7709 7710 var callback = function cbOnce(err, response, body) { 7711 if (!called) { 7712 called = true; 7713 options.callback(err, response, body); 7714 } 7715 }; 7716 7717 function readystatechange() { 7718 if (xhr.readyState === 4) { 7719 setTimeout(loadFunc, 0); 7720 } 7721 } 7722 7723 function getBody() { 7724 // Chrome with requestType=blob throws errors arround when even testing access to responseText 7725 var body = undefined; 7726 7727 if (xhr.response) { 7728 body = xhr.response; 7729 } else { 7730 body = xhr.responseText || getXml(xhr); 7731 } 7732 7733 if (isJson) { 7734 try { 7735 body = JSON.parse(body); 7736 } catch (e) {} 7737 } 7738 7739 return body; 7740 } 7741 7742 function errorFunc(evt) { 7743 clearTimeout(timeoutTimer); 7744 7745 if (!(evt instanceof Error)) { 7746 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error")); 7747 } 7748 7749 evt.statusCode = 0; 7750 return callback(evt, failureResponse); 7751 } // will load the data & process the response in a special response object 7752 7753 7754 function loadFunc() { 7755 if (aborted) return; 7756 var status; 7757 clearTimeout(timeoutTimer); 7758 7759 if (options.useXDR && xhr.status === undefined) { 7760 //IE8 CORS GET successful response doesn't have a status field, but body is fine 7761 status = 200; 7762 } else { 7763 status = xhr.status === 1223 ? 204 : xhr.status; 7764 } 7765 7766 var response = failureResponse; 7767 var err = null; 7768 7769 if (status !== 0) { 7770 response = { 7771 body: getBody(), 7772 statusCode: status, 7773 method: method, 7774 headers: {}, 7775 url: uri, 7776 rawRequest: xhr 7777 }; 7778 7779 if (xhr.getAllResponseHeaders) { 7780 //remember xhr can in fact be XDR for CORS in IE 7781 response.headers = parseHeaders(xhr.getAllResponseHeaders()); 7782 } 7783 } else { 7784 err = new Error("Internal XMLHttpRequest Error"); 7785 } 7786 7787 return callback(err, response, response.body); 7788 } 7789 7790 var xhr = options.xhr || null; 7791 7792 if (!xhr) { 7793 if (options.cors || options.useXDR) { 7794 xhr = new createXHR.XDomainRequest(); 7795 } else { 7796 xhr = new createXHR.XMLHttpRequest(); 7797 } 7798 } 7799 7800 var key; 7801 var aborted; 7802 var uri = xhr.url = options.uri || options.url; 7803 var method = xhr.method = options.method || "GET"; 7804 var body = options.body || options.data; 7805 var headers = xhr.headers = options.headers || {}; 7806 var sync = !!options.sync; 7807 var isJson = false; 7808 var timeoutTimer; 7809 var failureResponse = { 7810 body: undefined, 7811 headers: {}, 7812 statusCode: 0, 7813 method: method, 7814 url: uri, 7815 rawRequest: xhr 7816 }; 7817 7818 if ("json" in options && options.json !== false) { 7819 isJson = true; 7820 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 7821 7822 if (method !== "GET" && method !== "HEAD") { 7823 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 7824 7825 body = JSON.stringify(options.json === true ? body : options.json); 7826 } 7827 } 7828 7829 xhr.onreadystatechange = readystatechange; 7830 xhr.onload = loadFunc; 7831 xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function. 7832 7833 xhr.onprogress = function () {// IE must die 7834 }; 7835 7836 xhr.onabort = function () { 7837 aborted = true; 7838 }; 7839 7840 xhr.ontimeout = errorFunc; 7841 xhr.open(method, uri, !sync, options.username, options.password); //has to be after open 7842 7843 if (!sync) { 7844 xhr.withCredentials = !!options.withCredentials; 7845 } // Cannot set timeout with sync request 7846 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 7847 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 7848 7849 7850 if (!sync && options.timeout > 0) { 7851 timeoutTimer = setTimeout(function () { 7852 if (aborted) return; 7853 aborted = true; //IE9 may still call readystatechange 7854 7855 xhr.abort("timeout"); 7856 var e = new Error("XMLHttpRequest timeout"); 7857 e.code = "ETIMEDOUT"; 7858 errorFunc(e); 7859 }, options.timeout); 7860 } 7861 7862 if (xhr.setRequestHeader) { 7863 for (key in headers) { 7864 if (headers.hasOwnProperty(key)) { 7865 xhr.setRequestHeader(key, headers[key]); 7866 } 7867 } 7868 } else if (options.headers && !isEmpty(options.headers)) { 7869 throw new Error("Headers cannot be set on an XDomainRequest object"); 7870 } 7871 7872 if ("responseType" in options) { 7873 xhr.responseType = options.responseType; 7874 } 7875 7876 if ("beforeSend" in options && typeof options.beforeSend === "function") { 7877 options.beforeSend(xhr); 7878 } // Microsoft Edge browser sends "undefined" when send is called with undefined value. 7879 // XMLHttpRequest spec says to pass null as body to indicate no body 7880 // See https://github.com/naugtur/xhr/issues/100. 7881 7882 7883 xhr.send(body || null); 7884 return xhr; 7885 } 7886 7887 function getXml(xhr) { 7888 // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" 7889 // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. 7890 try { 7891 if (xhr.responseType === "document") { 7892 return xhr.responseXML; 7893 } 7894 7895 var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 7896 7897 if (xhr.responseType === "" && !firefoxBugTakenEffect) { 7898 return xhr.responseXML; 7899 } 7900 } catch (e) {} 7901 7902 return null; 7903 } 7904 7905 function noop() {} 7906 xhr["default"] = default_1; 7907 7908 /** 7909 * Takes a webvtt file contents and parses it into cues 7910 * 7911 * @param {string} srcContent 7912 * webVTT file contents 7913 * 7914 * @param {TextTrack} track 7915 * TextTrack to add cues to. Cues come from the srcContent. 7916 * 7917 * @private 7918 */ 7919 7920 var parseCues = function parseCues(srcContent, track) { 7921 var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder()); 7922 var errors = []; 7923 7924 parser.oncue = function (cue) { 7925 track.addCue(cue); 7926 }; 7927 7928 parser.onparsingerror = function (error) { 7929 errors.push(error); 7930 }; 7931 7932 parser.onflush = function () { 7933 track.trigger({ 7934 type: 'loadeddata', 7935 target: track 7936 }); 7937 }; 7938 7939 parser.parse(srcContent); 7940 7941 if (errors.length > 0) { 7942 if (window$1.console && window$1.console.groupCollapsed) { 7943 window$1.console.groupCollapsed("Text Track parsing errors for " + track.src); 7944 } 7945 7946 errors.forEach(function (error) { 7947 return log.error(error); 7948 }); 7949 7950 if (window$1.console && window$1.console.groupEnd) { 7951 window$1.console.groupEnd(); 7952 } 7953 } 7954 7955 parser.flush(); 7956 }; 7957 /** 7958 * Load a `TextTrack` from a specified url. 7959 * 7960 * @param {string} src 7961 * Url to load track from. 7962 * 7963 * @param {TextTrack} track 7964 * Track to add cues to. Comes from the content at the end of `url`. 7965 * 7966 * @private 7967 */ 7968 7969 7970 var loadTrack = function loadTrack(src, track) { 7971 var opts = { 7972 uri: src 7973 }; 7974 var crossOrigin = isCrossOrigin(src); 7975 7976 if (crossOrigin) { 7977 opts.cors = crossOrigin; 7978 } 7979 7980 var withCredentials = track.tech_.crossOrigin() === 'use-credentials'; 7981 7982 if (withCredentials) { 7983 opts.withCredentials = withCredentials; 7984 } 7985 7986 xhr(opts, bind(this, function (err, response, responseBody) { 7987 if (err) { 7988 return log.error(err, response); 7989 } 7990 7991 track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 7992 // NOTE: this is only used for the alt/video.novtt.js build 7993 7994 if (typeof window$1.WebVTT !== 'function') { 7995 if (track.tech_) { 7996 // to prevent use before define eslint error, we define loadHandler 7997 // as a let here 7998 track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) { 7999 if (event.type === 'vttjserror') { 8000 log.error("vttjs failed to load, stopping trying to process " + track.src); 8001 return; 8002 } 8003 8004 return parseCues(responseBody, track); 8005 }); 8006 } 8007 } else { 8008 parseCues(responseBody, track); 8009 } 8010 })); 8011 }; 8012 /** 8013 * A representation of a single `TextTrack`. 8014 * 8015 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} 8016 * @extends Track 8017 */ 8018 8019 8020 var TextTrack = /*#__PURE__*/function (_Track) { 8021 inheritsLoose(TextTrack, _Track); 8022 8023 /** 8024 * Create an instance of this class. 8025 * 8026 * @param {Object} options={} 8027 * Object of option names and values 8028 * 8029 * @param {Tech} options.tech 8030 * A reference to the tech that owns this TextTrack. 8031 * 8032 * @param {TextTrack~Kind} [options.kind='subtitles'] 8033 * A valid text track kind. 8034 * 8035 * @param {TextTrack~Mode} [options.mode='disabled'] 8036 * A valid text track mode. 8037 * 8038 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8039 * A unique id for this TextTrack. 8040 * 8041 * @param {string} [options.label=''] 8042 * The menu label for this track. 8043 * 8044 * @param {string} [options.language=''] 8045 * A valid two character language code. 8046 * 8047 * @param {string} [options.srclang=''] 8048 * A valid two character language code. An alternative, but deprioritized 8049 * version of `options.language` 8050 * 8051 * @param {string} [options.src] 8052 * A url to TextTrack cues. 8053 * 8054 * @param {boolean} [options.default] 8055 * If this track should default to on or off. 8056 */ 8057 function TextTrack(options) { 8058 var _this; 8059 8060 if (options === void 0) { 8061 options = {}; 8062 } 8063 8064 if (!options.tech) { 8065 throw new Error('A tech was not provided.'); 8066 } 8067 8068 var settings = mergeOptions(options, { 8069 kind: TextTrackKind[options.kind] || 'subtitles', 8070 language: options.language || options.srclang || '' 8071 }); 8072 var mode = TextTrackMode[settings.mode] || 'disabled'; 8073 var default_ = settings["default"]; 8074 8075 if (settings.kind === 'metadata' || settings.kind === 'chapters') { 8076 mode = 'hidden'; 8077 } 8078 8079 _this = _Track.call(this, settings) || this; 8080 _this.tech_ = settings.tech; 8081 _this.cues_ = []; 8082 _this.activeCues_ = []; 8083 _this.preload_ = _this.tech_.preloadTextTracks !== false; 8084 var cues = new TextTrackCueList(_this.cues_); 8085 var activeCues = new TextTrackCueList(_this.activeCues_); 8086 var changed = false; 8087 var timeupdateHandler = bind(assertThisInitialized(_this), function () { 8088 // Accessing this.activeCues for the side-effects of updating itself 8089 // due to its nature as a getter function. Do not remove or cues will 8090 // stop updating! 8091 // Use the setter to prevent deletion from uglify (pure_getters rule) 8092 this.activeCues = this.activeCues; 8093 8094 if (changed) { 8095 this.trigger('cuechange'); 8096 changed = false; 8097 } 8098 }); 8099 8100 if (mode !== 'disabled') { 8101 _this.tech_.ready(function () { 8102 _this.tech_.on('timeupdate', timeupdateHandler); 8103 }, true); 8104 } 8105 8106 Object.defineProperties(assertThisInitialized(_this), { 8107 /** 8108 * @memberof TextTrack 8109 * @member {boolean} default 8110 * If this track was set to be on or off by default. Cannot be changed after 8111 * creation. 8112 * @instance 8113 * 8114 * @readonly 8115 */ 8116 "default": { 8117 get: function get() { 8118 return default_; 8119 }, 8120 set: function set() {} 8121 }, 8122 8123 /** 8124 * @memberof TextTrack 8125 * @member {string} mode 8126 * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will 8127 * not be set if setting to an invalid mode. 8128 * @instance 8129 * 8130 * @fires TextTrack#modechange 8131 */ 8132 mode: { 8133 get: function get() { 8134 return mode; 8135 }, 8136 set: function set(newMode) { 8137 var _this2 = this; 8138 8139 if (!TextTrackMode[newMode]) { 8140 return; 8141 } 8142 8143 mode = newMode; 8144 8145 if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) { 8146 // On-demand load. 8147 loadTrack(this.src, this); 8148 } 8149 8150 if (mode !== 'disabled') { 8151 this.tech_.ready(function () { 8152 _this2.tech_.on('timeupdate', timeupdateHandler); 8153 }, true); 8154 } else { 8155 this.tech_.off('timeupdate', timeupdateHandler); 8156 } 8157 /** 8158 * An event that fires when mode changes on this track. This allows 8159 * the TextTrackList that holds this track to act accordingly. 8160 * 8161 * > Note: This is not part of the spec! 8162 * 8163 * @event TextTrack#modechange 8164 * @type {EventTarget~Event} 8165 */ 8166 8167 8168 this.trigger('modechange'); 8169 } 8170 }, 8171 8172 /** 8173 * @memberof TextTrack 8174 * @member {TextTrackCueList} cues 8175 * The text track cue list for this TextTrack. 8176 * @instance 8177 */ 8178 cues: { 8179 get: function get() { 8180 if (!this.loaded_) { 8181 return null; 8182 } 8183 8184 return cues; 8185 }, 8186 set: function set() {} 8187 }, 8188 8189 /** 8190 * @memberof TextTrack 8191 * @member {TextTrackCueList} activeCues 8192 * The list text track cues that are currently active for this TextTrack. 8193 * @instance 8194 */ 8195 activeCues: { 8196 get: function get() { 8197 if (!this.loaded_) { 8198 return null; 8199 } // nothing to do 8200 8201 8202 if (this.cues.length === 0) { 8203 return activeCues; 8204 } 8205 8206 var ct = this.tech_.currentTime(); 8207 var active = []; 8208 8209 for (var i = 0, l = this.cues.length; i < l; i++) { 8210 var cue = this.cues[i]; 8211 8212 if (cue.startTime <= ct && cue.endTime >= ct) { 8213 active.push(cue); 8214 } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { 8215 active.push(cue); 8216 } 8217 } 8218 8219 changed = false; 8220 8221 if (active.length !== this.activeCues_.length) { 8222 changed = true; 8223 } else { 8224 for (var _i = 0; _i < active.length; _i++) { 8225 if (this.activeCues_.indexOf(active[_i]) === -1) { 8226 changed = true; 8227 } 8228 } 8229 } 8230 8231 this.activeCues_ = active; 8232 activeCues.setCues_(this.activeCues_); 8233 return activeCues; 8234 }, 8235 // /!\ Keep this setter empty (see the timeupdate handler above) 8236 set: function set() {} 8237 } 8238 }); 8239 8240 if (settings.src) { 8241 _this.src = settings.src; 8242 8243 if (!_this.preload_) { 8244 // Tracks will load on-demand. 8245 // Act like we're loaded for other purposes. 8246 _this.loaded_ = true; 8247 } 8248 8249 if (_this.preload_ || default_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') { 8250 loadTrack(_this.src, assertThisInitialized(_this)); 8251 } 8252 } else { 8253 _this.loaded_ = true; 8254 } 8255 8256 return _this; 8257 } 8258 /** 8259 * Add a cue to the internal list of cues. 8260 * 8261 * @param {TextTrack~Cue} cue 8262 * The cue to add to our internal list 8263 */ 8264 8265 8266 var _proto = TextTrack.prototype; 8267 8268 _proto.addCue = function addCue(originalCue) { 8269 var cue = originalCue; 8270 8271 if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) { 8272 cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); 8273 8274 for (var prop in originalCue) { 8275 if (!(prop in cue)) { 8276 cue[prop] = originalCue[prop]; 8277 } 8278 } // make sure that `id` is copied over 8279 8280 8281 cue.id = originalCue.id; 8282 cue.originalCue_ = originalCue; 8283 } 8284 8285 var tracks = this.tech_.textTracks(); 8286 8287 for (var i = 0; i < tracks.length; i++) { 8288 if (tracks[i] !== this) { 8289 tracks[i].removeCue(cue); 8290 } 8291 } 8292 8293 this.cues_.push(cue); 8294 this.cues.setCues_(this.cues_); 8295 } 8296 /** 8297 * Remove a cue from our internal list 8298 * 8299 * @param {TextTrack~Cue} removeCue 8300 * The cue to remove from our internal list 8301 */ 8302 ; 8303 8304 _proto.removeCue = function removeCue(_removeCue) { 8305 var i = this.cues_.length; 8306 8307 while (i--) { 8308 var cue = this.cues_[i]; 8309 8310 if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { 8311 this.cues_.splice(i, 1); 8312 this.cues.setCues_(this.cues_); 8313 break; 8314 } 8315 } 8316 }; 8317 8318 return TextTrack; 8319 }(Track); 8320 /** 8321 * cuechange - One or more cues in the track have become active or stopped being active. 8322 */ 8323 8324 8325 TextTrack.prototype.allowedEvents_ = { 8326 cuechange: 'cuechange' 8327 }; 8328 8329 /** 8330 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} 8331 * only one `AudioTrack` in the list will be enabled at a time. 8332 * 8333 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} 8334 * @extends Track 8335 */ 8336 8337 var AudioTrack = /*#__PURE__*/function (_Track) { 8338 inheritsLoose(AudioTrack, _Track); 8339 8340 /** 8341 * Create an instance of this class. 8342 * 8343 * @param {Object} [options={}] 8344 * Object of option names and values 8345 * 8346 * @param {AudioTrack~Kind} [options.kind=''] 8347 * A valid audio track kind 8348 * 8349 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8350 * A unique id for this AudioTrack. 8351 * 8352 * @param {string} [options.label=''] 8353 * The menu label for this track. 8354 * 8355 * @param {string} [options.language=''] 8356 * A valid two character language code. 8357 * 8358 * @param {boolean} [options.enabled] 8359 * If this track is the one that is currently playing. If this track is part of 8360 * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. 8361 */ 8362 function AudioTrack(options) { 8363 var _this; 8364 8365 if (options === void 0) { 8366 options = {}; 8367 } 8368 8369 var settings = mergeOptions(options, { 8370 kind: AudioTrackKind[options.kind] || '' 8371 }); 8372 _this = _Track.call(this, settings) || this; 8373 var enabled = false; 8374 /** 8375 * @memberof AudioTrack 8376 * @member {boolean} enabled 8377 * If this `AudioTrack` is enabled or not. When setting this will 8378 * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. 8379 * @instance 8380 * 8381 * @fires VideoTrack#selectedchange 8382 */ 8383 8384 Object.defineProperty(assertThisInitialized(_this), 'enabled', { 8385 get: function get() { 8386 return enabled; 8387 }, 8388 set: function set(newEnabled) { 8389 // an invalid or unchanged value 8390 if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { 8391 return; 8392 } 8393 8394 enabled = newEnabled; 8395 /** 8396 * An event that fires when enabled changes on this track. This allows 8397 * the AudioTrackList that holds this track to act accordingly. 8398 * 8399 * > Note: This is not part of the spec! Native tracks will do 8400 * this internally without an event. 8401 * 8402 * @event AudioTrack#enabledchange 8403 * @type {EventTarget~Event} 8404 */ 8405 8406 this.trigger('enabledchange'); 8407 } 8408 }); // if the user sets this track to selected then 8409 // set selected to that true value otherwise 8410 // we keep it false 8411 8412 if (settings.enabled) { 8413 _this.enabled = settings.enabled; 8414 } 8415 8416 _this.loaded_ = true; 8417 return _this; 8418 } 8419 8420 return AudioTrack; 8421 }(Track); 8422 8423 /** 8424 * A representation of a single `VideoTrack`. 8425 * 8426 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} 8427 * @extends Track 8428 */ 8429 8430 var VideoTrack = /*#__PURE__*/function (_Track) { 8431 inheritsLoose(VideoTrack, _Track); 8432 8433 /** 8434 * Create an instance of this class. 8435 * 8436 * @param {Object} [options={}] 8437 * Object of option names and values 8438 * 8439 * @param {string} [options.kind=''] 8440 * A valid {@link VideoTrack~Kind} 8441 * 8442 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8443 * A unique id for this AudioTrack. 8444 * 8445 * @param {string} [options.label=''] 8446 * The menu label for this track. 8447 * 8448 * @param {string} [options.language=''] 8449 * A valid two character language code. 8450 * 8451 * @param {boolean} [options.selected] 8452 * If this track is the one that is currently playing. 8453 */ 8454 function VideoTrack(options) { 8455 var _this; 8456 8457 if (options === void 0) { 8458 options = {}; 8459 } 8460 8461 var settings = mergeOptions(options, { 8462 kind: VideoTrackKind[options.kind] || '' 8463 }); 8464 _this = _Track.call(this, settings) || this; 8465 var selected = false; 8466 /** 8467 * @memberof VideoTrack 8468 * @member {boolean} selected 8469 * If this `VideoTrack` is selected or not. When setting this will 8470 * fire {@link VideoTrack#selectedchange} if the state of selected changed. 8471 * @instance 8472 * 8473 * @fires VideoTrack#selectedchange 8474 */ 8475 8476 Object.defineProperty(assertThisInitialized(_this), 'selected', { 8477 get: function get() { 8478 return selected; 8479 }, 8480 set: function set(newSelected) { 8481 // an invalid or unchanged value 8482 if (typeof newSelected !== 'boolean' || newSelected === selected) { 8483 return; 8484 } 8485 8486 selected = newSelected; 8487 /** 8488 * An event that fires when selected changes on this track. This allows 8489 * the VideoTrackList that holds this track to act accordingly. 8490 * 8491 * > Note: This is not part of the spec! Native tracks will do 8492 * this internally without an event. 8493 * 8494 * @event VideoTrack#selectedchange 8495 * @type {EventTarget~Event} 8496 */ 8497 8498 this.trigger('selectedchange'); 8499 } 8500 }); // if the user sets this track to selected then 8501 // set selected to that true value otherwise 8502 // we keep it false 8503 8504 if (settings.selected) { 8505 _this.selected = settings.selected; 8506 } 8507 8508 return _this; 8509 } 8510 8511 return VideoTrack; 8512 }(Track); 8513 8514 /** 8515 * @memberof HTMLTrackElement 8516 * @typedef {HTMLTrackElement~ReadyState} 8517 * @enum {number} 8518 */ 8519 8520 var NONE = 0; 8521 var LOADING = 1; 8522 var LOADED = 2; 8523 var ERROR = 3; 8524 /** 8525 * A single track represented in the DOM. 8526 * 8527 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement} 8528 * @extends EventTarget 8529 */ 8530 8531 var HTMLTrackElement = /*#__PURE__*/function (_EventTarget) { 8532 inheritsLoose(HTMLTrackElement, _EventTarget); 8533 8534 /** 8535 * Create an instance of this class. 8536 * 8537 * @param {Object} options={} 8538 * Object of option names and values 8539 * 8540 * @param {Tech} options.tech 8541 * A reference to the tech that owns this HTMLTrackElement. 8542 * 8543 * @param {TextTrack~Kind} [options.kind='subtitles'] 8544 * A valid text track kind. 8545 * 8546 * @param {TextTrack~Mode} [options.mode='disabled'] 8547 * A valid text track mode. 8548 * 8549 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8550 * A unique id for this TextTrack. 8551 * 8552 * @param {string} [options.label=''] 8553 * The menu label for this track. 8554 * 8555 * @param {string} [options.language=''] 8556 * A valid two character language code. 8557 * 8558 * @param {string} [options.srclang=''] 8559 * A valid two character language code. An alternative, but deprioritized 8560 * vesion of `options.language` 8561 * 8562 * @param {string} [options.src] 8563 * A url to TextTrack cues. 8564 * 8565 * @param {boolean} [options.default] 8566 * If this track should default to on or off. 8567 */ 8568 function HTMLTrackElement(options) { 8569 var _this; 8570 8571 if (options === void 0) { 8572 options = {}; 8573 } 8574 8575 _this = _EventTarget.call(this) || this; 8576 var readyState; 8577 var track = new TextTrack(options); 8578 _this.kind = track.kind; 8579 _this.src = track.src; 8580 _this.srclang = track.language; 8581 _this.label = track.label; 8582 _this["default"] = track["default"]; 8583 Object.defineProperties(assertThisInitialized(_this), { 8584 /** 8585 * @memberof HTMLTrackElement 8586 * @member {HTMLTrackElement~ReadyState} readyState 8587 * The current ready state of the track element. 8588 * @instance 8589 */ 8590 readyState: { 8591 get: function get() { 8592 return readyState; 8593 } 8594 }, 8595 8596 /** 8597 * @memberof HTMLTrackElement 8598 * @member {TextTrack} track 8599 * The underlying TextTrack object. 8600 * @instance 8601 * 8602 */ 8603 track: { 8604 get: function get() { 8605 return track; 8606 } 8607 } 8608 }); 8609 readyState = NONE; 8610 /** 8611 * @listens TextTrack#loadeddata 8612 * @fires HTMLTrackElement#load 8613 */ 8614 8615 track.addEventListener('loadeddata', function () { 8616 readyState = LOADED; 8617 8618 _this.trigger({ 8619 type: 'load', 8620 target: assertThisInitialized(_this) 8621 }); 8622 }); 8623 return _this; 8624 } 8625 8626 return HTMLTrackElement; 8627 }(EventTarget); 8628 8629 HTMLTrackElement.prototype.allowedEvents_ = { 8630 load: 'load' 8631 }; 8632 HTMLTrackElement.NONE = NONE; 8633 HTMLTrackElement.LOADING = LOADING; 8634 HTMLTrackElement.LOADED = LOADED; 8635 HTMLTrackElement.ERROR = ERROR; 8636 8637 /* 8638 * This file contains all track properties that are used in 8639 * player.js, tech.js, html5.js and possibly other techs in the future. 8640 */ 8641 8642 var NORMAL = { 8643 audio: { 8644 ListClass: AudioTrackList, 8645 TrackClass: AudioTrack, 8646 capitalName: 'Audio' 8647 }, 8648 video: { 8649 ListClass: VideoTrackList, 8650 TrackClass: VideoTrack, 8651 capitalName: 'Video' 8652 }, 8653 text: { 8654 ListClass: TextTrackList, 8655 TrackClass: TextTrack, 8656 capitalName: 'Text' 8657 } 8658 }; 8659 Object.keys(NORMAL).forEach(function (type) { 8660 NORMAL[type].getterName = type + "Tracks"; 8661 NORMAL[type].privateName = type + "Tracks_"; 8662 }); 8663 var REMOTE = { 8664 remoteText: { 8665 ListClass: TextTrackList, 8666 TrackClass: TextTrack, 8667 capitalName: 'RemoteText', 8668 getterName: 'remoteTextTracks', 8669 privateName: 'remoteTextTracks_' 8670 }, 8671 remoteTextEl: { 8672 ListClass: HtmlTrackElementList, 8673 TrackClass: HTMLTrackElement, 8674 capitalName: 'RemoteTextTrackEls', 8675 getterName: 'remoteTextTrackEls', 8676 privateName: 'remoteTextTrackEls_' 8677 } 8678 }; 8679 8680 var ALL = _extends_1({}, NORMAL, REMOTE); 8681 8682 REMOTE.names = Object.keys(REMOTE); 8683 NORMAL.names = Object.keys(NORMAL); 8684 ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); 8685 8686 /** 8687 * Copyright 2013 vtt.js Contributors 8688 * 8689 * Licensed under the Apache License, Version 2.0 (the "License");
8690 * you may not use this file except in compliance with the License. 8691 * You may obtain a copy of the License at 8692 * 8693 * http://www.apache.org/licenses/LICENSE-2.0 8694 * 8695 * Unless required by applicable law or agreed to in writing, software 8696 * distributed under the License is distributed on an "AS IS" BASIS, 8697 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8698 * See the License for the specific language governing permissions and 8699 * limitations under the License. 8700 */ 8701 8702 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 8703 8704 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 8705 8706 var _objCreate = Object.create || function () { 8707 function F() {} 8708 8709 return function (o) { 8710 if (arguments.length !== 1) { 8711 throw new Error('Object.create shim only accepts one parameter.'); 8712 } 8713 8714 F.prototype = o; 8715 return new F(); 8716 }; 8717 }(); // Creates a new ParserError object from an errorData object. The errorData 8718 // object should have default code and message properties. The default message 8719 // property can be overriden by passing in a message parameter. 8720 // See ParsingError.Errors below for acceptable errors. 8721 8722 8723 function ParsingError(errorData, message) { 8724 this.name = "ParsingError"; 8725 this.code = errorData.code; 8726 this.message = message || errorData.message; 8727 } 8728 8729 ParsingError.prototype = _objCreate(Error.prototype); 8730 ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors. 8731 8732 ParsingError.Errors = { 8733 BadSignature: { 8734 code: 0, 8735 message: "Malformed WebVTT signature." 8736 }, 8737 BadTimeStamp: { 8738 code: 1, 8739 message: "Malformed time stamp." 8740 } 8741 }; // Try to parse input as a time stamp. 8742 8743 function parseTimeStamp(input) { 8744 function computeSeconds(h, m, s, f) { 8745 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 8746 } 8747 8748 var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/); 8749 8750 if (!m) { 8751 return null; 8752 } 8753 8754 if (m[3]) { 8755 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 8756 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 8757 } else if (m[1] > 59) { 8758 // Timestamp takes the form of [hours]:[minutes].[milliseconds] 8759 // First position is hours as it's over 59. 8760 return computeSeconds(m[1], m[2], 0, m[4]); 8761 } else { 8762 // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 8763 return computeSeconds(0, m[1], m[2], m[4]); 8764 } 8765 } // A settings object holds key/value pairs and will ignore anything but the first 8766 // assignment to a specific key. 8767 8768 8769 function Settings() { 8770 this.values = _objCreate(null); 8771 } 8772 8773 Settings.prototype = { 8774 // Only accept the first assignment to any key. 8775 set: function set(k, v) { 8776 if (!this.get(k) && v !== "") { 8777 this.values[k] = v; 8778 } 8779 }, 8780 // Return the value for a key, or a default value. 8781 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 8782 // a number of possible default values as properties where 'defaultKey' is 8783 // the key of the property that will be chosen; otherwise it's assumed to be 8784 // a single value. 8785 get: function get(k, dflt, defaultKey) { 8786 if (defaultKey) { 8787 return this.has(k) ? this.values[k] : dflt[defaultKey]; 8788 } 8789 8790 return this.has(k) ? this.values[k] : dflt; 8791 }, 8792 // Check whether we have a value for a key. 8793 has: function has(k) { 8794 return k in this.values; 8795 }, 8796 // Accept a setting if its one of the given alternatives. 8797 alt: function alt(k, v, a) { 8798 for (var n = 0; n < a.length; ++n) { 8799 if (v === a[n]) { 8800 this.set(k, v); 8801 break; 8802 } 8803 } 8804 }, 8805 // Accept a setting if its a valid (signed) integer. 8806 integer: function integer(k, v) { 8807 if (/^-?\d+$/.test(v)) { 8808 // integer 8809 this.set(k, parseInt(v, 10)); 8810 } 8811 }, 8812 // Accept a setting if its a valid percentage. 8813 percent: function percent(k, v) { 8814 var m; 8815 8816 if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) { 8817 v = parseFloat(v); 8818 8819 if (v >= 0 && v <= 100) { 8820 this.set(k, v); 8821 return true; 8822 } 8823 } 8824 8825 return false;
vendor: 5,448 bytes, lines 8826-9027
8826 } 8827 }; // Helper function to parse input into groups separated by 'groupDelim', and 8828 // interprete each group as a key/value pair separated by 'keyValueDelim'. 8829 8830 function parseOptions(input, callback, keyValueDelim, groupDelim) { 8831 var groups = groupDelim ? input.split(groupDelim) : [input]; 8832 8833 for (var i in groups) { 8834 if (typeof groups[i] !== "string") { 8835 continue; 8836 } 8837 8838 var kv = groups[i].split(keyValueDelim); 8839 8840 if (kv.length !== 2) { 8841 continue; 8842 } 8843 8844 var k = kv[0]; 8845 var v = kv[1]; 8846 callback(k, v); 8847 } 8848 } 8849 8850 function parseCue(input, cue, regionList) { 8851 // Remember the original input if we need to throw an error. 8852 var oInput = input; // 4.1 WebVTT timestamp 8853 8854 function consumeTimeStamp() { 8855 var ts = parseTimeStamp(input); 8856 8857 if (ts === null) { 8858 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput); 8859 } // Remove time stamp from input. 8860 8861 8862 input = input.replace(/^[^\sa-zA-Z-]+/, ""); 8863 return ts; 8864 } // 4.4.2 WebVTT cue settings 8865 8866 8867 function consumeCueSettings(input, cue) { 8868 var settings = new Settings(); 8869 parseOptions(input, function (k, v) { 8870 switch (k) { 8871 case "region": 8872 // Find the last region we parsed with the same region id. 8873 for (var i = regionList.length - 1; i >= 0; i--) { 8874 if (regionList[i].id === v) { 8875 settings.set(k, regionList[i].region); 8876 break; 8877 } 8878 } 8879 8880 break; 8881 8882 case "vertical": 8883 settings.alt(k, v, ["rl", "lr"]); 8884 break; 8885 8886 case "line": 8887 var vals = v.split(","), 8888 vals0 = vals[0]; 8889 settings.integer(k, vals0); 8890 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 8891 settings.alt(k, vals0, ["auto"]); 8892 8893 if (vals.length === 2) { 8894 settings.alt("lineAlign", vals[1], ["start", "center", "end"]); 8895 } 8896 8897 break; 8898 8899 case "position": 8900 vals = v.split(","); 8901 settings.percent(k, vals[0]); 8902 8903 if (vals.length === 2) { 8904 settings.alt("positionAlign", vals[1], ["start", "center", "end"]); 8905 } 8906 8907 break; 8908 8909 case "size": 8910 settings.percent(k, v); 8911 break; 8912 8913 case "align": 8914 settings.alt(k, v, ["start", "center", "end", "left", "right"]); 8915 break; 8916 } 8917 }, /:/, /\s/); // Apply default values for any missing fields. 8918 8919 cue.region = settings.get("region", null); 8920 cue.vertical = settings.get("vertical", ""); 8921 8922 try { 8923 cue.line = settings.get("line", "auto"); 8924 } catch (e) {} 8925 8926 cue.lineAlign = settings.get("lineAlign", "start"); 8927 cue.snapToLines = settings.get("snapToLines", true); 8928 cue.size = settings.get("size", 100); // Safari still uses the old middle value and won't accept center 8929 8930 try { 8931 cue.align = settings.get("align", "center"); 8932 } catch (e) { 8933 cue.align = settings.get("align", "middle"); 8934 } 8935 8936 try { 8937 cue.position = settings.get("position", "auto"); 8938 } catch (e) { 8939 cue.position = settings.get("position", { 8940 start: 0, 8941 left: 0, 8942 center: 50, 8943 middle: 50, 8944 end: 100, 8945 right: 100 8946 }, cue.align); 8947 } 8948 8949 cue.positionAlign = settings.get("positionAlign", { 8950 start: "start", 8951 left: "start", 8952 center: "center", 8953 middle: "center", 8954 end: "end", 8955 right: "end" 8956 }, cue.align); 8957 } 8958 8959 function skipWhitespace() { 8960 input = input.replace(/^\s+/, ""); 8961 } // 4.1 WebVTT cue timings. 8962 8963 8964 skipWhitespace(); 8965 cue.startTime = consumeTimeStamp(); // (1) collect cue start time 8966 8967 skipWhitespace(); 8968 8969 if (input.substr(0, 3) !== "-->") { 8970 // (3) next characters must match "-->" 8971 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput); 8972 } 8973 8974 input = input.substr(3); 8975 skipWhitespace(); 8976 cue.endTime = consumeTimeStamp(); // (5) collect cue end time 8977 // 4.1 WebVTT cue settings list. 8978 8979 skipWhitespace(); 8980 consumeCueSettings(input, cue); 8981 } 8982 8983 var TEXTAREA_ELEMENT = document.createElement("textarea"); 8984 var TAG_NAME = { 8985 c: "span", 8986 i: "i", 8987 b: "b", 8988 u: "u", 8989 ruby: "ruby", 8990 rt: "rt", 8991 v: "span", 8992 lang: "span" 8993 }; // 5.1 default text color 8994 // 5.2 default text background color is equivalent to text color with bg_ prefix 8995 8996 var DEFAULT_COLOR_CLASS = { 8997 white: 'rgba(255,255,255,1)', 8998 lime: 'rgba(0,255,0,1)', 8999 cyan: 'rgba(0,255,255,1)', 9000 red: 'rgba(255,0,0,1)', 9001 yellow: 'rgba(255,255,0,1)', 9002 magenta: 'rgba(255,0,255,1)', 9003 blue: 'rgba(0,0,255,1)', 9004 black: 'rgba(0,0,0,1)' 9005 }; 9006 var TAG_ANNOTATION = { 9007 v: "title", 9008 lang: "lang" 9009 }; 9010 var NEEDS_PARENT = { 9011 rt: "ruby" 9012 }; // Parse content into a document fragment. 9013 9014 function parseContent(window, input) { 9015 function nextToken() { 9016 // Check for end-of-string. 9017 if (!input) { 9018 return null; 9019 } // Consume 'n' characters from the input. 9020 9021 9022 function consume(result) { 9023 input = input.substr(result.length); 9024 return result; 9025 } 9026 9027 var m = input.match(/^([^<]*)(<[^>
vendor: 23,198 bytes, lines 9027-9687
9027]*>?)?/); // If there is some text before the next tag, return it, otherwise return 9028 // the tag. 9029 9030 return consume(m[1] ? m[1] : m[2]); 9031 } 9032 9033 function unescape(s) { 9034 TEXTAREA_ELEMENT.innerHTML = s; 9035 s = TEXTAREA_ELEMENT.textContent; 9036 TEXTAREA_ELEMENT.textContent = ""; 9037 return s; 9038 } 9039 9040 function shouldAdd(current, element) { 9041 return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName; 9042 } // Create an element for this tag. 9043 9044 9045 function createElement(type, annotation) { 9046 var tagName = TAG_NAME[type]; 9047 9048 if (!tagName) { 9049 return null; 9050 } 9051 9052 var element = window.document.createElement(tagName); 9053 var name = TAG_ANNOTATION[type]; 9054 9055 if (name && annotation) { 9056 element[name] = annotation.trim(); 9057 } 9058 9059 return element; 9060 } 9061 9062 var rootDiv = window.document.createElement("div"), 9063 current = rootDiv, 9064 t, 9065 tagStack = []; 9066 9067 while ((t = nextToken()) !== null) { 9068 if (t[0] === '<') { 9069 if (t[1] === "/") { 9070 // If the closing tag matches, move back up to the parent node. 9071 if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 9072 tagStack.pop(); 9073 current = current.parentNode; 9074 } // Otherwise just ignore the end tag. 9075 9076 9077 continue; 9078 } 9079 9080 var ts = parseTimeStamp(t.substr(1, t.length - 2)); 9081 var node; 9082 9083 if (ts) { 9084 // Timestamps are lead nodes as well. 9085 node = window.document.createProcessingInstruction("timestamp", ts); 9086 current.appendChild(node); 9087 continue; 9088 } 9089 9090 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag. 9091 9092 if (!m) { 9093 continue; 9094 } // Try to construct an element, and ignore the tag if we couldn't. 9095 9096 9097 node = createElement(m[1], m[3]); 9098 9099 if (!node) { 9100 continue; 9101 } // Determine if the tag should be added based on the context of where it 9102 // is placed in the cuetext. 9103 9104 9105 if (!shouldAdd(current, node)) { 9106 continue; 9107 } // Set the class list (as a list of classes, separated by space). 9108 9109 9110 if (m[2]) { 9111 var classes = m[2].split('.'); 9112 classes.forEach(function (cl) { 9113 var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color 9114 9115 var colorName = bgColor ? cl.slice(3) : cl; 9116 9117 if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) { 9118 var propName = bgColor ? 'background-color' : 'color'; 9119 var propValue = DEFAULT_COLOR_CLASS[colorName]; 9120 node.style[propName] = propValue; 9121 } 9122 }); 9123 node.className = classes.join(' '); 9124 } // Append the node to the current node, and enter the scope of the new 9125 // node. 9126 9127 9128 tagStack.push(m[1]); 9129 current.appendChild(node); 9130 current = node; 9131 continue; 9132 } // Text nodes are leaf nodes. 9133 9134 9135 current.appendChild(window.document.createTextNode(unescape(t))); 9136 } 9137 9138 return rootDiv; 9139 } // This is a list of all the Unicode characters that have a strong 9140 // right-to-left category. What this means is that these characters are 9141 // written right-to-left for sure. It was generated by pulling all the strong 9142 // right-to-left characters out of the Unicode data table. That table can 9143 // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 9144 9145 9146 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]]; 9147 9148 function isStrongRTLChar(charCode) { 9149 for (var i = 0; i < strongRTLRanges.length; i++) { 9150 var currentRange = strongRTLRanges[i]; 9151 9152 if (charCode >= currentRange[0] && charCode <= currentRange[1]) { 9153 return true; 9154 } 9155 } 9156 9157 return false; 9158 } 9159 9160 function determineBidi(cueDiv) { 9161 var nodeStack = [], 9162 text = "", 9163 charCode; 9164 9165 if (!cueDiv || !cueDiv.childNodes) { 9166 return "ltr"; 9167 } 9168 9169 function pushNodes(nodeStack, node) { 9170 for (var i = node.childNodes.length - 1; i >= 0; i--) { 9171 nodeStack.push(node.childNodes[i]); 9172 } 9173 } 9174 9175 function nextTextNode(nodeStack) { 9176 if (!nodeStack || !nodeStack.length) { 9177 return null; 9178 } 9179 9180 var node = nodeStack.pop(), 9181 text = node.textContent || node.innerText; 9182 9183 if (text) { 9184 // TODO: This should match all unicode type B characters (paragraph 9185 // separator characters). See issue #115. 9186 var m = text.match(/^.*(\n|\r)/); 9187 9188 if (m) { 9189 nodeStack.length = 0; 9190 return m[0]; 9191 } 9192 9193 return text; 9194 } 9195 9196 if (node.tagName === "ruby") { 9197 return nextTextNode(nodeStack); 9198 } 9199 9200 if (node.childNodes) { 9201 pushNodes(nodeStack, node); 9202 return nextTextNode(nodeStack); 9203 } 9204 } 9205 9206 pushNodes(nodeStack, cueDiv); 9207 9208 while (text = nextTextNode(nodeStack)) { 9209 for (var i = 0; i < text.length; i++) { 9210 charCode = text.charCodeAt(i); 9211 9212 if (isStrongRTLChar(charCode)) { 9213 return "rtl"; 9214 } 9215 } 9216 } 9217 9218 return "ltr"; 9219 } 9220 9221 function computeLinePos(cue) { 9222 if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) { 9223 return cue.line; 9224 } 9225 9226 if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) { 9227 return -1; 9228 } 9229 9230 var track = cue.track, 9231 trackList = track.textTrackList, 9232 count = 0; 9233 9234 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 9235 if (trackList[i].mode === "showing") { 9236 count++; 9237 } 9238 } 9239 9240 return ++count * -1; 9241 } 9242 9243 function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the 9244 // div on 'this'. 9245 9246 9247 StyleBox.prototype.applyStyles = function (styles, div) { 9248 div = div || this.div; 9249 9250 for (var prop in styles) { 9251 if (styles.hasOwnProperty(prop)) { 9252 div.style[prop] = styles[prop]; 9253 } 9254 } 9255 }; 9256 9257 StyleBox.prototype.formatStyle = function (val, unit) { 9258 return val === 0 ? 0 : val + unit; 9259 }; // Constructs the computed display state of the cue (a div). Places the div 9260 // into the overlay which should be a block level element (usually a div). 9261 9262 9263 function CueStyleBox(window, cue, styleOptions) { 9264 StyleBox.call(this); 9265 this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 9266 // have inline positioning and will function as the cue background box. 9267 9268 this.cueDiv = parseContent(window, cue.text); 9269 var styles = { 9270 color: "rgba(255, 255, 255, 1)", 9271 backgroundColor: "rgba(0, 0, 0, 0.8)", 9272 position: "relative", 9273 left: 0, 9274 right: 0, 9275 top: 0, 9276 bottom: 0, 9277 display: "inline", 9278 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9279 unicodeBidi: "plaintext" 9280 }; 9281 this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue 9282 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 9283 // mirrors of them except middle instead of center on Safari. 9284 9285 this.div = window.document.createElement("div"); 9286 styles = { 9287 direction: determineBidi(this.cueDiv), 9288 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9289 unicodeBidi: "plaintext", 9290 textAlign: cue.align === "middle" ? "center" : cue.align, 9291 font: styleOptions.font, 9292 whiteSpace: "pre-line", 9293 position: "absolute" 9294 }; 9295 this.applyStyles(styles); 9296 this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text 9297 // position of the cue box. The reference edge will be resolved later when 9298 // the box orientation styles are applied. 9299 9300 var textPos = 0; 9301 9302 switch (cue.positionAlign) { 9303 case "start": 9304 textPos = cue.position; 9305 break; 9306 9307 case "center": 9308 textPos = cue.position - cue.size / 2; 9309 break; 9310 9311 case "end": 9312 textPos = cue.position - cue.size; 9313 break; 9314 } // Horizontal box orientation; textPos is the distance from the left edge of the 9315 // area to the left edge of the box and cue.size is the distance extending to 9316 // the right from there. 9317 9318 9319 if (cue.vertical === "") { 9320 this.applyStyles({ 9321 left: this.formatStyle(textPos, "%"), 9322 width: this.formatStyle(cue.size, "%") 9323 }); // Vertical box orientation; textPos is the distance from the top edge of the 9324 // area to the top edge of the box and cue.size is the height extending 9325 // downwards from there. 9326 } else { 9327 this.applyStyles({ 9328 top: this.formatStyle(textPos, "%"), 9329 height: this.formatStyle(cue.size, "%") 9330 }); 9331 } 9332 9333 this.move = function (box) { 9334 this.applyStyles({ 9335 top: this.formatStyle(box.top, "px"), 9336 bottom: this.formatStyle(box.bottom, "px"), 9337 left: this.formatStyle(box.left, "px"), 9338 right: this.formatStyle(box.right, "px"), 9339 height: this.formatStyle(box.height, "px"), 9340 width: this.formatStyle(box.width, "px") 9341 }); 9342 }; 9343 } 9344 9345 CueStyleBox.prototype = _objCreate(StyleBox.prototype); 9346 CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily 9347 // compute things with such as if it overlaps or intersects with another Element. 9348 // Can initialize it with either a StyleBox or another BoxPosition. 9349 9350 function BoxPosition(obj) { 9351 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 9352 // was passed in and we need to copy the results of 'getBoundingClientRect' 9353 // as the object returned is readonly. All co-ordinate values are in reference 9354 // to the viewport origin (top left). 9355 var lh, height, width, top; 9356 9357 if (obj.div) { 9358 height = obj.div.offsetHeight; 9359 width = obj.div.offsetWidth; 9360 top = obj.div.offsetTop; 9361 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); 9362 obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of 9363 // the inner div's lines. This could be due to bold text, etc, on some platforms. 9364 // In this case we should get the average line height and use that. This will 9365 // result in the desired behaviour. 9366 9367 lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0; 9368 } 9369 9370 this.left = obj.left; 9371 this.right = obj.right; 9372 this.top = obj.top || top; 9373 this.height = obj.height || height; 9374 this.bottom = obj.bottom || top + (obj.height || height); 9375 this.width = obj.width || width; 9376 this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 9377 } // Move the box along a particular axis. Optionally pass in an amount to move 9378 // the box. If no amount is passed then the default is the line height of the 9379 // box. 9380 9381 9382 BoxPosition.prototype.move = function (axis, toMove) { 9383 toMove = toMove !== undefined ? toMove : this.lineHeight; 9384 9385 switch (axis) { 9386 case "+x": 9387 this.left += toMove; 9388 this.right += toMove; 9389 break; 9390 9391 case "-x": 9392 this.left -= toMove; 9393 this.right -= toMove; 9394 break; 9395 9396 case "+y": 9397 this.top += toMove; 9398 this.bottom += toMove; 9399 break; 9400 9401 case "-y": 9402 this.top -= toMove; 9403 this.bottom -= toMove; 9404 break; 9405 } 9406 }; // Check if this box overlaps another box, b2. 9407 9408 9409 BoxPosition.prototype.overlaps = function (b2) { 9410 return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top; 9411 }; // Check if this box overlaps any other boxes in boxes. 9412 9413 9414 BoxPosition.prototype.overlapsAny = function (boxes) { 9415 for (var i = 0; i < boxes.length; i++) { 9416 if (this.overlaps(boxes[i])) { 9417 return true; 9418 } 9419 } 9420 9421 return false; 9422 }; // Check if this box is within another box. 9423 9424 9425 BoxPosition.prototype.within = function (container) { 9426 return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right; 9427 }; // Check if this box is entirely within the container or it is overlapping 9428 // on the edge opposite of the axis direction passed. For example, if "+x" is 9429 // passed and the box is overlapping on the left edge of the container, then 9430 // return true. 9431 9432 9433 BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) { 9434 switch (axis) { 9435 case "+x": 9436 return this.left < container.left; 9437 9438 case "-x": 9439 return this.right > container.right; 9440 9441 case "+y": 9442 return this.top < container.top; 9443 9444 case "-y": 9445 return this.bottom > container.bottom; 9446 } 9447 }; // Find the percentage of the area that this box is overlapping with another 9448 // box. 9449 9450 9451 BoxPosition.prototype.intersectPercentage = function (b2) { 9452 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 9453 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 9454 intersectArea = x * y; 9455 return intersectArea / (this.height * this.width); 9456 }; // Convert the positions from this box to CSS compatible positions using 9457 // the reference container's positions. This has to be done because this 9458 // box's positions are in reference to the viewport origin, whereas, CSS 9459 // values are in referecne to their respective edges. 9460 9461 9462 BoxPosition.prototype.toCSSCompatValues = function (reference) { 9463 return { 9464 top: this.top - reference.top, 9465 bottom: reference.bottom - this.bottom, 9466 left: this.left - reference.left, 9467 right: reference.right - this.right, 9468 height: this.height, 9469 width: this.width 9470 }; 9471 }; // Get an object that represents the box's position without anything extra. 9472 // Can pass a StyleBox, HTMLElement, or another BoxPositon. 9473 9474 9475 BoxPosition.getSimpleBoxPosition = function (obj) { 9476 var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; 9477 var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; 9478 var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; 9479 obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; 9480 var ret = { 9481 left: obj.left, 9482 right: obj.right, 9483 top: obj.top || top, 9484 height: obj.height || height, 9485 bottom: obj.bottom || top + (obj.height || height), 9486 width: obj.width || width 9487 }; 9488 return ret; 9489 }; // Move a StyleBox to its specified, or next best, position. The containerBox 9490 // is the box that contains the StyleBox, such as a div. boxPositions are 9491 // a list of other boxes that the styleBox can't overlap with. 9492 9493 9494 function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 9495 // Find the best position for a cue box, b, on the video. The axis parameter 9496 // is a list of axis, the order of which, it will move the box along. For example: 9497 // Passing ["+x", "-x"] will move the box first along the x axis in the positive 9498 // direction. If it doesn't find a good position for it there it will then move 9499 // it along the x axis in the negative direction. 9500 function findBestPosition(b, axis) { 9501 var bestPosition, 9502 specifiedPosition = new BoxPosition(b), 9503 percentage = 1; // Highest possible so the first thing we get is better. 9504 9505 for (var i = 0; i < axis.length; i++) { 9506 while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) { 9507 b.move(axis[i]); 9508 } // We found a spot where we aren't overlapping anything. This is our 9509 // best position. 9510 9511 9512 if (b.within(containerBox)) { 9513 return b; 9514 } 9515 9516 var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try 9517 // then remember this position as the best position. 9518 9519 if (percentage > p) { 9520 bestPosition = new BoxPosition(b); 9521 percentage = p; 9522 } // Reset the box position to the specified position. 9523 9524 9525 b = new BoxPosition(specifiedPosition); 9526 } 9527 9528 return bestPosition || specifiedPosition; 9529 } 9530 9531 var boxPosition = new BoxPosition(styleBox), 9532 cue = styleBox.cue, 9533 linePos = computeLinePos(cue), 9534 axis = []; // If we have a line number to align the cue to. 9535 9536 if (cue.snapToLines) { 9537 var size; 9538 9539 switch (cue.vertical) { 9540 case "": 9541 axis = ["+y", "-y"]; 9542 size = "height"; 9543 break; 9544 9545 case "rl": 9546 axis = ["+x", "-x"]; 9547 size = "width"; 9548 break; 9549 9550 case "lr": 9551 axis = ["-x", "+x"]; 9552 size = "width"; 9553 break; 9554 } 9555 9556 var step = boxPosition.lineHeight, 9557 position = step * Math.round(linePos), 9558 maxPosition = containerBox[size] + step, 9559 initialAxis = axis[0]; // If the specified intial position is greater then the max position then 9560 // clamp the box to the amount of steps it would take for the box to 9561 // reach the max position. 9562 9563 if (Math.abs(position) > maxPosition) { 9564 position = position < 0 ? -1 : 1; 9565 position *= Math.ceil(maxPosition / step) * step; 9566 } // If computed line position returns negative then line numbers are 9567 // relative to the bottom of the video instead of the top. Therefore, we 9568 // need to increase our initial position by the length or width of the 9569 // video, depending on the writing direction, and reverse our axis directions. 9570 9571 9572 if (linePos < 0) { 9573 position += cue.vertical === "" ? containerBox.height : containerBox.width; 9574 axis = axis.reverse(); 9575 } // Move the box to the specified position. This may not be its best 9576 // position. 9577 9578 9579 boxPosition.move(initialAxis, position); 9580 } else { 9581 // If we have a percentage line value for the cue. 9582 var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100; 9583 9584 switch (cue.lineAlign) { 9585 case "center": 9586 linePos -= calculatedPercentage / 2; 9587 break; 9588 9589 case "end": 9590 linePos -= calculatedPercentage; 9591 break; 9592 } // Apply initial line position to the cue box. 9593 9594 9595 switch (cue.vertical) { 9596 case "": 9597 styleBox.applyStyles({ 9598 top: styleBox.formatStyle(linePos, "%") 9599 }); 9600 break; 9601 9602 case "rl": 9603 styleBox.applyStyles({ 9604 left: styleBox.formatStyle(linePos, "%") 9605 }); 9606 break; 9607 9608 case "lr": 9609 styleBox.applyStyles({ 9610 right: styleBox.formatStyle(linePos, "%") 9611 }); 9612 break; 9613 } 9614 9615 axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning 9616 // to it. 9617 9618 boxPosition = new BoxPosition(styleBox); 9619 } 9620 9621 var bestPosition = findBestPosition(boxPosition, axis); 9622 styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 9623 } 9624 9625 function WebVTT$1() {} // Nothing 9626 // Helper to allow strings to be decoded instead of the default binary utf8 data. 9627 9628 9629 WebVTT$1.StringDecoder = function () { 9630 return { 9631 decode: function decode(data) { 9632 if (!data) { 9633 return ""; 9634 } 9635 9636 if (typeof data !== "string") { 9637 throw new Error("Error - expected string data."); 9638 } 9639 9640 return decodeURIComponent(encodeURIComponent(data)); 9641 } 9642 }; 9643 }; 9644 9645 WebVTT$1.convertCueToDOMTree = function (window, cuetext) { 9646 if (!window || !cuetext) { 9647 return null; 9648 } 9649 9650 return parseContent(window, cuetext); 9651 }; 9652 9653 var FONT_SIZE_PERCENT = 0.05; 9654 var FONT_STYLE = "sans-serif"; 9655 var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it. 9656 // @param overlay A block level element (usually a div) that the computed cues 9657 // and regions will be placed into. 9658 9659 WebVTT$1.processCues = function (window, cues, overlay) { 9660 if (!window || !cues || !overlay) { 9661 return null; 9662 } // Remove all previous children. 9663 9664 9665 while (overlay.firstChild) { 9666 overlay.removeChild(overlay.firstChild); 9667 } 9668 9669 var paddedOverlay = window.document.createElement("div"); 9670 paddedOverlay.style.position = "absolute"; 9671 paddedOverlay.style.left = "0"; 9672 paddedOverlay.style.right = "0"; 9673 paddedOverlay.style.top = "0"; 9674 paddedOverlay.style.bottom = "0"; 9675 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 9676 overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could 9677 // be the case if a cue's state has been changed since the last computation or 9678 // if it has not been computed yet. 9679 9680 function shouldCompute(cues) { 9681 for (var i = 0; i < cues.length; i++) { 9682 if (cues[i].hasBeenReset || !cues[i].displayState) { 9683 return true; 9684 } 9685 } 9686 9687 return false;
vendor: 6,356 bytes, lines 9688-9897
9688 } // We don't need to recompute the cues' display states. Just reuse them. 9689 9690 9691 if (!shouldCompute(cues)) { 9692 for (var i = 0; i < cues.length; i++) { 9693 paddedOverlay.appendChild(cues[i].displayState); 9694 } 9695 9696 return; 9697 } 9698 9699 var boxPositions = [], 9700 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 9701 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 9702 var styleOptions = { 9703 font: fontSize + "px " + FONT_STYLE 9704 }; 9705 9706 (function () { 9707 var styleBox, cue; 9708 9709 for (var i = 0; i < cues.length; i++) { 9710 cue = cues[i]; // Compute the intial position and styles of the cue div. 9711 9712 styleBox = new CueStyleBox(window, cue, styleOptions); 9713 paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. 9714 9715 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later 9716 // if we don't have too. 9717 9718 cue.displayState = styleBox.div; 9719 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 9720 } 9721 })(); 9722 }; 9723 9724 WebVTT$1.Parser = function (window, vttjs, decoder) { 9725 if (!decoder) { 9726 decoder = vttjs; 9727 vttjs = {}; 9728 } 9729 9730 if (!vttjs) { 9731 vttjs = {}; 9732 } 9733 9734 this.window = window; 9735 this.vttjs = vttjs; 9736 this.state = "INITIAL"; 9737 this.buffer = ""; 9738 this.decoder = decoder || new TextDecoder("utf8"); 9739 this.regionList = []; 9740 }; 9741 9742 WebVTT$1.Parser.prototype = { 9743 // If the error is a ParsingError then report it to the consumer if 9744 // possible. If it's not a ParsingError then throw it like normal. 9745 reportOrThrowError: function reportOrThrowError(e) { 9746 if (e instanceof ParsingError) { 9747 this.onparsingerror && this.onparsingerror(e); 9748 } else { 9749 throw e; 9750 } 9751 }, 9752 parse: function parse(data) { 9753 var self = this; // If there is no data then we won't decode it, but will just try to parse 9754 // whatever is in buffer already. This may occur in circumstances, for 9755 // example when flush() is called. 9756 9757 if (data) { 9758 // Try to decode the data that we received. 9759 self.buffer += self.decoder.decode(data, { 9760 stream: true 9761 }); 9762 } 9763 9764 function collectNextLine() { 9765 var buffer = self.buffer; 9766 var pos = 0; 9767 9768 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 9769 ++pos; 9770 } 9771 9772 var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below. 9773 9774 if (buffer[pos] === '\r') { 9775 ++pos; 9776 } 9777 9778 if (buffer[pos] === '\n') { 9779 ++pos; 9780 } 9781 9782 self.buffer = buffer.substr(pos); 9783 return line; 9784 } // 3.4 WebVTT region and WebVTT region settings syntax 9785 9786 9787 function parseRegion(input) { 9788 var settings = new Settings(); 9789 parseOptions(input, function (k, v) { 9790 switch (k) { 9791 case "id": 9792 settings.set(k, v); 9793 break; 9794 9795 case "width": 9796 settings.percent(k, v); 9797 break; 9798 9799 case "lines": 9800 settings.integer(k, v); 9801 break; 9802 9803 case "regionanchor": 9804 case "viewportanchor": 9805 var xy = v.split(','); 9806 9807 if (xy.length !== 2) { 9808 break; 9809 } // We have to make sure both x and y parse, so use a temporary 9810 // settings object here. 9811 9812 9813 var anchor = new Settings(); 9814 anchor.percent("x", xy[0]); 9815 anchor.percent("y", xy[1]); 9816 9817 if (!anchor.has("x") || !anchor.has("y")) { 9818 break; 9819 } 9820 9821 settings.set(k + "X", anchor.get("x")); 9822 settings.set(k + "Y", anchor.get("y")); 9823 break; 9824 9825 case "scroll": 9826 settings.alt(k, v, ["up"]); 9827 break; 9828 } 9829 }, /=/, /\s/); // Create the region, using default values for any values that were not 9830 // specified. 9831 9832 if (settings.has("id")) { 9833 var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); 9834 region.width = settings.get("width", 100); 9835 region.lines = settings.get("lines", 3); 9836 region.regionAnchorX = settings.get("regionanchorX", 0); 9837 region.regionAnchorY = settings.get("regionanchorY", 100); 9838 region.viewportAnchorX = settings.get("viewportanchorX", 0); 9839 region.viewportAnchorY = settings.get("viewportanchorY", 100); 9840 region.scroll = settings.get("scroll", ""); // Register the region. 9841 9842 self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that 9843 // reference it. 9844 9845 self.regionList.push({ 9846 id: settings.get("id"), 9847 region: region 9848 }); 9849 } 9850 } // draft-pantos-http-live-streaming-20 9851 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 9852 // 3.5 WebVTT 9853 9854 9855 function parseTimestampMap(input) { 9856 var settings = new Settings(); 9857 parseOptions(input, function (k, v) { 9858 switch (k) { 9859 case "MPEGT": 9860 settings.integer(k + 'S', v); 9861 break; 9862 9863 case "LOCA": 9864 settings.set(k + 'L', parseTimeStamp(v)); 9865 break; 9866 } 9867 }, /[^\d]:/, /,/); 9868 self.ontimestampmap && self.ontimestampmap({ 9869 "MPEGTS": settings.get("MPEGTS"), 9870 "LOCAL": settings.get("LOCAL") 9871 }); 9872 } // 3.2 WebVTT metadata header syntax 9873 9874 9875 function parseHeader(input) { 9876 if (input.match(/X-TIMESTAMP-MAP/)) { 9877 // This line contains HLS X-TIMESTAMP-MAP metadata 9878 parseOptions(input, function (k, v) { 9879 switch (k) { 9880 case "X-TIMESTAMP-MAP": 9881 parseTimestampMap(v); 9882 break; 9883 } 9884 }, /=/); 9885 } else { 9886 parseOptions(input, function (k, v) { 9887 switch (k) { 9888 case "Region": 9889 // 3.3 WebVTT region metadata header syntax 9890 parseRegion(v); 9891 break; 9892 } 9893 }, /:/); 9894 } 9895 } // 5.1 WebVTT file parsing. 9896 9897
vendor: 4,445 bytes, lines 9898-10045
9898 try { 9899 var line; 9900 9901 if (self.state === "INITIAL") { 9902 // We can't start parsing until we have the first line. 9903 if (!/\r\n|\n/.test(self.buffer)) { 9904 return this; 9905 } 9906 9907 line = collectNextLine(); 9908 var m = line.match(/^WEBVTT([ \t].*)?$/); 9909 9910 if (!m || !m[0]) { 9911 throw new ParsingError(ParsingError.Errors.BadSignature); 9912 } 9913 9914 self.state = "HEADER"; 9915 } 9916 9917 var alreadyCollectedLine = false; 9918 9919 while (self.buffer) { 9920 // We can't parse a line until we have the full line. 9921 if (!/\r\n|\n/.test(self.buffer)) { 9922 return this; 9923 } 9924 9925 if (!alreadyCollectedLine) { 9926 line = collectNextLine(); 9927 } else { 9928 alreadyCollectedLine = false; 9929 } 9930 9931 switch (self.state) { 9932 case "HEADER": 9933 // 13-18 - Allow a header (metadata) under the WEBVTT line. 9934 if (/:/.test(line)) { 9935 parseHeader(line); 9936 } else if (!line) { 9937 // An empty line terminates the header and starts the body (cues). 9938 self.state = "ID"; 9939 } 9940 9941 continue; 9942 9943 case "NOTE": 9944 // Ignore NOTE blocks. 9945 if (!line) { 9946 self.state = "ID"; 9947 } 9948 9949 continue; 9950 9951 case "ID": 9952 // Check for the start of NOTE blocks. 9953 if (/^NOTE($|[ \t])/.test(line)) { 9954 self.state = "NOTE"; 9955 break; 9956 } // 19-29 - Allow any number of line terminators, then initialize new cue values. 9957 9958 9959 if (!line) { 9960 continue; 9961 } 9962 9963 self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center 9964 9965 try { 9966 self.cue.align = "center"; 9967 } catch (e) { 9968 self.cue.align = "middle"; 9969 } 9970 9971 self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data. 9972 9973 if (line.indexOf("-->") === -1) { 9974 self.cue.id = line; 9975 continue; 9976 } 9977 9978 // Process line as start of a cue. 9979 9980 /*falls through*/ 9981 9982 case "CUE": 9983 // 40 - Collect cue timings and settings. 9984 try { 9985 parseCue(line, self.cue, self.regionList); 9986 } catch (e) { 9987 self.reportOrThrowError(e); // In case of an error ignore rest of the cue. 9988 9989 self.cue = null; 9990 self.state = "BADCUE"; 9991 continue; 9992 } 9993 9994 self.state = "CUETEXT"; 9995 continue; 9996 9997 case "CUETEXT": 9998 var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue. 9999 // 35 - If we have the special substring '-->' then report the cue, 10000 // but do not collect the line as we need to process the current 10001 // one as a new cue. 10002 10003 if (!line || hasSubstring && (alreadyCollectedLine = true)) { 10004 // We are done parsing self cue. 10005 self.oncue && self.oncue(self.cue); 10006 self.cue = null; 10007 self.state = "ID"; 10008 continue; 10009 } 10010 10011 if (self.cue.text) { 10012 self.cue.text += "\n"; 10013 } 10014 10015 self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n'); 10016 continue; 10017 10018 case "BADCUE": 10019 // BADCUE 10020 // 54-62 - Collect and discard the remaining cue. 10021 if (!line) { 10022 self.state = "ID"; 10023 } 10024 10025 continue; 10026 } 10027 } 10028 } catch (e) { 10029 self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have. 10030 10031 if (self.state === "CUETEXT" && self.cue && self.oncue) { 10032 self.oncue(self.cue); 10033 } 10034 10035 self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise 10036 // another exception occurred so enter BADCUE state. 10037 10038 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 10039 } 10040 10041 return this; 10042 }, 10043 flush: function flush() { 10044 var self = this; 10045
vendor: 8,966 bytes, lines 10046-10378
10046 try { 10047 // Finish decoding the stream. 10048 self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region. 10049 10050 if (self.cue || self.state === "HEADER") { 10051 self.buffer += "\n\n"; 10052 self.parse(); 10053 } // If we've flushed, parsed, and we're still on the INITIAL state then 10054 // that means we don't have enough of the stream to parse the first 10055 // line. 10056 10057 10058 if (self.state === "INITIAL") { 10059 throw new ParsingError(ParsingError.Errors.BadSignature); 10060 } 10061 } catch (e) { 10062 self.reportOrThrowError(e); 10063 } 10064 10065 self.onflush && self.onflush(); 10066 return this; 10067 } 10068 }; 10069 var vtt = WebVTT$1; 10070 10071 /** 10072 * Copyright 2013 vtt.js Contributors 10073 * 10074 * Licensed under the Apache License, Version 2.0 (the "License"); 10075 * you may not use this file except in compliance with the License. 10076 * You may obtain a copy of the License at 10077 * 10078 * http://www.apache.org/licenses/LICENSE-2.0 10079 * 10080 * Unless required by applicable law or agreed to in writing, software 10081 * distributed under the License is distributed on an "AS IS" BASIS, 10082 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10083 * See the License for the specific language governing permissions and 10084 * limitations under the License. 10085 */ 10086 var autoKeyword = "auto"; 10087 var directionSetting = { 10088 "": 1, 10089 "lr": 1, 10090 "rl": 1 10091 }; 10092 var alignSetting = { 10093 "start": 1, 10094 "center": 1, 10095 "end": 1, 10096 "left": 1, 10097 "right": 1, 10098 "auto": 1, 10099 "line-left": 1, 10100 "line-right": 1 10101 }; 10102 10103 function findDirectionSetting(value) { 10104 if (typeof value !== "string") { 10105 return false; 10106 } 10107 10108 var dir = directionSetting[value.toLowerCase()]; 10109 return dir ? value.toLowerCase() : false; 10110 } 10111 10112 function findAlignSetting(value) { 10113 if (typeof value !== "string") { 10114 return false; 10115 } 10116 10117 var align = alignSetting[value.toLowerCase()]; 10118 return align ? value.toLowerCase() : false; 10119 } 10120 10121 function VTTCue(startTime, endTime, text) { 10122 /** 10123 * Shim implementation specific properties. These properties are not in 10124 * the spec. 10125 */ 10126 // Lets us know when the VTTCue's data has changed in such a way that we need 10127 // to recompute its display state. This lets us compute its display state 10128 // lazily. 10129 this.hasBeenReset = false; 10130 /** 10131 * VTTCue and TextTrackCue properties 10132 * http://dev.w3.org/html5/webvtt/#vttcue-interface 10133 */ 10134 10135 var _id = ""; 10136 var _pauseOnExit = false; 10137 var _startTime = startTime; 10138 var _endTime = endTime; 10139 var _text = text; 10140 var _region = null; 10141 var _vertical = ""; 10142 var _snapToLines = true; 10143 var _line = "auto"; 10144 var _lineAlign = "start"; 10145 var _position = "auto"; 10146 var _positionAlign = "auto"; 10147 var _size = 100; 10148 var _align = "center"; 10149 Object.defineProperties(this, { 10150 "id": { 10151 enumerable: true, 10152 get: function get() { 10153 return _id; 10154 }, 10155 set: function set(value) { 10156 _id = "" + value; 10157 } 10158 }, 10159 "pauseOnExit": { 10160 enumerable: true, 10161 get: function get() { 10162 return _pauseOnExit; 10163 }, 10164 set: function set(value) { 10165 _pauseOnExit = !!value; 10166 } 10167 }, 10168 "startTime": { 10169 enumerable: true, 10170 get: function get() { 10171 return _startTime; 10172 }, 10173 set: function set(value) { 10174 if (typeof value !== "number") { 10175 throw new TypeError("Start time must be set to a number."); 10176 } 10177 10178 _startTime = value; 10179 this.hasBeenReset = true; 10180 } 10181 }, 10182 "endTime": { 10183 enumerable: true, 10184 get: function get() { 10185 return _endTime; 10186 }, 10187 set: function set(value) { 10188 if (typeof value !== "number") { 10189 throw new TypeError("End time must be set to a number."); 10190 } 10191 10192 _endTime = value; 10193 this.hasBeenReset = true; 10194 } 10195 }, 10196 "text": { 10197 enumerable: true, 10198 get: function get() { 10199 return _text; 10200 }, 10201 set: function set(value) { 10202 _text = "" + value; 10203 this.hasBeenReset = true; 10204 } 10205 }, 10206 "region": { 10207 enumerable: true, 10208 get: function get() { 10209 return _region; 10210 }, 10211 set: function set(value) { 10212 _region = value; 10213 this.hasBeenReset = true; 10214 } 10215 }, 10216 "vertical": { 10217 enumerable: true, 10218 get: function get() { 10219 return _vertical; 10220 }, 10221 set: function set(value) { 10222 var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. 10223 10224 if (setting === false) { 10225 throw new SyntaxError("Vertical: an invalid or illegal direction string was specified."); 10226 } 10227 10228 _vertical = setting; 10229 this.hasBeenReset = true; 10230 } 10231 }, 10232 "snapToLines": { 10233 enumerable: true, 10234 get: function get() { 10235 return _snapToLines; 10236 }, 10237 set: function set(value) { 10238 _snapToLines = !!value; 10239 this.hasBeenReset = true; 10240 } 10241 }, 10242 "line": { 10243 enumerable: true, 10244 get: function get() { 10245 return _line; 10246 }, 10247 set: function set(value) { 10248 if (typeof value !== "number" && value !== autoKeyword) { 10249 throw new SyntaxError("Line: an invalid number or illegal string was specified."); 10250 } 10251 10252 _line = value; 10253 this.hasBeenReset = true; 10254 } 10255 }, 10256 "lineAlign": { 10257 enumerable: true, 10258 get: function get() { 10259 return _lineAlign; 10260 }, 10261 set: function set(value) { 10262 var setting = findAlignSetting(value); 10263 10264 if (!setting) { 10265 console.warn("lineAlign: an invalid or illegal string was specified."); 10266 } else { 10267 _lineAlign = setting; 10268 this.hasBeenReset = true; 10269 } 10270 } 10271 }, 10272 "position": { 10273 enumerable: true, 10274 get: function get() { 10275 return _position; 10276 }, 10277 set: function set(value) { 10278 if (value < 0 || value > 100) { 10279 throw new Error("Position must be between 0 and 100."); 10280 } 10281 10282 _position = value; 10283 this.hasBeenReset = true; 10284 } 10285 }, 10286 "positionAlign": { 10287 enumerable: true, 10288 get: function get() { 10289 return _positionAlign; 10290 }, 10291 set: function set(value) { 10292 var setting = findAlignSetting(value); 10293 10294 if (!setting) { 10295 console.warn("positionAlign: an invalid or illegal string was specified."); 10296 } else { 10297 _positionAlign = setting; 10298 this.hasBeenReset = true; 10299 } 10300 } 10301 }, 10302 "size": { 10303 enumerable: true, 10304 get: function get() { 10305 return _size; 10306 }, 10307 set: function set(value) { 10308 if (value < 0 || value > 100) { 10309 throw new Error("Size must be between 0 and 100."); 10310 } 10311 10312 _size = value; 10313 this.hasBeenReset = true; 10314 } 10315 }, 10316 "align": { 10317 enumerable: true, 10318 get: function get() { 10319 return _align; 10320 }, 10321 set: function set(value) { 10322 var setting = findAlignSetting(value); 10323 10324 if (!setting) { 10325 throw new SyntaxError("align: an invalid or illegal alignment string was specified."); 10326 } 10327 10328 _align = setting; 10329 this.hasBeenReset = true; 10330 } 10331 } 10332 }); 10333 /** 10334 * Other <track> spec defined properties 10335 */ 10336 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state 10337 10338 this.displayState = undefined; 10339 } 10340 /** 10341 * VTTCue methods 10342 */ 10343 10344 10345 VTTCue.prototype.getCueAsHTML = function () { 10346 // Assume WebVTT.convertCueToDOMTree is on the global. 10347 return WebVTT.convertCueToDOMTree(window, this.text); 10348 }; 10349 10350 var vttcue = VTTCue; 10351 10352 /** 10353 * Copyright 2013 vtt.js Contributors 10354 * 10355 * Licensed under the Apache License, Version 2.0 (the "License"); 10356 * you may not use this file except in compliance with the License. 10357 * You may obtain a copy of the License at 10358 * 10359 * http://www.apache.org/licenses/LICENSE-2.0 10360 * 10361 * Unless required by applicable law or agreed to in writing, software 10362 * distributed under the License is distributed on an "AS IS" BASIS, 10363 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10364 * See the License for the specific language governing permissions and 10365 * limitations under the License. 10366 */ 10367 var scrollSetting = { 10368 "": true, 10369 "up": true 10370 }; 10371 10372 function findScrollSetting(value) { 10373 if (typeof value !== "string") { 10374 return false; 10375 } 10376 10377 var scroll = scrollSetting[value.toLowerCase()]; 10378 return scroll ? value.toLowerCase() : false;
10379 } 10380 10381 function isValidPercentValue(value) { 10382 return typeof value === "number" && value >= 0 && value <= 100; 10383 } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 10384 10385 10386 function VTTRegion() { 10387 var _width = 100; 10388 var _lines = 3; 10389 var _regionAnchorX = 0; 10390 var _regionAnchorY = 100; 10391 var _viewportAnchorX = 0; 10392 var _viewportAnchorY = 100; 10393 var _scroll = ""; 10394 Object.defineProperties(this, { 10395 "width": { 10396 enumerable: true, 10397 get: function get() { 10398 return _width; 10399 }, 10400 set: function set(value) { 10401 if (!isValidPercentValue(value)) { 10402 throw new Error("Width must be between 0 and 100."); 10403 } 10404 10405 _width = value; 10406 } 10407 }, 10408 "lines": { 10409 enumerable: true, 10410 get: function get() { 10411 return _lines; 10412 }, 10413 set: function set(value) { 10414 if (typeof value !== "number") { 10415 throw new TypeError("Lines must be set to a number."); 10416 } 10417 10418 _lines = value; 10419 } 10420 }, 10421 "regionAnchorY": { 10422 enumerable: true, 10423 get: function get() { 10424 return _regionAnchorY; 10425 }, 10426 set: function set(value) { 10427 if (!isValidPercentValue(value)) { 10428 throw new Error("RegionAnchorX must be between 0 and 100."); 10429 } 10430 10431 _regionAnchorY = value; 10432 } 10433 }, 10434 "regionAnchorX": { 10435 enumerable: true, 10436 get: function get() { 10437 return _regionAnchorX; 10438 }, 10439 set: function set(value) { 10440 if (!isValidPercentValue(value)) { 10441 throw new Error("RegionAnchorY must be between 0 and 100."); 10442 } 10443 10444 _regionAnchorX = value; 10445 } 10446 }, 10447 "viewportAnchorY": { 10448 enumerable: true, 10449 get: function get() { 10450 return _viewportAnchorY; 10451 }, 10452 set: function set(value) { 10453 if (!isValidPercentValue(value)) { 10454 throw new Error("ViewportAnchorY must be between 0 and 100."); 10455 } 10456 10457 _viewportAnchorY = value; 10458 } 10459 }, 10460 "viewportAnchorX": { 10461 enumerable: true, 10462 get: function get() { 10463 return _viewportAnchorX; 10464 }, 10465 set: function set(value) { 10466 if (!isValidPercentValue(value)) { 10467 throw new Error("ViewportAnchorX must be between 0 and 100."); 10468 } 10469 10470 _viewportAnchorX = value; 10471 } 10472 }, 10473 "scroll": { 10474 enumerable: true, 10475 get: function get() { 10476 return _scroll; 10477 }, 10478 set: function set(value) { 10479 var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value. 10480 10481 if (setting === false) { 10482 console.warn("Scroll: an invalid or illegal string was specified."); 10483 } else { 10484 _scroll = setting; 10485 } 10486 } 10487 } 10488 }); 10489 } 10490 10491 var vttregion = VTTRegion; 10492 10493 var browserIndex = createCommonjsModule(function (module) { 10494 /** 10495 * Copyright 2013 vtt.js Contributors 10496 * 10497 * Licensed under the Apache License, Version 2.0 (the "License"); 10498 * you may not use this file except in compliance with the License. 10499 * You may obtain a copy of the License at 10500 * 10501 * http://www.apache.org/licenses/LICENSE-2.0 10502 * 10503 * Unless required by applicable law or agreed to in writing, software 10504 * distributed under the License is distributed on an "AS IS" BASIS, 10505 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10506 * See the License for the specific language governing permissions and 10507 * limitations under the License. 10508 */ 10509 // Default exports for Node. Export the extended versions of VTTCue and 10510 // VTTRegion in Node since we likely want the capability to convert back and 10511 // forth between JSON. If we don't then it's not that big of a deal since we're 10512 // off browser. 10513 var vttjs = module.exports = { 10514 WebVTT: vtt, 10515 VTTCue: vttcue, 10516 VTTRegion: vttregion 10517 }; 10518 window$1.vttjs = vttjs; 10519 window$1.WebVTT = vttjs.WebVTT; 10520 var cueShim = vttjs.VTTCue; 10521 var regionShim = vttjs.VTTRegion; 10522 var nativeVTTCue = window$1.VTTCue; 10523 var nativeVTTRegion = window$1.VTTRegion; 10524 10525 vttjs.shim = function () { 10526 window$1.VTTCue = cueShim; 10527 window$1.VTTRegion = regionShim; 10528 }; 10529 10530 vttjs.restore = function () { 10531 window$1.VTTCue = nativeVTTCue; 10532 window$1.VTTRegion = nativeVTTRegion; 10533 }; 10534 10535 if (!window$1.VTTCue) { 10536 vttjs.shim(); 10537 } 10538 }); 10539 var browserIndex_1 = browserIndex.WebVTT; 10540 var browserIndex_2 = browserIndex.VTTCue; 10541 var browserIndex_3 = browserIndex.VTTRegion; 10542 10543 /** 10544 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string 10545 * that just contains the src url alone. 10546 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` 10547 * `var SourceString = 'http://example.com/some-video.mp4';` 10548 * 10549 * @typedef {Object|string} Tech~SourceObject 10550 * 10551 * @property {string} src 10552 * The url to the source 10553 * 10554 * @property {string} type 10555 * The mime type of the source 10556 */ 10557 10558 /** 10559 * A function used by {@link Tech} to create a new {@link TextTrack}. 10560 * 10561 * @private 10562 * 10563 * @param {Tech} self 10564 * An instance of the Tech class. 10565 * 10566 * @param {string} kind 10567 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10568 * 10569 * @param {string} [label] 10570 * Label to identify the text track 10571 * 10572 * @param {string} [language] 10573 * Two letter language abbreviation 10574 * 10575 * @param {Object} [options={}] 10576 * An object with additional text track options 10577 * 10578 * @return {TextTrack} 10579 * The text track that was created. 10580 */ 10581 10582 function createTrackHelper(self, kind, label, language, options) { 10583 if (options === void 0) { 10584 options = {}; 10585 } 10586 10587 var tracks = self.textTracks(); 10588 options.kind = kind; 10589 10590 if (label) { 10591 options.label = label; 10592 } 10593 10594 if (language) { 10595 options.language = language; 10596 } 10597 10598 options.tech = self; 10599 var track = new ALL.text.TrackClass(options); 10600 tracks.addTrack(track); 10601 return track; 10602 } 10603 /** 10604 * This is the base class for media playback technology controllers, such as 10605 * {@link Flash} and {@link HTML5} 10606 * 10607 * @extends Component 10608 */ 10609 10610 10611 var Tech = /*#__PURE__*/function (_Component) { 10612 inheritsLoose(Tech, _Component); 10613 10614 /** 10615 * Create an instance of this Tech. 10616 * 10617 * @param {Object} [options] 10618 * The key/value store of player options. 10619 * 10620 * @param {Component~ReadyCallback} ready 10621 * Callback function to call when the `HTML5` Tech is ready. 10622 */ 10623 function Tech(options, ready) { 10624 var _this; 10625 10626 if (options === void 0) { 10627 options = {}; 10628 } 10629 10630 if (ready === void 0) { 10631 ready = function ready() {}; 10632 } 10633 10634 // we don't want the tech to report user activity automatically.
vendor: 38,861 bytes, lines 10635-11982
10635 // This is done manually in addControlsListeners 10636 options.reportTouchActivity = false; 10637 _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to 10638 // implement a very limited played() 10639 10640 _this.hasStarted_ = false; 10641 10642 _this.on('playing', function () { 10643 this.hasStarted_ = true; 10644 }); 10645 10646 _this.on('loadstart', function () { 10647 this.hasStarted_ = false; 10648 }); 10649 10650 ALL.names.forEach(function (name) { 10651 var props = ALL[name]; 10652 10653 if (options && options[props.getterName]) { 10654 _this[props.privateName] = options[props.getterName]; 10655 } 10656 }); // Manually track progress in cases where the browser/flash player doesn't report it. 10657 10658 if (!_this.featuresProgressEvents) { 10659 _this.manualProgressOn(); 10660 } // Manually track timeupdates in cases where the browser/flash player doesn't report it. 10661 10662 10663 if (!_this.featuresTimeupdateEvents) { 10664 _this.manualTimeUpdatesOn(); 10665 } 10666 10667 ['Text', 'Audio', 'Video'].forEach(function (track) { 10668 if (options["native" + track + "Tracks"] === false) { 10669 _this["featuresNative" + track + "Tracks"] = false; 10670 } 10671 }); 10672 10673 if (options.nativeCaptions === false || options.nativeTextTracks === false) { 10674 _this.featuresNativeTextTracks = false; 10675 } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { 10676 _this.featuresNativeTextTracks = true; 10677 } 10678 10679 if (!_this.featuresNativeTextTracks) { 10680 _this.emulateTextTracks(); 10681 } 10682 10683 _this.preloadTextTracks = options.preloadTextTracks !== false; 10684 _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); 10685 10686 _this.initTrackListeners(); // Turn on component tap events only if not using native controls 10687 10688 10689 if (!options.nativeControlsForTouch) { 10690 _this.emitTapEvents(); 10691 } 10692 10693 if (_this.constructor) { 10694 _this.name_ = _this.constructor.name || 'Unknown Tech'; 10695 } 10696 10697 return _this; 10698 } 10699 /** 10700 * A special function to trigger source set in a way that will allow player 10701 * to re-trigger if the player or tech are not ready yet. 10702 * 10703 * @fires Tech#sourceset 10704 * @param {string} src The source string at the time of the source changing. 10705 */ 10706 10707 10708 var _proto = Tech.prototype; 10709 10710 _proto.triggerSourceset = function triggerSourceset(src) { 10711 var _this2 = this; 10712 10713 if (!this.isReady_) { 10714 // on initial ready we have to trigger source set 10715 // 1ms after ready so that player can watch for it. 10716 this.one('ready', function () { 10717 return _this2.setTimeout(function () { 10718 return _this2.triggerSourceset(src); 10719 }, 1); 10720 }); 10721 } 10722 /** 10723 * Fired when the source is set on the tech causing the media element 10724 * to reload. 10725 * 10726 * @see {@link Player#event:sourceset} 10727 * @event Tech#sourceset 10728 * @type {EventTarget~Event} 10729 */ 10730 10731 10732 this.trigger({ 10733 src: src, 10734 type: 'sourceset' 10735 }); 10736 } 10737 /* Fallbacks for unsupported event types 10738 ================================================================================ */ 10739 10740 /** 10741 * Polyfill the `progress` event for browsers that don't support it natively. 10742 * 10743 * @see {@link Tech#trackProgress} 10744 */ 10745 ; 10746 10747 _proto.manualProgressOn = function manualProgressOn() { 10748 this.on('durationchange', this.onDurationChange); 10749 this.manualProgress = true; // Trigger progress watching when a source begins loading 10750 10751 this.one('ready', this.trackProgress); 10752 } 10753 /** 10754 * Turn off the polyfill for `progress` events that was created in 10755 * {@link Tech#manualProgressOn} 10756 */ 10757 ; 10758 10759 _proto.manualProgressOff = function manualProgressOff() { 10760 this.manualProgress = false; 10761 this.stopTrackingProgress(); 10762 this.off('durationchange', this.onDurationChange); 10763 } 10764 /** 10765 * This is used to trigger a `progress` event when the buffered percent changes. It 10766 * sets an interval function that will be called every 500 milliseconds to check if the 10767 * buffer end percent has changed. 10768 * 10769 * > This function is called by {@link Tech#manualProgressOn} 10770 * 10771 * @param {EventTarget~Event} event 10772 * The `ready` event that caused this to run. 10773 * 10774 * @listens Tech#ready 10775 * @fires Tech#progress 10776 */ 10777 ; 10778 10779 _proto.trackProgress = function trackProgress(event) { 10780 this.stopTrackingProgress(); 10781 this.progressInterval = this.setInterval(bind(this, function () { 10782 // Don't trigger unless buffered amount is greater than last time 10783 var numBufferedPercent = this.bufferedPercent(); 10784 10785 if (this.bufferedPercent_ !== numBufferedPercent) { 10786 /** 10787 * See {@link Player#progress} 10788 * 10789 * @event Tech#progress 10790 * @type {EventTarget~Event} 10791 */ 10792 this.trigger('progress'); 10793 } 10794 10795 this.bufferedPercent_ = numBufferedPercent; 10796 10797 if (numBufferedPercent === 1) { 10798 this.stopTrackingProgress(); 10799 } 10800 }), 500); 10801 } 10802 /** 10803 * Update our internal duration on a `durationchange` event by calling 10804 * {@link Tech#duration}. 10805 * 10806 * @param {EventTarget~Event} event 10807 * The `durationchange` event that caused this to run. 10808 * 10809 * @listens Tech#durationchange 10810 */ 10811 ; 10812 10813 _proto.onDurationChange = function onDurationChange(event) { 10814 this.duration_ = this.duration(); 10815 } 10816 /** 10817 * Get and create a `TimeRange` object for buffering. 10818 * 10819 * @return {TimeRange} 10820 * The time range object that was created. 10821 */ 10822 ; 10823 10824 _proto.buffered = function buffered() { 10825 return createTimeRanges(0, 0); 10826 } 10827 /** 10828 * Get the percentage of the current video that is currently buffered. 10829 * 10830 * @return {number} 10831 * A number from 0 to 1 that represents the decimal percentage of the 10832 * video that is buffered. 10833 * 10834 */ 10835 ; 10836 10837 _proto.bufferedPercent = function bufferedPercent$1() { 10838 return bufferedPercent(this.buffered(), this.duration_); 10839 } 10840 /** 10841 * Turn off the polyfill for `progress` events that was created in 10842 * {@link Tech#manualProgressOn} 10843 * Stop manually tracking progress events by clearing the interval that was set in 10844 * {@link Tech#trackProgress}. 10845 */ 10846 ; 10847 10848 _proto.stopTrackingProgress = function stopTrackingProgress() { 10849 this.clearInterval(this.progressInterval); 10850 } 10851 /** 10852 * Polyfill the `timeupdate` event for browsers that don't support it. 10853 * 10854 * @see {@link Tech#trackCurrentTime} 10855 */ 10856 ; 10857 10858 _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() { 10859 this.manualTimeUpdates = true; 10860 this.on('play', this.trackCurrentTime); 10861 this.on('pause', this.stopTrackingCurrentTime); 10862 } 10863 /** 10864 * Turn off the polyfill for `timeupdate` events that was created in 10865 * {@link Tech#manualTimeUpdatesOn} 10866 */ 10867 ; 10868 10869 _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() { 10870 this.manualTimeUpdates = false; 10871 this.stopTrackingCurrentTime(); 10872 this.off('play', this.trackCurrentTime); 10873 this.off('pause', this.stopTrackingCurrentTime); 10874 } 10875 /** 10876 * Sets up an interval function to track current time and trigger `timeupdate` every 10877 * 250 milliseconds. 10878 * 10879 * @listens Tech#play 10880 * @triggers Tech#timeupdate 10881 */ 10882 ; 10883 10884 _proto.trackCurrentTime = function trackCurrentTime() { 10885 if (this.currentTimeInterval) { 10886 this.stopTrackingCurrentTime(); 10887 } 10888 10889 this.currentTimeInterval = this.setInterval(function () { 10890 /** 10891 * Triggered at an interval of 250ms to indicated that time is passing in the video. 10892 * 10893 * @event Tech#timeupdate 10894 * @type {EventTarget~Event} 10895 */ 10896 this.trigger({ 10897 type: 'timeupdate', 10898 target: this, 10899 manuallyTriggered: true 10900 }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 10901 }, 250); 10902 } 10903 /** 10904 * Stop the interval function created in {@link Tech#trackCurrentTime} so that the 10905 * `timeupdate` event is no longer triggered. 10906 * 10907 * @listens {Tech#pause} 10908 */ 10909 ; 10910 10911 _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() { 10912 this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen, 10913 // the progress bar won't make it all the way to the end 10914 10915 this.trigger({ 10916 type: 'timeupdate', 10917 target: this, 10918 manuallyTriggered: true 10919 }); 10920 } 10921 /** 10922 * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, 10923 * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. 10924 * 10925 * @fires Component#dispose 10926 */ 10927 ; 10928 10929 _proto.dispose = function dispose() { 10930 // clear out all tracks because we can't reuse them between techs 10931 this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking 10932 10933 if (this.manualProgress) { 10934 this.manualProgressOff(); 10935 } 10936 10937 if (this.manualTimeUpdates) { 10938 this.manualTimeUpdatesOff(); 10939 } 10940 10941 _Component.prototype.dispose.call(this); 10942 } 10943 /** 10944 * Clear out a single `TrackList` or an array of `TrackLists` given their names. 10945 * 10946 * > Note: Techs without source handlers should call this between sources for `video` 10947 * & `audio` tracks. You don't want to use them between tracks! 10948 * 10949 * @param {string[]|string} types 10950 * TrackList names to clear, valid names are `video`, `audio`, and 10951 * `text`. 10952 */ 10953 ; 10954 10955 _proto.clearTracks = function clearTracks(types) { 10956 var _this3 = this; 10957 10958 types = [].concat(types); // clear out all tracks because we can't reuse them between techs 10959 10960 types.forEach(function (type) { 10961 var list = _this3[type + "Tracks"]() || []; 10962 var i = list.length; 10963 10964 while (i--) { 10965 var track = list[i]; 10966 10967 if (type === 'text') { 10968 _this3.removeRemoteTextTrack(track); 10969 } 10970 10971 list.removeTrack(track); 10972 } 10973 }); 10974 } 10975 /** 10976 * Remove any TextTracks added via addRemoteTextTrack that are 10977 * flagged for automatic garbage collection 10978 */ 10979 ; 10980 10981 _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() { 10982 var list = this.autoRemoteTextTracks_ || []; 10983 var i = list.length; 10984 10985 while (i--) { 10986 var track = list[i]; 10987 this.removeRemoteTextTrack(track); 10988 } 10989 } 10990 /** 10991 * Reset the tech, which will removes all sources and reset the internal readyState. 10992 * 10993 * @abstract 10994 */ 10995 ; 10996 10997 _proto.reset = function reset() {} 10998 /** 10999 * Get the value of `crossOrigin` from the tech. 11000 * 11001 * @abstract 11002 * 11003 * @see {Html5#crossOrigin} 11004 */ 11005 ; 11006 11007 _proto.crossOrigin = function crossOrigin() {} 11008 /** 11009 * Set the value of `crossOrigin` on the tech. 11010 * 11011 * @abstract 11012 * 11013 * @param {string} crossOrigin the crossOrigin value 11014 * @see {Html5#setCrossOrigin} 11015 */ 11016 ; 11017 11018 _proto.setCrossOrigin = function setCrossOrigin() {} 11019 /** 11020 * Get or set an error on the Tech. 11021 * 11022 * @param {MediaError} [err] 11023 * Error to set on the Tech 11024 * 11025 * @return {MediaError|null} 11026 * The current error object on the tech, or null if there isn't one. 11027 */ 11028 ; 11029 11030 _proto.error = function error(err) { 11031 if (err !== undefined) { 11032 this.error_ = new MediaError(err); 11033 this.trigger('error'); 11034 } 11035 11036 return this.error_; 11037 } 11038 /** 11039 * Returns the `TimeRange`s that have been played through for the current source. 11040 * 11041 * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. 11042 * It only checks whether the source has played at all or not. 11043 * 11044 * @return {TimeRange} 11045 * - A single time range if this video has played 11046 * - An empty set of ranges if not. 11047 */ 11048 ; 11049 11050 _proto.played = function played() { 11051 if (this.hasStarted_) { 11052 return createTimeRanges(0, 0); 11053 } 11054 11055 return createTimeRanges(); 11056 } 11057 /** 11058 * Set whether we are scrubbing or not 11059 * 11060 * @abstract 11061 * 11062 * @see {Html5#setScrubbing} 11063 */ 11064 ; 11065 11066 _proto.setScrubbing = function setScrubbing() {} 11067 /** 11068 * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was 11069 * previously called. 11070 * 11071 * @fires Tech#timeupdate 11072 */ 11073 ; 11074 11075 _proto.setCurrentTime = function setCurrentTime() { 11076 // improve the accuracy of manual timeupdates 11077 if (this.manualTimeUpdates) { 11078 /** 11079 * A manual `timeupdate` event. 11080 * 11081 * @event Tech#timeupdate 11082 * @type {EventTarget~Event} 11083 */ 11084 this.trigger({ 11085 type: 'timeupdate', 11086 target: this, 11087 manuallyTriggered: true 11088 }); 11089 } 11090 } 11091 /** 11092 * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and 11093 * {@link TextTrackList} events. 11094 * 11095 * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. 11096 * 11097 * @fires Tech#audiotrackchange 11098 * @fires Tech#videotrackchange 11099 * @fires Tech#texttrackchange 11100 */ 11101 ; 11102 11103 _proto.initTrackListeners = function initTrackListeners() { 11104 var _this4 = this; 11105 11106 /** 11107 * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} 11108 * 11109 * @event Tech#audiotrackchange 11110 * @type {EventTarget~Event} 11111 */ 11112 11113 /** 11114 * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} 11115 * 11116 * @event Tech#videotrackchange 11117 * @type {EventTarget~Event} 11118 */ 11119 11120 /** 11121 * Triggered when tracks are added or removed on the Tech {@link TextTrackList} 11122 * 11123 * @event Tech#texttrackchange 11124 * @type {EventTarget~Event} 11125 */ 11126 NORMAL.names.forEach(function (name) { 11127 var props = NORMAL[name]; 11128 11129 var trackListChanges = function trackListChanges() { 11130 _this4.trigger(name + "trackchange"); 11131 }; 11132 11133 var tracks = _this4[props.getterName](); 11134 11135 tracks.addEventListener('removetrack', trackListChanges); 11136 tracks.addEventListener('addtrack', trackListChanges); 11137 11138 _this4.on('dispose', function () { 11139 tracks.removeEventListener('removetrack', trackListChanges); 11140 tracks.removeEventListener('addtrack', trackListChanges); 11141 }); 11142 }); 11143 } 11144 /** 11145 * Emulate TextTracks using vtt.js if necessary 11146 * 11147 * @fires Tech#vttjsloaded 11148 * @fires Tech#vttjserror 11149 */ 11150 ; 11151 11152 _proto.addWebVttScript_ = function addWebVttScript_() { 11153 var _this5 = this; 11154 11155 if (window$1.WebVTT) { 11156 return; 11157 } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system 11158 // signals that the Tech is ready at which point Tech.el_ is part of the DOM 11159 // before inserting the WebVTT script 11160 11161 11162 if (document.body.contains(this.el())) { 11163 // load via require if available and vtt.js script location was not passed in 11164 // as an option. novtt builds will turn the above require call into an empty object 11165 // which will cause this if check to always fail. 11166 if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { 11167 this.trigger('vttjsloaded'); 11168 return; 11169 } // load vtt.js via the script location option or the cdn of no location was 11170 // passed in 11171 11172 11173 var script = document.createElement('script'); 11174 script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; 11175 11176 script.onload = function () { 11177 /** 11178 * Fired when vtt.js is loaded. 11179 * 11180 * @event Tech#vttjsloaded 11181 * @type {EventTarget~Event} 11182 */ 11183 _this5.trigger('vttjsloaded'); 11184 }; 11185 11186 script.onerror = function () { 11187 /** 11188 * Fired when vtt.js was not loaded due to an error 11189 * 11190 * @event Tech#vttjsloaded 11191 * @type {EventTarget~Event} 11192 */ 11193 _this5.trigger('vttjserror'); 11194 }; 11195 11196 this.on('dispose', function () { 11197 script.onload = null; 11198 script.onerror = null; 11199 }); // but have not loaded yet and we set it to true before the inject so that 11200 // we don't overwrite the injected window.WebVTT if it loads right away 11201 11202 window$1.WebVTT = true; 11203 this.el().parentNode.appendChild(script); 11204 } else { 11205 this.ready(this.addWebVttScript_); 11206 } 11207 } 11208 /** 11209 * Emulate texttracks 11210 * 11211 */ 11212 ; 11213 11214 _proto.emulateTextTracks = function emulateTextTracks() { 11215 var _this6 = this; 11216 11217 var tracks = this.textTracks(); 11218 var remoteTracks = this.remoteTextTracks(); 11219 11220 var handleAddTrack = function handleAddTrack(e) { 11221 return tracks.addTrack(e.track); 11222 }; 11223 11224 var handleRemoveTrack = function handleRemoveTrack(e) { 11225 return tracks.removeTrack(e.track); 11226 }; 11227 11228 remoteTracks.on('addtrack', handleAddTrack); 11229 remoteTracks.on('removetrack', handleRemoveTrack); 11230 this.addWebVttScript_(); 11231 11232 var updateDisplay = function updateDisplay() { 11233 return _this6.trigger('texttrackchange'); 11234 }; 11235 11236 var textTracksChanges = function textTracksChanges() { 11237 updateDisplay(); 11238 11239 for (var i = 0; i < tracks.length; i++) { 11240 var track = tracks[i]; 11241 track.removeEventListener('cuechange', updateDisplay); 11242 11243 if (track.mode === 'showing') { 11244 track.addEventListener('cuechange', updateDisplay); 11245 } 11246 } 11247 }; 11248 11249 textTracksChanges(); 11250 tracks.addEventListener('change', textTracksChanges); 11251 tracks.addEventListener('addtrack', textTracksChanges); 11252 tracks.addEventListener('removetrack', textTracksChanges); 11253 this.on('dispose', function () { 11254 remoteTracks.off('addtrack', handleAddTrack); 11255 remoteTracks.off('removetrack', handleRemoveTrack); 11256 tracks.removeEventListener('change', textTracksChanges); 11257 tracks.removeEventListener('addtrack', textTracksChanges); 11258 tracks.removeEventListener('removetrack', textTracksChanges); 11259 11260 for (var i = 0; i < tracks.length; i++) { 11261 var track = tracks[i]; 11262 track.removeEventListener('cuechange', updateDisplay); 11263 } 11264 }); 11265 } 11266 /** 11267 * Create and returns a remote {@link TextTrack} object. 11268 * 11269 * @param {string} kind 11270 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 11271 * 11272 * @param {string} [label] 11273 * Label to identify the text track 11274 * 11275 * @param {string} [language] 11276 * Two letter language abbreviation 11277 * 11278 * @return {TextTrack} 11279 * The TextTrack that gets created. 11280 */ 11281 ; 11282 11283 _proto.addTextTrack = function addTextTrack(kind, label, language) { 11284 if (!kind) { 11285 throw new Error('TextTrack kind is required but was not provided'); 11286 } 11287 11288 return createTrackHelper(this, kind, label, language); 11289 } 11290 /** 11291 * Create an emulated TextTrack for use by addRemoteTextTrack 11292 * 11293 * This is intended to be overridden by classes that inherit from 11294 * Tech in order to create native or custom TextTracks. 11295 * 11296 * @param {Object} options 11297 * The object should contain the options to initialize the TextTrack with. 11298 * 11299 * @param {string} [options.kind] 11300 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 11301 * 11302 * @param {string} [options.label]. 11303 * Label to identify the text track 11304 * 11305 * @param {string} [options.language] 11306 * Two letter language abbreviation. 11307 * 11308 * @return {HTMLTrackElement} 11309 * The track element that gets created. 11310 */ 11311 ; 11312 11313 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 11314 var track = mergeOptions(options, { 11315 tech: this 11316 }); 11317 return new REMOTE.remoteTextEl.TrackClass(track); 11318 } 11319 /** 11320 * Creates a remote text track object and returns an html track element. 11321 * 11322 * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. 11323 * 11324 * @param {Object} options 11325 * See {@link Tech#createRemoteTextTrack} for more detailed properties. 11326 * 11327 * @param {boolean} [manualCleanup=true] 11328 * - When false: the TextTrack will be automatically removed from the video 11329 * element whenever the source changes 11330 * - When True: The TextTrack will have to be cleaned up manually 11331 * 11332 * @return {HTMLTrackElement} 11333 * An Html Track Element. 11334 * 11335 * @deprecated The default functionality for this function will be equivalent 11336 * to "manualCleanup=false" in the future. The manualCleanup parameter will 11337 * also be removed. 11338 */ 11339 ; 11340 11341 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 11342 var _this7 = this; 11343 11344 if (options === void 0) { 11345 options = {}; 11346 } 11347 11348 var htmlTrackElement = this.createRemoteTextTrack(options); 11349 11350 if (manualCleanup !== true && manualCleanup !== false) { 11351 // deprecation warning 11352 log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); 11353 manualCleanup = true; 11354 } // store HTMLTrackElement and TextTrack to remote list 11355 11356 11357 this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); 11358 this.remoteTextTracks().addTrack(htmlTrackElement.track); 11359 11360 if (manualCleanup !== true) { 11361 // create the TextTrackList if it doesn't exist 11362 this.ready(function () { 11363 return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); 11364 }); 11365 } 11366 11367 return htmlTrackElement; 11368 } 11369 /** 11370 * Remove a remote text track from the remote `TextTrackList`. 11371 * 11372 * @param {TextTrack} track 11373 * `TextTrack` to remove from the `TextTrackList` 11374 */ 11375 ; 11376 11377 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 11378 var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list 11379 11380 this.remoteTextTrackEls().removeTrackElement_(trackElement); 11381 this.remoteTextTracks().removeTrack(track); 11382 this.autoRemoteTextTracks_.removeTrack(track); 11383 } 11384 /** 11385 * Gets available media playback quality metrics as specified by the W3C's Media 11386 * Playback Quality API. 11387 * 11388 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 11389 * 11390 * @return {Object} 11391 * An object with supported media playback quality metrics 11392 * 11393 * @abstract 11394 */ 11395 ; 11396 11397 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 11398 return {}; 11399 } 11400 /** 11401 * Attempt to create a floating video window always on top of other windows 11402 * so that users may continue consuming media while they interact with other 11403 * content sites, or applications on their device. 11404 * 11405 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 11406 * 11407 * @return {Promise|undefined} 11408 * A promise with a Picture-in-Picture window if the browser supports 11409 * Promises (or one was passed in as an option). It returns undefined 11410 * otherwise. 11411 * 11412 * @abstract 11413 */ 11414 ; 11415 11416 _proto.requestPictureInPicture = function requestPictureInPicture() { 11417 var PromiseClass = this.options_.Promise || window$1.Promise; 11418 11419 if (PromiseClass) { 11420 return PromiseClass.reject(); 11421 } 11422 } 11423 /** 11424 * A method to check for the value of the 'disablePictureInPicture' <video> property. 11425 * Defaults to true, as it should be considered disabled if the tech does not support pip 11426 * 11427 * @abstract 11428 */ 11429 ; 11430 11431 _proto.disablePictureInPicture = function disablePictureInPicture() { 11432 return true; 11433 } 11434 /** 11435 * A method to set or unset the 'disablePictureInPicture' <video> property. 11436 * 11437 * @abstract 11438 */ 11439 ; 11440 11441 _proto.setDisablePictureInPicture = function setDisablePictureInPicture() {} 11442 /** 11443 * A method to set a poster from a `Tech`. 11444 * 11445 * @abstract 11446 */ 11447 ; 11448 11449 _proto.setPoster = function setPoster() {} 11450 /** 11451 * A method to check for the presence of the 'playsinline' <video> attribute. 11452 * 11453 * @abstract 11454 */ 11455 ; 11456 11457 _proto.playsinline = function playsinline() {} 11458 /** 11459 * A method to set or unset the 'playsinline' <video> attribute. 11460 * 11461 * @abstract 11462 */ 11463 ; 11464 11465 _proto.setPlaysinline = function setPlaysinline() {} 11466 /** 11467 * Attempt to force override of native audio tracks. 11468 * 11469 * @param {boolean} override - If set to true native audio will be overridden, 11470 * otherwise native audio will potentially be used. 11471 * 11472 * @abstract 11473 */ 11474 ; 11475 11476 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {} 11477 /** 11478 * Attempt to force override of native video tracks. 11479 * 11480 * @param {boolean} override - If set to true native video will be overridden, 11481 * otherwise native video will potentially be used. 11482 * 11483 * @abstract 11484 */ 11485 ; 11486 11487 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {} 11488 /* 11489 * Check if the tech can support the given mime-type. 11490 * 11491 * The base tech does not support any type, but source handlers might 11492 * overwrite this. 11493 * 11494 * @param {string} type 11495 * The mimetype to check for support 11496 * 11497 * @return {string} 11498 * 'probably', 'maybe', or empty string 11499 * 11500 * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType} 11501 * 11502 * @abstract 11503 */ 11504 ; 11505 11506 _proto.canPlayType = function canPlayType() { 11507 return ''; 11508 } 11509 /** 11510 * Check if the type is supported by this tech. 11511 * 11512 * The base tech does not support any type, but source handlers might 11513 * overwrite this. 11514 * 11515 * @param {string} type 11516 * The media type to check 11517 * @return {string} Returns the native video element's response 11518 */ 11519 ; 11520 11521 Tech.canPlayType = function canPlayType() { 11522 return ''; 11523 } 11524 /** 11525 * Check if the tech can support the given source 11526 * 11527 * @param {Object} srcObj 11528 * The source object 11529 * @param {Object} options 11530 * The options passed to the tech 11531 * @return {string} 'probably', 'maybe', or '' (empty string) 11532 */ 11533 ; 11534 11535 Tech.canPlaySource = function canPlaySource(srcObj, options) { 11536 return Tech.canPlayType(srcObj.type); 11537 } 11538 /* 11539 * Return whether the argument is a Tech or not. 11540 * Can be passed either a Class like `Html5` or a instance like `player.tech_` 11541 * 11542 * @param {Object} component 11543 * The item to check 11544 * 11545 * @return {boolean} 11546 * Whether it is a tech or not 11547 * - True if it is a tech 11548 * - False if it is not 11549 */ 11550 ; 11551 11552 Tech.isTech = function isTech(component) { 11553 return component.prototype instanceof Tech || component instanceof Tech || component === Tech; 11554 } 11555 /** 11556 * Registers a `Tech` into a shared list for videojs. 11557 * 11558 * @param {string} name 11559 * Name of the `Tech` to register. 11560 * 11561 * @param {Object} tech 11562 * The `Tech` class to register. 11563 */ 11564 ; 11565 11566 Tech.registerTech = function registerTech(name, tech) { 11567 if (!Tech.techs_) { 11568 Tech.techs_ = {}; 11569 } 11570 11571 if (!Tech.isTech(tech)) { 11572 throw new Error("Tech " + name + " must be a Tech"); 11573 } 11574 11575 if (!Tech.canPlayType) { 11576 throw new Error('Techs must have a static canPlayType method on them'); 11577 } 11578 11579 if (!Tech.canPlaySource) { 11580 throw new Error('Techs must have a static canPlaySource method on them'); 11581 } 11582 11583 name = toTitleCase(name); 11584 Tech.techs_[name] = tech; 11585 Tech.techs_[toLowerCase(name)] = tech; 11586 11587 if (name !== 'Tech') { 11588 // camel case the techName for use in techOrder 11589 Tech.defaultTechOrder_.push(name); 11590 } 11591 11592 return tech; 11593 } 11594 /** 11595 * Get a `Tech` from the shared list by name. 11596 * 11597 * @param {string} name 11598 * `camelCase` or `TitleCase` name of the Tech to get 11599 * 11600 * @return {Tech|undefined} 11601 * The `Tech` or undefined if there was no tech with the name requested. 11602 */ 11603 ; 11604 11605 Tech.getTech = function getTech(name) { 11606 if (!name) { 11607 return; 11608 } 11609 11610 if (Tech.techs_ && Tech.techs_[name]) { 11611 return Tech.techs_[name]; 11612 } 11613 11614 name = toTitleCase(name); 11615 11616 if (window$1 && window$1.videojs && window$1.videojs[name]) { 11617 log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)"); 11618 return window$1.videojs[name]; 11619 } 11620 }; 11621 11622 return Tech; 11623 }(Component); 11624 /** 11625 * Get the {@link VideoTrackList} 11626 * 11627 * @returns {VideoTrackList} 11628 * @method Tech.prototype.videoTracks 11629 */ 11630 11631 /** 11632 * Get the {@link AudioTrackList} 11633 * 11634 * @returns {AudioTrackList} 11635 * @method Tech.prototype.audioTracks 11636 */ 11637 11638 /** 11639 * Get the {@link TextTrackList} 11640 * 11641 * @returns {TextTrackList} 11642 * @method Tech.prototype.textTracks 11643 */ 11644 11645 /** 11646 * Get the remote element {@link TextTrackList} 11647 * 11648 * @returns {TextTrackList} 11649 * @method Tech.prototype.remoteTextTracks 11650 */ 11651 11652 /** 11653 * Get the remote element {@link HtmlTrackElementList} 11654 * 11655 * @returns {HtmlTrackElementList} 11656 * @method Tech.prototype.remoteTextTrackEls 11657 */ 11658 11659 11660 ALL.names.forEach(function (name) { 11661 var props = ALL[name]; 11662 11663 Tech.prototype[props.getterName] = function () { 11664 this[props.privateName] = this[props.privateName] || new props.ListClass(); 11665 return this[props.privateName]; 11666 }; 11667 }); 11668 /** 11669 * List of associated text tracks 11670 * 11671 * @type {TextTrackList} 11672 * @private 11673 * @property Tech#textTracks_ 11674 */ 11675 11676 /** 11677 * List of associated audio tracks. 11678 * 11679 * @type {AudioTrackList} 11680 * @private 11681 * @property Tech#audioTracks_ 11682 */ 11683 11684 /** 11685 * List of associated video tracks. 11686 * 11687 * @type {VideoTrackList} 11688 * @private 11689 * @property Tech#videoTracks_ 11690 */ 11691 11692 /** 11693 * Boolean indicating whether the `Tech` supports volume control. 11694 * 11695 * @type {boolean} 11696 * @default 11697 */ 11698 11699 Tech.prototype.featuresVolumeControl = true; 11700 /** 11701 * Boolean indicating whether the `Tech` supports muting volume. 11702 * 11703 * @type {bolean} 11704 * @default 11705 */ 11706 11707 Tech.prototype.featuresMuteControl = true; 11708 /** 11709 * Boolean indicating whether the `Tech` supports fullscreen resize control. 11710 * Resizing plugins using request fullscreen reloads the plugin 11711 * 11712 * @type {boolean} 11713 * @default 11714 */ 11715 11716 Tech.prototype.featuresFullscreenResize = false; 11717 /** 11718 * Boolean indicating whether the `Tech` supports changing the speed at which the video 11719 * plays. Examples: 11720 * - Set player to play 2x (twice) as fast 11721 * - Set player to play 0.5x (half) as fast 11722 * 11723 * @type {boolean} 11724 * @default 11725 */ 11726 11727 Tech.prototype.featuresPlaybackRate = false; 11728 /** 11729 * Boolean indicating whether the `Tech` supports the `progress` event. This is currently 11730 * not triggered by video-js-swf. This will be used to determine if 11731 * {@link Tech#manualProgressOn} should be called. 11732 * 11733 * @type {boolean} 11734 * @default 11735 */ 11736 11737 Tech.prototype.featuresProgressEvents = false; 11738 /** 11739 * Boolean indicating whether the `Tech` supports the `sourceset` event. 11740 * 11741 * A tech should set this to `true` and then use {@link Tech#triggerSourceset} 11742 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting 11743 * a new source. 11744 * 11745 * @type {boolean} 11746 * @default 11747 */ 11748 11749 Tech.prototype.featuresSourceset = false; 11750 /** 11751 * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently 11752 * not triggered by video-js-swf. This will be used to determine if 11753 * {@link Tech#manualTimeUpdates} should be called. 11754 * 11755 * @type {boolean} 11756 * @default 11757 */ 11758 11759 Tech.prototype.featuresTimeupdateEvents = false; 11760 /** 11761 * Boolean indicating whether the `Tech` supports the native `TextTrack`s. 11762 * This will help us integrate with native `TextTrack`s if the browser supports them. 11763 * 11764 * @type {boolean} 11765 * @default 11766 */ 11767 11768 Tech.prototype.featuresNativeTextTracks = false; 11769 /** 11770 * A functional mixin for techs that want to use the Source Handler pattern. 11771 * Source handlers are scripts for handling specific formats. 11772 * The source handler pattern is used for adaptive formats (HLS, DASH) that 11773 * manually load video data and feed it into a Source Buffer (Media Source Extensions) 11774 * Example: `Tech.withSourceHandlers.call(MyTech);` 11775 * 11776 * @param {Tech} _Tech 11777 * The tech to add source handler functions to. 11778 * 11779 * @mixes Tech~SourceHandlerAdditions 11780 */ 11781 11782 Tech.withSourceHandlers = function (_Tech) { 11783 /** 11784 * Register a source handler 11785 * 11786 * @param {Function} handler 11787 * The source handler class 11788 * 11789 * @param {number} [index] 11790 * Register it at the following index 11791 */ 11792 _Tech.registerSourceHandler = function (handler, index) { 11793 var handlers = _Tech.sourceHandlers; 11794 11795 if (!handlers) { 11796 handlers = _Tech.sourceHandlers = []; 11797 } 11798 11799 if (index === undefined) { 11800 // add to the end of the list 11801 index = handlers.length; 11802 } 11803 11804 handlers.splice(index, 0, handler); 11805 }; 11806 /** 11807 * Check if the tech can support the given type. Also checks the 11808 * Techs sourceHandlers. 11809 * 11810 * @param {string} type 11811 * The mimetype to check. 11812 * 11813 * @return {string} 11814 * 'probably', 'maybe', or '' (empty string) 11815 */ 11816 11817 11818 _Tech.canPlayType = function (type) { 11819 var handlers = _Tech.sourceHandlers || []; 11820 var can; 11821 11822 for (var i = 0; i < handlers.length; i++) { 11823 can = handlers[i].canPlayType(type); 11824 11825 if (can) { 11826 return can; 11827 } 11828 } 11829 11830 return ''; 11831 }; 11832 /** 11833 * Returns the first source handler that supports the source. 11834 * 11835 * TODO: Answer question: should 'probably' be prioritized over 'maybe' 11836 * 11837 * @param {Tech~SourceObject} source 11838 * The source object 11839 * 11840 * @param {Object} options 11841 * The options passed to the tech 11842 * 11843 * @return {SourceHandler|null} 11844 * The first source handler that supports the source or null if 11845 * no SourceHandler supports the source 11846 */ 11847 11848 11849 _Tech.selectSourceHandler = function (source, options) { 11850 var handlers = _Tech.sourceHandlers || []; 11851 var can; 11852 11853 for (var i = 0; i < handlers.length; i++) { 11854 can = handlers[i].canHandleSource(source, options); 11855 11856 if (can) { 11857 return handlers[i]; 11858 } 11859 } 11860 11861 return null; 11862 }; 11863 /** 11864 * Check if the tech can support the given source. 11865 * 11866 * @param {Tech~SourceObject} srcObj 11867 * The source object 11868 * 11869 * @param {Object} options 11870 * The options passed to the tech 11871 * 11872 * @return {string} 11873 * 'probably', 'maybe', or '' (empty string) 11874 */ 11875 11876 11877 _Tech.canPlaySource = function (srcObj, options) { 11878 var sh = _Tech.selectSourceHandler(srcObj, options); 11879 11880 if (sh) { 11881 return sh.canHandleSource(srcObj, options); 11882 } 11883 11884 return ''; 11885 }; 11886 /** 11887 * When using a source handler, prefer its implementation of 11888 * any function normally provided by the tech. 11889 */ 11890 11891 11892 var deferrable = ['seekable', 'seeking', 'duration']; 11893 /** 11894 * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable 11895 * function if it exists, with a fallback to the Techs seekable function. 11896 * 11897 * @method _Tech.seekable 11898 */ 11899 11900 /** 11901 * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration 11902 * function if it exists, otherwise it will fallback to the techs duration function. 11903 * 11904 * @method _Tech.duration 11905 */ 11906 11907 deferrable.forEach(function (fnName) { 11908 var originalFn = this[fnName]; 11909 11910 if (typeof originalFn !== 'function') { 11911 return; 11912 } 11913 11914 this[fnName] = function () { 11915 if (this.sourceHandler_ && this.sourceHandler_[fnName]) { 11916 return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); 11917 } 11918 11919 return originalFn.apply(this, arguments); 11920 }; 11921 }, _Tech.prototype); 11922 /** 11923 * Create a function for setting the source using a source object 11924 * and source handlers. 11925 * Should never be called unless a source handler was found. 11926 * 11927 * @param {Tech~SourceObject} source 11928 * A source object with src and type keys 11929 */ 11930 11931 _Tech.prototype.setSource = function (source) { 11932 var sh = _Tech.selectSourceHandler(source, this.options_); 11933 11934 if (!sh) { 11935 // Fall back to a native source hander when unsupported sources are 11936 // deliberately set 11937 if (_Tech.nativeSourceHandler) { 11938 sh = _Tech.nativeSourceHandler; 11939 } else { 11940 log.error('No source handler found for the current source.'); 11941 } 11942 } // Dispose any existing source handler 11943 11944 11945 this.disposeSourceHandler(); 11946 this.off('dispose', this.disposeSourceHandler); 11947 11948 if (sh !== _Tech.nativeSourceHandler) { 11949 this.currentSource_ = source; 11950 } 11951 11952 this.sourceHandler_ = sh.handleSource(source, this, this.options_); 11953 this.one('dispose', this.disposeSourceHandler); 11954 }; 11955 /** 11956 * Clean up any existing SourceHandlers and listeners when the Tech is disposed. 11957 * 11958 * @listens Tech#dispose 11959 */ 11960 11961 11962 _Tech.prototype.disposeSourceHandler = function () { 11963 // if we have a source and get another one 11964 // then we are loading something new 11965 // than clear all of our current tracks 11966 if (this.currentSource_) { 11967 this.clearTracks(['audio', 'video']); 11968 this.currentSource_ = null; 11969 } // always clean up auto-text tracks 11970 11971 11972 this.cleanupAutoTextTracks(); 11973 11974 if (this.sourceHandler_) { 11975 if (this.sourceHandler_.dispose) { 11976 this.sourceHandler_.dispose(); 11977 } 11978 11979 this.sourceHandler_ = null; 11980 } 11981 }; 11982 };
vendor: 4,340 bytes, lines 11982-12132
11982 // The base Tech class needs to be registered as a Component. It is the only 11983 // Tech that can be registered as a Component. 11984 11985 11986 Component.registerComponent('Tech', Tech); 11987 Tech.registerTech('Tech', Tech); 11988 /** 11989 * A list of techs that should be added to techOrder on Players 11990 * 11991 * @private 11992 */ 11993 11994 Tech.defaultTechOrder_ = []; 11995 11996 /** 11997 * @file middleware.js 11998 * @module middleware 11999 */ 12000 var middlewares = {}; 12001 var middlewareInstances = {}; 12002 var TERMINATOR = {}; 12003 /** 12004 * A middleware object is a plain JavaScript object that has methods that 12005 * match the {@link Tech} methods found in the lists of allowed 12006 * {@link module:middleware.allowedGetters|getters}, 12007 * {@link module:middleware.allowedSetters|setters}, and 12008 * {@link module:middleware.allowedMediators|mediators}. 12009 * 12010 * @typedef {Object} MiddlewareObject 12011 */ 12012 12013 /** 12014 * A middleware factory function that should return a 12015 * {@link module:middleware~MiddlewareObject|MiddlewareObject}. 12016 * 12017 * This factory will be called for each player when needed, with the player 12018 * passed in as an argument. 12019 * 12020 * @callback MiddlewareFactory 12021 * @param {Player} player 12022 * A Video.js player. 12023 */ 12024 12025 /** 12026 * Define a middleware that the player should use by way of a factory function 12027 * that returns a middleware object. 12028 * 12029 * @param {string} type 12030 * The MIME type to match or `"*"` for all MIME types. 12031 * 12032 * @param {MiddlewareFactory} middleware 12033 * A middleware factory function that will be executed for 12034 * matching types. 12035 */ 12036 12037 function use(type, middleware) { 12038 middlewares[type] = middlewares[type] || []; 12039 middlewares[type].push(middleware); 12040 } 12041 /** 12042 * Asynchronously sets a source using middleware by recursing through any 12043 * matching middlewares and calling `setSource` on each, passing along the 12044 * previous returned value each time. 12045 * 12046 * @param {Player} player 12047 * A {@link Player} instance. 12048 * 12049 * @param {Tech~SourceObject} src 12050 * A source object. 12051 * 12052 * @param {Function} 12053 * The next middleware to run. 12054 */ 12055 12056 function setSource(player, src, next) { 12057 player.setTimeout(function () { 12058 return setSourceHelper(src, middlewares[src.type], next, player); 12059 }, 1); 12060 } 12061 /** 12062 * When the tech is set, passes the tech to each middleware's `setTech` method. 12063 * 12064 * @param {Object[]} middleware 12065 * An array of middleware instances. 12066 * 12067 * @param {Tech} tech 12068 * A Video.js tech. 12069 */ 12070 12071 function setTech(middleware, tech) { 12072 middleware.forEach(function (mw) { 12073 return mw.setTech && mw.setTech(tech); 12074 }); 12075 } 12076 /** 12077 * Calls a getter on the tech first, through each middleware 12078 * from right to left to the player. 12079 * 12080 * @param {Object[]} middleware 12081 * An array of middleware instances. 12082 * 12083 * @param {Tech} tech 12084 * The current tech. 12085 * 12086 * @param {string} method 12087 * A method name. 12088 * 12089 * @return {Mixed} 12090 * The final value from the tech after middleware has intercepted it. 12091 */ 12092 12093 function get(middleware, tech, method) { 12094 return middleware.reduceRight(middlewareIterator(method), tech[method]()); 12095 } 12096 /** 12097 * Takes the argument given to the player and calls the setter method on each 12098 * middleware from left to right to the tech. 12099 * 12100 * @param {Object[]} middleware 12101 * An array of middleware instances. 12102 * 12103 * @param {Tech} tech 12104 * The current tech. 12105 * 12106 * @param {string} method 12107 * A method name. 12108 * 12109 * @param {Mixed} arg 12110 * The value to set on the tech. 12111 * 12112 * @return {Mixed} 12113 * The return value of the `method` of the `tech`. 12114 */ 12115 12116 function set(middleware, tech, method, arg) { 12117 return tech[method](middleware.reduce(middlewareIterator(method), arg)); 12118 } 12119 /** 12120 * Takes the argument given to the player and calls the `call` version of the 12121 * method on each middleware from left to right. 12122 * 12123 * Then, call the passed in method on the tech and return the result unchanged 12124 * back to the player, through middleware, this time from right to left. 12125 * 12126 * @param {Object[]} middleware 12127 * An array of middleware instances. 12128 * 12129 * @param {Tech} tech 12130 * The current tech. 12131 * 12132 * @param {string} method
vendor: 182,697 bytes, lines 12133-18508
12133 * A method name. 12134 * 12135 * @param {Mixed} arg 12136 * The value to set on the tech. 12137 * 12138 * @return {Mixed} 12139 * The return value of the `method` of the `tech`, regardless of the 12140 * return values of middlewares. 12141 */ 12142 12143 function mediate(middleware, tech, method, arg) { 12144 if (arg === void 0) { 12145 arg = null; 12146 } 12147 12148 var callMethod = 'call' + toTitleCase(method); 12149 var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg); 12150 var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to 12151 // prevent confusion if a techs method actually returns null. 12152 12153 var returnValue = terminated ? null : tech[method](middlewareValue); 12154 executeRight(middleware, method, returnValue, terminated); 12155 return returnValue; 12156 } 12157 /** 12158 * Enumeration of allowed getters where the keys are method names. 12159 * 12160 * @type {Object} 12161 */ 12162 12163 var allowedGetters = { 12164 buffered: 1, 12165 currentTime: 1, 12166 duration: 1, 12167 muted: 1, 12168 played: 1, 12169 paused: 1, 12170 seekable: 1, 12171 volume: 1 12172 }; 12173 /** 12174 * Enumeration of allowed setters where the keys are method names. 12175 * 12176 * @type {Object} 12177 */ 12178 12179 var allowedSetters = { 12180 setCurrentTime: 1, 12181 setMuted: 1, 12182 setVolume: 1 12183 }; 12184 /** 12185 * Enumeration of allowed mediators where the keys are method names. 12186 * 12187 * @type {Object} 12188 */ 12189 12190 var allowedMediators = { 12191 play: 1, 12192 pause: 1 12193 }; 12194 12195 function middlewareIterator(method) { 12196 return function (value, mw) { 12197 // if the previous middleware terminated, pass along the termination 12198 if (value === TERMINATOR) { 12199 return TERMINATOR; 12200 } 12201 12202 if (mw[method]) { 12203 return mw[method](value); 12204 } 12205 12206 return value; 12207 }; 12208 } 12209 12210 function executeRight(mws, method, value, terminated) { 12211 for (var i = mws.length - 1; i >= 0; i--) { 12212 var mw = mws[i]; 12213 12214 if (mw[method]) { 12215 mw[method](terminated, value); 12216 } 12217 } 12218 } 12219 /** 12220 * Clear the middleware cache for a player. 12221 * 12222 * @param {Player} player 12223 * A {@link Player} instance. 12224 */ 12225 12226 12227 function clearCacheForPlayer(player) { 12228 middlewareInstances[player.id()] = null; 12229 } 12230 /** 12231 * { 12232 * [playerId]: [[mwFactory, mwInstance], ...] 12233 * } 12234 * 12235 * @private 12236 */ 12237 12238 function getOrCreateFactory(player, mwFactory) { 12239 var mws = middlewareInstances[player.id()]; 12240 var mw = null; 12241 12242 if (mws === undefined || mws === null) { 12243 mw = mwFactory(player); 12244 middlewareInstances[player.id()] = [[mwFactory, mw]]; 12245 return mw; 12246 } 12247 12248 for (var i = 0; i < mws.length; i++) { 12249 var _mws$i = mws[i], 12250 mwf = _mws$i[0], 12251 mwi = _mws$i[1]; 12252 12253 if (mwf !== mwFactory) { 12254 continue; 12255 } 12256 12257 mw = mwi; 12258 } 12259 12260 if (mw === null) { 12261 mw = mwFactory(player); 12262 mws.push([mwFactory, mw]); 12263 } 12264 12265 return mw; 12266 } 12267 12268 function setSourceHelper(src, middleware, next, player, acc, lastRun) { 12269 if (src === void 0) { 12270 src = {}; 12271 } 12272 12273 if (middleware === void 0) { 12274 middleware = []; 12275 } 12276 12277 if (acc === void 0) { 12278 acc = []; 12279 } 12280 12281 if (lastRun === void 0) { 12282 lastRun = false; 12283 } 12284 12285 var _middleware = middleware, 12286 mwFactory = _middleware[0], 12287 mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road 12288 12289 12290 if (typeof mwFactory === 'string') { 12291 setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw, 12292 // then call the mw's setSource method 12293 } else if (mwFactory) { 12294 var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware 12295 12296 if (!mw.setSource) { 12297 acc.push(mw); 12298 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12299 } 12300 12301 mw.setSource(assign({}, src), function (err, _src) { 12302 // something happened, try the next middleware on the current level 12303 // make sure to use the old src 12304 if (err) { 12305 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12306 } // we've succeeded, now we need to go deeper 12307 12308 12309 acc.push(mw); // if it's the same type, continue down the current chain 12310 // otherwise, we want to go down the new chain 12311 12312 setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); 12313 }); 12314 } else if (mwrest.length) { 12315 setSourceHelper(src, mwrest, next, player, acc, lastRun); 12316 } else if (lastRun) { 12317 next(src, acc); 12318 } else { 12319 setSourceHelper(src, middlewares['*'], next, player, acc, true); 12320 } 12321 } 12322 12323 /** 12324 * Mimetypes 12325 * 12326 * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm 12327 * @typedef Mimetypes~Kind 12328 * @enum 12329 */ 12330 12331 var MimetypesKind = { 12332 opus: 'video/ogg', 12333 ogv: 'video/ogg', 12334 mp4: 'video/mp4', 12335 mov: 'video/mp4', 12336 m4v: 'video/mp4', 12337 mkv: 'video/x-matroska', 12338 m4a: 'audio/mp4', 12339 mp3: 'audio/mpeg', 12340 aac: 'audio/aac', 12341 caf: 'audio/x-caf', 12342 flac: 'audio/flac', 12343 oga: 'audio/ogg', 12344 wav: 'audio/wav', 12345 m3u8: 'application/x-mpegURL', 12346 jpg: 'image/jpeg', 12347 jpeg: 'image/jpeg', 12348 gif: 'image/gif', 12349 png: 'image/png', 12350 svg: 'image/svg+xml', 12351 webp: 'image/webp' 12352 }; 12353 /** 12354 * Get the mimetype of a given src url if possible 12355 * 12356 * @param {string} src 12357 * The url to the src 12358 * 12359 * @return {string} 12360 * return the mimetype if it was known or empty string otherwise 12361 */ 12362 12363 var getMimetype = function getMimetype(src) { 12364 if (src === void 0) { 12365 src = ''; 12366 } 12367 12368 var ext = getFileExtension(src); 12369 var mimetype = MimetypesKind[ext.toLowerCase()]; 12370 return mimetype || ''; 12371 }; 12372 /** 12373 * Find the mime type of a given source string if possible. Uses the player 12374 * source cache. 12375 * 12376 * @param {Player} player 12377 * The player object 12378 * 12379 * @param {string} src 12380 * The source string 12381 * 12382 * @return {string} 12383 * The type that was found 12384 */ 12385 12386 var findMimetype = function findMimetype(player, src) { 12387 if (!src) { 12388 return ''; 12389 } // 1. check for the type in the `source` cache 12390 12391 12392 if (player.cache_.source.src === src && player.cache_.source.type) { 12393 return player.cache_.source.type; 12394 } // 2. see if we have this source in our `currentSources` cache 12395 12396 12397 var matchingSources = player.cache_.sources.filter(function (s) { 12398 return s.src === src; 12399 }); 12400 12401 if (matchingSources.length) { 12402 return matchingSources[0].type; 12403 } // 3. look for the src url in source elements and use the type there 12404 12405 12406 var sources = player.$$('source'); 12407 12408 for (var i = 0; i < sources.length; i++) { 12409 var s = sources[i]; 12410 12411 if (s.type && s.src && s.src === src) { 12412 return s.type; 12413 } 12414 } // 4. finally fallback to our list of mime types based on src url extension 12415 12416 12417 return getMimetype(src); 12418 }; 12419 12420 /** 12421 * @module filter-source 12422 */ 12423 /** 12424 * Filter out single bad source objects or multiple source objects in an 12425 * array. Also flattens nested source object arrays into a 1 dimensional 12426 * array of source objects. 12427 * 12428 * @param {Tech~SourceObject|Tech~SourceObject[]} src 12429 * The src object to filter 12430 * 12431 * @return {Tech~SourceObject[]} 12432 * An array of sourceobjects containing only valid sources 12433 * 12434 * @private 12435 */ 12436 12437 var filterSource = function filterSource(src) { 12438 // traverse array 12439 if (Array.isArray(src)) { 12440 var newsrc = []; 12441 src.forEach(function (srcobj) { 12442 srcobj = filterSource(srcobj); 12443 12444 if (Array.isArray(srcobj)) { 12445 newsrc = newsrc.concat(srcobj); 12446 } else if (isObject(srcobj)) { 12447 newsrc.push(srcobj); 12448 } 12449 }); 12450 src = newsrc; 12451 } else if (typeof src === 'string' && src.trim()) { 12452 // convert string into object 12453 src = [fixSource({ 12454 src: src 12455 })]; 12456 } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { 12457 // src is already valid 12458 src = [fixSource(src)]; 12459 } else { 12460 // invalid source, turn it into an empty array 12461 src = []; 12462 } 12463 12464 return src; 12465 }; 12466 /** 12467 * Checks src mimetype, adding it when possible 12468 * 12469 * @param {Tech~SourceObject} src 12470 * The src object to check 12471 * @return {Tech~SourceObject} 12472 * src Object with known type 12473 */ 12474 12475 12476 function fixSource(src) { 12477 if (!src.type) { 12478 var mimetype = getMimetype(src.src); 12479 12480 if (mimetype) { 12481 src.type = mimetype; 12482 } 12483 } 12484 12485 return src; 12486 } 12487 12488 /** 12489 * The `MediaLoader` is the `Component` that decides which playback technology to load 12490 * when a player is initialized. 12491 * 12492 * @extends Component 12493 */ 12494 12495 var MediaLoader = /*#__PURE__*/function (_Component) { 12496 inheritsLoose(MediaLoader, _Component); 12497 12498 /** 12499 * Create an instance of this class. 12500 * 12501 * @param {Player} player 12502 * The `Player` that this class should attach to. 12503 * 12504 * @param {Object} [options] 12505 * The key/value store of player options. 12506 * 12507 * @param {Component~ReadyCallback} [ready] 12508 * The function that is run when this component is ready. 12509 */ 12510 function MediaLoader(player, options, ready) { 12511 var _this; 12512 12513 // MediaLoader has no element 12514 var options_ = mergeOptions({ 12515 createEl: false 12516 }, options); 12517 _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized, 12518 // load the first supported playback technology. 12519 12520 if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { 12521 for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { 12522 var techName = toTitleCase(j[i]); 12523 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 12524 // Remove once that deprecated behavior is removed. 12525 12526 if (!techName) { 12527 tech = Component.getComponent(techName); 12528 } // Check if the browser supports this technology 12529 12530 12531 if (tech && tech.isSupported()) { 12532 player.loadTech_(techName); 12533 break; 12534 } 12535 } 12536 } else { 12537 // Loop through playback technologies (HTML5, Flash) and check for support. 12538 // Then load the best source. 12539 // A few assumptions here: 12540 // All playback technologies respect preload false. 12541 player.src(options.playerOptions.sources); 12542 } 12543 12544 return _this; 12545 } 12546 12547 return MediaLoader; 12548 }(Component); 12549 12550 Component.registerComponent('MediaLoader', MediaLoader); 12551 12552 /** 12553 * Component which is clickable or keyboard actionable, but is not a 12554 * native HTML button. 12555 * 12556 * @extends Component 12557 */ 12558 12559 var ClickableComponent = /*#__PURE__*/function (_Component) { 12560 inheritsLoose(ClickableComponent, _Component); 12561 12562 /** 12563 * Creates an instance of this class. 12564 * 12565 * @param {Player} player 12566 * The `Player` that this class should be attached to. 12567 * 12568 * @param {Object} [options] 12569 * The key/value store of player options. 12570 * 12571 * @param {function} [options.clickHandler] 12572 * The function to call when the button is clicked / activated 12573 */ 12574 function ClickableComponent(player, options) { 12575 var _this; 12576 12577 _this = _Component.call(this, player, options) || this; 12578 12579 _this.emitTapEvents(); 12580 12581 _this.enable(); 12582 12583 return _this; 12584 } 12585 /** 12586 * Create the `ClickableComponent`s DOM element. 12587 * 12588 * @param {string} [tag=div] 12589 * The element's node type. 12590 * 12591 * @param {Object} [props={}] 12592 * An object of properties that should be set on the element. 12593 * 12594 * @param {Object} [attributes={}] 12595 * An object of attributes that should be set on the element. 12596 * 12597 * @return {Element} 12598 * The element that gets created. 12599 */ 12600 12601 12602 var _proto = ClickableComponent.prototype; 12603 12604 _proto.createEl = function createEl(tag, props, attributes) { 12605 if (tag === void 0) { 12606 tag = 'div'; 12607 } 12608 12609 if (props === void 0) { 12610 props = {}; 12611 } 12612 12613 if (attributes === void 0) { 12614 attributes = {}; 12615 } 12616 12617 props = assign({ 12618 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 12619 className: this.buildCSSClass(), 12620 tabIndex: 0 12621 }, props); 12622 12623 if (tag === 'button') { 12624 log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead."); 12625 } // Add ARIA attributes for clickable element which is not a native HTML button 12626 12627 12628 attributes = assign({ 12629 role: 'button' 12630 }, attributes); 12631 this.tabIndex_ = props.tabIndex; 12632 12633 var el = _Component.prototype.createEl.call(this, tag, props, attributes); 12634 12635 this.createControlTextEl(el); 12636 return el; 12637 }; 12638 12639 _proto.dispose = function dispose() { 12640 // remove controlTextEl_ on dispose 12641 this.controlTextEl_ = null; 12642 12643 _Component.prototype.dispose.call(this); 12644 } 12645 /** 12646 * Create a control text element on this `ClickableComponent` 12647 * 12648 * @param {Element} [el] 12649 * Parent element for the control text. 12650 * 12651 * @return {Element} 12652 * The control text element that gets created. 12653 */ 12654 ; 12655 12656 _proto.createControlTextEl = function createControlTextEl(el) { 12657 this.controlTextEl_ = createEl('span', { 12658 className: 'vjs-control-text' 12659 }, { 12660 // let the screen reader user know that the text of the element may change 12661 'aria-live': 'polite' 12662 }); 12663 12664 if (el) { 12665 el.appendChild(this.controlTextEl_); 12666 } 12667 12668 this.controlText(this.controlText_, el); 12669 return this.controlTextEl_; 12670 } 12671 /** 12672 * Get or set the localize text to use for the controls on the `ClickableComponent`. 12673 * 12674 * @param {string} [text] 12675 * Control text for element. 12676 * 12677 * @param {Element} [el=this.el()] 12678 * Element to set the title on. 12679 * 12680 * @return {string} 12681 * - The control text when getting 12682 */ 12683 ; 12684 12685 _proto.controlText = function controlText(text, el) { 12686 if (el === void 0) { 12687 el = this.el(); 12688 } 12689 12690 if (text === undefined) { 12691 return this.controlText_ || 'Need Text'; 12692 } 12693 12694 var localizedText = this.localize(text); 12695 this.controlText_ = text; 12696 textContent(this.controlTextEl_, localizedText); 12697 12698 if (!this.nonIconControl) { 12699 // Set title attribute if only an icon is shown 12700 el.setAttribute('title', localizedText); 12701 } 12702 } 12703 /** 12704 * Builds the default DOM `className`. 12705 * 12706 * @return {string} 12707 * The DOM `className` for this object. 12708 */ 12709 ; 12710 12711 _proto.buildCSSClass = function buildCSSClass() { 12712 return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this); 12713 } 12714 /** 12715 * Enable this `ClickableComponent` 12716 */ 12717 ; 12718 12719 _proto.enable = function enable() { 12720 if (!this.enabled_) { 12721 this.enabled_ = true; 12722 this.removeClass('vjs-disabled'); 12723 this.el_.setAttribute('aria-disabled', 'false'); 12724 12725 if (typeof this.tabIndex_ !== 'undefined') { 12726 this.el_.setAttribute('tabIndex', this.tabIndex_); 12727 } 12728 12729 this.on(['tap', 'click'], this.handleClick); 12730 this.on('keydown', this.handleKeyDown); 12731 } 12732 } 12733 /** 12734 * Disable this `ClickableComponent` 12735 */ 12736 ; 12737 12738 _proto.disable = function disable() { 12739 this.enabled_ = false; 12740 this.addClass('vjs-disabled'); 12741 this.el_.setAttribute('aria-disabled', 'true'); 12742 12743 if (typeof this.tabIndex_ !== 'undefined') { 12744 this.el_.removeAttribute('tabIndex'); 12745 } 12746 12747 this.off('mouseover', this.handleMouseOver); 12748 this.off('mouseout', this.handleMouseOut); 12749 this.off(['tap', 'click'], this.handleClick); 12750 this.off('keydown', this.handleKeyDown); 12751 } 12752 /** 12753 * Event handler that is called when a `ClickableComponent` receives a 12754 * `click` or `tap` event. 12755 * 12756 * @param {EventTarget~Event} event 12757 * The `tap` or `click` event that caused this function to be called. 12758 * 12759 * @listens tap 12760 * @listens click 12761 * @abstract 12762 */ 12763 ; 12764 12765 _proto.handleClick = function handleClick(event) { 12766 if (this.options_.clickHandler) { 12767 this.options_.clickHandler.call(this, arguments); 12768 } 12769 } 12770 /** 12771 * Event handler that is called when a `ClickableComponent` receives a 12772 * `keydown` event. 12773 * 12774 * By default, if the key is Space or Enter, it will trigger a `click` event. 12775 * 12776 * @param {EventTarget~Event} event 12777 * The `keydown` event that caused this function to be called. 12778 * 12779 * @listens keydown 12780 */ 12781 ; 12782 12783 _proto.handleKeyDown = function handleKeyDown(event) { 12784 // Support Space or Enter key operation to fire a click event. Also, 12785 // prevent the event from propagating through the DOM and triggering 12786 // Player hotkeys. 12787 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 12788 event.preventDefault(); 12789 event.stopPropagation(); 12790 this.trigger('click'); 12791 } else { 12792 // Pass keypress handling up for unsupported keys 12793 _Component.prototype.handleKeyDown.call(this, event); 12794 } 12795 }; 12796 12797 return ClickableComponent; 12798 }(Component); 12799 12800 Component.registerComponent('ClickableComponent', ClickableComponent); 12801 12802 /** 12803 * A `ClickableComponent` that handles showing the poster image for the player. 12804 * 12805 * @extends ClickableComponent 12806 */ 12807 12808 var PosterImage = /*#__PURE__*/function (_ClickableComponent) { 12809 inheritsLoose(PosterImage, _ClickableComponent); 12810 12811 /** 12812 * Create an instance of this class. 12813 * 12814 * @param {Player} player 12815 * The `Player` that this class should attach to. 12816 * 12817 * @param {Object} [options] 12818 * The key/value store of player options. 12819 */ 12820 function PosterImage(player, options) { 12821 var _this; 12822 12823 _this = _ClickableComponent.call(this, player, options) || this; 12824 12825 _this.update(); 12826 12827 player.on('posterchange', bind(assertThisInitialized(_this), _this.update)); 12828 return _this; 12829 } 12830 /** 12831 * Clean up and dispose of the `PosterImage`. 12832 */ 12833 12834 12835 var _proto = PosterImage.prototype; 12836 12837 _proto.dispose = function dispose() { 12838 this.player().off('posterchange', this.update); 12839 12840 _ClickableComponent.prototype.dispose.call(this); 12841 } 12842 /** 12843 * Create the `PosterImage`s DOM element. 12844 * 12845 * @return {Element} 12846 * The element that gets created. 12847 */ 12848 ; 12849 12850 _proto.createEl = function createEl$1() { 12851 var el = createEl('div', { 12852 className: 'vjs-poster', 12853 // Don't want poster to be tabbable. 12854 tabIndex: -1 12855 }); 12856 return el; 12857 } 12858 /** 12859 * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. 12860 * 12861 * @listens Player#posterchange 12862 * 12863 * @param {EventTarget~Event} [event] 12864 * The `Player#posterchange` event that triggered this function. 12865 */ 12866 ; 12867 12868 _proto.update = function update(event) { 12869 var url = this.player().poster(); 12870 this.setSrc(url); // If there's no poster source we should display:none on this component 12871 // so it's not still clickable or right-clickable 12872 12873 if (url) { 12874 this.show(); 12875 } else { 12876 this.hide(); 12877 } 12878 } 12879 /** 12880 * Set the source of the `PosterImage` depending on the display method. 12881 * 12882 * @param {string} url 12883 * The URL to the source for the `PosterImage`. 12884 */ 12885 ; 12886 12887 _proto.setSrc = function setSrc(url) { 12888 var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise 12889 // this will throw an extra error 12890 12891 if (url) { 12892 backgroundImage = "url(\"" + url + "\")"; 12893 } 12894 12895 this.el_.style.backgroundImage = backgroundImage; 12896 } 12897 /** 12898 * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See 12899 * {@link ClickableComponent#handleClick} for instances where this will be triggered. 12900 * 12901 * @listens tap 12902 * @listens click 12903 * @listens keydown 12904 * 12905 * @param {EventTarget~Event} event 12906 + The `click`, `tap` or `keydown` event that caused this function to be called. 12907 */ 12908 ; 12909 12910 _proto.handleClick = function handleClick(event) { 12911 // We don't want a click to trigger playback when controls are disabled 12912 if (!this.player_.controls()) { 12913 return; 12914 } 12915 12916 var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0; 12917 12918 if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where 12919 // calling .focus() on the video element causes the video to go black, 12920 // so we avoid it in that specific case 12921 !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) { 12922 this.player_.tech(true).focus(); 12923 } 12924 12925 if (this.player_.paused()) { 12926 silencePromise(this.player_.play()); 12927 } else { 12928 this.player_.pause(); 12929 } 12930 }; 12931 12932 return PosterImage; 12933 }(ClickableComponent); 12934 12935 Component.registerComponent('PosterImage', PosterImage); 12936 12937 var darkGray = '#222'; 12938 var lightGray = '#ccc'; 12939 var fontMap = { 12940 monospace: 'monospace', 12941 sansSerif: 'sans-serif', 12942 serif: 'serif', 12943 monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', 12944 monospaceSerif: '"Courier New", monospace', 12945 proportionalSansSerif: 'sans-serif', 12946 proportionalSerif: 'serif', 12947 casual: '"Comic Sans MS", Impact, fantasy', 12948 script: '"Monotype Corsiva", cursive', 12949 smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' 12950 }; 12951 /** 12952 * Construct an rgba color from a given hex color code. 12953 * 12954 * @param {number} color 12955 * Hex number for color, like #f0e or #f604e2. 12956 * 12957 * @param {number} opacity 12958 * Value for opacity, 0.0 - 1.0. 12959 * 12960 * @return {string} 12961 * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. 12962 */ 12963 12964 function constructColor(color, opacity) { 12965 var hex; 12966 12967 if (color.length === 4) { 12968 // color looks like "#f0e" 12969 hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; 12970 } else if (color.length === 7) { 12971 // color looks like "#f604e2" 12972 hex = color.slice(1); 12973 } else { 12974 throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.'); 12975 } 12976 12977 return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')'; 12978 } 12979 /** 12980 * Try to update the style of a DOM element. Some style changes will throw an error, 12981 * particularly in IE8. Those should be noops. 12982 * 12983 * @param {Element} el 12984 * The DOM element to be styled. 12985 * 12986 * @param {string} style 12987 * The CSS property on the element that should be styled. 12988 * 12989 * @param {string} rule 12990 * The style rule that should be applied to the property. 12991 * 12992 * @private 12993 */ 12994 12995 function tryUpdateStyle(el, style, rule) { 12996 try { 12997 el.style[style] = rule; 12998 } catch (e) { 12999 // Satisfies linter. 13000 return; 13001 } 13002 } 13003 /** 13004 * The component for displaying text track cues. 13005 * 13006 * @extends Component 13007 */ 13008 13009 13010 var TextTrackDisplay = /*#__PURE__*/function (_Component) { 13011 inheritsLoose(TextTrackDisplay, _Component); 13012 13013 /** 13014 * Creates an instance of this class. 13015 * 13016 * @param {Player} player 13017 * The `Player` that this class should be attached to. 13018 * 13019 * @param {Object} [options] 13020 * The key/value store of player options. 13021 * 13022 * @param {Component~ReadyCallback} [ready] 13023 * The function to call when `TextTrackDisplay` is ready. 13024 */ 13025 function TextTrackDisplay(player, options, ready) { 13026 var _this; 13027 13028 _this = _Component.call(this, player, options, ready) || this; 13029 var updateDisplayHandler = bind(assertThisInitialized(_this), _this.updateDisplay); 13030 player.on('loadstart', bind(assertThisInitialized(_this), _this.toggleDisplay)); 13031 player.on('texttrackchange', updateDisplayHandler); 13032 player.on('loadedmetadata', bind(assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error 13033 // if a track should show by default and the display hadn't loaded yet. 13034 // Should probably be moved to an external track loader when we support 13035 // tracks that don't need a display. 13036 13037 player.ready(bind(assertThisInitialized(_this), function () { 13038 if (player.tech_ && player.tech_.featuresNativeTextTracks) { 13039 this.hide(); 13040 return; 13041 } 13042 13043 player.on('fullscreenchange', updateDisplayHandler); 13044 player.on('playerresize', updateDisplayHandler); 13045 window$1.addEventListener('orientationchange', updateDisplayHandler); 13046 player.on('dispose', function () { 13047 return window$1.removeEventListener('orientationchange', updateDisplayHandler); 13048 }); 13049 var tracks = this.options_.playerOptions.tracks || []; 13050 13051 for (var i = 0; i < tracks.length; i++) { 13052 this.player_.addRemoteTextTrack(tracks[i], true); 13053 } 13054 13055 this.preselectTrack(); 13056 })); 13057 return _this; 13058 } 13059 /** 13060 * Preselect a track following this precedence: 13061 * - matches the previously selected {@link TextTrack}'s language and kind 13062 * - matches the previously selected {@link TextTrack}'s language only 13063 * - is the first default captions track 13064 * - is the first default descriptions track 13065 * 13066 * @listens Player#loadstart 13067 */ 13068 13069 13070 var _proto = TextTrackDisplay.prototype; 13071 13072 _proto.preselectTrack = function preselectTrack() { 13073 var modes = { 13074 captions: 1, 13075 subtitles: 1 13076 }; 13077 var trackList = this.player_.textTracks(); 13078 var userPref = this.player_.cache_.selectedLanguage; 13079 var firstDesc; 13080 var firstCaptions; 13081 var preferredTrack; 13082 13083 for (var i = 0; i < trackList.length; i++) { 13084 var track = trackList[i]; 13085 13086 if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) { 13087 // Always choose the track that matches both language and kind 13088 if (track.kind === userPref.kind) { 13089 preferredTrack = track; // or choose the first track that matches language 13090 } else if (!preferredTrack) { 13091 preferredTrack = track; 13092 } // clear everything if offTextTrackMenuItem was clicked 13093 13094 } else if (userPref && !userPref.enabled) { 13095 preferredTrack = null; 13096 firstDesc = null; 13097 firstCaptions = null; 13098 } else if (track["default"]) { 13099 if (track.kind === 'descriptions' && !firstDesc) { 13100 firstDesc = track; 13101 } else if (track.kind in modes && !firstCaptions) { 13102 firstCaptions = track; 13103 } 13104 } 13105 } // The preferredTrack matches the user preference and takes 13106 // precedence over all the other tracks. 13107 // So, display the preferredTrack before the first default track 13108 // and the subtitles/captions track before the descriptions track 13109 13110 13111 if (preferredTrack) { 13112 preferredTrack.mode = 'showing'; 13113 } else if (firstCaptions) { 13114 firstCaptions.mode = 'showing'; 13115 } else if (firstDesc) { 13116 firstDesc.mode = 'showing'; 13117 } 13118 } 13119 /** 13120 * Turn display of {@link TextTrack}'s from the current state into the other state. 13121 * There are only two states: 13122 * - 'shown' 13123 * - 'hidden' 13124 * 13125 * @listens Player#loadstart 13126 */ 13127 ; 13128 13129 _proto.toggleDisplay = function toggleDisplay() { 13130 if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { 13131 this.hide(); 13132 } else { 13133 this.show(); 13134 } 13135 } 13136 /** 13137 * Create the {@link Component}'s DOM element. 13138 * 13139 * @return {Element} 13140 * The element that was created. 13141 */ 13142 ; 13143 13144 _proto.createEl = function createEl() { 13145 return _Component.prototype.createEl.call(this, 'div', { 13146 className: 'vjs-text-track-display' 13147 }, { 13148 'aria-live': 'off', 13149 'aria-atomic': 'true' 13150 }); 13151 } 13152 /** 13153 * Clear all displayed {@link TextTrack}s. 13154 */ 13155 ; 13156 13157 _proto.clearDisplay = function clearDisplay() { 13158 if (typeof window$1.WebVTT === 'function') { 13159 window$1.WebVTT.processCues(window$1, [], this.el_); 13160 } 13161 } 13162 /** 13163 * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or 13164 * a {@link Player#fullscreenchange} is fired. 13165 * 13166 * @listens Player#texttrackchange 13167 * @listens Player#fullscreenchange 13168 */ 13169 ; 13170 13171 _proto.updateDisplay = function updateDisplay() { 13172 var tracks = this.player_.textTracks(); 13173 var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks; 13174 this.clearDisplay(); 13175 13176 if (allowMultipleShowingTracks) { 13177 var showingTracks = []; 13178 13179 for (var _i = 0; _i < tracks.length; ++_i) { 13180 var track = tracks[_i]; 13181 13182 if (track.mode !== 'showing') { 13183 continue; 13184 } 13185 13186 showingTracks.push(track); 13187 } 13188 13189 this.updateForTrack(showingTracks); 13190 return; 13191 } // Track display prioritization model: if multiple tracks are 'showing', 13192 // display the first 'subtitles' or 'captions' track which is 'showing', 13193 // otherwise display the first 'descriptions' track which is 'showing' 13194 13195 13196 var descriptionsTrack = null; 13197 var captionsSubtitlesTrack = null; 13198 var i = tracks.length; 13199 13200 while (i--) { 13201 var _track = tracks[i]; 13202 13203 if (_track.mode === 'showing') { 13204 if (_track.kind === 'descriptions') { 13205 descriptionsTrack = _track; 13206 } else { 13207 captionsSubtitlesTrack = _track; 13208 } 13209 } 13210 } 13211 13212 if (captionsSubtitlesTrack) { 13213 if (this.getAttribute('aria-live') !== 'off') { 13214 this.setAttribute('aria-live', 'off'); 13215 } 13216 13217 this.updateForTrack(captionsSubtitlesTrack); 13218 } else if (descriptionsTrack) { 13219 if (this.getAttribute('aria-live') !== 'assertive') { 13220 this.setAttribute('aria-live', 'assertive'); 13221 } 13222 13223 this.updateForTrack(descriptionsTrack); 13224 } 13225 } 13226 /** 13227 * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}. 13228 * 13229 * @param {TextTrack} track 13230 * Text track object containing active cues to style. 13231 */ 13232 ; 13233 13234 _proto.updateDisplayState = function updateDisplayState(track) { 13235 var overrides = this.player_.textTrackSettings.getValues(); 13236 var cues = track.activeCues; 13237 var i = cues.length; 13238 13239 while (i--) { 13240 var cue = cues[i]; 13241 13242 if (!cue) { 13243 continue; 13244 } 13245 13246 var cueDiv = cue.displayState; 13247 13248 if (overrides.color) { 13249 cueDiv.firstChild.style.color = overrides.color; 13250 } 13251 13252 if (overrides.textOpacity) { 13253 tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); 13254 } 13255 13256 if (overrides.backgroundColor) { 13257 cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; 13258 } 13259 13260 if (overrides.backgroundOpacity) { 13261 tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); 13262 } 13263 13264 if (overrides.windowColor) { 13265 if (overrides.windowOpacity) { 13266 tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); 13267 } else { 13268 cueDiv.style.backgroundColor = overrides.windowColor; 13269 } 13270 } 13271 13272 if (overrides.edgeStyle) { 13273 if (overrides.edgeStyle === 'dropshadow') { 13274 cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray; 13275 } else if (overrides.edgeStyle === 'raised') { 13276 cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray; 13277 } else if (overrides.edgeStyle === 'depressed') { 13278 cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray; 13279 } else if (overrides.edgeStyle === 'uniform') { 13280 cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray; 13281 } 13282 } 13283 13284 if (overrides.fontPercent && overrides.fontPercent !== 1) { 13285 var fontSize = window$1.parseFloat(cueDiv.style.fontSize); 13286 cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; 13287 cueDiv.style.height = 'auto'; 13288 cueDiv.style.top = 'auto'; 13289 } 13290 13291 if (overrides.fontFamily && overrides.fontFamily !== 'default') { 13292 if (overrides.fontFamily === 'small-caps') { 13293 cueDiv.firstChild.style.fontVariant = 'small-caps'; 13294 } else { 13295 cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; 13296 } 13297 } 13298 } 13299 } 13300 /** 13301 * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}. 13302 * 13303 * @param {TextTrack|TextTrack[]} tracks 13304 * Text track object or text track array to be added to the list. 13305 */ 13306 ; 13307 13308 _proto.updateForTrack = function updateForTrack(tracks) { 13309 if (!Array.isArray(tracks)) { 13310 tracks = [tracks]; 13311 } 13312 13313 if (typeof window$1.WebVTT !== 'function' || tracks.every(function (track) { 13314 return !track.activeCues; 13315 })) { 13316 return; 13317 } 13318 13319 var cues = []; // push all active track cues 13320 13321 for (var i = 0; i < tracks.length; ++i) { 13322 var track = tracks[i]; 13323 13324 for (var j = 0; j < track.activeCues.length; ++j) { 13325 cues.push(track.activeCues[j]); 13326 } 13327 } // removes all cues before it processes new ones 13328 13329 13330 window$1.WebVTT.processCues(window$1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary 13331 13332 for (var _i2 = 0; _i2 < tracks.length; ++_i2) { 13333 var _track2 = tracks[_i2]; 13334 13335 for (var _j = 0; _j < _track2.activeCues.length; ++_j) { 13336 var cueEl = _track2.activeCues[_j].displayState; 13337 addClass(cueEl, 'vjs-text-track-cue'); 13338 addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2)); 13339 } 13340 13341 if (this.player_.textTrackSettings) { 13342 this.updateDisplayState(_track2); 13343 } 13344 } 13345 }; 13346 13347 return TextTrackDisplay; 13348 }(Component); 13349 13350 Component.registerComponent('TextTrackDisplay', TextTrackDisplay); 13351 13352 /** 13353 * A loading spinner for use during waiting/loading events. 13354 * 13355 * @extends Component 13356 */ 13357 13358 var LoadingSpinner = /*#__PURE__*/function (_Component) { 13359 inheritsLoose(LoadingSpinner, _Component); 13360 13361 function LoadingSpinner() { 13362 return _Component.apply(this, arguments) || this; 13363 } 13364 13365 var _proto = LoadingSpinner.prototype; 13366 13367 /** 13368 * Create the `LoadingSpinner`s DOM element. 13369 * 13370 * @return {Element} 13371 * The dom element that gets created. 13372 */ 13373 _proto.createEl = function createEl$1() { 13374 var isAudio = this.player_.isAudio(); 13375 var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player'); 13376 var controlText = createEl('span', { 13377 className: 'vjs-control-text', 13378 innerHTML: this.localize('{1} is loading.', [playerType]) 13379 }); 13380 13381 var el = _Component.prototype.createEl.call(this, 'div', { 13382 className: 'vjs-loading-spinner', 13383 dir: 'ltr' 13384 }); 13385 13386 el.appendChild(controlText); 13387 return el; 13388 }; 13389 13390 return LoadingSpinner; 13391 }(Component); 13392 13393 Component.registerComponent('LoadingSpinner', LoadingSpinner); 13394 13395 /** 13396 * Base class for all buttons. 13397 * 13398 * @extends ClickableComponent 13399 */ 13400 13401 var Button = /*#__PURE__*/function (_ClickableComponent) { 13402 inheritsLoose(Button, _ClickableComponent); 13403 13404 function Button() { 13405 return _ClickableComponent.apply(this, arguments) || this; 13406 } 13407 13408 var _proto = Button.prototype; 13409 13410 /** 13411 * Create the `Button`s DOM element. 13412 * 13413 * @param {string} [tag="button"] 13414 * The element's node type. This argument is IGNORED: no matter what 13415 * is passed, it will always create a `button` element. 13416 * 13417 * @param {Object} [props={}] 13418 * An object of properties that should be set on the element. 13419 * 13420 * @param {Object} [attributes={}] 13421 * An object of attributes that should be set on the element. 13422 * 13423 * @return {Element} 13424 * The element that gets created. 13425 */ 13426 _proto.createEl = function createEl(tag, props, attributes) { 13427 if (props === void 0) { 13428 props = {}; 13429 } 13430 13431 if (attributes === void 0) { 13432 attributes = {}; 13433 } 13434 13435 tag = 'button'; 13436 props = assign({ 13437 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 13438 className: this.buildCSSClass() 13439 }, props); // Add attributes for button element 13440 13441 attributes = assign({ 13442 // Necessary since the default button type is "submit" 13443 type: 'button' 13444 }, attributes); 13445 var el = Component.prototype.createEl.call(this, tag, props, attributes); 13446 this.createControlTextEl(el); 13447 return el; 13448 } 13449 /** 13450 * Add a child `Component` inside of this `Button`. 13451 * 13452 * @param {string|Component} child 13453 * The name or instance of a child to add. 13454 * 13455 * @param {Object} [options={}] 13456 * The key/value store of options that will get passed to children of 13457 * the child. 13458 * 13459 * @return {Component} 13460 * The `Component` that gets added as a child. When using a string the 13461 * `Component` will get created by this process. 13462 * 13463 * @deprecated since version 5 13464 */ 13465 ; 13466 13467 _proto.addChild = function addChild(child, options) { 13468 if (options === void 0) { 13469 options = {}; 13470 } 13471 13472 var className = this.constructor.name; 13473 log.warn("Adding an actionable (user controllable) child to a Button (" + className + ") is not supported; use a ClickableComponent instead."); // Avoid the error message generated by ClickableComponent's addChild method 13474 13475 return Component.prototype.addChild.call(this, child, options); 13476 } 13477 /** 13478 * Enable the `Button` element so that it can be activated or clicked. Use this with 13479 * {@link Button#disable}. 13480 */ 13481 ; 13482 13483 _proto.enable = function enable() { 13484 _ClickableComponent.prototype.enable.call(this); 13485 13486 this.el_.removeAttribute('disabled'); 13487 } 13488 /** 13489 * Disable the `Button` element so that it cannot be activated or clicked. Use this with 13490 * {@link Button#enable}. 13491 */ 13492 ; 13493 13494 _proto.disable = function disable() { 13495 _ClickableComponent.prototype.disable.call(this); 13496 13497 this.el_.setAttribute('disabled', 'disabled'); 13498 } 13499 /** 13500 * This gets called when a `Button` has focus and `keydown` is triggered via a key 13501 * press. 13502 * 13503 * @param {EventTarget~Event} event 13504 * The event that caused this function to get called. 13505 * 13506 * @listens keydown 13507 */ 13508 ; 13509 13510 _proto.handleKeyDown = function handleKeyDown(event) { 13511 // Ignore Space or Enter key operation, which is handled by the browser for 13512 // a button - though not for its super class, ClickableComponent. Also, 13513 // prevent the event from propagating through the DOM and triggering Player 13514 // hotkeys. We do not preventDefault here because we _want_ the browser to 13515 // handle it. 13516 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 13517 event.stopPropagation(); 13518 return; 13519 } // Pass keypress handling up for unsupported keys 13520 13521 13522 _ClickableComponent.prototype.handleKeyDown.call(this, event); 13523 }; 13524 13525 return Button; 13526 }(ClickableComponent); 13527 13528 Component.registerComponent('Button', Button); 13529 13530 /** 13531 * The initial play button that shows before the video has played. The hiding of the 13532 * `BigPlayButton` get done via CSS and `Player` states. 13533 * 13534 * @extends Button 13535 */ 13536 13537 var BigPlayButton = /*#__PURE__*/function (_Button) { 13538 inheritsLoose(BigPlayButton, _Button); 13539 13540 function BigPlayButton(player, options) { 13541 var _this; 13542 13543 _this = _Button.call(this, player, options) || this; 13544 _this.mouseused_ = false; 13545 13546 _this.on('mousedown', _this.handleMouseDown); 13547 13548 return _this; 13549 } 13550 /** 13551 * Builds the default DOM `className`. 13552 * 13553 * @return {string} 13554 * The DOM `className` for this object. Always returns 'vjs-big-play-button'. 13555 */ 13556 13557 13558 var _proto = BigPlayButton.prototype; 13559 13560 _proto.buildCSSClass = function buildCSSClass() { 13561 return 'vjs-big-play-button'; 13562 } 13563 /** 13564 * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} 13565 * for more detailed information on what a click can be. 13566 * 13567 * @param {EventTarget~Event} event 13568 * The `keydown`, `tap`, or `click` event that caused this function to be 13569 * called. 13570 * 13571 * @listens tap 13572 * @listens click 13573 */ 13574 ; 13575 13576 _proto.handleClick = function handleClick(event) { 13577 var playPromise = this.player_.play(); // exit early if clicked via the mouse 13578 13579 if (this.mouseused_ && event.clientX && event.clientY) { 13580 var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0; 13581 silencePromise(playPromise); 13582 13583 if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where 13584 // calling .focus() on the video element causes the video to go black, 13585 // so we avoid it in that specific case 13586 !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) { 13587 this.player_.tech(true).focus(); 13588 } 13589 13590 return; 13591 } 13592 13593 var cb = this.player_.getChild('controlBar'); 13594 var playToggle = cb && cb.getChild('playToggle'); 13595 13596 if (!playToggle) { 13597 this.player_.tech(true).focus(); 13598 return; 13599 } 13600 13601 var playFocus = function playFocus() { 13602 return playToggle.focus(); 13603 }; 13604 13605 if (isPromise(playPromise)) { 13606 playPromise.then(playFocus, function () {}); 13607 } else { 13608 this.setTimeout(playFocus, 1); 13609 } 13610 }; 13611 13612 _proto.handleKeyDown = function handleKeyDown(event) { 13613 this.mouseused_ = false; 13614 13615 _Button.prototype.handleKeyDown.call(this, event); 13616 }; 13617 13618 _proto.handleMouseDown = function handleMouseDown(event) { 13619 this.mouseused_ = true; 13620 }; 13621 13622 return BigPlayButton; 13623 }(Button); 13624 /** 13625 * The text that should display over the `BigPlayButton`s controls. Added to for localization. 13626 * 13627 * @type {string} 13628 * @private 13629 */ 13630 13631 13632 BigPlayButton.prototype.controlText_ = 'Play Video'; 13633 Component.registerComponent('BigPlayButton', BigPlayButton); 13634 13635 /** 13636 * The `CloseButton` is a `{@link Button}` that fires a `close` event when 13637 * it gets clicked. 13638 * 13639 * @extends Button 13640 */ 13641 13642 var CloseButton = /*#__PURE__*/function (_Button) { 13643 inheritsLoose(CloseButton, _Button); 13644 13645 /** 13646 * Creates an instance of the this class. 13647 * 13648 * @param {Player} player 13649 * The `Player` that this class should be attached to. 13650 * 13651 * @param {Object} [options] 13652 * The key/value store of player options. 13653 */ 13654 function CloseButton(player, options) { 13655 var _this; 13656 13657 _this = _Button.call(this, player, options) || this; 13658 13659 _this.controlText(options && options.controlText || _this.localize('Close')); 13660 13661 return _this; 13662 } 13663 /** 13664 * Builds the default DOM `className`. 13665 * 13666 * @return {string} 13667 * The DOM `className` for this object. 13668 */ 13669 13670 13671 var _proto = CloseButton.prototype; 13672 13673 _proto.buildCSSClass = function buildCSSClass() { 13674 return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this); 13675 } 13676 /** 13677 * This gets called when a `CloseButton` gets clicked. See 13678 * {@link ClickableComponent#handleClick} for more information on when 13679 * this will be triggered 13680 * 13681 * @param {EventTarget~Event} event 13682 * The `keydown`, `tap`, or `click` event that caused this function to be 13683 * called. 13684 * 13685 * @listens tap 13686 * @listens click 13687 * @fires CloseButton#close 13688 */ 13689 ; 13690 13691 _proto.handleClick = function handleClick(event) { 13692 /** 13693 * Triggered when the a `CloseButton` is clicked. 13694 * 13695 * @event CloseButton#close 13696 * @type {EventTarget~Event} 13697 * 13698 * @property {boolean} [bubbles=false] 13699 * set to false so that the close event does not 13700 * bubble up to parents if there is no listener 13701 */ 13702 this.trigger({ 13703 type: 'close', 13704 bubbles: false 13705 }); 13706 } 13707 /** 13708 * Event handler that is called when a `CloseButton` receives a 13709 * `keydown` event. 13710 * 13711 * By default, if the key is Esc, it will trigger a `click` event. 13712 * 13713 * @param {EventTarget~Event} event 13714 * The `keydown` event that caused this function to be called. 13715 * 13716 * @listens keydown 13717 */ 13718 ; 13719 13720 _proto.handleKeyDown = function handleKeyDown(event) { 13721 // Esc button will trigger `click` event 13722 if (keycode.isEventKey(event, 'Esc')) { 13723 event.preventDefault(); 13724 event.stopPropagation(); 13725 this.trigger('click'); 13726 } else { 13727 // Pass keypress handling up for unsupported keys 13728 _Button.prototype.handleKeyDown.call(this, event); 13729 } 13730 }; 13731 13732 return CloseButton; 13733 }(Button); 13734 13735 Component.registerComponent('CloseButton', CloseButton); 13736 13737 /** 13738 * Button to toggle between play and pause. 13739 * 13740 * @extends Button 13741 */ 13742 13743 var PlayToggle = /*#__PURE__*/function (_Button) { 13744 inheritsLoose(PlayToggle, _Button); 13745 13746 /** 13747 * Creates an instance of this class. 13748 * 13749 * @param {Player} player 13750 * The `Player` that this class should be attached to. 13751 * 13752 * @param {Object} [options={}] 13753 * The key/value store of player options. 13754 */ 13755 function PlayToggle(player, options) { 13756 var _this; 13757 13758 if (options === void 0) { 13759 options = {}; 13760 } 13761 13762 _this = _Button.call(this, player, options) || this; // show or hide replay icon 13763 13764 options.replay = options.replay === undefined || options.replay; 13765 13766 _this.on(player, 'play', _this.handlePlay); 13767 13768 _this.on(player, 'pause', _this.handlePause); 13769 13770 if (options.replay) { 13771 _this.on(player, 'ended', _this.handleEnded); 13772 } 13773 13774 return _this; 13775 } 13776 /** 13777 * Builds the default DOM `className`. 13778 * 13779 * @return {string} 13780 * The DOM `className` for this object. 13781 */ 13782 13783 13784 var _proto = PlayToggle.prototype; 13785 13786 _proto.buildCSSClass = function buildCSSClass() { 13787 return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this); 13788 } 13789 /** 13790 * This gets called when an `PlayToggle` is "clicked". See 13791 * {@link ClickableComponent} for more detailed information on what a click can be. 13792 * 13793 * @param {EventTarget~Event} [event] 13794 * The `keydown`, `tap`, or `click` event that caused this function to be 13795 * called. 13796 * 13797 * @listens tap 13798 * @listens click 13799 */ 13800 ; 13801 13802 _proto.handleClick = function handleClick(event) { 13803 if (this.player_.paused()) { 13804 this.player_.play(); 13805 } else { 13806 this.player_.pause(); 13807 } 13808 } 13809 /** 13810 * This gets called once after the video has ended and the user seeks so that 13811 * we can change the replay button back to a play button. 13812 * 13813 * @param {EventTarget~Event} [event] 13814 * The event that caused this function to run. 13815 * 13816 * @listens Player#seeked 13817 */ 13818 ; 13819 13820 _proto.handleSeeked = function handleSeeked(event) { 13821 this.removeClass('vjs-ended'); 13822 13823 if (this.player_.paused()) { 13824 this.handlePause(event); 13825 } else { 13826 this.handlePlay(event); 13827 } 13828 } 13829 /** 13830 * Add the vjs-playing class to the element so it can change appearance. 13831 * 13832 * @param {EventTarget~Event} [event] 13833 * The event that caused this function to run. 13834 * 13835 * @listens Player#play 13836 */ 13837 ; 13838 13839 _proto.handlePlay = function handlePlay(event) { 13840 this.removeClass('vjs-ended'); 13841 this.removeClass('vjs-paused'); 13842 this.addClass('vjs-playing'); // change the button text to "Pause" 13843 13844 this.controlText('Pause'); 13845 } 13846 /** 13847 * Add the vjs-paused class to the element so it can change appearance. 13848 * 13849 * @param {EventTarget~Event} [event] 13850 * The event that caused this function to run. 13851 * 13852 * @listens Player#pause 13853 */ 13854 ; 13855 13856 _proto.handlePause = function handlePause(event) { 13857 this.removeClass('vjs-playing'); 13858 this.addClass('vjs-paused'); // change the button text to "Play" 13859 13860 this.controlText('Play'); 13861 } 13862 /** 13863 * Add the vjs-ended class to the element so it can change appearance 13864 * 13865 * @param {EventTarget~Event} [event] 13866 * The event that caused this function to run. 13867 * 13868 * @listens Player#ended 13869 */ 13870 ; 13871 13872 _proto.handleEnded = function handleEnded(event) { 13873 this.removeClass('vjs-playing'); 13874 this.addClass('vjs-ended'); // change the button text to "Replay" 13875 13876 this.controlText('Replay'); // on the next seek remove the replay button 13877 13878 this.one(this.player_, 'seeked', this.handleSeeked); 13879 }; 13880 13881 return PlayToggle; 13882 }(Button); 13883 /** 13884 * The text that should display over the `PlayToggle`s controls. Added for localization. 13885 * 13886 * @type {string} 13887 * @private 13888 */ 13889 13890 13891 PlayToggle.prototype.controlText_ = 'Play'; 13892 Component.registerComponent('PlayToggle', PlayToggle); 13893 13894 /** 13895 * @file format-time.js 13896 * @module format-time 13897 */ 13898 13899 /** 13900 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in 13901 * seconds) will force a number of leading zeros to cover the length of the 13902 * guide. 13903 * 13904 * @private 13905 * @param {number} seconds 13906 * Number of seconds to be turned into a string 13907 * 13908 * @param {number} guide 13909 * Number (in seconds) to model the string after 13910 * 13911 * @return {string} 13912 * Time formatted as H:MM:SS or M:SS 13913 */ 13914 var defaultImplementation = function defaultImplementation(seconds, guide) { 13915 seconds = seconds < 0 ? 0 : seconds; 13916 var s = Math.floor(seconds % 60); 13917 var m = Math.floor(seconds / 60 % 60); 13918 var h = Math.floor(seconds / 3600); 13919 var gm = Math.floor(guide / 60 % 60); 13920 var gh = Math.floor(guide / 3600); // handle invalid times 13921 13922 if (isNaN(seconds) || seconds === Infinity) { 13923 // '-' is false for all relational operators (e.g. <, >=) so this setting 13924 // will add the minimum number of fields specified by the guide 13925 h = m = s = '-'; 13926 } // Check if we need to show hours 13927 13928 13929 h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero. 13930 // Always show at least one digit of minutes. 13931 13932 m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds 13933 13934 s = s < 10 ? '0' + s : s; 13935 return h + m + s; 13936 }; // Internal pointer to the current implementation. 13937 13938 13939 var implementation = defaultImplementation; 13940 /** 13941 * Replaces the default formatTime implementation with a custom implementation. 13942 * 13943 * @param {Function} customImplementation 13944 * A function which will be used in place of the default formatTime 13945 * implementation. Will receive the current time in seconds and the 13946 * guide (in seconds) as arguments. 13947 */ 13948 13949 function setFormatTime(customImplementation) { 13950 implementation = customImplementation; 13951 } 13952 /** 13953 * Resets formatTime to the default implementation. 13954 */ 13955 13956 function resetFormatTime() { 13957 implementation = defaultImplementation; 13958 } 13959 /** 13960 * Delegates to either the default time formatting function or a custom 13961 * function supplied via `setFormatTime`. 13962 * 13963 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a 13964 * guide (in seconds) will force a number of leading zeros to cover the 13965 * length of the guide. 13966 * 13967 * @static 13968 * @example formatTime(125, 600) === "02:05" 13969 * @param {number} seconds 13970 * Number of seconds to be turned into a string 13971 * 13972 * @param {number} guide 13973 * Number (in seconds) to model the string after 13974 * 13975 * @return {string} 13976 * Time formatted as H:MM:SS or M:SS 13977 */ 13978 13979 function formatTime(seconds, guide) { 13980 if (guide === void 0) { 13981 guide = seconds; 13982 } 13983 13984 return implementation(seconds, guide); 13985 } 13986 13987 /** 13988 * Displays time information about the video 13989 * 13990 * @extends Component 13991 */ 13992 13993 var TimeDisplay = /*#__PURE__*/function (_Component) { 13994 inheritsLoose(TimeDisplay, _Component); 13995 13996 /** 13997 * Creates an instance of this class. 13998 * 13999 * @param {Player} player 14000 * The `Player` that this class should be attached to. 14001 * 14002 * @param {Object} [options] 14003 * The key/value store of player options. 14004 */ 14005 function TimeDisplay(player, options) { 14006 var _this; 14007 14008 _this = _Component.call(this, player, options) || this; 14009 14010 _this.on(player, ['timeupdate', 'ended'], _this.updateContent); 14011 14012 _this.updateTextNode_(); 14013 14014 return _this; 14015 } 14016 /** 14017 * Create the `Component`'s DOM element 14018 * 14019 * @return {Element} 14020 * The element that was created. 14021 */ 14022 14023 14024 var _proto = TimeDisplay.prototype; 14025 14026 _proto.createEl = function createEl$1() { 14027 var className = this.buildCSSClass(); 14028 14029 var el = _Component.prototype.createEl.call(this, 'div', { 14030 className: className + " vjs-time-control vjs-control", 14031 innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>" 14032 }); 14033 14034 this.contentEl_ = createEl('span', { 14035 className: className + "-display" 14036 }, { 14037 // tell screen readers not to automatically read the time as it changes 14038 'aria-live': 'off', 14039 // span elements have no implicit role, but some screen readers (notably VoiceOver) 14040 // treat them as a break between items in the DOM when using arrow keys 14041 // (or left-to-right swipes on iOS) to read contents of a page. Using 14042 // role='presentation' causes VoiceOver to NOT treat this span as a break. 14043 'role': 'presentation' 14044 }); 14045 el.appendChild(this.contentEl_); 14046 return el; 14047 }; 14048 14049 _proto.dispose = function dispose() { 14050 this.contentEl_ = null; 14051 this.textNode_ = null; 14052 14053 _Component.prototype.dispose.call(this); 14054 } 14055 /** 14056 * Updates the time display text node with a new time 14057 * 14058 * @param {number} [time=0] the time to update to 14059 * 14060 * @private 14061 */ 14062 ; 14063 14064 _proto.updateTextNode_ = function updateTextNode_(time) { 14065 var _this2 = this; 14066 14067 if (time === void 0) { 14068 time = 0; 14069 } 14070 14071 time = formatTime(time); 14072 14073 if (this.formattedTime_ === time) { 14074 return; 14075 } 14076 14077 this.formattedTime_ = time; 14078 this.requestNamedAnimationFrame('TimeDisplay#updateTextNode_', function () { 14079 if (!_this2.contentEl_) { 14080 return; 14081 } 14082 14083 var oldNode = _this2.textNode_; 14084 _this2.textNode_ = document.createTextNode(_this2.formattedTime_); 14085 14086 if (!_this2.textNode_) { 14087 return; 14088 } 14089 14090 if (oldNode) { 14091 _this2.contentEl_.replaceChild(_this2.textNode_, oldNode); 14092 } else { 14093 _this2.contentEl_.appendChild(_this2.textNode_); 14094 } 14095 }); 14096 } 14097 /** 14098 * To be filled out in the child class, should update the displayed time 14099 * in accordance with the fact that the current time has changed. 14100 * 14101 * @param {EventTarget~Event} [event] 14102 * The `timeupdate` event that caused this to run. 14103 * 14104 * @listens Player#timeupdate 14105 */ 14106 ; 14107 14108 _proto.updateContent = function updateContent(event) {}; 14109 14110 return TimeDisplay; 14111 }(Component); 14112 /** 14113 * The text that is added to the `TimeDisplay` for screen reader users. 14114 * 14115 * @type {string} 14116 * @private 14117 */ 14118 14119 14120 TimeDisplay.prototype.labelText_ = 'Time'; 14121 /** 14122 * The text that should display over the `TimeDisplay`s controls. Added to for localization. 14123 * 14124 * @type {string} 14125 * @private 14126 * 14127 * @deprecated in v7; controlText_ is not used in non-active display Components 14128 */ 14129 14130 TimeDisplay.prototype.controlText_ = 'Time'; 14131 Component.registerComponent('TimeDisplay', TimeDisplay); 14132 14133 /** 14134 * Displays the current time 14135 * 14136 * @extends Component 14137 */ 14138 14139 var CurrentTimeDisplay = /*#__PURE__*/function (_TimeDisplay) { 14140 inheritsLoose(CurrentTimeDisplay, _TimeDisplay); 14141 14142 function CurrentTimeDisplay() { 14143 return _TimeDisplay.apply(this, arguments) || this; 14144 } 14145 14146 var _proto = CurrentTimeDisplay.prototype; 14147 14148 /** 14149 * Builds the default DOM `className`. 14150 * 14151 * @return {string} 14152 * The DOM `className` for this object. 14153 */ 14154 _proto.buildCSSClass = function buildCSSClass() { 14155 return 'vjs-current-time'; 14156 } 14157 /** 14158 * Update current time display 14159 * 14160 * @param {EventTarget~Event} [event] 14161 * The `timeupdate` event that caused this function to run. 14162 * 14163 * @listens Player#timeupdate 14164 */ 14165 ; 14166 14167 _proto.updateContent = function updateContent(event) { 14168 // Allows for smooth scrubbing, when player can't keep up. 14169 var time; 14170 14171 if (this.player_.ended()) { 14172 time = this.player_.duration(); 14173 } else { 14174 time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 14175 } 14176 14177 this.updateTextNode_(time); 14178 }; 14179 14180 return CurrentTimeDisplay; 14181 }(TimeDisplay); 14182 /** 14183 * The text that is added to the `CurrentTimeDisplay` for screen reader users. 14184 * 14185 * @type {string} 14186 * @private 14187 */ 14188 14189 14190 CurrentTimeDisplay.prototype.labelText_ = 'Current Time'; 14191 /** 14192 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization. 14193 * 14194 * @type {string} 14195 * @private 14196 * 14197 * @deprecated in v7; controlText_ is not used in non-active display Components 14198 */ 14199 14200 CurrentTimeDisplay.prototype.controlText_ = 'Current Time'; 14201 Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); 14202 14203 /** 14204 * Displays the duration 14205 * 14206 * @extends Component 14207 */ 14208 14209 var DurationDisplay = /*#__PURE__*/function (_TimeDisplay) { 14210 inheritsLoose(DurationDisplay, _TimeDisplay); 14211 14212 /** 14213 * Creates an instance of this class. 14214 * 14215 * @param {Player} player 14216 * The `Player` that this class should be attached to. 14217 * 14218 * @param {Object} [options] 14219 * The key/value store of player options. 14220 */ 14221 function DurationDisplay(player, options) { 14222 var _this; 14223 14224 _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes, 14225 // as they should always display the changed duration as 14226 // it has changed 14227 14228 _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded, 14229 // but the durationchange on the user agent will not fire. 14230 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 14231 14232 14233 _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those 14234 // listeners could have broken dependent applications/libraries. These 14235 // can likely be removed for 7.0. 14236 14237 14238 _this.on(player, 'loadedmetadata', _this.updateContent); 14239 14240 return _this; 14241 } 14242 /** 14243 * Builds the default DOM `className`. 14244 * 14245 * @return {string} 14246 * The DOM `className` for this object. 14247 */ 14248 14249 14250 var _proto = DurationDisplay.prototype; 14251 14252 _proto.buildCSSClass = function buildCSSClass() { 14253 return 'vjs-duration'; 14254 } 14255 /** 14256 * Update duration time display. 14257 * 14258 * @param {EventTarget~Event} [event] 14259 * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused 14260 * this function to be called. 14261 * 14262 * @listens Player#durationchange 14263 * @listens Player#timeupdate 14264 * @listens Player#loadedmetadata 14265 */ 14266 ; 14267 14268 _proto.updateContent = function updateContent(event) { 14269 var duration = this.player_.duration(); 14270 this.updateTextNode_(duration); 14271 }; 14272 14273 return DurationDisplay; 14274 }(TimeDisplay); 14275 /** 14276 * The text that is added to the `DurationDisplay` for screen reader users. 14277 * 14278 * @type {string} 14279 * @private 14280 */ 14281 14282 14283 DurationDisplay.prototype.labelText_ = 'Duration'; 14284 /** 14285 * The text that should display over the `DurationDisplay`s controls. Added to for localization. 14286 * 14287 * @type {string} 14288 * @private 14289 * 14290 * @deprecated in v7; controlText_ is not used in non-active display Components 14291 */ 14292 14293 DurationDisplay.prototype.controlText_ = 'Duration'; 14294 Component.registerComponent('DurationDisplay', DurationDisplay); 14295 14296 /** 14297 * The separator between the current time and duration. 14298 * Can be hidden if it's not needed in the design. 14299 * 14300 * @extends Component 14301 */ 14302 14303 var TimeDivider = /*#__PURE__*/function (_Component) { 14304 inheritsLoose(TimeDivider, _Component); 14305 14306 function TimeDivider() { 14307 return _Component.apply(this, arguments) || this; 14308 } 14309 14310 var _proto = TimeDivider.prototype; 14311 14312 /** 14313 * Create the component's DOM element 14314 * 14315 * @return {Element} 14316 * The element that was created. 14317 */ 14318 _proto.createEl = function createEl() { 14319 return _Component.prototype.createEl.call(this, 'div', { 14320 className: 'vjs-time-control vjs-time-divider', 14321 innerHTML: '<div><span>/</span></div>' 14322 }, { 14323 // this element and its contents can be hidden from assistive techs since 14324 // it is made extraneous by the announcement of the control text 14325 // for the current time and duration displays 14326 'aria-hidden': true 14327 }); 14328 }; 14329 14330 return TimeDivider; 14331 }(Component); 14332 14333 Component.registerComponent('TimeDivider', TimeDivider); 14334 14335 /** 14336 * Displays the time left in the video 14337 * 14338 * @extends Component 14339 */ 14340 14341 var RemainingTimeDisplay = /*#__PURE__*/function (_TimeDisplay) { 14342 inheritsLoose(RemainingTimeDisplay, _TimeDisplay); 14343 14344 /** 14345 * Creates an instance of this class. 14346 * 14347 * @param {Player} player 14348 * The `Player` that this class should be attached to. 14349 * 14350 * @param {Object} [options] 14351 * The key/value store of player options. 14352 */ 14353 function RemainingTimeDisplay(player, options) { 14354 var _this; 14355 14356 _this = _TimeDisplay.call(this, player, options) || this; 14357 14358 _this.on(player, 'durationchange', _this.updateContent); 14359 14360 return _this; 14361 } 14362 /** 14363 * Builds the default DOM `className`. 14364 * 14365 * @return {string} 14366 * The DOM `className` for this object. 14367 */ 14368 14369 14370 var _proto = RemainingTimeDisplay.prototype; 14371 14372 _proto.buildCSSClass = function buildCSSClass() { 14373 return 'vjs-remaining-time'; 14374 } 14375 /** 14376 * Create the `Component`'s DOM element with the "minus" characted prepend to the time 14377 * 14378 * @return {Element} 14379 * The element that was created. 14380 */ 14381 ; 14382 14383 _proto.createEl = function createEl$1() { 14384 var el = _TimeDisplay.prototype.createEl.call(this); 14385 14386 el.insertBefore(createEl('span', {}, { 14387 'aria-hidden': true 14388 }, '-'), this.contentEl_); 14389 return el; 14390 } 14391 /** 14392 * Update remaining time display. 14393 * 14394 * @param {EventTarget~Event} [event] 14395 * The `timeupdate` or `durationchange` event that caused this to run. 14396 * 14397 * @listens Player#timeupdate 14398 * @listens Player#durationchange 14399 */ 14400 ; 14401 14402 _proto.updateContent = function updateContent(event) { 14403 if (typeof this.player_.duration() !== 'number') { 14404 return; 14405 } 14406 14407 var time; // @deprecated We should only use remainingTimeDisplay 14408 // as of video.js 7 14409 14410 if (this.player_.ended()) { 14411 time = 0; 14412 } else if (this.player_.remainingTimeDisplay) { 14413 time = this.player_.remainingTimeDisplay(); 14414 } else { 14415 time = this.player_.remainingTime(); 14416 } 14417 14418 this.updateTextNode_(time); 14419 }; 14420 14421 return RemainingTimeDisplay; 14422 }(TimeDisplay); 14423 /** 14424 * The text that is added to the `RemainingTimeDisplay` for screen reader users. 14425 * 14426 * @type {string} 14427 * @private 14428 */ 14429 14430 14431 RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time'; 14432 /** 14433 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization. 14434 * 14435 * @type {string} 14436 * @private 14437 * 14438 * @deprecated in v7; controlText_ is not used in non-active display Components 14439 */ 14440 14441 RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time'; 14442 Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); 14443 14444 /** 14445 * Displays the live indicator when duration is Infinity. 14446 * 14447 * @extends Component 14448 */ 14449 14450 var LiveDisplay = /*#__PURE__*/function (_Component) { 14451 inheritsLoose(LiveDisplay, _Component); 14452 14453 /** 14454 * Creates an instance of this class. 14455 * 14456 * @param {Player} player 14457 * The `Player` that this class should be attached to. 14458 * 14459 * @param {Object} [options] 14460 * The key/value store of player options. 14461 */ 14462 function LiveDisplay(player, options) { 14463 var _this; 14464 14465 _this = _Component.call(this, player, options) || this; 14466 14467 _this.updateShowing(); 14468 14469 _this.on(_this.player(), 'durationchange', _this.updateShowing); 14470 14471 return _this; 14472 } 14473 /** 14474 * Create the `Component`'s DOM element 14475 * 14476 * @return {Element} 14477 * The element that was created. 14478 */ 14479 14480 14481 var _proto = LiveDisplay.prototype; 14482 14483 _proto.createEl = function createEl$1() { 14484 var el = _Component.prototype.createEl.call(this, 'div', { 14485 className: 'vjs-live-control vjs-control' 14486 }); 14487 14488 this.contentEl_ = createEl('div', { 14489 className: 'vjs-live-display', 14490 innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE') 14491 }, { 14492 'aria-live': 'off' 14493 }); 14494 el.appendChild(this.contentEl_); 14495 return el; 14496 }; 14497 14498 _proto.dispose = function dispose() { 14499 this.contentEl_ = null; 14500 14501 _Component.prototype.dispose.call(this); 14502 } 14503 /** 14504 * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide 14505 * it accordingly 14506 * 14507 * @param {EventTarget~Event} [event] 14508 * The {@link Player#durationchange} event that caused this function to run. 14509 * 14510 * @listens Player#durationchange 14511 */ 14512 ; 14513 14514 _proto.updateShowing = function updateShowing(event) { 14515 if (this.player().duration() === Infinity) { 14516 this.show(); 14517 } else { 14518 this.hide(); 14519 } 14520 }; 14521 14522 return LiveDisplay; 14523 }(Component); 14524 14525 Component.registerComponent('LiveDisplay', LiveDisplay); 14526 14527 /** 14528 * Displays the live indicator when duration is Infinity. 14529 * 14530 * @extends Component 14531 */ 14532 14533 var SeekToLive = /*#__PURE__*/function (_Button) { 14534 inheritsLoose(SeekToLive, _Button); 14535 14536 /** 14537 * Creates an instance of this class. 14538 * 14539 * @param {Player} player 14540 * The `Player` that this class should be attached to. 14541 * 14542 * @param {Object} [options] 14543 * The key/value store of player options. 14544 */ 14545 function SeekToLive(player, options) { 14546 var _this; 14547 14548 _this = _Button.call(this, player, options) || this; 14549 14550 _this.updateLiveEdgeStatus(); 14551 14552 if (_this.player_.liveTracker) { 14553 _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus); 14554 } 14555 14556 return _this; 14557 } 14558 /** 14559 * Create the `Component`'s DOM element 14560 * 14561 * @return {Element} 14562 * The element that was created. 14563 */ 14564 14565 14566 var _proto = SeekToLive.prototype; 14567 14568 _proto.createEl = function createEl$1() { 14569 var el = _Button.prototype.createEl.call(this, 'button', { 14570 className: 'vjs-seek-to-live-control vjs-control' 14571 }); 14572 14573 this.textEl_ = createEl('span', { 14574 className: 'vjs-seek-to-live-text', 14575 innerHTML: this.localize('LIVE') 14576 }, { 14577 'aria-hidden': 'true' 14578 }); 14579 el.appendChild(this.textEl_); 14580 return el; 14581 } 14582 /** 14583 * Update the state of this button if we are at the live edge 14584 * or not 14585 */ 14586 ; 14587 14588 _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus() { 14589 // default to live edge 14590 if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) { 14591 this.setAttribute('aria-disabled', true); 14592 this.addClass('vjs-at-live-edge'); 14593 this.controlText('Seek to live, currently playing live'); 14594 } else { 14595 this.setAttribute('aria-disabled', false); 14596 this.removeClass('vjs-at-live-edge'); 14597 this.controlText('Seek to live, currently behind live'); 14598 } 14599 } 14600 /** 14601 * On click bring us as near to the live point as possible. 14602 * This requires that we wait for the next `live-seekable-change` 14603 * event which will happen every segment length seconds. 14604 */ 14605 ; 14606 14607 _proto.handleClick = function handleClick() { 14608 this.player_.liveTracker.seekToLiveEdge(); 14609 } 14610 /** 14611 * Dispose of the element and stop tracking 14612 */ 14613 ; 14614 14615 _proto.dispose = function dispose() { 14616 if (this.player_.liveTracker) { 14617 this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus); 14618 } 14619 14620 this.textEl_ = null; 14621 14622 _Button.prototype.dispose.call(this); 14623 }; 14624 14625 return SeekToLive; 14626 }(Button); 14627 14628 SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live'; 14629 Component.registerComponent('SeekToLive', SeekToLive); 14630 14631 /** 14632 * Keep a number between a min and a max value 14633 * 14634 * @param {number} number 14635 * The number to clamp 14636 * 14637 * @param {number} min 14638 * The minimum value 14639 * @param {number} max 14640 * The maximum value 14641 * 14642 * @return {number} 14643 * the clamped number 14644 */ 14645 var clamp = function clamp(number, min, max) { 14646 number = Number(number); 14647 return Math.min(max, Math.max(min, isNaN(number) ? min : number)); 14648 }; 14649 14650 /** 14651 * The base functionality for a slider. Can be vertical or horizontal. 14652 * For instance the volume bar or the seek bar on a video is a slider. 14653 * 14654 * @extends Component 14655 */ 14656 14657 var Slider = /*#__PURE__*/function (_Component) { 14658 inheritsLoose(Slider, _Component); 14659 14660 /** 14661 * Create an instance of this class 14662 * 14663 * @param {Player} player 14664 * The `Player` that this class should be attached to. 14665 * 14666 * @param {Object} [options] 14667 * The key/value store of player options. 14668 */ 14669 function Slider(player, options) { 14670 var _this; 14671 14672 _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for 14673 14674 _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type 14675 14676 _this.vertical(!!_this.options_.vertical); 14677 14678 _this.enable(); 14679 14680 return _this; 14681 } 14682 /** 14683 * Are controls are currently enabled for this slider or not. 14684 * 14685 * @return {boolean} 14686 * true if controls are enabled, false otherwise 14687 */ 14688 14689 14690 var _proto = Slider.prototype; 14691 14692 _proto.enabled = function enabled() { 14693 return this.enabled_; 14694 } 14695 /** 14696 * Enable controls for this slider if they are disabled 14697 */ 14698 ; 14699 14700 _proto.enable = function enable() { 14701 if (this.enabled()) { 14702 return; 14703 } 14704 14705 this.on('mousedown', this.handleMouseDown); 14706 this.on('touchstart', this.handleMouseDown); 14707 this.on('keydown', this.handleKeyDown); 14708 this.on('click', this.handleClick); // TODO: deprecated, controlsvisible does not seem to be fired 14709 14710 this.on(this.player_, 'controlsvisible', this.update); 14711 14712 if (this.playerEvent) { 14713 this.on(this.player_, this.playerEvent, this.update); 14714 } 14715 14716 this.removeClass('disabled'); 14717 this.setAttribute('tabindex', 0); 14718 this.enabled_ = true; 14719 } 14720 /** 14721 * Disable controls for this slider if they are enabled 14722 */ 14723 ; 14724 14725 _proto.disable = function disable() { 14726 if (!this.enabled()) { 14727 return; 14728 } 14729 14730 var doc = this.bar.el_.ownerDocument; 14731 this.off('mousedown', this.handleMouseDown); 14732 this.off('touchstart', this.handleMouseDown); 14733 this.off('keydown', this.handleKeyDown); 14734 this.off('click', this.handleClick); 14735 this.off(this.player_, 'controlsvisible', this.update); 14736 this.off(doc, 'mousemove', this.handleMouseMove); 14737 this.off(doc, 'mouseup', this.handleMouseUp); 14738 this.off(doc, 'touchmove', this.handleMouseMove); 14739 this.off(doc, 'touchend', this.handleMouseUp); 14740 this.removeAttribute('tabindex'); 14741 this.addClass('disabled'); 14742 14743 if (this.playerEvent) { 14744 this.off(this.player_, this.playerEvent, this.update); 14745 } 14746 14747 this.enabled_ = false; 14748 } 14749 /** 14750 * Create the `Slider`s DOM element. 14751 * 14752 * @param {string} type 14753 * Type of element to create. 14754 * 14755 * @param {Object} [props={}] 14756 * List of properties in Object form. 14757 * 14758 * @param {Object} [attributes={}] 14759 * list of attributes in Object form. 14760 * 14761 * @return {Element} 14762 * The element that gets created. 14763 */ 14764 ; 14765 14766 _proto.createEl = function createEl(type, props, attributes) { 14767 if (props === void 0) { 14768 props = {}; 14769 } 14770 14771 if (attributes === void 0) { 14772 attributes = {}; 14773 } 14774 14775 // Add the slider element class to all sub classes 14776 props.className = props.className + ' vjs-slider'; 14777 props = assign({ 14778 tabIndex: 0 14779 }, props); 14780 attributes = assign({ 14781 'role': 'slider', 14782 'aria-valuenow': 0, 14783 'aria-valuemin': 0, 14784 'aria-valuemax': 100, 14785 'tabIndex': 0 14786 }, attributes); 14787 return _Component.prototype.createEl.call(this, type, props, attributes); 14788 } 14789 /** 14790 * Handle `mousedown` or `touchstart` events on the `Slider`. 14791 * 14792 * @param {EventTarget~Event} event 14793 * `mousedown` or `touchstart` event that triggered this function 14794 * 14795 * @listens mousedown 14796 * @listens touchstart 14797 * @fires Slider#slideractive 14798 */ 14799 ; 14800 14801 _proto.handleMouseDown = function handleMouseDown(event) { 14802 var doc = this.bar.el_.ownerDocument; 14803 14804 if (event.type === 'mousedown') { 14805 event.preventDefault(); 14806 } // Do not call preventDefault() on touchstart in Chrome 14807 // to avoid console warnings. Use a 'touch-action: none' style 14808 // instead to prevent unintented scrolling. 14809 // https://developers.google.com/web/updates/2017/01/scrolling-intervention 14810 14811 14812 if (event.type === 'touchstart' && !IS_CHROME) { 14813 event.preventDefault(); 14814 } 14815 14816 blockTextSelection(); 14817 this.addClass('vjs-sliding'); 14818 /** 14819 * Triggered when the slider is in an active state 14820 * 14821 * @event Slider#slideractive 14822 * @type {EventTarget~Event} 14823 */ 14824 14825 this.trigger('slideractive'); 14826 this.on(doc, 'mousemove', this.handleMouseMove); 14827 this.on(doc, 'mouseup', this.handleMouseUp); 14828 this.on(doc, 'touchmove', this.handleMouseMove); 14829 this.on(doc, 'touchend', this.handleMouseUp); 14830 this.handleMouseMove(event); 14831 } 14832 /** 14833 * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. 14834 * The `mousemove` and `touchmove` events will only only trigger this function during 14835 * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and 14836 * {@link Slider#handleMouseUp}. 14837 * 14838 * @param {EventTarget~Event} event 14839 * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered 14840 * this function 14841 * 14842 * @listens mousemove 14843 * @listens touchmove 14844 */ 14845 ; 14846 14847 _proto.handleMouseMove = function handleMouseMove(event) {} 14848 /** 14849 * Handle `mouseup` or `touchend` events on the `Slider`. 14850 * 14851 * @param {EventTarget~Event} event 14852 * `mouseup` or `touchend` event that triggered this function. 14853 * 14854 * @listens touchend 14855 * @listens mouseup 14856 * @fires Slider#sliderinactive 14857 */ 14858 ; 14859 14860 _proto.handleMouseUp = function handleMouseUp() { 14861 var doc = this.bar.el_.ownerDocument; 14862 unblockTextSelection(); 14863 this.removeClass('vjs-sliding'); 14864 /** 14865 * Triggered when the slider is no longer in an active state. 14866 * 14867 * @event Slider#sliderinactive 14868 * @type {EventTarget~Event} 14869 */ 14870 14871 this.trigger('sliderinactive'); 14872 this.off(doc, 'mousemove', this.handleMouseMove); 14873 this.off(doc, 'mouseup', this.handleMouseUp); 14874 this.off(doc, 'touchmove', this.handleMouseMove); 14875 this.off(doc, 'touchend', this.handleMouseUp); 14876 this.update(); 14877 } 14878 /** 14879 * Update the progress bar of the `Slider`. 14880 * 14881 * @return {number} 14882 * The percentage of progress the progress bar represents as a 14883 * number from 0 to 1. 14884 */ 14885 ; 14886 14887 _proto.update = function update() { 14888 var _this2 = this; 14889 14890 // In VolumeBar init we have a setTimeout for update that pops and update 14891 // to the end of the execution stack. The player is destroyed before then 14892 // update will cause an error 14893 // If there's no bar... 14894 if (!this.el_ || !this.bar) { 14895 return; 14896 } // clamp progress between 0 and 1 14897 // and only round to four decimal places, as we round to two below 14898 14899 14900 var progress = this.getProgress(); 14901 14902 if (progress === this.progress_) { 14903 return progress; 14904 } 14905 14906 this.progress_ = progress; 14907 this.requestNamedAnimationFrame('Slider#update', function () { 14908 // Set the new bar width or height 14909 var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value 14910 14911 _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%'; 14912 }); 14913 return progress; 14914 } 14915 /** 14916 * Get the percentage of the bar that should be filled 14917 * but clamped and rounded. 14918 * 14919 * @return {number} 14920 * percentage filled that the slider is 14921 */ 14922 ; 14923 14924 _proto.getProgress = function getProgress() { 14925 return Number(clamp(this.getPercent(), 0, 1).toFixed(4)); 14926 } 14927 /** 14928 * Calculate distance for slider 14929 * 14930 * @param {EventTarget~Event} event 14931 * The event that caused this function to run. 14932 * 14933 * @return {number} 14934 * The current position of the Slider. 14935 * - position.x for vertical `Slider`s 14936 * - position.y for horizontal `Slider`s 14937 */ 14938 ; 14939 14940 _proto.calculateDistance = function calculateDistance(event) { 14941 var position = getPointerPosition(this.el_, event); 14942 14943 if (this.vertical()) { 14944 return position.y; 14945 } 14946 14947 return position.x; 14948 } 14949 /** 14950 * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down 14951 * arrow keys. This function will only be called when the slider has focus. See 14952 * {@link Slider#handleFocus} and {@link Slider#handleBlur}. 14953 * 14954 * @param {EventTarget~Event} event 14955 * the `keydown` event that caused this function to run. 14956 * 14957 * @listens keydown 14958 */ 14959 ; 14960 14961 _proto.handleKeyDown = function handleKeyDown(event) { 14962 // Left and Down Arrows 14963 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 14964 event.preventDefault(); 14965 event.stopPropagation(); 14966 this.stepBack(); // Up and Right Arrows 14967 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 14968 event.preventDefault(); 14969 event.stopPropagation(); 14970 this.stepForward(); 14971 } else { 14972 // Pass keydown handling up for unsupported keys 14973 _Component.prototype.handleKeyDown.call(this, event); 14974 } 14975 } 14976 /** 14977 * Listener for click events on slider, used to prevent clicks 14978 * from bubbling up to parent elements like button menus. 14979 * 14980 * @param {Object} event 14981 * Event that caused this object to run 14982 */ 14983 ; 14984 14985 _proto.handleClick = function handleClick(event) { 14986 event.stopPropagation(); 14987 event.preventDefault(); 14988 } 14989 /** 14990 * Get/set if slider is horizontal for vertical 14991 * 14992 * @param {boolean} [bool] 14993 * - true if slider is vertical, 14994 * - false is horizontal 14995 * 14996 * @return {boolean} 14997 * - true if slider is vertical, and getting 14998 * - false if the slider is horizontal, and getting 14999 */ 15000 ; 15001 15002 _proto.vertical = function vertical(bool) { 15003 if (bool === undefined) { 15004 return this.vertical_ || false; 15005 } 15006 15007 this.vertical_ = !!bool; 15008 15009 if (this.vertical_) { 15010 this.addClass('vjs-slider-vertical'); 15011 } else { 15012 this.addClass('vjs-slider-horizontal'); 15013 } 15014 }; 15015 15016 return Slider; 15017 }(Component); 15018 15019 Component.registerComponent('Slider', Slider); 15020 15021 var percentify = function percentify(time, end) { 15022 return clamp(time / end * 100, 0, 100).toFixed(2) + '%'; 15023 }; 15024 /** 15025 * Shows loading progress 15026 * 15027 * @extends Component 15028 */ 15029 15030 15031 var LoadProgressBar = /*#__PURE__*/function (_Component) { 15032 inheritsLoose(LoadProgressBar, _Component); 15033 15034 /** 15035 * Creates an instance of this class. 15036 * 15037 * @param {Player} player 15038 * The `Player` that this class should be attached to. 15039 * 15040 * @param {Object} [options] 15041 * The key/value store of player options. 15042 */ 15043 function LoadProgressBar(player, options) { 15044 var _this; 15045 15046 _this = _Component.call(this, player, options) || this; 15047 _this.partEls_ = []; 15048 15049 _this.on(player, 'progress', _this.update); 15050 15051 return _this; 15052 } 15053 /** 15054 * Create the `Component`'s DOM element 15055 * 15056 * @return {Element} 15057 * The element that was created. 15058 */ 15059 15060 15061 var _proto = LoadProgressBar.prototype; 15062 15063 _proto.createEl = function createEl$1() { 15064 var el = _Component.prototype.createEl.call(this, 'div', { 15065 className: 'vjs-load-progress' 15066 }); 15067 15068 var wrapper = createEl('span', { 15069 className: 'vjs-control-text' 15070 }); 15071 var loadedText = createEl('span', { 15072 textContent: this.localize('Loaded') 15073 }); 15074 var separator = document.createTextNode(': '); 15075 this.percentageEl_ = createEl('span', { 15076 className: 'vjs-control-text-loaded-percentage', 15077 textContent: '0%' 15078 }); 15079 el.appendChild(wrapper); 15080 wrapper.appendChild(loadedText); 15081 wrapper.appendChild(separator); 15082 wrapper.appendChild(this.percentageEl_); 15083 return el; 15084 }; 15085 15086 _proto.dispose = function dispose() { 15087 this.partEls_ = null; 15088 this.percentageEl_ = null; 15089 15090 _Component.prototype.dispose.call(this); 15091 } 15092 /** 15093 * Update progress bar 15094 * 15095 * @param {EventTarget~Event} [event] 15096 * The `progress` event that caused this function to run. 15097 * 15098 * @listens Player#progress 15099 */ 15100 ; 15101 15102 _proto.update = function update(event) { 15103 var _this2 = this; 15104 15105 this.requestNamedAnimationFrame('LoadProgressBar#update', function () { 15106 var liveTracker = _this2.player_.liveTracker; 15107 15108 var buffered = _this2.player_.buffered(); 15109 15110 var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration(); 15111 15112 var bufferedEnd = _this2.player_.bufferedEnd(); 15113 15114 var children = _this2.partEls_; 15115 var percent = percentify(bufferedEnd, duration); 15116 15117 if (_this2.percent_ !== percent) { 15118 // update the width of the progress bar 15119 _this2.el_.style.width = percent; // update the control-text 15120 15121 textContent(_this2.percentageEl_, percent); 15122 _this2.percent_ = percent; 15123 } // add child elements to represent the individual buffered time ranges 15124 15125 15126 for (var i = 0; i < buffered.length; i++) { 15127 var start = buffered.start(i); 15128 var end = buffered.end(i); 15129 var part = children[i]; 15130 15131 if (!part) { 15132 part = _this2.el_.appendChild(createEl()); 15133 children[i] = part; 15134 } // only update if changed 15135 15136 15137 if (part.dataset.start === start && part.dataset.end === end) { 15138 continue; 15139 } 15140 15141 part.dataset.start = start; 15142 part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd) 15143 15144 part.style.left = percentify(start, bufferedEnd); 15145 part.style.width = percentify(end - start, bufferedEnd); 15146 } // remove unused buffered range elements 15147 15148 15149 for (var _i = children.length; _i > buffered.length; _i--) { 15150 _this2.el_.removeChild(children[_i - 1]); 15151 } 15152 15153 children.length = buffered.length; 15154 }); 15155 }; 15156 15157 return LoadProgressBar; 15158 }(Component); 15159 15160 Component.registerComponent('LoadProgressBar', LoadProgressBar); 15161 15162 /** 15163 * Time tooltips display a time above the progress bar. 15164 * 15165 * @extends Component 15166 */ 15167 15168 var TimeTooltip = /*#__PURE__*/function (_Component) { 15169 inheritsLoose(TimeTooltip, _Component); 15170 15171 /** 15172 * Creates an instance of this class. 15173 * 15174 * @param {Player} player 15175 * The {@link Player} that this class should be attached to. 15176 * 15177 * @param {Object} [options] 15178 * The key/value store of player options. 15179 */ 15180 function TimeTooltip(player, options) { 15181 var _this; 15182 15183 _this = _Component.call(this, player, options) || this; 15184 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15185 return _this; 15186 } 15187 /** 15188 * Create the time tooltip DOM element 15189 * 15190 * @return {Element} 15191 * The element that was created. 15192 */ 15193 15194 15195 var _proto = TimeTooltip.prototype; 15196 15197 _proto.createEl = function createEl() { 15198 return _Component.prototype.createEl.call(this, 'div', { 15199 className: 'vjs-time-tooltip' 15200 }, { 15201 'aria-hidden': 'true' 15202 }); 15203 } 15204 /** 15205 * Updates the position of the time tooltip relative to the `SeekBar`. 15206 * 15207 * @param {Object} seekBarRect 15208 * The `ClientRect` for the {@link SeekBar} element. 15209 * 15210 * @param {number} seekBarPoint 15211 * A number from 0 to 1, representing a horizontal reference point 15212 * from the left edge of the {@link SeekBar} 15213 */ 15214 ; 15215 15216 _proto.update = function update(seekBarRect, seekBarPoint, content) { 15217 var tooltipRect = findPosition(this.el_); 15218 var playerRect = getBoundingClientRect(this.player_.el()); 15219 var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available 15220 // for example, if the player isn't in the DOM for testing 15221 15222 if (!playerRect || !tooltipRect) { 15223 return; 15224 } // This is the space left of the `seekBarPoint` available within the bounds 15225 // of the player. We calculate any gap between the left edge of the player 15226 // and the left edge of the `SeekBar` and add the number of pixels in the 15227 // `SeekBar` before hitting the `seekBarPoint` 15228 15229 15230 var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds 15231 // of the player. We calculate the number of pixels from the `seekBarPoint` 15232 // to the right edge of the `SeekBar` and add to that any gap between the 15233 // right edge of the `SeekBar` and the player. 15234 15235 var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled 15236 // further to the right to center it over the `seekBarPoint`. 15237 15238 var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on 15239 // the results of the space calculations above. 15240 15241 if (spaceLeftOfPoint < pullTooltipBy) { 15242 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 15243 } else if (spaceRightOfPoint < pullTooltipBy) { 15244 pullTooltipBy = spaceRightOfPoint; 15245 } // Due to the imprecision of decimal/ratio based calculations and varying 15246 // rounding behaviors, there are cases where the spacing adjustment is off 15247 // by a pixel or two. This adds insurance to these calculations. 15248 15249 15250 if (pullTooltipBy < 0) { 15251 pullTooltipBy = 0; 15252 } else if (pullTooltipBy > tooltipRect.width) { 15253 pullTooltipBy = tooltipRect.width; 15254 } 15255 15256 this.el_.style.right = "-" + pullTooltipBy + "px"; 15257 this.write(content); 15258 } 15259 /** 15260 * Write the time to the tooltip DOM element. 15261 * 15262 * @param {string} content 15263 * The formatted time for the tooltip. 15264 */ 15265 ; 15266 15267 _proto.write = function write(content) { 15268 textContent(this.el_, content); 15269 } 15270 /** 15271 * Updates the position of the time tooltip relative to the `SeekBar`. 15272 * 15273 * @param {Object} seekBarRect 15274 * The `ClientRect` for the {@link SeekBar} element. 15275 * 15276 * @param {number} seekBarPoint 15277 * A number from 0 to 1, representing a horizontal reference point 15278 * from the left edge of the {@link SeekBar} 15279 * 15280 * @param {number} time 15281 * The time to update the tooltip to, not used during live playback 15282 * 15283 * @param {Function} cb 15284 * A function that will be called during the request animation frame 15285 * for tooltips that need to do additional animations from the default 15286 */ 15287 ; 15288 15289 _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) { 15290 var _this2 = this; 15291 15292 this.requestNamedAnimationFrame('TimeTooltip#updateTime', function () { 15293 var content; 15294 15295 var duration = _this2.player_.duration(); 15296 15297 if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) { 15298 var liveWindow = _this2.player_.liveTracker.liveWindow(); 15299 15300 var secondsBehind = liveWindow - seekBarPoint * liveWindow; 15301 content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow); 15302 } else { 15303 content = formatTime(time, duration); 15304 } 15305 15306 _this2.update(seekBarRect, seekBarPoint, content); 15307 15308 if (cb) { 15309 cb(); 15310 } 15311 }); 15312 }; 15313 15314 return TimeTooltip; 15315 }(Component); 15316 15317 Component.registerComponent('TimeTooltip', TimeTooltip); 15318 15319 /** 15320 * Used by {@link SeekBar} to display media playback progress as part of the 15321 * {@link ProgressControl}. 15322 * 15323 * @extends Component 15324 */ 15325 15326 var PlayProgressBar = /*#__PURE__*/function (_Component) { 15327 inheritsLoose(PlayProgressBar, _Component); 15328 15329 /** 15330 * Creates an instance of this class. 15331 * 15332 * @param {Player} player 15333 * The {@link Player} that this class should be attached to. 15334 * 15335 * @param {Object} [options] 15336 * The key/value store of player options. 15337 */ 15338 function PlayProgressBar(player, options) { 15339 var _this; 15340 15341 _this = _Component.call(this, player, options) || this; 15342 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15343 return _this; 15344 } 15345 /** 15346 * Create the the DOM element for this class. 15347 * 15348 * @return {Element} 15349 * The element that was created. 15350 */ 15351 15352 15353 var _proto = PlayProgressBar.prototype; 15354 15355 _proto.createEl = function createEl() { 15356 return _Component.prototype.createEl.call(this, 'div', { 15357 className: 'vjs-play-progress vjs-slider-bar' 15358 }, { 15359 'aria-hidden': 'true' 15360 }); 15361 } 15362 /** 15363 * Enqueues updates to its own DOM as well as the DOM of its 15364 * {@link TimeTooltip} child. 15365 * 15366 * @param {Object} seekBarRect 15367 * The `ClientRect` for the {@link SeekBar} element. 15368 * 15369 * @param {number} seekBarPoint 15370 * A number from 0 to 1, representing a horizontal reference point 15371 * from the left edge of the {@link SeekBar} 15372 */ 15373 ; 15374 15375 _proto.update = function update(seekBarRect, seekBarPoint) { 15376 var timeTooltip = this.getChild('timeTooltip'); 15377 15378 if (!timeTooltip) { 15379 return; 15380 } 15381 15382 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 15383 timeTooltip.updateTime(seekBarRect, seekBarPoint, time); 15384 }; 15385 15386 return PlayProgressBar; 15387 }(Component); 15388 /** 15389 * Default options for {@link PlayProgressBar}. 15390 * 15391 * @type {Object} 15392 * @private 15393 */ 15394 15395 15396 PlayProgressBar.prototype.options_ = { 15397 children: [] 15398 }; // Time tooltips should not be added to a player on mobile devices 15399 15400 if (!IS_IOS && !IS_ANDROID) { 15401 PlayProgressBar.prototype.options_.children.push('timeTooltip'); 15402 } 15403 15404 Component.registerComponent('PlayProgressBar', PlayProgressBar); 15405 15406 /** 15407 * The {@link MouseTimeDisplay} component tracks mouse movement over the 15408 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} 15409 * indicating the time which is represented by a given point in the 15410 * {@link ProgressControl}. 15411 * 15412 * @extends Component 15413 */ 15414 15415 var MouseTimeDisplay = /*#__PURE__*/function (_Component) { 15416 inheritsLoose(MouseTimeDisplay, _Component); 15417 15418 /** 15419 * Creates an instance of this class. 15420 * 15421 * @param {Player} player 15422 * The {@link Player} that this class should be attached to. 15423 * 15424 * @param {Object} [options] 15425 * The key/value store of player options. 15426 */ 15427 function MouseTimeDisplay(player, options) { 15428 var _this; 15429 15430 _this = _Component.call(this, player, options) || this; 15431 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15432 return _this; 15433 } 15434 /** 15435 * Create the DOM element for this class. 15436 * 15437 * @return {Element} 15438 * The element that was created. 15439 */ 15440 15441 15442 var _proto = MouseTimeDisplay.prototype; 15443 15444 _proto.createEl = function createEl() { 15445 return _Component.prototype.createEl.call(this, 'div', { 15446 className: 'vjs-mouse-display' 15447 }); 15448 } 15449 /** 15450 * Enqueues updates to its own DOM as well as the DOM of its 15451 * {@link TimeTooltip} child. 15452 * 15453 * @param {Object} seekBarRect 15454 * The `ClientRect` for the {@link SeekBar} element. 15455 * 15456 * @param {number} seekBarPoint 15457 * A number from 0 to 1, representing a horizontal reference point 15458 * from the left edge of the {@link SeekBar} 15459 */ 15460 ; 15461 15462 _proto.update = function update(seekBarRect, seekBarPoint) { 15463 var _this2 = this; 15464 15465 var time = seekBarPoint * this.player_.duration(); 15466 this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () { 15467 _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px"; 15468 }); 15469 }; 15470 15471 return MouseTimeDisplay; 15472 }(Component); 15473 /** 15474 * Default options for `MouseTimeDisplay` 15475 * 15476 * @type {Object} 15477 * @private 15478 */ 15479 15480 15481 MouseTimeDisplay.prototype.options_ = { 15482 children: ['timeTooltip'] 15483 }; 15484 Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); 15485 15486 var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline. 15487 15488 var PAGE_KEY_MULTIPLIER = 12; 15489 /** 15490 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} 15491 * as its `bar`. 15492 * 15493 * @extends Slider 15494 */ 15495 15496 var SeekBar = /*#__PURE__*/function (_Slider) { 15497 inheritsLoose(SeekBar, _Slider); 15498 15499 /** 15500 * Creates an instance of this class. 15501 * 15502 * @param {Player} player 15503 * The `Player` that this class should be attached to. 15504 * 15505 * @param {Object} [options] 15506 * The key/value store of player options. 15507 */ 15508 function SeekBar(player, options) { 15509 var _this; 15510 15511 _this = _Slider.call(this, player, options) || this; 15512 15513 _this.setEventHandlers_(); 15514 15515 return _this; 15516 } 15517 /** 15518 * Sets the event handlers 15519 * 15520 * @private 15521 */ 15522 15523 15524 var _proto = SeekBar.prototype; 15525 15526 _proto.setEventHandlers_ = function setEventHandlers_() { 15527 this.update_ = bind(this, this.update); 15528 this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL); 15529 this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update); 15530 15531 if (this.player_.liveTracker) { 15532 this.on(this.player_.liveTracker, 'liveedgechange', this.update); 15533 } // when playing, let's ensure we smoothly update the play progress bar 15534 // via an interval 15535 15536 15537 this.updateInterval = null; 15538 this.on(this.player_, ['playing'], this.enableInterval_); 15539 this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden, 15540 // also, this causes the CPU to spike and eventually crash the page on IE11. 15541 15542 if ('hidden' in document && 'visibilityState' in document) { 15543 this.on(document, 'visibilitychange', this.toggleVisibility_); 15544 } 15545 }; 15546 15547 _proto.toggleVisibility_ = function toggleVisibility_(e) { 15548 if (document.hidden) { 15549 this.disableInterval_(e); 15550 } else { 15551 this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP 15552 15553 this.update(); 15554 } 15555 }; 15556 15557 _proto.enableInterval_ = function enableInterval_() { 15558 if (this.updateInterval) { 15559 return; 15560 } 15561 15562 this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL); 15563 }; 15564 15565 _proto.disableInterval_ = function disableInterval_(e) { 15566 if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') { 15567 return; 15568 } 15569 15570 if (!this.updateInterval) { 15571 return; 15572 } 15573 15574 this.clearInterval(this.updateInterval); 15575 this.updateInterval = null; 15576 } 15577 /** 15578 * Create the `Component`'s DOM element 15579 * 15580 * @return {Element} 15581 * The element that was created. 15582 */ 15583 ; 15584 15585 _proto.createEl = function createEl() { 15586 return _Slider.prototype.createEl.call(this, 'div', { 15587 className: 'vjs-progress-holder' 15588 }, { 15589 'aria-label': this.localize('Progress Bar') 15590 }); 15591 } 15592 /** 15593 * This function updates the play progress bar and accessibility 15594 * attributes to whatever is passed in. 15595 * 15596 * @param {EventTarget~Event} [event] 15597 * The `timeupdate` or `ended` event that caused this to run. 15598 * 15599 * @listens Player#timeupdate 15600 * 15601 * @return {number} 15602 * The current percent at a number from 0-1 15603 */ 15604 ; 15605 15606 _proto.update = function update(event) { 15607 var _this2 = this; 15608 15609 var percent = _Slider.prototype.update.call(this); 15610 15611 this.requestNamedAnimationFrame('SeekBar#update', function () { 15612 var currentTime = _this2.player_.ended() ? _this2.player_.duration() : _this2.getCurrentTime_(); 15613 var liveTracker = _this2.player_.liveTracker; 15614 15615 var duration = _this2.player_.duration(); 15616 15617 if (liveTracker && liveTracker.isLive()) { 15618 duration = _this2.player_.liveTracker.liveCurrentTime(); 15619 } 15620 15621 if (_this2.percent_ !== percent) { 15622 // machine readable value of progress bar (percentage complete) 15623 _this2.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); 15624 15625 _this2.percent_ = percent; 15626 } 15627 15628 if (_this2.currentTime_ !== currentTime || _this2.duration_ !== duration) { 15629 // human readable value of progress bar (time complete) 15630 _this2.el_.setAttribute('aria-valuetext', _this2.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); 15631 15632 _this2.currentTime_ = currentTime; 15633 _this2.duration_ = duration; 15634 } // update the progress bar time tooltip with the current time 15635 15636 15637 if (_this2.bar) { 15638 _this2.bar.update(getBoundingClientRect(_this2.el()), _this2.getProgress()); 15639 } 15640 }); 15641 return percent; 15642 } 15643 /** 15644 * Get the value of current time but allows for smooth scrubbing, 15645 * when player can't keep up. 15646 * 15647 * @return {number} 15648 * The current time value to display 15649 * 15650 * @private 15651 */ 15652 ; 15653 15654 _proto.getCurrentTime_ = function getCurrentTime_() { 15655 return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 15656 } 15657 /** 15658 * Get the percentage of media played so far. 15659 * 15660 * @return {number} 15661 * The percentage of media played so far (0 to 1). 15662 */ 15663 ; 15664 15665 _proto.getPercent = function getPercent() { 15666 var currentTime = this.getCurrentTime_(); 15667 var percent; 15668 var liveTracker = this.player_.liveTracker; 15669 15670 if (liveTracker && liveTracker.isLive()) { 15671 percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge 15672 15673 if (liveTracker.atLiveEdge()) { 15674 percent = 1; 15675 } 15676 } else { 15677 percent = currentTime / this.player_.duration(); 15678 } 15679 15680 return percent; 15681 } 15682 /** 15683 * Handle mouse down on seek bar 15684 * 15685 * @param {EventTarget~Event} event 15686 * The `mousedown` event that caused this to run. 15687 * 15688 * @listens mousedown 15689 */ 15690 ; 15691 15692 _proto.handleMouseDown = function handleMouseDown(event) { 15693 if (!isSingleLeftClick(event)) { 15694 return; 15695 } // Stop event propagation to prevent double fire in progress-control.js 15696 15697 15698 event.stopPropagation(); 15699 this.player_.scrubbing(true); 15700 this.videoWasPlaying = !this.player_.paused(); 15701 this.player_.pause(); 15702 15703 _Slider.prototype.handleMouseDown.call(this, event); 15704 } 15705 /** 15706 * Handle mouse move on seek bar 15707 * 15708 * @param {EventTarget~Event} event 15709 * The `mousemove` event that caused this to run. 15710 * 15711 * @listens mousemove 15712 */ 15713 ; 15714 15715 _proto.handleMouseMove = function handleMouseMove(event) { 15716 if (!isSingleLeftClick(event)) { 15717 return; 15718 } 15719 15720 var newTime; 15721 var distance = this.calculateDistance(event); 15722 var liveTracker = this.player_.liveTracker; 15723 15724 if (!liveTracker || !liveTracker.isLive()) { 15725 newTime = distance * this.player_.duration(); // Don't let video end while scrubbing. 15726 15727 if (newTime === this.player_.duration()) { 15728 newTime = newTime - 0.1; 15729 } 15730 } else { 15731 if (distance >= 0.99) { 15732 liveTracker.seekToLiveEdge(); 15733 return; 15734 } 15735 15736 var seekableStart = liveTracker.seekableStart(); 15737 var seekableEnd = liveTracker.liveCurrentTime(); 15738 newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing. 15739 15740 if (newTime >= seekableEnd) { 15741 newTime = seekableEnd; 15742 } // Compensate for precision differences so that currentTime is not less 15743 // than seekable start 15744 15745 15746 if (newTime <= seekableStart) { 15747 newTime = seekableStart + 0.1; 15748 } // On android seekableEnd can be Infinity sometimes, 15749 // this will cause newTime to be Infinity, which is 15750 // not a valid currentTime. 15751 15752 15753 if (newTime === Infinity) { 15754 return; 15755 } 15756 } // Set new time (tell player to seek to new time) 15757 15758 15759 this.player_.currentTime(newTime); 15760 }; 15761 15762 _proto.enable = function enable() { 15763 _Slider.prototype.enable.call(this); 15764 15765 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 15766 15767 if (!mouseTimeDisplay) { 15768 return; 15769 } 15770 15771 mouseTimeDisplay.show(); 15772 }; 15773 15774 _proto.disable = function disable() { 15775 _Slider.prototype.disable.call(this); 15776 15777 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 15778 15779 if (!mouseTimeDisplay) { 15780 return; 15781 } 15782 15783 mouseTimeDisplay.hide(); 15784 } 15785 /** 15786 * Handle mouse up on seek bar 15787 * 15788 * @param {EventTarget~Event} event 15789 * The `mouseup` event that caused this to run. 15790 * 15791 * @listens mouseup 15792 */ 15793 ; 15794 15795 _proto.handleMouseUp = function handleMouseUp(event) { 15796 _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js 15797 15798 15799 if (event) { 15800 event.stopPropagation(); 15801 } 15802 15803 this.player_.scrubbing(false); 15804 /** 15805 * Trigger timeupdate because we're done seeking and the time has changed. 15806 * This is particularly useful for if the player is paused to time the time displays. 15807 * 15808 * @event Tech#timeupdate 15809 * @type {EventTarget~Event} 15810 */ 15811 15812 this.player_.trigger({ 15813 type: 'timeupdate', 15814 target: this, 15815 manuallyTriggered: true 15816 }); 15817 15818 if (this.videoWasPlaying) { 15819 silencePromise(this.player_.play()); 15820 } else { 15821 // We're done seeking and the time has changed. 15822 // If the player is paused, make sure we display the correct time on the seek bar. 15823 this.update_(); 15824 } 15825 } 15826 /** 15827 * Move more quickly fast forward for keyboard-only users 15828 */ 15829 ; 15830 15831 _proto.stepForward = function stepForward() { 15832 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); 15833 } 15834 /** 15835 * Move more quickly rewind for keyboard-only users 15836 */ 15837 ; 15838 15839 _proto.stepBack = function stepBack() { 15840 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); 15841 } 15842 /** 15843 * Toggles the playback state of the player 15844 * This gets called when enter or space is used on the seekbar 15845 * 15846 * @param {EventTarget~Event} event 15847 * The `keydown` event that caused this function to be called 15848 * 15849 */ 15850 ; 15851 15852 _proto.handleAction = function handleAction(event) { 15853 if (this.player_.paused()) { 15854 this.player_.play(); 15855 } else { 15856 this.player_.pause(); 15857 } 15858 } 15859 /** 15860 * Called when this SeekBar has focus and a key gets pressed down. 15861 * Supports the following keys: 15862 * 15863 * Space or Enter key fire a click event 15864 * Home key moves to start of the timeline 15865 * End key moves to end of the timeline 15866 * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline 15867 * PageDown key moves back a larger step than ArrowDown 15868 * PageUp key moves forward a large step 15869 * 15870 * @param {EventTarget~Event} event 15871 * The `keydown` event that caused this function to be called. 15872 * 15873 * @listens keydown 15874 */ 15875 ; 15876 15877 _proto.handleKeyDown = function handleKeyDown(event) { 15878 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 15879 event.preventDefault(); 15880 event.stopPropagation(); 15881 this.handleAction(event); 15882 } else if (keycode.isEventKey(event, 'Home')) { 15883 event.preventDefault(); 15884 event.stopPropagation(); 15885 this.player_.currentTime(0); 15886 } else if (keycode.isEventKey(event, 'End')) { 15887 event.preventDefault(); 15888 event.stopPropagation(); 15889 this.player_.currentTime(this.player_.duration()); 15890 } else if (/^[0-9]$/.test(keycode(event))) { 15891 event.preventDefault(); 15892 event.stopPropagation(); 15893 var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0; 15894 this.player_.currentTime(this.player_.duration() * gotoFraction); 15895 } else if (keycode.isEventKey(event, 'PgDn')) { 15896 event.preventDefault(); 15897 event.stopPropagation(); 15898 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER); 15899 } else if (keycode.isEventKey(event, 'PgUp')) { 15900 event.preventDefault(); 15901 event.stopPropagation(); 15902 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER); 15903 } else { 15904 // Pass keydown handling up for unsupported keys 15905 _Slider.prototype.handleKeyDown.call(this, event); 15906 } 15907 }; 15908 15909 _proto.dispose = function dispose() { 15910 this.disableInterval_(); 15911 this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update); 15912 15913 if (this.player_.liveTracker) { 15914 this.on(this.player_.liveTracker, 'liveedgechange', this.update); 15915 } 15916 15917 this.off(this.player_, ['playing'], this.enableInterval_); 15918 this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden, 15919 // also, this causes the CPU to spike and eventually crash the page on IE11. 15920 15921 if ('hidden' in document && 'visibilityState' in document) { 15922 this.off(document, 'visibilitychange', this.toggleVisibility_); 15923 } 15924 15925 _Slider.prototype.dispose.call(this); 15926 }; 15927 15928 return SeekBar; 15929 }(Slider); 15930 /** 15931 * Default options for the `SeekBar` 15932 * 15933 * @type {Object} 15934 * @private 15935 */ 15936 15937 15938 SeekBar.prototype.options_ = { 15939 children: ['loadProgressBar', 'playProgressBar'], 15940 barName: 'playProgressBar' 15941 }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices 15942 15943 if (!IS_IOS && !IS_ANDROID) { 15944 SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); 15945 } 15946 15947 Component.registerComponent('SeekBar', SeekBar); 15948 15949 /** 15950 * The Progress Control component contains the seek bar, load progress, 15951 * and play progress. 15952 * 15953 * @extends Component 15954 */ 15955 15956 var ProgressControl = /*#__PURE__*/function (_Component) { 15957 inheritsLoose(ProgressControl, _Component); 15958 15959 /** 15960 * Creates an instance of this class. 15961 * 15962 * @param {Player} player 15963 * The `Player` that this class should be attached to. 15964 * 15965 * @param {Object} [options] 15966 * The key/value store of player options. 15967 */ 15968 function ProgressControl(player, options) { 15969 var _this; 15970 15971 _this = _Component.call(this, player, options) || this; 15972 _this.handleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 15973 _this.throttledHandleMouseSeek = throttle(bind(assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL); 15974 15975 _this.enable(); 15976 15977 return _this; 15978 } 15979 /** 15980 * Create the `Component`'s DOM element 15981 * 15982 * @return {Element} 15983 * The element that was created. 15984 */ 15985 15986 15987 var _proto = ProgressControl.prototype; 15988 15989 _proto.createEl = function createEl() { 15990 return _Component.prototype.createEl.call(this, 'div', { 15991 className: 'vjs-progress-control vjs-control' 15992 }); 15993 } 15994 /** 15995 * When the mouse moves over the `ProgressControl`, the pointer position 15996 * gets passed down to the `MouseTimeDisplay` component. 15997 * 15998 * @param {EventTarget~Event} event 15999 * The `mousemove` event that caused this function to run. 16000 * 16001 * @listen mousemove 16002 */ 16003 ; 16004 16005 _proto.handleMouseMove = function handleMouseMove(event) { 16006 var seekBar = this.getChild('seekBar'); 16007 16008 if (!seekBar) { 16009 return; 16010 } 16011 16012 var playProgressBar = seekBar.getChild('playProgressBar'); 16013 var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); 16014 16015 if (!playProgressBar && !mouseTimeDisplay) { 16016 return; 16017 } 16018 16019 var seekBarEl = seekBar.el(); 16020 var seekBarRect = findPosition(seekBarEl); 16021 var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means 16022 // that it's possible to trigger this behavior outside the boundaries of 16023 // the `SeekBar`. This ensures we stay within it at all times. 16024 16025 seekBarPoint = clamp(seekBarPoint, 0, 1); 16026 16027 if (mouseTimeDisplay) { 16028 mouseTimeDisplay.update(seekBarRect, seekBarPoint); 16029 } 16030 16031 if (playProgressBar) { 16032 playProgressBar.update(seekBarRect, seekBar.getProgress()); 16033 } 16034 } 16035 /** 16036 * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. 16037 * 16038 * @method ProgressControl#throttledHandleMouseSeek 16039 * @param {EventTarget~Event} event 16040 * The `mousemove` event that caused this function to run. 16041 * 16042 * @listen mousemove 16043 * @listen touchmove 16044 */ 16045 16046 /** 16047 * Handle `mousemove` or `touchmove` events on the `ProgressControl`. 16048 * 16049 * @param {EventTarget~Event} event 16050 * `mousedown` or `touchstart` event that triggered this function 16051 * 16052 * @listens mousemove 16053 * @listens touchmove 16054 */ 16055 ; 16056 16057 _proto.handleMouseSeek = function handleMouseSeek(event) { 16058 var seekBar = this.getChild('seekBar'); 16059 16060 if (seekBar) { 16061 seekBar.handleMouseMove(event); 16062 } 16063 } 16064 /** 16065 * Are controls are currently enabled for this progress control. 16066 * 16067 * @return {boolean} 16068 * true if controls are enabled, false otherwise 16069 */ 16070 ; 16071 16072 _proto.enabled = function enabled() { 16073 return this.enabled_; 16074 } 16075 /** 16076 * Disable all controls on the progress control and its children 16077 */ 16078 ; 16079 16080 _proto.disable = function disable() { 16081 this.children().forEach(function (child) { 16082 return child.disable && child.disable(); 16083 }); 16084 16085 if (!this.enabled()) { 16086 return; 16087 } 16088 16089 this.off(['mousedown', 'touchstart'], this.handleMouseDown); 16090 this.off(this.el_, 'mousemove', this.handleMouseMove); 16091 this.handleMouseUp(); 16092 this.addClass('disabled'); 16093 this.enabled_ = false; 16094 } 16095 /** 16096 * Enable all controls on the progress control and its children 16097 */ 16098 ; 16099 16100 _proto.enable = function enable() { 16101 this.children().forEach(function (child) { 16102 return child.enable && child.enable(); 16103 }); 16104 16105 if (this.enabled()) { 16106 return; 16107 } 16108 16109 this.on(['mousedown', 'touchstart'], this.handleMouseDown); 16110 this.on(this.el_, 'mousemove', this.handleMouseMove); 16111 this.removeClass('disabled'); 16112 this.enabled_ = true; 16113 } 16114 /** 16115 * Handle `mousedown` or `touchstart` events on the `ProgressControl`. 16116 * 16117 * @param {EventTarget~Event} event 16118 * `mousedown` or `touchstart` event that triggered this function 16119 * 16120 * @listens mousedown 16121 * @listens touchstart 16122 */ 16123 ; 16124 16125 _proto.handleMouseDown = function handleMouseDown(event) { 16126 var doc = this.el_.ownerDocument; 16127 var seekBar = this.getChild('seekBar'); 16128 16129 if (seekBar) { 16130 seekBar.handleMouseDown(event); 16131 } 16132 16133 this.on(doc, 'mousemove', this.throttledHandleMouseSeek); 16134 this.on(doc, 'touchmove', this.throttledHandleMouseSeek); 16135 this.on(doc, 'mouseup', this.handleMouseUp); 16136 this.on(doc, 'touchend', this.handleMouseUp); 16137 } 16138 /** 16139 * Handle `mouseup` or `touchend` events on the `ProgressControl`. 16140 * 16141 * @param {EventTarget~Event} event 16142 * `mouseup` or `touchend` event that triggered this function. 16143 * 16144 * @listens touchend 16145 * @listens mouseup 16146 */ 16147 ; 16148 16149 _proto.handleMouseUp = function handleMouseUp(event) { 16150 var doc = this.el_.ownerDocument; 16151 var seekBar = this.getChild('seekBar'); 16152 16153 if (seekBar) { 16154 seekBar.handleMouseUp(event); 16155 } 16156 16157 this.off(doc, 'mousemove', this.throttledHandleMouseSeek); 16158 this.off(doc, 'touchmove', this.throttledHandleMouseSeek); 16159 this.off(doc, 'mouseup', this.handleMouseUp); 16160 this.off(doc, 'touchend', this.handleMouseUp); 16161 }; 16162 16163 return ProgressControl; 16164 }(Component); 16165 /** 16166 * Default options for `ProgressControl` 16167 * 16168 * @type {Object} 16169 * @private 16170 */ 16171 16172 16173 ProgressControl.prototype.options_ = { 16174 children: ['seekBar'] 16175 }; 16176 Component.registerComponent('ProgressControl', ProgressControl); 16177 16178 /** 16179 * Toggle Picture-in-Picture mode 16180 * 16181 * @extends Button 16182 */ 16183 16184 var PictureInPictureToggle = /*#__PURE__*/function (_Button) { 16185 inheritsLoose(PictureInPictureToggle, _Button); 16186 16187 /** 16188 * Creates an instance of this class. 16189 * 16190 * @param {Player} player 16191 * The `Player` that this class should be attached to. 16192 * 16193 * @param {Object} [options] 16194 * The key/value store of player options. 16195 * 16196 * @listens Player#enterpictureinpicture 16197 * @listens Player#leavepictureinpicture 16198 */ 16199 function PictureInPictureToggle(player, options) { 16200 var _this; 16201 16202 _this = _Button.call(this, player, options) || this; 16203 16204 _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], _this.handlePictureInPictureChange); 16205 16206 _this.on(player, ['disablepictureinpicturechanged', 'loadedmetadata'], _this.handlePictureInPictureEnabledChange); // TODO: Deactivate button on player emptied event. 16207 16208 16209 _this.disable(); 16210 16211 return _this; 16212 } 16213 /** 16214 * Builds the default DOM `className`. 16215 * 16216 * @return {string} 16217 * The DOM `className` for this object. 16218 */ 16219 16220 16221 var _proto = PictureInPictureToggle.prototype; 16222 16223 _proto.buildCSSClass = function buildCSSClass() { 16224 return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this); 16225 } 16226 /** 16227 * Enables or disables button based on document.pictureInPictureEnabled property value 16228 * or on value returned by player.disablePictureInPicture() method. 16229 */ 16230 ; 16231 16232 _proto.handlePictureInPictureEnabledChange = function handlePictureInPictureEnabledChange() { 16233 if (document.pictureInPictureEnabled && this.player_.disablePictureInPicture() === false) { 16234 this.enable(); 16235 } else { 16236 this.disable(); 16237 } 16238 } 16239 /** 16240 * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly. 16241 * 16242 * @param {EventTarget~Event} [event] 16243 * The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be 16244 * called. 16245 * 16246 * @listens Player#enterpictureinpicture 16247 * @listens Player#leavepictureinpicture 16248 */ 16249 ; 16250 16251 _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) { 16252 if (this.player_.isInPictureInPicture()) { 16253 this.controlText('Exit Picture-in-Picture'); 16254 } else { 16255 this.controlText('Picture-in-Picture'); 16256 } 16257 16258 this.handlePictureInPictureEnabledChange(); 16259 } 16260 /** 16261 * This gets called when an `PictureInPictureToggle` is "clicked". See 16262 * {@link ClickableComponent} for more detailed information on what a click can be. 16263 * 16264 * @param {EventTarget~Event} [event] 16265 * The `keydown`, `tap`, or `click` event that caused this function to be 16266 * called. 16267 * 16268 * @listens tap 16269 * @listens click 16270 */ 16271 ; 16272 16273 _proto.handleClick = function handleClick(event) { 16274 if (!this.player_.isInPictureInPicture()) { 16275 this.player_.requestPictureInPicture(); 16276 } else { 16277 this.player_.exitPictureInPicture(); 16278 } 16279 }; 16280 16281 return PictureInPictureToggle; 16282 }(Button); 16283 /** 16284 * The text that should display over the `PictureInPictureToggle`s controls. Added for localization. 16285 * 16286 * @type {string} 16287 * @private 16288 */ 16289 16290 16291 PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture'; 16292 Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle); 16293 16294 /** 16295 * Toggle fullscreen video 16296 * 16297 * @extends Button 16298 */ 16299 16300 var FullscreenToggle = /*#__PURE__*/function (_Button) { 16301 inheritsLoose(FullscreenToggle, _Button); 16302 16303 /** 16304 * Creates an instance of this class. 16305 * 16306 * @param {Player} player 16307 * The `Player` that this class should be attached to. 16308 * 16309 * @param {Object} [options] 16310 * The key/value store of player options. 16311 */ 16312 function FullscreenToggle(player, options) { 16313 var _this; 16314 16315 _this = _Button.call(this, player, options) || this; 16316 16317 _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); 16318 16319 if (document[player.fsApi_.fullscreenEnabled] === false) { 16320 _this.disable(); 16321 } 16322 16323 return _this; 16324 } 16325 /** 16326 * Builds the default DOM `className`. 16327 * 16328 * @return {string} 16329 * The DOM `className` for this object. 16330 */ 16331 16332 16333 var _proto = FullscreenToggle.prototype; 16334 16335 _proto.buildCSSClass = function buildCSSClass() { 16336 return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this); 16337 } 16338 /** 16339 * Handles fullscreenchange on the player and change control text accordingly. 16340 * 16341 * @param {EventTarget~Event} [event] 16342 * The {@link Player#fullscreenchange} event that caused this function to be 16343 * called. 16344 * 16345 * @listens Player#fullscreenchange 16346 */ 16347 ; 16348 16349 _proto.handleFullscreenChange = function handleFullscreenChange(event) { 16350 if (this.player_.isFullscreen()) { 16351 this.controlText('Non-Fullscreen'); 16352 } else { 16353 this.controlText('Fullscreen'); 16354 } 16355 } 16356 /** 16357 * This gets called when an `FullscreenToggle` is "clicked". See 16358 * {@link ClickableComponent} for more detailed information on what a click can be. 16359 * 16360 * @param {EventTarget~Event} [event] 16361 * The `keydown`, `tap`, or `click` event that caused this function to be 16362 * called. 16363 * 16364 * @listens tap 16365 * @listens click 16366 */ 16367 ; 16368 16369 _proto.handleClick = function handleClick(event) { 16370 if (!this.player_.isFullscreen()) { 16371 this.player_.requestFullscreen(); 16372 } else { 16373 this.player_.exitFullscreen(); 16374 } 16375 }; 16376 16377 return FullscreenToggle; 16378 }(Button); 16379 /** 16380 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 16381 * 16382 * @type {string} 16383 * @private 16384 */ 16385 16386 16387 FullscreenToggle.prototype.controlText_ = 'Fullscreen'; 16388 Component.registerComponent('FullscreenToggle', FullscreenToggle); 16389 16390 /** 16391 * Check if volume control is supported and if it isn't hide the 16392 * `Component` that was passed using the `vjs-hidden` class. 16393 * 16394 * @param {Component} self 16395 * The component that should be hidden if volume is unsupported 16396 * 16397 * @param {Player} player 16398 * A reference to the player 16399 * 16400 * @private 16401 */ 16402 var checkVolumeSupport = function checkVolumeSupport(self, player) { 16403 // hide volume controls when they're not supported by the current tech 16404 if (player.tech_ && !player.tech_.featuresVolumeControl) { 16405 self.addClass('vjs-hidden'); 16406 } 16407 16408 self.on(player, 'loadstart', function () { 16409 if (!player.tech_.featuresVolumeControl) { 16410 self.addClass('vjs-hidden'); 16411 } else { 16412 self.removeClass('vjs-hidden'); 16413 } 16414 }); 16415 }; 16416 16417 /** 16418 * Shows volume level 16419 * 16420 * @extends Component 16421 */ 16422 16423 var VolumeLevel = /*#__PURE__*/function (_Component) { 16424 inheritsLoose(VolumeLevel, _Component); 16425 16426 function VolumeLevel() { 16427 return _Component.apply(this, arguments) || this; 16428 } 16429 16430 var _proto = VolumeLevel.prototype; 16431 16432 /** 16433 * Create the `Component`'s DOM element 16434 * 16435 * @return {Element} 16436 * The element that was created. 16437 */ 16438 _proto.createEl = function createEl() { 16439 return _Component.prototype.createEl.call(this, 'div', { 16440 className: 'vjs-volume-level', 16441 innerHTML: '<span class="vjs-control-text"></span>' 16442 }); 16443 }; 16444 16445 return VolumeLevel; 16446 }(Component); 16447 16448 Component.registerComponent('VolumeLevel', VolumeLevel); 16449 16450 /** 16451 * The bar that contains the volume level and can be clicked on to adjust the level 16452 * 16453 * @extends Slider 16454 */ 16455 16456 var VolumeBar = /*#__PURE__*/function (_Slider) { 16457 inheritsLoose(VolumeBar, _Slider); 16458 16459 /** 16460 * Creates an instance of this class. 16461 * 16462 * @param {Player} player 16463 * The `Player` that this class should be attached to. 16464 * 16465 * @param {Object} [options] 16466 * The key/value store of player options. 16467 */ 16468 function VolumeBar(player, options) { 16469 var _this; 16470 16471 _this = _Slider.call(this, player, options) || this; 16472 16473 _this.on('slideractive', _this.updateLastVolume_); 16474 16475 _this.on(player, 'volumechange', _this.updateARIAAttributes); 16476 16477 player.ready(function () { 16478 return _this.updateARIAAttributes(); 16479 }); 16480 return _this; 16481 } 16482 /** 16483 * Create the `Component`'s DOM element 16484 * 16485 * @return {Element} 16486 * The element that was created. 16487 */ 16488 16489 16490 var _proto = VolumeBar.prototype; 16491 16492 _proto.createEl = function createEl() { 16493 return _Slider.prototype.createEl.call(this, 'div', { 16494 className: 'vjs-volume-bar vjs-slider-bar' 16495 }, { 16496 'aria-label': this.localize('Volume Level'), 16497 'aria-live': 'polite' 16498 }); 16499 } 16500 /** 16501 * Handle mouse down on volume bar 16502 * 16503 * @param {EventTarget~Event} event 16504 * The `mousedown` event that caused this to run. 16505 * 16506 * @listens mousedown 16507 */ 16508 ; 16509 16510 _proto.handleMouseDown = function handleMouseDown(event) { 16511 if (!isSingleLeftClick(event)) { 16512 return; 16513 } 16514 16515 _Slider.prototype.handleMouseDown.call(this, event); 16516 } 16517 /** 16518 * Handle movement events on the {@link VolumeMenuButton}. 16519 * 16520 * @param {EventTarget~Event} event 16521 * The event that caused this function to run. 16522 * 16523 * @listens mousemove 16524 */ 16525 ; 16526 16527 _proto.handleMouseMove = function handleMouseMove(event) { 16528 if (!isSingleLeftClick(event)) { 16529 return; 16530 } 16531 16532 this.checkMuted(); 16533 this.player_.volume(this.calculateDistance(event)); 16534 } 16535 /** 16536 * If the player is muted unmute it. 16537 */ 16538 ; 16539 16540 _proto.checkMuted = function checkMuted() { 16541 if (this.player_.muted()) { 16542 this.player_.muted(false); 16543 } 16544 } 16545 /** 16546 * Get percent of volume level 16547 * 16548 * @return {number} 16549 * Volume level percent as a decimal number. 16550 */ 16551 ; 16552 16553 _proto.getPercent = function getPercent() { 16554 if (this.player_.muted()) { 16555 return 0; 16556 } 16557 16558 return this.player_.volume(); 16559 } 16560 /** 16561 * Increase volume level for keyboard users 16562 */ 16563 ; 16564 16565 _proto.stepForward = function stepForward() { 16566 this.checkMuted(); 16567 this.player_.volume(this.player_.volume() + 0.1); 16568 } 16569 /** 16570 * Decrease volume level for keyboard users 16571 */ 16572 ; 16573 16574 _proto.stepBack = function stepBack() { 16575 this.checkMuted(); 16576 this.player_.volume(this.player_.volume() - 0.1); 16577 } 16578 /** 16579 * Update ARIA accessibility attributes 16580 * 16581 * @param {EventTarget~Event} [event] 16582 * The `volumechange` event that caused this function to run. 16583 * 16584 * @listens Player#volumechange 16585 */ 16586 ; 16587 16588 _proto.updateARIAAttributes = function updateARIAAttributes(event) { 16589 var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); 16590 this.el_.setAttribute('aria-valuenow', ariaValue); 16591 this.el_.setAttribute('aria-valuetext', ariaValue + '%'); 16592 } 16593 /** 16594 * Returns the current value of the player volume as a percentage 16595 * 16596 * @private 16597 */ 16598 ; 16599 16600 _proto.volumeAsPercentage_ = function volumeAsPercentage_() { 16601 return Math.round(this.player_.volume() * 100); 16602 } 16603 /** 16604 * When user starts dragging the VolumeBar, store the volume and listen for 16605 * the end of the drag. When the drag ends, if the volume was set to zero, 16606 * set lastVolume to the stored volume. 16607 * 16608 * @listens slideractive 16609 * @private 16610 */ 16611 ; 16612 16613 _proto.updateLastVolume_ = function updateLastVolume_() { 16614 var _this2 = this; 16615 16616 var volumeBeforeDrag = this.player_.volume(); 16617 this.one('sliderinactive', function () { 16618 if (_this2.player_.volume() === 0) { 16619 _this2.player_.lastVolume_(volumeBeforeDrag); 16620 } 16621 }); 16622 }; 16623 16624 return VolumeBar; 16625 }(Slider); 16626 /** 16627 * Default options for the `VolumeBar` 16628 * 16629 * @type {Object} 16630 * @private 16631 */ 16632 16633 16634 VolumeBar.prototype.options_ = { 16635 children: ['volumeLevel'], 16636 barName: 'volumeLevel' 16637 }; 16638 /** 16639 * Call the update event for this Slider when this event happens on the player. 16640 * 16641 * @type {string} 16642 */ 16643 16644 VolumeBar.prototype.playerEvent = 'volumechange'; 16645 Component.registerComponent('VolumeBar', VolumeBar); 16646 16647 /** 16648 * The component for controlling the volume level 16649 * 16650 * @extends Component 16651 */ 16652 16653 var VolumeControl = /*#__PURE__*/function (_Component) { 16654 inheritsLoose(VolumeControl, _Component); 16655 16656 /** 16657 * Creates an instance of this class. 16658 * 16659 * @param {Player} player 16660 * The `Player` that this class should be attached to. 16661 * 16662 * @param {Object} [options={}] 16663 * The key/value store of player options. 16664 */ 16665 function VolumeControl(player, options) { 16666 var _this; 16667 16668 if (options === void 0) { 16669 options = {}; 16670 } 16671 16672 options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if 16673 // the VolumeBar is turned on. 16674 16675 if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { 16676 options.volumeBar = options.volumeBar || {}; 16677 options.volumeBar.vertical = options.vertical; 16678 } 16679 16680 _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing 16681 16682 checkVolumeSupport(assertThisInitialized(_this), player); 16683 _this.throttledHandleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 16684 16685 _this.on('mousedown', _this.handleMouseDown); 16686 16687 _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and 16688 // is dragging) or in focus we do not want to hide the VolumeBar 16689 16690 16691 _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { 16692 _this.volumeBar.addClass('vjs-slider-active'); 16693 16694 _this.addClass('vjs-slider-active'); 16695 16696 _this.trigger('slideractive'); 16697 }); 16698 16699 _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { 16700 _this.volumeBar.removeClass('vjs-slider-active'); 16701 16702 _this.removeClass('vjs-slider-active'); 16703 16704 _this.trigger('sliderinactive'); 16705 }); 16706 16707 return _this; 16708 } 16709 /** 16710 * Create the `Component`'s DOM element 16711 * 16712 * @return {Element} 16713 * The element that was created. 16714 */ 16715 16716 16717 var _proto = VolumeControl.prototype; 16718 16719 _proto.createEl = function createEl() { 16720 var orientationClass = 'vjs-volume-horizontal'; 16721 16722 if (this.options_.vertical) { 16723 orientationClass = 'vjs-volume-vertical'; 16724 } 16725 16726 return _Component.prototype.createEl.call(this, 'div', { 16727 className: "vjs-volume-control vjs-control " + orientationClass 16728 }); 16729 } 16730 /** 16731 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 16732 * 16733 * @param {EventTarget~Event} event 16734 * `mousedown` or `touchstart` event that triggered this function 16735 * 16736 * @listens mousedown 16737 * @listens touchstart 16738 */ 16739 ; 16740 16741 _proto.handleMouseDown = function handleMouseDown(event) { 16742 var doc = this.el_.ownerDocument; 16743 this.on(doc, 'mousemove', this.throttledHandleMouseMove); 16744 this.on(doc, 'touchmove', this.throttledHandleMouseMove); 16745 this.on(doc, 'mouseup', this.handleMouseUp); 16746 this.on(doc, 'touchend', this.handleMouseUp); 16747 } 16748 /** 16749 * Handle `mouseup` or `touchend` events on the `VolumeControl`. 16750 * 16751 * @param {EventTarget~Event} event 16752 * `mouseup` or `touchend` event that triggered this function. 16753 * 16754 * @listens touchend 16755 * @listens mouseup 16756 */ 16757 ; 16758 16759 _proto.handleMouseUp = function handleMouseUp(event) { 16760 var doc = this.el_.ownerDocument; 16761 this.off(doc, 'mousemove', this.throttledHandleMouseMove); 16762 this.off(doc, 'touchmove', this.throttledHandleMouseMove); 16763 this.off(doc, 'mouseup', this.handleMouseUp); 16764 this.off(doc, 'touchend', this.handleMouseUp); 16765 } 16766 /** 16767 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 16768 * 16769 * @param {EventTarget~Event} event 16770 * `mousedown` or `touchstart` event that triggered this function 16771 * 16772 * @listens mousedown 16773 * @listens touchstart 16774 */ 16775 ; 16776 16777 _proto.handleMouseMove = function handleMouseMove(event) { 16778 this.volumeBar.handleMouseMove(event); 16779 }; 16780 16781 return VolumeControl; 16782 }(Component); 16783 /** 16784 * Default options for the `VolumeControl` 16785 * 16786 * @type {Object} 16787 * @private 16788 */ 16789 16790 16791 VolumeControl.prototype.options_ = { 16792 children: ['volumeBar'] 16793 }; 16794 Component.registerComponent('VolumeControl', VolumeControl); 16795 16796 /** 16797 * Check if muting volume is supported and if it isn't hide the mute toggle 16798 * button. 16799 * 16800 * @param {Component} self 16801 * A reference to the mute toggle button 16802 * 16803 * @param {Player} player 16804 * A reference to the player 16805 * 16806 * @private 16807 */ 16808 var checkMuteSupport = function checkMuteSupport(self, player) { 16809 // hide mute toggle button if it's not supported by the current tech 16810 if (player.tech_ && !player.tech_.featuresMuteControl) { 16811 self.addClass('vjs-hidden'); 16812 } 16813 16814 self.on(player, 'loadstart', function () { 16815 if (!player.tech_.featuresMuteControl) { 16816 self.addClass('vjs-hidden'); 16817 } else { 16818 self.removeClass('vjs-hidden'); 16819 } 16820 }); 16821 }; 16822 16823 /** 16824 * A button component for muting the audio. 16825 * 16826 * @extends Button 16827 */ 16828 16829 var MuteToggle = /*#__PURE__*/function (_Button) { 16830 inheritsLoose(MuteToggle, _Button); 16831 16832 /** 16833 * Creates an instance of this class. 16834 * 16835 * @param {Player} player 16836 * The `Player` that this class should be attached to. 16837 * 16838 * @param {Object} [options] 16839 * The key/value store of player options. 16840 */ 16841 function MuteToggle(player, options) { 16842 var _this; 16843 16844 _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing 16845 16846 checkMuteSupport(assertThisInitialized(_this), player); 16847 16848 _this.on(player, ['loadstart', 'volumechange'], _this.update); 16849 16850 return _this; 16851 } 16852 /** 16853 * Builds the default DOM `className`. 16854 * 16855 * @return {string} 16856 * The DOM `className` for this object. 16857 */ 16858 16859 16860 var _proto = MuteToggle.prototype; 16861 16862 _proto.buildCSSClass = function buildCSSClass() { 16863 return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this); 16864 } 16865 /** 16866 * This gets called when an `MuteToggle` is "clicked". See 16867 * {@link ClickableComponent} for more detailed information on what a click can be. 16868 * 16869 * @param {EventTarget~Event} [event] 16870 * The `keydown`, `tap`, or `click` event that caused this function to be 16871 * called. 16872 * 16873 * @listens tap 16874 * @listens click 16875 */ 16876 ; 16877 16878 _proto.handleClick = function handleClick(event) { 16879 var vol = this.player_.volume(); 16880 var lastVolume = this.player_.lastVolume_(); 16881 16882 if (vol === 0) { 16883 var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; 16884 this.player_.volume(volumeToSet); 16885 this.player_.muted(false); 16886 } else { 16887 this.player_.muted(this.player_.muted() ? false : true); 16888 } 16889 } 16890 /** 16891 * Update the `MuteToggle` button based on the state of `volume` and `muted` 16892 * on the player. 16893 * 16894 * @param {EventTarget~Event} [event] 16895 * The {@link Player#loadstart} event if this function was called 16896 * through an event. 16897 * 16898 * @listens Player#loadstart 16899 * @listens Player#volumechange 16900 */ 16901 ; 16902 16903 _proto.update = function update(event) { 16904 this.updateIcon_(); 16905 this.updateControlText_(); 16906 } 16907 /** 16908 * Update the appearance of the `MuteToggle` icon. 16909 * 16910 * Possible states (given `level` variable below): 16911 * - 0: crossed out 16912 * - 1: zero bars of volume 16913 * - 2: one bar of volume 16914 * - 3: two bars of volume 16915 * 16916 * @private 16917 */ 16918 ; 16919 16920 _proto.updateIcon_ = function updateIcon_() { 16921 var vol = this.player_.volume(); 16922 var level = 3; // in iOS when a player is loaded with muted attribute 16923 // and volume is changed with a native mute button 16924 // we want to make sure muted state is updated 16925 16926 if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) { 16927 this.player_.muted(this.player_.tech_.el_.muted); 16928 } 16929 16930 if (vol === 0 || this.player_.muted()) { 16931 level = 0; 16932 } else if (vol < 0.33) { 16933 level = 1; 16934 } else if (vol < 0.67) { 16935 level = 2; 16936 } // TODO improve muted icon classes 16937 16938 16939 for (var i = 0; i < 4; i++) { 16940 removeClass(this.el_, "vjs-vol-" + i); 16941 } 16942 16943 addClass(this.el_, "vjs-vol-" + level); 16944 } 16945 /** 16946 * If `muted` has changed on the player, update the control text 16947 * (`title` attribute on `vjs-mute-control` element and content of 16948 * `vjs-control-text` element). 16949 * 16950 * @private 16951 */ 16952 ; 16953 16954 _proto.updateControlText_ = function updateControlText_() { 16955 var soundOff = this.player_.muted() || this.player_.volume() === 0; 16956 var text = soundOff ? 'Unmute' : 'Mute'; 16957 16958 if (this.controlText() !== text) { 16959 this.controlText(text); 16960 } 16961 }; 16962 16963 return MuteToggle; 16964 }(Button); 16965 /** 16966 * The text that should display over the `MuteToggle`s controls. Added for localization. 16967 * 16968 * @type {string} 16969 * @private 16970 */ 16971 16972 16973 MuteToggle.prototype.controlText_ = 'Mute'; 16974 Component.registerComponent('MuteToggle', MuteToggle); 16975 16976 /** 16977 * A Component to contain the MuteToggle and VolumeControl so that 16978 * they can work together. 16979 * 16980 * @extends Component 16981 */ 16982 16983 var VolumePanel = /*#__PURE__*/function (_Component) { 16984 inheritsLoose(VolumePanel, _Component); 16985 16986 /** 16987 * Creates an instance of this class. 16988 * 16989 * @param {Player} player 16990 * The `Player` that this class should be attached to. 16991 * 16992 * @param {Object} [options={}] 16993 * The key/value store of player options. 16994 */ 16995 function VolumePanel(player, options) { 16996 var _this; 16997 16998 if (options === void 0) { 16999 options = {}; 17000 } 17001 17002 if (typeof options.inline !== 'undefined') { 17003 options.inline = options.inline; 17004 } else { 17005 options.inline = true; 17006 } // pass the inline option down to the VolumeControl as vertical if 17007 // the VolumeControl is on. 17008 17009 17010 if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { 17011 options.volumeControl = options.volumeControl || {}; 17012 options.volumeControl.vertical = !options.inline; 17013 } 17014 17015 _this = _Component.call(this, player, options) || this; 17016 17017 _this.on(player, ['loadstart'], _this.volumePanelState_); 17018 17019 _this.on(_this.muteToggle, 'keyup', _this.handleKeyPress); 17020 17021 _this.on(_this.volumeControl, 'keyup', _this.handleVolumeControlKeyUp); 17022 17023 _this.on('keydown', _this.handleKeyPress); 17024 17025 _this.on('mouseover', _this.handleMouseOver); 17026 17027 _this.on('mouseout', _this.handleMouseOut); // while the slider is active (the mouse has been pressed down and 17028 // is dragging) we do not want to hide the VolumeBar 17029 17030 17031 _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); 17032 17033 _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); 17034 17035 return _this; 17036 } 17037 /** 17038 * Add vjs-slider-active class to the VolumePanel 17039 * 17040 * @listens VolumeControl#slideractive 17041 * @private 17042 */ 17043 17044 17045 var _proto = VolumePanel.prototype; 17046 17047 _proto.sliderActive_ = function sliderActive_() { 17048 this.addClass('vjs-slider-active'); 17049 } 17050 /** 17051 * Removes vjs-slider-active class to the VolumePanel 17052 * 17053 * @listens VolumeControl#sliderinactive 17054 * @private 17055 */ 17056 ; 17057 17058 _proto.sliderInactive_ = function sliderInactive_() { 17059 this.removeClass('vjs-slider-active'); 17060 } 17061 /** 17062 * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel 17063 * depending on MuteToggle and VolumeControl state 17064 * 17065 * @listens Player#loadstart 17066 * @private 17067 */ 17068 ; 17069 17070 _proto.volumePanelState_ = function volumePanelState_() { 17071 // hide volume panel if neither volume control or mute toggle 17072 // are displayed 17073 if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) { 17074 this.addClass('vjs-hidden'); 17075 } // if only mute toggle is visible we don't want 17076 // volume panel expanding when hovered or active 17077 17078 17079 if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) { 17080 this.addClass('vjs-mute-toggle-only'); 17081 } 17082 } 17083 /** 17084 * Create the `Component`'s DOM element 17085 * 17086 * @return {Element} 17087 * The element that was created. 17088 */ 17089 ; 17090 17091 _proto.createEl = function createEl() { 17092 var orientationClass = 'vjs-volume-panel-horizontal'; 17093 17094 if (!this.options_.inline) { 17095 orientationClass = 'vjs-volume-panel-vertical'; 17096 } 17097 17098 return _Component.prototype.createEl.call(this, 'div', { 17099 className: "vjs-volume-panel vjs-control " + orientationClass 17100 }); 17101 } 17102 /** 17103 * Dispose of the `volume-panel` and all child components. 17104 */ 17105 ; 17106 17107 _proto.dispose = function dispose() { 17108 this.handleMouseOut(); 17109 17110 _Component.prototype.dispose.call(this); 17111 } 17112 /** 17113 * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes 17114 * the volume panel and sets focus on `MuteToggle`. 17115 * 17116 * @param {EventTarget~Event} event 17117 * The `keyup` event that caused this function to be called. 17118 * 17119 * @listens keyup 17120 */ 17121 ; 17122 17123 _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) { 17124 if (keycode.isEventKey(event, 'Esc')) { 17125 this.muteToggle.focus(); 17126 } 17127 } 17128 /** 17129 * This gets called when a `VolumePanel` gains hover via a `mouseover` event. 17130 * Turns on listening for `mouseover` event. When they happen it 17131 * calls `this.handleMouseOver`. 17132 * 17133 * @param {EventTarget~Event} event 17134 * The `mouseover` event that caused this function to be called. 17135 * 17136 * @listens mouseover 17137 */ 17138 ; 17139 17140 _proto.handleMouseOver = function handleMouseOver(event) { 17141 this.addClass('vjs-hover'); 17142 on(document, 'keyup', bind(this, this.handleKeyPress)); 17143 } 17144 /** 17145 * This gets called when a `VolumePanel` gains hover via a `mouseout` event. 17146 * Turns on listening for `mouseout` event. When they happen it 17147 * calls `this.handleMouseOut`. 17148 * 17149 * @param {EventTarget~Event} event 17150 * The `mouseout` event that caused this function to be called. 17151 * 17152 * @listens mouseout 17153 */ 17154 ; 17155 17156 _proto.handleMouseOut = function handleMouseOut(event) { 17157 this.removeClass('vjs-hover'); 17158 off(document, 'keyup', bind(this, this.handleKeyPress)); 17159 } 17160 /** 17161 * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`, 17162 * looking for ESC, which hides the `VolumeControl`. 17163 * 17164 * @param {EventTarget~Event} event 17165 * The keypress that triggered this event. 17166 * 17167 * @listens keydown | keyup 17168 */ 17169 ; 17170 17171 _proto.handleKeyPress = function handleKeyPress(event) { 17172 if (keycode.isEventKey(event, 'Esc')) { 17173 this.handleMouseOut(); 17174 } 17175 }; 17176 17177 return VolumePanel; 17178 }(Component); 17179 /** 17180 * Default options for the `VolumeControl` 17181 * 17182 * @type {Object} 17183 * @private 17184 */ 17185 17186 17187 VolumePanel.prototype.options_ = { 17188 children: ['muteToggle', 'volumeControl'] 17189 }; 17190 Component.registerComponent('VolumePanel', VolumePanel); 17191 17192 /** 17193 * The Menu component is used to build popup menus, including subtitle and 17194 * captions selection menus. 17195 * 17196 * @extends Component 17197 */ 17198 17199 var Menu = /*#__PURE__*/function (_Component) { 17200 inheritsLoose(Menu, _Component); 17201 17202 /** 17203 * Create an instance of this class. 17204 * 17205 * @param {Player} player 17206 * the player that this component should attach to 17207 * 17208 * @param {Object} [options] 17209 * Object of option names and values 17210 * 17211 */ 17212 function Menu(player, options) { 17213 var _this; 17214 17215 _this = _Component.call(this, player, options) || this; 17216 17217 if (options) { 17218 _this.menuButton_ = options.menuButton; 17219 } 17220 17221 _this.focusedChild_ = -1; 17222 17223 _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container. 17224 17225 17226 _this.boundHandleBlur_ = bind(assertThisInitialized(_this), _this.handleBlur); 17227 _this.boundHandleTapClick_ = bind(assertThisInitialized(_this), _this.handleTapClick); 17228 return _this; 17229 } 17230 /** 17231 * Add event listeners to the {@link MenuItem}. 17232 * 17233 * @param {Object} component 17234 * The instance of the `MenuItem` to add listeners to. 17235 * 17236 */ 17237 17238 17239 var _proto = Menu.prototype; 17240 17241 _proto.addEventListenerForItem = function addEventListenerForItem(component) { 17242 if (!(component instanceof Component)) { 17243 return; 17244 } 17245 17246 this.on(component, 'blur', this.boundHandleBlur_); 17247 this.on(component, ['tap', 'click'], this.boundHandleTapClick_); 17248 } 17249 /** 17250 * Remove event listeners from the {@link MenuItem}. 17251 * 17252 * @param {Object} component 17253 * The instance of the `MenuItem` to remove listeners. 17254 * 17255 */ 17256 ; 17257 17258 _proto.removeEventListenerForItem = function removeEventListenerForItem(component) { 17259 if (!(component instanceof Component)) { 17260 return; 17261 } 17262 17263 this.off(component, 'blur', this.boundHandleBlur_); 17264 this.off(component, ['tap', 'click'], this.boundHandleTapClick_); 17265 } 17266 /** 17267 * This method will be called indirectly when the component has been added 17268 * before the component adds to the new menu instance by `addItem`. 17269 * In this case, the original menu instance will remove the component 17270 * by calling `removeChild`. 17271 * 17272 * @param {Object} component 17273 * The instance of the `MenuItem` 17274 */ 17275 ; 17276 17277 _proto.removeChild = function removeChild(component) { 17278 if (typeof component === 'string') { 17279 component = this.getChild(component); 17280 } 17281 17282 this.removeEventListenerForItem(component); 17283 17284 _Component.prototype.removeChild.call(this, component); 17285 } 17286 /** 17287 * Add a {@link MenuItem} to the menu. 17288 * 17289 * @param {Object|string} component 17290 * The name or instance of the `MenuItem` to add. 17291 * 17292 */ 17293 ; 17294 17295 _proto.addItem = function addItem(component) { 17296 var childComponent = this.addChild(component); 17297 17298 if (childComponent) { 17299 this.addEventListenerForItem(childComponent); 17300 } 17301 } 17302 /** 17303 * Create the `Menu`s DOM element. 17304 * 17305 * @return {Element} 17306 * the element that was created 17307 */ 17308 ; 17309 17310 _proto.createEl = function createEl$1() { 17311 var contentElType = this.options_.contentElType || 'ul'; 17312 this.contentEl_ = createEl(contentElType, { 17313 className: 'vjs-menu-content' 17314 }); 17315 this.contentEl_.setAttribute('role', 'menu'); 17316 17317 var el = _Component.prototype.createEl.call(this, 'div', { 17318 append: this.contentEl_, 17319 className: 'vjs-menu' 17320 }); 17321 17322 el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons, 17323 // where a click on the parent is significant 17324 17325 on(el, 'click', function (event) { 17326 event.preventDefault(); 17327 event.stopImmediatePropagation(); 17328 }); 17329 return el; 17330 }; 17331 17332 _proto.dispose = function dispose() { 17333 this.contentEl_ = null; 17334 this.boundHandleBlur_ = null; 17335 this.boundHandleTapClick_ = null; 17336 17337 _Component.prototype.dispose.call(this); 17338 } 17339 /** 17340 * Called when a `MenuItem` loses focus. 17341 * 17342 * @param {EventTarget~Event} event 17343 * The `blur` event that caused this function to be called. 17344 * 17345 * @listens blur 17346 */ 17347 ; 17348 17349 _proto.handleBlur = function handleBlur(event) { 17350 var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu 17351 17352 if (!this.children().some(function (element) { 17353 return element.el() === relatedTarget; 17354 })) { 17355 var btn = this.menuButton_; 17356 17357 if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) { 17358 btn.unpressButton(); 17359 } 17360 } 17361 } 17362 /** 17363 * Called when a `MenuItem` gets clicked or tapped. 17364 * 17365 * @param {EventTarget~Event} event 17366 * The `click` or `tap` event that caused this function to be called. 17367 * 17368 * @listens click,tap 17369 */ 17370 ; 17371 17372 _proto.handleTapClick = function handleTapClick(event) { 17373 // Unpress the associated MenuButton, and move focus back to it 17374 if (this.menuButton_) { 17375 this.menuButton_.unpressButton(); 17376 var childComponents = this.children(); 17377 17378 if (!Array.isArray(childComponents)) { 17379 return; 17380 } 17381 17382 var foundComponent = childComponents.filter(function (component) { 17383 return component.el() === event.target; 17384 })[0]; 17385 17386 if (!foundComponent) { 17387 return; 17388 } // don't focus menu button if item is a caption settings item 17389 // because focus will move elsewhere 17390 17391 17392 if (foundComponent.name() !== 'CaptionSettingsMenuItem') { 17393 this.menuButton_.focus(); 17394 } 17395 } 17396 } 17397 /** 17398 * Handle a `keydown` event on this menu. This listener is added in the constructor. 17399 * 17400 * @param {EventTarget~Event} event 17401 * A `keydown` event that happened on the menu. 17402 * 17403 * @listens keydown 17404 */ 17405 ; 17406 17407 _proto.handleKeyDown = function handleKeyDown(event) { 17408 // Left and Down Arrows 17409 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 17410 event.preventDefault(); 17411 event.stopPropagation(); 17412 this.stepForward(); // Up and Right Arrows 17413 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 17414 event.preventDefault(); 17415 event.stopPropagation(); 17416 this.stepBack(); 17417 } 17418 } 17419 /** 17420 * Move to next (lower) menu item for keyboard users. 17421 */ 17422 ; 17423 17424 _proto.stepForward = function stepForward() { 17425 var stepChild = 0; 17426 17427 if (this.focusedChild_ !== undefined) { 17428 stepChild = this.focusedChild_ + 1; 17429 } 17430 17431 this.focus(stepChild); 17432 } 17433 /** 17434 * Move to previous (higher) menu item for keyboard users. 17435 */ 17436 ; 17437 17438 _proto.stepBack = function stepBack() { 17439 var stepChild = 0; 17440 17441 if (this.focusedChild_ !== undefined) { 17442 stepChild = this.focusedChild_ - 1; 17443 } 17444 17445 this.focus(stepChild); 17446 } 17447 /** 17448 * Set focus on a {@link MenuItem} in the `Menu`. 17449 * 17450 * @param {Object|string} [item=0] 17451 * Index of child item set focus on. 17452 */ 17453 ; 17454 17455 _proto.focus = function focus(item) { 17456 if (item === void 0) { 17457 item = 0; 17458 } 17459 17460 var children = this.children().slice(); 17461 var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); 17462 17463 if (haveTitle) { 17464 children.shift(); 17465 } 17466 17467 if (children.length > 0) { 17468 if (item < 0) { 17469 item = 0; 17470 } else if (item >= children.length) { 17471 item = children.length - 1; 17472 } 17473 17474 this.focusedChild_ = item; 17475 children[item].el_.focus(); 17476 } 17477 }; 17478 17479 return Menu; 17480 }(Component); 17481 17482 Component.registerComponent('Menu', Menu); 17483 17484 /** 17485 * A `MenuButton` class for any popup {@link Menu}. 17486 * 17487 * @extends Component 17488 */ 17489 17490 var MenuButton = /*#__PURE__*/function (_Component) { 17491 inheritsLoose(MenuButton, _Component); 17492 17493 /** 17494 * Creates an instance of this class. 17495 * 17496 * @param {Player} player 17497 * The `Player` that this class should be attached to. 17498 * 17499 * @param {Object} [options={}] 17500 * The key/value store of player options. 17501 */ 17502 function MenuButton(player, options) { 17503 var _this; 17504 17505 if (options === void 0) { 17506 options = {}; 17507 } 17508 17509 _this = _Component.call(this, player, options) || this; 17510 _this.menuButton_ = new Button(player, options); 17511 17512 _this.menuButton_.controlText(_this.controlText_); 17513 17514 _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper 17515 17516 17517 var buttonClass = Button.prototype.buildCSSClass(); 17518 _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; 17519 17520 _this.menuButton_.removeClass('vjs-control'); 17521 17522 _this.addChild(_this.menuButton_); 17523 17524 _this.update(); 17525 17526 _this.enabled_ = true; 17527 17528 _this.on(_this.menuButton_, 'tap', _this.handleClick); 17529 17530 _this.on(_this.menuButton_, 'click', _this.handleClick); 17531 17532 _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown); 17533 17534 _this.on(_this.menuButton_, 'mouseenter', function () { 17535 _this.addClass('vjs-hover'); 17536 17537 _this.menu.show(); 17538 17539 on(document, 'keyup', bind(assertThisInitialized(_this), _this.handleMenuKeyUp)); 17540 }); 17541 17542 _this.on('mouseleave', _this.handleMouseLeave); 17543 17544 _this.on('keydown', _this.handleSubmenuKeyDown); 17545 17546 return _this; 17547 } 17548 /** 17549 * Update the menu based on the current state of its items. 17550 */ 17551 17552 17553 var _proto = MenuButton.prototype; 17554 17555 _proto.update = function update() { 17556 var menu = this.createMenu(); 17557 17558 if (this.menu) { 17559 this.menu.dispose(); 17560 this.removeChild(this.menu); 17561 } 17562 17563 this.menu = menu; 17564 this.addChild(menu); 17565 /** 17566 * Track the state of the menu button 17567 * 17568 * @type {Boolean} 17569 * @private 17570 */ 17571 17572 this.buttonPressed_ = false; 17573 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 17574 17575 if (this.items && this.items.length <= this.hideThreshold_) { 17576 this.hide(); 17577 } else { 17578 this.show(); 17579 } 17580 } 17581 /** 17582 * Create the menu and add all items to it. 17583 * 17584 * @return {Menu} 17585 * The constructed menu 17586 */ 17587 ; 17588 17589 _proto.createMenu = function createMenu() { 17590 var menu = new Menu(this.player_, { 17591 menuButton: this 17592 }); 17593 /** 17594 * Hide the menu if the number of items is less than or equal to this threshold. This defaults 17595 * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list 17596 * it here because every time we run `createMenu` we need to reset the value. 17597 * 17598 * @protected 17599 * @type {Number} 17600 */ 17601 17602 this.hideThreshold_ = 0; // Add a title list item to the top 17603 17604 if (this.options_.title) { 17605 var titleEl = createEl('li', { 17606 className: 'vjs-menu-title', 17607 innerHTML: toTitleCase(this.options_.title), 17608 tabIndex: -1 17609 }); 17610 this.hideThreshold_ += 1; 17611 var titleComponent = new Component(this.player_, { 17612 el: titleEl 17613 }); 17614 menu.addItem(titleComponent); 17615 } 17616 17617 this.items = this.createItems(); 17618 17619 if (this.items) { 17620 // Add menu items to the menu 17621 for (var i = 0; i < this.items.length; i++) { 17622 menu.addItem(this.items[i]); 17623 } 17624 } 17625 17626 return menu; 17627 } 17628 /** 17629 * Create the list of menu items. Specific to each subclass. 17630 * 17631 * @abstract 17632 */ 17633 ; 17634 17635 _proto.createItems = function createItems() {} 17636 /** 17637 * Create the `MenuButtons`s DOM element. 17638 * 17639 * @return {Element} 17640 * The element that gets created. 17641 */ 17642 ; 17643 17644 _proto.createEl = function createEl() { 17645 return _Component.prototype.createEl.call(this, 'div', { 17646 className: this.buildWrapperCSSClass() 17647 }, {}); 17648 } 17649 /** 17650 * Allow sub components to stack CSS class names for the wrapper element 17651 * 17652 * @return {string} 17653 * The constructed wrapper DOM `className` 17654 */ 17655 ; 17656 17657 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 17658 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 17659 17660 if (this.options_.inline === true) { 17661 menuButtonClass += '-inline'; 17662 } else { 17663 menuButtonClass += '-popup'; 17664 } // TODO: Fix the CSS so that this isn't necessary 17665 17666 17667 var buttonClass = Button.prototype.buildCSSClass(); 17668 return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this); 17669 } 17670 /** 17671 * Builds the default DOM `className`. 17672 * 17673 * @return {string} 17674 * The DOM `className` for this object. 17675 */ 17676 ; 17677 17678 _proto.buildCSSClass = function buildCSSClass() { 17679 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 17680 17681 if (this.options_.inline === true) { 17682 menuButtonClass += '-inline'; 17683 } else { 17684 menuButtonClass += '-popup'; 17685 } 17686 17687 return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this); 17688 } 17689 /** 17690 * Get or set the localized control text that will be used for accessibility. 17691 * 17692 * > NOTE: This will come from the internal `menuButton_` element. 17693 * 17694 * @param {string} [text] 17695 * Control text for element. 17696 * 17697 * @param {Element} [el=this.menuButton_.el()] 17698 * Element to set the title on. 17699 * 17700 * @return {string} 17701 * - The control text when getting 17702 */ 17703 ; 17704 17705 _proto.controlText = function controlText(text, el) { 17706 if (el === void 0) { 17707 el = this.menuButton_.el(); 17708 } 17709 17710 return this.menuButton_.controlText(text, el); 17711 } 17712 /** 17713 * Dispose of the `menu-button` and all child components. 17714 */ 17715 ; 17716 17717 _proto.dispose = function dispose() { 17718 this.handleMouseLeave(); 17719 17720 _Component.prototype.dispose.call(this); 17721 } 17722 /** 17723 * Handle a click on a `MenuButton`. 17724 * See {@link ClickableComponent#handleClick} for instances where this is called. 17725 * 17726 * @param {EventTarget~Event} event 17727 * The `keydown`, `tap`, or `click` event that caused this function to be 17728 * called. 17729 * 17730 * @listens tap 17731 * @listens click 17732 */ 17733 ; 17734 17735 _proto.handleClick = function handleClick(event) { 17736 if (this.buttonPressed_) { 17737 this.unpressButton(); 17738 } else { 17739 this.pressButton(); 17740 } 17741 } 17742 /** 17743 * Handle `mouseleave` for `MenuButton`. 17744 * 17745 * @param {EventTarget~Event} event 17746 * The `mouseleave` event that caused this function to be called. 17747 * 17748 * @listens mouseleave 17749 */ 17750 ; 17751 17752 _proto.handleMouseLeave = function handleMouseLeave(event) { 17753 this.removeClass('vjs-hover'); 17754 off(document, 'keyup', bind(this, this.handleMenuKeyUp)); 17755 } 17756 /** 17757 * Set the focus to the actual button, not to this element 17758 */ 17759 ; 17760 17761 _proto.focus = function focus() { 17762 this.menuButton_.focus(); 17763 } 17764 /** 17765 * Remove the focus from the actual button, not this element 17766 */ 17767 ; 17768 17769 _proto.blur = function blur() { 17770 this.menuButton_.blur(); 17771 } 17772 /** 17773 * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See 17774 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 17775 * 17776 * @param {EventTarget~Event} event 17777 * The `keydown` event that caused this function to be called. 17778 * 17779 * @listens keydown 17780 */ 17781 ; 17782 17783 _proto.handleKeyDown = function handleKeyDown(event) { 17784 // Escape or Tab unpress the 'button' 17785 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17786 if (this.buttonPressed_) { 17787 this.unpressButton(); 17788 } // Don't preventDefault for Tab key - we still want to lose focus 17789 17790 17791 if (!keycode.isEventKey(event, 'Tab')) { 17792 event.preventDefault(); // Set focus back to the menu button's button 17793 17794 this.menuButton_.focus(); 17795 } // Up Arrow or Down Arrow also 'press' the button to open the menu 17796 17797 } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) { 17798 if (!this.buttonPressed_) { 17799 event.preventDefault(); 17800 this.pressButton(); 17801 } 17802 } 17803 } 17804 /** 17805 * Handle a `keyup` event on a `MenuButton`. The listener for this is added in 17806 * the constructor. 17807 * 17808 * @param {EventTarget~Event} event 17809 * Key press event 17810 * 17811 * @listens keyup 17812 */ 17813 ; 17814 17815 _proto.handleMenuKeyUp = function handleMenuKeyUp(event) { 17816 // Escape hides popup menu 17817 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17818 this.removeClass('vjs-hover'); 17819 } 17820 } 17821 /** 17822 * This method name now delegates to `handleSubmenuKeyDown`. This means 17823 * anyone calling `handleSubmenuKeyPress` will not see their method calls 17824 * stop working. 17825 * 17826 * @param {EventTarget~Event} event 17827 * The event that caused this function to be called. 17828 */ 17829 ; 17830 17831 _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { 17832 this.handleSubmenuKeyDown(event); 17833 } 17834 /** 17835 * Handle a `keydown` event on a sub-menu. The listener for this is added in 17836 * the constructor. 17837 * 17838 * @param {EventTarget~Event} event 17839 * Key press event 17840 * 17841 * @listens keydown 17842 */ 17843 ; 17844 17845 _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) { 17846 // Escape or Tab unpress the 'button' 17847 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17848 if (this.buttonPressed_) { 17849 this.unpressButton(); 17850 } // Don't preventDefault for Tab key - we still want to lose focus 17851 17852 17853 if (!keycode.isEventKey(event, 'Tab')) { 17854 event.preventDefault(); // Set focus back to the menu button's button 17855 17856 this.menuButton_.focus(); 17857 } 17858 } 17859 } 17860 /** 17861 * Put the current `MenuButton` into a pressed state. 17862 */ 17863 ; 17864 17865 _proto.pressButton = function pressButton() { 17866 if (this.enabled_) { 17867 this.buttonPressed_ = true; 17868 this.menu.show(); 17869 this.menu.lockShowing(); 17870 this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in 17871 // undesired scrolling behavior when the player is in an iframe 17872 17873 if (IS_IOS && isInFrame()) { 17874 // Return early so that the menu isn't focused 17875 return; 17876 } 17877 17878 this.menu.focus(); 17879 } 17880 } 17881 /** 17882 * Take the current `MenuButton` out of a pressed state. 17883 */ 17884 ; 17885 17886 _proto.unpressButton = function unpressButton() { 17887 if (this.enabled_) { 17888 this.buttonPressed_ = false; 17889 this.menu.unlockShowing(); 17890 this.menu.hide(); 17891 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 17892 } 17893 } 17894 /** 17895 * Disable the `MenuButton`. Don't allow it to be clicked. 17896 */ 17897 ; 17898 17899 _proto.disable = function disable() { 17900 this.unpressButton(); 17901 this.enabled_ = false; 17902 this.addClass('vjs-disabled'); 17903 this.menuButton_.disable(); 17904 } 17905 /** 17906 * Enable the `MenuButton`. Allow it to be clicked. 17907 */ 17908 ; 17909 17910 _proto.enable = function enable() { 17911 this.enabled_ = true; 17912 this.removeClass('vjs-disabled'); 17913 this.menuButton_.enable(); 17914 }; 17915 17916 return MenuButton; 17917 }(Component); 17918 17919 Component.registerComponent('MenuButton', MenuButton); 17920 17921 /** 17922 * The base class for buttons that toggle specific track types (e.g. subtitles). 17923 * 17924 * @extends MenuButton 17925 */ 17926 17927 var TrackButton = /*#__PURE__*/function (_MenuButton) { 17928 inheritsLoose(TrackButton, _MenuButton); 17929 17930 /** 17931 * Creates an instance of this class. 17932 * 17933 * @param {Player} player 17934 * The `Player` that this class should be attached to. 17935 * 17936 * @param {Object} [options] 17937 * The key/value store of player options. 17938 */ 17939 function TrackButton(player, options) { 17940 var _this; 17941 17942 var tracks = options.tracks; 17943 _this = _MenuButton.call(this, player, options) || this; 17944 17945 if (_this.items.length <= 1) { 17946 _this.hide(); 17947 } 17948 17949 if (!tracks) { 17950 return assertThisInitialized(_this); 17951 } 17952 17953 var updateHandler = bind(assertThisInitialized(_this), _this.update); 17954 tracks.addEventListener('removetrack', updateHandler); 17955 tracks.addEventListener('addtrack', updateHandler); 17956 17957 _this.player_.on('ready', updateHandler); 17958 17959 _this.player_.on('dispose', function () { 17960 tracks.removeEventListener('removetrack', updateHandler); 17961 tracks.removeEventListener('addtrack', updateHandler); 17962 }); 17963 17964 return _this; 17965 } 17966 17967 return TrackButton; 17968 }(MenuButton); 17969 17970 Component.registerComponent('TrackButton', TrackButton); 17971 17972 /** 17973 * @file menu-keys.js 17974 */ 17975 17976 /** 17977 * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`) 17978 * Note that 'Enter' and 'Space' are not included here (otherwise they would 17979 * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable) 17980 * @typedef MenuKeys 17981 * @array 17982 */ 17983 var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left']; 17984 17985 /** 17986 * The component for a menu item. `<li>` 17987 * 17988 * @extends ClickableComponent 17989 */ 17990 17991 var MenuItem = /*#__PURE__*/function (_ClickableComponent) { 17992 inheritsLoose(MenuItem, _ClickableComponent); 17993 17994 /** 17995 * Creates an instance of the this class. 17996 * 17997 * @param {Player} player 17998 * The `Player` that this class should be attached to. 17999 * 18000 * @param {Object} [options={}] 18001 * The key/value store of player options. 18002 * 18003 */ 18004 function MenuItem(player, options) { 18005 var _this; 18006 18007 _this = _ClickableComponent.call(this, player, options) || this; 18008 _this.selectable = options.selectable; 18009 _this.isSelected_ = options.selected || false; 18010 _this.multiSelectable = options.multiSelectable; 18011 18012 _this.selected(_this.isSelected_); 18013 18014 if (_this.selectable) { 18015 if (_this.multiSelectable) { 18016 _this.el_.setAttribute('role', 'menuitemcheckbox'); 18017 } else { 18018 _this.el_.setAttribute('role', 'menuitemradio'); 18019 } 18020 } else { 18021 _this.el_.setAttribute('role', 'menuitem'); 18022 } 18023 18024 return _this; 18025 } 18026 /** 18027 * Create the `MenuItem's DOM element 18028 * 18029 * @param {string} [type=li] 18030 * Element's node type, not actually used, always set to `li`. 18031 * 18032 * @param {Object} [props={}] 18033 * An object of properties that should be set on the element 18034 * 18035 * @param {Object} [attrs={}] 18036 * An object of attributes that should be set on the element 18037 * 18038 * @return {Element} 18039 * The element that gets created. 18040 */ 18041 18042 18043 var _proto = MenuItem.prototype; 18044 18045 _proto.createEl = function createEl(type, props, attrs) { 18046 // The control is textual, not just an icon 18047 this.nonIconControl = true; 18048 return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ 18049 className: 'vjs-menu-item', 18050 innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>", 18051 tabIndex: -1 18052 }, props), attrs); 18053 } 18054 /** 18055 * Ignore keys which are used by the menu, but pass any other ones up. See 18056 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 18057 * 18058 * @param {EventTarget~Event} event 18059 * The `keydown` event that caused this function to be called. 18060 * 18061 * @listens keydown 18062 */ 18063 ; 18064 18065 _proto.handleKeyDown = function handleKeyDown(event) { 18066 if (!MenuKeys.some(function (key) { 18067 return keycode.isEventKey(event, key); 18068 })) { 18069 // Pass keydown handling up for unused keys 18070 _ClickableComponent.prototype.handleKeyDown.call(this, event); 18071 } 18072 } 18073 /** 18074 * Any click on a `MenuItem` puts it into the selected state. 18075 * See {@link ClickableComponent#handleClick} for instances where this is called. 18076 * 18077 * @param {EventTarget~Event} event 18078 * The `keydown`, `tap`, or `click` event that caused this function to be 18079 * called. 18080 * 18081 * @listens tap 18082 * @listens click 18083 */ 18084 ; 18085 18086 _proto.handleClick = function handleClick(event) { 18087 this.selected(true); 18088 } 18089 /** 18090 * Set the state for this menu item as selected or not. 18091 * 18092 * @param {boolean} selected 18093 * if the menu item is selected or not 18094 */ 18095 ; 18096 18097 _proto.selected = function selected(_selected) { 18098 if (this.selectable) { 18099 if (_selected) { 18100 this.addClass('vjs-selected'); 18101 this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers, 18102 // so indicate selected state to screen reader in the control text. 18103 18104 this.controlText(', selected'); 18105 this.isSelected_ = true; 18106 } else { 18107 this.removeClass('vjs-selected'); 18108 this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader 18109 18110 this.controlText(''); 18111 this.isSelected_ = false; 18112 } 18113 } 18114 }; 18115 18116 return MenuItem; 18117 }(ClickableComponent); 18118 18119 Component.registerComponent('MenuItem', MenuItem); 18120 18121 /** 18122 * The specific menu item type for selecting a language within a text track kind 18123 * 18124 * @extends MenuItem 18125 */ 18126 18127 var TextTrackMenuItem = /*#__PURE__*/function (_MenuItem) { 18128 inheritsLoose(TextTrackMenuItem, _MenuItem); 18129 18130 /** 18131 * Creates an instance of this class. 18132 * 18133 * @param {Player} player 18134 * The `Player` that this class should be attached to. 18135 * 18136 * @param {Object} [options] 18137 * The key/value store of player options. 18138 */ 18139 function TextTrackMenuItem(player, options) { 18140 var _this; 18141 18142 var track = options.track; 18143 var tracks = player.textTracks(); // Modify options for parent MenuItem class's init. 18144 18145 options.label = track.label || track.language || 'Unknown'; 18146 options.selected = track.mode === 'showing'; 18147 _this = _MenuItem.call(this, player, options) || this; 18148 _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter 18149 // out empty kinds. 18150 18151 _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean); 18152 18153 var changeHandler = function changeHandler() { 18154 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 18155 args[_key] = arguments[_key]; 18156 } 18157 18158 _this.handleTracksChange.apply(assertThisInitialized(_this), args); 18159 }; 18160 18161 var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() { 18162 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 18163 args[_key2] = arguments[_key2]; 18164 } 18165 18166 _this.handleSelectedLanguageChange.apply(assertThisInitialized(_this), args); 18167 }; 18168 18169 player.on(['loadstart', 'texttrackchange'], changeHandler); 18170 tracks.addEventListener('change', changeHandler); 18171 tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 18172 18173 _this.on('dispose', function () { 18174 player.off(['loadstart', 'texttrackchange'], changeHandler); 18175 tracks.removeEventListener('change', changeHandler); 18176 tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 18177 }); // iOS7 doesn't dispatch change events to TextTrackLists when an 18178 // associated track's mode changes. Without something like 18179 // Object.observe() (also not present on iOS7), it's not 18180 // possible to detect changes to the mode attribute and polyfill 18181 // the change event. As a poor substitute, we manually dispatch 18182 // change events whenever the controls modify the mode. 18183 18184 18185 if (tracks.onchange === undefined) { 18186 var event; 18187 18188 _this.on(['tap', 'click'], function () { 18189 if (typeof window$1.Event !== 'object') { 18190 // Android 2.3 throws an Illegal Constructor error for window.Event 18191 try { 18192 event = new window$1.Event('change'); 18193 } catch (err) {// continue regardless of error 18194 } 18195 } 18196 18197 if (!event) { 18198 event = document.createEvent('Event'); 18199 event.initEvent('change', true, true); 18200 } 18201 18202 tracks.dispatchEvent(event); 18203 }); 18204 } // set the default state based on current tracks 18205 18206 18207 _this.handleTracksChange(); 18208 18209 return _this; 18210 } 18211 /** 18212 * This gets called when an `TextTrackMenuItem` is "clicked". See 18213 * {@link ClickableComponent} for more detailed information on what a click can be. 18214 * 18215 * @param {EventTarget~Event} event 18216 * The `keydown`, `tap`, or `click` event that caused this function to be 18217 * called. 18218 * 18219 * @listens tap 18220 * @listens click 18221 */ 18222 18223 18224 var _proto = TextTrackMenuItem.prototype; 18225 18226 _proto.handleClick = function handleClick(event) { 18227 var referenceTrack = this.track; 18228 var tracks = this.player_.textTracks(); 18229 18230 _MenuItem.prototype.handleClick.call(this, event); 18231 18232 if (!tracks) { 18233 return; 18234 } 18235 18236 for (var i = 0; i < tracks.length; i++) { 18237 var track = tracks[i]; // If the track from the text tracks list is not of the right kind, 18238 // skip it. We do not want to affect tracks of incompatible kind(s). 18239 18240 if (this.kinds.indexOf(track.kind) === -1) { 18241 continue; 18242 } // If this text track is the component's track and it is not showing, 18243 // set it to showing. 18244 18245 18246 if (track === referenceTrack) { 18247 if (track.mode !== 'showing') { 18248 track.mode = 'showing'; 18249 } // If this text track is not the component's track and it is not 18250 // disabled, set it to disabled. 18251 18252 } else if (track.mode !== 'disabled') { 18253 track.mode = 'disabled'; 18254 } 18255 } 18256 } 18257 /** 18258 * Handle text track list change 18259 * 18260 * @param {EventTarget~Event} event 18261 * The `change` event that caused this function to be called. 18262 * 18263 * @listens TextTrackList#change 18264 */ 18265 ; 18266 18267 _proto.handleTracksChange = function handleTracksChange(event) { 18268 var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause 18269 // screen readers to read the appended control text unnecessarily 18270 18271 if (shouldBeSelected !== this.isSelected_) { 18272 this.selected(shouldBeSelected); 18273 } 18274 }; 18275 18276 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 18277 if (this.track.mode === 'showing') { 18278 var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language 18279 18280 if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { 18281 return; 18282 } 18283 18284 this.player_.cache_.selectedLanguage = { 18285 enabled: true, 18286 language: this.track.language, 18287 kind: this.track.kind 18288 }; 18289 } 18290 }; 18291 18292 _proto.dispose = function dispose() { 18293 // remove reference to track object on dispose 18294 this.track = null; 18295 18296 _MenuItem.prototype.dispose.call(this); 18297 }; 18298 18299 return TextTrackMenuItem; 18300 }(MenuItem); 18301 18302 Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); 18303 18304 /** 18305 * A special menu item for turning of a specific type of text track 18306 * 18307 * @extends TextTrackMenuItem 18308 */ 18309 18310 var OffTextTrackMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) { 18311 inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem); 18312 18313 /** 18314 * Creates an instance of this class. 18315 * 18316 * @param {Player} player 18317 * The `Player` that this class should be attached to. 18318 * 18319 * @param {Object} [options] 18320 * The key/value store of player options. 18321 */ 18322 function OffTextTrackMenuItem(player, options) { 18323 // Create pseudo track info 18324 // Requires options['kind'] 18325 options.track = { 18326 player: player, 18327 // it is no longer necessary to store `kind` or `kinds` on the track itself 18328 // since they are now stored in the `kinds` property of all instances of 18329 // TextTrackMenuItem, but this will remain for backwards compatibility 18330 kind: options.kind, 18331 kinds: options.kinds, 18332 "default": false, 18333 mode: 'disabled' 18334 }; 18335 18336 if (!options.kinds) { 18337 options.kinds = [options.kind]; 18338 } 18339 18340 if (options.label) { 18341 options.track.label = options.label; 18342 } else { 18343 options.track.label = options.kinds.join(' and ') + ' off'; 18344 } // MenuItem is selectable 18345 18346 18347 options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 18348 18349 options.multiSelectable = false; 18350 return _TextTrackMenuItem.call(this, player, options) || this; 18351 } 18352 /** 18353 * Handle text track change 18354 * 18355 * @param {EventTarget~Event} event 18356 * The event that caused this function to run 18357 */ 18358 18359 18360 var _proto = OffTextTrackMenuItem.prototype; 18361 18362 _proto.handleTracksChange = function handleTracksChange(event) { 18363 var tracks = this.player().textTracks(); 18364 var shouldBeSelected = true; 18365 18366 for (var i = 0, l = tracks.length; i < l; i++) { 18367 var track = tracks[i]; 18368 18369 if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { 18370 shouldBeSelected = false; 18371 break; 18372 } 18373 } // Prevent redundant selected() calls because they may cause 18374 // screen readers to read the appended control text unnecessarily 18375 18376 18377 if (shouldBeSelected !== this.isSelected_) { 18378 this.selected(shouldBeSelected); 18379 } 18380 }; 18381 18382 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 18383 var tracks = this.player().textTracks(); 18384 var allHidden = true; 18385 18386 for (var i = 0, l = tracks.length; i < l; i++) { 18387 var track = tracks[i]; 18388 18389 if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { 18390 allHidden = false; 18391 break; 18392 } 18393 } 18394 18395 if (allHidden) { 18396 this.player_.cache_.selectedLanguage = { 18397 enabled: false 18398 }; 18399 } 18400 }; 18401 18402 return OffTextTrackMenuItem; 18403 }(TextTrackMenuItem); 18404 18405 Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); 18406 18407 /** 18408 * The base class for buttons that toggle specific text track types (e.g. subtitles) 18409 * 18410 * @extends MenuButton 18411 */ 18412 18413 var TextTrackButton = /*#__PURE__*/function (_TrackButton) { 18414 inheritsLoose(TextTrackButton, _TrackButton); 18415 18416 /** 18417 * Creates an instance of this class. 18418 * 18419 * @param {Player} player 18420 * The `Player` that this class should be attached to. 18421 * 18422 * @param {Object} [options={}] 18423 * The key/value store of player options. 18424 */ 18425 function TextTrackButton(player, options) { 18426 if (options === void 0) { 18427 options = {}; 18428 } 18429 18430 options.tracks = player.textTracks(); 18431 return _TrackButton.call(this, player, options) || this; 18432 } 18433 /** 18434 * Create a menu item for each text track 18435 * 18436 * @param {TextTrackMenuItem[]} [items=[]] 18437 * Existing array of items to use during creation 18438 * 18439 * @return {TextTrackMenuItem[]} 18440 * Array of menu items that were created 18441 */ 18442 18443 18444 var _proto = TextTrackButton.prototype; 18445 18446 _proto.createItems = function createItems(items, TrackMenuItem) { 18447 if (items === void 0) { 18448 items = []; 18449 } 18450 18451 if (TrackMenuItem === void 0) { 18452 TrackMenuItem = TextTrackMenuItem; 18453 } 18454 18455 // Label is an override for the [track] off label 18456 // USed to localise captions/subtitles 18457 var label; 18458 18459 if (this.label_) { 18460 label = this.label_ + " off"; 18461 } // Add an OFF menu item to turn all tracks off 18462 18463 18464 items.push(new OffTextTrackMenuItem(this.player_, { 18465 kinds: this.kinds_, 18466 kind: this.kind_, 18467 label: label 18468 })); 18469 this.hideThreshold_ += 1; 18470 var tracks = this.player_.textTracks(); 18471 18472 if (!Array.isArray(this.kinds_)) { 18473 this.kinds_ = [this.kind_]; 18474 } 18475 18476 for (var i = 0; i < tracks.length; i++) { 18477 var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label 18478 18479 if (this.kinds_.indexOf(track.kind) > -1) { 18480 var item = new TrackMenuItem(this.player_, { 18481 track: track, 18482 kinds: this.kinds_, 18483 kind: this.kind_, 18484 // MenuItem is selectable 18485 selectable: true, 18486 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 18487 multiSelectable: false 18488 }); 18489 item.addClass("vjs-" + track.kind + "-menu-item"); 18490 items.push(item); 18491 } 18492 } 18493 18494 return items; 18495 }; 18496 18497 return TextTrackButton; 18498 }(TrackButton); 18499 18500 Component.registerComponent('TextTrackButton', TextTrackButton); 18501 18502 /** 18503 * The chapter track menu item 18504 * 18505 * @extends MenuItem 18506 */ 18507 18508 var ChaptersTrackMenuItem = /*#__PURE__*/function (_MenuItem) {
vendor: 5,535 bytes, lines 18509-18690
18509 inheritsLoose(ChaptersTrackMenuItem, _MenuItem); 18510 18511 /** 18512 * Creates an instance of this class. 18513 * 18514 * @param {Player} player 18515 * The `Player` that this class should be attached to. 18516 * 18517 * @param {Object} [options] 18518 * The key/value store of player options. 18519 */ 18520 function ChaptersTrackMenuItem(player, options) { 18521 var _this; 18522 18523 var track = options.track; 18524 var cue = options.cue; 18525 var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init. 18526 18527 options.selectable = true; 18528 options.multiSelectable = false; 18529 options.label = cue.text; 18530 options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; 18531 _this = _MenuItem.call(this, player, options) || this; 18532 _this.track = track; 18533 _this.cue = cue; 18534 track.addEventListener('cuechange', bind(assertThisInitialized(_this), _this.update)); 18535 return _this; 18536 } 18537 /** 18538 * This gets called when an `ChaptersTrackMenuItem` is "clicked". See 18539 * {@link ClickableComponent} for more detailed information on what a click can be. 18540 * 18541 * @param {EventTarget~Event} [event] 18542 * The `keydown`, `tap`, or `click` event that caused this function to be 18543 * called. 18544 * 18545 * @listens tap 18546 * @listens click 18547 */ 18548 18549 18550 var _proto = ChaptersTrackMenuItem.prototype; 18551 18552 _proto.handleClick = function handleClick(event) { 18553 _MenuItem.prototype.handleClick.call(this); 18554 18555 this.player_.currentTime(this.cue.startTime); 18556 this.update(this.cue.startTime); 18557 } 18558 /** 18559 * Update chapter menu item 18560 * 18561 * @param {EventTarget~Event} [event] 18562 * The `cuechange` event that caused this function to run. 18563 * 18564 * @listens TextTrack#cuechange 18565 */ 18566 ; 18567 18568 _proto.update = function update(event) { 18569 var cue = this.cue; 18570 var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime); 18571 18572 this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); 18573 }; 18574 18575 return ChaptersTrackMenuItem; 18576 }(MenuItem); 18577 18578 Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); 18579 18580 /** 18581 * The button component for toggling and selecting chapters 18582 * Chapters act much differently than other text tracks 18583 * Cues are navigation vs. other tracks of alternative languages 18584 * 18585 * @extends TextTrackButton 18586 */ 18587 18588 var ChaptersButton = /*#__PURE__*/function (_TextTrackButton) { 18589 inheritsLoose(ChaptersButton, _TextTrackButton); 18590 18591 /** 18592 * Creates an instance of this class. 18593 * 18594 * @param {Player} player 18595 * The `Player` that this class should be attached to. 18596 * 18597 * @param {Object} [options] 18598 * The key/value store of player options. 18599 * 18600 * @param {Component~ReadyCallback} [ready] 18601 * The function to call when this function is ready. 18602 */ 18603 function ChaptersButton(player, options, ready) { 18604 return _TextTrackButton.call(this, player, options, ready) || this; 18605 } 18606 /** 18607 * Builds the default DOM `className`. 18608 * 18609 * @return {string} 18610 * The DOM `className` for this object. 18611 */ 18612 18613 18614 var _proto = ChaptersButton.prototype; 18615 18616 _proto.buildCSSClass = function buildCSSClass() { 18617 return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18618 }; 18619 18620 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18621 return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18622 } 18623 /** 18624 * Update the menu based on the current state of its items. 18625 * 18626 * @param {EventTarget~Event} [event] 18627 * An event that triggered this function to run. 18628 * 18629 * @listens TextTrackList#addtrack 18630 * @listens TextTrackList#removetrack 18631 * @listens TextTrackList#change 18632 */ 18633 ; 18634 18635 _proto.update = function update(event) { 18636 if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) { 18637 this.setTrack(this.findChaptersTrack()); 18638 } 18639 18640 _TextTrackButton.prototype.update.call(this); 18641 } 18642 /** 18643 * Set the currently selected track for the chapters button. 18644 * 18645 * @param {TextTrack} track 18646 * The new track to select. Nothing will change if this is the currently selected 18647 * track. 18648 */ 18649 ; 18650 18651 _proto.setTrack = function setTrack(track) { 18652 if (this.track_ === track) { 18653 return; 18654 } 18655 18656 if (!this.updateHandler_) { 18657 this.updateHandler_ = this.update.bind(this); 18658 } // here this.track_ refers to the old track instance 18659 18660 18661 if (this.track_) { 18662 var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 18663 18664 if (remoteTextTrackEl) { 18665 remoteTextTrackEl.removeEventListener('load', this.updateHandler_); 18666 } 18667 18668 this.track_ = null; 18669 } 18670 18671 this.track_ = track; // here this.track_ refers to the new track instance 18672 18673 if (this.track_) { 18674 this.track_.mode = 'hidden'; 18675 18676 var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 18677 18678 if (_remoteTextTrackEl) { 18679 _remoteTextTrackEl.addEventListener('load', this.updateHandler_); 18680 } 18681 } 18682 } 18683 /** 18684 * Find the track object that is currently in use by this ChaptersButton 18685 * 18686 * @return {TextTrack|undefined} 18687 * The current track or undefined if none was found. 18688 */ 18689 ; 18690
vendor: 15,561 bytes, lines 18691-19254
18691 _proto.findChaptersTrack = function findChaptersTrack() { 18692 var tracks = this.player_.textTracks() || []; 18693 18694 for (var i = tracks.length - 1; i >= 0; i--) { 18695 // We will always choose the last track as our chaptersTrack 18696 var track = tracks[i]; 18697 18698 if (track.kind === this.kind_) { 18699 return track; 18700 } 18701 } 18702 } 18703 /** 18704 * Get the caption for the ChaptersButton based on the track label. This will also 18705 * use the current tracks localized kind as a fallback if a label does not exist. 18706 * 18707 * @return {string} 18708 * The tracks current label or the localized track kind. 18709 */ 18710 ; 18711 18712 _proto.getMenuCaption = function getMenuCaption() { 18713 if (this.track_ && this.track_.label) { 18714 return this.track_.label; 18715 } 18716 18717 return this.localize(toTitleCase(this.kind_)); 18718 } 18719 /** 18720 * Create menu from chapter track 18721 * 18722 * @return {Menu} 18723 * New menu for the chapter buttons 18724 */ 18725 ; 18726 18727 _proto.createMenu = function createMenu() { 18728 this.options_.title = this.getMenuCaption(); 18729 return _TextTrackButton.prototype.createMenu.call(this); 18730 } 18731 /** 18732 * Create a menu item for each text track 18733 * 18734 * @return {TextTrackMenuItem[]} 18735 * Array of menu items 18736 */ 18737 ; 18738 18739 _proto.createItems = function createItems() { 18740 var items = []; 18741 18742 if (!this.track_) { 18743 return items; 18744 } 18745 18746 var cues = this.track_.cues; 18747 18748 if (!cues) { 18749 return items; 18750 } 18751 18752 for (var i = 0, l = cues.length; i < l; i++) { 18753 var cue = cues[i]; 18754 var mi = new ChaptersTrackMenuItem(this.player_, { 18755 track: this.track_, 18756 cue: cue 18757 }); 18758 items.push(mi); 18759 } 18760 18761 return items; 18762 }; 18763 18764 return ChaptersButton; 18765 }(TextTrackButton); 18766 /** 18767 * `kind` of TextTrack to look for to associate it with this menu. 18768 * 18769 * @type {string} 18770 * @private 18771 */ 18772 18773 18774 ChaptersButton.prototype.kind_ = 'chapters'; 18775 /** 18776 * The text that should display over the `ChaptersButton`s controls. Added for localization. 18777 * 18778 * @type {string} 18779 * @private 18780 */ 18781 18782 ChaptersButton.prototype.controlText_ = 'Chapters'; 18783 Component.registerComponent('ChaptersButton', ChaptersButton); 18784 18785 /** 18786 * The button component for toggling and selecting descriptions 18787 * 18788 * @extends TextTrackButton 18789 */ 18790 18791 var DescriptionsButton = /*#__PURE__*/function (_TextTrackButton) { 18792 inheritsLoose(DescriptionsButton, _TextTrackButton); 18793 18794 /** 18795 * Creates an instance of this class. 18796 * 18797 * @param {Player} player 18798 * The `Player` that this class should be attached to. 18799 * 18800 * @param {Object} [options] 18801 * The key/value store of player options. 18802 * 18803 * @param {Component~ReadyCallback} [ready] 18804 * The function to call when this component is ready. 18805 */ 18806 function DescriptionsButton(player, options, ready) { 18807 var _this; 18808 18809 _this = _TextTrackButton.call(this, player, options, ready) || this; 18810 var tracks = player.textTracks(); 18811 var changeHandler = bind(assertThisInitialized(_this), _this.handleTracksChange); 18812 tracks.addEventListener('change', changeHandler); 18813 18814 _this.on('dispose', function () { 18815 tracks.removeEventListener('change', changeHandler); 18816 }); 18817 18818 return _this; 18819 } 18820 /** 18821 * Handle text track change 18822 * 18823 * @param {EventTarget~Event} event 18824 * The event that caused this function to run 18825 * 18826 * @listens TextTrackList#change 18827 */ 18828 18829 18830 var _proto = DescriptionsButton.prototype; 18831 18832 _proto.handleTracksChange = function handleTracksChange(event) { 18833 var tracks = this.player().textTracks(); 18834 var disabled = false; // Check whether a track of a different kind is showing 18835 18836 for (var i = 0, l = tracks.length; i < l; i++) { 18837 var track = tracks[i]; 18838 18839 if (track.kind !== this.kind_ && track.mode === 'showing') { 18840 disabled = true; 18841 break; 18842 } 18843 } // If another track is showing, disable this menu button 18844 18845 18846 if (disabled) { 18847 this.disable(); 18848 } else { 18849 this.enable(); 18850 } 18851 } 18852 /** 18853 * Builds the default DOM `className`. 18854 * 18855 * @return {string} 18856 * The DOM `className` for this object. 18857 */ 18858 ; 18859 18860 _proto.buildCSSClass = function buildCSSClass() { 18861 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18862 }; 18863 18864 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18865 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18866 }; 18867 18868 return DescriptionsButton; 18869 }(TextTrackButton); 18870 /** 18871 * `kind` of TextTrack to look for to associate it with this menu. 18872 * 18873 * @type {string} 18874 * @private 18875 */ 18876 18877 18878 DescriptionsButton.prototype.kind_ = 'descriptions'; 18879 /** 18880 * The text that should display over the `DescriptionsButton`s controls. Added for localization. 18881 * 18882 * @type {string} 18883 * @private 18884 */ 18885 18886 DescriptionsButton.prototype.controlText_ = 'Descriptions'; 18887 Component.registerComponent('DescriptionsButton', DescriptionsButton); 18888 18889 /** 18890 * The button component for toggling and selecting subtitles 18891 * 18892 * @extends TextTrackButton 18893 */ 18894 18895 var SubtitlesButton = /*#__PURE__*/function (_TextTrackButton) { 18896 inheritsLoose(SubtitlesButton, _TextTrackButton); 18897 18898 /** 18899 * Creates an instance of this class. 18900 * 18901 * @param {Player} player 18902 * The `Player` that this class should be attached to. 18903 * 18904 * @param {Object} [options] 18905 * The key/value store of player options. 18906 * 18907 * @param {Component~ReadyCallback} [ready] 18908 * The function to call when this component is ready. 18909 */ 18910 function SubtitlesButton(player, options, ready) { 18911 return _TextTrackButton.call(this, player, options, ready) || this; 18912 } 18913 /** 18914 * Builds the default DOM `className`. 18915 * 18916 * @return {string} 18917 * The DOM `className` for this object. 18918 */ 18919 18920 18921 var _proto = SubtitlesButton.prototype; 18922 18923 _proto.buildCSSClass = function buildCSSClass() { 18924 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18925 }; 18926 18927 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18928 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18929 }; 18930 18931 return SubtitlesButton; 18932 }(TextTrackButton); 18933 /** 18934 * `kind` of TextTrack to look for to associate it with this menu. 18935 * 18936 * @type {string} 18937 * @private 18938 */ 18939 18940 18941 SubtitlesButton.prototype.kind_ = 'subtitles'; 18942 /** 18943 * The text that should display over the `SubtitlesButton`s controls. Added for localization. 18944 * 18945 * @type {string} 18946 * @private 18947 */ 18948 18949 SubtitlesButton.prototype.controlText_ = 'Subtitles'; 18950 Component.registerComponent('SubtitlesButton', SubtitlesButton); 18951 18952 /** 18953 * The menu item for caption track settings menu 18954 * 18955 * @extends TextTrackMenuItem 18956 */ 18957 18958 var CaptionSettingsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) { 18959 inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem); 18960 18961 /** 18962 * Creates an instance of this class. 18963 * 18964 * @param {Player} player 18965 * The `Player` that this class should be attached to. 18966 * 18967 * @param {Object} [options] 18968 * The key/value store of player options. 18969 */ 18970 function CaptionSettingsMenuItem(player, options) { 18971 var _this; 18972 18973 options.track = { 18974 player: player, 18975 kind: options.kind, 18976 label: options.kind + ' settings', 18977 selectable: false, 18978 "default": false, 18979 mode: 'disabled' 18980 }; // CaptionSettingsMenuItem has no concept of 'selected' 18981 18982 options.selectable = false; 18983 options.name = 'CaptionSettingsMenuItem'; 18984 _this = _TextTrackMenuItem.call(this, player, options) || this; 18985 18986 _this.addClass('vjs-texttrack-settings'); 18987 18988 _this.controlText(', opens ' + options.kind + ' settings dialog'); 18989 18990 return _this; 18991 } 18992 /** 18993 * This gets called when an `CaptionSettingsMenuItem` is "clicked". See 18994 * {@link ClickableComponent} for more detailed information on what a click can be. 18995 * 18996 * @param {EventTarget~Event} [event] 18997 * The `keydown`, `tap`, or `click` event that caused this function to be 18998 * called. 18999 * 19000 * @listens tap 19001 * @listens click 19002 */ 19003 19004 19005 var _proto = CaptionSettingsMenuItem.prototype; 19006 19007 _proto.handleClick = function handleClick(event) { 19008 this.player().getChild('textTrackSettings').open(); 19009 }; 19010 19011 return CaptionSettingsMenuItem; 19012 }(TextTrackMenuItem); 19013 19014 Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); 19015 19016 /** 19017 * The button component for toggling and selecting captions 19018 * 19019 * @extends TextTrackButton 19020 */ 19021 19022 var CaptionsButton = /*#__PURE__*/function (_TextTrackButton) { 19023 inheritsLoose(CaptionsButton, _TextTrackButton); 19024 19025 /** 19026 * Creates an instance of this class. 19027 * 19028 * @param {Player} player 19029 * The `Player` that this class should be attached to. 19030 * 19031 * @param {Object} [options] 19032 * The key/value store of player options. 19033 * 19034 * @param {Component~ReadyCallback} [ready] 19035 * The function to call when this component is ready. 19036 */ 19037 function CaptionsButton(player, options, ready) { 19038 return _TextTrackButton.call(this, player, options, ready) || this; 19039 } 19040 /** 19041 * Builds the default DOM `className`. 19042 * 19043 * @return {string} 19044 * The DOM `className` for this object. 19045 */ 19046 19047 19048 var _proto = CaptionsButton.prototype; 19049 19050 _proto.buildCSSClass = function buildCSSClass() { 19051 return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19052 }; 19053 19054 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19055 return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19056 } 19057 /** 19058 * Create caption menu items 19059 * 19060 * @return {CaptionSettingsMenuItem[]} 19061 * The array of current menu items. 19062 */ 19063 ; 19064 19065 _proto.createItems = function createItems() { 19066 var items = []; 19067 19068 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 19069 items.push(new CaptionSettingsMenuItem(this.player_, { 19070 kind: this.kind_ 19071 })); 19072 this.hideThreshold_ += 1; 19073 } 19074 19075 return _TextTrackButton.prototype.createItems.call(this, items); 19076 }; 19077 19078 return CaptionsButton; 19079 }(TextTrackButton); 19080 /** 19081 * `kind` of TextTrack to look for to associate it with this menu. 19082 * 19083 * @type {string} 19084 * @private 19085 */ 19086 19087 19088 CaptionsButton.prototype.kind_ = 'captions'; 19089 /** 19090 * The text that should display over the `CaptionsButton`s controls. Added for localization. 19091 * 19092 * @type {string} 19093 * @private 19094 */ 19095 19096 CaptionsButton.prototype.controlText_ = 'Captions'; 19097 Component.registerComponent('CaptionsButton', CaptionsButton); 19098 19099 /** 19100 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles 19101 * in the SubsCapsMenu. 19102 * 19103 * @extends TextTrackMenuItem 19104 */ 19105 19106 var SubsCapsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) { 19107 inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem); 19108 19109 function SubsCapsMenuItem() { 19110 return _TextTrackMenuItem.apply(this, arguments) || this; 19111 } 19112 19113 var _proto = SubsCapsMenuItem.prototype; 19114 19115 _proto.createEl = function createEl(type, props, attrs) { 19116 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 19117 19118 if (this.options_.track.kind === 'captions') { 19119 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n "; 19120 } 19121 19122 innerHTML += '</span>'; 19123 19124 var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({ 19125 innerHTML: innerHTML 19126 }, props), attrs); 19127 19128 return el; 19129 }; 19130 19131 return SubsCapsMenuItem; 19132 }(TextTrackMenuItem); 19133 19134 Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); 19135 19136 /** 19137 * The button component for toggling and selecting captions and/or subtitles 19138 * 19139 * @extends TextTrackButton 19140 */ 19141 19142 var SubsCapsButton = /*#__PURE__*/function (_TextTrackButton) { 19143 inheritsLoose(SubsCapsButton, _TextTrackButton); 19144 19145 function SubsCapsButton(player, options) { 19146 var _this; 19147 19148 if (options === void 0) { 19149 options = {}; 19150 } 19151 19152 _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for 19153 // "captions and subtitles" other locales use "subtitles" 19154 19155 _this.label_ = 'subtitles'; 19156 19157 if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { 19158 _this.label_ = 'captions'; 19159 } 19160 19161 _this.menuButton_.controlText(toTitleCase(_this.label_)); 19162 19163 return _this; 19164 } 19165 /** 19166 * Builds the default DOM `className`. 19167 * 19168 * @return {string} 19169 * The DOM `className` for this object. 19170 */ 19171 19172 19173 var _proto = SubsCapsButton.prototype; 19174 19175 _proto.buildCSSClass = function buildCSSClass() { 19176 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19177 }; 19178 19179 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19180 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19181 } 19182 /** 19183 * Create caption/subtitles menu items 19184 * 19185 * @return {CaptionSettingsMenuItem[]} 19186 * The array of current menu items. 19187 */ 19188 ; 19189 19190 _proto.createItems = function createItems() { 19191 var items = []; 19192 19193 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 19194 items.push(new CaptionSettingsMenuItem(this.player_, { 19195 kind: this.label_ 19196 })); 19197 this.hideThreshold_ += 1; 19198 } 19199 19200 items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); 19201 return items; 19202 }; 19203 19204 return SubsCapsButton; 19205 }(TextTrackButton); 19206 /** 19207 * `kind`s of TextTrack to look for to associate it with this menu. 19208 * 19209 * @type {array} 19210 * @private 19211 */ 19212 19213 19214 SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; 19215 /** 19216 * The text that should display over the `SubsCapsButton`s controls. 19217 * 19218 * 19219 * @type {string} 19220 * @private 19221 */ 19222 19223 SubsCapsButton.prototype.controlText_ = 'Subtitles'; 19224 Component.registerComponent('SubsCapsButton', SubsCapsButton); 19225 19226 /** 19227 * An {@link AudioTrack} {@link MenuItem} 19228 * 19229 * @extends MenuItem 19230 */ 19231 19232 var AudioTrackMenuItem = /*#__PURE__*/function (_MenuItem) { 19233 inheritsLoose(AudioTrackMenuItem, _MenuItem); 19234 19235 /** 19236 * Creates an instance of this class. 19237 * 19238 * @param {Player} player 19239 * The `Player` that this class should be attached to. 19240 * 19241 * @param {Object} [options] 19242 * The key/value store of player options. 19243 */ 19244 function AudioTrackMenuItem(player, options) { 19245 var _this; 19246 19247 var track = options.track; 19248 var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init. 19249 19250 options.label = track.label || track.language || 'Unknown'; 19251 options.selected = track.enabled; 19252 _this = _MenuItem.call(this, player, options) || this; 19253 _this.track = track; 19254
vendor: 5,647 bytes, lines 19255-19450
19255 _this.addClass("vjs-" + track.kind + "-menu-item"); 19256 19257 var changeHandler = function changeHandler() { 19258 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 19259 args[_key] = arguments[_key]; 19260 } 19261 19262 _this.handleTracksChange.apply(assertThisInitialized(_this), args); 19263 }; 19264 19265 tracks.addEventListener('change', changeHandler); 19266 19267 _this.on('dispose', function () { 19268 tracks.removeEventListener('change', changeHandler); 19269 }); 19270 19271 return _this; 19272 } 19273 19274 var _proto = AudioTrackMenuItem.prototype; 19275 19276 _proto.createEl = function createEl(type, props, attrs) { 19277 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 19278 19279 if (this.options_.track.kind === 'main-desc') { 19280 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n "; 19281 } 19282 19283 innerHTML += '</span>'; 19284 19285 var el = _MenuItem.prototype.createEl.call(this, type, assign({ 19286 innerHTML: innerHTML 19287 }, props), attrs); 19288 19289 return el; 19290 } 19291 /** 19292 * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} 19293 * for more detailed information on what a click can be. 19294 * 19295 * @param {EventTarget~Event} [event] 19296 * The `keydown`, `tap`, or `click` event that caused this function to be 19297 * called. 19298 * 19299 * @listens tap 19300 * @listens click 19301 */ 19302 ; 19303 19304 _proto.handleClick = function handleClick(event) { 19305 var tracks = this.player_.audioTracks(); 19306 19307 _MenuItem.prototype.handleClick.call(this, event); 19308 19309 for (var i = 0; i < tracks.length; i++) { 19310 var track = tracks[i]; 19311 track.enabled = track === this.track; 19312 } 19313 } 19314 /** 19315 * Handle any {@link AudioTrack} change. 19316 * 19317 * @param {EventTarget~Event} [event] 19318 * The {@link AudioTrackList#change} event that caused this to run. 19319 * 19320 * @listens AudioTrackList#change 19321 */ 19322 ; 19323 19324 _proto.handleTracksChange = function handleTracksChange(event) { 19325 this.selected(this.track.enabled); 19326 }; 19327 19328 return AudioTrackMenuItem; 19329 }(MenuItem); 19330 19331 Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); 19332 19333 /** 19334 * The base class for buttons that toggle specific {@link AudioTrack} types. 19335 * 19336 * @extends TrackButton 19337 */ 19338 19339 var AudioTrackButton = /*#__PURE__*/function (_TrackButton) { 19340 inheritsLoose(AudioTrackButton, _TrackButton); 19341 19342 /** 19343 * Creates an instance of this class. 19344 * 19345 * @param {Player} player 19346 * The `Player` that this class should be attached to. 19347 * 19348 * @param {Object} [options={}] 19349 * The key/value store of player options. 19350 */ 19351 function AudioTrackButton(player, options) { 19352 if (options === void 0) { 19353 options = {}; 19354 } 19355 19356 options.tracks = player.audioTracks(); 19357 return _TrackButton.call(this, player, options) || this; 19358 } 19359 /** 19360 * Builds the default DOM `className`. 19361 * 19362 * @return {string} 19363 * The DOM `className` for this object. 19364 */ 19365 19366 19367 var _proto = AudioTrackButton.prototype; 19368 19369 _proto.buildCSSClass = function buildCSSClass() { 19370 return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this); 19371 }; 19372 19373 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19374 return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this); 19375 } 19376 /** 19377 * Create a menu item for each audio track 19378 * 19379 * @param {AudioTrackMenuItem[]} [items=[]] 19380 * An array of existing menu items to use. 19381 * 19382 * @return {AudioTrackMenuItem[]} 19383 * An array of menu items 19384 */ 19385 ; 19386 19387 _proto.createItems = function createItems(items) { 19388 if (items === void 0) { 19389 items = []; 19390 } 19391 19392 // if there's only one audio track, there no point in showing it 19393 this.hideThreshold_ = 1; 19394 var tracks = this.player_.audioTracks(); 19395 19396 for (var i = 0; i < tracks.length; i++) { 19397 var track = tracks[i]; 19398 items.push(new AudioTrackMenuItem(this.player_, { 19399 track: track, 19400 // MenuItem is selectable 19401 selectable: true, 19402 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 19403 multiSelectable: false 19404 })); 19405 } 19406 19407 return items; 19408 }; 19409 19410 return AudioTrackButton; 19411 }(TrackButton); 19412 /** 19413 * The text that should display over the `AudioTrackButton`s controls. Added for localization. 19414 * 19415 * @type {string} 19416 * @private 19417 */ 19418 19419 19420 AudioTrackButton.prototype.controlText_ = 'Audio Track'; 19421 Component.registerComponent('AudioTrackButton', AudioTrackButton); 19422 19423 /** 19424 * The specific menu item type for selecting a playback rate. 19425 * 19426 * @extends MenuItem 19427 */ 19428 19429 var PlaybackRateMenuItem = /*#__PURE__*/function (_MenuItem) { 19430 inheritsLoose(PlaybackRateMenuItem, _MenuItem); 19431 19432 /** 19433 * Creates an instance of this class. 19434 * 19435 * @param {Player} player 19436 * The `Player` that this class should be attached to. 19437 * 19438 * @param {Object} [options] 19439 * The key/value store of player options. 19440 */ 19441 function PlaybackRateMenuItem(player, options) { 19442 var _this; 19443 19444 var label = options.rate; 19445 var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init. 19446 19447 options.label = label; 19448 options.selected = rate === 1; 19449 options.selectable = true; 19450 options.multiSelectable = false;
vendor: 110,920 bytes, lines 19451-22976
19451 _this = _MenuItem.call(this, player, options) || this; 19452 _this.label = label; 19453 _this.rate = rate; 19454 19455 _this.on(player, 'ratechange', _this.update); 19456 19457 return _this; 19458 } 19459 /** 19460 * This gets called when an `PlaybackRateMenuItem` is "clicked". See 19461 * {@link ClickableComponent} for more detailed information on what a click can be. 19462 * 19463 * @param {EventTarget~Event} [event] 19464 * The `keydown`, `tap`, or `click` event that caused this function to be 19465 * called. 19466 * 19467 * @listens tap 19468 * @listens click 19469 */ 19470 19471 19472 var _proto = PlaybackRateMenuItem.prototype; 19473 19474 _proto.handleClick = function handleClick(event) { 19475 _MenuItem.prototype.handleClick.call(this); 19476 19477 this.player().playbackRate(this.rate); 19478 } 19479 /** 19480 * Update the PlaybackRateMenuItem when the playbackrate changes. 19481 * 19482 * @param {EventTarget~Event} [event] 19483 * The `ratechange` event that caused this function to run. 19484 * 19485 * @listens Player#ratechange 19486 */ 19487 ; 19488 19489 _proto.update = function update(event) { 19490 this.selected(this.player().playbackRate() === this.rate); 19491 }; 19492 19493 return PlaybackRateMenuItem; 19494 }(MenuItem); 19495 /** 19496 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. 19497 * 19498 * @type {string} 19499 * @private 19500 */ 19501 19502 19503 PlaybackRateMenuItem.prototype.contentElType = 'button'; 19504 Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); 19505 19506 /** 19507 * The component for controlling the playback rate. 19508 * 19509 * @extends MenuButton 19510 */ 19511 19512 var PlaybackRateMenuButton = /*#__PURE__*/function (_MenuButton) { 19513 inheritsLoose(PlaybackRateMenuButton, _MenuButton); 19514 19515 /** 19516 * Creates an instance of this class. 19517 * 19518 * @param {Player} player 19519 * The `Player` that this class should be attached to. 19520 * 19521 * @param {Object} [options] 19522 * The key/value store of player options. 19523 */ 19524 function PlaybackRateMenuButton(player, options) { 19525 var _this; 19526 19527 _this = _MenuButton.call(this, player, options) || this; 19528 19529 _this.updateVisibility(); 19530 19531 _this.updateLabel(); 19532 19533 _this.on(player, 'loadstart', _this.updateVisibility); 19534 19535 _this.on(player, 'ratechange', _this.updateLabel); 19536 19537 return _this; 19538 } 19539 /** 19540 * Create the `Component`'s DOM element 19541 * 19542 * @return {Element} 19543 * The element that was created. 19544 */ 19545 19546 19547 var _proto = PlaybackRateMenuButton.prototype; 19548 19549 _proto.createEl = function createEl$1() { 19550 var el = _MenuButton.prototype.createEl.call(this); 19551 19552 this.labelEl_ = createEl('div', { 19553 className: 'vjs-playback-rate-value', 19554 innerHTML: '1x' 19555 }); 19556 el.appendChild(this.labelEl_); 19557 return el; 19558 }; 19559 19560 _proto.dispose = function dispose() { 19561 this.labelEl_ = null; 19562 19563 _MenuButton.prototype.dispose.call(this); 19564 } 19565 /** 19566 * Builds the default DOM `className`. 19567 * 19568 * @return {string} 19569 * The DOM `className` for this object. 19570 */ 19571 ; 19572 19573 _proto.buildCSSClass = function buildCSSClass() { 19574 return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this); 19575 }; 19576 19577 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19578 return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this); 19579 } 19580 /** 19581 * Create the playback rate menu 19582 * 19583 * @return {Menu} 19584 * Menu object populated with {@link PlaybackRateMenuItem}s 19585 */ 19586 ; 19587 19588 _proto.createMenu = function createMenu() { 19589 var menu = new Menu(this.player()); 19590 var rates = this.playbackRates(); 19591 19592 if (rates) { 19593 for (var i = rates.length - 1; i >= 0; i--) { 19594 menu.addChild(new PlaybackRateMenuItem(this.player(), { 19595 rate: rates[i] + 'x' 19596 })); 19597 } 19598 } 19599 19600 return menu; 19601 } 19602 /** 19603 * Updates ARIA accessibility attributes 19604 */ 19605 ; 19606 19607 _proto.updateARIAAttributes = function updateARIAAttributes() { 19608 // Current playback rate 19609 this.el().setAttribute('aria-valuenow', this.player().playbackRate()); 19610 } 19611 /** 19612 * This gets called when an `PlaybackRateMenuButton` is "clicked". See 19613 * {@link ClickableComponent} for more detailed information on what a click can be. 19614 * 19615 * @param {EventTarget~Event} [event] 19616 * The `keydown`, `tap`, or `click` event that caused this function to be 19617 * called. 19618 * 19619 * @listens tap 19620 * @listens click 19621 */ 19622 ; 19623 19624 _proto.handleClick = function handleClick(event) { 19625 // select next rate option 19626 var currentRate = this.player().playbackRate(); 19627 var rates = this.playbackRates(); // this will select first one if the last one currently selected 19628 19629 var newRate = rates[0]; 19630 19631 for (var i = 0; i < rates.length; i++) { 19632 if (rates[i] > currentRate) { 19633 newRate = rates[i]; 19634 break; 19635 } 19636 } 19637 19638 this.player().playbackRate(newRate); 19639 } 19640 /** 19641 * Get possible playback rates 19642 * 19643 * @return {Array} 19644 * All possible playback rates 19645 */ 19646 ; 19647 19648 _proto.playbackRates = function playbackRates() { 19649 return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; 19650 } 19651 /** 19652 * Get whether playback rates is supported by the tech 19653 * and an array of playback rates exists 19654 * 19655 * @return {boolean} 19656 * Whether changing playback rate is supported 19657 */ 19658 ; 19659 19660 _proto.playbackRateSupported = function playbackRateSupported() { 19661 return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; 19662 } 19663 /** 19664 * Hide playback rate controls when they're no playback rate options to select 19665 * 19666 * @param {EventTarget~Event} [event] 19667 * The event that caused this function to run. 19668 * 19669 * @listens Player#loadstart 19670 */ 19671 ; 19672 19673 _proto.updateVisibility = function updateVisibility(event) { 19674 if (this.playbackRateSupported()) { 19675 this.removeClass('vjs-hidden'); 19676 } else { 19677 this.addClass('vjs-hidden'); 19678 } 19679 } 19680 /** 19681 * Update button label when rate changed 19682 * 19683 * @param {EventTarget~Event} [event] 19684 * The event that caused this function to run. 19685 * 19686 * @listens Player#ratechange 19687 */ 19688 ; 19689 19690 _proto.updateLabel = function updateLabel(event) { 19691 if (this.playbackRateSupported()) { 19692 this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; 19693 } 19694 }; 19695 19696 return PlaybackRateMenuButton; 19697 }(MenuButton); 19698 /** 19699 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 19700 * 19701 * @type {string} 19702 * @private 19703 */ 19704 19705 19706 PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; 19707 Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); 19708 19709 /** 19710 * Just an empty spacer element that can be used as an append point for plugins, etc. 19711 * Also can be used to create space between elements when necessary. 19712 * 19713 * @extends Component 19714 */ 19715 19716 var Spacer = /*#__PURE__*/function (_Component) { 19717 inheritsLoose(Spacer, _Component); 19718 19719 function Spacer() { 19720 return _Component.apply(this, arguments) || this; 19721 } 19722 19723 var _proto = Spacer.prototype; 19724 19725 /** 19726 * Builds the default DOM `className`. 19727 * 19728 * @return {string} 19729 * The DOM `className` for this object. 19730 */ 19731 _proto.buildCSSClass = function buildCSSClass() { 19732 return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this); 19733 } 19734 /** 19735 * Create the `Component`'s DOM element 19736 * 19737 * @return {Element} 19738 * The element that was created. 19739 */ 19740 ; 19741 19742 _proto.createEl = function createEl() { 19743 return _Component.prototype.createEl.call(this, 'div', { 19744 className: this.buildCSSClass() 19745 }); 19746 }; 19747 19748 return Spacer; 19749 }(Component); 19750 19751 Component.registerComponent('Spacer', Spacer); 19752 19753 /** 19754 * Spacer specifically meant to be used as an insertion point for new plugins, etc. 19755 * 19756 * @extends Spacer 19757 */ 19758 19759 var CustomControlSpacer = /*#__PURE__*/function (_Spacer) { 19760 inheritsLoose(CustomControlSpacer, _Spacer); 19761 19762 function CustomControlSpacer() { 19763 return _Spacer.apply(this, arguments) || this; 19764 } 19765 19766 var _proto = CustomControlSpacer.prototype; 19767 19768 /** 19769 * Builds the default DOM `className`. 19770 * 19771 * @return {string} 19772 * The DOM `className` for this object. 19773 */ 19774 _proto.buildCSSClass = function buildCSSClass() { 19775 return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this); 19776 } 19777 /** 19778 * Create the `Component`'s DOM element 19779 * 19780 * @return {Element} 19781 * The element that was created. 19782 */ 19783 ; 19784 19785 _proto.createEl = function createEl() { 19786 var el = _Spacer.prototype.createEl.call(this, { 19787 className: this.buildCSSClass() 19788 }); // No-flex/table-cell mode requires there be some content 19789 // in the cell to fill the remaining space of the table. 19790 19791 19792 el.innerHTML = "\xA0"; 19793 return el; 19794 }; 19795 19796 return CustomControlSpacer; 19797 }(Spacer); 19798 19799 Component.registerComponent('CustomControlSpacer', CustomControlSpacer); 19800 19801 /** 19802 * Container of main controls. 19803 * 19804 * @extends Component 19805 */ 19806 19807 var ControlBar = /*#__PURE__*/function (_Component) { 19808 inheritsLoose(ControlBar, _Component); 19809 19810 function ControlBar() { 19811 return _Component.apply(this, arguments) || this; 19812 } 19813 19814 var _proto = ControlBar.prototype; 19815 19816 /** 19817 * Create the `Component`'s DOM element 19818 * 19819 * @return {Element} 19820 * The element that was created. 19821 */ 19822 _proto.createEl = function createEl() { 19823 return _Component.prototype.createEl.call(this, 'div', { 19824 className: 'vjs-control-bar', 19825 dir: 'ltr' 19826 }); 19827 }; 19828 19829 return ControlBar; 19830 }(Component); 19831 /** 19832 * Default options for `ControlBar` 19833 * 19834 * @type {Object} 19835 * @private 19836 */ 19837 19838 19839 ControlBar.prototype.options_ = { 19840 children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] 19841 }; 19842 19843 if ('exitPictureInPicture' in document) { 19844 ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle'); 19845 } 19846 19847 Component.registerComponent('ControlBar', ControlBar); 19848 19849 /** 19850 * A display that indicates an error has occurred. This means that the video 19851 * is unplayable. 19852 * 19853 * @extends ModalDialog 19854 */ 19855 19856 var ErrorDisplay = /*#__PURE__*/function (_ModalDialog) { 19857 inheritsLoose(ErrorDisplay, _ModalDialog); 19858 19859 /** 19860 * Creates an instance of this class. 19861 * 19862 * @param {Player} player 19863 * The `Player` that this class should be attached to. 19864 * 19865 * @param {Object} [options] 19866 * The key/value store of player options. 19867 */ 19868 function ErrorDisplay(player, options) { 19869 var _this; 19870 19871 _this = _ModalDialog.call(this, player, options) || this; 19872 19873 _this.on(player, 'error', _this.open); 19874 19875 return _this; 19876 } 19877 /** 19878 * Builds the default DOM `className`. 19879 * 19880 * @return {string} 19881 * The DOM `className` for this object. 19882 * 19883 * @deprecated Since version 5. 19884 */ 19885 19886 19887 var _proto = ErrorDisplay.prototype; 19888 19889 _proto.buildCSSClass = function buildCSSClass() { 19890 return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this); 19891 } 19892 /** 19893 * Gets the localized error message based on the `Player`s error. 19894 * 19895 * @return {string} 19896 * The `Player`s error message localized or an empty string. 19897 */ 19898 ; 19899 19900 _proto.content = function content() { 19901 var error = this.player().error(); 19902 return error ? this.localize(error.message) : ''; 19903 }; 19904 19905 return ErrorDisplay; 19906 }(ModalDialog); 19907 /** 19908 * The default options for an `ErrorDisplay`. 19909 * 19910 * @private 19911 */ 19912 19913 19914 ErrorDisplay.prototype.options_ = _extends_1({}, ModalDialog.prototype.options_, { 19915 pauseOnOpen: false, 19916 fillAlways: true, 19917 temporary: false, 19918 uncloseable: true 19919 }); 19920 Component.registerComponent('ErrorDisplay', ErrorDisplay); 19921 19922 var LOCAL_STORAGE_KEY = 'vjs-text-track-settings'; 19923 var COLOR_BLACK = ['#000', 'Black']; 19924 var COLOR_BLUE = ['#00F', 'Blue']; 19925 var COLOR_CYAN = ['#0FF', 'Cyan']; 19926 var COLOR_GREEN = ['#0F0', 'Green']; 19927 var COLOR_MAGENTA = ['#F0F', 'Magenta']; 19928 var COLOR_RED = ['#F00', 'Red']; 19929 var COLOR_WHITE = ['#FFF', 'White']; 19930 var COLOR_YELLOW = ['#FF0', 'Yellow']; 19931 var OPACITY_OPAQUE = ['1', 'Opaque']; 19932 var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; 19933 var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component. 19934 // 19935 // Possible keys include: 19936 // 19937 // `default`: 19938 // The default option index. Only needs to be provided if not zero. 19939 // `parser`: 19940 // A function which is used to parse the value from the selected option in 19941 // a customized way. 19942 // `selector`: 19943 // The selector used to find the associated <select> element. 19944 19945 var selectConfigs = { 19946 backgroundColor: { 19947 selector: '.vjs-bg-color > select', 19948 id: 'captions-background-color-%s', 19949 label: 'Color', 19950 options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 19951 }, 19952 backgroundOpacity: { 19953 selector: '.vjs-bg-opacity > select', 19954 id: 'captions-background-opacity-%s', 19955 label: 'Transparency', 19956 options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] 19957 }, 19958 color: { 19959 selector: '.vjs-fg-color > select', 19960 id: 'captions-foreground-color-%s', 19961 label: 'Color', 19962 options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 19963 }, 19964 edgeStyle: { 19965 selector: '.vjs-edge-style > select', 19966 id: '%s', 19967 label: 'Text Edge Style', 19968 options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] 19969 }, 19970 fontFamily: { 19971 selector: '.vjs-font-family > select', 19972 id: 'captions-font-family-%s', 19973 label: 'Font Family', 19974 options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] 19975 }, 19976 fontPercent: { 19977 selector: '.vjs-font-percent > select', 19978 id: 'captions-font-size-%s', 19979 label: 'Font Size', 19980 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%']], 19981 "default": 2, 19982 parser: function parser(v) { 19983 return v === '1.00' ? null : Number(v); 19984 } 19985 }, 19986 textOpacity: { 19987 selector: '.vjs-text-opacity > select', 19988 id: 'captions-foreground-opacity-%s', 19989 label: 'Transparency', 19990 options: [OPACITY_OPAQUE, OPACITY_SEMI] 19991 }, 19992 // Options for this object are defined below. 19993 windowColor: { 19994 selector: '.vjs-window-color > select', 19995 id: 'captions-window-color-%s', 19996 label: 'Color' 19997 }, 19998 // Options for this object are defined below. 19999 windowOpacity: { 20000 selector: '.vjs-window-opacity > select', 20001 id: 'captions-window-opacity-%s', 20002 label: 'Transparency', 20003 options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] 20004 } 20005 }; 20006 selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; 20007 /** 20008 * Get the actual value of an option. 20009 * 20010 * @param {string} value 20011 * The value to get 20012 * 20013 * @param {Function} [parser] 20014 * Optional function to adjust the value. 20015 * 20016 * @return {Mixed} 20017 * - Will be `undefined` if no value exists 20018 * - Will be `undefined` if the given value is "none". 20019 * - Will be the actual value otherwise. 20020 * 20021 * @private 20022 */ 20023 20024 function parseOptionValue(value, parser) { 20025 if (parser) { 20026 value = parser(value); 20027 } 20028 20029 if (value && value !== 'none') { 20030 return value; 20031 } 20032 } 20033 /** 20034 * Gets the value of the selected <option> element within a <select> element. 20035 * 20036 * @param {Element} el 20037 * the element to look in 20038 * 20039 * @param {Function} [parser] 20040 * Optional function to adjust the value. 20041 * 20042 * @return {Mixed} 20043 * - Will be `undefined` if no value exists 20044 * - Will be `undefined` if the given value is "none". 20045 * - Will be the actual value otherwise. 20046 * 20047 * @private 20048 */ 20049 20050 20051 function getSelectedOptionValue(el, parser) { 20052 var value = el.options[el.options.selectedIndex].value; 20053 return parseOptionValue(value, parser); 20054 } 20055 /** 20056 * Sets the selected <option> element within a <select> element based on a 20057 * given value. 20058 * 20059 * @param {Element} el 20060 * The element to look in. 20061 * 20062 * @param {string} value 20063 * the property to look on. 20064 * 20065 * @param {Function} [parser] 20066 * Optional function to adjust the value before comparing. 20067 * 20068 * @private 20069 */ 20070 20071 20072 function setSelectedOption(el, value, parser) { 20073 if (!value) { 20074 return; 20075 } 20076 20077 for (var i = 0; i < el.options.length; i++) { 20078 if (parseOptionValue(el.options[i].value, parser) === value) { 20079 el.selectedIndex = i; 20080 break; 20081 } 20082 } 20083 } 20084 /** 20085 * Manipulate Text Tracks settings. 20086 * 20087 * @extends ModalDialog 20088 */ 20089 20090 20091 var TextTrackSettings = /*#__PURE__*/function (_ModalDialog) { 20092 inheritsLoose(TextTrackSettings, _ModalDialog); 20093 20094 /** 20095 * Creates an instance of this class. 20096 * 20097 * @param {Player} player 20098 * The `Player` that this class should be attached to. 20099 * 20100 * @param {Object} [options] 20101 * The key/value store of player options. 20102 */ 20103 function TextTrackSettings(player, options) { 20104 var _this; 20105 20106 options.temporary = false; 20107 _this = _ModalDialog.call(this, player, options) || this; 20108 _this.updateDisplay = bind(assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it 20109 20110 _this.fill(); 20111 20112 _this.hasBeenOpened_ = _this.hasBeenFilled_ = true; 20113 _this.endDialog = createEl('p', { 20114 className: 'vjs-control-text', 20115 textContent: _this.localize('End of dialog window.') 20116 }); 20117 20118 _this.el().appendChild(_this.endDialog); 20119 20120 _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options 20121 20122 20123 if (options.persistTextTrackSettings === undefined) { 20124 _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings; 20125 } 20126 20127 _this.on(_this.$('.vjs-done-button'), 'click', function () { 20128 _this.saveSettings(); 20129 20130 _this.close(); 20131 }); 20132 20133 _this.on(_this.$('.vjs-default-button'), 'click', function () { 20134 _this.setDefaults(); 20135 20136 _this.updateDisplay(); 20137 }); 20138 20139 each(selectConfigs, function (config) { 20140 _this.on(_this.$(config.selector), 'change', _this.updateDisplay); 20141 }); 20142 20143 if (_this.options_.persistTextTrackSettings) { 20144 _this.restoreSettings(); 20145 } 20146 20147 return _this; 20148 } 20149 20150 var _proto = TextTrackSettings.prototype; 20151 20152 _proto.dispose = function dispose() { 20153 this.endDialog = null; 20154 20155 _ModalDialog.prototype.dispose.call(this); 20156 } 20157 /** 20158 * Create a <select> element with configured options. 20159 * 20160 * @param {string} key 20161 * Configuration key to use during creation. 20162 * 20163 * @return {string} 20164 * An HTML string. 20165 * 20166 * @private 20167 */ 20168 ; 20169 20170 _proto.createElSelect_ = function createElSelect_(key, legendId, type) { 20171 var _this2 = this; 20172 20173 if (legendId === void 0) { 20174 legendId = ''; 20175 } 20176 20177 if (type === void 0) { 20178 type = 'label'; 20179 } 20180 20181 var config = selectConfigs[key]; 20182 var id = config.id.replace('%s', this.id_); 20183 var selectLabelledbyIds = [legendId, id].join(' ').trim(); 20184 return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) { 20185 var optionId = id + '-' + o[1].replace(/\W+/g, ''); 20186 return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join(''); 20187 })).concat('</select>').join(''); 20188 } 20189 /** 20190 * Create foreground color element for the component 20191 * 20192 * @return {string} 20193 * An HTML string. 20194 * 20195 * @private 20196 */ 20197 ; 20198 20199 _proto.createElFgColor_ = function createElFgColor_() { 20200 var legendId = "captions-text-legend-" + this.id_; 20201 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(''); 20202 } 20203 /** 20204 * Create background color element for the component 20205 * 20206 * @return {string} 20207 * An HTML string. 20208 * 20209 * @private 20210 */ 20211 ; 20212 20213 _proto.createElBgColor_ = function createElBgColor_() { 20214 var legendId = "captions-background-" + this.id_; 20215 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(''); 20216 } 20217 /** 20218 * Create window color element for the component 20219 * 20220 * @return {string} 20221 * An HTML string. 20222 * 20223 * @private 20224 */ 20225 ; 20226 20227 _proto.createElWinColor_ = function createElWinColor_() { 20228 var legendId = "captions-window-" + this.id_; 20229 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(''); 20230 } 20231 /** 20232 * Create color elements for the component 20233 * 20234 * @return {Element} 20235 * The element that was created 20236 * 20237 * @private 20238 */ 20239 ; 20240 20241 _proto.createElColors_ = function createElColors_() { 20242 return createEl('div', { 20243 className: 'vjs-track-settings-colors', 20244 innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') 20245 }); 20246 } 20247 /** 20248 * Create font elements for the component 20249 * 20250 * @return {Element} 20251 * The element that was created. 20252 * 20253 * @private 20254 */ 20255 ; 20256 20257 _proto.createElFont_ = function createElFont_() { 20258 return createEl('div', { 20259 className: 'vjs-track-settings-font', 20260 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('') 20261 }); 20262 } 20263 /** 20264 * Create controls for the component 20265 * 20266 * @return {Element} 20267 * The element that was created. 20268 * 20269 * @private 20270 */ 20271 ; 20272 20273 _proto.createElControls_ = function createElControls_() { 20274 var defaultsDescription = this.localize('restore all settings to the default values'); 20275 return createEl('div', { 20276 className: 'vjs-track-settings-controls', 20277 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('') 20278 }); 20279 }; 20280 20281 _proto.content = function content() { 20282 return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; 20283 }; 20284 20285 _proto.label = function label() { 20286 return this.localize('Caption Settings Dialog'); 20287 }; 20288 20289 _proto.description = function description() { 20290 return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); 20291 }; 20292 20293 _proto.buildCSSClass = function buildCSSClass() { 20294 return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; 20295 } 20296 /** 20297 * Gets an object of text track settings (or null). 20298 * 20299 * @return {Object} 20300 * An object with config values parsed from the DOM or localStorage. 20301 */ 20302 ; 20303 20304 _proto.getValues = function getValues() { 20305 var _this3 = this; 20306 20307 return reduce(selectConfigs, function (accum, config, key) { 20308 var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); 20309 20310 if (value !== undefined) { 20311 accum[key] = value; 20312 } 20313 20314 return accum; 20315 }, {}); 20316 } 20317 /** 20318 * Sets text track settings from an object of values. 20319 * 20320 * @param {Object} values 20321 * An object with config values parsed from the DOM or localStorage. 20322 */ 20323 ; 20324 20325 _proto.setValues = function setValues(values) { 20326 var _this4 = this; 20327 20328 each(selectConfigs, function (config, key) { 20329 setSelectedOption(_this4.$(config.selector), values[key], config.parser); 20330 }); 20331 } 20332 /** 20333 * Sets all `<select>` elements to their default values. 20334 */ 20335 ; 20336 20337 _proto.setDefaults = function setDefaults() { 20338 var _this5 = this; 20339 20340 each(selectConfigs, function (config) { 20341 var index = config.hasOwnProperty('default') ? config["default"] : 0; 20342 _this5.$(config.selector).selectedIndex = index; 20343 }); 20344 } 20345 /** 20346 * Restore texttrack settings from localStorage 20347 */ 20348 ; 20349 20350 _proto.restoreSettings = function restoreSettings() { 20351 var values; 20352 20353 try { 20354 values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY)); 20355 } catch (err) { 20356 log.warn(err); 20357 } 20358 20359 if (values) { 20360 this.setValues(values); 20361 } 20362 } 20363 /** 20364 * Save text track settings to localStorage 20365 */ 20366 ; 20367 20368 _proto.saveSettings = function saveSettings() { 20369 if (!this.options_.persistTextTrackSettings) { 20370 return; 20371 } 20372 20373 var values = this.getValues(); 20374 20375 try { 20376 if (Object.keys(values).length) { 20377 window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values)); 20378 } else { 20379 window$1.localStorage.removeItem(LOCAL_STORAGE_KEY); 20380 } 20381 } catch (err) { 20382 log.warn(err); 20383 } 20384 } 20385 /** 20386 * Update display of text track settings 20387 */ 20388 ; 20389 20390 _proto.updateDisplay = function updateDisplay() { 20391 var ttDisplay = this.player_.getChild('textTrackDisplay'); 20392 20393 if (ttDisplay) { 20394 ttDisplay.updateDisplay(); 20395 } 20396 } 20397 /** 20398 * conditionally blur the element and refocus the captions button 20399 * 20400 * @private 20401 */ 20402 ; 20403 20404 _proto.conditionalBlur_ = function conditionalBlur_() { 20405 this.previouslyActiveEl_ = null; 20406 var cb = this.player_.controlBar; 20407 var subsCapsBtn = cb && cb.subsCapsButton; 20408 var ccBtn = cb && cb.captionsButton; 20409 20410 if (subsCapsBtn) { 20411 subsCapsBtn.focus(); 20412 } else if (ccBtn) { 20413 ccBtn.focus(); 20414 } 20415 }; 20416 20417 return TextTrackSettings; 20418 }(ModalDialog); 20419 20420 Component.registerComponent('TextTrackSettings', TextTrackSettings); 20421 20422 /** 20423 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions. 20424 * 20425 * 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}. 20426 * 20427 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option. 20428 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below. 20429 * @example <caption>How to disable the resize manager</caption> 20430 * const player = videojs('#vid', { 20431 * resizeManager: false 20432 * }); 20433 * 20434 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification} 20435 * 20436 * @extends Component 20437 */ 20438 20439 var ResizeManager = /*#__PURE__*/function (_Component) { 20440 inheritsLoose(ResizeManager, _Component); 20441 20442 /** 20443 * Create the ResizeManager. 20444 * 20445 * @param {Object} player 20446 * The `Player` that this class should be attached to. 20447 * 20448 * @param {Object} [options] 20449 * The key/value store of ResizeManager options. 20450 * 20451 * @param {Object} [options.ResizeObserver] 20452 * A polyfill for ResizeObserver can be passed in here. 20453 * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback. 20454 */ 20455 function ResizeManager(player, options) { 20456 var _this; 20457 20458 var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver 20459 20460 if (options.ResizeObserver === null) { 20461 RESIZE_OBSERVER_AVAILABLE = false; 20462 } // Only create an element when ResizeObserver isn't available 20463 20464 20465 var options_ = mergeOptions({ 20466 createEl: !RESIZE_OBSERVER_AVAILABLE, 20467 reportTouchActivity: false 20468 }, options); 20469 _this = _Component.call(this, player, options_) || this; 20470 _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver; 20471 _this.loadListener_ = null; 20472 _this.resizeObserver_ = null; 20473 _this.debouncedHandler_ = debounce(function () { 20474 _this.resizeHandler(); 20475 }, 100, false, assertThisInitialized(_this)); 20476 20477 if (RESIZE_OBSERVER_AVAILABLE) { 20478 _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_); 20479 20480 _this.resizeObserver_.observe(player.el()); 20481 } else { 20482 _this.loadListener_ = function () { 20483 if (!_this.el_ || !_this.el_.contentWindow) { 20484 return; 20485 } 20486 20487 var debouncedHandler_ = _this.debouncedHandler_; 20488 20489 var unloadListener_ = _this.unloadListener_ = function () { 20490 off(this, 'resize', debouncedHandler_); 20491 off(this, 'unload', unloadListener_); 20492 unloadListener_ = null; 20493 }; // safari and edge can unload the iframe before resizemanager dispose 20494 // we have to dispose of event handlers correctly before that happens 20495 20496 20497 on(_this.el_.contentWindow, 'unload', unloadListener_); 20498 on(_this.el_.contentWindow, 'resize', debouncedHandler_); 20499 }; 20500 20501 _this.one('load', _this.loadListener_); 20502 } 20503 20504 return _this; 20505 } 20506 20507 var _proto = ResizeManager.prototype; 20508 20509 _proto.createEl = function createEl() { 20510 return _Component.prototype.createEl.call(this, 'iframe', { 20511 className: 'vjs-resize-manager', 20512 tabIndex: -1 20513 }, { 20514 'aria-hidden': 'true' 20515 }); 20516 } 20517 /** 20518 * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver 20519 * 20520 * @fires Player#playerresize 20521 */ 20522 ; 20523 20524 _proto.resizeHandler = function resizeHandler() { 20525 /** 20526 * Called when the player size has changed 20527 * 20528 * @event Player#playerresize 20529 * @type {EventTarget~Event} 20530 */ 20531 // make sure player is still around to trigger 20532 // prevents this from causing an error after dispose 20533 if (!this.player_ || !this.player_.trigger) { 20534 return; 20535 } 20536 20537 this.player_.trigger('playerresize'); 20538 }; 20539 20540 _proto.dispose = function dispose() { 20541 if (this.debouncedHandler_) { 20542 this.debouncedHandler_.cancel(); 20543 } 20544 20545 if (this.resizeObserver_) { 20546 if (this.player_.el()) { 20547 this.resizeObserver_.unobserve(this.player_.el()); 20548 } 20549 20550 this.resizeObserver_.disconnect(); 20551 } 20552 20553 if (this.loadListener_) { 20554 this.off('load', this.loadListener_); 20555 } 20556 20557 if (this.el_ && this.el_.contentWindow && this.unloadListener_) { 20558 this.unloadListener_.call(this.el_.contentWindow); 20559 } 20560 20561 this.ResizeObserver = null; 20562 this.resizeObserver = null; 20563 this.debouncedHandler_ = null; 20564 this.loadListener_ = null; 20565 20566 _Component.prototype.dispose.call(this); 20567 }; 20568 20569 return ResizeManager; 20570 }(Component); 20571 20572 Component.registerComponent('ResizeManager', ResizeManager); 20573 20574 var defaults = { 20575 trackingThreshold: 30, 20576 liveTolerance: 15 20577 }; 20578 /* 20579 track when we are at the live edge, and other helpers for live playback */ 20580 20581 /** 20582 * A class for checking live current time and determining when the player 20583 * is at or behind the live edge. 20584 */ 20585 20586 var LiveTracker = /*#__PURE__*/function (_Component) { 20587 inheritsLoose(LiveTracker, _Component); 20588 20589 /** 20590 * Creates an instance of this class. 20591 * 20592 * @param {Player} player 20593 * The `Player` that this class should be attached to. 20594 * 20595 * @param {Object} [options] 20596 * The key/value store of player options. 20597 * 20598 * @param {number} [options.trackingThreshold=30] 20599 * Number of seconds of live window (seekableEnd - seekableStart) that 20600 * media needs to have before the liveui will be shown. 20601 * 20602 * @param {number} [options.liveTolerance=15] 20603 * Number of seconds behind live that we have to be 20604 * before we will be considered non-live. Note that this will only 20605 * be used when playing at the live edge. This allows large seekable end 20606 * changes to not effect wether we are live or not. 20607 */ 20608 function LiveTracker(player, options) { 20609 var _this; 20610 20611 // LiveTracker does not need an element 20612 var options_ = mergeOptions(defaults, options, { 20613 createEl: false 20614 }); 20615 _this = _Component.call(this, player, options_) || this; 20616 20617 _this.reset_(); 20618 20619 _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden, 20620 // also, tracking when the document is hidden can 20621 // cause the CPU to spike and eventually crash the page on IE11. 20622 20623 20624 if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) { 20625 _this.on(document, 'visibilitychange', _this.handleVisibilityChange); 20626 } 20627 20628 return _this; 20629 } 20630 /** 20631 * toggle tracking based on document visiblility 20632 */ 20633 20634 20635 var _proto = LiveTracker.prototype; 20636 20637 _proto.handleVisibilityChange = function handleVisibilityChange() { 20638 if (this.player_.duration() !== Infinity) { 20639 return; 20640 } 20641 20642 if (document.hidden) { 20643 this.stopTracking(); 20644 } else { 20645 this.startTracking(); 20646 } 20647 } 20648 /** 20649 * all the functionality for tracking when seek end changes 20650 * and for tracking how far past seek end we should be 20651 */ 20652 ; 20653 20654 _proto.trackLive_ = function trackLive_() { 20655 var seekable = this.player_.seekable(); // skip undefined seekable 20656 20657 if (!seekable || !seekable.length) { 20658 return; 20659 } 20660 20661 var newTime = Number(window$1.performance.now().toFixed(4)); 20662 var deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000; 20663 this.lastTime_ = newTime; 20664 this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime; 20665 var liveCurrentTime = this.liveCurrentTime(); 20666 var currentTime = this.player_.currentTime(); // we are behind live if any are true 20667 // 1. the player is paused 20668 // 2. the user seeked to a location 2 seconds away from live 20669 // 3. the difference between live and current time is greater 20670 // liveTolerance which defaults to 15s 20671 20672 var isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance; // we cannot be behind if 20673 // 1. until we have not seen a timeupdate yet 20674 // 2. liveCurrentTime is Infinity, which happens on Android 20675 20676 if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) { 20677 isBehind = false; 20678 } 20679 20680 if (isBehind !== this.behindLiveEdge_) { 20681 this.behindLiveEdge_ = isBehind; 20682 this.trigger('liveedgechange'); 20683 } 20684 } 20685 /** 20686 * handle a durationchange event on the player 20687 * and start/stop tracking accordingly. 20688 */ 20689 ; 20690 20691 _proto.handleDurationchange = function handleDurationchange() { 20692 if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) { 20693 if (this.player_.options_.liveui) { 20694 this.player_.addClass('vjs-liveui'); 20695 } 20696 20697 this.startTracking(); 20698 } else { 20699 this.player_.removeClass('vjs-liveui'); 20700 this.stopTracking(); 20701 } 20702 } 20703 /** 20704 * start tracking live playback 20705 */ 20706 ; 20707 20708 _proto.startTracking = function startTracking() { 20709 if (this.isTracking()) { 20710 return; 20711 } // If we haven't seen a timeupdate, we need to check whether playback 20712 // began before this component started tracking. This can happen commonly 20713 // when using autoplay. 20714 20715 20716 if (!this.timeupdateSeen_) { 20717 this.timeupdateSeen_ = this.player_.hasStarted(); 20718 } 20719 20720 this.trackingInterval_ = this.setInterval(this.trackLive_, UPDATE_REFRESH_INTERVAL); 20721 this.trackLive_(); 20722 this.on(this.player_, ['play', 'pause'], this.trackLive_); 20723 20724 if (!this.timeupdateSeen_) { 20725 this.one(this.player_, 'play', this.handlePlay); 20726 this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate); 20727 } else { 20728 this.on(this.player_, 'seeked', this.handleSeeked); 20729 } 20730 } 20731 /** 20732 * handle the first timeupdate on the player if it wasn't already playing 20733 * when live tracker started tracking. 20734 */ 20735 ; 20736 20737 _proto.handleFirstTimeupdate = function handleFirstTimeupdate() { 20738 this.timeupdateSeen_ = true; 20739 this.on(this.player_, 'seeked', this.handleSeeked); 20740 } 20741 /** 20742 * Keep track of what time a seek starts, and listen for seeked 20743 * to find where a seek ends. 20744 */ 20745 ; 20746 20747 _proto.handleSeeked = function handleSeeked() { 20748 var timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime()); 20749 this.seekedBehindLive_ = this.skipNextSeeked_ ? false : timeDiff > 2; 20750 this.skipNextSeeked_ = false; 20751 this.trackLive_(); 20752 } 20753 /** 20754 * handle the first play on the player, and make sure that we seek 20755 * right to the live edge. 20756 */ 20757 ; 20758 20759 _proto.handlePlay = function handlePlay() { 20760 this.one(this.player_, 'timeupdate', this.seekToLiveEdge); 20761 } 20762 /** 20763 * Stop tracking, and set all internal variables to 20764 * their initial value. 20765 */ 20766 ; 20767 20768 _proto.reset_ = function reset_() { 20769 this.lastTime_ = -1; 20770 this.pastSeekEnd_ = 0; 20771 this.lastSeekEnd_ = -1; 20772 this.behindLiveEdge_ = true; 20773 this.timeupdateSeen_ = false; 20774 this.seekedBehindLive_ = false; 20775 this.skipNextSeeked_ = false; 20776 this.clearInterval(this.trackingInterval_); 20777 this.trackingInterval_ = null; 20778 this.off(this.player_, ['play', 'pause'], this.trackLive_); 20779 this.off(this.player_, 'seeked', this.handleSeeked); 20780 this.off(this.player_, 'play', this.handlePlay); 20781 this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate); 20782 this.off(this.player_, 'timeupdate', this.seekToLiveEdge); 20783 } 20784 /** 20785 * stop tracking live playback 20786 */ 20787 ; 20788 20789 _proto.stopTracking = function stopTracking() { 20790 if (!this.isTracking()) { 20791 return; 20792 } 20793 20794 this.reset_(); 20795 this.trigger('liveedgechange'); 20796 } 20797 /** 20798 * A helper to get the player seekable end 20799 * so that we don't have to null check everywhere 20800 * 20801 * @return {number} 20802 * The furthest seekable end or Infinity. 20803 */ 20804 ; 20805 20806 _proto.seekableEnd = function seekableEnd() { 20807 var seekable = this.player_.seekable(); 20808 var seekableEnds = []; 20809 var i = seekable ? seekable.length : 0; 20810 20811 while (i--) { 20812 seekableEnds.push(seekable.end(i)); 20813 } // grab the furthest seekable end after sorting, or if there are none 20814 // default to Infinity 20815 20816 20817 return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity; 20818 } 20819 /** 20820 * A helper to get the player seekable start 20821 * so that we don't have to null check everywhere 20822 * 20823 * @return {number} 20824 * The earliest seekable start or 0. 20825 */ 20826 ; 20827 20828 _proto.seekableStart = function seekableStart() { 20829 var seekable = this.player_.seekable(); 20830 var seekableStarts = []; 20831 var i = seekable ? seekable.length : 0; 20832 20833 while (i--) { 20834 seekableStarts.push(seekable.start(i)); 20835 } // grab the first seekable start after sorting, or if there are none 20836 // default to 0 20837 20838 20839 return seekableStarts.length ? seekableStarts.sort()[0] : 0; 20840 } 20841 /** 20842 * Get the live time window aka 20843 * the amount of time between seekable start and 20844 * live current time. 20845 * 20846 * @return {number} 20847 * The amount of seconds that are seekable in 20848 * the live video. 20849 */ 20850 ; 20851 20852 _proto.liveWindow = function liveWindow() { 20853 var liveCurrentTime = this.liveCurrentTime(); 20854 20855 if (liveCurrentTime === Infinity) { 20856 return Infinity; 20857 } 20858 20859 return liveCurrentTime - this.seekableStart(); 20860 } 20861 /** 20862 * Determines if the player is live, only checks if this component 20863 * is tracking live playback or not 20864 * 20865 * @return {boolean} 20866 * Wether liveTracker is tracking 20867 */ 20868 ; 20869 20870 _proto.isLive = function isLive() { 20871 return this.isTracking(); 20872 } 20873 /** 20874 * Determines if currentTime is at the live edge and won't fall behind 20875 * on each seekableendchange 20876 * 20877 * @return {boolean} 20878 * Wether playback is at the live edge 20879 */ 20880 ; 20881 20882 _proto.atLiveEdge = function atLiveEdge() { 20883 return !this.behindLiveEdge(); 20884 } 20885 /** 20886 * get what we expect the live current time to be 20887 * 20888 * @return {number} 20889 * The expected live current time 20890 */ 20891 ; 20892 20893 _proto.liveCurrentTime = function liveCurrentTime() { 20894 return this.pastSeekEnd() + this.seekableEnd(); 20895 } 20896 /** 20897 * The number of seconds that have occured after seekable end 20898 * changed. This will be reset to 0 once seekable end changes. 20899 * 20900 * @return {number} 20901 * Seconds past the current seekable end 20902 */ 20903 ; 20904 20905 _proto.pastSeekEnd = function pastSeekEnd() { 20906 var seekableEnd = this.seekableEnd(); 20907 20908 if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) { 20909 this.pastSeekEnd_ = 0; 20910 } 20911 20912 this.lastSeekEnd_ = seekableEnd; 20913 return this.pastSeekEnd_; 20914 } 20915 /** 20916 * If we are currently behind the live edge, aka currentTime will be 20917 * behind on a seekableendchange 20918 * 20919 * @return {boolean} 20920 * If we are behind the live edge 20921 */ 20922 ; 20923 20924 _proto.behindLiveEdge = function behindLiveEdge() { 20925 return this.behindLiveEdge_; 20926 } 20927 /** 20928 * Wether live tracker is currently tracking or not. 20929 */ 20930 ; 20931 20932 _proto.isTracking = function isTracking() { 20933 return typeof this.trackingInterval_ === 'number'; 20934 } 20935 /** 20936 * Seek to the live edge if we are behind the live edge 20937 */ 20938 ; 20939 20940 _proto.seekToLiveEdge = function seekToLiveEdge() { 20941 this.seekedBehindLive_ = false; 20942 20943 if (this.atLiveEdge()) { 20944 return; 20945 } // skipNextSeeked_ 20946 20947 20948 this.skipNextSeeked_ = true; 20949 this.player_.currentTime(this.liveCurrentTime()); 20950 } 20951 /** 20952 * Dispose of liveTracker 20953 */ 20954 ; 20955 20956 _proto.dispose = function dispose() { 20957 this.off(document, 'visibilitychange', this.handleVisibilityChange); 20958 this.stopTracking(); 20959 20960 _Component.prototype.dispose.call(this); 20961 }; 20962 20963 return LiveTracker; 20964 }(Component); 20965 20966 Component.registerComponent('LiveTracker', LiveTracker); 20967 20968 /** 20969 * This function is used to fire a sourceset when there is something 20970 * similar to `mediaEl.load()` being called. It will try to find the source via 20971 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset` 20972 * with the source that was found or empty string if we cannot know. If it cannot 20973 * find a source then `sourceset` will not be fired. 20974 * 20975 * @param {Html5} tech 20976 * The tech object that sourceset was setup on 20977 * 20978 * @return {boolean} 20979 * returns false if the sourceset was not fired and true otherwise. 20980 */ 20981 20982 var sourcesetLoad = function sourcesetLoad(tech) { 20983 var el = tech.el(); // if `el.src` is set, that source will be loaded. 20984 20985 if (el.hasAttribute('src')) { 20986 tech.triggerSourceset(el.src); 20987 return true; 20988 } 20989 /** 20990 * Since there isn't a src property on the media element, source elements will be used for 20991 * implementing the source selection algorithm. This happens asynchronously and 20992 * for most cases were there is more than one source we cannot tell what source will 20993 * be loaded, without re-implementing the source selection algorithm. At this time we are not 20994 * going to do that. There are three special cases that we do handle here though: 20995 * 20996 * 1. If there are no sources, do not fire `sourceset`. 20997 * 2. If there is only one `<source>` with a `src` property/attribute that is our `src` 20998 * 3. If there is more than one `<source>` but all of them have the same `src` url. 20999 * That will be our src. 21000 */ 21001 21002 21003 var sources = tech.$$('source'); 21004 var srcUrls = []; 21005 var src = ''; // if there are no sources, do not fire sourceset 21006 21007 if (!sources.length) { 21008 return false; 21009 } // only count valid/non-duplicate source elements 21010 21011 21012 for (var i = 0; i < sources.length; i++) { 21013 var url = sources[i].src; 21014 21015 if (url && srcUrls.indexOf(url) === -1) { 21016 srcUrls.push(url); 21017 } 21018 } // there were no valid sources 21019 21020 21021 if (!srcUrls.length) { 21022 return false; 21023 } // there is only one valid source element url 21024 // use that 21025 21026 21027 if (srcUrls.length === 1) { 21028 src = srcUrls[0]; 21029 } 21030 21031 tech.triggerSourceset(src); 21032 return true; 21033 }; 21034 /** 21035 * our implementation of an `innerHTML` descriptor for browsers 21036 * that do not have one. 21037 */ 21038 21039 21040 var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', { 21041 get: function get() { 21042 return this.cloneNode(true).innerHTML; 21043 }, 21044 set: function set(v) { 21045 // make a dummy node to use innerHTML on 21046 var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided 21047 21048 dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy 21049 21050 var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy 21051 // to the document fragment 21052 21053 while (dummy.childNodes.length) { 21054 docFrag.appendChild(dummy.childNodes[0]); 21055 } // remove content 21056 21057 21058 this.innerText = ''; // now we add all of that html in one by appending the 21059 // document fragment. This is how innerHTML does it. 21060 21061 window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would 21062 21063 return this.innerHTML; 21064 } 21065 }); 21066 /** 21067 * Get a property descriptor given a list of priorities and the 21068 * property to get. 21069 */ 21070 21071 var getDescriptor = function getDescriptor(priority, prop) { 21072 var descriptor = {}; 21073 21074 for (var i = 0; i < priority.length; i++) { 21075 descriptor = Object.getOwnPropertyDescriptor(priority[i], prop); 21076 21077 if (descriptor && descriptor.set && descriptor.get) { 21078 break; 21079 } 21080 } 21081 21082 descriptor.enumerable = true; 21083 descriptor.configurable = true; 21084 return descriptor; 21085 }; 21086 21087 var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) { 21088 return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML'); 21089 }; 21090 /** 21091 * Patches browser internal functions so that we can tell synchronously 21092 * if a `<source>` was appended to the media element. For some reason this 21093 * causes a `sourceset` if the the media element is ready and has no source. 21094 * This happens when: 21095 * - The page has just loaded and the media element does not have a source. 21096 * - The media element was emptied of all sources, then `load()` was called. 21097 * 21098 * It does this by patching the following functions/properties when they are supported: 21099 * 21100 * - `append()` - can be used to add a `<source>` element to the media element 21101 * - `appendChild()` - can be used to add a `<source>` element to the media element 21102 * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element 21103 * - `innerHTML` - can be used to add a `<source>` element to the media element 21104 * 21105 * @param {Html5} tech 21106 * The tech object that sourceset is being setup on. 21107 */ 21108 21109 21110 var firstSourceWatch = function firstSourceWatch(tech) { 21111 var el = tech.el(); // make sure firstSourceWatch isn't setup twice. 21112 21113 if (el.resetSourceWatch_) { 21114 return; 21115 } 21116 21117 var old = {}; 21118 var innerDescriptor = getInnerHTMLDescriptor(tech); 21119 21120 var appendWrapper = function appendWrapper(appendFn) { 21121 return function () { 21122 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 21123 args[_key] = arguments[_key]; 21124 } 21125 21126 var retval = appendFn.apply(el, args); 21127 sourcesetLoad(tech); 21128 return retval; 21129 }; 21130 }; 21131 21132 ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) { 21133 if (!el[k]) { 21134 return; 21135 } // store the old function 21136 21137 21138 old[k] = el[k]; // call the old function with a sourceset if a source 21139 // was loaded 21140 21141 el[k] = appendWrapper(old[k]); 21142 }); 21143 Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, { 21144 set: appendWrapper(innerDescriptor.set) 21145 })); 21146 21147 el.resetSourceWatch_ = function () { 21148 el.resetSourceWatch_ = null; 21149 Object.keys(old).forEach(function (k) { 21150 el[k] = old[k]; 21151 }); 21152 Object.defineProperty(el, 'innerHTML', innerDescriptor); 21153 }; // on the first sourceset, we need to revert our changes 21154 21155 21156 tech.one('sourceset', el.resetSourceWatch_); 21157 }; 21158 /** 21159 * our implementation of a `src` descriptor for browsers 21160 * that do not have one. 21161 */ 21162 21163 21164 var srcDescriptorPolyfill = Object.defineProperty({}, 'src', { 21165 get: function get() { 21166 if (this.hasAttribute('src')) { 21167 return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src')); 21168 } 21169 21170 return ''; 21171 }, 21172 set: function set(v) { 21173 window$1.Element.prototype.setAttribute.call(this, 'src', v); 21174 return v; 21175 } 21176 }); 21177 21178 var getSrcDescriptor = function getSrcDescriptor(tech) { 21179 return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src'); 21180 }; 21181 /** 21182 * setup `sourceset` handling on the `Html5` tech. This function 21183 * patches the following element properties/functions: 21184 * 21185 * - `src` - to determine when `src` is set 21186 * - `setAttribute()` - to determine when `src` is set 21187 * - `load()` - this re-triggers the source selection algorithm, and can 21188 * cause a sourceset. 21189 * 21190 * If there is no source when we are adding `sourceset` support or during a `load()` 21191 * we also patch the functions listed in `firstSourceWatch`. 21192 * 21193 * @param {Html5} tech 21194 * The tech to patch 21195 */ 21196 21197 21198 var setupSourceset = function setupSourceset(tech) { 21199 if (!tech.featuresSourceset) { 21200 return; 21201 } 21202 21203 var el = tech.el(); // make sure sourceset isn't setup twice. 21204 21205 if (el.resetSourceset_) { 21206 return; 21207 } 21208 21209 var srcDescriptor = getSrcDescriptor(tech); 21210 var oldSetAttribute = el.setAttribute; 21211 var oldLoad = el.load; 21212 Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, { 21213 set: function set(v) { 21214 var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src 21215 21216 tech.triggerSourceset(el.src); 21217 return retval; 21218 } 21219 })); 21220 21221 el.setAttribute = function (n, v) { 21222 var retval = oldSetAttribute.call(el, n, v); 21223 21224 if (/src/i.test(n)) { 21225 tech.triggerSourceset(el.src); 21226 } 21227 21228 return retval; 21229 }; 21230 21231 el.load = function () { 21232 var retval = oldLoad.call(el); // if load was called, but there was no source to fire 21233 // sourceset on. We have to watch for a source append 21234 // as that can trigger a `sourceset` when the media element 21235 // has no source 21236 21237 if (!sourcesetLoad(tech)) { 21238 tech.triggerSourceset(''); 21239 firstSourceWatch(tech); 21240 } 21241 21242 return retval; 21243 }; 21244 21245 if (el.currentSrc) { 21246 tech.triggerSourceset(el.currentSrc); 21247 } else if (!sourcesetLoad(tech)) { 21248 firstSourceWatch(tech); 21249 } 21250 21251 el.resetSourceset_ = function () { 21252 el.resetSourceset_ = null; 21253 el.load = oldLoad; 21254 el.setAttribute = oldSetAttribute; 21255 Object.defineProperty(el, 'src', srcDescriptor); 21256 21257 if (el.resetSourceWatch_) { 21258 el.resetSourceWatch_(); 21259 } 21260 }; 21261 }; 21262 21263 /** 21264 * Object.defineProperty but "lazy", which means that the value is only set after 21265 * it retrieved the first time, rather than being set right away. 21266 * 21267 * @param {Object} obj the object to set the property on 21268 * @param {string} key the key for the property to set 21269 * @param {Function} getValue the function used to get the value when it is needed. 21270 * @param {boolean} setter wether a setter shoould be allowed or not 21271 */ 21272 var defineLazyProperty = function defineLazyProperty(obj, key, getValue, setter) { 21273 if (setter === void 0) { 21274 setter = true; 21275 } 21276 21277 var set = function set(value) { 21278 return Object.defineProperty(obj, key, { 21279 value: value, 21280 enumerable: true, 21281 writable: true 21282 }); 21283 }; 21284 21285 var options = { 21286 configurable: true, 21287 enumerable: true, 21288 get: function get() { 21289 var value = getValue(); 21290 set(value); 21291 return value; 21292 } 21293 }; 21294 21295 if (setter) { 21296 options.set = set; 21297 } 21298 21299 return Object.defineProperty(obj, key, options); 21300 }; 21301 21302 /** 21303 * HTML5 Media Controller - Wrapper for HTML5 Media API 21304 * 21305 * @mixes Tech~SourceHandlerAdditions 21306 * @extends Tech 21307 */ 21308 21309 var Html5 = /*#__PURE__*/function (_Tech) { 21310 inheritsLoose(Html5, _Tech); 21311 21312 /** 21313 * Create an instance of this Tech. 21314 * 21315 * @param {Object} [options] 21316 * The key/value store of player options. 21317 * 21318 * @param {Component~ReadyCallback} ready 21319 * Callback function to call when the `HTML5` Tech is ready. 21320 */ 21321 function Html5(options, ready) { 21322 var _this; 21323 21324 _this = _Tech.call(this, options, ready) || this; 21325 var source = options.source; 21326 var crossoriginTracks = false; // Set the source if one is provided 21327 // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) 21328 // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source 21329 // anyway so the error gets fired. 21330 21331 if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) { 21332 _this.setSource(source); 21333 } else { 21334 _this.handleLateInit_(_this.el_); 21335 } // setup sourceset after late sourceset/init 21336 21337 21338 if (options.enableSourceset) { 21339 _this.setupSourcesetHandling_(); 21340 } 21341 21342 if (_this.el_.hasChildNodes()) { 21343 var nodes = _this.el_.childNodes; 21344 var nodesLength = nodes.length; 21345 var removeNodes = []; 21346 21347 while (nodesLength--) { 21348 var node = nodes[nodesLength]; 21349 var nodeName = node.nodeName.toLowerCase(); 21350 21351 if (nodeName === 'track') { 21352 if (!_this.featuresNativeTextTracks) { 21353 // Empty video tag tracks so the built-in player doesn't use them also. 21354 // This may not be fast enough to stop HTML5 browsers from reading the tags 21355 // so we'll need to turn off any default tracks if we're manually doing 21356 // captions and subtitles. videoElement.textTracks 21357 removeNodes.push(node); 21358 } else { 21359 // store HTMLTrackElement and TextTrack to remote list 21360 _this.remoteTextTrackEls().addTrackElement_(node); 21361 21362 _this.remoteTextTracks().addTrack(node.track); 21363 21364 _this.textTracks().addTrack(node.track); 21365 21366 if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) { 21367 crossoriginTracks = true; 21368 } 21369 } 21370 } 21371 } 21372 21373 for (var i = 0; i < removeNodes.length; i++) { 21374 _this.el_.removeChild(removeNodes[i]); 21375 } 21376 } 21377 21378 _this.proxyNativeTracks_(); 21379 21380 if (_this.featuresNativeTextTracks && crossoriginTracks) { 21381 log.warn('Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n' + 'This may prevent text tracks from loading.'); 21382 } // prevent iOS Safari from disabling metadata text tracks during native playback 21383 21384 21385 _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used 21386 // Our goal should be to get the custom controls on mobile solid everywhere 21387 // so we can remove this all together. Right now this will block custom 21388 // controls on touch enabled laptops like the Chrome Pixel 21389 21390 21391 if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { 21392 _this.setControls(true); 21393 } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` 21394 // into a `fullscreenchange` event 21395 21396 21397 _this.proxyWebkitFullscreen_(); 21398 21399 _this.triggerReady(); 21400 21401 return _this; 21402 } 21403 /** 21404 * Dispose of `HTML5` media element and remove all tracks. 21405 */ 21406 21407 21408 var _proto = Html5.prototype; 21409 21410 _proto.dispose = function dispose() { 21411 if (this.el_ && this.el_.resetSourceset_) { 21412 this.el_.resetSourceset_(); 21413 } 21414 21415 Html5.disposeMediaElement(this.el_); 21416 this.options_ = null; // tech will handle clearing of the emulated track list 21417 21418 _Tech.prototype.dispose.call(this); 21419 } 21420 /** 21421 * Modify the media element so that we can detect when 21422 * the source is changed. Fires `sourceset` just after the source has changed 21423 */ 21424 ; 21425 21426 _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() { 21427 setupSourceset(this); 21428 } 21429 /** 21430 * When a captions track is enabled in the iOS Safari native player, all other 21431 * tracks are disabled (including metadata tracks), which nulls all of their 21432 * associated cue points. This will restore metadata tracks to their pre-fullscreen 21433 * state in those cases so that cue points are not needlessly lost. 21434 * 21435 * @private 21436 */ 21437 ; 21438 21439 _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() { 21440 var textTracks = this.textTracks(); 21441 var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state 21442 21443 var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() { 21444 metadataTracksPreFullscreenState = []; 21445 21446 for (var i = 0; i < textTracks.length; i++) { 21447 var track = textTracks[i]; 21448 21449 if (track.kind === 'metadata') { 21450 metadataTracksPreFullscreenState.push({ 21451 track: track, 21452 storedMode: track.mode 21453 }); 21454 } 21455 } 21456 }; // snapshot each metadata track's initial state, and update the snapshot 21457 // each time there is a track 'change' event 21458 21459 21460 takeMetadataTrackSnapshot(); 21461 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 21462 this.on('dispose', function () { 21463 return textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 21464 }); 21465 21466 var restoreTrackMode = function restoreTrackMode() { 21467 for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) { 21468 var storedTrack = metadataTracksPreFullscreenState[i]; 21469 21470 if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) { 21471 storedTrack.track.mode = storedTrack.storedMode; 21472 } 21473 } // we only want this handler to be executed on the first 'change' event 21474 21475 21476 textTracks.removeEventListener('change', restoreTrackMode); 21477 }; // when we enter fullscreen playback, stop updating the snapshot and 21478 // restore all track modes to their pre-fullscreen state 21479 21480 21481 this.on('webkitbeginfullscreen', function () { 21482 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed 21483 21484 textTracks.removeEventListener('change', restoreTrackMode); 21485 textTracks.addEventListener('change', restoreTrackMode); 21486 }); // start updating the snapshot again after leaving fullscreen 21487 21488 this.on('webkitendfullscreen', function () { 21489 // remove the listener before adding it just in case it wasn't previously removed 21490 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 21491 textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback 21492 21493 textTracks.removeEventListener('change', restoreTrackMode); 21494 }); 21495 } 21496 /** 21497 * Attempt to force override of tracks for the given type 21498 * 21499 * @param {string} type - Track type to override, possible values include 'Audio', 21500 * 'Video', and 'Text'. 21501 * @param {boolean} override - If set to true native audio/video will be overridden, 21502 * otherwise native audio/video will potentially be used. 21503 * @private 21504 */ 21505 ; 21506 21507 _proto.overrideNative_ = function overrideNative_(type, override) { 21508 var _this2 = this; 21509 21510 // If there is no behavioral change don't add/remove listeners 21511 if (override !== this["featuresNative" + type + "Tracks"]) { 21512 return; 21513 } 21514 21515 var lowerCaseType = type.toLowerCase(); 21516 21517 if (this[lowerCaseType + "TracksListeners_"]) { 21518 Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) { 21519 var elTracks = _this2.el()[lowerCaseType + "Tracks"]; 21520 21521 elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]); 21522 }); 21523 } 21524 21525 this["featuresNative" + type + "Tracks"] = !override; 21526 this[lowerCaseType + "TracksListeners_"] = null; 21527 this.proxyNativeTracksForType_(lowerCaseType); 21528 } 21529 /** 21530 * Attempt to force override of native audio tracks. 21531 * 21532 * @param {boolean} override - If set to true native audio will be overridden, 21533 * otherwise native audio will potentially be used. 21534 */ 21535 ; 21536 21537 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) { 21538 this.overrideNative_('Audio', override); 21539 } 21540 /** 21541 * Attempt to force override of native video tracks. 21542 * 21543 * @param {boolean} override - If set to true native video will be overridden, 21544 * otherwise native video will potentially be used. 21545 */ 21546 ; 21547 21548 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) { 21549 this.overrideNative_('Video', override); 21550 } 21551 /** 21552 * Proxy native track list events for the given type to our track 21553 * lists if the browser we are playing in supports that type of track list. 21554 * 21555 * @param {string} name - Track type; values include 'audio', 'video', and 'text' 21556 * @private 21557 */ 21558 ; 21559 21560 _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) { 21561 var _this3 = this; 21562 21563 var props = NORMAL[name]; 21564 var elTracks = this.el()[props.getterName]; 21565 var techTracks = this[props.getterName](); 21566 21567 if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) { 21568 return; 21569 } 21570 21571 var listeners = { 21572 change: function change(e) { 21573 var event = { 21574 type: 'change', 21575 target: techTracks, 21576 currentTarget: techTracks, 21577 srcElement: techTracks 21578 }; 21579 techTracks.trigger(event); // if we are a text track change event, we should also notify the 21580 // remote text track list. This can potentially cause a false positive 21581 // if we were to get a change event on a non-remote track and 21582 // we triggered the event on the remote text track list which doesn't 21583 // contain that track. However, best practices mean looping through the 21584 // list of tracks and searching for the appropriate mode value, so, 21585 // this shouldn't pose an issue 21586 21587 if (name === 'text') { 21588 _this3[REMOTE.remoteText.getterName]().trigger(event); 21589 } 21590 }, 21591 addtrack: function addtrack(e) { 21592 techTracks.addTrack(e.track); 21593 }, 21594 removetrack: function removetrack(e) { 21595 techTracks.removeTrack(e.track); 21596 } 21597 }; 21598 21599 var removeOldTracks = function removeOldTracks() { 21600 var removeTracks = []; 21601 21602 for (var i = 0; i < techTracks.length; i++) { 21603 var found = false; 21604 21605 for (var j = 0; j < elTracks.length; j++) { 21606 if (elTracks[j] === techTracks[i]) { 21607 found = true; 21608 break; 21609 } 21610 } 21611 21612 if (!found) { 21613 removeTracks.push(techTracks[i]); 21614 } 21615 } 21616 21617 while (removeTracks.length) { 21618 techTracks.removeTrack(removeTracks.shift()); 21619 } 21620 }; 21621 21622 this[props.getterName + 'Listeners_'] = listeners; 21623 Object.keys(listeners).forEach(function (eventName) { 21624 var listener = listeners[eventName]; 21625 elTracks.addEventListener(eventName, listener); 21626 21627 _this3.on('dispose', function (e) { 21628 return elTracks.removeEventListener(eventName, listener); 21629 }); 21630 }); // Remove (native) tracks that are not used anymore 21631 21632 this.on('loadstart', removeOldTracks); 21633 this.on('dispose', function (e) { 21634 return _this3.off('loadstart', removeOldTracks); 21635 }); 21636 } 21637 /** 21638 * Proxy all native track list events to our track lists if the browser we are playing 21639 * in supports that type of track list. 21640 * 21641 * @private 21642 */ 21643 ; 21644 21645 _proto.proxyNativeTracks_ = function proxyNativeTracks_() { 21646 var _this4 = this; 21647 21648 NORMAL.names.forEach(function (name) { 21649 _this4.proxyNativeTracksForType_(name); 21650 }); 21651 } 21652 /** 21653 * Create the `Html5` Tech's DOM element. 21654 * 21655 * @return {Element} 21656 * The element that gets created. 21657 */ 21658 ; 21659 21660 _proto.createEl = function createEl() { 21661 var el = this.options_.tag; // Check if this browser supports moving the element into the box. 21662 // On the iPhone video will break if you move the element, 21663 // So we have to create a brand new element. 21664 // If we ingested the player div, we do not need to move the media element. 21665 21666 if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { 21667 // If the original tag is still there, clone and remove it. 21668 if (el) { 21669 var clone = el.cloneNode(true); 21670 21671 if (el.parentNode) { 21672 el.parentNode.insertBefore(clone, el); 21673 } 21674 21675 Html5.disposeMediaElement(el); 21676 el = clone; 21677 } else { 21678 el = document.createElement('video'); // determine if native controls should be used 21679 21680 var tagAttributes = this.options_.tag && getAttributes(this.options_.tag); 21681 var attributes = mergeOptions({}, tagAttributes); 21682 21683 if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { 21684 delete attributes.controls; 21685 } 21686 21687 setAttributes(el, assign(attributes, { 21688 id: this.options_.techId, 21689 "class": 'vjs-tech' 21690 })); 21691 } 21692 21693 el.playerId = this.options_.playerId; 21694 } 21695 21696 if (typeof this.options_.preload !== 'undefined') { 21697 setAttribute(el, 'preload', this.options_.preload); 21698 } 21699 21700 if (this.options_.disablePictureInPicture !== undefined) { 21701 el.disablePictureInPicture = this.options_.disablePictureInPicture; 21702 } // Update specific tag settings, in case they were overridden 21703 // `autoplay` has to be *last* so that `muted` and `playsinline` are present 21704 // when iOS/Safari or other browsers attempt to autoplay. 21705 21706 21707 var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay']; 21708 21709 for (var i = 0; i < settingsAttrs.length; i++) { 21710 var attr = settingsAttrs[i]; 21711 var value = this.options_[attr]; 21712 21713 if (typeof value !== 'undefined') { 21714 if (value) { 21715 setAttribute(el, attr, attr); 21716 } else { 21717 removeAttribute(el, attr); 21718 } 21719 21720 el[attr] = value; 21721 } 21722 } 21723 21724 return el; 21725 } 21726 /** 21727 * This will be triggered if the loadstart event has already fired, before videojs was 21728 * ready. Two known examples of when this can happen are: 21729 * 1. If we're loading the playback object after it has started loading 21730 * 2. The media is already playing the (often with autoplay on) then 21731 * 21732 * This function will fire another loadstart so that videojs can catchup. 21733 * 21734 * @fires Tech#loadstart 21735 * 21736 * @return {undefined} 21737 * returns nothing. 21738 */ 21739 ; 21740 21741 _proto.handleLateInit_ = function handleLateInit_(el) { 21742 if (el.networkState === 0 || el.networkState === 3) { 21743 // The video element hasn't started loading the source yet 21744 // or didn't find a source 21745 return; 21746 } 21747 21748 if (el.readyState === 0) { 21749 // NetworkState is set synchronously BUT loadstart is fired at the 21750 // end of the current stack, usually before setInterval(fn, 0). 21751 // So at this point we know loadstart may have already fired or is 21752 // about to fire, and either way the player hasn't seen it yet. 21753 // We don't want to fire loadstart prematurely here and cause a 21754 // double loadstart so we'll wait and see if it happens between now 21755 // and the next loop, and fire it if not. 21756 // HOWEVER, we also want to make sure it fires before loadedmetadata 21757 // which could also happen between now and the next loop, so we'll 21758 // watch for that also. 21759 var loadstartFired = false; 21760 21761 var setLoadstartFired = function setLoadstartFired() { 21762 loadstartFired = true; 21763 }; 21764 21765 this.on('loadstart', setLoadstartFired); 21766 21767 var triggerLoadstart = function triggerLoadstart() { 21768 // We did miss the original loadstart. Make sure the player 21769 // sees loadstart before loadedmetadata 21770 if (!loadstartFired) { 21771 this.trigger('loadstart'); 21772 } 21773 }; 21774 21775 this.on('loadedmetadata', triggerLoadstart); 21776 this.ready(function () { 21777 this.off('loadstart', setLoadstartFired); 21778 this.off('loadedmetadata', triggerLoadstart); 21779 21780 if (!loadstartFired) { 21781 // We did miss the original native loadstart. Fire it now. 21782 this.trigger('loadstart'); 21783 } 21784 }); 21785 return; 21786 } // From here on we know that loadstart already fired and we missed it. 21787 // The other readyState events aren't as much of a problem if we double 21788 // them, so not going to go to as much trouble as loadstart to prevent 21789 // that unless we find reason to. 21790 21791 21792 var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater 21793 21794 eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater 21795 21796 if (el.readyState >= 2) { 21797 eventsToTrigger.push('loadeddata'); 21798 } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater 21799 21800 21801 if (el.readyState >= 3) { 21802 eventsToTrigger.push('canplay'); 21803 } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) 21804 21805 21806 if (el.readyState >= 4) { 21807 eventsToTrigger.push('canplaythrough'); 21808 } // We still need to give the player time to add event listeners 21809 21810 21811 this.ready(function () { 21812 eventsToTrigger.forEach(function (type) { 21813 this.trigger(type); 21814 }, this); 21815 }); 21816 } 21817 /** 21818 * Set whether we are scrubbing or not. 21819 * This is used to decide whether we should use `fastSeek` or not. 21820 * `fastSeek` is used to provide trick play on Safari browsers. 21821 * 21822 * @param {boolean} isScrubbing 21823 * - true for we are currently scrubbing 21824 * - false for we are no longer scrubbing 21825 */ 21826 ; 21827 21828 _proto.setScrubbing = function setScrubbing(isScrubbing) { 21829 this.isScrubbing_ = isScrubbing; 21830 } 21831 /** 21832 * Set current time for the `HTML5` tech. 21833 * 21834 * @param {number} seconds 21835 * Set the current time of the media to this. 21836 */ 21837 ; 21838 21839 _proto.setCurrentTime = function setCurrentTime(seconds) { 21840 try { 21841 if (this.isScrubbing_ && this.el_.fastSeek && IS_ANY_SAFARI) { 21842 this.el_.fastSeek(seconds); 21843 } else { 21844 this.el_.currentTime = seconds; 21845 } 21846 } catch (e) { 21847 log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady); 21848 } 21849 } 21850 /** 21851 * Get the current duration of the HTML5 media element. 21852 * 21853 * @return {number} 21854 * The duration of the media or 0 if there is no duration. 21855 */ 21856 ; 21857 21858 _proto.duration = function duration() { 21859 var _this5 = this; 21860 21861 // Android Chrome will report duration as Infinity for VOD HLS until after 21862 // playback has started, which triggers the live display erroneously. 21863 // Return NaN if playback has not started and trigger a durationupdate once 21864 // the duration can be reliably known. 21865 if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) { 21866 // Wait for the first `timeupdate` with currentTime > 0 - there may be 21867 // several with 0 21868 var checkProgress = function checkProgress() { 21869 if (_this5.el_.currentTime > 0) { 21870 // Trigger durationchange for genuinely live video 21871 if (_this5.el_.duration === Infinity) { 21872 _this5.trigger('durationchange'); 21873 } 21874 21875 _this5.off('timeupdate', checkProgress); 21876 } 21877 }; 21878 21879 this.on('timeupdate', checkProgress); 21880 return NaN; 21881 } 21882 21883 return this.el_.duration || NaN; 21884 } 21885 /** 21886 * Get the current width of the HTML5 media element. 21887 * 21888 * @return {number} 21889 * The width of the HTML5 media element. 21890 */ 21891 ; 21892 21893 _proto.width = function width() { 21894 return this.el_.offsetWidth; 21895 } 21896 /** 21897 * Get the current height of the HTML5 media element. 21898 * 21899 * @return {number} 21900 * The height of the HTML5 media element. 21901 */ 21902 ; 21903 21904 _proto.height = function height() { 21905 return this.el_.offsetHeight; 21906 } 21907 /** 21908 * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into 21909 * `fullscreenchange` event. 21910 * 21911 * @private 21912 * @fires fullscreenchange 21913 * @listens webkitendfullscreen 21914 * @listens webkitbeginfullscreen 21915 * @listens webkitbeginfullscreen 21916 */ 21917 ; 21918 21919 _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { 21920 var _this6 = this; 21921 21922 if (!('webkitDisplayingFullscreen' in this.el_)) { 21923 return; 21924 } 21925 21926 var endFn = function endFn() { 21927 this.trigger('fullscreenchange', { 21928 isFullscreen: false 21929 }); 21930 }; 21931 21932 var beginFn = function beginFn() { 21933 if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') { 21934 this.one('webkitendfullscreen', endFn); 21935 this.trigger('fullscreenchange', { 21936 isFullscreen: true, 21937 // set a flag in case another tech triggers fullscreenchange 21938 nativeIOSFullscreen: true 21939 }); 21940 } 21941 }; 21942 21943 this.on('webkitbeginfullscreen', beginFn); 21944 this.on('dispose', function () { 21945 _this6.off('webkitbeginfullscreen', beginFn); 21946 21947 _this6.off('webkitendfullscreen', endFn); 21948 }); 21949 } 21950 /** 21951 * Check if fullscreen is supported on the current playback device. 21952 * 21953 * @return {boolean} 21954 * - True if fullscreen is supported. 21955 * - False if fullscreen is not supported. 21956 */ 21957 ; 21958 21959 _proto.supportsFullScreen = function supportsFullScreen() { 21960 if (typeof this.el_.webkitEnterFullScreen === 'function') { 21961 var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard 21962 21963 if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { 21964 return true; 21965 } 21966 } 21967 21968 return false; 21969 } 21970 /** 21971 * Request that the `HTML5` Tech enter fullscreen. 21972 */ 21973 ; 21974 21975 _proto.enterFullScreen = function enterFullScreen() { 21976 var video = this.el_; 21977 21978 if (video.paused && video.networkState <= video.HAVE_METADATA) { 21979 // attempt to prime the video element for programmatic access 21980 // this isn't necessary on the desktop but shouldn't hurt 21981 silencePromise(this.el_.play()); // playing and pausing synchronously during the transition to fullscreen 21982 // can get iOS ~6.1 devices into a play/pause loop 21983 21984 this.setTimeout(function () { 21985 video.pause(); 21986 21987 try { 21988 video.webkitEnterFullScreen(); 21989 } catch (e) { 21990 this.trigger('fullscreenerror', e); 21991 } 21992 }, 0); 21993 } else { 21994 try { 21995 video.webkitEnterFullScreen(); 21996 } catch (e) { 21997 this.trigger('fullscreenerror', e); 21998 } 21999 } 22000 } 22001 /** 22002 * Request that the `HTML5` Tech exit fullscreen. 22003 */ 22004 ; 22005 22006 _proto.exitFullScreen = function exitFullScreen() { 22007 if (!this.el_.webkitDisplayingFullscreen) { 22008 this.trigger('fullscreenerror', new Error('The video is not fullscreen')); 22009 return; 22010 } 22011 22012 this.el_.webkitExitFullScreen(); 22013 } 22014 /** 22015 * Create a floating video window always on top of other windows so that users may 22016 * continue consuming media while they interact with other content sites, or 22017 * applications on their device. 22018 * 22019 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 22020 * 22021 * @return {Promise} 22022 * A promise with a Picture-in-Picture window. 22023 */ 22024 ; 22025 22026 _proto.requestPictureInPicture = function requestPictureInPicture() { 22027 return this.el_.requestPictureInPicture(); 22028 } 22029 /** 22030 * A getter/setter for the `Html5` Tech's source object. 22031 * > Note: Please use {@link Html5#setSource} 22032 * 22033 * @param {Tech~SourceObject} [src] 22034 * The source object you want to set on the `HTML5` techs element. 22035 * 22036 * @return {Tech~SourceObject|undefined} 22037 * - The current source object when a source is not passed in. 22038 * - undefined when setting 22039 * 22040 * @deprecated Since version 5. 22041 */ 22042 ; 22043 22044 _proto.src = function src(_src) { 22045 if (_src === undefined) { 22046 return this.el_.src; 22047 } // Setting src through `src` instead of `setSrc` will be deprecated 22048 22049 22050 this.setSrc(_src); 22051 } 22052 /** 22053 * Reset the tech by removing all sources and then calling 22054 * {@link Html5.resetMediaElement}. 22055 */ 22056 ; 22057 22058 _proto.reset = function reset() { 22059 Html5.resetMediaElement(this.el_); 22060 } 22061 /** 22062 * Get the current source on the `HTML5` Tech. Falls back to returning the source from 22063 * the HTML5 media element. 22064 * 22065 * @return {Tech~SourceObject} 22066 * The current source object from the HTML5 tech. With a fallback to the 22067 * elements source. 22068 */ 22069 ; 22070 22071 _proto.currentSrc = function currentSrc() { 22072 if (this.currentSource_) { 22073 return this.currentSource_.src; 22074 } 22075 22076 return this.el_.currentSrc; 22077 } 22078 /** 22079 * Set controls attribute for the HTML5 media Element. 22080 * 22081 * @param {string} val 22082 * Value to set the controls attribute to 22083 */ 22084 ; 22085 22086 _proto.setControls = function setControls(val) { 22087 this.el_.controls = !!val; 22088 } 22089 /** 22090 * Create and returns a remote {@link TextTrack} object. 22091 * 22092 * @param {string} kind 22093 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 22094 * 22095 * @param {string} [label] 22096 * Label to identify the text track 22097 * 22098 * @param {string} [language] 22099 * Two letter language abbreviation 22100 * 22101 * @return {TextTrack} 22102 * The TextTrack that gets created. 22103 */ 22104 ; 22105 22106 _proto.addTextTrack = function addTextTrack(kind, label, language) { 22107 if (!this.featuresNativeTextTracks) { 22108 return _Tech.prototype.addTextTrack.call(this, kind, label, language); 22109 } 22110 22111 return this.el_.addTextTrack(kind, label, language); 22112 } 22113 /** 22114 * Creates either native TextTrack or an emulated TextTrack depending 22115 * on the value of `featuresNativeTextTracks` 22116 * 22117 * @param {Object} options 22118 * The object should contain the options to initialize the TextTrack with. 22119 * 22120 * @param {string} [options.kind] 22121 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 22122 * 22123 * @param {string} [options.label] 22124 * Label to identify the text track 22125 * 22126 * @param {string} [options.language] 22127 * Two letter language abbreviation. 22128 * 22129 * @param {boolean} [options.default] 22130 * Default this track to on. 22131 * 22132 * @param {string} [options.id] 22133 * The internal id to assign this track. 22134 * 22135 * @param {string} [options.src] 22136 * A source url for the track. 22137 * 22138 * @return {HTMLTrackElement} 22139 * The track element that gets created. 22140 */ 22141 ; 22142 22143 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 22144 if (!this.featuresNativeTextTracks) { 22145 return _Tech.prototype.createRemoteTextTrack.call(this, options); 22146 } 22147 22148 var htmlTrackElement = document.createElement('track'); 22149 22150 if (options.kind) { 22151 htmlTrackElement.kind = options.kind; 22152 } 22153 22154 if (options.label) { 22155 htmlTrackElement.label = options.label; 22156 } 22157 22158 if (options.language || options.srclang) { 22159 htmlTrackElement.srclang = options.language || options.srclang; 22160 } 22161 22162 if (options["default"]) { 22163 htmlTrackElement["default"] = options["default"]; 22164 } 22165 22166 if (options.id) { 22167 htmlTrackElement.id = options.id; 22168 } 22169 22170 if (options.src) { 22171 htmlTrackElement.src = options.src; 22172 } 22173 22174 return htmlTrackElement; 22175 } 22176 /** 22177 * Creates a remote text track object and returns an html track element. 22178 * 22179 * @param {Object} options The object should contain values for 22180 * kind, language, label, and src (location of the WebVTT file) 22181 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 22182 * automatically removed from the video element whenever the source changes 22183 * @return {HTMLTrackElement} An Html Track Element. 22184 * This can be an emulated {@link HTMLTrackElement} or a native one. 22185 * @deprecated The default value of the "manualCleanup" parameter will default 22186 * to "false" in upcoming versions of Video.js 22187 */ 22188 ; 22189 22190 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 22191 var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); 22192 22193 if (this.featuresNativeTextTracks) { 22194 this.el().appendChild(htmlTrackElement); 22195 } 22196 22197 return htmlTrackElement; 22198 } 22199 /** 22200 * Remove remote `TextTrack` from `TextTrackList` object 22201 * 22202 * @param {TextTrack} track 22203 * `TextTrack` object to remove 22204 */ 22205 ; 22206 22207 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 22208 _Tech.prototype.removeRemoteTextTrack.call(this, track); 22209 22210 if (this.featuresNativeTextTracks) { 22211 var tracks = this.$$('track'); 22212 var i = tracks.length; 22213 22214 while (i--) { 22215 if (track === tracks[i] || track === tracks[i].track) { 22216 this.el().removeChild(tracks[i]); 22217 } 22218 } 22219 } 22220 } 22221 /** 22222 * Gets available media playback quality metrics as specified by the W3C's Media 22223 * Playback Quality API. 22224 * 22225 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 22226 * 22227 * @return {Object} 22228 * An object with supported media playback quality metrics 22229 */ 22230 ; 22231 22232 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 22233 if (typeof this.el().getVideoPlaybackQuality === 'function') { 22234 return this.el().getVideoPlaybackQuality(); 22235 } 22236 22237 var videoPlaybackQuality = {}; 22238 22239 if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { 22240 videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; 22241 videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; 22242 } 22243 22244 if (window$1.performance && typeof window$1.performance.now === 'function') { 22245 videoPlaybackQuality.creationTime = window$1.performance.now(); 22246 } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') { 22247 videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart; 22248 } 22249 22250 return videoPlaybackQuality; 22251 }; 22252 22253 return Html5; 22254 }(Tech); 22255 /* HTML5 Support Testing ---------------------------------------------------- */ 22256 22257 /** 22258 * Element for testing browser HTML5 media capabilities 22259 * 22260 * @type {Element} 22261 * @constant 22262 * @private 22263 */ 22264 22265 22266 defineLazyProperty(Html5, 'TEST_VID', function () { 22267 if (!isReal()) { 22268 return; 22269 } 22270 22271 var video = document.createElement('video'); 22272 var track = document.createElement('track'); 22273 track.kind = 'captions'; 22274 track.srclang = 'en'; 22275 track.label = 'English'; 22276 video.appendChild(track); 22277 return video; 22278 }); 22279 /** 22280 * Check if HTML5 media is supported by this browser/device. 22281 * 22282 * @return {boolean} 22283 * - True if HTML5 media is supported. 22284 * - False if HTML5 media is not supported. 22285 */ 22286 22287 Html5.isSupported = function () { 22288 // IE with no Media Player is a LIAR! (#984) 22289 try { 22290 Html5.TEST_VID.volume = 0.5; 22291 } catch (e) { 22292 return false; 22293 } 22294 22295 return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); 22296 }; 22297 /** 22298 * Check if the tech can support the given type 22299 * 22300 * @param {string} type 22301 * The mimetype to check 22302 * @return {string} 'probably', 'maybe', or '' (empty string) 22303 */ 22304 22305 22306 Html5.canPlayType = function (type) { 22307 return Html5.TEST_VID.canPlayType(type); 22308 }; 22309 /** 22310 * Check if the tech can support the given source 22311 * 22312 * @param {Object} srcObj 22313 * The source object 22314 * @param {Object} options 22315 * The options passed to the tech 22316 * @return {string} 'probably', 'maybe', or '' (empty string) 22317 */ 22318 22319 22320 Html5.canPlaySource = function (srcObj, options) { 22321 return Html5.canPlayType(srcObj.type); 22322 }; 22323 /** 22324 * Check if the volume can be changed in this browser/device. 22325 * Volume cannot be changed in a lot of mobile devices. 22326 * Specifically, it can't be changed from 1 on iOS. 22327 * 22328 * @return {boolean} 22329 * - True if volume can be controlled 22330 * - False otherwise 22331 */ 22332 22333 22334 Html5.canControlVolume = function () { 22335 // IE will error if Windows Media Player not installed #3315 22336 try { 22337 var volume = Html5.TEST_VID.volume; 22338 Html5.TEST_VID.volume = volume / 2 + 0.1; 22339 return volume !== Html5.TEST_VID.volume; 22340 } catch (e) { 22341 return false; 22342 } 22343 }; 22344 /** 22345 * Check if the volume can be muted in this browser/device. 22346 * Some devices, e.g. iOS, don't allow changing volume 22347 * but permits muting/unmuting. 22348 * 22349 * @return {bolean} 22350 * - True if volume can be muted 22351 * - False otherwise 22352 */ 22353 22354 22355 Html5.canMuteVolume = function () { 22356 try { 22357 var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always 22358 // work, so we want to set both property and attribute 22359 22360 Html5.TEST_VID.muted = !muted; 22361 22362 if (Html5.TEST_VID.muted) { 22363 setAttribute(Html5.TEST_VID, 'muted', 'muted'); 22364 } else { 22365 removeAttribute(Html5.TEST_VID, 'muted', 'muted'); 22366 } 22367 22368 return muted !== Html5.TEST_VID.muted; 22369 } catch (e) { 22370 return false; 22371 } 22372 }; 22373 /** 22374 * Check if the playback rate can be changed in this browser/device. 22375 * 22376 * @return {boolean} 22377 * - True if playback rate can be controlled 22378 * - False otherwise 22379 */ 22380 22381 22382 Html5.canControlPlaybackRate = function () { 22383 // Playback rate API is implemented in Android Chrome, but doesn't do anything 22384 // https://github.com/videojs/video.js/issues/3180 22385 if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) { 22386 return false; 22387 } // IE will error if Windows Media Player not installed #3315 22388 22389 22390 try { 22391 var playbackRate = Html5.TEST_VID.playbackRate; 22392 Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; 22393 return playbackRate !== Html5.TEST_VID.playbackRate; 22394 } catch (e) { 22395 return false; 22396 } 22397 }; 22398 /** 22399 * Check if we can override a video/audio elements attributes, with 22400 * Object.defineProperty. 22401 * 22402 * @return {boolean} 22403 * - True if builtin attributes can be overridden 22404 * - False otherwise 22405 */ 22406 22407 22408 Html5.canOverrideAttributes = function () { 22409 // if we cannot overwrite the src/innerHTML property, there is no support 22410 // iOS 7 safari for instance cannot do this. 22411 try { 22412 var noop = function noop() {}; 22413 22414 Object.defineProperty(document.createElement('video'), 'src', { 22415 get: noop, 22416 set: noop 22417 }); 22418 Object.defineProperty(document.createElement('audio'), 'src', { 22419 get: noop, 22420 set: noop 22421 }); 22422 Object.defineProperty(document.createElement('video'), 'innerHTML', { 22423 get: noop, 22424 set: noop 22425 }); 22426 Object.defineProperty(document.createElement('audio'), 'innerHTML', { 22427 get: noop, 22428 set: noop 22429 }); 22430 } catch (e) { 22431 return false; 22432 } 22433 22434 return true; 22435 }; 22436 /** 22437 * Check to see if native `TextTrack`s are supported by this browser/device. 22438 * 22439 * @return {boolean} 22440 * - True if native `TextTrack`s are supported. 22441 * - False otherwise 22442 */ 22443 22444 22445 Html5.supportsNativeTextTracks = function () { 22446 return IS_ANY_SAFARI || IS_IOS && IS_CHROME; 22447 }; 22448 /** 22449 * Check to see if native `VideoTrack`s are supported by this browser/device 22450 * 22451 * @return {boolean} 22452 * - True if native `VideoTrack`s are supported. 22453 * - False otherwise 22454 */ 22455 22456 22457 Html5.supportsNativeVideoTracks = function () { 22458 return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); 22459 }; 22460 /** 22461 * Check to see if native `AudioTrack`s are supported by this browser/device 22462 * 22463 * @return {boolean} 22464 * - True if native `AudioTrack`s are supported. 22465 * - False otherwise 22466 */ 22467 22468 22469 Html5.supportsNativeAudioTracks = function () { 22470 return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); 22471 }; 22472 /** 22473 * An array of events available on the Html5 tech. 22474 * 22475 * @private 22476 * @type {Array} 22477 */ 22478 22479 22480 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']; 22481 /** 22482 * Boolean indicating whether the `Tech` supports volume control. 22483 * 22484 * @type {boolean} 22485 * @default {@link Html5.canControlVolume} 22486 */ 22487 22488 /** 22489 * Boolean indicating whether the `Tech` supports muting volume. 22490 * 22491 * @type {bolean} 22492 * @default {@link Html5.canMuteVolume} 22493 */ 22494 22495 /** 22496 * Boolean indicating whether the `Tech` supports changing the speed at which the media 22497 * plays. Examples: 22498 * - Set player to play 2x (twice) as fast 22499 * - Set player to play 0.5x (half) as fast 22500 * 22501 * @type {boolean} 22502 * @default {@link Html5.canControlPlaybackRate} 22503 */ 22504 22505 /** 22506 * Boolean indicating whether the `Tech` supports the `sourceset` event. 22507 * 22508 * @type {boolean} 22509 * @default 22510 */ 22511 22512 /** 22513 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. 22514 * 22515 * @type {boolean} 22516 * @default {@link Html5.supportsNativeTextTracks} 22517 */ 22518 22519 /** 22520 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. 22521 * 22522 * @type {boolean} 22523 * @default {@link Html5.supportsNativeVideoTracks} 22524 */ 22525 22526 /** 22527 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. 22528 * 22529 * @type {boolean} 22530 * @default {@link Html5.supportsNativeAudioTracks} 22531 */ 22532 22533 [['featuresVolumeControl', 'canControlVolume'], ['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) { 22534 var key = _ref[0], 22535 fn = _ref[1]; 22536 defineLazyProperty(Html5.prototype, key, function () { 22537 return Html5[fn](); 22538 }, true); 22539 }); 22540 /** 22541 * Boolean indicating whether the `HTML5` tech currently supports the media element 22542 * moving in the DOM. iOS breaks if you move the media element, so this is set this to 22543 * false there. Everywhere else this should be true. 22544 * 22545 * @type {boolean} 22546 * @default 22547 */ 22548 22549 Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed. 22550 // Is this true? 22551 22552 /** 22553 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize 22554 * when going into fullscreen. 22555 * 22556 * @type {boolean} 22557 * @default 22558 */ 22559 22560 Html5.prototype.featuresFullscreenResize = true; 22561 /** 22562 * Boolean indicating whether the `HTML5` tech currently supports the progress event. 22563 * If this is false, manual `progress` events will be triggered instead. 22564 * 22565 * @type {boolean} 22566 * @default 22567 */ 22568 22569 Html5.prototype.featuresProgressEvents = true; 22570 /** 22571 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. 22572 * If this is false, manual `timeupdate` events will be triggered instead. 22573 * 22574 * @default 22575 */ 22576 22577 Html5.prototype.featuresTimeupdateEvents = true; // HTML5 Feature detection and Device Fixes --------------------------------- // 22578 22579 var canPlayType; 22580 22581 Html5.patchCanPlayType = function () { 22582 // Android 4.0 and above can play HLS to some extent but it reports being unable to do so 22583 // Firefox and Chrome report correctly 22584 if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) { 22585 canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; 22586 22587 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 22588 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 22589 22590 if (type && mpegurlRE.test(type)) { 22591 return 'maybe'; 22592 } 22593 22594 return canPlayType.call(this, type); 22595 }; 22596 } 22597 }; 22598 22599 Html5.unpatchCanPlayType = function () { 22600 var r = Html5.TEST_VID.constructor.prototype.canPlayType; 22601 22602 if (canPlayType) { 22603 Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; 22604 } 22605 22606 return r; 22607 }; // by default, patch the media element 22608 22609 22610 Html5.patchCanPlayType(); 22611 22612 Html5.disposeMediaElement = function (el) { 22613 if (!el) { 22614 return; 22615 } 22616 22617 if (el.parentNode) { 22618 el.parentNode.removeChild(el); 22619 } // remove any child track or source nodes to prevent their loading 22620 22621 22622 while (el.hasChildNodes()) { 22623 el.removeChild(el.firstChild); 22624 } // remove any src reference. not setting `src=''` because that causes a warning 22625 // in firefox 22626 22627 22628 el.removeAttribute('src'); // force the media element to update its loading state by calling load() 22629 // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) 22630 22631 if (typeof el.load === 'function') { 22632 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 22633 (function () { 22634 try { 22635 el.load(); 22636 } catch (e) {// not supported 22637 } 22638 })(); 22639 } 22640 }; 22641 22642 Html5.resetMediaElement = function (el) { 22643 if (!el) { 22644 return; 22645 } 22646 22647 var sources = el.querySelectorAll('source'); 22648 var i = sources.length; 22649 22650 while (i--) { 22651 el.removeChild(sources[i]); 22652 } // remove any src reference. 22653 // not setting `src=''` because that throws an error 22654 22655 22656 el.removeAttribute('src'); 22657 22658 if (typeof el.load === 'function') { 22659 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 22660 (function () { 22661 try { 22662 el.load(); 22663 } catch (e) {// satisfy linter 22664 } 22665 })(); 22666 } 22667 }; 22668 /* Native HTML5 element property wrapping ----------------------------------- */ 22669 // Wrap native boolean attributes with getters that check both property and attribute 22670 // The list is as followed: 22671 // muted, defaultMuted, autoplay, controls, loop, playsinline 22672 22673 22674 [ 22675 /** 22676 * Get the value of `muted` from the media element. `muted` indicates 22677 * that the volume for the media should be set to silent. This does not actually change 22678 * the `volume` attribute. 22679 * 22680 * @method Html5#muted 22681 * @return {boolean} 22682 * - True if the value of `volume` should be ignored and the audio set to silent. 22683 * - False if the value of `volume` should be used. 22684 * 22685 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 22686 */ 22687 'muted', 22688 /** 22689 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates 22690 * whether the media should start muted or not. Only changes the default state of the 22691 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the 22692 * current state. 22693 * 22694 * @method Html5#defaultMuted 22695 * @return {boolean} 22696 * - The value of `defaultMuted` from the media element. 22697 * - True indicates that the media should start muted. 22698 * - False indicates that the media should not start muted 22699 * 22700 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 22701 */ 22702 'defaultMuted', 22703 /** 22704 * Get the value of `autoplay` from the media element. `autoplay` indicates 22705 * that the media should start to play as soon as the page is ready. 22706 * 22707 * @method Html5#autoplay 22708 * @return {boolean} 22709 * - The value of `autoplay` from the media element. 22710 * - True indicates that the media should start as soon as the page loads. 22711 * - False indicates that the media should not start as soon as the page loads. 22712 * 22713 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 22714 */ 22715 'autoplay', 22716 /** 22717 * Get the value of `controls` from the media element. `controls` indicates 22718 * whether the native media controls should be shown or hidden. 22719 * 22720 * @method Html5#controls 22721 * @return {boolean} 22722 * - The value of `controls` from the media element. 22723 * - True indicates that native controls should be showing. 22724 * - False indicates that native controls should be hidden. 22725 * 22726 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} 22727 */ 22728 'controls', 22729 /** 22730 * Get the value of `loop` from the media element. `loop` indicates 22731 * that the media should return to the start of the media and continue playing once 22732 * it reaches the end. 22733 * 22734 * @method Html5#loop 22735 * @return {boolean} 22736 * - The value of `loop` from the media element. 22737 * - True indicates that playback should seek back to start once 22738 * the end of a media is reached. 22739 * - False indicates that playback should not loop back to the start when the 22740 * end of the media is reached. 22741 * 22742 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 22743 */ 22744 'loop', 22745 /** 22746 * Get the value of `playsinline` from the media element. `playsinline` indicates 22747 * to the browser that non-fullscreen playback is preferred when fullscreen 22748 * playback is the native default, such as in iOS Safari. 22749 * 22750 * @method Html5#playsinline 22751 * @return {boolean} 22752 * - The value of `playsinline` from the media element. 22753 * - True indicates that the media should play inline. 22754 * - False indicates that the media should not play inline. 22755 * 22756 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 22757 */ 22758 'playsinline'].forEach(function (prop) { 22759 Html5.prototype[prop] = function () { 22760 return this.el_[prop] || this.el_.hasAttribute(prop); 22761 }; 22762 }); // Wrap native boolean attributes with setters that set both property and attribute 22763 // The list is as followed: 22764 // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline 22765 // setControls is special-cased above 22766 22767 [ 22768 /** 22769 * Set the value of `muted` on the media element. `muted` indicates that the current 22770 * audio level should be silent. 22771 * 22772 * @method Html5#setMuted 22773 * @param {boolean} muted 22774 * - True if the audio should be set to silent 22775 * - False otherwise 22776 * 22777 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 22778 */ 22779 'muted', 22780 /** 22781 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current 22782 * audio level should be silent, but will only effect the muted level on initial playback.. 22783 * 22784 * @method Html5.prototype.setDefaultMuted 22785 * @param {boolean} defaultMuted 22786 * - True if the audio should be set to silent 22787 * - False otherwise 22788 * 22789 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 22790 */ 22791 'defaultMuted', 22792 /** 22793 * Set the value of `autoplay` on the media element. `autoplay` indicates 22794 * that the media should start to play as soon as the page is ready. 22795 * 22796 * @method Html5#setAutoplay 22797 * @param {boolean} autoplay 22798 * - True indicates that the media should start as soon as the page loads. 22799 * - False indicates that the media should not start as soon as the page loads. 22800 * 22801 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 22802 */ 22803 'autoplay', 22804 /** 22805 * Set the value of `loop` on the media element. `loop` indicates 22806 * that the media should return to the start of the media and continue playing once 22807 * it reaches the end. 22808 * 22809 * @method Html5#setLoop 22810 * @param {boolean} loop 22811 * - True indicates that playback should seek back to start once 22812 * the end of a media is reached. 22813 * - False indicates that playback should not loop back to the start when the 22814 * end of the media is reached. 22815 * 22816 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 22817 */ 22818 'loop', 22819 /** 22820 * Set the value of `playsinline` from the media element. `playsinline` indicates 22821 * to the browser that non-fullscreen playback is preferred when fullscreen 22822 * playback is the native default, such as in iOS Safari. 22823 * 22824 * @method Html5#setPlaysinline 22825 * @param {boolean} playsinline 22826 * - True indicates that the media should play inline. 22827 * - False indicates that the media should not play inline. 22828 * 22829 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 22830 */ 22831 'playsinline'].forEach(function (prop) { 22832 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 22833 this.el_[prop] = v; 22834 22835 if (v) { 22836 this.el_.setAttribute(prop, prop); 22837 } else { 22838 this.el_.removeAttribute(prop); 22839 } 22840 }; 22841 }); // Wrap native properties with a getter 22842 // The list is as followed 22843 // paused, currentTime, buffered, volume, poster, preload, error, seeking 22844 // seekable, ended, playbackRate, defaultPlaybackRate, disablePictureInPicture 22845 // played, networkState, readyState, videoWidth, videoHeight, crossOrigin 22846 22847 [ 22848 /** 22849 * Get the value of `paused` from the media element. `paused` indicates whether the media element 22850 * is currently paused or not. 22851 * 22852 * @method Html5#paused 22853 * @return {boolean} 22854 * The value of `paused` from the media element. 22855 * 22856 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} 22857 */ 22858 'paused', 22859 /** 22860 * Get the value of `currentTime` from the media element. `currentTime` indicates 22861 * the current second that the media is at in playback. 22862 * 22863 * @method Html5#currentTime 22864 * @return {number} 22865 * The value of `currentTime` from the media element. 22866 * 22867 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} 22868 */ 22869 'currentTime', 22870 /** 22871 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` 22872 * object that represents the parts of the media that are already downloaded and 22873 * available for playback. 22874 * 22875 * @method Html5#buffered 22876 * @return {TimeRange} 22877 * The value of `buffered` from the media element. 22878 * 22879 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} 22880 */ 22881 'buffered', 22882 /** 22883 * Get the value of `volume` from the media element. `volume` indicates 22884 * the current playback volume of audio for a media. `volume` will be a value from 0 22885 * (silent) to 1 (loudest and default). 22886 * 22887 * @method Html5#volume 22888 * @return {number} 22889 * The value of `volume` from the media element. Value will be between 0-1. 22890 * 22891 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 22892 */ 22893 'volume', 22894 /** 22895 * Get the value of `poster` from the media element. `poster` indicates 22896 * that the url of an image file that can/will be shown when no media data is available. 22897 * 22898 * @method Html5#poster 22899 * @return {string} 22900 * The value of `poster` from the media element. Value will be a url to an 22901 * image. 22902 * 22903 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} 22904 */ 22905 'poster', 22906 /** 22907 * Get the value of `preload` from the media element. `preload` indicates 22908 * what should download before the media is interacted with. It can have the following 22909 * values: 22910 * - none: nothing should be downloaded 22911 * - metadata: poster and the first few frames of the media may be downloaded to get 22912 * media dimensions and other metadata 22913 * - auto: allow the media and metadata for the media to be downloaded before 22914 * interaction 22915 * 22916 * @method Html5#preload 22917 * @return {string} 22918 * The value of `preload` from the media element. Will be 'none', 'metadata', 22919 * or 'auto'. 22920 * 22921 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 22922 */ 22923 'preload', 22924 /** 22925 * Get the value of the `error` from the media element. `error` indicates any 22926 * MediaError that may have occurred during playback. If error returns null there is no 22927 * current error. 22928 * 22929 * @method Html5#error 22930 * @return {MediaError|null} 22931 * The value of `error` from the media element. Will be `MediaError` if there 22932 * is a current error and null otherwise. 22933 * 22934 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} 22935 */ 22936 'error', 22937 /** 22938 * Get the value of `seeking` from the media element. `seeking` indicates whether the 22939 * media is currently seeking to a new position or not. 22940 * 22941 * @method Html5#seeking 22942 * @return {boolean} 22943 * - The value of `seeking` from the media element. 22944 * - True indicates that the media is currently seeking to a new position. 22945 * - False indicates that the media is not seeking to a new position at this time. 22946 * 22947 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} 22948 */ 22949 'seeking', 22950 /** 22951 * Get the value of `seekable` from the media element. `seekable` returns a 22952 * `TimeRange` object indicating ranges of time that can currently be `seeked` to. 22953 * 22954 * @method Html5#seekable 22955 * @return {TimeRange} 22956 * The value of `seekable` from the media element. A `TimeRange` object 22957 * indicating the current ranges of time that can be seeked to. 22958 * 22959 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} 22960 */ 22961 'seekable', 22962 /** 22963 * Get the value of `ended` from the media element. `ended` indicates whether 22964 * the media has reached the end or not. 22965 * 22966 * @method Html5#ended 22967 * @return {boolean} 22968 * - The value of `ended` from the media element. 22969 * - True indicates that the media has ended. 22970 * - False indicates that the media has not ended. 22971 * 22972 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} 22973 */ 22974 'ended', 22975 /** 22976 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
22977 * the rate at which the media is currently playing back. Examples: 22978 * - if playbackRate is set to 2, media will play twice as fast. 22979 * - if playbackRate is set to 0.5, media will play half as fast. 22980 * 22981 * @method Html5#playbackRate 22982 * @return {number} 22983 * The value of `playbackRate` from the media element. A number indicating 22984 * the current playback speed of the media, where 1 is normal speed. 22985 * 22986 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22987 */ 22988 'playbackRate', 22989 /** 22990 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates 22991 * the rate at which the media is currently playing back. This value will not indicate the current 22992 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. 22993 * 22994 * Examples: 22995 * - if defaultPlaybackRate is set to 2, media will play twice as fast. 22996 * - if defaultPlaybackRate is set to 0.5, media will play half as fast. 22997 * 22998 * @method Html5.prototype.defaultPlaybackRate 22999 * @return {number} 23000 * The value of `defaultPlaybackRate` from the media element. A number indicating 23001 * the current playback speed of the media, where 1 is normal speed. 23002 * 23003 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 23004 */ 23005 'defaultPlaybackRate', 23006 /** 23007 * Get the value of 'disablePictureInPicture' from the video element. 23008 * 23009 * @method Html5#disablePictureInPicture 23010 * @return {boolean} value 23011 * - The value of `disablePictureInPicture` from the video element. 23012 * - True indicates that the video can't be played in Picture-In-Picture mode 23013 * - False indicates that the video can be played in Picture-In-Picture mode 23014 * 23015 * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip} 23016 */ 23017 'disablePictureInPicture', 23018 /** 23019 * Get the value of `played` from the media element. `played` returns a `TimeRange` 23020 * object representing points in the media timeline that have been played. 23021 * 23022 * @method Html5#played 23023 * @return {TimeRange} 23024 * The value of `played` from the media element. A `TimeRange` object indicating 23025 * the ranges of time that have been played. 23026 * 23027 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} 23028 */ 23029 'played', 23030 /** 23031 * Get the value of `networkState` from the media element. `networkState` indicates 23032 * the current network state. It returns an enumeration from the following list: 23033 * - 0: NETWORK_EMPTY 23034 * - 1: NETWORK_IDLE 23035 * - 2: NETWORK_LOADING 23036 * - 3: NETWORK_NO_SOURCE 23037 * 23038 * @method Html5#networkState 23039 * @return {number} 23040 * The value of `networkState` from the media element. This will be a number 23041 * from the list in the description. 23042 * 23043 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} 23044 */ 23045 'networkState', 23046 /** 23047 * Get the value of `readyState` from the media element. `readyState` indicates 23048 * the current state of the media element. It returns an enumeration from the 23049 * following list: 23050 * - 0: HAVE_NOTHING 23051 * - 1: HAVE_METADATA 23052 * - 2: HAVE_CURRENT_DATA 23053 * - 3: HAVE_FUTURE_DATA 23054 * - 4: HAVE_ENOUGH_DATA 23055 * 23056 * @method Html5#readyState 23057 * @return {number} 23058 * The value of `readyState` from the media element. This will be a number 23059 * from the list in the description. 23060 * 23061 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} 23062 */ 23063 'readyState', 23064 /** 23065 * Get the value of `videoWidth` from the video element. `videoWidth` indicates 23066 * the current width of the video in css pixels. 23067 * 23068 * @method Html5#videoWidth 23069 * @return {number} 23070 * The value of `videoWidth` from the video element. This will be a number 23071 * in css pixels. 23072 * 23073 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 23074 */ 23075 'videoWidth', 23076 /** 23077 * Get the value of `videoHeight` from the video element. `videoHeight` indicates 23078 * the current height of the video in css pixels. 23079 * 23080 * @method Html5#videoHeight 23081 * @return {number} 23082 * The value of `videoHeight` from the video element. This will be a number 23083 * in css pixels. 23084 * 23085 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 23086 */ 23087 'videoHeight', 23088 /** 23089 * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates 23090 * to the browser that should sent the cookies along with the requests for the 23091 * different assets/playlists 23092 * 23093 * @method Html5#crossOrigin 23094 * @return {string} 23095 * - anonymous indicates that the media should not sent cookies. 23096 * - use-credentials indicates that the media should sent cookies along the requests. 23097 * 23098 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin} 23099 */ 23100 'crossOrigin'].forEach(function (prop) { 23101 Html5.prototype[prop] = function () { 23102 return this.el_[prop]; 23103 }; 23104 }); // Wrap native properties with a setter in this format: 23105 // set + toTitleCase(name) 23106 // The list is as follows: 23107 // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate, 23108 // setDisablePictureInPicture, setCrossOrigin 23109 23110 [ 23111 /** 23112 * Set the value of `volume` on the media element. `volume` indicates the current 23113 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and 23114 * so on. 23115 * 23116 * @method Html5#setVolume 23117 * @param {number} percentAsDecimal 23118 * The volume percent as a decimal. Valid range is from 0-1. 23119 * 23120 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 23121 */ 23122 'volume', 23123 /** 23124 * Set the value of `src` on the media element. `src` indicates the current 23125 * {@link Tech~SourceObject} for the media. 23126 * 23127 * @method Html5#setSrc 23128 * @param {Tech~SourceObject} src 23129 * The source object to set as the current source. 23130 * 23131 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} 23132 */ 23133 'src', 23134 /** 23135 * Set the value of `poster` on the media element. `poster` is the url to 23136 * an image file that can/will be shown when no media data is available. 23137 * 23138 * @method Html5#setPoster 23139 * @param {string} poster 23140 * The url to an image that should be used as the `poster` for the media 23141 * element. 23142 * 23143 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} 23144 */ 23145 'poster', 23146 /** 23147 * Set the value of `preload` on the media element. `preload` indicates 23148 * what should download before the media is interacted with. It can have the following 23149 * values: 23150 * - none: nothing should be downloaded 23151 * - metadata: poster and the first few frames of the media may be downloaded to get 23152 * media dimensions and other metadata 23153 * - auto: allow the media and metadata for the media to be downloaded before 23154 * interaction 23155 * 23156 * @method Html5#setPreload 23157 * @param {string} preload 23158 * The value of `preload` to set on the media element. Must be 'none', 'metadata', 23159 * or 'auto'. 23160 * 23161 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 23162 */ 23163 'preload', 23164 /** 23165 * Set the value of `playbackRate` on the media element. `playbackRate` indicates 23166 * the rate at which the media should play back. Examples: 23167 * - if playbackRate is set to 2, media will play twice as fast. 23168 * - if playbackRate is set to 0.5, media will play half as fast. 23169 * 23170 * @method Html5#setPlaybackRate 23171 * @return {number} 23172 * The value of `playbackRate` from the media element. A number indicating 23173 * the current playback speed of the media, where 1 is normal speed. 23174 * 23175 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 23176 */ 23177 'playbackRate', 23178 /** 23179 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates 23180 * the rate at which the media should play back upon initial startup. Changing this value 23181 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. 23182 * 23183 * Example Values: 23184 * - if playbackRate is set to 2, media will play twice as fast. 23185 * - if playbackRate is set to 0.5, media will play half as fast. 23186 * 23187 * @method Html5.prototype.setDefaultPlaybackRate 23188 * @return {number} 23189 * The value of `defaultPlaybackRate` from the media element. A number indicating 23190 * the current playback speed of the media, where 1 is normal speed. 23191 * 23192 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} 23193 */ 23194 'defaultPlaybackRate', 23195 /** 23196 * Prevents the browser from suggesting a Picture-in-Picture context menu 23197 * or to request Picture-in-Picture automatically in some cases. 23198 * 23199 * @method Html5#setDisablePictureInPicture 23200 * @param {boolean} value 23201 * The true value will disable Picture-in-Picture mode. 23202 * 23203 * @see [Spec]{@link https://w3c.github.io/picture-in-picture/#disable-pip} 23204 */ 23205 'disablePictureInPicture', 23206 /** 23207 * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates 23208 * to the browser that should sent the cookies along with the requests for the 23209 * different assets/playlists 23210 * 23211 * @method Html5#setCrossOrigin 23212 * @param {string} crossOrigin 23213 * - anonymous indicates that the media should not sent cookies. 23214 * - use-credentials indicates that the media should sent cookies along the requests. 23215 * 23216 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin} 23217 */ 23218 'crossOrigin'].forEach(function (prop) { 23219 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 23220 this.el_[prop] = v; 23221 }; 23222 }); // wrap native functions with a function 23223 // The list is as follows: 23224 // pause, load, play 23225 23226 [ 23227 /** 23228 * A wrapper around the media elements `pause` function. This will call the `HTML5` 23229 * media elements `pause` function. 23230 * 23231 * @method Html5#pause 23232 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} 23233 */ 23234 'pause', 23235 /** 23236 * A wrapper around the media elements `load` function. This will call the `HTML5`s 23237 * media element `load` function. 23238 * 23239 * @method Html5#load 23240 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} 23241 */ 23242 'load', 23243 /** 23244 * A wrapper around the media elements `play` function. This will call the `HTML5`s 23245 * media element `play` function. 23246 * 23247 * @method Html5#play 23248 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} 23249 */ 23250 'play'].forEach(function (prop) { 23251 Html5.prototype[prop] = function () { 23252 return this.el_[prop](); 23253 }; 23254 }); 23255 Tech.withSourceHandlers(Html5); 23256 /** 23257 * Native source handler for Html5, simply passes the source to the media element. 23258 * 23259 * @property {Tech~SourceObject} source 23260 * The source object 23261 * 23262 * @property {Html5} tech 23263 * The instance of the HTML5 tech. 23264 */ 23265 23266 Html5.nativeSourceHandler = {}; 23267 /** 23268 * Check if the media element can play the given mime type. 23269 * 23270 * @param {string} type 23271 * The mimetype to check 23272 * 23273 * @return {string} 23274 * 'probably', 'maybe', or '' (empty string) 23275 */ 23276 23277 Html5.nativeSourceHandler.canPlayType = function (type) { 23278 // IE without MediaPlayer throws an error (#519) 23279 try {
23280 return Html5.TEST_VID.canPlayType(type); 23281 } catch (e) { 23282 return ''; 23283 } 23284 }; 23285 /** 23286 * Check if the media element can handle a source natively. 23287 * 23288 * @param {Tech~SourceObject} source 23289 * The source object 23290 * 23291 * @param {Object} [options] 23292 * Options to be passed to the tech. 23293 * 23294 * @return {string} 23295 * 'probably', 'maybe', or '' (empty string). 23296 */ 23297 23298 23299 Html5.nativeSourceHandler.canHandleSource = function (source, options) { 23300 // If a type was provided we should rely on that 23301 if (source.type) { 23302 return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]' 23303 } else if (source.src) { 23304 var ext = getFileExtension(source.src); 23305 return Html5.nativeSourceHandler.canPlayType("video/" + ext); 23306 } 23307 23308 return ''; 23309 }; 23310 /** 23311 * Pass the source to the native media element. 23312 * 23313 * @param {Tech~SourceObject} source 23314 * The source object 23315 * 23316 * @param {Html5} tech 23317 * The instance of the Html5 tech 23318 * 23319 * @param {Object} [options] 23320 * The options to pass to the source 23321 */ 23322 23323 23324 Html5.nativeSourceHandler.handleSource = function (source, tech, options) { 23325 tech.setSrc(source.src); 23326 }; 23327 /** 23328 * A noop for the native dispose function, as cleanup is not needed. 23329 */ 23330 23331 23332 Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler 23333 23334 23335 Html5.registerSourceHandler(Html5.nativeSourceHandler); 23336 Tech.registerTech('Html5', Html5); 23337 23338 // on the player when they happen 23339 23340 var TECH_EVENTS_RETRIGGER = [ 23341 /** 23342 * Fired while the user agent is downloading media data. 23343 * 23344 * @event Player#progress 23345 * @type {EventTarget~Event} 23346 */ 23347 23348 /** 23349 * Retrigger the `progress` event that was triggered by the {@link Tech}. 23350 * 23351 * @private 23352 * @method Player#handleTechProgress_ 23353 * @fires Player#progress 23354 * @listens Tech#progress 23355 */ 23356 'progress', 23357 /** 23358 * Fires when the loading of an audio/video is aborted. 23359 * 23360 * @event Player#abort 23361 * @type {EventTarget~Event} 23362 */ 23363 23364 /** 23365 * Retrigger the `abort` event that was triggered by the {@link Tech}. 23366 * 23367 * @private 23368 * @method Player#handleTechAbort_ 23369 * @fires Player#abort 23370 * @listens Tech#abort 23371 */ 23372 'abort', 23373 /** 23374 * Fires when the browser is intentionally not getting media data. 23375 * 23376 * @event Player#suspend 23377 * @type {EventTarget~Event} 23378 */ 23379 23380 /** 23381 * Retrigger the `suspend` event that was triggered by the {@link Tech}. 23382 * 23383 * @private 23384 * @method Player#handleTechSuspend_ 23385 * @fires Player#suspend 23386 * @listens Tech#suspend 23387 */ 23388 'suspend', 23389 /** 23390 * Fires when the current playlist is empty. 23391 * 23392 * @event Player#emptied 23393 * @type {EventTarget~Event} 23394 */ 23395 23396 /** 23397 * Retrigger the `emptied` event that was triggered by the {@link Tech}. 23398 * 23399 * @private 23400 * @method Player#handleTechEmptied_ 23401 * @fires Player#emptied 23402 * @listens Tech#emptied 23403 */ 23404 'emptied', 23405 /** 23406 * Fires when the browser is trying to get media data, but data is not available. 23407 * 23408 * @event Player#stalled 23409 * @type {EventTarget~Event} 23410 */ 23411 23412 /** 23413 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 23414 * 23415 * @private 23416 * @method Player#handleTechStalled_ 23417 * @fires Player#stalled 23418 * @listens Tech#stalled 23419 */ 23420 'stalled', 23421 /** 23422 * Fires when the browser has loaded meta data for the audio/video. 23423 * 23424 * @event Player#loadedmetadata 23425 * @type {EventTarget~Event} 23426 */ 23427 23428 /** 23429 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 23430 * 23431 * @private 23432 * @method Player#handleTechLoadedmetadata_ 23433 * @fires Player#loadedmetadata 23434 * @listens Tech#loadedmetadata 23435 */ 23436 'loadedmetadata', 23437 /** 23438 * Fires when the browser has loaded the current frame of the audio/video. 23439 * 23440 * @event Player#loadeddata 23441 * @type {event} 23442 */ 23443 23444 /** 23445 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. 23446 * 23447 * @private 23448 * @method Player#handleTechLoaddeddata_ 23449 * @fires Player#loadeddata 23450 * @listens Tech#loadeddata 23451 */ 23452 'loadeddata', 23453 /** 23454 * Fires when the current playback position has changed. 23455 * 23456 * @event Player#timeupdate 23457 * @type {event} 23458 */ 23459 23460 /** 23461 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. 23462 * 23463 * @private 23464 * @method Player#handleTechTimeUpdate_ 23465 * @fires Player#timeupdate 23466 * @listens Tech#timeupdate 23467 */ 23468 'timeupdate', 23469 /** 23470 * Fires when the video's intrinsic dimensions change 23471 * 23472 * @event Player#resize 23473 * @type {event} 23474 */ 23475 23476 /** 23477 * Retrigger the `resize` event that was triggered by the {@link Tech}. 23478 * 23479 * @private 23480 * @method Player#handleTechResize_ 23481 * @fires Player#resize 23482 * @listens Tech#resize 23483 */ 23484 'resize', 23485 /** 23486 * Fires when the volume has been changed 23487 * 23488 * @event Player#volumechange 23489 * @type {event} 23490 */ 23491 23492 /** 23493 * Retrigger the `volumechange` event that was triggered by the {@link Tech}. 23494 * 23495 * @private 23496 * @method Player#handleTechVolumechange_ 23497 * @fires Player#volumechange 23498 * @listens Tech#volumechange 23499 */ 23500 'volumechange', 23501 /** 23502 * Fires when the text track has been changed 23503 * 23504 * @event Player#texttrackchange 23505 * @type {event} 23506 */ 23507 23508 /** 23509 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. 23510 * 23511 * @private 23512 * @method Player#handleTechTexttrackchange_ 23513 * @fires Player#texttrackchange 23514 * @listens Tech#texttrackchange 23515 */ 23516 'texttrackchange']; // events to queue when playback rate is zero 23517 // this is a hash for the sole purpose of mapping non-camel-cased event names 23518 // to camel-cased function names 23519 23520 var TECH_EVENTS_QUEUE = { 23521 canplay: 'CanPlay', 23522 canplaythrough: 'CanPlayThrough', 23523 playing: 'Playing', 23524 seeked: 'Seeked' 23525 }; 23526 var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge']; 23527 var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny 23528 // grep: vjs-layout-x-small 23529 // grep: vjs-layout-small 23530 // grep: vjs-layout-medium 23531 // grep: vjs-layout-large 23532 // grep: vjs-layout-x-large 23533 // grep: vjs-layout-huge 23534
vendor: 24,276 bytes, lines 23535-24265
23535 BREAKPOINT_ORDER.forEach(function (k) { 23536 var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k; 23537 BREAKPOINT_CLASSES[k] = "vjs-layout-" + v; 23538 }); 23539 var DEFAULT_BREAKPOINTS = { 23540 tiny: 210, 23541 xsmall: 320, 23542 small: 425, 23543 medium: 768, 23544 large: 1440, 23545 xlarge: 2560, 23546 huge: Infinity 23547 }; 23548 /** 23549 * An instance of the `Player` class is created when any of the Video.js setup methods 23550 * are used to initialize a video. 23551 * 23552 * After an instance has been created it can be accessed globally in two ways: 23553 * 1. By calling `videojs('example_video_1');` 23554 * 2. By using it directly via `videojs.players.example_video_1;` 23555 * 23556 * @extends Component 23557 */ 23558 23559 var Player = /*#__PURE__*/function (_Component) { 23560 inheritsLoose(Player, _Component); 23561 23562 /** 23563 * Create an instance of this class. 23564 * 23565 * @param {Element} tag 23566 * The original video DOM element used for configuring options. 23567 * 23568 * @param {Object} [options] 23569 * Object of option names and values. 23570 * 23571 * @param {Component~ReadyCallback} [ready] 23572 * Ready callback function. 23573 */ 23574 function Player(tag, options, ready) { 23575 var _this; 23576 23577 // Make sure tag ID exists 23578 tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options 23579 // The options argument overrides options set in the video tag 23580 // which overrides globally set options. 23581 // This latter part coincides with the load order 23582 // (tag must exist before Player) 23583 23584 options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up 23585 // player properties first, and can't use `this` before `super()` 23586 23587 options.initChildren = false; // Same with creating the element 23588 23589 options.createEl = false; // don't auto mixin the evented mixin 23590 23591 options.evented = false; // we don't want the player to report touch activity on itself 23592 // see enableTouchActivity in Component 23593 23594 options.reportTouchActivity = false; // If language is not set, get the closest lang attribute 23595 23596 if (!options.language) { 23597 if (typeof tag.closest === 'function') { 23598 var closest = tag.closest('[lang]'); 23599 23600 if (closest && closest.getAttribute) { 23601 options.language = closest.getAttribute('lang'); 23602 } 23603 } else { 23604 var element = tag; 23605 23606 while (element && element.nodeType === 1) { 23607 if (getAttributes(element).hasOwnProperty('lang')) { 23608 options.language = element.getAttribute('lang'); 23609 break; 23610 } 23611 23612 element = element.parentNode; 23613 } 23614 } 23615 } // Run base component initializing with new options 23616 23617 23618 _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners. 23619 23620 _this.boundDocumentFullscreenChange_ = bind(assertThisInitialized(_this), _this.documentFullscreenChange_); 23621 _this.boundFullWindowOnEscKey_ = bind(assertThisInitialized(_this), _this.fullWindowOnEscKey); // default isFullscreen_ to false 23622 23623 _this.isFullscreen_ = false; // create logger 23624 23625 _this.log = createLogger$1(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests 23626 23627 _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster 23628 23629 _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero 23630 // and a seek is happening 23631 23632 _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously 23633 23634 _this.isReady_ = false; // Init state hasStarted_ 23635 23636 _this.hasStarted_ = false; // Init state userActive_ 23637 23638 _this.userActive_ = false; // Init debugEnabled_ 23639 23640 _this.debugEnabled_ = false; // if the global option object was accidentally blown away by 23641 // someone, bail early with an informative error 23642 23643 if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { 23644 throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); 23645 } // Store the original tag used to set options 23646 23647 23648 _this.tag = tag; // Store the tag attributes used to restore html5 element 23649 23650 _this.tagAttributes = tag && getAttributes(tag); // Update current language 23651 23652 _this.language(_this.options_.language); // Update Supported Languages 23653 23654 23655 if (options.languages) { 23656 // Normalise player option languages to lowercase 23657 var languagesToLower = {}; 23658 Object.getOwnPropertyNames(options.languages).forEach(function (name) { 23659 languagesToLower[name.toLowerCase()] = options.languages[name]; 23660 }); 23661 _this.languages_ = languagesToLower; 23662 } else { 23663 _this.languages_ = Player.prototype.options_.languages; 23664 } 23665 23666 _this.resetCache_(); // Set poster 23667 23668 23669 _this.poster_ = options.poster || ''; // Set controls 23670 23671 _this.controls_ = !!options.controls; // Original tag settings stored in options 23672 // now remove immediately so native controls don't flash. 23673 // May be turned back on by HTML5 tech if nativeControlsForTouch is true 23674 23675 tag.controls = false; 23676 tag.removeAttribute('controls'); 23677 _this.changingSrc_ = false; 23678 _this.playCallbacks_ = []; 23679 _this.playTerminatedQueue_ = []; // the attribute overrides the option 23680 23681 if (tag.hasAttribute('autoplay')) { 23682 _this.autoplay(true); 23683 } else { 23684 // otherwise use the setter to validate and 23685 // set the correct value. 23686 _this.autoplay(_this.options_.autoplay); 23687 } // check plugins 23688 23689 23690 if (options.plugins) { 23691 Object.keys(options.plugins).forEach(function (name) { 23692 if (typeof _this[name] !== 'function') { 23693 throw new Error("plugin \"" + name + "\" does not exist"); 23694 } 23695 }); 23696 } 23697 /* 23698 * Store the internal state of scrubbing 23699 * 23700 * @private 23701 * @return {Boolean} True if the user is scrubbing 23702 */ 23703 23704 23705 _this.scrubbing_ = false; 23706 _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus. 23707 23708 evented(assertThisInitialized(_this), { 23709 eventBusKey: 'el_' 23710 }); // listen to document and player fullscreenchange handlers so we receive those events 23711 // before a user can receive them so we can update isFullscreen appropriately. 23712 // make sure that we listen to fullscreenchange events before everything else to make sure that 23713 // our isFullscreen method is updated properly for internal components as well as external. 23714 23715 if (_this.fsApi_.requestFullscreen) { 23716 on(document, _this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_); 23717 23718 _this.on(_this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_); 23719 } 23720 23721 if (_this.fluid_) { 23722 _this.on('playerreset', _this.updateStyleEl_); 23723 } // We also want to pass the original player options to each component and plugin 23724 // as well so they don't need to reach back into the player for options later. 23725 // We also need to do another copy of this.options_ so we don't end up with 23726 // an infinite loop. 23727 23728 23729 var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins 23730 23731 if (options.plugins) { 23732 Object.keys(options.plugins).forEach(function (name) { 23733 _this[name](options.plugins[name]); 23734 }); 23735 } // Enable debug mode to fire debugon event for all plugins. 23736 23737 23738 if (options.debug) { 23739 _this.debug(true); 23740 } 23741 23742 _this.options_.playerOptions = playerOptionsCopy; 23743 _this.middleware_ = []; 23744 23745 _this.initChildren(); // Set isAudio based on whether or not an audio tag was used 23746 23747 23748 _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially 23749 // set because the element doesn't exist yet. 23750 23751 23752 if (_this.controls()) { 23753 _this.addClass('vjs-controls-enabled'); 23754 } else { 23755 _this.addClass('vjs-controls-disabled'); 23756 } // Set ARIA label and region role depending on player type 23757 23758 23759 _this.el_.setAttribute('role', 'region'); 23760 23761 if (_this.isAudio()) { 23762 _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); 23763 } else { 23764 _this.el_.setAttribute('aria-label', _this.localize('Video Player')); 23765 } 23766 23767 if (_this.isAudio()) { 23768 _this.addClass('vjs-audio'); 23769 } 23770 23771 if (_this.flexNotSupported_()) { 23772 _this.addClass('vjs-no-flex'); 23773 } // TODO: Make this smarter. Toggle user state between touching/mousing 23774 // using events, since devices can have both touch and mouse events. 23775 // TODO: Make this check be performed again when the window switches between monitors 23776 // (See https://github.com/videojs/video.js/issues/5683) 23777 23778 23779 if (TOUCH_ENABLED) { 23780 _this.addClass('vjs-touch-enabled'); 23781 } // iOS Safari has broken hover handling 23782 23783 23784 if (!IS_IOS) { 23785 _this.addClass('vjs-workinghover'); 23786 } // Make player easily findable by ID 23787 23788 23789 Player.players[_this.id_] = assertThisInitialized(_this); // Add a major version class to aid css in plugins 23790 23791 var majorVersion = version.split('.')[0]; 23792 23793 _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components 23794 // like the control bar show themselves if needed 23795 23796 23797 _this.userActive(true); 23798 23799 _this.reportUserActivity(); 23800 23801 _this.one('play', _this.listenForUserActivity_); 23802 23803 _this.on('stageclick', _this.handleStageClick_); 23804 23805 _this.on('keydown', _this.handleKeyDown); 23806 23807 _this.breakpoints(_this.options_.breakpoints); 23808 23809 _this.responsive(_this.options_.responsive); 23810 23811 return _this; 23812 } 23813 /** 23814 * Destroys the video player and does any necessary cleanup. 23815 * 23816 * This is especially helpful if you are dynamically adding and removing videos 23817 * to/from the DOM. 23818 * 23819 * @fires Player#dispose 23820 */ 23821 23822 23823 var _proto = Player.prototype; 23824 23825 _proto.dispose = function dispose() { 23826 var _this2 = this; 23827 23828 /** 23829 * Called when the player is being disposed of. 23830 * 23831 * @event Player#dispose 23832 * @type {EventTarget~Event} 23833 */ 23834 this.trigger('dispose'); // prevent dispose from being called twice 23835 23836 this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is 23837 23838 off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_); 23839 off(document, 'keydown', this.boundFullWindowOnEscKey_); 23840 23841 if (this.styleEl_ && this.styleEl_.parentNode) { 23842 this.styleEl_.parentNode.removeChild(this.styleEl_); 23843 this.styleEl_ = null; 23844 } // Kill reference to this player 23845 23846 23847 Player.players[this.id_] = null; 23848 23849 if (this.tag && this.tag.player) { 23850 this.tag.player = null; 23851 } 23852 23853 if (this.el_ && this.el_.player) { 23854 this.el_.player = null; 23855 } 23856 23857 if (this.tech_) { 23858 this.tech_.dispose(); 23859 this.isPosterFromTech_ = false; 23860 this.poster_ = ''; 23861 } 23862 23863 if (this.playerElIngest_) { 23864 this.playerElIngest_ = null; 23865 } 23866 23867 if (this.tag) { 23868 this.tag = null; 23869 } 23870 23871 clearCacheForPlayer(this); // remove all event handlers for track lists 23872 // all tracks and track listeners are removed on 23873 // tech dispose 23874 23875 ALL.names.forEach(function (name) { 23876 var props = ALL[name]; 23877 23878 var list = _this2[props.getterName](); // if it is not a native list 23879 // we have to manually remove event listeners 23880 23881 23882 if (list && list.off) { 23883 list.off(); 23884 } 23885 }); // the actual .el_ is removed here 23886 23887 _Component.prototype.dispose.call(this); 23888 } 23889 /** 23890 * Create the `Player`'s DOM element. 23891 * 23892 * @return {Element} 23893 * The DOM element that gets created. 23894 */ 23895 ; 23896 23897 _proto.createEl = function createEl() { 23898 var tag = this.tag; 23899 var el; 23900 var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); 23901 var divEmbed = this.tag.tagName.toLowerCase() === 'video-js'; 23902 23903 if (playerElIngest) { 23904 el = this.el_ = tag.parentNode; 23905 } else if (!divEmbed) { 23906 el = this.el_ = _Component.prototype.createEl.call(this, 'div'); 23907 } // Copy over all the attributes from the tag, including ID and class 23908 // ID will now reference player box, not the video tag 23909 23910 23911 var attrs = getAttributes(tag); 23912 23913 if (divEmbed) { 23914 el = this.el_ = tag; 23915 tag = this.tag = document.createElement('video'); 23916 23917 while (el.children.length) { 23918 tag.appendChild(el.firstChild); 23919 } 23920 23921 if (!hasClass(el, 'video-js')) { 23922 addClass(el, 'video-js'); 23923 } 23924 23925 el.appendChild(tag); 23926 playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element 23927 // to our new `video` element. This will move things like 23928 // `src` or `controls` that were set via js before the player 23929 // was initialized. 23930 23931 Object.keys(el).forEach(function (k) { 23932 try { 23933 tag[k] = el[k]; 23934 } catch (e) {// we got a a property like outerHTML which we can't actually copy, ignore it 23935 } 23936 }); 23937 } // set tabindex to -1 to remove the video element from the focus order 23938 23939 23940 tag.setAttribute('tabindex', '-1'); 23941 attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and 23942 // for the same issue with Chrome (on Windows) with JAWS. 23943 // See https://github.com/FreedomScientific/VFO-standards-support/issues/78 23944 // Note that we can't detect if JAWS is being used, but this ARIA attribute 23945 // doesn't change behavior of IE11 or Chrome if JAWS is not being used 23946 23947 if (IE_VERSION || IS_CHROME && IS_WINDOWS) { 23948 tag.setAttribute('role', 'application'); 23949 attrs.role = 'application'; 23950 } // Remove width/height attrs from tag so CSS can make it 100% width/height 23951 23952 23953 tag.removeAttribute('width'); 23954 tag.removeAttribute('height'); 23955 23956 if ('width' in attrs) { 23957 delete attrs.width; 23958 } 23959 23960 if ('height' in attrs) { 23961 delete attrs.height; 23962 } 23963 23964 Object.getOwnPropertyNames(attrs).forEach(function (attr) { 23965 // don't copy over the class attribute to the player element when we're in a div embed 23966 // the class is already set up properly in the divEmbed case 23967 // and we want to make sure that the `video-js` class doesn't get lost 23968 if (!(divEmbed && attr === 'class')) { 23969 el.setAttribute(attr, attrs[attr]); 23970 } 23971 23972 if (divEmbed) { 23973 tag.setAttribute(attr, attrs[attr]); 23974 } 23975 }); // Update tag id/class for use as HTML5 playback tech 23976 // Might think we should do this after embedding in container so .vjs-tech class 23977 // doesn't flash 100% width/height, but class only applies with .video-js parent 23978 23979 tag.playerId = tag.id; 23980 tag.id += '_html5_api'; 23981 tag.className = 'vjs-tech'; // Make player findable on elements 23982 23983 tag.player = el.player = this; // Default state of video is paused 23984 23985 this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height 23986 // of the player in a way that's still overrideable by CSS, just like the 23987 // video element 23988 23989 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) { 23990 this.styleEl_ = createStyleElement('vjs-styles-dimensions'); 23991 var defaultsStyleEl = $('.vjs-styles-defaults'); 23992 var head = $('head'); 23993 head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); 23994 } 23995 23996 this.fill_ = false; 23997 this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el 23998 23999 this.width(this.options_.width); 24000 this.height(this.options_.height); 24001 this.fill(this.options_.fill); 24002 this.fluid(this.options_.fluid); 24003 this.aspectRatio(this.options_.aspectRatio); // support both crossOrigin and crossorigin to reduce confusion and issues around the name 24004 24005 this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin); // Hide any links within the video/audio tag, 24006 // because IE doesn't hide them completely from screen readers. 24007 24008 var links = tag.getElementsByTagName('a'); 24009 24010 for (var i = 0; i < links.length; i++) { 24011 var linkEl = links.item(i); 24012 addClass(linkEl, 'vjs-hidden'); 24013 linkEl.setAttribute('hidden', 'hidden'); 24014 } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so 24015 // keep track of the original for later so we can know if the source originally failed 24016 24017 24018 tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container 24019 24020 if (tag.parentNode && !playerElIngest) { 24021 tag.parentNode.insertBefore(el, tag); 24022 } // insert the tag as the first child of the player element 24023 // then manually add it to the children array so that this.addChild 24024 // will work properly for other components 24025 // 24026 // Breaks iPhone, fixed in HTML5 setup. 24027 24028 24029 prependTo(tag, el); 24030 this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player 24031 // if it's been set to something different to the doc 24032 24033 this.el_.setAttribute('lang', this.language_); 24034 this.el_ = el; 24035 return el; 24036 } 24037 /** 24038 * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this 24039 * sets the `crossOrigin` property on the `<video>` tag to control the CORS 24040 * behavior. 24041 * 24042 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin} 24043 * 24044 * @param {string} [value] 24045 * The value to set the `Player`'s crossOrigin to. If an argument is 24046 * given, must be one of `anonymous` or `use-credentials`. 24047 * 24048 * @return {string|undefined} 24049 * - The current crossOrigin value of the `Player` when getting. 24050 * - undefined when setting 24051 */ 24052 ; 24053 24054 _proto.crossOrigin = function crossOrigin(value) { 24055 if (!value) { 24056 return this.techGet_('crossOrigin'); 24057 } 24058 24059 if (value !== 'anonymous' && value !== 'use-credentials') { 24060 log.warn("crossOrigin must be \"anonymous\" or \"use-credentials\", given \"" + value + "\""); 24061 return; 24062 } 24063 24064 this.techCall_('setCrossOrigin', value); 24065 return; 24066 } 24067 /** 24068 * A getter/setter for the `Player`'s width. Returns the player's configured value. 24069 * To get the current width use `currentWidth()`. 24070 * 24071 * @param {number} [value] 24072 * The value to set the `Player`'s width to. 24073 * 24074 * @return {number} 24075 * The current width of the `Player` when getting. 24076 */ 24077 ; 24078 24079 _proto.width = function width(value) { 24080 return this.dimension('width', value); 24081 } 24082 /** 24083 * A getter/setter for the `Player`'s height. Returns the player's configured value. 24084 * To get the current height use `currentheight()`. 24085 * 24086 * @param {number} [value] 24087 * The value to set the `Player`'s heigth to. 24088 * 24089 * @return {number} 24090 * The current height of the `Player` when getting. 24091 */ 24092 ; 24093 24094 _proto.height = function height(value) { 24095 return this.dimension('height', value); 24096 } 24097 /** 24098 * A getter/setter for the `Player`'s width & height. 24099 * 24100 * @param {string} dimension 24101 * This string can be: 24102 * - 'width' 24103 * - 'height' 24104 * 24105 * @param {number} [value] 24106 * Value for dimension specified in the first argument. 24107 * 24108 * @return {number} 24109 * The dimension arguments value when getting (width/height). 24110 */ 24111 ; 24112 24113 _proto.dimension = function dimension(_dimension, value) { 24114 var privDimension = _dimension + '_'; 24115 24116 if (value === undefined) { 24117 return this[privDimension] || 0; 24118 } 24119 24120 if (value === '' || value === 'auto') { 24121 // If an empty string is given, reset the dimension to be automatic 24122 this[privDimension] = undefined; 24123 this.updateStyleEl_(); 24124 return; 24125 } 24126 24127 var parsedVal = parseFloat(value); 24128 24129 if (isNaN(parsedVal)) { 24130 log.error("Improper value \"" + value + "\" supplied for for " + _dimension); 24131 return; 24132 } 24133 24134 this[privDimension] = parsedVal; 24135 this.updateStyleEl_(); 24136 } 24137 /** 24138 * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. 24139 * 24140 * Turning this on will turn off fill mode. 24141 * 24142 * @param {boolean} [bool] 24143 * - A value of true adds the class. 24144 * - A value of false removes the class. 24145 * - No value will be a getter. 24146 * 24147 * @return {boolean|undefined} 24148 * - The value of fluid when getting. 24149 * - `undefined` when setting. 24150 */ 24151 ; 24152 24153 _proto.fluid = function fluid(bool) { 24154 if (bool === undefined) { 24155 return !!this.fluid_; 24156 } 24157 24158 this.fluid_ = !!bool; 24159 24160 if (isEvented(this)) { 24161 this.off('playerreset', this.updateStyleEl_); 24162 } 24163 24164 if (bool) { 24165 this.addClass('vjs-fluid'); 24166 this.fill(false); 24167 addEventedCallback(function () { 24168 this.on('playerreset', this.updateStyleEl_); 24169 }); 24170 } else { 24171 this.removeClass('vjs-fluid'); 24172 } 24173 24174 this.updateStyleEl_(); 24175 } 24176 /** 24177 * A getter/setter/toggler for the vjs-fill `className` on the `Player`. 24178 * 24179 * Turning this on will turn off fluid mode. 24180 * 24181 * @param {boolean} [bool] 24182 * - A value of true adds the class. 24183 * - A value of false removes the class. 24184 * - No value will be a getter. 24185 * 24186 * @return {boolean|undefined} 24187 * - The value of fluid when getting. 24188 * - `undefined` when setting. 24189 */ 24190 ; 24191 24192 _proto.fill = function fill(bool) { 24193 if (bool === undefined) { 24194 return !!this.fill_; 24195 } 24196 24197 this.fill_ = !!bool; 24198 24199 if (bool) { 24200 this.addClass('vjs-fill'); 24201 this.fluid(false); 24202 } else { 24203 this.removeClass('vjs-fill'); 24204 } 24205 } 24206 /** 24207 * Get/Set the aspect ratio 24208 * 24209 * @param {string} [ratio] 24210 * Aspect ratio for player 24211 * 24212 * @return {string|undefined} 24213 * returns the current aspect ratio when getting 24214 */ 24215 24216 /** 24217 * A getter/setter for the `Player`'s aspect ratio. 24218 * 24219 * @param {string} [ratio] 24220 * The value to set the `Player`'s aspect ratio to. 24221 * 24222 * @return {string|undefined} 24223 * - The current aspect ratio of the `Player` when getting. 24224 * - undefined when setting 24225 */ 24226 ; 24227 24228 _proto.aspectRatio = function aspectRatio(ratio) { 24229 if (ratio === undefined) { 24230 return this.aspectRatio_; 24231 } // Check for width:height format 24232 24233 24234 if (!/^\d+\:\d+$/.test(ratio)) { 24235 throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); 24236 } 24237 24238 this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode, 24239 // because in fixed mode you could calculate width and height yourself. 24240 24241 this.fluid(true); 24242 this.updateStyleEl_(); 24243 } 24244 /** 24245 * Update styles of the `Player` element (height, width and aspect ratio). 24246 * 24247 * @private 24248 * @listens Tech#loadedmetadata 24249 */ 24250 ; 24251 24252 _proto.updateStyleEl_ = function updateStyleEl_() { 24253 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) { 24254 var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; 24255 24256 var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; 24257 24258 var techEl = this.tech_ && this.tech_.el(); 24259 24260 if (techEl) { 24261 if (_width >= 0) { 24262 techEl.width = _width; 24263 } 24264 24265 if (_height >= 0) {
vendor: 8,653 bytes, lines 24266-24477
24266 techEl.height = _height; 24267 } 24268 } 24269 24270 return; 24271 } 24272 24273 var width; 24274 var height; 24275 var aspectRatio; 24276 var idClass; // The aspect ratio is either used directly or to calculate width and height. 24277 24278 if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { 24279 // Use any aspectRatio that's been specifically set 24280 aspectRatio = this.aspectRatio_; 24281 } else if (this.videoWidth() > 0) { 24282 // Otherwise try to get the aspect ratio from the video metadata 24283 aspectRatio = this.videoWidth() + ':' + this.videoHeight(); 24284 } else { 24285 // Or use a default. The video element's is 2:1, but 16:9 is more common. 24286 aspectRatio = '16:9'; 24287 } // Get the ratio as a decimal we can use to calculate dimensions 24288 24289 24290 var ratioParts = aspectRatio.split(':'); 24291 var ratioMultiplier = ratioParts[1] / ratioParts[0]; 24292 24293 if (this.width_ !== undefined) { 24294 // Use any width that's been specifically set 24295 width = this.width_; 24296 } else if (this.height_ !== undefined) { 24297 // Or calulate the width from the aspect ratio if a height has been set 24298 width = this.height_ / ratioMultiplier; 24299 } else { 24300 // Or use the video's metadata, or use the video el's default of 300 24301 width = this.videoWidth() || 300; 24302 } 24303 24304 if (this.height_ !== undefined) { 24305 // Use any height that's been specifically set 24306 height = this.height_; 24307 } else { 24308 // Otherwise calculate the height from the ratio and the width 24309 height = width * ratioMultiplier; 24310 } // Ensure the CSS class is valid by starting with an alpha character 24311 24312 24313 if (/^[^a-zA-Z]/.test(this.id())) { 24314 idClass = 'dimensions-' + this.id(); 24315 } else { 24316 idClass = this.id() + '-dimensions'; 24317 } // Ensure the right class is still on the player for the style element 24318 24319 24320 this.addClass(idClass); 24321 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 "); 24322 } 24323 /** 24324 * Load/Create an instance of playback {@link Tech} including element 24325 * and API methods. Then append the `Tech` element in `Player` as a child. 24326 * 24327 * @param {string} techName 24328 * name of the playback technology 24329 * 24330 * @param {string} source 24331 * video source 24332 * 24333 * @private 24334 */ 24335 ; 24336 24337 _proto.loadTech_ = function loadTech_(techName, source) { 24338 var _this3 = this; 24339 24340 // Pause and remove current playback technology 24341 if (this.tech_) { 24342 this.unloadTech_(); 24343 } 24344 24345 var titleTechName = toTitleCase(techName); 24346 var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech 24347 24348 if (titleTechName !== 'Html5' && this.tag) { 24349 Tech.getTech('Html5').disposeMediaElement(this.tag); 24350 this.tag.player = null; 24351 this.tag = null; 24352 } 24353 24354 this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously 24355 24356 this.isReady_ = false; // if autoplay is a string we pass false to the tech 24357 // because the player is going to handle autoplay on `loadstart` 24358 24359 var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use. 24360 24361 var techOptions = { 24362 source: source, 24363 autoplay: autoplay, 24364 'nativeControlsForTouch': this.options_.nativeControlsForTouch, 24365 'playerId': this.id(), 24366 'techId': this.id() + "_" + camelTechName + "_api", 24367 'playsinline': this.options_.playsinline, 24368 'preload': this.options_.preload, 24369 'loop': this.options_.loop, 24370 'disablePictureInPicture': this.options_.disablePictureInPicture, 24371 'muted': this.options_.muted, 24372 'poster': this.poster(), 24373 'language': this.language(), 24374 'playerElIngest': this.playerElIngest_ || false, 24375 'vtt.js': this.options_['vtt.js'], 24376 'canOverridePoster': !!this.options_.techCanOverridePoster, 24377 'enableSourceset': this.options_.enableSourceset, 24378 'Promise': this.options_.Promise 24379 }; 24380 ALL.names.forEach(function (name) { 24381 var props = ALL[name]; 24382 techOptions[props.getterName] = _this3[props.privateName]; 24383 }); 24384 assign(techOptions, this.options_[titleTechName]); 24385 assign(techOptions, this.options_[camelTechName]); 24386 assign(techOptions, this.options_[techName.toLowerCase()]); 24387 24388 if (this.tag) { 24389 techOptions.tag = this.tag; 24390 } 24391 24392 if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { 24393 techOptions.startTime = this.cache_.currentTime; 24394 } // Initialize tech instance 24395 24396 24397 var TechClass = Tech.getTech(techName); 24398 24399 if (!TechClass) { 24400 throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'"); 24401 } 24402 24403 this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async 24404 24405 this.tech_.ready(bind(this, this.handleTechReady_), true); 24406 textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player 24407 24408 TECH_EVENTS_RETRIGGER.forEach(function (event) { 24409 _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]); 24410 }); 24411 Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) { 24412 _this3.on(_this3.tech_, event, function (eventObj) { 24413 if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) { 24414 _this3.queuedCallbacks_.push({ 24415 callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3), 24416 event: eventObj 24417 }); 24418 24419 return; 24420 } 24421 24422 _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj); 24423 }); 24424 }); 24425 this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); 24426 this.on(this.tech_, 'sourceset', this.handleTechSourceset_); 24427 this.on(this.tech_, 'waiting', this.handleTechWaiting_); 24428 this.on(this.tech_, 'ended', this.handleTechEnded_); 24429 this.on(this.tech_, 'seeking', this.handleTechSeeking_); 24430 this.on(this.tech_, 'play', this.handleTechPlay_); 24431 this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); 24432 this.on(this.tech_, 'pause', this.handleTechPause_); 24433 this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); 24434 this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); 24435 this.on(this.tech_, 'fullscreenerror', this.handleTechFullscreenError_); 24436 this.on(this.tech_, 'enterpictureinpicture', this.handleTechEnterPictureInPicture_); 24437 this.on(this.tech_, 'leavepictureinpicture', this.handleTechLeavePictureInPicture_); 24438 this.on(this.tech_, 'error', this.handleTechError_); 24439 this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); 24440 this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); 24441 this.on(this.tech_, 'textdata', this.handleTechTextData_); 24442 this.on(this.tech_, 'ratechange', this.handleTechRateChange_); 24443 this.usingNativeControls(this.techGet_('controls')); 24444 24445 if (this.controls() && !this.usingNativeControls()) { 24446 this.addTechControlsListeners_(); 24447 } // Add the tech element in the DOM if it was not already there 24448 // Make sure to not insert the original video element if using Html5 24449 24450 24451 if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { 24452 prependTo(this.tech_.el(), this.el()); 24453 } // Get rid of the original video tag reference after the first tech is loaded 24454 24455 24456 if (this.tag) { 24457 this.tag.player = null; 24458 this.tag = null; 24459 } 24460 } 24461 /** 24462 * Unload and dispose of the current playback {@link Tech}. 24463 * 24464 * @private 24465 */ 24466 ; 24467 24468 _proto.unloadTech_ = function unloadTech_() { 24469 var _this4 = this; 24470 24471 // Save the current text tracks so that we can reuse the same text tracks with the next tech 24472 ALL.names.forEach(function (name) { 24473 var props = ALL[name]; 24474 _this4[props.privateName] = _this4[props.getterName](); 24475 }); 24476 this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_); 24477 this.isReady_ = false;
vendor: 9,755 bytes, lines 24478-24764
24478 this.tech_.dispose(); 24479 this.tech_ = false; 24480 24481 if (this.isPosterFromTech_) { 24482 this.poster_ = ''; 24483 this.trigger('posterchange'); 24484 } 24485 24486 this.isPosterFromTech_ = false; 24487 } 24488 /** 24489 * Return a reference to the current {@link Tech}. 24490 * It will print a warning by default about the danger of using the tech directly 24491 * but any argument that is passed in will silence the warning. 24492 * 24493 * @param {*} [safety] 24494 * Anything passed in to silence the warning 24495 * 24496 * @return {Tech} 24497 * The Tech 24498 */ 24499 ; 24500 24501 _proto.tech = function tech(safety) { 24502 if (safety === undefined) { 24503 log.warn('Using the tech directly can be dangerous. I hope you know what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n'); 24504 } 24505 24506 return this.tech_; 24507 } 24508 /** 24509 * Set up click and touch listeners for the playback element 24510 * 24511 * - On desktops: a click on the video itself will toggle playback 24512 * - On mobile devices: a click on the video toggles controls 24513 * which is done by toggling the user state between active and 24514 * inactive 24515 * - A tap can signal that a user has become active or has become inactive 24516 * e.g. a quick tap on an iPhone movie should reveal the controls. Another 24517 * quick tap should hide them again (signaling the user is in an inactive 24518 * viewing state) 24519 * - In addition to this, we still want the user to be considered inactive after 24520 * a few seconds of inactivity. 24521 * 24522 * > Note: the only part of iOS interaction we can't mimic with this setup 24523 * is a touch and hold on the video element counting as activity in order to 24524 * keep the controls showing, but that shouldn't be an issue. A touch and hold 24525 * on any controls will still keep the user active 24526 * 24527 * @private 24528 */ 24529 ; 24530 24531 _proto.addTechControlsListeners_ = function addTechControlsListeners_() { 24532 // Make sure to remove all the previous listeners in case we are called multiple times. 24533 this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do 24534 // trigger mousedown/up. 24535 // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object 24536 // Any touch events are set to block the mousedown event from happening 24537 24538 this.on(this.tech_, 'mouseup', this.handleTechClick_); 24539 this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event 24540 // so we'll check if the controls were already showing before reporting user 24541 // activity 24542 24543 this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); 24544 this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); 24545 this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap 24546 // listener cancels out any reportedUserActivity when setting userActive(false) 24547 24548 this.on(this.tech_, 'tap', this.handleTechTap_); 24549 } 24550 /** 24551 * Remove the listeners used for click and tap controls. This is needed for 24552 * toggling to controls disabled, where a tap/touch should do nothing. 24553 * 24554 * @private 24555 */ 24556 ; 24557 24558 _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() { 24559 // We don't want to just use `this.off()` because there might be other needed 24560 // listeners added by techs that extend this. 24561 this.off(this.tech_, 'tap', this.handleTechTap_); 24562 this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); 24563 this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); 24564 this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); 24565 this.off(this.tech_, 'mouseup', this.handleTechClick_); 24566 this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_); 24567 } 24568 /** 24569 * Player waits for the tech to be ready 24570 * 24571 * @private 24572 */ 24573 ; 24574 24575 _proto.handleTechReady_ = function handleTechReady_() { 24576 this.triggerReady(); // Keep the same volume as before 24577 24578 if (this.cache_.volume) { 24579 this.techCall_('setVolume', this.cache_.volume); 24580 } // Look if the tech found a higher resolution poster while loading 24581 24582 24583 this.handleTechPosterChange_(); // Update the duration if available 24584 24585 this.handleTechDurationChange_(); 24586 } 24587 /** 24588 * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This 24589 * function will also trigger {@link Player#firstplay} if it is the first loadstart 24590 * for a video. 24591 * 24592 * @fires Player#loadstart 24593 * @fires Player#firstplay 24594 * @listens Tech#loadstart 24595 * @private 24596 */ 24597 ; 24598 24599 _proto.handleTechLoadStart_ = function handleTechLoadStart_() { 24600 // TODO: Update to use `emptied` event instead. See #1277. 24601 this.removeClass('vjs-ended'); 24602 this.removeClass('vjs-seeking'); // reset the error state 24603 24604 this.error(null); // Update the duration 24605 24606 this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now. 24607 // The firstplay event relies on both the play and loadstart events 24608 // which can happen in any order for a new source 24609 24610 if (!this.paused()) { 24611 /** 24612 * Fired when the user agent begins looking for media data 24613 * 24614 * @event Player#loadstart 24615 * @type {EventTarget~Event} 24616 */ 24617 this.trigger('loadstart'); 24618 this.trigger('firstplay'); 24619 } else { 24620 // reset the hasStarted state 24621 this.hasStarted(false); 24622 this.trigger('loadstart'); 24623 } // autoplay happens after loadstart for the browser, 24624 // so we mimic that behavior 24625 24626 24627 this.manualAutoplay_(this.autoplay()); 24628 } 24629 /** 24630 * Handle autoplay string values, rather than the typical boolean 24631 * values that should be handled by the tech. Note that this is not 24632 * part of any specification. Valid values and what they do can be 24633 * found on the autoplay getter at Player#autoplay() 24634 */ 24635 ; 24636 24637 _proto.manualAutoplay_ = function manualAutoplay_(type) { 24638 var _this5 = this; 24639 24640 if (!this.tech_ || typeof type !== 'string') { 24641 return; 24642 } 24643 24644 var muted = function muted() { 24645 var previouslyMuted = _this5.muted(); 24646 24647 _this5.muted(true); 24648 24649 var restoreMuted = function restoreMuted() { 24650 _this5.muted(previouslyMuted); 24651 }; // restore muted on play terminatation 24652 24653 24654 _this5.playTerminatedQueue_.push(restoreMuted); 24655 24656 var mutedPromise = _this5.play(); 24657 24658 if (!isPromise(mutedPromise)) { 24659 return; 24660 } 24661 24662 return mutedPromise["catch"](restoreMuted); 24663 }; 24664 24665 var promise; // if muted defaults to true 24666 // the only thing we can do is call play 24667 24668 if (type === 'any' && this.muted() !== true) { 24669 promise = this.play(); 24670 24671 if (isPromise(promise)) { 24672 promise = promise["catch"](muted); 24673 } 24674 } else if (type === 'muted' && this.muted() !== true) { 24675 promise = muted(); 24676 } else { 24677 promise = this.play(); 24678 } 24679 24680 if (!isPromise(promise)) { 24681 return; 24682 } 24683 24684 return promise.then(function () { 24685 _this5.trigger({ 24686 type: 'autoplay-success', 24687 autoplay: type 24688 }); 24689 })["catch"](function (e) { 24690 _this5.trigger({ 24691 type: 'autoplay-failure', 24692 autoplay: type 24693 }); 24694 }); 24695 } 24696 /** 24697 * Update the internal source caches so that we return the correct source from 24698 * `src()`, `currentSource()`, and `currentSources()`. 24699 * 24700 * > Note: `currentSources` will not be updated if the source that is passed in exists 24701 * in the current `currentSources` cache. 24702 * 24703 * 24704 * @param {Tech~SourceObject} srcObj 24705 * A string or object source to update our caches to. 24706 */ 24707 ; 24708 24709 _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) { 24710 if (srcObj === void 0) { 24711 srcObj = ''; 24712 } 24713 24714 var src = srcObj; 24715 var type = ''; 24716 24717 if (typeof src !== 'string') { 24718 src = srcObj.src; 24719 type = srcObj.type; 24720 } // make sure all the caches are set to default values 24721 // to prevent null checking 24722 24723 24724 this.cache_.source = this.cache_.source || {}; 24725 this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in 24726 24727 if (src && !type) { 24728 type = findMimetype(this, src); 24729 } // update `currentSource` cache always 24730 24731 24732 this.cache_.source = mergeOptions({}, srcObj, { 24733 src: src, 24734 type: type 24735 }); 24736 var matchingSources = this.cache_.sources.filter(function (s) { 24737 return s.src && s.src === src; 24738 }); 24739 var sourceElSources = []; 24740 var sourceEls = this.$$('source'); 24741 var matchingSourceEls = []; 24742 24743 for (var i = 0; i < sourceEls.length; i++) { 24744 var sourceObj = getAttributes(sourceEls[i]); 24745 sourceElSources.push(sourceObj); 24746 24747 if (sourceObj.src && sourceObj.src === src) { 24748 matchingSourceEls.push(sourceObj.src); 24749 } 24750 } // if we have matching source els but not matching sources 24751 // the current source cache is not up to date 24752 24753 24754 if (matchingSourceEls.length && !matchingSources.length) { 24755 this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the 24756 // sources cache to the `currentSource` cache 24757 } else if (!matchingSources.length) { 24758 this.cache_.sources = [this.cache_.source]; 24759 } // update the tech `src` cache 24760 24761 24762 this.cache_.src = src; 24763 } 24764 /**
24765 * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech} 24766 * causing the media element to reload. 24767 * 24768 * It will fire for the initial source and each subsequent source. 24769 * This event is a custom event from Video.js and is triggered by the {@link Tech}. 24770 * 24771 * The event object for this event contains a `src` property that will contain the source 24772 * that was available when the event was triggered. This is generally only necessary if Video.js 24773 * is switching techs while the source was being changed. 24774 * 24775 * It is also fired when `load` is called on the player (or media element) 24776 * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`} 24777 * says that the resource selection algorithm needs to be aborted and restarted. 24778 * In this case, it is very likely that the `src` property will be set to the 24779 * empty string `""` to indicate we do not know what the source will be but 24780 * that it is changing. 24781 * 24782 * *This event is currently still experimental and may change in minor releases.* 24783 * __To use this, pass `enableSourceset` option to the player.__ 24784 * 24785 * @event Player#sourceset 24786 * @type {EventTarget~Event} 24787 * @prop {string} src 24788 * The source url available when the `sourceset` was triggered. 24789 * It will be an empty string if we cannot know what the source is 24790 * but know that the source will change. 24791 */ 24792 24793 /** 24794 * Retrigger the `sourceset` event that was triggered by the {@link Tech}. 24795 * 24796 * @fires Player#sourceset 24797 * @listens Tech#sourceset 24798 * @private 24799 */ 24800 ; 24801 24802 _proto.handleTechSourceset_ = function handleTechSourceset_(event) { 24803 var _this6 = this; 24804 24805 // only update the source cache when the source 24806 // was not updated using the player api 24807 if (!this.changingSrc_) { 24808 var updateSourceCaches = function updateSourceCaches(src) { 24809 return _this6.updateSourceCaches_(src); 24810 }; 24811 24812 var playerSrc = this.currentSource().src; 24813 var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob 24814 24815 if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) { 24816 // if both the tech source and the player source were updated we assume 24817 // something like @videojs/http-streaming did the sourceset and skip updating the source cache. 24818 if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) { 24819 updateSourceCaches = function updateSourceCaches() {}; 24820 } 24821 } // update the source to the initial source right away 24822 // in some cases this will be empty string 24823 24824 24825 updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string 24826 // wait for a `loadstart` to update the cache to `currentSrc`. 24827 // If a sourceset happens before a `loadstart`, we reset the state 24828 24829 if (!event.src) { 24830 this.tech_.any(['sourceset', 'loadstart'], function (e) { 24831 // if a sourceset happens before a `loadstart` there 24832 // is nothing to do as this `handleTechSourceset_` 24833 // will be called again and this will be handled there. 24834 if (e.type === 'sourceset') { 24835 return; 24836 } 24837 24838 var techSrc = _this6.techGet('currentSrc'); 24839 24840 _this6.lastSource_.tech = techSrc; 24841 24842 _this6.updateSourceCaches_(techSrc); 24843 }); 24844 } 24845 } 24846 24847 this.lastSource_ = { 24848 player: this.currentSource().src, 24849 tech: event.src 24850 }; 24851 this.trigger({ 24852 src: event.src, 24853 type: 'sourceset' 24854 }); 24855 } 24856 /** 24857 * Add/remove the vjs-has-started class 24858 * 24859 * @fires Player#firstplay 24860 * 24861 * @param {boolean} request 24862 * - true: adds the class 24863 * - false: remove the class 24864 * 24865 * @return {boolean} 24866 * the boolean value of hasStarted_ 24867 */ 24868 ; 24869 24870 _proto.hasStarted = function hasStarted(request) { 24871 if (request === undefined) { 24872 // act as getter, if we have no request to change 24873 return this.hasStarted_; 24874 } 24875 24876 if (request === this.hasStarted_) { 24877 return; 24878 } 24879 24880 this.hasStarted_ = request; 24881 24882 if (this.hasStarted_) { 24883 this.addClass('vjs-has-started'); 24884 this.trigger('firstplay'); 24885 } else { 24886 this.removeClass('vjs-has-started'); 24887 } 24888 } 24889 /** 24890 * Fired whenever the media begins or resumes playback 24891 * 24892 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} 24893 * @fires Player#play 24894 * @listens Tech#play 24895 * @private 24896 */ 24897 ; 24898 24899 _proto.handleTechPlay_ = function handleTechPlay_() { 24900 this.removeClass('vjs-ended'); 24901 this.removeClass('vjs-paused'); 24902 this.addClass('vjs-playing'); // hide the poster when the user hits play 24903 24904 this.hasStarted(true); 24905 /** 24906 * Triggered whenever an {@link Tech#play} event happens. Indicates that 24907 * playback has started or resumed. 24908 * 24909 * @event Player#play 24910 * @type {EventTarget~Event} 24911 */ 24912 24913 this.trigger('play'); 24914 } 24915 /** 24916 * Retrigger the `ratechange` event that was triggered by the {@link Tech}. 24917 * 24918 * If there were any events queued while the playback rate was zero, fire 24919 * those events now. 24920 * 24921 * @private 24922 * @method Player#handleTechRateChange_ 24923 * @fires Player#ratechange 24924 * @listens Tech#ratechange 24925 */ 24926 ; 24927 24928 _proto.handleTechRateChange_ = function handleTechRateChange_() { 24929 if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) { 24930 this.queuedCallbacks_.forEach(function (queued) { 24931 return queued.callback(queued.event); 24932 }); 24933 this.queuedCallbacks_ = []; 24934 } 24935 24936 this.cache_.lastPlaybackRate = this.tech_.playbackRate(); 24937 /** 24938 * Fires when the playing speed of the audio/video is changed 24939 * 24940 * @event Player#ratechange 24941 * @type {event} 24942 */ 24943 24944 this.trigger('ratechange'); 24945 } 24946 /** 24947 * Retrigger the `waiting` event that was triggered by the {@link Tech}. 24948 * 24949 * @fires Player#waiting 24950 * @listens Tech#waiting 24951 * @private 24952 */ 24953 ; 24954 24955 _proto.handleTechWaiting_ = function handleTechWaiting_() { 24956 var _this7 = this; 24957 24958 this.addClass('vjs-waiting'); 24959 /** 24960 * A readyState change on the DOM element has caused playback to stop. 24961 * 24962 * @event Player#waiting 24963 * @type {EventTarget~Event} 24964 */ 24965 24966 this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent 24967 // premature removal of the waiting class, wait for the time to change. 24968 24969 var timeWhenWaiting = this.currentTime(); 24970 24971 var timeUpdateListener = function timeUpdateListener() { 24972 if (timeWhenWaiting !== _this7.currentTime()) { 24973 _this7.removeClass('vjs-waiting'); 24974 24975 _this7.off('timeupdate', timeUpdateListener); 24976 } 24977 }; 24978 24979 this.on('timeupdate', timeUpdateListener); 24980 } 24981 /** 24982 * Retrigger the `canplay` event that was triggered by the {@link Tech}. 24983 * > Note: This is not consistent between browsers. See #1351 24984 * 24985 * @fires Player#canplay 24986 * @listens Tech#canplay 24987 * @private 24988 */ 24989 ; 24990 24991 _proto.handleTechCanPlay_ = function handleTechCanPlay_() { 24992 this.removeClass('vjs-waiting'); 24993 /** 24994 * The media has a readyState of HAVE_FUTURE_DATA or greater. 24995 * 24996 * @event Player#canplay 24997 * @type {EventTarget~Event} 24998 */ 24999 25000 this.trigger('canplay'); 25001 } 25002 /** 25003 * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. 25004 * 25005 * @fires Player#canplaythrough 25006 * @listens Tech#canplaythrough 25007 * @private 25008 */ 25009 ; 25010 25011 _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { 25012 this.removeClass('vjs-waiting'); 25013 /** 25014 * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the 25015 * entire media file can be played without buffering. 25016 * 25017 * @event Player#canplaythrough 25018 * @type {EventTarget~Event} 25019 */ 25020 25021 this.trigger('canplaythrough'); 25022 } 25023 /** 25024 * Retrigger the `playing` event that was triggered by the {@link Tech}. 25025 * 25026 * @fires Player#playing 25027 * @listens Tech#playing 25028 * @private 25029 */ 25030 ; 25031 25032 _proto.handleTechPlaying_ = function handleTechPlaying_() { 25033 this.removeClass('vjs-waiting'); 25034 /** 25035 * The media is no longer blocked from playback, and has started playing. 25036 * 25037 * @event Player#playing 25038 * @type {EventTarget~Event} 25039 */ 25040 25041 this.trigger('playing'); 25042 } 25043 /** 25044 * Retrigger the `seeking` event that was triggered by the {@link Tech}. 25045 * 25046 * @fires Player#seeking 25047 * @listens Tech#seeking 25048 * @private 25049 */ 25050 ; 25051 25052 _proto.handleTechSeeking_ = function handleTechSeeking_() { 25053 this.addClass('vjs-seeking'); 25054 /** 25055 * Fired whenever the player is jumping to a new time 25056 * 25057 * @event Player#seeking 25058 * @type {EventTarget~Event} 25059 */ 25060 25061 this.trigger('seeking'); 25062 } 25063 /** 25064 * Retrigger the `seeked` event that was triggered by the {@link Tech}. 25065 * 25066 * @fires Player#seeked 25067 * @listens Tech#seeked 25068 * @private 25069 */ 25070 ; 25071 25072 _proto.handleTechSeeked_ = function handleTechSeeked_() { 25073 this.removeClass('vjs-seeking'); 25074 this.removeClass('vjs-ended'); 25075 /** 25076 * Fired when the player has finished jumping to a new time 25077 * 25078 * @event Player#seeked 25079 * @type {EventTarget~Event} 25080 */ 25081 25082 this.trigger('seeked'); 25083 } 25084 /** 25085 * Retrigger the `firstplay` event that was triggered by the {@link Tech}. 25086 * 25087 * @fires Player#firstplay 25088 * @listens Tech#firstplay 25089 * @deprecated As of 6.0 firstplay event is deprecated. 25090 * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. 25091 * @private 25092 */ 25093 ; 25094 25095 _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() { 25096 // If the first starttime attribute is specified 25097 // then we will start at the given offset in seconds 25098 if (this.options_.starttime) { 25099 log.warn('Passing the `starttime` option to the player will be deprecated
vendor: 25,490 bytes, lines 25099-25984
25099in 6.0'); 25100 this.currentTime(this.options_.starttime); 25101 } 25102 25103 this.addClass('vjs-has-started'); 25104 /** 25105 * Fired the first time a video is played. Not part of the HLS spec, and this is 25106 * probably not the best implementation yet, so use sparingly. If you don't have a 25107 * reason to prevent playback, use `myPlayer.one('play');` instead. 25108 * 25109 * @event Player#firstplay 25110 * @deprecated As of 6.0 firstplay event is deprecated. 25111 * @type {EventTarget~Event} 25112 */ 25113 25114 this.trigger('firstplay'); 25115 } 25116 /** 25117 * Retrigger the `pause` event that was triggered by the {@link Tech}. 25118 * 25119 * @fires Player#pause 25120 * @listens Tech#pause 25121 * @private 25122 */ 25123 ; 25124 25125 _proto.handleTechPause_ = function handleTechPause_() { 25126 this.removeClass('vjs-playing'); 25127 this.addClass('vjs-paused'); 25128 /** 25129 * Fired whenever the media has been paused 25130 * 25131 * @event Player#pause 25132 * @type {EventTarget~Event} 25133 */ 25134 25135 this.trigger('pause'); 25136 } 25137 /** 25138 * Retrigger the `ended` event that was triggered by the {@link Tech}. 25139 * 25140 * @fires Player#ended 25141 * @listens Tech#ended 25142 * @private 25143 */ 25144 ; 25145 25146 _proto.handleTechEnded_ = function handleTechEnded_() { 25147 this.addClass('vjs-ended'); 25148 25149 if (this.options_.loop) { 25150 this.currentTime(0); 25151 this.play(); 25152 } else if (!this.paused()) { 25153 this.pause(); 25154 } 25155 /** 25156 * Fired when the end of the media resource is reached (currentTime == duration) 25157 * 25158 * @event Player#ended 25159 * @type {EventTarget~Event} 25160 */ 25161 25162 25163 this.trigger('ended'); 25164 } 25165 /** 25166 * Fired when the duration of the media resource is first known or changed 25167 * 25168 * @listens Tech#durationchange 25169 * @private 25170 */ 25171 ; 25172 25173 _proto.handleTechDurationChange_ = function handleTechDurationChange_() { 25174 this.duration(this.techGet_('duration')); 25175 } 25176 /** 25177 * Handle a click on the media element to play/pause 25178 * 25179 * @param {EventTarget~Event} event 25180 * the event that caused this function to trigger 25181 * 25182 * @listens Tech#mouseup 25183 * @private 25184 */ 25185 ; 25186 25187 _proto.handleTechClick_ = function handleTechClick_(event) { 25188 if (!isSingleLeftClick(event)) { 25189 return; 25190 } // When controls are disabled a click should not toggle playback because 25191 // the click is considered a control 25192 25193 25194 if (!this.controls_) { 25195 return; 25196 } 25197 25198 if (this.paused()) { 25199 silencePromise(this.play()); 25200 } else { 25201 this.pause(); 25202 } 25203 } 25204 /** 25205 * Handle a double-click on the media element to enter/exit fullscreen 25206 * 25207 * @param {EventTarget~Event} event 25208 * the event that caused this function to trigger 25209 * 25210 * @listens Tech#dblclick 25211 * @private 25212 */ 25213 ; 25214 25215 _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) { 25216 if (!this.controls_) { 25217 return; 25218 } // we do not want to toggle fullscreen state 25219 // when double-clicking inside a control bar or a modal 25220 25221 25222 var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) { 25223 return el.contains(event.target); 25224 }); 25225 25226 if (!inAllowedEls) { 25227 /* 25228 * options.userActions.doubleClick 25229 * 25230 * If `undefined` or `true`, double-click toggles fullscreen if controls are present 25231 * Set to `false` to disable double-click handling 25232 * Set to a function to substitute an external double-click handler 25233 */ 25234 if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) { 25235 if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') { 25236 this.options_.userActions.doubleClick.call(this, event); 25237 } else if (this.isFullscreen()) { 25238 this.exitFullscreen(); 25239 } else { 25240 this.requestFullscreen(); 25241 } 25242 } 25243 } 25244 } 25245 /** 25246 * Handle a tap on the media element. It will toggle the user 25247 * activity state, which hides and shows the controls. 25248 * 25249 * @listens Tech#tap 25250 * @private 25251 */ 25252 ; 25253 25254 _proto.handleTechTap_ = function handleTechTap_() { 25255 this.userActive(!this.userActive()); 25256 } 25257 /** 25258 * Handle touch to start 25259 * 25260 * @listens Tech#touchstart 25261 * @private 25262 */ 25263 ; 25264 25265 _proto.handleTechTouchStart_ = function handleTechTouchStart_() { 25266 this.userWasActive = this.userActive(); 25267 } 25268 /** 25269 * Handle touch to move 25270 * 25271 * @listens Tech#touchmove 25272 * @private 25273 */ 25274 ; 25275 25276 _proto.handleTechTouchMove_ = function handleTechTouchMove_() { 25277 if (this.userWasActive) { 25278 this.reportUserActivity(); 25279 } 25280 } 25281 /** 25282 * Handle touch to end 25283 * 25284 * @param {EventTarget~Event} event 25285 * the touchend event that triggered 25286 * this function 25287 * 25288 * @listens Tech#touchend 25289 * @private 25290 */ 25291 ; 25292 25293 _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { 25294 // Stop the mouse events from also happening 25295 event.preventDefault(); 25296 } 25297 /** 25298 * native click events on the SWF aren't triggered on IE11, Win8.1RT 25299 * use stageclick events triggered from inside the SWF instead 25300 * 25301 * @private 25302 * @listens stageclick 25303 */ 25304 ; 25305 25306 _proto.handleStageClick_ = function handleStageClick_() { 25307 this.reportUserActivity(); 25308 } 25309 /** 25310 * @private 25311 */ 25312 ; 25313 25314 _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() { 25315 if (this.isFullscreen()) { 25316 this.addClass('vjs-fullscreen'); 25317 } else { 25318 this.removeClass('vjs-fullscreen'); 25319 } 25320 } 25321 /** 25322 * when the document fschange event triggers it calls this 25323 */ 25324 ; 25325 25326 _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) { 25327 var targetPlayer = e.target.player; // if another player was fullscreen 25328 // do a null check for targetPlayer because older firefox's would put document as e.target 25329 25330 if (targetPlayer && targetPlayer !== this) { 25331 return; 25332 } 25333 25334 var el = this.el(); 25335 var isFs = document[this.fsApi_.fullscreenElement] === el; 25336 25337 if (!isFs && el.matches) { 25338 isFs = el.matches(':' + this.fsApi_.fullscreen); 25339 } else if (!isFs && el.msMatchesSelector) { 25340 isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen); 25341 } 25342 25343 this.isFullscreen(isFs); 25344 } 25345 /** 25346 * Handle Tech Fullscreen Change 25347 * 25348 * @param {EventTarget~Event} event 25349 * the fullscreenchange event that triggered this function 25350 * 25351 * @param {Object} data 25352 * the data that was sent with the event 25353 * 25354 * @private 25355 * @listens Tech#fullscreenchange 25356 * @fires Player#fullscreenchange 25357 */ 25358 ; 25359 25360 _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { 25361 if (data) { 25362 if (data.nativeIOSFullscreen) { 25363 this.toggleClass('vjs-ios-native-fs'); 25364 } 25365 25366 this.isFullscreen(data.isFullscreen); 25367 } 25368 }; 25369 25370 _proto.handleTechFullscreenError_ = function handleTechFullscreenError_(event, err) { 25371 this.trigger('fullscreenerror', err); 25372 } 25373 /** 25374 * @private 25375 */ 25376 ; 25377 25378 _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() { 25379 if (this.isInPictureInPicture()) { 25380 this.addClass('vjs-picture-in-picture'); 25381 } else { 25382 this.removeClass('vjs-picture-in-picture'); 25383 } 25384 } 25385 /** 25386 * Handle Tech Enter Picture-in-Picture. 25387 * 25388 * @param {EventTarget~Event} event 25389 * the enterpictureinpicture event that triggered this function 25390 * 25391 * @private 25392 * @listens Tech#enterpictureinpicture 25393 */ 25394 ; 25395 25396 _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) { 25397 this.isInPictureInPicture(true); 25398 } 25399 /** 25400 * Handle Tech Leave Picture-in-Picture. 25401 * 25402 * @param {EventTarget~Event} event 25403 * the leavepictureinpicture event that triggered this function 25404 * 25405 * @private 25406 * @listens Tech#leavepictureinpicture 25407 */ 25408 ; 25409 25410 _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) { 25411 this.isInPictureInPicture(false); 25412 } 25413 /** 25414 * Fires when an error occurred during the loading of an audio/video. 25415 * 25416 * @private 25417 * @listens Tech#error 25418 */ 25419 ; 25420 25421 _proto.handleTechError_ = function handleTechError_() { 25422 var error = this.tech_.error(); 25423 this.error(error); 25424 } 25425 /** 25426 * Retrigger the `textdata` event that was triggered by the {@link Tech}. 25427 * 25428 * @fires Player#textdata 25429 * @listens Tech#textdata 25430 * @private 25431 */ 25432 ; 25433 25434 _proto.handleTechTextData_ = function handleTechTextData_() { 25435 var data = null; 25436 25437 if (arguments.length > 1) { 25438 data = arguments[1]; 25439 } 25440 /** 25441 * Fires when we get a textdata event from tech 25442 * 25443 * @event Player#textdata 25444 * @type {EventTarget~Event} 25445 */ 25446 25447 25448 this.trigger('textdata', data); 25449 } 25450 /** 25451 * Get object for cached values. 25452 * 25453 * @return {Object} 25454 * get the current object cache 25455 */ 25456 ; 25457 25458 _proto.getCache = function getCache() { 25459 return this.cache_; 25460 } 25461 /** 25462 * Resets the internal cache object. 25463 * 25464 * Using this function outside the player constructor or reset method may 25465 * have unintended side-effects. 25466 * 25467 * @private 25468 */ 25469 ; 25470 25471 _proto.resetCache_ = function resetCache_() { 25472 this.cache_ = { 25473 // Right now, the currentTime is not _really_ cached because it is always 25474 // retrieved from the tech (see: currentTime). However, for completeness, 25475 // we set it to zero here to ensure that if we do start actually caching 25476 // it, we reset it along with everything else. 25477 currentTime: 0, 25478 initTime: 0, 25479 inactivityTimeout: this.options_.inactivityTimeout, 25480 duration: NaN, 25481 lastVolume: 1, 25482 lastPlaybackRate: this.defaultPlaybackRate(), 25483 media: null, 25484 src: '', 25485 source: {}, 25486 sources: [], 25487 volume: 1 25488 }; 25489 } 25490 /** 25491 * Pass values to the playback tech 25492 * 25493 * @param {string} [method] 25494 * the method to call 25495 * 25496 * @param {Object} arg 25497 * the argument to pass 25498 * 25499 * @private 25500 */ 25501 ; 25502 25503 _proto.techCall_ = function techCall_(method, arg) { 25504 // If it's not ready yet, call method when it is 25505 this.ready(function () { 25506 if (method in allowedSetters) { 25507 return set(this.middleware_, this.tech_, method, arg); 25508 } else if (method in allowedMediators) { 25509 return mediate(this.middleware_, this.tech_, method, arg); 25510 } 25511 25512 try { 25513 if (this.tech_) { 25514 this.tech_[method](arg); 25515 } 25516 } catch (e) { 25517 log(e); 25518 throw e; 25519 } 25520 }, true); 25521 } 25522 /** 25523 * Get calls can't wait for the tech, and sometimes don't need to. 25524 * 25525 * @param {string} method 25526 * Tech method 25527 * 25528 * @return {Function|undefined} 25529 * the method or undefined 25530 * 25531 * @private 25532 */ 25533 ; 25534 25535 _proto.techGet_ = function techGet_(method) { 25536 if (!this.tech_ || !this.tech_.isReady_) { 25537 return; 25538 } 25539 25540 if (method in allowedGetters) { 25541 return get(this.middleware_, this.tech_, method); 25542 } else if (method in allowedMediators) { 25543 return mediate(this.middleware_, this.tech_, method); 25544 } // Flash likes to die and reload when you hide or reposition it. 25545 // In these cases the object methods go away and we get errors. 25546 // When that happens we'll catch the errors and inform tech that it's not ready any more. 25547 25548 25549 try { 25550 return this.tech_[method](); 25551 } catch (e) { 25552 // When building additional tech libs, an expected method may not be defined yet 25553 if (this.tech_[method] === undefined) { 25554 log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e); 25555 throw e; 25556 } // When a method isn't available on the object it throws a TypeError 25557 25558 25559 if (e.name === 'TypeError') { 25560 log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e); 25561 this.tech_.isReady_ = false; 25562 throw e; 25563 } // If error unknown, just log and throw 25564 25565 25566 log(e); 25567 throw e; 25568 } 25569 } 25570 /** 25571 * Attempt to begin playback at the first opportunity. 25572 * 25573 * @return {Promise|undefined} 25574 * Returns a promise if the browser supports Promises (or one 25575 * was passed in as an option). This promise will be resolved on 25576 * the return value of play. If this is undefined it will fulfill the 25577 * promise chain otherwise the promise chain will be fulfilled when 25578 * the promise from play is fulfilled. 25579 */ 25580 ; 25581 25582 _proto.play = function play() { 25583 var _this8 = this; 25584 25585 var PromiseClass = this.options_.Promise || window$1.Promise; 25586 25587 if (PromiseClass) { 25588 return new PromiseClass(function (resolve) { 25589 _this8.play_(resolve); 25590 }); 25591 } 25592 25593 return this.play_(); 25594 } 25595 /** 25596 * The actual logic for play, takes a callback that will be resolved on the 25597 * return value of play. This allows us to resolve to the play promise if there 25598 * is one on modern browsers. 25599 * 25600 * @private 25601 * @param {Function} [callback] 25602 * The callback that should be called when the techs play is actually called 25603 */ 25604 ; 25605 25606 _proto.play_ = function play_(callback) { 25607 var _this9 = this; 25608 25609 if (callback === void 0) { 25610 callback = silencePromise; 25611 } 25612 25613 this.playCallbacks_.push(callback); 25614 var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function 25615 25616 if (this.waitToPlay_) { 25617 this.off(['ready', 'loadstart'], this.waitToPlay_); 25618 this.waitToPlay_ = null; 25619 } // if the player/tech is not ready or the src itself is not ready 25620 // queue up a call to play on `ready` or `loadstart` 25621 25622 25623 if (!this.isReady_ || !isSrcReady) { 25624 this.waitToPlay_ = function (e) { 25625 _this9.play_(); 25626 }; 25627 25628 this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod 25629 // in that case, we need to prime the video element by calling load so it'll be ready in time 25630 25631 if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) { 25632 this.load(); 25633 } 25634 25635 return; 25636 } // If the player/tech is ready and we have a source, we can attempt playback. 25637 25638 25639 var val = this.techGet_('play'); // play was terminated if the returned value is null 25640 25641 if (val === null) { 25642 this.runPlayTerminatedQueue_(); 25643 } else { 25644 this.runPlayCallbacks_(val); 25645 } 25646 } 25647 /** 25648 * These functions will be run when if play is terminated. If play 25649 * runPlayCallbacks_ is run these function will not be run. This allows us 25650 * to differenciate between a terminated play and an actual call to play. 25651 */ 25652 ; 25653 25654 _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() { 25655 var queue = this.playTerminatedQueue_.slice(0); 25656 this.playTerminatedQueue_ = []; 25657 queue.forEach(function (q) { 25658 q(); 25659 }); 25660 } 25661 /** 25662 * When a callback to play is delayed we have to run these 25663 * callbacks when play is actually called on the tech. This function 25664 * runs the callbacks that were delayed and accepts the return value 25665 * from the tech. 25666 * 25667 * @param {undefined|Promise} val 25668 * The return value from the tech. 25669 */ 25670 ; 25671 25672 _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) { 25673 var callbacks = this.playCallbacks_.slice(0); 25674 this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play 25675 25676 this.playTerminatedQueue_ = []; 25677 callbacks.forEach(function (cb) { 25678 cb(val); 25679 }); 25680 } 25681 /** 25682 * Pause the video playback 25683 * 25684 * @return {Player} 25685 * A reference to the player object this function was called on 25686 */ 25687 ; 25688 25689 _proto.pause = function pause() { 25690 this.techCall_('pause'); 25691 } 25692 /** 25693 * Check if the player is paused or has yet to play 25694 * 25695 * @return {boolean} 25696 * - false: if the media is currently playing 25697 * - true: if media is not currently playing 25698 */ 25699 ; 25700 25701 _proto.paused = function paused() { 25702 // The initial state of paused should be true (in Safari it's actually false) 25703 return this.techGet_('paused') === false ? false : true; 25704 } 25705 /** 25706 * Get a TimeRange object representing the current ranges of time that the user 25707 * has played. 25708 * 25709 * @return {TimeRange} 25710 * A time range object that represents all the increments of time that have 25711 * been played. 25712 */ 25713 ; 25714 25715 _proto.played = function played() { 25716 return this.techGet_('played') || createTimeRanges(0, 0); 25717 } 25718 /** 25719 * Returns whether or not the user is "scrubbing". Scrubbing is 25720 * when the user has clicked the progress bar handle and is 25721 * dragging it along the progress bar. 25722 * 25723 * @param {boolean} [isScrubbing] 25724 * whether the user is or is not scrubbing 25725 * 25726 * @return {boolean} 25727 * The value of scrubbing when getting 25728 */ 25729 ; 25730 25731 _proto.scrubbing = function scrubbing(isScrubbing) { 25732 if (typeof isScrubbing === 'undefined') { 25733 return this.scrubbing_; 25734 } 25735 25736 this.scrubbing_ = !!isScrubbing; 25737 this.techCall_('setScrubbing', this.scrubbing_); 25738 25739 if (isScrubbing) { 25740 this.addClass('vjs-scrubbing'); 25741 } else { 25742 this.removeClass('vjs-scrubbing'); 25743 } 25744 } 25745 /** 25746 * Get or set the current time (in seconds) 25747 * 25748 * @param {number|string} [seconds] 25749 * The time to seek to in seconds 25750 * 25751 * @return {number} 25752 * - the current time in seconds when getting 25753 */ 25754 ; 25755 25756 _proto.currentTime = function currentTime(seconds) { 25757 if (typeof seconds !== 'undefined') { 25758 if (seconds < 0) { 25759 seconds = 0; 25760 } 25761 25762 if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) { 25763 this.cache_.initTime = seconds; 25764 this.off('canplay', this.applyInitTime_); 25765 this.one('canplay', this.applyInitTime_); 25766 return; 25767 } 25768 25769 this.techCall_('setCurrentTime', seconds); 25770 this.cache_.initTime = 0; 25771 return; 25772 } // cache last currentTime and return. default to 0 seconds 25773 // 25774 // Caching the currentTime is meant to prevent a massive amount of reads on the tech's 25775 // currentTime when scrubbing, but may not provide much performance benefit afterall. 25776 // Should be tested. Also something has to read the actual current time or the cache will 25777 // never get updated. 25778 25779 25780 this.cache_.currentTime = this.techGet_('currentTime') || 0; 25781 return this.cache_.currentTime; 25782 } 25783 /** 25784 * Apply the value of initTime stored in cache as currentTime. 25785 * 25786 * @private 25787 */ 25788 ; 25789 25790 _proto.applyInitTime_ = function applyInitTime_() { 25791 this.currentTime(this.cache_.initTime); 25792 } 25793 /** 25794 * Normally gets the length in time of the video in seconds; 25795 * in all but the rarest use cases an argument will NOT be passed to the method 25796 * 25797 * > **NOTE**: The video must have started loading before the duration can be 25798 * known, and in the case of Flash, may not be known until the video starts 25799 * playing. 25800 * 25801 * @fires Player#durationchange 25802 * 25803 * @param {number} [seconds] 25804 * The duration of the video to set in seconds 25805 * 25806 * @return {number} 25807 * - The duration of the video in seconds when getting 25808 */ 25809 ; 25810 25811 _proto.duration = function duration(seconds) { 25812 if (seconds === undefined) { 25813 // return NaN if the duration is not known 25814 return this.cache_.duration !== undefined ? this.cache_.duration : NaN; 25815 } 25816 25817 seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live 25818 25819 if (seconds < 0) { 25820 seconds = Infinity; 25821 } 25822 25823 if (seconds !== this.cache_.duration) { 25824 // Cache the last set value for optimized scrubbing (esp. Flash) 25825 this.cache_.duration = seconds; 25826 25827 if (seconds === Infinity) { 25828 this.addClass('vjs-live'); 25829 } else { 25830 this.removeClass('vjs-live'); 25831 } 25832 25833 if (!isNaN(seconds)) { 25834 // Do not fire durationchange unless the duration value is known. 25835 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 25836 25837 /** 25838 * @event Player#durationchange 25839 * @type {EventTarget~Event} 25840 */ 25841 this.trigger('durationchange'); 25842 } 25843 } 25844 } 25845 /** 25846 * Calculates how much time is left in the video. Not part 25847 * of the native video API. 25848 * 25849 * @return {number} 25850 * The time remaining in seconds 25851 */ 25852 ; 25853 25854 _proto.remainingTime = function remainingTime() { 25855 return this.duration() - this.currentTime(); 25856 } 25857 /** 25858 * A remaining time function that is intented to be used when 25859 * the time is to be displayed directly to the user. 25860 * 25861 * @return {number} 25862 * The rounded time remaining in seconds 25863 */ 25864 ; 25865 25866 _proto.remainingTimeDisplay = function remainingTimeDisplay() { 25867 return Math.floor(this.duration()) - Math.floor(this.currentTime()); 25868 } // 25869 // Kind of like an array of portions of the video that have been downloaded. 25870 25871 /** 25872 * Get a TimeRange object with an array of the times of the video 25873 * that have been downloaded. If you just want the percent of the 25874 * video that's been downloaded, use bufferedPercent. 25875 * 25876 * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} 25877 * 25878 * @return {TimeRange} 25879 * A mock TimeRange object (following HTML spec) 25880 */ 25881 ; 25882 25883 _proto.buffered = function buffered() { 25884 var buffered = this.techGet_('buffered'); 25885 25886 if (!buffered || !buffered.length) { 25887 buffered = createTimeRanges(0, 0); 25888 } 25889 25890 return buffered; 25891 } 25892 /** 25893 * Get the percent (as a decimal) of the video that's been downloaded. 25894 * This method is not a part of the native HTML video API. 25895 * 25896 * @return {number} 25897 * A decimal between 0 and 1 representing the percent 25898 * that is buffered 0 being 0% and 1 being 100% 25899 */ 25900 ; 25901 25902 _proto.bufferedPercent = function bufferedPercent$1() { 25903 return bufferedPercent(this.buffered(), this.duration()); 25904 } 25905 /** 25906 * Get the ending time of the last buffered time range 25907 * This is used in the progress bar to encapsulate all time ranges. 25908 * 25909 * @return {number} 25910 * The end of the last buffered time range 25911 */ 25912 ; 25913 25914 _proto.bufferedEnd = function bufferedEnd() { 25915 var buffered = this.buffered(); 25916 var duration = this.duration(); 25917 var end = buffered.end(buffered.length - 1); 25918 25919 if (end > duration) { 25920 end = duration; 25921 } 25922 25923 return end; 25924 } 25925 /** 25926 * Get or set the current volume of the media 25927 * 25928 * @param {number} [percentAsDecimal] 25929 * The new volume as a decimal percent: 25930 * - 0 is muted/0%/off 25931 * - 1.0 is 100%/full 25932 * - 0.5 is half volume or 50% 25933 * 25934 * @return {number} 25935 * The current volume as a percent when getting 25936 */ 25937 ; 25938 25939 _proto.volume = function volume(percentAsDecimal) { 25940 var vol; 25941 25942 if (percentAsDecimal !== undefined) { 25943 // Force value to between 0 and 1 25944 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); 25945 this.cache_.volume = vol; 25946 this.techCall_('setVolume', vol); 25947 25948 if (vol > 0) { 25949 this.lastVolume_(vol); 25950 } 25951 25952 return; 25953 } // Default to 1 when returning current volume. 25954 25955 25956 vol = parseFloat(this.techGet_('volume')); 25957 return isNaN(vol) ? 1 : vol; 25958 } 25959 /** 25960 * Get the current muted state, or turn mute on or off 25961 * 25962 * @param {boolean} [muted] 25963 * - true to mute 25964 * - false to unmute 25965 * 25966 * @return {boolean} 25967 * - true if mute is on and getting 25968 * - false if mute is off and getting 25969 */ 25970 ; 25971 25972 _proto.muted = function muted(_muted) { 25973 if (_muted !== undefined) { 25974 this.techCall_('setMuted', _muted); 25975 return; 25976 } 25977 25978 return this.techGet_('muted') || false; 25979 } 25980 /** 25981 * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted 25982 * indicates the state of muted on initial playback. 25983 * 25984 * ```js
25985 * var myPlayer = videojs('some-player-id'); 25986 * 25987 * myPlayer.src("http://www.example.com/path/to/video.mp4"); 25988 * 25989 * // get, should be false 25990 * console.log(myPlayer.defaultMuted()); 25991 * // set to true 25992 * myPlayer.defaultMuted(true); 25993 * // get should be true 25994 * console.log(myPlayer.defaultMuted()); 25995 * ``` 25996 * 25997 * @param {boolean} [defaultMuted] 25998 * - true to mute 25999 * - false to unmute 26000 * 26001 * @return {boolean|Player} 26002 * - true if defaultMuted is on and getting 26003 * - false if defaultMuted is off and getting 26004 * - A reference to the current player when setting 26005 */ 26006 ; 26007 26008 _proto.defaultMuted = function defaultMuted(_defaultMuted) { 26009 if (_defaultMuted !== undefined) { 26010 return this.techCall_('setDefaultMuted', _defaultMuted); 26011 } 26012 26013 return this.techGet_('defaultMuted') || false; 26014 } 26015 /** 26016 * Get the last volume, or set it 26017 * 26018 * @param {number} [percentAsDecimal] 26019 * The new last volume as a decimal percent: 26020 * - 0 is muted/0%/off 26021 * - 1.0 is 100%/full 26022 * - 0.5 is half volume or 50% 26023 * 26024 * @return {number} 26025 * the current value of lastVolume as a percent when getting 26026 * 26027 * @private 26028 */ 26029 ; 26030 26031 _proto.lastVolume_ = function lastVolume_(percentAsDecimal) { 26032 if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { 26033 this.cache_.lastVolume = percentAsDecimal; 26034 return; 26035 } 26036 26037 return this.cache_.lastVolume; 26038 } 26039 /** 26040 * Check if current tech can support native fullscreen 26041 * (e.g. with built in controls like iOS, so not our flash swf) 26042 * 26043 * @return {boolean} 26044 * if native fullscreen is supported 26045 */ 26046 ; 26047 26048 _proto.supportsFullScreen = function supportsFullScreen() { 26049 return this.techGet_('supportsFullScreen') || false; 26050 } 26051 /** 26052 * Check if the player is in fullscreen mode or tell the player that it 26053 * is or is not in fullscreen mode. 26054 * 26055 * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official 26056 * property and instead document.fullscreenElement is used. But isFullscreen is 26057 * still a valuable property for internal player workings. 26058 * 26059 * @param {boolean} [isFS] 26060 * Set the players current fullscreen state 26061 * 26062 * @return {boolean} 26063 * - true if fullscreen is on and getting 26064 * - false if fullscreen is off and getting 26065 */ 26066 ; 26067 26068 _proto.isFullscreen = function isFullscreen(isFS) { 26069 if (isFS !== undefined) { 26070 var oldValue = this.isFullscreen_; 26071 this.isFullscreen_ = Boolean(isFS); // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange 26072 // this is the only place where we trigger fullscreenchange events for older browsers 26073 // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well 26074 26075 if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) { 26076 /** 26077 * @event Player#fullscreenchange 26078 * @type {EventTarget~Event} 26079 */ 26080 this.trigger('fullscreenchange'); 26081 } 26082 26083 this.toggleFullscreenClass_(); 26084 return; 26085 } 26086 26087 return this.isFullscreen_; 26088 } 26089 /** 26090 * Increase the size of the video to full screen 26091 * In some browsers, full screen is not supported natively, so it enters 26092 * "full window mode", where the video fills the browser window. 26093 * In browsers and devices that support native full screen, sometimes the 26094 * browser's default controls will be shown, and not the Video.js custom skin. 26095 * This includes most mobile devices (iOS, Android) and older versions of 26096 * Safari. 26097 * 26098 * @param {Object} [fullscreenOptions] 26099 * Override the player fullscreen options 26100 * 26101 * @fires Player#fullscreenchange 26102 */ 26103 ; 26104 26105 _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) { 26106 var PromiseClass = this.options_.Promise || window$1.Promise; 26107 26108 if (PromiseClass) { 26109 var self = this; 26110 return new PromiseClass(function (resolve, reject) { 26111 function offHandler() { 26112 self.off('fullscreenerror', errorHandler); 26113 self.off('fullscreenchange', changeHandler); 26114 } 26115 26116 function changeHandler() { 26117 offHandler(); 26118 resolve(); 26119 } 26120 26121 function errorHandler(e, err) { 26122 offHandler(); 26123 reject(err); 26124 } 26125 26126 self.one('fullscreenchange', changeHandler); 26127 self.one('fullscreenerror', errorHandler); 26128 var promise = self.requestFullscreenHelper_(fullscreenOptions); 26129 26130 if (promise) { 26131 promise.then(offHandler, offHandler); 26132 return promise; 26133 } 26134 }); 26135 } 26136 26137 return this.requestFullscreenHelper_(); 26138 }; 26139 26140 _proto.requestFullscreenHelper_ = function requestFullscreenHelper_(fullscreenOptions) { 26141 var _this10 = this; 26142 26143 var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers. 26144 // Use defaults or player configured option unless passed directly to this method. 26145 26146 if (!this.fsApi_.prefixed) { 26147 fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {}; 26148 26149 if (fullscreenOptions !== undefined) { 26150 fsOptions = fullscreenOptions; 26151 } 26152 } // This method works as follows: 26153 // 1. if a fullscreen api is available, use it 26154 // 1. call requestFullscreen with potential options 26155 // 2. if we got a promise from above, use it to update isFullscreen() 26156 // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it. 26157 // This is particularly used for iPhone, older iPads, and non-safari browser on iOS. 26158 // 3. otherwise, use "fullWindow" mode 26159 26160 26161 if (this.fsApi_.requestFullscreen) { 26162 var promise = this.el_[this.fsApi_.requestFullscreen](fsOptions); 26163 26164 if (promise) { 26165 promise.then(function () { 26166 return _this10.isFullscreen(true); 26167 }, function () { 26168 return _this10.isFullscreen(false); 26169 }); 26170 } 26171 26172 return promise; 26173 } else if (this.tech_.supportsFullScreen()) { 26174 // we can't take the video.js controls fullscreen but we can go fullscreen 26175 // with native controls 26176 this.techCall_('enterFullScreen'); 26177 } else { 26178 // fullscreen isn't supported so we'll just stretch the video element to 26179 // fill the viewport 26180 this.enterFullWindow(); 26181 } 26182 } 26183 /** 26184 * Return the video to its normal size after having been in full screen mode 26185 * 26186 * @fires Player#fullscreenchange 26187 */ 26188 ; 26189 26190 _proto.exitFullscreen = function exitFullscreen() { 26191 var PromiseClass = this.options_.Promise || window$1.Promise; 26192 26193 if (PromiseClass) { 26194 var self = this; 26195 return new PromiseClass(function (resolve, reject) { 26196 function offHandler() { 26197 self.off('fullscreenerror', errorHandler); 26198 self.off('fullscreenchange', changeHandler); 26199 } 26200 26201 function changeHandler() { 26202 offHandler(); 26203 resolve(); 26204 } 26205 26206 function errorHandler(e, err) { 26207 offHandler(); 26208 reject(err); 26209 } 26210 26211 self.one('fullscreenchange', changeHandler); 26212 self.one('fullscreenerror', errorHandler); 26213 var promise = self.exitFullscreenHelper_(); 26214 26215 if (promise) { 26216 promise.then(offHandler, offHandler); 26217 return promise; 26218 } 26219 }); 26220 } 26221 26222 return this.exitFullscreenHelper_(); 26223 }; 26224 26225 _proto.exitFullscreenHelper_ = function exitFullscreenHelper_() { 26226 var _this11 = this; 26227 26228 if (this.fsApi_.requestFullscreen) { 26229 var promise = document[this.fsApi_.exitFullscreen](); 26230 26231 if (promise) { 26232 promise.then(function () { 26233 return _this11.isFullscreen(false); 26234 }); 26235 } 26236 26237 return promise; 26238 } else if (this.tech_.supportsFullScreen()) { 26239 this.techCall_('exitFullScreen'); 26240 } else { 26241 this.exitFullWindow(); 26242 } 26243 } 26244 /** 26245 * When fullscreen isn't supported we can stretch the 26246 * video container to as wide as the browser will let us. 26247 * 26248 * @fires Player#enterFullWindow 26249 */ 26250 ; 26251 26252 _proto.enterFullWindow = function enterFullWindow() { 26253 this.isFullscreen(true); 26254 this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off 26255 26256 this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen 26257 26258 on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars 26259 26260 document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles 26261 26262 addClass(document.body, 'vjs-full-window'); 26263 /** 26264 * @event Player#enterFullWindow 26265 * @type {EventTarget~Event} 26266 */ 26267 26268 this.trigger('enterFullWindow'); 26269 } 26270 /** 26271 * Check for call to either exit full window or 26272 * full screen on ESC key 26273 * 26274 * @param {string} event 26275 * Event to check for key press 26276 */ 26277 ; 26278 26279 _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) { 26280 if (keycode.isEventKey(event, 'Esc')) { 26281 if (this.isFullscreen() === true) { 26282 this.exitFullscreen(); 26283 } else { 26284 this.exitFullWindow(); 26285 } 26286 } 26287 } 26288 /** 26289 * Exit full window 26290 * 26291 * @fires Player#exitFullWindow 26292 */ 26293 ; 26294 26295 _proto.exitFullWindow = function exitFullWindow() { 26296 this.isFullscreen(false); 26297 this.isFullWindow = false;
vendor: 9,567 bytes, lines 26298-26576
26298 off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars. 26299 26300 document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles 26301 26302 removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes 26303 // this.positionAll(); 26304 26305 /** 26306 * @event Player#exitFullWindow 26307 * @type {EventTarget~Event} 26308 */ 26309 26310 this.trigger('exitFullWindow'); 26311 } 26312 /** 26313 * Disable Picture-in-Picture mode. 26314 * 26315 * @param {boolean} value 26316 * - true will disable Picture-in-Picture mode 26317 * - false will enable Picture-in-Picture mode 26318 */ 26319 ; 26320 26321 _proto.disablePictureInPicture = function disablePictureInPicture(value) { 26322 if (value === undefined) { 26323 return this.techGet_('disablePictureInPicture'); 26324 } 26325 26326 this.techCall_('setDisablePictureInPicture', value); 26327 this.options_.disablePictureInPicture = value; 26328 this.trigger('disablepictureinpicturechanged'); 26329 } 26330 /** 26331 * Check if the player is in Picture-in-Picture mode or tell the player that it 26332 * is or is not in Picture-in-Picture mode. 26333 * 26334 * @param {boolean} [isPiP] 26335 * Set the players current Picture-in-Picture state 26336 * 26337 * @return {boolean} 26338 * - true if Picture-in-Picture is on and getting 26339 * - false if Picture-in-Picture is off and getting 26340 */ 26341 ; 26342 26343 _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) { 26344 if (isPiP !== undefined) { 26345 this.isInPictureInPicture_ = !!isPiP; 26346 this.togglePictureInPictureClass_(); 26347 return; 26348 } 26349 26350 return !!this.isInPictureInPicture_; 26351 } 26352 /** 26353 * Create a floating video window always on top of other windows so that users may 26354 * continue consuming media while they interact with other content sites, or 26355 * applications on their device. 26356 * 26357 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 26358 * 26359 * @fires Player#enterpictureinpicture 26360 * 26361 * @return {Promise} 26362 * A promise with a Picture-in-Picture window. 26363 */ 26364 ; 26365 26366 _proto.requestPictureInPicture = function requestPictureInPicture() { 26367 if ('pictureInPictureEnabled' in document && this.disablePictureInPicture() === false) { 26368 /** 26369 * This event fires when the player enters picture in picture mode 26370 * 26371 * @event Player#enterpictureinpicture 26372 * @type {EventTarget~Event} 26373 */ 26374 return this.techGet_('requestPictureInPicture'); 26375 } 26376 } 26377 /** 26378 * Exit Picture-in-Picture mode. 26379 * 26380 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 26381 * 26382 * @fires Player#leavepictureinpicture 26383 * 26384 * @return {Promise} 26385 * A promise. 26386 */ 26387 ; 26388 26389 _proto.exitPictureInPicture = function exitPictureInPicture() { 26390 if ('pictureInPictureEnabled' in document) { 26391 /** 26392 * This event fires when the player leaves picture in picture mode 26393 * 26394 * @event Player#leavepictureinpicture 26395 * @type {EventTarget~Event} 26396 */ 26397 return document.exitPictureInPicture(); 26398 } 26399 } 26400 /** 26401 * Called when this Player has focus and a key gets pressed down, or when 26402 * any Component of this player receives a key press that it doesn't handle. 26403 * This allows player-wide hotkeys (either as defined below, or optionally 26404 * by an external function). 26405 * 26406 * @param {EventTarget~Event} event 26407 * The `keydown` event that caused this function to be called. 26408 * 26409 * @listens keydown 26410 */ 26411 ; 26412 26413 _proto.handleKeyDown = function handleKeyDown(event) { 26414 var userActions = this.options_.userActions; // Bail out if hotkeys are not configured. 26415 26416 if (!userActions || !userActions.hotkeys) { 26417 return; 26418 } // Function that determines whether or not to exclude an element from 26419 // hotkeys handling. 26420 26421 26422 var excludeElement = function excludeElement(el) { 26423 var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements. 26424 26425 if (el.isContentEditable) { 26426 return true; 26427 } // Inputs matching these types will still trigger hotkey handling as 26428 // they are not text inputs. 26429 26430 26431 var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit']; 26432 26433 if (tagName === 'input') { 26434 return allowedInputTypes.indexOf(el.type) === -1; 26435 } // The final test is by tag name. These tags will be excluded entirely. 26436 26437 26438 var excludedTags = ['textarea']; 26439 return excludedTags.indexOf(tagName) !== -1; 26440 }; // Bail out if the user is focused on an interactive form element. 26441 26442 26443 if (excludeElement(this.el_.ownerDocument.activeElement)) { 26444 return; 26445 } 26446 26447 if (typeof userActions.hotkeys === 'function') { 26448 userActions.hotkeys.call(this, event); 26449 } else { 26450 this.handleHotkeys(event); 26451 } 26452 } 26453 /** 26454 * Called when this Player receives a hotkey keydown event. 26455 * Supported player-wide hotkeys are: 26456 * 26457 * f - toggle fullscreen 26458 * m - toggle mute 26459 * k or Space - toggle play/pause 26460 * 26461 * @param {EventTarget~Event} event 26462 * The `keydown` event that caused this function to be called. 26463 */ 26464 ; 26465 26466 _proto.handleHotkeys = function handleHotkeys(event) { 26467 var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set 26468 26469 var _hotkeys$fullscreenKe = hotkeys.fullscreenKey, 26470 fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) { 26471 return keycode.isEventKey(keydownEvent, 'f'); 26472 } : _hotkeys$fullscreenKe, 26473 _hotkeys$muteKey = hotkeys.muteKey, 26474 muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) { 26475 return keycode.isEventKey(keydownEvent, 'm'); 26476 } : _hotkeys$muteKey, 26477 _hotkeys$playPauseKey = hotkeys.playPauseKey, 26478 playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) { 26479 return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'); 26480 } : _hotkeys$playPauseKey; 26481 26482 if (fullscreenKey.call(this, event)) { 26483 event.preventDefault(); 26484 event.stopPropagation(); 26485 var FSToggle = Component.getComponent('FullscreenToggle'); 26486 26487 if (document[this.fsApi_.fullscreenEnabled] !== false) { 26488 FSToggle.prototype.handleClick.call(this, event); 26489 } 26490 } else if (muteKey.call(this, event)) { 26491 event.preventDefault(); 26492 event.stopPropagation(); 26493 var MuteToggle = Component.getComponent('MuteToggle'); 26494 MuteToggle.prototype.handleClick.call(this, event); 26495 } else if (playPauseKey.call(this, event)) { 26496 event.preventDefault(); 26497 event.stopPropagation(); 26498 var PlayToggle = Component.getComponent('PlayToggle'); 26499 PlayToggle.prototype.handleClick.call(this, event); 26500 } 26501 } 26502 /** 26503 * Check whether the player can play a given mimetype 26504 * 26505 * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype 26506 * 26507 * @param {string} type 26508 * The mimetype to check 26509 * 26510 * @return {string} 26511 * 'probably', 'maybe', or '' (empty string) 26512 */ 26513 ; 26514 26515 _proto.canPlayType = function canPlayType(type) { 26516 var can; // Loop through each playback technology in the options order 26517 26518 for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { 26519 var techName = j[i]; 26520 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 26521 // Remove once that deprecated behavior is removed. 26522 26523 if (!tech) { 26524 tech = Component.getComponent(techName); 26525 } // Check if the current tech is defined before continuing 26526 26527 26528 if (!tech) { 26529 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 26530 continue; 26531 } // Check if the browser supports this technology 26532 26533 26534 if (tech.isSupported()) { 26535 can = tech.canPlayType(type); 26536 26537 if (can) { 26538 return can; 26539 } 26540 } 26541 } 26542 26543 return ''; 26544 } 26545 /** 26546 * Select source based on tech-order or source-order 26547 * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, 26548 * defaults to tech-order selection 26549 * 26550 * @param {Array} sources 26551 * The sources for a media asset 26552 * 26553 * @return {Object|boolean} 26554 * Object of source and tech order or false 26555 */ 26556 ; 26557 26558 _proto.selectSource = function selectSource(sources) { 26559 var _this12 = this; 26560 26561 // Get only the techs specified in `techOrder` that exist and are supported by the 26562 // current platform 26563 var techs = this.options_.techOrder.map(function (techName) { 26564 return [techName, Tech.getTech(techName)]; 26565 }).filter(function (_ref) { 26566 var techName = _ref[0], 26567 tech = _ref[1]; 26568 26569 // Check if the current tech is defined before continuing 26570 if (tech) { 26571 // Check if the browser supports this technology 26572 return tech.isSupported(); 26573 } 26574 26575 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 26576 return false;
vendor: 5,209 bytes, lines 26577-26736
26577 }); // Iterate over each `innerArray` element once per `outerArray` element and execute 26578 // `tester` with both. If `tester` returns a non-falsy value, exit early and return 26579 // that value. 26580 26581 var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { 26582 var found; 26583 outerArray.some(function (outerChoice) { 26584 return innerArray.some(function (innerChoice) { 26585 found = tester(outerChoice, innerChoice); 26586 26587 if (found) { 26588 return true; 26589 } 26590 }); 26591 }); 26592 return found; 26593 }; 26594 26595 var foundSourceAndTech; 26596 26597 var flip = function flip(fn) { 26598 return function (a, b) { 26599 return fn(b, a); 26600 }; 26601 }; 26602 26603 var finder = function finder(_ref2, source) { 26604 var techName = _ref2[0], 26605 tech = _ref2[1]; 26606 26607 if (tech.canPlaySource(source, _this12.options_[techName.toLowerCase()])) { 26608 return { 26609 source: source, 26610 tech: techName 26611 }; 26612 } 26613 }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources 26614 // to select from them based on their priority. 26615 26616 26617 if (this.options_.sourceOrder) { 26618 // Source-first ordering 26619 foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); 26620 } else { 26621 // Tech-first ordering 26622 foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); 26623 } 26624 26625 return foundSourceAndTech || false; 26626 } 26627 /** 26628 * Get or set the video source. 26629 * 26630 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 26631 * A SourceObject, an array of SourceObjects, or a string referencing 26632 * a URL to a media source. It is _highly recommended_ that an object 26633 * or array of objects is used here, so that source selection 26634 * algorithms can take the `type` into account. 26635 * 26636 * If not provided, this method acts as a getter. 26637 * 26638 * @return {string|undefined} 26639 * If the `source` argument is missing, returns the current source 26640 * URL. Otherwise, returns nothing/undefined. 26641 */ 26642 ; 26643 26644 _proto.src = function src(source) { 26645 var _this13 = this; 26646 26647 // getter usage 26648 if (typeof source === 'undefined') { 26649 return this.cache_.src || ''; 26650 } // filter out invalid sources and turn our source into 26651 // an array of source objects 26652 26653 26654 var sources = filterSource(source); // if a source was passed in then it is invalid because 26655 // it was filtered to a zero length Array. So we have to 26656 // show an error 26657 26658 if (!sources.length) { 26659 this.setTimeout(function () { 26660 this.error({ 26661 code: 4, 26662 message: this.localize(this.options_.notSupportedMessage) 26663 }); 26664 }, 0); 26665 return; 26666 } // initial sources 26667 26668 26669 this.changingSrc_ = true; 26670 this.cache_.sources = sources; 26671 this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware 26672 26673 setSource(this, sources[0], function (middlewareSource, mws) { 26674 _this13.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since 26675 // the source that is selected could be out of order from the cache update above this callback. 26676 26677 _this13.cache_.sources = sources; 26678 26679 _this13.updateSourceCaches_(middlewareSource); 26680 26681 var err = _this13.src_(middlewareSource); 26682 26683 if (err) { 26684 if (sources.length > 1) { 26685 return _this13.src(sources.slice(1)); 26686 } 26687 26688 _this13.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers 26689 26690 _this13.setTimeout(function () { 26691 this.error({ 26692 code: 4, 26693 message: this.localize(this.options_.notSupportedMessage) 26694 }); 26695 }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it 26696 // this needs a better comment about why this is needed 26697 26698 26699 _this13.triggerReady(); 26700 26701 return; 26702 } 26703 26704 setTech(mws, _this13.tech_); 26705 }); 26706 } 26707 /** 26708 * Set the source object on the tech, returns a boolean that indicates whether 26709 * there is a tech that can play the source or not 26710 * 26711 * @param {Tech~SourceObject} source 26712 * The source object to set on the Tech 26713 * 26714 * @return {boolean} 26715 * - True if there is no Tech to playback this source 26716 * - False otherwise 26717 * 26718 * @private 26719 */ 26720 ; 26721 26722 _proto.src_ = function src_(source) { 26723 var _this14 = this; 26724 26725 var sourceTech = this.selectSource([source]); 26726 26727 if (!sourceTech) { 26728 return true; 26729 } 26730 26731 if (!titleCaseEquals(sourceTech.tech, this.techName_)) { 26732 this.changingSrc_ = true; // load this technology with the chosen source 26733 26734 this.loadTech_(sourceTech.tech, sourceTech.source); 26735 this.tech_.ready(function () { 26736 _this14.changingSrc_ = false;
vendor: 47,890 bytes, lines 26737-28291
26737 }); 26738 return false; 26739 } // wait until the tech is ready to set the source 26740 // and set it synchronously if possible (#2326) 26741 26742 26743 this.ready(function () { 26744 // The setSource tech method was added with source handlers 26745 // so older techs won't support it 26746 // We need to check the direct prototype for the case where subclasses 26747 // of the tech do not support source handlers 26748 if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { 26749 this.techCall_('setSource', source); 26750 } else { 26751 this.techCall_('src', source.src); 26752 } 26753 26754 this.changingSrc_ = false; 26755 }, true); 26756 return false; 26757 } 26758 /** 26759 * Begin loading the src data. 26760 */ 26761 ; 26762 26763 _proto.load = function load() { 26764 this.techCall_('load'); 26765 } 26766 /** 26767 * Reset the player. Loads the first tech in the techOrder, 26768 * removes all the text tracks in the existing `tech`, 26769 * and calls `reset` on the `tech`. 26770 */ 26771 ; 26772 26773 _proto.reset = function reset() { 26774 var _this15 = this; 26775 26776 var PromiseClass = this.options_.Promise || window$1.Promise; 26777 26778 if (this.paused() || !PromiseClass) { 26779 this.doReset_(); 26780 } else { 26781 var playPromise = this.play(); 26782 silencePromise(playPromise.then(function () { 26783 return _this15.doReset_(); 26784 })); 26785 } 26786 }; 26787 26788 _proto.doReset_ = function doReset_() { 26789 if (this.tech_) { 26790 this.tech_.clearTracks('text'); 26791 } 26792 26793 this.resetCache_(); 26794 this.poster(''); 26795 this.loadTech_(this.options_.techOrder[0], null); 26796 this.techCall_('reset'); 26797 this.resetControlBarUI_(); 26798 26799 if (isEvented(this)) { 26800 this.trigger('playerreset'); 26801 } 26802 } 26803 /** 26804 * Reset Control Bar's UI by calling sub-methods that reset 26805 * all of Control Bar's components 26806 */ 26807 ; 26808 26809 _proto.resetControlBarUI_ = function resetControlBarUI_() { 26810 this.resetProgressBar_(); 26811 this.resetPlaybackRate_(); 26812 this.resetVolumeBar_(); 26813 } 26814 /** 26815 * Reset tech's progress so progress bar is reset in the UI 26816 */ 26817 ; 26818 26819 _proto.resetProgressBar_ = function resetProgressBar_() { 26820 this.currentTime(0); 26821 var _this$controlBar = this.controlBar, 26822 durationDisplay = _this$controlBar.durationDisplay, 26823 remainingTimeDisplay = _this$controlBar.remainingTimeDisplay; 26824 26825 if (durationDisplay) { 26826 durationDisplay.updateContent(); 26827 } 26828 26829 if (remainingTimeDisplay) { 26830 remainingTimeDisplay.updateContent(); 26831 } 26832 } 26833 /** 26834 * Reset Playback ratio 26835 */ 26836 ; 26837 26838 _proto.resetPlaybackRate_ = function resetPlaybackRate_() { 26839 this.playbackRate(this.defaultPlaybackRate()); 26840 this.handleTechRateChange_(); 26841 } 26842 /** 26843 * Reset Volume bar 26844 */ 26845 ; 26846 26847 _proto.resetVolumeBar_ = function resetVolumeBar_() { 26848 this.volume(1.0); 26849 this.trigger('volumechange'); 26850 } 26851 /** 26852 * Returns all of the current source objects. 26853 * 26854 * @return {Tech~SourceObject[]} 26855 * The current source objects 26856 */ 26857 ; 26858 26859 _proto.currentSources = function currentSources() { 26860 var source = this.currentSource(); 26861 var sources = []; // assume `{}` or `{ src }` 26862 26863 if (Object.keys(source).length !== 0) { 26864 sources.push(source); 26865 } 26866 26867 return this.cache_.sources || sources; 26868 } 26869 /** 26870 * Returns the current source object. 26871 * 26872 * @return {Tech~SourceObject} 26873 * The current source object 26874 */ 26875 ; 26876 26877 _proto.currentSource = function currentSource() { 26878 return this.cache_.source || {}; 26879 } 26880 /** 26881 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 26882 * Can be used in conjunction with `currentType` to assist in rebuilding the current source object. 26883 * 26884 * @return {string} 26885 * The current source 26886 */ 26887 ; 26888 26889 _proto.currentSrc = function currentSrc() { 26890 return this.currentSource() && this.currentSource().src || ''; 26891 } 26892 /** 26893 * Get the current source type e.g. video/mp4 26894 * This can allow you rebuild the current source object so that you could load the same 26895 * source and tech later 26896 * 26897 * @return {string} 26898 * The source MIME type 26899 */ 26900 ; 26901 26902 _proto.currentType = function currentType() { 26903 return this.currentSource() && this.currentSource().type || ''; 26904 } 26905 /** 26906 * Get or set the preload attribute 26907 * 26908 * @param {boolean} [value] 26909 * - true means that we should preload 26910 * - false means that we should not preload 26911 * 26912 * @return {string} 26913 * The preload attribute value when getting 26914 */ 26915 ; 26916 26917 _proto.preload = function preload(value) { 26918 if (value !== undefined) { 26919 this.techCall_('setPreload', value); 26920 this.options_.preload = value; 26921 return; 26922 } 26923 26924 return this.techGet_('preload'); 26925 } 26926 /** 26927 * Get or set the autoplay option. When this is a boolean it will 26928 * modify the attribute on the tech. When this is a string the attribute on 26929 * the tech will be removed and `Player` will handle autoplay on loadstarts. 26930 * 26931 * @param {boolean|string} [value] 26932 * - true: autoplay using the browser behavior 26933 * - false: do not autoplay 26934 * - 'play': call play() on every loadstart 26935 * - 'muted': call muted() then play() on every loadstart 26936 * - 'any': call play() on every loadstart. if that fails call muted() then play(). 26937 * - *: values other than those listed here will be set `autoplay` to true 26938 * 26939 * @return {boolean|string} 26940 * The current value of autoplay when getting 26941 */ 26942 ; 26943 26944 _proto.autoplay = function autoplay(value) { 26945 // getter usage 26946 if (value === undefined) { 26947 return this.options_.autoplay || false; 26948 } 26949 26950 var techAutoplay; // if the value is a valid string set it to that 26951 26952 if (typeof value === 'string' && /(any|play|muted)/.test(value)) { 26953 this.options_.autoplay = value; 26954 this.manualAutoplay_(value); 26955 techAutoplay = false; // any falsy value sets autoplay to false in the browser, 26956 // lets do the same 26957 } else if (!value) { 26958 this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true 26959 } else { 26960 this.options_.autoplay = true; 26961 } 26962 26963 techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up 26964 // a setAutoplay call on tech ready. We do this because the 26965 // autoplay option will be passed in the constructor and we 26966 // do not need to set it twice 26967 26968 if (this.tech_) { 26969 this.techCall_('setAutoplay', techAutoplay); 26970 } 26971 } 26972 /** 26973 * Set or unset the playsinline attribute. 26974 * Playsinline tells the browser that non-fullscreen playback is preferred. 26975 * 26976 * @param {boolean} [value] 26977 * - true means that we should try to play inline by default 26978 * - false means that we should use the browser's default playback mode, 26979 * which in most cases is inline. iOS Safari is a notable exception 26980 * and plays fullscreen by default. 26981 * 26982 * @return {string|Player} 26983 * - the current value of playsinline 26984 * - the player when setting 26985 * 26986 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 26987 */ 26988 ; 26989 26990 _proto.playsinline = function playsinline(value) { 26991 if (value !== undefined) { 26992 this.techCall_('setPlaysinline', value); 26993 this.options_.playsinline = value; 26994 return this; 26995 } 26996 26997 return this.techGet_('playsinline'); 26998 } 26999 /** 27000 * Get or set the loop attribute on the video element. 27001 * 27002 * @param {boolean} [value] 27003 * - true means that we should loop the video 27004 * - false means that we should not loop the video 27005 * 27006 * @return {boolean} 27007 * The current value of loop when getting 27008 */ 27009 ; 27010 27011 _proto.loop = function loop(value) { 27012 if (value !== undefined) { 27013 this.techCall_('setLoop', value); 27014 this.options_.loop = value; 27015 return; 27016 } 27017 27018 return this.techGet_('loop'); 27019 } 27020 /** 27021 * Get or set the poster image source url 27022 * 27023 * @fires Player#posterchange 27024 * 27025 * @param {string} [src] 27026 * Poster image source URL 27027 * 27028 * @return {string} 27029 * The current value of poster when getting 27030 */ 27031 ; 27032 27033 _proto.poster = function poster(src) { 27034 if (src === undefined) { 27035 return this.poster_; 27036 } // The correct way to remove a poster is to set as an empty string 27037 // other falsey values will throw errors 27038 27039 27040 if (!src) { 27041 src = ''; 27042 } 27043 27044 if (src === this.poster_) { 27045 return; 27046 } // update the internal poster variable 27047 27048 27049 this.poster_ = src; // update the tech's poster 27050 27051 this.techCall_('setPoster', src); 27052 this.isPosterFromTech_ = false; // alert components that the poster has been set 27053 27054 /** 27055 * This event fires when the poster image is changed on the player. 27056 * 27057 * @event Player#posterchange 27058 * @type {EventTarget~Event} 27059 */ 27060 27061 this.trigger('posterchange'); 27062 } 27063 /** 27064 * Some techs (e.g. YouTube) can provide a poster source in an 27065 * asynchronous way. We want the poster component to use this 27066 * poster source so that it covers up the tech's controls. 27067 * (YouTube's play button). However we only want to use this 27068 * source if the player user hasn't set a poster through 27069 * the normal APIs. 27070 * 27071 * @fires Player#posterchange 27072 * @listens Tech#posterchange 27073 * @private 27074 */ 27075 ; 27076 27077 _proto.handleTechPosterChange_ = function handleTechPosterChange_() { 27078 if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) { 27079 var newPoster = this.tech_.poster() || ''; 27080 27081 if (newPoster !== this.poster_) { 27082 this.poster_ = newPoster; 27083 this.isPosterFromTech_ = true; // Let components know the poster has changed 27084 27085 this.trigger('posterchange'); 27086 } 27087 } 27088 } 27089 /** 27090 * Get or set whether or not the controls are showing. 27091 * 27092 * @fires Player#controlsenabled 27093 * 27094 * @param {boolean} [bool] 27095 * - true to turn controls on 27096 * - false to turn controls off 27097 * 27098 * @return {boolean} 27099 * The current value of controls when getting 27100 */ 27101 ; 27102 27103 _proto.controls = function controls(bool) { 27104 if (bool === undefined) { 27105 return !!this.controls_; 27106 } 27107 27108 bool = !!bool; // Don't trigger a change event unless it actually changed 27109 27110 if (this.controls_ === bool) { 27111 return; 27112 } 27113 27114 this.controls_ = bool; 27115 27116 if (this.usingNativeControls()) { 27117 this.techCall_('setControls', bool); 27118 } 27119 27120 if (this.controls_) { 27121 this.removeClass('vjs-controls-disabled'); 27122 this.addClass('vjs-controls-enabled'); 27123 /** 27124 * @event Player#controlsenabled 27125 * @type {EventTarget~Event} 27126 */ 27127 27128 this.trigger('controlsenabled'); 27129 27130 if (!this.usingNativeControls()) { 27131 this.addTechControlsListeners_(); 27132 } 27133 } else { 27134 this.removeClass('vjs-controls-enabled'); 27135 this.addClass('vjs-controls-disabled'); 27136 /** 27137 * @event Player#controlsdisabled 27138 * @type {EventTarget~Event} 27139 */ 27140 27141 this.trigger('controlsdisabled'); 27142 27143 if (!this.usingNativeControls()) { 27144 this.removeTechControlsListeners_(); 27145 } 27146 } 27147 } 27148 /** 27149 * Toggle native controls on/off. Native controls are the controls built into 27150 * devices (e.g. default iPhone controls), Flash, or other techs 27151 * (e.g. Vimeo Controls) 27152 * **This should only be set by the current tech, because only the tech knows 27153 * if it can support native controls** 27154 * 27155 * @fires Player#usingnativecontrols 27156 * @fires Player#usingcustomcontrols 27157 * 27158 * @param {boolean} [bool] 27159 * - true to turn native controls on 27160 * - false to turn native controls off 27161 * 27162 * @return {boolean} 27163 * The current value of native controls when getting 27164 */ 27165 ; 27166 27167 _proto.usingNativeControls = function usingNativeControls(bool) { 27168 if (bool === undefined) { 27169 return !!this.usingNativeControls_; 27170 } 27171 27172 bool = !!bool; // Don't trigger a change event unless it actually changed 27173 27174 if (this.usingNativeControls_ === bool) { 27175 return; 27176 } 27177 27178 this.usingNativeControls_ = bool; 27179 27180 if (this.usingNativeControls_) { 27181 this.addClass('vjs-using-native-controls'); 27182 /** 27183 * player is using the native device controls 27184 * 27185 * @event Player#usingnativecontrols 27186 * @type {EventTarget~Event} 27187 */ 27188 27189 this.trigger('usingnativecontrols'); 27190 } else { 27191 this.removeClass('vjs-using-native-controls'); 27192 /** 27193 * player is using the custom HTML controls 27194 * 27195 * @event Player#usingcustomcontrols 27196 * @type {EventTarget~Event} 27197 */ 27198 27199 this.trigger('usingcustomcontrols'); 27200 } 27201 } 27202 /** 27203 * Set or get the current MediaError 27204 * 27205 * @fires Player#error 27206 * 27207 * @param {MediaError|string|number} [err] 27208 * A MediaError or a string/number to be turned 27209 * into a MediaError 27210 * 27211 * @return {MediaError|null} 27212 * The current MediaError when getting (or null) 27213 */ 27214 ; 27215 27216 _proto.error = function error(err) { 27217 if (err === undefined) { 27218 return this.error_ || null; 27219 } // Suppress the first error message for no compatible source until 27220 // user interaction 27221 27222 27223 if (this.options_.suppressNotSupportedError && err && err.code === 4) { 27224 var triggerSuppressedError = function triggerSuppressedError() { 27225 this.error(err); 27226 }; 27227 27228 this.options_.suppressNotSupportedError = false; 27229 this.any(['click', 'touchstart'], triggerSuppressedError); 27230 this.one('loadstart', function () { 27231 this.off(['click', 'touchstart'], triggerSuppressedError); 27232 }); 27233 return; 27234 } // restoring to default 27235 27236 27237 if (err === null) { 27238 this.error_ = err; 27239 this.removeClass('vjs-error'); 27240 27241 if (this.errorDisplay) { 27242 this.errorDisplay.close(); 27243 } 27244 27245 return; 27246 } 27247 27248 this.error_ = new MediaError(err); // add the vjs-error classname to the player 27249 27250 this.addClass('vjs-error'); // log the name of the error type and any message 27251 // IE11 logs "[object object]" and required you to expand message to see error object 27252 27253 log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_); 27254 /** 27255 * @event Player#error 27256 * @type {EventTarget~Event} 27257 */ 27258 27259 this.trigger('error'); 27260 return; 27261 } 27262 /** 27263 * Report user activity 27264 * 27265 * @param {Object} event 27266 * Event object 27267 */ 27268 ; 27269 27270 _proto.reportUserActivity = function reportUserActivity(event) { 27271 this.userActivity_ = true; 27272 } 27273 /** 27274 * Get/set if user is active 27275 * 27276 * @fires Player#useractive 27277 * @fires Player#userinactive 27278 * 27279 * @param {boolean} [bool] 27280 * - true if the user is active 27281 * - false if the user is inactive 27282 * 27283 * @return {boolean} 27284 * The current value of userActive when getting 27285 */ 27286 ; 27287 27288 _proto.userActive = function userActive(bool) { 27289 if (bool === undefined) { 27290 return this.userActive_; 27291 } 27292 27293 bool = !!bool; 27294 27295 if (bool === this.userActive_) { 27296 return; 27297 } 27298 27299 this.userActive_ = bool; 27300 27301 if (this.userActive_) { 27302 this.userActivity_ = true; 27303 this.removeClass('vjs-user-inactive'); 27304 this.addClass('vjs-user-active'); 27305 /** 27306 * @event Player#useractive 27307 * @type {EventTarget~Event} 27308 */ 27309 27310 this.trigger('useractive'); 27311 return; 27312 } // Chrome/Safari/IE have bugs where when you change the cursor it can 27313 // trigger a mousemove event. This causes an issue when you're hiding 27314 // the cursor when the user is inactive, and a mousemove signals user 27315 // activity. Making it impossible to go into inactive mode. Specifically 27316 // this happens in fullscreen when we really need to hide the cursor. 27317 // 27318 // When this gets resolved in ALL browsers it can be removed 27319 // https://code.google.com/p/chromium/issues/detail?id=103041 27320 27321 27322 if (this.tech_) { 27323 this.tech_.one('mousemove', function (e) { 27324 e.stopPropagation(); 27325 e.preventDefault(); 27326 }); 27327 } 27328 27329 this.userActivity_ = false; 27330 this.removeClass('vjs-user-active'); 27331 this.addClass('vjs-user-inactive'); 27332 /** 27333 * @event Player#userinactive 27334 * @type {EventTarget~Event} 27335 */ 27336 27337 this.trigger('userinactive'); 27338 } 27339 /** 27340 * Listen for user activity based on timeout value 27341 * 27342 * @private 27343 */ 27344 ; 27345 27346 _proto.listenForUserActivity_ = function listenForUserActivity_() { 27347 var mouseInProgress; 27348 var lastMoveX; 27349 var lastMoveY; 27350 var handleActivity = bind(this, this.reportUserActivity); 27351 27352 var handleMouseMove = function handleMouseMove(e) { 27353 // #1068 - Prevent mousemove spamming 27354 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 27355 if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { 27356 lastMoveX = e.screenX; 27357 lastMoveY = e.screenY; 27358 handleActivity(); 27359 } 27360 }; 27361 27362 var handleMouseDown = function handleMouseDown() { 27363 handleActivity(); // For as long as the they are touching the device or have their mouse down, 27364 // we consider them active even if they're not moving their finger or mouse. 27365 // So we want to continue to update that they are active 27366 27367 this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time 27368 // as the activityCheck interval (250) should ensure we never miss the 27369 // next activityCheck 27370 27371 mouseInProgress = this.setInterval(handleActivity, 250); 27372 }; 27373 27374 var handleMouseUpAndMouseLeave = function handleMouseUpAndMouseLeave(event) { 27375 handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down 27376 27377 this.clearInterval(mouseInProgress); 27378 }; // Any mouse movement will be considered user activity 27379 27380 27381 this.on('mousedown', handleMouseDown); 27382 this.on('mousemove', handleMouseMove); 27383 this.on('mouseup', handleMouseUpAndMouseLeave); 27384 this.on('mouseleave', handleMouseUpAndMouseLeave); 27385 var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed) 27386 // controlBar would no longer be hidden by default timeout. 27387 27388 if (controlBar && !IS_IOS && !IS_ANDROID) { 27389 controlBar.on('mouseenter', function (event) { 27390 this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout; 27391 this.player().options_.inactivityTimeout = 0; 27392 }); 27393 controlBar.on('mouseleave', function (event) { 27394 this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout; 27395 }); 27396 } // Listen for keyboard navigation 27397 // Shouldn't need to use inProgress interval because of key repeat 27398 27399 27400 this.on('keydown', handleActivity); 27401 this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into 27402 // the mousemove/touchmove function itself, to prevent performance degradation. 27403 // `this.reportUserActivity` simply sets this.userActivity_ to true, which 27404 // then gets picked up by this loop 27405 // http://ejohn.org/blog/learning-from-twitter/ 27406 27407 var inactivityTimeout; 27408 this.setInterval(function () { 27409 // Check to see if mouse/touch activity has happened 27410 if (!this.userActivity_) { 27411 return; 27412 } // Reset the activity tracker 27413 27414 27415 this.userActivity_ = false; // If the user state was inactive, set the state to active 27416 27417 this.userActive(true); // Clear any existing inactivity timeout to start the timer over 27418 27419 this.clearTimeout(inactivityTimeout); 27420 var timeout = this.options_.inactivityTimeout; 27421 27422 if (timeout <= 0) { 27423 return; 27424 } // In <timeout> milliseconds, if no more activity has occurred the 27425 // user will be considered inactive 27426 27427 27428 inactivityTimeout = this.setTimeout(function () { 27429 // Protect against the case where the inactivityTimeout can trigger just 27430 // before the next user activity is picked up by the activity check loop 27431 // causing a flicker 27432 if (!this.userActivity_) { 27433 this.userActive(false); 27434 } 27435 }, timeout); 27436 }, 250); 27437 } 27438 /** 27439 * Gets or sets the current playback rate. A playback rate of 27440 * 1.0 represents normal speed and 0.5 would indicate half-speed 27441 * playback, for instance. 27442 * 27443 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate 27444 * 27445 * @param {number} [rate] 27446 * New playback rate to set. 27447 * 27448 * @return {number} 27449 * The current playback rate when getting or 1.0 27450 */ 27451 ; 27452 27453 _proto.playbackRate = function playbackRate(rate) { 27454 if (rate !== undefined) { 27455 // NOTE: this.cache_.lastPlaybackRate is set from the tech handler 27456 // that is registered above 27457 this.techCall_('setPlaybackRate', rate); 27458 return; 27459 } 27460 27461 if (this.tech_ && this.tech_.featuresPlaybackRate) { 27462 return this.cache_.lastPlaybackRate || this.techGet_('playbackRate'); 27463 } 27464 27465 return 1.0; 27466 } 27467 /** 27468 * Gets or sets the current default playback rate. A default playback rate of 27469 * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. 27470 * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not 27471 * not the current playbackRate. 27472 * 27473 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate 27474 * 27475 * @param {number} [rate] 27476 * New default playback rate to set. 27477 * 27478 * @return {number|Player} 27479 * - The default playback rate when getting or 1.0 27480 * - the player when setting 27481 */ 27482 ; 27483 27484 _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) { 27485 if (rate !== undefined) { 27486 return this.techCall_('setDefaultPlaybackRate', rate); 27487 } 27488 27489 if (this.tech_ && this.tech_.featuresPlaybackRate) { 27490 return this.techGet_('defaultPlaybackRate'); 27491 } 27492 27493 return 1.0; 27494 } 27495 /** 27496 * Gets or sets the audio flag 27497 * 27498 * @param {boolean} bool 27499 * - true signals that this is an audio player 27500 * - false signals that this is not an audio player 27501 * 27502 * @return {boolean} 27503 * The current value of isAudio when getting 27504 */ 27505 ; 27506 27507 _proto.isAudio = function isAudio(bool) { 27508 if (bool !== undefined) { 27509 this.isAudio_ = !!bool; 27510 return; 27511 } 27512 27513 return !!this.isAudio_; 27514 } 27515 /** 27516 * A helper method for adding a {@link TextTrack} to our 27517 * {@link TextTrackList}. 27518 * 27519 * In addition to the W3C settings we allow adding additional info through options. 27520 * 27521 * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack 27522 * 27523 * @param {string} [kind] 27524 * the kind of TextTrack you are adding 27525 * 27526 * @param {string} [label] 27527 * the label to give the TextTrack label 27528 * 27529 * @param {string} [language] 27530 * the language to set on the TextTrack 27531 * 27532 * @return {TextTrack|undefined} 27533 * the TextTrack that was added or undefined 27534 * if there is no tech 27535 */ 27536 ; 27537 27538 _proto.addTextTrack = function addTextTrack(kind, label, language) { 27539 if (this.tech_) { 27540 return this.tech_.addTextTrack(kind, label, language); 27541 } 27542 } 27543 /** 27544 * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. 27545 * When manualCleanup is set to false, the track will be automatically removed 27546 * on source changes. 27547 * 27548 * @param {Object} options 27549 * Options to pass to {@link HTMLTrackElement} during creation. See 27550 * {@link HTMLTrackElement} for object properties that you should use. 27551 * 27552 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 27553 * removed on a source change 27554 * 27555 * @return {HtmlTrackElement} 27556 * the HTMLTrackElement that was created and added 27557 * to the HtmlTrackElementList and the remote 27558 * TextTrackList 27559 * 27560 * @deprecated The default value of the "manualCleanup" parameter will default 27561 * to "false" in upcoming versions of Video.js 27562 */ 27563 ; 27564 27565 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 27566 if (this.tech_) { 27567 return this.tech_.addRemoteTextTrack(options, manualCleanup); 27568 } 27569 } 27570 /** 27571 * Remove a remote {@link TextTrack} from the respective 27572 * {@link TextTrackList} and {@link HtmlTrackElementList}. 27573 * 27574 * @param {Object} track 27575 * Remote {@link TextTrack} to remove 27576 * 27577 * @return {undefined} 27578 * does not return anything 27579 */ 27580 ; 27581 27582 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) { 27583 if (obj === void 0) { 27584 obj = {}; 27585 } 27586 27587 var _obj = obj, 27588 track = _obj.track; 27589 27590 if (!track) { 27591 track = obj; 27592 } // destructure the input into an object with a track argument, defaulting to arguments[0] 27593 // default the whole argument to an empty object if nothing was passed in 27594 27595 27596 if (this.tech_) { 27597 return this.tech_.removeRemoteTextTrack(track); 27598 } 27599 } 27600 /** 27601 * Gets available media playback quality metrics as specified by the W3C's Media 27602 * Playback Quality API. 27603 * 27604 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 27605 * 27606 * @return {Object|undefined} 27607 * An object with supported media playback quality metrics or undefined if there 27608 * is no tech or the tech does not support it. 27609 */ 27610 ; 27611 27612 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 27613 return this.techGet_('getVideoPlaybackQuality'); 27614 } 27615 /** 27616 * Get video width 27617 * 27618 * @return {number} 27619 * current video width 27620 */ 27621 ; 27622 27623 _proto.videoWidth = function videoWidth() { 27624 return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; 27625 } 27626 /** 27627 * Get video height 27628 * 27629 * @return {number} 27630 * current video height 27631 */ 27632 ; 27633 27634 _proto.videoHeight = function videoHeight() { 27635 return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; 27636 } 27637 /** 27638 * The player's language code 27639 * NOTE: The language should be set in the player options if you want the 27640 * the controls to be built with a specific language. Changing the language 27641 * later will not update controls text. 27642 * 27643 * @param {string} [code] 27644 * the language code to set the player to 27645 * 27646 * @return {string} 27647 * The current language code when getting 27648 */ 27649 ; 27650 27651 _proto.language = function language(code) { 27652 if (code === undefined) { 27653 return this.language_; 27654 } 27655 27656 this.language_ = String(code).toLowerCase(); 27657 } 27658 /** 27659 * Get the player's language dictionary 27660 * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time 27661 * Languages specified directly in the player options have precedence 27662 * 27663 * @return {Array} 27664 * An array of of supported languages 27665 */ 27666 ; 27667 27668 _proto.languages = function languages() { 27669 return mergeOptions(Player.prototype.options_.languages, this.languages_); 27670 } 27671 /** 27672 * returns a JavaScript object reperesenting the current track 27673 * information. **DOES not return it as JSON** 27674 * 27675 * @return {Object} 27676 * Object representing the current of track info 27677 */ 27678 ; 27679 27680 _proto.toJSON = function toJSON() { 27681 var options = mergeOptions(this.options_); 27682 var tracks = options.tracks; 27683 options.tracks = []; 27684 27685 for (var i = 0; i < tracks.length; i++) { 27686 var track = tracks[i]; // deep merge tracks and null out player so no circular references 27687 27688 track = mergeOptions(track); 27689 track.player = undefined; 27690 options.tracks[i] = track; 27691 } 27692 27693 return options; 27694 } 27695 /** 27696 * Creates a simple modal dialog (an instance of the {@link ModalDialog} 27697 * component) that immediately overlays the player with arbitrary 27698 * content and removes itself when closed. 27699 * 27700 * @param {string|Function|Element|Array|null} content 27701 * Same as {@link ModalDialog#content}'s param of the same name. 27702 * The most straight-forward usage is to provide a string or DOM 27703 * element. 27704 * 27705 * @param {Object} [options] 27706 * Extra options which will be passed on to the {@link ModalDialog}. 27707 * 27708 * @return {ModalDialog} 27709 * the {@link ModalDialog} that was created 27710 */ 27711 ; 27712 27713 _proto.createModal = function createModal(content, options) { 27714 var _this16 = this; 27715 27716 options = options || {}; 27717 options.content = content || ''; 27718 var modal = new ModalDialog(this, options); 27719 this.addChild(modal); 27720 modal.on('dispose', function () { 27721 _this16.removeChild(modal); 27722 }); 27723 modal.open(); 27724 return modal; 27725 } 27726 /** 27727 * Change breakpoint classes when the player resizes. 27728 * 27729 * @private 27730 */ 27731 ; 27732 27733 _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() { 27734 if (!this.responsive()) { 27735 return; 27736 } 27737 27738 var currentBreakpoint = this.currentBreakpoint(); 27739 var currentWidth = this.currentWidth(); 27740 27741 for (var i = 0; i < BREAKPOINT_ORDER.length; i++) { 27742 var candidateBreakpoint = BREAKPOINT_ORDER[i]; 27743 var maxWidth = this.breakpoints_[candidateBreakpoint]; 27744 27745 if (currentWidth <= maxWidth) { 27746 // The current breakpoint did not change, nothing to do. 27747 if (currentBreakpoint === candidateBreakpoint) { 27748 return; 27749 } // Only remove a class if there is a current breakpoint. 27750 27751 27752 if (currentBreakpoint) { 27753 this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]); 27754 } 27755 27756 this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]); 27757 this.breakpoint_ = candidateBreakpoint; 27758 break; 27759 } 27760 } 27761 } 27762 /** 27763 * Removes the current breakpoint. 27764 * 27765 * @private 27766 */ 27767 ; 27768 27769 _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() { 27770 var className = this.currentBreakpointClass(); 27771 this.breakpoint_ = ''; 27772 27773 if (className) { 27774 this.removeClass(className); 27775 } 27776 } 27777 /** 27778 * Get or set breakpoints on the player. 27779 * 27780 * Calling this method with an object or `true` will remove any previous 27781 * custom breakpoints and start from the defaults again. 27782 * 27783 * @param {Object|boolean} [breakpoints] 27784 * If an object is given, it can be used to provide custom 27785 * breakpoints. If `true` is given, will set default breakpoints. 27786 * If this argument is not given, will simply return the current 27787 * breakpoints. 27788 * 27789 * @param {number} [breakpoints.tiny] 27790 * The maximum width for the "vjs-layout-tiny" class. 27791 * 27792 * @param {number} [breakpoints.xsmall] 27793 * The maximum width for the "vjs-layout-x-small" class. 27794 * 27795 * @param {number} [breakpoints.small] 27796 * The maximum width for the "vjs-layout-small" class. 27797 * 27798 * @param {number} [breakpoints.medium] 27799 * The maximum width for the "vjs-layout-medium" class. 27800 * 27801 * @param {number} [breakpoints.large] 27802 * The maximum width for the "vjs-layout-large" class. 27803 * 27804 * @param {number} [breakpoints.xlarge] 27805 * The maximum width for the "vjs-layout-x-large" class. 27806 * 27807 * @param {number} [breakpoints.huge] 27808 * The maximum width for the "vjs-layout-huge" class. 27809 * 27810 * @return {Object} 27811 * An object mapping breakpoint names to maximum width values. 27812 */ 27813 ; 27814 27815 _proto.breakpoints = function breakpoints(_breakpoints) { 27816 // Used as a getter. 27817 if (_breakpoints === undefined) { 27818 return assign(this.breakpoints_); 27819 } 27820 27821 this.breakpoint_ = ''; 27822 this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently 27823 // selected breakpoint. 27824 27825 this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning. 27826 27827 return assign(this.breakpoints_); 27828 } 27829 /** 27830 * Get or set a flag indicating whether or not this player should adjust 27831 * its UI based on its dimensions. 27832 * 27833 * @param {boolean} value 27834 * Should be `true` if the player should adjust its UI based on its 27835 * dimensions; otherwise, should be `false`. 27836 * 27837 * @return {boolean} 27838 * Will be `true` if this player should adjust its UI based on its 27839 * dimensions; otherwise, will be `false`. 27840 */ 27841 ; 27842 27843 _proto.responsive = function responsive(value) { 27844 // Used as a getter. 27845 if (value === undefined) { 27846 return this.responsive_; 27847 } 27848 27849 value = Boolean(value); 27850 var current = this.responsive_; // Nothing changed. 27851 27852 if (value === current) { 27853 return; 27854 } // The value actually changed, set it. 27855 27856 27857 this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the 27858 // player is now responsive. 27859 27860 if (value) { 27861 this.on('playerresize', this.updateCurrentBreakpoint_); 27862 this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive. 27863 } else { 27864 this.off('playerresize', this.updateCurrentBreakpoint_); 27865 this.removeCurrentBreakpoint_(); 27866 } 27867 27868 return value; 27869 } 27870 /** 27871 * Get current breakpoint name, if any. 27872 * 27873 * @return {string} 27874 * If there is currently a breakpoint set, returns a the key from the 27875 * breakpoints object matching it. Otherwise, returns an empty string. 27876 */ 27877 ; 27878 27879 _proto.currentBreakpoint = function currentBreakpoint() { 27880 return this.breakpoint_; 27881 } 27882 /** 27883 * Get the current breakpoint class name. 27884 * 27885 * @return {string} 27886 * The matching class name (e.g. `"vjs-layout-tiny"` or 27887 * `"vjs-layout-large"`) for the current breakpoint. Empty string if 27888 * there is no current breakpoint. 27889 */ 27890 ; 27891 27892 _proto.currentBreakpointClass = function currentBreakpointClass() { 27893 return BREAKPOINT_CLASSES[this.breakpoint_] || ''; 27894 } 27895 /** 27896 * An object that describes a single piece of media. 27897 * 27898 * Properties that are not part of this type description will be retained; so, 27899 * this can be viewed as a generic metadata storage mechanism as well. 27900 * 27901 * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface} 27902 * @typedef {Object} Player~MediaObject 27903 * 27904 * @property {string} [album] 27905 * Unused, except if this object is passed to the `MediaSession` 27906 * API. 27907 * 27908 * @property {string} [artist] 27909 * Unused, except if this object is passed to the `MediaSession` 27910 * API. 27911 * 27912 * @property {Object[]} [artwork] 27913 * Unused, except if this object is passed to the `MediaSession` 27914 * API. If not specified, will be populated via the `poster`, if 27915 * available. 27916 * 27917 * @property {string} [poster] 27918 * URL to an image that will display before playback. 27919 * 27920 * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src] 27921 * A single source object, an array of source objects, or a string 27922 * referencing a URL to a media source. It is _highly recommended_ 27923 * that an object or array of objects is used here, so that source 27924 * selection algorithms can take the `type` into account. 27925 * 27926 * @property {string} [title] 27927 * Unused, except if this object is passed to the `MediaSession` 27928 * API. 27929 * 27930 * @property {Object[]} [textTracks] 27931 * An array of objects to be used to create text tracks, following 27932 * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}. 27933 * For ease of removal, these will be created as "remote" text 27934 * tracks and set to automatically clean up on source changes. 27935 * 27936 * These objects may have properties like `src`, `kind`, `label`, 27937 * and `language`, see {@link Tech#createRemoteTextTrack}. 27938 */ 27939 27940 /** 27941 * Populate the player using a {@link Player~MediaObject|MediaObject}. 27942 * 27943 * @param {Player~MediaObject} media 27944 * A media object. 27945 * 27946 * @param {Function} ready 27947 * A callback to be called when the player is ready. 27948 */ 27949 ; 27950 27951 _proto.loadMedia = function loadMedia(media, ready) { 27952 var _this17 = this; 27953 27954 if (!media || typeof media !== 'object') { 27955 return; 27956 } 27957 27958 this.reset(); // Clone the media object so it cannot be mutated from outside. 27959 27960 this.cache_.media = mergeOptions(media); 27961 var _this$cache_$media = this.cache_.media, 27962 artwork = _this$cache_$media.artwork, 27963 poster = _this$cache_$media.poster, 27964 src = _this$cache_$media.src, 27965 textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`. 27966 27967 if (!artwork && poster) { 27968 this.cache_.media.artwork = [{ 27969 src: poster, 27970 type: getMimetype(poster) 27971 }]; 27972 } 27973 27974 if (src) { 27975 this.src(src); 27976 } 27977 27978 if (poster) { 27979 this.poster(poster); 27980 } 27981 27982 if (Array.isArray(textTracks)) { 27983 textTracks.forEach(function (tt) { 27984 return _this17.addRemoteTextTrack(tt, false); 27985 }); 27986 } 27987 27988 this.ready(ready); 27989 } 27990 /** 27991 * Get a clone of the current {@link Player~MediaObject} for this player. 27992 * 27993 * If the `loadMedia` method has not been used, will attempt to return a 27994 * {@link Player~MediaObject} based on the current state of the player. 27995 * 27996 * @return {Player~MediaObject} 27997 */ 27998 ; 27999 28000 _proto.getMedia = function getMedia() { 28001 if (!this.cache_.media) { 28002 var poster = this.poster(); 28003 var src = this.currentSources(); 28004 var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) { 28005 return { 28006 kind: tt.kind, 28007 label: tt.label, 28008 language: tt.language, 28009 src: tt.src 28010 }; 28011 }); 28012 var media = { 28013 src: src, 28014 textTracks: textTracks 28015 }; 28016 28017 if (poster) { 28018 media.poster = poster; 28019 media.artwork = [{ 28020 src: media.poster, 28021 type: getMimetype(media.poster) 28022 }]; 28023 } 28024 28025 return media; 28026 } 28027 28028 return mergeOptions(this.cache_.media); 28029 } 28030 /** 28031 * Gets tag settings 28032 * 28033 * @param {Element} tag 28034 * The player tag 28035 * 28036 * @return {Object} 28037 * An object containing all of the settings 28038 * for a player tag 28039 */ 28040 ; 28041 28042 Player.getTagSettings = function getTagSettings(tag) { 28043 var baseOptions = { 28044 sources: [], 28045 tracks: [] 28046 }; 28047 var tagOptions = getAttributes(tag); 28048 var dataSetup = tagOptions['data-setup']; 28049 28050 if (hasClass(tag, 'vjs-fill')) { 28051 tagOptions.fill = true; 28052 } 28053 28054 if (hasClass(tag, 'vjs-fluid')) { 28055 tagOptions.fluid = true; 28056 } // Check if data-setup attr exists. 28057 28058 28059 if (dataSetup !== null) { 28060 // Parse options JSON 28061 // If empty string, make it a parsable json object. 28062 var _safeParseTuple = tuple(dataSetup || '{}'), 28063 err = _safeParseTuple[0], 28064 data = _safeParseTuple[1]; 28065 28066 if (err) { 28067 log.error(err); 28068 } 28069 28070 assign(tagOptions, data); 28071 } 28072 28073 assign(baseOptions, tagOptions); // Get tag children settings 28074 28075 if (tag.hasChildNodes()) { 28076 var children = tag.childNodes; 28077 28078 for (var i = 0, j = children.length; i < j; i++) { 28079 var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ 28080 28081 var childName = child.nodeName.toLowerCase(); 28082 28083 if (childName === 'source') { 28084 baseOptions.sources.push(getAttributes(child)); 28085 } else if (childName === 'track') { 28086 baseOptions.tracks.push(getAttributes(child)); 28087 } 28088 } 28089 } 28090 28091 return baseOptions; 28092 } 28093 /** 28094 * Determine whether or not flexbox is supported 28095 * 28096 * @return {boolean} 28097 * - true if flexbox is supported 28098 * - false if flexbox is not supported 28099 */ 28100 ; 28101 28102 _proto.flexNotSupported_ = function flexNotSupported_() { 28103 var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more 28104 // common flex features that we can rely on when checking for flex support. 28105 28106 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 28107 'msFlexOrder' in elem.style); 28108 } 28109 /** 28110 * Set debug mode to enable/disable logs at info level. 28111 * 28112 * @param {boolean} enabled 28113 * @fires Player#debugon 28114 * @fires Player#debugoff 28115 */ 28116 ; 28117 28118 _proto.debug = function debug(enabled) { 28119 if (enabled === undefined) { 28120 return this.debugEnabled_; 28121 } 28122 28123 if (enabled) { 28124 this.trigger('debugon'); 28125 this.previousLogLevel_ = this.log.level; 28126 this.log.level('debug'); 28127 this.debugEnabled_ = true; 28128 } else { 28129 this.trigger('debugoff'); 28130 this.log.level(this.previousLogLevel_); 28131 this.previousLogLevel_ = undefined; 28132 this.debugEnabled_ = false; 28133 } 28134 }; 28135 28136 return Player; 28137 }(Component); 28138 /** 28139 * Get the {@link VideoTrackList} 28140 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist 28141 * 28142 * @return {VideoTrackList} 28143 * the current video track list 28144 * 28145 * @method Player.prototype.videoTracks 28146 */ 28147 28148 /** 28149 * Get the {@link AudioTrackList} 28150 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist 28151 * 28152 * @return {AudioTrackList} 28153 * the current audio track list 28154 * 28155 * @method Player.prototype.audioTracks 28156 */ 28157 28158 /** 28159 * Get the {@link TextTrackList} 28160 * 28161 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks 28162 * 28163 * @return {TextTrackList} 28164 * the current text track list 28165 * 28166 * @method Player.prototype.textTracks 28167 */ 28168 28169 /** 28170 * Get the remote {@link TextTrackList} 28171 * 28172 * @return {TextTrackList} 28173 * The current remote text track list 28174 * 28175 * @method Player.prototype.remoteTextTracks 28176 */ 28177 28178 /** 28179 * Get the remote {@link HtmlTrackElementList} tracks. 28180 * 28181 * @return {HtmlTrackElementList} 28182 * The current remote text track element list 28183 * 28184 * @method Player.prototype.remoteTextTrackEls 28185 */ 28186 28187 28188 ALL.names.forEach(function (name) { 28189 var props = ALL[name]; 28190 28191 Player.prototype[props.getterName] = function () { 28192 if (this.tech_) { 28193 return this.tech_[props.getterName](); 28194 } // if we have not yet loadTech_, we create {video,audio,text}Tracks_ 28195 // these will be passed to the tech during loading 28196 28197 28198 this[props.privateName] = this[props.privateName] || new props.ListClass(); 28199 return this[props.privateName]; 28200 }; 28201 }); 28202 /** 28203 * Get or set the `Player`'s crossorigin option. For the HTML5 player, this 28204 * sets the `crossOrigin` property on the `<video>` tag to control the CORS 28205 * behavior. 28206 * 28207 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin} 28208 * 28209 * @param {string} [value] 28210 * The value to set the `Player`'s crossorigin to. If an argument is 28211 * given, must be one of `anonymous` or `use-credentials`. 28212 * 28213 * @return {string|undefined} 28214 * - The current crossorigin value of the `Player` when getting. 28215 * - undefined when setting 28216 */ 28217 28218 Player.prototype.crossorigin = Player.prototype.crossOrigin; 28219 /** 28220 * Global enumeration of players. 28221 * 28222 * The keys are the player IDs and the values are either the {@link Player} 28223 * instance or `null` for disposed players. 28224 * 28225 * @type {Object} 28226 */ 28227 28228 Player.players = {}; 28229 var navigator = window$1.navigator; 28230 /* 28231 * Player instance options, surfaced using options 28232 * options = Player.prototype.options_ 28233 * Make changes in options, not here. 28234 * 28235 * @type {Object} 28236 * @private 28237 */ 28238 28239 Player.prototype.options_ = { 28240 // Default order of fallback technology 28241 techOrder: Tech.defaultTechOrder_, 28242 html5: {}, 28243 flash: {}, 28244 // default inactivity timeout 28245 inactivityTimeout: 2000, 28246 // default playback rates 28247 playbackRates: [], 28248 // Add playback rate selection by adding rates 28249 // 'playbackRates': [0.5, 1, 1.5, 2], 28250 liveui: false, 28251 // Included control sets 28252 children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'], 28253 language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', 28254 // locales and their language translations 28255 languages: {}, 28256 // Default message to show when a video cannot be played. 28257 notSupportedMessage: 'No compatible source was found for this media.', 28258 fullscreen: { 28259 options: { 28260 navigationUI: 'hide' 28261 } 28262 }, 28263 breakpoints: {}, 28264 responsive: false 28265 }; 28266 [ 28267 /** 28268 * Returns whether or not the player is in the "ended" state. 28269 * 28270 * @return {Boolean} True if the player is in the ended state, false if not. 28271 * @method Player#ended 28272 */ 28273 'ended', 28274 /** 28275 * Returns whether or not the player is in the "seeking" state. 28276 * 28277 * @return {Boolean} True if the player is in the seeking state, false if not. 28278 * @method Player#seeking 28279 */ 28280 'seeking', 28281 /** 28282 * Returns the TimeRanges of the media that are currently available 28283 * for seeking to. 28284 * 28285 * @return {TimeRanges} the seekable intervals of the media timeline 28286 * @method Player#seekable 28287 */ 28288 'seekable', 28289 /** 28290 * Returns the current state of network activity for the element, from 28291 * the codes in the list below.
28292 * - NETWORK_EMPTY (numeric value 0) 28293 * The element has not yet been initialised. All attributes are in 28294 * their initial states. 28295 * - NETWORK_IDLE (numeric value 1) 28296 * The element's resource selection algorithm is active and has 28297 * selected a resource, but it is not actually using the network at 28298 * this time. 28299 * - NETWORK_LOADING (numeric value 2) 28300 * The user agent is actively trying to download data. 28301 * - NETWORK_NO_SOURCE (numeric value 3) 28302 * The element's resource selection algorithm is active, but it has 28303 * not yet found a resource to use. 28304 * 28305 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states 28306 * @return {number} the current network activity state 28307 * @method Player#networkState 28308 */ 28309 'networkState', 28310 /** 28311 * Returns a value that expresses the current state of the element 28312 * with respect to rendering the current playback position, from the 28313 * codes in the list below. 28314 * - HAVE_NOTHING (numeric value 0) 28315 * No information regarding the media resource is available. 28316 * - HAVE_METADATA (numeric value 1) 28317 * Enough of the resource has been obtained that the duration of the 28318 * resource is available. 28319 * - HAVE_CURRENT_DATA (numeric value 2) 28320 * Data for the immediate current playback position is available. 28321 * - HAVE_FUTURE_DATA (numeric value 3) 28322 * Data for the immediate current playback position is available, as 28323 * well as enough data for the user agent to advance the current 28324 * playback position in the direction of playback. 28325 * - HAVE_ENOUGH_DATA (numeric value 4) 28326 * The user agent estimates that enough data is available for 28327 * playback to proceed uninterrupted. 28328 * 28329 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate 28330 * @return {number} the current playback rendering state 28331 * @method Player#readyState 28332 */ 28333 'readyState'].forEach(function (fn) { 28334 Player.prototype[fn] = function () { 28335 return this.techGet_(fn); 28336 }; 28337 }); 28338 TECH_EVENTS_RETRIGGER.forEach(function (event) { 28339 Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () { 28340 return this.trigger(event); 28341 }; 28342 }); 28343 /** 28344 * Fired when the player has initial duration and dimension information 28345 * 28346 * @event Player#loadedmetadata 28347 * @type {EventTarget~Event} 28348 */ 28349 28350 /** 28351 * Fired when the player has downloaded data at the current playback position 28352 * 28353 * @event Player#loadeddata 28354 * @type {EventTarget~Event} 28355 */ 28356 28357 /** 28358 * Fired when the current playback position has changed * 28359 * During playback this is fired every 15-250 milliseconds, depending on the 28360 * playback technology in use. 28361 * 28362 * @event Player#timeupdate 28363 * @type {EventTarget~Event} 28364 */ 28365 28366 /** 28367 * Fired when the volume changes 28368 * 28369 * @event Player#volumechange 28370 * @type {EventTarget~Event} 28371 */ 28372 28373 /** 28374 * Reports whether or not a player has a plugin available. 28375 * 28376 * This does not report whether or not the plugin has ever been initialized 28377 * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. 28378 * 28379 * @method Player#hasPlugin 28380 * @param {string} name 28381 * The name of a plugin. 28382 * 28383 * @return {boolean} 28384 * Whether or not this player has the requested plugin available. 28385 */ 28386 28387 /** 28388 * Reports whether or not a player is using a plugin by name. 28389 * 28390 * For basic plugins, this only reports whether the plugin has _ever_ been 28391 * initialized on this player. 28392 * 28393 * @method Player#usingPlugin 28394 * @param {string} name 28395 * The name of a plugin. 28396 * 28397 * @return {boolean} 28398 * Whether or not this player is using the requested plugin. 28399 */ 28400 28401 Component.registerComponent('Player', Player); 28402 28403 var setPrototypeOf = createCommonjsModule(function (module) { 28404 function _setPrototypeOf(o, p) { 28405 module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 28406 o.__proto__ = p; 28407 return o; 28408 }; 28409 28410 return _setPrototypeOf(o, p); 28411 } 28412 28413 module.exports = _setPrototypeOf; 28414 }); 28415 28416 function _isNativeReflectConstruct() { 28417 if (typeof Reflect === "undefined" || !Reflect.construct) return false;
28418 if (Reflect.construct.sham) return false; 28419 if (typeof Proxy === "function") return true; 28420 28421 try { 28422 Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 28423 return true; 28424 } catch (e) { 28425 return false; 28426 } 28427 } 28428 28429 var isNativeReflectConstruct = _isNativeReflectConstruct; 28430 28431 var construct = createCommonjsModule(function (module) { 28432 function _construct(Parent, args, Class) { 28433 if (isNativeReflectConstruct()) { 28434 module.exports = _construct = Reflect.construct; 28435 } else { 28436 module.exports = _construct = function _construct(Parent, args, Class) { 28437 var a = [null]; 28438 a.push.apply(a, args); 28439 var Constructor = Function.bind.apply(Parent, a); 28440 var instance = new Constructor(); 28441 if (Class) setPrototypeOf(instance, Class.prototype); 28442 return instance; 28443 }; 28444 } 28445 28446 return _construct.apply(null, arguments); 28447 } 28448 28449 module.exports = _construct; 28450 }); 28451 28452 /** 28453 * The base plugin name. 28454 * 28455 * @private 28456 * @constant 28457 * @type {string} 28458 */ 28459 28460 var BASE_PLUGIN_NAME = 'plugin'; 28461 /** 28462 * The key on which a player's active plugins cache is stored. 28463 * 28464 * @private 28465 * @constant 28466 * @type {string} 28467 */ 28468 28469 var PLUGIN_CACHE_KEY = 'activePlugins_'; 28470 /** 28471 * Stores registered plugins in a private space. 28472 * 28473 * @private 28474 * @type {Object} 28475 */ 28476 28477 var pluginStorage = {}; 28478 /** 28479 * Reports whether or not a plugin has been registered. 28480 * 28481 * @private 28482 * @param {string} name 28483 * The name of a plugin. 28484 * 28485 * @return {boolean} 28486 * Whether or not the plugin has been registered. 28487 */ 28488 28489 var pluginExists = function pluginExists(name) { 28490 return pluginStorage.hasOwnProperty(name); 28491 }; 28492 /** 28493 * Get a single registered plugin by name. 28494 * 28495 * @private 28496 * @param {string} name 28497 * The name of a plugin. 28498 * 28499 * @return {Function|undefined} 28500 * The plugin (or undefined). 28501 */ 28502 28503 28504 var getPlugin = function getPlugin(name) { 28505 return pluginExists(name) ? pluginStorage[name] : undefined; 28506 }; 28507 /** 28508 * Marks a plugin as "active" on a player. 28509 * 28510 * Also, ensures that the player has an object for tracking active plugins. 28511 * 28512 * @private 28513 * @param {Player} player 28514 * A Video.js player instance. 28515 * 28516 * @param {string} name 28517 * The name of a plugin. 28518 */ 28519 28520 28521 var markPluginAsActive = function markPluginAsActive(player, name) { 28522 player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; 28523 player[PLUGIN_CACHE_KEY][name] = true; 28524 }; 28525 /** 28526 * Triggers a pair of plugin setup events. 28527 * 28528 * @private 28529 * @param {Player} player 28530 * A Video.js player instance. 28531 * 28532 * @param {Plugin~PluginEventHash} hash 28533 * A plugin event hash. 28534 * 28535 * @param {boolean} [before] 28536 * If true, prefixes the event name with "before". In other words, 28537 * use this to trigger "beforepluginsetup" instead of "pluginsetup". 28538 */ 28539 28540 28541 var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { 28542 var eventName = (before ? 'before' : '') + 'pluginsetup'; 28543 player.trigger(eventName, hash); 28544 player.trigger(eventName + ':' + hash.name, hash); 28545 }; 28546 /** 28547 * Takes a basic plugin function and returns a wrapper function which marks 28548 * on the player that the plugin has been activated. 28549 * 28550 * @private 28551 * @param {string} name 28552 * The name of the plugin. 28553 * 28554 * @param {Function} plugin 28555 * The basic plugin. 28556 * 28557 * @return {Function} 28558 * A wrapper function for the given plugin. 28559 */ 28560 28561 28562 var createBasicPlugin = function createBasicPlugin(name, plugin) { 28563 var basicPluginWrapper = function basicPluginWrapper() { 28564 // We trigger the "beforepluginsetup" and "pluginsetup" events on the player 28565 // regardless, but we want the hash to be consistent with the hash provided 28566 // for advanced plugins. 28567 // 28568 // The only potentially counter-intuitive thing here is the `instance` in 28569 // the "pluginsetup" event is the value returned by the `plugin` function. 28570 triggerSetupEvent(this, { 28571 name: name, 28572 plugin: plugin, 28573 instance: null 28574 }, true); 28575 var instance = plugin.apply(this, arguments); 28576 markPluginAsActive(this, name); 28577 triggerSetupEvent(this, { 28578 name: name, 28579 plugin: plugin, 28580 instance: instance 28581 }); 28582 return instance; 28583 }; 28584
vendor: 8,993 bytes, lines 28585-28869
28585 Object.keys(plugin).forEach(function (prop) { 28586 basicPluginWrapper[prop] = plugin[prop]; 28587 }); 28588 return basicPluginWrapper; 28589 }; 28590 /** 28591 * Takes a plugin sub-class and returns a factory function for generating 28592 * instances of it. 28593 * 28594 * This factory function will replace itself with an instance of the requested 28595 * sub-class of Plugin. 28596 * 28597 * @private 28598 * @param {string} name 28599 * The name of the plugin. 28600 * 28601 * @param {Plugin} PluginSubClass 28602 * The advanced plugin. 28603 * 28604 * @return {Function} 28605 */ 28606 28607 28608 var createPluginFactory = function createPluginFactory(name, PluginSubClass) { 28609 // Add a `name` property to the plugin prototype so that each plugin can 28610 // refer to itself by name. 28611 PluginSubClass.prototype.name = name; 28612 return function () { 28613 triggerSetupEvent(this, { 28614 name: name, 28615 plugin: PluginSubClass, 28616 instance: null 28617 }, true); 28618 28619 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 28620 args[_key] = arguments[_key]; 28621 } 28622 28623 var instance = construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance. 28624 28625 28626 this[name] = function () { 28627 return instance; 28628 }; 28629 28630 triggerSetupEvent(this, instance.getEventHash()); 28631 return instance; 28632 }; 28633 }; 28634 /** 28635 * Parent class for all advanced plugins. 28636 * 28637 * @mixes module:evented~EventedMixin 28638 * @mixes module:stateful~StatefulMixin 28639 * @fires Player#beforepluginsetup 28640 * @fires Player#beforepluginsetup:$name 28641 * @fires Player#pluginsetup 28642 * @fires Player#pluginsetup:$name 28643 * @listens Player#dispose 28644 * @throws {Error} 28645 * If attempting to instantiate the base {@link Plugin} class 28646 * directly instead of via a sub-class. 28647 */ 28648 28649 28650 var Plugin = /*#__PURE__*/function () { 28651 /** 28652 * Creates an instance of this class. 28653 * 28654 * Sub-classes should call `super` to ensure plugins are properly initialized. 28655 * 28656 * @param {Player} player 28657 * A Video.js player instance. 28658 */ 28659 function Plugin(player) { 28660 if (this.constructor === Plugin) { 28661 throw new Error('Plugin must be sub-classed; not directly instantiated.'); 28662 } 28663 28664 this.player = player; 28665 28666 if (!this.log) { 28667 this.log = this.player.log.createLogger(this.name); 28668 } // Make this object evented, but remove the added `trigger` method so we 28669 // use the prototype version instead. 28670 28671 28672 evented(this); 28673 delete this.trigger; 28674 stateful(this, this.constructor.defaultState); 28675 markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind 28676 // it later easily. 28677 28678 this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin. 28679 28680 player.on('dispose', this.dispose); 28681 } 28682 /** 28683 * Get the version of the plugin that was set on <pluginName>.VERSION 28684 */ 28685 28686 28687 var _proto = Plugin.prototype; 28688 28689 _proto.version = function version() { 28690 return this.constructor.VERSION; 28691 } 28692 /** 28693 * Each event triggered by plugins includes a hash of additional data with 28694 * conventional properties. 28695 * 28696 * This returns that object or mutates an existing hash. 28697 * 28698 * @param {Object} [hash={}] 28699 * An object to be used as event an event hash. 28700 * 28701 * @return {Plugin~PluginEventHash} 28702 * An event hash object with provided properties mixed-in. 28703 */ 28704 ; 28705 28706 _proto.getEventHash = function getEventHash(hash) { 28707 if (hash === void 0) { 28708 hash = {}; 28709 } 28710 28711 hash.name = this.name; 28712 hash.plugin = this.constructor; 28713 hash.instance = this; 28714 return hash; 28715 } 28716 /** 28717 * Triggers an event on the plugin object and overrides 28718 * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. 28719 * 28720 * @param {string|Object} event 28721 * An event type or an object with a type property. 28722 * 28723 * @param {Object} [hash={}] 28724 * Additional data hash to merge with a 28725 * {@link Plugin~PluginEventHash|PluginEventHash}. 28726 * 28727 * @return {boolean} 28728 * Whether or not default was prevented. 28729 */ 28730 ; 28731 28732 _proto.trigger = function trigger$1(event, hash) { 28733 if (hash === void 0) { 28734 hash = {}; 28735 } 28736 28737 return trigger(this.eventBusEl_, event, this.getEventHash(hash)); 28738 } 28739 /** 28740 * Handles "statechanged" events on the plugin. No-op by default, override by 28741 * subclassing. 28742 * 28743 * @abstract 28744 * @param {Event} e 28745 * An event object provided by a "statechanged" event. 28746 * 28747 * @param {Object} e.changes 28748 * An object describing changes that occurred with the "statechanged" 28749 * event. 28750 */ 28751 ; 28752 28753 _proto.handleStateChanged = function handleStateChanged(e) {} 28754 /** 28755 * Disposes a plugin. 28756 * 28757 * Subclasses can override this if they want, but for the sake of safety, 28758 * it's probably best to subscribe the "dispose" event. 28759 * 28760 * @fires Plugin#dispose 28761 */ 28762 ; 28763 28764 _proto.dispose = function dispose() { 28765 var name = this.name, 28766 player = this.player; 28767 /** 28768 * Signals that a advanced plugin is about to be disposed. 28769 * 28770 * @event Plugin#dispose 28771 * @type {EventTarget~Event} 28772 */ 28773 28774 this.trigger('dispose'); 28775 this.off(); 28776 player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up 28777 // references between the player and the plugin instance and nulling out 28778 // the plugin's state and replacing methods with a function that throws. 28779 28780 player[PLUGIN_CACHE_KEY][name] = false; 28781 this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory 28782 // function, so that the plugin is ready to be set up again. 28783 28784 player[name] = createPluginFactory(name, pluginStorage[name]); 28785 } 28786 /** 28787 * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). 28788 * 28789 * @param {string|Function} plugin 28790 * If a string, matches the name of a plugin. If a function, will be 28791 * tested directly. 28792 * 28793 * @return {boolean} 28794 * Whether or not a plugin is a basic plugin. 28795 */ 28796 ; 28797 28798 Plugin.isBasic = function isBasic(plugin) { 28799 var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; 28800 return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); 28801 } 28802 /** 28803 * Register a Video.js plugin. 28804 * 28805 * @param {string} name 28806 * The name of the plugin to be registered. Must be a string and 28807 * must not match an existing plugin or a method on the `Player` 28808 * prototype. 28809 * 28810 * @param {Function} plugin 28811 * A sub-class of `Plugin` or a function for basic plugins. 28812 * 28813 * @return {Function} 28814 * For advanced plugins, a factory function for that plugin. For 28815 * basic plugins, a wrapper function that initializes the plugin. 28816 */ 28817 ; 28818 28819 Plugin.registerPlugin = function registerPlugin(name, plugin) { 28820 if (typeof name !== 'string') { 28821 throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + "."); 28822 } 28823 28824 if (pluginExists(name)) { 28825 log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!"); 28826 } else if (Player.prototype.hasOwnProperty(name)) { 28827 throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!"); 28828 } 28829 28830 if (typeof plugin !== 'function') { 28831 throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + "."); 28832 } 28833 28834 pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for 28835 // the base Plugin class). 28836 28837 if (name !== BASE_PLUGIN_NAME) { 28838 if (Plugin.isBasic(plugin)) { 28839 Player.prototype[name] = createBasicPlugin(name, plugin); 28840 } else { 28841 Player.prototype[name] = createPluginFactory(name, plugin); 28842 } 28843 } 28844 28845 return plugin; 28846 } 28847 /** 28848 * De-register a Video.js plugin. 28849 * 28850 * @param {string} name 28851 * The name of the plugin to be de-registered. Must be a string that 28852 * matches an existing plugin. 28853 * 28854 * @throws {Error} 28855 * If an attempt is made to de-register the base plugin. 28856 */ 28857 ; 28858 28859 Plugin.deregisterPlugin = function deregisterPlugin(name) { 28860 if (name === BASE_PLUGIN_NAME) { 28861 throw new Error('Cannot de-register base plugin.'); 28862 } 28863 28864 if (pluginExists(name)) { 28865 delete pluginStorage[name]; 28866 delete Player.prototype[name]; 28867 } 28868 } 28869 /**
28870 * Gets an object containing multiple Video.js plugins. 28871 * 28872 * @param {Array} [names] 28873 * If provided, should be an array of plugin names. Defaults to _all_ 28874 * plugin names. 28875 * 28876 * @return {Object|undefined} 28877 * An object containing plugin(s) associated with their name(s) or 28878 * `undefined` if no matching plugins exist). 28879 */ 28880 ; 28881 28882 Plugin.getPlugins = function getPlugins(names) { 28883 if (names === void 0) { 28884 names = Object.keys(pluginStorage); 28885 } 28886 28887 var result; 28888 names.forEach(function (name) { 28889 var plugin = getPlugin(name); 28890 28891 if (plugin) { 28892 result = result || {}; 28893 result[name] = plugin; 28894 } 28895 }); 28896 return result; 28897 } 28898 /** 28899 * Gets a plugin's version, if available 28900 * 28901 * @param {string} name 28902 * The name of a plugin. 28903 * 28904 * @return {string} 28905 * The plugin's version or an empty string. 28906 */ 28907 ; 28908 28909 Plugin.getPluginVersion = function getPluginVersion(name) { 28910 var plugin = getPlugin(name); 28911 return plugin && plugin.VERSION || ''; 28912 }; 28913 28914 return Plugin; 28915 }(); 28916 /** 28917 * Gets a plugin by name if it exists. 28918 * 28919 * @static 28920 * @method getPlugin 28921 * @memberOf Plugin 28922 * @param {string} name 28923 * The name of a plugin. 28924 * 28925 * @returns {Function|undefined} 28926 * The plugin (or `undefined`). 28927 */ 28928 28929 28930 Plugin.getPlugin = getPlugin; 28931 /** 28932 * The name of the base plugin class as it is registered. 28933 * 28934 * @type {string} 28935 */ 28936 28937 Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; 28938 Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); 28939 /** 28940 * Documented in player.js 28941 * 28942 * @ignore 28943 */ 28944 28945 Player.prototype.usingPlugin = function (name) { 28946 return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; 28947 }; 28948 /** 28949 * Documented in player.js 28950 * 28951 * @ignore 28952 */ 28953 28954 28955 Player.prototype.hasPlugin = function (name) { 28956 return !!pluginExists(name); 28957 }; 28958 /** 28959 * Signals that a plugin is about to be set up on a player. 28960 * 28961 * @event Player#beforepluginsetup 28962 * @type {Plugin~PluginEventHash} 28963 */ 28964 28965 /** 28966 * Signals that a plugin is about to be set up on a player - by name. The name 28967 * is the name of the plugin. 28968 * 28969 * @event Player#beforepluginsetup:$name 28970 * @type {Plugin~PluginEventHash} 28971 */ 28972 28973 /** 28974 * Signals that a plugin has just been set up on a player. 28975 * 28976 * @event Player#pluginsetup 28977 * @type {Plugin~PluginEventHash} 28978 */ 28979 28980 /** 28981 * Signals that a plugin has just been set up on a player - by name. The name 28982 * is the name of the plugin. 28983 * 28984 * @event Player#pluginsetup:$name 28985 * @type {Plugin~PluginEventHash} 28986 */ 28987 28988 /** 28989 * @typedef {Object} Plugin~PluginEventHash 28990 * 28991 * @property {string} instance 28992 * For basic plugins, the return value of the plugin function. For 28993 * advanced plugins, the plugin instance on which the event is fired. 28994 * 28995 * @property {string} name 28996 * The name of the plugin. 28997 * 28998 * @property {string} plugin 28999 * For basic plugins, the plugin function. For advanced plugins, the 29000 * plugin class/constructor. 29001 */ 29002 29003 function _inherits(subClass, superClass) { 29004 if (typeof superClass !== "function" && superClass !== null) { 29005 throw new TypeError("Super expression must either be null or a function"); 29006 } 29007 29008 subClass.prototype = Object.create(superClass && superClass.prototype, { 29009 constructor: { 29010 value: subClass, 29011 writable: true, 29012 configurable: true 29013 } 29014 }); 29015 if (superClass) setPrototypeOf(subClass, superClass); 29016 } 29017 29018 var inherits = _inherits; 29019 29020 /** 29021 * @file extend.js 29022 * @module extend 29023 */ 29024 /** 29025 * Used to subclass an existing class by emulating ES subclassing using the 29026 * `extends` keyword. 29027 * 29028 * @function 29029 * @example 29030 * var MyComponent = videojs.extend(videojs.getComponent('Component'), { 29031 * myCustomMethod: function() { 29032 * // Do things in my method. 29033 * } 29034 * }); 29035 * 29036 * @param {Function} superClass 29037 * The class to inherit from 29038 * 29039 * @param {Object} [subClassMethods={}] 29040 * Methods of the new class 29041 * 29042 * @return {Function} 29043 * The new class with subClassMethods that inherited superClass. 29044 */ 29045 29046 var extend = function extend(superClass, subClassMethods) { 29047 if (subClassMethods === void 0) { 29048 subClassMethods = {}; 29049 } 29050 29051 var subClass = function subClass() { 29052 superClass.apply(this, arguments); 29053 }; 29054 29055 var methods = {}; 29056 29057 if (typeof subClassMethods === 'object') { 29058 if (subClassMethods.constructor !== Object.prototype.constructor) { 29059 subClass = subClassMethods.constructor; 29060 } 29061 29062 methods = subClassMethods; 29063 } else if (typeof subClassMethods === 'function') { 29064 subClass = subClassMethods; 29065 } 29066 29067 inherits(subClass, superClass); // this is needed for backward-compatibility and node compatibility. 29068 29069 29070 if (superClass) { 29071 subClass.super_ = superClass; 29072 } // Extend subObj's prototype with functions and other properties from props 29073 29074 29075 for (var name in methods) { 29076 if (methods.hasOwnProperty(name)) { 29077 subClass.prototype[name] = methods[name]; 29078 } 29079 } 29080 29081 return subClass; 29082 }; 29083 29084 /** 29085 * @file video.js 29086 * @module videojs 29087 */ 29088 /** 29089 * Normalize an `id` value by trimming off a leading `#` 29090 * 29091 * @private 29092 * @param {string} id 29093 * A string, maybe with a leading `#`. 29094 * 29095 * @return {string} 29096 * The string, without any leading `#`. 29097 */ 29098 29099 var normalizeId = function normalizeId(id) { 29100 return id.indexOf('#') === 0 ? id.slice(1) : id; 29101 }; 29102 /** 29103 * The `videojs()` function doubles as the main function for users to create a 29104 * {@link Player} instance as well as the main library namespace. 29105 * 29106 * It can also be used as a getter for a pre-existing {@link Player} instance. 29107 * However, we _strongly_ recommend using `videojs.getPlayer()` for this 29108 * purpose because it avoids any potential for unintended initialization. 29109 * 29110 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 29111 * of our JSDoc template, we cannot properly document this as both a function 29112 * and a namespace, so its function signature is documented here. 29113 * 29114 * #### Arguments 29115 * ##### id 29116 * string|Element, **required** 29117 * 29118 * Video element or video element ID. 29119 * 29120 * ##### options 29121 * Object, optional 29122 * 29123 * Options object for providing settings. 29124 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 29125 * 29126 * ##### ready 29127 * {@link Component~ReadyCallback}, optional 29128 * 29129 * A function to be called when the {@link Player} and {@link Tech} are ready. 29130 * 29131 * #### Return Value 29132 * 29133 * The `videojs()` function returns a {@link Player} instance. 29134 * 29135 * @namespace 29136 * 29137 * @borrows AudioTrack as AudioTrack 29138 * @borrows Component.getComponent as getComponent 29139 * @borrows module:computed-style~computedStyle as computedStyle 29140 * @borrows module:events.on as on 29141 * @borrows module:events.one as one 29142 * @borrows module:events.off as off 29143 * @borrows module:events.trigger as trigger 29144 * @borrows EventTarget as EventTarget 29145 * @borrows module:extend~extend as extend 29146 * @borrows module:fn.bind as bind 29147 * @borrows module:format-time.formatTime as formatTime 29148 * @borrows module:format-time.resetFormatTime as resetFormatTime 29149 * @borrows module:format-time.setFormatTime as setFormatTime 29150 * @borrows module:merge-options.mergeOptions as mergeOptions 29151 * @borrows module:middleware.use as use 29152 * @borrows Player.players as players 29153 * @borrows Plugin.registerPlugin as registerPlugin 29154 * @borrows Plugin.deregisterPlugin as deregisterPlugin 29155 * @borrows Plugin.getPlugins as getPlugins 29156 * @borrows Plugin.getPlugin as getPlugin 29157 * @borrows Plugin.getPluginVersion as getPluginVersion 29158 * @borrows Tech.getTech as getTech 29159 * @borrows Tech.registerTech as registerTech 29160 * @borrows TextTrack as TextTrack 29161 * @borrows module:time-ranges.createTimeRanges as createTimeRange 29162 * @borrows module:time-ranges.createTimeRanges as createTimeRanges 29163 * @borrows module:url.isCrossOrigin as isCrossOrigin 29164 * @borrows module:url.parseUrl as parseUrl 29165 * @borrows VideoTrack as VideoTrack 29166 * 29167 * @param {string|Element}
29167 id 29168 * Video element or video element ID. 29169 * 29170 * @param {Object} [options] 29171 * Options object for providing settings. 29172 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 29173 * 29174 * @param {Component~ReadyCallback} [ready] 29175 * A function to be called when the {@link Player} and {@link Tech} are 29176 * ready. 29177 * 29178 * @return {Player} 29179 * The `videojs()` function returns a {@link Player|Player} instance. 29180 */ 29181 29182 29183 function videojs$1(id, options, ready) { 29184 var player = videojs$1.getPlayer(id); 29185 29186 if (player) { 29187 if (options) { 29188 log.warn("Player \"" + id + "\" is already initialised. Options will not be applied."); 29189 } 29190 29191 if (ready) { 29192 player.ready(ready); 29193 } 29194 29195 return player; 29196 } 29197 29198 var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id; 29199 29200 if (!isEl(el)) { 29201 throw new TypeError('The element or ID supplied is not valid. (videojs)'); 29202 } // document.body.contains(el) will only check if el is contained within that one document. 29203 // This causes problems for elements in iframes. 29204 // Instead, use the element's ownerDocument instead of the global document. 29205 // This will make sure that the element is indeed in the dom of that document. 29206 // Additionally, check that the document in question has a default view. 29207 // If the document is no longer attached to the dom, the defaultView of the document will be null. 29208 29209 29210 if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) { 29211 log.warn('The element supplied is not included in the DOM'); 29212 } 29213 29214 options = options || {}; 29215 videojs$1.hooks('beforesetup').forEach(function (hookFunction) { 29216 var opts = hookFunction(el, mergeOptions(options)); 29217 29218 if (!isObject(opts) || Array.isArray(opts)) { 29219 log.error('please return an object in beforesetup hooks'); 29220 return; 29221 } 29222 29223 options = mergeOptions(options, opts); 29224 }); // We get the current "Player" component here in case an integration has 29225 // replaced it with a custom player. 29226 29227 var PlayerComponent = Component.getComponent('Player'); 29228 player = new PlayerComponent(el, options, ready); 29229 videojs$1.hooks('setup').forEach(function (hookFunction) { 29230 return hookFunction(player); 29231 }); 29232 return player; 29233 } 29234 /** 29235 * An Object that contains lifecycle hooks as keys which point to an array 29236 * of functions that are run when a lifecycle is triggered 29237 * 29238 * @private 29239 */ 29240 29241 29242 videojs$1.hooks_ = {}; 29243 /** 29244 * Get a list of hooks for a specific lifecycle 29245 * 29246 * @param {string} type 29247 * the lifecyle to get hooks from 29248 * 29249 * @param {Function|Function[]} [fn] 29250 * Optionally add a hook (or hooks) to the lifecycle that your are getting. 29251 * 29252 * @return {Array} 29253 * an array of hooks, or an empty array if there are none. 29254 */ 29255 29256 videojs$1.hooks = function (type, fn) { 29257 videojs$1.hooks_[type] = videojs$1.hooks_[type] || []; 29258 29259 if (fn) { 29260 videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn); 29261 } 29262 29263 return videojs$1.hooks_[type]; 29264 }; 29265 /** 29266 * Add a function hook to a specific videojs lifecycle. 29267 * 29268 * @param {string} type 29269 * the lifecycle to hook the function to. 29270 * 29271 * @param {Function|Function[]} 29272 * The function or array of functions to attach. 29273 */ 29274 29275 29276 videojs$1.hook = function (type, fn) { 29277 videojs$1.hooks(type, fn); 29278 }; 29279 /** 29280 * Add a function hook that will only run once to a specific videojs lifecycle. 29281 * 29282 * @param {string} type 29283 * the lifecycle to hook the function to. 29284 * 29285 * @param {Function|Function[]} 29286 * The function or array of functions to attach. 29287 */ 29288 29289 29290 videojs$1.hookOnce = function (type, fn) { 29291 videojs$1.hooks(type, [].concat(fn).map(function (original) { 29292 var wrapper = function wrapper() { 29293 videojs$1.removeHook(type, wrapper); 29294 return original.apply(void 0, arguments); 29295 }; 29296 29297 return wrapper; 29298 })); 29299 }; 29300 /** 29301 * Remove a hook from a specific videojs lifecycle. 29302 * 29303 * @param {string} type 29304 * the lifecycle that the function hooked to 29305 * 29306 * @param {Function} fn 29307 * The hooked function to remove 29308 * 29309 * @return {boolean} 29310 * The function that was removed or undef 29311 */ 29312 29313
vendor: 8,807 bytes, lines 29314-29610
29314 videojs$1.removeHook = function (type, fn) { 29315 var index = videojs$1.hooks(type).indexOf(fn); 29316 29317 if (index <= -1) { 29318 return false; 29319 } 29320 29321 videojs$1.hooks_[type] = videojs$1.hooks_[type].slice(); 29322 videojs$1.hooks_[type].splice(index, 1); 29323 return true; 29324 }; // Add default styles 29325 29326 29327 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) { 29328 var style = $('.vjs-styles-defaults'); 29329 29330 if (!style) { 29331 style = createStyleElement('vjs-styles-defaults'); 29332 var head = $('head'); 29333 29334 if (head) { 29335 head.insertBefore(style, head.firstChild); 29336 } 29337 29338 setTextContent(style, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n "); 29339 } 29340 } // Run Auto-load players 29341 // You have to wait at least once in case this script is loaded after your 29342 // video in the DOM (weird behavior only with minified version) 29343 29344 29345 autoSetupTimeout(1, videojs$1); 29346 /** 29347 * Current Video.js version. Follows [semantic versioning](https://semver.org/). 29348 * 29349 * @type {string} 29350 */ 29351 29352 videojs$1.VERSION = version; 29353 /** 29354 * The global options object. These are the settings that take effect 29355 * if no overrides are specified when the player is created. 29356 * 29357 * @type {Object} 29358 */ 29359 29360 videojs$1.options = Player.prototype.options_; 29361 /** 29362 * Get an object with the currently created players, keyed by player ID 29363 * 29364 * @return {Object} 29365 * The created players 29366 */ 29367 29368 videojs$1.getPlayers = function () { 29369 return Player.players; 29370 }; 29371 /** 29372 * Get a single player based on an ID or DOM element. 29373 * 29374 * This is useful if you want to check if an element or ID has an associated 29375 * Video.js player, but not create one if it doesn't. 29376 * 29377 * @param {string|Element} id 29378 * An HTML element - `<video>`, `<audio>`, or `<video-js>` - 29379 * or a string matching the `id` of such an element. 29380 * 29381 * @return {Player|undefined} 29382 * A player instance or `undefined` if there is no player instance 29383 * matching the argument. 29384 */ 29385 29386 29387 videojs$1.getPlayer = function (id) { 29388 var players = Player.players; 29389 var tag; 29390 29391 if (typeof id === 'string') { 29392 var nId = normalizeId(id); 29393 var player = players[nId]; 29394 29395 if (player) { 29396 return player; 29397 } 29398 29399 tag = $('#' + nId); 29400 } else { 29401 tag = id; 29402 } 29403 29404 if (isEl(tag)) { 29405 var _tag = tag, 29406 _player = _tag.player, 29407 playerId = _tag.playerId; // Element may have a `player` property referring to an already created 29408 // player instance. If so, return that. 29409 29410 if (_player || players[playerId]) { 29411 return _player || players[playerId]; 29412 } 29413 } 29414 }; 29415 /** 29416 * Returns an array of all current players. 29417 * 29418 * @return {Array} 29419 * An array of all players. The array will be in the order that 29420 * `Object.keys` provides, which could potentially vary between 29421 * JavaScript engines. 29422 * 29423 */ 29424 29425 29426 videojs$1.getAllPlayers = function () { 29427 return (// Disposed players leave a key with a `null` value, so we need to make sure 29428 // we filter those out. 29429 Object.keys(Player.players).map(function (k) { 29430 return Player.players[k]; 29431 }).filter(Boolean) 29432 ); 29433 }; 29434 29435 videojs$1.players = Player.players; 29436 videojs$1.getComponent = Component.getComponent; 29437 /** 29438 * Register a component so it can referred to by name. Used when adding to other 29439 * components, either through addChild `component.addChild('myComponent')` or through 29440 * default children options `{ children: ['myComponent'] }`. 29441 * 29442 * > NOTE: You could also just initialize the component before adding. 29443 * `component.addChild(new MyComponent());` 29444 * 29445 * @param {string} name 29446 * The class name of the component 29447 * 29448 * @param {Component} comp 29449 * The component class 29450 * 29451 * @return {Component} 29452 * The newly registered component 29453 */ 29454 29455 videojs$1.registerComponent = function (name, comp) { 29456 if (Tech.isTech(comp)) { 29457 log.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)"); 29458 } 29459 29460 Component.registerComponent.call(Component, name, comp); 29461 }; 29462 29463 videojs$1.getTech = Tech.getTech; 29464 videojs$1.registerTech = Tech.registerTech; 29465 videojs$1.use = use; 29466 /** 29467 * An object that can be returned by a middleware to signify 29468 * that the middleware is being terminated. 29469 * 29470 * @type {object} 29471 * @property {object} middleware.TERMINATOR 29472 */ 29473 29474 Object.defineProperty(videojs$1, 'middleware', { 29475 value: {}, 29476 writeable: false, 29477 enumerable: true 29478 }); 29479 Object.defineProperty(videojs$1.middleware, 'TERMINATOR', { 29480 value: TERMINATOR, 29481 writeable: false, 29482 enumerable: true 29483 }); 29484 /** 29485 * A reference to the {@link module:browser|browser utility module} as an object. 29486 * 29487 * @type {Object} 29488 * @see {@link module:browser|browser} 29489 */ 29490 29491 videojs$1.browser = browser; 29492 /** 29493 * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only 29494 * included for backward-compatibility with 4.x. 29495 * 29496 * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead. 29497 * @type {boolean} 29498 */ 29499 29500 videojs$1.TOUCH_ENABLED = TOUCH_ENABLED; 29501 videojs$1.extend = extend; 29502 videojs$1.mergeOptions = mergeOptions; 29503 videojs$1.bind = bind; 29504 videojs$1.registerPlugin = Plugin.registerPlugin; 29505 videojs$1.deregisterPlugin = Plugin.deregisterPlugin; 29506 /** 29507 * Deprecated method to register a plugin with Video.js 29508 * 29509 * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead 29510 * 29511 * @param {string} name 29512 * The plugin name 29513 * 29514 * @param {Plugin|Function} plugin 29515 * The plugin sub-class or function 29516 */ 29517 29518 videojs$1.plugin = function (name, plugin) { 29519 log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead'); 29520 return Plugin.registerPlugin(name, plugin); 29521 }; 29522 29523 videojs$1.getPlugins = Plugin.getPlugins; 29524 videojs$1.getPlugin = Plugin.getPlugin; 29525 videojs$1.getPluginVersion = Plugin.getPluginVersion; 29526 /** 29527 * Adding languages so that they're available to all players. 29528 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });` 29529 * 29530 * @param {string} code 29531 * The language code or dictionary property 29532 * 29533 * @param {Object} data 29534 * The data values to be translated 29535 * 29536 * @return {Object} 29537 * The resulting language dictionary object 29538 */ 29539 29540 videojs$1.addLanguage = function (code, data) { 29541 var _mergeOptions; 29542 29543 code = ('' + code).toLowerCase(); 29544 videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions)); 29545 return videojs$1.options.languages[code]; 29546 }; 29547 /** 29548 * A reference to the {@link module:log|log utility module} as an object. 29549 * 29550 * @type {Function} 29551 * @see {@link module:log|log} 29552 */ 29553 29554 29555 videojs$1.log = log; 29556 videojs$1.createLogger = createLogger$1; 29557 videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges; 29558 videojs$1.formatTime = formatTime; 29559 videojs$1.setFormatTime = setFormatTime; 29560 videojs$1.resetFormatTime = resetFormatTime; 29561 videojs$1.parseUrl = parseUrl; 29562 videojs$1.isCrossOrigin = isCrossOrigin; 29563 videojs$1.EventTarget = EventTarget; 29564 videojs$1.on = on; 29565 videojs$1.one = one; 29566 videojs$1.off = off; 29567 videojs$1.trigger = trigger; 29568 /** 29569 * A cross-browser XMLHttpRequest wrapper. 29570 * 29571 * @function 29572 * @param {Object} options 29573 * Settings for the request. 29574 * 29575 * @return {XMLHttpRequest|XDomainRequest} 29576 * The request object. 29577 * 29578 * @see https://github.com/Raynos/xhr 29579 */ 29580 29581 videojs$1.xhr = xhr; 29582 videojs$1.TextTrack = TextTrack; 29583 videojs$1.AudioTrack = AudioTrack; 29584 videojs$1.VideoTrack = VideoTrack; 29585 ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) { 29586 videojs$1[k] = function () { 29587 log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead"); 29588 return Dom[k].apply(null, arguments); 29589 }; 29590 }); 29591 videojs$1.computedStyle = computedStyle; 29592 /** 29593 * A reference to the {@link module:dom|DOM utility module} as an object. 29594 * 29595 * @type {Object} 29596 * @see {@link module:dom|dom} 29597 */ 29598 29599 videojs$1.dom = Dom; 29600 /** 29601 * A reference to the {@link module:url|URL utility module} as an object. 29602 * 29603 * @type {Object} 29604 * @see {@link module:url|url} 29605 */ 29606 29607 videojs$1.url = Url; 29608 videojs$1.defineLazyProperty = defineLazyProperty; 29609 29610 var urlToolkit = createCommonjsModule(function (module, exports
vendor: 4,642 bytes, lines 29610-29717
29610) { 29611 // see https://tools.ietf.org/html/rfc1808 29612 29613 /* jshint ignore:start */ 29614 (function (root) { 29615 /* jshint ignore:end */ 29616 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; 29617 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; 29618 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 29619 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; 29620 var URLToolkit = { 29621 // jshint ignore:line 29622 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 29623 // E.g 29624 // With opts.alwaysNormalize = false (default, spec compliant) 29625 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 29626 // With opts.alwaysNormalize = true (not spec compliant) 29627 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 29628 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 29629 opts = opts || {}; // remove any remaining space and CRLF 29630 29631 baseURL = baseURL.trim(); 29632 relativeURL = relativeURL.trim(); 29633 29634 if (!relativeURL) { 29635 // 2a) If the embedded URL is entirely empty, it inherits the 29636 // entire base URL (i.e., is set equal to the base URL) 29637 // and we are done. 29638 if (!opts.alwaysNormalize) { 29639 return baseURL; 29640 } 29641 29642 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 29643 29644 if (!basePartsForNormalise) { 29645 throw new Error('Error trying to parse base URL.'); 29646 } 29647 29648 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 29649 return URLToolkit.buildURLFromParts(basePartsForNormalise); 29650 } 29651 29652 var relativeParts = URLToolkit.parseURL(relativeURL); 29653 29654 if (!relativeParts) { 29655 throw new Error('Error trying to parse relative URL.'); 29656 } 29657 29658 if (relativeParts.scheme) { 29659 // 2b) If the embedded URL starts with a scheme name, it is 29660 // interpreted as an absolute URL and we are done. 29661 if (!opts.alwaysNormalize) { 29662 return relativeURL; 29663 } 29664 29665 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 29666 return URLToolkit.buildURLFromParts(relativeParts); 29667 } 29668 29669 var baseParts = URLToolkit.parseURL(baseURL); 29670 29671 if (!baseParts) { 29672 throw new Error('Error trying to parse base URL.'); 29673 } 29674 29675 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 29676 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 29677 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 29678 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 29679 baseParts.netLoc = pathParts[1]; 29680 baseParts.path = pathParts[2]; 29681 } 29682 29683 if (baseParts.netLoc && !baseParts.path) { 29684 baseParts.path = '/'; 29685 } 29686 29687 var builtParts = { 29688 // 2c) Otherwise, the embedded URL inherits the scheme of 29689 // the base URL. 29690 scheme: baseParts.scheme, 29691 netLoc: relativeParts.netLoc, 29692 path: null, 29693 params: relativeParts.params, 29694 query: relativeParts.query, 29695 fragment: relativeParts.fragment 29696 }; 29697 29698 if (!relativeParts.netLoc) { 29699 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 29700 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 29701 // (if any) of the base URL. 29702 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 29703 // path is not relative and we skip to Step 7. 29704 29705 if (relativeParts.path[0] !== '/') { 29706 if (!relativeParts.path) { 29707 // 5) If the embedded URL path is empty (and not preceded by a 29708 // slash), then the embedded URL inherits the base URL path 29709 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 29710 // step 7; otherwise, it inherits the <params> of the base 29711 // URL (if any) and 29712 29713 if (!relativeParts.params) { 29714 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 29715 // step 7; otherwise, it inherits the <query> of the base 29716 // URL (if any) and we skip to step 7. 29717
29718 if (!relativeParts.query) { 29719 builtParts.query = baseParts.query; 29720 } 29721 } 29722 } else { 29723 // 6) The last segment of the base URL's path (anything 29724 // following the rightmost slash "/", or the entire path if no 29725 // slash is present) is removed and the embedded URL's path is 29726 // appended in its place. 29727 var baseURLPath = baseParts.path; 29728 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 29729 builtParts.path = URLToolkit.normalizePath(newPath); 29730 } 29731 } 29732 } 29733 29734 if (builtParts.path === null) { 29735 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 29736 } 29737 29738 return URLToolkit.buildURLFromParts(builtParts); 29739 }, 29740 parseURL: function parseURL(url) { 29741 var parts = URL_REGEX.exec(url); 29742 29743 if (!parts) { 29744 return null; 29745 } 29746 29747 return { 29748 scheme: parts[1] || '', 29749 netLoc: parts[2] || '', 29750 path: parts[3] || '', 29751 params: parts[4] || '', 29752 query: parts[5] || '', 29753 fragment: parts[6] || '' 29754 }; 29755 }, 29756 normalizePath: function normalizePath(path) { 29757 // The following operations are 29758 // then applied, in order, to the new path: 29759 // 6a) All occurrences of "./", where "." is a complete path 29760 // segment, are removed. 29761 // 6b) If the path ends with "." as a complete path segment, 29762 // that "." is removed. 29763 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 29764 // complete path segment not equal to "..", are removed. 29765 // Removal of these path segments is performed iteratively, 29766 // removing the leftmost matching pattern on each iteration, 29767 // until no matching pattern remains. 29768 // 6d) If the path ends with "<segment>/..", where <segment> is a 29769 // complete path segment not equal to "..", that 29770 // "<segment>/.." is removed. 29771 29772 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line 29773 29774 29775 return path.split('').reverse().join(''); 29776 }, 29777 buildURLFromParts: function buildURLFromParts(parts) { 29778 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 29779 } 29780 }; 29781 /* jshint ignore:start */ 29782 29783 module.exports = URLToolkit; 29784 })(); 29785 /* jshint ignore:end */ 29786 29787 }); 29788 29789 function _interopDefaultLegacy(e) { 29790 return e && typeof e === 'object' && 'default' in e ? e : { 29791 'default': e 29792 }; 29793 } 29794 29795 var URLToolkit__default = /*#__PURE__*/_interopDefaultLegacy(urlToolkit); 29796 29797 var window__default = /*#__PURE__*/_interopDefaultLegacy(window$1); 29798 29799 var resolveUrl = function resolveUrl(baseUrl, relativeUrl) { 29800 // return early if we don't need to resolve 29801 if (/^[a-z]+:/i.test(relativeUrl)) { 29802 return relativeUrl; 29803 } // if the base URL is relative then combine with the current location 29804 29805 29806 if (!/\/\//i.test(baseUrl)) { 29807 baseUrl = URLToolkit__default['default'].buildAbsoluteURL(window__default['default'].location && window__default['default'].location.href || '', baseUrl); 29808 } 29809 29810 return URLToolkit__default['default'].buildAbsoluteURL(baseUrl, relativeUrl); 29811 }; 29812 29813 var resolveUrl_1 = resolveUrl; 29814 29815 /*! @name @videojs/vhs-utils @version 2.2.1 @license MIT */ 29816 /** 29817 * @file stream.js 29818 */ 29819 29820 /** 29821 * A lightweight readable stream implemention that handles event dispatching. 29822 * 29823 * @class Stream 29824 */ 29825 29826 var Stream = /*#__PURE__*/function () { 29827 function Stream() { 29828 this.listeners = {}; 29829 } 29830 /** 29831 * Add a listener for a specified event type. 29832 * 29833 * @param {string} type the event name 29834 * @param {Function} listener the callback to be invoked when an event of 29835 * the specified type occurs 29836 */ 29837 29838 29839 var _proto = Stream.prototype; 29840 29841 _proto.on = function on(type, listener) { 29842 if (!this.listeners[type]) { 29843 this.listeners[type] = []; 29844 } 29845 29846 this.listeners[type].push(listener); 29847 } 29848 /** 29849 * Remove a listener for a specified event type. 29850 * 29851 * @param {string} type the event name 29852 * @param {Function} listener a function previously registered for this 29853 * type of event through `on` 29854 * @return {boolean} if we could turn it off or not 29855 */ 29856 ; 29857 29858 _proto.off = function off(type, listener) { 29859 if (!this.listeners[type]) { 29860 return false;
vendor: 42,620 bytes, lines 29861-31243
29861 } 29862 29863 var index = this.listeners[type].indexOf(listener); // TODO: which is better? 29864 // In Video.js we slice listener functions 29865 // on trigger so that it does not mess up the order 29866 // while we loop through. 29867 // 29868 // Here we slice on off so that the loop in trigger 29869 // can continue using it's old reference to loop without 29870 // messing up the order. 29871 29872 this.listeners[type] = this.listeners[type].slice(0); 29873 this.listeners[type].splice(index, 1); 29874 return index > -1; 29875 } 29876 /** 29877 * Trigger an event of the specified type on this stream. Any additional 29878 * arguments to this function are passed as parameters to event listeners. 29879 * 29880 * @param {string} type the event name 29881 */ 29882 ; 29883 29884 _proto.trigger = function trigger(type) { 29885 var callbacks = this.listeners[type]; 29886 29887 if (!callbacks) { 29888 return; 29889 } // Slicing the arguments on every invocation of this method 29890 // can add a significant amount of overhead. Avoid the 29891 // intermediate object creation for the common case of a 29892 // single callback argument 29893 29894 29895 if (arguments.length === 2) { 29896 var length = callbacks.length; 29897 29898 for (var i = 0; i < length; ++i) { 29899 callbacks[i].call(this, arguments[1]); 29900 } 29901 } else { 29902 var args = Array.prototype.slice.call(arguments, 1); 29903 var _length = callbacks.length; 29904 29905 for (var _i = 0; _i < _length; ++_i) { 29906 callbacks[_i].apply(this, args); 29907 } 29908 } 29909 } 29910 /** 29911 * Destroys the stream and cleans up. 29912 */ 29913 ; 29914 29915 _proto.dispose = function dispose() { 29916 this.listeners = {}; 29917 } 29918 /** 29919 * Forwards all `data` events on this stream to the destination stream. The 29920 * destination stream should provide a method `push` to receive the data 29921 * events as they arrive. 29922 * 29923 * @param {Stream} destination the stream that will receive all `data` events 29924 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 29925 */ 29926 ; 29927 29928 _proto.pipe = function pipe(destination) { 29929 this.on('data', function (data) { 29930 destination.push(data); 29931 }); 29932 }; 29933 29934 return Stream; 29935 }(); 29936 29937 var stream = Stream; 29938 29939 function _interopDefaultLegacy$1(e) { 29940 return e && typeof e === 'object' && 'default' in e ? e : { 29941 'default': e 29942 }; 29943 } 29944 29945 var window__default$1 = /*#__PURE__*/_interopDefaultLegacy$1(window$1); 29946 29947 var atob = function atob(s) { 29948 return window__default$1['default'].atob ? window__default$1['default'].atob(s) : Buffer.from(s, 'base64').toString('binary'); 29949 }; 29950 29951 function decodeB64ToUint8Array(b64Text) { 29952 var decodedString = atob(b64Text); 29953 var array = new Uint8Array(decodedString.length); 29954 29955 for (var i = 0; i < decodedString.length; i++) { 29956 array[i] = decodedString.charCodeAt(i); 29957 } 29958 29959 return array; 29960 } 29961 29962 var decodeB64ToUint8Array_1 = decodeB64ToUint8Array; 29963 29964 /*! @name m3u8-parser @version 4.5.0 @license Apache-2.0 */ 29965 /** 29966 * A stream that buffers string input and generates a `data` event for each 29967 * line. 29968 * 29969 * @class LineStream 29970 * @extends Stream 29971 */ 29972 29973 var LineStream = /*#__PURE__*/function (_Stream) { 29974 inheritsLoose(LineStream, _Stream); 29975 29976 function LineStream() { 29977 var _this; 29978 29979 _this = _Stream.call(this) || this; 29980 _this.buffer = ''; 29981 return _this; 29982 } 29983 /** 29984 * Add new data to be parsed. 29985 * 29986 * @param {string} data the text to process 29987 */ 29988 29989 29990 var _proto = LineStream.prototype; 29991 29992 _proto.push = function push(data) { 29993 var nextNewline; 29994 this.buffer += data; 29995 nextNewline = this.buffer.indexOf('\n'); 29996 29997 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 29998 this.trigger('data', this.buffer.substring(0, nextNewline)); 29999 this.buffer = this.buffer.substring(nextNewline + 1); 30000 } 30001 }; 30002 30003 return LineStream; 30004 }(stream); 30005 /** 30006 * "forgiving" attribute list psuedo-grammar: 30007 * attributes -> keyvalue (',' keyvalue)* 30008 * keyvalue -> key '=' value 30009 * key -> [^=]* 30010 * value -> '"' [^"]* '"' | [^,]* 30011 */ 30012 30013 30014 var attributeSeparator = function attributeSeparator() { 30015 var key = '[^=]*'; 30016 var value = '"[^"]*"|[^,]*'; 30017 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 30018 return new RegExp('(?:^|,)(' + keyvalue + ')'); 30019 }; 30020 /** 30021 * Parse attributes from a line given the separator 30022 * 30023 * @param {string} attributes the attribute line to parse 30024 */ 30025 30026 30027 var parseAttributes = function parseAttributes(attributes) { 30028 // split the string using attributes as the separator 30029 var attrs = attributes.split(attributeSeparator()); 30030 var result = {}; 30031 var i = attrs.length; 30032 var attr; 30033 30034 while (i--) { 30035 // filter out unmatched portions of the string 30036 if (attrs[i] === '') { 30037 continue; 30038 } // split the key and value 30039 30040 30041 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value 30042 30043 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 30044 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 30045 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 30046 result[attr[0]] = attr[1]; 30047 } 30048 30049 return result; 30050 }; 30051 /** 30052 * A line-level M3U8 parser event stream. It expects to receive input one 30053 * line at a time and performs a context-free parse of its contents. A stream 30054 * interpretation of a manifest can be useful if the manifest is expected to 30055 * be too large to fit comfortably into memory or the entirety of the input 30056 * is not immediately available. Otherwise, it's probably much easier to work 30057 * with a regular `Parser` object. 30058 * 30059 * Produces `data` events with an object that captures the parser's 30060 * interpretation of the input. That object has a property `tag` that is one 30061 * of `uri`, `comment`, or `tag`. URIs only have a single additional 30062 * property, `line`, which captures the entirety of the input without 30063 * interpretation. Comments similarly have a single additional property 30064 * `text` which is the input without the leading `#`. 30065 * 30066 * Tags always have a property `tagType` which is the lower-cased version of 30067 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 30068 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 30069 * tags are given the tag type `unknown` and a single additional property 30070 * `data` with the remainder of the input. 30071 * 30072 * @class ParseStream 30073 * @extends Stream 30074 */ 30075 30076 30077 var ParseStream = /*#__PURE__*/function (_Stream) { 30078 inheritsLoose(ParseStream, _Stream); 30079 30080 function ParseStream() { 30081 var _this; 30082 30083 _this = _Stream.call(this) || this; 30084 _this.customParsers = []; 30085 _this.tagMappers = []; 30086 return _this; 30087 } 30088 /** 30089 * Parses an additional line of input. 30090 * 30091 * @param {string} line a single line of an M3U8 file to parse 30092 */ 30093 30094 30095 var _proto = ParseStream.prototype; 30096 30097 _proto.push = function push(line) { 30098 var _this2 = this; 30099 30100 var match; 30101 var event; // strip whitespace 30102 30103 line = line.trim(); 30104 30105 if (line.length === 0) { 30106 // ignore empty lines 30107 return; 30108 } // URIs 30109 30110 30111 if (line[0] !== '#') { 30112 this.trigger('data', { 30113 type: 'uri', 30114 uri: line 30115 }); 30116 return; 30117 } // map tags 30118 30119 30120 var newLines = this.tagMappers.reduce(function (acc, mapper) { 30121 var mappedLine = mapper(line); // skip if unchanged 30122 30123 if (mappedLine === line) { 30124 return acc; 30125 } 30126 30127 return acc.concat([mappedLine]); 30128 }, [line]); 30129 newLines.forEach(function (newLine) { 30130 for (var i = 0; i < _this2.customParsers.length; i++) { 30131 if (_this2.customParsers[i].call(_this2, newLine)) { 30132 return; 30133 } 30134 } // Comments 30135 30136 30137 if (newLine.indexOf('#EXT') !== 0) { 30138 _this2.trigger('data', { 30139 type: 'comment', 30140 text: newLine.slice(1) 30141 }); 30142 30143 return; 30144 } // strip off any carriage returns here so the regex matching 30145 // doesn't have to account for them. 30146 30147 30148 newLine = newLine.replace('\r', ''); // Tags 30149 30150 match = /^#EXTM3U/.exec(newLine); 30151 30152 if (match) { 30153 _this2.trigger('data', { 30154 type: 'tag', 30155 tagType: 'm3u' 30156 }); 30157 30158 return; 30159 } 30160 30161 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine); 30162 30163 if (match) { 30164 event = { 30165 type: 'tag', 30166 tagType: 'inf' 30167 }; 30168 30169 if (match[1]) { 30170 event.duration = parseFloat(match[1]); 30171 } 30172 30173 if (match[2]) { 30174 event.title = match[2]; 30175 } 30176 30177 _this2.trigger('data', event); 30178 30179 return; 30180 } 30181 30182 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine); 30183 30184 if (match) { 30185 event = { 30186 type: 'tag', 30187 tagType: 'targetduration' 30188 }; 30189 30190 if (match[1]) { 30191 event.duration = parseInt(match[1], 10); 30192 } 30193 30194 _this2.trigger('data', event); 30195 30196 return; 30197 } 30198 30199 match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine); 30200 30201 if (match) { 30202 event = { 30203 type: 'tag', 30204 tagType: 'totalduration' 30205 }; 30206 30207 if (match[1]) { 30208 event.duration = parseInt(match[1], 10); 30209 } 30210 30211 _this2.trigger('data', event); 30212 30213 return; 30214 } 30215 30216 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine); 30217 30218 if (match) { 30219 event = { 30220 type: 'tag', 30221 tagType: 'version' 30222 }; 30223 30224 if (match[1]) { 30225 event.version = parseInt(match[1], 10); 30226 } 30227 30228 _this2.trigger('data', event); 30229 30230 return; 30231 } 30232 30233 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 30234 30235 if (match) { 30236 event = { 30237 type: 'tag', 30238 tagType: 'media-sequence' 30239 }; 30240 30241 if (match[1]) { 30242 event.number = parseInt(match[1], 10); 30243 } 30244 30245 _this2.trigger('data', event); 30246 30247 return; 30248 } 30249 30250 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 30251 30252 if (match) { 30253 event = { 30254 type: 'tag', 30255 tagType: 'discontinuity-sequence' 30256 }; 30257 30258 if (match[1]) { 30259 event.number = parseInt(match[1], 10); 30260 } 30261 30262 _this2.trigger('data', event); 30263 30264 return; 30265 } 30266 30267 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine); 30268 30269 if (match) { 30270 event = { 30271 type: 'tag', 30272 tagType: 'playlist-type' 30273 }; 30274 30275 if (match[1]) { 30276 event.playlistType = match[1]; 30277 } 30278 30279 _this2.trigger('data', event); 30280 30281 return; 30282 } 30283 30284 match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine); 30285 30286 if (match) { 30287 event = { 30288 type: 'tag', 30289 tagType: 'byterange' 30290 }; 30291 30292 if (match[1]) { 30293 event.length = parseInt(match[1], 10); 30294 } 30295 30296 if (match[2]) { 30297 event.offset = parseInt(match[2], 10); 30298 } 30299 30300 _this2.trigger('data', event); 30301 30302 return; 30303 } 30304 30305 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine); 30306 30307 if (match) { 30308 event = { 30309 type: 'tag', 30310 tagType: 'allow-cache' 30311 }; 30312 30313 if (match[1]) { 30314 event.allowed = !/NO/.test(match[1]); 30315 } 30316 30317 _this2.trigger('data', event); 30318 30319 return; 30320 } 30321 30322 match = /^#EXT-X-MAP:?(.*)$/.exec(newLine); 30323 30324 if (match) { 30325 event = { 30326 type: 'tag', 30327 tagType: 'map' 30328 }; 30329 30330 if (match[1]) { 30331 var attributes = parseAttributes(match[1]); 30332 30333 if (attributes.URI) { 30334 event.uri = attributes.URI; 30335 } 30336 30337 if (attributes.BYTERANGE) { 30338 var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'), 30339 length = _attributes$BYTERANGE[0], 30340 offset = _attributes$BYTERANGE[1]; 30341 30342 event.byterange = {}; 30343 30344 if (length) { 30345 event.byterange.length = parseInt(length, 10); 30346 } 30347 30348 if (offset) { 30349 event.byterange.offset = parseInt(offset, 10); 30350 } 30351 } 30352 } 30353 30354 _this2.trigger('data', event); 30355 30356 return; 30357 } 30358 30359 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine); 30360 30361 if (match) { 30362 event = { 30363 type: 'tag', 30364 tagType: 'stream-inf' 30365 }; 30366 30367 if (match[1]) { 30368 event.attributes = parseAttributes(match[1]); 30369 30370 if (event.attributes.RESOLUTION) { 30371 var split = event.attributes.RESOLUTION.split('x'); 30372 var resolution = {}; 30373 30374 if (split[0]) { 30375 resolution.width = parseInt(split[0], 10); 30376 } 30377 30378 if (split[1]) { 30379 resolution.height = parseInt(split[1], 10); 30380 } 30381 30382 event.attributes.RESOLUTION = resolution; 30383 } 30384 30385 if (event.attributes.BANDWIDTH) { 30386 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 30387 } 30388 30389 if (event.attributes['PROGRAM-ID']) { 30390 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 30391 } 30392 } 30393 30394 _this2.trigger('data', event); 30395 30396 return; 30397 } 30398 30399 match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine); 30400 30401 if (match) { 30402 event = { 30403 type: 'tag', 30404 tagType: 'media' 30405 }; 30406 30407 if (match[1]) { 30408 event.attributes = parseAttributes(match[1]); 30409 } 30410 30411 _this2.trigger('data', event); 30412 30413 return; 30414 } 30415 30416 match = /^#EXT-X-ENDLIST/.exec(newLine); 30417 30418 if (match) { 30419 _this2.trigger('data', { 30420 type: 'tag', 30421 tagType: 'endlist' 30422 }); 30423 30424 return; 30425 } 30426 30427 match = /^#EXT-X-DISCONTINUITY/.exec(newLine); 30428 30429 if (match) { 30430 _this2.trigger('data', { 30431 type: 'tag', 30432 tagType: 'discontinuity' 30433 }); 30434 30435 return; 30436 } 30437 30438 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine); 30439 30440 if (match) { 30441 event = { 30442 type: 'tag', 30443 tagType: 'program-date-time' 30444 }; 30445 30446 if (match[1]) { 30447 event.dateTimeString = match[1]; 30448 event.dateTimeObject = new Date(match[1]); 30449 } 30450 30451 _this2.trigger('data', event); 30452 30453 return; 30454 } 30455 30456 match = /^#EXT-X-KEY:?(.*)$/.exec(newLine); 30457 30458 if (match) { 30459 event = { 30460 type: 'tag', 30461 tagType: 'key' 30462 }; 30463 30464 if (match[1]) { 30465 event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array 30466 30467 if (event.attributes.IV) { 30468 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 30469 event.attributes.IV = event.attributes.IV.substring(2); 30470 } 30471 30472 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 30473 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 30474 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 30475 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 30476 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 30477 event.attributes.IV = new Uint32Array(event.attributes.IV); 30478 } 30479 } 30480 30481 _this2.trigger('data', event); 30482 30483 return; 30484 } 30485 30486 match = /^#EXT-X-START:?(.*)$/.exec(newLine); 30487 30488 if (match) { 30489 event = { 30490 type: 'tag', 30491 tagType: 'start' 30492 }; 30493 30494 if (match[1]) { 30495 event.attributes = parseAttributes(match[1]); 30496 event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']); 30497 event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE); 30498 } 30499 30500 _this2.trigger('data', event); 30501 30502 return; 30503 } 30504 30505 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine); 30506 30507 if (match) { 30508 event = { 30509 type: 'tag', 30510 tagType: 'cue-out-cont' 30511 }; 30512 30513 if (match[1]) { 30514 event.data = match[1]; 30515 } else { 30516 event.data = ''; 30517 } 30518 30519 _this2.trigger('data', event); 30520 30521 return; 30522 } 30523 30524 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine); 30525 30526 if (match) { 30527 event = { 30528 type: 'tag', 30529 tagType: 'cue-out' 30530 }; 30531 30532 if (match[1]) { 30533 event.data = match[1]; 30534 } else { 30535 event.data = ''; 30536 } 30537 30538 _this2.trigger('data', event); 30539 30540 return; 30541 } 30542 30543 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine); 30544 30545 if (match) { 30546 event = { 30547 type: 'tag', 30548 tagType: 'cue-in' 30549 }; 30550 30551 if (match[1]) { 30552 event.data = match[1]; 30553 } else { 30554 event.data = ''; 30555 } 30556 30557 _this2.trigger('data', event); 30558 30559 return; 30560 } // unknown tag type 30561 30562 30563 _this2.trigger('data', { 30564 type: 'tag', 30565 data: newLine.slice(4) 30566 }); 30567 }); 30568 } 30569 /** 30570 * Add a parser for custom headers 30571 * 30572 * @param {Object} options a map of options for the added parser 30573 * @param {RegExp} options.expression a regular expression to match the custom header 30574 * @param {string} options.customType the custom type to register to the output 30575 * @param {Function} [options.dataParser] function to parse the line into an object 30576 * @param {boolean} [options.segment] should tag data be attached to the segment object 30577 */ 30578 ; 30579 30580 _proto.addParser = function addParser(_ref) { 30581 var _this3 = this; 30582 30583 var expression = _ref.expression, 30584 customType = _ref.customType, 30585 dataParser = _ref.dataParser, 30586 segment = _ref.segment; 30587 30588 if (typeof dataParser !== 'function') { 30589 dataParser = function dataParser(line) { 30590 return line; 30591 }; 30592 } 30593 30594 this.customParsers.push(function (line) { 30595 var match = expression.exec(line); 30596 30597 if (match) { 30598 _this3.trigger('data', { 30599 type: 'custom', 30600 data: dataParser(line), 30601 customType: customType, 30602 segment: segment 30603 }); 30604 30605 return true; 30606 } 30607 }); 30608 } 30609 /** 30610 * Add a custom header mapper 30611 * 30612 * @param {Object} options 30613 * @param {RegExp} options.expression a regular expression to match the custom header 30614 * @param {Function} options.map function to translate tag into a different tag 30615 */ 30616 ; 30617 30618 _proto.addTagMapper = function addTagMapper(_ref2) { 30619 var expression = _ref2.expression, 30620 map = _ref2.map; 30621 30622 var mapFn = function mapFn(line) { 30623 if (expression.test(line)) { 30624 return map(line); 30625 } 30626 30627 return line; 30628 }; 30629 30630 this.tagMappers.push(mapFn); 30631 }; 30632 30633 return ParseStream; 30634 }(stream); 30635 /** 30636 * A parser for M3U8 files. The current interpretation of the input is 30637 * exposed as a property `manifest` on parser objects. It's just two lines to 30638 * create and parse a manifest once you have the contents available as a string: 30639 * 30640 * ```js 30641 * var parser = new m3u8.Parser(); 30642 * parser.push(xhr.responseText); 30643 * ``` 30644 * 30645 * New input can later be applied to update the manifest object by calling 30646 * `push` again. 30647 * 30648 * The parser attempts to create a usable manifest object even if the 30649 * underlying input is somewhat nonsensical. It emits `info` and `warning` 30650 * events during the parse if it encounters input that seems invalid or 30651 * requires some property of the manifest object to be defaulted. 30652 * 30653 * @class Parser 30654 * @extends Stream 30655 */ 30656 30657 30658 var Parser = /*#__PURE__*/function (_Stream) { 30659 inheritsLoose(Parser, _Stream); 30660 30661 function Parser() { 30662 var _this; 30663 30664 _this = _Stream.call(this) || this; 30665 _this.lineStream = new LineStream(); 30666 _this.parseStream = new ParseStream(); 30667 30668 _this.lineStream.pipe(_this.parseStream); 30669 /* eslint-disable consistent-this */ 30670 30671 30672 var self = assertThisInitialized(_this); 30673 /* eslint-enable consistent-this */ 30674 30675 30676 var uris = []; 30677 var currentUri = {}; // if specified, the active EXT-X-MAP definition 30678 30679 var currentMap; // if specified, the active decryption key 30680 30681 var _key; 30682 30683 var noop = function noop() {}; 30684 30685 var defaultMediaGroups = { 30686 'AUDIO': {}, 30687 'VIDEO': {}, 30688 'CLOSED-CAPTIONS': {}, 30689 'SUBTITLES': {} 30690 }; // This is the Widevine UUID from DASH IF IOP. The same exact string is 30691 // used in MPDs with Widevine encrypted streams. 30692 30693 var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities 30694 30695 var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data 30696 30697 _this.manifest = { 30698 allowCache: true, 30699 discontinuityStarts: [], 30700 segments: [] 30701 }; // keep track of the last seen segment's byte range end, as segments are not required 30702 // to provide the offset, in which case it defaults to the next byte after the 30703 // previous segment 30704 30705 var lastByterangeEnd = 0; // update the manifest with the m3u8 entry from the parse stream 30706 30707 _this.parseStream.on('data', function (entry) { 30708 var mediaGroup; 30709 var rendition; 30710 ({ 30711 tag: function tag() { 30712 // switch based on the tag type 30713 (({ 30714 'allow-cache': function allowCache() { 30715 this.manifest.allowCache = entry.allowed; 30716 30717 if (!('allowed' in entry)) { 30718 this.trigger('info', { 30719 message: 'defaulting allowCache to YES' 30720 }); 30721 this.manifest.allowCache = true; 30722 } 30723 }, 30724 byterange: function byterange() { 30725 var byterange = {}; 30726 30727 if ('length' in entry) { 30728 currentUri.byterange = byterange; 30729 byterange.length = entry.length; 30730 30731 if (!('offset' in entry)) { 30732 /* 30733 * From the latest spec (as of this writing): 30734 * https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2 30735 * 30736 * Same text since EXT-X-BYTERANGE's introduction in draft 7: 30737 * https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1) 30738 * 30739 * "If o [offset] is not present, the sub-range begins at the next byte 30740 * following the sub-range of the previous media segment." 30741 */ 30742 entry.offset = lastByterangeEnd; 30743 } 30744 } 30745 30746 if ('offset' in entry) { 30747 currentUri.byterange = byterange; 30748 byterange.offset = entry.offset; 30749 } 30750 30751 lastByterangeEnd = byterange.offset + byterange.length; 30752 }, 30753 endlist: function endlist() { 30754 this.manifest.endList = true; 30755 }, 30756 inf: function inf() { 30757 if (!('mediaSequence' in this.manifest)) { 30758 this.manifest.mediaSequence = 0; 30759 this.trigger('info', { 30760 message: 'defaulting media sequence to zero' 30761 }); 30762 } 30763 30764 if (!('discontinuitySequence' in this.manifest)) { 30765 this.manifest.discontinuitySequence = 0; 30766 this.trigger('info', { 30767 message: 'defaulting discontinuity sequence to zero' 30768 }); 30769 } 30770 30771 if (entry.duration > 0) { 30772 currentUri.duration = entry.duration; 30773 } 30774 30775 if (entry.duration === 0) { 30776 currentUri.duration = 0.01; 30777 this.trigger('info', { 30778 message: 'updating zero segment duration to a small value' 30779 }); 30780 } 30781 30782 this.manifest.segments = uris; 30783 }, 30784 key: function key() { 30785 if (!entry.attributes) { 30786 this.trigger('warn', { 30787 message: 'ignoring key declaration without attribute list' 30788 }); 30789 return; 30790 } // clear the active encryption key 30791 30792 30793 if (entry.attributes.METHOD === 'NONE') { 30794 _key = null; 30795 return; 30796 } 30797 30798 if (!entry.attributes.URI) { 30799 this.trigger('warn', { 30800 message: 'ignoring key declaration without URI' 30801 }); 30802 return; 30803 } // check if the content is encrypted for Widevine 30804 // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf 30805 30806 30807 if (entry.attributes.KEYFORMAT === widevineUuid) { 30808 var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC']; 30809 30810 if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) { 30811 this.trigger('warn', { 30812 message: 'invalid key method provided for Widevine' 30813 }); 30814 return; 30815 } 30816 30817 if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') { 30818 this.trigger('warn', { 30819 message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead' 30820 }); 30821 } 30822 30823 if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') { 30824 this.trigger('warn', { 30825 message: 'invalid key URI provided for Widevine' 30826 }); 30827 return; 30828 } 30829 30830 if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) { 30831 this.trigger('warn', { 30832 message: 'invalid key ID provided for Widevine' 30833 }); 30834 return; 30835 } // if Widevine key attributes are valid, store them as `contentProtection` 30836 // on the manifest to emulate Widevine tag structure in a DASH mpd 30837 30838 30839 this.manifest.contentProtection = { 30840 'com.widevine.alpha': { 30841 attributes: { 30842 schemeIdUri: entry.attributes.KEYFORMAT, 30843 // remove '0x' from the key id string 30844 keyId: entry.attributes.KEYID.substring(2) 30845 }, 30846 // decode the base64-encoded PSSH box 30847 pssh: decodeB64ToUint8Array_1(entry.attributes.URI.split(',')[1]) 30848 } 30849 }; 30850 return; 30851 } 30852 30853 if (!entry.attributes.METHOD) { 30854 this.trigger('warn', { 30855 message: 'defaulting key method to AES-128' 30856 }); 30857 } // setup an encryption key for upcoming segments 30858 30859 30860 _key = { 30861 method: entry.attributes.METHOD || 'AES-128', 30862 uri: entry.attributes.URI 30863 }; 30864 30865 if (typeof entry.attributes.IV !== 'undefined') { 30866 _key.iv = entry.attributes.IV; 30867 } 30868 }, 30869 'media-sequence': function mediaSequence() { 30870 if (!isFinite(entry.number)) { 30871 this.trigger('warn', { 30872 message: 'ignoring invalid media sequence: ' + entry.number 30873 }); 30874 return; 30875 } 30876 30877 this.manifest.mediaSequence = entry.number; 30878 }, 30879 'discontinuity-sequence': function discontinuitySequence() { 30880 if (!isFinite(entry.number)) { 30881 this.trigger('warn', { 30882 message: 'ignoring invalid discontinuity sequence: ' + entry.number 30883 }); 30884 return; 30885 } 30886 30887 this.manifest.discontinuitySequence = entry.number; 30888 currentTimeline = entry.number; 30889 }, 30890 'playlist-type': function playlistType() { 30891 if (!/VOD|EVENT/.test(entry.playlistType)) { 30892 this.trigger('warn', { 30893 message: 'ignoring unknown playlist type: ' + entry.playlist 30894 }); 30895 return; 30896 } 30897 30898 this.manifest.playlistType = entry.playlistType; 30899 }, 30900 map: function map() { 30901 currentMap = {}; 30902 30903 if (entry.uri) { 30904 currentMap.uri = entry.uri; 30905 } 30906 30907 if (entry.byterange) { 30908 currentMap.byterange = entry.byterange; 30909 } 30910 }, 30911 'stream-inf': function streamInf() { 30912 this.manifest.playlists = uris; 30913 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 30914 30915 if (!entry.attributes) { 30916 this.trigger('warn', { 30917 message: 'ignoring empty stream-inf attributes' 30918 }); 30919 return; 30920 } 30921 30922 if (!currentUri.attributes) { 30923 currentUri.attributes = {}; 30924 } 30925 30926 _extends_1(currentUri.attributes, entry.attributes); 30927 }, 30928 media: function media() { 30929 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 30930 30931 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 30932 this.trigger('warn', { 30933 message: 'ignoring incomplete or missing media group' 30934 }); 30935 return; 30936 } // find the media group, creating defaults as necessary 30937 30938 30939 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 30940 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 30941 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata 30942 30943 rendition = { 30944 "default": /yes/i.test(entry.attributes.DEFAULT) 30945 }; 30946 30947 if (rendition["default"]) { 30948 rendition.autoselect = true; 30949 } else { 30950 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 30951 } 30952 30953 if (entry.attributes.LANGUAGE) { 30954 rendition.language = entry.attributes.LANGUAGE; 30955 } 30956 30957 if (entry.attributes.URI) { 30958 rendition.uri = entry.attributes.URI; 30959 } 30960 30961 if (entry.attributes['INSTREAM-ID']) { 30962 rendition.instreamId = entry.attributes['INSTREAM-ID']; 30963 } 30964 30965 if (entry.attributes.CHARACTERISTICS) { 30966 rendition.characteristics = entry.attributes.CHARACTERISTICS; 30967 } 30968 30969 if (entry.attributes.FORCED) { 30970 rendition.forced = /yes/i.test(entry.attributes.FORCED); 30971 } // insert the new rendition 30972 30973 30974 mediaGroup[entry.attributes.NAME] = rendition; 30975 }, 30976 discontinuity: function discontinuity() { 30977 currentTimeline += 1; 30978 currentUri.discontinuity = true; 30979 this.manifest.discontinuityStarts.push(uris.length); 30980 }, 30981 'program-date-time': function programDateTime() { 30982 if (typeof this.manifest.dateTimeString === 'undefined') { 30983 // PROGRAM-DATE-TIME is a media-segment tag, but for backwards 30984 // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag 30985 // to the manifest object 30986 // TODO: Consider removing this in future major version 30987 this.manifest.dateTimeString = entry.dateTimeString; 30988 this.manifest.dateTimeObject = entry.dateTimeObject; 30989 } 30990 30991 currentUri.dateTimeString = entry.dateTimeString; 30992 currentUri.dateTimeObject = entry.dateTimeObject; 30993 }, 30994 targetduration: function targetduration() { 30995 if (!isFinite(entry.duration) || entry.duration < 0) { 30996 this.trigger('warn', { 30997 message: 'ignoring invalid target duration: ' + entry.duration 30998 }); 30999 return; 31000 } 31001 31002 this.manifest.targetDuration = entry.duration; 31003 }, 31004 totalduration: function totalduration() { 31005 if (!isFinite(entry.duration) || entry.duration < 0) { 31006 this.trigger('warn', { 31007 message: 'ignoring invalid total duration: ' + entry.duration 31008 }); 31009 return; 31010 } 31011 31012 this.manifest.totalDuration = entry.duration; 31013 }, 31014 start: function start() { 31015 if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) { 31016 this.trigger('warn', { 31017 message: 'ignoring start declaration without appropriate attribute list' 31018 }); 31019 return; 31020 } 31021 31022 this.manifest.start = { 31023 timeOffset: entry.attributes['TIME-OFFSET'], 31024 precise: entry.attributes.PRECISE 31025 }; 31026 }, 31027 'cue-out': function cueOut() { 31028 currentUri.cueOut = entry.data; 31029 }, 31030 'cue-out-cont': function cueOutCont() { 31031 currentUri.cueOutCont = entry.data; 31032 }, 31033 'cue-in': function cueIn() { 31034 currentUri.cueIn = entry.data; 31035 } 31036 })[entry.tagType] || noop).call(self); 31037 }, 31038 uri: function uri() { 31039 currentUri.uri = entry.uri; 31040 uris.push(currentUri); // if no explicit duration was declared, use the target duration 31041 31042 if (this.manifest.targetDuration && !('duration' in currentUri)) { 31043 this.trigger('warn', { 31044 message: 'defaulting segment duration to the target duration' 31045 }); 31046 currentUri.duration = this.manifest.targetDuration; 31047 } // annotate with encryption information, if necessary 31048 31049 31050 if (_key) { 31051 currentUri.key = _key; 31052 } 31053 31054 currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary 31055 31056 if (currentMap) { 31057 currentUri.map = currentMap; 31058 } // prepare for the next URI 31059 31060 31061 currentUri = {}; 31062 }, 31063 comment: function comment() {// comments are not important for playback 31064 }, 31065 custom: function custom() { 31066 // if this is segment-level data attach the output to the segment 31067 if (entry.segment) { 31068 currentUri.custom = currentUri.custom || {}; 31069 currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object 31070 } else { 31071 this.manifest.custom = this.manifest.custom || {}; 31072 this.manifest.custom[entry.customType] = entry.data; 31073 } 31074 } 31075 })[entry.type].call(self); 31076 }); 31077 31078 return _this; 31079 } 31080 /** 31081 * Parse the input string and update the manifest object. 31082 * 31083 * @param {string} chunk a potentially incomplete portion of the manifest 31084 */ 31085 31086 31087 var _proto = Parser.prototype; 31088 31089 _proto.push = function push(chunk) { 31090 this.lineStream.push(chunk); 31091 } 31092 /** 31093 * Flush any remaining input. This can be handy if the last line of an M3U8 31094 * manifest did not contain a trailing newline but the file has been 31095 * completely received. 31096 */ 31097 ; 31098 31099 _proto.end = function end() { 31100 // flush any buffered input 31101 this.lineStream.push('\n'); 31102 } 31103 /** 31104 * Add an additional parser for non-standard tags 31105 * 31106 * @param {Object} options a map of options for the added parser 31107 * @param {RegExp} options.expression a regular expression to match the custom header 31108 * @param {string} options.type the type to register to the output 31109 * @param {Function} [options.dataParser] function to parse the line into an object 31110 * @param {boolean} [options.segment] should tag data be attached to the segment object 31111 */ 31112 ; 31113 31114 _proto.addParser = function addParser(options) { 31115 this.parseStream.addParser(options); 31116 } 31117 /** 31118 * Add a custom header mapper 31119 * 31120 * @param {Object} options 31121 * @param {RegExp} options.expression a regular expression to match the custom header 31122 * @param {Function} options.map function to translate tag into a different tag 31123 */ 31124 ; 31125 31126 _proto.addTagMapper = function addTagMapper(options) { 31127 this.parseStream.addTagMapper(options); 31128 }; 31129 31130 return Parser; 31131 }(stream); 31132 31133 var mediaTypes = createCommonjsModule(function (module, exports) { 31134 31135 Object.defineProperty(exports, '__esModule', { 31136 value: true 31137 }); 31138 var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 31139 var DASH_REGEX = /^application\/dash\+xml/i; 31140 /** 31141 * Returns a string that describes the type of source based on a video source object's 31142 * media type. 31143 * 31144 * @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type} 31145 * 31146 * @param {string} type 31147 * Video source object media type 31148 * @return {('hls'|'dash'|'vhs-json'|null)} 31149 * VHS source type string 31150 */ 31151 31152 var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) { 31153 if (MPEGURL_REGEX.test(type)) { 31154 return 'hls'; 31155 } 31156 31157 if (DASH_REGEX.test(type)) { 31158 return 'dash'; 31159 } // Denotes the special case of a manifest object passed to http-streaming instead of a 31160 // source URL. 31161 // 31162 // See https://en.wikipedia.org/wiki/Media_type for details on specifying media types. 31163 // 31164 // In this case, vnd stands for vendor, video.js for the organization, VHS for this 31165 // project, and the +json suffix identifies the structure of the media type. 31166 31167 31168 if (type === 'application/vnd.videojs.vhs+json') { 31169 return 'vhs-json'; 31170 } 31171 31172 return null; 31173 }; 31174 31175 exports.simpleTypeFromSourceType = simpleTypeFromSourceType; 31176 }); 31177 unwrapExports(mediaTypes); 31178 var mediaTypes_1 = mediaTypes.simpleTypeFromSourceType; 31179 31180 //[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] 31181 //[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] 31182 //[5] Name ::= NameStartChar (NameChar)* 31183 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 31184 31185 var nameChar = new RegExp("[\\-\\.0-9" + nameStartChar.source.slice(1, -1) + "\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"); 31186 var tagNamePattern = new RegExp('^' + nameStartChar.source + nameChar.source + '*(?:\:' + nameStartChar.source + nameChar.source + '*)?$'); //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/ 31187 //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(',') 31188 //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE 31189 //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE 31190 31191 var S_TAG = 0; //tag name offerring 31192 31193 var S_ATTR = 1; //attr name offerring 31194 31195 var S_ATTR_SPACE = 2; //attr name end and space offer 31196 31197 var S_EQ = 3; //=space? 31198 31199 var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only) 31200 31201 var S_ATTR_END = 5; //attr value end and no space(quot end) 31202 31203 var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer) 31204 31205 var S_TAG_CLOSE = 7; //closed el<el /> 31206 31207 function XMLReader() {} 31208 31209 XMLReader.prototype = { 31210 parse: function parse(source, defaultNSMap, entityMap) { 31211 var domBuilder = this.domBuilder; 31212 domBuilder.startDocument(); 31213 31214 _copy(defaultNSMap, defaultNSMap = {}); 31215 31216 _parse(source, defaultNSMap, entityMap, domBuilder, this.errorHandler); 31217 31218 domBuilder.endDocument(); 31219 } 31220 }; 31221 31222 function _parse(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) { 31223 function fixedFromCharCode(code) { 31224 // String.prototype.fromCharCode does not supports 31225 // > 2 bytes unicode chars directly 31226 if (code > 0xffff) { 31227 code -= 0x10000; 31228 var surrogate1 = 0xd800 + (code >> 10), 31229 surrogate2 = 0xdc00 + (code & 0x3ff); 31230 return String.fromCharCode(surrogate1, surrogate2); 31231 } else { 31232 return String.fromCharCode(code); 31233 } 31234 } 31235 31236 function entityReplacer(a) { 31237 var k = a.slice(1, -1); 31238 31239 if (k in entityMap) { 31240 return entityMap[k]; 31241 } else if (k.charAt(0) === '#') { 31242 return fixedFromCharCode(parseInt(k.substr(1).replace('x', '0x'))); 31243 } else {
vendor: 16,384 bytes, lines 31244-31799
31244 errorHandler.error('entity not found:' + a); 31245 return a; 31246 } 31247 } 31248 31249 function appendText(end) { 31250 //has some bugs 31251 if (end > start) { 31252 var xt = source.substring(start, end).replace(/&#?\w+;/g, entityReplacer); 31253 locator && position(start); 31254 domBuilder.characters(xt, 0, end - start); 31255 start = end; 31256 } 31257 } 31258 31259 function position(p, m) { 31260 while (p >= lineEnd && (m = linePattern.exec(source))) { 31261 lineStart = m.index; 31262 lineEnd = lineStart + m[0].length; 31263 locator.lineNumber++; //console.log('line++:',locator,startPos,endPos) 31264 } 31265 31266 locator.columnNumber = p - lineStart + 1; 31267 } 31268 31269 var lineStart = 0; 31270 var lineEnd = 0; 31271 var linePattern = /.*(?:\r\n?|\n)|.*$/g; 31272 var locator = domBuilder.locator; 31273 var parseStack = [{ 31274 currentNSMap: defaultNSMapCopy 31275 }]; 31276 var closeMap = {}; 31277 var start = 0; 31278 31279 while (true) { 31280 try { 31281 var tagStart = source.indexOf('<', start); 31282 31283 if (tagStart < 0) { 31284 if (!source.substr(start).match(/^\s*$/)) { 31285 var doc = domBuilder.doc; 31286 var text = doc.createTextNode(source.substr(start)); 31287 doc.appendChild(text); 31288 domBuilder.currentElement = text; 31289 } 31290 31291 return; 31292 } 31293 31294 if (tagStart > start) { 31295 appendText(tagStart); 31296 } 31297 31298 switch (source.charAt(tagStart + 1)) { 31299 case '/': 31300 var end = source.indexOf('>', tagStart + 3); 31301 var tagName = source.substring(tagStart + 2, end); 31302 var config = parseStack.pop(); 31303 31304 if (end < 0) { 31305 tagName = source.substring(tagStart + 2).replace(/[\s<].*/, ''); //console.error('#@@@@@@'+tagName) 31306 31307 errorHandler.error("end tag name: " + tagName + ' is not complete:' + config.tagName); 31308 end = tagStart + 1 + tagName.length; 31309 } else if (tagName.match(/\s</)) { 31310 tagName = tagName.replace(/[\s<].*/, ''); 31311 errorHandler.error("end tag name: " + tagName + ' maybe not complete'); 31312 end = tagStart + 1 + tagName.length; 31313 } //console.error(parseStack.length,parseStack) 31314 //console.error(config); 31315 31316 31317 var localNSMap = config.localNSMap; 31318 var endMatch = config.tagName == tagName; 31319 var endIgnoreCaseMach = endMatch || config.tagName && config.tagName.toLowerCase() == tagName.toLowerCase(); 31320 31321 if (endIgnoreCaseMach) { 31322 domBuilder.endElement(config.uri, config.localName, tagName); 31323 31324 if (localNSMap) { 31325 for (var prefix in localNSMap) { 31326 domBuilder.endPrefixMapping(prefix); 31327 } 31328 } 31329 31330 if (!endMatch) { 31331 errorHandler.fatalError("end tag name: " + tagName + ' is not match the current start tagName:' + config.tagName); 31332 } 31333 } else { 31334 parseStack.push(config); 31335 } 31336 31337 end++; 31338 break; 31339 // end elment 31340 31341 case '?': 31342 // <?...?> 31343 locator && position(tagStart); 31344 end = parseInstruction(source, tagStart, domBuilder); 31345 break; 31346 31347 case '!': 31348 // <!doctype,<![CDATA,<!-- 31349 locator && position(tagStart); 31350 end = parseDCC(source, tagStart, domBuilder, errorHandler); 31351 break; 31352 31353 default: 31354 locator && position(tagStart); 31355 var el = new ElementAttributes(); 31356 var currentNSMap = parseStack[parseStack.length - 1].currentNSMap; //elStartEnd 31357 31358 var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler); 31359 var len = el.length; 31360 31361 if (!el.closed && fixSelfClosed(source, end, el.tagName, closeMap)) { 31362 el.closed = true; 31363 31364 if (!entityMap.nbsp) { 31365 errorHandler.warning('unclosed xml attribute'); 31366 } 31367 } 31368 31369 if (locator && len) { 31370 var locator2 = copyLocator(locator, {}); //try{//attribute position fixed 31371 31372 for (var i = 0; i < len; i++) { 31373 var a = el[i]; 31374 position(a.offset); 31375 a.locator = copyLocator(locator, {}); 31376 } //}catch(e){console.error('@@@@@'+e)} 31377 31378 31379 domBuilder.locator = locator2; 31380 31381 if (appendElement(el, domBuilder, currentNSMap)) { 31382 parseStack.push(el); 31383 } 31384 31385 domBuilder.locator = locator; 31386 } else { 31387 if (appendElement(el, domBuilder, currentNSMap)) { 31388 parseStack.push(el); 31389 } 31390 } 31391 31392 if (el.uri === 'http://www.w3.org/1999/xhtml' && !el.closed) { 31393 end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder); 31394 } else { 31395 end++; 31396 } 31397 31398 } 31399 } catch (e) { 31400 errorHandler.error('element parse error: ' + e); //errorHandler.error('element parse error: '+e); 31401 31402 end = -1; //throw e; 31403 } 31404 31405 if (end > start) { 31406 start = end; 31407 } else { 31408 //TODO: è¿éæå¯è½saxåéï¼æä½ç½®é误é£é© 31409 appendText(Math.max(tagStart, start) + 1); 31410 } 31411 } 31412 } 31413 31414 function copyLocator(f, t) { 31415 t.lineNumber = f.lineNumber; 31416 t.columnNumber = f.columnNumber; 31417 return t; 31418 } 31419 /** 31420 * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack); 31421 * @return end of the elementStartPart(end of elementEndPart for selfClosed el) 31422 */ 31423 31424 31425 function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler) { 31426 var attrName; 31427 var value; 31428 var p = ++start; 31429 var s = S_TAG; //status 31430 31431 while (true) { 31432 var c = source.charAt(p); 31433 31434 switch (c) { 31435 case '=': 31436 if (s === S_ATTR) { 31437 //attrName 31438 attrName = source.slice(start, p); 31439 s = S_EQ; 31440 } else if (s === S_ATTR_SPACE) { 31441 s = S_EQ; 31442 } else { 31443 //fatalError: equal must after attrName or space after attrName 31444 throw new Error('attribute equal must after attrName'); 31445 } 31446 31447 break; 31448 31449 case '\'': 31450 case '"': 31451 if (s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE 31452 ) { 31453 //equal 31454 if (s === S_ATTR) { 31455 errorHandler.warning('attribute value must after "="'); 31456 attrName = source.slice(start, p); 31457 } 31458 31459 start = p + 1; 31460 p = source.indexOf(c, start); 31461 31462 if (p > 0) { 31463 value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); 31464 el.add(attrName, value, start - 1); 31465 s = S_ATTR_END; 31466 } else { 31467 //fatalError: no end quot match 31468 throw new Error('attribute value no end \'' + c + '\' match'); 31469 } 31470 } else if (s == S_ATTR_NOQUOT_VALUE) { 31471 value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); //console.log(attrName,value,start,p) 31472 31473 el.add(attrName, value, start); //console.dir(el) 31474 31475 errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!'); 31476 start = p + 1; 31477 s = S_ATTR_END; 31478 } else { 31479 //fatalError: no equal before 31480 throw new Error('attribute value must after "="'); 31481 } 31482 31483 break; 31484 31485 case '/': 31486 switch (s) { 31487 case S_TAG: 31488 el.setTagName(source.slice(start, p)); 31489 31490 case S_ATTR_END: 31491 case S_TAG_SPACE: 31492 case S_TAG_CLOSE: 31493 s = S_TAG_CLOSE; 31494 el.closed = true; 31495 31496 case S_ATTR_NOQUOT_VALUE: 31497 case S_ATTR: 31498 case S_ATTR_SPACE: 31499 break; 31500 //case S_EQ: 31501 31502 default: 31503 throw new Error("attribute invalid close char('/')"); 31504 } 31505 31506 break; 31507 31508 case '': 31509 //end document 31510 //throw new Error('unexpected end of input') 31511 errorHandler.error('unexpected end of input'); 31512 31513 if (s == S_TAG) { 31514 el.setTagName(source.slice(start, p)); 31515 } 31516 31517 return p; 31518 31519 case '>': 31520 switch (s) { 31521 case S_TAG: 31522 el.setTagName(source.slice(start, p)); 31523 31524 case S_ATTR_END: 31525 case S_TAG_SPACE: 31526 case S_TAG_CLOSE: 31527 break; 31528 //normal 31529 31530 case S_ATTR_NOQUOT_VALUE: //Compatible state 31531 31532 case S_ATTR: 31533 value = source.slice(start, p); 31534 31535 if (value.slice(-1) === '/') { 31536 el.closed = true; 31537 value = value.slice(0, -1); 31538 } 31539 31540 case S_ATTR_SPACE: 31541 if (s === S_ATTR_SPACE) { 31542 value = attrName; 31543 } 31544 31545 if (s == S_ATTR_NOQUOT_VALUE) { 31546 errorHandler.warning('attribute "' + value + '" missed quot(")!!'); 31547 el.add(attrName, value.replace(/&#?\w+;/g, entityReplacer), start); 31548 } else { 31549 if (currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !value.match(/^(?:disabled|checked|selected)$/i)) { 31550 errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!'); 31551 } 31552 31553 el.add(value, value, start); 31554 } 31555 31556 break; 31557 31558 case S_EQ: 31559 throw new Error('attribute value missed!!'); 31560 } // console.log(tagName,tagNamePattern,tagNamePattern.test(tagName)) 31561 31562 31563 return p; 31564 31565 /*xml space '\x20' | #x9 | #xD | #xA; */ 31566 31567 case "\x80": 31568 c = ' '; 31569 31570 default: 31571 if (c <= ' ') { 31572 //space 31573 switch (s) { 31574 case S_TAG: 31575 el.setTagName(source.slice(start, p)); //tagName 31576 31577 s = S_TAG_SPACE; 31578 break; 31579 31580 case S_ATTR: 31581 attrName = source.slice(start, p); 31582 s = S_ATTR_SPACE; 31583 break; 31584 31585 case S_ATTR_NOQUOT_VALUE: 31586 var value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); 31587 errorHandler.warning('attribute "' + value + '" missed quot(")!!'); 31588 el.add(attrName, value, start); 31589 31590 case S_ATTR_END: 31591 s = S_TAG_SPACE; 31592 break; 31593 //case S_TAG_SPACE: 31594 //case S_EQ: 31595 //case S_ATTR_SPACE: 31596 // void();break; 31597 //case S_TAG_CLOSE: 31598 //ignore warning 31599 } 31600 } else { 31601 //not space 31602 //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE 31603 //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE 31604 switch (s) { 31605 //case S_TAG:void();break; 31606 //case S_ATTR:void();break; 31607 //case S_ATTR_NOQUOT_VALUE:void();break; 31608 case S_ATTR_SPACE: 31609 var tagName = el.tagName; 31610 31611 if (currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !attrName.match(/^(?:disabled|checked|selected)$/i)) { 31612 errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!'); 31613 } 31614 31615 el.add(attrName, attrName, start); 31616 start = p; 31617 s = S_ATTR; 31618 break; 31619 31620 case S_ATTR_END: 31621 errorHandler.warning('attribute space is required"' + attrName + '"!!'); 31622 31623 case S_TAG_SPACE: 31624 s = S_ATTR; 31625 start = p; 31626 break; 31627 31628 case S_EQ: 31629 s = S_ATTR_NOQUOT_VALUE; 31630 start = p; 31631 break; 31632 31633 case S_TAG_CLOSE: 31634 throw new Error("elements closed character '/' and '>' must be connected to"); 31635 } 31636 } 31637 31638 } //end outer switch 31639 //console.log('p++',p) 31640 31641 31642 p++; 31643 } 31644 } 31645 /** 31646 * @return true if has new namespace define 31647 */ 31648 31649 31650 function appendElement(el, domBuilder, currentNSMap) { 31651 var tagName = el.tagName; 31652 var localNSMap = null; //var currentNSMap = parseStack[parseStack.length-1].currentNSMap; 31653 31654 var i = el.length; 31655 31656 while (i--) { 31657 var a = el[i]; 31658 var qName = a.qName; 31659 var value = a.value; 31660 var nsp = qName.indexOf(':'); 31661 31662 if (nsp > 0) { 31663 var prefix = a.prefix = qName.slice(0, nsp); 31664 var localName = qName.slice(nsp + 1); 31665 var nsPrefix = prefix === 'xmlns' && localName; 31666 } else { 31667 localName = qName; 31668 prefix = null; 31669 nsPrefix = qName === 'xmlns' && ''; 31670 } //can not set prefix,because prefix !== '' 31671 31672 31673 a.localName = localName; //prefix == null for no ns prefix attribute 31674 31675 if (nsPrefix !== false) { 31676 //hack!! 31677 if (localNSMap == null) { 31678 localNSMap = {}; //console.log(currentNSMap,0) 31679 31680 _copy(currentNSMap, currentNSMap = {}); //console.log(currentNSMap,1) 31681 31682 } 31683 31684 currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; 31685 a.uri = 'http://www.w3.org/2000/xmlns/'; 31686 domBuilder.startPrefixMapping(nsPrefix, value); 31687 } 31688 } 31689 31690 var i = el.length; 31691 31692 while (i--) { 31693 a = el[i]; 31694 var prefix = a.prefix; 31695 31696 if (prefix) { 31697 //no prefix attribute has no namespace 31698 if (prefix === 'xml') { 31699 a.uri = 'http://www.w3.org/XML/1998/namespace'; 31700 } 31701 31702 if (prefix !== 'xmlns') { 31703 a.uri = currentNSMap[prefix || '']; //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)} 31704 } 31705 } 31706 } 31707 31708 var nsp = tagName.indexOf(':'); 31709 31710 if (nsp > 0) { 31711 prefix = el.prefix = tagName.slice(0, nsp); 31712 localName = el.localName = tagName.slice(nsp + 1); 31713 } else { 31714 prefix = null; //important!! 31715 31716 localName = el.localName = tagName; 31717 } //no prefix element has default namespace 31718 31719 31720 var ns = el.uri = currentNSMap[prefix || '']; 31721 domBuilder.startElement(ns, localName, tagName, el); //endPrefixMapping and startPrefixMapping have not any help for dom builder 31722 //localNSMap = null 31723 31724 if (el.closed) { 31725 domBuilder.endElement(ns, localName, tagName); 31726 31727 if (localNSMap) { 31728 for (prefix in localNSMap) { 31729 domBuilder.endPrefixMapping(prefix); 31730 } 31731 } 31732 } else { 31733 el.currentNSMap = currentNSMap; 31734 el.localNSMap = localNSMap; //parseStack.push(el); 31735 31736 return true; 31737 } 31738 } 31739 31740 function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) { 31741 if (/^(?:script|textarea)$/i.test(tagName)) { 31742 var elEndStart = source.indexOf('</' + tagName + '>', elStartEnd); 31743 var text = source.substring(elStartEnd + 1, elEndStart); 31744 31745 if (/[&<]/.test(text)) { 31746 if (/^script$/i.test(tagName)) { 31747 //if(!/\]\]>/.test(text)){ 31748 //lexHandler.startCDATA(); 31749 domBuilder.characters(text, 0, text.length); //lexHandler.endCDATA(); 31750 31751 return elEndStart; //} 31752 } //}else{//text area 31753 31754 31755 text = text.replace(/&#?\w+;/g, entityReplacer); 31756 domBuilder.characters(text, 0, text.length); 31757 return elEndStart; //} 31758 } 31759 } 31760 31761 return elStartEnd + 1; 31762 } 31763 31764 function fixSelfClosed(source, elStartEnd, tagName, closeMap) { 31765 //if(tagName in closeMap){ 31766 var pos = closeMap[tagName]; 31767 31768 if (pos == null) { 31769 //console.log(tagName) 31770 pos = source.lastIndexOf('</' + tagName + '>'); 31771 31772 if (pos < elStartEnd) { 31773 //å¿è®°éå 31774 pos = source.lastIndexOf('</' + tagName); 31775 } 31776 31777 closeMap[tagName] = pos; 31778 } 31779 31780 return pos < elStartEnd; //} 31781 } 31782 31783 function _copy(source, target) { 31784 for (var n in source) { 31785 target[n] = source[n]; 31786 } 31787 } 31788 31789 function parseDCC(source, start, domBuilder, errorHandler) { 31790 //sure start with '<!' 31791 var next = source.charAt(start + 2); 31792 31793 switch (next) { 31794 case '-': 31795 if (source.charAt(start + 3) === '-') { 31796 var end = source.indexOf('-->', start + 4); //append comment source.substring(4,end)//<!-- 31797 31798 if (end > start) { 31799 domBuilder.commen
31799t(source, start + 4, end - start - 4); 31800 return end + 3; 31801 } else { 31802 errorHandler.error("Unclosed comment"); 31803 return -1; 31804 } 31805 } else { 31806 //error 31807 return -1; 31808 } 31809 31810 default: 31811 if (source.substr(start + 3, 6) == 'CDATA[') { 31812 var end = source.indexOf(']]>', start + 9); 31813 domBuilder.startCDATA(); 31814 domBuilder.characters(source, start + 9, end - start - 9); 31815 domBuilder.endCDATA(); 31816 return end + 3; 31817 } //<!DOCTYPE 31818 //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId) 31819 31820 31821 var matchs = split(source, start); 31822 var len = matchs.length; 31823 31824 if (len > 1 && /!doctype/i.test(matchs[0][0])) { 31825 var name = matchs[1][0]; 31826 var pubid = len > 3 && /^public$/i.test(matchs[2][0]) && matchs[3][0]; 31827 var sysid = len > 4 && matchs[4][0]; 31828 var lastMatch = matchs[len - 1]; 31829 domBuilder.startDTD(name, pubid && pubid.replace(/^(['"])(.*?)\1$/, '$2'), sysid && sysid.replace(/^(['"])(.*?)\1$/, '$2')); 31830 domBuilder.endDTD(); 31831 return lastMatch.index + lastMatch[0].length; 31832 } 31833 31834 } 31835 31836 return -1; 31837 } 31838 31839 function parseInstruction(source, start, domBuilder) { 31840 var end = source.indexOf('?>', start); 31841 31842 if (end) { 31843 var match = source.substring(start, end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/); 31844 31845 if (match) { 31846 var len = match[0].length; 31847 domBuilder.processingInstruction(match[1], match[2]); 31848 return end + 2; 31849 } else { 31850 //error 31851 return -1; 31852 } 31853 } 31854 31855 return -1; 31856 } 31857 /** 31858 * @param source 31859 */ 31860 31861 31862 function ElementAttributes(source) {} 31863 31864 ElementAttributes.prototype = { 31865 setTagName: function setTagName(tagName) { 31866 if (!tagNamePattern.test(tagName)) { 31867 throw new Error('invalid tagName:' + tagName); 31868 } 31869 31870 this.tagName = tagName; 31871 }, 31872 add: function add(qName, value, offset) { 31873 if (!tagNamePattern.test(qName)) { 31874 throw new Error('invalid attribute:' + qName); 31875 } 31876 31877 this[this.length++] = { 31878 qName: qName, 31879 value: value, 31880 offset: offset 31881 }; 31882 }, 31883 length: 0, 31884 getLocalName: function getLocalName(i) { 31885 return this[i].localName; 31886 }, 31887 getLocator: function getLocator(i) { 31888 return this[i].locator; 31889 }, 31890 getQName: function getQName(i) { 31891 return this[i].qName; 31892 }, 31893 getURI: function getURI(i) { 31894 return this[i].uri; 31895 }, 31896 getValue: function getValue(i) { 31897 return this[i].value; 31898 } // ,getIndex:function(uri, localName)){ 31899 // if(localName){ 31900 // 31901 // }else{ 31902 // var qName = uri 31903 // } 31904 // }, 31905 // getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))}, 31906 // getType:function(uri,localName){} 31907 // getType:function(i){}, 31908 31909 }; 31910 31911 function _set_proto_(thiz, parent) { 31912 thiz.__proto__ = parent; 31913 return thiz; 31914 } 31915 31916 if (!(_set_proto_({}, _set_proto_.prototype) instanceof _set_proto_)) { 31917 _set_proto_ = function _set_proto_(thiz, parent) { 31918 function p() {} 31919 p.prototype = parent; 31920 p = new p(); 31921 31922 for (parent in thiz) { 31923 p[parent] = thiz[parent]; 31924 } 31925 31926 return p; 31927 }; 31928 } 31929 31930 function split(source, start) { 31931 var match; 31932 var buf = []; 31933 var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g; 31934 reg.lastIndex = start; 31935 reg.exec(source); //skip < 31936 31937 while (match = reg.exec(source)) { 31938 buf.push(match); 31939 if (match[1]) return buf; 31940 } 31941 } 31942 31943 var XMLReader_1 = XMLReader; 31944 var sax = { 31945 XMLReader: XMLReader_1 31946 }; 31947 31948 /* 31949 * DOM Level 2 31950 * Object DOMException 31951 * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html 31952 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html 31953 */ 31954 function copy(src, dest) { 31955 for (var p in src) { 31956 dest[p] = src[p]; 31957 } 31958 } 31959 /** 31960 ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));? 31961 ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));? 31962 */ 31963 31964 31965 function _extends(Class, Super) { 31966 var pt = Class.prototype; 31967 31968 if (Object.create) { 31969 var ppt = Object.create(Super.prototype); 31970 pt.__proto__ = ppt; 31971 } 31972 31973 if (!(pt instanceof Super)) { 31974 var t = function t() {}; 31975 t.prototype = Super.prototype; 31976 t = new t(); 31977 copy(pt, t); 31978 Class.prototype = pt = t; 31979 } 31980 31981 if (pt.constructor != Class) { 31982 if (typeof Class != 'function') { 31983 console.error("unknow Class:" + Class); 31984 } 31985 31986 pt.constructor = Class; 31987 } 31988 } 31989 31990 var htmlns = 'http://www.w3.org/1999/xhtml'; // Node Types 31991 31992 var NodeType = {};
vendor: 13,342 bytes, lines 31993-32425
31993 var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; 31994 var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; 31995 var TEXT_NODE = NodeType.TEXT_NODE = 3; 31996 var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; 31997 var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; 31998 var ENTITY_NODE = NodeType.ENTITY_NODE = 6; 31999 var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; 32000 var COMMENT_NODE = NodeType.COMMENT_NODE = 8; 32001 var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; 32002 var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; 32003 var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; 32004 var NOTATION_NODE = NodeType.NOTATION_NODE = 12; // ExceptionCode 32005 32006 var ExceptionCode = {}; 32007 var ExceptionMessage = {}; 32008 var INDEX_SIZE_ERR = ExceptionCode.INDEX_SIZE_ERR = (ExceptionMessage[1] = "Index size error", 1); 32009 var DOMSTRING_SIZE_ERR = ExceptionCode.DOMSTRING_SIZE_ERR = (ExceptionMessage[2] = "DOMString size error", 2); 32010 var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = (ExceptionMessage[3] = "Hierarchy request error", 3); 32011 var WRONG_DOCUMENT_ERR = ExceptionCode.WRONG_DOCUMENT_ERR = (ExceptionMessage[4] = "Wrong document", 4); 32012 var INVALID_CHARACTER_ERR = ExceptionCode.INVALID_CHARACTER_ERR = (ExceptionMessage[5] = "Invalid character", 5); 32013 var NO_DATA_ALLOWED_ERR = ExceptionCode.NO_DATA_ALLOWED_ERR = (ExceptionMessage[6] = "No data allowed", 6); 32014 var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = (ExceptionMessage[7] = "No modification allowed", 7); 32015 var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = (ExceptionMessage[8] = "Not found", 8); 32016 var NOT_SUPPORTED_ERR = ExceptionCode.NOT_SUPPORTED_ERR = (ExceptionMessage[9] = "Not supported", 9); 32017 var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = (ExceptionMessage[10] = "Attribute in use", 10); //level2 32018 32019 var INVALID_STATE_ERR = ExceptionCode.INVALID_STATE_ERR = (ExceptionMessage[11] = "Invalid state", 11); 32020 var SYNTAX_ERR = ExceptionCode.SYNTAX_ERR = (ExceptionMessage[12] = "Syntax error", 12); 32021 var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = (ExceptionMessage[13] = "Invalid modification", 13); 32022 var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = (ExceptionMessage[14] = "Invalid namespace", 14); 32023 var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = (ExceptionMessage[15] = "Invalid access", 15); 32024 32025 function DOMException(code, message) { 32026 if (message instanceof Error) { 32027 var error = message; 32028 } else { 32029 error = this; 32030 Error.call(this, ExceptionMessage[code]); 32031 this.message = ExceptionMessage[code]; 32032 if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException); 32033 } 32034 32035 error.code = code; 32036 if (message) this.message = this.message + ": " + message; 32037 return error; 32038 } 32039 DOMException.prototype = Error.prototype; 32040 copy(ExceptionCode, DOMException); 32041 /** 32042 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 32043 * 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. 32044 * The items in the NodeList are accessible via an integral index, starting from 0. 32045 */ 32046 32047 function NodeList() {} 32048 NodeList.prototype = { 32049 /** 32050 * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive. 32051 * @standard level1 32052 */ 32053 length: 0, 32054 32055 /** 32056 * 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. 32057 * @standard level1 32058 * @param index unsigned long 32059 * Index into the collection. 32060 * @return Node 32061 * The node at the indexth position in the NodeList, or null if that is not a valid index. 32062 */ 32063 item: function item(index) { 32064 return this[index] || null; 32065 }, 32066 toString: function toString(isHTML, nodeFilter) { 32067 for (var buf = [], i = 0; i < this.length; i++) { 32068 serializeToString(this[i], buf, isHTML, nodeFilter); 32069 } 32070 32071 return buf.join(''); 32072 } 32073 }; 32074 32075 function LiveNodeList(node, refresh) { 32076 this._node = node; 32077 this._refresh = refresh; 32078 32079 _updateLiveList(this); 32080 } 32081 32082 function _updateLiveList(list) { 32083 var inc = list._node._inc || list._node.ownerDocument._inc; 32084 32085 if (list._inc != inc) { 32086 var ls = list._refresh(list._node); //console.log(ls.length) 32087 32088 32089 __set__(list, 'length', ls.length); 32090 32091 copy(ls, list); 32092 list._inc = inc; 32093 } 32094 } 32095 32096 LiveNodeList.prototype.item = function (i) { 32097 _updateLiveList(this); 32098 32099 return this[i]; 32100 }; 32101 32102 _extends(LiveNodeList, NodeList); 32103 /** 32104 * 32105 * Objects implementing the NamedNodeMap interface are used to represent collections of nodes that can be accessed by name. Note that NamedNodeMap does not inherit from NodeList; NamedNodeMaps are not maintained in any particular order. Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index, but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, and does not imply that the DOM specifies an order to these Nodes. 32106 * NamedNodeMap objects in the DOM are live. 32107 * used for attributes or DocumentType entities 32108 */ 32109 32110 32111 function NamedNodeMap() {} 32112 32113 function _findNodeIndex(list, node) { 32114 var i = list.length; 32115 32116 while (i--) { 32117 if (list[i] === node) { 32118 return i; 32119 } 32120 } 32121 } 32122 32123 function _addNamedNode(el, list, newAttr, oldAttr) { 32124 if (oldAttr) { 32125 list[_findNodeIndex(list, oldAttr)] = newAttr; 32126 } else { 32127 list[list.length++] = newAttr; 32128 } 32129 32130 if (el) { 32131 newAttr.ownerElement = el; 32132 var doc = el.ownerDocument; 32133 32134 if (doc) { 32135 oldAttr && _onRemoveAttribute(doc, el, oldAttr); 32136 32137 _onAddAttribute(doc, el, newAttr); 32138 } 32139 } 32140 } 32141 32142 function _removeNamedNode(el, list, attr) { 32143 //console.log('remove attr:'+attr) 32144 var i = _findNodeIndex(list, attr); 32145 32146 if (i >= 0) { 32147 var lastIndex = list.length - 1; 32148 32149 while (i < lastIndex) { 32150 list[i] = list[++i]; 32151 } 32152 32153 list.length = lastIndex; 32154 32155 if (el) { 32156 var doc = el.ownerDocument; 32157 32158 if (doc) { 32159 _onRemoveAttribute(doc, el, attr); 32160 32161 attr.ownerElement = null; 32162 } 32163 } 32164 } else { 32165 throw DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr)); 32166 } 32167 } 32168 32169 NamedNodeMap.prototype = { 32170 length: 0, 32171 item: NodeList.prototype.item, 32172 getNamedItem: function getNamedItem(key) { 32173 // if(key.indexOf(':')>0 || key == 'xmlns'){ 32174 // return null; 32175 // } 32176 //console.log() 32177 var i = this.length; 32178 32179 while (i--) { 32180 var attr = this[i]; //console.log(attr.nodeName,key) 32181 32182 if (attr.nodeName == key) { 32183 return attr; 32184 } 32185 } 32186 }, 32187 setNamedItem: function setNamedItem(attr) { 32188 var el = attr.ownerElement; 32189 32190 if (el && el != this._ownerElement) { 32191 throw new DOMException(INUSE_ATTRIBUTE_ERR); 32192 } 32193 32194 var oldAttr = this.getNamedItem(attr.nodeName); 32195 32196 _addNamedNode(this._ownerElement, this, attr, oldAttr); 32197 32198 return oldAttr; 32199 }, 32200 32201 /* returns Node */ 32202 setNamedItemNS: function setNamedItemNS(attr) { 32203 // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR 32204 var el = attr.ownerElement, 32205 oldAttr; 32206 32207 if (el && el != this._ownerElement) { 32208 throw new DOMException(INUSE_ATTRIBUTE_ERR); 32209 } 32210 32211 oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName); 32212 32213 _addNamedNode(this._ownerElement, this, attr, oldAttr); 32214 32215 return oldAttr; 32216 }, 32217 32218 /* returns Node */ 32219 removeNamedItem: function removeNamedItem(key) { 32220 var attr = this.getNamedItem(key); 32221 32222 _removeNamedNode(this._ownerElement, this, attr); 32223 32224 return attr; 32225 }, 32226 // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR 32227 //for level2 32228 removeNamedItemNS: function removeNamedItemNS(namespaceURI, localName) { 32229 var attr = this.getNamedItemNS(namespaceURI, localName); 32230 32231 _removeNamedNode(this._ownerElement, this, attr); 32232 32233 return attr; 32234 }, 32235 getNamedItemNS: function getNamedItemNS(namespaceURI, localName) { 32236 var i = this.length; 32237 32238 while (i--) { 32239 var node = this[i]; 32240 32241 if (node.localName == localName && node.namespaceURI == namespaceURI) { 32242 return node; 32243 } 32244 } 32245 32246 return null; 32247 } 32248 }; 32249 /** 32250 * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 32251 */ 32252 32253 function DOMImplementation( 32254 /* Object */ 32255 features) { 32256 this._features = {}; 32257 32258 if (features) { 32259 for (var feature in features) { 32260 this._features = features[feature]; 32261 } 32262 } 32263 } 32264 DOMImplementation.prototype = { 32265 hasFeature: function hasFeature( 32266 /* string */ 32267 feature, 32268 /* string */ 32269 version) { 32270 var versions = this._features[feature.toLowerCase()]; 32271 32272 if (versions && (!version || version in versions)) { 32273 return true; 32274 } else { 32275 return false; 32276 } 32277 }, 32278 // Introduced in DOM Level 2: 32279 createDocument: function createDocument(namespaceURI, qualifiedName, doctype) { 32280 // raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR,WRONG_DOCUMENT_ERR 32281 var doc = new Document(); 32282 doc.implementation = this; 32283 doc.childNodes = new NodeList(); 32284 doc.doctype = doctype; 32285 32286 if (doctype) { 32287 doc.appendChild(doctype); 32288 } 32289 32290 if (qualifiedName) { 32291 var root = doc.createElementNS(namespaceURI, qualifiedName); 32292 doc.appendChild(root); 32293 } 32294 32295 return doc; 32296 }, 32297 // Introduced in DOM Level 2: 32298 createDocumentType: function createDocumentType(qualifiedName, publicId, systemId) { 32299 // raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR 32300 var node = new DocumentType(); 32301 node.name = qualifiedName; 32302 node.nodeName = qualifiedName; 32303 node.publicId = publicId; 32304 node.systemId = systemId; // Introduced in DOM Level 2: 32305 //readonly attribute DOMString internalSubset; 32306 //TODO:.. 32307 // readonly attribute NamedNodeMap entities; 32308 // readonly attribute NamedNodeMap notations; 32309 32310 return node; 32311 } 32312 }; 32313 /** 32314 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 32315 */ 32316 32317 function Node() {} 32318 Node.prototype = { 32319 firstChild: null, 32320 lastChild: null, 32321 previousSibling: null, 32322 nextSibling: null, 32323 attributes: null, 32324 parentNode: null, 32325 childNodes: null, 32326 ownerDocument: null, 32327 nodeValue: null, 32328 namespaceURI: null, 32329 prefix: null, 32330 localName: null, 32331 // Modified in DOM Level 2: 32332 insertBefore: function insertBefore(newChild, refChild) { 32333 //raises 32334 return _insertBefore(this, newChild, refChild); 32335 }, 32336 replaceChild: function replaceChild(newChild, oldChild) { 32337 //raises 32338 this.insertBefore(newChild, oldChild); 32339 32340 if (oldChild) { 32341 this.removeChild(oldChild); 32342 } 32343 }, 32344 removeChild: function removeChild(oldChild) { 32345 return _removeChild(this, oldChild); 32346 }, 32347 appendChild: function appendChild(newChild) { 32348 return this.insertBefore(newChild, null); 32349 }, 32350 hasChildNodes: function hasChildNodes() { 32351 return this.firstChild != null; 32352 }, 32353 cloneNode: function cloneNode(deep) { 32354 return _cloneNode(this.ownerDocument || this, this, deep); 32355 }, 32356 // Modified in DOM Level 2: 32357 normalize: function normalize() { 32358 var child = this.firstChild; 32359 32360 while (child) { 32361 var next = child.nextSibling; 32362 32363 if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) { 32364 this.removeChild(next); 32365 child.appendData(next.data); 32366 } else { 32367 child.normalize(); 32368 child = next; 32369 } 32370 } 32371 }, 32372 // Introduced in DOM Level 2: 32373 isSupported: function isSupported(feature, version) { 32374 return this.ownerDocument.implementation.hasFeature(feature, version); 32375 }, 32376 // Introduced in DOM Level 2: 32377 hasAttributes: function hasAttributes() { 32378 return this.attributes.length > 0; 32379 }, 32380 lookupPrefix: function lookupPrefix(namespaceURI) { 32381 var el = this; 32382 32383 while (el) { 32384 var map = el._nsMap; //console.dir(map) 32385 32386 if (map) { 32387 for (var n in map) { 32388 if (map[n] == namespaceURI) { 32389 return n; 32390 } 32391 } 32392 } 32393 32394 el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode; 32395 } 32396 32397 return null; 32398 }, 32399 // Introduced in DOM Level 3: 32400 lookupNamespaceURI: function lookupNamespaceURI(prefix) { 32401 var el = this; 32402 32403 while (el) { 32404 var map = el._nsMap; //console.dir(map) 32405 32406 if (map) { 32407 if (prefix in map) { 32408 return map[prefix]; 32409 } 32410 } 32411 32412 el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode; 32413 } 32414 32415 return null; 32416 }, 32417 // Introduced in DOM Level 3: 32418 isDefaultNamespace: function isDefaultNamespace(namespaceURI) { 32419 var prefix = this.lookupPrefix(namespaceURI); 32420 return prefix == null; 32421 } 32422 }; 32423 32424 function _xmlEncoder(c) { 32425 return c == '<' && '<' || c == '>' && '>' || c == '&' && '&' || c == '"' && '"
vendor: 13,054 bytes, lines 32425-32882
32425' || '&#' + c.charCodeAt() + ';'; 32426 } 32427 32428 copy(NodeType, Node); 32429 copy(NodeType, Node.prototype); 32430 /** 32431 * @param callback return true for continue,false for break 32432 * @return boolean true: break visit; 32433 */ 32434 32435 function _visitNode(node, callback) { 32436 if (callback(node)) { 32437 return true; 32438 } 32439 32440 if (node = node.firstChild) { 32441 do { 32442 if (_visitNode(node, callback)) { 32443 return true; 32444 } 32445 } while (node = node.nextSibling); 32446 } 32447 } 32448 32449 function Document() {} 32450 32451 function _onAddAttribute(doc, el, newAttr) { 32452 doc && doc._inc++; 32453 var ns = newAttr.namespaceURI; 32454 32455 if (ns == 'http://www.w3.org/2000/xmlns/') { 32456 //update namespace 32457 el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value; 32458 } 32459 } 32460 32461 function _onRemoveAttribute(doc, el, newAttr, remove) { 32462 doc && doc._inc++; 32463 var ns = newAttr.namespaceURI; 32464 32465 if (ns == 'http://www.w3.org/2000/xmlns/') { 32466 //update namespace 32467 delete el._nsMap[newAttr.prefix ? newAttr.localName : '']; 32468 } 32469 } 32470 32471 function _onUpdateChild(doc, el, newChild) { 32472 if (doc && doc._inc) { 32473 doc._inc++; //update childNodes 32474 32475 var cs = el.childNodes; 32476 32477 if (newChild) { 32478 cs[cs.length++] = newChild; 32479 } else { 32480 //console.log(1) 32481 var child = el.firstChild; 32482 var i = 0; 32483 32484 while (child) { 32485 cs[i++] = child; 32486 child = child.nextSibling; 32487 } 32488 32489 cs.length = i; 32490 } 32491 } 32492 } 32493 /** 32494 * attributes; 32495 * children; 32496 * 32497 * writeable properties: 32498 * nodeValue,Attr:value,CharacterData:data 32499 * prefix 32500 */ 32501 32502 32503 function _removeChild(parentNode, child) { 32504 var previous = child.previousSibling; 32505 var next = child.nextSibling; 32506 32507 if (previous) { 32508 previous.nextSibling = next; 32509 } else { 32510 parentNode.firstChild = next; 32511 } 32512 32513 if (next) { 32514 next.previousSibling = previous; 32515 } else { 32516 parentNode.lastChild = previous; 32517 } 32518 32519 _onUpdateChild(parentNode.ownerDocument, parentNode); 32520 32521 return child; 32522 } 32523 /** 32524 * preformance key(refChild == null) 32525 */ 32526 32527 32528 function _insertBefore(parentNode, newChild, nextChild) { 32529 var cp = newChild.parentNode; 32530 32531 if (cp) { 32532 cp.removeChild(newChild); //remove and update 32533 } 32534 32535 if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) { 32536 var newFirst = newChild.firstChild; 32537 32538 if (newFirst == null) { 32539 return newChild; 32540 } 32541 32542 var newLast = newChild.lastChild; 32543 } else { 32544 newFirst = newLast = newChild; 32545 } 32546 32547 var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild; 32548 newFirst.previousSibling = pre; 32549 newLast.nextSibling = nextChild; 32550 32551 if (pre) { 32552 pre.nextSibling = newFirst; 32553 } else { 32554 parentNode.firstChild = newFirst; 32555 } 32556 32557 if (nextChild == null) { 32558 parentNode.lastChild = newLast; 32559 } else { 32560 nextChild.previousSibling = newLast; 32561 } 32562 32563 do { 32564 newFirst.parentNode = parentNode; 32565 } while (newFirst !== newLast && (newFirst = newFirst.nextSibling)); 32566 32567 _onUpdateChild(parentNode.ownerDocument || parentNode, parentNode); //console.log(parentNode.lastChild.nextSibling == null) 32568 32569 32570 if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) { 32571 newChild.firstChild = newChild.lastChild = null; 32572 } 32573 32574 return newChild; 32575 } 32576 32577 function _appendSingleChild(parentNode, newChild) { 32578 var cp = newChild.parentNode; 32579 32580 if (cp) { 32581 var pre = parentNode.lastChild; 32582 cp.removeChild(newChild); //remove and update 32583 32584 var pre = parentNode.lastChild; 32585 } 32586 32587 var pre = parentNode.lastChild; 32588 newChild.parentNode = parentNode; 32589 newChild.previousSibling = pre; 32590 newChild.nextSibling = null; 32591 32592 if (pre) { 32593 pre.nextSibling = newChild; 32594 } else { 32595 parentNode.firstChild = newChild; 32596 } 32597 32598 parentNode.lastChild = newChild; 32599 32600 _onUpdateChild(parentNode.ownerDocument, parentNode, newChild); 32601 32602 return newChild; //console.log("__aa",parentNode.lastChild.nextSibling == null) 32603 } 32604 32605 Document.prototype = { 32606 //implementation : null, 32607 nodeName: '#document', 32608 nodeType: DOCUMENT_NODE, 32609 doctype: null, 32610 documentElement: null, 32611 _inc: 1, 32612 insertBefore: function insertBefore(newChild, refChild) { 32613 //raises 32614 if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) { 32615 var child = newChild.firstChild; 32616 32617 while (child) { 32618 var next = child.nextSibling; 32619 this.insertBefore(child, refChild); 32620 child = next; 32621 } 32622 32623 return newChild; 32624 } 32625 32626 if (this.documentElement == null && newChild.nodeType == ELEMENT_NODE) { 32627 this.documentElement = newChild; 32628 } 32629 32630 return _insertBefore(this, newChild, refChild), newChild.ownerDocument = this, newChild; 32631 }, 32632 removeChild: function removeChild(oldChild) { 32633 if (this.documentElement == oldChild) { 32634 this.documentElement = null; 32635 } 32636 32637 return _removeChild(this, oldChild); 32638 }, 32639 // Introduced in DOM Level 2: 32640 importNode: function importNode(importedNode, deep) { 32641 return _importNode(this, importedNode, deep); 32642 }, 32643 // Introduced in DOM Level 2: 32644 getElementById: function getElementById(id) { 32645 var rtv = null; 32646 32647 _visitNode(this.documentElement, function (node) { 32648 if (node.nodeType == ELEMENT_NODE) { 32649 if (node.getAttribute('id') == id) { 32650 rtv = node; 32651 return true; 32652 } 32653 } 32654 }); 32655 32656 return rtv; 32657 }, 32658 //document factory method: 32659 createElement: function createElement(tagName) { 32660 var node = new Element(); 32661 node.ownerDocument = this; 32662 node.nodeName = tagName; 32663 node.tagName = tagName; 32664 node.childNodes = new NodeList(); 32665 var attrs = node.attributes = new NamedNodeMap(); 32666 attrs._ownerElement = node; 32667 return node; 32668 }, 32669 createDocumentFragment: function createDocumentFragment() { 32670 var node = new DocumentFragment(); 32671 node.ownerDocument = this; 32672 node.childNodes = new NodeList(); 32673 return node; 32674 }, 32675 createTextNode: function createTextNode(data) { 32676 var node = new Text(); 32677 node.ownerDocument = this; 32678 node.appendData(data); 32679 return node; 32680 }, 32681 createComment: function createComment(data) { 32682 var node = new Comment(); 32683 node.ownerDocument = this; 32684 node.appendData(data); 32685 return node; 32686 }, 32687 createCDATASection: function createCDATASection(data) { 32688 var node = new CDATASection(); 32689 node.ownerDocument = this; 32690 node.appendData(data); 32691 return node; 32692 }, 32693 createProcessingInstruction: function createProcessingInstruction(target, data) { 32694 var node = new ProcessingInstruction(); 32695 node.ownerDocument = this; 32696 node.tagName = node.target = target; 32697 node.nodeValue = node.data = data; 32698 return node; 32699 }, 32700 createAttribute: function createAttribute(name) { 32701 var node = new Attr(); 32702 node.ownerDocument = this; 32703 node.name = name; 32704 node.nodeName = name; 32705 node.localName = name; 32706 node.specified = true; 32707 return node; 32708 }, 32709 createEntityReference: function createEntityReference(name) { 32710 var node = new EntityReference(); 32711 node.ownerDocument = this; 32712 node.nodeName = name; 32713 return node; 32714 }, 32715 // Introduced in DOM Level 2: 32716 createElementNS: function createElementNS(namespaceURI, qualifiedName) { 32717 var node = new Element(); 32718 var pl = qualifiedName.split(':'); 32719 var attrs = node.attributes = new NamedNodeMap(); 32720 node.childNodes = new NodeList(); 32721 node.ownerDocument = this; 32722 node.nodeName = qualifiedName; 32723 node.tagName = qualifiedName; 32724 node.namespaceURI = namespaceURI; 32725 32726 if (pl.length == 2) { 32727 node.prefix = pl[0]; 32728 node.localName = pl[1]; 32729 } else { 32730 //el.prefix = null; 32731 node.localName = qualifiedName; 32732 } 32733 32734 attrs._ownerElement = node; 32735 return node; 32736 }, 32737 // Introduced in DOM Level 2: 32738 createAttributeNS: function createAttributeNS(namespaceURI, qualifiedName) { 32739 var node = new Attr(); 32740 var pl = qualifiedName.split(':'); 32741 node.ownerDocument = this; 32742 node.nodeName = qualifiedName; 32743 node.name = qualifiedName; 32744 node.namespaceURI = namespaceURI; 32745 node.specified = true; 32746 32747 if (pl.length == 2) { 32748 node.prefix = pl[0]; 32749 node.localName = pl[1]; 32750 } else { 32751 //el.prefix = null; 32752 node.localName = qualifiedName; 32753 } 32754 32755 return node; 32756 } 32757 }; 32758 32759 _extends(Document, Node); 32760 32761 function Element() { 32762 this._nsMap = {}; 32763 } 32764 Element.prototype = { 32765 nodeType: ELEMENT_NODE, 32766 hasAttribute: function hasAttribute(name) { 32767 return this.getAttributeNode(name) != null; 32768 }, 32769 getAttribute: function getAttribute(name) { 32770 var attr = this.getAttributeNode(name); 32771 return attr && attr.value || ''; 32772 }, 32773 getAttributeNode: function getAttributeNode(name) { 32774 return this.attributes.getNamedItem(name); 32775 }, 32776 setAttribute: function setAttribute(name, value) { 32777 var attr = this.ownerDocument.createAttribute(name); 32778 attr.value = attr.nodeValue = "" + value; 32779 this.setAttributeNode(attr); 32780 }, 32781 removeAttribute: function removeAttribute(name) { 32782 var attr = this.getAttributeNode(name); 32783 attr && this.removeAttributeNode(attr); 32784 }, 32785 //four real opeartion method 32786 appendChild: function appendChild(newChild) { 32787 if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) { 32788 return this.insertBefore(newChild, null); 32789 } else { 32790 return _appendSingleChild(this, newChild); 32791 } 32792 }, 32793 setAttributeNode: function setAttributeNode(newAttr) { 32794 return this.attributes.setNamedItem(newAttr); 32795 }, 32796 setAttributeNodeNS: function setAttributeNodeNS(newAttr) { 32797 return this.attributes.setNamedItemNS(newAttr); 32798 }, 32799 removeAttributeNode: function removeAttributeNode(oldAttr) { 32800 //console.log(this == oldAttr.ownerElement) 32801 return this.attributes.removeNamedItem(oldAttr.nodeName); 32802 }, 32803 //get real attribute name,and remove it by removeAttributeNode 32804 removeAttributeNS: function removeAttributeNS(namespaceURI, localName) { 32805 var old = this.getAttributeNodeNS(namespaceURI, localName); 32806 old && this.removeAttributeNode(old); 32807 }, 32808 hasAttributeNS: function hasAttributeNS(namespaceURI, localName) { 32809 return this.getAttributeNodeNS(namespaceURI, localName) != null; 32810 }, 32811 getAttributeNS: function getAttributeNS(namespaceURI, localName) { 32812 var attr = this.getAttributeNodeNS(namespaceURI, localName); 32813 return attr && attr.value || ''; 32814 }, 32815 setAttributeNS: function setAttributeNS(namespaceURI, qualifiedName, value) { 32816 var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); 32817 attr.value = attr.nodeValue = "" + value; 32818 this.setAttributeNode(attr); 32819 }, 32820 getAttributeNodeNS: function getAttributeNodeNS(namespaceURI, localName) { 32821 return this.attributes.getNamedItemNS(namespaceURI, localName); 32822 }, 32823 getElementsByTagName: function getElementsByTagName(tagName) { 32824 return new LiveNodeList(this, function (base) { 32825 var ls = []; 32826 32827 _visitNode(base, function (node) { 32828 if (node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)) { 32829 ls.push(node); 32830 } 32831 }); 32832 32833 return ls; 32834 }); 32835 }, 32836 getElementsByTagNameNS: function getElementsByTagNameNS(namespaceURI, localName) { 32837 return new LiveNodeList(this, function (base) { 32838 var ls = []; 32839 32840 _visitNode(base, function (node) { 32841 if (node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)) { 32842 ls.push(node); 32843 } 32844 }); 32845 32846 return ls; 32847 }); 32848 } 32849 }; 32850 Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; 32851 Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; 32852 32853 _extends(Element, Node); 32854 32855 function Attr() {} 32856 Attr.prototype.nodeType = ATTRIBUTE_NODE; 32857 32858 _extends(Attr, Node); 32859 32860 function CharacterData() {} 32861 CharacterData.prototype = { 32862 data: '', 32863 substringData: function substringData(offset, count) { 32864 return this.data.substring(offset, offset + count); 32865 }, 32866 appendData: function appendData(text) { 32867 text = this.data + text; 32868 this.nodeValue = this.data = text; 32869 this.length = text.length; 32870 }, 32871 insertData: function insertData(offset, text) { 32872 this.replaceData(offset, 0, text); 32873 }, 32874 appendChild: function appendChild(newChild) { 32875 throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]); 32876 }, 32877 deleteData: function deleteData(offset, count) { 32878 this.replaceData(offset, count, ""); 32879 }, 32880 replaceData: function replaceData(offset, count, text) { 32881 var start = this.data.substring(0, offset); 32882 var end = this.data.substring(offset + count);
vendor: 8,936 bytes, lines 32883-33214
32883 text = start + text + end; 32884 this.nodeValue = this.data = text; 32885 this.length = text.length; 32886 } 32887 }; 32888 32889 _extends(CharacterData, Node); 32890 32891 function Text() {} 32892 Text.prototype = { 32893 nodeName: "#text", 32894 nodeType: TEXT_NODE, 32895 splitText: function splitText(offset) { 32896 var text = this.data; 32897 var newText = text.substring(offset); 32898 text = text.substring(0, offset); 32899 this.data = this.nodeValue = text; 32900 this.length = text.length; 32901 var newNode = this.ownerDocument.createTextNode(newText); 32902 32903 if (this.parentNode) { 32904 this.parentNode.insertBefore(newNode, this.nextSibling); 32905 } 32906 32907 return newNode; 32908 } 32909 }; 32910 32911 _extends(Text, CharacterData); 32912 32913 function Comment() {} 32914 Comment.prototype = { 32915 nodeName: "#comment", 32916 nodeType: COMMENT_NODE 32917 }; 32918 32919 _extends(Comment, CharacterData); 32920 32921 function CDATASection() {} 32922 CDATASection.prototype = { 32923 nodeName: "#cdata-section", 32924 nodeType: CDATA_SECTION_NODE 32925 }; 32926 32927 _extends(CDATASection, CharacterData); 32928 32929 function DocumentType() {} 32930 DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; 32931 32932 _extends(DocumentType, Node); 32933 32934 function Notation() {} 32935 Notation.prototype.nodeType = NOTATION_NODE; 32936 32937 _extends(Notation, Node); 32938 32939 function Entity() {} 32940 Entity.prototype.nodeType = ENTITY_NODE; 32941 32942 _extends(Entity, Node); 32943 32944 function EntityReference() {} 32945 EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; 32946 32947 _extends(EntityReference, Node); 32948 32949 function DocumentFragment() {} 32950 DocumentFragment.prototype.nodeName = "#document-fragment"; 32951 DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; 32952 32953 _extends(DocumentFragment, Node); 32954 32955 function ProcessingInstruction() {} 32956 32957 ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; 32958 32959 _extends(ProcessingInstruction, Node); 32960 32961 function XMLSerializer() {} 32962 32963 XMLSerializer.prototype.serializeToString = function (node, isHtml, nodeFilter) { 32964 return nodeSerializeToString.call(node, isHtml, nodeFilter); 32965 }; 32966 32967 Node.prototype.toString = nodeSerializeToString; 32968 32969 function nodeSerializeToString(isHtml, nodeFilter) { 32970 var buf = []; 32971 var refNode = this.nodeType == 9 ? this.documentElement : this; 32972 var prefix = refNode.prefix; 32973 var uri = refNode.namespaceURI; 32974 32975 if (uri && prefix == null) { 32976 //console.log(prefix) 32977 var prefix = refNode.lookupPrefix(uri); 32978 32979 if (prefix == null) { 32980 //isHTML = true; 32981 var visibleNamespaces = [{ 32982 namespace: uri, 32983 prefix: null 32984 } //{namespace:uri,prefix:''} 32985 ]; 32986 } 32987 } 32988 32989 serializeToString(this, buf, isHtml, nodeFilter, visibleNamespaces); //console.log('###',this.nodeType,uri,prefix,buf.join('')) 32990 32991 return buf.join(''); 32992 } 32993 32994 function needNamespaceDefine(node, isHTML, visibleNamespaces) { 32995 var prefix = node.prefix || ''; 32996 var uri = node.namespaceURI; 32997 32998 if (!prefix && !uri) { 32999 return false; 33000 } 33001 33002 if (prefix === "xml" && uri === "http://www.w3.org/XML/1998/namespace" || uri == 'http://www.w3.org/2000/xmlns/') { 33003 return false; 33004 } 33005 33006 var i = visibleNamespaces.length; //console.log('@@@@',node.tagName,prefix,uri,visibleNamespaces) 33007 33008 while (i--) { 33009 var ns = visibleNamespaces[i]; // get namespace prefix 33010 //console.log(node.nodeType,node.tagName,ns.prefix,prefix) 33011 33012 if (ns.prefix == prefix) { 33013 return ns.namespace != uri; 33014 } 33015 } //console.log(isHTML,uri,prefix=='') 33016 //if(isHTML && prefix ==null && uri == 'http://www.w3.org/1999/xhtml'){ 33017 // return false; 33018 //} 33019 //node.flag = '11111' 33020 //console.error(3,true,node.flag,node.prefix,node.namespaceURI) 33021 33022 33023 return true; 33024 } 33025 33026 function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces) { 33027 if (nodeFilter) { 33028 node = nodeFilter(node); 33029 33030 if (node) { 33031 if (typeof node == 'string') { 33032 buf.push(node); 33033 return; 33034 } 33035 } else { 33036 return; 33037 } //buf.sort.apply(attrs, attributeSorter); 33038 33039 } 33040 33041 switch (node.nodeType) { 33042 case ELEMENT_NODE: 33043 if (!visibleNamespaces) visibleNamespaces = []; 33044 var startVisibleNamespaces = visibleNamespaces.length; 33045 var attrs = node.attributes; 33046 var len = attrs.length; 33047 var child = node.firstChild; 33048 var nodeName = node.tagName; 33049 isHTML = htmlns === node.namespaceURI || isHTML; 33050 buf.push('<', nodeName); 33051 33052 for (var i = 0; i < len; i++) { 33053 // add namespaces for attributes 33054 var attr = attrs.item(i); 33055 33056 if (attr.prefix == 'xmlns') { 33057 visibleNamespaces.push({ 33058 prefix: attr.localName, 33059 namespace: attr.value 33060 }); 33061 } else if (attr.nodeName == 'xmlns') { 33062 visibleNamespaces.push({ 33063 prefix: '', 33064 namespace: attr.value 33065 }); 33066 } 33067 } 33068 33069 for (var i = 0; i < len; i++) { 33070 var attr = attrs.item(i); 33071 33072 if (needNamespaceDefine(attr, isHTML, visibleNamespaces)) { 33073 var prefix = attr.prefix || ''; 33074 var uri = attr.namespaceURI; 33075 var ns = prefix ? ' xmlns:' + prefix : " xmlns"; 33076 buf.push(ns, '="', uri, '"'); 33077 visibleNamespaces.push({ 33078 prefix: prefix, 33079 namespace: uri 33080 }); 33081 } 33082 33083 serializeToString(attr, buf, isHTML, nodeFilter, visibleNamespaces); 33084 } // add namespace for current node 33085 33086 33087 if (needNamespaceDefine(node, isHTML, visibleNamespaces)) { 33088 var prefix = node.prefix || ''; 33089 var uri = node.namespaceURI; 33090 var ns = prefix ? ' xmlns:' + prefix : " xmlns"; 33091 buf.push(ns, '="', uri, '"'); 33092 visibleNamespaces.push({ 33093 prefix: prefix, 33094 namespace: uri 33095 }); 33096 } 33097 33098 if (child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) { 33099 buf.push('>'); //if is cdata child node 33100 33101 if (isHTML && /^script$/i.test(nodeName)) { 33102 while (child) { 33103 if (child.data) { 33104 buf.push(child.data); 33105 } else { 33106 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces); 33107 } 33108 33109 child = child.nextSibling; 33110 } 33111 } else { 33112 while (child) { 33113 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces); 33114 child = child.nextSibling; 33115 } 33116 } 33117 33118 buf.push('</', nodeName, '>'); 33119 } else { 33120 buf.push('/>'); 33121 } // remove added visible namespaces 33122 //visibleNamespaces.length = startVisibleNamespaces; 33123 33124 33125 return; 33126 33127 case DOCUMENT_NODE: 33128 case DOCUMENT_FRAGMENT_NODE: 33129 var child = node.firstChild; 33130 33131 while (child) { 33132 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces); 33133 child = child.nextSibling; 33134 } 33135 33136 return; 33137 33138 case ATTRIBUTE_NODE: 33139 return buf.push(' ', node.name, '="', node.value.replace(/[<&"]/g, _xmlEncoder), '"'); 33140 33141 case TEXT_NODE: 33142 return buf.push(node.data.replace(/[<&]/g, _xmlEncoder)); 33143 33144 case CDATA_SECTION_NODE: 33145 return buf.push('<![CDATA[', node.data, ']]>'); 33146 33147 case COMMENT_NODE: 33148 return buf.push("<!--", node.data, "-->"); 33149 33150 case DOCUMENT_TYPE_NODE: 33151 var pubid = node.publicId; 33152 var sysid = node.systemId; 33153 buf.push('<!DOCTYPE ', node.name); 33154 33155 if (pubid) { 33156 buf.push(' PUBLIC "', pubid); 33157 33158 if (sysid && sysid != '.') { 33159 buf.push('" "', sysid); 33160 } 33161 33162 buf.push('">'); 33163 } else if (sysid && sysid != '.') { 33164 buf.push(' SYSTEM "', sysid, '">'); 33165 } else { 33166 var sub = node.internalSubset; 33167 33168 if (sub) { 33169 buf.push(" [", sub, "]"); 33170 } 33171 33172 buf.push(">"); 33173 } 33174 33175 return; 33176 33177 case PROCESSING_INSTRUCTION_NODE: 33178 return buf.push("<?", node.target, " ", node.data, "?>"); 33179 33180 case ENTITY_REFERENCE_NODE: 33181 return buf.push('&', node.nodeName, ';'); 33182 //case ENTITY_NODE: 33183 //case NOTATION_NODE: 33184 33185 default: 33186 buf.push('??', node.nodeName); 33187 } 33188 } 33189 33190 function _importNode(doc, node, deep) { 33191 var node2; 33192 33193 switch (node.nodeType) { 33194 case ELEMENT_NODE: 33195 node2 = node.cloneNode(false); 33196 node2.ownerDocument = doc; 33197 //var attrs = node2.attributes; 33198 //var len = attrs.length; 33199 //for(var i=0;i<len;i++){ 33200 //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep)); 33201 //} 33202 33203 case DOCUMENT_FRAGMENT_NODE: 33204 break; 33205 33206 case ATTRIBUTE_NODE: 33207 deep = true; 33208 break; 33209 //case ENTITY_REFERENCE_NODE: 33210 //case PROCESSING_INSTRUCTION_NODE: 33211 ////case TEXT_NODE: 33212 //case CDATA_SECTION_NODE: 33213 //case COMMENT_NODE: 33214 // deep = false;
vendor: 6,277 bytes, lines 33215-33467
33215 // break; 33216 //case DOCUMENT_NODE: 33217 //case DOCUMENT_TYPE_NODE: 33218 //cannot be imported. 33219 //case ENTITY_NODE: 33220 //case NOTATION_NODEï¼ 33221 //can not hit in level3 33222 //default:throw e; 33223 } 33224 33225 if (!node2) { 33226 node2 = node.cloneNode(false); //false 33227 } 33228 33229 node2.ownerDocument = doc; 33230 node2.parentNode = null; 33231 33232 if (deep) { 33233 var child = node.firstChild; 33234 33235 while (child) { 33236 node2.appendChild(_importNode(doc, child, deep)); 33237 child = child.nextSibling; 33238 } 33239 } 33240 33241 return node2; 33242 } // 33243 //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1, 33244 // attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,}; 33245 33246 33247 function _cloneNode(doc, node, deep) { 33248 var node2 = new node.constructor(); 33249 33250 for (var n in node) { 33251 var v = node[n]; 33252 33253 if (typeof v != 'object') { 33254 if (v != node2[n]) { 33255 node2[n] = v; 33256 } 33257 } 33258 } 33259 33260 if (node.childNodes) { 33261 node2.childNodes = new NodeList(); 33262 } 33263 33264 node2.ownerDocument = doc; 33265 33266 switch (node2.nodeType) { 33267 case ELEMENT_NODE: 33268 var attrs = node.attributes; 33269 var attrs2 = node2.attributes = new NamedNodeMap(); 33270 var len = attrs.length; 33271 attrs2._ownerElement = node2; 33272 33273 for (var i = 0; i < len; i++) { 33274 node2.setAttributeNode(_cloneNode(doc, attrs.item(i), true)); 33275 } 33276 33277 break; 33278 33279 case ATTRIBUTE_NODE: 33280 deep = true; 33281 } 33282 33283 if (deep) { 33284 var child = node.firstChild; 33285 33286 while (child) { 33287 node2.appendChild(_cloneNode(doc, child, deep)); 33288 child = child.nextSibling; 33289 } 33290 } 33291 33292 return node2; 33293 } 33294 33295 function __set__(object, key, value) { 33296 object[key] = value; 33297 } //do dynamic 33298 33299 33300 try { 33301 if (Object.defineProperty) { 33302 var getTextContent = function getTextContent(node) { 33303 switch (node.nodeType) { 33304 case ELEMENT_NODE: 33305 case DOCUMENT_FRAGMENT_NODE: 33306 var buf = []; 33307 node = node.firstChild; 33308 33309 while (node) { 33310 if (node.nodeType !== 7 && node.nodeType !== 8) { 33311 buf.push(getTextContent(node)); 33312 } 33313 33314 node = node.nextSibling; 33315 } 33316 33317 return buf.join(''); 33318 33319 default: 33320 return node.nodeValue; 33321 } 33322 }; 33323 33324 Object.defineProperty(LiveNodeList.prototype, 'length', { 33325 get: function get() { 33326 _updateLiveList(this); 33327 33328 return this.$$length; 33329 } 33330 }); 33331 Object.defineProperty(Node.prototype, 'textContent', { 33332 get: function get() { 33333 return getTextContent(this); 33334 }, 33335 set: function set(data) { 33336 switch (this.nodeType) { 33337 case ELEMENT_NODE: 33338 case DOCUMENT_FRAGMENT_NODE: 33339 while (this.firstChild) { 33340 this.removeChild(this.firstChild); 33341 } 33342 33343 if (data || String(data)) { 33344 this.appendChild(this.ownerDocument.createTextNode(data)); 33345 } 33346 33347 break; 33348 33349 default: 33350 //TODO: 33351 this.data = data; 33352 this.value = data; 33353 this.nodeValue = data; 33354 } 33355 } 33356 }); 33357 33358 __set__ = function __set__(object, key, value) { 33359 //console.log(value) 33360 object['$$' + key] = value; 33361 }; 33362 } 33363 } catch (e) {} //ie8 33364 //if(typeof require == 'function'){ 33365 33366 33367 var DOMImplementation_1 = DOMImplementation; 33368 var XMLSerializer_1 = XMLSerializer; //} 33369 33370 var dom = { 33371 DOMImplementation: DOMImplementation_1, 33372 XMLSerializer: XMLSerializer_1 33373 }; 33374 33375 var domParser = createCommonjsModule(function (module, exports) { 33376 function DOMParser(options) { 33377 this.options = options || { 33378 locator: {} 33379 }; 33380 } 33381 33382 DOMParser.prototype.parseFromString = function (source, mimeType) { 33383 var options = this.options; 33384 var sax = new XMLReader(); 33385 var domBuilder = options.domBuilder || new DOMHandler(); //contentHandler and LexicalHandler 33386 33387 var errorHandler = options.errorHandler; 33388 var locator = options.locator; 33389 var defaultNSMap = options.xmlns || {}; 33390 var entityMap = { 33391 'lt': '<', 33392 'gt': '>', 33393 'amp': '&', 33394 'quot': '"', 33395 'apos': "'" 33396 }; 33397 33398 if (locator) { 33399 domBuilder.setDocumentLocator(locator); 33400 } 33401 33402 sax.errorHandler = buildErrorHandler(errorHandler, domBuilder, locator); 33403 sax.domBuilder = options.domBuilder || domBuilder; 33404 33405 if (/\/x?html?$/.test(mimeType)) { 33406 entityMap.nbsp = '\xa0'; 33407 entityMap.copy = '\xa9'; 33408 defaultNSMap[''] = 'http://www.w3.org/1999/xhtml'; 33409 } 33410 33411 defaultNSMap.xml = defaultNSMap.xml || 'http://www.w3.org/XML/1998/namespace'; 33412 33413 if (source) { 33414 sax.parse(source, defaultNSMap, entityMap); 33415 } else { 33416 sax.errorHandler.error("invalid doc source"); 33417 } 33418 33419 return domBuilder.doc; 33420 }; 33421 33422 function buildErrorHandler(errorImpl, domBuilder, locator) { 33423 if (!errorImpl) { 33424 if (domBuilder instanceof DOMHandler) { 33425 return domBuilder; 33426 } 33427 33428 errorImpl = domBuilder; 33429 } 33430 33431 var errorHandler = {}; 33432 var isCallback = errorImpl instanceof Function; 33433 locator = locator || {}; 33434 33435 function build(key) { 33436 var fn = errorImpl[key]; 33437 33438 if (!fn && isCallback) { 33439 fn = errorImpl.length == 2 ? function (msg) { 33440 errorImpl(key, msg); 33441 } : errorImpl; 33442 } 33443 33444 errorHandler[key] = fn && function (msg) { 33445 fn('[xmldom ' + key + ']\t' + msg + _locator(locator)); 33446 } || function () {}; 33447 } 33448 33449 build('warning'); 33450 build('error'); 33451 build('fatalError'); 33452 return errorHandler; 33453 } //console.log('#\n\n\n\n\n\n\n####') 33454 33455 /** 33456 * +ContentHandler+ErrorHandler 33457 * +LexicalHandler+EntityResolver2 33458 * -DeclHandler-DTDHandler 33459 * 33460 * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler 33461 * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 33462 * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html 33463 */ 33464 33465 33466 function DOMHandler() { 33467 this.cdata = false;
33468 } 33469 33470 function position(locator, node) { 33471 node.lineNumber = locator.lineNumber; 33472 node.columnNumber = locator.columnNumber; 33473 } 33474 /** 33475 * @see org.xml.sax.ContentHandler#startDocument 33476 * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html 33477 */ 33478 33479 33480 DOMHandler.prototype = { 33481 startDocument: function startDocument() { 33482 this.doc = new DOMImplementation().createDocument(null, null, null); 33483 33484 if (this.locator) { 33485 this.doc.documentURI = this.locator.systemId; 33486 } 33487 }, 33488 startElement: function startElement(namespaceURI, localName, qName, attrs) { 33489 var doc = this.doc; 33490 var el = doc.createElementNS(namespaceURI, qName || localName); 33491 var len = attrs.length; 33492 appendElement(this, el); 33493 this.currentElement = el; 33494 this.locator && position(this.locator, el); 33495 33496 for (var i = 0; i < len; i++) { 33497 var namespaceURI = attrs.getURI(i); 33498 var value = attrs.getValue(i); 33499 var qName = attrs.getQName(i); 33500 var attr = doc.createAttributeNS(namespaceURI, qName); 33501 this.locator && position(attrs.getLocator(i), attr); 33502 attr.value = attr.nodeValue = value; 33503 el.setAttributeNode(attr); 33504 } 33505 }, 33506 endElement: function endElement(namespaceURI, localName, qName) { 33507 var current = this.currentElement; 33508 var tagName = current.tagName; 33509 this.currentElement = current.parentNode; 33510 }, 33511 startPrefixMapping: function startPrefixMapping(prefix, uri) {}, 33512 endPrefixMapping: function endPrefixMapping(prefix) {}, 33513 processingInstruction: function processingInstruction(target, data) { 33514 var ins = this.doc.createProcessingInstruction(target, data); 33515 this.locator && position(this.locator, ins); 33516 appendElement(this, ins); 33517 }, 33518 ignorableWhitespace: function ignorableWhitespace(ch, start, length) {}, 33519 characters: function characters(chars, start, length) { 33520 chars = _toString.apply(this, arguments); //console.log(chars) 33521 33522 if (chars) { 33523 if (this.cdata) { 33524 var charNode = this.doc.createCDATASection(chars); 33525 } else { 33526 var charNode = this.doc.createTextNode(chars); 33527 } 33528 33529 if (this.currentElement) { 33530 this.currentElement.appendChild(charNode); 33531 } else if (/^\s*$/.test(chars)) { 33532 this.doc.appendChild(charNode); //process xml 33533 } 33534 33535 this.locator && position(this.locator, charNode); 33536 } 33537 }, 33538 skippedEntity: function skippedEntity(name) {}, 33539 endDocument: function endDocument() { 33540 this.doc.normalize(); 33541 }, 33542 setDocumentLocator: function setDocumentLocator(locator) { 33543 if (this.locator = locator) { 33544 // && !('lineNumber' in locator)){ 33545 locator.lineNumber = 0; 33546 } 33547 }, 33548 //LexicalHandler 33549 comment: function comment(chars, start, length) { 33550 chars = _toString.apply(this, arguments); 33551 var comm = this.doc.createComment(chars); 33552 this.locator && position(this.locator, comm); 33553 appendElement(this, comm); 33554 }, 33555 startCDATA: function startCDATA() { 33556 //used in characters() methods 33557 this.cdata = true; 33558 }, 33559 endCDATA: function endCDATA() { 33560 this.cdata = false; 33561 }, 33562 startDTD: function startDTD(name, publicId, systemId) { 33563 var impl = this.doc.implementation; 33564 33565 if (impl && impl.createDocumentType) { 33566 var dt = impl.createDocumentType(name, publicId, systemId); 33567 this.locator && position(this.locator, dt); 33568 appendElement(this, dt); 33569 } 33570 }, 33571 33572 /** 33573 * @see org.xml.sax.ErrorHandler 33574 * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html 33575 */ 33576 warning: function warning(error) { 33577 console.warn('[xmldom warning]\t' + error, _locator(this.locator)); 33578 }, 33579 error: function error(_error) { 33580 console.error('[xmldom error]\t' + _error, _locator(this.locator)); 33581 }, 33582 fatalError: function fatalError(error) { 33583 console.error('[xmldom fatalError]\t' + error, _locator(this.locator)); 33584 throw error; 33585 } 33586 }; 33587 33588 function _locator(l) { 33589 if (l) { 33590 return '\n@' + (l.systemId || '') + '#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']'; 33591 } 33592 } 33593 33594 function _toString(chars, start, length) { 33595 if (typeof chars == 'string') { 33596 return chars.substr(start, length); 33597 } else { 33598 //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") 33599 if (chars.length >= start + length || start) { 33600 return new java.lang.String(chars, start, length) + ''; 33601 } 33602 33603 return chars; 33604 } 33605 } 33606 /* 33607 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html 33608 * used method of org.xml.sax.ext.LexicalHandler: 33609 * #comment(chars, start, length) 33610 * #startCDATA() 33611 * #endCDATA() 33612 * #startDTD(name, publicId, systemId) 33613 * 33614 * 33615 * IGNORED method of org.xml.sax.ext.LexicalHandler: 33616 * #endDTD() 33617 * #startEntity(name) 33618 * #endEntity(name) 33619 * 33620 * 33621 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html 33622 * IGNORED method of org.xml.sax.ext.DeclHandler 33623 * #attributeDecl(eName, aName, type, mode, value) 33624 * #elementDecl(name, model) 33625 * #externalEntityDecl(name, publicId, systemId) 33626 * #internalEntityDecl(name, value) 33627 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html 33628 * IGNORED method of org.xml.sax.EntityResolver2 33629 * #resolveEntity(String name,String publicId,String baseURI,String systemId) 33630 * #resolveEntity(publicId, systemId) 33631 * #getExternalSubset(name, baseURI) 33632 * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html 33633 * IGNORED method of org.xml.sax.DTDHandler 33634 * #notationDecl(name, publicId, systemId) {}; 33635 * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; 33636 */ 33637 33638 33639 "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g, function (key) { 33640 DOMHandler.prototype[key] = function () { 33641 return null; 33642 }; 33643 }); 33644 /* 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 */ 33645 33646 function appendElement(hander, node) { 33647 if (!hander.currentElement) { 33648 hander.doc.appendChild(node); 33649 } else { 33650 hander.currentElement.appendChild(node); 33651 } 33652 } //appendChild and setAttributeNS are preformance key 33653 //if(typeof require == 'function'){ 33654 33655 33656 var XMLReader = sax.XMLReader; 33657 var DOMImplementation = exports.DOMImplementation = dom.DOMImplementation; 33658 exports.XMLSerializer = dom.XMLSerializer; 33659 exports.DOMParser = DOMParser; //} 33660 }); 33661 var domParser_1 = domParser.DOMImplementation; 33662 var domParser_2 = domParser.XMLSerializer; 33663 var domParser_3 = domParser.DOMParser; 33664
vendor: 22,867 bytes, lines 33665-34357
33665 /*! @name mpd-parser @version 0.14.0 @license Apache-2.0 */ 33666 33667 var isObject$1 = function isObject(obj) { 33668 return !!obj && typeof obj === 'object'; 33669 }; 33670 33671 var merge = function merge() { 33672 for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) { 33673 objects[_key] = arguments[_key]; 33674 } 33675 33676 return objects.reduce(function (result, source) { 33677 Object.keys(source).forEach(function (key) { 33678 if (Array.isArray(result[key]) && Array.isArray(source[key])) { 33679 result[key] = result[key].concat(source[key]); 33680 } else if (isObject$1(result[key]) && isObject$1(source[key])) { 33681 result[key] = merge(result[key], source[key]); 33682 } else { 33683 result[key] = source[key]; 33684 } 33685 }); 33686 return result; 33687 }, {}); 33688 }; 33689 33690 var values = function values(o) { 33691 return Object.keys(o).map(function (k) { 33692 return o[k]; 33693 }); 33694 }; 33695 33696 var range = function range(start, end) { 33697 var result = []; 33698 33699 for (var i = start; i < end; i++) { 33700 result.push(i); 33701 } 33702 33703 return result; 33704 }; 33705 33706 var flatten = function flatten(lists) { 33707 return lists.reduce(function (x, y) { 33708 return x.concat(y); 33709 }, []); 33710 }; 33711 33712 var from = function from(list) { 33713 if (!list.length) { 33714 return []; 33715 } 33716 33717 var result = []; 33718 33719 for (var i = 0; i < list.length; i++) { 33720 result.push(list[i]); 33721 } 33722 33723 return result; 33724 }; 33725 33726 var findIndexes = function findIndexes(l, key) { 33727 return l.reduce(function (a, e, i) { 33728 if (e[key]) { 33729 a.push(i); 33730 } 33731 33732 return a; 33733 }, []); 33734 }; 33735 33736 var errors = { 33737 INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD', 33738 DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST', 33739 DASH_INVALID_XML: 'DASH_INVALID_XML', 33740 NO_BASE_URL: 'NO_BASE_URL', 33741 MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', 33742 SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED', 33743 UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME' 33744 }; 33745 /** 33746 * @typedef {Object} SingleUri 33747 * @property {string} uri - relative location of segment 33748 * @property {string} resolvedUri - resolved location of segment 33749 * @property {Object} byterange - Object containing information on how to make byte range 33750 * requests following byte-range-spec per RFC2616. 33751 * @property {String} byterange.length - length of range request 33752 * @property {String} byterange.offset - byte offset of range request 33753 * 33754 * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 33755 */ 33756 33757 /** 33758 * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object 33759 * that conforms to how m3u8-parser is structured 33760 * 33761 * @see https://github.com/videojs/m3u8-parser 33762 * 33763 * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes 33764 * @param {string} source - source url for segment 33765 * @param {string} range - optional range used for range calls, 33766 * follows RFC 2616, Clause 14.35.1 33767 * @return {SingleUri} full segment information transformed into a format similar 33768 * to m3u8-parser 33769 */ 33770 33771 var urlTypeToSegment = function urlTypeToSegment(_ref) { 33772 var _ref$baseUrl = _ref.baseUrl, 33773 baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl, 33774 _ref$source = _ref.source, 33775 source = _ref$source === void 0 ? '' : _ref$source, 33776 _ref$range = _ref.range, 33777 range = _ref$range === void 0 ? '' : _ref$range, 33778 _ref$indexRange = _ref.indexRange, 33779 indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange; 33780 var segment = { 33781 uri: source, 33782 resolvedUri: resolveUrl_1(baseUrl || '', source) 33783 }; 33784 33785 if (range || indexRange) { 33786 var rangeStr = range ? range : indexRange; 33787 var ranges = rangeStr.split('-'); 33788 var startRange = parseInt(ranges[0], 10); 33789 var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to 33790 // RFC 2616, Clause 14.35.1 33791 33792 segment.byterange = { 33793 length: endRange - startRange + 1, 33794 offset: startRange 33795 }; 33796 } 33797 33798 return segment; 33799 }; 33800 33801 var byteRangeToString = function byteRangeToString(byterange) { 33802 // `endRange` is one less than `offset + length` because the HTTP range 33803 // header uses inclusive ranges 33804 var endRange = byterange.offset + byterange.length - 1; 33805 return byterange.offset + "-" + endRange; 33806 }; 33807 /** 33808 * Functions for calculating the range of available segments in static and dynamic 33809 * manifests. 33810 */ 33811 33812 33813 var segmentRange = { 33814 /** 33815 * Returns the entire range of available segments for a static MPD 33816 * 33817 * @param {Object} attributes 33818 * Inheritied MPD attributes 33819 * @return {{ start: number, end: number }} 33820 * The start and end numbers for available segments 33821 */ 33822 "static": function _static(attributes) { 33823 var duration = attributes.duration, 33824 _attributes$timescale = attributes.timescale, 33825 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 33826 sourceDuration = attributes.sourceDuration; 33827 return { 33828 start: 0, 33829 end: Math.ceil(sourceDuration / (duration / timescale)) 33830 }; 33831 }, 33832 33833 /** 33834 * Returns the current live window range of available segments for a dynamic MPD 33835 * 33836 * @param {Object} attributes 33837 * Inheritied MPD attributes 33838 * @return {{ start: number, end: number }} 33839 * The start and end numbers for available segments 33840 */ 33841 dynamic: function dynamic(attributes) { 33842 var NOW = attributes.NOW, 33843 clientOffset = attributes.clientOffset, 33844 availabilityStartTime = attributes.availabilityStartTime, 33845 _attributes$timescale2 = attributes.timescale, 33846 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 33847 duration = attributes.duration, 33848 _attributes$start = attributes.start, 33849 start = _attributes$start === void 0 ? 0 : _attributes$start, 33850 _attributes$minimumUp = attributes.minimumUpdatePeriod, 33851 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp, 33852 _attributes$timeShift = attributes.timeShiftBufferDepth, 33853 timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift; 33854 var now = (NOW + clientOffset) / 1000; 33855 var periodStartWC = availabilityStartTime + start; 33856 var periodEndWC = now + minimumUpdatePeriod; 33857 var periodDuration = periodEndWC - periodStartWC; 33858 var segmentCount = Math.ceil(periodDuration * timescale / duration); 33859 var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration); 33860 var availableEnd = Math.floor((now - periodStartWC) * timescale / duration); 33861 return { 33862 start: Math.max(0, availableStart), 33863 end: Math.min(segmentCount, availableEnd) 33864 }; 33865 } 33866 }; 33867 /** 33868 * Maps a range of numbers to objects with information needed to build the corresponding 33869 * segment list 33870 * 33871 * @name toSegmentsCallback 33872 * @function 33873 * @param {number} number 33874 * Number of the segment 33875 * @param {number} index 33876 * Index of the number in the range list 33877 * @return {{ number: Number, duration: Number, timeline: Number, time: Number }} 33878 * Object with segment timing and duration info 33879 */ 33880 33881 /** 33882 * Returns a callback for Array.prototype.map for mapping a range of numbers to 33883 * information needed to build the segment list. 33884 * 33885 * @param {Object} attributes 33886 * Inherited MPD attributes 33887 * @return {toSegmentsCallback} 33888 * Callback map function 33889 */ 33890 33891 var toSegments = function toSegments(attributes) { 33892 return function (number, index) { 33893 var duration = attributes.duration, 33894 _attributes$timescale3 = attributes.timescale, 33895 timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3, 33896 periodIndex = attributes.periodIndex, 33897 _attributes$startNumb = attributes.startNumber, 33898 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb; 33899 return { 33900 number: startNumber + number, 33901 duration: duration / timescale, 33902 timeline: periodIndex, 33903 time: index * duration 33904 }; 33905 }; 33906 }; 33907 /** 33908 * Returns a list of objects containing segment timing and duration info used for 33909 * building the list of segments. This uses the @duration attribute specified 33910 * in the MPD manifest to derive the range of segments. 33911 * 33912 * @param {Object} attributes 33913 * Inherited MPD attributes 33914 * @return {{number: number, duration: number, time: number, timeline: number}[]} 33915 * List of Objects with segment timing and duration info 33916 */ 33917 33918 33919 var parseByDuration = function parseByDuration(attributes) { 33920 var _attributes$type = attributes.type, 33921 type = _attributes$type === void 0 ? 'static' : _attributes$type, 33922 duration = attributes.duration, 33923 _attributes$timescale4 = attributes.timescale, 33924 timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4, 33925 sourceDuration = attributes.sourceDuration; 33926 33927 var _segmentRange$type = segmentRange[type](attributes), 33928 start = _segmentRange$type.start, 33929 end = _segmentRange$type.end; 33930 33931 var segments = range(start, end).map(toSegments(attributes)); 33932 33933 if (type === 'static') { 33934 var index = segments.length - 1; // final segment may be less than full segment duration 33935 33936 segments[index].duration = sourceDuration - duration / timescale * index; 33937 } 33938 33939 return segments; 33940 }; 33941 /** 33942 * Translates SegmentBase into a set of segments. 33943 * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 33944 * node should be translated into segment. 33945 * 33946 * @param {Object} attributes 33947 * Object containing all inherited attributes from parent elements with attribute 33948 * names as keys 33949 * @return {Object.<Array>} list of segments 33950 */ 33951 33952 33953 var segmentsFromBase = function segmentsFromBase(attributes) { 33954 var baseUrl = attributes.baseUrl, 33955 _attributes$initializ = attributes.initialization, 33956 initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ, 33957 sourceDuration = attributes.sourceDuration, 33958 _attributes$indexRang = attributes.indexRange, 33959 indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang, 33960 duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) 33961 33962 if (!baseUrl) { 33963 throw new Error(errors.NO_BASE_URL); 33964 } 33965 33966 var initSegment = urlTypeToSegment({ 33967 baseUrl: baseUrl, 33968 source: initialization.sourceURL, 33969 range: initialization.range 33970 }); 33971 var segment = urlTypeToSegment({ 33972 baseUrl: baseUrl, 33973 source: baseUrl, 33974 indexRange: indexRange 33975 }); 33976 segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source 33977 // (since SegmentBase is only for one total segment) 33978 33979 if (duration) { 33980 var segmentTimeInfo = parseByDuration(attributes); 33981 33982 if (segmentTimeInfo.length) { 33983 segment.duration = segmentTimeInfo[0].duration; 33984 segment.timeline = segmentTimeInfo[0].timeline; 33985 } 33986 } else if (sourceDuration) { 33987 segment.duration = sourceDuration; 33988 segment.timeline = 0; 33989 } // This is used for mediaSequence 33990 33991 33992 segment.number = 0; 33993 return [segment]; 33994 }; 33995 /** 33996 * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist 33997 * according to the sidx information given. 33998 * 33999 * playlist.sidx has metadadata about the sidx where-as the sidx param 34000 * is the parsed sidx box itself. 34001 * 34002 * @param {Object} playlist the playlist to update the sidx information for 34003 * @param {Object} sidx the parsed sidx box 34004 * @return {Object} the playlist object with the updated sidx information 34005 */ 34006 34007 34008 var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) { 34009 // Retain init segment information 34010 var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing 34011 34012 var sourceDuration = playlist.sidx.duration; // Retain source timeline 34013 34014 var timeline = playlist.timeline || 0; 34015 var sidxByteRange = playlist.sidx.byterange; 34016 var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx 34017 34018 var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes 34019 34020 var mediaReferences = sidx.references.filter(function (r) { 34021 return r.referenceType !== 1; 34022 }); 34023 var segments = []; // firstOffset is the offset from the end of the sidx box 34024 34025 var startIndex = sidxEnd + sidx.firstOffset; 34026 34027 for (var i = 0; i < mediaReferences.length; i++) { 34028 var reference = sidx.references[i]; // size of the referenced (sub)segment 34029 34030 var size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale 34031 // this will be converted to seconds when generating segments 34032 34033 var duration = reference.subsegmentDuration; // should be an inclusive range 34034 34035 var endIndex = startIndex + size - 1; 34036 var indexRange = startIndex + "-" + endIndex; 34037 var attributes = { 34038 baseUrl: baseUrl, 34039 timescale: timescale, 34040 timeline: timeline, 34041 // this is used in parseByDuration 34042 periodIndex: timeline, 34043 duration: duration, 34044 sourceDuration: sourceDuration, 34045 indexRange: indexRange 34046 }; 34047 var segment = segmentsFromBase(attributes)[0]; 34048 34049 if (initSegment) { 34050 segment.map = initSegment; 34051 } 34052 34053 segments.push(segment); 34054 startIndex += size; 34055 } 34056 34057 playlist.segments = segments; 34058 return playlist; 34059 }; 34060 34061 var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) { 34062 var mergedPlaylists = values(playlists.reduce(function (acc, playlist) { 34063 // assuming playlist IDs are the same across periods 34064 // TODO: handle multiperiod where representation sets are not the same 34065 // across periods 34066 var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first 34067 34068 if (acc[name]) { 34069 var _acc$name$segments; // first segment of subsequent periods signal a discontinuity 34070 34071 34072 if (playlist.segments[0]) { 34073 playlist.segments[0].discontinuity = true; 34074 } 34075 34076 (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content 34077 // has the same contentProtection 34078 34079 34080 if (playlist.attributes.contentProtection) { 34081 acc[name].attributes.contentProtection = playlist.attributes.contentProtection; 34082 } 34083 } else { 34084 // first Period 34085 acc[name] = playlist; 34086 } 34087 34088 return acc; 34089 }, {})); 34090 return mergedPlaylists.map(function (playlist) { 34091 playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity'); 34092 return playlist; 34093 }); 34094 }; 34095 34096 var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) { 34097 if (sidxMapping === void 0) { 34098 sidxMapping = {}; 34099 } 34100 34101 if (!Object.keys(sidxMapping).length) { 34102 return playlists; 34103 } 34104 34105 for (var i in playlists) { 34106 var playlist = playlists[i]; 34107 34108 if (!playlist.sidx) { 34109 continue; 34110 } 34111 34112 var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange); 34113 var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx; 34114 34115 if (playlist.sidx && sidxMatch) { 34116 addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri); 34117 } 34118 } 34119 34120 return playlists; 34121 }; 34122 34123 var formatAudioPlaylist = function formatAudioPlaylist(_ref) { 34124 var _attributes; 34125 34126 var attributes = _ref.attributes, 34127 segments = _ref.segments, 34128 sidx = _ref.sidx; 34129 var playlist = { 34130 attributes: (_attributes = { 34131 NAME: attributes.id, 34132 BANDWIDTH: attributes.bandwidth, 34133 CODECS: attributes.codecs 34134 }, _attributes['PROGRAM-ID'] = 1, _attributes), 34135 uri: '', 34136 endList: (attributes.type || 'static') === 'static', 34137 timeline: attributes.periodIndex, 34138 resolvedUri: '', 34139 targetDuration: attributes.duration, 34140 segments: segments, 34141 mediaSequence: segments.length ? segments[0].number : 1 34142 }; 34143 34144 if (attributes.contentProtection) { 34145 playlist.contentProtection = attributes.contentProtection; 34146 } 34147 34148 if (sidx) { 34149 playlist.sidx = sidx; 34150 } 34151 34152 return playlist; 34153 }; 34154 34155 var formatVttPlaylist = function formatVttPlaylist(_ref2) { 34156 var _m3u8Attributes; 34157 34158 var attributes = _ref2.attributes, 34159 segments = _ref2.segments; 34160 34161 if (typeof segments === 'undefined') { 34162 // vtt tracks may use single file in BaseURL 34163 segments = [{ 34164 uri: attributes.baseUrl, 34165 timeline: attributes.periodIndex, 34166 resolvedUri: attributes.baseUrl || '', 34167 duration: attributes.sourceDuration, 34168 number: 0 34169 }]; // targetDuration should be the same duration as the only segment 34170 34171 attributes.duration = attributes.sourceDuration; 34172 } 34173 34174 var m3u8Attributes = (_m3u8Attributes = { 34175 NAME: attributes.id, 34176 BANDWIDTH: attributes.bandwidth 34177 }, _m3u8Attributes['PROGRAM-ID'] = 1, _m3u8Attributes); 34178 34179 if (attributes.codecs) { 34180 m3u8Attributes.CODECS = attributes.codecs; 34181 } 34182 34183 return { 34184 attributes: m3u8Attributes, 34185 uri: '', 34186 endList: (attributes.type || 'static') === 'static', 34187 timeline: attributes.periodIndex, 34188 resolvedUri: attributes.baseUrl || '', 34189 targetDuration: attributes.duration, 34190 segments: segments, 34191 mediaSequence: segments.length ? segments[0].number : 1 34192 }; 34193 }; 34194 34195 var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) { 34196 if (sidxMapping === void 0) { 34197 sidxMapping = {}; 34198 } 34199 34200 var mainPlaylist; 34201 var formattedPlaylists = playlists.reduce(function (a, playlist) { 34202 var role = playlist.attributes.role && playlist.attributes.role.value || ''; 34203 var language = playlist.attributes.lang || ''; 34204 var label = 'main'; 34205 34206 if (language) { 34207 var roleLabel = role ? " (" + role + ")" : ''; 34208 label = "" + playlist.attributes.lang + roleLabel; 34209 } // skip if we already have the highest quality audio for a language 34210 34211 34212 if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) { 34213 return a; 34214 } 34215 34216 a[label] = { 34217 language: language, 34218 autoselect: true, 34219 "default": role === 'main', 34220 playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping), 34221 uri: '' 34222 }; 34223 34224 if (typeof mainPlaylist === 'undefined' && role === 'main') { 34225 mainPlaylist = playlist; 34226 mainPlaylist["default"] = true; 34227 } 34228 34229 return a; 34230 }, {}); // if no playlists have role "main", mark the first as main 34231 34232 if (!mainPlaylist) { 34233 var firstLabel = Object.keys(formattedPlaylists)[0]; 34234 formattedPlaylists[firstLabel]["default"] = true; 34235 } 34236 34237 return formattedPlaylists; 34238 }; 34239 34240 var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) { 34241 if (sidxMapping === void 0) { 34242 sidxMapping = {}; 34243 } 34244 34245 return playlists.reduce(function (a, playlist) { 34246 var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles 34247 34248 if (a[label]) { 34249 return a; 34250 } 34251 34252 a[label] = { 34253 language: label, 34254 "default": false, 34255 autoselect: false, 34256 playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping), 34257 uri: '' 34258 }; 34259 return a; 34260 }, {}); 34261 }; 34262 34263 var formatVideoPlaylist = function formatVideoPlaylist(_ref3) { 34264 var _attributes2; 34265 34266 var attributes = _ref3.attributes, 34267 segments = _ref3.segments, 34268 sidx = _ref3.sidx; 34269 var playlist = { 34270 attributes: (_attributes2 = { 34271 NAME: attributes.id, 34272 AUDIO: 'audio', 34273 SUBTITLES: 'subs', 34274 RESOLUTION: { 34275 width: attributes.width, 34276 height: attributes.height 34277 }, 34278 CODECS: attributes.codecs, 34279 BANDWIDTH: attributes.bandwidth 34280 }, _attributes2['PROGRAM-ID'] = 1, _attributes2), 34281 uri: '', 34282 endList: (attributes.type || 'static') === 'static', 34283 timeline: attributes.periodIndex, 34284 resolvedUri: '', 34285 targetDuration: attributes.duration, 34286 segments: segments, 34287 mediaSequence: segments.length ? segments[0].number : 1 34288 }; 34289 34290 if (attributes.contentProtection) { 34291 playlist.contentProtection = attributes.contentProtection; 34292 } 34293 34294 if (sidx) { 34295 playlist.sidx = sidx; 34296 } 34297 34298 return playlist; 34299 }; 34300 34301 var toM3u8 = function toM3u8(dashPlaylists, locations, sidxMapping) { 34302 var _mediaGroups; 34303 34304 if (sidxMapping === void 0) { 34305 sidxMapping = {}; 34306 } 34307 34308 if (!dashPlaylists.length) { 34309 return {}; 34310 } // grab all master attributes 34311 34312 34313 var _dashPlaylists$0$attr = dashPlaylists[0].attributes, 34314 duration = _dashPlaylists$0$attr.sourceDuration, 34315 _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.type, 34316 type = _dashPlaylists$0$attr2 === void 0 ? 'static' : _dashPlaylists$0$attr2, 34317 suggestedPresentationDelay = _dashPlaylists$0$attr.suggestedPresentationDelay, 34318 minimumUpdatePeriod = _dashPlaylists$0$attr.minimumUpdatePeriod; 34319 34320 var videoOnly = function videoOnly(_ref4) { 34321 var attributes = _ref4.attributes; 34322 return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video'; 34323 }; 34324 34325 var audioOnly = function audioOnly(_ref5) { 34326 var attributes = _ref5.attributes; 34327 return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio'; 34328 }; 34329 34330 var vttOnly = function vttOnly(_ref6) { 34331 var attributes = _ref6.attributes; 34332 return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text'; 34333 }; 34334 34335 var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist); 34336 var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly)); 34337 var vttPlaylists = dashPlaylists.filter(vttOnly); 34338 var master = { 34339 allowCache: true, 34340 discontinuityStarts: [], 34341 segments: [], 34342 endList: true, 34343 mediaGroups: (_mediaGroups = { 34344 AUDIO: {}, 34345 VIDEO: {} 34346 }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups), 34347 uri: '', 34348 duration: duration, 34349 playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping) 34350 }; 34351 34352 if (minimumUpdatePeriod >= 0) { 34353 master.minimumUpdatePeriod = minimumUpdatePeriod * 1000; 34354 } 34355 34356 if (locations) { 34357
vendor: 12,359 bytes, lines 34357-34696
34357 master.locations = locations; 34358 } 34359 34360 if (type === 'dynamic') { 34361 master.suggestedPresentationDelay = suggestedPresentationDelay; 34362 } 34363 34364 if (audioPlaylists.length) { 34365 master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping); 34366 } 34367 34368 if (vttPlaylists.length) { 34369 master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping); 34370 } 34371 34372 return master; 34373 }; 34374 /** 34375 * Calculates the R (repetition) value for a live stream (for the final segment 34376 * in a manifest where the r value is negative 1) 34377 * 34378 * @param {Object} attributes 34379 * Object containing all inherited attributes from parent elements with attribute 34380 * names as keys 34381 * @param {number} time 34382 * current time (typically the total time up until the final segment) 34383 * @param {number} duration 34384 * duration property for the given <S /> 34385 * 34386 * @return {number} 34387 * R value to reach the end of the given period 34388 */ 34389 34390 34391 var getLiveRValue = function getLiveRValue(attributes, time, duration) { 34392 var NOW = attributes.NOW, 34393 clientOffset = attributes.clientOffset, 34394 availabilityStartTime = attributes.availabilityStartTime, 34395 _attributes$timescale = attributes.timescale, 34396 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 34397 _attributes$start = attributes.start, 34398 start = _attributes$start === void 0 ? 0 : _attributes$start, 34399 _attributes$minimumUp = attributes.minimumUpdatePeriod, 34400 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp; 34401 var now = (NOW + clientOffset) / 1000; 34402 var periodStartWC = availabilityStartTime + start; 34403 var periodEndWC = now + minimumUpdatePeriod; 34404 var periodDuration = periodEndWC - periodStartWC; 34405 return Math.ceil((periodDuration * timescale - time) / duration); 34406 }; 34407 /** 34408 * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment 34409 * timing and duration 34410 * 34411 * @param {Object} attributes 34412 * Object containing all inherited attributes from parent elements with attribute 34413 * names as keys 34414 * @param {Object[]} segmentTimeline 34415 * List of objects representing the attributes of each S element contained within 34416 * 34417 * @return {{number: number, duration: number, time: number, timeline: number}[]} 34418 * List of Objects with segment timing and duration info 34419 */ 34420 34421 34422 var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) { 34423 var _attributes$type = attributes.type, 34424 type = _attributes$type === void 0 ? 'static' : _attributes$type, 34425 _attributes$minimumUp2 = attributes.minimumUpdatePeriod, 34426 minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2, 34427 _attributes$media = attributes.media, 34428 media = _attributes$media === void 0 ? '' : _attributes$media, 34429 sourceDuration = attributes.sourceDuration, 34430 _attributes$timescale2 = attributes.timescale, 34431 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 34432 _attributes$startNumb = attributes.startNumber, 34433 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb, 34434 timeline = attributes.periodIndex; 34435 var segments = []; 34436 var time = -1; 34437 34438 for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { 34439 var S = segmentTimeline[sIndex]; 34440 var duration = S.d; 34441 var repeat = S.r || 0; 34442 var segmentTime = S.t || 0; 34443 34444 if (time < 0) { 34445 // first segment 34446 time = segmentTime; 34447 } 34448 34449 if (segmentTime && segmentTime > time) { 34450 // discontinuity 34451 // TODO: How to handle this type of discontinuity 34452 // timeline++ here would treat it like HLS discontuity and content would 34453 // get appended without gap 34454 // E.G. 34455 // <S t="0" d="1" /> 34456 // <S d="1" /> 34457 // <S d="1" /> 34458 // <S t="5" d="1" /> 34459 // would have $Time$ values of [0, 1, 2, 5] 34460 // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) 34461 // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) 34462 // does the value of sourceDuration consider this when calculating arbitrary 34463 // negative @r repeat value? 34464 // E.G. Same elements as above with this added at the end 34465 // <S d="1" r="-1" /> 34466 // with a sourceDuration of 10 34467 // Would the 2 gaps be included in the time duration calculations resulting in 34468 // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments 34469 // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? 34470 time = segmentTime; 34471 } 34472 34473 var count = void 0; 34474 34475 if (repeat < 0) { 34476 var nextS = sIndex + 1; 34477 34478 if (nextS === segmentTimeline.length) { 34479 // last segment 34480 if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) { 34481 count = getLiveRValue(attributes, time, duration); 34482 } else { 34483 // TODO: This may be incorrect depending on conclusion of TODO above 34484 count = (sourceDuration * timescale - time) / duration; 34485 } 34486 } else { 34487 count = (segmentTimeline[nextS].t - time) / duration; 34488 } 34489 } else { 34490 count = repeat + 1; 34491 } 34492 34493 var end = startNumber + segments.length + count; 34494 var number = startNumber + segments.length; 34495 34496 while (number < end) { 34497 segments.push({ 34498 number: number, 34499 duration: duration / timescale, 34500 time: time, 34501 timeline: timeline 34502 }); 34503 time += duration; 34504 number++; 34505 } 34506 } 34507 34508 return segments; 34509 }; 34510 34511 var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; 34512 /** 34513 * Replaces template identifiers with corresponding values. To be used as the callback 34514 * for String.prototype.replace 34515 * 34516 * @name replaceCallback 34517 * @function 34518 * @param {string} match 34519 * Entire match of identifier 34520 * @param {string} identifier 34521 * Name of matched identifier 34522 * @param {string} format 34523 * Format tag string. Its presence indicates that padding is expected 34524 * @param {string} width 34525 * Desired length of the replaced value. Values less than this width shall be left 34526 * zero padded 34527 * @return {string} 34528 * Replacement for the matched identifier 34529 */ 34530 34531 /** 34532 * Returns a function to be used as a callback for String.prototype.replace to replace 34533 * template identifiers 34534 * 34535 * @param {Obect} values 34536 * Object containing values that shall be used to replace known identifiers 34537 * @param {number} values.RepresentationID 34538 * Value of the Representation@id attribute 34539 * @param {number} values.Number 34540 * Number of the corresponding segment 34541 * @param {number} values.Bandwidth 34542 * Value of the Representation@bandwidth attribute. 34543 * @param {number} values.Time 34544 * Timestamp value of the corresponding segment 34545 * @return {replaceCallback} 34546 * Callback to be used with String.prototype.replace to replace identifiers 34547 */ 34548 34549 var identifierReplacement = function identifierReplacement(values) { 34550 return function (match, identifier, format, width) { 34551 if (match === '$$') { 34552 // escape sequence 34553 return '$'; 34554 } 34555 34556 if (typeof values[identifier] === 'undefined') { 34557 return match; 34558 } 34559 34560 var value = '' + values[identifier]; 34561 34562 if (identifier === 'RepresentationID') { 34563 // Format tag shall not be present with RepresentationID 34564 return value; 34565 } 34566 34567 if (!format) { 34568 width = 1; 34569 } else { 34570 width = parseInt(width, 10); 34571 } 34572 34573 if (value.length >= width) { 34574 return value; 34575 } 34576 34577 return "" + new Array(width - value.length + 1).join('0') + value; 34578 }; 34579 }; 34580 /** 34581 * Constructs a segment url from a template string 34582 * 34583 * @param {string} url 34584 * Template string to construct url from 34585 * @param {Obect} values 34586 * Object containing values that shall be used to replace known identifiers 34587 * @param {number} values.RepresentationID 34588 * Value of the Representation@id attribute 34589 * @param {number} values.Number 34590 * Number of the corresponding segment 34591 * @param {number} values.Bandwidth 34592 * Value of the Representation@bandwidth attribute. 34593 * @param {number} values.Time 34594 * Timestamp value of the corresponding segment 34595 * @return {string} 34596 * Segment url with identifiers replaced 34597 */ 34598 34599 34600 var constructTemplateUrl = function constructTemplateUrl(url, values) { 34601 return url.replace(identifierPattern, identifierReplacement(values)); 34602 }; 34603 /** 34604 * Generates a list of objects containing timing and duration information about each 34605 * segment needed to generate segment uris and the complete segment object 34606 * 34607 * @param {Object} attributes 34608 * Object containing all inherited attributes from parent elements with attribute 34609 * names as keys 34610 * @param {Object[]|undefined} segmentTimeline 34611 * List of objects representing the attributes of each S element contained within 34612 * the SegmentTimeline element 34613 * @return {{number: number, duration: number, time: number, timeline: number}[]} 34614 * List of Objects with segment timing and duration info 34615 */ 34616 34617 34618 var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) { 34619 if (!attributes.duration && !segmentTimeline) { 34620 // if neither @duration or SegmentTimeline are present, then there shall be exactly 34621 // one media segment 34622 return [{ 34623 number: attributes.startNumber || 1, 34624 duration: attributes.sourceDuration, 34625 time: 0, 34626 timeline: attributes.periodIndex 34627 }]; 34628 } 34629 34630 if (attributes.duration) { 34631 return parseByDuration(attributes); 34632 } 34633 34634 return parseByTimeline(attributes, segmentTimeline); 34635 }; 34636 /** 34637 * Generates a list of segments using information provided by the SegmentTemplate element 34638 * 34639 * @param {Object} attributes 34640 * Object containing all inherited attributes from parent elements with attribute 34641 * names as keys 34642 * @param {Object[]|undefined} segmentTimeline 34643 * List of objects representing the attributes of each S element contained within 34644 * the SegmentTimeline element 34645 * @return {Object[]} 34646 * List of segment objects 34647 */ 34648 34649 34650 var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) { 34651 var templateValues = { 34652 RepresentationID: attributes.id, 34653 Bandwidth: attributes.bandwidth || 0 34654 }; 34655 var _attributes$initializ = attributes.initialization, 34656 initialization = _attributes$initializ === void 0 ? { 34657 sourceURL: '', 34658 range: '' 34659 } : _attributes$initializ; 34660 var mapSegment = urlTypeToSegment({ 34661 baseUrl: attributes.baseUrl, 34662 source: constructTemplateUrl(initialization.sourceURL, templateValues), 34663 range: initialization.range 34664 }); 34665 var segments = parseTemplateInfo(attributes, segmentTimeline); 34666 return segments.map(function (segment) { 34667 templateValues.Number = segment.number; 34668 templateValues.Time = segment.time; 34669 var uri = constructTemplateUrl(attributes.media || '', templateValues); 34670 return { 34671 uri: uri, 34672 timeline: segment.timeline, 34673 duration: segment.duration, 34674 resolvedUri: resolveUrl_1(attributes.baseUrl || '', uri), 34675 map: mapSegment, 34676 number: segment.number 34677 }; 34678 }); 34679 }; 34680 /** 34681 * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14) 34682 * to an object that matches the output of a segment in videojs/mpd-parser 34683 * 34684 * @param {Object} attributes 34685 * Object containing all inherited attributes from parent elements with attribute 34686 * names as keys 34687 * @param {Object} segmentUrl 34688 * <SegmentURL> node to translate into a segment object 34689 * @return {Object} translated segment object 34690 */ 34691 34692 34693 var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) { 34694 var baseUrl = attributes.baseUrl, 34695 _attributes$initializ = attributes.initialization, 34696 initialization = _attributes$initializ === void 0 ? {}
vendor: 15,650 bytes, lines 34696-35181
34696 : _attributes$initializ; 34697 var initSegment = urlTypeToSegment({ 34698 baseUrl: baseUrl, 34699 source: initialization.sourceURL, 34700 range: initialization.range 34701 }); 34702 var segment = urlTypeToSegment({ 34703 baseUrl: baseUrl, 34704 source: segmentUrl.media, 34705 range: segmentUrl.mediaRange 34706 }); 34707 segment.map = initSegment; 34708 return segment; 34709 }; 34710 /** 34711 * Generates a list of segments using information provided by the SegmentList element 34712 * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 34713 * node should be translated into segment. 34714 * 34715 * @param {Object} attributes 34716 * Object containing all inherited attributes from parent elements with attribute 34717 * names as keys 34718 * @param {Object[]|undefined} segmentTimeline 34719 * List of objects representing the attributes of each S element contained within 34720 * the SegmentTimeline element 34721 * @return {Object.<Array>} list of segments 34722 */ 34723 34724 34725 var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) { 34726 var duration = attributes.duration, 34727 _attributes$segmentUr = attributes.segmentUrls, 34728 segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR 34729 // if both SegmentTimeline and @duration are defined, it is outside of spec. 34730 34731 if (!duration && !segmentTimeline || duration && segmentTimeline) { 34732 throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); 34733 } 34734 34735 var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) { 34736 return SegmentURLToSegmentObject(attributes, segmentUrlObject); 34737 }); 34738 var segmentTimeInfo; 34739 34740 if (duration) { 34741 segmentTimeInfo = parseByDuration(attributes); 34742 } 34743 34744 if (segmentTimeline) { 34745 segmentTimeInfo = parseByTimeline(attributes, segmentTimeline); 34746 } 34747 34748 var segments = segmentTimeInfo.map(function (segmentTime, index) { 34749 if (segmentUrlMap[index]) { 34750 var segment = segmentUrlMap[index]; 34751 segment.timeline = segmentTime.timeline; 34752 segment.duration = segmentTime.duration; 34753 segment.number = segmentTime.number; 34754 return segment; 34755 } // Since we're mapping we should get rid of any blank segments (in case 34756 // the given SegmentTimeline is handling for more elements than we have 34757 // SegmentURLs for). 34758 34759 }).filter(function (segment) { 34760 return segment; 34761 }); 34762 return segments; 34763 }; 34764 34765 var generateSegments = function generateSegments(_ref) { 34766 var attributes = _ref.attributes, 34767 segmentInfo = _ref.segmentInfo; 34768 var segmentAttributes; 34769 var segmentsFn; 34770 34771 if (segmentInfo.template) { 34772 segmentsFn = segmentsFromTemplate; 34773 segmentAttributes = merge(attributes, segmentInfo.template); 34774 } else if (segmentInfo.base) { 34775 segmentsFn = segmentsFromBase; 34776 segmentAttributes = merge(attributes, segmentInfo.base); 34777 } else if (segmentInfo.list) { 34778 segmentsFn = segmentsFromList; 34779 segmentAttributes = merge(attributes, segmentInfo.list); 34780 } 34781 34782 var segmentsInfo = { 34783 attributes: attributes 34784 }; 34785 34786 if (!segmentsFn) { 34787 return segmentsInfo; 34788 } 34789 34790 var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which 34791 // must be in seconds. Since we've generated the segment list, we no longer need 34792 // @duration to be in @timescale units, so we can convert it here. 34793 34794 if (segmentAttributes.duration) { 34795 var _segmentAttributes = segmentAttributes, 34796 duration = _segmentAttributes.duration, 34797 _segmentAttributes$ti = _segmentAttributes.timescale, 34798 timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti; 34799 segmentAttributes.duration = duration / timescale; 34800 } else if (segments.length) { 34801 // if there is no @duration attribute, use the largest segment duration as 34802 // as target duration 34803 segmentAttributes.duration = segments.reduce(function (max, segment) { 34804 return Math.max(max, Math.ceil(segment.duration)); 34805 }, 0); 34806 } else { 34807 segmentAttributes.duration = 0; 34808 } 34809 34810 segmentsInfo.attributes = segmentAttributes; 34811 segmentsInfo.segments = segments; // This is a sidx box without actual segment information 34812 34813 if (segmentInfo.base && segmentAttributes.indexRange) { 34814 segmentsInfo.sidx = segments[0]; 34815 segmentsInfo.segments = []; 34816 } 34817 34818 return segmentsInfo; 34819 }; 34820 34821 var toPlaylists = function toPlaylists(representations) { 34822 return representations.map(generateSegments); 34823 }; 34824 34825 var findChildren = function findChildren(element, name) { 34826 return from(element.childNodes).filter(function (_ref) { 34827 var tagName = _ref.tagName; 34828 return tagName === name; 34829 }); 34830 }; 34831 34832 var getContent = function getContent(element) { 34833 return element.textContent.trim(); 34834 }; 34835 34836 var parseDuration = function parseDuration(str) { 34837 var SECONDS_IN_YEAR = 365 * 24 * 60 * 60; 34838 var SECONDS_IN_MONTH = 30 * 24 * 60 * 60; 34839 var SECONDS_IN_DAY = 24 * 60 * 60; 34840 var SECONDS_IN_HOUR = 60 * 60; 34841 var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S 34842 34843 var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/; 34844 var match = durationRegex.exec(str); 34845 34846 if (!match) { 34847 return 0; 34848 } 34849 34850 var _match$slice = match.slice(1), 34851 year = _match$slice[0], 34852 month = _match$slice[1], 34853 day = _match$slice[2], 34854 hour = _match$slice[3], 34855 minute = _match$slice[4], 34856 second = _match$slice[5]; 34857 34858 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); 34859 }; 34860 34861 var parseDate = function parseDate(str) { 34862 // Date format without timezone according to ISO 8601 34863 // YYY-MM-DDThh:mm:ss.ssssss 34864 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 34865 // expressed by ending with 'Z' 34866 34867 if (dateRegex.test(str)) { 34868 str += 'Z'; 34869 } 34870 34871 return Date.parse(str); 34872 }; 34873 34874 var parsers = { 34875 /** 34876 * Specifies the duration of the entire Media Presentation. Format is a duration string 34877 * as specified in ISO 8601 34878 * 34879 * @param {string} value 34880 * value of attribute as a string 34881 * @return {number} 34882 * The duration in seconds 34883 */ 34884 mediaPresentationDuration: function mediaPresentationDuration(value) { 34885 return parseDuration(value); 34886 }, 34887 34888 /** 34889 * Specifies the Segment availability start time for all Segments referred to in this 34890 * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability 34891 * time. Format is a date string as specified in ISO 8601 34892 * 34893 * @param {string} value 34894 * value of attribute as a string 34895 * @return {number} 34896 * The date as seconds from unix epoch 34897 */ 34898 availabilityStartTime: function availabilityStartTime(value) { 34899 return parseDate(value) / 1000; 34900 }, 34901 34902 /** 34903 * Specifies the smallest period between potential changes to the MPD. Format is a 34904 * duration string as specified in ISO 8601 34905 * 34906 * @param {string} value 34907 * value of attribute as a string 34908 * @return {number} 34909 * The duration in seconds 34910 */ 34911 minimumUpdatePeriod: function minimumUpdatePeriod(value) { 34912 return parseDuration(value); 34913 }, 34914 34915 /** 34916 * Specifies the suggested presentation delay. Format is a 34917 * duration string as specified in ISO 8601 34918 * 34919 * @param {string} value 34920 * value of attribute as a string 34921 * @return {number} 34922 * The duration in seconds 34923 */ 34924 suggestedPresentationDelay: function suggestedPresentationDelay(value) { 34925 return parseDuration(value); 34926 }, 34927 34928 /** 34929 * specifices the type of mpd. Can be either "static" or "dynamic" 34930 * 34931 * @param {string} value 34932 * value of attribute as a string 34933 * 34934 * @return {string} 34935 * The type as a string 34936 */ 34937 type: function type(value) { 34938 return value; 34939 }, 34940 34941 /** 34942 * Specifies the duration of the smallest time shifting buffer for any Representation 34943 * in the MPD. Format is a duration string as specified in ISO 8601 34944 * 34945 * @param {string} value 34946 * value of attribute as a string 34947 * @return {number} 34948 * The duration in seconds 34949 */ 34950 timeShiftBufferDepth: function timeShiftBufferDepth(value) { 34951 return parseDuration(value); 34952 }, 34953 34954 /** 34955 * Specifies the PeriodStart time of the Period relative to the availabilityStarttime. 34956 * Format is a duration string as specified in ISO 8601 34957 * 34958 * @param {string} value 34959 * value of attribute as a string 34960 * @return {number} 34961 * The duration in seconds 34962 */ 34963 start: function start(value) { 34964 return parseDuration(value); 34965 }, 34966 34967 /** 34968 * Specifies the width of the visual presentation 34969 * 34970 * @param {string} value 34971 * value of attribute as a string 34972 * @return {number} 34973 * The parsed width 34974 */ 34975 width: function width(value) { 34976 return parseInt(value, 10); 34977 }, 34978 34979 /** 34980 * Specifies the height of the visual presentation 34981 * 34982 * @param {string} value 34983 * value of attribute as a string 34984 * @return {number} 34985 * The parsed height 34986 */ 34987 height: function height(value) { 34988 return parseInt(value, 10); 34989 }, 34990 34991 /** 34992 * Specifies the bitrate of the representation 34993 * 34994 * @param {string} value 34995 * value of attribute as a string 34996 * @return {number} 34997 * The parsed bandwidth 34998 */ 34999 bandwidth: function bandwidth(value) { 35000 return parseInt(value, 10); 35001 }, 35002 35003 /** 35004 * Specifies the number of the first Media Segment in this Representation in the Period 35005 * 35006 * @param {string} value 35007 * value of attribute as a string 35008 * @return {number} 35009 * The parsed number 35010 */ 35011 startNumber: function startNumber(value) { 35012 return parseInt(value, 10); 35013 }, 35014 35015 /** 35016 * Specifies the timescale in units per seconds 35017 * 35018 * @param {string} value 35019 * value of attribute as a string 35020 * @return {number} 35021 * The aprsed timescale 35022 */ 35023 timescale: function timescale(value) { 35024 return parseInt(value, 10); 35025 }, 35026 35027 /** 35028 * Specifies the constant approximate Segment duration 35029 * NOTE: The <Period> element also contains an @duration attribute. This duration 35030 * specifies the duration of the Period. This attribute is currently not 35031 * supported by the rest of the parser, however we still check for it to prevent 35032 * errors. 35033 * 35034 * @param {string} value 35035 * value of attribute as a string 35036 * @return {number} 35037 * The parsed duration 35038 */ 35039 duration: function duration(value) { 35040 var parsedValue = parseInt(value, 10); 35041 35042 if (isNaN(parsedValue)) { 35043 return parseDuration(value); 35044 } 35045 35046 return parsedValue; 35047 }, 35048 35049 /** 35050 * Specifies the Segment duration, in units of the value of the @timescale. 35051 * 35052 * @param {string} value 35053 * value of attribute as a string 35054 * @return {number} 35055 * The parsed duration 35056 */ 35057 d: function d(value) { 35058 return parseInt(value, 10); 35059 }, 35060 35061 /** 35062 * Specifies the MPD start time, in @timescale units, the first Segment in the series 35063 * starts relative to the beginning of the Period 35064 * 35065 * @param {string} value 35066 * value of attribute as a string 35067 * @return {number} 35068 * The parsed time 35069 */ 35070 t: function t(value) { 35071 return parseInt(value, 10); 35072 }, 35073 35074 /** 35075 * Specifies the repeat count of the number of following contiguous Segments with the 35076 * same duration expressed by the value of @d 35077 * 35078 * @param {string} value 35079 * value of attribute as a string 35080 * @return {number} 35081 * The parsed number 35082 */ 35083 r: function r(value) { 35084 return parseInt(value, 10); 35085 }, 35086 35087 /** 35088 * Default parser for all other attributes. Acts as a no-op and just returns the value 35089 * as a string 35090 * 35091 * @param {string} value 35092 * value of attribute as a string 35093 * @return {string} 35094 * Unparsed value 35095 */ 35096 DEFAULT: function DEFAULT(value) { 35097 return value; 35098 } 35099 }; 35100 /** 35101 * Gets all the attributes and values of the provided node, parses attributes with known 35102 * types, and returns an object with attribute names mapped to values. 35103 * 35104 * @param {Node} el 35105 * The node to parse attributes from 35106 * @return {Object} 35107 * Object with all attributes of el parsed 35108 */ 35109 35110 var parseAttributes$1 = function parseAttributes(el) { 35111 if (!(el && el.attributes)) { 35112 return {}; 35113 } 35114 35115 return from(el.attributes).reduce(function (a, e) { 35116 var parseFn = parsers[e.name] || parsers.DEFAULT; 35117 a[e.name] = parseFn(e.value); 35118 return a; 35119 }, {}); 35120 }; 35121 35122 var keySystemsMap = { 35123 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', 35124 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', 35125 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready', 35126 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime' 35127 }; 35128 /** 35129 * Builds a list of urls that is the product of the reference urls and BaseURL values 35130 * 35131 * @param {string[]} referenceUrls 35132 * List of reference urls to resolve to 35133 * @param {Node[]} baseUrlElements 35134 * List of BaseURL nodes from the mpd 35135 * @return {string[]} 35136 * List of resolved urls 35137 */ 35138 35139 var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) { 35140 if (!baseUrlElements.length) { 35141 return referenceUrls; 35142 } 35143 35144 return flatten(referenceUrls.map(function (reference) { 35145 return baseUrlElements.map(function (baseUrlElement) { 35146 return resolveUrl_1(reference, getContent(baseUrlElement)); 35147 }); 35148 })); 35149 }; 35150 /** 35151 * Contains all Segment information for its containing AdaptationSet 35152 * 35153 * @typedef {Object} SegmentInformation 35154 * @property {Object|undefined} template 35155 * Contains the attributes for the SegmentTemplate node 35156 * @property {Object[]|undefined} timeline 35157 * Contains a list of atrributes for each S node within the SegmentTimeline node 35158 * @property {Object|undefined} list 35159 * Contains the attributes for the SegmentList node 35160 * @property {Object|undefined} base 35161 * Contains the attributes for the SegmentBase node 35162 */ 35163 35164 /** 35165 * Returns all available Segment information contained within the AdaptationSet node 35166 * 35167 * @param {Node} adaptationSet 35168 * The AdaptationSet node to get Segment information from 35169 * @return {SegmentInformation} 35170 * The Segment information contained within the provided AdaptationSet 35171 */ 35172 35173 35174 var getSegmentInformation = function getSegmentInformation(adaptationSet) { 35175 var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; 35176 var segmentList = findChildren(adaptationSet, 'SegmentList')[0]; 35177 var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) { 35178 return merge({ 35179 tag: 'SegmentURL' 35180 }, parseAttributes$1(s)); 35181 });
vendor: 2,523 bytes, lines 35182-35236
35182 var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; 35183 var segmentTimelineParentNode = segmentList || segmentTemplate; 35184 var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0]; 35185 var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate; 35186 var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both 35187 // @initialization and an <Initialization> node. @initialization can be templated, 35188 // while the node can have a url and range specified. If the <SegmentTemplate> has 35189 // both @initialization and an <Initialization> subelement we opt to override with 35190 // the node, as this interaction is not defined in the spec. 35191 35192 var template = segmentTemplate && parseAttributes$1(segmentTemplate); 35193 35194 if (template && segmentInitialization) { 35195 template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization); 35196 } else if (template && template.initialization) { 35197 // If it is @initialization we convert it to an object since this is the format that 35198 // later functions will rely on for the initialization segment. This is only valid 35199 // for <SegmentTemplate> 35200 template.initialization = { 35201 sourceURL: template.initialization 35202 }; 35203 } 35204 35205 var segmentInfo = { 35206 template: template, 35207 timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) { 35208 return parseAttributes$1(s); 35209 }), 35210 list: segmentList && merge(parseAttributes$1(segmentList), { 35211 segmentUrls: segmentUrls, 35212 initialization: parseAttributes$1(segmentInitialization) 35213 }), 35214 base: segmentBase && merge(parseAttributes$1(segmentBase), { 35215 initialization: parseAttributes$1(segmentInitialization) 35216 }) 35217 }; 35218 Object.keys(segmentInfo).forEach(function (key) { 35219 if (!segmentInfo[key]) { 35220 delete segmentInfo[key]; 35221 } 35222 }); 35223 return segmentInfo; 35224 }; 35225 /** 35226 * Contains Segment information and attributes needed to construct a Playlist object 35227 * from a Representation 35228 * 35229 * @typedef {Object} RepresentationInformation 35230 * @property {SegmentInformation} segmentInfo 35231 * Segment information for this Representation 35232 * @property {Object} attributes 35233 * Inherited attributes for this Representation 35234 */ 35235 35236 /**
35237 * Maps a Representation node to an object containing Segment information and attributes 35238 * 35239 * @name inheritBaseUrlsCallback 35240 * @function 35241 * @param {Node} representation 35242 * Representation node from the mpd 35243 * @return {RepresentationInformation} 35244 * Representation information needed to construct a Playlist object 35245 */ 35246 35247 /** 35248 * Returns a callback for Array.prototype.map for mapping Representation nodes to 35249 * Segment information and attributes using inherited BaseURL nodes. 35250 * 35251 * @param {Object} adaptationSetAttributes 35252 * Contains attributes inherited by the AdaptationSet 35253 * @param {string[]} adaptationSetBaseUrls 35254 * Contains list of resolved base urls inherited by the AdaptationSet 35255 * @param {SegmentInformation} adaptationSetSegmentInfo 35256 * Contains Segment information for the AdaptationSet 35257 * @return {inheritBaseUrlsCallback} 35258 * Callback map function 35259 */ 35260 35261 35262 var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) { 35263 return function (representation) { 35264 var repBaseUrlElements = findChildren(representation, 'BaseURL'); 35265 var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements); 35266 var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation)); 35267 var representationSegmentInfo = getSegmentInformation(representation); 35268 return repBaseUrls.map(function (baseUrl) { 35269 return { 35270 segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo), 35271 attributes: merge(attributes, { 35272 baseUrl: baseUrl 35273 }) 35274 }; 35275 }); 35276 }; 35277 }; 35278 /** 35279 * Tranforms a series of content protection nodes to
35280 * an object containing pssh data by key system 35281 * 35282 * @param {Node[]} contentProtectionNodes 35283 * Content protection nodes 35284 * @return {Object} 35285 * Object containing pssh data by key system 35286 */ 35287 35288 35289 var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) { 35290 return contentProtectionNodes.reduce(function (acc, node) { 35291 var attributes = parseAttributes$1(node); 35292 var keySystem = keySystemsMap[attributes.schemeIdUri]; 35293 35294 if (keySystem) { 35295 acc[keySystem] = { 35296 attributes: attributes 35297 }; 35298 var psshNode = findChildren(node, 'cenc:pssh')[0]; 35299 35300 if (psshNode) { 35301 var pssh = getContent(psshNode); 35302 var psshBuffer = pssh && decodeB64ToUint8Array_1(pssh); 35303 acc[keySystem].pssh = psshBuffer; 35304 } 35305 } 35306 35307 return acc; 35308 }, {}); 35309 }; 35310 /** 35311 * Maps an AdaptationSet node to a list of Representation information objects 35312 * 35313 * @name toRepresentationsCallback 35314 * @function 35315 * @param {Node} adaptationSet 35316 * AdaptationSet node from the mpd 35317 * @return {RepresentationInformation[]} 35318 * List of objects containing Representaion information 35319 */ 35320 35321 /** 35322 * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of 35323 * Representation information objects 35324 * 35325 * @param {Object} periodAttributes 35326 * Contains attributes inherited by the Period 35327 * @param {string[]} periodBaseUrls 35328 * Contains list of resolved base urls inherited by the Period 35329 * @param {string[]} periodSegmentInfo 35330 * Contains Segment Information at the period level 35331 * @return {toRepresentationsCallback} 35332 * Callback map function 35333 */ 35334 35335 35336 var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) { 35337 return function (adaptationSet) { 35338 var adaptationSetAttributes = parseAttributes$1(adaptationSet); 35339 var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL')); 35340 var role = findChildren(adaptationSet, 'Role')[0]; 35341 var roleAttributes = { 35342 role: parseAttributes$1(role) 35343 }; 35344 var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes); 35345 var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection')); 35346 35347 if (Object.keys(contentProtection).length) { 35348 attrs = merge(attrs, { 35349 contentProtection: contentProtection 35350 }); 35351 } 35352 35353 var segmentInfo = getSegmentInformation(adaptationSet); 35354 var representations = findChildren(adaptationSet, 'Representation'); 35355 var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo); 35356 return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo))); 35357 }; 35358 }; 35359 /** 35360 * Maps an Period node to a list of Representation inforamtion objects for all 35361 * AdaptationSet nodes contained within the Period 35362 * 35363 * @name toAdaptationSetsCallback 35364 * @function 35365 * @param {Node} period 35366 * Period node from the mpd 35367 * @param {number} periodIndex 35368 * Index of the Period within the mpd 35369 * @return {RepresentationInformation[]} 35370 * List of objects containing Representaion information 35371 */ 35372 35373 /** 35374 * Returns a callback for Array.prototype.map for mapping Period nodes to a list of 35375 * Representation information objects 35376 * 35377 * @param {Object} mpdAttributes 35378 * Contains attributes inherited by the mpd 35379 * @param {string[]} mpdBaseUrls 35380 * Contains list of resolved base urls inherited by the mpd 35381 * @return {toAdaptationSetsCallback} 35382 * Callback map function 35383 */ 35384 35385 35386 var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) { 35387 return function (period, index) { 35388 var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL')); 35389 var periodAtt = parseAttributes$1(period); 35390 var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number 35391 35392 var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId; 35393 var periodAttributes = merge(mpdAttributes, { 35394 periodIndex: periodIndex 35395 }); 35396 var adaptationSets = findChildren(period, 'AdaptationSet'); 35397 var periodSegmentInfo = getSegmentInformation(period); 35398 return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo))); 35399 }; 35400 }; 35401 /** 35402 * Traverses the mpd xml tree to generate a list of Representation information objects 35403 * that have inherited attributes from parent nodes 35404 * 35405 * @param {Node} mpd 35406 * The root node of the mpd 35407 * @param {Object} options 35408 * Available options for inheritAttributes 35409 * @param {string} options.manifestUri 35410 * The uri source of the mpd 35411 * @param {number} options.NOW 35412 * Current time per DASH IOP. Default is current time in ms since epoch 35413 * @param {number} options.clientOffset 35414 * Client time difference from NOW (in milliseconds) 35415 * @return {RepresentationInformation[]} 35416 * List of objects containing Representation information 35417 */ 35418 35419 35420 var inheritAttributes = function inheritAttributes(mpd, options) { 35421 if (options === void 0) { 35422 options = {}; 35423 } 35424 35425 var _options = options, 35426 _options$manifestUri = _options.manifestUri, 35427 manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri, 35428 _options$NOW = _options.NOW, 35429 NOW = _options$NOW === void 0 ? Date.now() : _options$NOW, 35430 _options$clientOffset = _options.clientOffset, 35431 clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
vendor: 4,425 bytes, lines 35432-35571
35432 var periods = findChildren(mpd, 'Period'); 35433 35434 if (!periods.length) { 35435 throw new Error(errors.INVALID_NUMBER_OF_PERIOD); 35436 } 35437 35438 var locations = findChildren(mpd, 'Location'); 35439 var mpdAttributes = parseAttributes$1(mpd); 35440 var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); 35441 mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0; 35442 mpdAttributes.NOW = NOW; 35443 mpdAttributes.clientOffset = clientOffset; 35444 35445 if (locations.length) { 35446 mpdAttributes.locations = locations.map(getContent); 35447 } 35448 35449 return { 35450 locations: mpdAttributes.locations, 35451 representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))) 35452 }; 35453 }; 35454 35455 var stringToMpdXml = function stringToMpdXml(manifestString) { 35456 if (manifestString === '') { 35457 throw new Error(errors.DASH_EMPTY_MANIFEST); 35458 } 35459 35460 var parser = new domParser_3(); 35461 var xml = parser.parseFromString(manifestString, 'application/xml'); 35462 var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null; 35463 35464 if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) { 35465 throw new Error(errors.DASH_INVALID_XML); 35466 } 35467 35468 return mpd; 35469 }; 35470 /** 35471 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 35472 * 35473 * @param {string} mpd 35474 * XML string of the MPD manifest 35475 * @return {Object|null} 35476 * Attributes of UTCTiming node specified in the manifest. Null if none found 35477 */ 35478 35479 35480 var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) { 35481 var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0]; 35482 35483 if (!UTCTimingNode) { 35484 return null; 35485 } 35486 35487 var attributes = parseAttributes$1(UTCTimingNode); 35488 35489 switch (attributes.schemeIdUri) { 35490 case 'urn:mpeg:dash:utc:http-head:2014': 35491 case 'urn:mpeg:dash:utc:http-head:2012': 35492 attributes.method = 'HEAD'; 35493 break; 35494 35495 case 'urn:mpeg:dash:utc:http-xsdate:2014': 35496 case 'urn:mpeg:dash:utc:http-iso:2014': 35497 case 'urn:mpeg:dash:utc:http-xsdate:2012': 35498 case 'urn:mpeg:dash:utc:http-iso:2012': 35499 attributes.method = 'GET'; 35500 break; 35501 35502 case 'urn:mpeg:dash:utc:direct:2014': 35503 case 'urn:mpeg:dash:utc:direct:2012': 35504 attributes.method = 'DIRECT'; 35505 attributes.value = Date.parse(attributes.value); 35506 break; 35507 35508 case 'urn:mpeg:dash:utc:http-ntp:2014': 35509 case 'urn:mpeg:dash:utc:ntp:2014': 35510 case 'urn:mpeg:dash:utc:sntp:2014': 35511 default: 35512 throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME); 35513 } 35514 35515 return attributes; 35516 }; 35517 35518 var parse = function parse(manifestString, options) { 35519 if (options === void 0) { 35520 options = {}; 35521 } 35522 35523 var parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options); 35524 var playlists = toPlaylists(parsedManifestInfo.representationInfo); 35525 return toM3u8(playlists, parsedManifestInfo.locations, options.sidxMapping); 35526 }; 35527 /** 35528 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 35529 * 35530 * @param {string} manifestString 35531 * XML string of the MPD manifest 35532 * @return {Object|null} 35533 * Attributes of UTCTiming node specified in the manifest. Null if none found 35534 */ 35535 35536 35537 var parseUTCTiming = function parseUTCTiming(manifestString) { 35538 return parseUTCTimingScheme(stringToMpdXml(manifestString)); 35539 }; 35540 35541 var parseSidx = function parseSidx(data) { 35542 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35543 result = { 35544 version: data[0], 35545 flags: new Uint8Array(data.subarray(1, 4)), 35546 references: [], 35547 referenceId: view.getUint32(4), 35548 timescale: view.getUint32(8), 35549 earliestPresentationTime: view.getUint32(12), 35550 firstOffset: view.getUint32(16) 35551 }, 35552 referenceCount = view.getUint16(22), 35553 i; 35554 35555 for (i = 24; referenceCount; i += 12, referenceCount--) { 35556 result.references.push({ 35557 referenceType: (data[i] & 0x80) >>> 7, 35558 referencedSize: view.getUint32(i) & 0x7FFFFFFF, 35559 subsegmentDuration: view.getUint32(i + 4), 35560 startsWithSap: !!(data[i + 8] & 0x80), 35561 sapType: (data[i + 8] & 0x70) >>> 4, 35562 sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF 35563 }); 35564 } 35565 35566 return result; 35567 }; 35568 35569 var parseSidx_1 = parseSidx; 35570 35571 var containers = createCommonjsModule(function (module, exports
35571) { 35572 35573 Object.defineProperty(exports, '__esModule', { 35574 value: true 35575 }); 35576 35577 var toUint8 = function toUint8(bytes) { 35578 return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0); 35579 }; 35580 35581 var bytesToString = function bytesToString(bytes) { 35582 if (!bytes) { 35583 return ''; 35584 } 35585 35586 bytes = Array.prototype.slice.call(bytes); 35587 var string = String.fromCharCode.apply(null, toUint8(bytes)); 35588 35589 try { 35590 return decodeURIComponent(escape(string)); 35591 } catch (e) {// if decodeURIComponent/escape fails, we are dealing with partial 35592 // or full non string data. Just return the potentially garbled string. 35593 } 35594 35595 return string; 35596 }; 35597 35598 var id3Size = function id3Size(bytes, offset) { 35599 if (offset === void 0) { 35600 offset = 0; 35601 } 35602 35603 bytes = toUint8(bytes); 35604 var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9]; 35605 var flags = bytes[offset + 5]; 35606 var footerPresent = (flags & 16) >> 4; 35607 35608 if (footerPresent) { 35609 return returnSize + 20; 35610 } 35611 35612 return returnSize + 10; 35613 }; 35614 35615 var getId3Offset = function getId3Offset(bytes, offset) { 35616 if (offset === void 0) { 35617 offset = 0; 35618 } 35619 35620 bytes = toUint8(bytes); 35621 35622 if (bytes.length - offset < 10 || bytesToString(bytes.subarray(offset, offset + 3)) !== 'ID3') { 35623 return offset; 35624 } 35625 35626 offset += id3Size(bytes, offset); // recursive check for id3 tags as some files 35627 // have multiple ID3 tag sections even though 35628 // they should not. 35629 35630 return getId3Offset(bytes, offset); 35631 }; 35632 35633 var isLikely = { 35634 aac: function aac(bytes) { 35635 var offset = getId3Offset(bytes); 35636 return bytes.length >= offset + 2 && (bytes[offset] & 0xFF) === 0xFF && (bytes[offset + 1] & 0xE0) === 0xE0 && (bytes[offset + 1] & 0x16) === 0x10; 35637 }, 35638 mp3: function mp3(bytes) { 35639 var offset = getId3Offset(bytes); 35640 return bytes.length >= offset + 2 && (bytes[offset] & 0xFF) === 0xFF && (bytes[offset + 1] & 0xE0) === 0xE0 && (bytes[offset + 1] & 0x06) === 0x02; 35641 }, 35642 webm: function webm(bytes) { 35643 return bytes.length >= 4 && (bytes[0] & 0xFF) === 0x1A && (bytes[1] & 0xFF) === 0x45 && (bytes[2] & 0xFF) === 0xDF && (bytes[3] & 0xFF) === 0xA3; 35644 }, 35645 mp4: function mp4(bytes) { 35646 return bytes.length >= 8 && /^(f|s)typ$/.test(bytesToString(bytes.subarray(4, 8))) && // not 3gp data 35647 !/^ftyp3g$/.test(bytesToString(bytes.subarray(4, 10))); 35648 }, 35649 '3gp': function gp(bytes) { 35650 return bytes.length >= 10 && /^ftyp3g$/.test(bytesToString(bytes.subarray(4, 10))); 35651 }, 35652 ts: function ts(bytes) {
35653 if (bytes.length < 189 && bytes.length >= 1) { 35654 return bytes[0] === 0x47; 35655 } 35656 35657 var i = 0; // check the first 376 bytes for two matching sync bytes 35658 35659 while (i + 188 < bytes.length && i < 188) { 35660 if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) { 35661 return true; 35662 } 35663 35664 i += 1; 35665 } 35666 35667 return false; 35668 }, 35669 flac: function flac(bytes) { 35670 return bytes.length >= 4 && /^fLaC$/.test(bytesToString(bytes.subarray(0, 4))); 35671 }, 35672 ogg: function ogg(bytes) { 35673 return bytes.length >= 4 && /^OggS$/.test(bytesToString(bytes.subarray(0, 4))); 35674 } 35675 }; // get all the isLikely functions 35676 // but make sure 'ts' is at the bottom 35677 // as it is the least specific 35678 35679 var isLikelyTypes = Object.keys(isLikely) // remove ts 35680 .filter(function (t) { 35681 return t !== 'ts'; 35682 }) // add it back to the bottom 35683 .concat('ts'); // make sure we are dealing with uint8 data. 35684 35685 isLikelyTypes.forEach(function (type) { 35686 var isLikelyFn = isLikely[type]; 35687 35688 isLikely[type] = function (bytes) { 35689 return isLikelyFn(toUint8(bytes)); 35690 }; 35691 }); // A useful list of file signatures can be found here 35692 // https://en.wikipedia.org/wiki/List_of_file_signatures 35693 35694 var detectContainerForBytes = function detectContainerForBytes(bytes) { 35695 bytes = toUint8(bytes); 35696 35697 for (var i = 0; i < isLikelyTypes.length; i++) { 35698 var type = isLikelyTypes[i]; 35699 35700 if (isLikely[type](bytes)) { 35701 return type; 35702 } 35703 } 35704 35705 return ''; 35706 }; // fmp4 is not a container 35707 35708 35709 var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) { 35710 bytes = toUint8(bytes); 35711 var i = 0; 35712 35713 while (i < bytes.length) { 35714 var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0; 35715 var type = bytesToString(bytes.subarray(i + 4, i + 8)); 35716 35717 if (type === 'moof') { 35718 return true; 35719 } 35720 35721 if (size === 0 || size + i > bytes.length) { 35722 i = bytes.length; 35723 } else { 35724 i += size; 35725 } 35726 } 35727 35728 return false; 35729 }; 35730 35731 exports.detectContainerForBytes = detectContainerForBytes; 35732 exports.getId3Offset = getId3Offset; 35733 exports.id3Size = id3Size; 35734 exports.isLikely = isLikely; 35735 exports.isLikelyFmp4MediaSegment = isLikelyFmp4MediaSegment; 35736 }); 35737 unwrapExports(containers); 35738 var containers_1 = containers.detectContainerForBytes; 35739 var containers_2 = containers.getId3Offset; 35740 var containers_3 = containers.id3Size; 35741 var containers_4 = containers.isLikely; 35742 var containers_5 = containers.isLikelyFmp4MediaSegment; 35743 35744 var byteHelpers = createCommonjsModule(function (module, exports) { 35745 35746 Object.defineProperty(exports, '__esModule', { 35747 value: true 35748 }); 35749 35750 var isTypedArray = function isTypedArray(obj) { 35751 return ArrayBuffer.isView(obj); 35752 }; 35753 35754 var toUint8 = function toUint8(bytes) { 35755 return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0); 35756 }; 35757 35758 var bytesToString = function bytesToString(bytes) { 35759 if (!bytes) { 35760 return ''; 35761 } 35762 35763 bytes = Array.prototype.slice.call(bytes); 35764 var string = String.fromCharCode.apply(null, toUint8(bytes)); 35765 35766 try { 35767 return decodeURIComponent(escape(string)); 35768 } catch (e) {// if decodeURIComponent/escape fails, we are dealing with partial 35769 // or full non string data. Just return the potentially garbled string. 35770 } 35771 35772 return string; 35773 }; 35774 35775 var stringToBytes = function stringToBytes(string, stringIsBytes) { 35776 if (stringIsBytes === void 0) { 35777 stringIsBytes = false; 35778 } 35779 35780 var bytes = []; 35781 35782 if (typeof string !== 'string' && string && typeof string.toString === 'function') { 35783 string = string.toString(); 35784 } 35785 35786 if (typeof string !== 'string') { 35787 return bytes; 35788 } // If the string already is bytes, we don't have to do this 35789 35790 35791 if (!stringIsBytes) {
vendor: 5,499 bytes, lines 35792-35973
35792 string = unescape(encodeURIComponent(string)); 35793 } 35794 35795 return string.split('').map(function (s) { 35796 return s.charCodeAt(0) & 0xFF; 35797 }); 35798 }; 35799 35800 var concatTypedArrays = function concatTypedArrays() { 35801 for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) { 35802 buffers[_key] = arguments[_key]; 35803 } 35804 35805 var totalLength = buffers.reduce(function (total, buf) { 35806 var len = buf && (buf.byteLength || buf.length); 35807 total += len || 0; 35808 return total; 35809 }, 0); 35810 var tempBuffer = new Uint8Array(totalLength); 35811 var offset = 0; 35812 buffers.forEach(function (buf) { 35813 buf = toUint8(buf); 35814 tempBuffer.set(buf, offset); 35815 offset += buf.byteLength; 35816 }); 35817 return tempBuffer; 35818 }; 35819 35820 exports.bytesToString = bytesToString; 35821 exports.concatTypedArrays = concatTypedArrays; 35822 exports.isTypedArray = isTypedArray; 35823 exports.stringToBytes = stringToBytes; 35824 exports.toUint8 = toUint8; 35825 }); 35826 unwrapExports(byteHelpers); 35827 var byteHelpers_1 = byteHelpers.bytesToString; 35828 var byteHelpers_2 = byteHelpers.concatTypedArrays; 35829 var byteHelpers_3 = byteHelpers.isTypedArray; 35830 var byteHelpers_4 = byteHelpers.stringToBytes; 35831 var byteHelpers_5 = byteHelpers.toUint8; 35832 35833 /** 35834 * mux.js 35835 * 35836 * Copyright (c) Brightcove 35837 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 35838 */ 35839 35840 var streamTypes = { 35841 H264_STREAM_TYPE: 0x1B, 35842 ADTS_STREAM_TYPE: 0x0F, 35843 METADATA_STREAM_TYPE: 0x15 35844 }; 35845 35846 /** 35847 * mux.js 35848 * 35849 * Copyright (c) Brightcove 35850 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 35851 * 35852 * A lightweight readable stream implemention that handles event dispatching. 35853 * Objects that inherit from streams should call init in their constructors. 35854 */ 35855 35856 var Stream$1 = function Stream() { 35857 this.init = function () { 35858 var listeners = {}; 35859 /** 35860 * Add a listener for a specified event type. 35861 * @param type {string} the event name 35862 * @param listener {function} the callback to be invoked when an event of 35863 * the specified type occurs 35864 */ 35865 35866 this.on = function (type, listener) { 35867 if (!listeners[type]) { 35868 listeners[type] = []; 35869 } 35870 35871 listeners[type] = listeners[type].concat(listener); 35872 }; 35873 /** 35874 * Remove a listener for a specified event type. 35875 * @param type {string} the event name 35876 * @param listener {function} a function previously registered for this 35877 * type of event through `on` 35878 */ 35879 35880 35881 this.off = function (type, listener) { 35882 var index; 35883 35884 if (!listeners[type]) { 35885 return false; 35886 } 35887 35888 index = listeners[type].indexOf(listener); 35889 listeners[type] = listeners[type].slice(); 35890 listeners[type].splice(index, 1); 35891 return index > -1; 35892 }; 35893 /** 35894 * Trigger an event of the specified type on this stream. Any additional 35895 * arguments to this function are passed as parameters to event listeners. 35896 * @param type {string} the event name 35897 */ 35898 35899 35900 this.trigger = function (type) { 35901 var callbacks, i, length, args; 35902 callbacks = listeners[type]; 35903 35904 if (!callbacks) { 35905 return; 35906 } // Slicing the arguments on every invocation of this method 35907 // can add a significant amount of overhead. Avoid the 35908 // intermediate object creation for the common case of a 35909 // single callback argument 35910 35911 35912 if (arguments.length === 2) { 35913 length = callbacks.length; 35914 35915 for (i = 0; i < length; ++i) { 35916 callbacks[i].call(this, arguments[1]); 35917 } 35918 } else { 35919 args = []; 35920 i = arguments.length; 35921 35922 for (i = 1; i < arguments.length; ++i) { 35923 args.push(arguments[i]); 35924 } 35925 35926 length = callbacks.length; 35927 35928 for (i = 0; i < length; ++i) { 35929 callbacks[i].apply(this, args); 35930 } 35931 } 35932 }; 35933 /** 35934 * Destroys the stream and cleans up. 35935 */ 35936 35937 35938 this.dispose = function () { 35939 listeners = {}; 35940 }; 35941 }; 35942 }; 35943 /** 35944 * Forwards all `data` events on this stream to the destination stream. The 35945 * destination stream should provide a method `push` to receive the data 35946 * events as they arrive. 35947 * @param destination {stream} the stream that will receive all `data` events 35948 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 35949 * when the current stream emits a 'done' event 35950 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 35951 */ 35952 35953 35954 Stream$1.prototype.pipe = function (destination) { 35955 this.on('data', function (data) { 35956 destination.push(data); 35957 }); 35958 this.on('done', function (flushSource) { 35959 destination.flush(flushSource); 35960 }); 35961 this.on('partialdone', function (flushSource) { 35962 destination.partialFlush(flushSource); 35963 }); 35964 this.on('endedtimeline', function (flushSource) { 35965 destination.endTimeline(flushSource); 35966 }); 35967 this.on('reset', function (flushSource) { 35968 destination.reset(flushSource); 35969 }); 35970 return destination; 35971 }; // Default stream functions that are expected to be overridden to perform 35972 // actual work. These are provided by the prototype as a sort of no-op 35973 // implementation so that we don't have to check for their existence in the
vendor: 40,390 bytes, lines 35974-37305
35974 // `pipe` function above. 35975 35976 35977 Stream$1.prototype.push = function (data) { 35978 this.trigger('data', data); 35979 }; 35980 35981 Stream$1.prototype.flush = function (flushSource) { 35982 this.trigger('done', flushSource); 35983 }; 35984 35985 Stream$1.prototype.partialFlush = function (flushSource) { 35986 this.trigger('partialdone', flushSource); 35987 }; 35988 35989 Stream$1.prototype.endTimeline = function (flushSource) { 35990 this.trigger('endedtimeline', flushSource); 35991 }; 35992 35993 Stream$1.prototype.reset = function (flushSource) { 35994 this.trigger('reset', flushSource); 35995 }; 35996 35997 var stream$1 = Stream$1; 35998 35999 var MAX_TS = 8589934592; 36000 var RO_THRESH = 4294967296; 36001 var TYPE_SHARED = 'shared'; 36002 36003 var handleRollover = function handleRollover(value, reference) { 36004 var direction = 1; 36005 36006 if (value > reference) { 36007 // If the current timestamp value is greater than our reference timestamp and we detect a 36008 // timestamp rollover, this means the roll over is happening in the opposite direction. 36009 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 36010 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 36011 // rollover point. In loading this segment, the timestamp values will be very large, 36012 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 36013 // the time stamp to be `value - 2^33`. 36014 direction = -1; 36015 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 36016 // cause an incorrect adjustment. 36017 36018 36019 while (Math.abs(reference - value) > RO_THRESH) { 36020 value += direction * MAX_TS; 36021 } 36022 36023 return value; 36024 }; 36025 36026 var TimestampRolloverStream = function TimestampRolloverStream(type) { 36027 var lastDTS, referenceDTS; 36028 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 36029 // video and audio. We could use `undefined` here, but having a string 36030 // makes debugging a little clearer. 36031 36032 this.type_ = type || TYPE_SHARED; 36033 36034 this.push = function (data) { 36035 // Any "shared" rollover streams will accept _all_ data. Otherwise, 36036 // streams will only accept data that matches their type. 36037 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 36038 return; 36039 } 36040 36041 if (referenceDTS === undefined) { 36042 referenceDTS = data.dts; 36043 } 36044 36045 data.dts = handleRollover(data.dts, referenceDTS); 36046 data.pts = handleRollover(data.pts, referenceDTS); 36047 lastDTS = data.dts; 36048 this.trigger('data', data); 36049 }; 36050 36051 this.flush = function () { 36052 referenceDTS = lastDTS; 36053 this.trigger('done'); 36054 }; 36055 36056 this.endTimeline = function () { 36057 this.flush(); 36058 this.trigger('endedtimeline'); 36059 }; 36060 36061 this.discontinuity = function () { 36062 referenceDTS = void 0; 36063 lastDTS = void 0; 36064 }; 36065 36066 this.reset = function () { 36067 this.discontinuity(); 36068 this.trigger('reset'); 36069 }; 36070 }; 36071 36072 TimestampRolloverStream.prototype = new stream$1(); 36073 var timestampRolloverStream = { 36074 TimestampRolloverStream: TimestampRolloverStream, 36075 handleRollover: handleRollover 36076 }; 36077 36078 var parsePid = function parsePid(packet) { 36079 var pid = packet[1] & 0x1f; 36080 pid <<= 8; 36081 pid |= packet[2]; 36082 return pid; 36083 }; 36084 36085 var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) { 36086 return !!(packet[1] & 0x40); 36087 }; 36088 36089 var parseAdaptionField = function parseAdaptionField(packet) { 36090 var offset = 0; // if an adaption field is present, its length is specified by the 36091 // fifth byte of the TS packet header. The adaptation field is 36092 // used to add stuffing to PES packets that don't fill a complete 36093 // TS packet, and to specify some forms of timing and control data 36094 // that we do not currently use. 36095 36096 if ((packet[3] & 0x30) >>> 4 > 0x01) { 36097 offset += packet[4] + 1; 36098 } 36099 36100 return offset; 36101 }; 36102 36103 var parseType = function parseType(packet, pmtPid) { 36104 var pid = parsePid(packet); 36105 36106 if (pid === 0) { 36107 return 'pat'; 36108 } else if (pid === pmtPid) { 36109 return 'pmt'; 36110 } else if (pmtPid) { 36111 return 'pes'; 36112 } 36113 36114 return null; 36115 }; 36116 36117 var parsePat = function parsePat(packet) { 36118 var pusi = parsePayloadUnitStartIndicator(packet); 36119 var offset = 4 + parseAdaptionField(packet); 36120 36121 if (pusi) { 36122 offset += packet[offset] + 1; 36123 } 36124 36125 return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; 36126 }; 36127 36128 var parsePmt = function parsePmt(packet) { 36129 var programMapTable = {}; 36130 var pusi = parsePayloadUnitStartIndicator(packet); 36131 var payloadOffset = 4 + parseAdaptionField(packet); 36132 36133 if (pusi) { 36134 payloadOffset += packet[payloadOffset] + 1; 36135 } // PMTs can be sent ahead of the time when they should actually 36136 // take effect. We don't believe this should ever be the case 36137 // for HLS but we'll ignore "forward" PMT declarations if we see 36138 // them. Future PMT declarations have the current_next_indicator 36139 // set to zero. 36140 36141 36142 if (!(packet[payloadOffset + 5] & 0x01)) { 36143 return; 36144 } 36145 36146 var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section 36147 36148 sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; 36149 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 36150 // long the program info descriptors are 36151 36152 programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table 36153 36154 var offset = 12 + programInfoLength; 36155 36156 while (offset < tableEnd) { 36157 var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type 36158 36159 programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry 36160 // skip past the elementary stream descriptors, if present 36161 36162 offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; 36163 } 36164 36165 return programMapTable; 36166 }; 36167 36168 var parsePesType = function parsePesType(packet, programMapTable) { 36169 var pid = parsePid(packet); 36170 var type = programMapTable[pid]; 36171 36172 switch (type) { 36173 case streamTypes.H264_STREAM_TYPE: 36174 return 'video'; 36175 36176 case streamTypes.ADTS_STREAM_TYPE: 36177 return 'audio'; 36178 36179 case streamTypes.METADATA_STREAM_TYPE: 36180 return 'timed-metadata'; 36181 36182 default: 36183 return null; 36184 } 36185 }; 36186 36187 var parsePesTime = function parsePesTime(packet) { 36188 var pusi = parsePayloadUnitStartIndicator(packet); 36189 36190 if (!pusi) { 36191 return null; 36192 } 36193 36194 var offset = 4 + parseAdaptionField(packet); 36195 36196 if (offset >= packet.byteLength) { 36197 // From the H 222.0 MPEG-TS spec 36198 // "For transport stream packets carrying PES packets, stuffing is needed when there 36199 // is insufficient PES packet data to completely fill the transport stream packet 36200 // payload bytes. Stuffing is accomplished by defining an adaptation field longer than 36201 // the sum of the lengths of the data elements in it, so that the payload bytes 36202 // remaining after the adaptation field exactly accommodates the available PES packet 36203 // data." 36204 // 36205 // If the offset is >= the length of the packet, then the packet contains no data 36206 // and instead is just adaption field stuffing bytes 36207 return null; 36208 } 36209 36210 var pes = null; 36211 var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value 36212 // and a DTS value. Determine what combination of values is 36213 // available to work with. 36214 36215 ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 36216 // performs all bitwise operations on 32-bit integers but javascript 36217 // supports a much greater range (52-bits) of integer using standard 36218 // mathematical operations. 36219 // We construct a 31-bit value using bitwise operators over the 31 36220 // most significant bits and then multiply by 4 (equal to a left-shift 36221 // of 2) before we add the final 2 least significant bits of the 36222 // timestamp (equal to an OR.) 36223 36224 if (ptsDtsFlags & 0xC0) { 36225 pes = {}; // the PTS and DTS are not written out directly. For information 36226 // on how they are encoded, see 36227 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 36228 36229 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; 36230 pes.pts *= 4; // Left shift by 2 36231 36232 pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs 36233 36234 pes.dts = pes.pts; 36235 36236 if (ptsDtsFlags & 0x40) { 36237 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; 36238 pes.dts *= 4; // Left shift by 2 36239 36240 pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs 36241 } 36242 } 36243 36244 return pes; 36245 }; 36246 36247 var parseNalUnitType = function parseNalUnitType(type) { 36248 switch (type) { 36249 case 0x05: 36250 return 'slice_layer_without_partitioning_rbsp_idr'; 36251 36252 case 0x06: 36253 return 'sei_rbsp'; 36254 36255 case 0x07: 36256 return 'seq_parameter_set_rbsp'; 36257 36258 case 0x08: 36259 return 'pic_parameter_set_rbsp'; 36260 36261 case 0x09: 36262 return 'access_unit_delimiter_rbsp'; 36263 36264 default: 36265 return null; 36266 } 36267 }; 36268 36269 var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) { 36270 var offset = 4 + parseAdaptionField(packet); 36271 var frameBuffer = packet.subarray(offset); 36272 var frameI = 0; 36273 var frameSyncPoint = 0; 36274 var foundKeyFrame = false; 36275 var nalType; // advance the sync point to a NAL start, if necessary 36276 36277 for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { 36278 if (frameBuffer[frameSyncPoint + 2] === 1) { 36279 // the sync point is properly aligned 36280 frameI = frameSyncPoint + 5; 36281 break; 36282 } 36283 } 36284 36285 while (frameI < frameBuffer.byteLength) { 36286 // look at the current byte to determine if we've hit the end of 36287 // a NAL unit boundary 36288 switch (frameBuffer[frameI]) { 36289 case 0: 36290 // skip past non-sync sequences 36291 if (frameBuffer[frameI - 1] !== 0) { 36292 frameI += 2; 36293 break; 36294 } else if (frameBuffer[frameI - 2] !== 0) { 36295 frameI++; 36296 break; 36297 } 36298 36299 if (frameSyncPoint + 3 !== frameI - 2) { 36300 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 36301 36302 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 36303 foundKeyFrame = true; 36304 } 36305 } // drop trailing zeroes 36306 36307 36308 do { 36309 frameI++; 36310 } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); 36311 36312 frameSyncPoint = frameI - 2; 36313 frameI += 3; 36314 break; 36315 36316 case 1: 36317 // skip past non-sync sequences 36318 if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) { 36319 frameI += 3; 36320 break; 36321 } 36322 36323 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 36324 36325 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 36326 foundKeyFrame = true; 36327 } 36328 36329 frameSyncPoint = frameI - 2; 36330 frameI += 3; 36331 break; 36332 36333 default: 36334 // the current byte isn't a one or zero, so it cannot be part 36335 // of a sync sequence 36336 frameI += 3; 36337 break; 36338 } 36339 } 36340 36341 frameBuffer = frameBuffer.subarray(frameSyncPoint); 36342 frameI -= frameSyncPoint; 36343 frameSyncPoint = 0; // parse the final nal 36344 36345 if (frameBuffer && frameBuffer.byteLength > 3) { 36346 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 36347 36348 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 36349 foundKeyFrame = true; 36350 } 36351 } 36352 36353 return foundKeyFrame; 36354 }; 36355 36356 var probe = { 36357 parseType: parseType, 36358 parsePat: parsePat, 36359 parsePmt: parsePmt, 36360 parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, 36361 parsePesType: parsePesType, 36362 parsePesTime: parsePesTime, 36363 videoPacketContainsKeyFrame: videoPacketContainsKeyFrame 36364 }; 36365 36366 /** 36367 * mux.js 36368 * 36369 * Copyright (c) Brightcove 36370 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 36371 * 36372 * Utilities to detect basic properties and metadata about Aac data. 36373 */ 36374 36375 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 36376 36377 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 36378 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 36379 flags = header[byteIndex + 5], 36380 footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0 36381 36382 returnSize = returnSize >= 0 ? returnSize : 0; 36383 36384 if (footerPresent) { 36385 return returnSize + 20; 36386 } 36387 36388 return returnSize + 10; 36389 }; 36390 36391 var getId3Offset = function getId3Offset(data, offset) { 36392 if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) { 36393 return offset; 36394 } 36395 36396 offset += parseId3TagSize(data, offset); 36397 return getId3Offset(data, offset); 36398 }; // TODO: use vhs-utils 36399 36400 36401 var isLikelyAacData = function isLikelyAacData(data) { 36402 var offset = getId3Offset(data, 0); 36403 return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 && // verify that the 2 layer bits are 0, aka this 36404 // is not mp3 data but aac data. 36405 (data[offset + 1] & 0x16) === 0x10; 36406 }; 36407 36408 var parseSyncSafeInteger = function parseSyncSafeInteger(data) { 36409 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 36410 }; // return a percent-encoded representation of the specified byte range 36411 // @see http://en.wikipedia.org/wiki/Percent-encoding 36412 36413 36414 var percentEncode = function percentEncode(bytes, start, end) { 36415 var i, 36416 result = ''; 36417 36418 for (i = start; i < end; i++) { 36419 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 36420 } 36421 36422 return result; 36423 }; // return the string representation of the specified byte range, 36424 // interpreted as ISO-8859-1. 36425 36426 36427 var parseIso88591 = function parseIso88591(bytes, start, end) { 36428 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 36429 }; 36430 36431 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 36432 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 36433 middle = header[byteIndex + 4] << 3, 36434 highTwo = header[byteIndex + 3] & 0x3 << 11; 36435 return highTwo | middle | lowThree; 36436 }; 36437 36438 var parseType$1 = function parseType(header, byteIndex) { 36439 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 36440 return 'timed-metadata'; 36441 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 36442 return 'audio'; 36443 } 36444 36445 return null; 36446 }; 36447 36448 var parseSampleRate = function parseSampleRate(packet) { 36449 var i = 0; 36450 36451 while (i + 5 < packet.length) { 36452 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 36453 // If a valid header was not found, jump one forward and attempt to 36454 // find a valid ADTS header starting at the next byte 36455 i++; 36456 continue; 36457 } 36458 36459 return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2]; 36460 } 36461 36462 return null; 36463 }; 36464 36465 var parseAacTimestamp = function parseAacTimestamp(packet) { 36466 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 36467 36468 frameStart = 10; 36469 36470 if (packet[5] & 0x40) { 36471 // advance the frame start past the extended header 36472 frameStart += 4; // header size field 36473 36474 frameStart += parseSyncSafeInteger(packet.subarray(10, 14)); 36475 } // parse one or more ID3 frames 36476 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 36477 36478 36479 do { 36480 // determine the number of bytes in this frame 36481 frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8)); 36482 36483 if (frameSize < 1) { 36484 return null; 36485 } 36486 36487 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 36488 36489 if (frameHeader === 'PRIV') { 36490 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 36491 36492 for (var i = 0; i < frame.byteLength; i++) { 36493 if (frame[i] === 0) { 36494 var owner = parseIso88591(frame, 0, i); 36495 36496 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 36497 var d = frame.subarray(i + 1); 36498 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 36499 size *= 4; 36500 size += d[7] & 0x03; 36501 return size; 36502 } 36503 36504 break; 36505 } 36506 } 36507 } 36508 36509 frameStart += 10; // advance past the frame header 36510 36511 frameStart += frameSize; // advance past the frame body 36512 } while (frameStart < packet.byteLength); 36513 36514 return null; 36515 }; 36516 36517 var utils = { 36518 isLikelyAacData: isLikelyAacData, 36519 parseId3TagSize: parseId3TagSize, 36520 parseAdtsSize: parseAdtsSize, 36521 parseType: parseType$1, 36522 parseSampleRate: parseSampleRate, 36523 parseAacTimestamp: parseAacTimestamp 36524 }; 36525 36526 /** 36527 * mux.js 36528 * 36529 * Copyright (c) Brightcove 36530 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 36531 */ 36532 var ONE_SECOND_IN_TS = 90000, 36533 // 90kHz clock 36534 secondsToVideoTs, 36535 secondsToAudioTs, 36536 videoTsToSeconds, 36537 audioTsToSeconds, 36538 audioTsToVideoTs, 36539 videoTsToAudioTs, 36540 metadataTsToSeconds; 36541 36542 secondsToVideoTs = function secondsToVideoTs(seconds) { 36543 return seconds * ONE_SECOND_IN_TS; 36544 }; 36545 36546 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 36547 return seconds * sampleRate; 36548 }; 36549 36550 videoTsToSeconds = function videoTsToSeconds(timestamp) { 36551 return timestamp / ONE_SECOND_IN_TS; 36552 }; 36553 36554 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 36555 return timestamp / sampleRate; 36556 }; 36557 36558 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 36559 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 36560 }; 36561 36562 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 36563 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 36564 }; 36565 /** 36566 * Adjust ID3 tag or caption timing information by the timeline pts values 36567 * (if keepOriginalTimestamps is false) and convert to seconds 36568 */ 36569 36570 36571 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) { 36572 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 36573 }; 36574 36575 var clock = { 36576 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 36577 secondsToVideoTs: secondsToVideoTs, 36578 secondsToAudioTs: secondsToAudioTs, 36579 videoTsToSeconds: videoTsToSeconds, 36580 audioTsToSeconds: audioTsToSeconds, 36581 audioTsToVideoTs: audioTsToVideoTs, 36582 videoTsToAudioTs: videoTsToAudioTs, 36583 metadataTsToSeconds: metadataTsToSeconds 36584 }; 36585 var clock_1 = clock.ONE_SECOND_IN_TS; 36586 36587 var handleRollover$1 = timestampRolloverStream.handleRollover; 36588 var probe$1 = {}; 36589 probe$1.ts = probe; 36590 probe$1.aac = utils; 36591 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; 36592 var MP2T_PACKET_LENGTH = 188, 36593 // bytes 36594 SYNC_BYTE = 0x47; 36595 /** 36596 * walks through segment data looking for pat and pmt packets to parse out 36597 * program map table information 36598 */ 36599 36600 var parsePsi_ = function parsePsi_(bytes, pmt) { 36601 var startIndex = 0, 36602 endIndex = MP2T_PACKET_LENGTH, 36603 packet, 36604 type; 36605 36606 while (endIndex < bytes.byteLength) { 36607 // Look for a pair of start and end sync bytes in the data.. 36608 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 36609 // We found a packet 36610 packet = bytes.subarray(startIndex, endIndex); 36611 type = probe$1.ts.parseType(packet, pmt.pid); 36612 36613 switch (type) { 36614 case 'pat': 36615 if (!pmt.pid) { 36616 pmt.pid = probe$1.ts.parsePat(packet); 36617 } 36618 36619 break; 36620 36621 case 'pmt': 36622 if (!pmt.table) { 36623 pmt.table = probe$1.ts.parsePmt(packet); 36624 } 36625 36626 break; 36627 } // Found the pat and pmt, we can stop walking the segment 36628 36629 36630 if (pmt.pid && pmt.table) { 36631 return; 36632 } 36633 36634 startIndex += MP2T_PACKET_LENGTH; 36635 endIndex += MP2T_PACKET_LENGTH; 36636 continue; 36637 } // If we get here, we have somehow become de-synchronized and we need to step 36638 // forward one byte at a time until we find a pair of sync bytes that denote 36639 // a packet 36640 36641 36642 startIndex++; 36643 endIndex++; 36644 } 36645 }; 36646 /** 36647 * walks through the segment data from the start and end to get timing information 36648 * for the first and last audio pes packets 36649 */ 36650 36651 36652 var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) { 36653 var startIndex = 0, 36654 endIndex = MP2T_PACKET_LENGTH, 36655 packet, 36656 type, 36657 pesType, 36658 pusi, 36659 parsed; 36660 var endLoop = false; // Start walking from start of segment to get first audio packet 36661 36662 while (endIndex <= bytes.byteLength) { 36663 // Look for a pair of start and end sync bytes in the data.. 36664 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 36665 // We found a packet 36666 packet = bytes.subarray(startIndex, endIndex); 36667 type = probe$1.ts.parseType(packet, pmt.pid); 36668 36669 switch (type) { 36670 case 'pes': 36671 pesType = probe$1.ts.parsePesType(packet, pmt.table); 36672 pusi = probe$1.ts.parsePayloadUnitStartIndicator(packet); 36673 36674 if (pesType === 'audio' && pusi) { 36675 parsed = probe$1.ts.parsePesTime(packet); 36676 36677 if (parsed) { 36678 parsed.type = 'audio'; 36679 result.audio.push(parsed); 36680 endLoop = true; 36681 } 36682 } 36683 36684 break; 36685 } 36686 36687 if (endLoop) { 36688 break; 36689 } 36690 36691 startIndex += MP2T_PACKET_LENGTH; 36692 endIndex += MP2T_PACKET_LENGTH; 36693 continue; 36694 } // If we get here, we have somehow become de-synchronized and we need to step 36695 // forward one byte at a time until we find a pair of sync bytes that denote 36696 // a packet 36697 36698 36699 startIndex++; 36700 endIndex++; 36701 } // Start walking from end of segment to get last audio packet 36702 36703 36704 endIndex = bytes.byteLength; 36705 startIndex = endIndex - MP2T_PACKET_LENGTH; 36706 endLoop = false; 36707 36708 while (startIndex >= 0) { 36709 // Look for a pair of start and end sync bytes in the data.. 36710 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 36711 // We found a packet 36712 packet = bytes.subarray(startIndex, endIndex); 36713 type = probe$1.ts.parseType(packet, pmt.pid); 36714 36715 switch (type) { 36716 case 'pes': 36717 pesType = probe$1.ts.parsePesType(packet, pmt.table); 36718 pusi = probe$1.ts.parsePayloadUnitStartIndicator(packet); 36719 36720 if (pesType === 'audio' && pusi) { 36721 parsed = probe$1.ts.parsePesTime(packet); 36722 36723 if (parsed) { 36724 parsed.type = 'audio'; 36725 result.audio.push(parsed); 36726 endLoop = true; 36727 } 36728 } 36729 36730 break; 36731 } 36732 36733 if (endLoop) { 36734 break; 36735 } 36736 36737 startIndex -= MP2T_PACKET_LENGTH; 36738 endIndex -= MP2T_PACKET_LENGTH; 36739 continue; 36740 } // If we get here, we have somehow become de-synchronized and we need to step 36741 // forward one byte at a time until we find a pair of sync bytes that denote 36742 // a packet 36743 36744 36745 startIndex--; 36746 endIndex--; 36747 } 36748 }; 36749 /** 36750 * walks through the segment data from the start and end to get timing information 36751 * for the first and last video pes packets as well as timing information for the first 36752 * key frame. 36753 */ 36754 36755 36756 var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) { 36757 var startIndex = 0, 36758 endIndex = MP2T_PACKET_LENGTH, 36759 packet, 36760 type, 36761 pesType, 36762 pusi, 36763 parsed, 36764 frame, 36765 i, 36766 pes; 36767 var endLoop = false; 36768 var currentFrame = { 36769 data: [], 36770 size: 0 36771 }; // Start walking from start of segment to get first video packet 36772 36773 while (endIndex < bytes.byteLength) { 36774 // Look for a pair of start and end sync bytes in the data.. 36775 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 36776 // We found a packet 36777 packet = bytes.subarray(startIndex, endIndex); 36778 type = probe$1.ts.parseType(packet, pmt.pid); 36779 36780 switch (type) { 36781 case 'pes': 36782 pesType = probe$1.ts.parsePesType(packet, pmt.table); 36783 pusi = probe$1.ts.parsePayloadUnitStartIndicator(packet); 36784 36785 if (pesType === 'video') { 36786 if (pusi && !endLoop) { 36787 parsed = probe$1.ts.parsePesTime(packet); 36788 36789 if (parsed) { 36790 parsed.type = 'video'; 36791 result.video.push(parsed); 36792 endLoop = true; 36793 } 36794 } 36795 36796 if (!result.firstKeyFrame) { 36797 if (pusi) { 36798 if (currentFrame.size !== 0) { 36799 frame = new Uint8Array(currentFrame.size); 36800 i = 0; 36801 36802 while (currentFrame.data.length) { 36803 pes = currentFrame.data.shift(); 36804 frame.set(pes, i); 36805 i += pes.byteLength; 36806 } 36807 36808 if (probe$1.ts.videoPacketContainsKeyFrame(frame)) { 36809 var firstKeyFrame = probe$1.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting 36810 // the keyframe seems to work fine with HLS playback 36811 // and definitely preferable to a crash with TypeError... 36812 36813 if (firstKeyFrame) { 36814 result.firstKeyFrame = firstKeyFrame; 36815 result.firstKeyFrame.type = 'video'; 36816 } else { 36817 // eslint-disable-next-line 36818 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.'); 36819 } 36820 } 36821 36822 currentFrame.size = 0; 36823 } 36824 } 36825 36826 currentFrame.data.push(packet); 36827 currentFrame.size += packet.byteLength; 36828 } 36829 } 36830 36831 break; 36832 } 36833 36834 if (endLoop && result.firstKeyFrame) { 36835 break; 36836 } 36837 36838 startIndex += MP2T_PACKET_LENGTH; 36839 endIndex += MP2T_PACKET_LENGTH; 36840 continue; 36841 } // If we get here, we have somehow become de-synchronized and we need to step 36842 // forward one byte at a time until we find a pair of sync bytes that denote 36843 // a packet 36844 36845 36846 startIndex++; 36847 endIndex++; 36848 } // Start walking from end of segment to get last video packet 36849 36850 36851 endIndex = bytes.byteLength; 36852 startIndex = endIndex - MP2T_PACKET_LENGTH; 36853 endLoop = false; 36854 36855 while (startIndex >= 0) { 36856 // Look for a pair of start and end sync bytes in the data.. 36857 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 36858 // We found a packet 36859 packet = bytes.subarray(startIndex, endIndex); 36860 type = probe$1.ts.parseType(packet, pmt.pid); 36861 36862 switch (type) { 36863 case 'pes': 36864 pesType = probe$1.ts.parsePesType(packet, pmt.table); 36865 pusi = probe$1.ts.parsePayloadUnitStartIndicator(packet); 36866 36867 if (pesType === 'video' && pusi) { 36868 parsed = probe$1.ts.parsePesTime(packet); 36869 36870 if (parsed) { 36871 parsed.type = 'video'; 36872 result.video.push(parsed); 36873 endLoop = true; 36874 } 36875 } 36876 36877 break; 36878 } 36879 36880 if (endLoop) { 36881 break; 36882 } 36883 36884 startIndex -= MP2T_PACKET_LENGTH; 36885 endIndex -= MP2T_PACKET_LENGTH; 36886 continue; 36887 } // If we get here, we have somehow become de-synchronized and we need to step 36888 // forward one byte at a time until we find a pair of sync bytes that denote 36889 // a packet 36890 36891 36892 startIndex--; 36893 endIndex--; 36894 } 36895 }; 36896 /** 36897 * Adjusts the timestamp information for the segment to account for 36898 * rollover and convert to seconds based on pes packet timescale (90khz clock) 36899 */ 36900 36901 36902 var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) { 36903 if (segmentInfo.audio && segmentInfo.audio.length) { 36904 var audioBaseTimestamp = baseTimestamp; 36905 36906 if (typeof audioBaseTimestamp === 'undefined') { 36907 audioBaseTimestamp = segmentInfo.audio[0].dts; 36908 } 36909 36910 segmentInfo.audio.forEach(function (info) { 36911 info.dts = handleRollover$1(info.dts, audioBaseTimestamp); 36912 info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds 36913 36914 info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; 36915 info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; 36916 }); 36917 } 36918 36919 if (segmentInfo.video && segmentInfo.video.length) { 36920 var videoBaseTimestamp = baseTimestamp; 36921 36922 if (typeof videoBaseTimestamp === 'undefined') { 36923 videoBaseTimestamp = segmentInfo.video[0].dts; 36924 } 36925 36926 segmentInfo.video.forEach(function (info) { 36927 info.dts = handleRollover$1(info.dts, videoBaseTimestamp); 36928 info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds 36929 36930 info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; 36931 info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; 36932 }); 36933 36934 if (segmentInfo.firstKeyFrame) { 36935 var frame = segmentInfo.firstKeyFrame; 36936 frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp); 36937 frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds 36938 36939 frame.dtsTime = frame.dts / ONE_SECOND_IN_TS$1; 36940 frame.ptsTime = frame.dts / ONE_SECOND_IN_TS$1; 36941 } 36942 } 36943 }; 36944 /** 36945 * inspects the aac data stream for start and end time information 36946 */ 36947 36948 36949 var inspectAac_ = function inspectAac_(bytes) { 36950 var endLoop = false, 36951 audioCount = 0, 36952 sampleRate = null, 36953 timestamp = null, 36954 frameSize = 0, 36955 byteIndex = 0, 36956 packet; 36957 36958 while (bytes.length - byteIndex >= 3) { 36959 var type = probe$1.aac.parseType(bytes, byteIndex); 36960 36961 switch (type) { 36962 case 'timed-metadata': 36963 // Exit early because we don't have enough to parse 36964 // the ID3 tag header 36965 if (bytes.length - byteIndex < 10) { 36966 endLoop = true; 36967 break; 36968 } 36969 36970 frameSize = probe$1.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 36971 // to emit a full packet 36972 36973 if (frameSize > bytes.length) { 36974 endLoop = true; 36975 break; 36976 } 36977 36978 if (timestamp === null) { 36979 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 36980 timestamp = probe$1.aac.parseAacTimestamp(packet); 36981 } 36982 36983 byteIndex += frameSize; 36984 break; 36985 36986 case 'audio': 36987 // Exit early because we don't have enough to parse 36988 // the ADTS frame header 36989 if (bytes.length - byteIndex < 7) { 36990 endLoop = true; 36991 break; 36992 } 36993 36994 frameSize = probe$1.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 36995 // to emit a full packet 36996 36997 if (frameSize > bytes.length) { 36998 endLoop = true; 36999 break; 37000 } 37001 37002 if (sampleRate === null) { 37003 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 37004 sampleRate = probe$1.aac.parseSampleRate(packet); 37005 } 37006 37007 audioCount++; 37008 byteIndex += frameSize; 37009 break; 37010 37011 default: 37012 byteIndex++; 37013 break; 37014 } 37015 37016 if (endLoop) { 37017 return null; 37018 } 37019 } 37020 37021 if (sampleRate === null || timestamp === null) { 37022 return null; 37023 } 37024 37025 var audioTimescale = ONE_SECOND_IN_TS$1 / sampleRate; 37026 var result = { 37027 audio: [{ 37028 type: 'audio', 37029 dts: timestamp, 37030 pts: timestamp 37031 }, { 37032 type: 'audio', 37033 dts: timestamp + audioCount * 1024 * audioTimescale, 37034 pts: timestamp + audioCount * 1024 * audioTimescale 37035 }] 37036 }; 37037 return result; 37038 }; 37039 /** 37040 * inspects the transport stream segment data for start and end time information 37041 * of the audio and video tracks (when present) as well as the first key frame's 37042 * start time. 37043 */ 37044 37045 37046 var inspectTs_ = function inspectTs_(bytes) { 37047 var pmt = { 37048 pid: null, 37049 table: null 37050 }; 37051 var result = {}; 37052 parsePsi_(bytes, pmt); 37053 37054 for (var pid in pmt.table) { 37055 if (pmt.table.hasOwnProperty(pid)) { 37056 var type = pmt.table[pid]; 37057 37058 switch (type) { 37059 case streamTypes.H264_STREAM_TYPE: 37060 result.video = []; 37061 parseVideoPes_(bytes, pmt, result); 37062 37063 if (result.video.length === 0) { 37064 delete result.video; 37065 } 37066 37067 break; 37068 37069 case streamTypes.ADTS_STREAM_TYPE: 37070 result.audio = []; 37071 parseAudioPes_(bytes, pmt, result); 37072 37073 if (result.audio.length === 0) { 37074 delete result.audio; 37075 } 37076 37077 break; 37078 } 37079 } 37080 } 37081 37082 return result; 37083 }; 37084 /** 37085 * Inspects segment byte data and returns an object with start and end timing information 37086 * 37087 * @param {Uint8Array} bytes The segment byte data 37088 * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame 37089 * timestamps for rollover. This value must be in 90khz clock. 37090 * @return {Object} Object containing start and end frame timing info of segment. 37091 */ 37092 37093 37094 var inspect = function inspect(bytes, baseTimestamp) { 37095 var isAacData = probe$1.aac.isLikelyAacData(bytes); 37096 var result; 37097 37098 if (isAacData) { 37099 result = inspectAac_(bytes); 37100 } else { 37101 result = inspectTs_(bytes); 37102 } 37103 37104 if (!result || !result.audio && !result.video) { 37105 return null; 37106 } 37107 37108 adjustTimestamp_(result, baseTimestamp); 37109 return result; 37110 }; 37111 37112 var tsInspector = { 37113 inspect: inspect, 37114 parseAudioPes_: parseAudioPes_ 37115 }; 37116 37117 /** 37118 * mux.js 37119 * 37120 * Copyright (c) Brightcove 37121 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 37122 */ 37123 var toUnsigned = function toUnsigned(value) { 37124 return value >>> 0; 37125 }; 37126 37127 var toHexString = function toHexString(value) { 37128 return ('00' + value.toString(16)).slice(-2); 37129 }; 37130 37131 var bin = { 37132 toUnsigned: toUnsigned, 37133 toHexString: toHexString 37134 }; 37135 37136 var parseType$2 = function parseType(buffer) { 37137 var result = ''; 37138 result += String.fromCharCode(buffer[0]); 37139 result += String.fromCharCode(buffer[1]); 37140 result += String.fromCharCode(buffer[2]); 37141 result += String.fromCharCode(buffer[3]); 37142 return result; 37143 }; 37144 37145 var parseType_1 = parseType$2; 37146 37147 var toUnsigned$1 = bin.toUnsigned; 37148 37149 var findBox = function findBox(data, path) { 37150 var results = [], 37151 i, 37152 size, 37153 type, 37154 end, 37155 subresults; 37156 37157 if (!path.length) { 37158 // short-circuit the search for empty paths 37159 return null; 37160 } 37161 37162 for (i = 0; i < data.byteLength;) { 37163 size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]); 37164 type = parseType_1(data.subarray(i + 4, i + 8)); 37165 end = size > 1 ? i + size : data.byteLength; 37166 37167 if (type === path[0]) { 37168 if (path.length === 1) { 37169 // this is the end of the path and we've found the box we were 37170 // looking for 37171 results.push(data.subarray(i + 8, end)); 37172 } else { 37173 // recursively search for the next box along the path 37174 subresults = findBox(data.subarray(i + 8, end), path.slice(1)); 37175 37176 if (subresults.length) { 37177 results = results.concat(subresults); 37178 } 37179 } 37180 } 37181 37182 i = end; 37183 } // we've finished searching all of data 37184 37185 37186 return results; 37187 }; 37188 37189 var findBox_1 = findBox; 37190 37191 var tfhd = function tfhd(data) { 37192 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37193 result = { 37194 version: data[0], 37195 flags: new Uint8Array(data.subarray(1, 4)), 37196 trackId: view.getUint32(4) 37197 }, 37198 baseDataOffsetPresent = result.flags[2] & 0x01, 37199 sampleDescriptionIndexPresent = result.flags[2] & 0x02, 37200 defaultSampleDurationPresent = result.flags[2] & 0x08, 37201 defaultSampleSizePresent = result.flags[2] & 0x10, 37202 defaultSampleFlagsPresent = result.flags[2] & 0x20, 37203 durationIsEmpty = result.flags[0] & 0x010000, 37204 defaultBaseIsMoof = result.flags[0] & 0x020000, 37205 i; 37206 i = 8; 37207 37208 if (baseDataOffsetPresent) { 37209 i += 4; // truncate top 4 bytes 37210 // FIXME: should we read the full 64 bits? 37211 37212 result.baseDataOffset = view.getUint32(12); 37213 i += 4; 37214 } 37215 37216 if (sampleDescriptionIndexPresent) { 37217 result.sampleDescriptionIndex = view.getUint32(i); 37218 i += 4; 37219 } 37220 37221 if (defaultSampleDurationPresent) { 37222 result.defaultSampleDuration = view.getUint32(i); 37223 i += 4; 37224 } 37225 37226 if (defaultSampleSizePresent) { 37227 result.defaultSampleSize = view.getUint32(i); 37228 i += 4; 37229 } 37230 37231 if (defaultSampleFlagsPresent) { 37232 result.defaultSampleFlags = view.getUint32(i); 37233 } 37234 37235 if (durationIsEmpty) { 37236 result.durationIsEmpty = true; 37237 } 37238 37239 if (!baseDataOffsetPresent && defaultBaseIsMoof) { 37240 result.baseDataOffsetIsMoof = true; 37241 } 37242 37243 return result; 37244 }; 37245 37246 var parseTfhd = tfhd; 37247 37248 var parseSampleFlags = function parseSampleFlags(flags) { 37249 return { 37250 isLeading: (flags[0] & 0x0c) >>> 2, 37251 dependsOn: flags[0] & 0x03, 37252 isDependedOn: (flags[1] & 0xc0) >>> 6, 37253 hasRedundancy: (flags[1] & 0x30) >>> 4, 37254 paddingValue: (flags[1] & 0x0e) >>> 1, 37255 isNonSyncSample: flags[1] & 0x01, 37256 degradationPriority: flags[2] << 8 | flags[3] 37257 }; 37258 }; 37259 37260 var parseSampleFlags_1 = parseSampleFlags; 37261 37262 var trun = function trun(data) { 37263 var result = { 37264 version: data[0], 37265 flags: new Uint8Array(data.subarray(1, 4)), 37266 samples: [] 37267 }, 37268 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 37269 // Flag interpretation 37270 dataOffsetPresent = result.flags[2] & 0x01, 37271 // compare with 2nd byte of 0x1 37272 firstSampleFlagsPresent = result.flags[2] & 0x04, 37273 // compare with 2nd byte of 0x4 37274 sampleDurationPresent = result.flags[1] & 0x01, 37275 // compare with 2nd byte of 0x100 37276 sampleSizePresent = result.flags[1] & 0x02, 37277 // compare with 2nd byte of 0x200 37278 sampleFlagsPresent = result.flags[1] & 0x04, 37279 // compare with 2nd byte of 0x400 37280 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, 37281 // compare with 2nd byte of 0x800 37282 sampleCount = view.getUint32(4), 37283 offset = 8, 37284 sample; 37285 37286 if (dataOffsetPresent) { 37287 // 32 bit signed integer 37288 result.dataOffset = view.getInt32(offset); 37289 offset += 4; 37290 } // Overrides the flags for the first sample only. The order of 37291 // optional values will be: duration, size, compositionTimeOffset 37292 37293 37294 if (firstSampleFlagsPresent && sampleCount) { 37295 sample = { 37296 flags: parseSampleFlags_1(data.subarray(offset, offset + 4)) 37297 }; 37298 offset += 4; 37299 37300 if (sampleDurationPresent) { 37301 sample.duration = view.getUint32(offset); 37302 offset += 4; 37303 } 37304 37305 if (sampleSizePresent) {
vendor: 8,367 bytes, lines 37306-37558
37306 sample.size = view.getUint32(offset); 37307 offset += 4; 37308 } 37309 37310 if (sampleCompositionTimeOffsetPresent) { 37311 if (result.version === 1) { 37312 sample.compositionTimeOffset = view.getInt32(offset); 37313 } else { 37314 sample.compositionTimeOffset = view.getUint32(offset); 37315 } 37316 37317 offset += 4; 37318 } 37319 37320 result.samples.push(sample); 37321 sampleCount--; 37322 } 37323 37324 while (sampleCount--) { 37325 sample = {}; 37326 37327 if (sampleDurationPresent) { 37328 sample.duration = view.getUint32(offset); 37329 offset += 4; 37330 } 37331 37332 if (sampleSizePresent) { 37333 sample.size = view.getUint32(offset); 37334 offset += 4; 37335 } 37336 37337 if (sampleFlagsPresent) { 37338 sample.flags = parseSampleFlags_1(data.subarray(offset, offset + 4)); 37339 offset += 4; 37340 } 37341 37342 if (sampleCompositionTimeOffsetPresent) { 37343 if (result.version === 1) { 37344 sample.compositionTimeOffset = view.getInt32(offset); 37345 } else { 37346 sample.compositionTimeOffset = view.getUint32(offset); 37347 } 37348 37349 offset += 4; 37350 } 37351 37352 result.samples.push(sample); 37353 } 37354 37355 return result; 37356 }; 37357 37358 var parseTrun = trun; 37359 37360 var toUnsigned$2 = bin.toUnsigned; 37361 37362 var tfdt = function tfdt(data) { 37363 var result = { 37364 version: data[0], 37365 flags: new Uint8Array(data.subarray(1, 4)), 37366 baseMediaDecodeTime: toUnsigned$2(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]) 37367 }; 37368 37369 if (result.version === 1) { 37370 result.baseMediaDecodeTime *= Math.pow(2, 32); 37371 result.baseMediaDecodeTime += toUnsigned$2(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]); 37372 } 37373 37374 return result; 37375 }; 37376 37377 var parseTfdt = tfdt; 37378 37379 var toUnsigned$3 = bin.toUnsigned; 37380 var toHexString$1 = bin.toHexString; 37381 var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader; 37382 /** 37383 * Parses an MP4 initialization segment and extracts the timescale 37384 * values for any declared tracks. Timescale values indicate the 37385 * number of clock ticks per second to assume for time-based values 37386 * elsewhere in the MP4. 37387 * 37388 * To determine the start time of an MP4, you need two pieces of 37389 * information: the timescale unit and the earliest base media decode 37390 * time. Multiple timescales can be specified within an MP4 but the 37391 * base media decode time is always expressed in the timescale from 37392 * the media header box for the track: 37393 * ``` 37394 * moov > trak > mdia > mdhd.timescale 37395 * ``` 37396 * @param init {Uint8Array} the bytes of the init segment 37397 * @return {object} a hash of track ids to timescale values or null if 37398 * the init segment is malformed. 37399 */ 37400 37401 timescale = function timescale(init) { 37402 var result = {}, 37403 traks = findBox_1(init, ['moov', 'trak']); // mdhd timescale 37404 37405 return traks.reduce(function (result, trak) { 37406 var tkhd, version, index, id, mdhd; 37407 tkhd = findBox_1(trak, ['tkhd'])[0]; 37408 37409 if (!tkhd) { 37410 return null; 37411 } 37412 37413 version = tkhd[0]; 37414 index = version === 0 ? 12 : 20; 37415 id = toUnsigned$3(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]); 37416 mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0]; 37417 37418 if (!mdhd) { 37419 return null; 37420 } 37421 37422 version = mdhd[0]; 37423 index = version === 0 ? 12 : 20; 37424 result[id] = toUnsigned$3(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 37425 return result; 37426 }, result); 37427 }; 37428 /** 37429 * Determine the base media decode start time, in seconds, for an MP4 37430 * fragment. If multiple fragments are specified, the earliest time is 37431 * returned. 37432 * 37433 * The base media decode time can be parsed from track fragment 37434 * metadata: 37435 * ``` 37436 * moof > traf > tfdt.baseMediaDecodeTime 37437 * ``` 37438 * It requires the timescale value from the mdhd to interpret. 37439 * 37440 * @param timescale {object} a hash of track ids to timescale values. 37441 * @return {number} the earliest base media decode start time for the 37442 * fragment, in seconds 37443 */ 37444 37445 37446 startTime = function startTime(timescale, fragment) { 37447 var trafs, baseTimes, result; // we need info from two childrend of each track fragment box 37448 37449 trafs = findBox_1(fragment, ['moof', 'traf']); // determine the start times for each track 37450 37451 baseTimes = [].concat.apply([], trafs.map(function (traf) { 37452 return findBox_1(traf, ['tfhd']).map(function (tfhd) { 37453 var id, scale, baseTime; // get the track id from the tfhd 37454 37455 id = toUnsigned$3(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified 37456 37457 scale = timescale[id] || 90e3; // get the base media decode time from the tfdt 37458 37459 baseTime = findBox_1(traf, ['tfdt']).map(function (tfdt) { 37460 var version, result; 37461 version = tfdt[0]; 37462 result = toUnsigned$3(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]); 37463 37464 if (version === 1) { 37465 result *= Math.pow(2, 32); 37466 result += toUnsigned$3(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]); 37467 } 37468 37469 return result; 37470 })[0]; 37471 baseTime = baseTime || Infinity; // convert base time to seconds 37472 37473 return baseTime / scale; 37474 }); 37475 })); // return the minimum 37476 37477 result = Math.min.apply(null, baseTimes); 37478 return isFinite(result) ? result : 0; 37479 }; 37480 /** 37481 * Determine the composition start, in seconds, for an MP4 37482 * fragment. 37483 * 37484 * The composition start time of a fragment can be calculated using the base 37485 * media decode time, composition time offset, and timescale, as follows: 37486 * 37487 * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale 37488 * 37489 * All of the aforementioned information is contained within a media fragment's 37490 * `traf` box, except for timescale info, which comes from the initialization 37491 * segment, so a track id (also contained within a `traf`) is also necessary to 37492 * associate it with a timescale 37493 * 37494 * 37495 * @param timescales {object} - a hash of track ids to timescale values. 37496 * @param fragment {Unit8Array} - the bytes of a media segment 37497 * @return {number} the composition start time for the fragment, in seconds 37498 **/ 37499 37500 37501 compositionStartTime = function compositionStartTime(timescales, fragment) { 37502 var trafBoxes = findBox_1(fragment, ['moof', 'traf']); 37503 var baseMediaDecodeTime = 0; 37504 var compositionTimeOffset = 0; 37505 var trackId; 37506 37507 if (trafBoxes && trafBoxes.length) { 37508 // The spec states that track run samples contained within a `traf` box are contiguous, but 37509 // it does not explicitly state whether the `traf` boxes themselves are contiguous. 37510 // We will assume that they are, so we only need the first to calculate start time. 37511 var tfhd = findBox_1(trafBoxes[0], ['tfhd'])[0]; 37512 var trun = findBox_1(trafBoxes[0], ['trun'])[0]; 37513 var tfdt = findBox_1(trafBoxes[0], ['tfdt'])[0]; 37514 37515 if (tfhd) { 37516 var parsedTfhd = parseTfhd(tfhd); 37517 trackId = parsedTfhd.trackId; 37518 } 37519 37520 if (tfdt) { 37521 var parsedTfdt = parseTfdt(tfdt); 37522 baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime; 37523 } 37524 37525 if (trun) { 37526 var parsedTrun = parseTrun(trun); 37527 37528 if (parsedTrun.samples && parsedTrun.samples.length) { 37529 compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0; 37530 } 37531 } 37532 } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was 37533 // specified. 37534 37535 37536 var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds 37537 37538 return (baseMediaDecodeTime + compositionTimeOffset) / timescale; 37539 }; 37540 /** 37541 * Find the trackIds of the video tracks in this source. 37542 * Found by parsing the Handler Reference and Track Header Boxes: 37543 * moov > trak > mdia > hdlr 37544 * moov > trak > tkhd 37545 * 37546 * @param {Uint8Array} init - The bytes of the init segment for this source 37547 * @return {Number[]} A list of trackIds 37548 * 37549 * @see ISO-BMFF-12/2015, Section 8.4.3 37550 **/ 37551 37552 37553 getVideoTrackIds = function getVideoTrackIds(init) { 37554 var traks = findBox_1(init, ['moov', 'trak']); 37555 var videoTrackIds = []; 37556 traks.forEach(function (trak) { 37557 var hdlrs = findBox_1(trak, ['mdia', 'hdlr']); 37558 var tkhds = findBox_1(trak, ['tkhd']);
vendor: 4,496 bytes, lines 37559-37689
37559 hdlrs.forEach(function (hdlr, index) { 37560 var handlerType = parseType_1(hdlr.subarray(8, 12)); 37561 var tkhd = tkhds[index]; 37562 var view; 37563 var version; 37564 var trackId; 37565 37566 if (handlerType === 'vide') { 37567 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 37568 version = view.getUint8(0); 37569 trackId = version === 0 ? view.getUint32(12) : view.getUint32(20); 37570 videoTrackIds.push(trackId); 37571 } 37572 }); 37573 }); 37574 return videoTrackIds; 37575 }; 37576 37577 getTimescaleFromMediaHeader = function getTimescaleFromMediaHeader(mdhd) { 37578 // mdhd is a FullBox, meaning it will have its own version as the first byte 37579 var version = mdhd[0]; 37580 var index = version === 0 ? 12 : 20; 37581 return toUnsigned$3(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 37582 }; 37583 /** 37584 * Get all the video, audio, and hint tracks from a non fragmented 37585 * mp4 segment 37586 */ 37587 37588 37589 getTracks = function getTracks(init) { 37590 var traks = findBox_1(init, ['moov', 'trak']); 37591 var tracks = []; 37592 traks.forEach(function (trak) { 37593 var track = {}; 37594 var tkhd = findBox_1(trak, ['tkhd'])[0]; 37595 var view, tkhdVersion; // id 37596 37597 if (tkhd) { 37598 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 37599 tkhdVersion = view.getUint8(0); 37600 track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20); 37601 } 37602 37603 var hdlr = findBox_1(trak, ['mdia', 'hdlr'])[0]; // type 37604 37605 if (hdlr) { 37606 var type = parseType_1(hdlr.subarray(8, 12)); 37607 37608 if (type === 'vide') { 37609 track.type = 'video'; 37610 } else if (type === 'soun') { 37611 track.type = 'audio'; 37612 } else { 37613 track.type = type; 37614 } 37615 } // codec 37616 37617 37618 var stsd = findBox_1(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; 37619 37620 if (stsd) { 37621 var sampleDescriptions = stsd.subarray(8); // gives the codec type string 37622 37623 track.codec = parseType_1(sampleDescriptions.subarray(4, 8)); 37624 var codecBox = findBox_1(sampleDescriptions, [track.codec])[0]; 37625 var codecConfig, codecConfigType; 37626 37627 if (codecBox) { 37628 // https://tools.ietf.org/html/rfc6381#section-3.3 37629 if (/^[a-z]vc[1-9]$/i.test(track.codec)) { 37630 // we don't need anything but the "config" parameter of the 37631 // avc1 codecBox 37632 codecConfig = codecBox.subarray(78); 37633 codecConfigType = parseType_1(codecConfig.subarray(4, 8)); 37634 37635 if (codecConfigType === 'avcC' && codecConfig.length > 11) { 37636 track.codec += '.'; // left padded with zeroes for single digit hex 37637 // profile idc 37638 37639 track.codec += toHexString$1(codecConfig[9]); // the byte containing the constraint_set flags 37640 37641 track.codec += toHexString$1(codecConfig[10]); // level idc 37642 37643 track.codec += toHexString$1(codecConfig[11]); 37644 } else { 37645 // TODO: show a warning that we couldn't parse the codec 37646 // and are using the default 37647 track.codec = 'avc1.4d400d'; 37648 } 37649 } else if (/^mp4[a,v]$/i.test(track.codec)) { 37650 // we do not need anything but the streamDescriptor of the mp4a codecBox 37651 codecConfig = codecBox.subarray(28); 37652 codecConfigType = parseType_1(codecConfig.subarray(4, 8)); 37653 37654 if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) { 37655 track.codec += '.' + toHexString$1(codecConfig[19]); // this value is only a single digit 37656 37657 track.codec += '.' + toHexString$1(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, ''); 37658 } else { 37659 // TODO: show a warning that we couldn't parse the codec 37660 // and are using the default 37661 track.codec = 'mp4a.40.2'; 37662 } 37663 } 37664 } 37665 } 37666 37667 var mdhd = findBox_1(trak, ['mdia', 'mdhd'])[0]; 37668 37669 if (mdhd) { 37670 track.timescale = getTimescaleFromMediaHeader(mdhd); 37671 } 37672 37673 tracks.push(track); 37674 }); 37675 return tracks; 37676 }; 37677 37678 var probe$2 = { 37679 // export mp4 inspector's findBox and parseType for backwards compatibility 37680 findBox: findBox_1, 37681 parseType: parseType_1, 37682 timescale: timescale, 37683 startTime: startTime, 37684 compositionStartTime: compositionStartTime, 37685 videoTrackIds: getVideoTrackIds, 37686 tracks: getTracks, 37687 getTimescaleFromMediaHeader: getTimescaleFromMediaHeader 37688 }; 37689
37690 var codecs = createCommonjsModule(function (module, exports) { 37691 37692 Object.defineProperty(exports, '__esModule', { 37693 value: true 37694 }); 37695 37696 function _interopDefaultLegacy(e) { 37697 return e && typeof e === 'object' && 'default' in e ? e : { 37698 'default': e 37699 }; 37700 } 37701 37702 var window__default = /*#__PURE__*/_interopDefaultLegacy(window$1); 37703 37704 var regexs = { 37705 // to determine mime types 37706 mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v)/, 37707 webm: /^(vp0?[89]|av0?1|opus|vorbis)/, 37708 ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/, 37709 // to determine if a codec is audio or video 37710 video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/, 37711 audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3)/, 37712 // mux.js support regex 37713 muxerVideo: /^(avc0?1)/, 37714 muxerAudio: /^(mp4a)/ 37715 }; 37716 /** 37717 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 37718 * `avc1.<hhhhhh>` 37719 * 37720 * @param {string} codec 37721 * Codec string to translate 37722 * @return {string} 37723 * The translated codec string 37724 */ 37725 37726 var translateLegacyCodec = function translateLegacyCodec(codec) { 37727 if (!codec) { 37728 return codec; 37729 } 37730 37731 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 37732 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 37733 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 37734 return 'avc1.' + profileHex + '00' + avcLevelHex; 37735 }); 37736 }; 37737 /** 37738 * Replace the old apple-style `avc1.<dd>.<dd>` codec strings with the standard 37739 * `avc1.<hhhhhh>` 37740 * 37741 * @param {string[]} codecs 37742 * An array of codec strings to translate 37743 * @return {string[]} 37744 * The translated array of codec strings 37745 */ 37746 37747 37748 var translateLegacyCodecs = function translateLegacyCodecs(codecs) { 37749 return codecs.map(translateLegacyCodec); 37750 }; 37751 /** 37752 * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the 37753 * standard `avc1.<hhhhhh>`. 37754 * 37755 * @param {string} codecString 37756 * The codec string 37757 * @return {string} 37758 * The codec string with old apple-style codecs replaced 37759 * 37760 * @private 37761 */ 37762 37763 37764 var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) { 37765 return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) { 37766 return translateLegacyCodecs([match])[0]; 37767 }); 37768 }; 37769 /** 37770 * @typedef {Object} ParsedCodecInfo 37771 * @property {number} codecCount 37772 * Number of codecs parsed 37773 * @property {string} [videoCodec] 37774 * Parsed video codec (if found) 37775 * @property {string} [videoObjectTypeIndicator] 37776 * Video object type indicator (if found) 37777 * @property {string|null} audioProfile 37778 * Audio profile 37779 */ 37780 37781 /** 37782 * Parses a codec string to retrieve the number of codecs specified, the video codec and 37783 * object type indicator, and the audio profile. 37784 * 37785 * @param {string} [codecString] 37786 * The codec string to parse 37787 * @return {ParsedCodecInfo} 37788 * Parsed codec info 37789 */ 37790 37791 37792 var parseCodecs = function parseCodecs(codecString) { 37793 if (codecString === void 0) { 37794 codecString = ''; 37795 } 37796 37797 var codecs = codecString.split(','); 37798 var result = {}; 37799 codecs.forEach(function (codec) { 37800 codec = codec.trim(); 37801 ['video', 'audio'].forEach(function (name) { 37802 var match = regexs[name].exec(codec.toLowerCase()); 37803 37804 if (!match || match.length <= 1) { 37805 return; 37806 } // maintain codec case 37807 37808 37809 var type = codec.substring(0, match[1].length); 37810 var details = codec.replace(type, ''); 37811 result[name] = { 37812 type: type, 37813 details: details 37814 }; 37815 }); 37816 }); 37817 return result; 37818 }; 37819 /** 37820 * Returns a ParsedCodecInfo object for the default alternate audio playlist if there is 37821 * a default alternate audio playlist for the provided audio group. 37822 * 37823 * @param {Object} master 37824 * The master playlist 37825 * @param {string} audioGroupId 37826 * ID of the audio group for which to find the default co
37826dec info 37827 * @return {ParsedCodecInfo} 37828 * Parsed codec info 37829 */ 37830 37831 37832 var codecsFromDefault = function codecsFromDefault(master, audioGroupId) { 37833 if (!master.mediaGroups.AUDIO || !audioGroupId) { 37834 return null; 37835 } 37836 37837 var audioGroup = master.mediaGroups.AUDIO[audioGroupId]; 37838 37839 if (!audioGroup) { 37840 return null; 37841 } 37842 37843 for (var name in audioGroup) { 37844 var audioType = audioGroup[name]; 37845 37846 if (audioType["default"] && audioType.playlists) { 37847 // codec should be the same for all playlists within the audio type 37848 return parseCodecs(audioType.playlists[0].attributes.CODECS); 37849 } 37850 } 37851 37852 return null; 37853 }; 37854 37855 var isVideoCodec = function isVideoCodec(codec) { 37856 if (codec === void 0) { 37857 codec = ''; 37858 } 37859 37860 return regexs.video.test(codec.trim().toLowerCase()); 37861 }; 37862 37863 var isAudioCodec = function isAudioCodec(codec) { 37864 if (codec === void 0) { 37865 codec = ''; 37866 } 37867 37868 return regexs.audio.test(codec.trim().toLowerCase()); 37869 }; 37870 37871 var getMimeForCodec = function getMimeForCodec(codecString) { 37872 if (!codecString || typeof codecString !== 'string') { 37873 return; 37874 } 37875 37876 var codecs = codecString.toLowerCase().split(',').map(function (c) { 37877 return translateLegacyCodec(c.trim()); 37878 }); // default to video type 37879 37880 var type = 'video'; // only change to audio type if the only codec we have is 37881 // audio 37882 37883 if (codecs.length === 1 && isAudioCodec(codecs[0])) { 37884 type = 'audio'; 37885 } // default the container to mp4 37886 37887 37888 var container = 'mp4'; // every codec must be able to go into the container 37889 // for that container to be the correct one 37890 37891 if (codecs.every(function (c) { 37892 return regexs.mp4.test(c); 37893 })) { 37894 container = 'mp4'; 37895 } else if (codecs.every(function (c) { 37896 return regexs.webm.test(c); 37897 })) { 37898 container = 'webm'; 37899 } else if (codecs.every(function (c) { 37900 return regexs.ogg.test(c); 37901 })) { 37902 container = 'ogg'; 37903 } 37904 37905 return type + "/" + container + ";codecs=\"" + codecString + "\""; 37906 }; 37907 37908 var browserSupportsCodec = function browserSupportsCodec(codecString) { 37909 if (codecString === void 0) { 37910 codecString = ''; 37911 } 37912 37913 return window__default['default'].MediaSource && window__default['default'].MediaSource.isTypeSupported && window__default['default'].MediaSource.isTypeSupported(getMimeForCodec(codecString)) || false; 37914 }; 37915 37916 var muxerSupportsCodec = function muxerSupportsCodec(codecString) { 37917 if (codecString === void 0) { 37918 codecString = ''; 37919 } 37920 37921 return codecString.toLowerCase().split(',').every(function (codec) { 37922 codec = codec.trim(); 37923 return regexs.muxerVideo.test(codec) || regexs.muxerAudio.test(codec); 37924 }); 37925 }; 37926 37927 var DEFAULT_AUDIO_CODEC = 'mp4a.40.2'; 37928 var DEFAULT_VIDEO_CODEC = 'avc1.4d400d'; 37929 exports.DEFAULT_AUDIO_CODEC = DEFAULT_AUDIO_CODEC; 37930 exports.DEFAULT_VIDEO_CODEC = DEFAULT_VIDEO_CODEC; 37931 exports.browserSupportsCodec = browserSupportsCodec; 37932 exports.codecsFromDefault = codecsFromDefault; 37933 exports.getMimeForCodec = getMimeForCodec; 37934 exports.isAudioCodec = isAudioCodec; 37935 exports.isVideoCodec = isVideoCodec; 37936 exports.mapLegacyAvcCodecs = mapLegacyAvcCodecs; 37937 exports.muxerSupportsCodec = muxerSupportsCodec; 37938 exports.parseCodecs = parseCodecs; 37939 exports.translateLegacyCodec = translateLegacyCodec; 37940 exports.translateLegacyCodecs = translateLegacyCodecs; 37941 }); 37942 unwrapExports(codecs); 37943 var codecs_1 = codecs.DEFAULT_AUDIO_CODEC; 37944 var codecs_2 = codecs.DEFAULT_VIDEO_CODEC; 37945 var codecs_3 = codecs.browserSupportsCodec; 37946 var codecs_4 = codecs.codecsFromDefault; 37947 var codecs_5 = codecs.getMimeForCodec; 37948 var codecs_6 = codecs.isAudioCodec; 37949 var codecs_7 = codecs.isVideoCodec; 37950 var codecs_8 = codecs.mapLegacyAvcCodecs; 37951 var codecs_9 = codecs.muxerSupportsCodec; 37952 var codecs_10 = codecs.parseCodecs; 37953 var codecs_11 = codecs.translateLegacyCodec; 37954 var codecs_12 = codecs.translateLegacyCodecs; 37955 37956 /*! @name @videojs/http-streaming @version 2.2.4 @license Apache-2.0 */ 37957 /** 37958 * @file resolve-url.js - Handling how URLs are resolved and manipulated 37959 */ 37960 37961 var resolveUrl$1 = resolveUrl_1; 37962 /** 37963 * Checks whether xhr request was redirected and returns correct url depending 37964 * on `handleManifestRedirects` option 37965 * 37966 * @api private 37967 * 37968 * @param {string} url - an url being requested 37969 * @param {XMLHttpRequest}
37969 req - xhr request result 37970 * 37971 * @return {string} 37972 */ 37973 37974 var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) { 37975 // To understand how the responseURL below is set and generated: 37976 // - https://fetch.spec.whatwg.org/#concept-response-url 37977 // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling 37978 if (handleManifestRedirect && req && req.responseURL && url !== req.responseURL) { 37979 return req.responseURL; 37980 } 37981 37982 return url; 37983 }; 37984 37985 var log$1 = videojs$1.log; 37986 37987 var createPlaylistID = function createPlaylistID(index, uri) { 37988 return index + "-" + uri; 37989 }; 37990 /** 37991 * Parses a given m3u8 playlist 37992 * 37993 * @param {string} manifestString 37994 * The downloaded manifest string 37995 * @param {Object[]} [customTagParsers] 37996 * An array of custom tag parsers for the m3u8-parser instance 37997 * @param {Object[]} [customTagMappers] 37998 * An array of custom tag mappers for the m3u8-parser instance 37999 * @return {Object} 38000 * The manifest object 38001 */ 38002 38003 38004 var parseManifest = function parseManifest(_ref) { 38005 var manifestString = _ref.manifestString, 38006 _ref$customTagParsers = _ref.customTagParsers, 38007 customTagParsers = _ref$customTagParsers === void 0 ? [] : _ref$customTagParsers, 38008 _ref$customTagMappers = _ref.customTagMappers, 38009 customTagMappers = _ref$customTagMappers === void 0 ? [] : _ref$customTagMappers; 38010 var parser = new Parser(); 38011 customTagParsers.forEach(function (customParser) { 38012 return parser.addParser(customParser); 38013 }); 38014 customTagMappers.forEach(function (mapper) { 38015 return parser.addTagMapper(mapper); 38016 }); 38017 parser.push(manifestString); 38018 parser.end(); 38019 return parser.manifest; 38020 }; 38021 /** 38022 * Loops through all supported media groups in master and calls the provided 38023 * callback for each group 38024 * 38025 * @param {Object} master 38026 * The parsed master manifest object 38027 * @param {Function} callback 38028 * Callback to call for each media group 38029 */ 38030 38031 38032 var forEachMediaGroup = function forEachMediaGroup(master, callback) { 38033 ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) { 38034 for (var groupKey in master.mediaGroups[mediaType]) { 38035 for (var labelKey in master.mediaGroups[mediaType][groupKey]) { 38036 var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey]; 38037 callback(mediaProperties, mediaType, groupKey, labelKey); 38038 } 38039 } 38040 }); 38041 }; 38042 /** 38043 * Adds properties and attributes to the playlist to keep consistent functionality for 38044 * playlists throughout VHS. 38045 * 38046 * @param {Object} config 38047 * Arguments object 38048 * @param {Object} config.playlist 38049 * The media playlist 38050 * @param {string} [config.uri] 38051 * The uri to the media playlist (if media playlist is not from within a master 38052 * playlist) 38053 * @param {string} id 38054 * ID to use for the playlist 38055 */ 38056 38057 38058 var setupMediaPlaylist = function setupMediaPlaylist(_ref2) { 38059 var playlist = _ref2.playlist, 38060 uri = _ref2.uri, 38061 id = _ref2.id; 38062 playlist.id = id; 38063 38064 if (uri) { 38065 // For media playlists, m3u8-parser does not have access to a URI, as HLS media 38066 // playlists do not contain their own source URI, but one is needed for c
38066onsistency in 38067 // VHS. 38068 playlist.uri = uri; 38069 } // For HLS master playlists, even though certain attributes MUST be defined, the 38070 // stream may still be played without them. 38071 // For HLS media playlists, m3u8-parser does not attach an attributes object to the 38072 // manifest. 38073 // 38074 // To avoid undefined reference errors through the project, and make the code easier 38075 // to write/read, add an empty attributes object for these cases. 38076 38077 38078 playlist.attributes = playlist.attributes || {}; 38079 }; 38080 /** 38081 * Adds ID, resolvedUri, and attributes properties to each playlist of the master, where 38082 * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to 38083 * playlist references to the playlists array. 38084 * 38085 * @param {Object} master 38086 * The master playlist 38087 */ 38088 38089 38090 var setupMediaPlaylists = function setupMediaPlaylists(master) { 38091 var i = master.playlists.length; 38092 38093 while (i--) { 38094 var playlist = master.playlists[i]; 38095 setupMediaPlaylist({ 38096 playlist: playlist, 38097 id: createPlaylistID(i, playlist.uri) 38098 }); 38099 playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri); 38100 master.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility 38101 38102 master.playlists[playlist.uri] = playlist; // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute, 38103 // the stream can be played without it. Although an attributes property may have been 38104 // added to the playlist to prevent undefined references, issue a warning to fix the 38105 // manifest. 38106 38107 if (!playlist.attributes.BANDWIDTH) { 38108 log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.'); 38109 } 38110 } 38111 }; 38112 /** 38113 * Adds resolvedUri properties to each media group. 38114 * 38115 * @param {Object} master 38116 * The master playlist 38117 */ 38118 38119 38120 var resolveMediaGroupUris = function resolveMediaGroupUris(master) { 38121 forEachMediaGroup(master, function (properties) { 38122 if (properties.uri) { 38123 properties.resolvedUri = resolveUrl$1(master.uri, properties.uri); 38124 } 38125 }); 38126 }; 38127 /** 38128 * Creates a master playlist wrapper to insert a sole media playlist into. 38129 * 38130 * @param {Object} media 38131 * Media playlist 38132 * @param {string} uri 38133 * The media URI 38134 * 38135 * @return {Object} 38136 * Master playlist 38137 */ 38138 38139 38140 var masterForMedia = function masterForMedia(media, uri) { 38141 var id = createPlaylistID(0, uri); 38142 var master = { 38143 mediaGroups: { 38144 'AUDIO': {}, 38145 'VIDEO': {}, 38146 'CLOSED-CAPTIONS': {}, 38147 'SUBTITLES': {} 38148 }, 38149 uri: window$1.location.href, 38150 resolvedUri: window$1.location.href, 38151 playlists: [{ 38152 uri: uri, 38153 id: id, 38154 resolvedUri: uri, 38155 // m3u8-parser does not attach an attributes property to media playlists so make 38156 // sure that the property is attached to avoid undefined reference errors 38157 attributes: {} 38158 }] 38159 }; // set up ID reference 38160 38161 master.playlists[id] = master.playlists[0]; // URI reference added for backwards compatibility 38162 38163 master.playlists[uri] = master.playlists[0]; 38164 return master; 38165 }; 38166 /** 38167 * Does an in-place update of the master manifest to add updated playlist URI references 38168 * as well as other properties needed by VHS that aren't included by the parser. 38169 * 38170 * @param {Object} master 38171 * Master manifest object 38172 * @param {string} uri 38173 * The source URI 38174 */ 38175 38176 38177 var addPropertiesToMaster = function addPropertiesToMaster(master, uri) { 38178 master.uri = uri; 38179 38180 for (var i = 0; i < master.playlists.length; i++) { 38181 if (!master.playlists[i].uri) { 38182 // Set up phony URIs for the playlists since playlists are referenced by their URIs 38183 // throughout VHS, but some formats (e.g., DASH) don't have external URIs 38184 // TODO: consider adding dummy URIs in mpd-parser 38185 var phonyUri = "placeholder-uri-" + i; 38186 master.playlists[i].uri = phonyUri; 38187 } 38188 } 38189 38190 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 38191 if (!properties.playlists || !properties.playlists.length || properties.playlists[0].uri) { 38192 return; 38193 } // Set up phony URIs for the media group playlists since playlists are referenced by 38194 // their URIs throughout VHS, but some formats (e.g., DASH) don't have external URIs 38195 38196 38197 var phonyUri = "placeholder-uri-" + mediaType + "-" + groupKey + "-" + labelKey; 38198 var id = createPlaylistID(0, phonyUri); 38199 properties.playlists[0].uri = phonyUri; 38200 properties.playlists[0].id = id; // setup ID and URI references (URI for backwards compatibility) 38201 38202 master.playlists[id] = properties.playlists[0]; 38203 master.playlists[phonyUri] = properties.playlists[0]; 38204 }); 38205 setupMediaPlaylists(master); 38206 resolveMediaGroupUris(master); 38207 }; 38208 38209 var mergeOptions$1 = videojs$1.mergeOptions, 38210 EventTarget$1 = videojs$1.EventTarget; 38211 /** 38212 * Returns a new array of segments that is the result of merging 38213 * properties from an older list of segments onto an updated 38214 * list. No properties on the updated playlist will be overridden. 38215 * 38216 * @param {Array} original the outdated list of segments 38217 * @param {Array} update the updated list of segments 38218 * @param {number=} offset the index of the first update 38219 * segment in the original segment list. For non-live playlists, 38220 * this should always be zero and does not need to be 38221 * specified. For live playlists, it should be the difference 38222 * between the media sequence numbers in the original and updated 38223 * playlists. 38224 * @return a list of merged segment objects 38225 */ 38226 38227 var updateSegments = function updateSegments(original, update, offset) { 38228 var result = update.slice(); 38229 offset = offset || 0; 38230 var length = Math.min(original.length, update.length + offset); 38231 38232 for (var i = offset; i < length; i++) { 38233 result[i - offset] = mergeOptions$1(original[i], result[i - offset]); 38234 } 38235 38236 return result; 38237 }; 38238 38239 var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) { 38240 if (!segment.resolvedUri) { 38241 segment.resolvedUri = resolveUrl$1(baseUri, segment.uri); 38242 } 38243 38244 if (segment.key && !segment.key.resolvedUri) { 38245 segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri); 38246 } 38247 38248 if (segment.map && !segment.map.resolvedUri) { 38249 segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri); 38250 } 38251 }; 38252 /** 38253 * Returns a new master playlist that is the result of merging an 38254 * updated media playlist into the original version. If the 38255 * updated media playlist does not match any of the playlist 38256 * entries in the original master playlist, null is returned. 38257 * 38258 * @param {Object} master a parsed master M3U8 object 38259 * @param {Object} media a parsed media M3U8 object 38260 * @return {Object} a new object that represents the original 38261 * master playlist with the updated media playlist merged in, or 38262 * null if the merge produced no change. 38263 */ 38264 38265 38266 var updateMaster = function updateMaster(master, media) { 38267 var result = mergeOptions$1(master, {}); 38268 var playlist = result.playlists[media.id]; 38269 38270 if (!playlist) { 38271 return null; 38272 } // consider the playlist unchanged if the number of segments is equal, the media 38273 // sequence number is unchanged, and this playlist hasn't become the end of the playlist 38274 38275 38276 if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) { 38277 return null; 38278 } 38279 38280 var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists 38281 38282 if (playlist.segments) { 38283 mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); 38284 } // resolve any segment URIs to prevent us from having to do it later 38285 38286
38287 mergedPlaylist.segments.forEach(function (segment) { 38288 resolveSegmentUris(segment, mergedPlaylist.resolvedUri); 38289 }); // TODO Right now in the playlists array there are two references to each playlist, one 38290 // that is referenced by index, and one by URI. The index reference may no longer be 38291 // necessary. 38292 38293 for (var i = 0; i < result.playlists.length; i++) { 38294 if (result.playlists[i].id === media.id) { 38295 result.playlists[i] = mergedPlaylist; 38296 } 38297 } 38298 38299 result.playlists[media.id] = mergedPlaylist; // URI reference added for backwards compatibility 38300 38301 result.playlists[media.uri] = mergedPlaylist; 38302 return result; 38303 }; 38304 /** 38305 * Calculates the time to wait before refreshing a live playlist 38306 * 38307 * @param {Object} media 38308 * The current media 38309 * @param {boolean} update 38310 * True if there were any updates from the last refresh, false otherwise 38311 * @return {number} 38312 * The time in ms to wait before refreshing the live playlist 38313 */ 38314 38315 38316 var refreshDelay = function refreshDelay(media, update) { 38317 var lastSegment = media.segments[media.segments.length - 1]; 38318 var delay; 38319 38320 if (update && lastSegment && lastSegment.duration) { 38321 delay = lastSegment.duration * 1000; 38322 } else { 38323 // if the playlist is unchanged since the last reload or last segment duration 38324 // cannot be determined, try again after half the target duration 38325 delay = (media.targetDuration || 10) * 500; 38326 } 38327 38328 return delay; 38329 }; 38330 /** 38331 * Load a playlist from a remote location 38332 * 38333 * @class PlaylistLoader 38334 * @extends Stream 38335 * @param {string|Object} src url or object of manifest 38336 * @param {boolean} withCredentials the withCredentials xhr option 38337 * @class 38338 */ 38339 38340 38341 var PlaylistLoader = /*#__PURE__*/function (_EventTarget) { 38342 inheritsLoose(PlaylistLoader, _EventTarget); 38343 38344 function PlaylistLoader(src, vhs, options) { 38345 var _this; 38346 38347 if (options === void 0) { 38348 options = {}; 38349 } 38350 38351 _this = _EventTarget.call(this) || this; 38352 38353 if (!src) { 38354 throw new Error('A non-empty playlist URL or object is required'); 38355 } 38356 38357 var _options = options, 38358 _options$withCredenti = _options.withCredentials, 38359 withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti, 38360 _options$handleManife = _options.handleManifestRedirects, 38361 handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife; 38362 _this.src = src; 38363 _this.vhs_ = vhs; 38364 _this.withCredentials = withCredentials; 38365 _this.handleManifestRedirects = handleManifestRedirects; 38366 var vhsOptions = vhs.options_; 38367 _this.customTagParsers = vhsOptions && vhsOptions.customTagParsers || []; 38368 _this.customTagMappers = vhsOptions && vhsOptions.customTagMappers || []; // initialize the loader state 38369 38370 _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout 38371 38372 _this.on('mediaupdatetimeout', function () { 38373 if (_this.state !== 'HAVE_METADATA') { 38374 // only refresh the media playlist if no other activity is going on 38375 return; 38376 } 38377 38378 _this.state = 'HAVE_CURRENT_METADATA'; 38379 _this.request = _this.vhs_.xhr({ 38380 uri: resolveUrl$1(_this.master.uri, _this.media().uri), 38381 withCredentials: _this.withCredentials 38382 }, function (error, req) { 38383 // disposed 38384 if (!_this.request) { 38385 return; 38386 } 38387 38388 if (error) { 38389 return _this.playlistRequestError(_this.request, _this.media(), 'HAVE_METADATA'); 38390 } 38391 38392 _this.haveMetadata({ 38393 playlistString: _this.request.responseText, 38394 url: _this.media().uri, 38395 id: _this.media().id 38396 }); 38397 }); 38398 }); 38399 38400 return _this; 38401 } 38402 38403 var _proto = PlaylistLoader.prototype; 38404 38405 _proto.playlistRequestError = function playlistRequestError(xhr, playlist, startingState) { 38406 var uri = playlist.uri, 38407 id = playlist.id; // any in-flight request is now finished 38408 38409 this.request = null; 38410 38411 if (startingState) { 38412 this.state = startingState; 38413 } 38414 38415 this.error = { 38416 playlist: this.master.playlists[id], 38417 status: xhr.status, 38418 message: "HLS playlist request error at URL: " + uri + ".", 38419 responseText: xhr.responseText, 38420 code: xhr.status >= 500 ? 4 : 2 38421 }; 38422 this.trigger('error'); 38423 } 38424 /** 38425 * Update the playlist loader's state in response to a new or updated playlist. 38426 * 38427 * @param {string} [playlistString]
vendor: 23,003 bytes, lines 38428-39204
38428 * Playlist string (if playlistObject is not provided) 38429 * @param {Object} [playlistObject] 38430 * Playlist object (if playlistString is not provided) 38431 * @param {string} url 38432 * URL of playlist 38433 * @param {string} id 38434 * ID to use for playlist 38435 */ 38436 ; 38437 38438 _proto.haveMetadata = function haveMetadata(_ref) { 38439 var _this2 = this; 38440 38441 var playlistString = _ref.playlistString, 38442 playlistObject = _ref.playlistObject, 38443 url = _ref.url, 38444 id = _ref.id; // any in-flight request is now finished 38445 38446 this.request = null; 38447 this.state = 'HAVE_METADATA'; 38448 var playlist = playlistObject || parseManifest({ 38449 manifestString: playlistString, 38450 customTagParsers: this.customTagParsers, 38451 customTagMappers: this.customTagMappers 38452 }); 38453 setupMediaPlaylist({ 38454 playlist: playlist, 38455 uri: url, 38456 id: id 38457 }); // merge this playlist into the master 38458 38459 var update = updateMaster(this.master, playlist); 38460 this.targetDuration = playlist.targetDuration; 38461 38462 if (update) { 38463 this.master = update; 38464 this.media_ = this.master.playlists[id]; 38465 } else { 38466 this.trigger('playlistunchanged'); 38467 } // refresh live playlists after a target duration passes 38468 38469 38470 if (!this.media().endList) { 38471 window$1.clearTimeout(this.mediaUpdateTimeout); 38472 this.mediaUpdateTimeout = window$1.setTimeout(function () { 38473 _this2.trigger('mediaupdatetimeout'); 38474 }, refreshDelay(this.media(), !!update)); 38475 } 38476 38477 this.trigger('loadedplaylist'); 38478 } 38479 /** 38480 * Abort any outstanding work and clean up. 38481 */ 38482 ; 38483 38484 _proto.dispose = function dispose() { 38485 this.trigger('dispose'); 38486 this.stopRequest(); 38487 window$1.clearTimeout(this.mediaUpdateTimeout); 38488 window$1.clearTimeout(this.finalRenditionTimeout); 38489 this.off(); 38490 }; 38491 38492 _proto.stopRequest = function stopRequest() { 38493 if (this.request) { 38494 var oldRequest = this.request; 38495 this.request = null; 38496 oldRequest.onreadystatechange = null; 38497 oldRequest.abort(); 38498 } 38499 } 38500 /** 38501 * When called without any arguments, returns the currently 38502 * active media playlist. When called with a single argument, 38503 * triggers the playlist loader to asynchronously switch to the 38504 * specified media playlist. Calling this method while the 38505 * loader is in the HAVE_NOTHING causes an error to be emitted 38506 * but otherwise has no effect. 38507 * 38508 * @param {Object=} playlist the parsed media playlist 38509 * object to switch to 38510 * @param {boolean=} is this the last available playlist 38511 * 38512 * @return {Playlist} the current loaded media 38513 */ 38514 ; 38515 38516 _proto.media = function media(playlist, isFinalRendition) { 38517 var _this3 = this; // getter 38518 38519 38520 if (!playlist) { 38521 return this.media_; 38522 } // setter 38523 38524 38525 if (this.state === 'HAVE_NOTHING') { 38526 throw new Error('Cannot switch media playlist from ' + this.state); 38527 } // find the playlist object if the target playlist has been 38528 // specified by URI 38529 38530 38531 if (typeof playlist === 'string') { 38532 if (!this.master.playlists[playlist]) { 38533 throw new Error('Unknown playlist URI: ' + playlist); 38534 } 38535 38536 playlist = this.master.playlists[playlist]; 38537 } 38538 38539 window$1.clearTimeout(this.finalRenditionTimeout); 38540 38541 if (isFinalRendition) { 38542 var delay = playlist.targetDuration / 2 * 1000 || 5 * 1000; 38543 this.finalRenditionTimeout = window$1.setTimeout(this.media.bind(this, playlist, false), delay); 38544 return; 38545 } 38546 38547 var startingState = this.state; 38548 var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to fully loaded playlists immediately 38549 38550 if (this.master.playlists[playlist.id].endList || // handle the case of a playlist object (e.g., if using vhs-json with a resolved 38551 // media playlist or, for the case of demuxed audio, a resolved audio media group) 38552 playlist.endList && playlist.segments.length) { 38553 // abort outstanding playlist requests 38554 if (this.request) { 38555 this.request.onreadystatechange = null; 38556 this.request.abort(); 38557 this.request = null; 38558 } 38559 38560 this.state = 'HAVE_METADATA'; 38561 this.media_ = playlist; // trigger media change if the active media has been updated 38562 38563 if (mediaChange) { 38564 this.trigger('mediachanging'); 38565 38566 if (startingState === 'HAVE_MASTER') { 38567 // The initial playlist was a master manifest, and the first media selected was 38568 // also provided (in the form of a resolved playlist object) as part of the 38569 // source object (rather than just a URL). Therefore, since the media playlist 38570 // doesn't need to be requested, loadedmetadata won't trigger as part of the 38571 // normal flow, and needs an explicit trigger here. 38572 this.trigger('loadedmetadata'); 38573 } else { 38574 this.trigger('mediachange'); 38575 } 38576 } 38577 38578 return; 38579 } // switching to the active playlist is a no-op 38580 38581 38582 if (!mediaChange) { 38583 return; 38584 } 38585 38586 this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request 38587 38588 if (this.request) { 38589 if (playlist.resolvedUri === this.request.url) { 38590 // requesting to switch to the same playlist multiple times 38591 // has no effect after the first 38592 return; 38593 } 38594 38595 this.request.onreadystatechange = null; 38596 this.request.abort(); 38597 this.request = null; 38598 } // request the new playlist 38599 38600 38601 if (this.media_) { 38602 this.trigger('mediachanging'); 38603 } 38604 38605 this.request = this.vhs_.xhr({ 38606 uri: playlist.resolvedUri, 38607 withCredentials: this.withCredentials 38608 }, function (error, req) { 38609 // disposed 38610 if (!_this3.request) { 38611 return; 38612 } 38613 38614 playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req); 38615 38616 if (error) { 38617 return _this3.playlistRequestError(_this3.request, playlist, startingState); 38618 } 38619 38620 _this3.haveMetadata({ 38621 playlistString: req.responseText, 38622 url: playlist.uri, 38623 id: playlist.id 38624 }); // fire loadedmetadata the first time a media playlist is loaded 38625 38626 38627 if (startingState === 'HAVE_MASTER') { 38628 _this3.trigger('loadedmetadata'); 38629 } else { 38630 _this3.trigger('mediachange'); 38631 } 38632 }); 38633 } 38634 /** 38635 * pause loading of the playlist 38636 */ 38637 ; 38638 38639 _proto.pause = function pause() { 38640 this.stopRequest(); 38641 window$1.clearTimeout(this.mediaUpdateTimeout); 38642 38643 if (this.state === 'HAVE_NOTHING') { 38644 // If we pause the loader before any data has been retrieved, its as if we never 38645 // started, so reset to an unstarted state. 38646 this.started = false; 38647 } // Need to restore state now that no activity is happening 38648 38649 38650 if (this.state === 'SWITCHING_MEDIA') { 38651 // if the loader was in the process of switching media, it should either return to 38652 // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media 38653 // playlist yet. This is determined by the existence of loader.media_ 38654 if (this.media_) { 38655 this.state = 'HAVE_METADATA'; 38656 } else { 38657 this.state = 'HAVE_MASTER'; 38658 } 38659 } else if (this.state === 'HAVE_CURRENT_METADATA') { 38660 this.state = 'HAVE_METADATA'; 38661 } 38662 } 38663 /** 38664 * start loading of the playlist 38665 */ 38666 ; 38667 38668 _proto.load = function load(isFinalRendition) { 38669 var _this4 = this; 38670 38671 window$1.clearTimeout(this.mediaUpdateTimeout); 38672 var media = this.media(); 38673 38674 if (isFinalRendition) { 38675 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 38676 this.mediaUpdateTimeout = window$1.setTimeout(function () { 38677 return _this4.load(); 38678 }, delay); 38679 return; 38680 } 38681 38682 if (!this.started) { 38683 this.start(); 38684 return; 38685 } 38686 38687 if (media && !media.endList) { 38688 this.trigger('mediaupdatetimeout'); 38689 } else { 38690 this.trigger('loadedplaylist'); 38691 } 38692 } 38693 /** 38694 * start loading of the playlist 38695 */ 38696 ; 38697 38698 _proto.start = function start() { 38699 var _this5 = this; 38700 38701 this.started = true; 38702 38703 if (typeof this.src === 'object') { 38704 // in the case of an entirely constructed manifest object (meaning there's no actual 38705 // manifest on a server), default the uri to the page's href 38706 if (!this.src.uri) { 38707 this.src.uri = window$1.location.href; 38708 } // resolvedUri is added on internally after the initial request. Since there's no 38709 // request for pre-resolved manifests, add on resolvedUri here. 38710 38711 38712 this.src.resolvedUri = this.src.uri; // Since a manifest object was passed in as the source (instead of a URL), the first 38713 // request can be skipped (since the top level of the manifest, at a minimum, is 38714 // already available as a parsed manifest object). However, if the manifest object 38715 // represents a master playlist, some media playlists may need to be resolved before 38716 // the starting segment list is available. Therefore, go directly to setup of the 38717 // initial playlist, and let the normal flow continue from there. 38718 // 38719 // Note that the call to setup is asynchronous, as other sections of VHS may assume 38720 // that the first request is asynchronous. 38721 38722 setTimeout(function () { 38723 _this5.setupInitialPlaylist(_this5.src); 38724 }, 0); 38725 return; 38726 } // request the specified URL 38727 38728 38729 this.request = this.vhs_.xhr({ 38730 uri: this.src, 38731 withCredentials: this.withCredentials 38732 }, function (error, req) { 38733 // disposed 38734 if (!_this5.request) { 38735 return; 38736 } // clear the loader's request reference 38737 38738 38739 _this5.request = null; 38740 38741 if (error) { 38742 _this5.error = { 38743 status: req.status, 38744 message: "HLS playlist request error at URL: " + _this5.src + ".", 38745 responseText: req.responseText, 38746 // MEDIA_ERR_NETWORK 38747 code: 2 38748 }; 38749 38750 if (_this5.state === 'HAVE_NOTHING') { 38751 _this5.started = false; 38752 } 38753 38754 return _this5.trigger('error'); 38755 } 38756 38757 _this5.src = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.src, req); 38758 var manifest = parseManifest({ 38759 manifestString: req.responseText, 38760 customTagParsers: _this5.customTagParsers, 38761 customTagMappers: _this5.customTagMappers 38762 }); 38763 38764 _this5.setupInitialPlaylist(manifest); 38765 }); 38766 }; 38767 38768 _proto.srcUri = function srcUri() { 38769 return typeof this.src === 'string' ? this.src : this.src.uri; 38770 } 38771 /** 38772 * Given a manifest object that's either a master or media playlist, trigger the proper 38773 * events and set the state of the playlist loader. 38774 * 38775 * If the manifest object represents a master playlist, `loadedplaylist` will be 38776 * triggered to allow listeners to select a playlist. If none is selected, the loader 38777 * will default to the first one in the playlists array. 38778 * 38779 * If the manifest object represents a media playlist, `loadedplaylist` will be 38780 * triggered followed by `loadedmetadata`, as the only available playlist is loaded. 38781 * 38782 * In the case of a media playlist, a master playlist object wrapper with one playlist 38783 * will be created so that all logic can handle playlists in the same fashion (as an 38784 * assumed manifest object schema). 38785 * 38786 * @param {Object} manifest 38787 * The parsed manifest object 38788 */ 38789 ; 38790 38791 _proto.setupInitialPlaylist = function setupInitialPlaylist(manifest) { 38792 this.state = 'HAVE_MASTER'; 38793 38794 if (manifest.playlists) { 38795 this.master = manifest; 38796 addPropertiesToMaster(this.master, this.srcUri()); // If the initial master playlist has playlists wtih segments already resolved, 38797 // then resolve URIs in advance, as they are usually done after a playlist request, 38798 // which may not happen if the playlist is resolved. 38799 38800 manifest.playlists.forEach(function (playlist) { 38801 if (playlist.segments) { 38802 playlist.segments.forEach(function (segment) { 38803 resolveSegmentUris(segment, playlist.resolvedUri); 38804 }); 38805 } 38806 }); 38807 this.trigger('loadedplaylist'); 38808 38809 if (!this.request) { 38810 // no media playlist was specifically selected so start 38811 // from the first listed one 38812 this.media(this.master.playlists[0]); 38813 } 38814 38815 return; 38816 } // In order to support media playlists passed in as vhs-json, the case where the uri 38817 // is not provided as part of the manifest should be considered, and an appropriate 38818 // default used. 38819 38820 38821 var uri = this.srcUri() || window$1.location.href; 38822 this.master = masterForMedia(manifest, uri); 38823 this.haveMetadata({ 38824 playlistObject: manifest, 38825 url: uri, 38826 id: this.master.playlists[0].id 38827 }); 38828 this.trigger('loadedmetadata'); 38829 }; 38830 38831 return PlaylistLoader; 38832 }(EventTarget$1); 38833 /** 38834 * ranges 38835 * 38836 * Utilities for working with TimeRanges. 38837 * 38838 */ 38839 38840 38841 var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range 38842 // can be misleading because of precision differences or when the current media has poorly 38843 // aligned audio and video, which can cause values to be slightly off from what you would 38844 // expect. This value is what we consider to be safe to use in such comparisons to account 38845 // for these scenarios. 38846 38847 var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3; 38848 38849 var filterRanges = function filterRanges(timeRanges, predicate) { 38850 var results = []; 38851 var i; 38852 38853 if (timeRanges && timeRanges.length) { 38854 // Search for ranges that match the predicate 38855 for (i = 0; i < timeRanges.length; i++) { 38856 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 38857 results.push([timeRanges.start(i), timeRanges.end(i)]); 38858 } 38859 } 38860 } 38861 38862 return videojs$1.createTimeRanges(results); 38863 }; 38864 /** 38865 * Attempts to find the buffered TimeRange that contains the specified 38866 * time. 38867 * 38868 * @param {TimeRanges} buffered - the TimeRanges object to query 38869 * @param {number} time - the time to filter on. 38870 * @return {TimeRanges} a new TimeRanges object 38871 */ 38872 38873 38874 var findRange = function findRange(buffered, time) { 38875 return filterRanges(buffered, function (start, end) { 38876 return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time; 38877 }); 38878 }; 38879 /** 38880 * Returns the TimeRanges that begin later than the specified time. 38881 * 38882 * @param {TimeRanges} timeRanges - the TimeRanges object to query 38883 * @param {number} time - the time to filter on. 38884 * @return {TimeRanges} a new TimeRanges object. 38885 */ 38886 38887 38888 var findNextRange = function findNextRange(timeRanges, time) { 38889 return filterRanges(timeRanges, function (start) { 38890 return start - TIME_FUDGE_FACTOR >= time; 38891 }); 38892 }; 38893 /** 38894 * Returns gaps within a list of TimeRanges 38895 * 38896 * @param {TimeRanges} buffered - the TimeRanges object 38897 * @return {TimeRanges} a TimeRanges object of gaps 38898 */ 38899 38900 38901 var findGaps = function findGaps(buffered) { 38902 if (buffered.length < 2) { 38903 return videojs$1.createTimeRanges(); 38904 } 38905 38906 var ranges = []; 38907 38908 for (var i = 1; i < buffered.length; i++) { 38909 var start = buffered.end(i - 1); 38910 var end = buffered.start(i); 38911 ranges.push([start, end]); 38912 } 38913 38914 return videojs$1.createTimeRanges(ranges); 38915 }; 38916 /** 38917 * Calculate the intersection of two TimeRanges 38918 * 38919 * @param {TimeRanges} bufferA 38920 * @param {TimeRanges} bufferB 38921 * @return {TimeRanges} The interesection of `bufferA` with `bufferB` 38922 */ 38923 38924 38925 var bufferIntersection = function bufferIntersection(bufferA, bufferB) { 38926 var start = null; 38927 var end = null; 38928 var arity = 0; 38929 var extents = []; 38930 var ranges = []; 38931 38932 if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) { 38933 return videojs$1.createTimeRange(); 38934 } // Handle the case where we have both buffers and create an 38935 // intersection of the two 38936 38937 38938 var count = bufferA.length; // A) Gather up all start and end times 38939 38940 while (count--) { 38941 extents.push({ 38942 time: bufferA.start(count), 38943 type: 'start' 38944 }); 38945 extents.push({ 38946 time: bufferA.end(count), 38947 type: 'end' 38948 }); 38949 } 38950 38951 count = bufferB.length; 38952 38953 while (count--) { 38954 extents.push({ 38955 time: bufferB.start(count), 38956 type: 'start' 38957 }); 38958 extents.push({ 38959 time: bufferB.end(count), 38960 type: 'end' 38961 }); 38962 } // B) Sort them by time 38963 38964 38965 extents.sort(function (a, b) { 38966 return a.time - b.time; 38967 }); // C) Go along one by one incrementing arity for start and decrementing 38968 // arity for ends 38969 38970 for (count = 0; count < extents.length; count++) { 38971 if (extents[count].type === 'start') { 38972 arity++; // D) If arity is ever incremented to 2 we are entering an 38973 // overlapping range 38974 38975 if (arity === 2) { 38976 start = extents[count].time; 38977 } 38978 } else if (extents[count].type === 'end') { 38979 arity--; // E) If arity is ever decremented to 1 we leaving an 38980 // overlapping range 38981 38982 if (arity === 1) { 38983 end = extents[count].time; 38984 } 38985 } // F) Record overlapping ranges 38986 38987 38988 if (start !== null && end !== null) { 38989 ranges.push([start, end]); 38990 start = null; 38991 end = null; 38992 } 38993 } 38994 38995 return videojs$1.createTimeRanges(ranges); 38996 }; 38997 /** 38998 * Gets a human readable string for a TimeRange 38999 * 39000 * @param {TimeRange} range 39001 * @return {string} a human readable string 39002 */ 39003 39004 39005 var printableRange = function printableRange(range) { 39006 var strArr = []; 39007 39008 if (!range || !range.length) { 39009 return ''; 39010 } 39011 39012 for (var i = 0; i < range.length; i++) { 39013 strArr.push(range.start(i) + ' => ' + range.end(i)); 39014 } 39015 39016 return strArr.join(', '); 39017 }; 39018 /** 39019 * Calculates the amount of time left in seconds until the player hits the end of the 39020 * buffer and causes a rebuffer 39021 * 39022 * @param {TimeRange} buffered 39023 * The state of the buffer 39024 * @param {Numnber} currentTime 39025 * The current time of the player 39026 * @param {number} playbackRate 39027 * The current playback rate of the player. Defaults to 1. 39028 * @return {number} 39029 * Time until the player has to start rebuffering in seconds. 39030 * @function timeUntilRebuffer 39031 */ 39032 39033 39034 var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime, playbackRate) { 39035 if (playbackRate === void 0) { 39036 playbackRate = 1; 39037 } 39038 39039 var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0; 39040 return (bufferedEnd - currentTime) / playbackRate; 39041 }; 39042 /** 39043 * Converts a TimeRanges object into an array representation 39044 * 39045 * @param {TimeRanges} timeRanges 39046 * @return {Array} 39047 */ 39048 39049 39050 var timeRangesToArray = function timeRangesToArray(timeRanges) { 39051 var timeRangesList = []; 39052 39053 for (var i = 0; i < timeRanges.length; i++) { 39054 timeRangesList.push({ 39055 start: timeRanges.start(i), 39056 end: timeRanges.end(i) 39057 }); 39058 } 39059 39060 return timeRangesList; 39061 }; 39062 /** 39063 * Determines if two time range objects are different. 39064 * 39065 * @param {TimeRange} a 39066 * the first time range object to check 39067 * 39068 * @param {TimeRange} b 39069 * the second time range object to check 39070 * 39071 * @return {Boolean} 39072 * Whether the time range objects differ 39073 */ 39074 39075 39076 var isRangeDifferent = function isRangeDifferent(a, b) { 39077 // same object 39078 if (a === b) { 39079 return false; 39080 } // one or the other is undefined 39081 39082 39083 if (!a && b || !b && a) { 39084 return true; 39085 } // length is different 39086 39087 39088 if (a.length !== b.length) { 39089 return true; 39090 } // see if any start/end pair is different 39091 39092 39093 for (var i = 0; i < a.length; i++) { 39094 if (a.start(i) !== b.start(i) || a.end(i) !== b.end(i)) { 39095 return true; 39096 } 39097 } // if the length and every pair is the same 39098 // this is the same time range 39099 39100 39101 return false; 39102 }; 39103 /** 39104 * @file playlist.js 39105 * 39106 * Playlist related utilities. 39107 */ 39108 39109 39110 var createTimeRange = videojs$1.createTimeRange; 39111 /** 39112 * walk backward until we find a duration we can use 39113 * or return a failure 39114 * 39115 * @param {Playlist} playlist the playlist to walk through 39116 * @param {Number} endSequence the mediaSequence to stop walking on 39117 */ 39118 39119 var backwardDuration = function backwardDuration(playlist, endSequence) { 39120 var result = 0; 39121 var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following 39122 // the interval, use it 39123 39124 var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline 39125 // information that is earlier than endSequence 39126 39127 if (segment) { 39128 if (typeof segment.start !== 'undefined') { 39129 return { 39130 result: segment.start, 39131 precise: true 39132 }; 39133 } 39134 39135 if (typeof segment.end !== 'undefined') { 39136 return { 39137 result: segment.end - segment.duration, 39138 precise: true 39139 }; 39140 } 39141 } 39142 39143 while (i--) { 39144 segment = playlist.segments[i]; 39145 39146 if (typeof segment.end !== 'undefined') { 39147 return { 39148 result: result + segment.end, 39149 precise: true 39150 }; 39151 } 39152 39153 result += segment.duration; 39154 39155 if (typeof segment.start !== 'undefined') { 39156 return { 39157 result: result + segment.start, 39158 precise: true 39159 }; 39160 } 39161 } 39162 39163 return { 39164 result: result, 39165 precise: false 39166 }; 39167 }; 39168 /** 39169 * walk forward until we find a duration we can use 39170 * or return a failure 39171 * 39172 * @param {Playlist} playlist the playlist to walk through 39173 * @param {number} endSequence the mediaSequence to stop walking on 39174 */ 39175 39176 39177 var forwardDuration = function forwardDuration(playlist, endSequence) { 39178 var result = 0; 39179 var segment; 39180 var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline 39181 // information 39182 39183 for (; i < playlist.segments.length; i++) { 39184 segment = playlist.segments[i]; 39185 39186 if (typeof segment.start !== 'undefined') { 39187 return { 39188 result: segment.start - result, 39189 precise: true 39190 }; 39191 } 39192 39193 result += segment.duration; 39194 39195 if (typeof segment.end !== 'undefined') { 39196 return { 39197 result: segment.end - result, 39198 precise: true 39199 }; 39200 } 39201 } // indicate we didn't find a useful duration estimate 39202 39203 39204 return {
vendor: 4,627 bytes, lines 39205-39345
39205 result: -1, 39206 precise: false 39207 }; 39208 }; 39209 /** 39210 * Calculate the media duration from the segments associated with a 39211 * playlist. The duration of a subinterval of the available segments 39212 * may be calculated by specifying an end index. 39213 * 39214 * @param {Object} playlist a media playlist object 39215 * @param {number=} endSequence an exclusive upper boundary 39216 * for the playlist. Defaults to playlist length. 39217 * @param {number} expired the amount of time that has dropped 39218 * off the front of the playlist in a live scenario 39219 * @return {number} the duration between the first available segment 39220 * and end index. 39221 */ 39222 39223 39224 var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 39225 if (typeof endSequence === 'undefined') { 39226 endSequence = playlist.mediaSequence + playlist.segments.length; 39227 } 39228 39229 if (endSequence < playlist.mediaSequence) { 39230 return 0; 39231 } // do a backward walk to estimate the duration 39232 39233 39234 var backward = backwardDuration(playlist, endSequence); 39235 39236 if (backward.precise) { 39237 // if we were able to base our duration estimate on timing 39238 // information provided directly from the Media Source, return 39239 // it 39240 return backward.result; 39241 } // walk forward to see if a precise duration estimate can be made 39242 // that way 39243 39244 39245 var forward = forwardDuration(playlist, endSequence); 39246 39247 if (forward.precise) { 39248 // we found a segment that has been buffered and so it's 39249 // position is known precisely 39250 return forward.result; 39251 } // return the less-precise, playlist-based duration estimate 39252 39253 39254 return backward.result + expired; 39255 }; 39256 /** 39257 * Calculates the duration of a playlist. If a start and end index 39258 * are specified, the duration will be for the subset of the media 39259 * timeline between those two indices. The total duration for live 39260 * playlists is always Infinity. 39261 * 39262 * @param {Object} playlist a media playlist object 39263 * @param {number=} endSequence an exclusive upper 39264 * boundary for the playlist. Defaults to the playlist media 39265 * sequence number plus its length. 39266 * @param {number=} expired the amount of time that has 39267 * dropped off the front of the playlist in a live scenario 39268 * @return {number} the duration between the start index and end 39269 * index. 39270 */ 39271 39272 39273 var duration = function duration(playlist, endSequence, expired) { 39274 if (!playlist) { 39275 return 0; 39276 } 39277 39278 if (typeof expired !== 'number') { 39279 expired = 0; 39280 } // if a slice of the total duration is not requested, use 39281 // playlist-level duration indicators when they're present 39282 39283 39284 if (typeof endSequence === 'undefined') { 39285 // if present, use the duration specified in the playlist 39286 if (playlist.totalDuration) { 39287 return playlist.totalDuration; 39288 } // duration should be Infinity for live playlists 39289 39290 39291 if (!playlist.endList) { 39292 return window$1.Infinity; 39293 } 39294 } // calculate the total duration based on the segment durations 39295 39296 39297 return intervalDuration(playlist, endSequence, expired); 39298 }; 39299 /** 39300 * Calculate the time between two indexes in the current playlist 39301 * neight the start- nor the end-index need to be within the current 39302 * playlist in which case, the targetDuration of the playlist is used 39303 * to approximate the durations of the segments 39304 * 39305 * @param {Object} playlist a media playlist object 39306 * @param {number} startIndex 39307 * @param {number} endIndex 39308 * @return {number} the number of seconds between startIndex and endIndex 39309 */ 39310 39311 39312 var sumDurations = function sumDurations(playlist, startIndex, endIndex) { 39313 var durations = 0; 39314 39315 if (startIndex > endIndex) { 39316 var _ref = [endIndex, startIndex]; 39317 startIndex = _ref[0]; 39318 endIndex = _ref[1]; 39319 } 39320 39321 if (startIndex < 0) { 39322 for (var i = startIndex; i < Math.min(0, endIndex); i++) { 39323 durations += playlist.targetDuration; 39324 } 39325 39326 startIndex = 0; 39327 } 39328 39329 for (var _i = startIndex; _i < endIndex; _i++) { 39330 durations += playlist.segments[_i].duration; 39331 } 39332 39333 return durations; 39334 }; 39335 /** 39336 * Determines the media index of the segment corresponding to the safe edge of the live 39337 * window which is the duration of the last segment plus 2 target durations from the end 39338 * of the playlist. 39339 * 39340 * A liveEdgePadding can be provided which will be used instead of calculating the safe live edge. 39341 * This corresponds to suggestedPresentationDelay in DASH manifests. 39342 * 39343 * @param {Object} playlist 39344 * a media playlist object 39345 * @param {number} [liveEdgePadding]
39346 * A number in seconds indicating how far from the end we want to be. 39347 * If provided, this value is used instead of calculating the safe live index from the target durations. 39348 * Corresponds to suggestedPresentationDelay in DASH manifests. 39349 * @return {number} 39350 * The media index of the segment at the safe live point. 0 if there is no "safe" 39351 * point. 39352 * @function safeLiveIndex 39353 */ 39354 39355 39356 var safeLiveIndex = function safeLiveIndex(playlist, liveEdgePadding) { 39357 if (!playlist.segments.length) { 39358 return 0; 39359 } 39360 39361 var i = playlist.segments.length; 39362 var lastSegmentDuration = playlist.segments[i - 1].duration || playlist.targetDuration; 39363 var safeDistance = typeof liveEdgePadding === 'number' ? liveEdgePadding : lastSegmentDuration + playlist.targetDuration * 2; 39364 39365 if (safeDistance === 0) { 39366 return i; 39367 } 39368 39369 var distanceFromEnd = 0; 39370 39371 while (i--) { 39372 distanceFromEnd += playlist.segments[i].duration; 39373 39374 if (distanceFromEnd >= safeDistance) { 39375 break; 39376 } 39377 } 39378 39379 return Math.max(0, i); 39380 }; 39381 /** 39382 * Calculates the playlist end time 39383 * 39384 * @param {Object} playlist a media playlist object 39385 * @param {number=} expired the amount of time that has 39386 * dropped off the front of the playlist in a live scenario 39387 * @param {boolean|false} useSafeLiveEnd a boolean value indicating whether or not the 39388 * playlist end calculation should consider the safe live end 39389 * (truncate the playlist end by three segments). This is normally 39390 * used for calculating the end of the playlist's seekable range. 39391 * This takes into account the value of liveEdgePadding. 39392 * Setting liveEdgePadding to 0 is equivalent to setting this to false. 39393 * @param {number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds. 39394 * If this is provided, it is used in the safe live end calculation. 39395 * Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent. 39396 * Corresponds to suggestedPresentationDelay in DASH manifests. 39397 * @return {number} the end time of playlist 39398 * @function playlistEnd 39399 */ 39400 39401 39402 var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding) { 39403 if (!playlist || !playlist.segments) { 39404 return null; 39405 } 39406 39407 if (playlist.endList) { 39408 return duration(playlist); 39409 } 39410 39411 if (expired === null) { 39412 return null; 39413 } 39414 39415 expired = expired || 0; 39416 var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist, liveEdgePadding) : playlist.segments.length; 39417 return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); 39418 }; 39419 /** 39420 * Calculates the interval of time that is currently seekable in a 39421 * playlist. The returned time ranges are relative to the earliest 39422 * moment in the specified playlist that is still available. A full 39423 * seekable implementation for live streams would need to offset 39424 * these values by the duration of content that has expired from the 39425 * stream. 39426 * 39427 * @param {Object} playlist a media playlist object 39428 * dropped off the front of the playlist in a live scenario 39429 * @param {number=} expired the amount of time that has 39430 * dropped off the front of the playlist in a live scenario 39431 * @param {number} liveEdgePadding how far from the end of the playlist we should be in seconds. 39432 * Corresponds to suggestedPresentationDelay in DASH manifests. 39433 * @return {TimeRanges} the periods of time that are valid targets 39434 * for seeking 39435 */ 39436 39437 39438 var seekable = function seekable(playlist, expired, liveEdgePadding) { 39439 var useSafeLiveEnd = true; 39440 var seekableStart = expired || 0; 39441 var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding); 39442 39443 if (seekableEnd === null) { 39444 return createTimeRange(); 39445 } 39446 39447 return createTimeRange(seekableStart, seekableEnd); 39448 }; 39449 /** 39450 * Determine the index and estimated starting time of the segment that 39451 * contains a specified playback position in a media playlist. 39452 * 39453 * @param {Object} playlist the media playlist to query 39454 * @param {number} currentTime The number of seconds since the earliest 39455 * possible position to determine the containing segment for 39456 * @param {number} startIndex 39457 * @param {number} startTime 39458 * @return {Object} 39459 */ 39460 39461 39462 var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) { 39463 var i; 39464 var segment; 39465 var numSegments = playlist.segments.length; 39466 var time = currentTime - startTime; 39467 39468 if (time < 0) { 39469 // Walk backward from startIndex in the playlist, adding durations 39470 // until we find a segment that contains `time` and return it 39471 if (startIndex > 0) { 39472 for (i = startIndex - 1; i >= 0; i--) { 39473 segment = playlist.segments[i]; 39474 time += segment.duration + TIME_FUDGE_FACTOR; 39475 39476 if (time > 0) { 39477 return { 39478 mediaIndex: i, 39479 startTime: startTime - sumDurations(playlist, startIndex, i) 39480 }; 39481 } 39482 } 39483 } // We were unable to find a good segment within the playlist 39484 // so select the first segment 39485 39486 39487 return { 39488 mediaIndex: 0, 39489 startTime: currentTime 39490 }; 39491 } // When startIndex is negative, we first walk forward to first segment 39492 // adding target durations. If we "run out of time" before getting to 39493 // the first segment, return the first segment 39494 39495 39496 if (startIndex < 0) { 39497 for (i = startIndex; i < 0; i++) { 39498 time -= playlist.targetDuration; 39499 39500 if (time < 0) { 39501 return { 39502 mediaIndex: 0, 39503 startTime: currentTime 39504 }; 39505 } 39506 } 39507 39508 startIndex = 0; 39509 } // Walk forward from startIndex in the playlist, subtracting durations 39510 // until we find a segment that contains `time` and return it 39511 39512 39513 for (i = startIndex; i < numSegments; i++) { 39514 segment = playlist.segments[i]; 39515 time -= segment.duration + TIME_FUDGE_FACTOR; 39516 39517 if (time < 0) { 39518 return { 39519 mediaIndex: i, 39520 startTime: startTime + sumDurations(playlist, startIndex, i) 39521 }; 39522 } 39523 } // We are out of possible candidates so load the last one... 39524 39525 39526 return { 39527 mediaIndex: numSegments - 1, 39528 startTime: currentTime 39529 }; 39530 }; 39531 /** 39532 * Check whether the playlist is blacklisted or not. 39533 * 39534 * @param {Object} playlist the media playlist object 39535 * @return {boolean} whether the playlist is blacklisted or not 39536 * @function isBlacklisted 39537 */ 39538 39539 39540 var isBlacklisted = function isBlacklisted(playlist) { 39541 return playlist.excludeUntil && playlist.excludeUntil > Date.now(); 39542 }; 39543 /** 39544 * Check whether the playlist is compatible with current playback configuration or has 39545 * been blacklisted permanently for being incompatible. 39546 * 39547 * @param {Object} playlist the media playlist object 39548 * @return {boolean} whether the playlist is incompatible or not 39549 * @function isIncompatible 39550 */ 39551 39552 39553 var isIncompatible = function isIncompatible(playlist) { 39554 return playlist.excludeUntil && playlist.excludeUntil === Infinity; 39555 }; 39556 /** 39557 * Check whether the playlist is enabled or not. 39558 * 39559 * @param {Object} playlist the media playlist object 39560 * @return {boolean} whether the playlist is enabled or not 39561 * @function isEnabled 39562 */ 39563 39564 39565 var isEnabled = function isEnabled(playlist) { 39566 var blacklisted = isBlacklisted(playlist); 39567 return !playlist.disabled && !blacklisted; 39568 }; 39569 /** 39570 * Check whether the playlist has been manually disabled through the representations api. 39571 * 39572 * @param {Object} playlist the media playlist object 39573 * @return {boolean} whether the playlist is disabled manually or not 39574 * @function isDisabled 39575 */ 39576 39577 39578 var isDisabled = function isDisabled(playlist) { 39579 return playlist.disabled; 39580 }; 39581 /** 39582 * Returns whether the current playlist is an AES encrypted HLS stream 39583 * 39584 * @return {boolean} true if it's an AES encrypted HLS stream 39585 */ 39586 39587 39588 var isAes = function isAes(media) { 39589 for (var i = 0; i < media.segments.length; i++) { 39590 if (media.segments[i].key) { 39591 return true; 39592 } 39593 } 39594 39595 return false;
vendor: 7,464 bytes, lines 39596-39838
39596 }; 39597 /** 39598 * Checks if the playlist has a value for the specified attribute 39599 * 39600 * @param {string} attr 39601 * Attribute to check for 39602 * @param {Object} playlist 39603 * The media playlist object 39604 * @return {boolean} 39605 * Whether the playlist contains a value for the attribute or not 39606 * @function hasAttribute 39607 */ 39608 39609 39610 var hasAttribute = function hasAttribute(attr, playlist) { 39611 return playlist.attributes && playlist.attributes[attr]; 39612 }; 39613 /** 39614 * Estimates the time required to complete a segment download from the specified playlist 39615 * 39616 * @param {number} segmentDuration 39617 * Duration of requested segment 39618 * @param {number} bandwidth 39619 * Current measured bandwidth of the player 39620 * @param {Object} playlist 39621 * The media playlist object 39622 * @param {number=} bytesReceived 39623 * Number of bytes already received for the request. Defaults to 0 39624 * @return {number|NaN} 39625 * The estimated time to request the segment. NaN if bandwidth information for 39626 * the given playlist is unavailable 39627 * @function estimateSegmentRequestTime 39628 */ 39629 39630 39631 var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist, bytesReceived) { 39632 if (bytesReceived === void 0) { 39633 bytesReceived = 0; 39634 } 39635 39636 if (!hasAttribute('BANDWIDTH', playlist)) { 39637 return NaN; 39638 } 39639 39640 var size = segmentDuration * playlist.attributes.BANDWIDTH; 39641 return (size - bytesReceived * 8) / bandwidth; 39642 }; 39643 /* 39644 * Returns whether the current playlist is the lowest rendition 39645 * 39646 * @return {Boolean} true if on lowest rendition 39647 */ 39648 39649 39650 var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) { 39651 if (master.playlists.length === 1) { 39652 return true; 39653 } 39654 39655 var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; 39656 return master.playlists.filter(function (playlist) { 39657 if (!isEnabled(playlist)) { 39658 return false; 39659 } 39660 39661 return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth; 39662 }).length === 0; 39663 }; // exports 39664 39665 39666 var Playlist = { 39667 duration: duration, 39668 seekable: seekable, 39669 safeLiveIndex: safeLiveIndex, 39670 getMediaInfoForTime: getMediaInfoForTime, 39671 isEnabled: isEnabled, 39672 isDisabled: isDisabled, 39673 isBlacklisted: isBlacklisted, 39674 isIncompatible: isIncompatible, 39675 playlistEnd: playlistEnd, 39676 isAes: isAes, 39677 hasAttribute: hasAttribute, 39678 estimateSegmentRequestTime: estimateSegmentRequestTime, 39679 isLowestEnabledRendition: isLowestEnabledRendition 39680 }; 39681 /** 39682 * @file xhr.js 39683 */ 39684 39685 var videojsXHR = videojs$1.xhr, 39686 mergeOptions$1$1 = videojs$1.mergeOptions; 39687 39688 var callbackWrapper = function callbackWrapper(request, error, response, callback) { 39689 var reqResponse = request.responseType === 'arraybuffer' ? request.response : request.responseText; 39690 39691 if (!error && reqResponse) { 39692 request.responseTime = Date.now(); 39693 request.roundTripTime = request.responseTime - request.requestTime; 39694 request.bytesReceived = reqResponse.byteLength || reqResponse.length; 39695 39696 if (!request.bandwidth) { 39697 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 39698 } 39699 } 39700 39701 if (response.headers) { 39702 request.responseHeaders = response.headers; 39703 } // videojs.xhr now uses a specific code on the error 39704 // object to signal that a request has timed out instead 39705 // of setting a boolean on the request object 39706 39707 39708 if (error && error.code === 'ETIMEDOUT') { 39709 request.timedout = true; 39710 } // videojs.xhr no longer considers status codes outside of 200 and 0 39711 // (for file uris) to be errors, but the old XHR did, so emulate that 39712 // behavior. Status 206 may be used in response to byterange requests. 39713 39714 39715 if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 39716 error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))); 39717 } 39718 39719 callback(error, request); 39720 }; 39721 39722 var xhrFactory = function xhrFactory() { 39723 var xhr = function XhrFunction(options, callback) { 39724 // Add a default timeout 39725 options = mergeOptions$1$1({ 39726 timeout: 45e3 39727 }, options); // Allow an optional user-specified function to modify the option 39728 // object before we construct the xhr request 39729 39730 var beforeRequest = XhrFunction.beforeRequest || videojs$1.Vhs.xhr.beforeRequest; 39731 39732 if (beforeRequest && typeof beforeRequest === 'function') { 39733 var newOptions = beforeRequest(options); 39734 39735 if (newOptions) { 39736 options = newOptions; 39737 } 39738 } 39739 39740 var request = videojsXHR(options, function (error, response) { 39741 return callbackWrapper(request, error, response, callback); 39742 }); 39743 var originalAbort = request.abort; 39744 39745 request.abort = function () { 39746 request.aborted = true; 39747 return originalAbort.apply(request, arguments); 39748 }; 39749 39750 request.uri = options.uri; 39751 request.requestTime = Date.now(); 39752 return request; 39753 }; 39754 39755 return xhr; 39756 }; 39757 /** 39758 * Turns segment byterange into a string suitable for use in 39759 * HTTP Range requests 39760 * 39761 * @param {Object} byterange - an object with two values defining the start and end 39762 * of a byte-range 39763 */ 39764 39765 39766 var byterangeStr = function byterangeStr(byterange) { 39767 // `byterangeEnd` is one less than `offset + length` because the HTTP range 39768 // header uses inclusive ranges 39769 var byterangeEnd = byterange.offset + byterange.length - 1; 39770 var byterangeStart = byterange.offset; 39771 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 39772 }; 39773 /** 39774 * Defines headers for use in the xhr request for a particular segment. 39775 * 39776 * @param {Object} segment - a simplified copy of the segmentInfo object 39777 * from SegmentLoader 39778 */ 39779 39780 39781 var segmentXhrHeaders = function segmentXhrHeaders(segment) { 39782 var headers = {}; 39783 39784 if (segment.byterange) { 39785 headers.Range = byterangeStr(segment.byterange); 39786 } 39787 39788 return headers; 39789 }; 39790 /** 39791 * @file bin-utils.js 39792 */ 39793 39794 /** 39795 * convert a TimeRange to text 39796 * 39797 * @param {TimeRange} range the timerange to use for conversion 39798 * @param {number} i the iterator on the range to convert 39799 */ 39800 39801 39802 var textRange = function textRange(range, i) { 39803 return range.start(i) + '-' + range.end(i); 39804 }; 39805 /** 39806 * format a number as hex string 39807 * 39808 * @param {number} e The number 39809 * @param {number} i the iterator 39810 */ 39811 39812 39813 var formatHexString = function formatHexString(e, i) { 39814 var value = e.toString(16); 39815 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 39816 }; 39817 39818 var formatAsciiString = function formatAsciiString(e) { 39819 if (e >= 0x20 && e < 0x7e) { 39820 return String.fromCharCode(e); 39821 } 39822 39823 return '.'; 39824 }; 39825 /** 39826 * Creates an object for sending to a web worker modifying properties that are TypedArrays 39827 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 39828 * 39829 * @param {Object} message 39830 * Object of properties and values to send to the web worker 39831 * @return {Object} 39832 * Modified message with TypedArray values expanded 39833 * @function createTransferableMessage 39834 */ 39835 39836 39837 var createTransferableMessage = function createTransferableMessage(message) { 39838 var transferable = {};
vendor: 33,153 bytes, lines 39839-40813
39839 Object.keys(message).forEach(function (key) { 39840 var value = message[key]; 39841 39842 if (ArrayBuffer.isView(value)) { 39843 transferable[key] = { 39844 bytes: value.buffer, 39845 byteOffset: value.byteOffset, 39846 byteLength: value.byteLength 39847 }; 39848 } else { 39849 transferable[key] = value; 39850 } 39851 }); 39852 return transferable; 39853 }; 39854 /** 39855 * Returns a unique string identifier for a media initialization 39856 * segment. 39857 */ 39858 39859 39860 var initSegmentId = function initSegmentId(initSegment) { 39861 var byterange = initSegment.byterange || { 39862 length: Infinity, 39863 offset: 0 39864 }; 39865 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 39866 }; 39867 /** 39868 * Returns a unique string identifier for a media segment key. 39869 */ 39870 39871 39872 var segmentKeyId = function segmentKeyId(key) { 39873 return key.resolvedUri; 39874 }; 39875 /** 39876 * utils to help dump binary data to the console 39877 */ 39878 39879 39880 var hexDump = function hexDump(data) { 39881 var bytes = Array.prototype.slice.call(data); 39882 var step = 16; 39883 var result = ''; 39884 var hex; 39885 var ascii; 39886 39887 for (var j = 0; j < bytes.length / step; j++) { 39888 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 39889 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 39890 result += hex + ' ' + ascii + '\n'; 39891 } 39892 39893 return result; 39894 }; 39895 39896 var tagDump = function tagDump(_ref) { 39897 var bytes = _ref.bytes; 39898 return hexDump(bytes); 39899 }; 39900 39901 var textRanges = function textRanges(ranges) { 39902 var result = ''; 39903 var i; 39904 39905 for (i = 0; i < ranges.length; i++) { 39906 result += textRange(ranges, i) + ' '; 39907 } 39908 39909 return result; 39910 }; 39911 39912 var utils$1 = /*#__PURE__*/Object.freeze({ 39913 __proto__: null, 39914 createTransferableMessage: createTransferableMessage, 39915 initSegmentId: initSegmentId, 39916 segmentKeyId: segmentKeyId, 39917 hexDump: hexDump, 39918 tagDump: tagDump, 39919 textRanges: textRanges 39920 }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux 39921 // 25% was arbitrarily chosen, and may need to be refined over time. 39922 39923 var SEGMENT_END_FUDGE_PERCENT = 0.25; 39924 /** 39925 * Converts a player time (any time that can be gotten/set from player.currentTime(), 39926 * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a 39927 * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)). 39928 * 39929 * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor 39930 * point" (a point where we have a mapping from program time to player time, with player 39931 * time being the post transmux start of the segment). 39932 * 39933 * For more details, see [this doc](../../docs/program-time-from-player-time.md). 39934 * 39935 * @param {number} playerTime the player time 39936 * @param {Object} segment the segment which contains the player time 39937 * @return {Date} program time 39938 */ 39939 39940 var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) { 39941 if (!segment.dateTimeObject) { 39942 // Can't convert without an "anchor point" for the program time (i.e., a time that can 39943 // be used to map the start of a segment with a real world time). 39944 return null; 39945 } 39946 39947 var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds; 39948 var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended 39949 39950 var startOfSegment = transmuxedStart + transmuxerPrependedSeconds; 39951 var offsetFromSegmentStart = playerTime - startOfSegment; 39952 return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000); 39953 }; 39954 39955 var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) { 39956 return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds; 39957 }; 39958 /** 39959 * Finds a segment that contains the time requested given as an ISO-8601 string. The 39960 * returned segment might be an estimate or an accurate match. 39961 * 39962 * @param {string} programTime The ISO-8601 programTime to find a match for 39963 * @param {Object} playlist A playlist object to search within 39964 */ 39965 39966 39967 var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) { 39968 // Assumptions: 39969 // - verifyProgramDateTimeTags has already been run 39970 // - live streams have been started 39971 var dateTimeObject; 39972 39973 try { 39974 dateTimeObject = new Date(programTime); 39975 } catch (e) { 39976 return null; 39977 } 39978 39979 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 39980 return null; 39981 } 39982 39983 var segment = playlist.segments[0]; 39984 39985 if (dateTimeObject < segment.dateTimeObject) { 39986 // Requested time is before stream start. 39987 return null; 39988 } 39989 39990 for (var i = 0; i < playlist.segments.length - 1; i++) { 39991 segment = playlist.segments[i]; 39992 var nextSegmentStart = playlist.segments[i + 1].dateTimeObject; 39993 39994 if (dateTimeObject < nextSegmentStart) { 39995 break; 39996 } 39997 } 39998 39999 var lastSegment = playlist.segments[playlist.segments.length - 1]; 40000 var lastSegmentStart = lastSegment.dateTimeObject; 40001 var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT; 40002 var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000); 40003 40004 if (dateTimeObject > lastSegmentEnd) { 40005 // Beyond the end of the stream, or our best guess of the end of the stream. 40006 return null; 40007 } 40008 40009 if (dateTimeObject > lastSegmentStart) { 40010 segment = lastSegment; 40011 } 40012 40013 return { 40014 segment: segment, 40015 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)), 40016 // Although, given that all segments have accurate date time objects, the segment 40017 // selected should be accurate, unless the video has been transmuxed at some point 40018 // (determined by the presence of the videoTimingInfo object), the segment's "player 40019 // time" (the start time in the player) can't be considered accurate. 40020 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 40021 }; 40022 }; 40023 /** 40024 * Finds a segment that contains the given player time(in seconds). 40025 * 40026 * @param {number} time The player time to find a match for 40027 * @param {Object} playlist A playlist object to search within 40028 */ 40029 40030 40031 var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) { 40032 // Assumptions: 40033 // - there will always be a segment.duration 40034 // - we can start from zero 40035 // - segments are in time order 40036 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 40037 return null; 40038 } 40039 40040 var segmentEnd = 0; 40041 var segment; 40042 40043 for (var i = 0; i < playlist.segments.length; i++) { 40044 segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and 40045 // should contain the most accurate values we have for the segment's player times. 40046 // 40047 // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall 40048 // back to an estimate based on the manifest derived (inaccurate) segment.duration, to 40049 // calculate an end value. 40050 40051 segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration; 40052 40053 if (time <= segmentEnd) { 40054 break; 40055 } 40056 } 40057 40058 var lastSegment = playlist.segments[playlist.segments.length - 1]; 40059 40060 if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) { 40061 // The time requested is beyond the stream end. 40062 return null; 40063 } 40064 40065 if (time > segmentEnd) { 40066 // The time is within or beyond the last segment. 40067 // 40068 // Check to see if the time is beyond a reasonable guess of the end of the stream. 40069 if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) { 40070 // Technically, because the duration value is only an estimate, the time may still 40071 // exist in the last segment, however, there isn't enough information to make even 40072 // a reasonable estimate. 40073 return null; 40074 } 40075 40076 segment = lastSegment; 40077 } 40078 40079 return { 40080 segment: segment, 40081 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration, 40082 // Because videoTimingInfo is only set after transmux, it is the only way to get 40083 // accurate timing values. 40084 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 40085 }; 40086 }; 40087 /** 40088 * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds. 40089 * If the offset returned is positive, the programTime occurs after the 40090 * comparisonTimestamp. 40091 * If the offset is negative, the programTime occurs before the comparisonTimestamp. 40092 * 40093 * @param {string} comparisonTimeStamp An ISO-8601 timestamp to compare against 40094 * @param {string} programTime The programTime as an ISO-8601 string 40095 * @return {number} offset 40096 */ 40097 40098 40099 var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) { 40100 var segmentDateTime; 40101 var programDateTime; 40102 40103 try { 40104 segmentDateTime = new Date(comparisonTimeStamp); 40105 programDateTime = new Date(programTime); 40106 } catch (e) {// TODO handle error 40107 } 40108 40109 var segmentTimeEpoch = segmentDateTime.getTime(); 40110 var programTimeEpoch = programDateTime.getTime(); 40111 return (programTimeEpoch - segmentTimeEpoch) / 1000; 40112 }; 40113 /** 40114 * Checks that all segments in this playlist have programDateTime tags. 40115 * 40116 * @param {Object} playlist A playlist object 40117 */ 40118 40119 40120 var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) { 40121 if (!playlist.segments || playlist.segments.length === 0) { 40122 return false; 40123 } 40124 40125 for (var i = 0; i < playlist.segments.length; i++) { 40126 var segment = playlist.segments[i]; 40127 40128 if (!segment.dateTimeObject) { 40129 return false; 40130 } 40131 } 40132 40133 return true; 40134 }; 40135 /** 40136 * Returns the programTime of the media given a playlist and a playerTime. 40137 * The playlist must have programDateTime tags for a programDateTime tag to be returned. 40138 * If the segments containing the time requested have not been buffered yet, an estimate 40139 * may be returned to the callback. 40140 * 40141 * @param {Object} args 40142 * @param {Object} args.playlist A playlist object to search within 40143 * @param {number} time A playerTime in seconds 40144 * @param {Function} callback(err, programTime) 40145 * @return {string} err.message A detailed error message 40146 * @return {Object} programTime 40147 * @return {number} programTime.mediaSeconds The streamTime in seconds 40148 * @return {string} programTime.programDateTime The programTime as an ISO-8601 String 40149 */ 40150 40151 40152 var getProgramTime = function getProgramTime(_ref) { 40153 var playlist = _ref.playlist, 40154 _ref$time = _ref.time, 40155 time = _ref$time === void 0 ? undefined : _ref$time, 40156 callback = _ref.callback; 40157 40158 if (!callback) { 40159 throw new Error('getProgramTime: callback must be provided'); 40160 } 40161 40162 if (!playlist || time === undefined) { 40163 return callback({ 40164 message: 'getProgramTime: playlist and time must be provided' 40165 }); 40166 } 40167 40168 var matchedSegment = findSegmentForPlayerTime(time, playlist); 40169 40170 if (!matchedSegment) { 40171 return callback({ 40172 message: 'valid programTime was not found' 40173 }); 40174 } 40175 40176 if (matchedSegment.type === 'estimate') { 40177 return callback({ 40178 message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again', 40179 seekTime: matchedSegment.estimatedStart 40180 }); 40181 } 40182 40183 var programTimeObject = { 40184 mediaSeconds: time 40185 }; 40186 var programTime = playerTimeToProgramTime(time, matchedSegment.segment); 40187 40188 if (programTime) { 40189 programTimeObject.programDateTime = programTime.toISOString(); 40190 } 40191 40192 return callback(null, programTimeObject); 40193 }; 40194 /** 40195 * Seeks in the player to a time that matches the given programTime ISO-8601 string. 40196 * 40197 * @param {Object} args 40198 * @param {string} args.programTime A programTime to seek to as an ISO-8601 String 40199 * @param {Object} args.playlist A playlist to look within 40200 * @param {number} args.retryCount The number of times to try for an accurate seek. Default is 2. 40201 * @param {Function} args.seekTo A method to perform a seek 40202 * @param {boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true. 40203 * @param {Object} args.tech The tech to seek on 40204 * @param {Function} args.callback(err, newTime) A callback to return the new time to 40205 * @return {string} err.message A detailed error message 40206 * @return {number} newTime The exact time that was seeked to in seconds 40207 */ 40208 40209 40210 var seekToProgramTime = function seekToProgramTime(_ref2) { 40211 var programTime = _ref2.programTime, 40212 playlist = _ref2.playlist, 40213 _ref2$retryCount = _ref2.retryCount, 40214 retryCount = _ref2$retryCount === void 0 ? 2 : _ref2$retryCount, 40215 seekTo = _ref2.seekTo, 40216 _ref2$pauseAfterSeek = _ref2.pauseAfterSeek, 40217 pauseAfterSeek = _ref2$pauseAfterSeek === void 0 ? true : _ref2$pauseAfterSeek, 40218 tech = _ref2.tech, 40219 callback = _ref2.callback; 40220 40221 if (!callback) { 40222 throw new Error('seekToProgramTime: callback must be provided'); 40223 } 40224 40225 if (typeof programTime === 'undefined' || !playlist || !seekTo) { 40226 return callback({ 40227 message: 'seekToProgramTime: programTime, seekTo and playlist must be provided' 40228 }); 40229 } 40230 40231 if (!playlist.endList && !tech.hasStarted_) { 40232 return callback({ 40233 message: 'player must be playing a live stream to start buffering' 40234 }); 40235 } 40236 40237 if (!verifyProgramDateTimeTags(playlist)) { 40238 return callback({ 40239 message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri 40240 }); 40241 } 40242 40243 var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match 40244 40245 if (!matchedSegment) { 40246 return callback({ 40247 message: programTime + " was not found in the stream" 40248 }); 40249 } 40250 40251 var segment = matchedSegment.segment; 40252 var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime); 40253 40254 if (matchedSegment.type === 'estimate') { 40255 // we've run out of retries 40256 if (retryCount === 0) { 40257 return callback({ 40258 message: programTime + " is not buffered yet. Try again" 40259 }); 40260 } 40261 40262 seekTo(matchedSegment.estimatedStart + mediaOffset); 40263 tech.one('seeked', function () { 40264 seekToProgramTime({ 40265 programTime: programTime, 40266 playlist: playlist, 40267 retryCount: retryCount - 1, 40268 seekTo: seekTo, 40269 pauseAfterSeek: pauseAfterSeek, 40270 tech: tech, 40271 callback: callback 40272 }); 40273 }); 40274 return; 40275 } // Since the segment.start value is determined from the buffered end or ending time 40276 // of the prior segment, the seekToTime doesn't need to account for any transmuxer 40277 // modifications. 40278 40279 40280 var seekToTime = segment.start + mediaOffset; 40281 40282 var seekedCallback = function seekedCallback() { 40283 return callback(null, tech.currentTime()); 40284 }; // listen for seeked event 40285 40286 40287 tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state 40288 40289 if (pauseAfterSeek) { 40290 tech.pause(); 40291 } 40292 40293 seekTo(seekToTime); 40294 }; // which will only happen if the request is complete. 40295 40296 40297 var callbackOnCompleted = function callbackOnCompleted(request, cb) { 40298 if (request.readyState === 4) { 40299 return cb(); 40300 } 40301 40302 return; 40303 }; 40304 40305 var containerRequest = function containerRequest(uri, xhr, cb) { 40306 var bytes = []; 40307 var id3Offset; 40308 var finished = false; 40309 40310 var endRequestAndCallback = function endRequestAndCallback(err, req, type, _bytes) { 40311 req.abort(); 40312 finished = true; 40313 return cb(err, req, type, _bytes); 40314 }; 40315 40316 var progressListener = function progressListener(error, request) { 40317 if (finished) { 40318 return; 40319 } 40320 40321 if (error) { 40322 return endRequestAndCallback(error, request, '', bytes); 40323 } // grap the new part of content that was just downloaded 40324 40325 40326 var newPart = request.responseText.substring(bytes && bytes.byteLength || 0, request.responseText.length); // add that onto bytes 40327 40328 bytes = byteHelpers_2(bytes, byteHelpers_4(newPart, true)); 40329 id3Offset = id3Offset || containers_2(bytes); // we need at least 10 bytes to determine a type 40330 // or we need at least two bytes after an id3Offset 40331 40332 if (bytes.length < 10 || id3Offset && bytes.length < id3Offset + 2) { 40333 return callbackOnCompleted(request, function () { 40334 return endRequestAndCallback(error, request, '', bytes); 40335 }); 40336 } 40337 40338 var type = containers_1(bytes); // if this looks like a ts segment but we don't have enough data 40339 // to see the second sync byte, wait until we have enough data 40340 // before declaring it ts 40341 40342 if (type === 'ts' && bytes.length < 188) { 40343 return callbackOnCompleted(request, function () { 40344 return endRequestAndCallback(error, request, '', bytes); 40345 }); 40346 } // this may be an unsynced ts segment 40347 // wait for 376 bytes before detecting no container 40348 40349 40350 if (!type && bytes.length < 376) { 40351 return callbackOnCompleted(request, function () { 40352 return endRequestAndCallback(error, request, '', bytes); 40353 }); 40354 } 40355 40356 return endRequestAndCallback(null, request, type, bytes); 40357 }; 40358 40359 var options = { 40360 uri: uri, 40361 beforeSend: function beforeSend(request) { 40362 // this forces the browser to pass the bytes to us unprocessed 40363 request.overrideMimeType('text/plain; charset=x-user-defined'); 40364 request.addEventListener('progress', function (_ref) { 40365 var total = _ref.total, 40366 loaded = _ref.loaded; 40367 return callbackWrapper(request, null, { 40368 statusCode: request.status 40369 }, progressListener); 40370 }); 40371 } 40372 }; 40373 var request = xhr(options, function (error, response) { 40374 return callbackWrapper(request, error, response, progressListener); 40375 }); 40376 return request; 40377 }; 40378 40379 var EventTarget$1$1 = videojs$1.EventTarget, 40380 mergeOptions$2 = videojs$1.mergeOptions; 40381 /** 40382 * Parses the master XML string and updates playlist URI references. 40383 * 40384 * @param {Object} config 40385 * Object of arguments 40386 * @param {string} config.masterXml 40387 * The mpd XML 40388 * @param {string} config.srcUrl 40389 * The mpd URL 40390 * @param {Date} config.clientOffset 40391 * A time difference between server and client 40392 * @param {Object} config.sidxMapping 40393 * SIDX mappings for moof/mdat URIs and byte ranges 40394 * @return {Object} 40395 * The parsed mpd manifest object 40396 */ 40397 40398 var parseMasterXml = function parseMasterXml(_ref) { 40399 var masterXml = _ref.masterXml, 40400 srcUrl = _ref.srcUrl, 40401 clientOffset = _ref.clientOffset, 40402 sidxMapping = _ref.sidxMapping; 40403 var master = parse(masterXml, { 40404 manifestUri: srcUrl, 40405 clientOffset: clientOffset, 40406 sidxMapping: sidxMapping 40407 }); 40408 addPropertiesToMaster(master, srcUrl); 40409 return master; 40410 }; 40411 /** 40412 * Returns a new master manifest that is the result of merging an updated master manifest 40413 * into the original version. 40414 * 40415 * @param {Object} oldMaster 40416 * The old parsed mpd object 40417 * @param {Object} newMaster 40418 * The updated parsed mpd object 40419 * @return {Object} 40420 * A new object representing the original master manifest with the updated media 40421 * playlists merged in 40422 */ 40423 40424 40425 var updateMaster$1 = function updateMaster$1(oldMaster, newMaster) { 40426 var noChanges = true; 40427 var update = mergeOptions$2(oldMaster, { 40428 // These are top level properties that can be updated 40429 duration: newMaster.duration, 40430 minimumUpdatePeriod: newMaster.minimumUpdatePeriod 40431 }); // First update the playlists in playlist list 40432 40433 for (var i = 0; i < newMaster.playlists.length; i++) { 40434 var playlistUpdate = updateMaster(update, newMaster.playlists[i]); 40435 40436 if (playlistUpdate) { 40437 update = playlistUpdate; 40438 noChanges = false; 40439 } 40440 } // Then update media group playlists 40441 40442 40443 forEachMediaGroup(newMaster, function (properties, type, group, label) { 40444 if (properties.playlists && properties.playlists.length) { 40445 var id = properties.playlists[0].id; 40446 40447 var _playlistUpdate = updateMaster(update, properties.playlists[0]); 40448 40449 if (_playlistUpdate) { 40450 update = _playlistUpdate; // update the playlist reference within media groups 40451 40452 update.mediaGroups[type][group][label].playlists[0] = update.playlists[id]; 40453 noChanges = false; 40454 } 40455 } 40456 }); 40457 40458 if (newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) { 40459 noChanges = false; 40460 } 40461 40462 if (noChanges) { 40463 return null; 40464 } 40465 40466 return update; 40467 }; 40468 40469 var generateSidxKey = function generateSidxKey(sidxInfo) { 40470 // should be non-inclusive 40471 var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1; 40472 return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd; 40473 }; // SIDX should be equivalent if the URI and byteranges of the SIDX match. 40474 // If the SIDXs have maps, the two maps should match, 40475 // both `a` and `b` missing SIDXs is considered matching. 40476 // If `a` or `b` but not both have a map, they aren't matching. 40477 40478 40479 var equivalentSidx = function equivalentSidx(a, b) { 40480 var neitherMap = Boolean(!a.map && !b.map); 40481 var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length); 40482 return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length; 40483 }; // exported for testing 40484 40485 40486 var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) { 40487 var newSidxMapping = {}; 40488 40489 for (var id in playlists) { 40490 var playlist = playlists[id]; 40491 var currentSidxInfo = playlist.sidx; 40492 40493 if (currentSidxInfo) { 40494 var key = generateSidxKey(currentSidxInfo); 40495 40496 if (!oldSidxMapping[key]) { 40497 break; 40498 } 40499 40500 var savedSidxInfo = oldSidxMapping[key].sidxInfo; 40501 40502 if (equivalentSidx(savedSidxInfo, currentSidxInfo)) { 40503 newSidxMapping[key] = oldSidxMapping[key]; 40504 } 40505 } 40506 } 40507 40508 return newSidxMapping; 40509 }; 40510 /** 40511 * A function that filters out changed items as they need to be requested separately. 40512 * 40513 * The method is exported for testing 40514 * 40515 * @param {Object} masterXml the mpd XML 40516 * @param {string} srcUrl the mpd url 40517 * @param {Date} clientOffset a time difference between server and client (passed through and not used) 40518 * @param {Object} oldSidxMapping the SIDX to compare against 40519 */ 40520 40521 40522 var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) { 40523 // Don't pass current sidx mapping 40524 var master = parse(masterXml, { 40525 manifestUri: srcUrl, 40526 clientOffset: clientOffset 40527 }); 40528 var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping); 40529 var mediaGroupSidx = videoSidx; 40530 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 40531 if (properties.playlists && properties.playlists.length) { 40532 var playlists = properties.playlists; 40533 mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping)); 40534 } 40535 }); 40536 return mediaGroupSidx; 40537 }; // exported for testing 40538 40539 40540 var requestSidx_ = function requestSidx_(loader, sidxRange, playlist, xhr, options, finishProcessingFn) { 40541 var sidxInfo = { 40542 // resolve the segment URL relative to the playlist 40543 uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri), 40544 // resolvedUri: sidxRange.resolvedUri, 40545 byterange: sidxRange.byterange, 40546 // the segment's playlist 40547 playlist: playlist 40548 }; 40549 var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, { 40550 responseType: 'arraybuffer', 40551 headers: segmentXhrHeaders(sidxInfo) 40552 }); 40553 return containerRequest(sidxInfo.uri, xhr, function (err, request, container, bytes) { 40554 if (err) { 40555 return finishProcessingFn(err, request); 40556 } 40557 40558 if (!container || container !== 'mp4') { 40559 return finishProcessingFn({ 40560 status: request.status, 40561 message: "Unsupported " + (container || 'unknown') + " container type for sidx segment at URL: " + sidxInfo.uri, 40562 // response is just bytes in this case 40563 // but we really don't want to return that. 40564 response: '', 40565 playlist: playlist, 40566 internal: true, 40567 blacklistDuration: Infinity, 40568 // MEDIA_ERR_NETWORK 40569 code: 2 40570 }, request); 40571 } // if we already downloaded the sidx bytes in the container request, use them 40572 40573 40574 var _sidxInfo$byterange = sidxInfo.byterange, 40575 offset = _sidxInfo$byterange.offset, 40576 length = _sidxInfo$byterange.length; 40577 40578 if (bytes.length >= length + offset) { 40579 return finishProcessingFn(err, { 40580 response: bytes.subarray(offset, offset + length), 40581 status: request.status, 40582 uri: request.uri 40583 }); 40584 } // otherwise request sidx bytes 40585 40586 40587 loader.request = xhr(sidxRequestOptions, finishProcessingFn); 40588 }); 40589 }; 40590 40591 var DashPlaylistLoader = /*#__PURE__*/function (_EventTarget) { 40592 inheritsLoose(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent 40593 // playlist loader setups from media groups will expect to be able to pass a playlist 40594 // (since there aren't external URLs to media playlists with DASH) 40595 40596 40597 function DashPlaylistLoader(srcUrlOrPlaylist, vhs, options, masterPlaylistLoader) { 40598 var _this; 40599 40600 if (options === void 0) { 40601 options = {}; 40602 } 40603 40604 _this = _EventTarget.call(this) || this; 40605 var _options = options, 40606 _options$withCredenti = _options.withCredentials, 40607 withCredentials = _options$withCredenti === void 0 ? false : _options$withCredenti, 40608 _options$handleManife = _options.handleManifestRedirects, 40609 handleManifestRedirects = _options$handleManife === void 0 ? false : _options$handleManife; 40610 _this.vhs_ = vhs; 40611 _this.withCredentials = withCredentials; 40612 _this.handleManifestRedirects = handleManifestRedirects; 40613 40614 if (!srcUrlOrPlaylist) { 40615 throw new Error('A non-empty playlist URL or object is required'); 40616 } // event naming? 40617 40618 40619 _this.on('minimumUpdatePeriod', function () { 40620 _this.refreshXml_(); 40621 }); // live playlist staleness timeout 40622 40623 40624 _this.on('mediaupdatetimeout', function () { 40625 _this.refreshMedia_(_this.media().id); 40626 }); 40627 40628 _this.state = 'HAVE_NOTHING'; 40629 _this.loadedPlaylists_ = {}; // initialize the loader state 40630 // The masterPlaylistLoader will be created with a string 40631 40632 if (typeof srcUrlOrPlaylist === 'string') { 40633 _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes 40634 // once multi-period is refactored 40635 40636 _this.sidxMapping_ = {}; 40637 return assertThisInitialized(_this); 40638 } 40639 40640 _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist); 40641 40642 return _this; 40643 } 40644 40645 var _proto = DashPlaylistLoader.prototype; 40646 40647 _proto.setupChildLoader = function setupChildLoader(masterPlaylistLoader, playlist) { 40648 this.masterPlaylistLoader_ = masterPlaylistLoader; 40649 this.childPlaylist_ = playlist; 40650 }; 40651 40652 _proto.dispose = function dispose() { 40653 this.trigger('dispose'); 40654 this.stopRequest(); 40655 this.loadedPlaylists_ = {}; 40656 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 40657 window$1.clearTimeout(this.mediaRequest_); 40658 window$1.clearTimeout(this.mediaUpdateTimeout); 40659 this.off(); 40660 }; 40661 40662 _proto.hasPendingRequest = function hasPendingRequest() { 40663 return this.request || this.mediaRequest_; 40664 }; 40665 40666 _proto.stopRequest = function stopRequest() { 40667 if (this.request) { 40668 var oldRequest = this.request; 40669 this.request = null; 40670 oldRequest.onreadystatechange = null; 40671 oldRequest.abort(); 40672 } 40673 }; 40674 40675 _proto.sidxRequestFinished_ = function sidxRequestFinished_(playlist, master, startingState, doneFn) { 40676 var _this2 = this; 40677 40678 return function (err, request) { 40679 // disposed 40680 if (!_this2.request) { 40681 return; 40682 } // pending request is cleared 40683 40684 40685 _this2.request = null; 40686 40687 if (err) { 40688 // use the provided error or create one 40689 // see requestSidx_ for the container request 40690 // that can cause this. 40691 _this2.error = typeof err === 'object' ? err : { 40692 status: request.status, 40693 message: 'DASH playlist request error at URL: ' + playlist.uri, 40694 response: request.response, 40695 // MEDIA_ERR_NETWORK 40696 code: 2 40697 }; 40698 40699 if (startingState) { 40700 _this2.state = startingState; 40701 } 40702 40703 _this2.trigger('error'); 40704 40705 return; 40706 } 40707 40708 var bytes = byteHelpers_5(request.response); 40709 var sidx = parseSidx_1(bytes.subarray(8)); 40710 return doneFn(master, sidx); 40711 }; 40712 }; 40713 40714 _proto.media = function media(playlist) { 40715 var _this3 = this; // getter 40716 40717 40718 if (!playlist) { 40719 return this.media_; 40720 } // setter 40721 40722 40723 if (this.state === 'HAVE_NOTHING') { 40724 throw new Error('Cannot switch media playlist from ' + this.state); 40725 } 40726 40727 var startingState = this.state; // find the playlist object if the target playlist has been specified by URI 40728 40729 if (typeof playlist === 'string') { 40730 if (!this.master.playlists[playlist]) { 40731 throw new Error('Unknown playlist URI: ' + playlist); 40732 } 40733 40734 playlist = this.master.playlists[playlist]; 40735 } 40736 40737 var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately 40738 40739 if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) { 40740 this.state = 'HAVE_METADATA'; 40741 this.media_ = playlist; // trigger media change if the active media has been updated 40742 40743 if (mediaChange) { 40744 this.trigger('mediachanging'); 40745 this.trigger('mediachange'); 40746 } 40747 40748 return; 40749 } // switching to the active playlist is a no-op 40750 40751 40752 if (!mediaChange) { 40753 return; 40754 } // switching from an already loaded playlist 40755 40756 40757 if (this.media_) { 40758 this.trigger('mediachanging'); 40759 } 40760 40761 if (!playlist.sidx) { 40762 // Continue asynchronously if there is no sidx 40763 // wait one tick to allow haveMaster to run first on a child loader 40764 this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, { 40765 startingState: startingState, 40766 playlist: playlist 40767 }), 0); // exit early and don't do sidx work 40768 40769 return; 40770 } // we have sidx mappings 40771 40772 40773 var oldMaster; 40774 var sidxMapping; // sidxMapping is used when parsing the masterXml, so store 40775 // it on the masterPlaylistLoader 40776 40777 if (this.masterPlaylistLoader_) { 40778 oldMaster = this.masterPlaylistLoader_.master; 40779 sidxMapping = this.masterPlaylistLoader_.sidxMapping_; 40780 } else { 40781 oldMaster = this.master; 40782 sidxMapping = this.sidxMapping_; 40783 } 40784 40785 var sidxKey = generateSidxKey(playlist.sidx); 40786 sidxMapping[sidxKey] = { 40787 sidxInfo: playlist.sidx 40788 }; 40789 this.request = requestSidx_(this, playlist.sidx, playlist, this.vhs_.xhr, { 40790 handleManifestRedirects: this.handleManifestRedirects 40791 }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) { 40792 if (!newMaster || !sidx) { 40793 throw new Error('failed to request sidx'); 40794 } // update loader's sidxMapping with parsed sidx box 40795 40796 40797 sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata 40798 40799 _this3.haveMetadata({ 40800 startingState: startingState, 40801 playlist: newMaster.playlists[playlist.id] 40802 }); 40803 })); 40804 }; 40805 40806 _proto.haveMetadata = function haveMetadata(_ref2) { 40807 var startingState = _ref2.startingState, 40808 playlist = _ref2.playlist; 40809 this.state = 'HAVE_METADATA'; 40810 this.loadedPlaylists_[playlist.id] = playlist; 40811 this.mediaRequest_ = null; // This will trigger loadedplaylist 40812 40813 this.refreshMedia_(playlist.id);
40813 // fire loadedmetadata the first time a media playlist is loaded 40814 // to resolve setup of media groups 40815 40816 if (startingState === 'HAVE_MASTER') { 40817 this.trigger('loadedmetadata'); 40818 } else { 40819 // trigger media change if the active media has been updated 40820 this.trigger('mediachange'); 40821 } 40822 }; 40823 40824 _proto.pause = function pause() { 40825 this.stopRequest(); 40826 window$1.clearTimeout(this.mediaUpdateTimeout); 40827 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 40828 40829 if (this.state === 'HAVE_NOTHING') { 40830 // If we pause the loader before any data has been retrieved, its as if we never 40831 // started, so reset to an unstarted state. 40832 this.started = false; 40833 } 40834 }; 40835 40836 _proto.load = function load(isFinalRendition) { 40837 var _this4 = this; 40838 40839 window$1.clearTimeout(this.mediaUpdateTimeout); 40840 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 40841 var media = this.media(); 40842 40843 if (isFinalRendition) { 40844 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 40845 this.mediaUpdateTimeout = window$1.setTimeout(function () { 40846 return _this4.load(); 40847 }, delay); 40848 return; 40849 } // because the playlists are internal to the manifest, load should either load the 40850 // main manifest, or do nothing but trigger an event 40851 40852 40853 if (!this.started) { 40854 this.start(); 40855 return; 40856 } 40857 40858 if (media && !media.endList) { 40859 this.trigger('mediaupdatetimeout'); 40860 } else { 40861 this.trigger('loadedplaylist'); 40862 } 40863 }; 40864 40865 _proto.start = function start() { 40866 var _this5 = this; 40867 40868 this.started = true; // We don't need to request the master manifest again 40869 // Call this asynchronously to match the xhr request behavior below 40870 40871 if (this.masterPlaylistLoader_) { 40872 this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0); 40873 return; 40874 } // request the specified URL 40875 40876 40877 this.request = this.vhs_.xhr({ 40878 uri: this.srcUrl, 40879 withCredentials: this.withCredentials 40880 }, function (error, req) { 40881 // disposed 40882 if (!_this5.request) { 40883 return; 40884 } // clear the loader's request reference 40885 40886 40887 _this5.request = null; 40888 40889 if (error) { 40890 _this5.error = { 40891 status: req.status, 40892 message: 'DASH playlist request error at URL: ' + _this5.srcUrl, 40893 responseText: req.responseText, 40894 // MEDIA_ERR_NETWORK 40895 code: 2 40896 }; 40897 40898 if (_this5.state === 'HAVE_NOTHING') { 40899 _this5.started = false; 40900 } 40901 40902 return _this5.trigger('error'); 40903 } 40904 40905 _this5.masterXml_ = req.responseText; 40906 40907 if (req.responseHeaders && req.responseHeaders.date) { 40908 _this5.masterLoaded_ = Date.parse(req.responseHeaders.date); 40909 } else { 40910 _this5.masterLoaded_ = Date.now(); 40911 } 40912 40913 _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req); 40914 40915 _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5)); 40916 }); 40917 } 40918 /** 40919 * Parses the master xml for UTCTiming node to sync the client clock to the server 40920 * clock. If the UTCTiming node requires a HEAD or GET request, that request is made. 40921 * 40922 * @param {Function} done 40923 * Function to call when clock sync has completed 40924 */ 40925 ; 40926 40927 _proto.syncClientServerClock_ = function syncClientServerClock_(done) { 40928 var _this6 = this; 40929 40930 var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the 40931 // server clock 40932 40933 if (utcTiming === null) { 40934 this.clientOffset_ = this.masterLoaded_ - Date.now(); 40935 return done(); 40936 } 40937 40938 if (utcTiming.method === 'DIRECT') { 40939 this.clientOffset_ = utcTiming.value - Date.now(); 40940 return done(); 40941 } 40942 40943 this.request = this.vhs_.xhr({ 40944 uri: resolveUrl$1(this.srcUrl, utcTiming.value), 40945 method: utcTiming.method, 40946 withCredentials: this.withCredentials 40947 }, function (error, req) { 40948 // disposed 40949 if (!_this6.request) { 40950 return; 40951 } 40952 40953 if (error) { 40954 // sync request failed, fall back to using date header from mpd 40955 // TODO: log warning 40956 _this6.clientOffset_ = _this6.masterLoaded_ - Date.now(); 40957 return done(); 40958 } 40959 40960 var serverTime; 40961 40962 if (utcTiming.method === 'HEAD') { 40963 if (!req.responseHeaders || !req.responseHeaders.date) { 40964 // expected date header not preset, fall back to using date header from mpd 40965 // TODO: log warning 40966 serverTime = _this6.masterLoaded_; 40967 } else { 40968 serverTime = Date.parse(req.responseHeaders.date); 40969 } 40970 } else { 40971 serverTime = Date.parse(req.responseText); 40972 } 40973 40974 _this6.clientOffset_ = serverTime - Date.now(); 40975 done(); 40976 }); 40977 }; 40978 40979 _proto.haveMaster_ = function haveMaster_() { 40980 this.state = 'HAVE_MASTER'; // clear media request 40981 40982 this.mediaRequest_ = null; 40983 40984 if (!this.masterPlaylistLoader_) { 40985 this.updateMainManifest_(parseMasterXml({ 40986 masterXml: this.masterXml_, 40987 srcUrl: this.srcUrl, 40988 clientOffset: this.clientOffset_, 40989 sidxMapping: this.sidxMapping_ 40990 })); // We have the master playlist at this point, so 40991 // trigger this to allow MasterPlaylistController 40992 // to make an initial playlist selection 40993 40994 this.trigger('loadedplaylist'); 40995 } else if (!this.media_) { 40996 // no media playlist was specifically selected so select 40997 // the one the child playlist loader was created with 40998 this.media(this.childPlaylist_); 40999 } 41000 }; 41001 41002 _proto.updateMinimumUpdatePeriodTimeout_ = function updateMinimumUpdatePeriodTimeout_() { 41003 var _this7 = this; // Clear existing timeout 41004 41005 41006 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 41007 41008 var createMUPTimeout = function createMUPTimeout(mup) { 41009 _this7.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () { 41010 _this7.trigger('minimumUpdatePeriod'); 41011 }, mup); 41012 }; 41013 41014 var minimumUpdatePeriod = this.master && this.master.minimumUpdatePeriod; 41015 41016 if (minimumUpdatePeriod > 0) { 41017 createMUPTimeout(minimumUpdatePeriod); // If the minimumUpdatePeriod has a value of 0, that indicates that the current 41018 // MPD has no future validity, so a new one will need to be acquired when new 41019 // media segments are to be made available. Thus, we use the target duration 41020 // in this case 41021 } else if (minimumUpdatePeriod === 0) { 41022 // If we haven't yet selected a playlist, wait until then so we know the 41023 // target duration 41024 if (!this.media()) { 41025 this.one('loadedplaylist', function () { 41026 createMUPTimeout(_this7.media().targetDuration * 1000); 41027 }); 41028 } else { 41029 createMUPTimeout(this.media().targetDuration * 1000); 41030 } 41031 } 41032 } 41033 /** 41034 * Handler for after client/server clock synchronization has happened. Sets up 41035 * xml refresh timer if specificed by the manifest. 41036 */ 41037 ; 41038 41039 _proto.onClientServerClockSync_ = function onClientServerClockSync_() { 41040 this.haveMaster_(); 41041 41042 if (!this.hasPendingRequest() && !this.media_) { 41043 this.media(this.master.playlists[0]); 41044 } 41045 41046 this.updateMinimumUpdatePeriodTimeout_(); 41047 } 41048 /** 41049 * Given a new manifest, update our pointer to it and update the srcUrl based on the location elements of the manifest, if they exist. 41050 * 41051 * @param {Object} updatedManifest the manifest to update to 41052 */ 41053 ; 41054 41055 _proto.updateMainManifest_ = function updateMainManifest_(updatedManifest) { 41056 this.master = updatedManifest; // if locations isn't set or is an empty array, exit early 41057 41058 if (!this.master.locations || !this.master.locations.length) { 41059 return; 41060 } 41061 41062 var location = this.master.locations[0]; 41063 41064 if (location !== this.srcUrl) { 41065 this.srcUrl = location; 41066 } 41067 } 41068 /** 41069 * Sends request to refresh the master xml and updates the parsed master manifest 41070 * TODO: Does the client offset need to be recalculated when the xml is refreshed? 41071 */ 41072 ; 41073 41074 _proto.refreshXml_ = function refreshXml_() { 41075 var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when 41076 // sidx is implemented 41077 41078 41079 this.request = this.vhs_.xhr({ 41080 uri: this.srcUrl, 41081 withCredentials: this.withCredentials 41082 }, function (error, req) { 41083 // disposed 41084 if (!_this8.request) { 41085 return; 41086 } // clear the loader's request reference 41087 41088 41089 _this8.request = null; 41090 41091 if (error) { 41092 _this8.error = { 41093 status: req.status, 41094 message: 'DASH playlist request error at URL: ' + _this8.srcUrl, 41095 responseText: req.responseText, 41096 // MEDIA_ERR_NETWORK 41097 code: 2 41098 }; 41099 41100 if (_this8.state === 'HAVE_NOTHING') { 41101 _this8.started = false;
41102 } 41103 41104 return _this8.trigger('error'); 41105 } 41106 41107 _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping 41108 41109 _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_); 41110 var master = parseMasterXml({ 41111 masterXml: _this8.masterXml_, 41112 srcUrl: _this8.srcUrl, 41113 clientOffset: _this8.clientOffset_, 41114 sidxMapping: _this8.sidxMapping_ 41115 }); 41116 var updatedMaster = updateMaster$1(_this8.master, master); 41117 41118 var currentSidxInfo = _this8.media().sidx; 41119 41120 if (updatedMaster) { 41121 if (currentSidxInfo) { 41122 var sidxKey = generateSidxKey(currentSidxInfo); // the sidx was updated, so the previous mapping was removed 41123 41124 if (!_this8.sidxMapping_[sidxKey]) { 41125 var playlist = _this8.media(); 41126 41127 _this8.request = requestSidx_(_this8, playlist.sidx, playlist, _this8.vhs_.xhr, { 41128 handleManifestRedirects: _this8.handleManifestRedirects 41129 }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) { 41130 if (!newMaster || !sidx) { 41131 throw new Error('failed to request sidx on minimumUpdatePeriod'); 41132 } // update loader's sidxMapping with parsed sidx box 41133 41134 41135 _this8.sidxMapping_[sidxKey].sidx = sidx; 41136 41137 _this8.updateMinimumUpdatePeriodTimeout_(); // TODO: do we need to reload the current playlist? 41138 41139 41140 _this8.refreshMedia_(_this8.media().id); 41141 41142 return; 41143 })); 41144 } 41145 } else { 41146 _this8.updateMainManifest_(updatedMaster); 41147 41148 if (_this8.media_) { 41149 _this8.media_ = _this8.master.playlists[_this8.media_.id]; 41150 } 41151 } 41152 } 41153 41154 _this8.updateMinimumUpdatePeriodTimeout_(); 41155 }); 41156 } 41157 /** 41158 * Refreshes the media playlist by re-parsing the master xml and updating playlist 41159 * references. If this is an alternate loader, the updated parsed manifest is retrieved 41160 * from the master loader. 41161 */ 41162 ; 41163 41164 _proto.refreshMedia_ = function refreshMedia_(mediaID) { 41165 var _this9 = this; 41166 41167 if (!mediaID) { 41168 throw new Error('refreshMedia_ must take a media id'); 41169 } 41170 41171 var oldMaster; 41172 var newMaster; 41173 41174 if (this.masterPlaylistLoader_) { 41175 oldMaster = this.masterPlaylistLoader_.master; 41176 newMaster = parseMasterXml({ 41177 masterXml: this.masterPlaylistLoader_.masterXml_, 41178 srcUrl: this.masterPlaylistLoader_.srcUrl, 41179 clientOffset: this.masterPlaylistLoader_.clientOffset_, 41180 sidxMapping: this.masterPlaylistLoader_.sidxMapping_ 41181 }); 41182 } else { 41183 oldMaster = this.master; 41184 newMaster = parseMasterXml({ 41185 masterXml: this.masterXml_, 41186 srcUrl: this.srcUrl, 41187 clientOffset: this.clientOffset_, 41188 sidxMapping: this.sidxMapping_ 41189 }); 41190 } 41191 41192 var updatedMaster = updateMaster$1(oldMaster, newMaster); 41193 41194 if (updatedMaster) { 41195 if (this.masterPlaylistLoader_) { 41196 this.masterPlaylistLoader_.master = updatedMaster; 41197 } else { 41198 this.master = updatedMaster; 41199 } 41200 41201 this.media_ = updatedMaster.playlists[mediaID]; 41202 } else { 41203 this.media_ = oldMaster.playlists[mediaID]; 41204 this.trigger('playlistunchanged'); 41205 } 41206 41207 if (!this.media().endList) { 41208 this.mediaUpdateTimeout = window$1.setTimeout(function () { 41209 _this9.trigger('mediaupdatetimeout'); 41210 }, refreshDelay(this.media(), !!updatedMaster)); 41211 } 41212 41213 this.trigger('loadedplaylist'); 41214 }; 41215 41216 return DashPlaylistLoader; 41217 }(EventTarget$1$1); 41218 41219 var Config = { 41220 GOAL_BUFFER_LENGTH: 30, 41221 MAX_GOAL_BUFFER_LENGTH: 60, 41222 BACK_BUFFER_LENGTH: 30, 41223 GOAL_BUFFER_LENGTH_RATE: 1, 41224 // 0.5 MB/s 41225 INITIAL_BANDWIDTH: 4194304, 41226 // A fudge factor to apply to advertised playlist bitrates to account for 41227 // temporary flucations in client bandwidth 41228 BANDWIDTH_VARIANCE: 1.2, 41229 // How much of the buffer must be filled before we consider upswitching 41230 BUFFER_LOW_WATER_LINE: 0, 41231 MAX_BUFFER_LOW_WATER_LINE: 30, 41232 BUFFER_LOW_WATER_LINE_RATE: 1 41233 }; 41234 41235 var stringToArrayBuffer = function stringToArrayBuffer(string) { 41236 var view = new Uint8Array(new ArrayBuffer(string.length)); 41237 41238 for (var i = 0; i < string.length; i++) { 41239 view[i] = string.charCodeAt(i); 41240 } 41241 41242 return view.buffer; 41243 }; 41244 41245 var transmuxQueue = []; 41246 var currentTransmux; 41247 41248 var handleData_ = function handleData_(event, transmuxedData, callback) { 41249 var _event$data$segment = event.data.segment, 41250 type = _event$data$segment.type, 41251 initSegment = _event$data$segment.initSegment, 41252 captions = _event$data$segment.captions,
41253 captionStreams = _event$data$segment.captionStreams, 41254 metadata = _event$data$segment.metadata, 41255 videoFrameDtsTime = _event$data$segment.videoFrameDtsTime, 41256 videoFramePtsTime = _event$data$segment.videoFramePtsTime; 41257 transmuxedData.buffer.push({ 41258 captions: captions, 41259 captionStreams: captionStreams, 41260 metadata: metadata 41261 }); // right now, boxes will come back from partial transmuxer, data from full 41262 41263 var boxes = event.data.segment.boxes || { 41264 data: event.data.segment.data 41265 }; 41266 var result = { 41267 type: type, 41268 // cast ArrayBuffer to TypedArray 41269 data: new Uint8Array(boxes.data, boxes.data.byteOffset, boxes.data.byteLength), 41270 initSegment: new Uint8Array(initSegment.data, initSegment.byteOffset, initSegment.byteLength) 41271 }; 41272 41273 if (typeof videoFrameDtsTime !== 'undefined') { 41274 result.videoFrameDtsTime = videoFrameDtsTime; 41275 } 41276 41277 if (typeof videoFramePtsTime !== 'undefined') { 41278 result.videoFramePtsTime = videoFramePtsTime; 41279 } 41280 41281 callback(result); 41282 }; 41283 41284 var handleDone_ = function handleDone_(_ref) { 41285 var transmuxedData = _ref.transmuxedData, 41286 callback = _ref.callback; // Previously we only returned data on data events, 41287 // not on done events. Clear out the buffer to keep that consistent. 41288 41289 transmuxedData.buffer = []; // all buffers should have been flushed from the muxer, so start processing anything we 41290 // have received 41291 41292 callback(transmuxedData); 41293 }; 41294 41295 var handleGopInfo_ = function handleGopInfo_(event, transmuxedData) { 41296 transmuxedData.gopInfo = event.data.gopInfo; 41297 }; 41298 41299 var processTransmux = function processTransmux(_ref2) { 41300 var transmuxer = _ref2.transmuxer, 41301 bytes = _ref2.bytes, 41302 audioAppendStart = _ref2.audioAppendStart, 41303 gopsToAlignWith = _ref2.gopsToAlignWith, 41304 isPartial = _ref2.isPartial, 41305 remux = _ref2.remux, 41306 onData = _ref2.onData, 41307 onTrackInfo = _ref2.onTrackInfo, 41308 onAudioTimingInfo = _ref2.onAudioTimingInfo, 41309 onVideoTimingInfo = _ref2.onVideoTimingInfo, 41310 onVideoSegmentTimingInfo = _ref2.onVideoSegmentTimingInfo, 41311 onId3 = _ref2.onId3, 41312 onCaptions = _ref2.onCaptions, 41313 onDone = _ref2.onDone; 41314 var transmuxedData = { 41315 isPartial: isPartial, 41316 buffer: [] 41317 }; 41318 41319 var handleMessage = function handleMessage(event) { 41320 if (!currentTransmux) { 41321 // disposed 41322 return; 41323 } 41324 41325 if (event.data.action === 'data') { 41326 handleData_(event, transmuxedData, onData); 41327 } 41328 41329 if (event.data.action === 'trackinfo') { 41330 onTrackInfo(event.data.trackInfo); 41331 } 41332 41333 if (event.data.action === 'gopInfo') { 41334 handleGopInfo_(event, transmuxedData); 41335 } 41336 41337 if (event.data.action === 'audioTimingInfo') { 41338 onAudioTimingInfo(event.data.audioTimingInfo); 41339 } 41340 41341 if (event.data.action === 'videoTimingInfo') { 41342 onVideoTimingInfo(event.data.videoTimingInfo); 41343 } 41344 41345 if (event.data.action === 'videoSegmentTimingInfo') { 41346 onVideoSegmentTimingInfo(event.data.videoSegmentTimingInfo); 41347 } 41348 41349 if (event.data.action === 'id3Frame') { 41350 onId3([event.data.id3Frame], event.data.id3Frame.dispatchType); 41351 } 41352 41353 if (event.data.action === 'caption') { 41354 onCaptions(event.data.caption); 41355 } // wait for the transmuxed event since we may have audio and video 41356 41357 41358 if (event.data.type !== 'transmuxed') { 41359 return; 41360 } 41361 41362 transmuxer.onmessage = null; 41363 handleDone_({ 41364 transmuxedData: transmuxedData, 41365 callback: onDone 41366 }); 41367 /* eslint-disable no-use-before-define */ 41368 41369 dequeue(); 41370 /* eslint-enable */ 41371 }; 41372 41373 transmuxer.onmessage = handleMessage; 41374 41375 if (audioAppendStart) { 41376 transmuxer.postMessage({ 41377 action: 'setAudioAppendStart', 41378 appendStart: audioAppendStart 41379 }); 41380 } // allow empty arrays to be passed to clear out GOPs 41381 41382 41383 if (Array.isArray(gopsToAlignWith)) { 41384 transmuxer.postMessage({ 41385 action: 'alignGopsWith', 41386 gopsToAlignWith: gopsToAlignWith 41387 }); 41388 } 41389 41390 if (typeof remux !== 'undefined') { 41391 transmuxer.postMessage({ 41392 action: 'setRemux', 41393 remux: remux 41394 }); 41395 } 41396 41397 if (bytes.byteLength) { 41398 var buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer; 41399 var byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset; 41400 transmuxer.postMessage({ 41401 action: 'push', 41402 // Send the typed-array of data as an ArrayBuffer so that 41403 // it can be sent as a "Transferable" and avoid the costly 41404 // memory copy 41405 data: buffer, 41406 // To recreate the original typed-array, we need information 41407 // about what portion of the ArrayBuffer it was a view into 41408 byteOffset: byteOffset, 41409 byteLength: bytes.byteLength 41410 }, [buffer]); 41411 } // even if we didn't push any bytes, we have to make sure we flush in case we reached 41412 // the end of the segment 41413 41414 41415 transmuxer.postMessage({ 41416 action: isPartial ? 'partialFlush' : 'flush' 41417 }); 41418 }; 41419 41420 var dequeue = function dequeue() { 41421 currentTransmux = null; 41422 41423 if (transmuxQueue.length) { 41424 currentTransmux = transmuxQueue.shift(); 41425 41426 if (typeof currentTransmux === 'function') { 41427 currentTransmux(); 41428 } else { 41429 processTransmux(currentTransmux); 41430 } 41431 } 41432 }; 41433 41434 var processAction = function processAction(transmuxer, action) { 41435 transmuxer.postMessage({ 41436 action: action 41437 }); 41438 dequeue(); 41439 }; 41440 41441 var enqueueAction = function enqueueAction(action, transmuxer) { 41442 if (!currentTransmux) { 41443 currentTransmux = action; 41444 processAction(transmuxer, action); 41445 return; 41446 } 41447 41448 transmuxQueue.push(processAction.bind(null, transmuxer, action)); 41449 }; 41450 41451 var reset = function reset(transmuxer) { 41452 enqueueAction('reset', transmuxer); 41453 }; 41454 41455 var endTimeline = function endTimeline(transmuxer) { 41456 enqueueAction('endTimeline', transmuxer); 41457 }; 41458 41459 var transmux = function transmux(options) { 41460 if (!currentTransmux) { 41461 currentTransmux = options; 41462 processTransmux(options); 41463 return; 41464 } 41465 41466 transmuxQueue.push(options); 41467 }; 41468 41469 var dispose = function dispose() { 41470 // clear out module-level references 41471 currentTransmux = null; 41472 transmuxQueue.length = 0; 41473 }; 41474 41475 var segmentTransmuxer = { 41476 reset: reset,
vendor: 15,580 bytes, lines 41477-41934
41477 dispose: dispose, 41478 endTimeline: endTimeline, 41479 transmux: transmux 41480 }; 41481 /** 41482 * Probe an mpeg2-ts segment to determine the start time of the segment in it's 41483 * internal "media time," as well as whether it contains video and/or audio. 41484 * 41485 * @private 41486 * @param {Uint8Array} bytes - segment bytes 41487 * @return {Object} The start time of the current segment in "media time" as well as 41488 * whether it contains video and/or audio 41489 */ 41490 41491 var probeTsSegment = function probeTsSegment(bytes, baseStartTime) { 41492 var timeInfo = tsInspector.inspect(bytes, baseStartTime * clock_1); 41493 41494 if (!timeInfo) { 41495 return null; 41496 } 41497 41498 var result = { 41499 // each type's time info comes back as an array of 2 times, start and end 41500 hasVideo: timeInfo.video && timeInfo.video.length === 2 || false, 41501 hasAudio: timeInfo.audio && timeInfo.audio.length === 2 || false 41502 }; 41503 41504 if (result.hasVideo) { 41505 result.videoStart = timeInfo.video[0].ptsTime; 41506 } 41507 41508 if (result.hasAudio) { 41509 result.audioStart = timeInfo.audio[0].ptsTime; 41510 } 41511 41512 return result; 41513 }; 41514 /** 41515 * Combine all segments into a single Uint8Array 41516 * 41517 * @param {Object} segmentObj 41518 * @return {Uint8Array} concatenated bytes 41519 * @private 41520 */ 41521 41522 41523 var concatSegments = function concatSegments(segmentObj) { 41524 var offset = 0; 41525 var tempBuffer; 41526 41527 if (segmentObj.bytes) { 41528 tempBuffer = new Uint8Array(segmentObj.bytes); // combine the individual segments into one large typed-array 41529 41530 segmentObj.segments.forEach(function (segment) { 41531 tempBuffer.set(segment, offset); 41532 offset += segment.byteLength; 41533 }); 41534 } 41535 41536 return tempBuffer; 41537 }; 41538 41539 var REQUEST_ERRORS = { 41540 FAILURE: 2, 41541 TIMEOUT: -101, 41542 ABORTED: -102 41543 }; 41544 /** 41545 * Abort all requests 41546 * 41547 * @param {Object} activeXhrs - an object that tracks all XHR requests 41548 */ 41549 41550 var abortAll = function abortAll(activeXhrs) { 41551 activeXhrs.forEach(function (xhr) { 41552 xhr.abort(); 41553 }); 41554 }; 41555 /** 41556 * Gather important bandwidth stats once a request has completed 41557 * 41558 * @param {Object} request - the XHR request from which to gather stats 41559 */ 41560 41561 41562 var getRequestStats = function getRequestStats(request) { 41563 return { 41564 bandwidth: request.bandwidth, 41565 bytesReceived: request.bytesReceived || 0, 41566 roundTripTime: request.roundTripTime || 0 41567 }; 41568 }; 41569 /** 41570 * If possible gather bandwidth stats as a request is in 41571 * progress 41572 * 41573 * @param {Event} progressEvent - an event object from an XHR's progress event 41574 */ 41575 41576 41577 var getProgressStats = function getProgressStats(progressEvent) { 41578 var request = progressEvent.target; 41579 var roundTripTime = Date.now() - request.requestTime; 41580 var stats = { 41581 bandwidth: Infinity, 41582 bytesReceived: 0, 41583 roundTripTime: roundTripTime || 0 41584 }; 41585 stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok 41586 // because we should only use bandwidth stats on progress to determine when 41587 // abort a request early due to insufficient bandwidth 41588 41589 stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000); 41590 return stats; 41591 }; 41592 /** 41593 * Handle all error conditions in one place and return an object 41594 * with all the information 41595 * 41596 * @param {Error|null} error - if non-null signals an error occured with the XHR 41597 * @param {Object} request - the XHR request that possibly generated the error 41598 */ 41599 41600 41601 var handleErrors = function handleErrors(error, request) { 41602 if (request.timedout) { 41603 return { 41604 status: request.status, 41605 message: 'HLS request timed-out at URL: ' + request.uri, 41606 code: REQUEST_ERRORS.TIMEOUT, 41607 xhr: request 41608 }; 41609 } 41610 41611 if (request.aborted) { 41612 return { 41613 status: request.status, 41614 message: 'HLS request aborted at URL: ' + request.uri, 41615 code: REQUEST_ERRORS.ABORTED, 41616 xhr: request 41617 }; 41618 } 41619 41620 if (error) { 41621 return { 41622 status: request.status, 41623 message: 'HLS request errored at URL: ' + request.uri, 41624 code: REQUEST_ERRORS.FAILURE, 41625 xhr: request 41626 }; 41627 } 41628 41629 return null; 41630 }; 41631 /** 41632 * Handle responses for key data and convert the key data to the correct format 41633 * for the decryption step later 41634 * 41635 * @param {Object} segment - a simplified copy of the segmentInfo object 41636 * from SegmentLoader 41637 * @param {Function} finishProcessingFn - a callback to execute to continue processing 41638 * this request 41639 */ 41640 41641 41642 var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) { 41643 return function (error, request) { 41644 var response = request.response; 41645 var errorObj = handleErrors(error, request); 41646 41647 if (errorObj) { 41648 return finishProcessingFn(errorObj, segment); 41649 } 41650 41651 if (response.byteLength !== 16) { 41652 return finishProcessingFn({ 41653 status: request.status, 41654 message: 'Invalid HLS key at URL: ' + request.uri, 41655 code: REQUEST_ERRORS.FAILURE, 41656 xhr: request 41657 }, segment); 41658 } 41659 41660 var view = new DataView(response); 41661 segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 41662 return finishProcessingFn(null, segment); 41663 }; 41664 }; 41665 /** 41666 * Handle init-segment responses 41667 * 41668 * @param {Object} segment - a simplified copy of the segmentInfo object 41669 * from SegmentLoader 41670 * @param {Function} finishProcessingFn - a callback to execute to continue processing 41671 * this request 41672 */ 41673 41674 41675 var handleInitSegmentResponse = function handleInitSegmentResponse(_ref) { 41676 var segment = _ref.segment, 41677 finishProcessingFn = _ref.finishProcessingFn; 41678 return function (error, request) { 41679 var response = request.response; 41680 var errorObj = handleErrors(error, request); 41681 41682 if (errorObj) { 41683 return finishProcessingFn(errorObj, segment); 41684 } // stop processing if received empty content 41685 41686 41687 if (response.byteLength === 0) { 41688 return finishProcessingFn({ 41689 status: request.status, 41690 message: 'Empty HLS segment content at URL: ' + request.uri, 41691 code: REQUEST_ERRORS.FAILURE, 41692 xhr: request 41693 }, segment); 41694 } 41695 41696 segment.map.bytes = new Uint8Array(request.response); 41697 var type = containers_1(segment.map.bytes); // TODO: We should also handle ts init segments here, but we 41698 // only know how to parse mp4 init segments at the moment 41699 41700 if (type !== 'mp4') { 41701 return finishProcessingFn({ 41702 status: request.status, 41703 message: "Found unsupported " + (type || 'unknown') + " container for initialization segment at URL: " + request.uri, 41704 code: REQUEST_ERRORS.FAILURE, 41705 internal: true, 41706 xhr: request 41707 }, segment); 41708 } 41709 41710 var tracks = probe$2.tracks(segment.map.bytes); 41711 tracks.forEach(function (track) { 41712 segment.map.tracks = segment.map.tracks || {}; // only support one track of each type for now 41713 41714 if (segment.map.tracks[track.type]) { 41715 return; 41716 } 41717 41718 segment.map.tracks[track.type] = track; 41719 41720 if (track.id && track.timescale) { 41721 segment.map.timescales = segment.map.timescales || {}; 41722 segment.map.timescales[track.id] = track.timescale; 41723 } 41724 }); 41725 return finishProcessingFn(null, segment); 41726 }; 41727 }; 41728 /** 41729 * Response handler for segment-requests being sure to set the correct 41730 * property depending on whether the segment is encryped or not 41731 * Also records and keeps track of stats that are used for ABR purposes 41732 * 41733 * @param {Object} segment - a simplified copy of the segmentInfo object 41734 * from SegmentLoader 41735 * @param {Function} finishProcessingFn - a callback to execute to continue processing 41736 * this request 41737 */ 41738 41739 41740 var handleSegmentResponse = function handleSegmentResponse(_ref2) { 41741 var segment = _ref2.segment, 41742 finishProcessingFn = _ref2.finishProcessingFn, 41743 responseType = _ref2.responseType; 41744 return function (error, request) { 41745 var response = request.response; 41746 var errorObj = handleErrors(error, request); 41747 41748 if (errorObj) { 41749 return finishProcessingFn(errorObj, segment); 41750 } 41751 41752 var newBytes = // although responseText "should" exist, this guard serves to prevent an error being 41753 // thrown for two primary cases: 41754 // 1. the mime type override stops working, or is not implemented for a specific 41755 // browser 41756 // 2. when using mock XHR libraries like sinon that do not allow the override behavior 41757 responseType === 'arraybuffer' || !request.responseText ? request.response : stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0)); // stop processing if received empty content 41758 41759 if (response.byteLength === 0) { 41760 return finishProcessingFn({ 41761 status: request.status, 41762 message: 'Empty HLS segment content at URL: ' + request.uri, 41763 code: REQUEST_ERRORS.FAILURE, 41764 xhr: request 41765 }, segment); 41766 } 41767 41768 segment.stats = getRequestStats(request); 41769 41770 if (segment.key) { 41771 segment.encryptedBytes = new Uint8Array(newBytes); 41772 } else { 41773 segment.bytes = new Uint8Array(newBytes); 41774 } 41775 41776 return finishProcessingFn(null, segment); 41777 }; 41778 }; 41779 41780 var transmuxAndNotify = function transmuxAndNotify(_ref3) { 41781 var segment = _ref3.segment, 41782 bytes = _ref3.bytes, 41783 isPartial = _ref3.isPartial, 41784 trackInfoFn = _ref3.trackInfoFn, 41785 timingInfoFn = _ref3.timingInfoFn, 41786 videoSegmentTimingInfoFn = _ref3.videoSegmentTimingInfoFn, 41787 id3Fn = _ref3.id3Fn, 41788 captionsFn = _ref3.captionsFn, 41789 dataFn = _ref3.dataFn, 41790 doneFn = _ref3.doneFn; 41791 var fmp4Tracks = segment.map && segment.map.tracks || {}; 41792 var isMuxed = Boolean(fmp4Tracks.audio && fmp4Tracks.video); // Keep references to each function so we can null them out after we're done with them. 41793 // One reason for this is that in the case of full segments, we want to trust start 41794 // times from the probe, rather than the transmuxer. 41795 41796 var audioStartFn = timingInfoFn.bind(null, segment, 'audio', 'start'); 41797 var audioEndFn = timingInfoFn.bind(null, segment, 'audio', 'end'); 41798 var videoStartFn = timingInfoFn.bind(null, segment, 'video', 'start'); 41799 var videoEndFn = timingInfoFn.bind(null, segment, 'video', 'end'); // Check to see if we are appending a full segment. 41800 41801 if (!isPartial && !segment.lastReachedChar) { 41802 // In the full segment transmuxer, we don't yet have the ability to extract a "proper" 41803 // start time. Meaning cached frame data may corrupt our notion of where this segment 41804 // really starts. To get around this, full segment appends should probe for the info 41805 // needed. 41806 var probeResult = probeTsSegment(bytes, segment.baseStartTime); 41807 41808 if (probeResult) { 41809 trackInfoFn(segment, { 41810 hasAudio: probeResult.hasAudio, 41811 hasVideo: probeResult.hasVideo, 41812 isMuxed: isMuxed 41813 }); 41814 trackInfoFn = null; 41815 41816 if (probeResult.hasAudio && !isMuxed) { 41817 audioStartFn(probeResult.audioStart); 41818 } 41819 41820 if (probeResult.hasVideo) { 41821 videoStartFn(probeResult.videoStart); 41822 } 41823 41824 audioStartFn = null; 41825 videoStartFn = null; 41826 } 41827 } 41828 41829 transmux({ 41830 bytes: bytes, 41831 transmuxer: segment.transmuxer, 41832 audioAppendStart: segment.audioAppendStart, 41833 gopsToAlignWith: segment.gopsToAlignWith, 41834 isPartial: isPartial, 41835 remux: isMuxed, 41836 onData: function onData(result) { 41837 result.type = result.type === 'combined' ? 'video' : result.type; 41838 dataFn(segment, result); 41839 }, 41840 onTrackInfo: function onTrackInfo(trackInfo) { 41841 if (trackInfoFn) { 41842 if (isMuxed) { 41843 trackInfo.isMuxed = true; 41844 } 41845 41846 trackInfoFn(segment, trackInfo); 41847 } 41848 }, 41849 onAudioTimingInfo: function onAudioTimingInfo(audioTimingInfo) { 41850 // we only want the first start value we encounter 41851 if (audioStartFn && typeof audioTimingInfo.start !== 'undefined') { 41852 audioStartFn(audioTimingInfo.start); 41853 audioStartFn = null; 41854 } // we want to continually update the end time 41855 41856 41857 if (audioEndFn && typeof audioTimingInfo.end !== 'undefined') { 41858 audioEndFn(audioTimingInfo.end); 41859 } 41860 }, 41861 onVideoTimingInfo: function onVideoTimingInfo(videoTimingInfo) { 41862 // we only want the first start value we encounter 41863 if (videoStartFn && typeof videoTimingInfo.start !== 'undefined') { 41864 videoStartFn(videoTimingInfo.start); 41865 videoStartFn = null; 41866 } // we want to continually update the end time 41867 41868 41869 if (videoEndFn && typeof videoTimingInfo.end !== 'undefined') { 41870 videoEndFn(videoTimingInfo.end); 41871 } 41872 }, 41873 onVideoSegmentTimingInfo: function onVideoSegmentTimingInfo(videoSegmentTimingInfo) { 41874 videoSegmentTimingInfoFn(videoSegmentTimingInfo); 41875 }, 41876 onId3: function onId3(id3Frames, dispatchType) { 41877 id3Fn(segment, id3Frames, dispatchType); 41878 }, 41879 onCaptions: function onCaptions(captions) { 41880 captionsFn(segment, [captions]); 41881 }, 41882 onDone: function onDone(result) { 41883 // To handle partial appends, there won't be a done function passed in (since 41884 // there's still, potentially, more segment to process), so there's nothing to do. 41885 if (!doneFn || isPartial) { 41886 return; 41887 } 41888 41889 result.type = result.type === 'combined' ? 'video' : result.type; 41890 doneFn(null, segment, result); 41891 } 41892 }); 41893 }; 41894 41895 var handleSegmentBytes = function handleSegmentBytes(_ref4) { 41896 var segment = _ref4.segment, 41897 bytes = _ref4.bytes, 41898 isPartial = _ref4.isPartial, 41899 trackInfoFn = _ref4.trackInfoFn, 41900 timingInfoFn = _ref4.timingInfoFn, 41901 videoSegmentTimingInfoFn = _ref4.videoSegmentTimingInfoFn, 41902 id3Fn = _ref4.id3Fn, 41903 captionsFn = _ref4.captionsFn, 41904 dataFn = _ref4.dataFn, 41905 doneFn = _ref4.doneFn; 41906 var bytesAsUint8Array = new Uint8Array(bytes); // TODO: 41907 // We should have a handler that fetches the number of bytes required 41908 // to check if something is fmp4. This will allow us to save bandwidth 41909 // because we can only blacklist a playlist and abort requests 41910 // by codec after trackinfo triggers. 41911 41912 if (containers_5(bytesAsUint8Array)) { 41913 segment.isFmp4 = true; 41914 var tracks = segment.map.tracks; 41915 var trackInfo = { 41916 isFmp4: true, 41917 hasVideo: !!tracks.video, 41918 hasAudio: !!tracks.audio 41919 }; // if we have a audio track, with a codec that is not set to 41920 // encrypted audio 41921 41922 if (tracks.audio && tracks.audio.codec && tracks.audio.codec !== 'enca') { 41923 trackInfo.audioCodec = tracks.audio.codec; 41924 } // if we have a video track, with a codec that is not set to 41925 // encrypted video 41926 41927 41928 if (tracks.video && tracks.video.codec && tracks.video.codec !== 'encv') { 41929 trackInfo.videoCodec = tracks.video.codec; 41930 } 41931 41932 if (tracks.video && tracks.audio) { 41933 trackInfo.isMuxed = true; 41934 } // since we don't support appending fmp4 data on progress, we know
41934we have the full 41935 // segment here 41936 41937 41938 trackInfoFn(segment, trackInfo); // The probe doesn't provide the segment end time, so only callback with the start 41939 // time. The end time can be roughly calculated by the receiver using the duration. 41940 // 41941 // Note that the start time returned by the probe reflects the baseMediaDecodeTime, as 41942 // that is the true start of the segment (where the playback engine should begin 41943 // decoding). 41944 41945 var timingInfo = probe$2.startTime(segment.map.timescales, bytesAsUint8Array); 41946 41947 if (trackInfo.hasAudio && !trackInfo.isMuxed) { 41948 timingInfoFn(segment, 'audio', 'start', timingInfo); 41949 } 41950 41951 if (trackInfo.hasVideo) { 41952 timingInfoFn(segment, 'video', 'start', timingInfo); 41953 } 41954 41955 var finishLoading = function finishLoading(captions) { 41956 // if the track still has audio at this point it is only possible 41957 // for it to be audio only. See `tracks.video && tracks.audio` if statement 41958 // above. 41959 // we make sure to use segment.bytes here as that 41960 dataFn(segment, { 41961 data: bytes, 41962 type: trackInfo.hasAudio && !trackInfo.isMuxed ? 'audio' : 'video' 41963 }); 41964 41965 if (captions && captions.length) { 41966 captionsFn(segment, captions); 41967 } 41968 41969 doneFn(null, segment, {}); 41970 }; // Run through the CaptionParser in case there are captions. 41971 // Initialize CaptionParser if it hasn't been yet 41972 41973 41974 if (!tracks.video || !bytes.byteLength || !segment.transmuxer) { 41975 finishLoading(); 41976 return; 41977 } 41978 41979 var buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer; 41980 var byteOffset = bytes instanceof ArrayBuffer ? 0 : bytes.byteOffset; 41981 41982 var listenForCaptions = function listenForCaptions(event) { 41983 if (event.data.action !== 'mp4Captions') { 41984 return; 41985 } 41986 41987 segment.transmuxer.removeEventListener('message', listenForCaptions); 41988 var data = event.data.data; // transfer ownership of bytes back to us. 41989 41990 segment.bytes = bytes = new Uint8Array(data, data.byteOffset || 0, data.byteLength); 41991 finishLoading(event.data.captions); 41992 }; 41993 41994 segment.transmuxer.addEventListener('message', listenForCaptions); // transfer ownership of bytes to worker. 41995 41996 segment.transmuxer.postMessage({ 41997 action: 'pushMp4Captions', 41998 timescales: segment.map.timescales, 41999 trackIds: [tracks.video.id], 42000 data: buffer, 42001 byteOffset: byteOffset, 42002 byteLength: bytes.byteLength 42003 }, [buffer]); 42004 return; 42005 } // VTT or other segments that don't need processing 42006 42007 42008 if (!segment.transmuxer) { 42009 doneFn(null, segment, {}); 42010 return; 42011 } 42012 42013 if (typeof segment.container === 'undefined') { 42014 segment.container = containers_1(bytesAsUint8Array); 42015 } 42016 42017 if (segment.container !== 'ts' && segment.container !== 'aac') { 42018 trackInfoFn(segment, { 42019 hasAudio: false, 42020 hasVideo: false 42021 }); 42022 doneFn(null, segment, {}); 42023 return; 42024 } // ts or aac 42025 42026 42027 transmuxAndNotify({ 42028 segment: segment, 42029 bytes: bytes, 42030 isPartial: isPartial, 42031 trackInfoFn: trackInfoFn, 42032 timingInfoFn: timingInfoFn, 42033 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 42034 id3Fn: id3Fn, 42035 captionsFn: captionsFn, 42036 dataFn: dataFn, 42037 doneFn: doneFn 42038 }); 42039 }; 42040 /** 42041 * Decrypt the segment via the decryption web worker 42042 * 42043 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption 42044 * routines 42045 * @param {Object} segment - a simplified copy of the segmentInfo object 42046 * from SegmentLoader 42047 * @param {Function} trackInfoFn - a callback that receives track info 42048 * @param {Function} dataFn - a callback that is executed when segment bytes are available 42049 * and ready to use 42050 * @param {Function} doneFn - a callback that is executed after decryption has completed 42051 */ 42052 42053 42054 var decryptSegment = function decryptSegment(_ref5) { 42055 var decryptionWorker = _ref5.decryptionWorker, 42056 segment = _ref5.segment, 42057 trackInfoFn = _ref5.trackInfoFn, 42058 timingInfoFn = _ref5.timingInfoFn, 42059 videoSegmentTimingInfoFn = _ref5.videoSegmentTimingInfoFn, 42060 id3Fn = _ref5.id3Fn, 42061 captionsFn = _ref5.captionsFn, 42062 dataFn = _ref5.dataFn, 42063 doneFn = _ref5.doneFn; 42064 42065 var decryptionHandler = function decryptionHandler(event) { 42066 if (event.data.source === segment.requestId) { 42067 decryptionWorker.removeEventListener('message', decryptionHandler); 42068 var decrypted = event.data.decrypted; 42069 segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength); 42070 handleSegmentBytes({ 42071 segment: segment, 42072 bytes: segment.bytes, 42073 isPartial: false, 42074 trackInfoFn: trackInfoFn, 42075 timingInfoFn: timingInfoFn, 42076 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 42077 id3Fn: id3Fn, 42078 captionsFn: captionsFn, 42079 dataFn: dataFn, 42080 doneFn: doneFn 42081 }); 42082 } 42083 }; 42084 42085 decryptionWorker.addEventListener('message', decryptionHandler); 42086 var keyBytes; 42087 42088 if (segment.key.bytes.slice) { 42089 keyBytes = segment.key.bytes.slice(); 42090 } else { 42091 keyBytes = new Uint32Array(Array.prototype.slice.call(segment.key.bytes)); 42092 } // this is an encrypted segment 42093 // incrementally decrypt the segment 42094 42095 42096 decryptionWorker.postMessage(createTransferableMessage({ 42097 source: segment.requestId, 42098 encrypted: segment.encryptedBytes, 42099 key: keyBytes, 42100 iv: segment.key.iv 42101 }), [segment.encryptedBytes.buffer, keyBytes.buffer]); 42102 }; 42103 /** 42104 * This function waits for all XHRs to finish (with either success or failure) 42105 * before continueing processing via it's callback. The function gathers errors 42106 * from each request into a single errors array so that the error status for 42107 * each request can be examined later. 42108 * 42109 * @param {Object} activeXhrs - an object that tracks all XHR requests 42110 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption 42111 * routines 42112 * @param {Function} trackInfoFn - a callback that receives track info 42113 * @param {Function} timingInfoFn - a callback that receives timing info 42114 * @param {Function} id3Fn - a callback that receives ID3 metadata 42115 * @param {Function} captionsFn - a callback that receives captions 42116 * @param {Function} dataFn - a callback that is executed when segment bytes are available 42117 * and ready to use 42118 * @param {Function} doneFn - a callback that is executed after all resources have been 42119 * downloaded and any decryption completed 42120 */ 42121 42122 42123 var waitForCompletion = function waitForCompletion(_ref6) { 42124 var activeXhrs = _ref6.activeXhrs, 42125 decryptionWorker = _ref6.decryptionWorker, 42126 trackInfoFn = _ref6.trackInfoFn, 42127 timingInfoFn = _ref6.timingInfoFn, 42128 videoSegmentTimingInfoFn = _ref6.videoSegmentTimingInfoFn, 42129 id3Fn = _ref6.id3Fn, 42130 captionsFn = _ref6.captionsFn, 42131 dataFn = _ref6.dataFn, 42132 doneFn = _ref6.doneFn; 42133 var count = 0; 42134 var didError = false;
42135 return function (error, segment) { 42136 if (didError) { 42137 return; 42138 } 42139 42140 if (error) { 42141 didError = true; // If there are errors, we have to abort any outstanding requests 42142 42143 abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we 42144 // handle the aborted events from those requests, there are some cases where we may 42145 // never get an aborted event. For instance, if the network connection is lost and 42146 // there were two requests, the first may have triggered an error immediately, while 42147 // the second request remains unsent. In that case, the aborted algorithm will not 42148 // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method 42149 // 42150 // We also can't rely on the ready state of the XHR, since the request that 42151 // triggered the connection error may also show as a ready state of 0 (unsent). 42152 // Therefore, we have to finish this group of requests immediately after the first 42153 // seen error. 42154 42155 return doneFn(error, segment); 42156 } 42157 42158 count += 1; 42159 42160 if (count === activeXhrs.length) { 42161 // Keep track of when *all* of the requests have completed 42162 segment.endOfAllRequests = Date.now(); 42163 42164 if (segment.encryptedBytes) { 42165 return decryptSegment({ 42166 decryptionWorker: decryptionWorker, 42167 segment: segment, 42168 trackInfoFn: trackInfoFn, 42169 timingInfoFn: timingInfoFn, 42170 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 42171 id3Fn: id3Fn, 42172 captionsFn: captionsFn, 42173 dataFn: dataFn, 42174 doneFn: doneFn 42175 }); 42176 } // Otherwise, everything is ready just continue 42177 42178 42179 handleSegmentBytes({ 42180 segment: segment, 42181 bytes: segment.bytes, 42182 isPartial: false, 42183 trackInfoFn: trackInfoFn, 42184 timingInfoFn: timingInfoFn, 42185 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 42186 id3Fn: id3Fn, 42187 captionsFn: captionsFn, 42188 dataFn: dataFn, 42189 doneFn: doneFn 42190 }); 42191 } 42192 }; 42193 }; 42194 /** 42195 * Calls the abort callback if any request within the batch was aborted. Will only call 42196 * the callback once per batch of requests, even if multiple were aborted. 42197 * 42198 * @param {Object} loadendState - state to check to see if the abort function was called 42199 * @param {Function} abortFn - callback to call for abort 42200 */ 42201 42202 42203 var handleLoadEnd = function handleLoadEnd(_ref7) { 42204 var loadendState = _ref7.loadendState, 42205 abortFn = _ref7.abortFn; 42206 return function (event) { 42207 var request = event.target; 42208 42209 if (request.aborted && abortFn && !loadendState.calledAbortFn) { 42210 abortFn(); 42211 loadendState.calledAbortFn = true; 42212 } 42213 }; 42214 }; 42215 /** 42216 * Simple progress event callback handler that gathers some stats before 42217 * executing a provided callback with the `segment` object 42218 * 42219 * @param {Object} segment - a simplified copy of the segmentInfo object 42220 * from SegmentLoader 42221 * @param {Function} progressFn - a callback that is executed each time a progress event 42222 * is received 42223 * @param {Function} trackInfoFn - a callback that receives track info 42224 * @param {Function} dataFn - a callback that is executed when segment bytes are available 42225 * and ready to use 42226 * @param {Event} event - the progress event object from XMLHttpRequest 42227 */ 42228 42229 42230 var handleProgress = function handleProgress(_ref8) { 42231 var segment = _ref8.segment, 42232 progressFn = _ref8.progressFn, 42233 trackInfoFn = _ref8.trackInfoFn, 42234 timingInfoFn = _ref8.timingInfoFn, 42235 videoSegmentTimingInfoFn = _ref8.videoSegmentTimingInfoFn, 42236 id3Fn = _ref8.id3Fn, 42237 captionsFn = _ref8.captionsFn, 42238 dataFn = _ref8.dataFn, 42239 handlePartialData = _ref8.handlePartialData; 42240 return function (event) { 42241 var request = event.target; 42242 42243 if (request.aborted) { 42244 return; 42245 } // don't support encrypted segments or fmp4 for now 42246 42247 42248 if (handlePartialData && !segment.key && // although responseText "should" exist, this guard serves to prevent an error being 42249 // thrown on the next check for two primary cases: 42250 // 1. the mime type override stops working, or is not implemented for a specific 42251 // browser 42252 // 2. when using mock XHR libraries like sinon that do not allow the override behavior 42253 request.responseText && // in order to determine if it's an fmp4 we need at least 8 bytes 42254 request.responseText.length >= 8) { 42255 var newBytes = stringToArrayBuffer(request.responseText.substring(segment.lastReachedChar || 0
42255)); 42256 42257 if (segment.lastReachedChar || !containers_5(new Uint8Array(newBytes))) { 42258 segment.lastReachedChar = request.responseText.length; 42259 handleSegmentBytes({ 42260 segment: segment, 42261 bytes: newBytes, 42262 isPartial: true, 42263 trackInfoFn: trackInfoFn, 42264 timingInfoFn: timingInfoFn, 42265 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 42266 id3Fn: id3Fn, 42267 captionsFn: captionsFn, 42268 dataFn: dataFn 42269 }); 42270 } 42271 } 42272 42273 segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data 42274 42275 if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) { 42276 segment.stats.firstBytesReceivedAt = Date.now(); 42277 } 42278 42279 return progressFn(event, segment); 42280 }; 42281 }; 42282 /** 42283 * Load all resources and does any processing necessary for a media-segment 42284 * 42285 * Features: 42286 * decrypts the media-segment if it has a key uri and an iv 42287 * aborts *all* requests if *any* one request fails 42288 * 42289 * The segment object, at minimum, has the following format: 42290 * { 42291 * resolvedUri: String, 42292 * [transmuxer]: Object, 42293 * [byterange]: { 42294 * offset: Number, 42295 * length: Number 42296 * }, 42297 * [key]: { 42298 * resolvedUri: String 42299 * [byterange]: { 42300 * offset: Number, 42301 * length: Number 42302 * }, 42303 * iv: { 42304 * bytes: Uint32Array 42305 * } 42306 * }, 42307 * [map]: { 42308 * resolvedUri: String, 42309 * [byterange]: { 42310 * offset: Number, 42311 * length: Number 42312 * }, 42313 * [bytes]: Uint8Array 42314 * } 42315 * } 42316 * ...where [name] denotes optional properties 42317 * 42318 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js 42319 * @param {Object} xhrOptions - the base options to provide to all xhr requests 42320 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 42321 * decryption routines 42322 * @param {Object} segment - a simplified copy of the segmentInfo object 42323 * from SegmentLoader 42324 * @param {Function} abortFn - a callback called (only once) if any piece of a request was 42325 * aborted 42326 * @param {Function} progressFn - a callback that receives progress events from the main 42327 * segment's xhr request 42328 * @param {Function} trackInfoFn - a callback that receives track info 42329 * @param {Function} id3Fn - a callback that receives ID3 metadata 42330 * @param {Function} captionsFn - a callback that receives captions 42331 * @param {Function} dataFn - a callback that receives data from the main segment's xhr 42332 * request, transmuxed if needed 42333 * @param {Function} doneFn - a callback that is executed only once all requests have 42334 * succeeded or failed 42335 * @return {Function} a function that, when invoked, immediately aborts all 42336 * outstanding requests 42337 */ 42338 42339 42340 var mediaSegmentRequest = function mediaSegmentRequest(_ref9) { 42341 var xhr = _ref9.xhr, 42342 xhrOptions = _ref9.xhrOptions, 42343 decryptionWorker = _ref9.decryptionWorker, 42344 segment = _ref9.segment, 42345 abortFn = _ref9.abortFn, 42346 progressFn = _ref9.progressFn, 42347 trackInfoFn = _ref9.trackInfoFn, 42348 timingInfoFn = _ref9.timingInfoFn, 42349 videoSegmentTimingInfoFn = _ref9.videoSegmentTimingInfoFn, 42350 id3Fn = _ref9.id3Fn, 42351 captionsFn = _ref9.captionsFn, 42352 dataFn = _ref9.dataFn, 42353 doneFn = _ref9.doneFn, 42354 handlePartialData = _ref9.handlePartialData; 42355 var activeXhrs = []; 42356 var finishProcessingFn = waitForCompletion({ 42357 activeXhrs: activeXhrs, 42358 decryptionWorker: decryptionWorker, 42359 trackInfoFn: trackInfoFn, 42360 timingInfoFn: timingInfoFn, 42361 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 42362 id3Fn: id3Fn, 42363 captionsFn: captionsFn, 42364 dataFn: dataFn, 42365 doneFn: doneFn 42366 }); // optionally, request the decryption key 42367 42368 if (segment.key && !segment.key.bytes) { 42369 var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, { 42370 uri: segment.key.resolvedUri, 42371 responseType: 'arraybuffer' 42372 }); 42373 var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn); 42374 var keyXhr = xhr(keyRequestOptions, keyRequestCallback); 42375 activeXhrs.push(keyXhr); 42376 } // optionally, request the associated media init segment 42377 42378 42379 if (segment.map && !segment.map.bytes) { 42380 var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, { 42381 uri: segment.map.resolvedUri, 42382 responseType: 'arraybuffer', 42383 headers: segmentXhrHeaders(segment.map) 42384 }); 42385 var initSegmentRequestCallback = handleInitSegmentResponse({ 42386 segment: segment, 42387 finishProcessingFn: finishProcessingFn 42388 }); 42389 var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); 42390 activeXhrs.push(initSegmentXhr); 42391 } 42392 42393 var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, { 42394 uri: segment.resolvedUri, 42395 responseType: 'arraybuffer', 42396 headers: segmentXhrHeaders(segment) 42397 }); 42398 42399 if (handlePartialData) { 42400 // setting to text is required for partial responses 42401 // conversion to ArrayBuffer happens later 42402 segmentRequestOptions.responseType = 'text'; 42403 42404 segmentRequestOptions.beforeSend = function (xhrObject) { 42405 // XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com] 42406 // makes the browser pass through the "text" unparsed 42407 xhrObject.overrideMimeType('text/plain;
vendor: 10,915 bytes, lines 42407-42762
42407 charset=x-user-defined'); 42408 }; 42409 } 42410 42411 var segmentRequestCallback = handleSegmentResponse({ 42412 segment: segment, 42413 finishProcessingFn: finishProcessingFn, 42414 responseType: segmentRequestOptions.responseType 42415 }); 42416 var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); 42417 segmentXhr.addEventListener('progress', handleProgress({ 42418 segment: segment, 42419 progressFn: progressFn, 42420 trackInfoFn: trackInfoFn, 42421 timingInfoFn: timingInfoFn, 42422 videoSegmentTimingInfoFn: videoSegmentTimingInfoFn, 42423 id3Fn: id3Fn, 42424 captionsFn: captionsFn, 42425 dataFn: dataFn, 42426 handlePartialData: handlePartialData 42427 })); 42428 activeXhrs.push(segmentXhr); // since all parts of the request must be considered, but should not make callbacks 42429 // multiple times, provide a shared state object 42430 42431 var loadendState = {}; 42432 activeXhrs.forEach(function (activeXhr) { 42433 activeXhr.addEventListener('loadend', handleLoadEnd({ 42434 loadendState: loadendState, 42435 abortFn: abortFn 42436 })); 42437 }); 42438 return function () { 42439 return abortAll(activeXhrs); 42440 }; 42441 }; 42442 42443 var win = typeof window !== 'undefined' ? window : {}, 42444 TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(), 42445 SCRIPT_TYPE = 'application/javascript', 42446 BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder, 42447 URL = win.URL || win.webkitURL || URL && URL.msURL, 42448 Worker = win.Worker; 42449 /** 42450 * Returns a wrapper around Web Worker code that is constructible. 42451 * 42452 * @function shimWorker 42453 * 42454 * @param { String } filename The name of the file 42455 * @param { Function } fn Function wrapping the code of the worker 42456 */ 42457 42458 function shimWorker(filename, fn) { 42459 return function ShimWorker(forceFallback) { 42460 var o = this; 42461 42462 if (!fn) { 42463 return new Worker(filename); 42464 } else if (Worker && !forceFallback) { 42465 // Convert the function's inner code to a string to construct the worker 42466 var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1), 42467 objURL = createSourceObject(source); 42468 this[TARGET] = new Worker(objURL); 42469 wrapTerminate(this[TARGET], objURL); 42470 return this[TARGET]; 42471 } else { 42472 var selfShim = { 42473 postMessage: function postMessage(m) { 42474 if (o.onmessage) { 42475 setTimeout(function () { 42476 o.onmessage({ 42477 data: m, 42478 target: selfShim 42479 }); 42480 }); 42481 } 42482 } 42483 }; 42484 fn.call(selfShim); 42485 42486 this.postMessage = function (m) { 42487 setTimeout(function () { 42488 selfShim.onmessage({ 42489 data: m, 42490 target: o 42491 }); 42492 }); 42493 }; 42494 42495 this.isThisThread = true; 42496 } 42497 }; 42498 } // Test Worker capabilities 42499 42500 42501 if (Worker) { 42502 var testWorker, 42503 objURL = createSourceObject('self.onmessage = function () {}'), 42504 testArray = new Uint8Array(1); 42505 42506 try { 42507 testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it 42508 42509 testWorker.postMessage(testArray, [testArray.buffer]); 42510 } catch (e) { 42511 Worker = null; 42512 } finally { 42513 URL.revokeObjectURL(objURL); 42514 42515 if (testWorker) { 42516 testWorker.terminate(); 42517 } 42518 } 42519 } 42520 42521 function createSourceObject(str) { 42522 try { 42523 return URL.createObjectURL(new Blob([str], { 42524 type: SCRIPT_TYPE 42525 })); 42526 } catch (e) { 42527 var blob = new BlobBuilder(); 42528 blob.append(str); 42529 return URL.createObjectURL(blob.getBlob(type)); 42530 } 42531 } 42532 42533 function wrapTerminate(worker, objURL) { 42534 if (!worker || !objURL) return; 42535 var term = worker.terminate; 42536 worker.objURL = objURL; 42537 42538 worker.terminate = function () { 42539 if (worker.objURL) URL.revokeObjectURL(worker.objURL); 42540 term.call(worker); 42541 }; 42542 } 42543 42544 var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document) { 42545 var self = this; 42546 /*! @name @videojs/http-streaming @version 2.2.4 @license Apache-2.0 */ 42547 42548 var transmuxerWorker = function () { 42549 /** 42550 * mux.js 42551 * 42552 * Copyright (c) Brightcove 42553 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 42554 * 42555 * A lightweight readable stream implemention that handles event dispatching. 42556 * Objects that inherit from streams should call init in their constructors. 42557 */ 42558 var Stream = function Stream() { 42559 this.init = function () { 42560 var listeners = {}; 42561 /** 42562 * Add a listener for a specified event type. 42563 * @param type {string} the event name 42564 * @param listener {function} the callback to be invoked when an event of 42565 * the specified type occurs 42566 */ 42567 42568 this.on = function (type, listener) { 42569 if (!listeners[type]) { 42570 listeners[type] = []; 42571 } 42572 42573 listeners[type] = listeners[type].concat(listener); 42574 }; 42575 /** 42576 * Remove a listener for a specified event type. 42577 * @param type {string} the event name 42578 * @param listener {function} a function previously registered for this 42579 * type of event through `on` 42580 */ 42581 42582 42583 this.off = function (type, listener) { 42584 var index; 42585 42586 if (!listeners[type]) { 42587 return false; 42588 } 42589 42590 index = listeners[type].indexOf(listener); 42591 listeners[type] = listeners[type].slice(); 42592 listeners[type].splice(index, 1); 42593 return index > -1; 42594 }; 42595 /** 42596 * Trigger an event of the specified type on this stream. Any additional 42597 * arguments to this function are passed as parameters to event listeners. 42598 * @param type {string} the event name 42599 */ 42600 42601 42602 this.trigger = function (type) { 42603 var callbacks, i, length, args; 42604 callbacks = listeners[type]; 42605 42606 if (!callbacks) { 42607 return; 42608 } // Slicing the arguments on every invocation of this method 42609 // can add a significant amount of overhead. Avoid the 42610 // intermediate object creation for the common case of a 42611 // single callback argument 42612 42613 42614 if (arguments.length === 2) { 42615 length = callbacks.length; 42616 42617 for (i = 0; i < length; ++i) { 42618 callbacks[i].call(this, arguments[1]); 42619 } 42620 } else { 42621 args = []; 42622 i = arguments.length; 42623 42624 for (i = 1; i < arguments.length; ++i) { 42625 args.push(arguments[i]); 42626 } 42627 42628 length = callbacks.length; 42629 42630 for (i = 0; i < length; ++i) { 42631 callbacks[i].apply(this, args); 42632 } 42633 } 42634 }; 42635 /** 42636 * Destroys the stream and cleans up. 42637 */ 42638 42639 42640 this.dispose = function () { 42641 listeners = {}; 42642 }; 42643 }; 42644 }; 42645 /** 42646 * Forwards all `data` events on this stream to the destination stream. The 42647 * destination stream should provide a method `push` to receive the data 42648 * events as they arrive. 42649 * @param destination {stream} the stream that will receive all `data` events 42650 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 42651 * when the current stream emits a 'done' event 42652 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 42653 */ 42654 42655 42656 Stream.prototype.pipe = function (destination) { 42657 this.on('data', function (data) { 42658 destination.push(data); 42659 }); 42660 this.on('done', function (flushSource) { 42661 destination.flush(flushSource); 42662 }); 42663 this.on('partialdone', function (flushSource) { 42664 destination.partialFlush(flushSource); 42665 }); 42666 this.on('endedtimeline', function (flushSource) { 42667 destination.endTimeline(flushSource); 42668 }); 42669 this.on('reset', function (flushSource) { 42670 destination.reset(flushSource); 42671 }); 42672 return destination; 42673 }; // Default stream functions that are expected to be overridden to perform 42674 // actual work. These are provided by the prototype as a sort of no-op 42675 // implementation so that we don't have to check for their existence in the 42676 // `pipe` function above. 42677 42678 42679 Stream.prototype.push = function (data) { 42680 this.trigger('data', data); 42681 }; 42682 42683 Stream.prototype.flush = function (flushSource) { 42684 this.trigger('done', flushSource); 42685 }; 42686 42687 Stream.prototype.partialFlush = function (flushSource) { 42688 this.trigger('partialdone', flushSource); 42689 }; 42690 42691 Stream.prototype.endTimeline = function (flushSource) { 42692 this.trigger('endedtimeline', flushSource); 42693 }; 42694 42695 Stream.prototype.reset = function (flushSource) { 42696 this.trigger('reset', flushSource); 42697 }; 42698 42699 var stream = Stream; 42700 /** 42701 * mux.js 42702 * 42703 * Copyright (c) Brightcove 42704 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 42705 * 42706 * Functions that generate fragmented MP4s suitable for use with Media 42707 * Source Extensions. 42708 */ 42709 42710 var UINT32_MAX = Math.pow(2, 32) - 1; 42711 var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants 42712 42713 (function () { 42714 var i; 42715 types = { 42716 avc1: [], 42717 // codingname 42718 avcC: [], 42719 btrt: [], 42720 dinf: [], 42721 dref: [], 42722 esds: [], 42723 ftyp: [], 42724 hdlr: [], 42725 mdat: [], 42726 mdhd: [], 42727 mdia: [], 42728 mfhd: [], 42729 minf: [], 42730 moof: [], 42731 moov: [], 42732 mp4a: [], 42733 // codingname 42734 mvex: [], 42735 mvhd: [], 42736 pasp: [], 42737 sdtp: [], 42738 smhd: [], 42739 stbl: [], 42740 stco: [], 42741 stsc: [], 42742 stsd: [], 42743 stsz: [], 42744 stts: [], 42745 styp: [], 42746 tfdt: [], 42747 tfhd: [], 42748 traf: [], 42749 trak: [], 42750 trun: [], 42751 trex: [], 42752 tkhd: [], 42753 vmhd: [] 42754 }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 42755 // don't throw an error 42756 42757 if (typeof Uint8Array === 'undefined') { 42758 return; 42759 } 42760 42761 for (i in types) { 42762 if (types.hasOwnProperty(i)) {
vendor: 51,974 bytes, lines 42763-44119
42763 types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; 42764 } 42765 } 42766 42767 MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]); 42768 AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]); 42769 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 42770 VIDEO_HDLR = new Uint8Array([0x00, // version 0 42771 0x00, 0x00, 0x00, // flags 42772 0x00, 0x00, 0x00, 0x00, // pre_defined 42773 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 42774 0x00, 0x00, 0x00, 0x00, // reserved 42775 0x00, 0x00, 0x00, 0x00, // reserved 42776 0x00, 0x00, 0x00, 0x00, // reserved 42777 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 42778 ]); 42779 AUDIO_HDLR = new Uint8Array([0x00, // version 0 42780 0x00, 0x00, 0x00, // flags 42781 0x00, 0x00, 0x00, 0x00, // pre_defined 42782 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 42783 0x00, 0x00, 0x00, 0x00, // reserved 42784 0x00, 0x00, 0x00, 0x00, // reserved 42785 0x00, 0x00, 0x00, 0x00, // reserved 42786 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 42787 ]); 42788 HDLR_TYPES = { 42789 video: VIDEO_HDLR, 42790 audio: AUDIO_HDLR 42791 }; 42792 DREF = new Uint8Array([0x00, // version 0 42793 0x00, 0x00, 0x00, // flags 42794 0x00, 0x00, 0x00, 0x01, // entry_count 42795 0x00, 0x00, 0x00, 0x0c, // entry_size 42796 0x75, 0x72, 0x6c, 0x20, // 'url' type 42797 0x00, // version 0 42798 0x00, 0x00, 0x01 // entry_flags 42799 ]); 42800 SMHD = new Uint8Array([0x00, // version 42801 0x00, 0x00, 0x00, // flags 42802 0x00, 0x00, // balance, 0 means centered 42803 0x00, 0x00 // reserved 42804 ]); 42805 STCO = new Uint8Array([0x00, // version 42806 0x00, 0x00, 0x00, // flags 42807 0x00, 0x00, 0x00, 0x00 // entry_count 42808 ]); 42809 STSC = STCO; 42810 STSZ = new Uint8Array([0x00, // version 42811 0x00, 0x00, 0x00, // flags 42812 0x00, 0x00, 0x00, 0x00, // sample_size 42813 0x00, 0x00, 0x00, 0x00 // sample_count 42814 ]); 42815 STTS = STCO; 42816 VMHD = new Uint8Array([0x00, // version 42817 0x00, 0x00, 0x01, // flags 42818 0x00, 0x00, // graphicsmode 42819 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor 42820 ]); 42821 })(); 42822 42823 box = function box(type) { 42824 var payload = [], 42825 size = 0, 42826 i, 42827 result, 42828 view; 42829 42830 for (i = 1; i < arguments.length; i++) { 42831 payload.push(arguments[i]); 42832 } 42833 42834 i = payload.length; // calculate the total size we need to allocate 42835 42836 while (i--) { 42837 size += payload[i].byteLength; 42838 } 42839 42840 result = new Uint8Array(size + 8); 42841 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 42842 view.setUint32(0, result.byteLength); 42843 result.set(type, 4); // copy the payload into the result 42844 42845 for (i = 0, size = 8; i < payload.length; i++) { 42846 result.set(payload[i], size); 42847 size += payload[i].byteLength; 42848 } 42849 42850 return result; 42851 }; 42852 42853 dinf = function dinf() { 42854 return box(types.dinf, box(types.dref, DREF)); 42855 }; 42856 42857 esds = function esds(track) { 42858 return box(types.esds, new Uint8Array([0x00, // version 42859 0x00, 0x00, 0x00, // flags 42860 // ES_Descriptor 42861 0x03, // tag, ES_DescrTag 42862 0x19, // length 42863 0x00, 0x00, // ES_ID 42864 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 42865 // DecoderConfigDescriptor 42866 0x04, // tag, DecoderConfigDescrTag 42867 0x11, // length 42868 0x40, // object type 42869 0x15, // streamType 42870 0x00, 0x06, 0x00, // bufferSizeDB 42871 0x00, 0x00, 0xda, 0xc0, // maxBitrate 42872 0x00, 0x00, 0xda, 0xc0, // avgBitrate 42873 // DecoderSpecificInfo 42874 0x05, // tag, DecoderSpecificInfoTag 42875 0x02, // length 42876 // ISO/IEC 14496-3, AudioSpecificConfig 42877 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 42878 track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig 42879 ])); 42880 }; 42881 42882 ftyp = function ftyp() { 42883 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 42884 }; 42885 42886 hdlr = function hdlr(type) { 42887 return box(types.hdlr, HDLR_TYPES[type]); 42888 }; 42889 42890 mdat = function mdat(data) { 42891 return box(types.mdat, data); 42892 }; 42893 42894 mdhd = function mdhd(track) { 42895 var result = new Uint8Array([0x00, // version 0 42896 0x00, 0x00, 0x00, // flags 42897 0x00, 0x00, 0x00, 0x02, // creation_time 42898 0x00, 0x00, 0x00, 0x03, // modification_time 42899 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 42900 track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration 42901 0x55, 0xc4, // 'und' language (undetermined) 42902 0x00, 0x00]); // Use the sample rate from the track metadata, when it is 42903 // defined. The sample rate can be parsed out of an ADTS header, for 42904 // instance. 42905 42906 if (track.samplerate) { 42907 result[12] = track.samplerate >>> 24 & 0xFF; 42908 result[13] = track.samplerate >>> 16 & 0xFF; 42909 result[14] = track.samplerate >>> 8 & 0xFF; 42910 result[15] = track.samplerate & 0xFF; 42911 } 42912 42913 return box(types.mdhd, result); 42914 }; 42915 42916 mdia = function mdia(track) { 42917 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 42918 }; 42919 42920 mfhd = function mfhd(sequenceNumber) { 42921 return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags 42922 (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number 42923 ])); 42924 }; 42925 42926 minf = function minf(track) { 42927 return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track)); 42928 }; 42929 42930 moof = function moof(sequenceNumber, tracks) { 42931 var trackFragments = [], 42932 i = tracks.length; // build traf boxes for each track fragment 42933 42934 while (i--) { 42935 trackFragments[i] = traf(tracks[i]); 42936 } 42937 42938 return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments)); 42939 }; 42940 /** 42941 * Returns a movie box. 42942 * @param tracks {array} the tracks associated with this movie 42943 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 42944 */ 42945 42946 42947 moov = function moov(tracks) { 42948 var i = tracks.length, 42949 boxes = []; 42950 42951 while (i--) { 42952 boxes[i] = trak(tracks[i]); 42953 } 42954 42955 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 42956 }; 42957 42958 mvex = function mvex(tracks) { 42959 var i = tracks.length, 42960 boxes = []; 42961 42962 while (i--) { 42963 boxes[i] = trex(tracks[i]); 42964 } 42965 42966 return box.apply(null, [types.mvex].concat(boxes)); 42967 }; 42968 42969 mvhd = function mvhd(duration) { 42970 var bytes = new Uint8Array([0x00, // version 0 42971 0x00, 0x00, 0x00, // flags 42972 0x00, 0x00, 0x00, 0x01, // creation_time 42973 0x00, 0x00, 0x00, 0x02, // modification_time 42974 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 42975 (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration 42976 0x00, 0x01, 0x00, 0x00, // 1.0 rate 42977 0x01, 0x00, // 1.0 volume 42978 0x00, 0x00, // reserved 42979 0x00, 0x00, 0x00, 0x00, // reserved 42980 0x00, 0x00, 0x00, 0x00, // reserved 42981 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 42982 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 42983 0xff, 0xff, 0xff, 0xff // next_track_ID 42984 ]); 42985 return box(types.mvhd, bytes); 42986 }; 42987 42988 sdtp = function sdtp(track) { 42989 var samples = track.samples || [], 42990 bytes = new Uint8Array(4 + samples.length), 42991 flags, 42992 i; // leave the full box header (4 bytes) all zero 42993 // write the sample table 42994 42995 for (i = 0; i < samples.length; i++) { 42996 flags = samples[i].flags; 42997 bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; 42998 } 42999 43000 return box(types.sdtp, bytes); 43001 }; 43002 43003 stbl = function stbl(track) { 43004 return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO)); 43005 }; 43006 43007 (function () { 43008 var videoSample, audioSample; 43009 43010 stsd = function stsd(track) { 43011 return box(types.stsd, new Uint8Array([0x00, // version 0 43012 0x00, 0x00, 0x00, // flags 43013 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track)); 43014 }; 43015 43016 videoSample = function videoSample(track) { 43017 var sps = track.sps || [], 43018 pps = track.pps || [], 43019 sequenceParameterSets = [], 43020 pictureParameterSets = [], 43021 i, 43022 avc1Box; // assemble the SPSs 43023 43024 for (i = 0; i < sps.length; i++) { 43025 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 43026 sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength 43027 43028 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 43029 } // assemble the PPSs 43030 43031 43032 for (i = 0; i < pps.length; i++) { 43033 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 43034 pictureParameterSets.push(pps[i].byteLength & 0xFF); 43035 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 43036 } 43037 43038 avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 43039 0x00, 0x01, // data_reference_index 43040 0x00, 0x00, // pre_defined 43041 0x00, 0x00, // reserved 43042 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 43043 (track.width & 0xff00) >> 8, track.width & 0xff, // width 43044 (track.height & 0xff00) >> 8, track.height & 0xff, // height 43045 0x00, 0x48, 0x00, 0x00, // horizresolution 43046 0x00, 0x48, 0x00, 0x00, // vertresolution 43047 0x00, 0x00, 0x00, 0x00, // reserved 43048 0x00, 0x01, // frame_count 43049 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 43050 0x00, 0x18, // depth = 24 43051 0x11, 0x11 // pre_defined = -1 43052 ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion 43053 track.profileIdc, // AVCProfileIndication 43054 track.profileCompatibility, // profile_compatibility 43055 track.levelIdc, // AVCLevelIndication 43056 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 43057 ].concat([sps.length], // numOfSequenceParameterSets 43058 sequenceParameterSets, // "SPS" 43059 [pps.length], // numOfPictureParameterSets 43060 pictureParameterSets // "PPS" 43061 ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 43062 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 43063 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate 43064 ]))]; 43065 43066 if (track.sarRatio) { 43067 var hSpacing = track.sarRatio[0], 43068 vSpacing = track.sarRatio[1]; 43069 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]))); 43070 } 43071 43072 return box.apply(null, avc1Box); 43073 }; 43074 43075 audioSample = function audioSample(track) { 43076 return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12 43077 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 43078 0x00, 0x01, // data_reference_index 43079 // AudioSampleEntry, ISO/IEC 14496-12 43080 0x00, 0x00, 0x00, 0x00, // reserved 43081 0x00, 0x00, 0x00, 0x00, // reserved 43082 (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount 43083 (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize 43084 0x00, 0x00, // pre_defined 43085 0x00, 0x00, // reserved 43086 (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16 43087 // MP4AudioSampleEntry, ISO/IEC 14496-14 43088 ]), esds(track)); 43089 }; 43090 })(); 43091 43092 tkhd = function tkhd(track) { 43093 var result = new Uint8Array([0x00, // version 0 43094 0x00, 0x00, 0x07, // flags 43095 0x00, 0x00, 0x00, 0x00, // creation_time 43096 0x00, 0x00, 0x00, 0x00, // modification_time 43097 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 43098 0x00, 0x00, 0x00, 0x00, // reserved 43099 (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration 43100 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 43101 0x00, 0x00, // layer 43102 0x00, 0x00, // alternate_group 43103 0x01, 0x00, // non-audio track volume 43104 0x00, 0x00, // reserved 43105 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 43106 (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width 43107 (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height 43108 ]); 43109 return box(types.tkhd, result); 43110 }; 43111 /** 43112 * Generate a track fragment (traf) box. A traf box collects metadata 43113 * about tracks in a movie fragment (moof) box. 43114 */ 43115 43116 43117 traf = function traf(track) { 43118 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 43119 trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0 43120 0x00, 0x00, 0x3a, // flags 43121 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 43122 0x00, 0x00, 0x00, 0x01, // sample_description_index 43123 0x00, 0x00, 0x00, 0x00, // default_sample_duration 43124 0x00, 0x00, 0x00, 0x00, // default_sample_size 43125 0x00, 0x00, 0x00, 0x00 // default_sample_flags 43126 ])); 43127 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 43128 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 43129 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1 43130 0x00, 0x00, 0x00, // flags 43131 // baseMediaDecodeTime 43132 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 43133 // the containing moof to the first payload byte of the associated 43134 // mdat 43135 43136 dataOffset = 32 + // tfhd 43137 20 + // tfdt 43138 8 + // traf header 43139 16 + // mfhd 43140 8 + // moof header 43141 8; // mdat header 43142 // audio tracks require less metadata 43143 43144 if (track.type === 'audio') { 43145 trackFragmentRun = trun(track, dataOffset); 43146 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun); 43147 } // video tracks should contain an independent and disposable samples 43148 // box (sdtp) 43149 // generate one and adjust offsets to match 43150 43151 43152 sampleDependencyTable = sdtp(track); 43153 trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset); 43154 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable); 43155 }; 43156 /** 43157 * Generate a track box. 43158 * @param track {object} a track definition 43159 * @return {Uint8Array} the track box 43160 */ 43161 43162 43163 trak = function trak(track) { 43164 track.duration = track.duration || 0xffffffff; 43165 return box(types.trak, tkhd(track), mdia(track)); 43166 }; 43167 43168 trex = function trex(track) { 43169 var result = new Uint8Array([0x00, // version 0 43170 0x00, 0x00, 0x00, // flags 43171 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 43172 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 43173 0x00, 0x00, 0x00, 0x00, // default_sample_duration 43174 0x00, 0x00, 0x00, 0x00, // default_sample_size 43175 0x00, 0x01, 0x00, 0x01 // default_sample_flags 43176 ]); // the last two bytes of default_sample_flags is the sample 43177 // degradation priority, a hint about the importance of this sample 43178 // relative to others. Lower the degradation priority for all sample 43179 // types other than video. 43180 43181 if (track.type !== 'video') { 43182 result[result.length - 1] = 0x00; 43183 } 43184 43185 return box(types.trex, result); 43186 }; 43187 43188 (function () { 43189 var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a 43190 // duration is present for the first sample, it will be present for 43191 // all subsequent samples. 43192 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 43193 43194 trunHeader = function trunHeader(samples, offset) { 43195 var durationPresent = 0, 43196 sizePresent = 0, 43197 flagsPresent = 0, 43198 compositionTimeOffset = 0; // trun flag constants 43199 43200 if (samples.length) { 43201 if (samples[0].duration !== undefined) { 43202 durationPresent = 0x1; 43203 } 43204 43205 if (samples[0].size !== undefined) { 43206 sizePresent = 0x2; 43207 } 43208 43209 if (samples[0].flags !== undefined) { 43210 flagsPresent = 0x4; 43211 } 43212 43213 if (samples[0].compositionTimeOffset !== undefined) { 43214 compositionTimeOffset = 0x8; 43215 } 43216 } 43217 43218 return [0x00, // version 0 43219 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags 43220 (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count 43221 (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset 43222 ]; 43223 }; 43224 43225 videoTrun = function videoTrun(track, offset) { 43226 var bytesOffest, bytes, header, samples, sample, i; 43227 samples = track.samples || []; 43228 offset += 8 + 12 + 16 * samples.length; 43229 header = trunHeader(samples, offset); 43230 bytes = new Uint8Array(header.length + samples.length * 16); 43231 bytes.set(header); 43232 bytesOffest = header.length; 43233 43234 for (i = 0; i < samples.length; i++) { 43235 sample = samples[i]; 43236 bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24; 43237 bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16; 43238 bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8; 43239 bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration 43240 43241 bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24; 43242 bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16; 43243 bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8; 43244 bytes[bytesOffest++] = sample.size & 0xFF; // sample_size 43245 43246 bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn; 43247 bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample; 43248 bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8; 43249 bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags 43250 43251 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24; 43252 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16; 43253 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8; 43254 bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset 43255 } 43256 43257 return box(types.trun, bytes); 43258 }; 43259 43260 audioTrun = function audioTrun(track, offset) { 43261 var bytes, bytesOffest, header, samples, sample, i; 43262 samples = track.samples || []; 43263 offset += 8 + 12 + 8 * samples.length; 43264 header = trunHeader(samples, offset); 43265 bytes = new Uint8Array(header.length + samples.length * 8); 43266 bytes.set(header); 43267 bytesOffest = header.length; 43268 43269 for (i = 0; i < samples.length; i++) { 43270 sample = samples[i]; 43271 bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24; 43272 bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16; 43273 bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8; 43274 bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration 43275 43276 bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24; 43277 bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16; 43278 bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8; 43279 bytes[bytesOffest++] = sample.size & 0xFF; // sample_size 43280 } 43281 43282 return box(types.trun, bytes); 43283 }; 43284 43285 trun = function trun(track, offset) { 43286 if (track.type === 'audio') { 43287 return audioTrun(track, offset); 43288 } 43289 43290 return videoTrun(track, offset); 43291 }; 43292 })(); 43293 43294 var mp4Generator = { 43295 ftyp: ftyp, 43296 mdat: mdat, 43297 moof: moof, 43298 moov: moov, 43299 initSegment: function initSegment(tracks) { 43300 var fileType = ftyp(), 43301 movie = moov(tracks), 43302 result; 43303 result = new Uint8Array(fileType.byteLength + movie.byteLength); 43304 result.set(fileType); 43305 result.set(movie, fileType.byteLength); 43306 return result; 43307 } 43308 }; 43309 /** 43310 * mux.js 43311 * 43312 * Copyright (c) Brightcove 43313 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43314 */ 43315 // Convert an array of nal units into an array of frames with each frame being 43316 // composed of the nal units that make up that frame 43317 // Also keep track of cummulative data about the frame from the nal units such 43318 // as the frame duration, starting pts, etc. 43319 43320 var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) { 43321 var i, 43322 currentNal, 43323 currentFrame = [], 43324 frames = []; // TODO added for LHLS, make sure this is OK 43325 43326 frames.byteLength = 0; 43327 frames.nalCount = 0; 43328 frames.duration = 0; 43329 currentFrame.byteLength = 0; 43330 43331 for (i = 0; i < nalUnits.length; i++) { 43332 currentNal = nalUnits[i]; // Split on 'aud'-type nal units 43333 43334 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 43335 // Since the very first nal unit is expected to be an AUD 43336 // only push to the frames array when currentFrame is not empty 43337 if (currentFrame.length) { 43338 currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK 43339 43340 frames.byteLength += currentFrame.byteLength; 43341 frames.nalCount += currentFrame.length; 43342 frames.duration += currentFrame.duration; 43343 frames.push(currentFrame); 43344 } 43345 43346 currentFrame = [currentNal]; 43347 currentFrame.byteLength = currentNal.data.byteLength; 43348 currentFrame.pts = currentNal.pts; 43349 currentFrame.dts = currentNal.dts; 43350 } else { 43351 // Specifically flag key frames for ease of use later 43352 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 43353 currentFrame.keyFrame = true; 43354 } 43355 43356 currentFrame.duration = currentNal.dts - currentFrame.dts; 43357 currentFrame.byteLength += currentNal.data.byteLength; 43358 currentFrame.push(currentNal); 43359 } 43360 } // For the last frame, use the duration of the previous frame if we 43361 // have nothing better to go on 43362 43363 43364 if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) { 43365 currentFrame.duration = frames[frames.length - 1].duration; 43366 } // Push the final frame 43367 // TODO added for LHLS, make sure this is OK 43368 43369 43370 frames.byteLength += currentFrame.byteLength; 43371 frames.nalCount += currentFrame.length; 43372 frames.duration += currentFrame.duration; 43373 frames.push(currentFrame); 43374 return frames; 43375 }; // Convert an array of frames into an array of Gop with each Gop being composed 43376 // of the frames that make up that Gop 43377 // Also keep track of cummulative data about the Gop from the frames such as the 43378 // Gop duration, starting pts, etc. 43379 43380 43381 var groupFramesIntoGops = function groupFramesIntoGops(frames) { 43382 var i, 43383 currentFrame, 43384 currentGop = [], 43385 gops = []; // We must pre-set some of the values on the Gop since we 43386 // keep running totals of these values 43387 43388 currentGop.byteLength = 0; 43389 currentGop.nalCount = 0; 43390 currentGop.duration = 0; 43391 currentGop.pts = frames[0].pts; 43392 currentGop.dts = frames[0].dts; // store some metadata about all the Gops 43393 43394 gops.byteLength = 0; 43395 gops.nalCount = 0; 43396 gops.duration = 0; 43397 gops.pts = frames[0].pts; 43398 gops.dts = frames[0].dts; 43399 43400 for (i = 0; i < frames.length; i++) { 43401 currentFrame = frames[i]; 43402 43403 if (currentFrame.keyFrame) { 43404 // Since the very first frame is expected to be an keyframe 43405 // only push to the gops array when currentGop is not empty 43406 if (currentGop.length) { 43407 gops.push(currentGop); 43408 gops.byteLength += currentGop.byteLength; 43409 gops.nalCount += currentGop.nalCount; 43410 gops.duration += currentGop.duration; 43411 } 43412 43413 currentGop = [currentFrame]; 43414 currentGop.nalCount = currentFrame.length; 43415 currentGop.byteLength = currentFrame.byteLength; 43416 currentGop.pts = currentFrame.pts; 43417 currentGop.dts = currentFrame.dts; 43418 currentGop.duration = currentFrame.duration; 43419 } else { 43420 currentGop.duration += currentFrame.duration; 43421 currentGop.nalCount += currentFrame.length; 43422 currentGop.byteLength += currentFrame.byteLength; 43423 currentGop.push(currentFrame); 43424 } 43425 } 43426 43427 if (gops.length && currentGop.duration <= 0) { 43428 currentGop.duration = gops[gops.length - 1].duration; 43429 } 43430 43431 gops.byteLength += currentGop.byteLength; 43432 gops.nalCount += currentGop.nalCount; 43433 gops.duration += currentGop.duration; // push the final Gop 43434 43435 gops.push(currentGop); 43436 return gops; 43437 }; 43438 /* 43439 * Search for the first keyframe in the GOPs and throw away all frames 43440 * until that keyframe. Then extend the duration of the pulled keyframe 43441 * and pull the PTS and DTS of the keyframe so that it covers the time 43442 * range of the frames that were disposed. 43443 * 43444 * @param {Array} gops video GOPs 43445 * @returns {Array} modified video GOPs 43446 */ 43447 43448 43449 var extendFirstKeyFrame = function extendFirstKeyFrame(gops) { 43450 var currentGop; 43451 43452 if (!gops[0][0].keyFrame && gops.length > 1) { 43453 // Remove the first GOP 43454 currentGop = gops.shift(); 43455 gops.byteLength -= currentGop.byteLength; 43456 gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the 43457 // first gop to cover the time period of the 43458 // frames we just removed 43459 43460 gops[0][0].dts = currentGop.dts; 43461 gops[0][0].pts = currentGop.pts; 43462 gops[0][0].duration += currentGop.duration; 43463 } 43464 43465 return gops; 43466 }; 43467 /** 43468 * Default sample object 43469 * see ISO/IEC 14496-12:2012, section 8.6.4.3 43470 */ 43471 43472 43473 var createDefaultSample = function createDefaultSample() { 43474 return { 43475 size: 0, 43476 flags: { 43477 isLeading: 0, 43478 dependsOn: 1, 43479 isDependedOn: 0, 43480 hasRedundancy: 0, 43481 degradationPriority: 0, 43482 isNonSyncSample: 1 43483 } 43484 }; 43485 }; 43486 /* 43487 * Collates information from a video frame into an object for eventual 43488 * entry into an MP4 sample table. 43489 * 43490 * @param {Object} frame the video frame 43491 * @param {Number} dataOffset the byte offset to position the sample 43492 * @return {Object} object containing sample table info for a frame 43493 */ 43494 43495 43496 var sampleForFrame = function sampleForFrame(frame, dataOffset) { 43497 var sample = createDefaultSample(); 43498 sample.dataOffset = dataOffset; 43499 sample.compositionTimeOffset = frame.pts - frame.dts; 43500 sample.duration = frame.duration; 43501 sample.size = 4 * frame.length; // Space for nal unit size 43502 43503 sample.size += frame.byteLength; 43504 43505 if (frame.keyFrame) { 43506 sample.flags.dependsOn = 2; 43507 sample.flags.isNonSyncSample = 0; 43508 } 43509 43510 return sample; 43511 }; // generate the track's sample table from an array of gops 43512 43513 43514 var generateSampleTable = function generateSampleTable(gops, baseDataOffset) { 43515 var h, 43516 i, 43517 sample, 43518 currentGop, 43519 currentFrame, 43520 dataOffset = baseDataOffset || 0, 43521 samples = []; 43522 43523 for (h = 0; h < gops.length; h++) { 43524 currentGop = gops[h]; 43525 43526 for (i = 0; i < currentGop.length; i++) { 43527 currentFrame = currentGop[i]; 43528 sample = sampleForFrame(currentFrame, dataOffset); 43529 dataOffset += sample.size; 43530 samples.push(sample); 43531 } 43532 } 43533 43534 return samples; 43535 }; // generate the track's raw mdat data from an array of gops 43536 43537 43538 var concatenateNalData = function concatenateNalData(gops) { 43539 var h, 43540 i, 43541 j, 43542 currentGop, 43543 currentFrame, 43544 currentNal, 43545 dataOffset = 0, 43546 nalsByteLength = gops.byteLength, 43547 numberOfNals = gops.nalCount, 43548 totalByteLength = nalsByteLength + 4 * numberOfNals, 43549 data = new Uint8Array(totalByteLength), 43550 view = new DataView(data.buffer); // For each Gop.. 43551 43552 for (h = 0; h < gops.length; h++) { 43553 currentGop = gops[h]; // For each Frame.. 43554 43555 for (i = 0; i < currentGop.length; i++) { 43556 currentFrame = currentGop[i]; // For each NAL.. 43557 43558 for (j = 0; j < currentFrame.length; j++) { 43559 currentNal = currentFrame[j]; 43560 view.setUint32(dataOffset, currentNal.data.byteLength); 43561 dataOffset += 4; 43562 data.set(currentNal.data, dataOffset); 43563 dataOffset += currentNal.data.byteLength; 43564 } 43565 } 43566 } 43567 43568 return data; 43569 }; // generate the track's sample table from a frame 43570 43571 43572 var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) { 43573 var sample, 43574 dataOffset = baseDataOffset || 0, 43575 samples = []; 43576 sample = sampleForFrame(frame, dataOffset); 43577 samples.push(sample); 43578 return samples; 43579 }; // generate the track's raw mdat data from a frame 43580 43581 43582 var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) { 43583 var i, 43584 currentNal, 43585 dataOffset = 0, 43586 nalsByteLength = frame.byteLength, 43587 numberOfNals = frame.length, 43588 totalByteLength = nalsByteLength + 4 * numberOfNals, 43589 data = new Uint8Array(totalByteLength), 43590 view = new DataView(data.buffer); // For each NAL.. 43591 43592 for (i = 0; i < frame.length; i++) { 43593 currentNal = frame[i]; 43594 view.setUint32(dataOffset, currentNal.data.byteLength); 43595 dataOffset += 4; 43596 data.set(currentNal.data, dataOffset); 43597 dataOffset += currentNal.data.byteLength; 43598 } 43599 43600 return data; 43601 }; 43602 43603 var frameUtils = { 43604 groupNalsIntoFrames: groupNalsIntoFrames, 43605 groupFramesIntoGops: groupFramesIntoGops, 43606 extendFirstKeyFrame: extendFirstKeyFrame, 43607 generateSampleTable: generateSampleTable, 43608 concatenateNalData: concatenateNalData, 43609 generateSampleTableForFrame: generateSampleTableForFrame, 43610 concatenateNalDataForFrame: concatenateNalDataForFrame 43611 }; 43612 /** 43613 * mux.js 43614 * 43615 * Copyright (c) Brightcove 43616 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43617 */ 43618 43619 var highPrefix = [33, 16, 5, 32, 164, 27]; 43620 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 43621 43622 var zeroFill = function zeroFill(count) { 43623 var a = []; 43624 43625 while (count--) { 43626 a.push(0); 43627 } 43628 43629 return a; 43630 }; 43631 43632 var makeTable = function makeTable(metaTable) { 43633 return Object.keys(metaTable).reduce(function (obj, key) { 43634 obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) { 43635 return arr.concat(part); 43636 }, [])); 43637 return obj; 43638 }, {}); 43639 }; 43640 43641 var silence; 43642 43643 var silence_1 = function silence_1() { 43644 if (!silence) { 43645 // Frames-of-silence to use for filling in missing AAC frames 43646 var coneOfSilence = { 43647 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 43648 88200: [highPrefix, [231], zeroFill(170), [56]], 43649 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 43650 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 43651 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 43652 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 43653 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 43654 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 43655 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]], 43656 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]], 43657 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 43658 }; 43659 silence = makeTable(coneOfSilence); 43660 } 43661 43662 return silence; 43663 }; 43664 /** 43665 * mux.js 43666 * 43667 * Copyright (c) Brightcove 43668 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43669 */ 43670 43671 43672 var ONE_SECOND_IN_TS = 90000, 43673 // 90kHz clock 43674 secondsToVideoTs, 43675 secondsToAudioTs, 43676 videoTsToSeconds, 43677 audioTsToSeconds, 43678 audioTsToVideoTs, 43679 videoTsToAudioTs, 43680 metadataTsToSeconds; 43681 43682 secondsToVideoTs = function secondsToVideoTs(seconds) { 43683 return seconds * ONE_SECOND_IN_TS; 43684 }; 43685 43686 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 43687 return seconds * sampleRate; 43688 }; 43689 43690 videoTsToSeconds = function videoTsToSeconds(timestamp) { 43691 return timestamp / ONE_SECOND_IN_TS; 43692 }; 43693 43694 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 43695 return timestamp / sampleRate; 43696 }; 43697 43698 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 43699 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 43700 }; 43701 43702 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 43703 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 43704 }; 43705 /** 43706 * Adjust ID3 tag or caption timing information by the timeline pts values 43707 * (if keepOriginalTimestamps is false) and convert to seconds 43708 */ 43709 43710 43711 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) { 43712 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 43713 }; 43714 43715 var clock = { 43716 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 43717 secondsToVideoTs: secondsToVideoTs, 43718 secondsToAudioTs: secondsToAudioTs, 43719 videoTsToSeconds: videoTsToSeconds, 43720 audioTsToSeconds: audioTsToSeconds, 43721 audioTsToVideoTs: audioTsToVideoTs, 43722 videoTsToAudioTs: videoTsToAudioTs, 43723 metadataTsToSeconds: metadataTsToSeconds 43724 }; 43725 var clock_2 = clock.secondsToVideoTs; 43726 var clock_4 = clock.videoTsToSeconds; 43727 /** 43728 * mux.js 43729 * 43730 * Copyright (c) Brightcove 43731 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43732 */ 43733 43734 /** 43735 * Sum the `byteLength` properties of the data in each AAC frame 43736 */ 43737 43738 var sumFrameByteLengths = function sumFrameByteLengths(array) { 43739 var i, 43740 currentObj, 43741 sum = 0; // sum the byteLength's all each nal unit in the frame 43742 43743 for (i = 0; i < array.length; i++) { 43744 currentObj = array[i]; 43745 sum += currentObj.data.byteLength; 43746 } 43747 43748 return sum; 43749 }; // Possibly pad (prefix) the audio track with silence if appending this track 43750 // would lead to the introduction of a gap in the audio buffer 43751 43752 43753 var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) { 43754 var baseMediaDecodeTimeTs, 43755 frameDuration = 0, 43756 audioGapDuration = 0, 43757 audioFillFrameCount = 0, 43758 audioFillDuration = 0, 43759 silentFrame, 43760 i, 43761 firstFrame; 43762 43763 if (!frames.length) { 43764 return; 43765 } 43766 43767 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills 43768 43769 frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024)); 43770 43771 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 43772 // insert the shortest possible amount (audio gap or audio to video gap) 43773 audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap 43774 43775 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 43776 audioFillDuration = audioFillFrameCount * frameDuration; 43777 } // don't attempt to fill gaps smaller than a single frame or larger 43778 // than a half second 43779 43780 43781 if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) { 43782 return; 43783 } 43784 43785 silentFrame = silence_1()[track.samplerate]; 43786 43787 if (!silentFrame) { 43788 // we don't have a silent frame pregenerated for the sample rate, so use a frame 43789 // from the content instead 43790 silentFrame = frames[0].data; 43791 } 43792 43793 for (i = 0; i < audioFillFrameCount; i++) { 43794 firstFrame = frames[0]; 43795 frames.splice(0, 0, { 43796 data: silentFrame, 43797 dts: firstFrame.dts - frameDuration, 43798 pts: firstFrame.pts - frameDuration 43799 }); 43800 } 43801 43802 track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 43803 }; // If the audio segment extends before the earliest allowed dts 43804 // value, remove AAC frames until starts at or after the earliest 43805 // allowed DTS so that we don't end up with a negative baseMedia- 43806 // DecodeTime for the audio track 43807 43808 43809 var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) { 43810 if (track.minSegmentDts >= earliestAllowedDts) { 43811 return adtsFrames; 43812 } // We will need to recalculate the earliest segment Dts 43813 43814 43815 track.minSegmentDts = Infinity; 43816 return adtsFrames.filter(function (currentFrame) { 43817 // If this is an allowed frame, keep it and record it's Dts 43818 if (currentFrame.dts >= earliestAllowedDts) { 43819 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 43820 track.minSegmentPts = track.minSegmentDts; 43821 return true; 43822 } // Otherwise, discard it 43823 43824 43825 return false; 43826 }); 43827 }; // generate the track's raw mdat data from an array of frames 43828 43829 43830 var generateSampleTable$1 = function generateSampleTable$1(frames) { 43831 var i, 43832 currentFrame, 43833 samples = []; 43834 43835 for (i = 0; i < frames.length; i++) { 43836 currentFrame = frames[i]; 43837 samples.push({ 43838 size: currentFrame.data.byteLength, 43839 duration: 1024 // For AAC audio, all samples contain 1024 samples 43840 43841 }); 43842 } 43843 43844 return samples; 43845 }; // generate the track's sample table from an array of frames 43846 43847 43848 var concatenateFrameData = function concatenateFrameData(frames) { 43849 var i, 43850 currentFrame, 43851 dataOffset = 0, 43852 data = new Uint8Array(sumFrameByteLengths(frames)); 43853 43854 for (i = 0; i < frames.length; i++) { 43855 currentFrame = frames[i]; 43856 data.set(currentFrame.data, dataOffset); 43857 dataOffset += currentFrame.data.byteLength; 43858 } 43859 43860 return data; 43861 }; 43862 43863 var audioFrameUtils = { 43864 prefixWithSilence: prefixWithSilence, 43865 trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts, 43866 generateSampleTable: generateSampleTable$1, 43867 concatenateFrameData: concatenateFrameData 43868 }; 43869 /** 43870 * mux.js 43871 * 43872 * Copyright (c) Brightcove 43873 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43874 */ 43875 43876 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; 43877 /** 43878 * Store information about the start and end of the track and the 43879 * duration for each frame/sample we process in order to calculate 43880 * the baseMediaDecodeTime 43881 */ 43882 43883 var collectDtsInfo = function collectDtsInfo(track, data) { 43884 if (typeof data.pts === 'number') { 43885 if (track.timelineStartInfo.pts === undefined) { 43886 track.timelineStartInfo.pts = data.pts; 43887 } 43888 43889 if (track.minSegmentPts === undefined) { 43890 track.minSegmentPts = data.pts; 43891 } else { 43892 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 43893 } 43894 43895 if (track.maxSegmentPts === undefined) { 43896 track.maxSegmentPts = data.pts; 43897 } else { 43898 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 43899 } 43900 } 43901 43902 if (typeof data.dts === 'number') { 43903 if (track.timelineStartInfo.dts === undefined) { 43904 track.timelineStartInfo.dts = data.dts; 43905 } 43906 43907 if (track.minSegmentDts === undefined) { 43908 track.minSegmentDts = data.dts; 43909 } else { 43910 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 43911 } 43912 43913 if (track.maxSegmentDts === undefined) { 43914 track.maxSegmentDts = data.dts; 43915 } else { 43916 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 43917 } 43918 } 43919 }; 43920 /** 43921 * Clear values used to calculate the baseMediaDecodeTime between 43922 * tracks 43923 */ 43924 43925 43926 var clearDtsInfo = function clearDtsInfo(track) { 43927 delete track.minSegmentDts; 43928 delete track.maxSegmentDts; 43929 delete track.minSegmentPts; 43930 delete track.maxSegmentPts; 43931 }; 43932 /** 43933 * Calculate the track's baseMediaDecodeTime based on the earliest 43934 * DTS the transmuxer has ever seen and the minimum DTS for the 43935 * current track 43936 * @param track {object} track metadata configuration 43937 * @param keepOriginalTimestamps {boolean} If true, keep the timestamps 43938 * in the source; false to adjust the first segment to start at 0. 43939 */ 43940 43941 43942 var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) { 43943 var baseMediaDecodeTime, 43944 scale, 43945 minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero. 43946 43947 if (!keepOriginalTimestamps) { 43948 minSegmentDts -= track.timelineStartInfo.dts; 43949 } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 43950 // we want the start of the first segment to be placed 43951 43952 43953 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first 43954 43955 baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative 43956 43957 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 43958 43959 if (track.type === 'audio') { 43960 // Audio has a different clock equal to the sampling_rate so we need to 43961 // scale the PTS values into the clock rate of the track 43962 scale = track.samplerate / ONE_SECOND_IN_TS$1; 43963 baseMediaDecodeTime *= scale; 43964 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 43965 } 43966 43967 return baseMediaDecodeTime; 43968 }; 43969 43970 var trackDecodeInfo = { 43971 clearDtsInfo: clearDtsInfo, 43972 calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime, 43973 collectDtsInfo: collectDtsInfo 43974 }; 43975 /** 43976 * mux.js 43977 * 43978 * Copyright (c) Brightcove 43979 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43980 * 43981 * Reads in-band caption information from a video elementary 43982 * stream. Captions must follow the CEA-708 standard for injection 43983 * into an MPEG-2 transport streams. 43984 * @see https://en.wikipedia.org/wiki/CEA-708 43985 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 43986 */ 43987 // Supplemental enhancement information (SEI) NAL units have a 43988 // payload type field to indicate how they are to be 43989 // interpreted. CEAS-708 caption content is always transmitted with 43990 // payload type 0x04. 43991 43992 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 43993 RBSP_TRAILING_BITS = 128; 43994 /** 43995 * Parse a supplemental enhancement information (SEI) NAL unit. 43996 * Stops parsing once a message of type ITU T T35 has been found. 43997 * 43998 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 43999 * @return {object} the parsed SEI payload 44000 * @see Rec. ITU-T H.264, 7.3.2.3.1 44001 */ 44002 44003 var parseSei = function parseSei(bytes) { 44004 var i = 0, 44005 result = { 44006 payloadType: -1, 44007 payloadSize: 0 44008 }, 44009 payloadType = 0, 44010 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 44011 44012 while (i < bytes.byteLength) { 44013 // stop once we have hit the end of the sei_rbsp 44014 if (bytes[i] === RBSP_TRAILING_BITS) { 44015 break; 44016 } // Parse payload type 44017 44018 44019 while (bytes[i] === 0xFF) { 44020 payloadType += 255; 44021 i++; 44022 } 44023 44024 payloadType += bytes[i++]; // Parse payload size 44025 44026 while (bytes[i] === 0xFF) { 44027 payloadSize += 255; 44028 i++; 44029 } 44030 44031 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 44032 // there can only ever be one caption message in a frame's sei 44033 44034 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 44035 var userIdentifier = String.fromCharCode(bytes[i + 3], bytes[i + 4], bytes[i + 5], bytes[i + 6]); 44036 44037 if (userIdentifier === 'GA94') { 44038 result.payloadType = payloadType; 44039 result.payloadSize = payloadSize; 44040 result.payload = bytes.subarray(i, i + payloadSize); 44041 break; 44042 } else { 44043 result.payload = void 0; 44044 } 44045 } // skip the payload and parse the next message 44046 44047 44048 i += payloadSize; 44049 payloadType = 0; 44050 payloadSize = 0; 44051 } 44052 44053 return result; 44054 }; // see ANSI/SCTE 128-1 (2013), section 8.1 44055 44056 44057 var parseUserData = function parseUserData(sei) { 44058 // itu_t_t35_contry_code must be 181 (United States) for 44059 // captions 44060 if (sei.payload[0] !== 181) { 44061 return null; 44062 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 44063 44064 44065 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 44066 return null; 44067 } // the user_identifier should be "GA94" to indicate ATSC1 data 44068 44069 44070 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 44071 return null; 44072 } // finally, user_data_type_code should be 0x03 for caption data 44073 44074 44075 if (sei.payload[7] !== 0x03) { 44076 return null; 44077 } // return the user_data_type_structure and strip the trailing 44078 // marker bits 44079 44080 44081 return sei.payload.subarray(8, sei.payload.length - 1); 44082 }; // see CEA-708-D, section 4.4 44083 44084 44085 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 44086 var results = [], 44087 i, 44088 count, 44089 offset, 44090 data; // if this is just filler, return immediately 44091 44092 if (!(userData[0] & 0x40)) { 44093 return results; 44094 } // parse out the cc_data_1 and cc_data_2 fields 44095 44096 44097 count = userData[0] & 0x1f; 44098 44099 for (i = 0; i < count; i++) { 44100 offset = i * 3; 44101 data = { 44102 type: userData[offset + 2] & 0x03, 44103 pts: pts 44104 }; // capture cc data when cc_valid is 1 44105 44106 if (userData[offset + 2] & 0x04) { 44107 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 44108 results.push(data); 44109 } 44110 } 44111 44112 return results; 44113 }; 44114 44115 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 44116 var length = data.byteLength, 44117 emulationPreventionBytesPositions = [], 44118 i = 1, 44119 newLength,
vendor: 4,053 bytes, lines 44120-44224
44120 newData; // Find all `Emulation Prevention Bytes` 44121 44122 while (i < length - 2) { 44123 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 44124 emulationPreventionBytesPositions.push(i + 2); 44125 i += 2; 44126 } else { 44127 i++; 44128 } 44129 } // If no Emulation Prevention Bytes were found just return the original 44130 // array 44131 44132 44133 if (emulationPreventionBytesPositions.length === 0) { 44134 return data; 44135 } // Create a new array to hold the NAL unit data 44136 44137 44138 newLength = length - emulationPreventionBytesPositions.length; 44139 newData = new Uint8Array(newLength); 44140 var sourceIndex = 0; 44141 44142 for (i = 0; i < newLength; sourceIndex++, i++) { 44143 if (sourceIndex === emulationPreventionBytesPositions[0]) { 44144 // Skip this byte 44145 sourceIndex++; // Remove this position index 44146 44147 emulationPreventionBytesPositions.shift(); 44148 } 44149 44150 newData[i] = data[sourceIndex]; 44151 } 44152 44153 return newData; 44154 }; // exports 44155 44156 44157 var captionPacketParser = { 44158 parseSei: parseSei, 44159 parseUserData: parseUserData, 44160 parseCaptionPackets: parseCaptionPackets, 44161 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 44162 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 44163 }; // ----------------- 44164 // Link To Transport 44165 // ----------------- 44166 44167 var CaptionStream = function CaptionStream() { 44168 CaptionStream.prototype.init.call(this); 44169 this.captionPackets_ = []; 44170 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 44171 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 44172 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 44173 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 44174 ]; 44175 this.reset(); // forward data and done events from CCs to this CaptionStream 44176 44177 this.ccStreams_.forEach(function (cc) { 44178 cc.on('data', this.trigger.bind(this, 'data')); 44179 cc.on('partialdone', this.trigger.bind(this, 'partialdone')); 44180 cc.on('done', this.trigger.bind(this, 'done')); 44181 }, this); 44182 }; 44183 44184 CaptionStream.prototype = new stream(); 44185 44186 CaptionStream.prototype.push = function (event) { 44187 var sei, userData, newCaptionPackets; // only examine SEI NALs 44188 44189 if (event.nalUnitType !== 'sei_rbsp') { 44190 return; 44191 } // parse the sei 44192 44193 44194 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 44195 44196 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 44197 return; 44198 } // parse out the user data payload 44199 44200 44201 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 44202 44203 if (!userData) { 44204 return; 44205 } // Sometimes, the same segment # will be downloaded twice. To stop the 44206 // caption data from being processed twice, we track the latest dts we've 44207 // received and ignore everything with a dts before that. However, since 44208 // data for a specific dts can be split across packets on either side of 44209 // a segment boundary, we need to make sure we *don't* ignore the packets 44210 // from the *next* segment that have dts === this.latestDts_. By constantly 44211 // tracking the number of packets received with dts === this.latestDts_, we 44212 // know how many should be ignored once we start receiving duplicates. 44213 44214 44215 if (event.dts < this.latestDts_) { 44216 // We've started getting older data, so set the flag. 44217 this.ignoreNextEqualDts_ = true; 44218 return; 44219 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 44220 this.numSameDts_--; 44221 44222 if (!this.numSameDts_) { 44223 // We've received the last duplicate packet, time to start processing again 44224 this.ignoreNextEqualDts_ = false;
vendor: 7,286 bytes, lines 44225-44471
44225 } 44226 44227 return; 44228 } // parse out CC data packets and save them for later 44229 44230 44231 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 44232 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 44233 44234 if (this.latestDts_ !== event.dts) { 44235 this.numSameDts_ = 0; 44236 } 44237 44238 this.numSameDts_++; 44239 this.latestDts_ = event.dts; 44240 }; 44241 44242 CaptionStream.prototype.flushCCStreams = function (flushType) { 44243 this.ccStreams_.forEach(function (cc) { 44244 return flushType === 'flush' ? cc.flush() : cc.partialFlush(); 44245 }, this); 44246 }; 44247 44248 CaptionStream.prototype.flushStream = function (flushType) { 44249 // make sure we actually parsed captions before proceeding 44250 if (!this.captionPackets_.length) { 44251 this.flushCCStreams(flushType); 44252 return; 44253 } // In Chrome, the Array#sort function is not stable so add a 44254 // presortIndex that we can use to ensure we get a stable-sort 44255 44256 44257 this.captionPackets_.forEach(function (elem, idx) { 44258 elem.presortIndex = idx; 44259 }); // sort caption byte-pairs based on their PTS values 44260 44261 this.captionPackets_.sort(function (a, b) { 44262 if (a.pts === b.pts) { 44263 return a.presortIndex - b.presortIndex; 44264 } 44265 44266 return a.pts - b.pts; 44267 }); 44268 this.captionPackets_.forEach(function (packet) { 44269 if (packet.type < 2) { 44270 // Dispatch packet to the right Cea608Stream 44271 this.dispatchCea608Packet(packet); 44272 } // this is where an 'else' would go for a dispatching packets 44273 // to a theoretical Cea708Stream that handles SERVICEn data 44274 44275 }, this); 44276 this.captionPackets_.length = 0; 44277 this.flushCCStreams(flushType); 44278 }; 44279 44280 CaptionStream.prototype.flush = function () { 44281 return this.flushStream('flush'); 44282 }; // Only called if handling partial data 44283 44284 44285 CaptionStream.prototype.partialFlush = function () { 44286 return this.flushStream('partialFlush'); 44287 }; 44288 44289 CaptionStream.prototype.reset = function () { 44290 this.latestDts_ = null; 44291 this.ignoreNextEqualDts_ = false; 44292 this.numSameDts_ = 0; 44293 this.activeCea608Channel_ = [null, null]; 44294 this.ccStreams_.forEach(function (ccStream) { 44295 ccStream.reset(); 44296 }); 44297 }; // From the CEA-608 spec: 44298 44299 /* 44300 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed 44301 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is 44302 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair 44303 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the 44304 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) 44305 * to switch to captioning or Text. 44306 */ 44307 // With that in mind, we ignore any data between an XDS control code and a 44308 // subsequent closed-captioning control code. 44309 44310 44311 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 44312 // NOTE: packet.type is the CEA608 field 44313 if (this.setsTextOrXDSActive(packet)) { 44314 this.activeCea608Channel_[packet.type] = null; 44315 } else if (this.setsChannel1Active(packet)) { 44316 this.activeCea608Channel_[packet.type] = 0; 44317 } else if (this.setsChannel2Active(packet)) { 44318 this.activeCea608Channel_[packet.type] = 1; 44319 } 44320 44321 if (this.activeCea608Channel_[packet.type] === null) { 44322 // If we haven't received anything to set the active channel, or the 44323 // packets are Text/XDS data, discard the data; we don't want jumbled 44324 // captions 44325 return; 44326 } 44327 44328 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 44329 }; 44330 44331 CaptionStream.prototype.setsChannel1Active = function (packet) { 44332 return (packet.ccData & 0x7800) === 0x1000; 44333 }; 44334 44335 CaptionStream.prototype.setsChannel2Active = function (packet) { 44336 return (packet.ccData & 0x7800) === 0x1800; 44337 }; 44338 44339 CaptionStream.prototype.setsTextOrXDSActive = function (packet) { 44340 return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a; 44341 }; // ---------------------- 44342 // Session to Application 44343 // ---------------------- 44344 // This hash maps non-ASCII, special, and extended character codes to their 44345 // proper Unicode equivalent. The first keys that are only a single byte 44346 // are the non-standard ASCII characters, which simply map the CEA608 byte 44347 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 44348 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 44349 // can be performed regardless of the field and data channel on which the 44350 // character code was received. 44351 44352 44353 var CHARACTER_TRANSLATION = { 44354 0x2a: 0xe1, 44355 // á 44356 0x5c: 0xe9, 44357 // é 44358 0x5e: 0xed, 44359 // à 44360 0x5f: 0xf3, 44361 // ó 44362 0x60: 0xfa, 44363 // ú 44364 0x7b: 0xe7, 44365 // ç 44366 0x7c: 0xf7, 44367 // ÷ 44368 0x7d: 0xd1, 44369 // à 44370 0x7e: 0xf1, 44371 // ñ 44372 0x7f: 0x2588, 44373 // â 44374 0x0130: 0xae, 44375 // ® 44376 0x0131: 0xb0, 44377 // ° 44378 0x0132: 0xbd, 44379 // ½ 44380 0x0133: 0xbf, 44381 // ¿ 44382 0x0134: 0x2122, 44383 // ⢠44384 0x0135: 0xa2, 44385 // ¢ 44386 0x0136: 0xa3, 44387 // £ 44388 0x0137: 0x266a, 44389 // ⪠44390 0x0138: 0xe0, 44391 // à 44392 0x0139: 0xa0, 44393 // 44394 0x013a: 0xe8, 44395 // è 44396 0x013b: 0xe2, 44397 // â 44398 0x013c: 0xea, 44399 // ê 44400 0x013d: 0xee, 44401 // î 44402 0x013e: 0xf4, 44403 // ô 44404 0x013f: 0xfb, 44405 // û 44406 0x0220: 0xc1, 44407 // à 44408 0x0221: 0xc9, 44409 // à 44410 0x0222: 0xd3, 44411 // à 44412 0x0223: 0xda, 44413 // à 44414 0x0224: 0xdc, 44415 // à 44416 0x0225: 0xfc, 44417 // ü 44418 0x0226: 0x2018, 44419 // â 44420 0x0227: 0xa1, 44421 // ¡ 44422 0x0228: 0x2a, 44423 // * 44424 0x0229: 0x27, 44425 // ' 44426 0x022a: 0x2014, 44427 // â 44428 0x022b: 0xa9, 44429 // © 44430 0x022c: 0x2120, 44431 // â 44432 0x022d: 0x2022, 44433 // ⢠44434 0x022e: 0x201c, 44435 // â 44436 0x022f: 0x201d, 44437 // â 44438 0x0230: 0xc0, 44439 // à 44440 0x0231: 0xc2, 44441 // à 44442 0x0232: 0xc7, 44443 // à 44444 0x0233: 0xc8, 44445 // à 44446 0x0234: 0xca, 44447 // à 44448 0x0235: 0xcb, 44449 // à 44450 0x0236: 0xeb, 44451 // ë 44452 0x0237: 0xce, 44453 // à 44454 0x0238: 0xcf, 44455 // à 44456 0x0239: 0xef, 44457 // ï 44458 0x023a: 0xd4, 44459 // à 44460 0x023b: 0xd9, 44461 // à 44462 0x023c: 0xf9, 44463 // ù 44464 0x023d: 0xdb, 44465 // à 44466 0x023e: 0xab, 44467 // « 44468 0x023f: 0xbb, 44469 // » 44470 0x0320: 0xc3, 44471 // Ã
vendor: 20,265 bytes, lines 44472-45004
44472 0x0321: 0xe3, 44473 // ã 44474 0x0322: 0xcd, 44475 // à 44476 0x0323: 0xcc, 44477 // à 44478 0x0324: 0xec, 44479 // ì 44480 0x0325: 0xd2, 44481 // à 44482 0x0326: 0xf2, 44483 // ò 44484 0x0327: 0xd5, 44485 // à 44486 0x0328: 0xf5, 44487 // õ 44488 0x0329: 0x7b, 44489 // { 44490 0x032a: 0x7d, 44491 // } 44492 0x032b: 0x5c, 44493 // \ 44494 0x032c: 0x5e, 44495 // ^ 44496 0x032d: 0x5f, 44497 // _ 44498 0x032e: 0x7c, 44499 // | 44500 0x032f: 0x7e, 44501 // ~ 44502 0x0330: 0xc4, 44503 // à 44504 0x0331: 0xe4, 44505 // ä 44506 0x0332: 0xd6, 44507 // à 44508 0x0333: 0xf6, 44509 // ö 44510 0x0334: 0xdf, 44511 // à 44512 0x0335: 0xa5, 44513 // Â¥ 44514 0x0336: 0xa4, 44515 // ¤ 44516 0x0337: 0x2502, 44517 // â 44518 0x0338: 0xc5, 44519 // à 44520 0x0339: 0xe5, 44521 // Ã¥ 44522 0x033a: 0xd8, 44523 // à 44524 0x033b: 0xf8, 44525 // ø 44526 0x033c: 0x250c, 44527 // â 44528 0x033d: 0x2510, 44529 // â 44530 0x033e: 0x2514, 44531 // â 44532 0x033f: 0x2518 // â 44533 44534 }; 44535 44536 var getCharFromCode = function getCharFromCode(code) { 44537 if (code === null) { 44538 return ''; 44539 } 44540 44541 code = CHARACTER_TRANSLATION[code] || code; 44542 return String.fromCharCode(code); 44543 }; // the index of the last row in a CEA-608 display buffer 44544 44545 44546 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 44547 // getting it through bit logic. 44548 44549 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 44550 // cells. The "bottom" row is the last element in the outer array. 44551 44552 var createDisplayBuffer = function createDisplayBuffer() { 44553 var result = [], 44554 i = BOTTOM_ROW + 1; 44555 44556 while (i--) { 44557 result.push(''); 44558 } 44559 44560 return result; 44561 }; 44562 44563 var Cea608Stream = function Cea608Stream(field, dataChannel) { 44564 Cea608Stream.prototype.init.call(this); 44565 this.field_ = field || 0; 44566 this.dataChannel_ = dataChannel || 0; 44567 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 44568 this.setConstants(); 44569 this.reset(); 44570 44571 this.push = function (packet) { 44572 var data, swap, char0, char1, text; // remove the parity bits 44573 44574 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 44575 44576 if (data === this.lastControlCode_) { 44577 this.lastControlCode_ = null; 44578 return; 44579 } // Store control codes 44580 44581 44582 if ((data & 0xf000) === 0x1000) { 44583 this.lastControlCode_ = data; 44584 } else if (data !== this.PADDING_) { 44585 this.lastControlCode_ = null; 44586 } 44587 44588 char0 = data >>> 8; 44589 char1 = data & 0xff; 44590 44591 if (data === this.PADDING_) { 44592 return; 44593 } else if (data === this.RESUME_CAPTION_LOADING_) { 44594 this.mode_ = 'popOn'; 44595 } else if (data === this.END_OF_CAPTION_) { 44596 // If an EOC is received while in paint-on mode, the displayed caption 44597 // text should be swapped to non-displayed memory as if it was a pop-on 44598 // caption. Because of that, we should explicitly switch back to pop-on 44599 // mode 44600 this.mode_ = 'popOn'; 44601 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 44602 44603 this.flushDisplayed(packet.pts); // flip memory 44604 44605 swap = this.displayed_; 44606 this.displayed_ = this.nonDisplayed_; 44607 this.nonDisplayed_ = swap; // start measuring the time to display the caption 44608 44609 this.startPts_ = packet.pts; 44610 } else if (data === this.ROLL_UP_2_ROWS_) { 44611 this.rollUpRows_ = 2; 44612 this.setRollUp(packet.pts); 44613 } else if (data === this.ROLL_UP_3_ROWS_) { 44614 this.rollUpRows_ = 3; 44615 this.setRollUp(packet.pts); 44616 } else if (data === this.ROLL_UP_4_ROWS_) { 44617 this.rollUpRows_ = 4; 44618 this.setRollUp(packet.pts); 44619 } else if (data === this.CARRIAGE_RETURN_) { 44620 this.clearFormatting(packet.pts); 44621 this.flushDisplayed(packet.pts); 44622 this.shiftRowsUp_(); 44623 this.startPts_ = packet.pts; 44624 } else if (data === this.BACKSPACE_) { 44625 if (this.mode_ === 'popOn') { 44626 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 44627 } else { 44628 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 44629 } 44630 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 44631 this.flushDisplayed(packet.pts); 44632 this.displayed_ = createDisplayBuffer(); 44633 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 44634 this.nonDisplayed_ = createDisplayBuffer(); 44635 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 44636 if (this.mode_ !== 'paintOn') { 44637 // NOTE: This should be removed when proper caption positioning is 44638 // implemented 44639 this.flushDisplayed(packet.pts); 44640 this.displayed_ = createDisplayBuffer(); 44641 } 44642 44643 this.mode_ = 'paintOn'; 44644 this.startPts_ = packet.pts; // Append special characters to caption text 44645 } else if (this.isSpecialCharacter(char0, char1)) { 44646 // Bitmask char0 so that we can apply character transformations 44647 // regardless of field and data channel. 44648 // Then byte-shift to the left and OR with char1 so we can pass the 44649 // entire character code to `getCharFromCode`. 44650 char0 = (char0 & 0x03) << 8; 44651 text = getCharFromCode(char0 | char1); 44652 this[this.mode_](packet.pts, text); 44653 this.column_++; // Append extended characters to caption text 44654 } else if (this.isExtCharacter(char0, char1)) { 44655 // Extended characters always follow their "non-extended" equivalents. 44656 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 44657 // decoders are supposed to drop the "è", while compliant decoders 44658 // backspace the "e" and insert "è". 44659 // Delete the previous character 44660 if (this.mode_ === 'popOn') { 44661 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 44662 } else { 44663 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 44664 } // Bitmask char0 so that we can apply character transformations 44665 // regardless of field and data channel. 44666 // Then byte-shift to the left and OR with char1 so we can pass the 44667 // entire character code to `getCharFromCode`. 44668 44669 44670 char0 = (char0 & 0x03) << 8; 44671 text = getCharFromCode(char0 | char1); 44672 this[this.mode_](packet.pts, text); 44673 this.column_++; // Process mid-row codes 44674 } else if (this.isMidRowCode(char0, char1)) { 44675 // Attributes are not additive, so clear all formatting 44676 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 44677 // should be replaced with spaces, so add one now 44678 44679 this[this.mode_](packet.pts, ' '); 44680 this.column_++; 44681 44682 if ((char1 & 0xe) === 0xe) { 44683 this.addFormatting(packet.pts, ['i']); 44684 } 44685 44686 if ((char1 & 0x1) === 0x1) { 44687 this.addFormatting(packet.pts, ['u']); 44688 } // Detect offset control codes and adjust cursor 44689 44690 } else if (this.isOffsetControlCode(char0, char1)) { 44691 // Cursor position is set by indent PAC (see below) in 4-column 44692 // increments, with an additional offset code of 1-3 to reach any 44693 // of the 32 columns specified by CEA-608. So all we need to do 44694 // here is increment the column cursor by the given offset. 44695 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 44696 } else if (this.isPAC(char0, char1)) { 44697 // There's no logic for PAC -> row mapping, so we have to just 44698 // find the row code in an array and use its index :( 44699 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 44700 44701 if (this.mode_ === 'rollUp') { 44702 // This implies that the base row is incorrectly set. 44703 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 44704 // of roll-up rows set. 44705 if (row - this.rollUpRows_ + 1 < 0) { 44706 row = this.rollUpRows_ - 1; 44707 } 44708 44709 this.setRollUp(packet.pts, row); 44710 } 44711 44712 if (row !== this.row_) { 44713 // formatting is only persistent for current row 44714 this.clearFormatting(packet.pts); 44715 this.row_ = row; 44716 } // All PACs can apply underline, so detect and apply 44717 // (All odd-numbered second bytes set underline) 44718 44719 44720 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 44721 this.addFormatting(packet.pts, ['u']); 44722 } 44723 44724 if ((data & 0x10) === 0x10) { 44725 // We've got an indent level code. Each successive even number 44726 // increments the column cursor by 4, so we can get the desired 44727 // column position by bit-shifting to the right (to get n/2) 44728 // and multiplying by 4. 44729 this.column_ = ((data & 0xe) >> 1) * 4; 44730 } 44731 44732 if (this.isColorPAC(char1)) { 44733 // it's a color code, though we only support white, which 44734 // can be either normal or italicized. white italics can be 44735 // either 0x4e or 0x6e depending on the row, so we just 44736 // bitwise-and with 0xe to see if italics should be turned on 44737 if ((char1 & 0xe) === 0xe) { 44738 this.addFormatting(packet.pts, ['i']); 44739 } 44740 } // We have a normal character in char0, and possibly one in char1 44741 44742 } else if (this.isNormalChar(char0)) { 44743 if (char1 === 0x00) { 44744 char1 = null; 44745 } 44746 44747 text = getCharFromCode(char0); 44748 text += getCharFromCode(char1); 44749 this[this.mode_](packet.pts, text); 44750 this.column_ += text.length; 44751 } // finish data processing 44752 44753 }; 44754 }; 44755 44756 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 44757 // display buffer 44758 44759 Cea608Stream.prototype.flushDisplayed = function (pts) { 44760 var content = this.displayed_ // remove spaces from the start and end of the string 44761 .map(function (row) { 44762 try { 44763 return row.trim(); 44764 } catch (e) { 44765 // Ordinarily, this shouldn't happen. However, caption 44766 // parsing errors should not throw exceptions and 44767 // break playback. 44768 // eslint-disable-next-line no-console 44769 console.error('Skipping malformed caption.'); 44770 return ''; 44771 } 44772 }) // combine all text rows to display in one cue 44773 .join('\n') // and remove blank rows from the start and end, but not the middle 44774 .replace(/^\n+|\n+$/g, ''); 44775 44776 if (content.length) { 44777 this.trigger('data', { 44778 startPts: this.startPts_, 44779 endPts: pts, 44780 text: content, 44781 stream: this.name_ 44782 }); 44783 } 44784 }; 44785 /** 44786 * Zero out the data, used for startup and on seek 44787 */ 44788 44789 44790 Cea608Stream.prototype.reset = function () { 44791 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 44792 // actually display captions. If a caption is shifted to a row 44793 // with a lower index than this, it is cleared from the display 44794 // buffer 44795 44796 this.topRow_ = 0; 44797 this.startPts_ = 0; 44798 this.displayed_ = createDisplayBuffer(); 44799 this.nonDisplayed_ = createDisplayBuffer(); 44800 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 44801 44802 this.column_ = 0; 44803 this.row_ = BOTTOM_ROW; 44804 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 44805 44806 this.formatting_ = []; 44807 }; 44808 /** 44809 * Sets up control code and related constants for this instance 44810 */ 44811 44812 44813 Cea608Stream.prototype.setConstants = function () { 44814 // The following attributes have these uses: 44815 // ext_ : char0 for mid-row codes, and the base for extended 44816 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 44817 // extended codes) 44818 // control_: char0 for control codes, except byte-shifted to the 44819 // left so that we can do this.control_ | CONTROL_CODE 44820 // offset_: char0 for tab offset codes 44821 // 44822 // It's also worth noting that control codes, and _only_ control codes, 44823 // differ between field 1 and field2. Field 2 control codes are always 44824 // their field 1 value plus 1. That's why there's the "| field" on the 44825 // control value. 44826 if (this.dataChannel_ === 0) { 44827 this.BASE_ = 0x10; 44828 this.EXT_ = 0x11; 44829 this.CONTROL_ = (0x14 | this.field_) << 8; 44830 this.OFFSET_ = 0x17; 44831 } else if (this.dataChannel_ === 1) { 44832 this.BASE_ = 0x18; 44833 this.EXT_ = 0x19; 44834 this.CONTROL_ = (0x1c | this.field_) << 8; 44835 this.OFFSET_ = 0x1f; 44836 } // Constants for the LSByte command codes recognized by Cea608Stream. This 44837 // list is not exhaustive. For a more comprehensive listing and semantics see 44838 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 44839 // Padding 44840 44841 44842 this.PADDING_ = 0x0000; // Pop-on Mode 44843 44844 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 44845 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 44846 44847 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 44848 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 44849 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 44850 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 44851 44852 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 44853 44854 this.BACKSPACE_ = this.CONTROL_ | 0x21; 44855 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 44856 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 44857 }; 44858 /** 44859 * Detects if the 2-byte packet data is a special character 44860 * 44861 * Special characters have a second byte in the range 0x30 to 0x3f, 44862 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 44863 * data channel 2). 44864 * 44865 * @param {Integer} char0 The first byte 44866 * @param {Integer} char1 The second byte 44867 * @return {Boolean} Whether the 2 bytes are an special character 44868 */ 44869 44870 44871 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 44872 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 44873 }; 44874 /** 44875 * Detects if the 2-byte packet data is an extended character 44876 * 44877 * Extended characters have a second byte in the range 0x20 to 0x3f, 44878 * with the first byte being 0x12 or 0x13 (for data channel 1) or 44879 * 0x1a or 0x1b (for data channel 2). 44880 * 44881 * @param {Integer} char0 The first byte 44882 * @param {Integer} char1 The second byte 44883 * @return {Boolean} Whether the 2 bytes are an extended character 44884 */ 44885 44886 44887 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 44888 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 44889 }; 44890 /** 44891 * Detects if the 2-byte packet is a mid-row code 44892 * 44893 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 44894 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 44895 * channel 2). 44896 * 44897 * @param {Integer} char0 The first byte 44898 * @param {Integer} char1 The second byte 44899 * @return {Boolean} Whether the 2 bytes are a mid-row code 44900 */ 44901 44902 44903 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 44904 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 44905 }; 44906 /** 44907 * Detects if the 2-byte packet is an offset control code 44908 * 44909 * Offset control codes have a second byte in the range 0x21 to 0x23, 44910 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 44911 * data channel 2). 44912 * 44913 * @param {Integer} char0 The first byte 44914 * @param {Integer} char1 The second byte 44915 * @return {Boolean} Whether the 2 bytes are an offset control code 44916 */ 44917 44918 44919 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 44920 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 44921 }; 44922 /** 44923 * Detects if the 2-byte packet is a Preamble Address Code 44924 * 44925 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 44926 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 44927 * range 0x40 to 0x7f. 44928 * 44929 * @param {Integer} char0 The first byte 44930 * @param {Integer} char1 The second byte 44931 * @return {Boolean} Whether the 2 bytes are a PAC 44932 */ 44933 44934 44935 Cea608Stream.prototype.isPAC = function (char0, char1) { 44936 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 44937 }; 44938 /** 44939 * Detects if a packet's second byte is in the range of a PAC color code 44940 * 44941 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 44942 * 0x60 to 0x6f. 44943 * 44944 * @param {Integer} char1 The second byte 44945 * @return {Boolean} Whether the byte is a color PAC 44946 */ 44947 44948 44949 Cea608Stream.prototype.isColorPAC = function (char1) { 44950 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 44951 }; 44952 /** 44953 * Detects if a single byte is in the range of a normal character 44954 * 44955 * Normal text bytes are in the range 0x20 to 0x7f. 44956 * 44957 * @param {Integer} char The byte 44958 * @return {Boolean} Whether the byte is a normal character 44959 */ 44960 44961 44962 Cea608Stream.prototype.isNormalChar = function (_char) { 44963 return _char >= 0x20 && _char <= 0x7f; 44964 }; 44965 /** 44966 * Configures roll-up 44967 * 44968 * @param {Integer} pts Current PTS 44969 * @param {Integer} newBaseRow Used by PACs to slide the current window to 44970 * a new position 44971 */ 44972 44973 44974 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 44975 // Reset the base row to the bottom row when switching modes 44976 if (this.mode_ !== 'rollUp') { 44977 this.row_ = BOTTOM_ROW; 44978 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 44979 44980 this.flushDisplayed(pts); 44981 this.nonDisplayed_ = createDisplayBuffer(); 44982 this.displayed_ = createDisplayBuffer(); 44983 } 44984 44985 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 44986 // move currently displayed captions (up or down) to the new base row 44987 for (var i = 0; i < this.rollUpRows_; i++) { 44988 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 44989 this.displayed_[this.row_ - i] = ''; 44990 } 44991 } 44992 44993 if (newBaseRow === undefined) { 44994 newBaseRow = this.row_; 44995 } 44996 44997 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 44998 }; // Adds the opening HTML tag for the passed character to the caption text, 44999 // and keeps track of it for later closing 45000 45001 45002 Cea608Stream.prototype.addFormatting = function (pts, format) { 45003 this.formatting_ = this.formatting_.concat(format); 45004 var text = format.reduce(function (text, format) {
vendor: 12,842 bytes, lines 45005-45367
45005 return text + '<' + format + '>'; 45006 }, ''); 45007 this[this.mode_](pts, text); 45008 }; // Adds HTML closing tags for current formatting to caption text and 45009 // clears remembered formatting 45010 45011 45012 Cea608Stream.prototype.clearFormatting = function (pts) { 45013 if (!this.formatting_.length) { 45014 return; 45015 } 45016 45017 var text = this.formatting_.reverse().reduce(function (text, format) { 45018 return text + '</' + format + '>'; 45019 }, ''); 45020 this.formatting_ = []; 45021 this[this.mode_](pts, text); 45022 }; // Mode Implementations 45023 45024 45025 Cea608Stream.prototype.popOn = function (pts, text) { 45026 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 45027 45028 baseRow += text; 45029 this.nonDisplayed_[this.row_] = baseRow; 45030 }; 45031 45032 Cea608Stream.prototype.rollUp = function (pts, text) { 45033 var baseRow = this.displayed_[this.row_]; 45034 baseRow += text; 45035 this.displayed_[this.row_] = baseRow; 45036 }; 45037 45038 Cea608Stream.prototype.shiftRowsUp_ = function () { 45039 var i; // clear out inactive rows 45040 45041 for (i = 0; i < this.topRow_; i++) { 45042 this.displayed_[i] = ''; 45043 } 45044 45045 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 45046 this.displayed_[i] = ''; 45047 } // shift displayed rows up 45048 45049 45050 for (i = this.topRow_; i < this.row_; i++) { 45051 this.displayed_[i] = this.displayed_[i + 1]; 45052 } // clear out the bottom row 45053 45054 45055 this.displayed_[this.row_] = ''; 45056 }; 45057 45058 Cea608Stream.prototype.paintOn = function (pts, text) { 45059 var baseRow = this.displayed_[this.row_]; 45060 baseRow += text; 45061 this.displayed_[this.row_] = baseRow; 45062 }; // exports 45063 45064 45065 var captionStream = { 45066 CaptionStream: CaptionStream, 45067 Cea608Stream: Cea608Stream 45068 }; 45069 /** 45070 * mux.js 45071 * 45072 * Copyright (c) Brightcove 45073 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 45074 */ 45075 45076 var streamTypes = { 45077 H264_STREAM_TYPE: 0x1B, 45078 ADTS_STREAM_TYPE: 0x0F, 45079 METADATA_STREAM_TYPE: 0x15 45080 }; 45081 var MAX_TS = 8589934592; 45082 var RO_THRESH = 4294967296; 45083 var TYPE_SHARED = 'shared'; 45084 45085 var handleRollover = function handleRollover(value, reference) { 45086 var direction = 1; 45087 45088 if (value > reference) { 45089 // If the current timestamp value is greater than our reference timestamp and we detect a 45090 // timestamp rollover, this means the roll over is happening in the opposite direction. 45091 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 45092 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 45093 // rollover point. In loading this segment, the timestamp values will be very large, 45094 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 45095 // the time stamp to be `value - 2^33`. 45096 direction = -1; 45097 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 45098 // cause an incorrect adjustment. 45099 45100 45101 while (Math.abs(reference - value) > RO_THRESH) { 45102 value += direction * MAX_TS; 45103 } 45104 45105 return value; 45106 }; 45107 45108 var TimestampRolloverStream = function TimestampRolloverStream(type) { 45109 var lastDTS, referenceDTS; 45110 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 45111 // video and audio. We could use `undefined` here, but having a string 45112 // makes debugging a little clearer. 45113 45114 this.type_ = type || TYPE_SHARED; 45115 45116 this.push = function (data) { 45117 // Any "shared" rollover streams will accept _all_ data. Otherwise, 45118 // streams will only accept data that matches their type. 45119 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 45120 return; 45121 } 45122 45123 if (referenceDTS === undefined) { 45124 referenceDTS = data.dts; 45125 } 45126 45127 data.dts = handleRollover(data.dts, referenceDTS); 45128 data.pts = handleRollover(data.pts, referenceDTS); 45129 lastDTS = data.dts; 45130 this.trigger('data', data); 45131 }; 45132 45133 this.flush = function () { 45134 referenceDTS = lastDTS; 45135 this.trigger('done'); 45136 }; 45137 45138 this.endTimeline = function () { 45139 this.flush(); 45140 this.trigger('endedtimeline'); 45141 }; 45142 45143 this.discontinuity = function () { 45144 referenceDTS = void 0; 45145 lastDTS = void 0; 45146 }; 45147 45148 this.reset = function () { 45149 this.discontinuity(); 45150 this.trigger('reset'); 45151 }; 45152 }; 45153 45154 TimestampRolloverStream.prototype = new stream(); 45155 var timestampRolloverStream = { 45156 TimestampRolloverStream: TimestampRolloverStream, 45157 handleRollover: handleRollover 45158 }; 45159 45160 var percentEncode = function percentEncode(bytes, start, end) { 45161 var i, 45162 result = ''; 45163 45164 for (i = start; i < end; i++) { 45165 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 45166 } 45167 45168 return result; 45169 }, 45170 // return the string representation of the specified byte range, 45171 // interpreted as UTf-8. 45172 parseUtf8 = function parseUtf8(bytes, start, end) { 45173 return decodeURIComponent(percentEncode(bytes, start, end)); 45174 }, 45175 // return the string representation of the specified byte range, 45176 // interpreted as ISO-8859-1. 45177 parseIso88591 = function parseIso88591(bytes, start, end) { 45178 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 45179 }, 45180 parseSyncSafeInteger = function parseSyncSafeInteger(data) { 45181 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 45182 }, 45183 tagParsers = { 45184 TXXX: function TXXX(tag) { 45185 var i; 45186 45187 if (tag.data[0] !== 3) { 45188 // ignore frames with unrecognized character encodings 45189 return; 45190 } 45191 45192 for (i = 1; i < tag.data.length; i++) { 45193 if (tag.data[i] === 0) { 45194 // parse the text fields 45195 tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value 45196 45197 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 45198 break; 45199 } 45200 } 45201 45202 tag.data = tag.value; 45203 }, 45204 WXXX: function WXXX(tag) { 45205 var i; 45206 45207 if (tag.data[0] !== 3) { 45208 // ignore frames with unrecognized character encodings 45209 return; 45210 } 45211 45212 for (i = 1; i < tag.data.length; i++) { 45213 if (tag.data[i] === 0) { 45214 // parse the description and URL fields 45215 tag.description = parseUtf8(tag.data, 1, i); 45216 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 45217 break; 45218 } 45219 } 45220 }, 45221 PRIV: function PRIV(tag) { 45222 var i; 45223 45224 for (i = 0; i < tag.data.length; i++) { 45225 if (tag.data[i] === 0) { 45226 // parse the description and URL fields 45227 tag.owner = parseIso88591(tag.data, 0, i); 45228 break; 45229 } 45230 } 45231 45232 tag.privateData = tag.data.subarray(i + 1); 45233 tag.data = tag.privateData; 45234 } 45235 }, 45236 _MetadataStream; 45237 45238 _MetadataStream = function MetadataStream(options) { 45239 var settings = { 45240 debug: !!(options && options.debug), 45241 // the bytes of the program-level descriptor field in MP2T 45242 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 45243 // program element descriptors" 45244 descriptor: options && options.descriptor 45245 }, 45246 // the total size in bytes of the ID3 tag being parsed 45247 tagSize = 0, 45248 // tag data that is not complete enough to be parsed 45249 buffer = [], 45250 // the total number of bytes currently in the buffer 45251 bufferSize = 0, 45252 i; 45253 45254 _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type 45255 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 45256 45257 45258 this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16); 45259 45260 if (settings.descriptor) { 45261 for (i = 0; i < settings.descriptor.length; i++) { 45262 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 45263 } 45264 } 45265 45266 this.push = function (chunk) { 45267 var tag, frameStart, frameSize, frame, i, frameHeader; 45268 45269 if (chunk.type !== 'timed-metadata') { 45270 return; 45271 } // if data_alignment_indicator is set in the PES header, 45272 // we must have the start of a new ID3 tag. Assume anything 45273 // remaining in the buffer was malformed and throw it out 45274 45275 45276 if (chunk.dataAlignmentIndicator) { 45277 bufferSize = 0; 45278 buffer.length = 0; 45279 } // ignore events that don't look like ID3 data 45280 45281 45282 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))) { 45283 if (settings.debug) { 45284 // eslint-disable-next-line no-console 45285 console.log('Skipping unrecognized metadata packet'); 45286 } 45287 45288 return; 45289 } // add this chunk to the data we've collected so far 45290 45291 45292 buffer.push(chunk); 45293 bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header 45294 45295 if (buffer.length === 1) { 45296 // the frame size is transmitted as a 28-bit integer in the 45297 // last four bytes of the ID3 header. 45298 // The most significant bit of each byte is dropped and the 45299 // results concatenated to recover the actual value. 45300 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more 45301 // convenient for our comparisons to include it 45302 45303 tagSize += 10; 45304 } // if the entire frame has not arrived, wait for more data 45305 45306 45307 if (bufferSize < tagSize) { 45308 return; 45309 } // collect the entire frame so it can be parsed 45310 45311 45312 tag = { 45313 data: new Uint8Array(tagSize), 45314 frames: [], 45315 pts: buffer[0].pts, 45316 dts: buffer[0].dts 45317 }; 45318 45319 for (i = 0; i < tagSize;) { 45320 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 45321 i += buffer[0].data.byteLength; 45322 bufferSize -= buffer[0].data.byteLength; 45323 buffer.shift(); 45324 } // find the start of the first frame and the end of the tag 45325 45326 45327 frameStart = 10; 45328 45329 if (tag.data[5] & 0x40) { 45330 // advance the frame start past the extended header 45331 frameStart += 4; // header size field 45332 45333 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end 45334 45335 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 45336 } // parse one or more ID3 frames 45337 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 45338 45339 45340 do { 45341 // determine the number of bytes in this frame 45342 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 45343 45344 if (frameSize < 1) { 45345 // eslint-disable-next-line no-console 45346 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 45347 } 45348 45349 frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]); 45350 frame = { 45351 id: frameHeader, 45352 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 45353 }; 45354 frame.key = frame.id; 45355 45356 if (tagParsers[frame.id]) { 45357 tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start 45358 // time for raw AAC data 45359 45360 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 45361 var d = frame.data, 45362 size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 45363 size *= 4; 45364 size += d[7] & 0x03; 45365 frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based 45366 // on the value of this frame 45367 // we couldn't have known the appropriate pts and dts before
vendor: 10,055 bytes, lines 45368-45624
45368 // parsing this ID3 tag so set those values now 45369 45370 if (tag.pts === undefined && tag.dts === undefined) { 45371 tag.pts = frame.timeStamp; 45372 tag.dts = frame.timeStamp; 45373 } 45374 45375 this.trigger('timestamp', frame); 45376 } 45377 } 45378 45379 tag.frames.push(frame); 45380 frameStart += 10; // advance past the frame header 45381 45382 frameStart += frameSize; // advance past the frame body 45383 } while (frameStart < tagSize); 45384 45385 this.trigger('data', tag); 45386 }; 45387 }; 45388 45389 _MetadataStream.prototype = new stream(); 45390 var metadataStream = _MetadataStream; 45391 var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types 45392 45393 var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants 45394 45395 45396 var MP2T_PACKET_LENGTH = 188, 45397 // bytes 45398 SYNC_BYTE = 0x47; 45399 /** 45400 * Splits an incoming stream of binary data into MPEG-2 Transport 45401 * Stream packets. 45402 */ 45403 45404 _TransportPacketStream = function TransportPacketStream() { 45405 var buffer = new Uint8Array(MP2T_PACKET_LENGTH), 45406 bytesInBuffer = 0; 45407 45408 _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream. 45409 45410 /** 45411 * Split a stream of data into M2TS packets 45412 **/ 45413 45414 45415 this.push = function (bytes) { 45416 var startIndex = 0, 45417 endIndex = MP2T_PACKET_LENGTH, 45418 everything; // If there are bytes remaining from the last segment, prepend them to the 45419 // bytes that were pushed in 45420 45421 if (bytesInBuffer) { 45422 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 45423 everything.set(buffer.subarray(0, bytesInBuffer)); 45424 everything.set(bytes, bytesInBuffer); 45425 bytesInBuffer = 0; 45426 } else { 45427 everything = bytes; 45428 } // While we have enough data for a packet 45429 45430 45431 while (endIndex < everything.byteLength) { 45432 // Look for a pair of start and end sync bytes in the data.. 45433 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 45434 // We found a packet so emit it and jump one whole packet forward in 45435 // the stream 45436 this.trigger('data', everything.subarray(startIndex, endIndex)); 45437 startIndex += MP2T_PACKET_LENGTH; 45438 endIndex += MP2T_PACKET_LENGTH; 45439 continue; 45440 } // If we get here, we have somehow become de-synchronized and we need to step 45441 // forward one byte at a time until we find a pair of sync bytes that denote 45442 // a packet 45443 45444 45445 startIndex++; 45446 endIndex++; 45447 } // If there was some data left over at the end of the segment that couldn't 45448 // possibly be a whole packet, keep it because it might be the start of a packet 45449 // that continues in the next segment 45450 45451 45452 if (startIndex < everything.byteLength) { 45453 buffer.set(everything.subarray(startIndex), 0); 45454 bytesInBuffer = everything.byteLength - startIndex; 45455 } 45456 }; 45457 /** 45458 * Passes identified M2TS packets to the TransportParseStream to be parsed 45459 **/ 45460 45461 45462 this.flush = function () { 45463 // If the buffer contains a whole packet when we are being flushed, emit it 45464 // and empty the buffer. Otherwise hold onto the data because it may be 45465 // important for decoding the next segment 45466 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 45467 this.trigger('data', buffer); 45468 bytesInBuffer = 0; 45469 } 45470 45471 this.trigger('done'); 45472 }; 45473 45474 this.endTimeline = function () { 45475 this.flush(); 45476 this.trigger('endedtimeline'); 45477 }; 45478 45479 this.reset = function () { 45480 bytesInBuffer = 0; 45481 this.trigger('reset'); 45482 }; 45483 }; 45484 45485 _TransportPacketStream.prototype = new stream(); 45486 /** 45487 * Accepts an MP2T TransportPacketStream and emits data events with parsed 45488 * forms of the individual transport stream packets. 45489 */ 45490 45491 _TransportParseStream = function TransportParseStream() { 45492 var parsePsi, parsePat, parsePmt, self; 45493 45494 _TransportParseStream.prototype.init.call(this); 45495 45496 self = this; 45497 this.packetsWaitingForPmt = []; 45498 this.programMapTable = undefined; 45499 45500 parsePsi = function parsePsi(payload, psi) { 45501 var offset = 0; // PSI packets may be split into multiple sections and those 45502 // sections may be split into multiple packets. If a PSI 45503 // section starts in this packet, the payload_unit_start_indicator 45504 // will be true and the first byte of the payload will indicate 45505 // the offset from the current position to the start of the 45506 // section. 45507 45508 if (psi.payloadUnitStartIndicator) { 45509 offset += payload[offset] + 1; 45510 } 45511 45512 if (psi.type === 'pat') { 45513 parsePat(payload.subarray(offset), psi); 45514 } else { 45515 parsePmt(payload.subarray(offset), psi); 45516 } 45517 }; 45518 45519 parsePat = function parsePat(payload, pat) { 45520 pat.section_number = payload[7]; // eslint-disable-line camelcase 45521 45522 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 45523 // skip the PSI header and parse the first PMT entry 45524 45525 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 45526 pat.pmtPid = self.pmtPid; 45527 }; 45528 /** 45529 * Parse out the relevant fields of a Program Map Table (PMT). 45530 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 45531 * packet. The first byte in this array should be the table_id 45532 * field. 45533 * @param pmt {object} the object that should be decorated with 45534 * fields parsed from the PMT. 45535 */ 45536 45537 45538 parsePmt = function parsePmt(payload, pmt) { 45539 var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually 45540 // take effect. We don't believe this should ever be the case 45541 // for HLS but we'll ignore "forward" PMT declarations if we see 45542 // them. Future PMT declarations have the current_next_indicator 45543 // set to zero. 45544 45545 if (!(payload[5] & 0x01)) { 45546 return; 45547 } // overwrite any existing program map table 45548 45549 45550 self.programMapTable = { 45551 video: null, 45552 audio: null, 45553 'timed-metadata': {} 45554 }; // the mapping table ends at the end of the current section 45555 45556 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 45557 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 45558 // long the program info descriptors are 45559 45560 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table 45561 45562 offset = 12 + programInfoLength; 45563 45564 while (offset < tableEnd) { 45565 var streamType = payload[offset]; 45566 var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types 45567 // TODO: should this be done for metadata too? for now maintain behavior of 45568 // multiple metadata streams 45569 45570 if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) { 45571 self.programMapTable.video = pid; 45572 } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) { 45573 self.programMapTable.audio = pid; 45574 } else if (streamType === streamTypes.METADATA_STREAM_TYPE) { 45575 // map pid to stream type for metadata streams 45576 self.programMapTable['timed-metadata'][pid] = streamType; 45577 } // move to the next table entry 45578 // skip past the elementary stream descriptors, if present 45579 45580 45581 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 45582 } // record the map on the packet as well 45583 45584 45585 pmt.programMapTable = self.programMapTable; 45586 }; 45587 /** 45588 * Deliver a new MP2T packet to the next stream in the pipeline. 45589 */ 45590 45591 45592 this.push = function (packet) { 45593 var result = {}, 45594 offset = 4; 45595 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1] 45596 45597 result.pid = packet[1] & 0x1f; 45598 result.pid <<= 8; 45599 result.pid |= packet[2]; // if an adaption field is present, its length is specified by the 45600 // fifth byte of the TS packet header. The adaptation field is 45601 // used to add stuffing to PES packets that don't fill a complete 45602 // TS packet, and to specify some forms of timing and control data 45603 // that we do not currently use. 45604 45605 if ((packet[3] & 0x30) >>> 4 > 0x01) { 45606 offset += packet[offset] + 1; 45607 } // parse the rest of the packet based on the type 45608 45609 45610 if (result.pid === 0) { 45611 result.type = 'pat'; 45612 parsePsi(packet.subarray(offset), result); 45613 this.trigger('data', result); 45614 } else if (result.pid === this.pmtPid) { 45615 result.type = 'pmt'; 45616 parsePsi(packet.subarray(offset), result); 45617 this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now 45618 45619 while (this.packetsWaitingForPmt.length) { 45620 this.processPes_.apply(this, this.packetsWaitingForPmt.shift()); 45621 } 45622 } else if (this.programMapTable === undefined) { 45623 // When we have not seen a PMT yet, defer further processing of 45624 // PES packets until one has been parsed
vendor: 9,362 bytes, lines 45625-45882
45625 this.packetsWaitingForPmt.push([packet, offset, result]); 45626 } else { 45627 this.processPes_(packet, offset, result); 45628 } 45629 }; 45630 45631 this.processPes_ = function (packet, offset, result) { 45632 // set the appropriate stream type 45633 if (result.pid === this.programMapTable.video) { 45634 result.streamType = streamTypes.H264_STREAM_TYPE; 45635 } else if (result.pid === this.programMapTable.audio) { 45636 result.streamType = streamTypes.ADTS_STREAM_TYPE; 45637 } else { 45638 // if not video or audio, it is timed-metadata or unknown 45639 // if unknown, streamType will be undefined 45640 result.streamType = this.programMapTable['timed-metadata'][result.pid]; 45641 } 45642 45643 result.type = 'pes'; 45644 result.data = packet.subarray(offset); 45645 this.trigger('data', result); 45646 }; 45647 }; 45648 45649 _TransportParseStream.prototype = new stream(); 45650 _TransportParseStream.STREAM_TYPES = { 45651 h264: 0x1b, 45652 adts: 0x0f 45653 }; 45654 /** 45655 * Reconsistutes program elementary stream (PES) packets from parsed 45656 * transport stream packets. That is, if you pipe an 45657 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 45658 * events will be events which capture the bytes for individual PES 45659 * packets plus relevant metadata that has been extracted from the 45660 * container. 45661 */ 45662 45663 _ElementaryStream = function ElementaryStream() { 45664 var self = this, 45665 // PES packet fragments 45666 video = { 45667 data: [], 45668 size: 0 45669 }, 45670 audio = { 45671 data: [], 45672 size: 0 45673 }, 45674 timedMetadata = { 45675 data: [], 45676 size: 0 45677 }, 45678 programMapTable, 45679 parsePes = function parsePes(payload, pes) { 45680 var ptsDtsFlags; // get the packet length, this will be 0 for video 45681 45682 pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe 45683 45684 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value 45685 // and a DTS value. Determine what combination of values is 45686 // available to work with. 45687 45688 ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 45689 // performs all bitwise operations on 32-bit integers but javascript 45690 // supports a much greater range (52-bits) of integer using standard 45691 // mathematical operations. 45692 // We construct a 31-bit value using bitwise operators over the 31 45693 // most significant bits and then multiply by 4 (equal to a left-shift 45694 // of 2) before we add the final 2 least significant bits of the 45695 // timestamp (equal to an OR.) 45696 45697 if (ptsDtsFlags & 0xC0) { 45698 // the PTS and DTS are not written out directly. For information 45699 // on how they are encoded, see 45700 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 45701 pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3; 45702 pes.pts *= 4; // Left shift by 2 45703 45704 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 45705 45706 pes.dts = pes.pts; 45707 45708 if (ptsDtsFlags & 0x40) { 45709 pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3; 45710 pes.dts *= 4; // Left shift by 2 45711 45712 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 45713 } 45714 } // the data section starts immediately after the PES header. 45715 // pes_header_data_length specifies the number of header bytes 45716 // that follow the last byte of the field. 45717 45718 45719 pes.data = payload.subarray(9 + payload[8]); 45720 }, 45721 45722 /** 45723 * Pass completely parsed PES packets to the next stream in the pipeline 45724 **/ 45725 flushStream = function flushStream(stream, type, forceFlush) { 45726 var packetData = new Uint8Array(stream.size), 45727 event = { 45728 type: type 45729 }, 45730 i = 0, 45731 offset = 0, 45732 packetFlushable = false, 45733 fragment; // do nothing if there is not enough buffered data for a complete 45734 // PES header 45735 45736 if (!stream.data.length || stream.size < 9) { 45737 return; 45738 } 45739 45740 event.trackId = stream.data[0].pid; // reassemble the packet 45741 45742 for (i = 0; i < stream.data.length; i++) { 45743 fragment = stream.data[i]; 45744 packetData.set(fragment.data, offset); 45745 offset += fragment.data.byteLength; 45746 } // parse assembled packet's PES header 45747 45748 45749 parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length 45750 // check that there is enough stream data to fill the packet 45751 45752 packetFlushable = type === 'video' || event.packetLength <= stream.size; // flush pending packets if the conditions are right 45753 45754 if (forceFlush || packetFlushable) { 45755 stream.size = 0; 45756 stream.data.length = 0; 45757 } // only emit packets that are complete. this is to avoid assembling 45758 // incomplete PES packets due to poor segmentation 45759 45760 45761 if (packetFlushable) { 45762 self.trigger('data', event); 45763 } 45764 }; 45765 45766 _ElementaryStream.prototype.init.call(this); 45767 /** 45768 * Identifies M2TS packet types and parses PES packets using metadata 45769 * parsed from the PMT 45770 **/ 45771 45772 45773 this.push = function (data) { 45774 ({ 45775 pat: function pat() {// we have to wait for the PMT to arrive as well before we 45776 // have any meaningful metadata 45777 }, 45778 pes: function pes() { 45779 var stream, streamType; 45780 45781 switch (data.streamType) { 45782 case streamTypes.H264_STREAM_TYPE: 45783 stream = video; 45784 streamType = 'video'; 45785 break; 45786 45787 case streamTypes.ADTS_STREAM_TYPE: 45788 stream = audio; 45789 streamType = 'audio'; 45790 break; 45791 45792 case streamTypes.METADATA_STREAM_TYPE: 45793 stream = timedMetadata; 45794 streamType = 'timed-metadata'; 45795 break; 45796 45797 default: 45798 // ignore unknown stream types 45799 return; 45800 } // if a new packet is starting, we can flush the completed 45801 // packet 45802 45803 45804 if (data.payloadUnitStartIndicator) { 45805 flushStream(stream, streamType, true); 45806 } // buffer this fragment until we are sure we've received the 45807 // complete payload 45808 45809 45810 stream.data.push(data); 45811 stream.size += data.data.byteLength; 45812 }, 45813 pmt: function pmt() { 45814 var event = { 45815 type: 'metadata', 45816 tracks: [] 45817 }; 45818 programMapTable = data.programMapTable; // translate audio and video streams to tracks 45819 45820 if (programMapTable.video !== null) { 45821 event.tracks.push({ 45822 timelineStartInfo: { 45823 baseMediaDecodeTime: 0 45824 }, 45825 id: +programMapTable.video, 45826 codec: 'avc', 45827 type: 'video' 45828 }); 45829 } 45830 45831 if (programMapTable.audio !== null) { 45832 event.tracks.push({ 45833 timelineStartInfo: { 45834 baseMediaDecodeTime: 0 45835 }, 45836 id: +programMapTable.audio, 45837 codec: 'adts', 45838 type: 'audio' 45839 }); 45840 } 45841 45842 self.trigger('data', event); 45843 } 45844 })[data.type](); 45845 }; 45846 45847 this.reset = function () { 45848 video.size = 0; 45849 video.data.length = 0; 45850 audio.size = 0; 45851 audio.data.length = 0; 45852 this.trigger('reset'); 45853 }; 45854 /** 45855 * Flush any remaining input. Video PES packets may be of variable 45856 * length. Normally, the start of a new video packet can trigger the 45857 * finalization of the previous packet. That is not possible if no 45858 * more video is forthcoming, however. In that case, some other 45859 * mechanism (like the end of the file) has to be employed. When it is 45860 * clear that no additional data is forthcoming, calling this method 45861 * will flush the buffered packets. 45862 */ 45863 45864 45865 this.flushStreams_ = function () { 45866 // !!THIS ORDER IS IMPORTANT!! 45867 // video first then audio 45868 flushStream(video, 'video'); 45869 flushStream(audio, 'audio'); 45870 flushStream(timedMetadata, 'timed-metadata'); 45871 }; 45872 45873 this.flush = function () { 45874 this.flushStreams_(); 45875 this.trigger('done'); 45876 }; 45877 }; 45878 45879 _ElementaryStream.prototype = new stream(); 45880 var m2ts = { 45881 PAT_PID: 0x0000, 45882 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
vendor: 5,940 bytes, lines 45883-46050
45883 TransportPacketStream: _TransportPacketStream, 45884 TransportParseStream: _TransportParseStream, 45885 ElementaryStream: _ElementaryStream, 45886 TimestampRolloverStream: TimestampRolloverStream$1, 45887 CaptionStream: captionStream.CaptionStream, 45888 Cea608Stream: captionStream.Cea608Stream, 45889 MetadataStream: metadataStream 45890 }; 45891 45892 for (var type in streamTypes) { 45893 if (streamTypes.hasOwnProperty(type)) { 45894 m2ts[type] = streamTypes[type]; 45895 } 45896 } 45897 45898 var m2ts_1 = m2ts; 45899 var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS; 45900 45901 var _AdtsStream; 45902 45903 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 45904 /* 45905 * Accepts a ElementaryStream and emits data events with parsed 45906 * AAC Audio Frames of the individual packets. Input audio in ADTS 45907 * format is unpacked and re-emitted as AAC frames. 45908 * 45909 * @see http://wiki.multimedia.cx/index.php?title=ADTS 45910 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 45911 */ 45912 45913 _AdtsStream = function AdtsStream(handlePartialSegments) { 45914 var buffer, 45915 frameNum = 0; 45916 45917 _AdtsStream.prototype.init.call(this); 45918 45919 this.push = function (packet) { 45920 var i = 0, 45921 frameLength, 45922 protectionSkipBytes, 45923 frameEnd, 45924 oldBuffer, 45925 sampleCount, 45926 adtsFrameDuration; 45927 45928 if (!handlePartialSegments) { 45929 frameNum = 0; 45930 } 45931 45932 if (packet.type !== 'audio') { 45933 // ignore non-audio data 45934 return; 45935 } // Prepend any data in the buffer to the input data so that we can parse 45936 // aac frames the cross a PES packet boundary 45937 45938 45939 if (buffer) { 45940 oldBuffer = buffer; 45941 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 45942 buffer.set(oldBuffer); 45943 buffer.set(packet.data, oldBuffer.byteLength); 45944 } else { 45945 buffer = packet.data; 45946 } // unpack any ADTS frames which have been fully received 45947 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 45948 45949 45950 while (i + 5 < buffer.length) { 45951 // Look for the start of an ADTS header.. 45952 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 45953 // If a valid header was not found, jump one forward and attempt to 45954 // find a valid ADTS header starting at the next byte 45955 i++; 45956 continue; 45957 } // The protection skip bit tells us if we have 2 bytes of CRC data at the 45958 // end of the ADTS header 45959 45960 45961 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the 45962 // end of the sync sequence 45963 45964 frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5; 45965 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 45966 adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 45967 frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return 45968 // and wait for more data 45969 45970 if (buffer.byteLength < frameEnd) { 45971 return; 45972 } // Otherwise, deliver the complete AAC frame 45973 45974 45975 this.trigger('data', { 45976 pts: packet.pts + frameNum * adtsFrameDuration, 45977 dts: packet.dts + frameNum * adtsFrameDuration, 45978 sampleCount: sampleCount, 45979 audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1, 45980 channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6, 45981 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 45982 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 45983 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 45984 samplesize: 16, 45985 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 45986 }); 45987 frameNum++; // If the buffer is empty, clear it and return 45988 45989 if (buffer.byteLength === frameEnd) { 45990 buffer = undefined; 45991 return; 45992 } // Remove the finished frame from the buffer and start the process again 45993 45994 45995 buffer = buffer.subarray(frameEnd); 45996 } 45997 }; 45998 45999 this.flush = function () { 46000 frameNum = 0; 46001 this.trigger('done'); 46002 }; 46003 46004 this.reset = function () { 46005 buffer = void 0; 46006 this.trigger('reset'); 46007 }; 46008 46009 this.endTimeline = function () { 46010 buffer = void 0; 46011 this.trigger('endedtimeline'); 46012 }; 46013 }; 46014 46015 _AdtsStream.prototype = new stream(); 46016 var adts = _AdtsStream; 46017 /** 46018 * mux.js 46019 * 46020 * Copyright (c) Brightcove 46021 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 46022 */ 46023 46024 var ExpGolomb; 46025 /** 46026 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 46027 * scheme used by h264. 46028 */ 46029 46030 ExpGolomb = function ExpGolomb(workingData) { 46031 var // the number of bytes left to examine in workingData 46032 workingBytesAvailable = workingData.byteLength, 46033 // the current word being examined 46034 workingWord = 0, 46035 // :uint 46036 // the number of bits left to examine in the current word 46037 workingBitsAvailable = 0; // :uint; 46038 // ():uint 46039 46040 this.length = function () { 46041 return 8 * workingBytesAvailable; 46042 }; // ():uint 46043 46044 46045 this.bitsAvailable = function () { 46046 return 8 * workingBytesAvailable + workingBitsAvailable; 46047 }; // ():void 46048 46049 46050 this.loadWord = function () {
vendor: 8,126 bytes, lines 46051-46328
46051 var position = workingData.byteLength - workingBytesAvailable, 46052 workingBytes = new Uint8Array(4), 46053 availableBytes = Math.min(4, workingBytesAvailable); 46054 46055 if (availableBytes === 0) { 46056 throw new Error('no bytes available'); 46057 } 46058 46059 workingBytes.set(workingData.subarray(position, position + availableBytes)); 46060 workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed 46061 46062 workingBitsAvailable = availableBytes * 8; 46063 workingBytesAvailable -= availableBytes; 46064 }; // (count:int):void 46065 46066 46067 this.skipBits = function (count) { 46068 var skipBytes; // :int 46069 46070 if (workingBitsAvailable > count) { 46071 workingWord <<= count; 46072 workingBitsAvailable -= count; 46073 } else { 46074 count -= workingBitsAvailable; 46075 skipBytes = Math.floor(count / 8); 46076 count -= skipBytes * 8; 46077 workingBytesAvailable -= skipBytes; 46078 this.loadWord(); 46079 workingWord <<= count; 46080 workingBitsAvailable -= count; 46081 } 46082 }; // (size:int):uint 46083 46084 46085 this.readBits = function (size) { 46086 var bits = Math.min(workingBitsAvailable, size), 46087 // :uint 46088 valu = workingWord >>> 32 - bits; // :uint 46089 // if size > 31, handle error 46090 46091 workingBitsAvailable -= bits; 46092 46093 if (workingBitsAvailable > 0) { 46094 workingWord <<= bits; 46095 } else if (workingBytesAvailable > 0) { 46096 this.loadWord(); 46097 } 46098 46099 bits = size - bits; 46100 46101 if (bits > 0) { 46102 return valu << bits | this.readBits(bits); 46103 } 46104 46105 return valu; 46106 }; // ():uint 46107 46108 46109 this.skipLeadingZeros = function () { 46110 var leadingZeroCount; // :uint 46111 46112 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 46113 if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) { 46114 // the first bit of working word is 1 46115 workingWord <<= leadingZeroCount; 46116 workingBitsAvailable -= leadingZeroCount; 46117 return leadingZeroCount; 46118 } 46119 } // we exhausted workingWord and still have not found a 1 46120 46121 46122 this.loadWord(); 46123 return leadingZeroCount + this.skipLeadingZeros(); 46124 }; // ():void 46125 46126 46127 this.skipUnsignedExpGolomb = function () { 46128 this.skipBits(1 + this.skipLeadingZeros()); 46129 }; // ():void 46130 46131 46132 this.skipExpGolomb = function () { 46133 this.skipBits(1 + this.skipLeadingZeros()); 46134 }; // ():uint 46135 46136 46137 this.readUnsignedExpGolomb = function () { 46138 var clz = this.skipLeadingZeros(); // :uint 46139 46140 return this.readBits(clz + 1) - 1; 46141 }; // ():int 46142 46143 46144 this.readExpGolomb = function () { 46145 var valu = this.readUnsignedExpGolomb(); // :int 46146 46147 if (0x01 & valu) { 46148 // the number is odd if the low order bit is set 46149 return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 46150 } 46151 46152 return -1 * (valu >>> 1); // divide by two then make it negative 46153 }; // Some convenience functions 46154 // :Boolean 46155 46156 46157 this.readBoolean = function () { 46158 return this.readBits(1) === 1; 46159 }; // ():int 46160 46161 46162 this.readUnsignedByte = function () { 46163 return this.readBits(8); 46164 }; 46165 46166 this.loadWord(); 46167 }; 46168 46169 var expGolomb = ExpGolomb; 46170 46171 var _H264Stream, _NalByteStream; 46172 46173 var PROFILES_WITH_OPTIONAL_SPS_DATA; 46174 /** 46175 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 46176 */ 46177 46178 _NalByteStream = function NalByteStream() { 46179 var syncPoint = 0, 46180 i, 46181 buffer; 46182 46183 _NalByteStream.prototype.init.call(this); 46184 /* 46185 * Scans a byte stream and triggers a data event with the NAL units found. 46186 * @param {Object} data Event received from H264Stream 46187 * @param {Uint8Array} data.data The h264 byte stream to be scanned 46188 * 46189 * @see H264Stream.push 46190 */ 46191 46192 46193 this.push = function (data) { 46194 var swapBuffer; 46195 46196 if (!buffer) { 46197 buffer = data.data; 46198 } else { 46199 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 46200 swapBuffer.set(buffer); 46201 swapBuffer.set(data.data, buffer.byteLength); 46202 buffer = swapBuffer; 46203 } 46204 46205 var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B 46206 // scan for NAL unit boundaries 46207 // a match looks like this: 46208 // 0 0 1 .. NAL .. 0 0 1 46209 // ^ sync point ^ i 46210 // or this: 46211 // 0 0 1 .. NAL .. 0 0 0 46212 // ^ sync point ^ i 46213 // advance the sync point to a NAL start, if necessary 46214 46215 for (; syncPoint < len - 3; syncPoint++) { 46216 if (buffer[syncPoint + 2] === 1) { 46217 // the sync point is properly aligned 46218 i = syncPoint + 5; 46219 break; 46220 } 46221 } 46222 46223 while (i < len) { 46224 // look at the current byte to determine if we've hit the end of 46225 // a NAL unit boundary 46226 switch (buffer[i]) { 46227 case 0: 46228 // skip past non-sync sequences 46229 if (buffer[i - 1] !== 0) { 46230 i += 2; 46231 break; 46232 } else if (buffer[i - 2] !== 0) { 46233 i++; 46234 break; 46235 } // deliver the NAL unit if it isn't empty 46236 46237 46238 if (syncPoint + 3 !== i - 2) { 46239 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 46240 } // drop trailing zeroes 46241 46242 46243 do { 46244 i++; 46245 } while (buffer[i] !== 1 && i < len); 46246 46247 syncPoint = i - 2; 46248 i += 3; 46249 break; 46250 46251 case 1: 46252 // skip past non-sync sequences 46253 if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) { 46254 i += 3; 46255 break; 46256 } // deliver the NAL unit 46257 46258 46259 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 46260 syncPoint = i - 2; 46261 i += 3; 46262 break; 46263 46264 default: 46265 // the current byte isn't a one or zero, so it cannot be part 46266 // of a sync sequence 46267 i += 3; 46268 break; 46269 } 46270 } // filter out the NAL units that were delivered 46271 46272 46273 buffer = buffer.subarray(syncPoint); 46274 i -= syncPoint; 46275 syncPoint = 0; 46276 }; 46277 46278 this.reset = function () { 46279 buffer = null; 46280 syncPoint = 0; 46281 this.trigger('reset'); 46282 }; 46283 46284 this.flush = function () { 46285 // deliver the last buffered NAL unit 46286 if (buffer && buffer.byteLength > 3) { 46287 this.trigger('data', buffer.subarray(syncPoint + 3)); 46288 } // reset the stream state 46289 46290 46291 buffer = null; 46292 syncPoint = 0; 46293 this.trigger('done'); 46294 }; 46295 46296 this.endTimeline = function () { 46297 this.flush(); 46298 this.trigger('endedtimeline'); 46299 }; 46300 }; 46301 46302 _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS 46303 // see Recommendation ITU-T H.264 (4/2013), 46304 // 7.3.2.1.1 Sequence parameter set data syntax 46305 46306 PROFILES_WITH_OPTIONAL_SPS_DATA = { 46307 100: true, 46308 110: true, 46309 122: true, 46310 244: true, 46311 44: true, 46312 83: true, 46313 86: true, 46314 118: true, 46315 128: true, 46316 138: true, 46317 139: true, 46318 134: true 46319 }; 46320 /** 46321 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 46322 * events. 46323 */ 46324 46325 _H264Stream = function H264Stream() { 46326 var nalByteStream = new _NalByteStream(), 46327 self, 46328 trackId,
46329 currentPts, 46330 currentDts, 46331 discardEmulationPreventionBytes, 46332 readSequenceParameterSet, 46333 skipScalingList; 46334 46335 _H264Stream.prototype.init.call(this); 46336 46337 self = this; 46338 /* 46339 * Pushes a packet from a stream onto the NalByteStream 46340 * 46341 * @param {Object} packet - A packet received from a stream 46342 * @param {Uint8Array} packet.data - The raw bytes of the packet 46343 * @param {Number} packet.dts - Decode timestamp of the packet 46344 * @param {Number} packet.pts - Presentation timestamp of the packet 46345 * @param {Number} packet.trackId - The id of the h264 track this packet came from 46346 * @param {('video'|'audio')} packet.type - The type of packet 46347 * 46348 */ 46349 46350 this.push = function (packet) { 46351 if (packet.type !== 'video') { 46352 return; 46353 } 46354 46355 trackId = packet.trackId; 46356 currentPts = packet.pts; 46357 currentDts = packet.dts; 46358 nalByteStream.push(packet); 46359 }; 46360 /* 46361 * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps 46362 * for the NALUs to the next stream component. 46363 * Also, preprocess caption and sequence parameter NALUs. 46364 * 46365 * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push` 46366 * @see NalByteStream.push 46367 */ 46368 46369 46370 nalByteStream.on('data', function (data) { 46371 var event = { 46372 trackId: trackId,
vendor: 16,384 bytes, lines 46373-46849
46373 pts: currentPts, 46374 dts: currentDts, 46375 data: data 46376 }; 46377 46378 switch (data[0] & 0x1f) { 46379 case 0x05: 46380 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 46381 break; 46382 46383 case 0x06: 46384 event.nalUnitType = 'sei_rbsp'; 46385 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 46386 break; 46387 46388 case 0x07: 46389 event.nalUnitType = 'seq_parameter_set_rbsp'; 46390 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 46391 event.config = readSequenceParameterSet(event.escapedRBSP); 46392 break; 46393 46394 case 0x08: 46395 event.nalUnitType = 'pic_parameter_set_rbsp'; 46396 break; 46397 46398 case 0x09: 46399 event.nalUnitType = 'access_unit_delimiter_rbsp'; 46400 break; 46401 } // This triggers data on the H264Stream 46402 46403 46404 self.trigger('data', event); 46405 }); 46406 nalByteStream.on('done', function () { 46407 self.trigger('done'); 46408 }); 46409 nalByteStream.on('partialdone', function () { 46410 self.trigger('partialdone'); 46411 }); 46412 nalByteStream.on('reset', function () { 46413 self.trigger('reset'); 46414 }); 46415 nalByteStream.on('endedtimeline', function () { 46416 self.trigger('endedtimeline'); 46417 }); 46418 46419 this.flush = function () { 46420 nalByteStream.flush(); 46421 }; 46422 46423 this.partialFlush = function () { 46424 nalByteStream.partialFlush(); 46425 }; 46426 46427 this.reset = function () { 46428 nalByteStream.reset(); 46429 }; 46430 46431 this.endTimeline = function () { 46432 nalByteStream.endTimeline(); 46433 }; 46434 /** 46435 * Advance the ExpGolomb decoder past a scaling list. The scaling 46436 * list is optionally transmitted as part of a sequence parameter 46437 * set and is not relevant to transmuxing. 46438 * @param count {number} the number of entries in this scaling list 46439 * @param expGolombDecoder {object} an ExpGolomb pointed to the 46440 * start of a scaling list 46441 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 46442 */ 46443 46444 46445 skipScalingList = function skipScalingList(count, expGolombDecoder) { 46446 var lastScale = 8, 46447 nextScale = 8, 46448 j, 46449 deltaScale; 46450 46451 for (j = 0; j < count; j++) { 46452 if (nextScale !== 0) { 46453 deltaScale = expGolombDecoder.readExpGolomb(); 46454 nextScale = (lastScale + deltaScale + 256) % 256; 46455 } 46456 46457 lastScale = nextScale === 0 ? lastScale : nextScale; 46458 } 46459 }; 46460 /** 46461 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 46462 * Sequence Payload" 46463 * @param data {Uint8Array} the bytes of a RBSP from a NAL 46464 * unit 46465 * @return {Uint8Array} the RBSP without any Emulation 46466 * Prevention Bytes 46467 */ 46468 46469 46470 discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 46471 var length = data.byteLength, 46472 emulationPreventionBytesPositions = [], 46473 i = 1, 46474 newLength, 46475 newData; // Find all `Emulation Prevention Bytes` 46476 46477 while (i < length - 2) { 46478 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 46479 emulationPreventionBytesPositions.push(i + 2); 46480 i += 2; 46481 } else { 46482 i++; 46483 } 46484 } // If no Emulation Prevention Bytes were found just return the original 46485 // array 46486 46487 46488 if (emulationPreventionBytesPositions.length === 0) { 46489 return data; 46490 } // Create a new array to hold the NAL unit data 46491 46492 46493 newLength = length - emulationPreventionBytesPositions.length; 46494 newData = new Uint8Array(newLength); 46495 var sourceIndex = 0; 46496 46497 for (i = 0; i < newLength; sourceIndex++, i++) { 46498 if (sourceIndex === emulationPreventionBytesPositions[0]) { 46499 // Skip this byte 46500 sourceIndex++; // Remove this position index 46501 46502 emulationPreventionBytesPositions.shift(); 46503 } 46504 46505 newData[i] = data[sourceIndex]; 46506 } 46507 46508 return newData; 46509 }; 46510 /** 46511 * Read a sequence parameter set and return some interesting video 46512 * properties. A sequence parameter set is the H264 metadata that 46513 * describes the properties of upcoming video frames. 46514 * @param data {Uint8Array} the bytes of a sequence parameter set 46515 * @return {object} an object with configuration parsed from the 46516 * sequence parameter set, including the dimensions of the 46517 * associated video frames. 46518 */ 46519 46520 46521 readSequenceParameterSet = function readSequenceParameterSet(data) { 46522 var frameCropLeftOffset = 0, 46523 frameCropRightOffset = 0, 46524 frameCropTopOffset = 0, 46525 frameCropBottomOffset = 0, 46526 sarScale = 1, 46527 expGolombDecoder, 46528 profileIdc, 46529 levelIdc, 46530 profileCompatibility, 46531 chromaFormatIdc, 46532 picOrderCntType, 46533 numRefFramesInPicOrderCntCycle, 46534 picWidthInMbsMinus1, 46535 picHeightInMapUnitsMinus1, 46536 frameMbsOnlyFlag, 46537 scalingListCount, 46538 sarRatio, 46539 aspectRatioIdc, 46540 i; 46541 expGolombDecoder = new expGolomb(data); 46542 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 46543 46544 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 46545 46546 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 46547 46548 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 46549 // some profiles have more optional data we don't need 46550 46551 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 46552 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 46553 46554 if (chromaFormatIdc === 3) { 46555 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 46556 } 46557 46558 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 46559 46560 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 46561 46562 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 46563 46564 if (expGolombDecoder.readBoolean()) { 46565 // seq_scaling_matrix_present_flag 46566 scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; 46567 46568 for (i = 0; i < scalingListCount; i++) { 46569 if (expGolombDecoder.readBoolean()) { 46570 // seq_scaling_list_present_flag[ i ] 46571 if (i < 6) { 46572 skipScalingList(16, expGolombDecoder); 46573 } else { 46574 skipScalingList(64, expGolombDecoder); 46575 } 46576 } 46577 } 46578 } 46579 } 46580 46581 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 46582 46583 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 46584 46585 if (picOrderCntType === 0) { 46586 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 46587 } else if (picOrderCntType === 1) { 46588 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 46589 46590 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 46591 46592 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 46593 46594 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 46595 46596 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 46597 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 46598 } 46599 } 46600 46601 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 46602 46603 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 46604 46605 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 46606 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 46607 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 46608 46609 if (frameMbsOnlyFlag === 0) { 46610 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 46611 } 46612 46613 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 46614 46615 if (expGolombDecoder.readBoolean()) { 46616 // frame_cropping_flag 46617 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 46618 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 46619 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 46620 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 46621 } 46622 46623 if (expGolombDecoder.readBoolean()) { 46624 // vui_parameters_present_flag 46625 if (expGolombDecoder.readBoolean()) { 46626 // aspect_ratio_info_present_flag 46627 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 46628 46629 switch (aspectRatioIdc) { 46630 case 1: 46631 sarRatio = [1, 1]; 46632 break; 46633 46634 case 2: 46635 sarRatio = [12, 11]; 46636 break; 46637 46638 case 3: 46639 sarRatio = [10, 11]; 46640 break; 46641 46642 case 4: 46643 sarRatio = [16, 11]; 46644 break; 46645 46646 case 5: 46647 sarRatio = [40, 33]; 46648 break; 46649 46650 case 6: 46651 sarRatio = [24, 11]; 46652 break; 46653 46654 case 7: 46655 sarRatio = [20, 11]; 46656 break; 46657 46658 case 8: 46659 sarRatio = [32, 11]; 46660 break; 46661 46662 case 9: 46663 sarRatio = [80, 33]; 46664 break; 46665 46666 case 10: 46667 sarRatio = [18, 11]; 46668 break; 46669 46670 case 11: 46671 sarRatio = [15, 11]; 46672 break; 46673 46674 case 12: 46675 sarRatio = [64, 33]; 46676 break; 46677 46678 case 13: 46679 sarRatio = [160, 99]; 46680 break; 46681 46682 case 14: 46683 sarRatio = [4, 3]; 46684 break; 46685 46686 case 15: 46687 sarRatio = [3, 2]; 46688 break; 46689 46690 case 16: 46691 sarRatio = [2, 1]; 46692 break; 46693 46694 case 255: 46695 { 46696 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()]; 46697 break; 46698 } 46699 } 46700 46701 if (sarRatio) { 46702 sarScale = sarRatio[0] / sarRatio[1]; 46703 } 46704 } 46705 } 46706 46707 return { 46708 profileIdc: profileIdc, 46709 levelIdc: levelIdc, 46710 profileCompatibility: profileCompatibility, 46711 width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 46712 height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2, 46713 sarRatio: sarRatio 46714 }; 46715 }; 46716 }; 46717 46718 _H264Stream.prototype = new stream(); 46719 var h264 = { 46720 H264Stream: _H264Stream, 46721 NalByteStream: _NalByteStream 46722 }; 46723 /** 46724 * mux.js 46725 * 46726 * Copyright (c) Brightcove 46727 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 46728 * 46729 * Utilities to detect basic properties and metadata about Aac data. 46730 */ 46731 46732 var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 46733 46734 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 46735 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 46736 flags = header[byteIndex + 5], 46737 footerPresent = (flags & 16) >> 4; // if we get a negative returnSize clamp it to 0 46738 46739 returnSize = returnSize >= 0 ? returnSize : 0; 46740 46741 if (footerPresent) { 46742 return returnSize + 20; 46743 } 46744 46745 return returnSize + 10; 46746 }; 46747 46748 var getId3Offset = function getId3Offset(data, offset) { 46749 if (data.length - offset < 10 || data[offset] !== 'I'.charCodeAt(0) || data[offset + 1] !== 'D'.charCodeAt(0) || data[offset + 2] !== '3'.charCodeAt(0)) { 46750 return offset; 46751 } 46752 46753 offset += parseId3TagSize(data, offset); 46754 return getId3Offset(data, offset); 46755 }; // TODO: use vhs-utils 46756 46757 46758 var isLikelyAacData = function isLikelyAacData(data) { 46759 var offset = getId3Offset(data, 0); 46760 return data.length >= offset + 2 && (data[offset] & 0xFF) === 0xFF && (data[offset + 1] & 0xF0) === 0xF0 && // verify that the 2 layer bits are 0, aka this 46761 // is not mp3 data but aac data. 46762 (data[offset + 1] & 0x16) === 0x10; 46763 }; 46764 46765 var parseSyncSafeInteger$1 = function parseSyncSafeInteger$1(data) { 46766 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 46767 }; // return a percent-encoded representation of the specified byte range 46768 // @see http://en.wikipedia.org/wiki/Percent-encoding 46769 46770 46771 var percentEncode$1 = function percentEncode$1(bytes, start, end) { 46772 var i, 46773 result = ''; 46774 46775 for (i = start; i < end; i++) { 46776 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 46777 } 46778 46779 return result; 46780 }; // return the string representation of the specified byte range, 46781 // interpreted as ISO-8859-1. 46782 46783 46784 var parseIso88591$1 = function parseIso88591$1(bytes, start, end) { 46785 return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line 46786 }; 46787 46788 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 46789 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 46790 middle = header[byteIndex + 4] << 3, 46791 highTwo = header[byteIndex + 3] & 0x3 << 11; 46792 return highTwo | middle | lowThree; 46793 }; 46794 46795 var parseType = function parseType(header, byteIndex) { 46796 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 46797 return 'timed-metadata'; 46798 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 46799 return 'audio'; 46800 } 46801 46802 return null; 46803 }; 46804 46805 var parseSampleRate = function parseSampleRate(packet) { 46806 var i = 0; 46807 46808 while (i + 5 < packet.length) { 46809 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 46810 // If a valid header was not found, jump one forward and attempt to 46811 // find a valid ADTS header starting at the next byte 46812 i++; 46813 continue; 46814 } 46815 46816 return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2]; 46817 } 46818 46819 return null; 46820 }; 46821 46822 var parseAacTimestamp = function parseAacTimestamp(packet) { 46823 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 46824 46825 frameStart = 10; 46826 46827 if (packet[5] & 0x40) { 46828 // advance the frame start past the extended header 46829 frameStart += 4; // header size field 46830 46831 frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14)); 46832 } // parse one or more ID3 frames 46833 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 46834 46835 46836 do { 46837 // determine the number of bytes in this frame 46838 frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8)); 46839 46840 if (frameSize < 1) { 46841 return null; 46842 } 46843 46844 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 46845 46846 if (frameHeader === 'PRIV') { 46847 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 46848 46849 for (var i = 0; i <
vendor: 4,724 bytes, lines 46849-46992
46849frame.byteLength; i++) { 46850 if (frame[i] === 0) { 46851 var owner = parseIso88591$1(frame, 0, i); 46852 46853 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 46854 var d = frame.subarray(i + 1); 46855 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 46856 size *= 4; 46857 size += d[7] & 0x03; 46858 return size; 46859 } 46860 46861 break; 46862 } 46863 } 46864 } 46865 46866 frameStart += 10; // advance past the frame header 46867 46868 frameStart += frameSize; // advance past the frame body 46869 } while (frameStart < packet.byteLength); 46870 46871 return null; 46872 }; 46873 46874 var utils = { 46875 isLikelyAacData: isLikelyAacData, 46876 parseId3TagSize: parseId3TagSize, 46877 parseAdtsSize: parseAdtsSize, 46878 parseType: parseType, 46879 parseSampleRate: parseSampleRate, 46880 parseAacTimestamp: parseAacTimestamp 46881 }; // Constants 46882 46883 var _AacStream; 46884 /** 46885 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 46886 */ 46887 46888 46889 _AacStream = function AacStream() { 46890 var everything = new Uint8Array(), 46891 timeStamp = 0; 46892 46893 _AacStream.prototype.init.call(this); 46894 46895 this.setTimestamp = function (timestamp) { 46896 timeStamp = timestamp; 46897 }; 46898 46899 this.push = function (bytes) { 46900 var frameSize = 0, 46901 byteIndex = 0, 46902 bytesLeft, 46903 chunk, 46904 packet, 46905 tempLength; // If there are bytes remaining from the last segment, prepend them to the 46906 // bytes that were pushed in 46907 46908 if (everything.length) { 46909 tempLength = everything.length; 46910 everything = new Uint8Array(bytes.byteLength + tempLength); 46911 everything.set(everything.subarray(0, tempLength)); 46912 everything.set(bytes, tempLength); 46913 } else { 46914 everything = bytes; 46915 } 46916 46917 while (everything.length - byteIndex >= 3) { 46918 if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) { 46919 // Exit early because we don't have enough to parse 46920 // the ID3 tag header 46921 if (everything.length - byteIndex < 10) { 46922 break; 46923 } // check framesize 46924 46925 46926 frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 46927 // to emit a full packet 46928 // Add to byteIndex to support multiple ID3 tags in sequence 46929 46930 if (byteIndex + frameSize > everything.length) { 46931 break; 46932 } 46933 46934 chunk = { 46935 type: 'timed-metadata', 46936 data: everything.subarray(byteIndex, byteIndex + frameSize) 46937 }; 46938 this.trigger('data', chunk); 46939 byteIndex += frameSize; 46940 continue; 46941 } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) { 46942 // Exit early because we don't have enough to parse 46943 // the ADTS frame header 46944 if (everything.length - byteIndex < 7) { 46945 break; 46946 } 46947 46948 frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 46949 // to emit a full packet 46950 46951 if (byteIndex + frameSize > everything.length) { 46952 break; 46953 } 46954 46955 packet = { 46956 type: 'audio', 46957 data: everything.subarray(byteIndex, byteIndex + frameSize), 46958 pts: timeStamp, 46959 dts: timeStamp 46960 }; 46961 this.trigger('data', packet); 46962 byteIndex += frameSize; 46963 continue; 46964 } 46965 46966 byteIndex++; 46967 } 46968 46969 bytesLeft = everything.length - byteIndex; 46970 46971 if (bytesLeft > 0) { 46972 everything = everything.subarray(byteIndex); 46973 } else { 46974 everything = new Uint8Array(); 46975 } 46976 }; 46977 46978 this.reset = function () { 46979 everything = new Uint8Array(); 46980 this.trigger('reset'); 46981 }; 46982 46983 this.endTimeline = function () { 46984 everything = new Uint8Array(); 46985 this.trigger('endedtimeline'); 46986 }; 46987 }; 46988 46989 _AacStream.prototype = new stream(); 46990 var aac = _AacStream; // constants 46991 46992 var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize'];
vendor: 7,111 bytes, lines 46993-47170
46993 var audioProperties = AUDIO_PROPERTIES; 46994 var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio']; 46995 var videoProperties = VIDEO_PROPERTIES; 46996 var H264Stream$1 = h264.H264Stream; 46997 var isLikelyAacData$1 = utils.isLikelyAacData; 46998 var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS; // object types 46999 47000 var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream; 47001 /** 47002 * Compare two arrays (even typed) for same-ness 47003 */ 47004 47005 47006 var arrayEquals = function arrayEquals(a, b) { 47007 var i; 47008 47009 if (a.length !== b.length) { 47010 return false; 47011 } // compare the value of each element in the array 47012 47013 47014 for (i = 0; i < a.length; i++) { 47015 if (a[i] !== b[i]) { 47016 return false; 47017 } 47018 } 47019 47020 return true; 47021 }; 47022 47023 var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) { 47024 var ptsOffsetFromDts = startPts - startDts, 47025 decodeDuration = endDts - startDts, 47026 presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment, 47027 // however, the player time values will reflect a start from the baseMediaDecodeTime. 47028 // In order to provide relevant values for the player times, base timing info on the 47029 // baseMediaDecodeTime and the DTS and PTS durations of the segment. 47030 47031 return { 47032 start: { 47033 dts: baseMediaDecodeTime, 47034 pts: baseMediaDecodeTime + ptsOffsetFromDts 47035 }, 47036 end: { 47037 dts: baseMediaDecodeTime + decodeDuration, 47038 pts: baseMediaDecodeTime + presentationDuration 47039 }, 47040 prependedContentDuration: prependedContentDuration, 47041 baseMediaDecodeTime: baseMediaDecodeTime 47042 }; 47043 }; 47044 /** 47045 * Constructs a single-track, ISO BMFF media segment from AAC data 47046 * events. The output of this stream can be fed to a SourceBuffer 47047 * configured with a suitable initialization segment. 47048 * @param track {object} track metadata configuration 47049 * @param options {object} transmuxer options object 47050 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 47051 * in the source; false to adjust the first segment to start at 0. 47052 */ 47053 47054 47055 _AudioSegmentStream = function AudioSegmentStream(track, options) { 47056 var adtsFrames = [], 47057 sequenceNumber = 0, 47058 earliestAllowedDts = 0, 47059 audioAppendStartTs = 0, 47060 videoBaseMediaDecodeTime = Infinity; 47061 options = options || {}; 47062 47063 _AudioSegmentStream.prototype.init.call(this); 47064 47065 this.push = function (data) { 47066 trackDecodeInfo.collectDtsInfo(track, data); 47067 47068 if (track) { 47069 audioProperties.forEach(function (prop) { 47070 track[prop] = data[prop]; 47071 }); 47072 } // buffer audio data until end() is called 47073 47074 47075 adtsFrames.push(data); 47076 }; 47077 47078 this.setEarliestDts = function (earliestDts) { 47079 earliestAllowedDts = earliestDts; 47080 }; 47081 47082 this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) { 47083 videoBaseMediaDecodeTime = baseMediaDecodeTime; 47084 }; 47085 47086 this.setAudioAppendStart = function (timestamp) { 47087 audioAppendStartTs = timestamp; 47088 }; 47089 47090 this.flush = function () { 47091 var frames, moof, mdat, boxes, frameDuration; // return early if no audio data has been observed 47092 47093 if (adtsFrames.length === 0) { 47094 this.trigger('done', 'AudioSegmentStream'); 47095 return; 47096 } 47097 47098 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts); 47099 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 47100 audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to 47101 // samples (that is, adts frames) in the audio data 47102 47103 track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat 47104 47105 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames)); 47106 adtsFrames = []; 47107 moof = mp4Generator.moof(sequenceNumber, [track]); 47108 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time 47109 47110 sequenceNumber++; 47111 boxes.set(moof); 47112 boxes.set(mdat, moof.byteLength); 47113 trackDecodeInfo.clearDtsInfo(track); 47114 frameDuration = Math.ceil(ONE_SECOND_IN_TS$3 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with 47115 // tests) on adding the timingInfo event. However, it seems unlikely that there's a 47116 // valid use-case where an init segment/data should be triggered without associated 47117 // frames. Leaving for now, but should be looked into. 47118 47119 if (frames.length) { 47120 this.trigger('timingInfo', { 47121 start: frames[0].pts, 47122 end: frames[0].pts + frames.length * frameDuration 47123 }); 47124 } 47125 47126 this.trigger('data', { 47127 track: track, 47128 boxes: boxes 47129 }); 47130 this.trigger('done', 'AudioSegmentStream'); 47131 }; 47132 47133 this.reset = function () { 47134 trackDecodeInfo.clearDtsInfo(track); 47135 adtsFrames = []; 47136 this.trigger('reset'); 47137 }; 47138 }; 47139 47140 _AudioSegmentStream.prototype = new stream(); 47141 /** 47142 * Constructs a single-track, ISO BMFF media segment from H264 data 47143 * events. The output of this stream can be fed to a SourceBuffer 47144 * configured with a suitable initialization segment. 47145 * @param track {object} track metadata configuration 47146 * @param options {object} transmuxer options object 47147 * @param options.alignGopsAtEnd {boolean} If true, start from the end of the 47148 * gopsToAlignWith list when attempting to align gop pts 47149 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 47150 * in the source; false to adjust the first segment to start at 0. 47151 */ 47152 47153 _VideoSegmentStream = function VideoSegmentStream(track, options) { 47154 var sequenceNumber = 0, 47155 nalUnits = [], 47156 gopsToAlignWith = [], 47157 config, 47158 pps; 47159 options = options || {}; 47160 47161 _VideoSegmentStream.prototype.init.call(this); 47162 47163 delete track.minPTS; 47164 this.gopCache_ = []; 47165 /** 47166 * Constructs a ISO BMFF segment given H264 nalUnits 47167 * @param {Object} nalUnit A data event representing a nalUnit 47168 * @param {String} nalUnit.nalUnitType 47169 * @param {Object} nalUnit.config Properties for a mp4 track 47170 * @param {Uint8Array}
vendor: 11,982 bytes, lines 47170-47476
47170 nalUnit.data The nalUnit bytes 47171 * @see lib/codecs/h264.js 47172 **/ 47173 47174 this.push = function (nalUnit) { 47175 trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config 47176 47177 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 47178 config = nalUnit.config; 47179 track.sps = [nalUnit.data]; 47180 videoProperties.forEach(function (prop) { 47181 track[prop] = config[prop]; 47182 }, this); 47183 } 47184 47185 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) { 47186 pps = nalUnit.data; 47187 track.pps = [nalUnit.data]; 47188 } // buffer video until flush() is called 47189 47190 47191 nalUnits.push(nalUnit); 47192 }; 47193 /** 47194 * Pass constructed ISO BMFF track and boxes on to the 47195 * next stream in the pipeline 47196 **/ 47197 47198 47199 this.flush = function () { 47200 var frames, 47201 gopForFusion, 47202 gops, 47203 moof, 47204 mdat, 47205 boxes, 47206 prependedContentDuration = 0, 47207 firstGop, 47208 lastGop; // Throw away nalUnits at the start of the byte stream until 47209 // we find the first AUD 47210 47211 while (nalUnits.length) { 47212 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 47213 break; 47214 } 47215 47216 nalUnits.shift(); 47217 } // Return early if no video data has been observed 47218 47219 47220 if (nalUnits.length === 0) { 47221 this.resetStream_(); 47222 this.trigger('done', 'VideoSegmentStream'); 47223 return; 47224 } // Organize the raw nal-units into arrays that represent 47225 // higher-level constructs such as frames and gops 47226 // (group-of-pictures) 47227 47228 47229 frames = frameUtils.groupNalsIntoFrames(nalUnits); 47230 gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have 47231 // a problem since MSE (on Chrome) requires a leading keyframe. 47232 // 47233 // We have two approaches to repairing this situation: 47234 // 1) GOP-FUSION: 47235 // This is where we keep track of the GOPS (group-of-pictures) 47236 // from previous fragments and attempt to find one that we can 47237 // prepend to the current fragment in order to create a valid 47238 // fragment. 47239 // 2) KEYFRAME-PULLING: 47240 // Here we search for the first keyframe in the fragment and 47241 // throw away all the frames between the start of the fragment 47242 // and that keyframe. We then extend the duration and pull the 47243 // PTS of the keyframe forward so that it covers the time range 47244 // of the frames that were disposed of. 47245 // 47246 // #1 is far prefereable over #2 which can cause "stuttering" but 47247 // requires more things to be just right. 47248 47249 if (!gops[0][0].keyFrame) { 47250 // Search for a gop for fusion from our gopCache 47251 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 47252 47253 if (gopForFusion) { 47254 // in order to provide more accurate timing information about the segment, save 47255 // the number of seconds prepended to the original segment due to GOP fusion 47256 prependedContentDuration = gopForFusion.duration; 47257 gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the 47258 // new gop at the beginning 47259 47260 gops.byteLength += gopForFusion.byteLength; 47261 gops.nalCount += gopForFusion.nalCount; 47262 gops.pts = gopForFusion.pts; 47263 gops.dts = gopForFusion.dts; 47264 gops.duration += gopForFusion.duration; 47265 } else { 47266 // If we didn't find a candidate gop fall back to keyframe-pulling 47267 gops = frameUtils.extendFirstKeyFrame(gops); 47268 } 47269 } // Trim gops to align with gopsToAlignWith 47270 47271 47272 if (gopsToAlignWith.length) { 47273 var alignedGops; 47274 47275 if (options.alignGopsAtEnd) { 47276 alignedGops = this.alignGopsAtEnd_(gops); 47277 } else { 47278 alignedGops = this.alignGopsAtStart_(gops); 47279 } 47280 47281 if (!alignedGops) { 47282 // save all the nals in the last GOP into the gop cache 47283 this.gopCache_.unshift({ 47284 gop: gops.pop(), 47285 pps: track.pps, 47286 sps: track.sps 47287 }); // Keep a maximum of 6 GOPs in the cache 47288 47289 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 47290 47291 nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith 47292 47293 this.resetStream_(); 47294 this.trigger('done', 'VideoSegmentStream'); 47295 return; 47296 } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct 47297 // when recalculated before sending off to CoalesceStream 47298 47299 47300 trackDecodeInfo.clearDtsInfo(track); 47301 gops = alignedGops; 47302 } 47303 47304 trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to 47305 // samples (that is, frames) in the video data 47306 47307 track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat 47308 47309 mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops)); 47310 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 47311 this.trigger('processedGopsInfo', gops.map(function (gop) { 47312 return { 47313 pts: gop.pts, 47314 dts: gop.dts, 47315 byteLength: gop.byteLength 47316 }; 47317 })); 47318 firstGop = gops[0]; 47319 lastGop = gops[gops.length - 1]; 47320 this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); 47321 this.trigger('timingInfo', { 47322 start: gops[0].pts, 47323 end: gops[gops.length - 1].pts + gops[gops.length - 1].duration 47324 }); // save all the nals in the last GOP into the gop cache 47325 47326 this.gopCache_.unshift({ 47327 gop: gops.pop(), 47328 pps: track.pps, 47329 sps: track.sps 47330 }); // Keep a maximum of 6 GOPs in the cache 47331 47332 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 47333 47334 nalUnits = []; 47335 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 47336 this.trigger('timelineStartInfo', track.timelineStartInfo); 47337 moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of 47338 // throwing away hundreds of media segment fragments 47339 47340 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time 47341 47342 sequenceNumber++; 47343 boxes.set(moof); 47344 boxes.set(mdat, moof.byteLength); 47345 this.trigger('data', { 47346 track: track, 47347 boxes: boxes 47348 }); 47349 this.resetStream_(); // Continue with the flush process now 47350 47351 this.trigger('done', 'VideoSegmentStream'); 47352 }; 47353 47354 this.reset = function () { 47355 this.resetStream_(); 47356 nalUnits = []; 47357 this.gopCache_.length = 0; 47358 gopsToAlignWith.length = 0; 47359 this.trigger('reset'); 47360 }; 47361 47362 this.resetStream_ = function () { 47363 trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments 47364 // for instance, when we are rendition switching 47365 47366 config = undefined; 47367 pps = undefined; 47368 }; // Search for a candidate Gop for gop-fusion from the gop cache and 47369 // return it or return null if no good candidate was found 47370 47371 47372 this.getGopForFusion_ = function (nalUnit) { 47373 var halfSecond = 45000, 47374 // Half-a-second in a 90khz clock 47375 allowableOverlap = 10000, 47376 // About 3 frames @ 30fps 47377 nearestDistance = Infinity, 47378 dtsDistance, 47379 nearestGopObj, 47380 currentGop, 47381 currentGopObj, 47382 i; // Search for the GOP nearest to the beginning of this nal unit 47383 47384 for (i = 0; i < this.gopCache_.length; i++) { 47385 currentGopObj = this.gopCache_[i]; 47386 currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS 47387 47388 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 47389 continue; 47390 } // Reject Gops that would require a negative baseMediaDecodeTime 47391 47392 47393 if (currentGop.dts < track.timelineStartInfo.dts) { 47394 continue; 47395 } // The distance between the end of the gop and the start of the nalUnit 47396 47397 47398 dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within 47399 // a half-second of the nal unit 47400 47401 if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) { 47402 // Always use the closest GOP we found if there is more than 47403 // one candidate 47404 if (!nearestGopObj || nearestDistance > dtsDistance) { 47405 nearestGopObj = currentGopObj; 47406 nearestDistance = dtsDistance; 47407 } 47408 } 47409 } 47410 47411 if (nearestGopObj) { 47412 return nearestGopObj.gop; 47413 } 47414 47415 return null; 47416 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 47417 // of gopsToAlignWith starting from the START of the list 47418 47419 47420 this.alignGopsAtStart_ = function (gops) { 47421 var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops; 47422 byteLength = gops.byteLength; 47423 nalCount = gops.nalCount; 47424 duration = gops.duration; 47425 alignIndex = gopIndex = 0; 47426 47427 while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) { 47428 align = gopsToAlignWith[alignIndex]; 47429 gop = gops[gopIndex]; 47430 47431 if (align.pts === gop.pts) { 47432 break; 47433 } 47434 47435 if (gop.pts > align.pts) { 47436 // this current gop starts after the current gop we want to align on, so increment 47437 // align index 47438 alignIndex++; 47439 continue; 47440 } // current gop starts before the current gop we want to align on. so increment gop 47441 // index 47442 47443 47444 gopIndex++; 47445 byteLength -= gop.byteLength; 47446 nalCount -= gop.nalCount; 47447 duration -= gop.duration; 47448 } 47449 47450 if (gopIndex === 0) { 47451 // no gops to trim 47452 return gops; 47453 } 47454 47455 if (gopIndex === gops.length) { 47456 // all gops trimmed, skip appending all gops 47457 return null; 47458 } 47459 47460 alignedGops = gops.slice(gopIndex); 47461 alignedGops.byteLength = byteLength; 47462 alignedGops.duration = duration; 47463 alignedGops.nalCount = nalCount; 47464 alignedGops.pts = alignedGops[0].pts; 47465 alignedGops.dts = alignedGops[0].dts; 47466 return alignedGops; 47467 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 47468 // of gopsToAlignWith starting from the END of the list 47469 47470 47471 this.alignGopsAtEnd_ = function (gops) { 47472 var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound; 47473 alignIndex = gopsToAlignWith.length - 1; 47474 gopIndex = gops.length - 1; 47475 alignEndIndex = null; 47476 matchFound = false;
vendor: 4,848 bytes, lines 47477-47623
47477 47478 while (alignIndex >= 0 && gopIndex >= 0) { 47479 align = gopsToAlignWith[alignIndex]; 47480 gop = gops[gopIndex]; 47481 47482 if (align.pts === gop.pts) { 47483 matchFound = true; 47484 break; 47485 } 47486 47487 if (align.pts > gop.pts) { 47488 alignIndex--; 47489 continue; 47490 } 47491 47492 if (alignIndex === gopsToAlignWith.length - 1) { 47493 // gop.pts is greater than the last alignment candidate. If no match is found 47494 // by the end of this loop, we still want to append gops that come after this 47495 // point 47496 alignEndIndex = gopIndex; 47497 } 47498 47499 gopIndex--; 47500 } 47501 47502 if (!matchFound && alignEndIndex === null) { 47503 return null; 47504 } 47505 47506 var trimIndex; 47507 47508 if (matchFound) { 47509 trimIndex = gopIndex; 47510 } else { 47511 trimIndex = alignEndIndex; 47512 } 47513 47514 if (trimIndex === 0) { 47515 return gops; 47516 } 47517 47518 var alignedGops = gops.slice(trimIndex); 47519 var metadata = alignedGops.reduce(function (total, gop) { 47520 total.byteLength += gop.byteLength; 47521 total.duration += gop.duration; 47522 total.nalCount += gop.nalCount; 47523 return total; 47524 }, { 47525 byteLength: 0, 47526 duration: 0, 47527 nalCount: 0 47528 }); 47529 alignedGops.byteLength = metadata.byteLength; 47530 alignedGops.duration = metadata.duration; 47531 alignedGops.nalCount = metadata.nalCount; 47532 alignedGops.pts = alignedGops[0].pts; 47533 alignedGops.dts = alignedGops[0].dts; 47534 return alignedGops; 47535 }; 47536 47537 this.alignGopsWith = function (newGopsToAlignWith) { 47538 gopsToAlignWith = newGopsToAlignWith; 47539 }; 47540 }; 47541 47542 _VideoSegmentStream.prototype = new stream(); 47543 /** 47544 * A Stream that can combine multiple streams (ie. audio & video) 47545 * into a single output segment for MSE. Also supports audio-only 47546 * and video-only streams. 47547 * @param options {object} transmuxer options object 47548 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 47549 * in the source; false to adjust the first segment to start at media timeline start. 47550 */ 47551 47552 _CoalesceStream = function CoalesceStream(options, metadataStream) { 47553 // Number of Tracks per output segment 47554 // If greater than 1, we combine multiple 47555 // tracks into a single segment 47556 this.numberOfTracks = 0; 47557 this.metadataStream = metadataStream; 47558 options = options || {}; 47559 47560 if (typeof options.remux !== 'undefined') { 47561 this.remuxTracks = !!options.remux; 47562 } else { 47563 this.remuxTracks = true; 47564 } 47565 47566 if (typeof options.keepOriginalTimestamps === 'boolean') { 47567 this.keepOriginalTimestamps = options.keepOriginalTimestamps; 47568 } else { 47569 this.keepOriginalTimestamps = false; 47570 } 47571 47572 this.pendingTracks = []; 47573 this.videoTrack = null; 47574 this.pendingBoxes = []; 47575 this.pendingCaptions = []; 47576 this.pendingMetadata = []; 47577 this.pendingBytes = 0; 47578 this.emittedTracks = 0; 47579 47580 _CoalesceStream.prototype.init.call(this); // Take output from multiple 47581 47582 47583 this.push = function (output) { 47584 // buffer incoming captions until the associated video segment 47585 // finishes 47586 if (output.text) { 47587 return this.pendingCaptions.push(output); 47588 } // buffer incoming id3 tags until the final flush 47589 47590 47591 if (output.frames) { 47592 return this.pendingMetadata.push(output); 47593 } // Add this track to the list of pending tracks and store 47594 // important information required for the construction of 47595 // the final segment 47596 47597 47598 this.pendingTracks.push(output.track); 47599 this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome? 47600 // We unshift audio and push video because 47601 // as of Chrome 75 when switching from 47602 // one init segment to another if the video 47603 // mdat does not appear after the audio mdat 47604 // only audio will play for the duration of our transmux. 47605 47606 if (output.track.type === 'video') { 47607 this.videoTrack = output.track; 47608 this.pendingBoxes.push(output.boxes); 47609 } 47610 47611 if (output.track.type === 'audio') { 47612 this.audioTrack = output.track; 47613 this.pendingBoxes.unshift(output.boxes); 47614 } 47615 }; 47616 }; 47617 47618 _CoalesceStream.prototype = new stream(); 47619 47620 _CoalesceStream.prototype.flush = function (flushSource) { 47621 var offset = 0, 47622 event = { 47623 captions: [],
vendor: 4,722 bytes, lines 47624-47729
47624 captionStreams: {}, 47625 metadata: [], 47626 info: {} 47627 }, 47628 caption, 47629 id3, 47630 initSegment, 47631 timelineStartPts = 0, 47632 i; 47633 47634 if (this.pendingTracks.length < this.numberOfTracks) { 47635 if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') { 47636 // Return because we haven't received a flush from a data-generating 47637 // portion of the segment (meaning that we have only recieved meta-data 47638 // or captions.) 47639 return; 47640 } else if (this.remuxTracks) { 47641 // Return until we have enough tracks from the pipeline to remux (if we 47642 // are remuxing audio and video into a single MP4) 47643 return; 47644 } else if (this.pendingTracks.length === 0) { 47645 // In the case where we receive a flush without any data having been 47646 // received we consider it an emitted track for the purposes of coalescing 47647 // `done` events. 47648 // We do this for the case where there is an audio and video track in the 47649 // segment but no audio data. (seen in several playlists with alternate 47650 // audio tracks and no audio present in the main TS segments.) 47651 this.emittedTracks++; 47652 47653 if (this.emittedTracks >= this.numberOfTracks) { 47654 this.trigger('done'); 47655 this.emittedTracks = 0; 47656 } 47657 47658 return; 47659 } 47660 } 47661 47662 if (this.videoTrack) { 47663 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 47664 videoProperties.forEach(function (prop) { 47665 event.info[prop] = this.videoTrack[prop]; 47666 }, this); 47667 } else if (this.audioTrack) { 47668 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 47669 audioProperties.forEach(function (prop) { 47670 event.info[prop] = this.audioTrack[prop]; 47671 }, this); 47672 } 47673 47674 if (this.videoTrack || this.audioTrack) { 47675 if (this.pendingTracks.length === 1) { 47676 event.type = this.pendingTracks[0].type; 47677 } else { 47678 event.type = 'combined'; 47679 } 47680 47681 this.emittedTracks += this.pendingTracks.length; 47682 initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment 47683 47684 event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov 47685 // and track definitions 47686 47687 event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats 47688 47689 event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together 47690 47691 for (i = 0; i < this.pendingBoxes.length; i++) { 47692 event.data.set(this.pendingBoxes[i], offset); 47693 offset += this.pendingBoxes[i].byteLength; 47694 } // Translate caption PTS times into second offsets to match the 47695 // video timeline for the segment, and add track info 47696 47697 47698 for (i = 0; i < this.pendingCaptions.length; i++) { 47699 caption = this.pendingCaptions[i]; 47700 caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps); 47701 caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps); 47702 event.captionStreams[caption.stream] = true; 47703 event.captions.push(caption); 47704 } // Translate ID3 frame PTS times into second offsets to match the 47705 // video timeline for the segment 47706 47707 47708 for (i = 0; i < this.pendingMetadata.length; i++) { 47709 id3 = this.pendingMetadata[i]; 47710 id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps); 47711 event.metadata.push(id3); 47712 } // We add this to every single emitted segment even though we only need 47713 // it for the first 47714 47715 47716 event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state 47717 47718 this.pendingTracks.length = 0; 47719 this.videoTrack = null; 47720 this.pendingBoxes.length = 0; 47721 this.pendingCaptions.length = 0; 47722 this.pendingBytes = 0; 47723 this.pendingMetadata.length = 0; // Emit the built segment 47724 // We include captions and ID3 tags for backwards compatibility, 47725 // ideally we should send only video and audio in the data event 47726 47727 this.trigger('data', event); // Emit each caption to the outside world 47728 // Ideally, this would happen immediately on parsing captions, 47729 // but we need to ensure that video data is sent back first
vendor: 11,951 bytes, lines 47730-47987
47730 // so that caption timing can be adjusted to match video timing 47731 47732 for (i = 0; i < event.captions.length; i++) { 47733 caption = event.captions[i]; 47734 this.trigger('caption', caption); 47735 } // Emit each id3 tag to the outside world 47736 // Ideally, this would happen immediately on parsing the tag, 47737 // but we need to ensure that video data is sent back first 47738 // so that ID3 frame timing can be adjusted to match video timing 47739 47740 47741 for (i = 0; i < event.metadata.length; i++) { 47742 id3 = event.metadata[i]; 47743 this.trigger('id3Frame', id3); 47744 } 47745 } // Only emit `done` if all tracks have been flushed and emitted 47746 47747 47748 if (this.emittedTracks >= this.numberOfTracks) { 47749 this.trigger('done'); 47750 this.emittedTracks = 0; 47751 } 47752 }; 47753 47754 _CoalesceStream.prototype.setRemux = function (val) { 47755 this.remuxTracks = val; 47756 }; 47757 /** 47758 * A Stream that expects MP2T binary data as input and produces 47759 * corresponding media segments, suitable for use with Media Source 47760 * Extension (MSE) implementations that support the ISO BMFF byte 47761 * stream format, like Chrome. 47762 */ 47763 47764 47765 _Transmuxer = function Transmuxer(options) { 47766 var self = this, 47767 hasFlushed = true, 47768 videoTrack, 47769 audioTrack; 47770 47771 _Transmuxer.prototype.init.call(this); 47772 47773 options = options || {}; 47774 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 47775 this.transmuxPipeline_ = {}; 47776 47777 this.setupAacPipeline = function () { 47778 var pipeline = {}; 47779 this.transmuxPipeline_ = pipeline; 47780 pipeline.type = 'aac'; 47781 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 47782 47783 pipeline.aacStream = new aac(); 47784 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 47785 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 47786 pipeline.adtsStream = new adts(); 47787 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 47788 pipeline.headOfPipeline = pipeline.aacStream; 47789 pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 47790 pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); 47791 pipeline.metadataStream.on('timestamp', function (frame) { 47792 pipeline.aacStream.setTimestamp(frame.timeStamp); 47793 }); 47794 pipeline.aacStream.on('data', function (data) { 47795 if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) { 47796 return; 47797 } 47798 47799 audioTrack = audioTrack || { 47800 timelineStartInfo: { 47801 baseMediaDecodeTime: self.baseMediaDecodeTime 47802 }, 47803 codec: 'adts', 47804 type: 'audio' 47805 }; // hook up the audio segment stream to the first track with aac data 47806 47807 pipeline.coalesceStream.numberOfTracks++; 47808 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 47809 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 47810 47811 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); // emit pmt info 47812 47813 self.trigger('trackinfo', { 47814 hasAudio: !!audioTrack, 47815 hasVideo: !!videoTrack 47816 }); 47817 }); // Re-emit any data coming from the coalesce stream to the outside world 47818 47819 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 47820 47821 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 47822 }; 47823 47824 this.setupTsPipeline = function () { 47825 var pipeline = {}; 47826 this.transmuxPipeline_ = pipeline; 47827 pipeline.type = 'ts'; 47828 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 47829 47830 pipeline.packetStream = new m2ts_1.TransportPacketStream(); 47831 pipeline.parseStream = new m2ts_1.TransportParseStream(); 47832 pipeline.elementaryStream = new m2ts_1.ElementaryStream(); 47833 pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream(); 47834 pipeline.adtsStream = new adts(); 47835 pipeline.h264Stream = new H264Stream$1(); 47836 pipeline.captionStream = new m2ts_1.CaptionStream(); 47837 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 47838 pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams 47839 47840 pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!! 47841 // demux the streams 47842 47843 pipeline.timestampRolloverStream.pipe(pipeline.h264Stream); 47844 pipeline.timestampRolloverStream.pipe(pipeline.adtsStream); 47845 pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream 47846 47847 pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream); 47848 pipeline.elementaryStream.on('data', function (data) { 47849 var i; 47850 47851 if (data.type === 'metadata') { 47852 i = data.tracks.length; // scan the tracks listed in the metadata 47853 47854 while (i--) { 47855 if (!videoTrack && data.tracks[i].type === 'video') { 47856 videoTrack = data.tracks[i]; 47857 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 47858 } else if (!audioTrack && data.tracks[i].type === 'audio') { 47859 audioTrack = data.tracks[i]; 47860 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 47861 } 47862 } // hook up the video segment stream to the first track with h264 data 47863 47864 47865 if (videoTrack && !pipeline.videoSegmentStream) { 47866 pipeline.coalesceStream.numberOfTracks++; 47867 pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options); 47868 pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) { 47869 // When video emits timelineStartInfo data after a flush, we forward that 47870 // info to the AudioSegmentStream, if it exists, because video timeline 47871 // data takes precedence. Do not do this if keepOriginalTimestamps is set, 47872 // because this is a particularly subtle form of timestamp alteration. 47873 if (audioTrack && !options.keepOriginalTimestamps) { 47874 audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the 47875 // very earliest DTS we have seen in video because Chrome will 47876 // interpret any video track with a baseMediaDecodeTime that is 47877 // non-zero as a gap. 47878 47879 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime); 47880 } 47881 }); 47882 pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo')); 47883 pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo')); 47884 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) { 47885 if (audioTrack) { 47886 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 47887 } 47888 }); 47889 pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline 47890 47891 pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream); 47892 } 47893 47894 if (audioTrack && !pipeline.audioSegmentStream) { 47895 // hook up the audio segment stream to the first track with aac data 47896 pipeline.coalesceStream.numberOfTracks++; 47897 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 47898 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 47899 47900 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 47901 } // emit pmt info 47902 47903 47904 self.trigger('trackinfo', { 47905 hasAudio: !!audioTrack, 47906 hasVideo: !!videoTrack 47907 }); 47908 } 47909 }); // Re-emit any data coming from the coalesce stream to the outside world 47910 47911 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 47912 pipeline.coalesceStream.on('id3Frame', function (id3Frame) { 47913 id3Frame.dispatchType = pipeline.metadataStream.dispatchType; 47914 self.trigger('id3Frame', id3Frame); 47915 }); 47916 pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline 47917 47918 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 47919 }; // hook up the segment streams once track metadata is delivered 47920 47921 47922 this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { 47923 var pipeline = this.transmuxPipeline_; 47924 47925 if (!options.keepOriginalTimestamps) { 47926 this.baseMediaDecodeTime = baseMediaDecodeTime; 47927 } 47928 47929 if (audioTrack) { 47930 audioTrack.timelineStartInfo.dts = undefined; 47931 audioTrack.timelineStartInfo.pts = undefined; 47932 trackDecodeInfo.clearDtsInfo(audioTrack); 47933 47934 if (pipeline.audioTimestampRolloverStream) { 47935 pipeline.audioTimestampRolloverStream.discontinuity(); 47936 } 47937 } 47938 47939 if (videoTrack) { 47940 if (pipeline.videoSegmentStream) { 47941 pipeline.videoSegmentStream.gopCache_ = []; 47942 } 47943 47944 videoTrack.timelineStartInfo.dts = undefined; 47945 videoTrack.timelineStartInfo.pts = undefined; 47946 trackDecodeInfo.clearDtsInfo(videoTrack); 47947 pipeline.captionStream.reset(); 47948 } 47949 47950 if (pipeline.timestampRolloverStream) { 47951 pipeline.timestampRolloverStream.discontinuity(); 47952 } 47953 }; 47954 47955 this.setAudioAppendStart = function (timestamp) { 47956 if (audioTrack) { 47957 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 47958 } 47959 }; 47960 47961 this.setRemux = function (val) { 47962 var pipeline = this.transmuxPipeline_; 47963 options.remux = val; 47964 47965 if (pipeline && pipeline.coalesceStream) { 47966 pipeline.coalesceStream.setRemux(val); 47967 } 47968 }; 47969 47970 this.alignGopsWith = function (gopsToAlignWith) { 47971 if (videoTrack && this.transmuxPipeline_.videoSegmentStream) { 47972 this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith); 47973 } 47974 }; // feed incoming data to the front of the parsing pipeline 47975 47976 47977 this.push = function (data) { 47978 if (hasFlushed) { 47979 var isAac = isLikelyAacData$1(data); 47980 47981 if (isAac && this.transmuxPipeline_.type !== 'aac') { 47982 this.setupAacPipeline(); 47983 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 47984 this.setupTsPipeline(); 47985 } 47986 47987 hasFlushed = false;
vendor: 33,297 bytes, lines 47988-48942
47988 } 47989 47990 this.transmuxPipeline_.headOfPipeline.push(data); 47991 }; // flush any buffered data 47992 47993 47994 this.flush = function () { 47995 hasFlushed = true; // Start at the top of the pipeline and flush all pending work 47996 47997 this.transmuxPipeline_.headOfPipeline.flush(); 47998 }; 47999 48000 this.endTimeline = function () { 48001 this.transmuxPipeline_.headOfPipeline.endTimeline(); 48002 }; 48003 48004 this.reset = function () { 48005 if (this.transmuxPipeline_.headOfPipeline) { 48006 this.transmuxPipeline_.headOfPipeline.reset(); 48007 } 48008 }; // Caption data has to be reset when seeking outside buffered range 48009 48010 48011 this.resetCaptions = function () { 48012 if (this.transmuxPipeline_.captionStream) { 48013 this.transmuxPipeline_.captionStream.reset(); 48014 } 48015 }; 48016 }; 48017 48018 _Transmuxer.prototype = new stream(); 48019 var transmuxer = { 48020 Transmuxer: _Transmuxer, 48021 VideoSegmentStream: _VideoSegmentStream, 48022 AudioSegmentStream: _AudioSegmentStream, 48023 AUDIO_PROPERTIES: audioProperties, 48024 VIDEO_PROPERTIES: videoProperties, 48025 // exported for testing 48026 generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo 48027 }; 48028 var transmuxer_1 = transmuxer.Transmuxer; 48029 /** 48030 * mux.js 48031 * 48032 * Copyright (c) Brightcove 48033 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 48034 */ 48035 48036 var codecs = { 48037 Adts: adts, 48038 h264: h264 48039 }; 48040 var ONE_SECOND_IN_TS$4 = clock.ONE_SECOND_IN_TS; 48041 /** 48042 * Constructs a single-track, ISO BMFF media segment from AAC data 48043 * events. The output of this stream can be fed to a SourceBuffer 48044 * configured with a suitable initialization segment. 48045 */ 48046 48047 var AudioSegmentStream$1 = function AudioSegmentStream$1(track, options) { 48048 var adtsFrames = [], 48049 sequenceNumber = 0, 48050 earliestAllowedDts = 0, 48051 audioAppendStartTs = 0, 48052 videoBaseMediaDecodeTime = Infinity, 48053 segmentStartPts = null, 48054 segmentEndPts = null; 48055 options = options || {}; 48056 AudioSegmentStream$1.prototype.init.call(this); 48057 48058 this.push = function (data) { 48059 trackDecodeInfo.collectDtsInfo(track, data); 48060 48061 if (track) { 48062 audioProperties.forEach(function (prop) { 48063 track[prop] = data[prop]; 48064 }); 48065 } // buffer audio data until end() is called 48066 48067 48068 adtsFrames.push(data); 48069 }; 48070 48071 this.setEarliestDts = function (earliestDts) { 48072 earliestAllowedDts = earliestDts; 48073 }; 48074 48075 this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) { 48076 videoBaseMediaDecodeTime = baseMediaDecodeTime; 48077 }; 48078 48079 this.setAudioAppendStart = function (timestamp) { 48080 audioAppendStartTs = timestamp; 48081 }; 48082 48083 this.processFrames_ = function () { 48084 var frames, moof, mdat, boxes, timingInfo; // return early if no audio data has been observed 48085 48086 if (adtsFrames.length === 0) { 48087 return; 48088 } 48089 48090 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts); 48091 48092 if (frames.length === 0) { 48093 // return early if the frames are all after the earliest allowed DTS 48094 // TODO should we clear the adtsFrames? 48095 return; 48096 } 48097 48098 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 48099 audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to 48100 // samples (that is, adts frames) in the audio data 48101 48102 track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat 48103 48104 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames)); 48105 adtsFrames = []; 48106 moof = mp4Generator.moof(sequenceNumber, [track]); // bump the sequence number for next time 48107 48108 sequenceNumber++; 48109 track.initSegment = mp4Generator.initSegment([track]); // it would be great to allocate this array up front instead of 48110 // throwing away hundreds of media segment fragments 48111 48112 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); 48113 boxes.set(moof); 48114 boxes.set(mdat, moof.byteLength); 48115 trackDecodeInfo.clearDtsInfo(track); 48116 48117 if (segmentStartPts === null) { 48118 segmentEndPts = segmentStartPts = frames[0].pts; 48119 } 48120 48121 segmentEndPts += frames.length * (ONE_SECOND_IN_TS$4 * 1024 / track.samplerate); 48122 timingInfo = { 48123 start: segmentStartPts 48124 }; 48125 this.trigger('timingInfo', timingInfo); 48126 this.trigger('data', { 48127 track: track, 48128 boxes: boxes 48129 }); 48130 }; 48131 48132 this.flush = function () { 48133 this.processFrames_(); // trigger final timing info 48134 48135 this.trigger('timingInfo', { 48136 start: segmentStartPts, 48137 end: segmentEndPts 48138 }); 48139 this.resetTiming_(); 48140 this.trigger('done', 'AudioSegmentStream'); 48141 }; 48142 48143 this.partialFlush = function () { 48144 this.processFrames_(); 48145 this.trigger('partialdone', 'AudioSegmentStream'); 48146 }; 48147 48148 this.endTimeline = function () { 48149 this.flush(); 48150 this.trigger('endedtimeline', 'AudioSegmentStream'); 48151 }; 48152 48153 this.resetTiming_ = function () { 48154 trackDecodeInfo.clearDtsInfo(track); 48155 segmentStartPts = null; 48156 segmentEndPts = null; 48157 }; 48158 48159 this.reset = function () { 48160 this.resetTiming_(); 48161 adtsFrames = []; 48162 this.trigger('reset'); 48163 }; 48164 }; 48165 48166 AudioSegmentStream$1.prototype = new stream(); 48167 var audioSegmentStream = AudioSegmentStream$1; 48168 48169 var VideoSegmentStream$1 = function VideoSegmentStream$1(track, options) { 48170 var sequenceNumber = 0, 48171 nalUnits = [], 48172 frameCache = [], 48173 // gopsToAlignWith = [], 48174 config, 48175 pps, 48176 segmentStartPts = null, 48177 segmentEndPts = null, 48178 gops, 48179 ensureNextFrameIsKeyFrame = true; 48180 options = options || {}; 48181 VideoSegmentStream$1.prototype.init.call(this); 48182 48183 this.push = function (nalUnit) { 48184 trackDecodeInfo.collectDtsInfo(track, nalUnit); 48185 48186 if (typeof track.timelineStartInfo.dts === 'undefined') { 48187 track.timelineStartInfo.dts = nalUnit.dts; 48188 } // record the track config 48189 48190 48191 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 48192 config = nalUnit.config; 48193 track.sps = [nalUnit.data]; 48194 videoProperties.forEach(function (prop) { 48195 track[prop] = config[prop]; 48196 }, this); 48197 } 48198 48199 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) { 48200 pps = nalUnit.data; 48201 track.pps = [nalUnit.data]; 48202 } // buffer video until flush() is called 48203 48204 48205 nalUnits.push(nalUnit); 48206 }; 48207 48208 this.processNals_ = function (cacheLastFrame) { 48209 var i; 48210 nalUnits = frameCache.concat(nalUnits); // Throw away nalUnits at the start of the byte stream until 48211 // we find the first AUD 48212 48213 while (nalUnits.length) { 48214 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 48215 break; 48216 } 48217 48218 nalUnits.shift(); 48219 } // Return early if no video data has been observed 48220 48221 48222 if (nalUnits.length === 0) { 48223 return; 48224 } 48225 48226 var frames = frameUtils.groupNalsIntoFrames(nalUnits); 48227 48228 if (!frames.length) { 48229 return; 48230 } // note that the frame cache may also protect us from cases where we haven't 48231 // pushed data for the entire first or last frame yet 48232 48233 48234 frameCache = frames[frames.length - 1]; 48235 48236 if (cacheLastFrame) { 48237 frames.pop(); 48238 frames.duration -= frameCache.duration; 48239 frames.nalCount -= frameCache.length; 48240 frames.byteLength -= frameCache.byteLength; 48241 } 48242 48243 if (!frames.length) { 48244 nalUnits = []; 48245 return; 48246 } 48247 48248 this.trigger('timelineStartInfo', track.timelineStartInfo); 48249 48250 if (ensureNextFrameIsKeyFrame) { 48251 gops = frameUtils.groupFramesIntoGops(frames); 48252 48253 if (!gops[0][0].keyFrame) { 48254 gops = frameUtils.extendFirstKeyFrame(gops); 48255 48256 if (!gops[0][0].keyFrame) { 48257 // we haven't yet gotten a key frame, so reset nal units to wait for more nal 48258 // units 48259 nalUnits = [].concat.apply([], frames).concat(frameCache); 48260 frameCache = []; 48261 return; 48262 } 48263 48264 frames = [].concat.apply([], gops); 48265 frames.duration = gops.duration; 48266 } 48267 48268 ensureNextFrameIsKeyFrame = false; 48269 } 48270 48271 if (segmentStartPts === null) { 48272 segmentStartPts = frames[0].pts; 48273 segmentEndPts = segmentStartPts; 48274 } 48275 48276 segmentEndPts += frames.duration; 48277 this.trigger('timingInfo', { 48278 start: segmentStartPts, 48279 end: segmentEndPts 48280 }); 48281 48282 for (i = 0; i < frames.length; i++) { 48283 var frame = frames[i]; 48284 track.samples = frameUtils.generateSampleTableForFrame(frame); 48285 var mdat = mp4Generator.mdat(frameUtils.concatenateNalDataForFrame(frame)); 48286 trackDecodeInfo.clearDtsInfo(track); 48287 trackDecodeInfo.collectDtsInfo(track, frame); 48288 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 48289 var moof = mp4Generator.moof(sequenceNumber, [track]); 48290 sequenceNumber++; 48291 track.initSegment = mp4Generator.initSegment([track]); 48292 var boxes = new Uint8Array(moof.byteLength + mdat.byteLength); 48293 boxes.set(moof); 48294 boxes.set(mdat, moof.byteLength); 48295 this.trigger('data', { 48296 track: track, 48297 boxes: boxes, 48298 sequence: sequenceNumber, 48299 videoFrameDts: frame.dts, 48300 videoFramePts: frame.pts 48301 }); 48302 } 48303 48304 nalUnits = []; 48305 }; 48306 48307 this.resetTimingAndConfig_ = function () { 48308 config = undefined; 48309 pps = undefined; 48310 segmentStartPts = null; 48311 segmentEndPts = null; 48312 }; 48313 48314 this.partialFlush = function () { 48315 this.processNals_(true); 48316 this.trigger('partialdone', 'VideoSegmentStream'); 48317 }; 48318 48319 this.flush = function () { 48320 this.processNals_(false); // reset config and pps because they may differ across segments 48321 // for instance, when we are rendition switching 48322 48323 this.resetTimingAndConfig_(); 48324 this.trigger('done', 'VideoSegmentStream'); 48325 }; 48326 48327 this.endTimeline = function () { 48328 this.flush(); 48329 this.trigger('endedtimeline', 'VideoSegmentStream'); 48330 }; 48331 48332 this.reset = function () { 48333 this.resetTimingAndConfig_(); 48334 frameCache = []; 48335 nalUnits = []; 48336 ensureNextFrameIsKeyFrame = true; 48337 this.trigger('reset'); 48338 }; 48339 }; 48340 48341 VideoSegmentStream$1.prototype = new stream(); 48342 var videoSegmentStream = VideoSegmentStream$1; 48343 var isLikelyAacData$2 = utils.isLikelyAacData; 48344 48345 var createPipeline = function createPipeline(object) { 48346 object.prototype = new stream(); 48347 object.prototype.init.call(object); 48348 return object; 48349 }; 48350 48351 var tsPipeline = function tsPipeline(options) { 48352 var pipeline = { 48353 type: 'ts', 48354 tracks: { 48355 audio: null, 48356 video: null 48357 }, 48358 packet: new m2ts_1.TransportPacketStream(), 48359 parse: new m2ts_1.TransportParseStream(), 48360 elementary: new m2ts_1.ElementaryStream(), 48361 timestampRollover: new m2ts_1.TimestampRolloverStream(), 48362 adts: new codecs.Adts(), 48363 h264: new codecs.h264.H264Stream(), 48364 captionStream: new m2ts_1.CaptionStream(), 48365 metadataStream: new m2ts_1.MetadataStream() 48366 }; 48367 pipeline.headOfPipeline = pipeline.packet; // Transport Stream 48368 48369 pipeline.packet.pipe(pipeline.parse).pipe(pipeline.elementary).pipe(pipeline.timestampRollover); // H264 48370 48371 pipeline.timestampRollover.pipe(pipeline.h264); // Hook up CEA-608/708 caption stream 48372 48373 pipeline.h264.pipe(pipeline.captionStream); 48374 pipeline.timestampRollover.pipe(pipeline.metadataStream); // ADTS 48375 48376 pipeline.timestampRollover.pipe(pipeline.adts); 48377 pipeline.elementary.on('data', function (data) { 48378 if (data.type !== 'metadata') { 48379 return; 48380 } 48381 48382 for (var i = 0; i < data.tracks.length; i++) { 48383 if (!pipeline.tracks[data.tracks[i].type]) { 48384 pipeline.tracks[data.tracks[i].type] = data.tracks[i]; 48385 pipeline.tracks[data.tracks[i].type].timelineStartInfo.baseMediaDecodeTime = options.baseMediaDecodeTime; 48386 } 48387 } 48388 48389 if (pipeline.tracks.video && !pipeline.videoSegmentStream) { 48390 pipeline.videoSegmentStream = new videoSegmentStream(pipeline.tracks.video, options); 48391 pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) { 48392 if (pipeline.tracks.audio && !options.keepOriginalTimestamps) { 48393 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - options.baseMediaDecodeTime); 48394 } 48395 }); 48396 pipeline.videoSegmentStream.on('timingInfo', pipeline.trigger.bind(pipeline, 'videoTimingInfo')); 48397 pipeline.videoSegmentStream.on('data', function (data) { 48398 pipeline.trigger('data', { 48399 type: 'video', 48400 data: data 48401 }); 48402 }); 48403 pipeline.videoSegmentStream.on('done', pipeline.trigger.bind(pipeline, 'done')); 48404 pipeline.videoSegmentStream.on('partialdone', pipeline.trigger.bind(pipeline, 'partialdone')); 48405 pipeline.videoSegmentStream.on('endedtimeline', pipeline.trigger.bind(pipeline, 'endedtimeline')); 48406 pipeline.h264.pipe(pipeline.videoSegmentStream); 48407 } 48408 48409 if (pipeline.tracks.audio && !pipeline.audioSegmentStream) { 48410 pipeline.audioSegmentStream = new audioSegmentStream(pipeline.tracks.audio, options); 48411 pipeline.audioSegmentStream.on('data', function (data) { 48412 pipeline.trigger('data', { 48413 type: 'audio', 48414 data: data 48415 }); 48416 }); 48417 pipeline.audioSegmentStream.on('done', pipeline.trigger.bind(pipeline, 'done')); 48418 pipeline.audioSegmentStream.on('partialdone', pipeline.trigger.bind(pipeline, 'partialdone')); 48419 pipeline.audioSegmentStream.on('endedtimeline', pipeline.trigger.bind(pipeline, 'endedtimeline')); 48420 pipeline.audioSegmentStream.on('timingInfo', pipeline.trigger.bind(pipeline, 'audioTimingInfo')); 48421 pipeline.adts.pipe(pipeline.audioSegmentStream); 48422 } // emit pmt info 48423 48424 48425 pipeline.trigger('trackinfo', { 48426 hasAudio: !!pipeline.tracks.audio, 48427 hasVideo: !!pipeline.tracks.video 48428 }); 48429 }); 48430 pipeline.captionStream.on('data', function (caption) { 48431 var timelineStartPts; 48432 48433 if (pipeline.tracks.video) { 48434 timelineStartPts = pipeline.tracks.video.timelineStartInfo.pts || 0; 48435 } else { 48436 // This will only happen if we encounter caption packets before 48437 // video data in a segment. This is an unusual/unlikely scenario, 48438 // so we assume the timeline starts at zero for now. 48439 timelineStartPts = 0; 48440 } // Translate caption PTS times into second offsets into the 48441 // video timeline for the segment 48442 48443 48444 caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, options.keepOriginalTimestamps); 48445 caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, options.keepOriginalTimestamps); 48446 pipeline.trigger('caption', caption); 48447 }); 48448 pipeline = createPipeline(pipeline); 48449 pipeline.metadataStream.on('data', pipeline.trigger.bind(pipeline, 'id3Frame')); 48450 return pipeline; 48451 }; 48452 48453 var aacPipeline = function aacPipeline(options) { 48454 var pipeline = { 48455 type: 'aac', 48456 tracks: { 48457 audio: null 48458 }, 48459 metadataStream: new m2ts_1.MetadataStream(), 48460 aacStream: new aac(), 48461 audioRollover: new m2ts_1.TimestampRolloverStream('audio'), 48462 timedMetadataRollover: new m2ts_1.TimestampRolloverStream('timed-metadata'), 48463 adtsStream: new adts(true) 48464 }; // set up the parsing pipeline 48465 48466 pipeline.headOfPipeline = pipeline.aacStream; 48467 pipeline.aacStream.pipe(pipeline.audioRollover).pipe(pipeline.adtsStream); 48468 pipeline.aacStream.pipe(pipeline.timedMetadataRollover).pipe(pipeline.metadataStream); 48469 pipeline.metadataStream.on('timestamp', function (frame) { 48470 pipeline.aacStream.setTimestamp(frame.timeStamp); 48471 }); 48472 pipeline.aacStream.on('data', function (data) { 48473 if (data.type !== 'timed-metadata' && data.type !== 'audio' || pipeline.audioSegmentStream) { 48474 return; 48475 } 48476 48477 pipeline.tracks.audio = pipeline.tracks.audio || { 48478 timelineStartInfo: { 48479 baseMediaDecodeTime: options.baseMediaDecodeTime 48480 }, 48481 codec: 'adts', 48482 type: 'audio' 48483 }; // hook up the audio segment stream to the first track with aac data 48484 48485 pipeline.audioSegmentStream = new audioSegmentStream(pipeline.tracks.audio, options); 48486 pipeline.audioSegmentStream.on('data', function (data) { 48487 pipeline.trigger('data', { 48488 type: 'audio', 48489 data: data 48490 }); 48491 }); 48492 pipeline.audioSegmentStream.on('partialdone', pipeline.trigger.bind(pipeline, 'partialdone')); 48493 pipeline.audioSegmentStream.on('done', pipeline.trigger.bind(pipeline, 'done')); 48494 pipeline.audioSegmentStream.on('endedtimeline', pipeline.trigger.bind(pipeline, 'endedtimeline')); 48495 pipeline.audioSegmentStream.on('timingInfo', pipeline.trigger.bind(pipeline, 'audioTimingInfo')); // Set up the final part of the audio pipeline 48496 48497 pipeline.adtsStream.pipe(pipeline.audioSegmentStream); 48498 pipeline.trigger('trackinfo', { 48499 hasAudio: !!pipeline.tracks.audio, 48500 hasVideo: !!pipeline.tracks.video 48501 }); 48502 }); // set the pipeline up as a stream before binding to get access to the trigger function 48503 48504 pipeline = createPipeline(pipeline); 48505 pipeline.metadataStream.on('data', pipeline.trigger.bind(pipeline, 'id3Frame')); 48506 return pipeline; 48507 }; 48508 48509 var setupPipelineListeners = function setupPipelineListeners(pipeline, transmuxer) { 48510 pipeline.on('data', transmuxer.trigger.bind(transmuxer, 'data')); 48511 pipeline.on('done', transmuxer.trigger.bind(transmuxer, 'done')); 48512 pipeline.on('partialdone', transmuxer.trigger.bind(transmuxer, 'partialdone')); 48513 pipeline.on('endedtimeline', transmuxer.trigger.bind(transmuxer, 'endedtimeline')); 48514 pipeline.on('audioTimingInfo', transmuxer.trigger.bind(transmuxer, 'audioTimingInfo')); 48515 pipeline.on('videoTimingInfo', transmuxer.trigger.bind(transmuxer, 'videoTimingInfo')); 48516 pipeline.on('trackinfo', transmuxer.trigger.bind(transmuxer, 'trackinfo')); 48517 pipeline.on('id3Frame', function (event) { 48518 // add this to every single emitted segment even though it's only needed for the first 48519 event.dispatchType = pipeline.metadataStream.dispatchType; // keep original time, can be adjusted if needed at a higher level 48520 48521 event.cueTime = clock.videoTsToSeconds(event.pts); 48522 transmuxer.trigger('id3Frame', event); 48523 }); 48524 pipeline.on('caption', function (event) { 48525 transmuxer.trigger('caption', event); 48526 }); 48527 }; 48528 48529 var Transmuxer$1 = function Transmuxer$1(options) { 48530 var pipeline = null, 48531 hasFlushed = true; 48532 options = options || {}; 48533 Transmuxer$1.prototype.init.call(this); 48534 options.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 48535 48536 this.push = function (bytes) { 48537 if (hasFlushed) { 48538 var isAac = isLikelyAacData$2(bytes); 48539 48540 if (isAac && (!pipeline || pipeline.type !== 'aac')) { 48541 pipeline = aacPipeline(options); 48542 setupPipelineListeners(pipeline, this); 48543 } else if (!isAac && (!pipeline || pipeline.type !== 'ts')) { 48544 pipeline = tsPipeline(options); 48545 setupPipelineListeners(pipeline, this); 48546 } 48547 48548 hasFlushed = false; 48549 } 48550 48551 pipeline.headOfPipeline.push(bytes); 48552 }; 48553 48554 this.flush = function () { 48555 if (!pipeline) { 48556 return; 48557 } 48558 48559 hasFlushed = true; 48560 pipeline.headOfPipeline.flush(); 48561 }; 48562 48563 this.partialFlush = function () { 48564 if (!pipeline) { 48565 return; 48566 } 48567 48568 pipeline.headOfPipeline.partialFlush(); 48569 }; 48570 48571 this.endTimeline = function () { 48572 if (!pipeline) { 48573 return; 48574 } 48575 48576 pipeline.headOfPipeline.endTimeline(); 48577 }; 48578 48579 this.reset = function () { 48580 if (!pipeline) { 48581 return; 48582 } 48583 48584 pipeline.headOfPipeline.reset(); 48585 }; 48586 48587 this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { 48588 if (!options.keepOriginalTimestamps) { 48589 options.baseMediaDecodeTime = baseMediaDecodeTime; 48590 } 48591 48592 if (!pipeline) { 48593 return; 48594 } 48595 48596 if (pipeline.tracks.audio) { 48597 pipeline.tracks.audio.timelineStartInfo.dts = undefined; 48598 pipeline.tracks.audio.timelineStartInfo.pts = undefined; 48599 trackDecodeInfo.clearDtsInfo(pipeline.tracks.audio); 48600 48601 if (pipeline.audioRollover) { 48602 pipeline.audioRollover.discontinuity(); 48603 } 48604 } 48605 48606 if (pipeline.tracks.video) { 48607 if (pipeline.videoSegmentStream) { 48608 pipeline.videoSegmentStream.gopCache_ = []; 48609 } 48610 48611 pipeline.tracks.video.timelineStartInfo.dts = undefined; 48612 pipeline.tracks.video.timelineStartInfo.pts = undefined; 48613 trackDecodeInfo.clearDtsInfo(pipeline.tracks.video); // pipeline.captionStream.reset(); 48614 } 48615 48616 if (pipeline.timestampRollover) { 48617 pipeline.timestampRollover.discontinuity(); 48618 } 48619 }; 48620 48621 this.setRemux = function (val) { 48622 options.remux = val; 48623 48624 if (pipeline && pipeline.coalesceStream) { 48625 pipeline.coalesceStream.setRemux(val); 48626 } 48627 }; 48628 48629 this.setAudioAppendStart = function (audioAppendStart) { 48630 if (!pipeline || !pipeline.tracks.audio || !pipeline.audioSegmentStream) { 48631 return; 48632 } 48633 48634 pipeline.audioSegmentStream.setAudioAppendStart(audioAppendStart); 48635 }; // TODO GOP alignment support 48636 // Support may be a bit trickier than with full segment appends, as GOPs may be split 48637 // and processed in a more granular fashion 48638 48639 48640 this.alignGopsWith = function (gopsToAlignWith) { 48641 return; 48642 }; 48643 }; 48644 48645 Transmuxer$1.prototype = new stream(); 48646 var transmuxer$1 = Transmuxer$1; 48647 /** 48648 * mux.js 48649 * 48650 * Copyright (c) Brightcove 48651 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 48652 */ 48653 48654 var toUnsigned = function toUnsigned(value) { 48655 return value >>> 0; 48656 }; 48657 48658 var toHexString = function toHexString(value) { 48659 return ('00' + value.toString(16)).slice(-2); 48660 }; 48661 48662 var bin = { 48663 toUnsigned: toUnsigned, 48664 toHexString: toHexString 48665 }; 48666 48667 var parseType$1 = function parseType$1(buffer) { 48668 var result = ''; 48669 result += String.fromCharCode(buffer[0]); 48670 result += String.fromCharCode(buffer[1]); 48671 result += String.fromCharCode(buffer[2]); 48672 result += String.fromCharCode(buffer[3]); 48673 return result; 48674 }; 48675 48676 var parseType_1 = parseType$1; 48677 var toUnsigned$1 = bin.toUnsigned; 48678 48679 var findBox = function findBox(data, path) { 48680 var results = [], 48681 i, 48682 size, 48683 type, 48684 end, 48685 subresults; 48686 48687 if (!path.length) { 48688 // short-circuit the search for empty paths 48689 return null; 48690 } 48691 48692 for (i = 0; i < data.byteLength;) { 48693 size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]); 48694 type = parseType_1(data.subarray(i + 4, i + 8)); 48695 end = size > 1 ? i + size : data.byteLength; 48696 48697 if (type === path[0]) { 48698 if (path.length === 1) { 48699 // this is the end of the path and we've found the box we were 48700 // looking for 48701 results.push(data.subarray(i + 8, end)); 48702 } else { 48703 // recursively search for the next box along the path 48704 subresults = findBox(data.subarray(i + 8, end), path.slice(1)); 48705 48706 if (subresults.length) { 48707 results = results.concat(subresults); 48708 } 48709 } 48710 } 48711 48712 i = end; 48713 } // we've finished searching all of data 48714 48715 48716 return results; 48717 }; 48718 48719 var findBox_1 = findBox; 48720 var toUnsigned$2 = bin.toUnsigned; 48721 48722 var tfdt = function tfdt(data) { 48723 var result = { 48724 version: data[0], 48725 flags: new Uint8Array(data.subarray(1, 4)), 48726 baseMediaDecodeTime: toUnsigned$2(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]) 48727 }; 48728 48729 if (result.version === 1) { 48730 result.baseMediaDecodeTime *= Math.pow(2, 32); 48731 result.baseMediaDecodeTime += toUnsigned$2(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]); 48732 } 48733 48734 return result; 48735 }; 48736 48737 var parseTfdt = tfdt; 48738 48739 var parseSampleFlags = function parseSampleFlags(flags) { 48740 return { 48741 isLeading: (flags[0] & 0x0c) >>> 2, 48742 dependsOn: flags[0] & 0x03, 48743 isDependedOn: (flags[1] & 0xc0) >>> 6, 48744 hasRedundancy: (flags[1] & 0x30) >>> 4, 48745 paddingValue: (flags[1] & 0x0e) >>> 1, 48746 isNonSyncSample: flags[1] & 0x01, 48747 degradationPriority: flags[2] << 8 | flags[3] 48748 }; 48749 }; 48750 48751 var parseSampleFlags_1 = parseSampleFlags; 48752 48753 var trun$1 = function trun$1(data) { 48754 var result = { 48755 version: data[0], 48756 flags: new Uint8Array(data.subarray(1, 4)), 48757 samples: [] 48758 }, 48759 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 48760 // Flag interpretation 48761 dataOffsetPresent = result.flags[2] & 0x01, 48762 // compare with 2nd byte of 0x1 48763 firstSampleFlagsPresent = result.flags[2] & 0x04, 48764 // compare with 2nd byte of 0x4 48765 sampleDurationPresent = result.flags[1] & 0x01, 48766 // compare with 2nd byte of 0x100 48767 sampleSizePresent = result.flags[1] & 0x02, 48768 // compare with 2nd byte of 0x200 48769 sampleFlagsPresent = result.flags[1] & 0x04, 48770 // compare with 2nd byte of 0x400 48771 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, 48772 // compare with 2nd byte of 0x800 48773 sampleCount = view.getUint32(4), 48774 offset = 8, 48775 sample; 48776 48777 if (dataOffsetPresent) { 48778 // 32 bit signed integer 48779 result.dataOffset = view.getInt32(offset); 48780 offset += 4; 48781 } // Overrides the flags for the first sample only. The order of 48782 // optional values will be: duration, size, compositionTimeOffset 48783 48784 48785 if (firstSampleFlagsPresent && sampleCount) { 48786 sample = { 48787 flags: parseSampleFlags_1(data.subarray(offset, offset + 4)) 48788 }; 48789 offset += 4; 48790 48791 if (sampleDurationPresent) { 48792 sample.duration = view.getUint32(offset); 48793 offset += 4; 48794 } 48795 48796 if (sampleSizePresent) { 48797 sample.size = view.getUint32(offset); 48798 offset += 4; 48799 } 48800 48801 if (sampleCompositionTimeOffsetPresent) { 48802 if (result.version === 1) { 48803 sample.compositionTimeOffset = view.getInt32(offset); 48804 } else { 48805 sample.compositionTimeOffset = view.getUint32(offset); 48806 } 48807 48808 offset += 4; 48809 } 48810 48811 result.samples.push(sample); 48812 sampleCount--; 48813 } 48814 48815 while (sampleCount--) { 48816 sample = {}; 48817 48818 if (sampleDurationPresent) { 48819 sample.duration = view.getUint32(offset); 48820 offset += 4; 48821 } 48822 48823 if (sampleSizePresent) { 48824 sample.size = view.getUint32(offset); 48825 offset += 4; 48826 } 48827 48828 if (sampleFlagsPresent) { 48829 sample.flags = parseSampleFlags_1(data.subarray(offset, offset + 4)); 48830 offset += 4; 48831 } 48832 48833 if (sampleCompositionTimeOffsetPresent) { 48834 if (result.version === 1) { 48835 sample.compositionTimeOffset = view.getInt32(offset); 48836 } else { 48837 sample.compositionTimeOffset = view.getUint32(offset); 48838 } 48839 48840 offset += 4; 48841 } 48842 48843 result.samples.push(sample); 48844 } 48845 48846 return result; 48847 }; 48848 48849 var parseTrun = trun$1; 48850 48851 var tfhd = function tfhd(data) { 48852 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 48853 result = { 48854 version: data[0], 48855 flags: new Uint8Array(data.subarray(1, 4)), 48856 trackId: view.getUint32(4) 48857 }, 48858 baseDataOffsetPresent = result.flags[2] & 0x01, 48859 sampleDescriptionIndexPresent = result.flags[2] & 0x02, 48860 defaultSampleDurationPresent = result.flags[2] & 0x08, 48861 defaultSampleSizePresent = result.flags[2] & 0x10, 48862 defaultSampleFlagsPresent = result.flags[2] & 0x20, 48863 durationIsEmpty = result.flags[0] & 0x010000, 48864 defaultBaseIsMoof = result.flags[0] & 0x020000, 48865 i; 48866 i = 8; 48867 48868 if (baseDataOffsetPresent) { 48869 i += 4; // truncate top 4 bytes 48870 // FIXME: should we read the full 64 bits? 48871 48872 result.baseDataOffset = view.getUint32(12); 48873 i += 4; 48874 } 48875 48876 if (sampleDescriptionIndexPresent) { 48877 result.sampleDescriptionIndex = view.getUint32(i); 48878 i += 4; 48879 } 48880 48881 if (defaultSampleDurationPresent) { 48882 result.defaultSampleDuration = view.getUint32(i); 48883 i += 4; 48884 } 48885 48886 if (defaultSampleSizePresent) { 48887 result.defaultSampleSize = view.getUint32(i); 48888 i += 4; 48889 } 48890 48891 if (defaultSampleFlagsPresent) { 48892 result.defaultSampleFlags = view.getUint32(i); 48893 } 48894 48895 if (durationIsEmpty) { 48896 result.durationIsEmpty = true; 48897 } 48898 48899 if (!baseDataOffsetPresent && defaultBaseIsMoof) { 48900 result.baseDataOffsetIsMoof = true; 48901 } 48902 48903 return result; 48904 }; 48905 48906 var parseTfhd = tfhd; 48907 var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes; 48908 var CaptionStream$1 = captionStream.CaptionStream; 48909 /** 48910 * Maps an offset in the mdat to a sample based on the the size of the samples. 48911 * Assumes that `parseSamples` has been called first. 48912 * 48913 * @param {Number} offset - The offset into the mdat 48914 * @param {Object[]} samples - An array of samples, parsed using `parseSamples` 48915 * @return {?Object} The matching sample, or null if no match was found. 48916 * 48917 * @see ISO-BMFF-12/2015, Section 8.8.8 48918 **/ 48919 48920 var mapToSample = function mapToSample(offset, samples) { 48921 var approximateOffset = offset; 48922 48923 for (var i = 0; i < samples.length; i++) { 48924 var sample = samples[i]; 48925 48926 if (approximateOffset < sample.size) { 48927 return sample; 48928 } 48929 48930 approximateOffset -= sample.size; 48931 } 48932 48933 return null; 48934 }; 48935 /** 48936 * Finds SEI nal units contained in a Media Data Box. 48937 * Assumes that `parseSamples` has been called first. 48938 * 48939 * @param {Uint8Array} avcStream - The bytes of the mdat 48940 * @param {Object[]} samples - The samples parsed out by `parseSamples` 48941 * @param {Number} trackId - The trackId of this video track 48942 * @return {Object[]}
48942 seiNals - the parsed SEI NALUs found. 48943 * The contents of the seiNal should match what is expected by 48944 * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts) 48945 * 48946 * @see ISO-BMFF-12/2015, Section 8.1.1 48947 * @see Rec. ITU-T H.264, 7.3.2.3.1 48948 **/ 48949 48950 48951 var findSeiNals = function findSeiNals(avcStream, samples, trackId) { 48952 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 48953 result = [], 48954 seiNal, 48955 i, 48956 length, 48957 lastMatchedSample; 48958 48959 for (i = 0; i + 4 < avcStream.length; i += length) { 48960 length = avcView.getUint32(i); 48961 i += 4; // Bail if this doesn't appear to be an H264 stream 48962 48963 if (length <= 0) { 48964 continue; 48965 } 48966 48967 switch (avcStream[i] & 0x1F) { 48968 case 0x06: 48969 var data = avcStream.subarray(i + 1, i + 1 + length); 48970 var matchingSample = mapToSample(i, samples); 48971 seiNal = { 48972 nalUnitType: 'sei_rbsp', 48973 size: length, 48974 data: data, 48975 escapedRBSP: discardEmulationPreventionBytes$1(data), 48976 trackId: trackId 48977 }; 48978 48979 if (matchingSample) { 48980 seiNal.pts = matchingSample.pts; 48981 seiNal.dts = matchingSample.dts; 48982 lastMatchedSample = matchingSample; 48983 } else if (lastMatchedSample) { 48984 // If a matching sample cannot be found, use the last 48985 // sample's values as they should be as close as possible 48986 seiNal.pts = lastMatchedSample.pts; 48987 seiNal.dts = lastMatchedSample.dts; 48988 } else { 48989 // eslint-disable-next-line no-console 48990 console.log("We've encountered a nal unit without data. See mux.js#233."); 48991 break; 48992 } 48993 48994 result.push(seiNal); 48995 break; 48996 } 48997 } 48998 48999 return result; 49000 }; 49001 /** 49002 * Parses sample information out of Track Run Boxes and calculates 49003 * the absolute presentation and decode timestamps of each sample. 49004 * 49005 * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed 49006 * @param {Number} baseMediaDecodeTime - base media decode time from tfdt 49007 @see ISO-BMFF-12/2015, Section 8.8.12 49008 * @param {Object} tfhd - The parsed Track Fragment Header 49009 * @see inspect.parseTfhd 49010 * @return {Object[]} the parsed samples 49011 * 49012 * @see ISO-BMFF-12/2015, Section 8.8.8 49013 **/ 49014 49015 49016 var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) { 49017 var currentDts = baseMediaDecodeTime; 49018 var defaultSampleDuration = tfhd.defaultSampleDuration || 0; 49019 var defaultSampleSize = tfhd.defaultSampleSize || 0; 49020 var trackId = tfhd.trackId; 49021 var allSamples = []; 49022 truns.forEach(function (trun) { 49023 // Note: We currently do not parse the sample table as well 49024 // as the trun. It's possible some sources will require this. 49025 // moov > trak > mdia > minf > stbl 49026 var trackRun = parseTrun(trun); 49027 var samples = trackRun.samples; 49028 samples.forEach(function (sample) { 49029 if (sample.duration === undefined) { 49030 sample.duration = defaultSampleDuration; 49031 } 49032 49033 if (sample.size === undefined) { 49034 sample.size = defaultSampleSize; 49035 } 49036 49037 sample.trackId = trackId; 49038 sample.dts = currentDts; 49039 49040 if (sample.compositionTimeOffset === undefined) { 49041 sample.compositionTimeOffset = 0; 49042 } 49043 49044 sample.pts = currentDts + sample.compositionTimeOffset; 49045 currentDts += sample.duration; 49046 }); 49047 allSamples = allSamples.concat(samples); 49048 }); 49049 return allSamples; 49050 }; 49051 /** 49052 * Parses out caption nals from an FMP4 segment's video tracks. 49053 * 49054 * @param {Uint8Array} segment - The bytes of a single segment 49055 * @param {Number} videoTrackId - The trackId of a video track in the segment 49056 * @return {Object.<Number, Object[]>} A mapping of video trackId to 49057 * a list of seiNals found in that track 49058 **/ 49059 49060 49061 var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) { 49062 // To get the samples 49063 var trafs = findBox_1(segment, ['moof', 'traf']); // To get SEI NAL units 49064 49065 var mdats = findBox_1(segment, ['mdat']); 49066 var captionNals = {}; 49067 var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs 49068
vendor: 3,962 bytes, lines 49069-49171
49069 mdats.forEach(function (mdat, index) { 49070 var matchingTraf = trafs[index]; 49071 mdatTrafPairs.push({ 49072 mdat: mdat, 49073 traf: matchingTraf 49074 }); 49075 }); 49076 mdatTrafPairs.forEach(function (pair) { 49077 var mdat = pair.mdat; 49078 var traf = pair.traf; 49079 var tfhd = findBox_1(traf, ['tfhd']); // Exactly 1 tfhd per traf 49080 49081 var headerInfo = parseTfhd(tfhd[0]); 49082 var trackId = headerInfo.trackId; 49083 var tfdt = findBox_1(traf, ['tfdt']); // Either 0 or 1 tfdt per traf 49084 49085 var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt(tfdt[0]).baseMediaDecodeTime : 0; 49086 var truns = findBox_1(traf, ['trun']); 49087 var samples; 49088 var seiNals; // Only parse video data for the chosen video track 49089 49090 if (videoTrackId === trackId && truns.length > 0) { 49091 samples = parseSamples(truns, baseMediaDecodeTime, headerInfo); 49092 seiNals = findSeiNals(mdat, samples, trackId); 49093 49094 if (!captionNals[trackId]) { 49095 captionNals[trackId] = []; 49096 } 49097 49098 captionNals[trackId] = captionNals[trackId].concat(seiNals); 49099 } 49100 }); 49101 return captionNals; 49102 }; 49103 /** 49104 * Parses out inband captions from an MP4 container and returns 49105 * caption objects that can be used by WebVTT and the TextTrack API. 49106 * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue 49107 * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack 49108 * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first 49109 * 49110 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 49111 * @param {Number} trackId - The id of the video track to parse 49112 * @param {Number} timescale - The timescale for the video track from the init segment 49113 * 49114 * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks 49115 * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds 49116 * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds 49117 * @return {String} parsedCaptions[].text - The visible content of the caption 49118 **/ 49119 49120 49121 var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) { 49122 var seiNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 49123 49124 if (trackId === null) { 49125 return null; 49126 } 49127 49128 seiNals = parseCaptionNals(segment, trackId); 49129 return { 49130 seiNals: seiNals[trackId], 49131 timescale: timescale 49132 }; 49133 }; 49134 /** 49135 * Converts SEI NALUs into captions that can be used by video.js 49136 **/ 49137 49138 49139 var CaptionParser = function CaptionParser() { 49140 var isInitialized = false; 49141 var captionStream; // Stores segments seen before trackId and timescale are set 49142 49143 var segmentCache; // Stores video track ID of the track being parsed 49144 49145 var trackId; // Stores the timescale of the track being parsed 49146 49147 var timescale; // Stores captions parsed so far 49148 49149 var parsedCaptions; // Stores whether we are receiving partial data or not 49150 49151 var parsingPartial; 49152 /** 49153 * A method to indicate whether a CaptionParser has been initalized 49154 * @returns {Boolean} 49155 **/ 49156 49157 this.isInitialized = function () { 49158 return isInitialized; 49159 }; 49160 /** 49161 * Initializes the underlying CaptionStream, SEI NAL parsing 49162 * and management, and caption collection 49163 **/ 49164 49165 49166 this.init = function (options) { 49167 captionStream = new CaptionStream$1(); 49168 isInitialized = true; 49169 parsingPartial = options ? options.isPartial : false; // Collect dispatched captions 49170 49171 captionStream.on('data', function (event) {
vendor: 4,888 bytes, lines 49172-49321
49172 // Convert to seconds in the source's timescale 49173 event.startTime = event.startPts / timescale; 49174 event.endTime = event.endPts / timescale; 49175 parsedCaptions.captions.push(event); 49176 parsedCaptions.captionStreams[event.stream] = true; 49177 }); 49178 }; 49179 /** 49180 * Determines if a new video track will be selected 49181 * or if the timescale changed 49182 * @return {Boolean} 49183 **/ 49184 49185 49186 this.isNewInit = function (videoTrackIds, timescales) { 49187 if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) { 49188 return false; 49189 } 49190 49191 return trackId !== videoTrackIds[0] || timescale !== timescales[trackId]; 49192 }; 49193 /** 49194 * Parses out SEI captions and interacts with underlying 49195 * CaptionStream to return dispatched captions 49196 * 49197 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 49198 * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment 49199 * @param {Object.<Number, Number>} timescales - The timescales found in the init segment 49200 * @see parseEmbeddedCaptions 49201 * @see m2ts/caption-stream.js 49202 **/ 49203 49204 49205 this.parse = function (segment, videoTrackIds, timescales) { 49206 var parsedData; 49207 49208 if (!this.isInitialized()) { 49209 return null; // This is not likely to be a video segment 49210 } else if (!videoTrackIds || !timescales) { 49211 return null; 49212 } else if (this.isNewInit(videoTrackIds, timescales)) { 49213 // Use the first video track only as there is no 49214 // mechanism to switch to other video tracks 49215 trackId = videoTrackIds[0]; 49216 timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment 49217 // data until we have one. 49218 // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 49219 } else if (trackId === null || !timescale) { 49220 segmentCache.push(segment); 49221 return null; 49222 } // Now that a timescale and trackId is set, parse cached segments 49223 49224 49225 while (segmentCache.length > 0) { 49226 var cachedSegment = segmentCache.shift(); 49227 this.parse(cachedSegment, videoTrackIds, timescales); 49228 } 49229 49230 parsedData = parseEmbeddedCaptions(segment, trackId, timescale); 49231 49232 if (parsedData === null || !parsedData.seiNals) { 49233 return null; 49234 } 49235 49236 this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched 49237 49238 this.flushStream(); 49239 return parsedCaptions; 49240 }; 49241 /** 49242 * Pushes SEI NALUs onto CaptionStream 49243 * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals` 49244 * Assumes that `parseCaptionNals` has been called first 49245 * @see m2ts/caption-stream.js 49246 **/ 49247 49248 49249 this.pushNals = function (nals) { 49250 if (!this.isInitialized() || !nals || nals.length === 0) { 49251 return null; 49252 } 49253 49254 nals.forEach(function (nal) { 49255 captionStream.push(nal); 49256 }); 49257 }; 49258 /** 49259 * Flushes underlying CaptionStream to dispatch processed, displayable captions 49260 * @see m2ts/caption-stream.js 49261 **/ 49262 49263 49264 this.flushStream = function () { 49265 if (!this.isInitialized()) { 49266 return null; 49267 } 49268 49269 if (!parsingPartial) { 49270 captionStream.flush(); 49271 } else { 49272 captionStream.partialFlush(); 49273 } 49274 }; 49275 /** 49276 * Reset caption buckets for new data 49277 **/ 49278 49279 49280 this.clearParsedCaptions = function () { 49281 parsedCaptions.captions = []; 49282 parsedCaptions.captionStreams = {}; 49283 }; 49284 /** 49285 * Resets underlying CaptionStream 49286 * @see m2ts/caption-stream.js 49287 **/ 49288 49289 49290 this.resetCaptionStream = function () { 49291 if (!this.isInitialized()) { 49292 return null; 49293 } 49294 49295 captionStream.reset(); 49296 }; 49297 /** 49298 * Convenience method to clear all captions flushed from the 49299 * CaptionStream and still being parsed 49300 * @see m2ts/caption-stream.js 49301 **/ 49302 49303 49304 this.clearAllCaptions = function () { 49305 this.clearParsedCaptions(); 49306 this.resetCaptionStream(); 49307 }; 49308 /** 49309 * Reset caption parser 49310 **/ 49311 49312 49313 this.reset = function () { 49314 segmentCache = []; 49315 trackId = null; 49316 timescale = null; 49317 49318 if (!parsedCaptions) { 49319 parsedCaptions = { 49320 captions: [], 49321 // CC1, CC2, CC3, CC4
49322 captionStreams: {} 49323 }; 49324 } else { 49325 this.clearParsedCaptions(); 49326 } 49327 49328 this.resetCaptionStream(); 49329 }; 49330 49331 this.reset(); 49332 }; 49333 49334 var captionParser = CaptionParser; 49335 /* global self */ 49336 49337 var typeFromStreamString = function typeFromStreamString(streamString) { 49338 if (streamString === 'AudioSegmentStream') { 49339 return 'audio'; 49340 } 49341 49342 return streamString === 'VideoSegmentStream' ? 'video' : ''; 49343 }; 49344 /** 49345 * Re-emits transmuxer events by converting them into messages to the 49346 * world outside the worker. 49347 * 49348 * @param {Object} transmuxer the transmuxer to wire events on 49349 * @private 49350 */ 49351 49352 49353 var wireFullTransmuxerEvents = function wireFullTransmuxerEvents(self, transmuxer) { 49354 transmuxer.on('data', function (segment) { 49355 // transfer ownership of the underlying ArrayBuffer 49356 // instead of doing a copy to save memory 49357 // ArrayBuffers are transferable but generic TypedArrays are not 49358 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 49359 var initArray = segment.initSegment; 49360 segment.initSegment = { 49361 data: initArray.buffer, 49362 byteOffset: initArray.byteOffset, 49363 byteLength: initArray.byteLength 49364 }; 49365 var typedArray = segment.data; 49366 segment.data = typedArray.buffer; 49367 self.postMessage({ 49368 action: 'data', 49369 segment: segment, 49370 byteOffset: typedArray.byteOffset, 49371 byteLength: typedArray.byteLength 49372 }, [segment.data]); 49373 }); 49374 transmuxer.on('done', function (data) { 49375 self.postMessage({ 49376 action: 'done' 49377 }); 49378 }); 49379 transmuxer.on('gopInfo', function (gopInfo) { 49380 self.postMessage({ 49381 action: 'gopInfo', 49382 gopInfo: gopInfo 49383 }); 49384 }); 49385 transmuxer.on('videoSegmentTimingInfo', function (timingInfo) { 49386 var videoSegmentTimingInfo = { 49387 start: { 49388 decode: clock_4(timingInfo.start.dts), 49389 presentation: clock_4(timingInfo.start.pts) 49390 }, 49391 end: { 49392 decode: clock_4(timingInfo.end.dts), 49393 presentation: clock_4(timingInfo.end.pts) 49394 }, 49395 baseMediaDecodeTime: clock_4(timingInfo.baseMediaDecodeTime) 49396 }; 49397 49398 if (timingInfo.prependedContentDuration) { 49399 videoSegmentTimingInfo.prependedContentDuration = clock_4(timingInfo.prependedContentDuration); 49400 } 49401 49402 self.postMessage({ 49403 action: 'videoSegmentTimingInfo', 49404 videoSegmentTimingInfo: videoSegmentTimingInfo 49405 }); 49406 }); 49407 transmuxer.on('id3Frame', function (id3Frame) { 49408 self.postMessage({ 49409 action: 'id3Frame', 49410 id3Frame: id3Frame 49411 }); 49412 }); 49413 transmuxer.on('caption', function (caption) { 49414 self.postMessage({ 49415 action: 'caption', 49416 caption: caption 49417 }); 49418 }); 49419 transmuxer.on('trackinfo', function (trackInfo) { 49420 self.postMessage({ 49421 action: 'trackinfo', 49422 trackInfo: trackInfo 49423 }); 49424 }); 49425 transmuxer.on('audioTimingInfo', function (audioTimingInfo) { 49426 // convert to video TS since we prioritize video time over audio 49427 self.postMessage({ 49428 action: 'audioTimingInfo', 49429 audioTimingInfo: { 49430 start: clock_4(audioTimingInfo.start), 49431 end: clock_4(audioTimingInfo.end) 49432 } 49433 }); 49434 }); 49435 transmuxer.on('videoTimingInfo', function (videoTimingInfo) { 49436 self.postMessage({ 49437 action: 'videoTimingInfo', 49438 videoTimingInfo: { 49439 start: clock_4(videoTimingInfo.start), 49440 end: clock_4(videoTimingInfo.end) 49441 } 49442 }); 49443 }); 49444 }; 49445 49446 var wirePartialTransmuxerEvents = function wirePartialTransmuxerEvents(self, transmuxer) { 49447 transmuxer.on('data', function (event) { 49448 // transfer ownership of the underlying ArrayBuffer 49449 // instead of doing a copy to save memory 49450 // ArrayBuffers are transferable but generic TypedArrays are not 49451 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 49452 var initSegment = { 49453 data: event.data.track.initSegment.buffer, 49454 byteOffset: event.data.track.initSegment.byteOffset, 49455 byteLength: event.data.track.initSegment.byteLength 49456 }; 49457 var boxes = { 49458 data: event.data.boxes.buffer, 49459 byteOffset: event.data.boxes.byteOffset, 49460 byteLength: event.data.boxes.byteLength 49461 }; 49462 var segment = { 49463 boxes: boxes, 49464 initSegment: initSegment, 49465 type: event.type, 49466 sequence: event.data.sequence 49467 }; 49468 49469 if (typeof event.data.videoFrameDts !== 'undefined') { 49470 segment.videoFrameDtsTime = clock_4(event.data.videoFrameDts); 49471 } 49472 49473 if (typeof event.data.videoFramePts !== 'undefined') { 49474 segment.videoFramePtsTime = clock_4(event.data.videoFramePts); 49475 } 49476 49477 self.postMessage({ 49478 action: 'data', 49479 segment: segment 49480 }, [segment.boxes.data, segment.initSegment.data]); 49481 }); 49482 transmuxer.on('id3Frame', function (id3Frame) { 49483 self.postMessage({ 49484 action: 'id3Frame', 49485 id3Frame: id3Frame 49486 }); 49487 }); 49488 transmuxer.on('caption', function (caption) { 49489 self.postMessage({ 49490 action: 'caption', 49491 caption: caption 49492 }); 49493 }); 49494 transmuxer.on('done', function (data) { 49495 self.postMessage({ 49496 action: 'done', 49497 type: typeFromStreamString(data) 49498 }); 49499 }); 49500 transmuxer.on('partialdone', function (data) { 49501 self.postMessage({ 49502 action: 'partialdone', 49503 type: typeFromStreamString(data) 49504 }); 49505 }); 49506 transmuxer.on('endedsegment', function (data) { 49507 self.postMessage({ 49508 action: 'endedSegment', 49509 type: typeFromStreamString(data) 49510 }); 49511 }); 49512 transmuxer.on('trackinfo', function (trackInfo) { 49513 self.postMessage({ 49514 action: 'trackinfo', 49515 trackInfo: trackInfo 49516 }); 49517 }); 49518 transmuxer.on('audioTimingInfo', function (audioTimingInfo) { 49519 // This can happen if flush is called when no 49520 // audio has been processed. This should be an 49521 // unusual case, but if it does occur should not 49522 // result in valid data being returned 49523 if (audioTimingInfo.start === null) { 49524 self.postMessage({ 49525 action: 'audioTimingInfo', 49526 audioTimingInfo: audioTimingInfo 49527 }); 49528 return; 49529 } // convert to video TS since we prioritize video time over audio 49530 49531 49532 var timingInfoInSeconds = { 49533 start: clock_4(audioTimingInfo.start) 49534 }; 49535 49536 if (audioTimingInfo.end) { 49537 timingInfoInSeconds.end = clock_4(audioTimingInfo.end); 49538 } 49539 49540 self.postMessage({ 49541 action: 'audioTimingInfo', 49542 audioTimingInfo: timingInfoInSeconds 49543 }); 49544 }); 49545 transmuxer.on('videoTimingInfo', function (videoTimingInfo) { 49546 var timingInfoInSeconds = { 49547 start: clock_4(videoTimingInfo.start) 49548 }; 49549 49550 if (videoTimingInfo.end) { 49551 timingInfoInSeconds.end = clock_4(videoTimingInfo.end); 49552 } 49553 49554 self.postMessage({ 49555 action: 'videoTimingInfo', 49556 videoTimingInfo: timingInfoInSeconds 49557 }); 49558 }); 49559 }; 49560 /** 49561 * All incoming messages route through this hash. If no function exists 49562 * to handle an incoming message, then we ignore the message. 49563 * 49564 * @class MessageHandlers 49565 * @param {Object} options the options to initialize with 49566 */ 49567 49568 49569 var MessageHandlers = /*#__PURE__*/function () { 49570 function MessageHandlers(self, options) { 49571 this.options = options || {}; 49572 this.self = self; 49573 this.init(); 49574 } 49575 /** 49576 * initialize our web worker and wire all the events. 49577 */ 49578 49579 49580 var _proto = MessageHandlers.prototype; 49581
49582 _proto.init = function init() { 49583 if (this.transmuxer) { 49584 this.transmuxer.dispose(); 49585 } 49586 49587 this.transmuxer = this.options.handlePartialData ? new transmuxer$1(this.options) : new transmuxer_1(this.options); 49588 49589 if (this.options.handlePartialData) { 49590 wirePartialTransmuxerEvents(this.self, this.transmuxer); 49591 } else { 49592 wireFullTransmuxerEvents(this.self, this.transmuxer); 49593 } 49594 }; 49595 49596 _proto.pushMp4Captions = function pushMp4Captions(data) { 49597 if (!this.captionParser) { 49598 this.captionParser = new captionParser(); 49599 this.captionParser.init(); 49600 } 49601 49602 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 49603 var parsed = this.captionParser.parse(segment, data.trackIds, data.timescales); 49604 this.self.postMessage({ 49605 action: 'mp4Captions', 49606 captions: parsed && parsed.captions || [], 49607 data: segment.buffer 49608 }, [segment.buffer]); 49609 }; 49610 49611 _proto.clearAllMp4Captions = function clearAllMp4Captions() { 49612 if (this.captionParser) { 49613 this.captionParser.clearAllCaptions(); 49614 } 49615 }; 49616 49617 _proto.clearParsedMp4Captions = function clearParsedMp4Captions() { 49618 if (this.captionParser) { 49619 this.captionParser.clearParsedCaptions(); 49620 } 49621 } 49622 /** 49623 * Adds data (a ts segment) to the start of the transmuxer pipeline for 49624 * processing. 49625 * 49626 * @param {ArrayBuffer} data data to push into the muxer 49627 */ 49628 ; 49629 49630 _proto.push = function push(data) { 49631 // Cast array buffer to correct type for transmuxer 49632 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 49633 this.transmuxer.push(segment); 49634 } 49635 /** 49636 * Recreate the transmuxer so that the next segment added via `push` 49637 * start with a fresh transmuxer. 49638 */ 49639 ; 49640 49641 _proto.reset = function reset() { 49642 this.transmuxer.reset(); 49643 } 49644 /** 49645 * Set the value that will be used as the `baseMediaDecodeTime` time for the 49646 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 49647 * set relative to the first based on the PTS values. 49648 * 49649 * @param {Object} data used to set the timestamp offset in the muxer 49650 */ 49651 ; 49652 49653 _proto.setTimestampOffset = function setTimestampOffset(data) { 49654 var timestampOffset = data.timestampOffset || 0; 49655 this.transmuxer.setBaseMediaDecodeTime(Math.round(clock_2(timestampOffset))); 49656 }; 49657 49658 _proto.setAudioAppendStart = function setAudioAppendStart(data) { 49659 this.transmuxer.setAudioAppendStart(Math.ceil(clock_2(data.appendStart))); 49660 }; 49661 49662 _proto.setRemux = function setRemux(data) { 49663 this.transmuxer.setRemux(data.remux); 49664 } 49665 /** 49666 * Forces the pipeline to finish processing the last segment and emit it's 49667 * results. 49668 * 49669 * @param {Object} data event data, not really used 49670 */ 49671 ; 49672 49673 _proto.flush = function flush(data) { 49674 this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed 49675 49676 self.postMessage({ 49677 action: 'done', 49678 type: 'transmuxed' 49679 }); 49680 }; 49681 49682 _proto.partialFlush = function partialFlush(data) { 49683 this.transmuxer.partialFlush(); // transmuxed partialdone action is fired after both audio/video pipelines are flushed 49684 49685 self.postMessage({ 49686 action: 'partialdone', 49687 type: 'transmuxed' 49688 }); 49689 }; 49690 49691 _proto.endTimeline = function endTimeline() { 49692 this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their 49693 // timelines 49694 49695 self.postMessage({ 49696 action: 'endedtimeline', 49697 type: 'transmuxed' 49698 }); 49699 }; 49700 49701 _proto.alignGopsWith = function alignGopsWith(data) { 49702 this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice()); 49703 }; 49704 49705 return MessageHandlers; 49706 }(); 49707 /** 49708 * Our web worker interface so that things can talk to mux.js 49709 * that will be running in a web worker. the scope is passed to this by 49710 * webworkify. 49711 * 49712 * @param {Object} self the scope for the web worker 49713 */ 49714 49715 49716 var TransmuxerWorker = function TransmuxerWorker(self) { 49717 self.onmessage = function (event) {
vendor: 38,075 bytes, lines 49718-50774
49718 if (event.data.action === 'init' && event.data.options) { 49719 this.messageHandlers = new MessageHandlers(self, event.data.options); 49720 return; 49721 } 49722 49723 if (!this.messageHandlers) { 49724 this.messageHandlers = new MessageHandlers(self); 49725 } 49726 49727 if (event.data && event.data.action && event.data.action !== 'init') { 49728 if (this.messageHandlers[event.data.action]) { 49729 this.messageHandlers[event.data.action](event.data); 49730 } 49731 } 49732 }; 49733 }; 49734 49735 var transmuxerWorker = new TransmuxerWorker(self); 49736 return transmuxerWorker; 49737 }(); 49738 }); 49739 /** 49740 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to 49741 * codec strings, or translating codec strings into objects that can be examined. 49742 */ 49743 49744 /** 49745 * Returns a set of codec strings parsed from the playlist or the default 49746 * codec strings if no codecs were specified in the playlist 49747 * 49748 * @param {Playlist} media the current media playlist 49749 * @return {Object} an object with the video and audio codecs 49750 */ 49751 49752 var getCodecs = function getCodecs(media) { 49753 // if the codecs were explicitly specified, use them instead of the 49754 // defaults 49755 var mediaAttributes = media.attributes || {}; 49756 49757 if (mediaAttributes.CODECS) { 49758 return codecs_10(mediaAttributes.CODECS); 49759 } 49760 }; 49761 49762 var isMaat = function isMaat(master, media) { 49763 var mediaAttributes = media.attributes || {}; 49764 return master && master.mediaGroups && master.mediaGroups.AUDIO && mediaAttributes.AUDIO && master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; 49765 }; 49766 49767 var isMuxed = function isMuxed(master, media) { 49768 if (!isMaat(master, media)) { 49769 return true; 49770 } 49771 49772 var mediaAttributes = media.attributes || {}; 49773 var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; 49774 49775 for (var groupId in audioGroup) { 49776 // If an audio group has a URI (the case for HLS, as HLS will use external playlists), 49777 // or there are listed playlists (the case for DASH, as the manifest will have already 49778 // provided all of the details necessary to generate the audio playlist, as opposed to 49779 // HLS' externally requested playlists), then the content is demuxed. 49780 if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) { 49781 return true; 49782 } 49783 } 49784 49785 return false; 49786 }; 49787 /** 49788 * Calculates the codec strings for a working configuration of 49789 * SourceBuffers to play variant streams in a master playlist. If 49790 * there is no possible working configuration, an empty object will be 49791 * returned. 49792 * 49793 * @param master {Object} the m3u8 object for the master playlist 49794 * @param media {Object} the m3u8 object for the variant playlist 49795 * @return {Object} the codec strings. 49796 * 49797 * @private 49798 */ 49799 49800 49801 var codecsForPlaylist = function codecsForPlaylist(master, media) { 49802 var mediaAttributes = media.attributes || {}; 49803 var codecInfo = getCodecs(media) || {}; // HLS with multiple-audio tracks must always get an audio codec. 49804 // Put another way, there is no way to have a video-only multiple-audio HLS! 49805 49806 if (isMaat(master, media) && !codecInfo.audio) { 49807 if (!isMuxed(master, media)) { 49808 // It is possible for codecs to be specified on the audio media group playlist but 49809 // not on the rendition playlist. This is mostly the case for DASH, where audio and 49810 // video are always separate (and separately specified). 49811 var defaultCodecs = codecs_4(master, mediaAttributes.AUDIO); 49812 49813 if (defaultCodecs) { 49814 codecInfo.audio = defaultCodecs.audio; 49815 } 49816 } 49817 } 49818 49819 var codecs = {}; 49820 49821 if (codecInfo.video) { 49822 codecs.video = codecs_11("" + codecInfo.video.type + codecInfo.video.details); 49823 } 49824 49825 if (codecInfo.audio) { 49826 codecs.audio = codecs_11("" + codecInfo.audio.type + codecInfo.audio.details); 49827 } 49828 49829 return codecs; 49830 }; 49831 49832 var logger = function logger(source) { 49833 if (videojs$1.log.debug) { 49834 return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + " >"); 49835 } 49836 49837 return function () {}; 49838 }; 49839 49840 var logFn = logger('PlaylistSelector'); 49841 49842 var representationToString = function representationToString(representation) { 49843 if (!representation || !representation.playlist) { 49844 return; 49845 } 49846 49847 var playlist = representation.playlist; 49848 return JSON.stringify({ 49849 id: playlist.id, 49850 bandwidth: representation.bandwidth, 49851 width: representation.width, 49852 height: representation.height, 49853 codecs: playlist.attributes && playlist.attributes.CODECS || '' 49854 }); 49855 }; // Utilities 49856 49857 /** 49858 * Returns the CSS value for the specified property on an element 49859 * using `getComputedStyle`. Firefox has a long-standing issue where 49860 * getComputedStyle() may return null when running in an iframe with 49861 * `display: none`. 49862 * 49863 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 49864 * @param {HTMLElement} el the htmlelement to work on 49865 * @param {string} the proprety to get the style for 49866 */ 49867 49868 49869 var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 49870 if (!el) { 49871 return ''; 49872 } 49873 49874 var result = window$1.getComputedStyle(el); 49875 49876 if (!result) { 49877 return ''; 49878 } 49879 49880 return result[property]; 49881 }; 49882 /** 49883 * Resuable stable sort function 49884 * 49885 * @param {Playlists} array 49886 * @param {Function} sortFn Different comparators 49887 * @function stableSort 49888 */ 49889 49890 49891 var stableSort = function stableSort(array, sortFn) { 49892 var newArray = array.slice(); 49893 array.sort(function (left, right) { 49894 var cmp = sortFn(left, right); 49895 49896 if (cmp === 0) { 49897 return newArray.indexOf(left) - newArray.indexOf(right); 49898 } 49899 49900 return cmp; 49901 }); 49902 }; 49903 /** 49904 * A comparator function to sort two playlist object by bandwidth. 49905 * 49906 * @param {Object} left a media playlist object 49907 * @param {Object} right a media playlist object 49908 * @return {number} Greater than zero if the bandwidth attribute of 49909 * left is greater than the corresponding attribute of right. Less 49910 * than zero if the bandwidth of right is greater than left and 49911 * exactly zero if the two are equal. 49912 */ 49913 49914 49915 var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) { 49916 var leftBandwidth; 49917 var rightBandwidth; 49918 49919 if (left.attributes.BANDWIDTH) { 49920 leftBandwidth = left.attributes.BANDWIDTH; 49921 } 49922 49923 leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE; 49924 49925 if (right.attributes.BANDWIDTH) { 49926 rightBandwidth = right.attributes.BANDWIDTH; 49927 } 49928 49929 rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE; 49930 return leftBandwidth - rightBandwidth; 49931 }; 49932 /** 49933 * A comparator function to sort two playlist object by resolution (width). 49934 * 49935 * @param {Object} left a media playlist object 49936 * @param {Object} right a media playlist object 49937 * @return {number} Greater than zero if the resolution.width attribute of 49938 * left is greater than the corresponding attribute of right. Less 49939 * than zero if the resolution.width of right is greater than left and 49940 * exactly zero if the two are equal. 49941 */ 49942 49943 49944 var comparePlaylistResolution = function comparePlaylistResolution(left, right) { 49945 var leftWidth; 49946 var rightWidth; 49947 49948 if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 49949 leftWidth = left.attributes.RESOLUTION.width; 49950 } 49951 49952 leftWidth = leftWidth || window$1.Number.MAX_VALUE; 49953 49954 if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 49955 rightWidth = right.attributes.RESOLUTION.width; 49956 } 49957 49958 rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 49959 // have the same media dimensions/ resolution 49960 49961 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 49962 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 49963 } 49964 49965 return leftWidth - rightWidth; 49966 }; 49967 /** 49968 * Chooses the appropriate media playlist based on bandwidth and player size 49969 * 49970 * @param {Object} master 49971 * Object representation of the master manifest 49972 * @param {number} playerBandwidth 49973 * Current calculated bandwidth of the player 49974 * @param {number} playerWidth 49975 * Current width of the player element (should account for the device pixel ratio) 49976 * @param {number} playerHeight 49977 * Current height of the player element (should account for the device pixel ratio) 49978 * @param {boolean} limitRenditionByPlayerDimensions 49979 * True if the player width and height should be used during the selection, false otherwise 49980 * @return {Playlist} the highest bitrate playlist less than the 49981 * currently detected bandwidth, accounting for some amount of 49982 * bandwidth variance 49983 */ 49984 49985 49986 var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) { 49987 var options = { 49988 bandwidth: playerBandwidth, 49989 width: playerWidth, 49990 height: playerHeight, 49991 limitRenditionByPlayerDimensions: limitRenditionByPlayerDimensions 49992 }; // convert the playlists to an intermediary representation to make comparisons easier 49993 49994 var sortedPlaylistReps = master.playlists.map(function (playlist) { 49995 var bandwidth; 49996 var width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width; 49997 var height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height; 49998 bandwidth = playlist.attributes.BANDWIDTH; 49999 bandwidth = bandwidth || window$1.Number.MAX_VALUE; 50000 return { 50001 bandwidth: bandwidth, 50002 width: width, 50003 height: height, 50004 playlist: playlist 50005 }; 50006 }); 50007 stableSort(sortedPlaylistReps, function (left, right) { 50008 return left.bandwidth - right.bandwidth; 50009 }); // filter out any playlists that have been excluded due to 50010 // incompatible configurations 50011 50012 sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) { 50013 return !Playlist.isIncompatible(rep.playlist); 50014 }); // filter out any playlists that have been disabled manually through the representations 50015 // api or blacklisted temporarily due to playback errors. 50016 50017 var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 50018 return Playlist.isEnabled(rep.playlist); 50019 }); 50020 50021 if (!enabledPlaylistReps.length) { 50022 // if there are no enabled playlists, then they have all been blacklisted or disabled 50023 // by the user through the representations api. In this case, ignore blacklisting and 50024 // fallback to what the user wants by using playlists the user has not disabled. 50025 enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 50026 return !Playlist.isDisabled(rep.playlist); 50027 }); 50028 } // filter out any variant that has greater effective bitrate 50029 // than the current estimated bandwidth 50030 50031 50032 var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) { 50033 return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth; 50034 }); 50035 var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth 50036 // and then taking the very first element 50037 50038 var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) { 50039 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 50040 })[0]; // if we're not going to limit renditions by player size, make an early decision. 50041 50042 if (limitRenditionByPlayerDimensions === false) { 50043 var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 50044 50045 if (_chosenRep && _chosenRep.playlist) { 50046 var type = 'sortedPlaylistReps'; 50047 50048 if (bandwidthBestRep) { 50049 type = 'bandwidthBestRep'; 50050 } 50051 50052 if (enabledPlaylistReps[0]) { 50053 type = 'enabledPlaylistReps'; 50054 } 50055 50056 logFn("choosing " + representationToString(_chosenRep) + " using " + type + " with options", options); 50057 return _chosenRep.playlist; 50058 } 50059 50060 logFn('could not choose a playlist with options', options); 50061 return null; 50062 } // filter out playlists without resolution information 50063 50064 50065 var haveResolution = bandwidthPlaylistReps.filter(function (rep) { 50066 return rep.width && rep.height; 50067 }); // sort variants by resolution 50068 50069 stableSort(haveResolution, function (left, right) { 50070 return left.width - right.width; 50071 }); // if we have the exact resolution as the player use it 50072 50073 var resolutionBestRepList = haveResolution.filter(function (rep) { 50074 return rep.width === playerWidth && rep.height === playerHeight; 50075 }); 50076 highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution 50077 50078 var resolutionBestRep = resolutionBestRepList.filter(function (rep) { 50079 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 50080 })[0]; 50081 var resolutionPlusOneList; 50082 var resolutionPlusOneSmallest; 50083 var resolutionPlusOneRep; // find the smallest variant that is larger than the player 50084 // if there is no match of exact resolution 50085 50086 if (!resolutionBestRep) { 50087 resolutionPlusOneList = haveResolution.filter(function (rep) { 50088 return rep.width > playerWidth || rep.height > playerHeight; 50089 }); // find all the variants have the same smallest resolution 50090 50091 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) { 50092 return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height; 50093 }); // ensure that we also pick the highest bandwidth variant that 50094 // is just-larger-than the video player 50095 50096 highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1]; 50097 resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) { 50098 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 50099 })[0]; 50100 } // fallback chain of variants 50101 50102 50103 var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 50104 50105 if (chosenRep && chosenRep.playlist) { 50106 var _type = 'sortedPlaylistReps'; 50107 50108 if (resolutionPlusOneRep) { 50109 _type = 'resolutionPlusOneRep'; 50110 } else if (resolutionBestRep) { 50111 _type = 'resolutionBestRep'; 50112 } else if (bandwidthBestRep) { 50113 _type = 'bandwidthBestRep'; 50114 } else if (enabledPlaylistReps[0]) { 50115 _type = 'enabledPlaylistReps'; 50116 } 50117 50118 logFn("choosing " + representationToString(chosenRep) + " using " + _type + " with options", options); 50119 return chosenRep.playlist; 50120 } 50121 50122 logFn('could not choose a playlist with options', options); 50123 return null; 50124 }; // Playlist Selectors 50125 50126 /** 50127 * Chooses the appropriate media playlist based on the most recent 50128 * bandwidth estimate and the player size. 50129 * 50130 * Expects to be called within the context of an instance of VhsHandler 50131 * 50132 * @return {Playlist} the highest bitrate playlist less than the 50133 * currently detected bandwidth, accounting for some amount of 50134 * bandwidth variance 50135 */ 50136 50137 50138 var lastBandwidthSelector = function lastBandwidthSelector() { 50139 var pixelRatio = this.useDevicePixelRatio ? window$1.devicePixelRatio || 1 : 1; 50140 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); 50141 }; 50142 /** 50143 * Chooses the appropriate media playlist based on the potential to rebuffer 50144 * 50145 * @param {Object} settings 50146 * Object of information required to use this selector 50147 * @param {Object} settings.master 50148 * Object representation of the master manifest 50149 * @param {number} settings.currentTime 50150 * The current time of the player 50151 * @param {number} settings.bandwidth 50152 * Current measured bandwidth 50153 * @param {number} settings.duration 50154 * Duration of the media 50155 * @param {number} settings.segmentDuration 50156 * Segment duration to be used in round trip time calculations 50157 * @param {number} settings.timeUntilRebuffer 50158 * Time left in seconds until the player has to rebuffer 50159 * @param {number} settings.currentTimeline 50160 * The current timeline segments are being loaded from 50161 * @param {SyncController} settings.syncController 50162 * SyncController for determining if we have a sync point for a given playlist 50163 * @return {Object|null} 50164 * {Object} return.playlist 50165 * The highest bandwidth playlist with the least amount of rebuffering 50166 * {Number} return.rebufferingImpact 50167 * The amount of time in seconds switching to this playlist will rebuffer. A 50168 * negative value means that switching will cause zero rebuffering. 50169 */ 50170 50171 50172 var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) { 50173 var master = settings.master, 50174 currentTime = settings.currentTime, 50175 bandwidth = settings.bandwidth, 50176 duration = settings.duration, 50177 segmentDuration = settings.segmentDuration, 50178 timeUntilRebuffer = settings.timeUntilRebuffer, 50179 currentTimeline = settings.currentTimeline, 50180 syncController = settings.syncController; // filter out any playlists that have been excluded due to 50181 // incompatible configurations 50182 50183 var compatiblePlaylists = master.playlists.filter(function (playlist) { 50184 return !Playlist.isIncompatible(playlist); 50185 }); // filter out any playlists that have been disabled manually through the representations 50186 // api or blacklisted temporarily due to playback errors. 50187 50188 var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled); 50189 50190 if (!enabledPlaylists.length) { 50191 // if there are no enabled playlists, then they have all been blacklisted or disabled 50192 // by the user through the representations api. In this case, ignore blacklisting and 50193 // fallback to what the user wants by using playlists the user has not disabled. 50194 enabledPlaylists = compatiblePlaylists.filter(function (playlist) { 50195 return !Playlist.isDisabled(playlist); 50196 }); 50197 } 50198 50199 var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH')); 50200 var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) { 50201 var syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a 50202 // sync request first. This will double the request time 50203 50204 var numRequests = syncPoint ? 1 : 2; 50205 var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist); 50206 var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer; 50207 return { 50208 playlist: playlist, 50209 rebufferingImpact: rebufferingImpact 50210 }; 50211 }); 50212 var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) { 50213 return estimate.rebufferingImpact <= 0; 50214 }); // Sort by bandwidth DESC 50215 50216 stableSort(noRebufferingPlaylists, function (a, b) { 50217 return comparePlaylistBandwidth(b.playlist, a.playlist); 50218 }); 50219 50220 if (noRebufferingPlaylists.length) { 50221 return noRebufferingPlaylists[0]; 50222 } 50223 50224 stableSort(rebufferingEstimates, function (a, b) { 50225 return a.rebufferingImpact - b.rebufferingImpact; 50226 }); 50227 return rebufferingEstimates[0] || null; 50228 }; 50229 /** 50230 * Chooses the appropriate media playlist, which in this case is the lowest bitrate 50231 * one with video. If no renditions with video exist, return the lowest audio rendition. 50232 * 50233 * Expects to be called within the context of an instance of VhsHandler 50234 * 50235 * @return {Object|null} 50236 * {Object} return.playlist 50237 * The lowest bitrate playlist that contains a video codec. If no such rendition 50238 * exists pick the lowest audio rendition. 50239 */ 50240 50241 50242 var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() { 50243 var _this = this; // filter out any playlists that have been excluded due to 50244 // incompatible configurations or playback errors 50245 50246 50247 var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate 50248 50249 stableSort(playlists, function (a, b) { 50250 return comparePlaylistBandwidth(a, b); 50251 }); // Parse and assume that playlists with no video codec have no video 50252 // (this is not necessarily true, although it is generally true). 50253 // 50254 // If an entire manifest has no valid videos everything will get filtered 50255 // out. 50256 50257 var playlistsWithVideo = playlists.filter(function (playlist) { 50258 return !!codecsForPlaylist(_this.playlists.master, playlist).video; 50259 }); 50260 return playlistsWithVideo[0] || null; 50261 }; 50262 /** 50263 * @file text-tracks.js 50264 */ 50265 50266 /** 50267 * Create captions text tracks on video.js if they do not exist 50268 * 50269 * @param {Object} inbandTextTracks a reference to current inbandTextTracks 50270 * @param {Object} tech the video.js tech 50271 * @param {Object} captionStream the caption stream to create 50272 * @private 50273 */ 50274 50275 50276 var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStream) { 50277 if (!inbandTextTracks[captionStream]) { 50278 tech.trigger({ 50279 type: 'usage', 50280 name: 'vhs-608' 50281 }); 50282 tech.trigger({ 50283 type: 'usage', 50284 name: 'hls-608' 50285 }); 50286 var track = tech.textTracks().getTrackById(captionStream); 50287 50288 if (track) { 50289 // Resuse an existing track with a CC# id because this was 50290 // very likely created by videojs-contrib-hls from information 50291 // in the m3u8 for us to use 50292 inbandTextTracks[captionStream] = track; 50293 } else { 50294 // Otherwise, create a track with the default `CC#` label and 50295 // without a language 50296 inbandTextTracks[captionStream] = tech.addRemoteTextTrack({ 50297 kind: 'captions', 50298 id: captionStream, 50299 label: captionStream 50300 }, false).track; 50301 } 50302 } 50303 }; 50304 /** 50305 * Add caption text track data to a source handler given an array of captions 50306 * 50307 * @param {Object} 50308 * @param {Object} inbandTextTracks the inband text tracks 50309 * @param {number} timestampOffset the timestamp offset of the source buffer 50310 * @param {Array} captionArray an array of caption data 50311 * @private 50312 */ 50313 50314 50315 var addCaptionData = function addCaptionData(_ref) { 50316 var inbandTextTracks = _ref.inbandTextTracks, 50317 captionArray = _ref.captionArray, 50318 timestampOffset = _ref.timestampOffset; 50319 50320 if (!captionArray) { 50321 return; 50322 } 50323 50324 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 50325 captionArray.forEach(function (caption) { 50326 var track = caption.stream; 50327 inbandTextTracks[track].addCue(new Cue(caption.startTime + timestampOffset, caption.endTime + timestampOffset, caption.text)); 50328 }); 50329 }; 50330 /** 50331 * Define properties on a cue for backwards compatability, 50332 * but warn the user that the way that they are using it 50333 * is depricated and will be removed at a later date. 50334 * 50335 * @param {Cue} cue the cue to add the properties on 50336 * @private 50337 */ 50338 50339 50340 var deprecateOldCue = function deprecateOldCue(cue) { 50341 Object.defineProperties(cue.frame, { 50342 id: { 50343 get: function get() { 50344 videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 50345 return cue.value.key; 50346 } 50347 }, 50348 value: { 50349 get: function get() { 50350 videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 50351 return cue.value.data; 50352 } 50353 }, 50354 privateData: { 50355 get: function get() { 50356 videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 50357 return cue.value.data; 50358 } 50359 } 50360 }); 50361 }; 50362 /** 50363 * Add metadata text track data to a source handler given an array of metadata 50364 * 50365 * @param {Object} 50366 * @param {Object} inbandTextTracks the inband text tracks 50367 * @param {Array} metadataArray an array of meta data 50368 * @param {number} timestampOffset the timestamp offset of the source buffer 50369 * @param {number} videoDuration the duration of the video 50370 * @private 50371 */ 50372 50373 50374 var addMetadata = function addMetadata(_ref2) { 50375 var inbandTextTracks = _ref2.inbandTextTracks, 50376 metadataArray = _ref2.metadataArray, 50377 timestampOffset = _ref2.timestampOffset, 50378 videoDuration = _ref2.videoDuration; 50379 50380 if (!metadataArray) { 50381 return; 50382 } 50383 50384 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 50385 var metadataTrack = inbandTextTracks.metadataTrack_; 50386 50387 if (!metadataTrack) { 50388 return; 50389 } 50390 50391 metadataArray.forEach(function (metadata) { 50392 var time = metadata.cueTime + timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN, 50393 // ignore this bit of metadata. 50394 // This likely occurs when you have an non-timed ID3 tag like TIT2, 50395 // which is the "Title/Songname/Content description" frame 50396 50397 if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) { 50398 return; 50399 } 50400 50401 metadata.frames.forEach(function (frame) { 50402 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 50403 cue.frame = frame; 50404 cue.value = frame; 50405 deprecateOldCue(cue); 50406 metadataTrack.addCue(cue); 50407 }); 50408 }); 50409 50410 if (!metadataTrack.cues || !metadataTrack.cues.length) { 50411 return; 50412 } // Updating the metadeta cues so that 50413 // the endTime of each cue is the startTime of the next cue 50414 // the endTime of last cue is the duration of the video 50415 50416 50417 var cues = metadataTrack.cues; 50418 var cuesArray = []; // Create a copy of the TextTrackCueList... 50419 // ...disregarding cues with a falsey value 50420 50421 for (var i = 0; i < cues.length; i++) { 50422 if (cues[i]) { 50423 cuesArray.push(cues[i]); 50424 } 50425 } // Group cues by their startTime value 50426 50427 50428 var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) { 50429 var timeSlot = obj[cue.startTime] || []; 50430 timeSlot.push(cue); 50431 obj[cue.startTime] = timeSlot; 50432 return obj; 50433 }, {}); // Sort startTimes by ascending order 50434 50435 var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) { 50436 return Number(a) - Number(b); 50437 }); // Map each cue group's endTime to the next group's startTime 50438 50439 sortedStartTimes.forEach(function (startTime, idx) { 50440 var cueGroup = cuesGroupedByStartTime[startTime]; 50441 var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime 50442 50443 cueGroup.forEach(function (cue) { 50444 cue.endTime = nextTime; 50445 }); 50446 }); 50447 }; 50448 /** 50449 * Create metadata text track on video.js if it does not exist 50450 * 50451 * @param {Object} inbandTextTracks a reference to current inbandTextTracks 50452 * @param {string} dispatchType the inband metadata track dispatch type 50453 * @param {Object} tech the video.js tech 50454 * @private 50455 */ 50456 50457 50458 var createMetadataTrackIfNotExists = function createMetadataTrackIfNotExists(inbandTextTracks, dispatchType, tech) { 50459 if (inbandTextTracks.metadataTrack_) { 50460 return; 50461 } 50462 50463 inbandTextTracks.metadataTrack_ = tech.addRemoteTextTrack({ 50464 kind: 'metadata', 50465 label: 'Timed Metadata' 50466 }, false).track; 50467 inbandTextTracks.metadataTrack_.inBandMetadataTrackDispatchType = dispatchType; 50468 }; 50469 /** 50470 * Remove cues from a track on video.js. 50471 * 50472 * @param {Double} start start of where we should remove the cue 50473 * @param {Double} end end of where the we should remove the cue 50474 * @param {Object} track the text track to remove the cues from 50475 * @private 50476 */ 50477 50478 50479 var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 50480 var i; 50481 var cue; 50482 50483 if (!track) { 50484 return; 50485 } 50486 50487 if (!track.cues) { 50488 return; 50489 } 50490 50491 i = track.cues.length; 50492 50493 while (i--) { 50494 cue = track.cues[i]; // Remove any overlapping cue 50495 50496 if (cue.startTime >= start && cue.endTime <= end) { 50497 track.removeCue(cue); 50498 } 50499 } 50500 }; 50501 /** 50502 * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in 50503 * front of current time. 50504 * 50505 * @param {Array} buffer 50506 * The current buffer of gop information 50507 * @param {number} currentTime 50508 * The current time 50509 * @param {Double} mapping 50510 * Offset to map display time to stream presentation time 50511 * @return {Array} 50512 * List of gops considered safe to append over 50513 */ 50514 50515 50516 var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) { 50517 if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) { 50518 return []; 50519 } // pts value for current time + 3 seconds to give a bit more wiggle room 50520 50521 50522 var currentTimePts = Math.ceil((currentTime - mapping + 3) * clock_1); 50523 var i; 50524 50525 for (i = 0; i < buffer.length; i++) { 50526 if (buffer[i].pts > currentTimePts) { 50527 break; 50528 } 50529 } 50530 50531 return buffer.slice(i); 50532 }; 50533 /** 50534 * Appends gop information (timing and byteLength) received by the transmuxer for the 50535 * gops appended in the last call to appendBuffer 50536 * 50537 * @param {Array} buffer 50538 * The current buffer of gop information 50539 * @param {Array} gops 50540 * List of new gop information 50541 * @param {boolean} replace 50542 * If true, replace the buffer with the new gop information. If false, append the 50543 * new gop information to the buffer in the right location of time. 50544 * @return {Array} 50545 * Updated list of gop information 50546 */ 50547 50548 50549 var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) { 50550 if (!gops.length) { 50551 return buffer; 50552 } 50553 50554 if (replace) { 50555 // If we are in safe append mode, then completely overwrite the gop buffer 50556 // with the most recent appeneded data. This will make sure that when appending 50557 // future segments, we only try to align with gops that are both ahead of current 50558 // time and in the last segment appended. 50559 return gops.slice(); 50560 } 50561 50562 var start = gops[0].pts; 50563 var i = 0; 50564 50565 for (i; i < buffer.length; i++) { 50566 if (buffer[i].pts >= start) { 50567 break; 50568 } 50569 } 50570 50571 return buffer.slice(0, i).concat(gops); 50572 }; 50573 /** 50574 * Removes gop information in buffer that overlaps with provided start and end 50575 * 50576 * @param {Array} buffer 50577 * The current buffer of gop information 50578 * @param {Double} start 50579 * position to start the remove at 50580 * @param {Double} end 50581 * position to end the remove at 50582 * @param {Double} mapping 50583 * Offset to map display time to stream presentation time 50584 */ 50585 50586 50587 var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) { 50588 var startPts = Math.ceil((start - mapping) * clock_1); 50589 var endPts = Math.ceil((end - mapping) * clock_1); 50590 var updatedBuffer = buffer.slice(); 50591 var i = buffer.length; 50592 50593 while (i--) { 50594 if (buffer[i].pts <= endPts) { 50595 break; 50596 } 50597 } 50598 50599 if (i === -1) { 50600 // no removal because end of remove range is before start of buffer 50601 return updatedBuffer; 50602 } 50603 50604 var j = i + 1; 50605 50606 while (j--) { 50607 if (buffer[j].pts <= startPts) { 50608 break; 50609 } 50610 } // clamp remove range start to 0 index 50611 50612 50613 j = Math.max(j, 0); 50614 updatedBuffer.splice(j, i - j + 1); 50615 return updatedBuffer; 50616 }; 50617 50618 var shallowEqual = function shallowEqual(a, b) { 50619 // if both are undefined 50620 // or one or the other is undefined 50621 // they are not equal 50622 if (!a && !b || !a && b || a && !b) { 50623 return false; 50624 } // they are the same object and thus, equal 50625 50626 50627 if (a === b) { 50628 return true; 50629 } // sort keys so we can make sure they have 50630 // all the same keys later. 50631 50632 50633 var akeys = Object.keys(a).sort(); 50634 var bkeys = Object.keys(b).sort(); // different number of keys, not equal 50635 50636 if (akeys.length !== bkeys.length) { 50637 return false; 50638 } 50639 50640 for (var i = 0; i < akeys.length; i++) { 50641 var key = akeys[i]; // different sorted keys, not equal 50642 50643 if (key !== bkeys[i]) { 50644 return false; 50645 } // different values, not equal 50646 50647 50648 if (a[key] !== b[key]) { 50649 return false; 50650 } 50651 } 50652 50653 return true; 50654 }; 50655 50656 var CHECK_BUFFER_DELAY = 500; 50657 50658 var finite = function finite(num) { 50659 return typeof num === 'number' && isFinite(num); 50660 }; 50661 50662 var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, trackInfo) { 50663 // Although these checks should most likely cover non 'main' types, for now it narrows 50664 // the scope of our checks. 50665 if (loaderType !== 'main' || !startingMedia || !trackInfo) { 50666 return null; 50667 } 50668 50669 if (!trackInfo.hasAudio && !trackInfo.hasVideo) { 50670 return 'Neither audio nor video found in segment.'; 50671 } 50672 50673 if (startingMedia.hasVideo && !trackInfo.hasVideo) { 50674 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.'; 50675 } 50676 50677 if (!startingMedia.hasVideo && trackInfo.hasVideo) { 50678 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.'; 50679 } 50680 50681 return null; 50682 }; 50683 /** 50684 * Calculates a time value that is safe to remove from the back buffer without interrupting 50685 * playback. 50686 * 50687 * @param {TimeRange} seekable 50688 * The current seekable range 50689 * @param {number} currentTime 50690 * The current time of the player 50691 * @param {number} targetDuration 50692 * The target duration of the current playlist 50693 * @return {number} 50694 * Time that is safe to remove from the back buffer without interrupting playback 50695 */ 50696 50697 50698 var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable, currentTime, targetDuration) { 50699 // 30 seconds before the playhead provides a safe default for trimming. 50700 // 50701 // Choosing a reasonable default is particularly important for high bitrate content and 50702 // VOD videos/live streams with large windows, as the buffer may end up overfilled and 50703 // throw an APPEND_BUFFER_ERR. 50704 var trimTime = currentTime - Config.BACK_BUFFER_LENGTH; 50705 50706 if (seekable.length) { 50707 // Some live playlists may have a shorter window of content than the full allowed back 50708 // buffer. For these playlists, don't save content that's no longer within the window. 50709 trimTime = Math.max(trimTime, seekable.start(0)); 50710 } // Don't remove within target duration of the current time to avoid the possibility of 50711 // removing the GOP currently being played, as removing it can cause playback stalls. 50712 50713 50714 var maxTrimTime = currentTime - targetDuration; 50715 return Math.min(maxTrimTime, trimTime); 50716 }; 50717 50718 var segmentInfoString = function segmentInfoString(segmentInfo) { 50719 var _segmentInfo$segment = segmentInfo.segment, 50720 start = _segmentInfo$segment.start, 50721 end = _segmentInfo$segment.end, 50722 _segmentInfo$playlist = segmentInfo.playlist, 50723 seq = _segmentInfo$playlist.mediaSequence, 50724 id = _segmentInfo$playlist.id, 50725 _segmentInfo$playlist2 = _segmentInfo$playlist.segments, 50726 segments = _segmentInfo$playlist2 === void 0 ? [] : _segmentInfo$playlist2, 50727 index = segmentInfo.mediaIndex, 50728 timeline = segmentInfo.timeline; 50729 return ["appending [" + index + "] of [" + seq + ", " + (seq + segments.length) + "] from playlist [" + id + "]", "[" + start + " => " + end + "] in timeline [" + timeline + "]"].join(' '); 50730 }; 50731 50732 var timingInfoPropertyForMedia = function timingInfoPropertyForMedia(mediaType) { 50733 return mediaType + "TimingInfo"; 50734 }; 50735 /** 50736 * Returns the timestamp offset to use for the segment. 50737 * 50738 * @param {number} segmentTimeline 50739 * The timeline of the segment 50740 * @param {number} currentTimeline 50741 * The timeline currently being followed by the loader 50742 * @param {number} startOfSegment 50743 * The estimated segment start 50744 * @param {TimeRange[]} buffered 50745 * The loader's buffer 50746 * @param {boolean} overrideCheck 50747 * If true, no checks are made to see if the timestamp offset value should be set, 50748 * but sets it directly to a value. 50749 * 50750 * @return {number|null} 50751 * Either a number representing a new timestamp offset, or null if the segment is 50752 * part of the same timeline 50753 */ 50754 50755 50756 var timestampOffsetForSegment = function timestampOffsetForSegment(_ref) { 50757 var segmentTimeline = _ref.segmentTimeline, 50758 currentTimeline = _ref.currentTimeline, 50759 startOfSegment = _ref.startOfSegment, 50760 buffered = _ref.buffered, 50761 overrideCheck = _ref.overrideCheck; // Check to see if we are crossing a discontinuity to see if we need to set the 50762 // timestamp offset on the transmuxer and source buffer. 50763 // 50764 // Previously, we changed the timestampOffset if the start of this segment was less than 50765 // the currently set timestampOffset, but this isn't desirable as it can produce bad 50766 // behavior, especially around long running live streams. 50767 50768 if (!overrideCheck && segmentTimeline === currentTimeline) { 50769 return null; 50770 } // segmentInfo.startOfSegment used to be used as the timestamp offset, however, that 50771 // value uses the end of the last segment if it is available. While this value 50772 // should often be correct, it's better to rely on the buffered end, as the new 50773 // content post discontinuity should line up with the buffered end as if it were 50774 // time 0 for the new content.
vendor: 5,339 bytes, lines 50775-50874
50775 50776 50777 return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment; 50778 }; 50779 /** 50780 * Returns whether or not the loader should wait for a timeline change from the timeline 50781 * change controller before processing the segment. 50782 * 50783 * Primary timing in VHS goes by video. This is different from most media players, as 50784 * audio is more often used as the primary timing source. For the foreseeable future, VHS 50785 * will continue to use video as the primary timing source, due to the current logic and 50786 * expectations built around it. 50787 50788 * Since the timing follows video, in order to maintain sync, the video loader is 50789 * responsible for setting both audio and video source buffer timestamp offsets. 50790 * 50791 * Setting different values for audio and video source buffers could lead to 50792 * desyncing. The following examples demonstrate some of the situations where this 50793 * distinction is important. Note that all of these cases involve demuxed content. When 50794 * content is muxed, the audio and video are packaged together, therefore syncing 50795 * separate media playlists is not an issue. 50796 * 50797 * CASE 1: Audio prepares to load a new timeline before video: 50798 * 50799 * Timeline: 0 1 50800 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 50801 * Audio Loader: ^ 50802 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 50803 * Video Loader ^ 50804 * 50805 * In the above example, the audio loader is preparing to load the 6th segment, the first 50806 * after a discontinuity, while the video loader is still loading the 5th segment, before 50807 * the discontinuity. 50808 * 50809 * If the audio loader goes ahead and loads and appends the 6th segment before the video 50810 * loader crosses the discontinuity, then when appended, the 6th audio segment will use 50811 * the timestamp offset from timeline 0. This will likely lead to desyncing. In addition, 50812 * the audio loader must provide the audioAppendStart value to trim the content in the 50813 * transmuxer, and that value relies on the audio timestamp offset. Since the audio 50814 * timestamp offset is set by the video (main) loader, the audio loader shouldn't load the 50815 * segment until that value is provided. 50816 * 50817 * CASE 2: Video prepares to load a new timeline before audio: 50818 * 50819 * Timeline: 0 1 50820 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 50821 * Audio Loader: ^ 50822 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 50823 * Video Loader ^ 50824 * 50825 * In the above example, the video loader is preparing to load the 6th segment, the first 50826 * after a discontinuity, while the audio loader is still loading the 5th segment, before 50827 * the discontinuity. 50828 * 50829 * If the video loader goes ahead and loads and appends the 6th segment, then once the 50830 * segment is loaded and processed, both the video and audio timestamp offsets will be 50831 * set, since video is used as the primary timing source. This is to ensure content lines 50832 * up appropriately, as any modifications to the video timing are reflected by audio when 50833 * the video loader sets the audio and video timestamp offsets to the same value. However, 50834 * setting the timestamp offset for audio before audio has had a chance to change 50835 * timelines will likely lead to desyncing, as the audio loader will append segment 5 with 50836 * a timestamp intended to apply to segments from timeline 1 rather than timeline 0. 50837 * 50838 * CASE 3: When seeking, audio prepares to load a new timeline before video 50839 * 50840 * Timeline: 0 1 50841 * Audio Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 50842 * Audio Loader: ^ 50843 * Video Segments: 0 1 2 3 4 5 DISCO 6 7 8 9 50844 * Video Loader ^ 50845 * 50846 * In the above example, both audio and video loaders are loading segments from timeline 50847 * 0, but imagine that the seek originated from timeline 1. 50848 * 50849 * When seeking to a new timeline, the timestamp offset will be set based on the expected 50850 * segment start of the loaded video segment. In order to maintain sync, the audio loader 50851 * must wait for the video loader to load its segment and update both the audio and video 50852 * timestamp offsets before it may load and append its own segment. This is the case 50853 * whether the seek results in a mismatched segment request (e.g., the audio loader 50854 * chooses to load segment 3 and the video loader chooses to load segment 4) or the 50855 * loaders choose to load the same segment index from each playlist, as the segments may 50856 * not be aligned perfectly, even for matching segment indexes. 50857 * 50858 * @param {Object} timelinechangeController 50859 * @param {number} currentTimeline 50860 * The timeline currently being followed by the loader 50861 * @param {number} segmentTimeline 50862 * The timeline of the segment being loaded 50863 * @param {('main'|'audio')} loaderType 50864 * The loader type 50865 * @param {boolean} audioDisabled 50866 * Whether the audio is disabled for the loader. This should only be true when the 50867 * loader may have muxed audio in its segment, but should not append it, e.g., for 50868 * the main loader when an alternate audio playlist is active. 50869 * 50870 * @return {boolean} 50871 * Whether the loader should wait for a timeline change from the timeline change 50872 * controller before processing the segment 50873 */ 50874
50875 50876 var shouldWaitForTimelineChange = function shouldWaitForTimelineChange(_ref2) { 50877 var timelineChangeController = _ref2.timelineChangeController, 50878 currentTimeline = _ref2.currentTimeline, 50879 segmentTimeline = _ref2.segmentTimeline, 50880 loaderType = _ref2.loaderType, 50881 audioDisabled = _ref2.audioDisabled; 50882 50883 if (currentTimeline === segmentTimeline) { 50884 return false; 50885 } 50886 50887 if (loaderType === 'audio') { 50888 var lastMainTimelineChange = timelineChangeController.lastTimelineChange({ 50889 type: 'main' 50890 }); // Audio loader should wait if: 50891 // 50892 // * main hasn't had a timeline change yet (thus has not loaded its first segment) 50893 // * main hasn't yet changed to the timeline audio is looking to load 50894 50895 return !lastMainTimelineChange || lastMainTimelineChange.to !== segmentTimeline; 50896 } // The main loader only needs to wait for timeline changes if there's demuxed audio. 50897 // Otherwise, there's nothing to wait for, since audio would be muxed into the main 50898 // loader's segments (or the content is audio/video only and handled by the main 50899 // loader). 50900 50901 50902 if (loaderType === 'main' && audioDisabled) { 50903 var pendingAudioTimelineChange = timelineChangeController.pendingTimelineChange({ 50904 type: 'audio' 50905 }); // Main loader should wait for the audio loader if audio is not pending a timeline 50906 // change to the current timeline. 50907 // 50908 // Since the main loader is responsible for setting the timestamp offset for both 50909 // audio and video, the main loader must wait for audio to be about to change to its 50910 // timeline before setting the offset, otherwise, if audio is behind in loading, 50911 // segments from the previous timeline would be adjusted by the new timestamp offset. 50912 // 50913 // This requirement means that video will not cross a timeline until the audio is 50914 // about to cross to it, so that way audio and video will always cross the timeline 50915 // together. 50916 // 50917 // In addition to normal timeline changes, these rules also apply to the start of a 50918 // stream (going from a non-existent timeline, -1, to timeline 0). It's important 50919 // that these rules apply to the first timeline change because if they did not, it's 50920 // possible that the main loader will cross two timelines before the audio loader has 50921 // crossed one. Logic may be implemented to handle the startup as a special case, but 50922 // it's easier to simply treat all timeline changes the same. 50923 50924 if (pendingAudioTimelineChange && pendingAudioTimelineChange.to === segmentTimeline) { 50925 return false;
vendor: 25,927 bytes, lines 50926-51749
50926 } 50927 50928 return true; 50929 } 50930 50931 return false; 50932 }; 50933 /** 50934 * An object that manages segment loading and appending. 50935 * 50936 * @class SegmentLoader 50937 * @param {Object} options required and optional options 50938 * @extends videojs.EventTarget 50939 */ 50940 50941 50942 var SegmentLoader = /*#__PURE__*/function (_videojs$EventTarget) { 50943 inheritsLoose(SegmentLoader, _videojs$EventTarget); 50944 50945 function SegmentLoader(settings, options) { 50946 var _this; 50947 50948 _this = _videojs$EventTarget.call(this) || this; // check pre-conditions 50949 50950 if (!settings) { 50951 throw new TypeError('Initialization settings are required'); 50952 } 50953 50954 if (typeof settings.currentTime !== 'function') { 50955 throw new TypeError('No currentTime getter specified'); 50956 } 50957 50958 if (!settings.mediaSource) { 50959 throw new TypeError('No MediaSource specified'); 50960 } // public properties 50961 50962 50963 _this.bandwidth = settings.bandwidth; 50964 _this.throughput = { 50965 rate: 0, 50966 count: 0 50967 }; 50968 _this.roundTrip = NaN; 50969 50970 _this.resetStats_(); 50971 50972 _this.mediaIndex = null; // private settings 50973 50974 _this.hasPlayed_ = settings.hasPlayed; 50975 _this.currentTime_ = settings.currentTime; 50976 _this.seekable_ = settings.seekable; 50977 _this.seeking_ = settings.seeking; 50978 _this.duration_ = settings.duration; 50979 _this.mediaSource_ = settings.mediaSource; 50980 _this.vhs_ = settings.vhs; 50981 _this.loaderType_ = settings.loaderType; 50982 _this.currentMediaInfo_ = void 0; 50983 _this.startingMediaInfo_ = void 0; 50984 _this.segmentMetadataTrack_ = settings.segmentMetadataTrack; 50985 _this.goalBufferLength_ = settings.goalBufferLength; 50986 _this.sourceType_ = settings.sourceType; 50987 _this.sourceUpdater_ = settings.sourceUpdater; 50988 _this.inbandTextTracks_ = settings.inbandTextTracks; 50989 _this.state_ = 'INIT'; 50990 _this.handlePartialData_ = settings.handlePartialData; 50991 _this.timelineChangeController_ = settings.timelineChangeController; 50992 _this.shouldSaveSegmentTimingInfo_ = true; // private instance variables 50993 50994 _this.checkBufferTimeout_ = null; 50995 _this.error_ = void 0; 50996 _this.currentTimeline_ = -1; 50997 _this.pendingSegment_ = null; 50998 _this.xhrOptions_ = null; 50999 _this.pendingSegments_ = []; 51000 _this.audioDisabled_ = false; 51001 _this.isPendingTimestampOffset_ = false; // TODO possibly move gopBuffer and timeMapping info to a separate controller 51002 51003 _this.gopBuffer_ = []; 51004 _this.timeMapping_ = 0; 51005 _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11; 51006 _this.appendInitSegment_ = { 51007 audio: true, 51008 video: true 51009 }; 51010 _this.playlistOfLastInitSegment_ = { 51011 audio: null, 51012 video: null 51013 }; 51014 _this.callQueue_ = []; // If the segment loader prepares to load a segment, but does not have enough 51015 // information yet to start the loading process (e.g., if the audio loader wants to 51016 // load a segment from the next timeline but the main loader hasn't yet crossed that 51017 // timeline), then the load call will be added to the queue until it is ready to be 51018 // processed. 51019 51020 _this.loadQueue_ = []; 51021 _this.metadataQueue_ = { 51022 id3: [], 51023 caption: [] 51024 }; // Fragmented mp4 playback 51025 51026 _this.activeInitSegmentId_ = null; 51027 _this.initSegments_ = {}; // HLSe playback 51028 51029 _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys; 51030 _this.keyCache_ = {}; 51031 _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings 51032 // between a time in the display time and a segment index within 51033 // a playlist 51034 51035 _this.syncController_ = settings.syncController; 51036 _this.syncPoint_ = { 51037 segmentIndex: 0, 51038 time: 0 51039 }; 51040 _this.transmuxer_ = _this.createTransmuxer_(); 51041 51042 _this.triggerSyncInfoUpdate_ = function () { 51043 return _this.trigger('syncinfoupdate'); 51044 }; 51045 51046 _this.syncController_.on('syncinfoupdate', _this.triggerSyncInfoUpdate_); 51047 51048 _this.mediaSource_.addEventListener('sourceopen', function () { 51049 if (!_this.isEndOfStream_()) { 51050 _this.ended_ = false; 51051 } 51052 }); // ...for determining the fetch location 51053 51054 51055 _this.fetchAtBuffer_ = false; 51056 _this.logger_ = logger("SegmentLoader[" + _this.loaderType_ + "]"); 51057 Object.defineProperty(assertThisInitialized(_this), 'state', { 51058 get: function get() { 51059 return this.state_; 51060 }, 51061 set: function set(newState) { 51062 if (newState !== this.state_) { 51063 this.logger_(this.state_ + " -> " + newState); 51064 this.state_ = newState; 51065 this.trigger('statechange'); 51066 } 51067 } 51068 }); 51069 51070 _this.sourceUpdater_.on('ready', function () { 51071 if (_this.hasEnoughInfoToAppend_()) { 51072 _this.processCallQueue_(); 51073 } 51074 }); // Only the main loader needs to listen for pending timeline changes, as the main 51075 // loader should wait for audio to be ready to change its timeline so that both main 51076 // and audio timelines change together. For more details, see the 51077 // shouldWaitForTimelineChange function. 51078 51079 51080 if (_this.loaderType_ === 'main') { 51081 _this.timelineChangeController_.on('pendingtimelinechange', function () { 51082 if (_this.hasEnoughInfoToAppend_()) { 51083 _this.processCallQueue_(); 51084 } 51085 }); 51086 } // The main loader only listens on pending timeline changes, but the audio loader, 51087 // since its loads follow main, needs to listen on timeline changes. For more details, 51088 // see the shouldWaitForTimelineChange function. 51089 51090 51091 if (_this.loaderType_ === 'audio') { 51092 _this.timelineChangeController_.on('timelinechange', function () { 51093 if (_this.hasEnoughInfoToLoad_()) { 51094 _this.processLoadQueue_(); 51095 } 51096 51097 if (_this.hasEnoughInfoToAppend_()) { 51098 _this.processCallQueue_(); 51099 } 51100 }); 51101 } 51102 51103 return _this; 51104 } 51105 51106 var _proto = SegmentLoader.prototype; 51107 51108 _proto.createTransmuxer_ = function createTransmuxer_() { 51109 var transmuxer = new TransmuxWorker(); 51110 transmuxer.postMessage({ 51111 action: 'init', 51112 options: { 51113 remux: false, 51114 alignGopsAtEnd: this.safeAppend_, 51115 keepOriginalTimestamps: true, 51116 handlePartialData: this.handlePartialData_ 51117 } 51118 }); 51119 return transmuxer; 51120 } 51121 /** 51122 * reset all of our media stats 51123 * 51124 * @private 51125 */ 51126 ; 51127 51128 _proto.resetStats_ = function resetStats_() { 51129 this.mediaBytesTransferred = 0; 51130 this.mediaRequests = 0; 51131 this.mediaRequestsAborted = 0; 51132 this.mediaRequestsTimedout = 0; 51133 this.mediaRequestsErrored = 0; 51134 this.mediaTransferDuration = 0; 51135 this.mediaSecondsLoaded = 0; 51136 } 51137 /** 51138 * dispose of the SegmentLoader and reset to the default state 51139 */ 51140 ; 51141 51142 _proto.dispose = function dispose() { 51143 this.trigger('dispose'); 51144 this.state = 'DISPOSED'; 51145 this.pause(); 51146 this.abort_(); 51147 51148 if (this.transmuxer_) { 51149 this.transmuxer_.terminate(); // Although it isn't an instance of a class, the segment transmuxer must still be 51150 // cleaned up. 51151 51152 segmentTransmuxer.dispose(); 51153 } 51154 51155 this.resetStats_(); 51156 51157 if (this.checkBufferTimeout_) { 51158 window$1.clearTimeout(this.checkBufferTimeout_); 51159 } 51160 51161 if (this.syncController_ && this.triggerSyncInfoUpdate_) { 51162 this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_); 51163 } 51164 51165 this.off(); 51166 }; 51167 51168 _proto.setAudio = function setAudio(enable) { 51169 this.audioDisabled_ = !enable; 51170 51171 if (enable) { 51172 this.appendInitSegment_.audio = true; 51173 } else { 51174 // remove current track audio if it gets disabled 51175 this.sourceUpdater_.removeAudio(0, this.duration_()); 51176 } 51177 } 51178 /** 51179 * abort anything that is currently doing on with the SegmentLoader 51180 * and reset to a default state 51181 */ 51182 ; 51183 51184 _proto.abort = function abort() { 51185 if (this.state !== 'WAITING') { 51186 if (this.pendingSegment_) { 51187 this.pendingSegment_ = null; 51188 } 51189 51190 return; 51191 } 51192 51193 this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY 51194 // since we are no longer "waiting" on any requests. XHR callback is not always run 51195 // when the request is aborted. This will prevent the loader from being stuck in the 51196 // WAITING state indefinitely. 51197 51198 this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the 51199 // next segment 51200 51201 if (!this.paused()) { 51202 this.monitorBuffer_(); 51203 } 51204 } 51205 /** 51206 * abort all pending xhr requests and null any pending segements 51207 * 51208 * @private 51209 */ 51210 ; 51211 51212 _proto.abort_ = function abort_() { 51213 if (this.pendingSegment_ && this.pendingSegment_.abortRequests) { 51214 this.pendingSegment_.abortRequests(); 51215 } // clear out the segment being processed 51216 51217 51218 this.pendingSegment_ = null; 51219 this.callQueue_ = []; 51220 this.loadQueue_ = []; 51221 this.metadataQueue_.id3 = []; 51222 this.metadataQueue_.caption = []; 51223 this.timelineChangeController_.clearPendingTimelineChange(this.loaderType_); 51224 }; 51225 51226 _proto.checkForAbort_ = function checkForAbort_(requestId) { 51227 // If the state is APPENDING, then aborts will not modify the state, meaning the first 51228 // callback that happens should reset the state to READY so that loading can continue. 51229 if (this.state === 'APPENDING' && !this.pendingSegment_) { 51230 this.state = 'READY'; 51231 return true; 51232 } 51233 51234 if (!this.pendingSegment_ || this.pendingSegment_.requestId !== requestId) { 51235 return true; 51236 } 51237 51238 return false; 51239 } 51240 /** 51241 * set an error on the segment loader and null out any pending segements 51242 * 51243 * @param {Error} error the error to set on the SegmentLoader 51244 * @return {Error} the error that was set or that is currently set 51245 */ 51246 ; 51247 51248 _proto.error = function error(_error) { 51249 if (typeof _error !== 'undefined') { 51250 this.logger_('error occurred:', _error); 51251 this.error_ = _error; 51252 } 51253 51254 this.pendingSegment_ = null; 51255 return this.error_; 51256 }; 51257 51258 _proto.endOfStream = function endOfStream() { 51259 this.ended_ = true; 51260 51261 if (this.transmuxer_) { 51262 // need to clear out any cached data to prepare for the new segment 51263 segmentTransmuxer.reset(this.transmuxer_); 51264 } 51265 51266 this.gopBuffer_.length = 0; 51267 this.pause(); 51268 this.trigger('ended'); 51269 } 51270 /** 51271 * Indicates which time ranges are buffered 51272 * 51273 * @return {TimeRange} 51274 * TimeRange object representing the current buffered ranges 51275 */ 51276 ; 51277 51278 _proto.buffered_ = function buffered_() { 51279 if (!this.sourceUpdater_ || !this.startingMediaInfo_) { 51280 return videojs$1.createTimeRanges(); 51281 } 51282 51283 if (this.loaderType_ === 'main') { 51284 var _this$startingMediaIn = this.startingMediaInfo_, 51285 hasAudio = _this$startingMediaIn.hasAudio, 51286 hasVideo = _this$startingMediaIn.hasVideo, 51287 isMuxed = _this$startingMediaIn.isMuxed; 51288 51289 if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) { 51290 return this.sourceUpdater_.buffered(); 51291 } 51292 51293 if (hasVideo) { 51294 return this.sourceUpdater_.videoBuffered(); 51295 } 51296 } // One case that can be ignored for now is audio only with alt audio, 51297 // as we don't yet have proper support for that. 51298 51299 51300 return this.sourceUpdater_.audioBuffered(); 51301 } 51302 /** 51303 * Gets and sets init segment for the provided map 51304 * 51305 * @param {Object} map 51306 * The map object representing the init segment to get or set 51307 * @param {boolean=} set 51308 * If true, the init segment for the provided map should be saved 51309 * @return {Object} 51310 * map object for desired init segment 51311 */ 51312 ; 51313 51314 _proto.initSegmentForMap = function initSegmentForMap(map, set) { 51315 if (set === void 0) { 51316 set = false; 51317 } 51318 51319 if (!map) { 51320 return null; 51321 } 51322 51323 var id = initSegmentId(map); 51324 var storedMap = this.initSegments_[id]; 51325 51326 if (set && !storedMap && map.bytes) { 51327 this.initSegments_[id] = storedMap = { 51328 resolvedUri: map.resolvedUri, 51329 byterange: map.byterange, 51330 bytes: map.bytes, 51331 tracks: map.tracks, 51332 timescales: map.timescales 51333 }; 51334 } 51335 51336 return storedMap || map; 51337 } 51338 /** 51339 * Gets and sets key for the provided key 51340 * 51341 * @param {Object} key 51342 * The key object representing the key to get or set 51343 * @param {boolean=} set 51344 * If true, the key for the provided key should be saved 51345 * @return {Object} 51346 * Key object for desired key 51347 */ 51348 ; 51349 51350 _proto.segmentKey = function segmentKey(key, set) { 51351 if (set === void 0) { 51352 set = false; 51353 } 51354 51355 if (!key) { 51356 return null; 51357 } 51358 51359 var id = segmentKeyId(key); 51360 var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per 51361 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3 51362 51363 if (this.cacheEncryptionKeys_ && set && !storedKey && key.bytes) { 51364 this.keyCache_[id] = storedKey = { 51365 resolvedUri: key.resolvedUri, 51366 bytes: key.bytes 51367 }; 51368 } 51369 51370 var result = { 51371 resolvedUri: (storedKey || key).resolvedUri 51372 }; 51373 51374 if (storedKey) { 51375 result.bytes = storedKey.bytes; 51376 } 51377 51378 return result; 51379 } 51380 /** 51381 * Returns true if all configuration required for loading is present, otherwise false. 51382 * 51383 * @return {boolean} True if the all configuration is ready for loading 51384 * @private 51385 */ 51386 ; 51387 51388 _proto.couldBeginLoading_ = function couldBeginLoading_() { 51389 return this.playlist_ && !this.paused(); 51390 } 51391 /** 51392 * load a playlist and start to fill the buffer 51393 */ 51394 ; 51395 51396 _proto.load = function load() { 51397 // un-pause 51398 this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be 51399 // specified 51400 51401 if (!this.playlist_) { 51402 return; 51403 } // not sure if this is the best place for this 51404 51405 51406 this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading 51407 51408 if (this.state === 'INIT' && this.couldBeginLoading_()) { 51409 return this.init_(); 51410 } // if we're in the middle of processing a segment already, don't 51411 // kick off an additional segment request 51412 51413 51414 if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') { 51415 return; 51416 } 51417 51418 this.state = 'READY'; 51419 } 51420 /** 51421 * Once all the starting parameters have been specified, begin 51422 * operation. This method should only be invoked from the INIT 51423 * state. 51424 * 51425 * @private 51426 */ 51427 ; 51428 51429 _proto.init_ = function init_() { 51430 this.state = 'READY'; // if this is the audio segment loader, and it hasn't been inited before, then any old 51431 // audio data from the muxed content should be removed 51432 51433 this.resetEverything(); 51434 return this.monitorBuffer_(); 51435 } 51436 /** 51437 * set a playlist on the segment loader 51438 * 51439 * @param {PlaylistLoader} media the playlist to set on the segment loader 51440 */ 51441 ; 51442 51443 _proto.playlist = function playlist(newPlaylist, options) { 51444 if (options === void 0) { 51445 options = {}; 51446 } 51447 51448 if (!newPlaylist) { 51449 return; 51450 } 51451 51452 var oldPlaylist = this.playlist_; 51453 var segmentInfo = this.pendingSegment_; 51454 this.playlist_ = newPlaylist; 51455 this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist 51456 // is always our zero-time so force a sync update each time the playlist 51457 // is refreshed from the server 51458 // 51459 // Use the INIT state to determine if playback has started, as the playlist sync info 51460 // should be fixed once requests begin (as sync points are generated based on sync 51461 // info), but not before then. 51462 51463 if (this.state === 'INIT') { 51464 newPlaylist.syncInfo = { 51465 mediaSequence: newPlaylist.mediaSequence, 51466 time: 0 51467 }; 51468 } 51469 51470 var oldId = null; 51471 51472 if (oldPlaylist) { 51473 if (oldPlaylist.id) { 51474 oldId = oldPlaylist.id; 51475 } else if (oldPlaylist.uri) { 51476 oldId = oldPlaylist.uri; 51477 } 51478 } 51479 51480 this.logger_("playlist update [" + oldId + " => " + (newPlaylist.id || newPlaylist.uri) + "]"); // in VOD, this is always a rendition switch (or we updated our syncInfo above) 51481 // in LIVE, we always want to update with new playlists (including refreshes) 51482 51483 this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start 51484 // buffering now 51485 51486 if (this.state === 'INIT' && this.couldBeginLoading_()) { 51487 return this.init_(); 51488 } 51489 51490 if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) { 51491 if (this.mediaIndex !== null || this.handlePartialData_) { 51492 // we must "resync" the segment loader when we switch renditions and 51493 // the segment loader is already synced to the previous rendition 51494 // 51495 // or if we're handling partial data, we need to ensure the transmuxer is cleared 51496 // out before we start adding more data 51497 this.resyncLoader(); 51498 } 51499 51500 this.currentMediaInfo_ = void 0; 51501 this.trigger('playlistupdate'); // the rest of this function depends on `oldPlaylist` being defined 51502 51503 return; 51504 } // we reloaded the same playlist so we are in a live scenario 51505 // and we will likely need to adjust the mediaIndex 51506 51507 51508 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 51509 this.logger_("live window shift [" + mediaSequenceDiff + "]"); // update the mediaIndex on the SegmentLoader 51510 // this is important because we can abort a request and this value must be 51511 // equal to the last appended mediaIndex 51512 51513 if (this.mediaIndex !== null) { 51514 this.mediaIndex -= mediaSequenceDiff; 51515 } // update the mediaIndex on the SegmentInfo object 51516 // this is important because we will update this.mediaIndex with this value 51517 // in `handleAppendsDone_` after the segment has been successfully appended 51518 51519 51520 if (segmentInfo) { 51521 segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is 51522 // saved for the new playlist's segment, however, if the segment fell off the 51523 // playlist, we can leave the old reference and just lose the timing info 51524 51525 if (segmentInfo.mediaIndex >= 0) { 51526 segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex]; 51527 } 51528 } 51529 51530 this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist); 51531 } 51532 /** 51533 * Prevent the loader from fetching additional segments. If there 51534 * is a segment request outstanding, it will finish processing 51535 * before the loader halts. A segment loader can be unpaused by 51536 * calling load(). 51537 */ 51538 ; 51539 51540 _proto.pause = function pause() { 51541 if (this.checkBufferTimeout_) { 51542 window$1.clearTimeout(this.checkBufferTimeout_); 51543 this.checkBufferTimeout_ = null; 51544 } 51545 } 51546 /** 51547 * Returns whether the segment loader is fetching additional 51548 * segments when given the opportunity. This property can be 51549 * modified through calls to pause() and load(). 51550 */ 51551 ; 51552 51553 _proto.paused = function paused() { 51554 return this.checkBufferTimeout_ === null; 51555 } 51556 /** 51557 * Delete all the buffered data and reset the SegmentLoader 51558 * 51559 * @param {Function} [done] an optional callback to be executed when the remove 51560 * operation is complete 51561 */ 51562 ; 51563 51564 _proto.resetEverything = function resetEverything(done) { 51565 this.ended_ = false; 51566 this.appendInitSegment_ = { 51567 audio: true, 51568 video: true 51569 }; 51570 this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything. 51571 // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader, 51572 // we then clamp the value to duration if necessary. 51573 51574 this.remove(0, Infinity, done); // clears fmp4 captions 51575 51576 if (this.transmuxer_) { 51577 this.transmuxer_.postMessage({ 51578 action: 'clearAllMp4Captions' 51579 }); 51580 } 51581 } 51582 /** 51583 * Force the SegmentLoader to resync and start loading around the currentTime instead 51584 * of starting at the end of the buffer 51585 * 51586 * Useful for fast quality changes 51587 */ 51588 ; 51589 51590 _proto.resetLoader = function resetLoader() { 51591 this.fetchAtBuffer_ = false; 51592 this.resyncLoader(); 51593 } 51594 /** 51595 * Force the SegmentLoader to restart synchronization and make a conservative guess 51596 * before returning to the simple walk-forward method 51597 */ 51598 ; 51599 51600 _proto.resyncLoader = function resyncLoader() { 51601 if (this.transmuxer_) { 51602 // need to clear out any cached data to prepare for the new segment 51603 segmentTransmuxer.reset(this.transmuxer_); 51604 } 51605 51606 this.mediaIndex = null; 51607 this.syncPoint_ = null; 51608 this.isPendingTimestampOffset_ = false; 51609 this.callQueue_ = []; 51610 this.loadQueue_ = []; 51611 this.metadataQueue_.id3 = []; 51612 this.metadataQueue_.caption = []; 51613 this.abort(); 51614 51615 if (this.transmuxer_) { 51616 this.transmuxer_.postMessage({ 51617 action: 'clearParsedMp4Captions' 51618 }); 51619 } 51620 } 51621 /** 51622 * Remove any data in the source buffer between start and end times 51623 * 51624 * @param {number} start - the start time of the region to remove from the buffer 51625 * @param {number} end - the end time of the region to remove from the buffer 51626 * @param {Function} [done] - an optional callback to be executed when the remove 51627 * operation is complete 51628 */ 51629 ; 51630 51631 _proto.remove = function remove(start, end, done) { 51632 if (done === void 0) { 51633 done = function done() {}; 51634 } // clamp end to duration if we need to remove everything. 51635 // This is due to a browser bug that causes issues if we remove to Infinity. 51636 // videojs/videojs-contrib-hls#1225 51637 51638 51639 if (end === Infinity) { 51640 end = this.duration_(); 51641 } 51642 51643 if (!this.sourceUpdater_ || !this.currentMediaInfo_) { 51644 // nothing to remove if we haven't processed any media 51645 return; 51646 } // set it to one to complete this function's removes 51647 51648 51649 var removesRemaining = 1; 51650 51651 var removeFinished = function removeFinished() { 51652 removesRemaining--; 51653 51654 if (removesRemaining === 0) { 51655 done(); 51656 } 51657 }; 51658 51659 if (!this.audioDisabled_) { 51660 removesRemaining++; 51661 this.sourceUpdater_.removeAudio(start, end, removeFinished); 51662 } 51663 51664 if (this.loaderType_ === 'main' && this.currentMediaInfo_ && this.currentMediaInfo_.hasVideo) { 51665 this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_); 51666 removesRemaining++; 51667 this.sourceUpdater_.removeVideo(start, end, removeFinished); 51668 } // remove any captions and ID3 tags 51669 51670 51671 for (var track in this.inbandTextTracks_) { 51672 removeCuesFromTrack(start, end, this.inbandTextTracks_[track]); 51673 } 51674 51675 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); // finished this function's removes 51676 51677 removeFinished(); 51678 } 51679 /** 51680 * (re-)schedule monitorBufferTick_ to run as soon as possible 51681 * 51682 * @private 51683 */ 51684 ; 51685 51686 _proto.monitorBuffer_ = function monitorBuffer_() { 51687 if (this.checkBufferTimeout_) { 51688 window$1.clearTimeout(this.checkBufferTimeout_); 51689 } 51690 51691 this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1); 51692 } 51693 /** 51694 * As long as the SegmentLoader is in the READY state, periodically 51695 * invoke fillBuffer_(). 51696 * 51697 * @private 51698 */ 51699 ; 51700 51701 _proto.monitorBufferTick_ = function monitorBufferTick_() { 51702 if (this.state === 'READY') { 51703 this.fillBuffer_(); 51704 } 51705 51706 if (this.checkBufferTimeout_) { 51707 window$1.clearTimeout(this.checkBufferTimeout_); 51708 } 51709 51710 this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY); 51711 } 51712 /** 51713 * fill the buffer with segements unless the sourceBuffers are 51714 * currently updating 51715 * 51716 * Note: this function should only ever be called by monitorBuffer_ 51717 * and never directly 51718 * 51719 * @private 51720 */ 51721 ; 51722 51723 _proto.fillBuffer_ = function fillBuffer_() { 51724 // TODO since the source buffer maintains a queue, and we shouldn't call this function 51725 // except when we're ready for the next segment, this check can most likely be removed 51726 if (this.sourceUpdater_.updating()) { 51727 return; 51728 } 51729 51730 if (!this.syncPoint_) { 51731 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 51732 } 51733 51734 var buffered = this.buffered_(); // see if we need to begin loading immediately 51735 51736 var segmentInfo = this.checkBuffer_(buffered, this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 51737 51738 if (!segmentInfo) { 51739 return; 51740 } 51741 51742 segmentInfo.timestampOffset = timestampOffsetForSegment({ 51743 segmentTimeline: segmentInfo.timeline, 51744 currentTimeline: this.currentTimeline_, 51745 startOfSegment: segmentInfo.startOfSegment, 51746 buffered: buffered, 51747 overrideCheck: this.isPendingTimestampOffset_ 51748 }); 51749 this.isPendingTimestampOffset_ = false;
51750 51751 if (typeof segmentInfo.timestampOffset === 'number') { 51752 this.timelineChangeController_.pendingTimelineChange({ 51753 type: this.loaderType_, 51754 from: this.currentTimeline_, 51755 to: segmentInfo.timeline 51756 }); 51757 } 51758 51759 this.loadSegment_(segmentInfo); 51760 } 51761 /** 51762 * Determines if we should call endOfStream on the media source based 51763 * on the state of the buffer or if appened segment was the final 51764 * segment in the playlist. 51765 * 51766 * @param {number} [mediaIndex] the media index of segment we last appended 51767 * @param {Object} [playlist] a media playlist object 51768 * @return {boolean} do we need to call endOfStream on the MediaSource 51769 */ 51770 ; 51771 51772 _proto.isEndOfStream_ = function isEndOfStream_(mediaIndex, playlist) { 51773 if (mediaIndex === void 0) { 51774 mediaIndex = this.mediaIndex; 51775 } 51776 51777 if (playlist === void 0) { 51778 playlist = this.playlist_; 51779 } 51780 51781 if (!playlist || !this.mediaSource_) { 51782 return false; 51783 } // mediaIndex is zero based but length is 1 based 51784 51785 51786 var appendedLastSegment = mediaIndex + 1 === playlist.segments.length; // if we've buffered to the end of the video, we need to call endOfStream 51787 // so that MediaSources can trigger the `ended` event when it runs out of 51788 // buffered data instead of waiting for me 51789 51790 return playlist.endList && this.mediaSource_.readyState === 'open' && appendedLastSegment; 51791 } 51792 /** 51793 * Determines what segment request should be made, given current playback 51794 * state. 51795 * 51796 * @param {TimeRanges} buffered - the state of the buffer 51797 * @param {Object} playlist - the playlist object to fetch segments from 51798 * @param {number} mediaIndex - the previous mediaIndex fetched or null 51799 * @param {boolean} hasPlayed - a flag indicating whether we have played or not 51800 * @param {number} currentTime - the playback position in seconds 51801 * @param {Object} syncPoint - a segment info object that describes the 51802 * @return {Object} a segment request object that describes the segment to load 51803 */ 51804 ; 51805 51806 _proto.checkBuffer_ = function checkBuffer_(buffered, playlist, currentMediaIndex, hasPlayed, currentTime, syncPoint) { 51807 var lastBufferedEnd = 0; 51808 51809 if (buffered.length) { 51810 lastBufferedEnd = buffered.end(buffered.length - 1); 51811 } 51812 51813 var bufferedTime = Math.max(0, lastBufferedEnd - currentTime); 51814 51815 if (!playlist.segments.length) { 51816 return null; 51817 } // if there is plenty of content buffered, and the video has 51818 // been played before relax for awhile 51819 51820 51821 if (bufferedTime >= this.goalBufferLength_()) { 51822 return null; 51823 } // if the video has not yet played once, and we already have 51824 // one segment downloaded do nothing 51825 51826 51827 if (!hasPlayed && bufferedTime >= 1) { 51828 return null; 51829 } 51830 51831 var nextMediaIndex = null; 51832 var startOfSegment; 51833 var isSyncRequest = false; // When the syncPoint is null, there is no way of determining a good 51834 // conservative segment index to fetch from 51835 // The best thing to do here is to get the kind of sync-point data by 51836 // making a request 51837 51838 if (syncPoint === null) { 51839 nextMediaIndex = this.getSyncSegmentCandidate_(playlist); 51840 isSyncRequest = true; 51841 } else if (currentMediaIndex !== null) { 51842 // Under normal playback conditions fetching is a simple walk forward 51843 var segment = playlist.segments[currentMediaIndex]; 51844 51845 if (segment && segment.end) { 51846 startOfSegment = segment.end; 51847 } else { 51848 startOfSegment = lastBufferedEnd; 51849 } 51850 51851 nextMediaIndex = currentMediaIndex + 1; // There is a sync-point but the lack of a mediaIndex indicates that 51852 // we need to make a good conservative guess about which segment to 51853 // fetch 51854 } else if (this.fetchAtBuffer_) { 51855 // Find the segment containing the end of the buffer 51856 var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time); 51857 nextMediaIndex = mediaSourceInfo.mediaIndex; 51858 startOfSegment = mediaSourceInfo.startTime; 51859 } else { 51860 // Find the segment containing currentTime 51861 var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time); 51862 51863 nextMediaIndex = _mediaSourceInfo.mediaIndex; 51864 startOfSegment = _mediaSourceInfo.startTime; 51865 } 51866 51867 var segmentInfo = this.generateSegmentInfo_(playlist, nextMediaIndex, startOfSegment, isSyncRequest); 51868 51869 if (!segmentInfo) { 51870 return; 51871 } // if this is the last segment in the playlist 51872 // we are not seeking and end of stream has already been called 51873 // do not re-request 51874 51875 51876 if (this.mediaSource_ && this.playlist_ && segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { 51877 return; 51878 } 51879 51880 this.logger_("checkBuffer_ returning " + segmentInfo.uri, { 51881 segmentInfo: segmentInfo, 51882 playlist: playlist, 51883 currentMediaIndex: currentMediaIndex, 51884 nextMediaIndex: nextMediaIndex, 51885 startOfSegment: startOfSegment, 51886 isSyncRequest: isSyncRequest 51887 }); 51888 return segmentInfo; 51889 } 51890 /** 51891 * The segment loader has no recourse except to fetch a segment in the 51892 * current playlist and use the internal timestamps in that segment to 51893 * generate a syncPoint. This function returns a good candidate index 51894 * for that process. 51895 * 51896 * @param {Object} playlist - the playlist object to look for a 51897 * @return {number} An index of a segment from the playlist to load 51898 */ 51899 ; 51900 51901 _proto.getSyncSegmentCandidate_ = function getSyncSegmentCandidate_(playlist) { 51902 var _this2 = this; 51903 51904 if (this.currentTimeline_ === -1) { 51905 return 0; 51906 } 51907 51908 var segmentIndexArray = playlist.segments.map(function (s, i) { 51909 return { 51910 timeline: s.timeline, 51911 segmentIndex: i 51912 }; 51913 }).filter(function (s) { 51914 return s.timeline === _this2.currentTimeline_; 51915 }); 51916 51917 if (segmentIndexArray.length) { 51918 return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex; 51919 } 51920 51921 return Math.max(playlist.segments.length - 1, 0); 51922 }; 51923 51924 _proto.generateSegmentInfo_ = function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) { 51925 if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) { 51926 return null; 51927 } 51928 51929 var segment = playlist.segments[mediaIndex];
51930 var audioBuffered = this.sourceUpdater_.audioBuffered(); 51931 var videoBuffered = this.sourceUpdater_.videoBuffered(); 51932 var audioAppendStart; 51933 var gopsToAlignWith; 51934 51935 if (audioBuffered.length) { 51936 // since the transmuxer is using the actual timing values, but the buffer is 51937 // adjusted by the timestamp offset, we must adjust the value here 51938 audioAppendStart = audioBuffered.end(audioBuffered.length - 1) - this.sourceUpdater_.audioTimestampOffset(); 51939 } 51940 51941 if (videoBuffered.length) { 51942 gopsToAlignWith = gopsSafeToAlignWith(this.gopBuffer_, // since the transmuxer is using the actual timing values, but the time is 51943 // adjusted by the timestmap offset, we must adjust the value here 51944 this.currentTime_() - this.sourceUpdater_.videoTimestampOffset(), this.timeMapping_); 51945 } 51946 51947 return { 51948 requestId: 'segment-loader-' + Math.random(), 51949 // resolve the segment URL relative to the playlist 51950 uri: segment.resolvedUri, 51951 // the segment's mediaIndex at the time it was requested 51952 mediaIndex: mediaIndex, 51953 // whether or not to update the SegmentLoader's state with this 51954 // segment's mediaIndex 51955 isSyncRequest: isSyncRequest, 51956 startOfSegment: startOfSegment, 51957 // the segment's playlist 51958 playlist: playlist, 51959 // unencrypted bytes of the segment 51960 bytes: null, 51961 // when a key is defined for this segment, the encrypted bytes 51962 encryptedBytes: null, 51963 // The target timestampOffset for this segment when we append it 51964 // to the source buffer 51965 timestampOffset: null, 51966 // The timeline that the segment is in 51967 timeline: segment.timeline, 51968 // The expected duration of the segment in seconds 51969 duration: segment.duration, 51970 // retain the segment in case the playlist updates while doing an async process 51971 segment: segment, 51972 byteLength: 0, 51973 transmuxer: this.transmuxer_, 51974 audioAppendStart: audioAppendStart, 51975 gopsToAlignWith: gopsToAlignWith 51976 }; 51977 } 51978 /** 51979 * Determines if the network has enough bandwidth to complete the current segment 51980 * request in a timely manner. If not, the request will be aborted early and bandwidth 51981 * updated to trigger a playlist switch. 51982 * 51983 * @param {Object} stats 51984 * Object containing stats about the request timing and size 51985 * @return {boolean} True if the request was aborted, false otherwise 51986 * @private 51987 */ 51988 ; 51989 51990 _proto.abortRequestEarly_ = function abortRequestEarly_(stats) { 51991 if (this.vhs_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition 51992 // TODO: Replace using timeout with a boolean indicating whether this playlist is 51993 // the lowestEnabledRendition. 51994 !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes 51995 !this.playlist_.attributes.BANDWIDTH) { 51996 return false; 51997 } // Wait at least 1 second since the first byte of data has been received before 51998 // using the calculated bandwidth from the progress event to allow the bitrate 51999 // to stabilize 52000 52001 52002 if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) { 52003 return false; 52004 } 52005 52006 var currentTime = this.currentTime_(); 52007 var measuredBandwidth = stats.bandwidth; 52008 var segmentDuration = this.pendingSegment_.duration; 52009 var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort 52010 // if we are only left with less than 1 second when the request completes. 52011 // A negative timeUntilRebuffering indicates we are already rebuffering 52012 52013 var timeUntilRebuffer$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.vhs_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download 52014 // is larger than the estimated time until the player runs out of forward buffer 52015 52016 if (requestTimeRemaining <= timeUntilRebuffer$1) { 52017 return false;
vendor: 20,105 bytes, lines 52018-52493
52018 } 52019 52020 var switchCandidate = minRebufferMaxBandwidthSelector({ 52021 master: this.vhs_.playlists.master, 52022 currentTime: currentTime, 52023 bandwidth: measuredBandwidth, 52024 duration: this.duration_(), 52025 segmentDuration: segmentDuration, 52026 timeUntilRebuffer: timeUntilRebuffer$1, 52027 currentTimeline: this.currentTimeline_, 52028 syncController: this.syncController_ 52029 }); 52030 52031 if (!switchCandidate) { 52032 return; 52033 } 52034 52035 var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$1; 52036 var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact; 52037 var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the 52038 // potential round trip time of the new request so that we are not too aggressive 52039 // with switching to a playlist that might save us a fraction of a second. 52040 52041 if (timeUntilRebuffer$1 <= TIME_FUDGE_FACTOR) { 52042 minimumTimeSaving = 1; 52043 } 52044 52045 if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) { 52046 return false; 52047 } // set the bandwidth to that of the desired playlist being sure to scale by 52048 // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it 52049 // don't trigger a bandwidthupdate as the bandwidth is artifial 52050 52051 52052 this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1; 52053 this.abort(); 52054 this.trigger('earlyabort'); 52055 return true; 52056 }; 52057 52058 _proto.handleAbort_ = function handleAbort_() { 52059 this.mediaRequestsAborted += 1; 52060 } 52061 /** 52062 * XHR `progress` event handler 52063 * 52064 * @param {Event} 52065 * The XHR `progress` event 52066 * @param {Object} simpleSegment 52067 * A simplified segment object copy 52068 * @private 52069 */ 52070 ; 52071 52072 _proto.handleProgress_ = function handleProgress_(event, simpleSegment) { 52073 if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) { 52074 return; 52075 } 52076 52077 this.trigger('progress'); 52078 }; 52079 52080 _proto.handleTrackInfo_ = function handleTrackInfo_(simpleSegment, trackInfo) { 52081 if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) { 52082 return; 52083 } 52084 52085 if (this.checkForIllegalMediaSwitch(trackInfo)) { 52086 return; 52087 } 52088 52089 trackInfo = trackInfo || {}; // When we have track info, determine what media types this loader is dealing with. 52090 // Guard against cases where we're not getting track info at all until we are 52091 // certain that all streams will provide it. 52092 52093 if (!shallowEqual(this.currentMediaInfo_, trackInfo)) { 52094 this.appendInitSegment_ = { 52095 audio: true, 52096 video: true 52097 }; 52098 this.startingMediaInfo_ = trackInfo; 52099 this.currentMediaInfo_ = trackInfo; 52100 this.logger_('trackinfo update', trackInfo); 52101 this.trigger('trackinfo'); 52102 } // trackinfo may cause an abort if the trackinfo 52103 // causes a codec change to an unsupported codec. 52104 52105 52106 if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) { 52107 return; 52108 } // set trackinfo on the pending segment so that 52109 // it can append. 52110 52111 52112 this.pendingSegment_.trackInfo = trackInfo; // check if any calls were waiting on the track info 52113 52114 if (this.hasEnoughInfoToAppend_()) { 52115 this.processCallQueue_(); 52116 } 52117 }; 52118 52119 _proto.handleTimingInfo_ = function handleTimingInfo_(simpleSegment, mediaType, timeType, time) { 52120 if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) { 52121 return; 52122 } 52123 52124 var segmentInfo = this.pendingSegment_; 52125 var timingInfoProperty = timingInfoPropertyForMedia(mediaType); 52126 segmentInfo[timingInfoProperty] = segmentInfo[timingInfoProperty] || {}; 52127 segmentInfo[timingInfoProperty][timeType] = time; 52128 this.logger_("timinginfo: " + mediaType + " - " + timeType + " - " + time); // check if any calls were waiting on the timing info 52129 52130 if (this.hasEnoughInfoToAppend_()) { 52131 this.processCallQueue_(); 52132 } 52133 }; 52134 52135 _proto.handleCaptions_ = function handleCaptions_(simpleSegment, captionData) { 52136 var _this3 = this; 52137 52138 if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) { 52139 return; 52140 } // This could only happen with fmp4 segments, but 52141 // should still not happen in general 52142 52143 52144 if (captionData.length === 0) { 52145 this.logger_('SegmentLoader received no captions from a caption event'); 52146 return; 52147 } 52148 52149 var segmentInfo = this.pendingSegment_; // Wait until we have some video data so that caption timing 52150 // can be adjusted by the timestamp offset 52151 52152 if (!segmentInfo.hasAppendedData_) { 52153 this.metadataQueue_.caption.push(this.handleCaptions_.bind(this, simpleSegment, captionData)); 52154 return; 52155 } 52156 52157 var timestampOffset = this.sourceUpdater_.videoTimestampOffset() === null ? this.sourceUpdater_.audioTimestampOffset() : this.sourceUpdater_.videoTimestampOffset(); 52158 var captionTracks = {}; // get total start/end and captions for each track/stream 52159 52160 captionData.forEach(function (caption) { 52161 // caption.stream is actually a track name... 52162 // set to the existing values in tracks or default values 52163 captionTracks[caption.stream] = captionTracks[caption.stream] || { 52164 // Infinity, as any other value will be less than this 52165 startTime: Infinity, 52166 captions: [], 52167 // 0 as an other value will be more than this 52168 endTime: 0 52169 }; 52170 var captionTrack = captionTracks[caption.stream]; 52171 captionTrack.startTime = Math.min(captionTrack.startTime, caption.startTime + timestampOffset); 52172 captionTrack.endTime = Math.max(captionTrack.endTime, caption.endTime + timestampOffset); 52173 captionTrack.captions.push(caption); 52174 }); 52175 Object.keys(captionTracks).forEach(function (trackName) { 52176 var _captionTracks$trackN = captionTracks[trackName], 52177 startTime = _captionTracks$trackN.startTime, 52178 endTime = _captionTracks$trackN.endTime, 52179 captions = _captionTracks$trackN.captions; 52180 var inbandTextTracks = _this3.inbandTextTracks_; 52181 52182 _this3.logger_("adding cues from " + startTime + " -> " + endTime + " for " + trackName); 52183 52184 createCaptionsTrackIfNotExists(inbandTextTracks, _this3.vhs_.tech_, trackName); // clear out any cues that start and end at the same time period for the same track. 52185 // We do this because a rendition change that also changes the timescale for captions 52186 // will result in captions being re-parsed for certain segments. If we add them again 52187 // without clearing we will have two of the same captions visible. 52188 52189 removeCuesFromTrack(startTime, endTime, inbandTextTracks[trackName]); 52190 addCaptionData({ 52191 captionArray: captions, 52192 inbandTextTracks: inbandTextTracks, 52193 timestampOffset: timestampOffset 52194 }); 52195 }); // Reset stored captions since we added parsed 52196 // captions to a text track at this point 52197 52198 if (this.transmuxer_) { 52199 this.transmuxer_.postMessage({ 52200 action: 'clearParsedMp4Captions' 52201 }); 52202 } 52203 }; 52204 52205 _proto.handleId3_ = function handleId3_(simpleSegment, id3Frames, dispatchType) { 52206 if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) { 52207 return; 52208 } 52209 52210 var segmentInfo = this.pendingSegment_; // we need to have appended data in order for the timestamp offset to be set 52211 52212 if (!segmentInfo.hasAppendedData_) { 52213 this.metadataQueue_.id3.push(this.handleId3_.bind(this, simpleSegment, id3Frames, dispatchType)); 52214 return; 52215 } 52216 52217 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 52218 // audio/video source with a metadata track, and an alt audio with a metadata track. 52219 // However, this probably won't happen, and if it does it can be handled then. 52220 52221 createMetadataTrackIfNotExists(this.inbandTextTracks_, dispatchType, this.vhs_.tech_); 52222 addMetadata({ 52223 inbandTextTracks: this.inbandTextTracks_, 52224 metadataArray: id3Frames, 52225 timestampOffset: timestampOffset, 52226 videoDuration: this.duration_() 52227 }); 52228 }; 52229 52230 _proto.processMetadataQueue_ = function processMetadataQueue_() { 52231 this.metadataQueue_.id3.forEach(function (fn) { 52232 return fn(); 52233 }); 52234 this.metadataQueue_.caption.forEach(function (fn) { 52235 return fn(); 52236 }); 52237 this.metadataQueue_.id3 = []; 52238 this.metadataQueue_.caption = []; 52239 }; 52240 52241 _proto.processCallQueue_ = function processCallQueue_() { 52242 var callQueue = this.callQueue_; // Clear out the queue before the queued functions are run, since some of the 52243 // functions may check the length of the load queue and default to pushing themselves 52244 // back onto the queue. 52245 52246 this.callQueue_ = []; 52247 callQueue.forEach(function (fun) { 52248 return fun(); 52249 }); 52250 }; 52251 52252 _proto.processLoadQueue_ = function processLoadQueue_() { 52253 var loadQueue = this.loadQueue_; // Clear out the queue before the queued functions are run, since some of the 52254 // functions may check the length of the load queue and default to pushing themselves 52255 // back onto the queue. 52256 52257 this.loadQueue_ = []; 52258 loadQueue.forEach(function (fun) { 52259 return fun(); 52260 }); 52261 } 52262 /** 52263 * Determines whether the loader has enough info to load the next segment. 52264 * 52265 * @return {boolean} 52266 * Whether or not the loader has enough info to load the next segment 52267 */ 52268 ; 52269 52270 _proto.hasEnoughInfoToLoad_ = function hasEnoughInfoToLoad_() { 52271 // Since primary timing goes by video, only the audio loader potentially needs to wait 52272 // to load. 52273 if (this.loaderType_ !== 'audio') { 52274 return true; 52275 } 52276 52277 var segmentInfo = this.pendingSegment_; // A fill buffer must have already run to establish a pending segment before there's 52278 // enough info to load. 52279 52280 if (!segmentInfo) { 52281 return false; 52282 } // The first segment can and should be loaded immediately so that source buffers are 52283 // created together (before appending). Source buffer creation uses the presence of 52284 // audio and video data to determine whether to create audio/video source buffers, and 52285 // uses processed (transmuxed or parsed) media to determine the types required. 52286 52287 52288 if (!this.currentMediaInfo_) { 52289 return true; 52290 } 52291 52292 if ( // Technically, instead of waiting to load a segment on timeline changes, a segment 52293 // can be requested and downloaded and only wait before it is transmuxed or parsed. 52294 // But in practice, there are a few reasons why it is better to wait until a loader 52295 // is ready to append that segment before requesting and downloading: 52296 // 52297 // 1. Because audio and main loaders cross discontinuities together, if this loader 52298 // is waiting for the other to catch up, then instead of requesting another 52299 // segment and using up more bandwidth, by not yet loading, more bandwidth is 52300 // allotted to the loader currently behind. 52301 // 2. media-segment-request doesn't have to have logic to consider whether a segment 52302 // is ready to be processed or not, isolating the queueing behavior to the loader. 52303 // 3. The audio loader bases some of its segment properties on timing information 52304 // provided by the main loader, meaning that, if the logic for waiting on 52305 // processing was in media-segment-request, then it would also need to know how 52306 // to re-generate the segment information after the main loader caught up. 52307 shouldWaitForTimelineChange({ 52308 timelineChangeController: this.timelineChangeController_, 52309 currentTimeline: this.currentTimeline_, 52310 segmentTimeline: segmentInfo.timeline, 52311 loaderType: this.loaderType_, 52312 audioDisabled: this.audioDisabled_ 52313 })) { 52314 return false; 52315 } 52316 52317 return true; 52318 }; 52319 52320 _proto.hasEnoughInfoToAppend_ = function hasEnoughInfoToAppend_() { 52321 if (!this.sourceUpdater_.ready()) { 52322 // waiting on one of the segment loaders to get enough data to create source buffers 52323 return false; 52324 } 52325 52326 var segmentInfo = this.pendingSegment_; // no segment to append any data for or 52327 // we do not have information on this specific 52328 // segment yet 52329 52330 if (!segmentInfo || !segmentInfo.trackInfo) { 52331 return false; 52332 } 52333 52334 if (!this.handlePartialData_) { 52335 var _this$currentMediaInf = this.currentMediaInfo_, 52336 hasAudio = _this$currentMediaInf.hasAudio, 52337 hasVideo = _this$currentMediaInf.hasVideo, 52338 isMuxed = _this$currentMediaInf.isMuxed; 52339 52340 if (hasVideo && !segmentInfo.videoTimingInfo) { 52341 return false; 52342 } // muxed content only relies on video timing information for now. 52343 52344 52345 if (hasAudio && !this.audioDisabled_ && !isMuxed && !segmentInfo.audioTimingInfo) { 52346 return false; 52347 } 52348 } 52349 52350 if (shouldWaitForTimelineChange({ 52351 timelineChangeController: this.timelineChangeController_, 52352 currentTimeline: this.currentTimeline_, 52353 segmentTimeline: segmentInfo.timeline, 52354 loaderType: this.loaderType_, 52355 audioDisabled: this.audioDisabled_ 52356 })) { 52357 return false; 52358 } 52359 52360 return true; 52361 }; 52362 52363 _proto.handleData_ = function handleData_(simpleSegment, result) { 52364 if (this.checkForAbort_(simpleSegment.requestId) || this.abortRequestEarly_(simpleSegment.stats)) { 52365 return; 52366 } // If there's anything in the call queue, then this data came later and should be 52367 // executed after the calls currently queued. 52368 52369 52370 if (this.callQueue_.length || !this.hasEnoughInfoToAppend_()) { 52371 this.callQueue_.push(this.handleData_.bind(this, simpleSegment, result)); 52372 return; 52373 } 52374 52375 var segmentInfo = this.pendingSegment_; // update the time mapping so we can translate from display time to media time 52376 52377 this.setTimeMapping_(segmentInfo.timeline); // for tracking overall stats 52378 52379 this.updateMediaSecondsLoaded_(segmentInfo.segment); // Note that the state isn't changed from loading to appending. This is because abort 52380 // logic may change behavior depending on the state, and changing state too early may 52381 // inflate our estimates of bandwidth. In the future this should be re-examined to 52382 // note more granular states. 52383 // don't process and append data if the mediaSource is closed 52384 52385 if (this.mediaSource_.readyState === 'closed') { 52386 return; 52387 } // if this request included an initialization segment, save that data 52388 // to the initSegment cache 52389 52390 52391 if (simpleSegment.map) { 52392 simpleSegment.map = this.initSegmentForMap(simpleSegment.map, true); // move over init segment properties to media request 52393 52394 segmentInfo.segment.map = simpleSegment.map; 52395 } // if this request included a segment key, save that data in the cache 52396 52397 52398 if (simpleSegment.key) { 52399 this.segmentKey(simpleSegment.key, true); 52400 } 52401 52402 segmentInfo.isFmp4 = simpleSegment.isFmp4; 52403 segmentInfo.timingInfo = segmentInfo.timingInfo || {}; 52404 52405 if (segmentInfo.isFmp4) { 52406 this.trigger('fmp4'); 52407 segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start; 52408 } else { 52409 var useVideoTimingInfo = this.loaderType_ === 'main' && this.currentMediaInfo_.hasVideo; 52410 var firstVideoFrameTimeForData; 52411 52412 if (useVideoTimingInfo) { 52413 firstVideoFrameTimeForData = this.handlePartialData_ ? result.videoFramePtsTime : segmentInfo.videoTimingInfo.start; 52414 } // Segment loader knows more about segment timing than the transmuxer (in certain 52415 // aspects), so make any changes required for a more accurate start time. 52416 // Don't set the end time yet, as the segment may not be finished processing. 52417 52418 52419 segmentInfo.timingInfo.start = this.trueSegmentStart_({ 52420 currentStart: segmentInfo.timingInfo.start, 52421 playlist: segmentInfo.playlist, 52422 mediaIndex: segmentInfo.mediaIndex, 52423 currentVideoTimestampOffset: this.sourceUpdater_.videoTimestampOffset(), 52424 useVideoTimingInfo: useVideoTimingInfo, 52425 firstVideoFrameTimeForData: firstVideoFrameTimeForData, 52426 videoTimingInfo: segmentInfo.videoTimingInfo, 52427 audioTimingInfo: segmentInfo.audioTimingInfo 52428 }); 52429 } // Init segments for audio and video only need to be appended in certain cases. Now 52430 // that data is about to be appended, we can check the final cases to determine 52431 // whether we should append an init segment. 52432 52433 52434 this.updateAppendInitSegmentStatus(segmentInfo, result.type); // Timestamp offset should be updated once we get new data and have its timing info, 52435 // as we use the start of the segment to offset the best guess (playlist provided) 52436 // timestamp offset. 52437 52438 this.updateSourceBufferTimestampOffset_(segmentInfo); // Save some state so that in the future anything waiting on first append (and/or 52439 // timestamp offset(s)) can process immediately. While the extra state isn't optimal, 52440 // we need some notion of whether the timestamp offset or other relevant information 52441 // has had a chance to be set. 52442 52443 segmentInfo.hasAppendedData_ = true; // Now that the timestamp offset should be set, we can append any waiting ID3 tags. 52444 52445 this.processMetadataQueue_(); 52446 this.appendData_(segmentInfo, result); 52447 }; 52448 52449 _proto.updateAppendInitSegmentStatus = function updateAppendInitSegmentStatus(segmentInfo, type) { 52450 // alt audio doesn't manage timestamp offset 52451 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 52452 // segment for each chunk 52453 !segmentInfo.changedTimestampOffset) { 52454 // if the timestamp offset changed, the timeline may have changed, so we have to re- 52455 // append init segments 52456 this.appendInitSegment_ = { 52457 audio: true, 52458 video: true 52459 }; 52460 } 52461 52462 if (this.playlistOfLastInitSegment_[type] !== segmentInfo.playlist) { 52463 // make sure we append init segment on playlist changes, in case the media config 52464 // changed 52465 this.appendInitSegment_[type] = true; 52466 } 52467 }; 52468 52469 _proto.getInitSegmentAndUpdateState_ = function getInitSegmentAndUpdateState_(_ref3) { 52470 var type = _ref3.type, 52471 initSegment = _ref3.initSegment, 52472 map = _ref3.map, 52473 playlist = _ref3.playlist; // "The EXT-X-MAP tag specifies how to obtain the Media Initialization Section 52474 // (Section 3) required to parse the applicable Media Segments. It applies to every 52475 // Media Segment that appears after it in the Playlist until the next EXT-X-MAP tag 52476 // or until the end of the playlist." 52477 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.5 52478 52479 if (map) { 52480 var id = initSegmentId(map); 52481 52482 if (this.activeInitSegmentId_ === id) { 52483 // don't need to re-append the init segment if the ID matches 52484 return null; 52485 } // a map-specified init segment takes priority over any transmuxed (or otherwise 52486 // obtained) init segment 52487 // 52488 // this also caches the init segment for later use 52489 52490 52491 initSegment = this.initSegmentForMap(map, true).bytes; 52492 this.activeInitSegmentId_ = id; 52493 }
52493 // We used to always prepend init segments for video, however, that shouldn't be 52494 // necessary. Instead, we should only append on changes, similar to what we've always 52495 // done for audio. This is more important (though may not be that important) for 52496 // frame-by-frame appending for LHLS, simply because of the increased quantity of 52497 // appends. 52498 52499 52500 if (initSegment && this.appendInitSegment_[type]) { 52501 // Make sure we track the playlist that we last used for the init segment, so that 52502 // we can re-append the init segment in the event that we get data from a new 52503 // playlist. Discontinuities and track changes are handled in other sections. 52504 this.playlistOfLastInitSegment_[type] = playlist; // we should only be appending the next init segment if we detect a change, or if 52505 // the segment has a map 52506 52507 this.appendInitSegment_[type] = map ? true : false; // we need to clear out the fmp4 active init segment id, since 52508 // we are appending the muxer init segment 52509 52510 this.activeInitSegmentId_ = null; 52511 return initSegment; 52512 } 52513 52514 return null; 52515 }; 52516 52517 _proto.appendToSourceBuffer_ = function appendToSourceBuffer_(_ref4) { 52518 var _this4 = this; 52519 52520 var segmentInfo = _ref4.segmentInfo, 52521 type = _ref4.type, 52522 initSegment = _ref4.initSegment, 52523 data = _ref4.data; 52524 var segments = [data]; 52525 var byteLength = data.byteLength; 52526 52527 if (initSegment) { 52528 // if the media initialization segment is changing, append it before the content 52529 // segment 52530 segments.unshift(initSegment); 52531 byteLength += initSegment.byteLength; 52532 } // Technically we should be OK appending the init segment separately, however, we 52533 // haven't yet tested that, and prepending is how we have always done things. 52534 52535 52536 var bytes = concatSegments({ 52537 bytes: byteLength, 52538 segments: segments 52539 }); 52540 this.sourceUpdater_.appendBuffer({ 52541 segmentInfo: segmentInfo, 52542 type: type, 52543 bytes: bytes 52544 }, function (error) { 52545 if (error) { 52546 _this4.error(type + " append of " + bytes.length + "b failed for segment #" + segmentInfo.mediaIndex + " in playlist " + segmentInfo.playlist.id); // If an append errors, we can't recover. 52547 // (see https://w3c.github.io/media-source/#sourcebuffer-append-error). 52548 // Trigger a special error so that it can be handled separately from normal, 52549 // recoverable errors. 52550 52551 52552 _this4.trigger('appenderror'); 52553 } 52554 }); 52555 }; 52556 52557 _proto.handleVideoSegmentTimingInfo_ = function handleVideoSegmentTimingInfo_(requestId, videoSegmentTimingInfo) { 52558 if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) { 52559 return; 52560 } 52561 52562 var segment = this.pendingSegment_.segment; 52563 52564 if (!segment.videoTimingInfo) { 52565 segment.videoTimingInfo = {}; 52566 } 52567 52568 segment.videoTimingInfo.transmuxerPrependedSeconds = videoSegmentTimingInfo.prependedContentDuration || 0; 52569 segment.videoTimingInfo.transmuxedPresentationStart = videoSegmentTimingInfo.start.presentation; 52570 segment.videoTimingInfo.transmuxedPresentationEnd = videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging 52571 52572 segment.videoTimingInfo.baseMediaDecodeTime = videoSegmentTimingInfo.baseMediaDecodeTime; 52573 }; 52574 52575 _proto.appendData_ = function appendData_(segmentInfo, result) { 52576 var type = result.type, 52577 data = result.data; 52578 52579 if (!data || !data.byteLength) { 52580 return; 52581 } 52582 52583 if (type === 'audio' && this.audioDisabled_) { 52584 return; 52585 } 52586 52587 var initSegment = this.getInitSegmentAndUpdateState_({ 52588 type: type, 52589 initSegment: result.initSegment, 52590 playlist: segmentInfo.playlist, 52591 map: segmentInfo.isFmp4 ? segmentInfo.segment.map : null 52592 }); 52593 this.appendToSourceBuffer_({ 52594 segmentInfo: segmentInfo, 52595 type: type, 52596 initSegment: initSegment, 52597 data: data 52598 }); 52599 } 52600 /** 52601 * load a specific segment from a request into the buffer 52602 * 52603 * @private 52604 */ 52605 ; 52606 52607 _proto.loadSegment_ = function loadSegment_(segmentInfo) { 52608 var _this5 = this; 52609 52610 this.state = 'WAITING'; 52611 this.pendingSegment_ = segmentInfo; 52612 this.trimBackBuffer_(segmentInfo); 52613 52614 if (typeof segmentInfo.timestampOffset === 'number') { 52615 if (this.transmuxer_) { 52616 this.transmuxer_.postMessage({ 52617 action: 'clearAllMp4Captions' 52618 }); 52619 } 52620 } 52621 52622 if (!this.hasEnoughInfoToLoad_()) { 52623 this.loadQueue_.push(function () { 52624 var buffered = _this5.buffered_(); 52625 52626 if (typeof segmentInfo.timestampOffset === 'number') { 52627 // The timestamp offset needs to be regenerated, as the buffer most likely 52628 // changed since the function was added to the queue. This is expected, as the 52629 // load is usually pending the main loader appending new segments. 52630 // 52631 // Note also that the overrideCheck property is set to true. This is because
52632 // isPendingTimestampOffset is set back to false after the first set of the 52633 // timestamp offset (before it was added to the queue). But the presence of 52634 // timestamp offset as a property of segmentInfo serves as enough evidence that 52635 // it should be regenerated. 52636 segmentInfo.timestampOffset = timestampOffsetForSegment({ 52637 segmentTimeline: segmentInfo.timeline, 52638 currentTimeline: _this5.currentTimeline_, 52639 startOfSegment: segmentInfo.startOfSegment, 52640 buffered: buffered, 52641 overrideCheck: true 52642 }); 52643 } 52644 52645 delete segmentInfo.audioAppendStart; 52646 52647 var audioBuffered = _this5.sourceUpdater_.audioBuffered(); 52648 52649 if (audioBuffered.length) { 52650 // Because the audio timestamp offset may have been changed by the main loader, 52651 // the audioAppendStart should be regenerated. 52652 // 52653 // Since the transmuxer is using the actual timing values, but the buffer is 52654 // adjusted by the timestamp offset, the value must be adjusted. 52655 segmentInfo.audioAppendStart = audioBuffered.end(audioBuffered.length - 1) - _this5.sourceUpdater_.audioTimestampOffset(); 52656 } 52657 52658 _this5.updateTransmuxerAndRequestSegment_(segmentInfo); 52659 }); 52660 return; 52661 } 52662 52663 this.updateTransmuxerAndRequestSegment_(segmentInfo); 52664 }; 52665 52666 _proto.updateTransmuxerAndRequestSegment_ = function updateTransmuxerAndRequestSegment_(segmentInfo) { 52667 // We'll update the source buffer's timestamp offset once we have transmuxed data, but 52668 // the transmuxer still needs to be updated before then. 52669 // 52670 // Even though keepOriginalTimestamps is set to true for the transmuxer, timestamp 52671 // offset must be passed to the transmuxer for stream correcting adjustments. 52672 if (this.shouldUpdateTransmuxerTimestampOffset_(segmentInfo.timestampOffset)) { 52673 this.gopBuffer_.length = 0; // gopsToAlignWith was set before the GOP buffer was cleared 52674 52675 segmentInfo.gopsToAlignWith = []; 52676 this.timeMapping_ = 0; // reset values in the transmuxer since a discontinuity should start fresh 52677 52678 this.transmuxer_.postMessage({ 52679 action: 'reset' 52680 }); 52681 this.transmuxer_.postMessage({ 52682 action: 'setTimestampOffset', 52683 timestampOffset: segmentInfo.timestampOffset 52684 }); 52685 } 52686 52687 var simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo); 52688 segmentInfo.abortRequests = mediaSegmentRequest({ 52689 xhr: this.vhs_.xhr, 52690 xhrOptions: this.xhrOptions_, 52691 decryptionWorker: this.decrypter_, 52692 segment: simpleSegment, 52693 handlePartialData: this.handlePartialData_, 52694 abortFn: this.handleAbort_.bind(this), 52695 progressFn: this.handleProgress_.bind(this), 52696 trackInfoFn: this.handleTrackInfo_.bind(this), 52697 timingInfoFn: this.handleTimingInfo_.bind(this), 52698 videoSegmentTimingInfoFn: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId), 52699 captionsFn: this.handleCaptions_.bind(this), 52700 id3Fn: this.handleId3_.bind(this), 52701 dataFn: this.handleData_.bind(this), 52702 doneFn: this.segmentRequestFinished_.bind(this) 52703 }); 52704 } 52705 /** 52706 * trim the back buffer so that we don't have too much data 52707 * in the source buffer 52708 * 52709 * @private 52710 * 52711 * @param {Object} segmentInfo - the current segment 52712 */ 52713 ; 52714 52715 _proto.trimBackBuffer_ = function trimBackBuffer_(segmentInfo) { 52716 var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of 52717 // buffer and a very conservative "garbage collector" 52718 // We manually clear out the old buffer to ensure 52719 // we don't trigger the QuotaExceeded error 52720 // on the source buffer during subsequent appends 52721 52722 if (removeToTime > 0) { 52723 this.remove(0, removeToTime); 52724 } 52725 } 52726 /** 52727 * created a simplified copy of the segment object with just the 52728 * information necessary to perform the XHR and decryption 52729 * 52730 * @private 52731 * 52732 * @param {Object} segmentInfo - the current segment 52733 * @return {Object}
vendor: 57,728 bytes, lines 52733-54399
52733 a simplified segment object copy 52734 */ 52735 ; 52736 52737 _proto.createSimplifiedSegmentObj_ = function createSimplifiedSegmentObj_(segmentInfo) { 52738 var segment = segmentInfo.segment; 52739 var simpleSegment = { 52740 resolvedUri: segment.resolvedUri, 52741 byterange: segment.byterange, 52742 requestId: segmentInfo.requestId, 52743 transmuxer: segmentInfo.transmuxer, 52744 audioAppendStart: segmentInfo.audioAppendStart, 52745 gopsToAlignWith: segmentInfo.gopsToAlignWith 52746 }; 52747 var previousSegment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 52748 52749 if (previousSegment && previousSegment.end && previousSegment.timeline === segment.timeline) { 52750 simpleSegment.baseStartTime = previousSegment.end + segmentInfo.timestampOffset; 52751 } 52752 52753 if (segment.key) { 52754 // if the media sequence is greater than 2^32, the IV will be incorrect 52755 // assuming 10s segments, that would be about 1300 years 52756 var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 52757 simpleSegment.key = this.segmentKey(segment.key); 52758 simpleSegment.key.iv = iv; 52759 } 52760 52761 if (segment.map) { 52762 simpleSegment.map = this.initSegmentForMap(segment.map); 52763 } 52764 52765 return simpleSegment; 52766 }; 52767 52768 _proto.saveTransferStats_ = function saveTransferStats_(stats) { 52769 // every request counts as a media request even if it has been aborted 52770 // or canceled due to a timeout 52771 this.mediaRequests += 1; 52772 52773 if (stats) { 52774 this.mediaBytesTransferred += stats.bytesReceived; 52775 this.mediaTransferDuration += stats.roundTripTime; 52776 } 52777 }; 52778 52779 _proto.saveBandwidthRelatedStats_ = function saveBandwidthRelatedStats_(stats) { 52780 this.bandwidth = stats.bandwidth; 52781 this.roundTrip = stats.roundTripTime; // byteLength will be used for throughput, and should be based on bytes receieved, 52782 // which we only know at the end of the request and should reflect total bytes 52783 // downloaded rather than just bytes processed from components of the segment 52784 52785 this.pendingSegment_.byteLength = stats.bytesReceived; 52786 }; 52787 52788 _proto.handleTimeout_ = function handleTimeout_() { 52789 // although the VTT segment loader bandwidth isn't really used, it's good to 52790 // maintain functinality between segment loaders 52791 this.mediaRequestsTimedout += 1; 52792 this.bandwidth = 1; 52793 this.roundTrip = NaN; 52794 this.trigger('bandwidthupdate'); 52795 } 52796 /** 52797 * Handle the callback from the segmentRequest function and set the 52798 * associated SegmentLoader state and errors if necessary 52799 * 52800 * @private 52801 */ 52802 ; 52803 52804 _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) { 52805 // TODO handle special cases, e.g., muxed audio/video but only audio in the segment 52806 // check the call queue directly since this function doesn't need to deal with any 52807 // data, and can continue even if the source buffers are not set up and we didn't get 52808 // any data from the segment 52809 if (this.callQueue_.length) { 52810 this.callQueue_.push(this.segmentRequestFinished_.bind(this, error, simpleSegment, result)); 52811 return; 52812 } 52813 52814 this.saveTransferStats_(simpleSegment.stats); // The request was aborted and the SegmentLoader has already been reset 52815 52816 if (!this.pendingSegment_) { 52817 return; 52818 } // the request was aborted and the SegmentLoader has already started 52819 // another request. this can happen when the timeout for an aborted 52820 // request triggers due to a limitation in the XHR library 52821 // do not count this as any sort of request or we risk double-counting 52822 52823 52824 if (simpleSegment.requestId !== this.pendingSegment_.requestId) { 52825 return; 52826 } // an error occurred from the active pendingSegment_ so reset everything 52827 52828 52829 if (error) { 52830 this.pendingSegment_ = null; 52831 this.state = 'READY'; // aborts are not a true error condition and nothing corrective needs to be done 52832 52833 if (error.code === REQUEST_ERRORS.ABORTED) { 52834 return; 52835 } 52836 52837 this.pause(); // the error is really just that at least one of the requests timed-out 52838 // set the bandwidth to a very low value and trigger an ABR switch to 52839 // take emergency action 52840 52841 if (error.code === REQUEST_ERRORS.TIMEOUT) { 52842 this.handleTimeout_(); 52843 return; 52844 } // if control-flow has arrived here, then the error is real 52845 // emit an error event to blacklist the current playlist 52846 52847 52848 this.mediaRequestsErrored += 1; 52849 this.error(error); 52850 this.trigger('error'); 52851 return; 52852 } // the response was a success so set any bandwidth stats the request 52853 // generated for ABR purposes 52854 52855 52856 this.saveBandwidthRelatedStats_(simpleSegment.stats); 52857 var segmentInfo = this.pendingSegment_; 52858 segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; 52859 52860 if (result.gopInfo) { 52861 this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, result.gopInfo, this.safeAppend_); 52862 } // Although we may have already started appending on progress, we shouldn't switch the 52863 // state away from loading until we are officially done loading the segment data. 52864 52865 52866 this.state = 'APPENDING'; 52867 var isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist); 52868 var isWalkingForward = this.mediaIndex !== null; 52869 var isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ && // TODO verify this behavior 52870 // currentTimeline starts at -1, but we shouldn't end the timeline switching to 0, 52871 // the first timeline 52872 segmentInfo.timeline > 0; 52873 52874 if (!segmentInfo.isFmp4 && (isEndOfStream || isWalkingForward && isDiscontinuity)) { 52875 segmentTransmuxer.endTimeline(this.transmuxer_); 52876 } // used for testing 52877 52878 52879 this.trigger('appending'); 52880 this.waitForAppendsToComplete_(segmentInfo); 52881 }; 52882 52883 _proto.setTimeMapping_ = function setTimeMapping_(timeline) { 52884 var timelineMapping = this.syncController_.mappingForTimeline(timeline); 52885 52886 if (timelineMapping !== null) { 52887 this.timeMapping_ = timelineMapping; 52888 } 52889 }; 52890 52891 _proto.updateMediaSecondsLoaded_ = function updateMediaSecondsLoaded_(segment) { 52892 if (typeof segment.start === 'number' && typeof segment.end === 'number') { 52893 this.mediaSecondsLoaded += segment.end - segment.start; 52894 } else { 52895 this.mediaSecondsLoaded += segment.duration; 52896 } 52897 }; 52898 52899 _proto.shouldUpdateTransmuxerTimestampOffset_ = function shouldUpdateTransmuxerTimestampOffset_(timestampOffset) { 52900 if (timestampOffset === null) { 52901 return false; 52902 } // note that we're potentially using the same timestamp offset for both video and 52903 // audio 52904 52905 52906 if (this.loaderType_ === 'main' && timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) { 52907 return true; 52908 } 52909 52910 if (!this.audioDisabled_ && timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) { 52911 return true; 52912 } 52913 52914 return false; 52915 }; 52916 52917 _proto.trueSegmentStart_ = function trueSegmentStart_(_ref5) { 52918 var currentStart = _ref5.currentStart, 52919 playlist = _ref5.playlist, 52920 mediaIndex = _ref5.mediaIndex, 52921 firstVideoFrameTimeForData = _ref5.firstVideoFrameTimeForData, 52922 currentVideoTimestampOffset = _ref5.currentVideoTimestampOffset, 52923 useVideoTimingInfo = _ref5.useVideoTimingInfo, 52924 videoTimingInfo = _ref5.videoTimingInfo, 52925 audioTimingInfo = _ref5.audioTimingInfo; 52926 52927 if (typeof currentStart !== 'undefined') { 52928 // if start was set once, keep using it 52929 return currentStart; 52930 } 52931 52932 if (!useVideoTimingInfo) { 52933 return audioTimingInfo.start; 52934 } 52935 52936 var previousSegment = playlist.segments[mediaIndex - 1]; // The start of a segment should be the start of the first full frame contained 52937 // within that segment. Since the transmuxer maintains a cache of incomplete data 52938 // from and/or the last frame seen, the start time may reflect a frame that starts 52939 // in the previous segment. Check for that case and ensure the start time is 52940 // accurate for the segment. 52941 52942 if (mediaIndex === 0 || !previousSegment || typeof previousSegment.start === 'undefined' || previousSegment.end !== firstVideoFrameTimeForData + currentVideoTimestampOffset) { 52943 return firstVideoFrameTimeForData; 52944 } 52945 52946 return videoTimingInfo.start; 52947 }; 52948 52949 _proto.waitForAppendsToComplete_ = function waitForAppendsToComplete_(segmentInfo) { 52950 if (!this.currentMediaInfo_) { 52951 this.error({ 52952 message: 'No starting media returned, likely due to an unsupported media format.', 52953 blacklistDuration: Infinity 52954 }); 52955 this.trigger('error'); 52956 return; 52957 } // Although transmuxing is done, appends may not yet be finished. Throw a marker 52958 // on each queue this loader is responsible for to ensure that the appends are 52959 // complete. 52960 52961 52962 var _this$currentMediaInf2 = this.currentMediaInfo_, 52963 hasAudio = _this$currentMediaInf2.hasAudio, 52964 hasVideo = _this$currentMediaInf2.hasVideo, 52965 isMuxed = _this$currentMediaInf2.isMuxed; 52966 var waitForVideo = this.loaderType_ === 'main' && hasVideo; // TODO: does this break partial support for muxed content? 52967 52968 var waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed; 52969 segmentInfo.waitingOnAppends = 0; // segments with no data 52970 52971 if (!segmentInfo.hasAppendedData_) { 52972 if (!segmentInfo.timingInfo && typeof segmentInfo.timestampOffset === 'number') { 52973 // When there's no audio or video data in the segment, there's no audio or video 52974 // timing information. 52975 // 52976 // If there's no audio or video timing information, then the timestamp offset 52977 // can't be adjusted to the appropriate value for the transmuxer and source 52978 // buffers. 52979 // 52980 // Therefore, the next segment should be used to set the timestamp offset. 52981 this.isPendingTimestampOffset_ = true; 52982 } // override settings for metadata only segments 52983 52984 52985 segmentInfo.timingInfo = { 52986 start: 0 52987 }; 52988 segmentInfo.waitingOnAppends++; 52989 52990 if (!this.isPendingTimestampOffset_) { 52991 // update the timestampoffset 52992 this.updateSourceBufferTimestampOffset_(segmentInfo); // make sure the metadata queue is processed even though we have 52993 // no video/audio data. 52994 52995 this.processMetadataQueue_(); 52996 } // append is "done" instantly with no data. 52997 52998 52999 this.checkAppendsDone_(segmentInfo); 53000 return; 53001 } // Since source updater could call back synchronously, do the increments first. 53002 53003 53004 if (waitForVideo) { 53005 segmentInfo.waitingOnAppends++; 53006 } 53007 53008 if (waitForAudio) { 53009 segmentInfo.waitingOnAppends++; 53010 } 53011 53012 if (waitForVideo) { 53013 this.sourceUpdater_.videoQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo)); 53014 } 53015 53016 if (waitForAudio) { 53017 this.sourceUpdater_.audioQueueCallback(this.checkAppendsDone_.bind(this, segmentInfo)); 53018 } 53019 }; 53020 53021 _proto.checkAppendsDone_ = function checkAppendsDone_(segmentInfo) { 53022 if (this.checkForAbort_(segmentInfo.requestId)) { 53023 return; 53024 } 53025 53026 segmentInfo.waitingOnAppends--; 53027 53028 if (segmentInfo.waitingOnAppends === 0) { 53029 this.handleAppendsDone_(); 53030 } 53031 }; 53032 53033 _proto.checkForIllegalMediaSwitch = function checkForIllegalMediaSwitch(trackInfo) { 53034 var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.currentMediaInfo_, trackInfo); 53035 53036 if (illegalMediaSwitchError) { 53037 this.error({ 53038 message: illegalMediaSwitchError, 53039 blacklistDuration: Infinity 53040 }); 53041 this.trigger('error'); 53042 return true; 53043 } 53044 53045 return false; 53046 }; 53047 53048 _proto.updateSourceBufferTimestampOffset_ = function updateSourceBufferTimestampOffset_(segmentInfo) { 53049 if (segmentInfo.timestampOffset === null || // we don't yet have the start for whatever media type (video or audio) has 53050 // priority, timing-wise, so we must wait 53051 typeof segmentInfo.timingInfo.start !== 'number' || // already updated the timestamp offset for this segment 53052 segmentInfo.changedTimestampOffset || // the alt audio loader should not be responsible for setting the timestamp offset 53053 this.loaderType_ !== 'main') { 53054 return; 53055 } 53056 53057 var didChange = false; // Primary timing goes by video, and audio is trimmed in the transmuxer, meaning that 53058 // the timing info here comes from video. In the event that the audio is longer than 53059 // the video, this will trim the start of the audio. 53060 // This also trims any offset from 0 at the beginning of the media 53061 53062 segmentInfo.timestampOffset -= segmentInfo.timingInfo.start; // In the event that there are partial segment downloads, each will try to update the 53063 // timestamp offset. Retaining this bit of state prevents us from updating in the 53064 // future (within the same segment), however, there may be a better way to handle it. 53065 53066 segmentInfo.changedTimestampOffset = true; 53067 53068 if (segmentInfo.timestampOffset !== this.sourceUpdater_.videoTimestampOffset()) { 53069 this.sourceUpdater_.videoTimestampOffset(segmentInfo.timestampOffset); 53070 didChange = true; 53071 } 53072 53073 if (segmentInfo.timestampOffset !== this.sourceUpdater_.audioTimestampOffset()) { 53074 this.sourceUpdater_.audioTimestampOffset(segmentInfo.timestampOffset); 53075 didChange = true; 53076 } 53077 53078 if (didChange) { 53079 this.trigger('timestampoffset'); 53080 } 53081 }; 53082 53083 _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_(segmentInfo) { 53084 segmentInfo.timingInfo = segmentInfo.timingInfo || {}; 53085 var useVideoTimingInfo = this.loaderType_ === 'main' && this.currentMediaInfo_.hasVideo; 53086 var prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo; 53087 53088 if (!prioritizedTimingInfo) { 53089 return; 53090 } 53091 53092 segmentInfo.timingInfo.end = typeof prioritizedTimingInfo.end === 'number' ? // End time may not exist in a case where we aren't parsing the full segment (one 53093 // current example is the case of fmp4), so use the rough duration to calculate an 53094 // end time. 53095 prioritizedTimingInfo.end : prioritizedTimingInfo.start + segmentInfo.duration; 53096 } 53097 /** 53098 * callback to run when appendBuffer is finished. detects if we are 53099 * in a good state to do things with the data we got, or if we need 53100 * to wait for more 53101 * 53102 * @private 53103 */ 53104 ; 53105 53106 _proto.handleAppendsDone_ = function handleAppendsDone_() { 53107 // appendsdone can cause an abort 53108 if (this.pendingSegment_) { 53109 this.trigger('appendsdone'); 53110 } 53111 53112 if (!this.pendingSegment_) { 53113 this.state = 'READY'; // TODO should this move into this.checkForAbort to speed up requests post abort in 53114 // all appending cases? 53115 53116 if (!this.paused()) { 53117 this.monitorBuffer_(); 53118 } 53119 53120 return; 53121 } 53122 53123 var segmentInfo = this.pendingSegment_; // Now that the end of the segment has been reached, we can set the end time. It's 53124 // best to wait until all appends are done so we're sure that the primary media is 53125 // finished (and we have its end time). 53126 53127 this.updateTimingInfoEnd_(segmentInfo); 53128 53129 if (this.shouldSaveSegmentTimingInfo_) { 53130 // Timeline mappings should only be saved for the main loader. This is for multiple 53131 // reasons: 53132 // 53133 // 1) Only one mapping is saved per timeline, meaning that if both the audio loader 53134 // and the main loader try to save the timeline mapping, whichever comes later 53135 // will overwrite the first. In theory this is OK, as the mappings should be the 53136 // same, however, it breaks for (2) 53137 // 2) In the event of a live stream, the initial live point will make for a somewhat 53138 // arbitrary mapping. If audio and video streams are not perfectly in-sync, then 53139 // the mapping will be off for one of the streams, dependent on which one was 53140 // first saved (see (1)). 53141 // 3) Primary timing goes by video in VHS, so the mapping should be video. 53142 // 53143 // Since the audio loader will wait for the main loader to load the first segment, 53144 // the main loader will save the first timeline mapping, and ensure that there won't 53145 // be a case where audio loads two segments without saving a mapping (thus leading 53146 // to missing segment timing info). 53147 this.syncController_.saveSegmentTimingInfo({ 53148 segmentInfo: segmentInfo, 53149 shouldSaveTimelineMapping: this.loaderType_ === 'main' 53150 }); 53151 } 53152 53153 this.logger_(segmentInfoString(segmentInfo)); 53154 this.recordThroughput_(segmentInfo); 53155 this.pendingSegment_ = null; 53156 this.state = 'READY'; // TODO minor, but for partial segment downloads, this can be done earlier to save 53157 // on bandwidth and download time 53158 53159 if (segmentInfo.isSyncRequest) { 53160 this.trigger('syncinfoupdate'); 53161 return; 53162 } 53163 53164 this.addSegmentMetadataCue_(segmentInfo); 53165 this.fetchAtBuffer_ = true; 53166 53167 if (this.currentTimeline_ !== segmentInfo.timeline) { 53168 this.timelineChangeController_.lastTimelineChange({ 53169 type: this.loaderType_, 53170 from: this.currentTimeline_, 53171 to: segmentInfo.timeline 53172 }); // If audio is not disabled, the main segment loader is responsible for updating 53173 // the audio timeline as well. If the content is video only, this won't have any 53174 // impact. 53175 53176 if (this.loaderType_ === 'main' && !this.audioDisabled_) { 53177 this.timelineChangeController_.lastTimelineChange({ 53178 type: 'audio', 53179 from: this.currentTimeline_, 53180 to: segmentInfo.timeline 53181 }); 53182 } 53183 } 53184 53185 this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before 53186 // the following conditional otherwise it may consider this a bad "guess" 53187 // and attempt to resync when the post-update seekable window and live 53188 // point would mean that this was the perfect segment to fetch 53189 53190 this.trigger('syncinfoupdate'); 53191 var segment = segmentInfo.segment; // If we previously appended a segment that ends more than 3 targetDurations before 53192 // the currentTime_ that means that our conservative guess was too conservative. 53193 // In that case, reset the loader state so that we try to use any information gained 53194 // from the previous request to create a new, more accurate, sync-point. 53195 53196 if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) { 53197 this.resetEverything(); 53198 return; 53199 } 53200 53201 var isWalkingForward = this.mediaIndex !== null; // Don't do a rendition switch unless we have enough time to get a sync segment 53202 // and conservatively guess 53203 53204 if (isWalkingForward) { 53205 this.trigger('bandwidthupdate'); 53206 } 53207 53208 this.trigger('progress'); 53209 this.mediaIndex = segmentInfo.mediaIndex; // any time an update finishes and the last segment is in the 53210 // buffer, end the stream. this ensures the "ended" event will 53211 // fire if playback reaches that point. 53212 53213 if (this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist)) { 53214 this.endOfStream(); 53215 } // used for testing 53216 53217 53218 this.trigger('appended'); 53219 53220 if (!this.paused()) { 53221 this.monitorBuffer_(); 53222 } 53223 } 53224 /** 53225 * Records the current throughput of the decrypt, transmux, and append 53226 * portion of the semgment pipeline. `throughput.rate` is a the cumulative 53227 * moving average of the throughput. `throughput.count` is the number of 53228 * data points in the average. 53229 * 53230 * @private 53231 * @param {Object} segmentInfo the object returned by loadSegment 53232 */ 53233 ; 53234 53235 _proto.recordThroughput_ = function recordThroughput_(segmentInfo) { 53236 var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide 53237 // by zero in the case where the throughput is ridiculously high 53238 53239 var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second 53240 53241 var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation: 53242 // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1) 53243 53244 this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count; 53245 } 53246 /** 53247 * Adds a cue to the segment-metadata track with some metadata information about the 53248 * segment 53249 * 53250 * @private 53251 * @param {Object} segmentInfo 53252 * the object returned by loadSegment 53253 * @method addSegmentMetadataCue_ 53254 */ 53255 ; 53256 53257 _proto.addSegmentMetadataCue_ = function addSegmentMetadataCue_(segmentInfo) { 53258 if (!this.segmentMetadataTrack_) { 53259 return; 53260 } 53261 53262 var segment = segmentInfo.segment; 53263 var start = segment.start; 53264 var end = segment.end; // Do not try adding the cue if the start and end times are invalid. 53265 53266 if (!finite(start) || !finite(end)) { 53267 return; 53268 } 53269 53270 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 53271 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 53272 var value = { 53273 custom: segment.custom, 53274 dateTimeObject: segment.dateTimeObject, 53275 dateTimeString: segment.dateTimeString, 53276 bandwidth: segmentInfo.playlist.attributes.BANDWIDTH, 53277 resolution: segmentInfo.playlist.attributes.RESOLUTION, 53278 codecs: segmentInfo.playlist.attributes.CODECS, 53279 byteLength: segmentInfo.byteLength, 53280 uri: segmentInfo.uri, 53281 timeline: segmentInfo.timeline, 53282 playlist: segmentInfo.playlist.id, 53283 start: start, 53284 end: end 53285 }; 53286 var data = JSON.stringify(value); 53287 var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between 53288 // the differences of WebKitDataCue in safari and VTTCue in other browsers 53289 53290 cue.value = value; 53291 this.segmentMetadataTrack_.addCue(cue); 53292 }; 53293 53294 return SegmentLoader; 53295 }(videojs$1.EventTarget); 53296 53297 function noop$1() {} 53298 53299 var toTitleCase$1 = function toTitleCase(string) { 53300 if (typeof string !== 'string') { 53301 return string; 53302 } 53303 53304 return string.replace(/./, function (w) { 53305 return w.toUpperCase(); 53306 }); 53307 }; 53308 53309 var bufferTypes = ['video', 'audio']; 53310 53311 var _updating = function updating(type, sourceUpdater) { 53312 var sourceBuffer = sourceUpdater[type + "Buffer"]; 53313 return sourceBuffer && sourceBuffer.updating || sourceUpdater.queuePending[type]; 53314 }; 53315 53316 var nextQueueIndexOfType = function nextQueueIndexOfType(type, queue) { 53317 for (var i = 0; i < queue.length; i++) { 53318 var queueEntry = queue[i]; 53319 53320 if (queueEntry.type === 'mediaSource') { 53321 // If the next entry is a media source entry (uses multiple source buffers), block 53322 // processing to allow it to go through first. 53323 return null; 53324 } 53325 53326 if (queueEntry.type === type) { 53327 return i; 53328 } 53329 } 53330 53331 return null; 53332 }; 53333 53334 var shiftQueue = function shiftQueue(type, sourceUpdater) { 53335 if (sourceUpdater.queue.length === 0) { 53336 return; 53337 } 53338 53339 var queueIndex = 0; 53340 var queueEntry = sourceUpdater.queue[queueIndex]; 53341 53342 if (queueEntry.type === 'mediaSource') { 53343 if (!sourceUpdater.updating() && sourceUpdater.mediaSource.readyState !== 'closed') { 53344 sourceUpdater.queue.shift(); 53345 queueEntry.action(sourceUpdater); 53346 53347 if (queueEntry.doneFn) { 53348 queueEntry.doneFn(); 53349 } // Only specific source buffer actions must wait for async updateend events. Media 53350 // Source actions process synchronously. Therefore, both audio and video source 53351 // buffers are now clear to process the next queue entries. 53352 53353 53354 shiftQueue('audio', sourceUpdater); 53355 shiftQueue('video', sourceUpdater); 53356 } // Media Source actions require both source buffers, so if the media source action 53357 // couldn't process yet (because one or both source buffers are busy), block other 53358 // queue actions until both are available and the media source action can process. 53359 53360 53361 return; 53362 } 53363 53364 if (type === 'mediaSource') { 53365 // If the queue was shifted by a media source action (this happens when pushing a 53366 // media source action onto the queue), then it wasn't from an updateend event from an 53367 // audio or video source buffer, so there's no change from previous state, and no 53368 // processing should be done. 53369 return; 53370 } // Media source queue entries don't need to consider whether the source updater is 53371 // started (i.e., source buffers are created) as they don't need the source buffers, but 53372 // source buffer queue entries do. 53373 53374 53375 if (!sourceUpdater.started_ || sourceUpdater.mediaSource.readyState === 'closed' || _updating(type, sourceUpdater)) { 53376 return; 53377 } 53378 53379 if (queueEntry.type !== type) { 53380 queueIndex = nextQueueIndexOfType(type, sourceUpdater.queue); 53381 53382 if (queueIndex === null) { 53383 // Either there's no queue entry that uses this source buffer type in the queue, or 53384 // there's a media source queue entry before the next entry of this type, in which 53385 // case wait for that action to process first. 53386 return; 53387 } 53388 53389 queueEntry = sourceUpdater.queue[queueIndex]; 53390 } 53391 53392 sourceUpdater.queue.splice(queueIndex, 1); 53393 queueEntry.action(type, sourceUpdater); 53394 53395 if (!queueEntry.doneFn) { 53396 // synchronous operation, process next entry 53397 shiftQueue(type, sourceUpdater); 53398 return; 53399 } // asynchronous operation, so keep a record that this source buffer type is in use 53400 53401 53402 sourceUpdater.queuePending[type] = queueEntry; 53403 }; 53404 53405 var cleanupBuffer = function cleanupBuffer(type, sourceUpdater) { 53406 var buffer = sourceUpdater[type + "Buffer"]; 53407 var titleType = toTitleCase$1(type); 53408 53409 if (!buffer) { 53410 return; 53411 } 53412 53413 buffer.removeEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]); 53414 buffer.removeEventListener('error', sourceUpdater["on" + titleType + "Error_"]); 53415 sourceUpdater.codecs[type] = null; 53416 sourceUpdater[type + "Buffer"] = null; 53417 }; 53418 53419 var inSourceBuffers = function inSourceBuffers(mediaSource, sourceBuffer) { 53420 return mediaSource && sourceBuffer && Array.prototype.indexOf.call(mediaSource.sourceBuffers, sourceBuffer) !== -1; 53421 }; 53422 53423 var actions = { 53424 appendBuffer: function appendBuffer(bytes, segmentInfo) { 53425 return function (type, sourceUpdater) { 53426 var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null 53427 // or the media source does not contain this source buffer. 53428 53429 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 53430 return; 53431 } 53432 53433 sourceUpdater.logger_("Appending segment " + segmentInfo.mediaIndex + "'s " + bytes.length + " bytes to " + type + "Buffer"); 53434 sourceBuffer.appendBuffer(bytes); 53435 }; 53436 }, 53437 remove: function remove(start, end) { 53438 return function (type, sourceUpdater) { 53439 var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null 53440 // or the media source does not contain this source buffer. 53441 53442 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 53443 return; 53444 } 53445 53446 sourceUpdater.logger_("Removing " + start + " to " + end + " from " + type + "Buffer"); 53447 sourceBuffer.remove(start, end); 53448 }; 53449 }, 53450 timestampOffset: function timestampOffset(offset) { 53451 return function (type, sourceUpdater) { 53452 var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null 53453 // or the media source does not contain this source buffer. 53454 53455 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 53456 return; 53457 } 53458 53459 sourceUpdater.logger_("Setting " + type + "timestampOffset to " + offset); 53460 sourceBuffer.timestampOffset = offset; 53461 }; 53462 }, 53463 callback: function callback(_callback) { 53464 return function (type, sourceUpdater) { 53465 _callback(); 53466 }; 53467 }, 53468 endOfStream: function endOfStream(error) { 53469 return function (sourceUpdater) { 53470 if (sourceUpdater.mediaSource.readyState !== 'open') { 53471 return; 53472 } 53473 53474 sourceUpdater.logger_("Calling mediaSource endOfStream(" + (error || '') + ")"); 53475 53476 try { 53477 sourceUpdater.mediaSource.endOfStream(error); 53478 } catch (e) { 53479 videojs$1.log.warn('Failed to call media source endOfStream', e); 53480 } 53481 }; 53482 }, 53483 duration: function duration(_duration) { 53484 return function (sourceUpdater) { 53485 sourceUpdater.logger_("Setting mediaSource duration to " + _duration); 53486 53487 try { 53488 sourceUpdater.mediaSource.duration = _duration; 53489 } catch (e) { 53490 videojs$1.log.warn('Failed to set media source duration', e); 53491 } 53492 }; 53493 }, 53494 abort: function abort() { 53495 return function (type, sourceUpdater) { 53496 if (sourceUpdater.mediaSource.readyState !== 'open') { 53497 return; 53498 } 53499 53500 var sourceBuffer = sourceUpdater[type + "Buffer"]; // can't do anything if the media source / source buffer is null 53501 // or the media source does not contain this source buffer. 53502 53503 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 53504 return; 53505 } 53506 53507 sourceUpdater.logger_("calling abort on " + type + "Buffer"); 53508 53509 try { 53510 sourceBuffer.abort(); 53511 } catch (e) { 53512 videojs$1.log.warn("Failed to abort on " + type + "Buffer", e); 53513 } 53514 }; 53515 }, 53516 addSourceBuffer: function addSourceBuffer(type, codec) { 53517 return function (sourceUpdater) { 53518 var titleType = toTitleCase$1(type); 53519 var mime = codecs_5(codec); 53520 sourceUpdater.logger_("Adding " + type + "Buffer with codec " + codec + " to mediaSource"); 53521 var sourceBuffer = sourceUpdater.mediaSource.addSourceBuffer(mime); 53522 sourceBuffer.addEventListener('updateend', sourceUpdater["on" + titleType + "UpdateEnd_"]); 53523 sourceBuffer.addEventListener('error', sourceUpdater["on" + titleType + "Error_"]); 53524 sourceUpdater.codecs[type] = codec; 53525 sourceUpdater[type + "Buffer"] = sourceBuffer; 53526 }; 53527 }, 53528 removeSourceBuffer: function removeSourceBuffer(type) { 53529 return function (sourceUpdater) { 53530 var sourceBuffer = sourceUpdater[type + "Buffer"]; 53531 cleanupBuffer(type, sourceUpdater); // can't do anything if the media source / source buffer is null 53532 // or the media source does not contain this source buffer. 53533 53534 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 53535 return; 53536 } 53537 53538 sourceUpdater.logger_("Removing " + type + "Buffer with codec " + sourceUpdater.codecs[type] + " from mediaSource"); 53539 53540 try { 53541 sourceUpdater.mediaSource.removeSourceBuffer(sourceBuffer); 53542 } catch (e) { 53543 videojs$1.log.warn("Failed to removeSourceBuffer " + type + "Buffer", e); 53544 } 53545 }; 53546 }, 53547 changeType: function changeType(codec) { 53548 return function (type, sourceUpdater) { 53549 var sourceBuffer = sourceUpdater[type + "Buffer"]; 53550 var mime = codecs_5(codec); // can't do anything if the media source / source buffer is null 53551 // or the media source does not contain this source buffer. 53552 53553 if (!inSourceBuffers(sourceUpdater.mediaSource, sourceBuffer)) { 53554 return; 53555 } // do not update codec if we don't need to. 53556 53557 53558 if (sourceUpdater.codecs[type] === codec) { 53559 return; 53560 } 53561 53562 sourceUpdater.logger_("changing " + type + "Buffer codec from " + sourceUpdater.codecs[type] + " to " + codec); 53563 sourceBuffer.changeType(mime); 53564 sourceUpdater.codecs[type] = codec; 53565 }; 53566 } 53567 }; 53568 53569 var pushQueue = function pushQueue(_ref) { 53570 var type = _ref.type, 53571 sourceUpdater = _ref.sourceUpdater, 53572 action = _ref.action, 53573 doneFn = _ref.doneFn, 53574 name = _ref.name; 53575 sourceUpdater.queue.push({ 53576 type: type, 53577 action: action, 53578 doneFn: doneFn, 53579 name: name 53580 }); 53581 shiftQueue(type, sourceUpdater); 53582 }; 53583 53584 var onUpdateend = function onUpdateend(type, sourceUpdater) { 53585 return function (e) { 53586 // Although there should, in theory, be a pending action for any updateend receieved, 53587 // there are some actions that may trigger updateend events without set definitions in 53588 // the w3c spec. For instance, setting the duration on the media source may trigger 53589 // updateend events on source buffers. This does not appear to be in the spec. As such, 53590 // if we encounter an updateend without a corresponding pending action from our queue 53591 // for that source buffer type, process the next action. 53592 if (sourceUpdater.queuePending[type]) { 53593 var doneFn = sourceUpdater.queuePending[type].doneFn; 53594 sourceUpdater.queuePending[type] = null; 53595 53596 if (doneFn) { 53597 // if there's an error, report it 53598 doneFn(sourceUpdater[type + "Error_"]); 53599 } 53600 } 53601 53602 shiftQueue(type, sourceUpdater); 53603 }; 53604 }; 53605 /** 53606 * A queue of callbacks to be serialized and applied when a 53607 * MediaSource and its associated SourceBuffers are not in the 53608 * updating state. It is used by the segment loader to update the 53609 * underlying SourceBuffers when new data is loaded, for instance. 53610 * 53611 * @class SourceUpdater 53612 * @param {MediaSource} mediaSource the MediaSource to create the SourceBuffer from 53613 * @param {string} mimeType the desired MIME type of the underlying SourceBuffer 53614 */ 53615 53616 53617 var SourceUpdater = /*#__PURE__*/function (_videojs$EventTarget) { 53618 inheritsLoose(SourceUpdater, _videojs$EventTarget); 53619 53620 function SourceUpdater(mediaSource) { 53621 var _this; 53622 53623 _this = _videojs$EventTarget.call(this) || this; 53624 _this.mediaSource = mediaSource; 53625 53626 _this.sourceopenListener_ = function () { 53627 return shiftQueue('mediaSource', assertThisInitialized(_this)); 53628 }; 53629 53630 _this.mediaSource.addEventListener('sourceopen', _this.sourceopenListener_); 53631 53632 _this.logger_ = logger('SourceUpdater'); // initial timestamp offset is 0 53633 53634 _this.audioTimestampOffset_ = 0; 53635 _this.videoTimestampOffset_ = 0; 53636 _this.queue = []; 53637 _this.queuePending = { 53638 audio: null, 53639 video: null 53640 }; 53641 _this.delayedAudioAppendQueue_ = []; 53642 _this.videoAppendQueued_ = false; 53643 _this.codecs = {}; 53644 _this.onVideoUpdateEnd_ = onUpdateend('video', assertThisInitialized(_this)); 53645 _this.onAudioUpdateEnd_ = onUpdateend('audio', assertThisInitialized(_this)); 53646 53647 _this.onVideoError_ = function (e) { 53648 // used for debugging 53649 _this.videoError_ = e; 53650 }; 53651 53652 _this.onAudioError_ = function (e) { 53653 // used for debugging 53654 _this.audioError_ = e; 53655 }; 53656 53657 _this.started_ = false; 53658 return _this; 53659 } 53660 53661 var _proto = SourceUpdater.prototype; 53662 53663 _proto.ready = function ready() { 53664 return this.started_; 53665 }; 53666 53667 _proto.createSourceBuffers = function createSourceBuffers(codecs) { 53668 if (this.ready()) { 53669 // already created them before 53670 return; 53671 } // the intial addOrChangeSourceBuffers will always be 53672 // two add buffers. 53673 53674 53675 this.addOrChangeSourceBuffers(codecs); 53676 this.started_ = true; 53677 this.trigger('ready'); 53678 } 53679 /** 53680 * Add a type of source buffer to the media source. 53681 * 53682 * @param {string} type 53683 * The type of source buffer to add. 53684 * 53685 * @param {string} codec 53686 * The codec to add the source buffer with. 53687 */ 53688 ; 53689 53690 _proto.addSourceBuffer = function addSourceBuffer(type, codec) { 53691 pushQueue({ 53692 type: 'mediaSource', 53693 sourceUpdater: this, 53694 action: actions.addSourceBuffer(type, codec), 53695 name: 'addSourceBuffer' 53696 }); 53697 } 53698 /** 53699 * call abort on a source buffer. 53700 * 53701 * @param {string} type 53702 * The type of source buffer to call abort on. 53703 */ 53704 ; 53705 53706 _proto.abort = function abort(type) { 53707 pushQueue({ 53708 type: type, 53709 sourceUpdater: this, 53710 action: actions.abort(type), 53711 name: 'abort' 53712 }); 53713 } 53714 /** 53715 * Call removeSourceBuffer and remove a specific type 53716 * of source buffer on the mediaSource. 53717 * 53718 * @param {string} type 53719 * The type of source buffer to remove. 53720 */ 53721 ; 53722 53723 _proto.removeSourceBuffer = function removeSourceBuffer(type) { 53724 if (!this.canRemoveSourceBuffer()) { 53725 videojs$1.log.error('removeSourceBuffer is not supported!'); 53726 return; 53727 } 53728 53729 pushQueue({ 53730 type: 'mediaSource', 53731 sourceUpdater: this, 53732 action: actions.removeSourceBuffer(type), 53733 name: 'removeSourceBuffer' 53734 }); 53735 } 53736 /** 53737 * Whether or not the removeSourceBuffer function is supported 53738 * on the mediaSource. 53739 * 53740 * @return {boolean} 53741 * if removeSourceBuffer can be called. 53742 */ 53743 ; 53744 53745 _proto.canRemoveSourceBuffer = function canRemoveSourceBuffer() { 53746 // IE reports that it supports removeSourceBuffer, but often throws 53747 // errors when attempting to use the function. So we report that it 53748 // does not support removeSourceBuffer. 53749 return !videojs$1.browser.IE_VERSION && window$1.MediaSource && window$1.MediaSource.prototype && typeof window$1.MediaSource.prototype.removeSourceBuffer === 'function'; 53750 } 53751 /** 53752 * Whether or not the changeType function is supported 53753 * on our SourceBuffers. 53754 * 53755 * @return {boolean} 53756 * if changeType can be called. 53757 */ 53758 ; 53759 53760 SourceUpdater.canChangeType = function canChangeType() { 53761 return window$1.SourceBuffer && window$1.SourceBuffer.prototype && typeof window$1.SourceBuffer.prototype.changeType === 'function'; 53762 } 53763 /** 53764 * Whether or not the changeType function is supported 53765 * on our SourceBuffers. 53766 * 53767 * @return {boolean} 53768 * if changeType can be called. 53769 */ 53770 ; 53771 53772 _proto.canChangeType = function canChangeType() { 53773 return this.constructor.canChangeType(); 53774 } 53775 /** 53776 * Call the changeType function on a source buffer, given the code and type. 53777 * 53778 * @param {string} type 53779 * The type of source buffer to call changeType on. 53780 * 53781 * @param {string} codec 53782 * The codec string to change type with on the source buffer. 53783 */ 53784 ; 53785 53786 _proto.changeType = function changeType(type, codec) { 53787 if (!this.canChangeType()) { 53788 videojs$1.log.error('changeType is not supported!'); 53789 return; 53790 } 53791 53792 pushQueue({ 53793 type: type, 53794 sourceUpdater: this, 53795 action: actions.changeType(codec), 53796 name: 'changeType' 53797 }); 53798 } 53799 /** 53800 * Add source buffers with a codec or, if they are already created, 53801 * call changeType on source buffers using changeType. 53802 * 53803 * @param {Object} codecs 53804 * Codecs to switch to 53805 */ 53806 ; 53807 53808 _proto.addOrChangeSourceBuffers = function addOrChangeSourceBuffers(codecs) { 53809 var _this2 = this; 53810 53811 if (!codecs || typeof codecs !== 'object' || Object.keys(codecs).length === 0) { 53812 throw new Error('Cannot addOrChangeSourceBuffers to undefined codecs'); 53813 } 53814 53815 Object.keys(codecs).forEach(function (type) { 53816 var codec = codecs[type]; 53817 53818 if (!_this2.ready()) { 53819 return _this2.addSourceBuffer(type, codec); 53820 } 53821 53822 if (_this2.canChangeType()) { 53823 _this2.changeType(type, codec); 53824 } 53825 }); 53826 } 53827 /** 53828 * Queue an update to append an ArrayBuffer. 53829 * 53830 * @param {MediaObject} object containing audioBytes and/or videoBytes 53831 * @param {Function} done the function to call when done 53832 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 53833 */ 53834 ; 53835 53836 _proto.appendBuffer = function appendBuffer(options, doneFn) { 53837 var _this3 = this; 53838 53839 var segmentInfo = options.segmentInfo, 53840 type = options.type, 53841 bytes = options.bytes; 53842 this.processedAppend_ = true; 53843 53844 if (type === 'audio' && this.videoBuffer && !this.videoAppendQueued_) { 53845 this.delayedAudioAppendQueue_.push([options, doneFn]); 53846 this.logger_("delayed audio append of " + bytes.length + " until video append"); 53847 return; 53848 } 53849 53850 pushQueue({ 53851 type: type, 53852 sourceUpdater: this, 53853 action: actions.appendBuffer(bytes, segmentInfo || { 53854 mediaIndex: -1 53855 }), 53856 doneFn: doneFn, 53857 name: 'appendBuffer' 53858 }); 53859 53860 if (type === 'video') { 53861 this.videoAppendQueued_ = true; 53862 53863 if (!this.delayedAudioAppendQueue_.length) { 53864 return; 53865 } 53866 53867 var queue = this.delayedAudioAppendQueue_.slice(); 53868 this.logger_("queuing delayed audio " + queue.length + " appendBuffers"); 53869 this.delayedAudioAppendQueue_.length = 0; 53870 queue.forEach(function (que) { 53871 _this3.appendBuffer.apply(_this3, que); 53872 }); 53873 } 53874 } 53875 /** 53876 * Get the audio buffer's buffered timerange. 53877 * 53878 * @return {TimeRange} 53879 * The audio buffer's buffered time range 53880 */ 53881 ; 53882 53883 _proto.audioBuffered = function audioBuffered() { 53884 // no media source/source buffer or it isn't in the media sources 53885 // source buffer list 53886 if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) { 53887 return videojs$1.createTimeRange(); 53888 } 53889 53890 return this.audioBuffer.buffered ? this.audioBuffer.buffered : videojs$1.createTimeRange(); 53891 } 53892 /** 53893 * Get the video buffer's buffered timerange. 53894 * 53895 * @return {TimeRange} 53896 * The video buffer's buffered time range 53897 */ 53898 ; 53899 53900 _proto.videoBuffered = function videoBuffered() { 53901 // no media source/source buffer or it isn't in the media sources 53902 // source buffer list 53903 if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) { 53904 return videojs$1.createTimeRange(); 53905 } 53906 53907 return this.videoBuffer.buffered ? this.videoBuffer.buffered : videojs$1.createTimeRange(); 53908 } 53909 /** 53910 * Get a combined video/audio buffer's buffered timerange. 53911 * 53912 * @return {TimeRange} 53913 * the combined time range 53914 */ 53915 ; 53916 53917 _proto.buffered = function buffered() { 53918 var video = inSourceBuffers(this.mediaSource, this.videoBuffer) ? this.videoBuffer : null; 53919 var audio = inSourceBuffers(this.mediaSource, this.audioBuffer) ? this.audioBuffer : null; 53920 53921 if (audio && !video) { 53922 return this.audioBuffered(); 53923 } 53924 53925 if (video && !audio) { 53926 return this.videoBuffered(); 53927 } 53928 53929 return bufferIntersection(this.audioBuffered(), this.videoBuffered()); 53930 } 53931 /** 53932 * Add a callback to the queue that will set duration on the mediaSource. 53933 * 53934 * @param {number} duration 53935 * The duration to set 53936 * 53937 * @param {Function} [doneFn] 53938 * function to run after duration has been set. 53939 */ 53940 ; 53941 53942 _proto.setDuration = function setDuration(duration, doneFn) { 53943 if (doneFn === void 0) { 53944 doneFn = noop$1; 53945 } // In order to set the duration on the media source, it's necessary to wait for all 53946 // source buffers to no longer be updating. "If the updating attribute equals true on 53947 // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and 53948 // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes). 53949 53950 53951 pushQueue({ 53952 type: 'mediaSource', 53953 sourceUpdater: this, 53954 action: actions.duration(duration), 53955 name: 'duration', 53956 doneFn: doneFn 53957 }); 53958 } 53959 /** 53960 * Add a mediaSource endOfStream call to the queue 53961 * 53962 * @param {Error} [error] 53963 * Call endOfStream with an error 53964 * 53965 * @param {Function} [doneFn] 53966 * A function that should be called when the 53967 * endOfStream call has finished. 53968 */ 53969 ; 53970 53971 _proto.endOfStream = function endOfStream(error, doneFn) { 53972 if (error === void 0) { 53973 error = null; 53974 } 53975 53976 if (doneFn === void 0) { 53977 doneFn = noop$1; 53978 } 53979 53980 if (typeof error !== 'string') { 53981 error = undefined; 53982 } // In order to set the duration on the media source, it's necessary to wait for all 53983 // source buffers to no longer be updating. "If the updating attribute equals true on 53984 // any SourceBuffer in sourceBuffers, then throw an InvalidStateError exception and 53985 // abort these steps." (source: https://www.w3.org/TR/media-source/#attributes). 53986 53987 53988 pushQueue({ 53989 type: 'mediaSource', 53990 sourceUpdater: this, 53991 action: actions.endOfStream(error), 53992 name: 'endOfStream', 53993 doneFn: doneFn 53994 }); 53995 } 53996 /** 53997 * Queue an update to remove a time range from the buffer. 53998 * 53999 * @param {number} start where to start the removal 54000 * @param {number} end where to end the removal 54001 * @param {Function} [done=noop] optional callback to be executed when the remove 54002 * operation is complete 54003 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 54004 */ 54005 ; 54006 54007 _proto.removeAudio = function removeAudio(start, end, done) { 54008 if (done === void 0) { 54009 done = noop$1; 54010 } 54011 54012 if (!this.audioBuffered().length || this.audioBuffered().end(0) === 0) { 54013 done(); 54014 return; 54015 } 54016 54017 pushQueue({ 54018 type: 'audio', 54019 sourceUpdater: this, 54020 action: actions.remove(start, end), 54021 doneFn: done, 54022 name: 'remove' 54023 }); 54024 } 54025 /** 54026 * Queue an update to remove a time range from the buffer. 54027 * 54028 * @param {number} start where to start the removal 54029 * @param {number} end where to end the removal 54030 * @param {Function} [done=noop] optional callback to be executed when the remove 54031 * operation is complete 54032 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 54033 */ 54034 ; 54035 54036 _proto.removeVideo = function removeVideo(start, end, done) { 54037 if (done === void 0) { 54038 done = noop$1; 54039 } 54040 54041 if (!this.videoBuffered().length || this.videoBuffered().end(0) === 0) { 54042 done(); 54043 return; 54044 } 54045 54046 pushQueue({ 54047 type: 'video', 54048 sourceUpdater: this, 54049 action: actions.remove(start, end), 54050 doneFn: done, 54051 name: 'remove' 54052 }); 54053 } 54054 /** 54055 * Whether the underlying sourceBuffer is updating or not 54056 * 54057 * @return {boolean} the updating status of the SourceBuffer 54058 */ 54059 ; 54060 54061 _proto.updating = function updating() { 54062 // the audio/video source buffer is updating 54063 if (_updating('audio', this) || _updating('video', this)) { 54064 return true; 54065 } 54066 54067 return false; 54068 } 54069 /** 54070 * Set/get the timestampoffset on the audio SourceBuffer 54071 * 54072 * @return {number} the timestamp offset 54073 */ 54074 ; 54075 54076 _proto.audioTimestampOffset = function audioTimestampOffset(offset) { 54077 if (typeof offset !== 'undefined' && this.audioBuffer && // no point in updating if it's the same 54078 this.audioTimestampOffset_ !== offset) { 54079 pushQueue({ 54080 type: 'audio', 54081 sourceUpdater: this, 54082 action: actions.timestampOffset(offset), 54083 name: 'timestampOffset' 54084 }); 54085 this.audioTimestampOffset_ = offset; 54086 } 54087 54088 return this.audioTimestampOffset_; 54089 } 54090 /** 54091 * Set/get the timestampoffset on the video SourceBuffer 54092 * 54093 * @return {number} the timestamp offset 54094 */ 54095 ; 54096 54097 _proto.videoTimestampOffset = function videoTimestampOffset(offset) { 54098 if (typeof offset !== 'undefined' && this.videoBuffer && // no point in updating if it's the same 54099 this.videoTimestampOffset !== offset) { 54100 pushQueue({ 54101 type: 'video', 54102 sourceUpdater: this, 54103 action: actions.timestampOffset(offset), 54104 name: 'timestampOffset' 54105 }); 54106 this.videoTimestampOffset_ = offset; 54107 } 54108 54109 return this.videoTimestampOffset_; 54110 } 54111 /** 54112 * Add a function to the queue that will be called 54113 * when it is its turn to run in the audio queue. 54114 * 54115 * @param {Function} callback 54116 * The callback to queue. 54117 */ 54118 ; 54119 54120 _proto.audioQueueCallback = function audioQueueCallback(callback) { 54121 if (!this.audioBuffer) { 54122 return; 54123 } 54124 54125 pushQueue({ 54126 type: 'audio', 54127 sourceUpdater: this, 54128 action: actions.callback(callback), 54129 name: 'callback' 54130 }); 54131 } 54132 /** 54133 * Add a function to the queue that will be called 54134 * when it is its turn to run in the video queue. 54135 * 54136 * @param {Function} callback 54137 * The callback to queue. 54138 */ 54139 ; 54140 54141 _proto.videoQueueCallback = function videoQueueCallback(callback) { 54142 if (!this.videoBuffer) { 54143 return; 54144 } 54145 54146 pushQueue({ 54147 type: 'video', 54148 sourceUpdater: this, 54149 action: actions.callback(callback), 54150 name: 'callback' 54151 }); 54152 } 54153 /** 54154 * dispose of the source updater and the underlying sourceBuffer 54155 */ 54156 ; 54157 54158 _proto.dispose = function dispose() { 54159 var _this4 = this; 54160 54161 this.trigger('dispose'); 54162 bufferTypes.forEach(function (type) { 54163 _this4.abort(type); 54164 54165 if (_this4.canRemoveSourceBuffer()) { 54166 _this4.removeSourceBuffer(type); 54167 } else { 54168 _this4[type + "QueueCallback"](function () { 54169 return cleanupBuffer(type, _this4); 54170 }); 54171 } 54172 }); 54173 this.videoAppendQueued_ = false; 54174 this.delayedAudioAppendQueue_.length = 0; 54175 54176 if (this.sourceopenListener_) { 54177 this.mediaSource.removeEventListener('sourceopen', this.sourceopenListener_); 54178 } 54179 54180 this.off(); 54181 }; 54182 54183 return SourceUpdater; 54184 }(videojs$1.EventTarget); 54185 54186 var uint8ToUtf8 = function uint8ToUtf8(uintArray) { 54187 return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray))); 54188 }; 54189 54190 var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char2) { 54191 return _char2.charCodeAt(0); 54192 })); 54193 /** 54194 * An object that manages segment loading and appending. 54195 * 54196 * @class VTTSegmentLoader 54197 * @param {Object} options required and optional options 54198 * @extends videojs.EventTarget 54199 */ 54200 54201 var VTTSegmentLoader = /*#__PURE__*/function (_SegmentLoader) { 54202 inheritsLoose(VTTSegmentLoader, _SegmentLoader); 54203 54204 function VTTSegmentLoader(settings, options) { 54205 var _this; 54206 54207 if (options === void 0) { 54208 options = {}; 54209 } 54210 54211 _this = _SegmentLoader.call(this, settings, options) || this; // VTT can't handle partial data 54212 54213 _this.handlePartialData_ = false; // SegmentLoader requires a MediaSource be specified or it will throw an error; 54214 // however, VTTSegmentLoader has no need of a media source, so delete the reference 54215 54216 _this.mediaSource_ = null; 54217 _this.subtitlesTrack_ = null; 54218 _this.loaderType_ = 'subtitle'; 54219 _this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks; // The VTT segment will have its own time mappings. Saving VTT segment timing info in 54220 // the sync controller leads to improper behavior. 54221 54222 _this.shouldSaveSegmentTimingInfo_ = false; 54223 return _this; 54224 } 54225 54226 var _proto = VTTSegmentLoader.prototype; 54227 54228 _proto.createTransmuxer_ = function createTransmuxer_() { 54229 // don't need to transmux any subtitles 54230 return null; 54231 } 54232 /** 54233 * Indicates which time ranges are buffered 54234 * 54235 * @return {TimeRange} 54236 * TimeRange object representing the current buffered ranges 54237 */ 54238 ; 54239 54240 _proto.buffered_ = function buffered_() { 54241 if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) { 54242 return videojs$1.createTimeRanges(); 54243 } 54244 54245 var cues = this.subtitlesTrack_.cues; 54246 var start = cues[0].startTime; 54247 var end = cues[cues.length - 1].startTime; 54248 return videojs$1.createTimeRanges([[start, end]]); 54249 } 54250 /** 54251 * Gets and sets init segment for the provided map 54252 * 54253 * @param {Object} map 54254 * The map object representing the init segment to get or set 54255 * @param {boolean=} set 54256 * If true, the init segment for the provided map should be saved 54257 * @return {Object} 54258 * map object for desired init segment 54259 */ 54260 ; 54261 54262 _proto.initSegmentForMap = function initSegmentForMap(map, set) { 54263 if (set === void 0) { 54264 set = false; 54265 } 54266 54267 if (!map) { 54268 return null; 54269 } 54270 54271 var id = initSegmentId(map); 54272 var storedMap = this.initSegments_[id]; 54273 54274 if (set && !storedMap && map.bytes) { 54275 // append WebVTT line terminators to the media initialization segment if it exists 54276 // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that 54277 // requires two or more WebVTT line terminators between the WebVTT header and the 54278 // rest of the file 54279 var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength; 54280 var combinedSegment = new Uint8Array(combinedByteLength); 54281 combinedSegment.set(map.bytes); 54282 combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength); 54283 this.initSegments_[id] = storedMap = { 54284 resolvedUri: map.resolvedUri, 54285 byterange: map.byterange, 54286 bytes: combinedSegment 54287 }; 54288 } 54289 54290 return storedMap || map; 54291 } 54292 /** 54293 * Returns true if all configuration required for loading is present, otherwise false. 54294 * 54295 * @return {boolean} True if the all configuration is ready for loading 54296 * @private 54297 */ 54298 ; 54299 54300 _proto.couldBeginLoading_ = function couldBeginLoading_() { 54301 return this.playlist_ && this.subtitlesTrack_ && !this.paused(); 54302 } 54303 /** 54304 * Once all the starting parameters have been specified, begin 54305 * operation. This method should only be invoked from the INIT 54306 * state. 54307 * 54308 * @private 54309 */ 54310 ; 54311 54312 _proto.init_ = function init_() { 54313 this.state = 'READY'; 54314 this.resetEverything(); 54315 return this.monitorBuffer_(); 54316 } 54317 /** 54318 * Set a subtitle track on the segment loader to add subtitles to 54319 * 54320 * @param {TextTrack=} track 54321 * The text track to add loaded subtitles to 54322 * @return {TextTrack} 54323 * Returns the subtitles track 54324 */ 54325 ; 54326 54327 _proto.track = function track(_track) { 54328 if (typeof _track === 'undefined') { 54329 return this.subtitlesTrack_; 54330 } 54331 54332 this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start 54333 // buffering now 54334 54335 if (this.state === 'INIT' && this.couldBeginLoading_()) { 54336 this.init_(); 54337 } 54338 54339 return this.subtitlesTrack_; 54340 } 54341 /** 54342 * Remove any data in the source buffer between start and end times 54343 * 54344 * @param {number} start - the start time of the region to remove from the buffer 54345 * @param {number} end - the end time of the region to remove from the buffer 54346 */ 54347 ; 54348 54349 _proto.remove = function remove(start, end) { 54350 removeCuesFromTrack(start, end, this.subtitlesTrack_); 54351 } 54352 /** 54353 * fill the buffer with segements unless the sourceBuffers are 54354 * currently updating 54355 * 54356 * Note: this function should only ever be called by monitorBuffer_ 54357 * and never directly 54358 * 54359 * @private 54360 */ 54361 ; 54362 54363 _proto.fillBuffer_ = function fillBuffer_() { 54364 var _this2 = this; 54365 54366 if (!this.syncPoint_) { 54367 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 54368 } // see if we need to begin loading immediately 54369 54370 54371 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 54372 segmentInfo = this.skipEmptySegments_(segmentInfo); 54373 54374 if (!segmentInfo) { 54375 return; 54376 } 54377 54378 if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) { 54379 // We don't have the timestamp offset that we need to sync subtitles. 54380 // Rerun on a timestamp offset or user interaction. 54381 var checkTimestampOffset = function checkTimestampOffset() { 54382 _this2.state = 'READY'; 54383 54384 if (!_this2.paused()) { 54385 // if not paused, queue a buffer check as soon as possible 54386 _this2.monitorBuffer_(); 54387 } 54388 }; 54389 54390 this.syncController_.one('timestampoffset', checkTimestampOffset); 54391 this.state = 'WAITING_ON_TIMELINE'; 54392 return; 54393 } 54394 54395 this.loadSegment_(segmentInfo); 54396 } 54397 /** 54398 * Prevents the segment loader from requesting segments we know contain no subtitles 54399 * by walking forward until we find the next segment that we don't know
54399whether it is 54400 * empty or not. 54401 * 54402 * @param {Object} segmentInfo 54403 * a segment info object that describes the current segment 54404 * @return {Object} 54405 * a segment info object that describes the current segment 54406 */ 54407 ; 54408 54409 _proto.skipEmptySegments_ = function skipEmptySegments_(segmentInfo) { 54410 while (segmentInfo && segmentInfo.segment.empty) { 54411 segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest); 54412 } 54413 54414 return segmentInfo; 54415 }; 54416 54417 _proto.stopForError = function stopForError(error) { 54418 this.error(error); 54419 this.state = 'READY'; 54420 this.pause(); 54421 this.trigger('error'); 54422 } 54423 /** 54424 * append a decrypted segement to the SourceBuffer through a SourceUpdater 54425 * 54426 * @private 54427 */ 54428 ; 54429 54430 _proto.segmentRequestFinished_ = function segmentRequestFinished_(error, simpleSegment, result) { 54431 var _this3 = this; 54432 54433 if (!this.subtitlesTrack_) { 54434 this.state = 'READY'; 54435 return; 54436 } 54437 54438 this.saveTransferStats_(simpleSegment.stats); // the request was aborted 54439 54440 if (!this.pendingSegment_) { 54441 this.state = 'READY'; 54442 this.mediaRequestsAborted += 1; 54443 return; 54444 } 54445 54446 if (error) { 54447 if (error.code === REQUEST_ERRORS.TIMEOUT) { 54448 this.handleTimeout_(); 54449 } 54450 54451 if (error.code === REQUEST_ERRORS.ABORTED) { 54452 this.mediaRequestsAborted += 1; 54453 } else { 54454 this.mediaRequestsErrored += 1; 54455 } 54456 54457 this.stopForError(error); 54458 return; 54459 } // although the VTT segment loader bandwidth isn't really used, it's good to 54460 // maintain functionality between segment loaders 54461 54462 54463 this.saveBandwidthRelatedStats_(simpleSegment.stats); 54464 this.state = 'APPENDING'; // used for tests 54465 54466 this.trigger('appending'); 54467 var segmentInfo = this.pendingSegment_; 54468 var segment = segmentInfo.segment; 54469 54470 if (segment.map) { 54471 segment.map.bytes = simpleSegment.map.bytes; 54472 } 54473 54474 segmentInfo.bytes = simpleSegment.bytes; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 54475 54476 if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) { 54477 var loadHandler; 54478 54479 var errorHandler = function errorHandler() { 54480 _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler); 54481 54482 _this3.stopForError({ 54483 message: 'Error loading vtt.js' 54484 }); 54485 54486 return; 54487 }; 54488 54489 loadHandler = function loadHandler() { 54490 _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler); 54491 54492 _this3.segmentRequestFinished_(error, simpleSegment, result); 54493 }; 54494 54495 this.state = 'WAITING_ON_VTTJS'; 54496 this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler); 54497 this.subtitlesTrack_.tech_.one('vttjserror', errorHandler); 54498 return; 54499 } 54500 54501 segment.requested = true; 54502 54503 try { 54504 this.parseVTTCues_(segmentInfo); 54505 } catch (e) { 54506 this.stopForError({ 54507 message: e.message 54508 }); 54509 return; 54510 } 54511 54512 this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_); 54513 54514 if (segmentInfo.cues.length) { 54515 segmentInfo.timingInfo = { 54516 start: segmentInfo.cues[0].startTime, 54517 end: segmentInfo.cues[segmentInfo.cues.length - 1].endTime 54518 }; 54519 } else { 54520 segmentInfo.timingInfo = { 54521 start: segmentInfo.startOfSegment, 54522 end: segmentInfo.startOfSegment + segmentInfo.duration 54523 }; 54524 } 54525 54526 if (segmentInfo.isSyncRequest) { 54527 this.trigger('syncinfoupdate'); 54528 this.pendingSegment_ = null; 54529 this.state = 'READY'; 54530 return; 54531 } 54532 54533 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 54534 this.mediaSecondsLoaded += segment.duration; 54535 segmentInfo.cues.forEach(function (cue) { 54536 // remove any overlapping cues to prevent doubling 54537 _this3.remove(cue.startTime, cue.endTime); 54538 54539 _this3.subtitlesTrack_.addCue(_this3.featuresNativeTextTracks_ ? new window$1.VTTCue(cue.startTime, cue.endTime, cue.text) : cue); 54540 }); 54541 this.handleAppendsDone_(); 54542 }; 54543 54544 _proto.handleData_ = function handleData_() {// noop as we shouldn't be getting video/audio data captions 54545 // that we do not support here. 54546 }; 54547 54548 _proto.updateTimingInfoEnd_ = function updateTimingInfoEnd_() {} // noop 54549 54550 /** 54551 * Uses the WebVTT parser to parse the segment response 54552 * 54553 * @param {Object} segmentInfo 54554 * a segment info object that describes the current segment 54555 * @private 54556 */ 54557 ; 54558 54559 _proto.parseVTTCues_ = function parseVTTCues_(segmentInfo) { 54560 var decoder; 54561 var decodeBytesToString = false;
vendor: 4,261 bytes, lines 54562-54704
54562 54563 if (typeof window$1.TextDecoder === 'function') { 54564 decoder = new window$1.TextDecoder('utf8'); 54565 } else { 54566 decoder = window$1.WebVTT.StringDecoder(); 54567 decodeBytesToString = true; 54568 } 54569 54570 var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder); 54571 segmentInfo.cues = []; 54572 segmentInfo.timestampmap = { 54573 MPEGTS: 0, 54574 LOCAL: 0 54575 }; 54576 parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues); 54577 54578 parser.ontimestampmap = function (map) { 54579 segmentInfo.timestampmap = map; 54580 }; 54581 54582 parser.onparsingerror = function (error) { 54583 videojs$1.log.warn('Error encountered when parsing cues: ' + error.message); 54584 }; 54585 54586 if (segmentInfo.segment.map) { 54587 var mapData = segmentInfo.segment.map.bytes; 54588 54589 if (decodeBytesToString) { 54590 mapData = uint8ToUtf8(mapData); 54591 } 54592 54593 parser.parse(mapData); 54594 } 54595 54596 var segmentData = segmentInfo.bytes; 54597 54598 if (decodeBytesToString) { 54599 segmentData = uint8ToUtf8(segmentData); 54600 } 54601 54602 parser.parse(segmentData); 54603 parser.flush(); 54604 } 54605 /** 54606 * Updates the start and end times of any cues parsed by the WebVTT parser using 54607 * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping 54608 * from the SyncController 54609 * 54610 * @param {Object} segmentInfo 54611 * a segment info object that describes the current segment 54612 * @param {Object} mappingObj 54613 * object containing a mapping from TS to media time 54614 * @param {Object} playlist 54615 * the playlist object containing the segment 54616 * @private 54617 */ 54618 ; 54619 54620 _proto.updateTimeMapping_ = function updateTimeMapping_(segmentInfo, mappingObj, playlist) { 54621 var segment = segmentInfo.segment; 54622 54623 if (!mappingObj) { 54624 // If the sync controller does not have a mapping of TS to Media Time for the 54625 // timeline, then we don't have enough information to update the cue 54626 // start/end times 54627 return; 54628 } 54629 54630 if (!segmentInfo.cues.length) { 54631 // If there are no cues, we also do not have enough information to figure out 54632 // segment timing. Mark that the segment contains no cues so we don't re-request 54633 // an empty segment. 54634 segment.empty = true; 54635 return; 54636 } 54637 54638 var timestampmap = segmentInfo.timestampmap; 54639 var diff = timestampmap.MPEGTS / clock_1 - timestampmap.LOCAL + mappingObj.mapping; 54640 segmentInfo.cues.forEach(function (cue) { 54641 // First convert cue time to TS time using the timestamp-map provided within the vtt 54642 cue.startTime += diff; 54643 cue.endTime += diff; 54644 }); 54645 54646 if (!playlist.syncInfo) { 54647 var firstStart = segmentInfo.cues[0].startTime; 54648 var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; 54649 playlist.syncInfo = { 54650 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 54651 time: Math.min(firstStart, lastStart - segment.duration) 54652 }; 54653 } 54654 }; 54655 54656 return VTTSegmentLoader; 54657 }(SegmentLoader); 54658 /** 54659 * @file ad-cue-tags.js 54660 */ 54661 54662 /** 54663 * Searches for an ad cue that overlaps with the given mediaTime 54664 */ 54665 54666 54667 var findAdCue = function findAdCue(track, mediaTime) { 54668 var cues = track.cues; 54669 54670 for (var i = 0; i < cues.length; i++) { 54671 var cue = cues[i]; 54672 54673 if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) { 54674 return cue; 54675 } 54676 } 54677 54678 return null; 54679 }; 54680 54681 var updateAdCues = function updateAdCues(media, track, offset) { 54682 if (offset === void 0) { 54683 offset = 0; 54684 } 54685 54686 if (!media.segments) { 54687 return; 54688 } 54689 54690 var mediaTime = offset; 54691 var cue; 54692 54693 for (var i = 0; i < media.segments.length; i++) { 54694 var segment = media.segments[i]; 54695 54696 if (!cue) { 54697 // Since the cues will span for at least the segment duration, adding a fudge 54698 // factor of half segment duration will prevent duplicate cues from being 54699 // created when timing info is not exact (e.g. cue start time initialized 54700 // at 10.006677, but next call mediaTime is 10.003332 ) 54701 cue = findAdCue(track, mediaTime + segment.duration / 2); 54702 } 54703 54704 if (cue) {
vendor: 12,932 bytes, lines 54705-55066
54705 if ('cueIn' in segment) { 54706 // Found a CUE-IN so end the cue 54707 cue.endTime = mediaTime; 54708 cue.adEndTime = mediaTime; 54709 mediaTime += segment.duration; 54710 cue = null; 54711 continue; 54712 } 54713 54714 if (mediaTime < cue.endTime) { 54715 // Already processed this mediaTime for this cue 54716 mediaTime += segment.duration; 54717 continue; 54718 } // otherwise extend cue until a CUE-IN is found 54719 54720 54721 cue.endTime += segment.duration; 54722 } else { 54723 if ('cueOut' in segment) { 54724 cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut); 54725 cue.adStartTime = mediaTime; // Assumes tag format to be 54726 // #EXT-X-CUE-OUT:30 54727 54728 cue.adEndTime = mediaTime + parseFloat(segment.cueOut); 54729 track.addCue(cue); 54730 } 54731 54732 if ('cueOutCont' in segment) { 54733 // Entered into the middle of an ad cue 54734 // Assumes tag formate to be 54735 // #EXT-X-CUE-OUT-CONT:10/30 54736 var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat), 54737 adOffset = _segment$cueOutCont$s[0], 54738 adTotal = _segment$cueOutCont$s[1]; 54739 54740 cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, ''); 54741 cue.adStartTime = mediaTime - adOffset; 54742 cue.adEndTime = cue.adStartTime + adTotal; 54743 track.addCue(cue); 54744 } 54745 } 54746 54747 mediaTime += segment.duration; 54748 } 54749 }; 54750 54751 var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always* 54752 // the equivalence display-time 0 === segment-index 0 54753 { 54754 name: 'VOD', 54755 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 54756 if (duration !== Infinity) { 54757 var syncPoint = { 54758 time: 0, 54759 segmentIndex: 0 54760 }; 54761 return syncPoint; 54762 } 54763 54764 return null; 54765 } 54766 }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist 54767 { 54768 name: 'ProgramDateTime', 54769 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 54770 if (!syncController.datetimeToDisplayTime) { 54771 return null; 54772 } 54773 54774 var segments = playlist.segments || []; 54775 var syncPoint = null; 54776 var lastDistance = null; 54777 currentTime = currentTime || 0; 54778 54779 for (var i = 0; i < segments.length; i++) { 54780 var segment = segments[i]; 54781 54782 if (segment.dateTimeObject) { 54783 var segmentTime = segment.dateTimeObject.getTime() / 1000; 54784 var segmentStart = segmentTime + syncController.datetimeToDisplayTime; 54785 var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, or if distance is 0, we have passed 54786 // currentTime and can stop looking for better candidates 54787 54788 if (lastDistance !== null && (distance === 0 || lastDistance < distance)) { 54789 break; 54790 } 54791 54792 lastDistance = distance; 54793 syncPoint = { 54794 time: segmentStart, 54795 segmentIndex: i 54796 }; 54797 } 54798 } 54799 54800 return syncPoint; 54801 } 54802 }, // Stategy "Segment": We have a known time mapping for a timeline and a 54803 // segment in the current timeline with timing data 54804 { 54805 name: 'Segment', 54806 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 54807 var segments = playlist.segments || []; 54808 var syncPoint = null; 54809 var lastDistance = null; 54810 currentTime = currentTime || 0; 54811 54812 for (var i = 0; i < segments.length; i++) { 54813 var segment = segments[i]; 54814 54815 if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') { 54816 var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed 54817 // currentTime and can stop looking for better candidates 54818 54819 if (lastDistance !== null && lastDistance < distance) { 54820 break; 54821 } 54822 54823 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 54824 lastDistance = distance; 54825 syncPoint = { 54826 time: segment.start, 54827 segmentIndex: i 54828 }; 54829 } 54830 } 54831 } 54832 54833 return syncPoint; 54834 } 54835 }, // Stategy "Discontinuity": We have a discontinuity with a known 54836 // display-time 54837 { 54838 name: 'Discontinuity', 54839 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 54840 var syncPoint = null; 54841 currentTime = currentTime || 0; 54842 54843 if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 54844 var lastDistance = null; 54845 54846 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 54847 var segmentIndex = playlist.discontinuityStarts[i]; 54848 var discontinuity = playlist.discontinuitySequence + i + 1; 54849 var discontinuitySync = syncController.discontinuities[discontinuity]; 54850 54851 if (discontinuitySync) { 54852 var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed 54853 // currentTime and can stop looking for better candidates 54854 54855 if (lastDistance !== null && lastDistance < distance) { 54856 break; 54857 } 54858 54859 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 54860 lastDistance = distance; 54861 syncPoint = { 54862 time: discontinuitySync.time, 54863 segmentIndex: segmentIndex 54864 }; 54865 } 54866 } 54867 } 54868 } 54869 54870 return syncPoint; 54871 } 54872 }, // Stategy "Playlist": We have a playlist with a known mapping of 54873 // segment index to display time 54874 { 54875 name: 'Playlist', 54876 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 54877 if (playlist.syncInfo) { 54878 var syncPoint = { 54879 time: playlist.syncInfo.time, 54880 segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence 54881 }; 54882 return syncPoint; 54883 } 54884 54885 return null; 54886 } 54887 }]; 54888 54889 var SyncController = /*#__PURE__*/function (_videojs$EventTarget) { 54890 inheritsLoose(SyncController, _videojs$EventTarget); 54891 54892 function SyncController(options) { 54893 var _this; 54894 54895 _this = _videojs$EventTarget.call(this) || this; // ...for synching across variants 54896 54897 _this.timelines = []; 54898 _this.discontinuities = []; 54899 _this.datetimeToDisplayTime = null; 54900 _this.logger_ = logger('SyncController'); 54901 return _this; 54902 } 54903 /** 54904 * Find a sync-point for the playlist specified 54905 * 54906 * A sync-point is defined as a known mapping from display-time to 54907 * a segment-index in the current playlist. 54908 * 54909 * @param {Playlist} playlist 54910 * The playlist that needs a sync-point 54911 * @param {number} duration 54912 * Duration of the MediaSource (Infinite if playing a live source) 54913 * @param {number} currentTimeline 54914 * The last timeline from which a segment was loaded 54915 * @return {Object} 54916 * A sync-point object 54917 */ 54918 54919 54920 var _proto = SyncController.prototype; 54921 54922 _proto.getSyncPoint = function getSyncPoint(playlist, duration, currentTimeline, currentTime) { 54923 var syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime); 54924 54925 if (!syncPoints.length) { 54926 // Signal that we need to attempt to get a sync-point manually 54927 // by fetching a segment in the playlist and constructing 54928 // a sync-point from that information 54929 return null; 54930 } // Now find the sync-point that is closest to the currentTime because 54931 // that should result in the most accurate guess about which segment 54932 // to fetch 54933 54934 54935 return this.selectSyncPoint_(syncPoints, { 54936 key: 'time', 54937 value: currentTime 54938 }); 54939 } 54940 /** 54941 * Calculate the amount of time that has expired off the playlist during playback 54942 * 54943 * @param {Playlist} playlist 54944 * Playlist object to calculate expired from 54945 * @param {number} duration 54946 * Duration of the MediaSource (Infinity if playling a live source) 54947 * @return {number|null} 54948 * The amount of time that has expired off the playlist during playback. Null 54949 * if no sync-points for the playlist can be found. 54950 */ 54951 ; 54952 54953 _proto.getExpiredTime = function getExpiredTime(playlist, duration) { 54954 if (!playlist || !playlist.segments) { 54955 return null; 54956 } 54957 54958 var syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time 54959 54960 if (!syncPoints.length) { 54961 return null; 54962 } 54963 54964 var syncPoint = this.selectSyncPoint_(syncPoints, { 54965 key: 'segmentIndex', 54966 value: 0 54967 }); // If the sync-point is beyond the start of the playlist, we want to subtract the 54968 // duration from index 0 to syncPoint.segmentIndex instead of adding. 54969 54970 if (syncPoint.segmentIndex > 0) { 54971 syncPoint.time *= -1; 54972 } 54973 54974 return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0)); 54975 } 54976 /** 54977 * Runs each sync-point strategy and returns a list of sync-points returned by the 54978 * strategies 54979 * 54980 * @private 54981 * @param {Playlist} playlist 54982 * The playlist that needs a sync-point 54983 * @param {number} duration 54984 * Duration of the MediaSource (Infinity if playing a live source) 54985 * @param {number} currentTimeline 54986 * The last timeline from which a segment was loaded 54987 * @return {Array} 54988 * A list of sync-point objects 54989 */ 54990 ; 54991 54992 _proto.runStrategies_ = function runStrategies_(playlist, duration, currentTimeline, currentTime) { 54993 var syncPoints = []; // Try to find a sync-point in by utilizing various strategies... 54994 54995 for (var i = 0; i < syncPointStrategies.length; i++) { 54996 var strategy = syncPointStrategies[i]; 54997 var syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime); 54998 54999 if (syncPoint) { 55000 syncPoint.strategy = strategy.name; 55001 syncPoints.push({ 55002 strategy: strategy.name, 55003 syncPoint: syncPoint 55004 }); 55005 } 55006 } 55007 55008 return syncPoints; 55009 } 55010 /** 55011 * Selects the sync-point nearest the specified target 55012 * 55013 * @private 55014 * @param {Array} syncPoints 55015 * List of sync-points to select from 55016 * @param {Object} target 55017 * Object specifying the property and value we are targeting 55018 * @param {string} target.key 55019 * Specifies the property to target. Must be either 'time' or 'segmentIndex' 55020 * @param {number} target.value 55021 * The value to target for the specified key. 55022 * @return {Object} 55023 * The sync-point nearest the target 55024 */ 55025 ; 55026 55027 _proto.selectSyncPoint_ = function selectSyncPoint_(syncPoints, target) { 55028 var bestSyncPoint = syncPoints[0].syncPoint; 55029 var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value); 55030 var bestStrategy = syncPoints[0].strategy; 55031 55032 for (var i = 1; i < syncPoints.length; i++) { 55033 var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value); 55034 55035 if (newDistance < bestDistance) { 55036 bestDistance = newDistance; 55037 bestSyncPoint = syncPoints[i].syncPoint; 55038 bestStrategy = syncPoints[i].strategy; 55039 } 55040 } 55041 55042 this.logger_("syncPoint for [" + target.key + ": " + target.value + "] chosen with strategy" + (" [" + bestStrategy + "]: [time:" + bestSyncPoint.time + ",") + (" segmentIndex:" + bestSyncPoint.segmentIndex + "]")); 55043 return bestSyncPoint; 55044 } 55045 /** 55046 * Save any meta-data present on the segments when segments leave 55047 * the live window to the playlist to allow for synchronization at the 55048 * playlist level later. 55049 * 55050 * @param {Playlist} oldPlaylist - The previous active playlist 55051 * @param {Playlist} newPlaylist - The updated and most current playlist 55052 */ 55053 ; 55054 55055 _proto.saveExpiredSegmentInfo = function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) { 55056 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time 55057 // save that information as a possible sync-point reference in future 55058 55059 for (var i = mediaSequenceDiff - 1; i >= 0; i--) { 55060 var lastRemovedSegment = oldPlaylist.segments[i]; 55061 55062 if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') { 55063 newPlaylist.syncInfo = { 55064 mediaSequence: oldPlaylist.mediaSequence + i, 55065 time: lastRemovedSegment.start 55066 };
55067 this.logger_("playlist refresh sync: [time:" + newPlaylist.syncInfo.time + "," + (" mediaSequence: " + newPlaylist.syncInfo.mediaSequence + "]")); 55068 this.trigger('syncinfoupdate'); 55069 break; 55070 } 55071 } 55072 } 55073 /** 55074 * Save the mapping from playlist's ProgramDateTime to display. This should 55075 * only ever happen once at the start of playback. 55076 * 55077 * @param {Playlist} playlist - The currently active playlist 55078 */ 55079 ; 55080 55081 _proto.setDateTimeMapping = function setDateTimeMapping(playlist) { 55082 if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) { 55083 var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000; 55084 this.datetimeToDisplayTime = -playlistTimestamp; 55085 } 55086 } 55087 /** 55088 * Calculates and saves timeline mappings, playlist sync info, and segment timing values 55089 * based on the latest timing information. 55090 * 55091 * @param {Object} options 55092 * Options object 55093 * @param {SegmentInfo} options.segmentInfo 55094 * The current active request information 55095 * @param {boolean} options.shouldSaveTimelineMapping 55096 * If there's a timeline change, determines if the timeline mapping should be 55097 * saved in timelines. 55098 */ 55099 ; 55100 55101 _proto.saveSegmentTimingInfo = function saveSegmentTimingInfo(_ref) { 55102 var segmentInfo = _ref.segmentInfo, 55103 shouldSaveTimelineMapping = _ref.shouldSaveTimelineMapping; 55104 var didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(segmentInfo, segmentInfo.timingInfo, shouldSaveTimelineMapping); 55105 55106 if (didCalculateSegmentTimeMapping) { 55107 this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information 55108 // now with segment timing information 55109 55110 if (!segmentInfo.playlist.syncInfo) { 55111 segmentInfo.playlist.syncInfo = { 55112 mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex, 55113 time: segmentInfo.segment.start 55114 }; 55115 } 55116 } 55117 }; 55118 55119 _proto.timestampOffsetForTimeline = function timestampOffsetForTimeline(timeline) { 55120 if (typeof this.timelines[timeline] === 'undefined') { 55121 return null; 55122 } 55123 55124 return this.timelines[timeline].time; 55125 }; 55126 55127 _proto.mappingForTimeline = function mappingForTimeline(timeline) { 55128 if (typeof this.timelines[timeline] === 'undefined') { 55129 return null; 55130 } 55131 55132 return this.timelines[timeline].mapping; 55133 } 55134 /** 55135 * Use the "media time" for a segment to generate a mapping to "display time" and 55136 * save that display time to the segment. 55137 * 55138 * @private 55139 * @param {SegmentInfo} segmentInfo 55140 * The current active request information 55141 * @param {Object} timingInfo 55142 * The start and end time of the current segment in "media time" 55143 * @param {boolean} shouldSaveTimelineMapping 55144 * If there's a timeline change, determines if the timeline mapping should be 55145 * saved in timelines. 55146 * @return {boolean} 55147 * Returns false if segment time mapping could not be calculated 55148 */ 55149 ; 55150 55151 _proto.calculateSegmentTimeMapping_ = function calculateSegmentTimeMapping_(segmentInfo, timingInfo, shouldSaveTimelineMapping) { 55152 var segment = segmentInfo.segment; 55153 var mappingObj = this.timelines[segmentInfo.timeline]; 55154 55155 if (segmentInfo.timestampOffset !== null) { 55156 mappingObj = { 55157 time: segmentInfo.startOfSegment, 55158 mapping: segmentInfo.startOfSegment - timingInfo.start 55159 }; 55160 55161 if (shouldSaveTimelineMapping) { 55162 this.timelines[segmentInfo.timeline] = mappingObj; 55163 this.trigger('timestampoffset'); 55164 this.logger_("time mapping for timeline " + segmentInfo.timeline + ": " + ("[time: " + mappingObj.time + "] [mapping: " + mappingObj.mapping + "]")); 55165 } 55166 55167 segment.start = segmentInfo.startOfSegment; 55168 segment.end = timingInfo.end + mappingObj.mapping; 55169 } else if (mappingObj) { 55170 segment.start = timingInfo.start + mappingObj.mapping; 55171 segment.end = timingInfo.end + mappingObj.mapping; 55172 } else { 55173 return false;
vendor: 6,954 bytes, lines 55174-55384
55174 } 55175 55176 return true; 55177 } 55178 /** 55179 * Each time we have discontinuity in the playlist, attempt to calculate the location 55180 * in display of the start of the discontinuity and save that. We also save an accuracy 55181 * value so that we save values with the most accuracy (closest to 0.) 55182 * 55183 * @private 55184 * @param {SegmentInfo} segmentInfo - The current active request information 55185 */ 55186 ; 55187 55188 _proto.saveDiscontinuitySyncInfo_ = function saveDiscontinuitySyncInfo_(segmentInfo) { 55189 var playlist = segmentInfo.playlist; 55190 var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where 55191 // the start of the range and it's accuracy is 0 (greater accuracy values 55192 // mean more approximation) 55193 55194 if (segment.discontinuity) { 55195 this.discontinuities[segment.timeline] = { 55196 time: segment.start, 55197 accuracy: 0 55198 }; 55199 } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 55200 // Search for future discontinuities that we can provide better timing 55201 // information for and save that information for sync purposes 55202 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 55203 var segmentIndex = playlist.discontinuityStarts[i]; 55204 var discontinuity = playlist.discontinuitySequence + i + 1; 55205 var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex; 55206 var accuracy = Math.abs(mediaIndexDiff); 55207 55208 if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) { 55209 var time = void 0; 55210 55211 if (mediaIndexDiff < 0) { 55212 time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex); 55213 } else { 55214 time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex); 55215 } 55216 55217 this.discontinuities[discontinuity] = { 55218 time: time, 55219 accuracy: accuracy 55220 }; 55221 } 55222 } 55223 } 55224 }; 55225 55226 _proto.dispose = function dispose() { 55227 this.trigger('dispose'); 55228 this.off(); 55229 }; 55230 55231 return SyncController; 55232 }(videojs$1.EventTarget); 55233 /** 55234 * The TimelineChangeController acts as a source for segment loaders to listen for and 55235 * keep track of latest and pending timeline changes. This is useful to ensure proper 55236 * sync, as each loader may need to make a consideration for what timeline the other 55237 * loader is on before making changes which could impact the other loader's media. 55238 * 55239 * @class TimelineChangeController 55240 * @extends videojs.EventTarget 55241 */ 55242 55243 55244 var TimelineChangeController = /*#__PURE__*/function (_videojs$EventTarget) { 55245 inheritsLoose(TimelineChangeController, _videojs$EventTarget); 55246 55247 function TimelineChangeController() { 55248 var _this; 55249 55250 _this = _videojs$EventTarget.call(this) || this; 55251 _this.pendingTimelineChanges_ = {}; 55252 _this.lastTimelineChanges_ = {}; 55253 return _this; 55254 } 55255 55256 var _proto = TimelineChangeController.prototype; 55257 55258 _proto.clearPendingTimelineChange = function clearPendingTimelineChange(type) { 55259 this.pendingTimelineChanges_[type] = null; 55260 this.trigger('pendingtimelinechange'); 55261 }; 55262 55263 _proto.pendingTimelineChange = function pendingTimelineChange(_ref) { 55264 var type = _ref.type, 55265 from = _ref.from, 55266 to = _ref.to; 55267 55268 if (typeof from === 'number' && typeof to === 'number') { 55269 this.pendingTimelineChanges_[type] = { 55270 type: type, 55271 from: from, 55272 to: to 55273 }; 55274 this.trigger('pendingtimelinechange'); 55275 } 55276 55277 return this.pendingTimelineChanges_[type]; 55278 }; 55279 55280 _proto.lastTimelineChange = function lastTimelineChange(_ref2) { 55281 var type = _ref2.type, 55282 from = _ref2.from, 55283 to = _ref2.to; 55284 55285 if (typeof from === 'number' && typeof to === 'number') { 55286 this.lastTimelineChanges_[type] = { 55287 type: type, 55288 from: from, 55289 to: to 55290 }; 55291 delete this.pendingTimelineChanges_[type]; 55292 this.trigger('timelinechange'); 55293 } 55294 55295 return this.lastTimelineChanges_[type]; 55296 }; 55297 55298 _proto.dispose = function dispose() { 55299 this.trigger('dispose'); 55300 this.pendingTimelineChanges_ = {}; 55301 this.lastTimelineChanges_ = {}; 55302 this.off(); 55303 }; 55304 55305 return TimelineChangeController; 55306 }(videojs$1.EventTarget); 55307 55308 var Decrypter = new shimWorker("./decrypter-worker.worker.js", function (window, document) { 55309 var self = this; 55310 /*! @name @videojs/http-streaming @version 2.2.4 @license Apache-2.0 */ 55311 55312 var decrypterWorker = function () { 55313 function _defineProperties(target, props) { 55314 for (var i = 0; i < props.length; i++) { 55315 var descriptor = props[i]; 55316 descriptor.enumerable = descriptor.enumerable || false; 55317 descriptor.configurable = true; 55318 if ("value" in descriptor) descriptor.writable = true; 55319 Object.defineProperty(target, descriptor.key, descriptor); 55320 } 55321 } 55322 55323 function _createClass(Constructor, protoProps, staticProps) { 55324 if (protoProps) _defineProperties(Constructor.prototype, protoProps); 55325 if (staticProps) _defineProperties(Constructor, staticProps); 55326 return Constructor; 55327 } 55328 55329 var createClass = _createClass; 55330 55331 function _inheritsLoose(subClass, superClass) { 55332 subClass.prototype = Object.create(superClass.prototype); 55333 subClass.prototype.constructor = subClass; 55334 subClass.__proto__ = superClass; 55335 } 55336 55337 var inheritsLoose = _inheritsLoose; 55338 /*! @name @videojs/vhs-utils @version 2.2.1 @license MIT */ 55339 55340 /** 55341 * @file stream.js 55342 */ 55343 55344 /** 55345 * A lightweight readable stream implemention that handles event dispatching. 55346 * 55347 * @class Stream 55348 */ 55349 55350 var Stream = /*#__PURE__*/function () { 55351 function Stream() { 55352 this.listeners = {}; 55353 } 55354 /** 55355 * Add a listener for a specified event type. 55356 * 55357 * @param {string} type the event name 55358 * @param {Function} listener the callback to be invoked when an event of 55359 * the specified type occurs 55360 */ 55361 55362 55363 var _proto = Stream.prototype; 55364 55365 _proto.on = function on(type, listener) { 55366 if (!this.listeners[type]) { 55367 this.listeners[type] = []; 55368 } 55369 55370 this.listeners[type].push(listener); 55371 } 55372 /** 55373 * Remove a listener for a specified event type. 55374 * 55375 * @param {string} type the event name 55376 * @param {Function} listener a function previously registered for this 55377 * type of event through `on` 55378 * @return {boolean} if we could turn it off or not 55379 */ 55380 ; 55381 55382 _proto.off = function off(type, listener) { 55383 if (!this.listeners[type]) { 55384 return false;
vendor: 4,383 bytes, lines 55385-55503
55385 } 55386 55387 var index = this.listeners[type].indexOf(listener); // TODO: which is better? 55388 // In Video.js we slice listener functions 55389 // on trigger so that it does not mess up the order 55390 // while we loop through. 55391 // 55392 // Here we slice on off so that the loop in trigger 55393 // can continue using it's old reference to loop without 55394 // messing up the order. 55395 55396 this.listeners[type] = this.listeners[type].slice(0); 55397 this.listeners[type].splice(index, 1); 55398 return index > -1; 55399 } 55400 /** 55401 * Trigger an event of the specified type on this stream. Any additional 55402 * arguments to this function are passed as parameters to event listeners. 55403 * 55404 * @param {string} type the event name 55405 */ 55406 ; 55407 55408 _proto.trigger = function trigger(type) { 55409 var callbacks = this.listeners[type]; 55410 55411 if (!callbacks) { 55412 return; 55413 } // Slicing the arguments on every invocation of this method 55414 // can add a significant amount of overhead. Avoid the 55415 // intermediate object creation for the common case of a 55416 // single callback argument 55417 55418 55419 if (arguments.length === 2) { 55420 var length = callbacks.length; 55421 55422 for (var i = 0; i < length; ++i) { 55423 callbacks[i].call(this, arguments[1]); 55424 } 55425 } else { 55426 var args = Array.prototype.slice.call(arguments, 1); 55427 var _length = callbacks.length; 55428 55429 for (var _i = 0; _i < _length; ++_i) { 55430 callbacks[_i].apply(this, args); 55431 } 55432 } 55433 } 55434 /** 55435 * Destroys the stream and cleans up. 55436 */ 55437 ; 55438 55439 _proto.dispose = function dispose() { 55440 this.listeners = {}; 55441 } 55442 /** 55443 * Forwards all `data` events on this stream to the destination stream. The 55444 * destination stream should provide a method `push` to receive the data 55445 * events as they arrive. 55446 * 55447 * @param {Stream} destination the stream that will receive all `data` events 55448 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 55449 */ 55450 ; 55451 55452 _proto.pipe = function pipe(destination) { 55453 this.on('data', function (data) { 55454 destination.push(data); 55455 }); 55456 }; 55457 55458 return Stream; 55459 }(); 55460 55461 var stream = Stream; 55462 /*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */ 55463 55464 /** 55465 * Returns the subarray of a Uint8Array without PKCS#7 padding. 55466 * 55467 * @param padded {Uint8Array} unencrypted bytes that have been padded 55468 * @return {Uint8Array} the unpadded bytes 55469 * @see http://tools.ietf.org/html/rfc5652 55470 */ 55471 55472 function unpad(padded) { 55473 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 55474 } 55475 /*! @name aes-decrypter @version 3.1.0 @license Apache-2.0 */ 55476 55477 /** 55478 * @file aes.js 55479 * 55480 * This file contains an adaptation of the AES decryption algorithm 55481 * from the Standford Javascript Cryptography Library. That work is 55482 * covered by the following copyright and permissions notice: 55483 * 55484 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 55485 * All rights reserved. 55486 * 55487 * Redistribution and use in source and binary forms, with or without 55488 * modification, are permitted provided that the following conditions are 55489 * met: 55490 * 55491 * 1. Redistributions of source code must retain the above copyright 55492 * notice, this list of conditions and the following disclaimer. 55493 * 55494 * 2. Redistributions in binary form must reproduce the above 55495 * copyright notice, this list of conditions and the following 55496 * disclaimer in the documentation and/or other materials provided 55497 * with the distribution. 55498 * 55499 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 55500 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 55501 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 55502 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 55503 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
vendor: 45,948 bytes, lines 55504-56767
55504 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 55505 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 55506 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 55507 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 55508 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 55509 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 55510 * 55511 * The views and conclusions contained in the software and documentation 55512 * are those of the authors and should not be interpreted as representing 55513 * official policies, either expressed or implied, of the authors. 55514 */ 55515 55516 /** 55517 * Expand the S-box tables. 55518 * 55519 * @private 55520 */ 55521 55522 55523 var precompute = function precompute() { 55524 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 55525 var encTable = tables[0]; 55526 var decTable = tables[1]; 55527 var sbox = encTable[4]; 55528 var sboxInv = decTable[4]; 55529 var i; 55530 var x; 55531 var xInv; 55532 var d = []; 55533 var th = []; 55534 var x2; 55535 var x4; 55536 var x8; 55537 var s; 55538 var tEnc; 55539 var tDec; // Compute double and third tables 55540 55541 for (i = 0; i < 256; i++) { 55542 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 55543 } 55544 55545 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 55546 // Compute sbox 55547 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 55548 s = s >> 8 ^ s & 255 ^ 99; 55549 sbox[x] = s; 55550 sboxInv[s] = x; // Compute MixColumns 55551 55552 x8 = d[x4 = d[x2 = d[x]]]; 55553 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 55554 tEnc = d[s] * 0x101 ^ s * 0x1010100; 55555 55556 for (i = 0; i < 4; i++) { 55557 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 55558 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 55559 } 55560 } // Compactify. Considerable speedup on Firefox. 55561 55562 55563 for (i = 0; i < 5; i++) { 55564 encTable[i] = encTable[i].slice(0); 55565 decTable[i] = decTable[i].slice(0); 55566 } 55567 55568 return tables; 55569 }; 55570 55571 var aesTables = null; 55572 /** 55573 * Schedule out an AES key for both encryption and decryption. This 55574 * is a low-level class. Use a cipher mode to do bulk encryption. 55575 * 55576 * @class AES 55577 * @param key {Array} The key as an array of 4, 6 or 8 words. 55578 */ 55579 55580 var AES = /*#__PURE__*/function () { 55581 function AES(key) { 55582 /** 55583 * The expanded S-box and inverse S-box tables. These will be computed 55584 * on the client so that we don't have to send them down the wire. 55585 * 55586 * There are two tables, _tables[0] is for encryption and 55587 * _tables[1] is for decryption. 55588 * 55589 * The first 4 sub-tables are the expanded S-box with MixColumns. The 55590 * last (_tables[01][4]) is the S-box itself. 55591 * 55592 * @private 55593 */ 55594 // if we have yet to precompute the S-box tables 55595 // do so now 55596 if (!aesTables) { 55597 aesTables = precompute(); 55598 } // then make a copy of that object for use 55599 55600 55601 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()]]; 55602 var i; 55603 var j; 55604 var tmp; 55605 var sbox = this._tables[0][4]; 55606 var decTable = this._tables[1]; 55607 var keyLen = key.length; 55608 var rcon = 1; 55609 55610 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 55611 throw new Error('Invalid aes key size'); 55612 } 55613 55614 var encKey = key.slice(0); 55615 var decKey = []; 55616 this._key = [encKey, decKey]; // schedule encryption keys 55617 55618 for (i = keyLen; i < 4 * keyLen + 28; i++) { 55619 tmp = encKey[i - 1]; // apply sbox 55620 55621 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 55622 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 55623 55624 if (i % keyLen === 0) { 55625 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 55626 rcon = rcon << 1 ^ (rcon >> 7) * 283; 55627 } 55628 } 55629 55630 encKey[i] = encKey[i - keyLen] ^ tmp; 55631 } // schedule decryption keys 55632 55633 55634 for (j = 0; i; j++, i--) { 55635 tmp = encKey[j & 3 ? i : i - 4]; 55636 55637 if (i <= 4 || j < 4) { 55638 decKey[j] = tmp; 55639 } else { 55640 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 55641 } 55642 } 55643 } 55644 /** 55645 * Decrypt 16 bytes, specified as four 32-bit words. 55646 * 55647 * @param {number} encrypted0 the first word to decrypt 55648 * @param {number} encrypted1 the second word to decrypt 55649 * @param {number} encrypted2 the third word to decrypt 55650 * @param {number} encrypted3 the fourth word to decrypt 55651 * @param {Int32Array} out the array to write the decrypted words 55652 * into 55653 * @param {number} offset the offset into the output array to start 55654 * writing results 55655 * @return {Array} The plaintext. 55656 */ 55657 55658 55659 var _proto = AES.prototype; 55660 55661 _proto.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 55662 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 55663 55664 var a = encrypted0 ^ key[0]; 55665 var b = encrypted3 ^ key[1]; 55666 var c = encrypted2 ^ key[2]; 55667 var d = encrypted1 ^ key[3]; 55668 var a2; 55669 var b2; 55670 var c2; // key.length === 2 ? 55671 55672 var nInnerRounds = key.length / 4 - 2; 55673 var i; 55674 var kIndex = 4; 55675 var table = this._tables[1]; // load up the tables 55676 55677 var table0 = table[0]; 55678 var table1 = table[1]; 55679 var table2 = table[2]; 55680 var table3 = table[3]; 55681 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 55682 55683 for (i = 0; i < nInnerRounds; i++) { 55684 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 55685 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 55686 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 55687 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 55688 kIndex += 4; 55689 a = a2; 55690 b = b2; 55691 c = c2; 55692 } // Last round. 55693 55694 55695 for (i = 0; i < 4; i++) { 55696 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 55697 a2 = a; 55698 a = b; 55699 b = c; 55700 c = d; 55701 d = a2; 55702 } 55703 }; 55704 55705 return AES; 55706 }(); 55707 /** 55708 * A wrapper around the Stream class to use setTimeout 55709 * and run stream "jobs" Asynchronously 55710 * 55711 * @class AsyncStream 55712 * @extends Stream 55713 */ 55714 55715 55716 var AsyncStream = /*#__PURE__*/function (_Stream) { 55717 inheritsLoose(AsyncStream, _Stream); 55718 55719 function AsyncStream() { 55720 var _this; 55721 55722 _this = _Stream.call(this, stream) || this; 55723 _this.jobs = []; 55724 _this.delay = 1; 55725 _this.timeout_ = null; 55726 return _this; 55727 } 55728 /** 55729 * process an async job 55730 * 55731 * @private 55732 */ 55733 55734 55735 var _proto = AsyncStream.prototype; 55736 55737 _proto.processJob_ = function processJob_() { 55738 this.jobs.shift()(); 55739 55740 if (this.jobs.length) { 55741 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 55742 } else { 55743 this.timeout_ = null; 55744 } 55745 } 55746 /** 55747 * push a job into the stream 55748 * 55749 * @param {Function} job the job to push into the stream 55750 */ 55751 ; 55752 55753 _proto.push = function push(job) { 55754 this.jobs.push(job); 55755 55756 if (!this.timeout_) { 55757 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 55758 } 55759 }; 55760 55761 return AsyncStream; 55762 }(stream); 55763 /** 55764 * Convert network-order (big-endian) bytes into their little-endian 55765 * representation. 55766 */ 55767 55768 55769 var ntoh = function ntoh(word) { 55770 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 55771 }; 55772 /** 55773 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 55774 * 55775 * @param {Uint8Array} encrypted the encrypted bytes 55776 * @param {Uint32Array} key the bytes of the decryption key 55777 * @param {Uint32Array} initVector the initialization vector (IV) to 55778 * use for the first round of CBC. 55779 * @return {Uint8Array} the decrypted bytes 55780 * 55781 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 55782 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 55783 * @see https://tools.ietf.org/html/rfc2315 55784 */ 55785 55786 55787 var decrypt = function decrypt(encrypted, key, initVector) { 55788 // word-level access to the encrypted bytes 55789 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 55790 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 55791 55792 var decrypted = new Uint8Array(encrypted.byteLength); 55793 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 55794 // decrypted data 55795 55796 var init0; 55797 var init1; 55798 var init2; 55799 var init3; 55800 var encrypted0; 55801 var encrypted1; 55802 var encrypted2; 55803 var encrypted3; // iteration variable 55804 55805 var wordIx; // pull out the words of the IV to ensure we don't modify the 55806 // passed-in reference and easier access 55807 55808 init0 = initVector[0]; 55809 init1 = initVector[1]; 55810 init2 = initVector[2]; 55811 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 55812 // to each decrypted block 55813 55814 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 55815 // convert big-endian (network order) words into little-endian 55816 // (javascript order) 55817 encrypted0 = ntoh(encrypted32[wordIx]); 55818 encrypted1 = ntoh(encrypted32[wordIx + 1]); 55819 encrypted2 = ntoh(encrypted32[wordIx + 2]); 55820 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 55821 55822 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 55823 // plaintext 55824 55825 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 55826 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 55827 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 55828 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 55829 55830 init0 = encrypted0; 55831 init1 = encrypted1; 55832 init2 = encrypted2; 55833 init3 = encrypted3; 55834 } 55835 55836 return decrypted; 55837 }; 55838 /** 55839 * The `Decrypter` class that manages decryption of AES 55840 * data through `AsyncStream` objects and the `decrypt` 55841 * function 55842 * 55843 * @param {Uint8Array} encrypted the encrypted bytes 55844 * @param {Uint32Array} key the bytes of the decryption key 55845 * @param {Uint32Array} initVector the initialization vector (IV) to 55846 * @param {Function} done the function to run when done 55847 * @class Decrypter 55848 */ 55849 55850 55851 var Decrypter = /*#__PURE__*/function () { 55852 function Decrypter(encrypted, key, initVector, done) { 55853 var step = Decrypter.STEP; 55854 var encrypted32 = new Int32Array(encrypted.buffer); 55855 var decrypted = new Uint8Array(encrypted.byteLength); 55856 var i = 0; 55857 this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously 55858 55859 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 55860 55861 for (i = step; i < encrypted32.length; i += step) { 55862 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 55863 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 55864 } // invoke the done() callback when everything is finished 55865 55866 55867 this.asyncStream_.push(function () { 55868 // remove pkcs#7 padding from the decrypted bytes 55869 done(null, unpad(decrypted)); 55870 }); 55871 } 55872 /** 55873 * a getter for step the maximum number of bytes to process at one time 55874 * 55875 * @return {number} the value of step 32000 55876 */ 55877 55878 55879 var _proto = Decrypter.prototype; 55880 /** 55881 * @private 55882 */ 55883 55884 _proto.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 55885 return function () { 55886 var bytes = decrypt(encrypted, key, initVector); 55887 decrypted.set(bytes, encrypted.byteOffset); 55888 }; 55889 }; 55890 55891 createClass(Decrypter, null, [{ 55892 key: "STEP", 55893 get: function get() { 55894 // 4 * 8000; 55895 return 32000; 55896 } 55897 }]); 55898 return Decrypter; 55899 }(); 55900 /** 55901 * @file bin-utils.js 55902 */ 55903 55904 /** 55905 * Creates an object for sending to a web worker modifying properties that are TypedArrays 55906 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 55907 * 55908 * @param {Object} message 55909 * Object of properties and values to send to the web worker 55910 * @return {Object} 55911 * Modified message with TypedArray values expanded 55912 * @function createTransferableMessage 55913 */ 55914 55915 55916 var createTransferableMessage = function createTransferableMessage(message) { 55917 var transferable = {}; 55918 Object.keys(message).forEach(function (key) { 55919 var value = message[key]; 55920 55921 if (ArrayBuffer.isView(value)) { 55922 transferable[key] = { 55923 bytes: value.buffer, 55924 byteOffset: value.byteOffset, 55925 byteLength: value.byteLength 55926 }; 55927 } else { 55928 transferable[key] = value; 55929 } 55930 }); 55931 return transferable; 55932 }; 55933 /* global self */ 55934 55935 /** 55936 * Our web worker interface so that things can talk to aes-decrypter 55937 * that will be running in a web worker. the scope is passed to this by 55938 * webworkify. 55939 * 55940 * @param {Object} self 55941 * the scope for the web worker 55942 */ 55943 55944 55945 var DecrypterWorker = function DecrypterWorker(self) { 55946 self.onmessage = function (event) { 55947 var data = event.data; 55948 var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength); 55949 var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4); 55950 var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4); 55951 /* eslint-disable no-new, handle-callback-err */ 55952 55953 new Decrypter(encrypted, key, iv, function (err, bytes) { 55954 self.postMessage(createTransferableMessage({ 55955 source: data.source, 55956 decrypted: bytes 55957 }), [bytes.buffer]); 55958 }); 55959 /* eslint-enable */ 55960 }; 55961 }; 55962 55963 var decrypterWorker = new DecrypterWorker(self); 55964 return decrypterWorker; 55965 }(); 55966 }); 55967 /** 55968 * Convert the properties of an HLS track into an audioTrackKind. 55969 * 55970 * @private 55971 */ 55972 55973 var audioTrackKind_ = function audioTrackKind_(properties) { 55974 var kind = properties["default"] ? 'main' : 'alternative'; 55975 55976 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) { 55977 kind = 'main-desc'; 55978 } 55979 55980 return kind; 55981 }; 55982 /** 55983 * Pause provided segment loader and playlist loader if active 55984 * 55985 * @param {SegmentLoader} segmentLoader 55986 * SegmentLoader to pause 55987 * @param {Object} mediaType 55988 * Active media type 55989 * @function stopLoaders 55990 */ 55991 55992 55993 var stopLoaders = function stopLoaders(segmentLoader, mediaType) { 55994 segmentLoader.abort(); 55995 segmentLoader.pause(); 55996 55997 if (mediaType && mediaType.activePlaylistLoader) { 55998 mediaType.activePlaylistLoader.pause(); 55999 mediaType.activePlaylistLoader = null; 56000 } 56001 }; 56002 /** 56003 * Start loading provided segment loader and playlist loader 56004 * 56005 * @param {PlaylistLoader} playlistLoader 56006 * PlaylistLoader to start loading 56007 * @param {Object} mediaType 56008 * Active media type 56009 * @function startLoaders 56010 */ 56011 56012 56013 var startLoaders = function startLoaders(playlistLoader, mediaType) { 56014 // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the 56015 // playlist loader 56016 mediaType.activePlaylistLoader = playlistLoader; 56017 playlistLoader.load(); 56018 }; 56019 /** 56020 * Returns a function to be called when the media group changes. It performs a 56021 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a 56022 * change of group is merely a rendition switch of the same content at another encoding, 56023 * rather than a change of content, such as switching audio from English to Spanish. 56024 * 56025 * @param {string} type 56026 * MediaGroup type 56027 * @param {Object} settings 56028 * Object containing required information for media groups 56029 * @return {Function} 56030 * Handler for a non-destructive resync of SegmentLoader when the active media 56031 * group changes. 56032 * @function onGroupChanged 56033 */ 56034 56035 56036 var onGroupChanged = function onGroupChanged(type, settings) { 56037 return function () { 56038 var _settings$segmentLoad = settings.segmentLoaders, 56039 segmentLoader = _settings$segmentLoad[type], 56040 mainSegmentLoader = _settings$segmentLoad.main, 56041 mediaType = settings.mediaTypes[type]; 56042 var activeTrack = mediaType.activeTrack(); 56043 var activeGroup = mediaType.activeGroup(activeTrack); 56044 var previousActiveLoader = mediaType.activePlaylistLoader; 56045 stopLoaders(segmentLoader, mediaType); 56046 56047 if (!activeGroup) { 56048 // there is no group active 56049 return; 56050 } 56051 56052 if (!activeGroup.playlistLoader) { 56053 if (previousActiveLoader) { 56054 // The previous group had a playlist loader but the new active group does not 56055 // this means we are switching from demuxed to muxed audio. In this case we want to 56056 // do a destructive reset of the main segment loader and not restart the audio 56057 // loaders. 56058 mainSegmentLoader.resetEverything(); 56059 } 56060 56061 return; 56062 } // Non-destructive resync 56063 56064 56065 segmentLoader.resyncLoader(); 56066 startLoaders(activeGroup.playlistLoader, mediaType); 56067 }; 56068 }; 56069 56070 var onGroupChanging = function onGroupChanging(type, settings) { 56071 return function () { 56072 var segmentLoader = settings.segmentLoaders[type]; 56073 segmentLoader.abort(); 56074 segmentLoader.pause(); 56075 }; 56076 }; 56077 /** 56078 * Returns a function to be called when the media track changes. It performs a 56079 * destructive reset of the SegmentLoader to ensure we start loading as close to 56080 * currentTime as possible. 56081 * 56082 * @param {string} type 56083 * MediaGroup type 56084 * @param {Object} settings 56085 * Object containing required information for media groups 56086 * @return {Function} 56087 * Handler for a destructive reset of SegmentLoader when the active media 56088 * track changes. 56089 * @function onTrackChanged 56090 */ 56091 56092 56093 var onTrackChanged = function onTrackChanged(type, settings) { 56094 return function () { 56095 var _settings$segmentLoad2 = settings.segmentLoaders, 56096 segmentLoader = _settings$segmentLoad2[type], 56097 mainSegmentLoader = _settings$segmentLoad2.main, 56098 mediaType = settings.mediaTypes[type]; 56099 var activeTrack = mediaType.activeTrack(); 56100 var activeGroup = mediaType.activeGroup(activeTrack); 56101 var previousActiveLoader = mediaType.activePlaylistLoader; 56102 stopLoaders(segmentLoader, mediaType); 56103 56104 if (!activeGroup) { 56105 // there is no group active so we do not want to restart loaders 56106 return; 56107 } 56108 56109 if (type === 'AUDIO') { 56110 if (!activeGroup.playlistLoader) { 56111 // when switching from demuxed audio/video to muxed audio/video (noted by no 56112 // playlist loader for the audio group), we want to do a destructive reset of the 56113 // main segment loader and not restart the audio loaders 56114 mainSegmentLoader.setAudio(true); // don't have to worry about disabling the audio of the audio segment loader since 56115 // it should be stopped 56116 56117 mainSegmentLoader.resetEverything(); 56118 return; 56119 } // although the segment loader is an audio segment loader, call the setAudio 56120 // function to ensure it is prepared to re-append the init segment (or handle other 56121 // config changes) 56122 56123 56124 segmentLoader.setAudio(true); 56125 mainSegmentLoader.setAudio(false); 56126 } 56127 56128 if (previousActiveLoader === activeGroup.playlistLoader) { 56129 // Nothing has actually changed. This can happen because track change events can fire 56130 // multiple times for a "single" change. One for enabling the new active track, and 56131 // one for disabling the track that was active 56132 startLoaders(activeGroup.playlistLoader, mediaType); 56133 return; 56134 } 56135 56136 if (segmentLoader.track) { 56137 // For WebVTT, set the new text track in the segmentloader 56138 segmentLoader.track(activeTrack); 56139 } // destructive reset 56140 56141 56142 segmentLoader.resetEverything(); 56143 startLoaders(activeGroup.playlistLoader, mediaType); 56144 }; 56145 }; 56146 56147 var onError = { 56148 /** 56149 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 56150 * an error. 56151 * 56152 * @param {string} type 56153 * MediaGroup type 56154 * @param {Object} settings 56155 * Object containing required information for media groups 56156 * @return {Function} 56157 * Error handler. Logs warning (or error if the playlist is blacklisted) to 56158 * console and switches back to default audio track. 56159 * @function onError.AUDIO 56160 */ 56161 AUDIO: function AUDIO(type, settings) { 56162 return function () { 56163 var segmentLoader = settings.segmentLoaders[type], 56164 mediaType = settings.mediaTypes[type], 56165 blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist; 56166 stopLoaders(segmentLoader, mediaType); // switch back to default audio track 56167 56168 var activeTrack = mediaType.activeTrack(); 56169 var activeGroup = mediaType.activeGroup(); 56170 var id = (activeGroup.filter(function (group) { 56171 return group["default"]; 56172 })[0] || activeGroup[0]).id; 56173 var defaultTrack = mediaType.tracks[id]; 56174 56175 if (activeTrack === defaultTrack) { 56176 // Default track encountered an error. All we can do now is blacklist the current 56177 // rendition and hope another will switch audio groups 56178 blacklistCurrentPlaylist({ 56179 message: 'Problem encountered loading the default audio track.' 56180 }); 56181 return; 56182 } 56183 56184 videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.'); 56185 56186 for (var trackId in mediaType.tracks) { 56187 mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack; 56188 } 56189 56190 mediaType.onTrackChanged(); 56191 }; 56192 }, 56193 56194 /** 56195 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 56196 * an error. 56197 * 56198 * @param {string} type 56199 * MediaGroup type 56200 * @param {Object} settings 56201 * Object containing required information for media groups 56202 * @return {Function} 56203 * Error handler. Logs warning to console and disables the active subtitle track 56204 * @function onError.SUBTITLES 56205 */ 56206 SUBTITLES: function SUBTITLES(type, settings) { 56207 return function () { 56208 var segmentLoader = settings.segmentLoaders[type], 56209 mediaType = settings.mediaTypes[type]; 56210 videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.'); 56211 stopLoaders(segmentLoader, mediaType); 56212 var track = mediaType.activeTrack(); 56213 56214 if (track) { 56215 track.mode = 'disabled'; 56216 } 56217 56218 mediaType.onTrackChanged(); 56219 }; 56220 } 56221 }; 56222 var setupListeners = { 56223 /** 56224 * Setup event listeners for audio playlist loader 56225 * 56226 * @param {string} type 56227 * MediaGroup type 56228 * @param {PlaylistLoader|null} playlistLoader 56229 * PlaylistLoader to register listeners on 56230 * @param {Object} settings 56231 * Object containing required information for media groups 56232 * @function setupListeners.AUDIO 56233 */ 56234 AUDIO: function AUDIO(type, playlistLoader, settings) { 56235 if (!playlistLoader) { 56236 // no playlist loader means audio will be muxed with the video 56237 return; 56238 } 56239 56240 var tech = settings.tech, 56241 requestOptions = settings.requestOptions, 56242 segmentLoader = settings.segmentLoaders[type]; 56243 playlistLoader.on('loadedmetadata', function () { 56244 var media = playlistLoader.media(); 56245 segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload 56246 // permits, start downloading segments 56247 56248 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 56249 segmentLoader.load(); 56250 } 56251 }); 56252 playlistLoader.on('loadedplaylist', function () { 56253 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 56254 56255 if (!tech.paused()) { 56256 segmentLoader.load(); 56257 } 56258 }); 56259 playlistLoader.on('error', onError[type](type, settings)); 56260 }, 56261 56262 /** 56263 * Setup event listeners for subtitle playlist loader 56264 * 56265 * @param {string} type 56266 * MediaGroup type 56267 * @param {PlaylistLoader|null} playlistLoader 56268 * PlaylistLoader to register listeners on 56269 * @param {Object} settings 56270 * Object containing required information for media groups 56271 * @function setupListeners.SUBTITLES 56272 */ 56273 SUBTITLES: function SUBTITLES(type, playlistLoader, settings) { 56274 var tech = settings.tech, 56275 requestOptions = settings.requestOptions, 56276 segmentLoader = settings.segmentLoaders[type], 56277 mediaType = settings.mediaTypes[type]; 56278 playlistLoader.on('loadedmetadata', function () { 56279 var media = playlistLoader.media(); 56280 segmentLoader.playlist(media, requestOptions); 56281 segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload 56282 // permits, start downloading segments 56283 56284 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 56285 segmentLoader.load(); 56286 } 56287 }); 56288 playlistLoader.on('loadedplaylist', function () { 56289 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 56290 56291 if (!tech.paused()) { 56292 segmentLoader.load(); 56293 } 56294 }); 56295 playlistLoader.on('error', onError[type](type, settings)); 56296 } 56297 }; 56298 var initialize = { 56299 /** 56300 * Setup PlaylistLoaders and AudioTracks for the audio groups 56301 * 56302 * @param {string} type 56303 * MediaGroup type 56304 * @param {Object} settings 56305 * Object containing required information for media groups 56306 * @function initialize.AUDIO 56307 */ 56308 'AUDIO': function AUDIO(type, settings) { 56309 var vhs = settings.vhs, 56310 sourceType = settings.sourceType, 56311 segmentLoader = settings.segmentLoaders[type], 56312 requestOptions = settings.requestOptions, 56313 _settings$master = settings.master, 56314 mediaGroups = _settings$master.mediaGroups, 56315 playlists = _settings$master.playlists, 56316 _settings$mediaTypes$ = settings.mediaTypes[type], 56317 groups = _settings$mediaTypes$.groups, 56318 tracks = _settings$mediaTypes$.tracks, 56319 masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none 56320 56321 if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) { 56322 mediaGroups[type] = { 56323 main: { 56324 "default": { 56325 "default": true 56326 } 56327 } 56328 }; 56329 } 56330 56331 var _loop = function _loop(groupId) { 56332 if (!groups[groupId]) { 56333 groups[groupId] = []; 56334 } // List of playlists that have an AUDIO attribute value matching the current 56335 // group ID 56336 56337 56338 var groupPlaylists = playlists.filter(function (playlist) { 56339 return playlist.attributes[type] === groupId; 56340 }); 56341 56342 var _loop2 = function _loop2(variantLabel) { 56343 var properties = mediaGroups[type][groupId][variantLabel]; // List of playlists for the current group ID that have a matching uri with 56344 // this alternate audio variant 56345 56346 var matchingPlaylists = groupPlaylists.filter(function (playlist) { 56347 return playlist.resolvedUri === properties.resolvedUri; 56348 }); 56349 56350 if (matchingPlaylists.length) { 56351 // If there is a playlist that has the same uri as this audio variant, assume 56352 // that the playlist is audio only. We delete the resolvedUri property here 56353 // to prevent a playlist loader from being created so that we don't have 56354 // both the main and audio segment loaders loading the same audio segments 56355 // from the same playlist. 56356 delete properties.resolvedUri; 56357 } 56358 56359 var playlistLoader = void 0; // if vhs-json was provided as the source, and the media playlist was resolved, 56360 // use the resolved media playlist object 56361 56362 if (sourceType === 'vhs-json' && properties.playlists) { 56363 playlistLoader = new PlaylistLoader(properties.playlists[0], vhs, requestOptions); 56364 } else if (properties.resolvedUri) { 56365 playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); 56366 } else if (properties.playlists && sourceType === 'dash') { 56367 playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader); 56368 } else { 56369 // no resolvedUri means the audio is muxed with the video when using this 56370 // audio track 56371 playlistLoader = null; 56372 } 56373 56374 properties = videojs$1.mergeOptions({ 56375 id: variantLabel, 56376 playlistLoader: playlistLoader 56377 }, properties); 56378 setupListeners[type](type, properties.playlistLoader, settings); 56379 groups[groupId].push(properties); 56380 56381 if (typeof tracks[variantLabel] === 'undefined') { 56382 var track = new videojs$1.AudioTrack({ 56383 id: variantLabel, 56384 kind: audioTrackKind_(properties), 56385 enabled: false, 56386 language: properties.language, 56387 "default": properties["default"], 56388 label: variantLabel 56389 }); 56390 tracks[variantLabel] = track; 56391 } 56392 }; 56393 56394 for (var variantLabel in mediaGroups[type][groupId]) { 56395 _loop2(variantLabel); 56396 } 56397 }; 56398 56399 for (var groupId in mediaGroups[type]) { 56400 _loop(groupId); 56401 } // setup single error event handler for the segment loader 56402 56403 56404 segmentLoader.on('error', onError[type](type, settings)); 56405 }, 56406 56407 /** 56408 * Setup PlaylistLoaders and TextTracks for the subtitle groups 56409 * 56410 * @param {string} type 56411 * MediaGroup type 56412 * @param {Object} settings 56413 * Object containing required information for media groups 56414 * @function initialize.SUBTITLES 56415 */ 56416 'SUBTITLES': function SUBTITLES(type, settings) { 56417 var tech = settings.tech, 56418 vhs = settings.vhs, 56419 sourceType = settings.sourceType, 56420 segmentLoader = settings.segmentLoaders[type], 56421 requestOptions = settings.requestOptions, 56422 mediaGroups = settings.master.mediaGroups, 56423 _settings$mediaTypes$2 = settings.mediaTypes[type], 56424 groups = _settings$mediaTypes$2.groups, 56425 tracks = _settings$mediaTypes$2.tracks, 56426 masterPlaylistLoader = settings.masterPlaylistLoader; 56427 56428 for (var groupId in mediaGroups[type]) { 56429 if (!groups[groupId]) { 56430 groups[groupId] = []; 56431 } 56432 56433 for (var variantLabel in mediaGroups[type][groupId]) { 56434 if (mediaGroups[type][groupId][variantLabel].forced) { 56435 // Subtitle playlists with the forced attribute are not selectable in Safari. 56436 // According to Apple's HLS Authoring Specification: 56437 // If content has forced subtitles and regular subtitles in a given language, 56438 // the regular subtitles track in that language MUST contain both the forced 56439 // subtitles and the regular subtitles for that language. 56440 // Because of this requirement and that Safari does not add forced subtitles, 56441 // forced subtitles are skipped here to maintain consistent experience across 56442 // all platforms 56443 continue; 56444 } 56445 56446 var properties = mediaGroups[type][groupId][variantLabel]; 56447 var playlistLoader = void 0; 56448 56449 if (sourceType === 'hls') { 56450 playlistLoader = new PlaylistLoader(properties.resolvedUri, vhs, requestOptions); 56451 } else if (sourceType === 'dash') { 56452 playlistLoader = new DashPlaylistLoader(properties.playlists[0], vhs, requestOptions, masterPlaylistLoader); 56453 } else if (sourceType === 'vhs-json') { 56454 playlistLoader = new PlaylistLoader( // if the vhs-json object included the media playlist, use the media playlist 56455 // as provided, otherwise use the resolved URI to load the playlist 56456 properties.playlists ? properties.playlists[0] : properties.resolvedUri, vhs, requestOptions); 56457 } 56458 56459 properties = videojs$1.mergeOptions({ 56460 id: variantLabel, 56461 playlistLoader: playlistLoader 56462 }, properties); 56463 setupListeners[type](type, properties.playlistLoader, settings); 56464 groups[groupId].push(properties); 56465 56466 if (typeof tracks[variantLabel] === 'undefined') { 56467 var track = tech.addRemoteTextTrack({ 56468 id: variantLabel, 56469 kind: 'subtitles', 56470 "default": properties["default"] && properties.autoselect, 56471 language: properties.language, 56472 label: variantLabel 56473 }, false).track; 56474 tracks[variantLabel] = track; 56475 } 56476 } 56477 } // setup single error event handler for the segment loader 56478 56479 56480 segmentLoader.on('error', onError[type](type, settings)); 56481 }, 56482 56483 /** 56484 * Setup TextTracks for the closed-caption groups 56485 * 56486 * @param {String} type 56487 * MediaGroup type 56488 * @param {Object} settings 56489 * Object containing required information for media groups 56490 * @function initialize['CLOSED-CAPTIONS'] 56491 */ 56492 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) { 56493 var tech = settings.tech, 56494 mediaGroups = settings.master.mediaGroups, 56495 _settings$mediaTypes$3 = settings.mediaTypes[type], 56496 groups = _settings$mediaTypes$3.groups, 56497 tracks = _settings$mediaTypes$3.tracks; 56498 56499 for (var groupId in mediaGroups[type]) { 56500 if (!groups[groupId]) { 56501 groups[groupId] = []; 56502 } 56503 56504 for (var variantLabel in mediaGroups[type][groupId]) { 56505 var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that 56506 // doesn't use a CCx INSTREAM-ID 56507 56508 if (!properties.instreamId.match(/CC\d/)) { 56509 continue; 56510 } // No PlaylistLoader is required for Closed-Captions because the captions are 56511 // embedded within the video stream 56512 56513 56514 groups[groupId].push(videojs$1.mergeOptions({ 56515 id: variantLabel 56516 }, properties)); 56517 56518 if (typeof tracks[variantLabel] === 'undefined') { 56519 var track = tech.addRemoteTextTrack({ 56520 id: properties.instreamId, 56521 kind: 'captions', 56522 "default": properties["default"] && properties.autoselect, 56523 language: properties.language, 56524 label: variantLabel 56525 }, false).track; 56526 tracks[variantLabel] = track; 56527 } 56528 } 56529 } 56530 } 56531 }; 56532 /** 56533 * Returns a function used to get the active group of the provided type 56534 * 56535 * @param {string} type 56536 * MediaGroup type 56537 * @param {Object} settings 56538 * Object containing required information for media groups 56539 * @return {Function} 56540 * Function that returns the active media group for the provided type. Takes an 56541 * optional parameter {TextTrack} track. If no track is provided, a list of all 56542 * variants in the group, otherwise the variant corresponding to the provided 56543 * track is returned. 56544 * @function activeGroup 56545 */ 56546 56547 var activeGroup = function activeGroup(type, settings) { 56548 return function (track) { 56549 var masterPlaylistLoader = settings.masterPlaylistLoader, 56550 groups = settings.mediaTypes[type].groups; 56551 var media = masterPlaylistLoader.media(); 56552 56553 if (!media) { 56554 return null; 56555 } 56556 56557 var variants = null; 56558 56559 if (media.attributes[type]) { 56560 variants = groups[media.attributes[type]]; 56561 } 56562 56563 variants = variants || groups.main; 56564 56565 if (typeof track === 'undefined') { 56566 return variants; 56567 } 56568 56569 if (track === null) { 56570 // An active track was specified so a corresponding group is expected. track === null 56571 // means no track is currently active so there is no corresponding group 56572 return null; 56573 } 56574 56575 return variants.filter(function (props) { 56576 return props.id === track.id; 56577 })[0] || null; 56578 }; 56579 }; 56580 56581 var activeTrack = { 56582 /** 56583 * Returns a function used to get the active track of type provided 56584 * 56585 * @param {string} type 56586 * MediaGroup type 56587 * @param {Object} settings 56588 * Object containing required information for media groups 56589 * @return {Function} 56590 * Function that returns the active media track for the provided type. Returns 56591 * null if no track is active 56592 * @function activeTrack.AUDIO 56593 */ 56594 AUDIO: function AUDIO(type, settings) { 56595 return function () { 56596 var tracks = settings.mediaTypes[type].tracks; 56597 56598 for (var id in tracks) { 56599 if (tracks[id].enabled) { 56600 return tracks[id]; 56601 } 56602 } 56603 56604 return null; 56605 }; 56606 }, 56607 56608 /** 56609 * Returns a function used to get the active track of type provided 56610 * 56611 * @param {string} type 56612 * MediaGroup type 56613 * @param {Object} settings 56614 * Object containing required information for media groups 56615 * @return {Function} 56616 * Function that returns the active media track for the provided type. Returns 56617 * null if no track is active 56618 * @function activeTrack.SUBTITLES 56619 */ 56620 SUBTITLES: function SUBTITLES(type, settings) { 56621 return function () { 56622 var tracks = settings.mediaTypes[type].tracks; 56623 56624 for (var id in tracks) { 56625 if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') { 56626 return tracks[id]; 56627 } 56628 } 56629 56630 return null; 56631 }; 56632 } 56633 }; 56634 /** 56635 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles, 56636 * Closed-Captions) specified in the master manifest. 56637 * 56638 * @param {Object} settings 56639 * Object containing required information for setting up the media groups 56640 * @param {Tech} settings.tech 56641 * The tech of the player 56642 * @param {Object} settings.requestOptions 56643 * XHR request options used by the segment loaders 56644 * @param {PlaylistLoader} settings.masterPlaylistLoader 56645 * PlaylistLoader for the master source 56646 * @param {VhsHandler} settings.vhs 56647 * VHS SourceHandler 56648 * @param {Object} settings.master 56649 * The parsed master manifest 56650 * @param {Object} settings.mediaTypes 56651 * Object to store the loaders, tracks, and utility methods for each media type 56652 * @param {Function} settings.blacklistCurrentPlaylist 56653 * Blacklists the current rendition and forces a rendition switch. 56654 * @function setupMediaGroups 56655 */ 56656 56657 var setupMediaGroups = function setupMediaGroups(settings) { 56658 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 56659 initialize[type](type, settings); 56660 }); 56661 var mediaTypes = settings.mediaTypes, 56662 masterPlaylistLoader = settings.masterPlaylistLoader, 56663 tech = settings.tech, 56664 vhs = settings.vhs; // setup active group and track getters and change event handlers 56665 56666 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 56667 mediaTypes[type].activeGroup = activeGroup(type, settings); 56668 mediaTypes[type].activeTrack = activeTrack[type](type, settings); 56669 mediaTypes[type].onGroupChanged = onGroupChanged(type, settings); 56670 mediaTypes[type].onGroupChanging = onGroupChanging(type, settings); 56671 mediaTypes[type].onTrackChanged = onTrackChanged(type, settings); 56672 }); // DO NOT enable the default subtitle or caption track. 56673 // DO enable the default audio track 56674 56675 var audioGroup = mediaTypes.AUDIO.activeGroup(); 56676 56677 if (audioGroup) { 56678 var groupId = (audioGroup.filter(function (group) { 56679 return group["default"]; 56680 })[0] || audioGroup[0]).id; 56681 mediaTypes.AUDIO.tracks[groupId].enabled = true; 56682 mediaTypes.AUDIO.onTrackChanged(); 56683 } 56684 56685 masterPlaylistLoader.on('mediachange', function () { 56686 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 56687 return mediaTypes[type].onGroupChanged(); 56688 }); 56689 }); 56690 masterPlaylistLoader.on('mediachanging', function () { 56691 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 56692 return mediaTypes[type].onGroupChanging(); 56693 }); 56694 }); // custom audio track change event handler for usage event 56695 56696 var onAudioTrackChanged = function onAudioTrackChanged() { 56697 mediaTypes.AUDIO.onTrackChanged(); 56698 tech.trigger({ 56699 type: 'usage', 56700 name: 'vhs-audio-change' 56701 }); 56702 tech.trigger({ 56703 type: 'usage', 56704 name: 'hls-audio-change' 56705 }); 56706 }; 56707 56708 tech.audioTracks().addEventListener('change', onAudioTrackChanged); 56709 tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 56710 vhs.on('dispose', function () { 56711 tech.audioTracks().removeEventListener('change', onAudioTrackChanged); 56712 tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 56713 }); // clear existing audio tracks and add the ones we just created 56714 56715 tech.clearTracks('audio'); 56716 56717 for (var id in mediaTypes.AUDIO.tracks) { 56718 tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]); 56719 } 56720 }; 56721 /** 56722 * Creates skeleton object used to store the loaders, tracks, and utility methods for each 56723 * media type 56724 * 56725 * @return {Object} 56726 * Object to store the loaders, tracks, and utility methods for each media type 56727 * @function createMediaTypes 56728 */ 56729 56730 56731 var createMediaTypes = function createMediaTypes() { 56732 var mediaTypes = {}; 56733 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 56734 mediaTypes[type] = { 56735 groups: {}, 56736 tracks: {}, 56737 activePlaylistLoader: null, 56738 activeGroup: noop$1, 56739 activeTrack: noop$1, 56740 onGroupChanged: noop$1, 56741 onTrackChanged: noop$1 56742 }; 56743 }); 56744 return mediaTypes; 56745 }; 56746 56747 var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2; 56748 var Vhs; // SegmentLoader stats that need to have each loader's 56749 // values summed to calculate the final value 56750 56751 var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred']; 56752 56753 var sumLoaderStat = function sumLoaderStat(stat) { 56754 return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat]; 56755 }; 56756 56757 var shouldSwitchToMedia = function shouldSwitchToMedia(_ref) { 56758 var currentPlaylist = _ref.currentPlaylist, 56759 nextPlaylist = _ref.nextPlaylist, 56760 forwardBuffer = _ref.forwardBuffer, 56761 bufferLowWaterLine = _ref.bufferLowWaterLine, 56762 duration = _ref.duration, 56763 log = _ref.log; // we have no other playlist to switch to 56764 56765 if (!nextPlaylist) { 56766 videojs$1.log.warn('We received no playlist to switch to. Please check your stream.'); 56767 return false;
56768 } // If the playlist is live, then we want to not take low water line into account. 56769 // This is because in LIVE, the player plays 3 segments from the end of the 56770 // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble 56771 // in those segments, a viewer will never experience a rendition upswitch. 56772 56773 56774 if (!currentPlaylist.endList) { 56775 return true; 56776 } // For the same reason as LIVE, we ignore the low water line when the VOD 56777 // duration is below the max potential low water line 56778 56779 56780 if (duration < Config.MAX_BUFFER_LOW_WATER_LINE) { 56781 return true; 56782 } // we want to switch down to lower resolutions quickly to continue playback, but 56783 56784 56785 if (nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH) { 56786 return true; 56787 } // ensure we have some buffer before we switch up to prevent us running out of 56788 // buffer while loading a higher rendition. 56789 56790 56791 if (forwardBuffer >= bufferLowWaterLine) { 56792 return true; 56793 } 56794 56795 return false; 56796 }; 56797 /** 56798 * the master playlist controller controller all interactons 56799 * between playlists and segmentloaders. At this time this mainly 56800 * involves a master playlist and a series of audio playlists 56801 * if they are available 56802 * 56803 * @class MasterPlaylistController 56804 * @extends videojs.EventTarget 56805 */ 56806 56807 56808 var MasterPlaylistController = /*#__PURE__*/function (_videojs$EventTarget) { 56809 inheritsLoose(MasterPlaylistController, _videojs$EventTarget); 56810 56811 function MasterPlaylistController(options) { 56812 var _this; 56813 56814 _this = _videojs$EventTarget.call(this) || this; 56815 var src = options.src, 56816 handleManifestRedirects = options.handleManifestRedirects, 56817 withCredentials = options.withCredentials, 56818 tech = options.tech, 56819 bandwidth = options.bandwidth, 56820 externVhs = options.externVhs, 56821 useCueTags = options.useCueTags, 56822 blacklistDuration = options.blacklistDuration, 56823 enableLowInitialPlaylist = options.enableLowInitialPlaylist, 56824 sourceType = options.sourceType, 56825 cacheEncryptionKeys = options.cacheEncryptionKeys, 56826 handlePartialData = options.handlePartialData; 56827 56828 if (!src) { 56829 throw new Error('A non-empty playlist URL or JSON manifest string is required'); 56830 } 56831 56832 Vhs = externVhs; 56833 _this.withCredentials = withCredentials; 56834 _this.tech_ = tech; 56835 _this.vhs_ = tech.vhs; 56836 _this.sourceType_ = sourceType; 56837 _this.useCueTags_ = useCueTags;
vendor: 51,539 bytes, lines 56838-58293
56838 _this.blacklistDuration = blacklistDuration; 56839 _this.enableLowInitialPlaylist = enableLowInitialPlaylist; 56840 56841 if (_this.useCueTags_) { 56842 _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues'); 56843 _this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 56844 } 56845 56846 _this.requestOptions_ = { 56847 withCredentials: withCredentials, 56848 handleManifestRedirects: handleManifestRedirects, 56849 timeout: null 56850 }; 56851 56852 _this.on('error', _this.pauseLoading); 56853 56854 _this.mediaTypes_ = createMediaTypes(); 56855 _this.mediaSource = new window$1.MediaSource(); 56856 _this.handleDurationChange_ = _this.handleDurationChange_.bind(assertThisInitialized(_this)); 56857 _this.handleSourceOpen_ = _this.handleSourceOpen_.bind(assertThisInitialized(_this)); 56858 _this.handleSourceEnded_ = _this.handleSourceEnded_.bind(assertThisInitialized(_this)); 56859 56860 _this.mediaSource.addEventListener('durationchange', _this.handleDurationChange_); // load the media source into the player 56861 56862 56863 _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_); 56864 56865 _this.mediaSource.addEventListener('sourceended', _this.handleSourceEnded_); // we don't have to handle sourceclose since dispose will handle termination of 56866 // everything, and the MediaSource should not be detached without a proper disposal 56867 56868 56869 _this.seekable_ = videojs$1.createTimeRanges(); 56870 _this.hasPlayed_ = false; 56871 _this.syncController_ = new SyncController(options); 56872 _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({ 56873 kind: 'metadata', 56874 label: 'segment-metadata' 56875 }, false).track; 56876 _this.decrypter_ = new Decrypter(); 56877 _this.sourceUpdater_ = new SourceUpdater(_this.mediaSource); 56878 _this.inbandTextTracks_ = {}; 56879 _this.timelineChangeController_ = new TimelineChangeController(); 56880 var segmentLoaderSettings = { 56881 vhs: _this.vhs_, 56882 mediaSource: _this.mediaSource, 56883 currentTime: _this.tech_.currentTime.bind(_this.tech_), 56884 seekable: function seekable() { 56885 return _this.seekable(); 56886 }, 56887 seeking: function seeking() { 56888 return _this.tech_.seeking(); 56889 }, 56890 duration: function duration() { 56891 return _this.duration(); 56892 }, 56893 hasPlayed: function hasPlayed() { 56894 return _this.hasPlayed_; 56895 }, 56896 goalBufferLength: function goalBufferLength() { 56897 return _this.goalBufferLength(); 56898 }, 56899 bandwidth: bandwidth, 56900 syncController: _this.syncController_, 56901 decrypter: _this.decrypter_, 56902 sourceType: _this.sourceType_, 56903 inbandTextTracks: _this.inbandTextTracks_, 56904 cacheEncryptionKeys: cacheEncryptionKeys, 56905 handlePartialData: handlePartialData, 56906 sourceUpdater: _this.sourceUpdater_, 56907 timelineChangeController: _this.timelineChangeController_ 56908 }; // The source type check not only determines whether a special DASH playlist loader 56909 // should be used, but also covers the case where the provided src is a vhs-json 56910 // manifest object (instead of a URL). In the case of vhs-json, the default 56911 // PlaylistLoader should be used. 56912 56913 _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(src, _this.vhs_, _this.requestOptions_) : new PlaylistLoader(src, _this.vhs_, _this.requestOptions_); 56914 56915 _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders 56916 // combined audio/video or just video when alternate audio track is selected 56917 56918 56919 _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 56920 segmentMetadataTrack: _this.segmentMetadataTrack_, 56921 loaderType: 'main' 56922 }), options); // alternate audio track 56923 56924 _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 56925 loaderType: 'audio' 56926 }), options); 56927 _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 56928 loaderType: 'vtt', 56929 featuresNativeTextTracks: _this.tech_.featuresNativeTextTracks 56930 }), options); 56931 56932 _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters 56933 // mediaRequests_ 56934 // mediaRequestsAborted_ 56935 // mediaRequestsTimedout_ 56936 // mediaRequestsErrored_ 56937 // mediaTransferDuration_ 56938 // mediaBytesTransferred_ 56939 56940 56941 loaderStats.forEach(function (stat) { 56942 _this[stat + '_'] = sumLoaderStat.bind(assertThisInitialized(_this), stat); 56943 }); 56944 _this.logger_ = logger('MPC'); 56945 _this.triggeredFmp4Usage = false; 56946 56947 _this.masterPlaylistLoader_.load(); 56948 56949 return _this; 56950 } 56951 /** 56952 * Register event handlers on the master playlist loader. A helper 56953 * function for construction time. 56954 * 56955 * @private 56956 */ 56957 56958 56959 var _proto = MasterPlaylistController.prototype; 56960 56961 _proto.setupMasterPlaylistLoaderListeners_ = function setupMasterPlaylistLoaderListeners_() { 56962 var _this2 = this; 56963 56964 this.masterPlaylistLoader_.on('loadedmetadata', function () { 56965 var media = _this2.masterPlaylistLoader_.media(); 56966 56967 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 56968 // timeout the request. 56969 56970 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 56971 _this2.requestOptions_.timeout = 0; 56972 } else { 56973 _this2.requestOptions_.timeout = requestTimeout; 56974 } // if this isn't a live video and preload permits, start 56975 // downloading segments 56976 56977 56978 if (media.endList && _this2.tech_.preload() !== 'none') { 56979 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 56980 56981 _this2.mainSegmentLoader_.load(); 56982 } 56983 56984 setupMediaGroups({ 56985 sourceType: _this2.sourceType_, 56986 segmentLoaders: { 56987 AUDIO: _this2.audioSegmentLoader_, 56988 SUBTITLES: _this2.subtitleSegmentLoader_, 56989 main: _this2.mainSegmentLoader_ 56990 }, 56991 tech: _this2.tech_, 56992 requestOptions: _this2.requestOptions_, 56993 masterPlaylistLoader: _this2.masterPlaylistLoader_, 56994 vhs: _this2.vhs_, 56995 master: _this2.master(), 56996 mediaTypes: _this2.mediaTypes_, 56997 blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2) 56998 }); 56999 57000 _this2.triggerPresenceUsage_(_this2.master(), media); 57001 57002 _this2.setupFirstPlay(); 57003 57004 if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) { 57005 _this2.trigger('selectedinitialmedia'); 57006 } else { 57007 // We must wait for the active audio playlist loader to 57008 // finish setting up before triggering this event so the 57009 // representations API and EME setup is correct 57010 _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () { 57011 _this2.trigger('selectedinitialmedia'); 57012 }); 57013 } 57014 }); 57015 this.masterPlaylistLoader_.on('loadedplaylist', function () { 57016 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 57017 57018 if (!updatedPlaylist) { 57019 // exclude any variants that are not supported by the browser before selecting 57020 // an initial media as the playlist selectors do not consider browser support 57021 _this2.excludeUnsupportedVariants_(); 57022 57023 var selectedMedia; 57024 57025 if (_this2.enableLowInitialPlaylist) { 57026 selectedMedia = _this2.selectInitialPlaylist(); 57027 } 57028 57029 if (!selectedMedia) { 57030 selectedMedia = _this2.selectPlaylist(); 57031 } 57032 57033 _this2.initialMedia_ = selectedMedia; 57034 57035 _this2.masterPlaylistLoader_.media(_this2.initialMedia_); // Under the standard case where a source URL is provided, loadedplaylist will 57036 // fire again since the playlist will be requested. In the case of vhs-json 57037 // (where the manifest object is provided as the source), when the media 57038 // playlist's `segments` list is already available, a media playlist won't be 57039 // requested, and loadedplaylist won't fire again, so the playlist handler must be 57040 // called on its own here. 57041 57042 57043 var haveJsonSource = _this2.sourceType_ === 'vhs-json' && _this2.initialMedia_.segments; 57044 57045 if (!haveJsonSource) { 57046 return; 57047 } 57048 57049 updatedPlaylist = _this2.initialMedia_; 57050 } 57051 57052 _this2.handleUpdatedMediaPlaylist(updatedPlaylist); 57053 }); 57054 this.masterPlaylistLoader_.on('error', function () { 57055 _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error); 57056 }); 57057 this.masterPlaylistLoader_.on('mediachanging', function () { 57058 _this2.mainSegmentLoader_.abort(); 57059 57060 _this2.mainSegmentLoader_.pause(); 57061 }); 57062 this.masterPlaylistLoader_.on('mediachange', function () { 57063 var media = _this2.masterPlaylistLoader_.media(); 57064 57065 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 57066 // timeout the request. 57067 57068 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 57069 _this2.requestOptions_.timeout = 0; 57070 } else { 57071 _this2.requestOptions_.timeout = requestTimeout; 57072 } // TODO: Create a new event on the PlaylistLoader that signals 57073 // that the segments have changed in some way and use that to 57074 // update the SegmentLoader instead of doing it twice here and 57075 // on `loadedplaylist` 57076 57077 57078 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 57079 57080 _this2.mainSegmentLoader_.load(); 57081 57082 _this2.tech_.trigger({ 57083 type: 'mediachange', 57084 bubbles: true 57085 }); 57086 }); 57087 this.masterPlaylistLoader_.on('playlistunchanged', function () { 57088 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 57089 57090 var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist); 57091 57092 if (playlistOutdated) { 57093 // Playlist has stopped updating and we're stuck at its end. Try to 57094 // blacklist it and switch to another playlist in the hope that that 57095 // one is updating (and give the player a chance to re-adjust to the 57096 // safe live point). 57097 _this2.blacklistCurrentPlaylist({ 57098 message: 'Playlist no longer updating.' 57099 }); // useful for monitoring QoS 57100 57101 57102 _this2.tech_.trigger('playliststuck'); 57103 } 57104 }); 57105 this.masterPlaylistLoader_.on('renditiondisabled', function () { 57106 _this2.tech_.trigger({ 57107 type: 'usage', 57108 name: 'vhs-rendition-disabled' 57109 }); 57110 57111 _this2.tech_.trigger({ 57112 type: 'usage', 57113 name: 'hls-rendition-disabled' 57114 }); 57115 }); 57116 this.masterPlaylistLoader_.on('renditionenabled', function () { 57117 _this2.tech_.trigger({ 57118 type: 'usage', 57119 name: 'vhs-rendition-enabled' 57120 }); 57121 57122 _this2.tech_.trigger({ 57123 type: 'usage', 57124 name: 'hls-rendition-enabled' 57125 }); 57126 }); 57127 } 57128 /** 57129 * Given an updated media playlist (whether it was loaded for the first time, or 57130 * refreshed for live playlists), update any relevant properties and state to reflect 57131 * changes in the media that should be accounted for (e.g., cues and duration). 57132 * 57133 * @param {Object} updatedPlaylist the updated media playlist object 57134 * 57135 * @private 57136 */ 57137 ; 57138 57139 _proto.handleUpdatedMediaPlaylist = function handleUpdatedMediaPlaylist(updatedPlaylist) { 57140 if (this.useCueTags_) { 57141 this.updateAdCues_(updatedPlaylist); 57142 } // TODO: Create a new event on the PlaylistLoader that signals 57143 // that the segments have changed in some way and use that to 57144 // update the SegmentLoader instead of doing it twice here and 57145 // on `mediachange` 57146 57147 57148 this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_); 57149 this.updateDuration(!updatedPlaylist.endList); // If the player isn't paused, ensure that the segment loader is running, 57150 // as it is possible that it was temporarily stopped while waiting for 57151 // a playlist (e.g., in case the playlist errored and we re-requested it). 57152 57153 if (!this.tech_.paused()) { 57154 this.mainSegmentLoader_.load(); 57155 57156 if (this.audioSegmentLoader_) { 57157 this.audioSegmentLoader_.load(); 57158 } 57159 } 57160 } 57161 /** 57162 * A helper function for triggerring presence usage events once per source 57163 * 57164 * @private 57165 */ 57166 ; 57167 57168 _proto.triggerPresenceUsage_ = function triggerPresenceUsage_(master, media) { 57169 var mediaGroups = master.mediaGroups || {}; 57170 var defaultDemuxed = true; 57171 var audioGroupKeys = Object.keys(mediaGroups.AUDIO); 57172 57173 for (var mediaGroup in mediaGroups.AUDIO) { 57174 for (var label in mediaGroups.AUDIO[mediaGroup]) { 57175 var properties = mediaGroups.AUDIO[mediaGroup][label]; 57176 57177 if (!properties.uri) { 57178 defaultDemuxed = false; 57179 } 57180 } 57181 } 57182 57183 if (defaultDemuxed) { 57184 this.tech_.trigger({ 57185 type: 'usage', 57186 name: 'vhs-demuxed' 57187 }); 57188 this.tech_.trigger({ 57189 type: 'usage', 57190 name: 'hls-demuxed' 57191 }); 57192 } 57193 57194 if (Object.keys(mediaGroups.SUBTITLES).length) { 57195 this.tech_.trigger({ 57196 type: 'usage', 57197 name: 'vhs-webvtt' 57198 }); 57199 this.tech_.trigger({ 57200 type: 'usage', 57201 name: 'hls-webvtt' 57202 }); 57203 } 57204 57205 if (Vhs.Playlist.isAes(media)) { 57206 this.tech_.trigger({ 57207 type: 'usage', 57208 name: 'vhs-aes' 57209 }); 57210 this.tech_.trigger({ 57211 type: 'usage', 57212 name: 'hls-aes' 57213 }); 57214 } 57215 57216 if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) { 57217 this.tech_.trigger({ 57218 type: 'usage', 57219 name: 'vhs-alternate-audio' 57220 }); 57221 this.tech_.trigger({ 57222 type: 'usage', 57223 name: 'hls-alternate-audio' 57224 }); 57225 } 57226 57227 if (this.useCueTags_) { 57228 this.tech_.trigger({ 57229 type: 'usage', 57230 name: 'vhs-playlist-cue-tags' 57231 }); 57232 this.tech_.trigger({ 57233 type: 'usage', 57234 name: 'hls-playlist-cue-tags' 57235 }); 57236 } 57237 } 57238 /** 57239 * Register event handlers on the segment loaders. A helper function 57240 * for construction time. 57241 * 57242 * @private 57243 */ 57244 ; 57245 57246 _proto.setupSegmentLoaderListeners_ = function setupSegmentLoaderListeners_() { 57247 var _this3 = this; 57248 57249 this.mainSegmentLoader_.on('bandwidthupdate', function () { 57250 var nextPlaylist = _this3.selectPlaylist(); 57251 57252 var currentPlaylist = _this3.masterPlaylistLoader_.media(); 57253 57254 var buffered = _this3.tech_.buffered(); 57255 57256 var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0; 57257 57258 var bufferLowWaterLine = _this3.bufferLowWaterLine(); 57259 57260 if (shouldSwitchToMedia({ 57261 currentPlaylist: currentPlaylist, 57262 nextPlaylist: nextPlaylist, 57263 forwardBuffer: forwardBuffer, 57264 bufferLowWaterLine: bufferLowWaterLine, 57265 duration: _this3.duration(), 57266 log: _this3.logger_ 57267 })) { 57268 _this3.masterPlaylistLoader_.media(nextPlaylist); 57269 } 57270 57271 _this3.tech_.trigger('bandwidthupdate'); 57272 }); 57273 this.mainSegmentLoader_.on('progress', function () { 57274 _this3.trigger('progress'); 57275 }); 57276 this.mainSegmentLoader_.on('error', function () { 57277 _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error()); 57278 }); 57279 this.mainSegmentLoader_.on('appenderror', function () { 57280 _this3.error = _this3.mainSegmentLoader_.error_; 57281 57282 _this3.trigger('error'); 57283 }); 57284 this.mainSegmentLoader_.on('syncinfoupdate', function () { 57285 _this3.onSyncInfoUpdate_(); 57286 }); 57287 this.mainSegmentLoader_.on('timestampoffset', function () { 57288 _this3.tech_.trigger({ 57289 type: 'usage', 57290 name: 'vhs-timestamp-offset' 57291 }); 57292 57293 _this3.tech_.trigger({ 57294 type: 'usage', 57295 name: 'hls-timestamp-offset' 57296 }); 57297 }); 57298 this.audioSegmentLoader_.on('syncinfoupdate', function () { 57299 _this3.onSyncInfoUpdate_(); 57300 }); 57301 this.audioSegmentLoader_.on('appenderror', function () { 57302 _this3.error = _this3.audioSegmentLoader_.error_; 57303 57304 _this3.trigger('error'); 57305 }); 57306 this.mainSegmentLoader_.on('ended', function () { 57307 _this3.logger_('main segment loader ended'); 57308 57309 _this3.onEndOfStream(); 57310 }); 57311 this.mainSegmentLoader_.on('earlyabort', function () { 57312 _this3.blacklistCurrentPlaylist({ 57313 message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.' 57314 }, ABORT_EARLY_BLACKLIST_SECONDS); 57315 }); 57316 57317 var updateCodecs = function updateCodecs() { 57318 if (!_this3.sourceUpdater_.ready()) { 57319 return _this3.tryToCreateSourceBuffers_(); 57320 } 57321 57322 var codecs = _this3.getCodecsOrExclude_(); // no codecs means that the playlist was excluded 57323 57324 57325 if (!codecs) { 57326 return; 57327 } 57328 57329 _this3.sourceUpdater_.addOrChangeSourceBuffers(codecs); 57330 }; 57331 57332 this.mainSegmentLoader_.on('trackinfo', updateCodecs); 57333 this.audioSegmentLoader_.on('trackinfo', updateCodecs); 57334 this.mainSegmentLoader_.on('fmp4', function () { 57335 if (!_this3.triggeredFmp4Usage) { 57336 _this3.tech_.trigger({ 57337 type: 'usage', 57338 name: 'vhs-fmp4' 57339 }); 57340 57341 _this3.tech_.trigger({ 57342 type: 'usage', 57343 name: 'hls-fmp4' 57344 }); 57345 57346 _this3.triggeredFmp4Usage = true; 57347 } 57348 }); 57349 this.audioSegmentLoader_.on('fmp4', function () { 57350 if (!_this3.triggeredFmp4Usage) { 57351 _this3.tech_.trigger({ 57352 type: 'usage', 57353 name: 'vhs-fmp4' 57354 }); 57355 57356 _this3.tech_.trigger({ 57357 type: 'usage', 57358 name: 'hls-fmp4' 57359 }); 57360 57361 _this3.triggeredFmp4Usage = true; 57362 } 57363 }); 57364 this.audioSegmentLoader_.on('ended', function () { 57365 _this3.logger_('audioSegmentLoader ended'); 57366 57367 _this3.onEndOfStream(); 57368 }); 57369 }; 57370 57371 _proto.mediaSecondsLoaded_ = function mediaSecondsLoaded_() { 57372 return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded); 57373 } 57374 /** 57375 * Call load on our SegmentLoaders 57376 */ 57377 ; 57378 57379 _proto.load = function load() { 57380 this.mainSegmentLoader_.load(); 57381 57382 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 57383 this.audioSegmentLoader_.load(); 57384 } 57385 57386 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 57387 this.subtitleSegmentLoader_.load(); 57388 } 57389 } 57390 /** 57391 * Re-tune playback quality level for the current player 57392 * conditions without performing destructive actions, like 57393 * removing already buffered content 57394 * 57395 * @private 57396 */ 57397 ; 57398 57399 _proto.smoothQualityChange_ = function smoothQualityChange_(media) { 57400 if (media === void 0) { 57401 media = this.selectPlaylist(); 57402 } 57403 57404 if (media === this.masterPlaylistLoader_.media()) { 57405 return; 57406 } 57407 57408 this.masterPlaylistLoader_.media(media); 57409 this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes 57410 } 57411 /** 57412 * Re-tune playback quality level for the current player 57413 * conditions. This method will perform destructive actions like removing 57414 * already buffered content in order to readjust the currently active 57415 * playlist quickly. This is good for manual quality changes 57416 * 57417 * @private 57418 */ 57419 ; 57420 57421 _proto.fastQualityChange_ = function fastQualityChange_(media) { 57422 var _this4 = this; 57423 57424 if (media === void 0) { 57425 media = this.selectPlaylist(); 57426 } 57427 57428 if (media === this.masterPlaylistLoader_.media()) { 57429 return; 57430 } 57431 57432 this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give 57433 // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds 57434 // ahead is roughly the minimum that will accomplish this across a variety of content 57435 // in IE and Edge, but seeking in place is sufficient on all other browsers) 57436 // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/ 57437 // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904 57438 57439 this.mainSegmentLoader_.resetEverything(function () { 57440 // Since this is not a typical seek, we avoid the seekTo method which can cause segments 57441 // from the previously enabled rendition to load before the new playlist has finished loading 57442 if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) { 57443 _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04); 57444 } else { 57445 _this4.tech_.setCurrentTime(_this4.tech_.currentTime()); 57446 } 57447 }); // don't need to reset audio as it is reset when media changes 57448 } 57449 /** 57450 * Begin playback. 57451 */ 57452 ; 57453 57454 _proto.play = function play() { 57455 if (this.setupFirstPlay()) { 57456 return; 57457 } 57458 57459 if (this.tech_.ended()) { 57460 this.tech_.setCurrentTime(0); 57461 } 57462 57463 if (this.hasPlayed_) { 57464 this.load(); 57465 } 57466 57467 var seekable = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window, 57468 // seek forward to the live point 57469 57470 if (this.tech_.duration() === Infinity) { 57471 if (this.tech_.currentTime() < seekable.start(0)) { 57472 return this.tech_.setCurrentTime(seekable.end(seekable.length - 1)); 57473 } 57474 } 57475 } 57476 /** 57477 * Seek to the latest media position if this is a live video and the 57478 * player and video are loaded and initialized. 57479 */ 57480 ; 57481 57482 _proto.setupFirstPlay = function setupFirstPlay() { 57483 var _this5 = this; 57484 57485 var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play 57486 // If 1) there is no active media 57487 // 2) the player is paused 57488 // 3) the first play has already been setup 57489 // then exit early 57490 57491 if (!media || this.tech_.paused() || this.hasPlayed_) { 57492 return false; 57493 } // when the video is a live stream 57494 57495 57496 if (!media.endList) { 57497 var seekable = this.seekable(); 57498 57499 if (!seekable.length) { 57500 // without a seekable range, the player cannot seek to begin buffering at the live 57501 // point 57502 return false; 57503 } 57504 57505 if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) { 57506 // IE11 throws an InvalidStateError if you try to set currentTime while the 57507 // readyState is 0, so it must be delayed until the tech fires loadedmetadata. 57508 this.tech_.one('loadedmetadata', function () { 57509 _this5.trigger('firstplay'); 57510 57511 _this5.tech_.setCurrentTime(seekable.end(0)); 57512 57513 _this5.hasPlayed_ = true; 57514 }); 57515 return false; 57516 } // trigger firstplay to inform the source handler to ignore the next seek event 57517 57518 57519 this.trigger('firstplay'); // seek to the live point 57520 57521 this.tech_.setCurrentTime(seekable.end(0)); 57522 } 57523 57524 this.hasPlayed_ = true; // we can begin loading now that everything is ready 57525 57526 this.load(); 57527 return true; 57528 } 57529 /** 57530 * handle the sourceopen event on the MediaSource 57531 * 57532 * @private 57533 */ 57534 ; 57535 57536 _proto.handleSourceOpen_ = function handleSourceOpen_() { 57537 // Only attempt to create the source buffer if none already exist. 57538 // handleSourceOpen is also called when we are "re-opening" a source buffer 57539 // after `endOfStream` has been called (in response to a seek for instance) 57540 this.tryToCreateSourceBuffers_(); // if autoplay is enabled, begin playback. This is duplicative of 57541 // code in video.js but is required because play() must be invoked 57542 // *after* the media source has opened. 57543 57544 if (this.tech_.autoplay()) { 57545 var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request 57546 // on browsers which return a promise 57547 57548 if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') { 57549 playPromise.then(null, function (e) {}); 57550 } 57551 } 57552 57553 this.trigger('sourceopen'); 57554 } 57555 /** 57556 * handle the sourceended event on the MediaSource 57557 * 57558 * @private 57559 */ 57560 ; 57561 57562 _proto.handleSourceEnded_ = function handleSourceEnded_() { 57563 if (!this.inbandTextTracks_.metadataTrack_) { 57564 return; 57565 } 57566 57567 var cues = this.inbandTextTracks_.metadataTrack_.cues; 57568 57569 if (!cues || !cues.length) { 57570 return; 57571 } 57572 57573 var duration = this.duration(); 57574 cues[cues.length - 1].endTime = isNaN(duration) || Math.abs(duration) === Infinity ? Number.MAX_VALUE : duration; 57575 } 57576 /** 57577 * handle the durationchange event on the MediaSource 57578 * 57579 * @private 57580 */ 57581 ; 57582 57583 _proto.handleDurationChange_ = function handleDurationChange_() { 57584 this.tech_.trigger('durationchange'); 57585 } 57586 /** 57587 * Calls endOfStream on the media source when all active stream types have called 57588 * endOfStream 57589 * 57590 * @param {string} streamType 57591 * Stream type of the segment loader that called endOfStream 57592 * @private 57593 */ 57594 ; 57595 57596 _proto.onEndOfStream = function onEndOfStream() { 57597 var isEndOfStream = this.mainSegmentLoader_.ended_; 57598 57599 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 57600 // if the audio playlist loader exists, then alternate audio is active 57601 if (!this.mainSegmentLoader_.currentMediaInfo_ || this.mainSegmentLoader_.currentMediaInfo_.hasVideo) { 57602 // if we do not know if the main segment loader contains video yet or if we 57603 // definitively know the main segment loader contains video, then we need to wait 57604 // for both main and audio segment loaders to call endOfStream 57605 isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_; 57606 } else { 57607 // otherwise just rely on the audio loader 57608 isEndOfStream = this.audioSegmentLoader_.ended_; 57609 } 57610 } 57611 57612 if (!isEndOfStream) { 57613 return; 57614 } 57615 57616 this.sourceUpdater_.endOfStream(); 57617 } 57618 /** 57619 * Check if a playlist has stopped being updated 57620 * 57621 * @param {Object} playlist the media playlist object 57622 * @return {boolean} whether the playlist has stopped being updated or not 57623 */ 57624 ; 57625 57626 _proto.stuckAtPlaylistEnd_ = function stuckAtPlaylistEnd_(playlist) { 57627 var seekable = this.seekable(); 57628 57629 if (!seekable.length) { 57630 // playlist doesn't have enough information to determine whether we are stuck 57631 return false; 57632 } 57633 57634 var expired = this.syncController_.getExpiredTime(playlist, this.duration()); 57635 57636 if (expired === null) { 57637 return false; 57638 } // does not use the safe live end to calculate playlist end, since we 57639 // don't want to say we are stuck while there is still content 57640 57641 57642 var absolutePlaylistEnd = Vhs.Playlist.playlistEnd(playlist, expired); 57643 var currentTime = this.tech_.currentTime(); 57644 var buffered = this.tech_.buffered(); 57645 57646 if (!buffered.length) { 57647 // return true if the playhead reached the absolute end of the playlist 57648 return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA; 57649 } 57650 57651 var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute 57652 // end of playlist 57653 57654 return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA; 57655 } 57656 /** 57657 * Blacklists a playlist when an error occurs for a set amount of time 57658 * making it unavailable for selection by the rendition selection algorithm 57659 * and then forces a new playlist (rendition) selection. 57660 * 57661 * @param {Object=} error an optional error that may include the playlist 57662 * to blacklist 57663 * @param {number=} blacklistDuration an optional number of seconds to blacklist the 57664 * playlist 57665 */ 57666 ; 57667 57668 _proto.blacklistCurrentPlaylist = function blacklistCurrentPlaylist(error, blacklistDuration) { 57669 if (error === void 0) { 57670 error = {}; 57671 } // If the `error` was generated by the playlist loader, it will contain 57672 // the playlist we were trying to load (but failed) and that should be 57673 // blacklisted instead of the currently selected playlist which is likely 57674 // out-of-date in this scenario 57675 57676 57677 var currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 57678 blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration; // If there is no current playlist, then an error occurred while we were 57679 // trying to load the master OR while we were disposing of the tech 57680 57681 if (!currentPlaylist) { 57682 this.error = error; 57683 57684 if (this.mediaSource.readyState !== 'open') { 57685 this.trigger('error'); 57686 } else { 57687 this.sourceUpdater_.endOfStream('network'); 57688 } 57689 57690 return; 57691 } 57692 57693 var playlists = this.masterPlaylistLoader_.master.playlists; 57694 var enabledPlaylists = playlists.filter(isEnabled); 57695 var isFinalRendition = enabledPlaylists.length === 1 && enabledPlaylists[0] === currentPlaylist; // Don't blacklist the only playlist unless it was blacklisted 57696 // forever 57697 57698 if (playlists.length === 1 && blacklistDuration !== Infinity) { 57699 videojs$1.log.warn("Problem encountered with playlist " + currentPlaylist.id + ". " + 'Trying again since it is the only playlist.'); 57700 this.tech_.trigger('retryplaylist'); 57701 return this.masterPlaylistLoader_.load(isFinalRendition); 57702 } 57703 57704 if (isFinalRendition) { 57705 // Since we're on the final non-blacklisted playlist, and we're about to blacklist 57706 // it, instead of erring the player or retrying this playlist, clear out the current 57707 // blacklist. This allows other playlists to be attempted in case any have been 57708 // fixed. 57709 var reincluded = false; 57710 playlists.forEach(function (playlist) { 57711 // skip current playlist which is about to be blacklisted 57712 if (playlist === currentPlaylist) { 57713 return; 57714 } 57715 57716 var excludeUntil = playlist.excludeUntil; // a playlist cannot be reincluded if it wasn't excluded to begin with. 57717 57718 if (typeof excludeUntil !== 'undefined' && excludeUntil !== Infinity) { 57719 reincluded = true; 57720 delete playlist.excludeUntil; 57721 } 57722 }); 57723 57724 if (reincluded) { 57725 videojs$1.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 57726 // playlist. This is needed for users relying on the retryplaylist event to catch a 57727 // case where the player might be stuck and looping through "dead" playlists. 57728 57729 this.tech_.trigger('retryplaylist'); 57730 } 57731 } // Blacklist this playlist 57732 57733 57734 currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000; 57735 this.tech_.trigger('blacklistplaylist'); 57736 this.tech_.trigger({ 57737 type: 'usage', 57738 name: 'vhs-rendition-blacklisted' 57739 }); 57740 this.tech_.trigger({ 57741 type: 'usage', 57742 name: 'hls-rendition-blacklisted' 57743 }); // TODO: should we select a new playlist if this blacklist wasn't for the currentPlaylist? 57744 // Would be something like media().id !=== currentPlaylist.id and we would need something 57745 // like `pendingMedia` in playlist loaders to check against that too. This will prevent us 57746 // from loading a new playlist on any blacklist. 57747 // Select a new playlist 57748 57749 var nextPlaylist = this.selectPlaylist(); 57750 57751 if (!nextPlaylist) { 57752 this.error = 'Playback cannot continue. No available working or supported playlists.'; 57753 this.trigger('error'); 57754 return; 57755 } 57756 57757 var logFn = error.internal ? this.logger_ : videojs$1.log.warn; 57758 var errorMessage = error.message ? ' ' + error.message : ''; 57759 logFn((error.internal ? 'Internal problem' : 'Problem') + " encountered with playlist " + currentPlaylist.id + "." + (errorMessage + " Switching to playlist " + nextPlaylist.id + ".")); // if audio group changed reset audio loaders 57760 57761 if (nextPlaylist.attributes.AUDIO !== currentPlaylist.attributes.AUDIO) { 57762 this.delegateLoaders_('audio', ['abort', 'pause']); 57763 } // if subtitle group changed reset subtitle loaders 57764 57765 57766 if (nextPlaylist.attributes.SUBTITLES !== currentPlaylist.attributes.SUBTITLES) { 57767 this.delegateLoaders_('subtitle', ['abort', 'pause']); 57768 } 57769 57770 this.delegateLoaders_('main', ['abort', 'pause']); 57771 return this.masterPlaylistLoader_.media(nextPlaylist, isFinalRendition); 57772 } 57773 /** 57774 * Pause all segment/playlist loaders 57775 */ 57776 ; 57777 57778 _proto.pauseLoading = function pauseLoading() { 57779 this.delegateLoaders_('all', ['abort', 'pause']); 57780 } 57781 /** 57782 * Call a set of functions in order on playlist loaders, segment loaders, 57783 * or both types of loaders. 57784 * 57785 * @param {string} filter 57786 * Filter loaders that should call fnNames using a string. Can be: 57787 * * all - run on all loaders 57788 * * audio - run on all audio loaders 57789 * * subtitle - run on all subtitle loaders 57790 * * main - run on the main/master loaders 57791 * 57792 * @param {Array|string} fnNames 57793 * A string or array of function names to call. 57794 */ 57795 ; 57796 57797 _proto.delegateLoaders_ = function delegateLoaders_(filter, fnNames) { 57798 var _this6 = this; 57799 57800 var loaders = []; 57801 var dontFilterPlaylist = filter === 'all'; 57802 57803 if (dontFilterPlaylist || filter === 'main') { 57804 loaders.push(this.masterPlaylistLoader_); 57805 } 57806 57807 var mediaTypes = []; 57808 57809 if (dontFilterPlaylist || filter === 'audio') { 57810 mediaTypes.push('AUDIO'); 57811 } 57812 57813 if (dontFilterPlaylist || filter === 'subtitle') { 57814 mediaTypes.push('CLOSED-CAPTIONS'); 57815 mediaTypes.push('SUBTITLES'); 57816 } 57817 57818 mediaTypes.forEach(function (mediaType) { 57819 var loader = _this6.mediaTypes_[mediaType] && _this6.mediaTypes_[mediaType].activePlaylistLoader; 57820 57821 if (loader) { 57822 loaders.push(loader); 57823 } 57824 }); 57825 ['main', 'audio', 'subtitle'].forEach(function (name) { 57826 var loader = _this6[name + "SegmentLoader_"]; 57827 57828 if (loader && (filter === name || filter === 'all')) { 57829 loaders.push(loader); 57830 } 57831 }); 57832 loaders.forEach(function (loader) { 57833 return fnNames.forEach(function (fnName) { 57834 if (typeof loader[fnName] === 'function') { 57835 loader[fnName](); 57836 } 57837 }); 57838 }); 57839 } 57840 /** 57841 * set the current time on all segment loaders 57842 * 57843 * @param {TimeRange} currentTime the current time to set 57844 * @return {TimeRange} the current time 57845 */ 57846 ; 57847 57848 _proto.setCurrentTime = function setCurrentTime(currentTime) { 57849 var buffered = findRange(this.tech_.buffered(), currentTime); 57850 57851 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 57852 // return immediately if the metadata is not ready yet 57853 return 0; 57854 } // it's clearly an edge-case but don't thrown an error if asked to 57855 // seek within an empty playlist 57856 57857 57858 if (!this.masterPlaylistLoader_.media().segments) { 57859 return 0; 57860 } // if the seek location is already buffered, continue buffering as usual 57861 57862 57863 if (buffered && buffered.length) { 57864 return currentTime; 57865 } // cancel outstanding requests so we begin buffering at the new 57866 // location 57867 57868 57869 this.mainSegmentLoader_.resetEverything(); 57870 this.mainSegmentLoader_.abort(); 57871 57872 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 57873 this.audioSegmentLoader_.resetEverything(); 57874 this.audioSegmentLoader_.abort(); 57875 } 57876 57877 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 57878 this.subtitleSegmentLoader_.resetEverything(); 57879 this.subtitleSegmentLoader_.abort(); 57880 } // start segment loader loading in case they are paused 57881 57882 57883 this.load(); 57884 } 57885 /** 57886 * get the current duration 57887 * 57888 * @return {TimeRange} the duration 57889 */ 57890 ; 57891 57892 _proto.duration = function duration() { 57893 if (!this.masterPlaylistLoader_) { 57894 return 0; 57895 } 57896 57897 var media = this.masterPlaylistLoader_.media(); 57898 57899 if (!media) { 57900 // no playlists loaded yet, so can't determine a duration 57901 return 0; 57902 } // Don't rely on the media source for duration in the case of a live playlist since 57903 // setting the native MediaSource's duration to infinity ends up with consequences to 57904 // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details. 57905 // 57906 // This is resolved in the spec by https://github.com/w3c/media-source/pull/92, 57907 // however, few browsers have support for setLiveSeekableRange() 57908 // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange 57909 // 57910 // Until a time when the duration of the media source can be set to infinity, and a 57911 // seekable range specified across browsers, just return Infinity. 57912 57913 57914 if (!media.endList) { 57915 return Infinity; 57916 } // Since this is a VOD video, it is safe to rely on the media source's duration (if 57917 // available). If it's not available, fall back to a playlist-calculated estimate. 57918 57919 57920 if (this.mediaSource) { 57921 return this.mediaSource.duration; 57922 } 57923 57924 return Vhs.Playlist.duration(media); 57925 } 57926 /** 57927 * check the seekable range 57928 * 57929 * @return {TimeRange} the seekable range 57930 */ 57931 ; 57932 57933 _proto.seekable = function seekable() { 57934 return this.seekable_; 57935 }; 57936 57937 _proto.onSyncInfoUpdate_ = function onSyncInfoUpdate_() { 57938 var audioSeekable; 57939 57940 if (!this.masterPlaylistLoader_) { 57941 return; 57942 } 57943 57944 var media = this.masterPlaylistLoader_.media(); 57945 57946 if (!media) { 57947 return; 57948 } 57949 57950 var expired = this.syncController_.getExpiredTime(media, this.duration()); 57951 57952 if (expired === null) { 57953 // not enough information to update seekable 57954 return; 57955 } 57956 57957 var suggestedPresentationDelay = this.masterPlaylistLoader_.master.suggestedPresentationDelay; 57958 var mainSeekable = Vhs.Playlist.seekable(media, expired, suggestedPresentationDelay); 57959 57960 if (mainSeekable.length === 0) { 57961 return; 57962 } 57963 57964 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 57965 media = this.mediaTypes_.AUDIO.activePlaylistLoader.media(); 57966 expired = this.syncController_.getExpiredTime(media, this.duration()); 57967 57968 if (expired === null) { 57969 return; 57970 } 57971 57972 audioSeekable = Vhs.Playlist.seekable(media, expired, suggestedPresentationDelay); 57973 57974 if (audioSeekable.length === 0) { 57975 return; 57976 } 57977 } 57978 57979 var oldEnd; 57980 var oldStart; 57981 57982 if (this.seekable_ && this.seekable_.length) { 57983 oldEnd = this.seekable_.end(0); 57984 oldStart = this.seekable_.start(0); 57985 } 57986 57987 if (!audioSeekable) { 57988 // seekable has been calculated based on buffering video data so it 57989 // can be returned directly 57990 this.seekable_ = mainSeekable; 57991 } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) { 57992 // seekables are pretty far off, rely on main 57993 this.seekable_ = mainSeekable; 57994 } else { 57995 this.seekable_ = videojs$1.createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]); 57996 } // seekable is the same as last time 57997 57998 57999 if (this.seekable_ && this.seekable_.length) { 58000 if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) { 58001 return; 58002 } 58003 } 58004 58005 this.logger_("seekable updated [" + printableRange(this.seekable_) + "]"); 58006 this.tech_.trigger('seekablechanged'); 58007 } 58008 /** 58009 * Update the player duration 58010 */ 58011 ; 58012 58013 _proto.updateDuration = function updateDuration(isLive) { 58014 if (this.updateDuration_) { 58015 this.mediaSource.removeEventListener('sourceopen', this.updateDuration_); 58016 this.updateDuration_ = null; 58017 } 58018 58019 if (this.mediaSource.readyState !== 'open') { 58020 this.updateDuration_ = this.updateDuration.bind(this, isLive); 58021 this.mediaSource.addEventListener('sourceopen', this.updateDuration_); 58022 return; 58023 } 58024 58025 if (isLive) { 58026 var seekable = this.seekable(); 58027 58028 if (!seekable.length) { 58029 return; 58030 } // Even in the case of a live playlist, the native MediaSource's duration should not 58031 // be set to Infinity (even though this would be expected for a live playlist), since 58032 // setting the native MediaSource's duration to infinity ends up with consequences to 58033 // seekable behavior. See https://github.com/w3c/media-source/issues/5 for details. 58034 // 58035 // This is resolved in the spec by https://github.com/w3c/media-source/pull/92, 58036 // however, few browsers have support for setLiveSeekableRange() 58037 // https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/setLiveSeekableRange 58038 // 58039 // Until a time when the duration of the media source can be set to infinity, and a 58040 // seekable range specified across browsers, the duration should be greater than or 58041 // equal to the last possible seekable value. 58042 // MediaSource duration starts as NaN 58043 // It is possible (and probable) that this case will never be reached for many 58044 // sources, since the MediaSource reports duration as the highest value without 58045 // accounting for timestamp offset. For example, if the timestamp offset is -100 and 58046 // we buffered times 0 to 100 with real times of 100 to 200, even though current 58047 // time will be between 0 and 100, the native media source may report the duration 58048 // as 200. However, since we report duration separate from the media source (as 58049 // Infinity), and as long as the native media source duration value is greater than 58050 // our reported seekable range, seeks will work as expected. The large number as 58051 // duration for live is actually a strategy used by some players to work around the 58052 // issue of live seekable ranges cited above. 58053 58054 58055 if (isNaN(this.mediaSource.duration) || this.mediaSource.duration < seekable.end(seekable.length - 1)) { 58056 this.sourceUpdater_.setDuration(seekable.end(seekable.length - 1)); 58057 } 58058 58059 return; 58060 } 58061 58062 var buffered = this.tech_.buffered(); 58063 var duration = Vhs.Playlist.duration(this.masterPlaylistLoader_.media()); 58064 58065 if (buffered.length > 0) { 58066 duration = Math.max(duration, buffered.end(buffered.length - 1)); 58067 } 58068 58069 if (this.mediaSource.duration !== duration) { 58070 this.sourceUpdater_.setDuration(duration); 58071 } 58072 } 58073 /** 58074 * dispose of the MasterPlaylistController and everything 58075 * that it controls 58076 */ 58077 ; 58078 58079 _proto.dispose = function dispose() { 58080 var _this7 = this; 58081 58082 this.trigger('dispose'); 58083 this.decrypter_.terminate(); 58084 this.masterPlaylistLoader_.dispose(); 58085 this.mainSegmentLoader_.dispose(); 58086 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 58087 var groups = _this7.mediaTypes_[type].groups; 58088 58089 for (var id in groups) { 58090 groups[id].forEach(function (group) { 58091 if (group.playlistLoader) { 58092 group.playlistLoader.dispose(); 58093 } 58094 }); 58095 } 58096 }); 58097 this.audioSegmentLoader_.dispose(); 58098 this.subtitleSegmentLoader_.dispose(); 58099 this.sourceUpdater_.dispose(); 58100 this.timelineChangeController_.dispose(); 58101 58102 if (this.updateDuration_) { 58103 this.mediaSource.removeEventListener('sourceopen', this.updateDuration_); 58104 } 58105 58106 this.mediaSource.removeEventListener('durationchange', this.handleDurationChange_); // load the media source into the player 58107 58108 this.mediaSource.removeEventListener('sourceopen', this.handleSourceOpen_); 58109 this.mediaSource.removeEventListener('sourceended', this.handleSourceEnded_); 58110 this.off(); 58111 } 58112 /** 58113 * return the master playlist object if we have one 58114 * 58115 * @return {Object} the master playlist object that we parsed 58116 */ 58117 ; 58118 58119 _proto.master = function master() { 58120 return this.masterPlaylistLoader_.master; 58121 } 58122 /** 58123 * return the currently selected playlist 58124 * 58125 * @return {Object} the currently selected playlist object that we parsed 58126 */ 58127 ; 58128 58129 _proto.media = function media() { 58130 // playlist loader will not return media if it has not been fully loaded 58131 return this.masterPlaylistLoader_.media() || this.initialMedia_; 58132 }; 58133 58134 _proto.areMediaTypesKnown_ = function areMediaTypesKnown_() { 58135 var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader; // one or both loaders has not loaded sufficently to get codecs 58136 58137 if (!this.mainSegmentLoader_.currentMediaInfo_ || usingAudioLoader && !this.audioSegmentLoader_.currentMediaInfo_) { 58138 return false; 58139 } 58140 58141 return true; 58142 }; 58143 58144 _proto.getCodecsOrExclude_ = function getCodecsOrExclude_() { 58145 var _this8 = this; 58146 58147 var media = { 58148 main: this.mainSegmentLoader_.currentMediaInfo_ || {}, 58149 audio: this.audioSegmentLoader_.currentMediaInfo_ || {} 58150 }; // set "main" media equal to video 58151 58152 media.video = media.main; 58153 var playlistCodecs = codecsForPlaylist(this.master(), this.media()); 58154 var codecs = {}; 58155 var usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader; 58156 58157 if (media.main.hasVideo) { 58158 codecs.video = playlistCodecs.video || media.main.videoCodec || codecs_2; 58159 } 58160 58161 if (media.main.isMuxed) { 58162 codecs.video += "," + (playlistCodecs.audio || media.main.audioCodec || codecs_1); 58163 } 58164 58165 if (media.main.hasAudio && !media.main.isMuxed || media.audio.hasAudio || usingAudioLoader) { 58166 codecs.audio = playlistCodecs.audio || media.main.audioCodec || media.audio.audioCodec || codecs_1; // set audio isFmp4 so we use the correct "supports" function below 58167 58168 media.audio.isFmp4 = media.main.hasAudio && !media.main.isMuxed ? media.main.isFmp4 : media.audio.isFmp4; 58169 } // no codecs, no playback. 58170 58171 58172 if (!codecs.audio && !codecs.video) { 58173 this.blacklistCurrentPlaylist({ 58174 playlist: this.media(), 58175 message: 'Could not determine codecs for playlist.', 58176 blacklistDuration: Infinity 58177 }); 58178 return; 58179 } // fmp4 relies on browser support, while ts relies on muxer support 58180 58181 58182 var supportFunction = function supportFunction(isFmp4, codec) { 58183 return isFmp4 ? codecs_3(codec) : codecs_9(codec); 58184 }; 58185 58186 var unsupportedCodecs = {}; 58187 var unsupportedAudio; 58188 ['video', 'audio'].forEach(function (type) { 58189 if (codecs.hasOwnProperty(type) && !supportFunction(media[type].isFmp4, codecs[type])) { 58190 var supporter = media[type].isFmp4 ? 'browser' : 'muxer'; 58191 unsupportedCodecs[supporter] = unsupportedCodecs[supporter] || []; 58192 unsupportedCodecs[supporter].push(codecs[type]); 58193 58194 if (type === 'audio') { 58195 unsupportedAudio = supporter; 58196 } 58197 } 58198 }); 58199 58200 if (usingAudioLoader && unsupportedAudio && this.media().attributes.AUDIO) { 58201 var audioGroup = this.media().attributes.AUDIO; 58202 this.master().playlists.forEach(function (variant) { 58203 var variantAudioGroup = variant.attributes && variant.attributes.AUDIO; 58204 58205 if (variantAudioGroup === audioGroup && variant !== _this8.media()) { 58206 variant.excludeUntil = Infinity; 58207 } 58208 }); 58209 this.logger_("excluding audio group " + audioGroup + " as " + unsupportedAudio + " does not support codec(s): \"" + codecs.audio + "\""); 58210 } // if we have any unsupported codecs blacklist this playlist. 58211 58212 58213 if (Object.keys(unsupportedCodecs).length) { 58214 var message = Object.keys(unsupportedCodecs).reduce(function (acc, supporter) { 58215 if (acc) { 58216 acc += ', '; 58217 } 58218 58219 acc += supporter + " does not support codec(s): \"" + unsupportedCodecs[supporter].join(',') + "\""; 58220 return acc; 58221 }, '') + '.'; 58222 this.blacklistCurrentPlaylist({ 58223 playlist: this.media(), 58224 internal: true, 58225 message: message, 58226 blacklistDuration: Infinity 58227 }); 58228 return; 58229 } // check if codec switching is happening 58230 58231 58232 if (this.sourceUpdater_.ready() && !this.sourceUpdater_.canChangeType()) { 58233 var switchMessages = []; 58234 ['video', 'audio'].forEach(function (type) { 58235 var newCodec = (codecs_10(_this8.sourceUpdater_.codecs[type] || '')[type] || {}).type; 58236 var oldCodec = (codecs_10(codecs[type] || '')[type] || {}).type; 58237 58238 if (newCodec && oldCodec && newCodec.toLowerCase() !== oldCodec.toLowerCase()) { 58239 switchMessages.push("\"" + _this8.sourceUpdater_.codecs[type] + "\" -> \"" + codecs[type] + "\""); 58240 } 58241 }); 58242 58243 if (switchMessages.length) { 58244 this.blacklistCurrentPlaylist({ 58245 playlist: this.media(), 58246 message: "Codec switching not supported: " + switchMessages.join(', ') + ".", 58247 blacklistDuration: Infinity, 58248 internal: true 58249 }); 58250 return; 58251 } 58252 } // TODO: when using the muxer shouldn't we just return 58253 // the codecs that the muxer outputs? 58254 58255 58256 return codecs; 58257 } 58258 /** 58259 * Create source buffers and exlude any incompatible renditions. 58260 * 58261 * @private 58262 */ 58263 ; 58264 58265 _proto.tryToCreateSourceBuffers_ = function tryToCreateSourceBuffers_() { 58266 // media source is not ready yet or sourceBuffers are already 58267 // created. 58268 if (this.mediaSource.readyState !== 'open' || this.sourceUpdater_.ready()) { 58269 return; 58270 } 58271 58272 if (!this.areMediaTypesKnown_()) { 58273 return; 58274 } 58275 58276 var codecs = this.getCodecsOrExclude_(); // no codecs means that the playlist was excluded 58277 58278 if (!codecs) { 58279 return; 58280 } 58281 58282 this.sourceUpdater_.createSourceBuffers(codecs); 58283 var codecString = [codecs.video, codecs.audio].filter(Boolean).join(','); 58284 this.excludeIncompatibleVariants_(codecString); 58285 } 58286 /** 58287 * Excludes playlists with codecs that are unsupported by the muxer and browser. 58288 */ 58289 ; 58290 58291 _proto.excludeUnsupportedVariants_ = function excludeUnsupportedVariants_() { 58292 var _this9 = this; 58293
58294 this.master().playlists.forEach(function (variant) { 58295 var codecs = codecsForPlaylist(_this9.master, variant); 58296 58297 if (codecs.audio && !codecs_9(codecs.audio) && !codecs_3(codecs.audio)) { 58298 variant.excludeUntil = Infinity; 58299 } 58300 58301 if (codecs.video && !codecs_9(codecs.video) && !codecs_3(codecs.video)) { 58302 variant.excludeUntil = Infinity; 58303 } 58304 }); 58305 } 58306 /** 58307 * Blacklist playlists that are known to be codec or 58308 * stream-incompatible with the SourceBuffer configuration. For 58309 * instance, Media Source Extensions would cause the video element to 58310 * stall waiting for video data if you switched from a variant with 58311 * video and audio to an audio-only one. 58312 * 58313 * @param {Object} media a media playlist compatible with the current 58314 * set of SourceBuffers. Variants in the current master playlist that 58315 * do not appear to have compatible codec or stream configurations 58316 * will be excluded from the default playlist selection algorithm 58317 * indefinitely. 58318 * @private 58319 */ 58320 ; 58321 58322 _proto.excludeIncompatibleVariants_ = function excludeIncompatibleVariants_(codecString) { 58323 var _this10 = this; 58324 58325 var codecs = codecs_10(codecString); 58326 var codecCount = Object.keys(codecs).length;
58327 this.master().playlists.forEach(function (variant) { 58328 // skip variants that are already blacklisted forever 58329 if (variant.excludeUntil === Infinity) { 58330 return; 58331 } 58332 /* TODO: Decide whether two codecs should be assumed here. 58333 * Right now, for playlists that don't specify codecs, VHS assumes 58334 * that there are two (one for audio and one for video). 58335 * Although this is often the case, this may lead to broken behavior 58336 * if the playlist only has one codec. It may be better in the future 58337 * to decide at time of segment download how many tracks there are and 58338 * determine the proper codecs. This will come at a cost of potentially 58339 * more bandwidth, but will be a more robust approach than the assumption here. 58340 */ 58341 58342 58343 var variantCodecs = {}; 58344 var variantCodecCount = 2; 58345 var blacklistReasons = []; // get codecs from the playlist for this variant 58346 58347 var variantCodecStrings = codecsForPlaylist(_this10.masterPlaylistLoader_.master, variant); 58348 58349 if (variantCodecStrings.audio || variantCodecStrings.video) { 58350 var variantCodecString = [variantCodecStrings.video, variantCodecStrings.audio].filter(Boolean).join(','); 58351 variantCodecs = codecs_10(variantCodecString); 58352 variantCodecCount = Object.keys(variantCodecs).length; 58353 } // TODO: we can support this by removing the 58354 // old media source and creating a new one, but it will take some work. 58355 // The number of streams cannot change 58356 58357 58358 if (variantCodecCount !== codecCount) { 58359 blacklistReasons.push("codec count \"" + variantCodecCount + "\" !== \"" + codecCount + "\""); 58360 variant.excludeUntil = Infinity; 58361 } // only exclude playlists by codec change, if codecs cannot switch 58362 // during playback. 58363 58364 58365 if (!_this10.sourceUpdater_.canChangeType()) { 58366 // the video codec cannot change 58367 if (variantCodecs.video && codecs.video && variantCodecs.video.type.toLowerCase() !== codecs.video.type.toLowerCase()) { 58368 blacklistReasons.push("video codec \"" + variantCodecs.video.type + "\" !== \"" + codecs.video.type + "\""); 58369 variant.excludeUntil = Infinity; 58370 } // the audio codec cannot change 58371 58372 58373 if (variantCodecs.audio && codecs.audio && variantCodecs.audio.type.toLowerCase() !== codecs.audio.type.toLowerCase()) { 58374 variant.excludeUntil = Infinity; 58375 blacklistReasons.push("audio codec \"" + variantCodecs.audio.type + "\" !== \"" + codecs.audio.type + "\""); 58376 } 58377 } 58378 58379 if (blacklistReasons.length) { 58380 _this10.logger_("blacklisting " + variant.id + ": " + blacklistReasons.join(' && ')); 58381 } 58382 }); 58383 }; 58384 58385 _proto.updateAdCues_ = function updateAdCues_(media) { 58386 var offset = 0; 58387 var seekable = this.seekable(); 58388 58389 if (seekable.length) { 58390 offset = seekable.start(0); 58391 } 58392 58393 updateAdCues(media, this.cueTagsTrack_, offset); 58394 } 58395 /** 58396 * Calculates the desired forward buffer length based on current time 58397 * 58398 * @return {number} Desired forward buffer length in seconds 58399 */ 58400 ; 58401 58402 _proto.goalBufferLength = function goalBufferLength() { 58403 var currentTime = this.tech_.currentTime(); 58404 var initial = Config.GOAL_BUFFER_LENGTH; 58405 var rate = Config.GOAL_BUFFER_LENGTH_RATE; 58406 var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH); 58407 return Math.min(initial + currentTime * rate, max); 58408 } 58409 /** 58410 * Calculates the desired buffer low water line based on current time 58411 * 58412 * @return {number} Desired buffer low water line in seconds 58413 */ 58414 ; 58415 58416 _proto.bufferLowWaterLine = function bufferLowWaterLine() { 58417 var currentTime = this.tech_.currentTime(); 58418 var initial = Config.BUFFER_LOW_WATER_LINE; 58419 var rate = Config.BUFFER_LOW_WATER_LINE_RATE; 58420 var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE); 58421 return Math.min(initial + currentTime * rate, max); 58422 }; 58423 58424 return MasterPlaylistController; 58425 }(videojs$1.EventTarget); 58426 /** 58427 * Returns a function that acts as the Enable/disable playlist function. 58428 * 58429 * @param {PlaylistLoader} loader - The master playlist loader 58430 * @param {string} playlistID - id of the playlist 58431 * @param {Function} changePlaylistFn - A function to be called after a 58432 * playlist's enabled-state has been changed. Will NOT be called if a 58433 * playlist's enabled-state is unchanged 58434 * @param {boolean=} enable - Value to set the playlist enabled-state to 58435 * or if undefined returns the current enabled-state for the playlist 58436 * @return {Function} Function for setting/getting enabled 58437 */ 58438 58439 58440 var enableFunction = function enableFunction(loader, playlistID, changePlaylistFn) { 58441 return function (enable) { 58442 var playlist = loader.master.playlists[playlistID]; 58443 var incompatible = isIncompatible(playlist); 58444 var currentlyEnabled = isEnabled(playlist); 58445 58446 if (typeof enable === 'undefined') { 58447 return currentlyEnabled; 58448 } 58449 58450 if (enable) { 58451 delete playlist.disabled; 58452 } else { 58453 playlist.disabled = true; 58454 } 58455 58456 if (enable !== currentlyEnabled && !incompatible) { 58457 // Ensure the outside world knows about our changes 58458 changePlaylistFn(); 58459 58460 if (enable) { 58461 loader.trigger('renditionenabled'); 58462 } else { 58463 loader.trigger('renditiondisabled'); 58464 } 58465 } 58466 58467 return enable; 58468 }; 58469 }; 58470 /** 58471 * The representation object encapsulates the publicly visible information 58472 * in a media playlist along with a setter/getter-type function (enabled) 58473 * for changing the enabled-state of a particular playlist entry 58474 * 58475 * @class Representation 58476 */ 58477 58478 58479 var Representation = function Representation(vhsHandler, playlist, id) { 58480 var mpc = vhsHandler.masterPlaylistController_, 58481 smoothQualityChange = vhsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function 58482 58483 var changeType = smoothQualityChange ? 'smooth' : 'fast'; 58484 var qualityChangeFunction = mpc[changeType + "QualityChange_"].bind(mpc); // some playlist attributes are optional 58485 58486 if (playlist.attributes.RESOLUTION) { 58487 var resolution = playlist.attributes.RESOLUTION; 58488 this.width = resolution.width; 58489 this.height = resolution.height; 58490 } 58491 58492 this.bandwidth = playlist.attributes.BANDWIDTH; 58493 this.codecs = codecsForPlaylist(mpc.master(), playlist); 58494 this.playlist = playlist; // The id is simply the ordinality of the media playlist 58495 // within the master playlist 58496 58497 this.id = id; // Partially-apply the enableFunction to create a playlist- 58498 // specific variant 58499 58500 this.enabled = enableFunction(vhsHandler.playlists, playlist.id, qualityChangeFunction); 58501 }; 58502 /** 58503 * A mixin function that adds the `representations` api to an instance 58504 * of the VhsHandler class 58505 * 58506 * @param {VhsHandler} vhsHandler - An instance of VhsHandler to add the 58507 * representation API into 58508 */ 58509 58510 58511 var renditionSelectionMixin = function renditionSelectionMixin(vhsHandler) { 58512 var playlists = vhsHandler.playlists; // Add a single API-specific function to the VhsHandler instance 58513 58514 vhsHandler.representations = function () { 58515 if (!playlists || !playlists.master || !playlists.master.playlists) { 58516 return []; 58517 } 58518 58519 return playlists.master.playlists.filter(function (media) { 58520 return !isIncompatible(media); 58521 }).map(function (e, i) { 58522 return new Representation(vhsHandler, e, e.id); 58523 }); 58524 }; 58525 }; 58526 /** 58527 * @file playback-watcher.js 58528 * 58529 * Playback starts, and now my watch begins. It shall not end until my death. I shall 58530 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns 58531 * and win no glory. I shall live and die at my post. I am the corrector of the underflow. 58532 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge 58533 * my life and honor to the Playback Watch, for this Player and all the Players to come. 58534 */ 58535 58536 58537 var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 58538 /** 58539 * Returns whether or not the current time should be considered close to buffered content, 58540 * taking into consideration whether there's enough buffered content for proper playback. 58541 * 58542 * @param {Object} options 58543 * Options object 58544 * @param {TimeRange} options.buffered 58545 * Current buffer 58546 * @param {number} options.targetDuration 58547 * The active playlist's target duration 58548 * @param {number} options.currentTime 58549 * The current time of the player 58550 * @return {boolean} 58551 * Whether the current time should be considered close to the buffer 58552 */ 58553 58554 var closeToBufferedContent = function closeToBufferedContent(_ref) { 58555 var buffered = _ref.buffered, 58556 targetDuration = _ref.targetDuration, 58557 currentTime = _ref.currentTime; 58558 58559 if (!buffered.length) { 58560 return false;
vendor: 64,298 bytes, lines 58561-60412
58561 } // At least two to three segments worth of content should be buffered before there's a 58562 // full enough buffer to consider taking any actions. 58563 58564 58565 if (buffered.end(0) - buffered.start(0) < targetDuration * 2) { 58566 return false; 58567 } // It's possible that, on seek, a remove hasn't completed and the buffered range is 58568 // somewhere past the current time. In that event, don't consider the buffered content 58569 // close. 58570 58571 58572 if (currentTime > buffered.start(0)) { 58573 return false; 58574 } // Since target duration generally represents the max (or close to max) duration of a 58575 // segment, if the buffer is within a segment of the current time, the gap probably 58576 // won't be closed, and current time should be considered close to buffered content. 58577 58578 58579 return buffered.start(0) - currentTime < targetDuration; 58580 }; 58581 /** 58582 * @class PlaybackWatcher 58583 */ 58584 58585 58586 var PlaybackWatcher = /*#__PURE__*/function () { 58587 /** 58588 * Represents an PlaybackWatcher object. 58589 * 58590 * @class 58591 * @param {Object} options an object that includes the tech and settings 58592 */ 58593 function PlaybackWatcher(options) { 58594 var _this = this; 58595 58596 this.masterPlaylistController_ = options.masterPlaylistController; 58597 this.tech_ = options.tech; 58598 this.seekable = options.seekable; 58599 this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow; 58600 this.media = options.media; 58601 this.consecutiveUpdates = 0; 58602 this.lastRecordedTime = null; 58603 this.timer_ = null; 58604 this.checkCurrentTimeTimeout_ = null; 58605 this.logger_ = logger('PlaybackWatcher'); 58606 this.logger_('initialize'); 58607 58608 var canPlayHandler = function canPlayHandler() { 58609 return _this.monitorCurrentTime_(); 58610 }; 58611 58612 var waitingHandler = function waitingHandler() { 58613 return _this.techWaiting_(); 58614 }; 58615 58616 var cancelTimerHandler = function cancelTimerHandler() { 58617 return _this.cancelTimer_(); 58618 }; 58619 58620 var fixesBadSeeksHandler = function fixesBadSeeksHandler() { 58621 return _this.fixesBadSeeks_(); 58622 }; 58623 58624 var mpc = this.masterPlaylistController_; 58625 var loaderTypes = ['main', 'subtitle', 'audio']; 58626 var loaderChecks = {}; 58627 loaderTypes.forEach(function (type) { 58628 loaderChecks[type] = { 58629 reset: function reset() { 58630 return _this.resetSegmentDownloads_(type); 58631 }, 58632 updateend: function updateend() { 58633 return _this.checkSegmentDownloads_(type); 58634 } 58635 }; 58636 mpc[type + "SegmentLoader_"].on('appendsdone', loaderChecks[type].updateend); // If a rendition switch happens during a playback stall where the buffer 58637 // isn't changing we want to reset. We cannot assume that the new rendition 58638 // will also be stalled, until after new appends. 58639 58640 mpc[type + "SegmentLoader_"].on('playlistupdate', loaderChecks[type].reset); // Playback stalls should not be detected right after seeking. 58641 // This prevents one segment playlists (single vtt or single segment content) 58642 // from being detected as stalling. As the buffer will not change in those cases, since 58643 // the buffer is the entire video duration. 58644 58645 _this.tech_.on(['seeked', 'seeking'], loaderChecks[type].reset); 58646 }); 58647 this.tech_.on('seekablechanged', fixesBadSeeksHandler); 58648 this.tech_.on('waiting', waitingHandler); 58649 this.tech_.on(timerCancelEvents, cancelTimerHandler); 58650 this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events 58651 58652 this.dispose = function () { 58653 _this.logger_('dispose'); 58654 58655 _this.tech_.off('seekablechanged', fixesBadSeeksHandler); 58656 58657 _this.tech_.off('waiting', waitingHandler); 58658 58659 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 58660 58661 _this.tech_.off('canplay', canPlayHandler); 58662 58663 loaderTypes.forEach(function (type) { 58664 mpc[type + "SegmentLoader_"].off('appendsdone', loaderChecks[type].updateend); 58665 mpc[type + "SegmentLoader_"].off('playlistupdate', loaderChecks[type].reset); 58666 58667 _this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset); 58668 }); 58669 58670 if (_this.checkCurrentTimeTimeout_) { 58671 window$1.clearTimeout(_this.checkCurrentTimeTimeout_); 58672 } 58673 58674 _this.cancelTimer_(); 58675 }; 58676 } 58677 /** 58678 * Periodically check current time to see if playback stopped 58679 * 58680 * @private 58681 */ 58682 58683 58684 var _proto = PlaybackWatcher.prototype; 58685 58686 _proto.monitorCurrentTime_ = function monitorCurrentTime_() { 58687 this.checkCurrentTime_(); 58688 58689 if (this.checkCurrentTimeTimeout_) { 58690 window$1.clearTimeout(this.checkCurrentTimeTimeout_); 58691 } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 58692 58693 58694 this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250); 58695 } 58696 /** 58697 * Reset stalled download stats for a specific type of loader 58698 * 58699 * @param {string} type 58700 * The segment loader type to check. 58701 * 58702 * @listens SegmentLoader#playlistupdate 58703 * @listens Tech#seeking 58704 * @listens Tech#seeked 58705 */ 58706 ; 58707 58708 _proto.resetSegmentDownloads_ = function resetSegmentDownloads_(type) { 58709 var loader = this.masterPlaylistController_[type + "SegmentLoader_"]; 58710 58711 if (this[type + "StalledDownloads_"] > 0) { 58712 this.logger_("resetting possible stalled download count for " + type + " loader"); 58713 } 58714 58715 this[type + "StalledDownloads_"] = 0; 58716 this[type + "Buffered_"] = loader.buffered_(); 58717 } 58718 /** 58719 * Checks on every segment `appendsdone` to see 58720 * if segment appends are making progress. If they are not 58721 * and we are still downloading bytes. We blacklist the playlist. 58722 * 58723 * @param {string} type 58724 * The segment loader type to check. 58725 * 58726 * @listens SegmentLoader#appendsdone 58727 */ 58728 ; 58729 58730 _proto.checkSegmentDownloads_ = function checkSegmentDownloads_(type) { 58731 var mpc = this.masterPlaylistController_; 58732 var loader = mpc[type + "SegmentLoader_"]; 58733 var buffered = loader.buffered_(); 58734 var isBufferedDifferent = isRangeDifferent(this[type + "Buffered_"], buffered); 58735 this[type + "Buffered_"] = buffered; // if another watcher is going to fix the issue or 58736 // the buffered value for this loader changed 58737 // appends are working 58738 58739 if (isBufferedDifferent) { 58740 this.resetSegmentDownloads_(type); 58741 return; 58742 } 58743 58744 this[type + "StalledDownloads_"]++; 58745 this.logger_("found #" + this[type + "StalledDownloads_"] + " " + type + " appends that did not increase buffer (possible stalled download)", { 58746 playlistId: loader.playlist_ && loader.playlist_.id, 58747 buffered: timeRangesToArray(buffered) 58748 }); // after 10 possibly stalled appends with no reset, exclude 58749 58750 if (this[type + "StalledDownloads_"] < 10) { 58751 return; 58752 } 58753 58754 this.logger_(type + " loader stalled download exclusion"); 58755 this.resetSegmentDownloads_(type); 58756 this.tech_.trigger({ 58757 type: 'usage', 58758 name: "vhs-" + type + "-download-exclusion" 58759 }); 58760 58761 if (type === 'subtitle') { 58762 return; 58763 } // TODO: should we exclude audio tracks rather than main tracks 58764 // when type is audio? 58765 58766 58767 mpc.blacklistCurrentPlaylist({ 58768 message: "Excessive " + type + " segment downloading detected." 58769 }, Infinity); 58770 } 58771 /** 58772 * The purpose of this function is to emulate the "waiting" event on 58773 * browsers that do not emit it when they are waiting for more 58774 * data to continue playback 58775 * 58776 * @private 58777 */ 58778 ; 58779 58780 _proto.checkCurrentTime_ = function checkCurrentTime_() { 58781 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 58782 this.consecutiveUpdates = 0; 58783 this.lastRecordedTime = this.tech_.currentTime(); 58784 return; 58785 } 58786 58787 if (this.tech_.paused() || this.tech_.seeking()) { 58788 return; 58789 } 58790 58791 var currentTime = this.tech_.currentTime(); 58792 var buffered = this.tech_.buffered(); 58793 58794 if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) { 58795 // If current time is at the end of the final buffered region, then any playback 58796 // stall is most likely caused by buffering in a low bandwidth environment. The tech 58797 // should fire a `waiting` event in this scenario, but due to browser and tech 58798 // inconsistencies. Calling `techWaiting_` here allows us to simulate 58799 // responding to a native `waiting` event when the tech fails to emit one. 58800 return this.techWaiting_(); 58801 } 58802 58803 if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) { 58804 this.consecutiveUpdates++; 58805 this.waiting_(); 58806 } else if (currentTime === this.lastRecordedTime) { 58807 this.consecutiveUpdates++; 58808 } else { 58809 this.consecutiveUpdates = 0; 58810 this.lastRecordedTime = currentTime; 58811 } 58812 } 58813 /** 58814 * Cancels any pending timers and resets the 'timeupdate' mechanism 58815 * designed to detect that we are stalled 58816 * 58817 * @private 58818 */ 58819 ; 58820 58821 _proto.cancelTimer_ = function cancelTimer_() { 58822 this.consecutiveUpdates = 0; 58823 58824 if (this.timer_) { 58825 this.logger_('cancelTimer_'); 58826 clearTimeout(this.timer_); 58827 } 58828 58829 this.timer_ = null; 58830 } 58831 /** 58832 * Fixes situations where there's a bad seek 58833 * 58834 * @return {boolean} whether an action was taken to fix the seek 58835 * @private 58836 */ 58837 ; 58838 58839 _proto.fixesBadSeeks_ = function fixesBadSeeks_() { 58840 var seeking = this.tech_.seeking(); 58841 58842 if (!seeking) { 58843 return false; 58844 } 58845 58846 var seekable = this.seekable(); 58847 var currentTime = this.tech_.currentTime(); 58848 var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow); 58849 var seekTo; 58850 58851 if (isAfterSeekableRange) { 58852 var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting) 58853 58854 seekTo = seekableEnd; 58855 } 58856 58857 if (this.beforeSeekableWindow_(seekable, currentTime)) { 58858 var seekableStart = seekable.start(0); // sync to the beginning of the live window 58859 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 58860 58861 seekTo = seekableStart + ( // if the playlist is too short and the seekable range is an exact time (can 58862 // happen in live with a 3 segment playlist), then don't use a time delta 58863 seekableStart === seekable.end(0) ? 0 : SAFE_TIME_DELTA); 58864 } 58865 58866 if (typeof seekTo !== 'undefined') { 58867 this.logger_("Trying to seek outside of seekable at time " + currentTime + " with " + ("seekable range " + printableRange(seekable) + ". Seeking to ") + (seekTo + ".")); 58868 this.tech_.setCurrentTime(seekTo); 58869 return true; 58870 } 58871 58872 var buffered = this.tech_.buffered(); 58873 58874 if (closeToBufferedContent({ 58875 buffered: buffered, 58876 targetDuration: this.media().targetDuration, 58877 currentTime: currentTime 58878 })) { 58879 seekTo = buffered.start(0) + SAFE_TIME_DELTA; 58880 this.logger_("Buffered region starts (" + buffered.start(0) + ") " + (" just beyond seek point (" + currentTime + "). Seeking to " + seekTo + ".")); 58881 this.tech_.setCurrentTime(seekTo); 58882 return true; 58883 } 58884 58885 return false; 58886 } 58887 /** 58888 * Handler for situations when we determine the player is waiting. 58889 * 58890 * @private 58891 */ 58892 ; 58893 58894 _proto.waiting_ = function waiting_() { 58895 if (this.techWaiting_()) { 58896 return; 58897 } // All tech waiting checks failed. Use last resort correction 58898 58899 58900 var currentTime = this.tech_.currentTime(); 58901 var buffered = this.tech_.buffered(); 58902 var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered 58903 // region with no indication that anything is amiss (seen in Firefox). Seeking to 58904 // currentTime is usually enough to kickstart the player. This checks that the player 58905 // is currently within a buffered region before attempting a corrective seek. 58906 // Chrome does not appear to continue `timeupdate` events after a `waiting` event 58907 // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also 58908 // make sure there is ~3 seconds of forward buffer before taking any corrective action 58909 // to avoid triggering an `unknownwaiting` event when the network is slow. 58910 58911 if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { 58912 this.cancelTimer_(); 58913 this.tech_.setCurrentTime(currentTime); 58914 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 58915 58916 this.tech_.trigger({ 58917 type: 'usage', 58918 name: 'vhs-unknown-waiting' 58919 }); 58920 this.tech_.trigger({ 58921 type: 'usage', 58922 name: 'hls-unknown-waiting' 58923 }); 58924 return; 58925 } 58926 } 58927 /** 58928 * Handler for situations when the tech fires a `waiting` event 58929 * 58930 * @return {boolean} 58931 * True if an action (or none) was needed to correct the waiting. False if no 58932 * checks passed 58933 * @private 58934 */ 58935 ; 58936 58937 _proto.techWaiting_ = function techWaiting_() { 58938 var seekable = this.seekable(); 58939 var currentTime = this.tech_.currentTime(); 58940 58941 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 58942 // Tech is seeking or bad seek fixed, no action needed 58943 return true; 58944 } 58945 58946 if (this.tech_.seeking() || this.timer_ !== null) { 58947 // Tech is seeking or already waiting on another action, no action needed 58948 return true; 58949 } 58950 58951 if (this.beforeSeekableWindow_(seekable, currentTime)) { 58952 var livePoint = seekable.end(seekable.length - 1); 58953 this.logger_("Fell out of live window at time " + currentTime + ". Seeking to " + ("live point (seekable end) " + livePoint)); 58954 this.cancelTimer_(); 58955 this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS 58956 58957 this.tech_.trigger({ 58958 type: 'usage', 58959 name: 'vhs-live-resync' 58960 }); 58961 this.tech_.trigger({ 58962 type: 'usage', 58963 name: 'hls-live-resync' 58964 }); 58965 return true; 58966 } 58967 58968 var sourceUpdater = this.tech_.vhs.masterPlaylistController_.sourceUpdater_; 58969 var buffered = this.tech_.buffered(); 58970 var videoUnderflow = this.videoUnderflow_({ 58971 audioBuffered: sourceUpdater.audioBuffered(), 58972 videoBuffered: sourceUpdater.videoBuffered(), 58973 currentTime: currentTime 58974 }); 58975 58976 if (videoUnderflow) { 58977 // Even though the video underflowed and was stuck in a gap, the audio overplayed 58978 // the gap, leading currentTime into a buffered range. Seeking to currentTime 58979 // allows the video to catch up to the audio position without losing any audio 58980 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 58981 this.cancelTimer_(); 58982 this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS 58983 58984 this.tech_.trigger({ 58985 type: 'usage', 58986 name: 'vhs-video-underflow' 58987 }); 58988 this.tech_.trigger({ 58989 type: 'usage', 58990 name: 'hls-video-underflow' 58991 }); 58992 return true; 58993 } 58994 58995 var nextRange = findNextRange(buffered, currentTime); // check for gap 58996 58997 if (nextRange.length > 0) { 58998 var difference = nextRange.start(0) - currentTime; 58999 this.logger_("Stopped at " + currentTime + ", setting timer for " + difference + ", seeking " + ("to " + nextRange.start(0))); 59000 this.cancelTimer_(); 59001 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 59002 return true; 59003 } // All checks failed. Returning false to indicate failure to correct waiting 59004 59005 59006 return false; 59007 }; 59008 59009 _proto.afterSeekableWindow_ = function afterSeekableWindow_(seekable, currentTime, playlist, allowSeeksWithinUnsafeLiveWindow) { 59010 if (allowSeeksWithinUnsafeLiveWindow === void 0) { 59011 allowSeeksWithinUnsafeLiveWindow = false; 59012 } 59013 59014 if (!seekable.length) { 59015 // we can't make a solid case if there's no seekable, default to false 59016 return false; 59017 } 59018 59019 var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA; 59020 var isLive = !playlist.endList; 59021 59022 if (isLive && allowSeeksWithinUnsafeLiveWindow) { 59023 allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3; 59024 } 59025 59026 if (currentTime > allowedEnd) { 59027 return true; 59028 } 59029 59030 return false; 59031 }; 59032 59033 _proto.beforeSeekableWindow_ = function beforeSeekableWindow_(seekable, currentTime) { 59034 if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream 59035 seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) { 59036 return true; 59037 } 59038 59039 return false; 59040 }; 59041 59042 _proto.videoUnderflow_ = function videoUnderflow_(_ref2) { 59043 var videoBuffered = _ref2.videoBuffered, 59044 audioBuffered = _ref2.audioBuffered, 59045 currentTime = _ref2.currentTime; // audio only content will not have video underflow :) 59046 59047 if (!videoBuffered) { 59048 return; 59049 } 59050 59051 var gap; // find a gap in demuxed content. 59052 59053 if (videoBuffered.length && audioBuffered.length) { 59054 // in Chrome audio will continue to play for ~3s when we run out of video 59055 // so we have to check that the video buffer did have some buffer in the 59056 // past. 59057 var lastVideoRange = findRange(videoBuffered, currentTime - 3); 59058 var videoRange = findRange(videoBuffered, currentTime); 59059 var audioRange = findRange(audioBuffered, currentTime); 59060 59061 if (audioRange.length && !videoRange.length && lastVideoRange.length) { 59062 gap = { 59063 start: lastVideoRange.end(0), 59064 end: audioRange.end(0) 59065 }; 59066 } // find a gap in muxed content. 59067 59068 } else { 59069 var nextRange = findNextRange(videoBuffered, currentTime); // Even if there is no available next range, there is still a possibility we are 59070 // stuck in a gap due to video underflow. 59071 59072 if (!nextRange.length) { 59073 gap = this.gapFromVideoUnderflow_(videoBuffered, currentTime); 59074 } 59075 } 59076 59077 if (gap) { 59078 this.logger_("Encountered a gap in video from " + gap.start + " to " + gap.end + ". " + ("Seeking to current time " + currentTime)); 59079 return true; 59080 } 59081 59082 return false; 59083 } 59084 /** 59085 * Timer callback. If playback still has not proceeded, then we seek 59086 * to the start of the next buffered region. 59087 * 59088 * @private 59089 */ 59090 ; 59091 59092 _proto.skipTheGap_ = function skipTheGap_(scheduledCurrentTime) { 59093 var buffered = this.tech_.buffered(); 59094 var currentTime = this.tech_.currentTime(); 59095 var nextRange = findNextRange(buffered, currentTime); 59096 this.cancelTimer_(); 59097 59098 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 59099 return; 59100 } 59101 59102 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played 59103 59104 this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR); 59105 this.tech_.trigger({ 59106 type: 'usage', 59107 name: 'vhs-gap-skip' 59108 }); 59109 this.tech_.trigger({ 59110 type: 'usage', 59111 name: 'hls-gap-skip' 59112 }); 59113 }; 59114 59115 _proto.gapFromVideoUnderflow_ = function gapFromVideoUnderflow_(buffered, currentTime) { 59116 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 59117 // playing for ~3 seconds after the video gap starts. This is done to account for 59118 // video buffer underflow/underrun (note that this is not done when there is audio 59119 // buffer underflow/underrun -- in that case the video will stop as soon as it 59120 // encounters the gap, as audio stalls are more noticeable/jarring to a user than 59121 // video stalls). The player's time will reflect the playthrough of audio, so the 59122 // time will appear as if we are in a buffered region, even if we are stuck in a 59123 // "gap." 59124 // 59125 // Example: 59126 // video buffer: 0 => 10.1, 10.2 => 20 59127 // audio buffer: 0 => 20 59128 // overall buffer: 0 => 10.1, 10.2 => 20 59129 // current time: 13 59130 // 59131 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 59132 // however, the audio continued playing until it reached ~3 seconds past the gap 59133 // (13 seconds), at which point it stops as well. Since current time is past the 59134 // gap, findNextRange will return no ranges. 59135 // 59136 // To check for this issue, we see if there is a gap that starts somewhere within 59137 // a 3 second range (3 seconds +/- 1 second) back from our current time. 59138 var gaps = findGaps(buffered); 59139 59140 for (var i = 0; i < gaps.length; i++) { 59141 var start = gaps.start(i); 59142 var end = gaps.end(i); // gap is starts no more than 4 seconds back 59143 59144 if (currentTime - start < 4 && currentTime - start > 2) { 59145 return { 59146 start: start, 59147 end: end 59148 }; 59149 } 59150 } 59151 59152 return null; 59153 }; 59154 59155 return PlaybackWatcher; 59156 }(); 59157 59158 var defaultOptions = { 59159 errorInterval: 30, 59160 getSource: function getSource(next) { 59161 var tech = this.tech({ 59162 IWillNotUseThisInPlugins: true 59163 }); 59164 var sourceObj = tech.currentSource_ || this.currentSource(); 59165 return next(sourceObj); 59166 } 59167 }; 59168 /** 59169 * Main entry point for the plugin 59170 * 59171 * @param {Player} player a reference to a videojs Player instance 59172 * @param {Object} [options] an object with plugin options 59173 * @private 59174 */ 59175 59176 var initPlugin = function initPlugin(player, options) { 59177 var lastCalled = 0; 59178 var seekTo = 0; 59179 var localOptions = videojs$1.mergeOptions(defaultOptions, options); 59180 player.ready(function () { 59181 player.trigger({ 59182 type: 'usage', 59183 name: 'vhs-error-reload-initialized' 59184 }); 59185 player.trigger({ 59186 type: 'usage', 59187 name: 'hls-error-reload-initialized' 59188 }); 59189 }); 59190 /** 59191 * Player modifications to perform that must wait until `loadedmetadata` 59192 * has been triggered 59193 * 59194 * @private 59195 */ 59196 59197 var loadedMetadataHandler = function loadedMetadataHandler() { 59198 if (seekTo) { 59199 player.currentTime(seekTo); 59200 } 59201 }; 59202 /** 59203 * Set the source on the player element, play, and seek if necessary 59204 * 59205 * @param {Object} sourceObj An object specifying the source url and mime-type to play 59206 * @private 59207 */ 59208 59209 59210 var setSource = function setSource(sourceObj) { 59211 if (sourceObj === null || sourceObj === undefined) { 59212 return; 59213 } 59214 59215 seekTo = player.duration() !== Infinity && player.currentTime() || 0; 59216 player.one('loadedmetadata', loadedMetadataHandler); 59217 player.src(sourceObj); 59218 player.trigger({ 59219 type: 'usage', 59220 name: 'vhs-error-reload' 59221 }); 59222 player.trigger({ 59223 type: 'usage', 59224 name: 'hls-error-reload' 59225 }); 59226 player.play(); 59227 }; 59228 /** 59229 * Attempt to get a source from either the built-in getSource function 59230 * or a custom function provided via the options 59231 * 59232 * @private 59233 */ 59234 59235 59236 var errorHandler = function errorHandler() { 59237 // Do not attempt to reload the source if a source-reload occurred before 59238 // 'errorInterval' time has elapsed since the last source-reload 59239 if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { 59240 player.trigger({ 59241 type: 'usage', 59242 name: 'vhs-error-reload-canceled' 59243 }); 59244 player.trigger({ 59245 type: 'usage', 59246 name: 'hls-error-reload-canceled' 59247 }); 59248 return; 59249 } 59250 59251 if (!localOptions.getSource || typeof localOptions.getSource !== 'function') { 59252 videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!'); 59253 return; 59254 } 59255 59256 lastCalled = Date.now(); 59257 return localOptions.getSource.call(player, setSource); 59258 }; 59259 /** 59260 * Unbind any event handlers that were bound by the plugin 59261 * 59262 * @private 59263 */ 59264 59265 59266 var cleanupEvents = function cleanupEvents() { 59267 player.off('loadedmetadata', loadedMetadataHandler); 59268 player.off('error', errorHandler); 59269 player.off('dispose', cleanupEvents); 59270 }; 59271 /** 59272 * Cleanup before re-initializing the plugin 59273 * 59274 * @param {Object} [newOptions] an object with plugin options 59275 * @private 59276 */ 59277 59278 59279 var reinitPlugin = function reinitPlugin(newOptions) { 59280 cleanupEvents(); 59281 initPlugin(player, newOptions); 59282 }; 59283 59284 player.on('error', errorHandler); 59285 player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before 59286 // initializing the plugin 59287 59288 player.reloadSourceOnError = reinitPlugin; 59289 }; 59290 /** 59291 * Reload the source when an error is detected as long as there 59292 * wasn't an error previously within the last 30 seconds 59293 * 59294 * @param {Object} [options] an object with plugin options 59295 */ 59296 59297 59298 var reloadSourceOnError = function reloadSourceOnError(options) { 59299 initPlugin(this, options); 59300 }; // keep these at the bottom they are replaced at build time 59301 // because webpack and rollup without plugins do not support json 59302 // and we do not want to break our users 59303 59304 59305 var vhsVersion = '2.2.4'; 59306 var muxVersion = '5.6.7'; 59307 var mpdVersion = '0.14.0'; 59308 var m3u8Version = '4.5.0'; 59309 var aesVersion = '3.1.0'; 59310 var Vhs$1 = { 59311 PlaylistLoader: PlaylistLoader, 59312 Playlist: Playlist, 59313 utils: utils$1, 59314 STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector, 59315 INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector, 59316 comparePlaylistBandwidth: comparePlaylistBandwidth, 59317 comparePlaylistResolution: comparePlaylistResolution, 59318 xhr: xhrFactory() 59319 }; // Define getter/setters for config properties 59320 59321 ['GOAL_BUFFER_LENGTH', 'MAX_GOAL_BUFFER_LENGTH', 'BACK_BUFFER_LENGTH', 'GOAL_BUFFER_LENGTH_RATE', 'BUFFER_LOW_WATER_LINE', 'MAX_BUFFER_LOW_WATER_LINE', 'BUFFER_LOW_WATER_LINE_RATE', 'BANDWIDTH_VARIANCE'].forEach(function (prop) { 59322 Object.defineProperty(Vhs$1, prop, { 59323 get: function get() { 59324 videojs$1.log.warn("using Vhs." + prop + " is UNSAFE be sure you know what you are doing"); 59325 return Config[prop]; 59326 }, 59327 set: function set(value) { 59328 videojs$1.log.warn("using Vhs." + prop + " is UNSAFE be sure you know what you are doing"); 59329 59330 if (typeof value !== 'number' || value < 0) { 59331 videojs$1.log.warn("value of Vhs." + prop + " must be greater than or equal to 0"); 59332 return; 59333 } 59334 59335 Config[prop] = value; 59336 } 59337 }); 59338 }); 59339 var LOCAL_STORAGE_KEY$1 = 'videojs-vhs'; 59340 /** 59341 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in vhs. 59342 * 59343 * @param {QualityLevelList} qualityLevels The QualityLevelList to update. 59344 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info. 59345 * @function handleVhsMediaChange 59346 */ 59347 59348 var handleVhsMediaChange = function handleVhsMediaChange(qualityLevels, playlistLoader) { 59349 var newPlaylist = playlistLoader.media(); 59350 var selectedIndex = -1; 59351 59352 for (var i = 0; i < qualityLevels.length; i++) { 59353 if (qualityLevels[i].id === newPlaylist.id) { 59354 selectedIndex = i; 59355 break; 59356 } 59357 } 59358 59359 qualityLevels.selectedIndex_ = selectedIndex; 59360 qualityLevels.trigger({ 59361 selectedIndex: selectedIndex, 59362 type: 'change' 59363 }); 59364 }; 59365 /** 59366 * Adds quality levels to list once playlist metadata is available 59367 * 59368 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to. 59369 * @param {Object} vhs Vhs object to listen to for media events. 59370 * @function handleVhsLoadedMetadata 59371 */ 59372 59373 59374 var handleVhsLoadedMetadata = function handleVhsLoadedMetadata(qualityLevels, vhs) { 59375 vhs.representations().forEach(function (rep) { 59376 qualityLevels.addQualityLevel(rep); 59377 }); 59378 handleVhsMediaChange(qualityLevels, vhs.playlists); 59379 }; // HLS is a source handler, not a tech. Make sure attempts to use it 59380 // as one do not cause exceptions. 59381 59382 59383 Vhs$1.canPlaySource = function () { 59384 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 59385 }; 59386 59387 var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) { 59388 if (!keySystemOptions) { 59389 return keySystemOptions; 59390 } 59391 59392 var codecs = { 59393 video: videoPlaylist && videoPlaylist.attributes && videoPlaylist.attributes.CODECS, 59394 audio: audioPlaylist && audioPlaylist.attributes && audioPlaylist.attributes.CODECS 59395 }; 59396 59397 if (!codecs.audio && codecs.video && codecs.video.split(',').length > 1) { 59398 codecs.video.split(',').forEach(function (codec) { 59399 codec = codec.trim(); 59400 59401 if (codecs_6(codec)) { 59402 codecs.audio = codec; 59403 } else if (codecs_7(codec)) { 59404 codecs.video = codec; 59405 } 59406 }); 59407 } 59408 59409 var videoContentType = codecs.video ? "video/mp4;codecs=\"" + codecs.video + "\"" : null; 59410 var audioContentType = codecs.audio ? "audio/mp4;codecs=\"" + codecs.audio + "\"" : null; // upsert the content types based on the selected playlist 59411 59412 var keySystemContentTypes = {}; 59413 59414 for (var keySystem in keySystemOptions) { 59415 keySystemContentTypes[keySystem] = { 59416 audioContentType: audioContentType, 59417 videoContentType: videoContentType 59418 }; 59419 59420 if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) { 59421 keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh; 59422 } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url' 59423 // so we need to prevent overwriting the URL entirely 59424 59425 59426 if (typeof keySystemOptions[keySystem] === 'string') { 59427 keySystemContentTypes[keySystem].url = keySystemOptions[keySystem]; 59428 } 59429 } 59430 59431 return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes); 59432 }; 59433 /** 59434 * @typedef {Object} KeySystems 59435 * 59436 * keySystems configuration for https://github.com/videojs/videojs-contrib-eme 59437 * Note: not all options are listed here. 59438 * 59439 * @property {Uint8Array} [pssh] 59440 * Protection System Specific Header 59441 */ 59442 59443 /** 59444 * Goes through all the playlists and collects an array of KeySystems options objects 59445 * containing each playlist's keySystems and their pssh values, if available. 59446 * 59447 * @param {Object[]} playlists 59448 * The playlists to look through 59449 * @param {string[]} keySystems 59450 * The keySystems to collect pssh values for 59451 * 59452 * @return {KeySystems[]} 59453 * An array of KeySystems objects containing available key systems and their 59454 * pssh values 59455 */ 59456 59457 59458 var getAllPsshKeySystemsOptions = function getAllPsshKeySystemsOptions(playlists, keySystems) { 59459 return playlists.reduce(function (keySystemsArr, playlist) { 59460 if (!playlist.contentProtection) { 59461 return keySystemsArr; 59462 } 59463 59464 var keySystemsOptions = keySystems.reduce(function (keySystemsObj, keySystem) { 59465 var keySystemOptions = playlist.contentProtection[keySystem]; 59466 59467 if (keySystemOptions && keySystemOptions.pssh) { 59468 keySystemsObj[keySystem] = { 59469 pssh: keySystemOptions.pssh 59470 }; 59471 } 59472 59473 return keySystemsObj; 59474 }, {}); 59475 59476 if (Object.keys(keySystemsOptions).length) { 59477 keySystemsArr.push(keySystemsOptions); 59478 } 59479 59480 return keySystemsArr; 59481 }, []); 59482 }; 59483 /** 59484 * If the [eme](https://github.com/videojs/videojs-contrib-eme) plugin is available, and 59485 * there are keySystems on the source, sets up source options to prepare the source for 59486 * eme and tries to initialize it early via eme's initializeMediaKeys API (if available). 59487 * 59488 * @param {Object} player 59489 * The player instance 59490 * @param {Object[]} sourceKeySystems 59491 * The key systems options from the player source 59492 * @param {Object} media 59493 * The active media playlist 59494 * @param {Object} [audioMedia] 59495 * The active audio media playlist (optional) 59496 * @param {Object[]} mainPlaylists 59497 * The playlists found on the master playlist object 59498 */ 59499 59500 59501 var setupEmeOptions = function setupEmeOptions(_ref) { 59502 var player = _ref.player, 59503 sourceKeySystems = _ref.sourceKeySystems, 59504 media = _ref.media, 59505 audioMedia = _ref.audioMedia, 59506 mainPlaylists = _ref.mainPlaylists; 59507 var sourceOptions = emeKeySystems(sourceKeySystems, media, audioMedia); 59508 59509 if (!sourceOptions) { 59510 return; 59511 } 59512 59513 player.currentSource().keySystems = sourceOptions; // eme handles the rest of the setup, so if it is missing 59514 // do nothing. 59515 59516 if (sourceOptions && !player.eme) { 59517 videojs$1.log.warn('DRM encrypted source cannot be decrypted without a DRM plugin'); 59518 return; 59519 } // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 59520 // in non-IE11 browsers. In IE11 this is too early to initialize media keys 59521 59522 59523 if (videojs$1.browser.IE_VERSION === 11 || !player.eme.initializeMediaKeys) { 59524 return; 59525 } // TODO should all audio PSSH values be initialized for DRM? 59526 // 59527 // All unique video rendition pssh values are initialized for DRM, but here only 59528 // the initial audio playlist license is initialized. In theory, an encrypted 59529 // event should be fired if the user switches to an alternative audio playlist 59530 // where a license is required, but this case hasn't yet been tested. In addition, there 59531 // may be many alternate audio playlists unlikely to be used (e.g., multiple different 59532 // languages). 59533 59534 59535 var playlists = audioMedia ? mainPlaylists.concat([audioMedia]) : mainPlaylists; 59536 var keySystemsOptionsArr = getAllPsshKeySystemsOptions(playlists, Object.keys(sourceKeySystems)); // Since PSSH values are interpreted as initData, EME will dedupe any duplicates. The 59537 // only place where it should not be deduped is for ms-prefixed APIs, but the early 59538 // return for IE11 above, and the existence of modern EME APIs in addition to 59539 // ms-prefixed APIs on Edge should prevent this from being a concern. 59540 // initializeMediaKeys also won't use the webkit-prefixed APIs. 59541 59542 keySystemsOptionsArr.forEach(function (keySystemsOptions) { 59543 player.eme.initializeMediaKeys({ 59544 keySystems: keySystemsOptions 59545 }); 59546 }); 59547 }; 59548 59549 var getVhsLocalStorage = function getVhsLocalStorage() { 59550 if (!window$1.localStorage) { 59551 return null; 59552 } 59553 59554 var storedObject = window$1.localStorage.getItem(LOCAL_STORAGE_KEY$1); 59555 59556 if (!storedObject) { 59557 return null; 59558 } 59559 59560 try { 59561 return JSON.parse(storedObject); 59562 } catch (e) { 59563 // someone may have tampered with the value 59564 return null; 59565 } 59566 }; 59567 59568 var updateVhsLocalStorage = function updateVhsLocalStorage(options) { 59569 if (!window$1.localStorage) { 59570 return false; 59571 } 59572 59573 var objectToStore = getVhsLocalStorage(); 59574 objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options; 59575 59576 try { 59577 window$1.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore)); 59578 } catch (e) { 59579 // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where 59580 // storage is set to 0). 59581 // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions 59582 // No need to perform any operation. 59583 return false; 59584 } 59585 59586 return objectToStore; 59587 }; 59588 /** 59589 * Parses VHS-supported media types from data URIs. See 59590 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs 59591 * for information on data URIs. 59592 * 59593 * @param {string} dataUri 59594 * The data URI 59595 * 59596 * @return {string|Object} 59597 * The parsed object/string, or the original string if no supported media type 59598 * was found 59599 */ 59600 59601 59602 var expandDataUri = function expandDataUri(dataUri) { 59603 if (dataUri.toLowerCase().indexOf('data:application/vnd.videojs.vhs+json,') === 0) { 59604 return JSON.parse(dataUri.substring(dataUri.indexOf(',') + 1)); 59605 } // no known case for this data URI, return the string as-is 59606 59607 59608 return dataUri; 59609 }; 59610 /** 59611 * Whether the browser has built-in HLS support. 59612 */ 59613 59614 59615 Vhs$1.supportsNativeHls = function () { 59616 if (!document || !document.createElement) { 59617 return false; 59618 } 59619 59620 var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't 59621 59622 if (!videojs$1.getTech('Html5').isSupported()) { 59623 return false; 59624 } // HLS manifests can go by many mime-types 59625 59626 59627 var canPlay = [// Apple santioned 59628 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility 59629 'audio/mpegurl', // Very common 59630 'audio/x-mpegurl', // Very common 59631 'application/x-mpegurl', // Included for completeness 59632 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 59633 return canPlay.some(function (canItPlay) { 59634 return /maybe|probably/i.test(video.canPlayType(canItPlay)); 59635 }); 59636 }(); 59637 59638 Vhs$1.supportsNativeDash = function () { 59639 if (!document || !document.createElement || !videojs$1.getTech('Html5').isSupported()) { 59640 return false; 59641 } 59642 59643 return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml')); 59644 }(); 59645 59646 Vhs$1.supportsTypeNatively = function (type) { 59647 if (type === 'hls') { 59648 return Vhs$1.supportsNativeHls; 59649 } 59650 59651 if (type === 'dash') { 59652 return Vhs$1.supportsNativeDash; 59653 } 59654 59655 return false; 59656 }; 59657 /** 59658 * HLS is a source handler, not a tech. Make sure attempts to use it 59659 * as one do not cause exceptions. 59660 */ 59661 59662 59663 Vhs$1.isSupported = function () { 59664 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 59665 }; 59666 59667 var Component$1 = videojs$1.getComponent('Component'); 59668 /** 59669 * The Vhs Handler object, where we orchestrate all of the parts 59670 * of HLS to interact with video.js 59671 * 59672 * @class VhsHandler 59673 * @extends videojs.Component 59674 * @param {Object} source the soruce object 59675 * @param {Tech} tech the parent tech object 59676 * @param {Object} options optional and required options 59677 */ 59678 59679 var VhsHandler = /*#__PURE__*/function (_Component) { 59680 inheritsLoose(VhsHandler, _Component); 59681 59682 function VhsHandler(source, tech, options) { 59683 var _this; 59684 59685 _this = _Component.call(this, tech, videojs$1.mergeOptions(options.hls, options.vhs)) || this; 59686 59687 if (options.hls && Object.keys(options.hls).length) { 59688 videojs$1.log.warn('Using hls options is deprecated. Use vhs instead.'); 59689 } // tech.player() is deprecated but setup a reference to HLS for 59690 // backwards-compatibility 59691 59692 59693 if (tech.options_ && tech.options_.playerId) { 59694 var _player = videojs$1(tech.options_.playerId); 59695 59696 if (!_player.hasOwnProperty('hls')) { 59697 Object.defineProperty(_player, 'hls', { 59698 get: function get() { 59699 videojs$1.log.warn('player.hls is deprecated. Use player.tech().vhs instead.'); 59700 tech.trigger({ 59701 type: 'usage', 59702 name: 'hls-player-access' 59703 }); 59704 return assertThisInitialized(_this); 59705 }, 59706 configurable: true 59707 }); 59708 } 59709 59710 if (!_player.hasOwnProperty('vhs')) { 59711 Object.defineProperty(_player, 'vhs', { 59712 get: function get() { 59713 videojs$1.log.warn('player.vhs is deprecated. Use player.tech().vhs instead.'); 59714 tech.trigger({ 59715 type: 'usage', 59716 name: 'vhs-player-access' 59717 }); 59718 return assertThisInitialized(_this); 59719 }, 59720 configurable: true 59721 }); 59722 } 59723 59724 if (!_player.hasOwnProperty('dash')) { 59725 Object.defineProperty(_player, 'dash', { 59726 get: function get() { 59727 videojs$1.log.warn('player.dash is deprecated. Use player.tech().vhs instead.'); 59728 return assertThisInitialized(_this); 59729 }, 59730 configurable: true 59731 }); 59732 } 59733 59734 _this.player_ = _player; 59735 } 59736 59737 _this.tech_ = tech; 59738 _this.source_ = source; 59739 _this.stats = {}; 59740 _this.ignoreNextSeekingEvent_ = false; 59741 59742 _this.setOptions_(); 59743 59744 if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) { 59745 tech.overrideNativeAudioTracks(true); 59746 tech.overrideNativeVideoTracks(true); 59747 } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { 59748 // overriding native HLS only works if audio tracks have been emulated 59749 // error early if we're misconfigured 59750 throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB'); 59751 } // listen for fullscreenchange events for this player so that we 59752 // can adjust our quality selection quickly 59753 59754 59755 _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 59756 var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; 59757 59758 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 59759 _this.masterPlaylistController_.smoothQualityChange_(); 59760 } 59761 }); 59762 59763 _this.on(_this.tech_, 'seeking', function () { 59764 if (this.ignoreNextSeekingEvent_) { 59765 this.ignoreNextSeekingEvent_ = false; 59766 return; 59767 } 59768 59769 this.setCurrentTime(this.tech_.currentTime()); 59770 }); 59771 59772 _this.on(_this.tech_, 'error', function () { 59773 // verify that the error was real and we are loaded 59774 // enough to have mpc loaded. 59775 if (this.tech_.error() && this.masterPlaylistController_) { 59776 this.masterPlaylistController_.pauseLoading(); 59777 } 59778 }); 59779 59780 _this.on(_this.tech_, 'play', _this.play); 59781 59782 return _this; 59783 } 59784 59785 var _proto = VhsHandler.prototype; 59786 59787 _proto.setOptions_ = function setOptions_() { 59788 var _this2 = this; // defaults 59789 59790 59791 this.options_.withCredentials = this.options_.withCredentials || false; 59792 this.options_.handleManifestRedirects = this.options_.handleManifestRedirects === false ? false : true; 59793 this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true; 59794 this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false; 59795 this.options_.smoothQualityChange = this.options_.smoothQualityChange || false; 59796 this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false; 59797 this.options_.customTagParsers = this.options_.customTagParsers || []; 59798 this.options_.customTagMappers = this.options_.customTagMappers || []; 59799 this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false; 59800 this.options_.handlePartialData = this.options_.handlePartialData || false; 59801 59802 if (typeof this.options_.blacklistDuration !== 'number') { 59803 this.options_.blacklistDuration = 5 * 60; 59804 } 59805 59806 if (typeof this.options_.bandwidth !== 'number') { 59807 if (this.options_.useBandwidthFromLocalStorage) { 59808 var storedObject = getVhsLocalStorage(); 59809 59810 if (storedObject && storedObject.bandwidth) { 59811 this.options_.bandwidth = storedObject.bandwidth; 59812 this.tech_.trigger({ 59813 type: 'usage', 59814 name: 'vhs-bandwidth-from-local-storage' 59815 }); 59816 this.tech_.trigger({ 59817 type: 'usage', 59818 name: 'hls-bandwidth-from-local-storage' 59819 }); 59820 } 59821 59822 if (storedObject && storedObject.throughput) { 59823 this.options_.throughput = storedObject.throughput; 59824 this.tech_.trigger({ 59825 type: 'usage', 59826 name: 'vhs-throughput-from-local-storage' 59827 }); 59828 this.tech_.trigger({ 59829 type: 'usage', 59830 name: 'hls-throughput-from-local-storage' 59831 }); 59832 } 59833 } 59834 } // if bandwidth was not set by options or pulled from local storage, start playlist 59835 // selection at a reasonable bandwidth 59836 59837 59838 if (typeof this.options_.bandwidth !== 'number') { 59839 this.options_.bandwidth = Config.INITIAL_BANDWIDTH; 59840 } // If the bandwidth number is unchanged from the initial setting 59841 // then this takes precedence over the enableLowInitialPlaylist option 59842 59843 59844 this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src 59845 59846 ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys', 'handlePartialData'].forEach(function (option) { 59847 if (typeof _this2.source_[option] !== 'undefined') { 59848 _this2.options_[option] = _this2.source_[option]; 59849 } 59850 }); 59851 this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions; 59852 this.useDevicePixelRatio = this.options_.useDevicePixelRatio; 59853 } 59854 /** 59855 * called when player.src gets called, handle a new source 59856 * 59857 * @param {Object} src the source object to handle 59858 */ 59859 ; 59860 59861 _proto.src = function src(_src, type) { 59862 var _this3 = this; // do nothing if the src is falsey 59863 59864 59865 if (!_src) { 59866 return; 59867 } 59868 59869 this.setOptions_(); // add master playlist controller options 59870 59871 this.options_.src = expandDataUri(this.source_.src); 59872 this.options_.tech = this.tech_; 59873 this.options_.externVhs = Vhs$1; 59874 this.options_.sourceType = mediaTypes_1(type); // Whenever we seek internally, we should update the tech 59875 59876 this.options_.seekTo = function (time) { 59877 _this3.tech_.setCurrentTime(time); 59878 }; 59879 59880 this.masterPlaylistController_ = new MasterPlaylistController(this.options_); 59881 this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, { 59882 seekable: function seekable() { 59883 return _this3.seekable(); 59884 }, 59885 media: function media() { 59886 return _this3.masterPlaylistController_.media(); 59887 }, 59888 masterPlaylistController: this.masterPlaylistController_ 59889 })); 59890 this.masterPlaylistController_.on('error', function () { 59891 var player = videojs$1.players[_this3.tech_.options_.playerId]; 59892 var error = _this3.masterPlaylistController_.error; 59893 59894 if (typeof error === 'object' && !error.code) { 59895 error.code = 3; 59896 } else if (typeof error === 'string') { 59897 error = { 59898 message: error, 59899 code: 3 59900 }; 59901 } 59902 59903 player.error(error); 59904 }); // `this` in selectPlaylist should be the VhsHandler for backwards 59905 // compatibility with < v2 59906 59907 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Vhs$1.STANDARD_PLAYLIST_SELECTOR.bind(this); 59908 this.masterPlaylistController_.selectInitialPlaylist = Vhs$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2 59909 59910 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 59911 this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist 59912 // controller. Using a custom property for backwards compatibility 59913 // with < v2 59914 59915 Object.defineProperties(this, { 59916 selectPlaylist: { 59917 get: function get() { 59918 return this.masterPlaylistController_.selectPlaylist; 59919 }, 59920 set: function set(selectPlaylist) { 59921 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 59922 } 59923 }, 59924 throughput: { 59925 get: function get() { 59926 return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; 59927 }, 59928 set: function set(throughput) { 59929 this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value 59930 // for the cumulative average 59931 59932 this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; 59933 } 59934 }, 59935 bandwidth: { 59936 get: function get() { 59937 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 59938 }, 59939 set: function set(bandwidth) { 59940 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter 59941 // `count` is set to zero that current value of `rate` isn't included 59942 // in the cumulative average 59943 59944 this.masterPlaylistController_.mainSegmentLoader_.throughput = { 59945 rate: 0, 59946 count: 0 59947 }; 59948 } 59949 }, 59950 59951 /** 59952 * `systemBandwidth` is a combination of two serial processes bit-rates. The first 59953 * is the network bitrate provided by `bandwidth` and the second is the bitrate of 59954 * the entire process after that - decryption, transmuxing, and appending - provided 59955 * by `throughput`. 59956 * 59957 * Since the two process are serial, the overall system bandwidth is given by: 59958 * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput) 59959 */ 59960 systemBandwidth: { 59961 get: function get() { 59962 var invBandwidth = 1 / (this.bandwidth || 1); 59963 var invThroughput; 59964 59965 if (this.throughput > 0) { 59966 invThroughput = 1 / this.throughput; 59967 } else { 59968 invThroughput = 0; 59969 } 59970 59971 var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput)); 59972 return systemBitrate; 59973 }, 59974 set: function set() { 59975 videojs$1.log.error('The "systemBandwidth" property is read-only'); 59976 } 59977 } 59978 }); 59979 59980 if (this.options_.bandwidth) { 59981 this.bandwidth = this.options_.bandwidth; 59982 } 59983 59984 if (this.options_.throughput) { 59985 this.throughput = this.options_.throughput; 59986 } 59987 59988 Object.defineProperties(this.stats, { 59989 bandwidth: { 59990 get: function get() { 59991 return _this3.bandwidth || 0; 59992 }, 59993 enumerable: true 59994 }, 59995 mediaRequests: { 59996 get: function get() { 59997 return _this3.masterPlaylistController_.mediaRequests_() || 0; 59998 }, 59999 enumerable: true 60000 }, 60001 mediaRequestsAborted: { 60002 get: function get() { 60003 return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0; 60004 }, 60005 enumerable: true 60006 }, 60007 mediaRequestsTimedout: { 60008 get: function get() { 60009 return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0; 60010 }, 60011 enumerable: true 60012 }, 60013 mediaRequestsErrored: { 60014 get: function get() { 60015 return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0; 60016 }, 60017 enumerable: true 60018 }, 60019 mediaTransferDuration: { 60020 get: function get() { 60021 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 60022 }, 60023 enumerable: true 60024 }, 60025 mediaBytesTransferred: { 60026 get: function get() { 60027 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 60028 }, 60029 enumerable: true 60030 }, 60031 mediaSecondsLoaded: { 60032 get: function get() { 60033 return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0; 60034 }, 60035 enumerable: true 60036 }, 60037 buffered: { 60038 get: function get() { 60039 return timeRangesToArray(_this3.tech_.buffered()); 60040 }, 60041 enumerable: true 60042 }, 60043 currentTime: { 60044 get: function get() { 60045 return _this3.tech_.currentTime(); 60046 }, 60047 enumerable: true 60048 }, 60049 currentSource: { 60050 get: function get() { 60051 return _this3.tech_.currentSource_; 60052 }, 60053 enumerable: true 60054 }, 60055 currentTech: { 60056 get: function get() { 60057 return _this3.tech_.name_; 60058 }, 60059 enumerable: true 60060 }, 60061 duration: { 60062 get: function get() { 60063 return _this3.tech_.duration(); 60064 }, 60065 enumerable: true 60066 }, 60067 master: { 60068 get: function get() { 60069 return _this3.playlists.master; 60070 }, 60071 enumerable: true 60072 }, 60073 playerDimensions: { 60074 get: function get() { 60075 return _this3.tech_.currentDimensions(); 60076 }, 60077 enumerable: true 60078 }, 60079 seekable: { 60080 get: function get() { 60081 return timeRangesToArray(_this3.tech_.seekable()); 60082 }, 60083 enumerable: true 60084 }, 60085 timestamp: { 60086 get: function get() { 60087 return Date.now(); 60088 }, 60089 enumerable: true 60090 }, 60091 videoPlaybackQuality: { 60092 get: function get() { 60093 return _this3.tech_.getVideoPlaybackQuality(); 60094 }, 60095 enumerable: true 60096 } 60097 }); 60098 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 60099 this.tech_.on('bandwidthupdate', function () { 60100 if (_this3.options_.useBandwidthFromLocalStorage) { 60101 updateVhsLocalStorage({ 60102 bandwidth: _this3.bandwidth, 60103 throughput: Math.round(_this3.throughput) 60104 }); 60105 } 60106 }); 60107 this.masterPlaylistController_.on('selectedinitialmedia', function () { 60108 // Add the manual rendition mix-in to VhsHandler 60109 renditionSelectionMixin(_this3); 60110 }); 60111 this.masterPlaylistController_.sourceUpdater_.on('ready', function () { 60112 var audioPlaylistLoader = _this3.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader; 60113 setupEmeOptions({ 60114 player: _this3.player_, 60115 sourceKeySystems: _this3.source_.keySystems, 60116 media: _this3.playlists.media(), 60117 audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(), 60118 mainPlaylists: _this3.playlists.master.playlists 60119 }); 60120 }); // the bandwidth of the primary segment loader is our best 60121 // estimate of overall bandwidth 60122 60123 this.on(this.masterPlaylistController_, 'progress', function () { 60124 this.tech_.trigger('progress'); 60125 }); // In the live case, we need to ignore the very first `seeking` event since 60126 // that will be the result of the seek-to-live behavior 60127 60128 this.on(this.masterPlaylistController_, 'firstplay', function () { 60129 this.ignoreNextSeekingEvent_ = true; 60130 }); 60131 this.setupQualityLevels_(); // do nothing if the tech has been disposed already 60132 // this can occur if someone sets the src in player.ready(), for instance 60133 60134 if (!this.tech_.el()) { 60135 return; 60136 } 60137 60138 this.mediaSourceUrl_ = window$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource); 60139 this.tech_.src(this.mediaSourceUrl_); 60140 } 60141 /** 60142 * Initializes the quality levels and sets listeners to update them. 60143 * 60144 * @method setupQualityLevels_ 60145 * @private 60146 */ 60147 ; 60148 60149 _proto.setupQualityLevels_ = function setupQualityLevels_() { 60150 var _this4 = this; 60151 60152 var player = videojs$1.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin 60153 // or qualityLevels_ listeners have already been setup, do nothing. 60154 60155 if (!player || !player.qualityLevels || this.qualityLevels_) { 60156 return; 60157 } 60158 60159 this.qualityLevels_ = player.qualityLevels(); 60160 this.masterPlaylistController_.on('selectedinitialmedia', function () { 60161 handleVhsLoadedMetadata(_this4.qualityLevels_, _this4); 60162 }); 60163 this.playlists.on('mediachange', function () { 60164 handleVhsMediaChange(_this4.qualityLevels_, _this4.playlists); 60165 }); 60166 } 60167 /** 60168 * return the version 60169 */ 60170 ; 60171 60172 VhsHandler.version = function version() { 60173 return { 60174 '@videojs/http-streaming': vhsVersion, 60175 'mux.js': muxVersion, 60176 'mpd-parser': mpdVersion, 60177 'm3u8-parser': m3u8Version, 60178 'aes-decrypter': aesVersion 60179 }; 60180 } 60181 /** 60182 * return the version 60183 */ 60184 ; 60185 60186 _proto.version = function version() { 60187 return this.constructor.version(); 60188 }; 60189 60190 _proto.canChangeType = function canChangeType() { 60191 return SourceUpdater.canChangeType(); 60192 } 60193 /** 60194 * Begin playing the video. 60195 */ 60196 ; 60197 60198 _proto.play = function play() { 60199 this.masterPlaylistController_.play(); 60200 } 60201 /** 60202 * a wrapper around the function in MasterPlaylistController 60203 */ 60204 ; 60205 60206 _proto.setCurrentTime = function setCurrentTime(currentTime) { 60207 this.masterPlaylistController_.setCurrentTime(currentTime); 60208 } 60209 /** 60210 * a wrapper around the function in MasterPlaylistController 60211 */ 60212 ; 60213 60214 _proto.duration = function duration() { 60215 return this.masterPlaylistController_.duration(); 60216 } 60217 /** 60218 * a wrapper around the function in MasterPlaylistController 60219 */ 60220 ; 60221 60222 _proto.seekable = function seekable() { 60223 return this.masterPlaylistController_.seekable(); 60224 } 60225 /** 60226 * Abort all outstanding work and cleanup. 60227 */ 60228 ; 60229 60230 _proto.dispose = function dispose() { 60231 if (this.playbackWatcher_) { 60232 this.playbackWatcher_.dispose(); 60233 } 60234 60235 if (this.masterPlaylistController_) { 60236 this.masterPlaylistController_.dispose(); 60237 } 60238 60239 if (this.qualityLevels_) { 60240 this.qualityLevels_.dispose(); 60241 } 60242 60243 if (this.player_) { 60244 delete this.player_.vhs; 60245 delete this.player_.dash; 60246 delete this.player_.hls; 60247 } 60248 60249 if (this.tech_ && this.tech_.vhs) { 60250 delete this.tech_.vhs; 60251 } // don't check this.tech_.hls as it will log a deprecated warning 60252 60253 60254 if (this.tech_) { 60255 delete this.tech_.hls; 60256 } 60257 60258 if (this.mediaSourceUrl_ && window$1.URL.revokeObjectURL) { 60259 window$1.URL.revokeObjectURL(this.mediaSourceUrl_); 60260 this.mediaSourceUrl_ = null; 60261 } 60262 60263 _Component.prototype.dispose.call(this); 60264 }; 60265 60266 _proto.convertToProgramTime = function convertToProgramTime(time, callback) { 60267 return getProgramTime({ 60268 playlist: this.masterPlaylistController_.media(), 60269 time: time, 60270 callback: callback 60271 }); 60272 } // the player must be playing before calling this 60273 ; 60274 60275 _proto.seekToProgramTime = function seekToProgramTime$1(programTime, callback, pauseAfterSeek, retryCount) { 60276 if (pauseAfterSeek === void 0) { 60277 pauseAfterSeek = true; 60278 } 60279 60280 if (retryCount === void 0) { 60281 retryCount = 2; 60282 } 60283 60284 return seekToProgramTime({ 60285 programTime: programTime, 60286 playlist: this.masterPlaylistController_.media(), 60287 retryCount: retryCount, 60288 pauseAfterSeek: pauseAfterSeek, 60289 seekTo: this.options_.seekTo, 60290 tech: this.options_.tech, 60291 callback: callback 60292 }); 60293 }; 60294 60295 return VhsHandler; 60296 }(Component$1); 60297 /** 60298 * The Source Handler object, which informs video.js what additional 60299 * MIME types are supported and sets up playback. It is registered 60300 * automatically to the appropriate tech based on the capabilities of 60301 * the browser it is running in. It is not necessary to use or modify 60302 * this object in normal usage. 60303 */ 60304 60305 60306 var VhsSourceHandler = { 60307 name: 'videojs-http-streaming', 60308 VERSION: vhsVersion, 60309 canHandleSource: function canHandleSource(srcObj, options) { 60310 if (options === void 0) { 60311 options = {}; 60312 } 60313 60314 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 60315 return VhsSourceHandler.canPlayType(srcObj.type, localOptions); 60316 }, 60317 handleSource: function handleSource(source, tech, options) { 60318 if (options === void 0) { 60319 options = {}; 60320 } 60321 60322 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 60323 tech.vhs = new VhsHandler(source, tech, localOptions); 60324 60325 if (!videojs$1.hasOwnProperty('hls')) { 60326 Object.defineProperty(tech, 'hls', { 60327 get: function get() { 60328 videojs$1.log.warn('player.tech().hls is deprecated. Use player.tech().vhs instead.'); 60329 return tech.vhs; 60330 }, 60331 configurable: true 60332 }); 60333 } 60334 60335 tech.vhs.xhr = xhrFactory(); 60336 tech.vhs.src(source.src, source.type); 60337 return tech.vhs; 60338 }, 60339 canPlayType: function canPlayType(type, options) { 60340 if (options === void 0) { 60341 options = {}; 60342 } 60343 60344 var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options), 60345 _videojs$mergeOptions2 = _videojs$mergeOptions.vhs.overrideNative, 60346 overrideNative = _videojs$mergeOptions2 === void 0 ? !videojs$1.browser.IS_ANY_SAFARI : _videojs$mergeOptions2; 60347 60348 var supportedType = mediaTypes_1(type); 60349 var canUseMsePlayback = supportedType && (!Vhs$1.supportsTypeNatively(supportedType) || overrideNative); 60350 return canUseMsePlayback ? 'maybe' : ''; 60351 } 60352 }; 60353 /** 60354 * Check to see if the native MediaSource object exists and supports 60355 * an MP4 container with both H.264 video and AAC-LC audio. 60356 * 60357 * @return {boolean} if native media sources are supported 60358 */ 60359 60360 var supportsNativeMediaSources = function supportsNativeMediaSources() { 60361 return codecs_3('avc1.4d400d,mp4a.40.2'); 60362 }; // register source handlers with the appropriate techs 60363 60364 60365 if (supportsNativeMediaSources()) { 60366 videojs$1.getTech('Html5').registerSourceHandler(VhsSourceHandler, 0); 60367 } 60368 60369 videojs$1.VhsHandler = VhsHandler; 60370 Object.defineProperty(videojs$1, 'HlsHandler', { 60371 get: function get() { 60372 videojs$1.log.warn('videojs.HlsHandler is deprecated. Use videojs.VhsHandler instead.'); 60373 return VhsHandler; 60374 }, 60375 configurable: true 60376 }); 60377 videojs$1.VhsSourceHandler = VhsSourceHandler; 60378 Object.defineProperty(videojs$1, 'HlsSourceHandler', { 60379 get: function get() { 60380 videojs$1.log.warn('videojs.HlsSourceHandler is deprecated. ' + 'Use videojs.VhsSourceHandler instead.'); 60381 return VhsSourceHandler; 60382 }, 60383 configurable: true 60384 }); 60385 videojs$1.Vhs = Vhs$1; 60386 Object.defineProperty(videojs$1, 'Hls', { 60387 get: function get() { 60388 videojs$1.log.warn('videojs.Hls is deprecated. Use videojs.Vhs instead.'); 60389 return Vhs$1; 60390 }, 60391 configurable: true 60392 }); 60393 60394 if (!videojs$1.use) { 60395 videojs$1.registerComponent('Hls', Vhs$1); 60396 videojs$1.registerComponent('Vhs', Vhs$1); 60397 } 60398 60399 videojs$1.options.vhs = videojs$1.options.vhs || {}; 60400 videojs$1.options.hls = videojs$1.options.hls || {}; 60401 60402 if (videojs$1.registerPlugin) { 60403 videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError); 60404 } else { 60405 videojs$1.plugin('reloadSourceOnError', reloadSourceOnError); 60406 } 60407 60408 return videojs$1; 60409 60410}))); 60411 60412!function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.10.2")}(window)}();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.