1/** 2 * @license 3 * Video.js 7.8.4 <http://videojs.com/> 4 * Copyright Brightcove, Inc. <https://www.brightcove.com/> 5 * Available under Apache License Version 2.0 6 * <https://github.com/videojs/video.js/blob/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$3, document) { 'use strict'; 18 19 window$3 = window$3 && Object.prototype.hasOwnProperty.call(window$3, 'default') ? window$3['default'] : window$3; 20 document = document && Object.prototype.hasOwnProperty.call(document, 'default') ? document['default'] : document; 21 22 var version = "7.8.4"; 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$3.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$3.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$3.console.info || window$3.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$3.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
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 createCommonjsModule(fn, module) { 317 return module = { exports: {} }, fn(module, module.exports), module.exports; 318 } 319 320 var _extends_1 = createCommonjsModule(function (module) { 321 function _extends() { 322 module.exports = _extends = Object.assign || function (target) { 323 for (var i = 1; i < arguments.length; i++) { 324 var source = arguments[i]; 325 326 for (var key in source) { 327 if (Object.prototype.hasOwnProperty.call(source, key)) { 328 target[key] = source[key]; 329 } 330 } 331 } 332 333 return target; 334 }; 335 336 return _extends.apply(this, arguments); 337 } 338 339 module.exports = _extends; 340 }); 341 342 /** 343 * @file obj.js 344 * @module obj 345 */ 346 347 /** 348 * @callback obj:EachCallback 349 * 350 * @param {Mixed} value 351 * The current key for the object that is being iterated over. 352 * 353 * @param {string} key 354 * The current key-value for object that is being iterated over 355 */ 356 357 /** 358 * @callback obj:ReduceCallback 359 * 360 * @param {Mixed} accum 361 * The value that is accumulating over the reduce loop. 362 * 363 * @param {Mixed} value 364 * The current key for the object that is being iterated over. 365 * 366 * @param {string} key 367 * The current key-value for object that is being iterated over 368 * 369 * @return {Mixed} 370 * The new accumulated value. 371 */ 372 var toString = Object.prototype.toString; 373 /** 374 * Get the keys of an Object 375 * 376 * @param {Object} 377 * The Object to get the keys from 378 * 379 * @return {string[]} 380 * An array of the keys from the object. Returns an empty array if the 381 * object passed in was invalid or had no keys. 382 * 383 * @private 384 */ 385 386 var keys = function keys(object) { 387 return isObject(object) ? Object.keys(object) : []; 388 }; 389 /** 390 * Array-like iteration for objects. 391 * 392 * @param {Object} object 393 * The object to iterate over 394 * 395 * @param {obj:EachCallback} fn 396 * The callback function which is called for each key in the object. 397 */ 398 399 400 function each(object, fn) { 401 keys(object).forEach(function (key) { 402 return fn(object[key], key); 403 }); 404 } 405 /** 406 * Array-like reduce for objects. 407 * 408 * @param {Object} object 409 * The Object that you want to reduce. 410 * 411 * @param {Function} fn 412 * A callback function which is called for each key in the object. It 413 * receives the accumulated value and the per-iteration value and key 414 * as arguments. 415 * 416 * @param {Mixed} [initial = 0] 417 * Starting value 418 * 419 * @return {Mixed} 420 * The final accumulated value. 421 */ 422 423 function reduce(object, fn, initial) { 424 if (initial === void 0) { 425 initial = 0; 426 } 427 428 return keys(object).reduce(function (accum, key) { 429 return fn(accum, object[key], key); 430 }, initial); 431 } 432 /** 433 * Object.assign-style object shallow merge/extend. 434 * 435 * @param {Object} target 436 * @param {Object} ...sources 437 * @return {Object} 438 */ 439 440 function assign(target) { 441 for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 442 sources[_key - 1] = arguments[_key]; 443 } 444 445 if (Object.assign) { 446 return _extends_1.apply(void 0, [target].concat(sources)); 447 } 448
449 sources.forEach(function (source) { 450 if (!source) { 451 return; 452 } 453 454 each(source, function (value, key) { 455 target[key] = value; 456 }); 457 }); 458 return target; 459 } 460 /** 461 * Returns whether a value is an object of any kind - including DOM nodes, 462 * arrays, regular expressions, etc. Not functions, though. 463 * 464 * This avoids the gotcha where using `typeof` on a `null` value 465 * results in `'object'`. 466 * 467 * @param {Object} value 468 * @return {boolean} 469 */ 470 471 function isObject(value) { 472 return !!value && typeof value === 'object'; 473 } 474 /** 475 * Returns whether an object appears to be a "plain" object - that is, a 476 * direct instance of `Object`. 477 * 478 * @param {Object} value 479 * @return {boolean} 480 */ 481 482 function isPlain(value) { 483 return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object; 484 } 485 486 /** 487 * @file computed-style.js 488 * @module computed-style 489 */ 490 /** 491 * A safe getComputedStyle. 492 * 493 * This is needed because in Firefox, if the player is loaded in an iframe with 494 * `display:none`, then `getComputedStyle` returns `null`, so, we do a 495 * null-check to make sure that the player doesn't break in these cases. 496 * 497 * @function 498 * @param {Element} el 499 * The element you want the computed style of 500 * 501 * @param {string} prop 502 * The property name you want 503 * 504 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 505 */ 506 507 function computedStyle(el, prop) { 508 if (!el || !prop) { 509 return ''; 510 } 511 512 if (typeof window$3.getComputedStyle === 'function') { 513 var computedStyleValue = window$3.getComputedStyle(el); 514 return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : ''; 515 } 516 517 return ''; 518 } 519 520 /** 521 * @file dom.js 522 * @module dom 523 */ 524 /** 525 * Detect if a value is a string with any non-whitespace characters. 526 * 527 * @private 528 * @param {string} str 529 * The string to check 530 * 531 * @return {boolean} 532 * Will be `true` if the string is non-blank, `false` otherwise. 533 * 534 */ 535 536 function isNonBlankString(str) { 537 // we use str.trim as it will trim any whitespace characters 538 // from the front or back of non-whitespace characters. aka 539 // Any string that contains non-whitespace characters will 540 // still contain them after `trim` but whitespace only strings 541 // will have a length of 0, failing this check. 542 return typeof str === 'string' && Boolean(str.trim()); 543 } 544 /** 545 * Throws an error if the passed string has whitespace. This is used by 546 * class methods to be relatively consistent with the classList API. 547 * 548 * @private 549 * @param {string} str 550 * The string to check for whitespace. 551 * 552 * @throws {Error} 553 * Throws an error if there is whitespace in the string. 554 */ 555 556 557 function throwIfWhitespace(str) { 558 // str.indexOf instead of regex because str.indexOf is faster performance wise. 559 if (str.indexOf(' ') >= 0) { 560 throw new Error('class has illegal whitespace characters'); 561 } 562 } 563 /** 564 * Produce a regular expression for matching a className within an elements className. 565 * 566 * @private 567 * @param {string} className 568 * The className to generate the RegExp for. 569 * 570 * @return {RegExp} 571 * The RegExp that will check for a specific `className` in an elements 572 * className. 573 */ 574 575 576 function classRegExp(className) { 577 return new RegExp('(^|\\s)' + className + '($|\\s)'); 578 } 579 /** 580 * Whether the current DOM interface appears to be real (i.e. not simulated). 581 * 582 * @return {boolean} 583 * Will be `true` if the DOM appears to be real, `false` otherwise. 584 */ 585 586 587 function isReal() { 588 // Both document and window will never be undefined thanks to `global`. 589 return document === window$3.document; 590 } 591 /** 592 * Determines, via duck typing, whether or not a value is a DOM element. 593 * 594 * @param {Mixed} value 595 * The value to check. 596 * 597 * @return {boolean} 598 * Will be `true` if the value is a DOM element, `false` otherwise. 599 */ 600 601 function isEl(value) { 602 return isObject(value) && value.nodeType === 1; 603 } 604 /** 605 * Determines if the current DOM is embedded in an iframe. 606 * 607 * @return {boolean} 608 * Will be `true` if the DOM is embedded in an iframe, `false` 609 * otherwise. 610 */ 611 612 function isInFrame() { 613 // We need a try/catch here because Safari will throw errors when attempting 614 // to get either `parent` or `self`
615 try { 616 return window$3.parent !== window$3.self; 617 } catch (x) { 618 return true; 619 } 620 } 621 /** 622 * Creates functions to query the DOM using a given method. 623 * 624 * @private 625 * @param {string} method 626 * The method to create the query with. 627 * 628 * @return {Function} 629 * The query method 630 */ 631 632 function createQuerier(method) { 633 return function (selector, context) { 634 if (!isNonBlankString(selector)) { 635 return document[method](null); 636 } 637 638 if (isNonBlankString(context)) { 639 context = document.querySelector(context); 640 } 641 642 var ctx = isEl(context) ? context : document; 643 return ctx[method] && ctx[method](selector); 644 }; 645 } 646 /** 647 * Creates an element and applies properties, attributes, and inserts content. 648 * 649 * @param {string} [tagName='div'] 650 * Name of tag to be created. 651 * 652 * @param {Object} [properties={}] 653 * Element properties to be applied. 654 * 655 * @param {Object} [attributes={}] 656 * Element attributes to be applied. 657 * 658 * @param {module:dom~ContentDescriptor} content 659 * A content descriptor object. 660 * 661 * @return {Element} 662 * The element that was created. 663 */ 664 665 666 function createEl(tagName, properties, attributes, content) { 667 if (tagName === void 0) { 668 tagName = 'div'; 669 } 670 671 if (properties === void 0) { 672 properties = {}; 673 } 674 675 if (attributes === void 0) { 676 attributes = {}; 677 } 678 679 var el = document.createElement(tagName); 680 Object.getOwnPropertyNames(properties).forEach(function (propName) { 681 var val = properties[propName]; // See #2176 682 // We originally were accepting both properties and attributes in the 683 // same object, but that doesn't work so well. 684 685 if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { 686 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 + ".")); 687 el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a 688 // method for it. 689 } else if (propName === 'textContent') { 690 textContent(el, val); 691 } else if (el[propName] !== val) { 692 el[propName] = val; 693 } 694 }); 695 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 696 el.setAttribute(attrName, attributes[attrName]); 697 }); 698 699 if (content) { 700 appendContent(el, content); 701 } 702 703 return el; 704 } 705 /** 706 * Injects text into an element, replacing any existing contents entirely. 707 * 708 * @param {Element} el 709 * The element to add text content into 710 * 711 * @param {string} text 712 * The text content to add. 713 * 714 * @return {Element} 715 * The element with added text content. 716 */ 717 718 function textContent(el, text) { 719 if (typeof el.textContent === 'undefined') { 720 el.innerText = text; 721 } else { 722 el.textContent = text; 723 } 724 725 return el; 726 } 727 /** 728 * Insert an element as the first child node of another 729 * 730 * @param {Element} child 731 * Element to insert 732 * 733 * @param {Element} parent 734 * Element to insert child into 735 */ 736 737 function prependTo(child, parent) { 738 if (parent.firstChild) { 739 parent.insertBefore(child, parent.firstChild); 740 } else { 741 parent.appendChild(child); 742 } 743 } 744 /** 745 * Check if an element has a class name. 746 * 747 * @param {Element} element 748 * Element to check 749 * 750 * @param {string} classToCheck 751 * Class name to check for 752 * 753 * @return {boolean} 754 * Will be `true` if the element has a class, `false` otherwise. 755 * 756 * @throws {Error} 757 * Throws an error if `classToCheck` has white space. 758 */ 759 760 function hasClass(element, classToCheck) { 761 throwIfWhitespace(classToCheck); 762 763 if (element.classList) { 764 return element.classList.contains(classToCheck); 765 } 766 767 return classRegExp(classToCheck).test(element.className); 768 } 769 /** 770 * Add a class name to an element. 771 * 772 * @param {Element} element
vendor: 22,010 bytes, lines 773-1433
773 * Element to add class name to. 774 * 775 * @param {string} classToAdd 776 * Class name to add. 777 * 778 * @return {Element} 779 * The DOM element with the added class name. 780 */ 781 782 function addClass(element, classToAdd) { 783 if (element.classList) { 784 element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it 785 // in the case of classList not being supported. 786 } else if (!hasClass(element, classToAdd)) { 787 element.className = (element.className + ' ' + classToAdd).trim(); 788 } 789 790 return element; 791 } 792 /** 793 * Remove a class name from an element. 794 * 795 * @param {Element} element 796 * Element to remove a class name from. 797 * 798 * @param {string} classToRemove 799 * Class name to remove 800 * 801 * @return {Element} 802 * The DOM element with class name removed. 803 */ 804 805 function removeClass(element, classToRemove) { 806 if (element.classList) { 807 element.classList.remove(classToRemove); 808 } else { 809 throwIfWhitespace(classToRemove); 810 element.className = element.className.split(/\s+/).filter(function (c) { 811 return c !== classToRemove; 812 }).join(' '); 813 } 814 815 return element; 816 } 817 /** 818 * The callback definition for toggleClass. 819 * 820 * @callback module:dom~PredicateCallback 821 * @param {Element} element 822 * The DOM element of the Component. 823 * 824 * @param {string} classToToggle 825 * The `className` that wants to be toggled 826 * 827 * @return {boolean|undefined} 828 * If `true` is returned, the `classToToggle` will be added to the 829 * `element`. If `false`, the `classToToggle` will be removed from 830 * the `element`. If `undefined`, the callback will be ignored. 831 */ 832 833 /** 834 * Adds or removes a class name to/from an element depending on an optional 835 * condition or the presence/absence of the class name. 836 * 837 * @param {Element} element 838 * The element to toggle a class name on. 839 * 840 * @param {string} classToToggle 841 * The class that should be toggled. 842 * 843 * @param {boolean|module:dom~PredicateCallback} [predicate] 844 * See the return value for {@link module:dom~PredicateCallback} 845 * 846 * @return {Element} 847 * The element with a class that has been toggled. 848 */ 849 850 function toggleClass(element, classToToggle, predicate) { 851 // This CANNOT use `classList` internally because IE11 does not support the 852 // second parameter to the `classList.toggle()` method! Which is fine because 853 // `classList` will be used by the add/remove functions. 854 var has = hasClass(element, classToToggle); 855 856 if (typeof predicate === 'function') { 857 predicate = predicate(element, classToToggle); 858 } 859 860 if (typeof predicate !== 'boolean') { 861 predicate = !has; 862 } // If the necessary class operation matches the current state of the 863 // element, no action is required. 864 865 866 if (predicate === has) { 867 return; 868 } 869 870 if (predicate) { 871 addClass(element, classToToggle); 872 } else { 873 removeClass(element, classToToggle); 874 } 875 876 return element; 877 } 878 /** 879 * Apply attributes to an HTML element. 880 * 881 * @param {Element} el 882 * Element to add attributes to. 883 * 884 * @param {Object} [attributes] 885 * Attributes to be applied. 886 */ 887 888 function setAttributes(el, attributes) { 889 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 890 var attrValue = attributes[attrName]; 891 892 if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { 893 el.removeAttribute(attrName); 894 } else { 895 el.setAttribute(attrName, attrValue === true ? '' : attrValue); 896 } 897 }); 898 } 899 /** 900 * Get an element's attribute values, as defined on the HTML tag. 901 * 902 * Attributes are not the same as properties. They're defined on the tag 903 * or with setAttribute. 904 * 905 * @param {Element} tag 906 * Element from which to get tag attributes. 907 * 908 * @return {Object} 909 * All attributes of the element. Boolean attributes will be `true` or 910 * `false`, others will be strings. 911 */ 912 913 function getAttributes(tag) { 914 var obj = {}; // known boolean attributes 915 // we can check for matching boolean properties, but not all browsers 916 // and not all tags know about these attributes, so, we still want to check them manually 917 918 var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; 919 920 if (tag && tag.attributes && tag.attributes.length > 0) { 921 var attrs = tag.attributes; 922 923 for (var i = attrs.length - 1; i >= 0; i--) { 924 var attrName = attrs[i].name; 925 var attrVal = attrs[i].value; // check for known booleans 926 // the matching element property will return a value for typeof 927 928 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { 929 // the value of an included boolean attribute is typically an empty 930 // string ('') which would equal false if we just check for a false value. 931 // we also don't want support bad code like autoplay='false' 932 attrVal = attrVal !== null ? true : false; 933 } 934 935 obj[attrName] = attrVal; 936 } 937 } 938 939 return obj; 940 } 941 /** 942 * Get the value of an element's attribute. 943 * 944 * @param {Element} el 945 * A DOM element. 946 * 947 * @param {string} attribute 948 * Attribute to get the value of. 949 * 950 * @return {string} 951 * The value of the attribute. 952 */ 953 954 function getAttribute(el, attribute) { 955 return el.getAttribute(attribute); 956 } 957 /** 958 * Set the value of an element's attribute. 959 * 960 * @param {Element} el 961 * A DOM element. 962 * 963 * @param {string} attribute 964 * Attribute to set. 965 * 966 * @param {string} value 967 * Value to set the attribute to. 968 */ 969 970 function setAttribute(el, attribute, value) { 971 el.setAttribute(attribute, value); 972 } 973 /** 974 * Remove an element's attribute. 975 * 976 * @param {Element} el 977 * A DOM element. 978 * 979 * @param {string} attribute 980 * Attribute to remove. 981 */ 982 983 function removeAttribute(el, attribute) { 984 el.removeAttribute(attribute); 985 } 986 /** 987 * Attempt to block the ability to select text. 988 */ 989 990 function blockTextSelection() { 991 document.body.focus(); 992 993 document.onselectstart = function () { 994 return false; 995 }; 996 } 997 /** 998 * Turn off text selection blocking. 999 */ 1000 1001 function unblockTextSelection() { 1002 document.onselectstart = function () { 1003 return true; 1004 }; 1005 } 1006 /** 1007 * Identical to the native `getBoundingClientRect` function, but ensures that 1008 * the method is supported at all (it is in all browsers we claim to support) 1009 * and that the element is in the DOM before continuing. 1010 * 1011 * This wrapper function also shims properties which are not provided by some 1012 * older browsers (namely, IE8). 1013 * 1014 * Additionally, some browsers do not support adding properties to a 1015 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard 1016 * properties (except `x` and `y` which are not widely supported). This helps 1017 * avoid implementations where keys are non-enumerable. 1018 * 1019 * @param {Element} el 1020 * Element whose `ClientRect` we want to calculate. 1021 * 1022 * @return {Object|undefined} 1023 * Always returns a plain object - or `undefined` if it cannot. 1024 */ 1025 1026 function getBoundingClientRect(el) { 1027 if (el && el.getBoundingClientRect && el.parentNode) { 1028 var rect = el.getBoundingClientRect(); 1029 var result = {}; 1030 ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { 1031 if (rect[k] !== undefined) { 1032 result[k] = rect[k]; 1033 } 1034 }); 1035 1036 if (!result.height) { 1037 result.height = parseFloat(computedStyle(el, 'height')); 1038 } 1039 1040 if (!result.width) { 1041 result.width = parseFloat(computedStyle(el, 'width')); 1042 } 1043 1044 return result; 1045 } 1046 } 1047 /** 1048 * Represents the position of a DOM element on the page. 1049 * 1050 * @typedef {Object} module:dom~Position 1051 * 1052 * @property {number} left 1053 * Pixels to the left. 1054 * 1055 * @property {number} top 1056 * Pixels from the top. 1057 */ 1058 1059 /** 1060 * Get the position of an element in the DOM. 1061 * 1062 * Uses `getBoundingClientRect` technique from John Resig. 1063 * 1064 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ 1065 * 1066 * @param {Element} el 1067 * Element from which to get offset. 1068 * 1069 * @return {module:dom~Position} 1070 * The position of the element that was passed in. 1071 */ 1072 1073 function findPosition(el) { 1074 var box; 1075 1076 if (el.getBoundingClientRect && el.parentNode) { 1077 box = el.getBoundingClientRect(); 1078 } 1079 1080 if (!box) { 1081 return { 1082 left: 0, 1083 top: 0 1084 }; 1085 } 1086 1087 var docEl = document.documentElement; 1088 var body = document.body; 1089 var clientLeft = docEl.clientLeft || body.clientLeft || 0; 1090 var scrollLeft = window$3.pageXOffset || body.scrollLeft; 1091 var left = box.left + scrollLeft - clientLeft; 1092 var clientTop = docEl.clientTop || body.clientTop || 0; 1093 var scrollTop = window$3.pageYOffset || body.scrollTop; 1094 var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round 1095 1096 return { 1097 left: Math.round(left), 1098 top: Math.round(top) 1099 }; 1100 } 1101 /** 1102 * Represents x and y coordinates for a DOM element or mouse pointer. 1103 * 1104 * @typedef {Object} module:dom~Coordinates 1105 * 1106 * @property {number} x 1107 * x coordinate in pixels 1108 * 1109 * @property {number} y 1110 * y coordinate in pixels 1111 */ 1112 1113 /** 1114 * Get the pointer position within an element. 1115 * 1116 * The base on the coordinates are the bottom left of the element. 1117 * 1118 * @param {Element} el 1119 * Element on which to get the pointer position on. 1120 * 1121 * @param {EventTarget~Event} event 1122 * Event object. 1123 * 1124 * @return {module:dom~Coordinates} 1125 * A coordinates object corresponding to the mouse position. 1126 * 1127 */ 1128 1129 function getPointerPosition(el, event) { 1130 var position = {}; 1131 var box = findPosition(el); 1132 var boxW = el.offsetWidth; 1133 var boxH = el.offsetHeight; 1134 var boxY = box.top; 1135 var boxX = box.left; 1136 var pageY = event.pageY; 1137 var pageX = event.pageX; 1138 1139 if (event.changedTouches) { 1140 pageX = event.changedTouches[0].pageX; 1141 pageY = event.changedTouches[0].pageY; 1142 } 1143 1144 position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH)); 1145 position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); 1146 return position; 1147 } 1148 /** 1149 * Determines, via duck typing, whether or not a value is a text node. 1150 * 1151 * @param {Mixed} value 1152 * Check if this value is a text node. 1153 * 1154 * @return {boolean} 1155 * Will be `true` if the value is a text node, `false` otherwise. 1156 */ 1157 1158 function isTextNode(value) { 1159 return isObject(value) && value.nodeType === 3; 1160 } 1161 /** 1162 * Empties the contents of an element. 1163 * 1164 * @param {Element} el 1165 * The element to empty children from 1166 * 1167 * @return {Element} 1168 * The element with no children 1169 */ 1170 1171 function emptyEl(el) { 1172 while (el.firstChild) { 1173 el.removeChild(el.firstChild); 1174 } 1175 1176 return el; 1177 } 1178 /** 1179 * This is a mixed value that describes content to be injected into the DOM 1180 * via some method. It can be of the following types: 1181 * 1182 * Type | Description 1183 * -----------|------------- 1184 * `string` | The value will be normalized into a text node. 1185 * `Element` | The value will be accepted as-is. 1186 * `TextNode` | The value will be accepted as-is. 1187 * `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). 1188 * `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. 1189 * 1190 * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor 1191 */ 1192 1193 /** 1194 * Normalizes content for eventual insertion into the DOM. 1195 * 1196 * This allows a wide range of content definition methods, but helps protect 1197 * from falling into the trap of simply writing to `innerHTML`, which could 1198 * be an XSS concern. 1199 * 1200 * The content for an element can be passed in multiple types and 1201 * combinations, whose behavior is as follows: 1202 * 1203 * @param {module:dom~ContentDescriptor} content 1204 * A content descriptor value. 1205 * 1206 * @return {Array} 1207 * All of the content that was passed in, normalized to an array of 1208 * elements or text nodes. 1209 */ 1210 1211 function normalizeContent(content) { 1212 // First, invoke content if it is a function. If it produces an array, 1213 // that needs to happen before normalization. 1214 if (typeof content === 'function') { 1215 content = content(); 1216 } // Next up, normalize to an array, so one or many items can be normalized, 1217 // filtered, and returned. 1218 1219 1220 return (Array.isArray(content) ? content : [content]).map(function (value) { 1221 // First, invoke value if it is a function to produce a new value, 1222 // which will be subsequently normalized to a Node of some kind. 1223 if (typeof value === 'function') { 1224 value = value(); 1225 } 1226 1227 if (isEl(value) || isTextNode(value)) { 1228 return value; 1229 } 1230 1231 if (typeof value === 'string' && /\S/.test(value)) { 1232 return document.createTextNode(value); 1233 } 1234 }).filter(function (value) { 1235 return value; 1236 }); 1237 } 1238 /** 1239 * Normalizes and appends content to an element. 1240 * 1241 * @param {Element} el 1242 * Element to append normalized content to. 1243 * 1244 * @param {module:dom~ContentDescriptor} content 1245 * A content descriptor value. 1246 * 1247 * @return {Element} 1248 * The element with appended normalized content. 1249 */ 1250 1251 function appendContent(el, content) { 1252 normalizeContent(content).forEach(function (node) { 1253 return el.appendChild(node); 1254 }); 1255 return el; 1256 } 1257 /** 1258 * Normalizes and inserts content into an element; this is identical to 1259 * `appendContent()`, except it empties the element first. 1260 * 1261 * @param {Element} el 1262 * Element to insert normalized content into. 1263 * 1264 * @param {module:dom~ContentDescriptor} content 1265 * A content descriptor value. 1266 * 1267 * @return {Element} 1268 * The element with inserted normalized content. 1269 */ 1270 1271 function insertContent(el, content) { 1272 return appendContent(emptyEl(el), content); 1273 } 1274 /** 1275 * Check if an event was a single left click. 1276 * 1277 * @param {EventTarget~Event} event 1278 * Event object. 1279 * 1280 * @return {boolean} 1281 * Will be `true` if a single left click, `false` otherwise. 1282 */ 1283 1284 function isSingleLeftClick(event) { 1285 // Note: if you create something draggable, be sure to 1286 // call it on both `mousedown` and `mousemove` event, 1287 // otherwise `mousedown` should be enough for a button 1288 if (event.button === undefined && event.buttons === undefined) { 1289 // Why do we need `buttons` ? 1290 // Because, middle mouse sometimes have this: 1291 // e.button === 0 and e.buttons === 4 1292 // Furthermore, we want to prevent combination click, something like 1293 // HOLD middlemouse then left click, that would be 1294 // e.button === 0, e.buttons === 5 1295 // just `button` is not gonna work 1296 // Alright, then what this block does ? 1297 // this is for chrome `simulate mobile devices` 1298 // I want to support this as well 1299 return true; 1300 } 1301 1302 if (event.button === 0 && event.buttons === undefined) { 1303 // Touch screen, sometimes on some specific device, `buttons` 1304 // doesn't have anything (safari on ios, blackberry...) 1305 return true; 1306 } // `mouseup` event on a single left click has 1307 // `button` and `buttons` equal to 0 1308 1309 1310 if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) { 1311 return true; 1312 } 1313 1314 if (event.button !== 0 || event.buttons !== 1) { 1315 // This is the reason we have those if else block above 1316 // if any special case we can catch and let it slide 1317 // we do it above, when get to here, this definitely 1318 // is-not-left-click 1319 return false; 1320 } 1321 1322 return true; 1323 } 1324 /** 1325 * Finds a single DOM element matching `selector` within the optional 1326 * `context` of another DOM element (defaulting to `document`). 1327 * 1328 * @param {string} selector 1329 * A valid CSS selector, which will be passed to `querySelector`. 1330 * 1331 * @param {Element|String} [context=document] 1332 * A DOM element within which to query. Can also be a selector 1333 * string in which case the first matching element will be used 1334 * as context. If missing (or no element matches selector), falls 1335 * back to `document`. 1336 * 1337 * @return {Element|null} 1338 * The element that was found or null. 1339 */ 1340 1341 var $ = createQuerier('querySelector'); 1342 /** 1343 * Finds a all DOM elements matching `selector` within the optional 1344 * `context` of another DOM element (defaulting to `document`). 1345 * 1346 * @param {string} selector 1347 * A valid CSS selector, which will be passed to `querySelectorAll`. 1348 * 1349 * @param {Element|String} [context=document] 1350 * A DOM element within which to query. Can also be a selector 1351 * string in which case the first matching element will be used 1352 * as context. If missing (or no element matches selector), falls 1353 * back to `document`. 1354 * 1355 * @return {NodeList} 1356 * A element list of elements that were found. Will be empty if none 1357 * were found. 1358 * 1359 */ 1360 1361 var $$ = createQuerier('querySelectorAll'); 1362 1363 var Dom = /*#__PURE__*/Object.freeze({ 1364 __proto__: null, 1365 isReal: isReal, 1366 isEl: isEl, 1367 isInFrame: isInFrame, 1368 createEl: createEl, 1369 textContent: textContent, 1370 prependTo: prependTo, 1371 hasClass: hasClass, 1372 addClass: addClass, 1373 removeClass: removeClass, 1374 toggleClass: toggleClass, 1375 setAttributes: setAttributes, 1376 getAttributes: getAttributes, 1377 getAttribute: getAttribute, 1378 setAttribute: setAttribute, 1379 removeAttribute: removeAttribute, 1380 blockTextSelection: blockTextSelection, 1381 unblockTextSelection: unblockTextSelection, 1382 getBoundingClientRect: getBoundingClientRect, 1383 findPosition: findPosition, 1384 getPointerPosition: getPointerPosition, 1385 isTextNode: isTextNode, 1386 emptyEl: emptyEl, 1387 normalizeContent: normalizeContent, 1388 appendContent: appendContent, 1389 insertContent: insertContent, 1390 isSingleLeftClick: isSingleLeftClick, 1391 $: $, 1392 $$: $$ 1393 }); 1394 1395 /** 1396 * @file setup.js - Functions for setting up a player without 1397 * user interaction based on the data-setup `attribute` of the video tag. 1398 * 1399 * @module setup 1400 */ 1401 var _windowLoaded = false; 1402 var videojs; 1403 /** 1404 * Set up any tags that have a data-setup `attribute` when the player is started. 1405 */ 1406 1407 var autoSetup = function autoSetup() { 1408 // Protect against breakage in non-browser environments and check global autoSetup option. 1409 if (!isReal() || videojs.options.autoSetup === false) { 1410 return; 1411 } 1412 1413 var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); 1414 var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); 1415 var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js')); 1416 var mediaEls = vids.concat(audios, divs); // Check if any media elements exist 1417 1418 if (mediaEls && mediaEls.length > 0) { 1419 for (var i = 0, e = mediaEls.length; i < e; i++) { 1420 var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. 1421 1422 if (mediaEl && mediaEl.getAttribute) { 1423 // Make sure this player hasn't already been set up. 1424 if (mediaEl.player === undefined) { 1425 var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. 1426 // We only auto-setup if they've added the data-setup attr. 1427 1428 if (options !== null) { 1429 // Create new video.js instance. 1430 videojs(mediaEl); 1431 } 1432 } // If getAttribute isn't defined, we need to wait for the DOM. 1433
1434 } else { 1435 autoSetupTimeout(1); 1436 break; 1437 } 1438 } // No videos were found, so keep looping unless page is finished loading. 1439 1440 } else if (!_windowLoaded) { 1441 autoSetupTimeout(1); 1442 } 1443 }; 1444 /** 1445 * Wait until the page is loaded before running autoSetup. This will be called in 1446 * autoSetup if `hasLoaded` returns false. 1447 * 1448 * @param {number} wait 1449 * How long to wait in ms 1450 * 1451 * @param {module:videojs} [vjs] 1452 * The videojs library function 1453 */ 1454 1455 1456 function autoSetupTimeout(wait, vjs) { 1457 if (vjs) { 1458 videojs = vjs; 1459 } 1460 1461 window$3.setTimeout(autoSetup, wait); 1462 } 1463 /** 1464 * Used to set the internal tracking of window loaded state to true. 1465 * 1466 * @private 1467 */ 1468 1469 1470 function setWindowLoaded() { 1471 _windowLoaded = true; 1472 window$3.removeEventListener('load', setWindowLoaded); 1473 } 1474 1475 if (isReal()) { 1476 if (document.readyState === 'complete') { 1477 setWindowLoaded(); 1478 } else { 1479 /** 1480 * Listen for the load event on window, and set _windowLoaded to true. 1481 * 1482 * We use a standard event listener here to avoid incrementing the GUID 1483 * before any players are created. 1484 * 1485 * @listens load 1486 */ 1487 window$3.addEventListener('load', setWindowLoaded); 1488 } 1489 } 1490 1491 /** 1492 * @file stylesheet.js 1493 * @module stylesheet 1494 */ 1495 /** 1496 * Create a DOM syle element given a className for it. 1497 * 1498 * @param {string} className 1499 * The className to add to the created style element. 1500 * 1501 * @return {Element} 1502 * The element that was created. 1503 */ 1504 1505 var createStyleElement = function createStyleElement(className) { 1506 var style = document.createElement('style'); 1507 style.className = className; 1508 return style; 1509 }; 1510 /** 1511 * Add text to a DOM element. 1512 * 1513 * @param {Element} el 1514 * The Element to add text content to. 1515 * 1516 * @param {string} content 1517 * The text to add to the element. 1518 */ 1519 1520 var setTextContent = function setTextContent(el, content) { 1521 if (el.styleSheet) { 1522 el.styleSheet.cssText = content; 1523 } else { 1524 el.textContent = content; 1525 } 1526 }; 1527 1528 /** 1529 * @file guid.js 1530 * @module guid 1531 */ 1532 // Default value for GUIDs. This allows us to reset the GUID counter in tests. 1533 // 1534 // The initial GUID is 3 because some users have come to rely on the first 1535 // default player ID ending up as `vjs_video_3`. 1536 // 1537 // See: https://github.com/videojs/video.js/pull/6216 1538 var _initialGuid = 3; 1539 /** 1540 * Unique ID for an element or function 1541 * 1542 * @type {Number} 1543 */ 1544 1545 var _guid = _initialGuid; 1546 /** 1547 * Get a unique auto-incrementing ID by number that has not been returned before. 1548 * 1549 * @return {number} 1550 * A new unique ID. 1551 */ 1552 1553 function newGUID() { 1554 return _guid++; 1555 } 1556 1557 /** 1558 * @file dom-data.js 1559 * @module dom-data 1560 */ 1561 var FakeWeakMap; 1562 1563 if (!window$3.WeakMap) { 1564 FakeWeakMap = /*#__PURE__*/function () { 1565 function FakeWeakMap() { 1566 this.vdata = 'vdata' + Math.floor(window$3.performance && window$3.performance.now() || Date.now()); 1567 this.data = {}; 1568 } 1569 1570 var _proto = FakeWeakMap.prototype; 1571 1572 _proto.set = function set(key, value) { 1573 var access = key[this.vdata] || newGUID(); 1574 1575 if (!key[this.vdata]) { 1576 key[this.vdata] = access; 1577 } 1578 1579 this.data[access] = value; 1580 return this; 1581 }; 1582 1583 _proto.get = function get(key) { 1584 var access = key[this.vdata]; // we have data, return it 1585 1586 if (access) { 1587 return this.data[access]; 1588 } // we don't have data, return nothing. 1589 // return undefined explicitly as that's the contract for this method 1590 1591 1592 log('We have no data for this element', key); 1593 return undefined; 1594 }; 1595 1596 _proto.has = function has(key) { 1597 var access = key[this.vdata]; 1598 return access in this.data; 1599 }; 1600 1601 _proto["delete"] = function _delete(key) { 1602 var access = key[this.vdata]; 1603 1604 if (access) { 1605 delete this.data[access]; 1606 delete key[this.vdata]; 1607 } 1608 }; 1609 1610 return FakeWeakMap; 1611 }(); 1612 } 1613 /** 1614 * Element Data Store. 1615 * 1616 * Allows for binding data to an element without putting it directly on the 1617 * element. Ex. Event listeners are stored here. 1618 * (also from jsninja.com, slightly modified and updated for closure compiler) 1619 * 1620 * @type {Object} 1621 * @private 1622 */ 1623 1624 1625 var DomData = window$3.WeakMap ? new WeakMap() : new FakeWeakMap(); 1626 1627 /** 1628 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) 1629 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) 1630 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as 1631 * robust as jquery's, so there's probably some differences. 1632 * 1633 * @file events.js 1634 * @module events 1635 */ 1636 /** 1637 * Clean up the listener cache and dispatchers 1638 *
1639 * @param {Element|Object} elem 1640 * Element to clean up 1641 * 1642 * @param {string} type 1643 * Type of event to clean up 1644 */ 1645 1646 function _cleanUpEvents(elem, type) { 1647 if (!DomData.has(elem)) { 1648 return; 1649 } 1650 1651 var data = DomData.get(elem); // Remove the events of a particular type if there are none left 1652 1653 if (data.handlers[type].length === 0) { 1654 delete data.handlers[type]; // data.handlers[type] = null; 1655 // Setting to null was causing an error with data.handlers 1656 // Remove the meta-handler from the element 1657 1658 if (elem.removeEventListener) { 1659 elem.removeEventListener(type, data.dispatcher, false); 1660 } else if (elem.detachEvent) { 1661 elem.detachEvent('on' + type, data.dispatcher); 1662 } 1663 } // Remove the events object if there are no types left 1664 1665 1666 if (Object.getOwnPropertyNames(data.handlers).length <= 0) { 1667 delete data.handlers; 1668 delete data.dispatcher; 1669 delete data.disabled; 1670 } // Finally remove the element data if there is no data left 1671 1672 1673 if (Object.getOwnPropertyNames(data).length === 0) { 1674 DomData["delete"](elem); 1675 } 1676 } 1677 /** 1678 * Loops through an array of event types and calls the requested method for each type. 1679 * 1680 * @param {Function} fn 1681 * The event method we want to use. 1682 * 1683 * @param {Element|Object} elem 1684 * Element or object to bind listeners to 1685 * 1686 * @param {string} type 1687 * Type of event to bind to. 1688 * 1689 * @param {EventTarget~EventListener} callback 1690 * Event listener. 1691 */ 1692 1693 1694 function _handleMultipleEvents(fn, elem, types, callback) { 1695 types.forEach(function (type) { 1696 // Call the event method for each one of the types 1697 fn(elem, type, callback); 1698 }); 1699 } 1700 /** 1701 * Fix a native event to have standard property values 1702 * 1703 * @param {Object} event 1704 * Event object to fix. 1705 * 1706 * @return {Object} 1707 * Fixed event object. 1708 */ 1709 1710 1711 function fixEvent(event) { 1712 if (event.fixed_) { 1713 return event; 1714 } 1715 1716 function returnTrue() { 1717 return true; 1718 } 1719 1720 function returnFalse() { 1721 return false; 1722 } // Test if fixing up is needed 1723 // Used to check if !event.stopPropagation instead of isPropagationStopped 1724 // But native events return true for stopPropagation, but don't have 1725 // other expected methods like isPropagationStopped. Seems to be a problem 1726 // with the Javascript Ninja code. So we're just overriding all events now. 1727 1728 1729 if (!event || !event.isPropagationStopped) { 1730 var old = event || window$3.event; 1731 event = {}; // Clone the old object so that we can modify the values event = {}; 1732 // IE8 Doesn't like when you mess with native event properties 1733 // Firefox returns false for event.hasOwnProperty('type') and other props 1734 // which makes copying more difficult. 1735 // TODO: Probably best to create a whitelist of event props 1736 1737 for (var key in old) { 1738 // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y 1739 // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation 1740 // and webkitMovementX/Y 1741 if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { 1742 // Chrome 32+ warns if you try to copy deprecated returnValue, but 1743 // we still want to if preventDefault isn't supported (IE8). 1744 if (!(key === 'returnValue' && old.preventDefault)) { 1745 event[key] = old[key]; 1746 } 1747 } 1748 } // The event occurred on this element 1749 1750 1751 if (!event.target) { 1752 event.target = event.srcElement || document; 1753 } // Handle which other element the event is related to 1754 1755 1756 if (!event.relatedTarget) { 1757 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 1758 } // Stop the default browser action 1759 1760 1761 event.preventDefault = function () { 1762 if (old.preventDefault) { 1763 old.preventDefault(); 1764 } 1765 1766 event.returnValue = false;
vendor: 4,748 bytes, lines 1767-1924
1767 old.returnValue = false; 1768 event.defaultPrevented = true; 1769 }; 1770 1771 event.defaultPrevented = false; // Stop the event from bubbling 1772 1773 event.stopPropagation = function () { 1774 if (old.stopPropagation) { 1775 old.stopPropagation(); 1776 } 1777 1778 event.cancelBubble = true; 1779 old.cancelBubble = true; 1780 event.isPropagationStopped = returnTrue; 1781 }; 1782 1783 event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers 1784 1785 event.stopImmediatePropagation = function () { 1786 if (old.stopImmediatePropagation) { 1787 old.stopImmediatePropagation(); 1788 } 1789 1790 event.isImmediatePropagationStopped = returnTrue; 1791 event.stopPropagation(); 1792 }; 1793 1794 event.isImmediatePropagationStopped = returnFalse; // Handle mouse position 1795 1796 if (event.clientX !== null && event.clientX !== undefined) { 1797 var doc = document.documentElement; 1798 var body = document.body; 1799 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 1800 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 1801 } // Handle key presses 1802 1803 1804 event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: 1805 // 0 == left; 1 == middle; 2 == right 1806 1807 if (event.button !== null && event.button !== undefined) { 1808 // The following is disabled because it does not pass videojs-standard 1809 // and... yikes. 1810 1811 /* eslint-disable */ 1812 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; 1813 /* eslint-enable */ 1814 } 1815 } 1816 1817 event.fixed_ = true; // Returns fixed-up instance 1818 1819 return event; 1820 } 1821 /** 1822 * Whether passive event listeners are supported 1823 */ 1824 1825 var _supportsPassive; 1826 1827 var supportsPassive = function supportsPassive() { 1828 if (typeof _supportsPassive !== 'boolean') { 1829 _supportsPassive = false; 1830 1831 try { 1832 var opts = Object.defineProperty({}, 'passive', { 1833 get: function get() { 1834 _supportsPassive = true; 1835 } 1836 }); 1837 window$3.addEventListener('test', null, opts); 1838 window$3.removeEventListener('test', null, opts); 1839 } catch (e) {// disregard 1840 } 1841 } 1842 1843 return _supportsPassive; 1844 }; 1845 /** 1846 * Touch events Chrome expects to be passive 1847 */ 1848 1849 1850 var passiveEvents = ['touchstart', 'touchmove']; 1851 /** 1852 * Add an event listener to element 1853 * It stores the handler function in a separate cache object 1854 * and adds a generic handler to the element's event, 1855 * along with a unique id (guid) to the element. 1856 * 1857 * @param {Element|Object} elem 1858 * Element or object to bind listeners to 1859 * 1860 * @param {string|string[]} type 1861 * Type of event to bind to. 1862 * 1863 * @param {EventTarget~EventListener} fn 1864 * Event listener. 1865 */ 1866 1867 function on(elem, type, fn) { 1868 if (Array.isArray(type)) { 1869 return _handleMultipleEvents(on, elem, type, fn); 1870 } 1871 1872 if (!DomData.has(elem)) { 1873 DomData.set(elem, {}); 1874 } 1875 1876 var data = DomData.get(elem); // We need a place to store all our handler data 1877 1878 if (!data.handlers) { 1879 data.handlers = {}; 1880 } 1881 1882 if (!data.handlers[type]) { 1883 data.handlers[type] = []; 1884 } 1885 1886 if (!fn.guid) { 1887 fn.guid = newGUID(); 1888 } 1889 1890 data.handlers[type].push(fn); 1891 1892 if (!data.dispatcher) { 1893 data.disabled = false; 1894 1895 data.dispatcher = function (event, hash) { 1896 if (data.disabled) { 1897 return; 1898 } 1899 1900 event = fixEvent(event); 1901 var handlers = data.handlers[event.type]; 1902 1903 if (handlers) { 1904 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 1905 var handlersCopy = handlers.slice(0); 1906 1907 for (var m = 0, n = handlersCopy.length; m < n; m++) { 1908 if (event.isImmediatePropagationStopped()) { 1909 break; 1910 } else { 1911 try { 1912 handlersCopy[m].call(elem, event, hash); 1913 } catch (e) { 1914 log.error(e); 1915 } 1916 } 1917 } 1918 } 1919 }; 1920 } 1921 1922 if (data.handlers[type].length === 1) { 1923 if (elem.addEventListener) { 1924 var options = false;
vendor: 11,738 bytes, lines 1925-2313
1925 1926 if (supportsPassive() && passiveEvents.indexOf(type) > -1) { 1927 options = { 1928 passive: true 1929 }; 1930 } 1931 1932 elem.addEventListener(type, data.dispatcher, options); 1933 } else if (elem.attachEvent) { 1934 elem.attachEvent('on' + type, data.dispatcher); 1935 } 1936 } 1937 } 1938 /** 1939 * Removes event listeners from an element 1940 * 1941 * @param {Element|Object} elem 1942 * Object to remove listeners from. 1943 * 1944 * @param {string|string[]} [type] 1945 * Type of listener to remove. Don't include to remove all events from element. 1946 * 1947 * @param {EventTarget~EventListener} [fn] 1948 * Specific listener to remove. Don't include to remove listeners for an event 1949 * type. 1950 */ 1951 1952 function off(elem, type, fn) { 1953 // Don't want to add a cache object through getElData if not needed 1954 if (!DomData.has(elem)) { 1955 return; 1956 } 1957 1958 var data = DomData.get(elem); // If no events exist, nothing to unbind 1959 1960 if (!data.handlers) { 1961 return; 1962 } 1963 1964 if (Array.isArray(type)) { 1965 return _handleMultipleEvents(off, elem, type, fn); 1966 } // Utility function 1967 1968 1969 var removeType = function removeType(el, t) { 1970 data.handlers[t] = []; 1971 1972 _cleanUpEvents(el, t); 1973 }; // Are we removing all bound events? 1974 1975 1976 if (type === undefined) { 1977 for (var t in data.handlers) { 1978 if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { 1979 removeType(elem, t); 1980 } 1981 } 1982 1983 return; 1984 } 1985 1986 var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind 1987 1988 if (!handlers) { 1989 return; 1990 } // If no listener was provided, remove all listeners for type 1991 1992 1993 if (!fn) { 1994 removeType(elem, type); 1995 return; 1996 } // We're only removing a single handler 1997 1998 1999 if (fn.guid) { 2000 for (var n = 0; n < handlers.length; n++) { 2001 if (handlers[n].guid === fn.guid) { 2002 handlers.splice(n--, 1); 2003 } 2004 } 2005 } 2006 2007 _cleanUpEvents(elem, type); 2008 } 2009 /** 2010 * Trigger an event for an element 2011 * 2012 * @param {Element|Object} elem 2013 * Element to trigger an event on 2014 * 2015 * @param {EventTarget~Event|string} event 2016 * A string (the type) or an event object with a type attribute 2017 * 2018 * @param {Object} [hash] 2019 * data hash to pass along with the event 2020 * 2021 * @return {boolean|undefined} 2022 * Returns the opposite of `defaultPrevented` if default was 2023 * prevented. Otherwise, returns `undefined` 2024 */ 2025 2026 function trigger(elem, event, hash) { 2027 // Fetches element data and a reference to the parent (for bubbling). 2028 // Don't want to add a data object to cache for every parent, 2029 // so checking hasElData first. 2030 var elemData = DomData.has(elem) ? DomData.get(elem) : {}; 2031 var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, 2032 // handler; 2033 // If an event name was passed as a string, creates an event out of it 2034 2035 if (typeof event === 'string') { 2036 event = { 2037 type: event, 2038 target: elem 2039 }; 2040 } else if (!event.target) { 2041 event.target = elem; 2042 } // Normalizes the event properties. 2043 2044 2045 event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. 2046 2047 if (elemData.dispatcher) { 2048 elemData.dispatcher.call(elem, event, hash); 2049 } // Unless explicitly stopped or the event does not bubble (e.g. media events) 2050 // recursively calls this function to bubble the event up the DOM. 2051 2052 2053 if (parent && !event.isPropagationStopped() && event.bubbles === true) { 2054 trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled. 2055 } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) { 2056 if (!DomData.has(event.target)) { 2057 DomData.set(event.target, {}); 2058 } 2059 2060 var targetData = DomData.get(event.target); // Checks if the target has a default action for this event. 2061 2062 if (event.target[event.type]) { 2063 // Temporarily disables event dispatching on the target as we have already executed the handler. 2064 targetData.disabled = true; // Executes the default action. 2065 2066 if (typeof event.target[event.type] === 'function') { 2067 event.target[event.type](); 2068 } // Re-enables event dispatching. 2069 2070 2071 targetData.disabled = false; 2072 } 2073 } // Inform the triggerer if the default was prevented by returning false 2074 2075 2076 return !event.defaultPrevented; 2077 } 2078 /** 2079 * Trigger a listener only once for an event. 2080 * 2081 * @param {Element|Object} elem 2082 * Element or object to bind to. 2083 * 2084 * @param {string|string[]} type 2085 * Name/type of event 2086 * 2087 * @param {Event~EventListener} fn 2088 * Event listener function 2089 */ 2090 2091 function one(elem, type, fn) { 2092 if (Array.isArray(type)) { 2093 return _handleMultipleEvents(one, elem, type, fn); 2094 } 2095 2096 var func = function func() { 2097 off(elem, type, func); 2098 fn.apply(this, arguments); 2099 }; // copy the guid to the new function so it can removed using the original function's ID 2100 2101 2102 func.guid = fn.guid = fn.guid || newGUID(); 2103 on(elem, type, func); 2104 } 2105 /** 2106 * Trigger a listener only once and then turn if off for all 2107 * configured events 2108 * 2109 * @param {Element|Object} elem 2110 * Element or object to bind to. 2111 * 2112 * @param {string|string[]} type 2113 * Name/type of event 2114 * 2115 * @param {Event~EventListener} fn 2116 * Event listener function 2117 */ 2118 2119 function any(elem, type, fn) { 2120 var func = function func() { 2121 off(elem, type, func); 2122 fn.apply(this, arguments); 2123 }; // copy the guid to the new function so it can removed using the original function's ID 2124 2125 2126 func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything 2127 2128 on(elem, type, func); 2129 } 2130 2131 var Events = /*#__PURE__*/Object.freeze({ 2132 __proto__: null, 2133 fixEvent: fixEvent, 2134 on: on, 2135 off: off, 2136 trigger: trigger, 2137 one: one, 2138 any: any 2139 }); 2140 2141 /** 2142 * @file fn.js 2143 * @module fn 2144 */ 2145 var UPDATE_REFRESH_INTERVAL = 30; 2146 /** 2147 * Bind (a.k.a proxy or context). A simple method for changing the context of 2148 * a function. 2149 * 2150 * It also stores a unique id on the function so it can be easily removed from 2151 * events. 2152 * 2153 * @function 2154 * @param {Mixed} context 2155 * The object to bind as scope. 2156 * 2157 * @param {Function} fn 2158 * The function to be bound to a scope. 2159 * 2160 * @param {number} [uid] 2161 * An optional unique ID for the function to be set 2162 * 2163 * @return {Function} 2164 * The new function that will be bound into the context given 2165 */ 2166 2167 var bind = function bind(context, fn, uid) { 2168 // Make sure the function has a unique ID 2169 if (!fn.guid) { 2170 fn.guid = newGUID(); 2171 } // Create the new function that changes the context 2172 2173 2174 var bound = fn.bind(context); // Allow for the ability to individualize this function 2175 // Needed in the case where multiple objects might share the same prototype 2176 // IF both items add an event listener with the same function, then you try to remove just one 2177 // it will remove both because they both have the same guid. 2178 // when using this, you need to use the bind method when you remove the listener as well. 2179 // currently used in text tracks 2180 2181 bound.guid = uid ? uid + '_' + fn.guid : fn.guid; 2182 return bound; 2183 }; 2184 /** 2185 * Wraps the given function, `fn`, with a new function that only invokes `fn` 2186 * at most once per every `wait` milliseconds. 2187 * 2188 * @function 2189 * @param {Function} fn 2190 * The function to be throttled. 2191 * 2192 * @param {number} wait 2193 * The number of milliseconds by which to throttle. 2194 * 2195 * @return {Function} 2196 */ 2197 2198 var throttle = function throttle(fn, wait) { 2199 var last = window$3.performance.now(); 2200 2201 var throttled = function throttled() { 2202 var now = window$3.performance.now(); 2203 2204 if (now - last >= wait) { 2205 fn.apply(void 0, arguments); 2206 last = now; 2207 } 2208 }; 2209 2210 return throttled; 2211 }; 2212 /** 2213 * Creates a debounced function that delays invoking `func` until after `wait` 2214 * milliseconds have elapsed since the last time the debounced function was 2215 * invoked. 2216 * 2217 * Inspired by lodash and underscore implementations. 2218 * 2219 * @function 2220 * @param {Function} func 2221 * The function to wrap with debounce behavior. 2222 * 2223 * @param {number} wait 2224 * The number of milliseconds to wait after the last invocation. 2225 * 2226 * @param {boolean} [immediate] 2227 * Whether or not to invoke the function immediately upon creation. 2228 * 2229 * @param {Object} [context=window] 2230 * The "context" in which the debounced function should debounce. For 2231 * example, if this function should be tied to a Video.js player, 2232 * the player can be passed here. Alternatively, defaults to the 2233 * global `window` object. 2234 * 2235 * @return {Function} 2236 * A debounced function. 2237 */ 2238 2239 var debounce = function debounce(func, wait, immediate, context) { 2240 if (context === void 0) { 2241 context = window$3; 2242 } 2243 2244 var timeout; 2245 2246 var cancel = function cancel() { 2247 context.clearTimeout(timeout); 2248 timeout = null; 2249 }; 2250 /* eslint-disable consistent-this */ 2251 2252 2253 var debounced = function debounced() { 2254 var self = this; 2255 var args = arguments; 2256 2257 var _later = function later() { 2258 timeout = null; 2259 _later = null; 2260 2261 if (!immediate) { 2262 func.apply(self, args); 2263 } 2264 }; 2265 2266 if (!timeout && immediate) { 2267 func.apply(self, args); 2268 } 2269 2270 context.clearTimeout(timeout); 2271 timeout = context.setTimeout(_later, wait); 2272 }; 2273 /* eslint-enable consistent-this */ 2274 2275 2276 debounced.cancel = cancel; 2277 return debounced; 2278 }; 2279 2280 /** 2281 * @file src/js/event-target.js 2282 */ 2283 /** 2284 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It 2285 * adds shorthand functions that wrap around lengthy functions. For example: 2286 * the `on` function is a wrapper around `addEventListener`. 2287 * 2288 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} 2289 * @class EventTarget 2290 */ 2291 2292 var EventTarget = function EventTarget() {}; 2293 /** 2294 * A Custom DOM event. 2295 * 2296 * @typedef {Object} EventTarget~Event 2297 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} 2298 */ 2299 2300 /** 2301 * All event listeners should follow the following format. 2302 * 2303 * @callback EventTarget~EventListener 2304 * @this {EventTarget} 2305 * 2306 * @param {EventTarget~Event} event 2307 * the event that triggered this function 2308 * 2309 * @param {Object} [hash] 2310 * hash of data sent during the event 2311 */ 2312 2313 /**
2314 * An object containing event names as keys and booleans as values. 2315 * 2316 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} 2317 * will have extra functionality. See that function for more information. 2318 * 2319 * @property EventTarget.prototype.allowedEvents_ 2320 * @private 2321 */ 2322 2323 2324 EventTarget.prototype.allowedEvents_ = {}; 2325 /** 2326 * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a 2327 * function that will get called when an event with a certain name gets triggered. 2328 * 2329 * @param {string|string[]} type 2330 * An event name or an array of event names. 2331 * 2332 * @param {EventTarget~EventListener} fn 2333 * The function to call with `EventTarget`s 2334 */ 2335 2336 EventTarget.prototype.on = function (type, fn) { 2337 // Remove the addEventListener alias before calling Events.on 2338 // so we don't get into an infinite type loop 2339 var ael = this.addEventListener; 2340 2341 this.addEventListener = function () {}; 2342 2343 on(this, type, fn); 2344 this.addEventListener = ael; 2345 }; 2346 /** 2347 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic 2348 * the standard DOM API. 2349 * 2350 * @function 2351 * @see {@link EventTarget#on} 2352 */ 2353 2354 2355 EventTarget.prototype.addEventListener = EventTarget.prototype.on; 2356 /** 2357 * Removes an `event listener` for a specific event from an instance of `EventTarget`. 2358 * This makes it so that the `event listener` will no longer get called when the 2359 * named event happens. 2360 * 2361 * @param {string|string[]} type 2362 * An event name or an array of event names. 2363 * 2364 * @param {EventTarget~EventListener} fn 2365 * The function to remove. 2366 */ 2367 2368 EventTarget.prototype.off = function (type, fn) { 2369 off(this, type, fn); 2370 }; 2371 /** 2372 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic 2373 * the standard DOM API. 2374 * 2375 * @function 2376 * @see {@link EventTarget#off} 2377 */ 2378 2379 2380 EventTarget.prototype.removeEventListener = EventTarget.prototype.off; 2381 /** 2382 * This function will add an `event listener` that gets triggered only once. After the 2383 * first trigger it will get removed. This is like adding an `event listener` 2384 * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. 2385 * 2386 * @param {string|string[]} type 2387 * An event name or an array of event names. 2388 * 2389 * @param {EventTarget~EventListener} fn 2390 * The function to be called once for each event name. 2391 */ 2392 2393 EventTarget.prototype.one = function (type, fn) { 2394 // Remove the addEventListener aliasing Events.on 2395 // so we don't get into an infinite type loop 2396 var ael = this.addEventListener; 2397 2398 this.addEventListener = function () {}; 2399 2400 one(this, type, fn); 2401 this.addEventListener = ael; 2402 }; 2403 2404 EventTarget.prototype.any = function (type, fn) { 2405 // Remove the addEventListener aliasing Events.on 2406 // so we don't get into an infinite type loop 2407 var ael = this.addEventListener; 2408 2409 this.addEventListener = function () {}; 2410 2411 any(this, type, fn); 2412 this.addEventListener = ael; 2413 }; 2414 /** 2415 * This function causes an event to happen. This will then cause any `event listeners` 2416 * that are waiting for that event, to get called. If there are no `event listeners` 2417 * for an event then nothing will happen. 2418 * 2419 * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. 2420 * Trigger will also call the `on` + `uppercaseEventName` function. 2421 * 2422 * Example: 2423 * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call 2424 * `onClick` if it exists. 2425 * 2426 * @param {string|EventTarget~Event|Object} event 2427 * The name of the event, an `Event`, or an object with a key of type set to 2428 * an event name. 2429 */ 2430 2431 2432 EventTarget.prototype.trigger = function (event) { 2433 var type = event.type || event; // deprecation 2434 // In a future version we should default target to `this` 2435 // similar to how we default the target to `elem` in 2436 // `Events.trigger`. Right now the default `target` will be 2437 // `document` due to the `Event.fixEvent` call. 2438 2439 if (typeof event === 'string') { 2440 event = { 2441 type: type 2442 }; 2443 } 2444 2445 event = fixEvent(event); 2446 2447 if (this.allowedEvents_[type] && this['on' + type]) { 2448 this['on' + type](event); 2449 } 2450 2451 trigger(this, event); 2452 }; 2453 /** 2454 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic 2455 * the standard DOM API. 2456 * 2457 * @function 2458 * @see {@link EventTarget#trigger} 2459 */ 2460 2461 2462 EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; 2463 var EVENT_MAP; 2464
vendor: 24,124 bytes, lines 2465-3178
2465 EventTarget.prototype.queueTrigger = function (event) { 2466 var _this = this; 2467 2468 // only set up EVENT_MAP if it'll be used 2469 if (!EVENT_MAP) { 2470 EVENT_MAP = new Map(); 2471 } 2472 2473 var type = event.type || event; 2474 var map = EVENT_MAP.get(this); 2475 2476 if (!map) { 2477 map = new Map(); 2478 EVENT_MAP.set(this, map); 2479 } 2480 2481 var oldTimeout = map.get(type); 2482 map["delete"](type); 2483 window$3.clearTimeout(oldTimeout); 2484 var timeout = window$3.setTimeout(function () { 2485 // if we cleared out all timeouts for the current target, delete its map 2486 if (map.size === 0) { 2487 map = null; 2488 EVENT_MAP["delete"](_this); 2489 } 2490 2491 _this.trigger(event); 2492 }, 0); 2493 map.set(type, timeout); 2494 }; 2495 2496 /** 2497 * @file mixins/evented.js 2498 * @module evented 2499 */ 2500 /** 2501 * Returns whether or not an object has had the evented mixin applied. 2502 * 2503 * @param {Object} object 2504 * An object to test. 2505 * 2506 * @return {boolean} 2507 * Whether or not the object appears to be evented. 2508 */ 2509 2510 var isEvented = function isEvented(object) { 2511 return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { 2512 return typeof object[k] === 'function'; 2513 }); 2514 }; 2515 /** 2516 * Adds a callback to run after the evented mixin applied. 2517 * 2518 * @param {Object} object 2519 * An object to Add 2520 * @param {Function} callback 2521 * The callback to run. 2522 */ 2523 2524 2525 var addEventedCallback = function addEventedCallback(target, callback) { 2526 if (isEvented(target)) { 2527 callback(); 2528 } else { 2529 if (!target.eventedCallbacks) { 2530 target.eventedCallbacks = []; 2531 } 2532 2533 target.eventedCallbacks.push(callback); 2534 } 2535 }; 2536 /** 2537 * Whether a value is a valid event type - non-empty string or array. 2538 * 2539 * @private 2540 * @param {string|Array} type 2541 * The type value to test. 2542 * 2543 * @return {boolean} 2544 * Whether or not the type is a valid event type. 2545 */ 2546 2547 2548 var isValidEventType = function isValidEventType(type) { 2549 return (// The regex here verifies that the `type` contains at least one non- 2550 // whitespace character. 2551 typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length 2552 ); 2553 }; 2554 /** 2555 * Validates a value to determine if it is a valid event target. Throws if not. 2556 * 2557 * @private 2558 * @throws {Error} 2559 * If the target does not appear to be a valid event target. 2560 * 2561 * @param {Object} target 2562 * The object to test. 2563 */ 2564 2565 2566 var validateTarget = function validateTarget(target) { 2567 if (!target.nodeName && !isEvented(target)) { 2568 throw new Error('Invalid target; must be a DOM node or evented object.'); 2569 } 2570 }; 2571 /** 2572 * Validates a value to determine if it is a valid event target. Throws if not. 2573 * 2574 * @private 2575 * @throws {Error} 2576 * If the type does not appear to be a valid event type. 2577 * 2578 * @param {string|Array} type 2579 * The type to test. 2580 */ 2581 2582 2583 var validateEventType = function validateEventType(type) { 2584 if (!isValidEventType(type)) { 2585 throw new Error('Invalid event type; must be a non-empty string or array.'); 2586 } 2587 }; 2588 /** 2589 * Validates a value to determine if it is a valid listener. Throws if not. 2590 * 2591 * @private 2592 * @throws {Error} 2593 * If the listener is not a function. 2594 * 2595 * @param {Function} listener 2596 * The listener to test. 2597 */ 2598 2599 2600 var validateListener = function validateListener(listener) { 2601 if (typeof listener !== 'function') { 2602 throw new Error('Invalid listener; must be a function.'); 2603 } 2604 }; 2605 /** 2606 * Takes an array of arguments given to `on()` or `one()`, validates them, and 2607 * normalizes them into an object. 2608 * 2609 * @private 2610 * @param {Object} self 2611 * The evented object on which `on()` or `one()` was called. This 2612 * object will be bound as the `this` value for the listener. 2613 * 2614 * @param {Array} args 2615 * An array of arguments passed to `on()` or `one()`. 2616 * 2617 * @return {Object} 2618 * An object containing useful values for `on()` or `one()` calls. 2619 */ 2620 2621 2622 var normalizeListenArgs = function normalizeListenArgs(self, args) { 2623 // If the number of arguments is less than 3, the target is always the 2624 // evented object itself. 2625 var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; 2626 var target; 2627 var type; 2628 var listener; 2629 2630 if (isTargetingSelf) { 2631 target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to 2632 // the evented object itself. 2633 2634 if (args.length >= 3) { 2635 args.shift(); 2636 } 2637 2638 type = args[0]; 2639 listener = args[1]; 2640 } else { 2641 target = args[0]; 2642 type = args[1]; 2643 listener = args[2]; 2644 } 2645 2646 validateTarget(target); 2647 validateEventType(type); 2648 validateListener(listener); 2649 listener = bind(self, listener); 2650 return { 2651 isTargetingSelf: isTargetingSelf, 2652 target: target, 2653 type: type, 2654 listener: listener 2655 }; 2656 }; 2657 /** 2658 * Adds the listener to the event type(s) on the target, normalizing for 2659 * the type of target. 2660 * 2661 * @private 2662 * @param {Element|Object} target 2663 * A DOM node or evented object. 2664 * 2665 * @param {string} method 2666 * The event binding method to use ("on" or "one"). 2667 * 2668 * @param {string|Array} type 2669 * One or more event type(s). 2670 * 2671 * @param {Function} listener 2672 * A listener function. 2673 */ 2674 2675 2676 var listen = function listen(target, method, type, listener) { 2677 validateTarget(target); 2678 2679 if (target.nodeName) { 2680 Events[method](target, type, listener); 2681 } else { 2682 target[method](type, listener); 2683 } 2684 }; 2685 /** 2686 * Contains methods that provide event capabilities to an object which is passed 2687 * to {@link module:evented|evented}. 2688 * 2689 * @mixin EventedMixin 2690 */ 2691 2692 2693 var EventedMixin = { 2694 /** 2695 * Add a listener to an event (or events) on this object or another evented 2696 * object. 2697 * 2698 * @param {string|Array|Element|Object} targetOrType 2699 * If this is a string or array, it represents the event type(s) 2700 * that will trigger the listener. 2701 * 2702 * Another evented object can be passed here instead, which will 2703 * cause the listener to listen for events on _that_ object. 2704 * 2705 * In either case, the listener's `this` value will be bound to 2706 * this object. 2707 * 2708 * @param {string|Array|Function} typeOrListener 2709 * If the first argument was a string or array, this should be the 2710 * listener function. Otherwise, this is a string or array of event 2711 * type(s). 2712 * 2713 * @param {Function} [listener] 2714 * If the first argument was another evented object, this will be 2715 * the listener function. 2716 */ 2717 on: function on() { 2718 var _this = this; 2719 2720 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 2721 args[_key] = arguments[_key]; 2722 } 2723 2724 var _normalizeListenArgs = normalizeListenArgs(this, args), 2725 isTargetingSelf = _normalizeListenArgs.isTargetingSelf, 2726 target = _normalizeListenArgs.target, 2727 type = _normalizeListenArgs.type, 2728 listener = _normalizeListenArgs.listener; 2729 2730 listen(target, 'on', type, listener); // If this object is listening to another evented object. 2731 2732 if (!isTargetingSelf) { 2733 // If this object is disposed, remove the listener. 2734 var removeListenerOnDispose = function removeListenerOnDispose() { 2735 return _this.off(target, type, listener); 2736 }; // Use the same function ID as the listener so we can remove it later it 2737 // using the ID of the original listener. 2738 2739 2740 removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures 2741 // that if the target is disposed BEFORE this object, we remove the 2742 // removal listener that was just added. Otherwise, we create a memory leak. 2743 2744 var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { 2745 return _this.off('dispose', removeListenerOnDispose); 2746 }; // Use the same function ID as the listener so we can remove it later 2747 // it using the ID of the original listener. 2748 2749 2750 removeRemoverOnTargetDispose.guid = listener.guid; 2751 listen(this, 'on', 'dispose', removeListenerOnDispose); 2752 listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); 2753 } 2754 }, 2755 2756 /** 2757 * Add a listener to an event (or events) on this object or another evented 2758 * object. The listener will be called once per event and then removed. 2759 * 2760 * @param {string|Array|Element|Object} targetOrType 2761 * If this is a string or array, it represents the event type(s) 2762 * that will trigger the listener. 2763 * 2764 * Another evented object can be passed here instead, which will 2765 * cause the listener to listen for events on _that_ object. 2766 * 2767 * In either case, the listener's `this` value will be bound to 2768 * this object. 2769 * 2770 * @param {string|Array|Function} typeOrListener 2771 * If the first argument was a string or array, this should be the 2772 * listener function. Otherwise, this is a string or array of event 2773 * type(s). 2774 * 2775 * @param {Function} [listener] 2776 * If the first argument was another evented object, this will be 2777 * the listener function. 2778 */ 2779 one: function one() { 2780 var _this2 = this; 2781 2782 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 2783 args[_key2] = arguments[_key2]; 2784 } 2785 2786 var _normalizeListenArgs2 = normalizeListenArgs(this, args), 2787 isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, 2788 target = _normalizeListenArgs2.target, 2789 type = _normalizeListenArgs2.type, 2790 listener = _normalizeListenArgs2.listener; // Targeting this evented object. 2791 2792 2793 if (isTargetingSelf) { 2794 listen(target, 'one', type, listener); // Targeting another evented object. 2795 } else { 2796 // TODO: This wrapper is incorrect! It should only 2797 // remove the wrapper for the event type that called it. 2798 // Instead all listners are removed on the first trigger! 2799 // see https://github.com/videojs/video.js/issues/5962 2800 var wrapper = function wrapper() { 2801 _this2.off(target, type, wrapper); 2802 2803 for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 2804 largs[_key3] = arguments[_key3]; 2805 } 2806 2807 listener.apply(null, largs); 2808 }; // Use the same function ID as the listener so we can remove it later 2809 // it using the ID of the original listener. 2810 2811 2812 wrapper.guid = listener.guid; 2813 listen(target, 'one', type, wrapper); 2814 } 2815 }, 2816 2817 /** 2818 * Add a listener to an event (or events) on this object or another evented 2819 * object. The listener will only be called once for the first event that is triggered 2820 * then removed. 2821 * 2822 * @param {string|Array|Element|Object} targetOrType 2823 * If this is a string or array, it represents the event type(s) 2824 * that will trigger the listener. 2825 * 2826 * Another evented object can be passed here instead, which will 2827 * cause the listener to listen for events on _that_ object. 2828 * 2829 * In either case, the listener's `this` value will be bound to 2830 * this object. 2831 * 2832 * @param {string|Array|Function} typeOrListener 2833 * If the first argument was a string or array, this should be the 2834 * listener function. Otherwise, this is a string or array of event 2835 * type(s). 2836 * 2837 * @param {Function} [listener] 2838 * If the first argument was another evented object, this will be 2839 * the listener function. 2840 */ 2841 any: function any() { 2842 var _this3 = this; 2843 2844 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 2845 args[_key4] = arguments[_key4]; 2846 } 2847 2848 var _normalizeListenArgs3 = normalizeListenArgs(this, args), 2849 isTargetingSelf = _normalizeListenArgs3.isTargetingSelf, 2850 target = _normalizeListenArgs3.target, 2851 type = _normalizeListenArgs3.type, 2852 listener = _normalizeListenArgs3.listener; // Targeting this evented object. 2853 2854 2855 if (isTargetingSelf) { 2856 listen(target, 'any', type, listener); // Targeting another evented object. 2857 } else { 2858 var wrapper = function wrapper() { 2859 _this3.off(target, type, wrapper); 2860 2861 for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 2862 largs[_key5] = arguments[_key5]; 2863 } 2864 2865 listener.apply(null, largs); 2866 }; // Use the same function ID as the listener so we can remove it later 2867 // it using the ID of the original listener. 2868 2869 2870 wrapper.guid = listener.guid; 2871 listen(target, 'any', type, wrapper); 2872 } 2873 }, 2874 2875 /** 2876 * Removes listener(s) from event(s) on an evented object. 2877 * 2878 * @param {string|Array|Element|Object} [targetOrType] 2879 * If this is a string or array, it represents the event type(s). 2880 * 2881 * Another evented object can be passed here instead, in which case 2882 * ALL 3 arguments are _required_. 2883 * 2884 * @param {string|Array|Function} [typeOrListener] 2885 * If the first argument was a string or array, this may be the 2886 * listener function. Otherwise, this is a string or array of event 2887 * type(s). 2888 * 2889 * @param {Function} [listener] 2890 * If the first argument was another evented object, this will be 2891 * the listener function; otherwise, _all_ listeners bound to the 2892 * event type(s) will be removed. 2893 */ 2894 off: function off$1(targetOrType, typeOrListener, listener) { 2895 // Targeting this evented object. 2896 if (!targetOrType || isValidEventType(targetOrType)) { 2897 off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object. 2898 } else { 2899 var target = targetOrType; 2900 var type = typeOrListener; // Fail fast and in a meaningful way! 2901 2902 validateTarget(target); 2903 validateEventType(type); 2904 validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used 2905 2906 listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given 2907 // the same guid as the event listener in on(). 2908 2909 this.off('dispose', listener); 2910 2911 if (target.nodeName) { 2912 off(target, type, listener); 2913 off(target, 'dispose', listener); 2914 } else if (isEvented(target)) { 2915 target.off(type, listener); 2916 target.off('dispose', listener); 2917 } 2918 } 2919 }, 2920 2921 /** 2922 * Fire an event on this evented object, causing its listeners to be called. 2923 * 2924 * @param {string|Object} event 2925 * An event type or an object with a type property. 2926 * 2927 * @param {Object} [hash] 2928 * An additional object to pass along to listeners. 2929 * 2930 * @return {boolean} 2931 * Whether or not the default behavior was prevented. 2932 */ 2933 trigger: function trigger$1(event, hash) { 2934 return trigger(this.eventBusEl_, event, hash); 2935 } 2936 }; 2937 /** 2938 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. 2939 * 2940 * @param {Object} target 2941 * The object to which to add event methods. 2942 * 2943 * @param {Object} [options={}] 2944 * Options for customizing the mixin behavior. 2945 * 2946 * @param {string} [options.eventBusKey] 2947 * By default, adds a `eventBusEl_` DOM element to the target object, 2948 * which is used as an event bus. If the target object already has a 2949 * DOM element that should be used, pass its key here. 2950 * 2951 * @return {Object} 2952 * The target object. 2953 */ 2954 2955 function evented(target, options) { 2956 if (options === void 0) { 2957 options = {}; 2958 } 2959 2960 var _options = options, 2961 eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_. 2962 2963 if (eventBusKey) { 2964 if (!target[eventBusKey].nodeName) { 2965 throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element."); 2966 } 2967 2968 target.eventBusEl_ = target[eventBusKey]; 2969 } else { 2970 target.eventBusEl_ = createEl('span', { 2971 className: 'vjs-event-bus' 2972 }); 2973 } 2974 2975 assign(target, EventedMixin); 2976 2977 if (target.eventedCallbacks) { 2978 target.eventedCallbacks.forEach(function (callback) { 2979 callback(); 2980 }); 2981 } // When any evented object is disposed, it removes all its listeners. 2982 2983 2984 target.on('dispose', function () { 2985 target.off(); 2986 window$3.setTimeout(function () { 2987 target.eventBusEl_ = null; 2988 }, 0); 2989 }); 2990 return target; 2991 } 2992 2993 /** 2994 * @file mixins/stateful.js 2995 * @module stateful 2996 */ 2997 /** 2998 * Contains methods that provide statefulness to an object which is passed 2999 * to {@link module:stateful}. 3000 * 3001 * @mixin StatefulMixin 3002 */ 3003 3004 var StatefulMixin = { 3005 /** 3006 * A hash containing arbitrary keys and values representing the state of 3007 * the object. 3008 * 3009 * @type {Object} 3010 */ 3011 state: {}, 3012 3013 /** 3014 * Set the state of an object by mutating its 3015 * {@link module:stateful~StatefulMixin.state|state} object in place. 3016 * 3017 * @fires module:stateful~StatefulMixin#statechanged 3018 * @param {Object|Function} stateUpdates 3019 * A new set of properties to shallow-merge into the plugin state. 3020 * Can be a plain object or a function returning a plain object. 3021 * 3022 * @return {Object|undefined} 3023 * An object containing changes that occurred. If no changes 3024 * occurred, returns `undefined`. 3025 */ 3026 setState: function setState(stateUpdates) { 3027 var _this = this; 3028 3029 // Support providing the `stateUpdates` state as a function. 3030 if (typeof stateUpdates === 'function') { 3031 stateUpdates = stateUpdates(); 3032 } 3033 3034 var changes; 3035 each(stateUpdates, function (value, key) { 3036 // Record the change if the value is different from what's in the 3037 // current state. 3038 if (_this.state[key] !== value) { 3039 changes = changes || {}; 3040 changes[key] = { 3041 from: _this.state[key], 3042 to: value 3043 }; 3044 } 3045 3046 _this.state[key] = value; 3047 }); // Only trigger "statechange" if there were changes AND we have a trigger 3048 // function. This allows us to not require that the target object be an 3049 // evented object. 3050 3051 if (changes && isEvented(this)) { 3052 /** 3053 * An event triggered on an object that is both 3054 * {@link module:stateful|stateful} and {@link module:evented|evented} 3055 * indicating that its state has changed. 3056 * 3057 * @event module:stateful~StatefulMixin#statechanged 3058 * @type {Object} 3059 * @property {Object} changes 3060 * A hash containing the properties that were changed and 3061 * the values they were changed `from` and `to`. 3062 */ 3063 this.trigger({ 3064 changes: changes, 3065 type: 'statechanged' 3066 }); 3067 } 3068 3069 return changes; 3070 } 3071 }; 3072 /** 3073 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target 3074 * object. 3075 * 3076 * If the target object is {@link module:evented|evented} and has a 3077 * `handleStateChanged` method, that method will be automatically bound to the 3078 * `statechanged` event on itself. 3079 * 3080 * @param {Object} target 3081 * The object to be made stateful. 3082 * 3083 * @param {Object} [defaultState] 3084 * A default set of properties to populate the newly-stateful object's 3085 * `state` property. 3086 * 3087 * @return {Object} 3088 * Returns the `target`. 3089 */ 3090 3091 function stateful(target, defaultState) { 3092 assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state` 3093 // added in that step. 3094 3095 target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists. 3096 3097 if (typeof target.handleStateChanged === 'function' && isEvented(target)) { 3098 target.on('statechanged', target.handleStateChanged); 3099 } 3100 3101 return target; 3102 } 3103 3104 /** 3105 * @file string-cases.js 3106 * @module to-lower-case 3107 */ 3108 3109 /** 3110 * Lowercase the first letter of a string. 3111 * 3112 * @param {string} string 3113 * String to be lowercased 3114 * 3115 * @return {string} 3116 * The string with a lowercased first letter 3117 */ 3118 var toLowerCase = function toLowerCase(string) { 3119 if (typeof string !== 'string') { 3120 return string; 3121 } 3122 3123 return string.replace(/./, function (w) { 3124 return w.toLowerCase(); 3125 }); 3126 }; 3127 /** 3128 * Uppercase the first letter of a string. 3129 * 3130 * @param {string} string 3131 * String to be uppercased 3132 * 3133 * @return {string} 3134 * The string with an uppercased first letter 3135 */ 3136 3137 var toTitleCase = function toTitleCase(string) { 3138 if (typeof string !== 'string') { 3139 return string; 3140 } 3141 3142 return string.replace(/./, function (w) { 3143 return w.toUpperCase(); 3144 }); 3145 }; 3146 /** 3147 * Compares the TitleCase versions of the two strings for equality. 3148 * 3149 * @param {string} str1 3150 * The first string to compare 3151 * 3152 * @param {string} str2 3153 * The second string to compare 3154 * 3155 * @return {boolean} 3156 * Whether the TitleCase versions of the strings are equal 3157 */ 3158 3159 var titleCaseEquals = function titleCaseEquals(str1, str2) { 3160 return toTitleCase(str1) === toTitleCase(str2); 3161 }; 3162 3163 /** 3164 * @file merge-options.js 3165 * @module merge-options 3166 */ 3167 /** 3168 * Merge two objects recursively. 3169 * 3170 * Performs a deep merge like 3171 * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges 3172 * plain objects (not arrays, elements, or anything else). 3173 * 3174 * Non-plain object values will be copied directly from the right-most 3175 * argument. 3176 * 3177 * @static 3178 * @param {Object[]} sources
3179 * One or more objects to merge into a new object. 3180 * 3181 * @return {Object} 3182 * A new object that is the merged result of all sources. 3183 */ 3184 3185 function mergeOptions() { 3186 var result = {}; 3187 3188 for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) { 3189 sources[_key] = arguments[_key]; 3190 } 3191 3192 sources.forEach(function (source) { 3193 if (!source) { 3194 return; 3195 } 3196 3197 each(source, function (value, key) { 3198 if (!isPlain(value)) { 3199 result[key] = value; 3200 return; 3201 } 3202 3203 if (!isPlain(result[key])) { 3204 result[key] = {}; 3205 } 3206 3207 result[key] = mergeOptions(result[key], value); 3208 }); 3209 }); 3210 return result; 3211 } 3212 3213 /** 3214 * Player Component - Base class for all UI objects 3215 * 3216 * @file component.js 3217 */ 3218 /** 3219 * Base class for all UI Components. 3220 * Components are UI objects which represent both a javascript object and an element 3221 * in the DOM. They can be children of other components, and can have 3222 * children themselves. 3223 * 3224 * Components can also use methods from {@link EventTarget} 3225 */ 3226 3227 var Component = /*#__PURE__*/function () { 3228 /** 3229 * A callback that is called when a component is ready. Does not have any 3230 * paramters and any callback value will be ignored. 3231 * 3232 * @callback Component~ReadyCallback 3233 * @this Component 3234 */ 3235 3236 /** 3237 * Creates an instance of this class. 3238 * 3239 * @param {Player} player 3240 * The `Player` that this class should be attached to. 3241 * 3242 * @param {Object} [options] 3243 * The key/value store of player options. 3244 * 3245 * @param {Object[]} [options.children] 3246 * An array of children objects to intialize this component with. Children objects have 3247 * a name property that will be used if more than one component of the same type needs to be 3248 * added. 3249 * 3250 * @param {Component~ReadyCallback} [ready] 3251 * Function that gets called when the `Component` is ready. 3252 */ 3253 function Component(player, options, ready) { 3254 // The component might be the player itself and we can't pass `this` to super 3255 if (!player && this.play) { 3256 this.player_ = player = this; // eslint-disable-line 3257 } else { 3258 this.player_ = player; 3259 } 3260 3261 this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method 3262 3263 this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults 3264 3265 this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options 3266 3267 options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied 3268 3269 this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one 3270 3271 if (!this.id_) { 3272 // Don't require the player ID function in the case of mock players 3273 var id = player && player.id && player.id() || 'no_player'; 3274 this.id_ = id + "_component_" + newGUID(); 3275 } 3276 3277 this.name_ = options.name || null; // Create element if one wasn't provided in options 3278 3279 if (options.el) { 3280 this.el_ = options.el; 3281 } else if (options.createEl !== false) { 3282 this.el_ = this.createEl(); 3283 } // if evented is anything except false, we want to mixin in evented 3284 3285 3286 if (options.evented !== false) { 3287 // Make this an evented object and use `el_`, if available, as its event bus 3288 evented(this, { 3289 eventBusKey: this.el_ ? 'el_' : null 3290 }); 3291 } 3292 3293 stateful(this, this.constructor.defaultState); 3294 this.children_ = []; 3295 this.childIndex_ = {}; 3296 this.childNameIndex_ = {}; 3297 var SetSham; 3298 3299 if (!window$3.Set) { 3300 SetSham = /*#__PURE__*/function () { 3301 function SetSham() { 3302 this.set_ = {}; 3303 } 3304 3305 var _proto2 = SetSham.prototype; 3306 3307 _proto2.has = function has(key) { 3308 return key in this.set_; 3309 }; 3310 3311 _proto2["delete"] = function _delete(key) { 3312 var has = this.has(key); 3313 delete this.set_[key]; 3314 return has; 3315 }; 3316 3317 _proto2.add = function add(key) { 3318 this.set_[key] = 1; 3319 return this; 3320 }; 3321 3322 _proto2.forEach = function forEach(callback, thisArg) { 3323 for (var key in this.set_) { 3324 callback.call(thisArg, key, key, this); 3325 } 3326 }; 3327 3328 return SetSham; 3329 }(); 3330 } 3331 3332 this.setTimeoutIds_ = window$3.Set ? new Set() : new SetSham(); 3333 this.setIntervalIds_ = window$3.Set ? new Set() : new SetSham(); 3334 this.rafIds_ = window$3.Set ? new Set() : new SetSham(); 3335 this.clearingTimersOnDispose_ = false; // Add any child components in options 3336 3337 if (options.initChildren !== false) { 3338 this.initChildren(); 3339 } 3340 3341 this.ready(ready); // Don't want to trigger ready here or it will before init is actually 3342 // finished for all children that run this constructor 3343 3344 if (options.reportTouchActivity !== false) { 3345 this.enableTouchActivity(); 3346 } 3347 } 3348 /**
3349 * Dispose of the `Component` and all child components. 3350 * 3351 * @fires Component#dispose 3352 */ 3353 3354 3355 var _proto = Component.prototype; 3356 3357 _proto.dispose = function dispose() { 3358 // Bail out if the component has already been disposed. 3359 if (this.isDisposed_) { 3360 return; 3361 } 3362 /** 3363 * Triggered when a `Component` is disposed. 3364 * 3365 * @event Component#dispose 3366 * @type {EventTarget~Event} 3367 * 3368 * @property {boolean} [bubbles=false] 3369 * set to false so that the dispose event does not 3370 * bubble up 3371 */ 3372 3373 3374 this.trigger({ 3375 type: 'dispose', 3376 bubbles: false 3377 }); 3378 this.isDisposed_ = true; // Dispose all children. 3379 3380 if (this.children_) { 3381 for (var i = this.children_.length - 1; i >= 0; i--) { 3382 if (this.children_[i].dispose) { 3383 this.children_[i].dispose(); 3384 } 3385 } 3386 } // Delete child references 3387 3388 3389 this.children_ = null; 3390 this.childIndex_ = null; 3391 this.childNameIndex_ = null; 3392 this.parentComponent_ = null; 3393 3394 if (this.el_) { 3395 // Remove element from DOM 3396 if (this.el_.parentNode) { 3397 this.el_.parentNode.removeChild(this.el_); 3398 } 3399 3400 if (DomData.has(this.el_)) { 3401 DomData["delete"](this.el_); 3402 } 3403 3404 this.el_ = null; 3405 } // remove reference to the player after disposing of the element 3406 3407 3408 this.player_ = null; 3409 } 3410 /** 3411 * Determine whether or not this component has been disposed. 3412 * 3413 * @return {boolean} 3414 * If the component has been disposed, will be `true`. Otherwise, `false`. 3415 */ 3416 ; 3417 3418 _proto.isDisposed = function isDisposed() { 3419 return Boolean(this.isDisposed_); 3420 } 3421 /** 3422 * Return the {@link Player} that the `Component` has attached to. 3423 * 3424 * @return {Player} 3425 * The player that this `Component` has attached to. 3426 */ 3427 ; 3428 3429 _proto.player = function player() { 3430 return this.player_; 3431 } 3432 /** 3433 * Deep merge of options objects with new options. 3434 * > Note: When both `obj` and `options` contain properties whose values are objects. 3435 * The two properties get merged using {@link module:mergeOptions} 3436 * 3437 * @param {Object} obj 3438 * The object that contains new options. 3439 * 3440 * @return {Object} 3441 * A new object of `this.options_` and `obj` merged together. 3442 */ 3443 ; 3444 3445 _proto.options = function options(obj) { 3446 if (!obj) { 3447 return this.options_; 3448 } 3449 3450 this.options_ = mergeOptions(this.options_, obj); 3451 return this.options_; 3452 } 3453 /** 3454 * Get the `Component`s DOM element 3455 * 3456 * @return {Element} 3457 * The DOM element for this `Component`. 3458 */ 3459 ; 3460 3461 _proto.el = function el() { 3462 return this.el_; 3463 } 3464 /** 3465 * Create the `Component`s DOM element. 3466 * 3467 * @param {string} [tagName] 3468 * Element's DOM node type. e.g. 'div' 3469 * 3470 * @param {Object} [properties] 3471 * An object of properties that should be set. 3472 * 3473 * @param {Object} [attributes] 3474 * An object of attributes that should be set. 3475 * 3476 * @return {Element} 3477 * The element that gets created. 3478 */ 3479 ; 3480 3481 _proto.createEl = function createEl$1(tagName, properties, attributes) { 3482 return createEl(tagName, properties, attributes); 3483 } 3484 /** 3485 * Localize a string given the string in english. 3486 * 3487 * If tokens are provided, it'll try and run a simple token replacement on the provided string. 3488 * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. 3489 * 3490 * If a `defaultValue` is provided, it'll use that over `string`, 3491 * if a value isn't found in provided language files. 3492 * This is useful if you want to have a descriptive key for token replacement 3493 * but have a succinct localized string and not require `en.json` to be included. 3494 * 3495 * Currently, it is used for the progress bar timing. 3496 * ```js 3497 * { 3498 * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" 3499 * } 3500 * ``` 3501 * It is then used like so: 3502 * ```js 3503 * this.localize('progress bar timing: currentTime={1} duration{2}', 3504 * [this.player_.currentTime(), this.player_.duration()], 3505 * '{1} of {2}'); 3506 * ``` 3507 * 3508 * Which outputs something like: `01:23 of 24:56`. 3509 * 3510 * 3511 * @param {string} string 3512 * The string to localize and the key to lookup in the language files. 3513 * @param {string[]} [tokens] 3514 * If the current item has token replacements, provide the tokens here. 3515 * @param {string} [defaultValue] 3516 * Defaults to `string`. Can be a default value to use for token replacement 3517 * if the lookup key is needed to be separate. 3518 * 3519 * @return {string} 3520 * The localized string or if no localization exists the english string. 3521 */ 3522 ; 3523 3524 _proto.localize = function localize(string, tokens, defaultValue) { 3525 if (defaultValue === void 0) { 3526 defaultValue = string; 3527 } 3528 3529 var code = this.player_.language && this.player_.language(); 3530 var languages = this.player_.languages && this.player_.languages(); 3531 var language = languages && languages[code]; 3532 var primaryCode = code && code.split('-')[0]; 3533 var primaryLang = languages && languages[primaryCode]; 3534 var localizedString = defaultValue; 3535 3536 if (language && language[string]) { 3537 localizedString = language[string]; 3538 } else if (primaryLang && primaryLang[string]) { 3539 localizedString = primaryLang[string]; 3540 } 3541 3542 if (tokens) { 3543 localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { 3544 var value = tokens[index - 1]; 3545 var ret = value; 3546 3547 if (typeof value === 'undefined') { 3548 ret = match; 3549 } 3550 3551 return ret; 3552 }); 3553 } 3554 3555 return localizedString; 3556 } 3557 /** 3558 * Return the `Component`s DOM element. This is where children get inserted. 3559 * This will usually be the the same as the element returned in {@link Component#el}.
3560 * 3561 * @return {Element} 3562 * The content element for this `Component`. 3563 */ 3564 ; 3565 3566 _proto.contentEl = function contentEl() { 3567 return this.contentEl_ || this.el_; 3568 } 3569 /** 3570 * Get this `Component`s ID 3571 * 3572 * @return {string} 3573 * The id of this `Component` 3574 */ 3575 ; 3576 3577 _proto.id = function id() { 3578 return this.id_; 3579 } 3580 /** 3581 * Get the `Component`s name. The name gets used to reference the `Component` 3582 * and is set during registration. 3583 * 3584 * @return {string} 3585 * The name of this `Component`. 3586 */ 3587 ; 3588 3589 _proto.name = function name() { 3590 return this.name_; 3591 } 3592 /** 3593 * Get an array of all child components 3594 * 3595 * @return {Array} 3596 * The children 3597 */ 3598 ; 3599 3600 _proto.children = function children() { 3601 return this.children_; 3602 } 3603 /** 3604 * Returns the child `Component` with the given `id`. 3605 * 3606 * @param {string} id 3607 * The id of the child `Component` to get. 3608 * 3609 * @return {Component|undefined} 3610 * The child `Component` with the given `id` or undefined. 3611 */ 3612 ; 3613 3614 _proto.getChildById = function getChildById(id) { 3615 return this.childIndex_[id]; 3616 } 3617 /** 3618 * Returns the child `Component` with the given `name`. 3619 * 3620 * @param {string} name 3621 * The name of the child `Component` to get. 3622 * 3623 * @return {Component|undefined} 3624 * The child `Component` with the given `name` or undefined. 3625 */ 3626 ; 3627 3628 _proto.getChild = function getChild(name) { 3629 if (!name) { 3630 return; 3631 } 3632 3633 return this.childNameIndex_[name]; 3634 } 3635 /** 3636 * Returns the descendant `Component` following the givent 3637 * descendant `names`. For instance ['foo', 'bar', 'baz'] would 3638 * try to get 'foo' on the current component, 'bar' on the 'foo' 3639 * component and 'baz' on the 'bar' component and return undefined 3640 * if any of those don't exist. 3641 * 3642 * @param {...string[]|...string} names 3643 * The name of the child `Component` to get. 3644 * 3645 * @return {Component|undefined} 3646 * The descendant `Component` following the given descendant 3647 * `names` or undefined. 3648 */ 3649 ; 3650 3651 _proto.getDescendant = function getDescendant() { 3652 for (var _len = arguments.length, names = new Array(_len), _key = 0; _key < _len; _key++) { 3653 names[_key] = arguments[_key]; 3654 } 3655 3656 // flatten array argument into the main array 3657 names = names.reduce(function (acc, n) { 3658 return acc.concat(n); 3659 }, []); 3660 var currentChild = this; 3661 3662 for (var i = 0; i < names.length; i++) { 3663 currentChild = currentChild.getChild(names[i]); 3664 3665 if (!currentChild || !currentChild.getChild) { 3666 return; 3667 } 3668 } 3669 3670 return currentChild; 3671 } 3672 /** 3673 * Add a child `Component` inside the current `Component`. 3674 * 3675 * 3676 * @param {string|Component} child 3677 * The name or instance of a child to add. 3678 * 3679 * @param {Object} [options={}] 3680 * The key/value store of options that will get passed to children of 3681 * the child. 3682 * 3683 * @param {number} [index=this.children_.length] 3684 * The index to attempt to add a child into. 3685 * 3686 * @return {Component} 3687 * The `Component` that gets added as a child. When using a string the 3688 * `Component` will get created by this process. 3689 */ 3690 ; 3691 3692 _proto.addChild = function addChild(child, options, index) { 3693 if (options === void 0) { 3694 options = {}; 3695 } 3696 3697 if (index === void 0) { 3698 index = this.children_.length; 3699 } 3700 3701 var component; 3702 var componentName; // If child is a string, create component with options 3703 3704 if (typeof child === 'string') { 3705 componentName = toTitleCase(child); 3706 var componentClassName = options.componentClass || componentName; // Set name through options 3707 3708 options.name = componentName; // Create a new object & element for this controls set 3709 // If there's no .player_, this is a player 3710 3711 var ComponentClass = Component.getComponent(componentClassName); 3712 3713 if (!ComponentClass) { 3714 throw new Error("Component " + componentClassName + " does not exist"); 3715 } // data stored directly on the videojs object may be 3716 // misidentified as a component to retain 3717 // backwards-compatibility with 4.x. check to make sure the 3718 // component class can be instantiated. 3719 3720 3721 if (typeof ComponentClass !== 'function') { 3722 return null; 3723 } 3724 3725 component = new ComponentClass(this.player_ || this, options); // child is a component instance 3726 } else { 3727 component = child; 3728 } 3729 3730 if (component.parentComponent_) { 3731 component.parentComponent_.removeChild(component); 3732 } 3733 3734 this.children_.splice(index, 0, component); 3735 component.parentComponent_ = this; 3736 3737 if (typeof component.id === 'function') { 3738 this.childIndex_[component.id()] = component; 3739 } // If a name wasn't used to create the component, check if we can use the 3740 // name function of the component 3741 3742 3743 componentName = componentName || component.name && toTitleCase(component.name()); 3744 3745 if (componentName) { 3746 this.childNameIndex_[componentName] = component; 3747 this.childNameIndex_[toLowerCase(componentName)] = component; 3748 } // Add the UI object's element to the container div (box) 3749 // Having an element is not required 3750 3751 3752 if (typeof component.el === 'function' && component.el()) { 3753 // If inserting before a component, insert before that component's element
3754 var refNode = null; 3755 3756 if (this.children_[index + 1]) { 3757 // Most children are components, but the video tech is an HTML element 3758 if (this.children_[index + 1].el_) { 3759 refNode = this.children_[index + 1].el_; 3760 } else if (isEl(this.children_[index + 1])) { 3761 refNode = this.children_[index + 1]; 3762 } 3763 } 3764 3765 this.contentEl().insertBefore(component.el(), refNode); 3766 } // Return so it can stored on parent object if desired. 3767 3768 3769 return component; 3770 } 3771 /** 3772 * Remove a child `Component` from this `Component`s list of children. Also removes 3773 * the child `Component`s element from this `Component`s element. 3774 * 3775 * @param {Component} component 3776 * The child `Component` to remove. 3777 */ 3778 ; 3779 3780 _proto.removeChild = function removeChild(component) { 3781 if (typeof component === 'string') { 3782 component = this.getChild(component); 3783 } 3784 3785 if (!component || !this.children_) { 3786 return; 3787 } 3788 3789 var childFound = false; 3790 3791 for (var i = this.children_.length - 1; i >= 0; i--) { 3792 if (this.children_[i] === component) { 3793 childFound = true; 3794 this.children_.splice(i, 1); 3795 break; 3796 } 3797 } 3798 3799 if (!childFound) { 3800 return; 3801 } 3802 3803 component.parentComponent_ = null; 3804 this.childIndex_[component.id()] = null; 3805 this.childNameIndex_[toTitleCase(component.name())] = null; 3806 this.childNameIndex_[toLowerCase(component.name())] = null; 3807 var compEl = component.el(); 3808 3809 if (compEl && compEl.parentNode === this.contentEl()) { 3810 this.contentEl().removeChild(component.el()); 3811 } 3812 } 3813 /** 3814 * Add and initialize default child `Component`s based upon options. 3815 */ 3816 ; 3817 3818 _proto.initChildren = function initChildren() { 3819 var _this = this; 3820 3821 var children = this.options_.children; 3822 3823 if (children) { 3824 // `this` is `parent` 3825 var parentOptions = this.options_; 3826 3827 var handleAdd = function handleAdd(child) { 3828 var name = child.name; 3829 var opts = child.opts; // Allow options for children to be set at the parent options 3830 // e.g. videojs(id, { controlBar: false }); 3831 // instead of videojs(id, { children: { controlBar: false }); 3832 3833 if (parentOptions[name] !== undefined) { 3834 opts = parentOptions[name]; 3835 } // Allow for disabling default components 3836 // e.g. options['children']['posterImage'] = false 3837 3838 3839 if (opts === false) { 3840 return; 3841 } // Allow options to be passed as a simple boolean if no configuration 3842 // is necessary. 3843 3844 3845 if (opts === true) { 3846 opts = {}; 3847 } // We also want to pass the original player options 3848 // to each component as well so they don't need to 3849 // reach back into the player for options later. 3850 3851 3852 opts.playerOptions = _this.options_.playerOptions; // Create and add the child component. 3853 // Add a direct reference to the child by name on the parent instance. 3854 // If two of the same component are used, different names should be supplied 3855 // for each 3856 3857 var newChild = _this.addChild(name, opts); 3858 3859 if (newChild) { 3860 _this[name] = newChild; 3861 } 3862 }; // Allow for an array of children details to passed in the options 3863 3864 3865 var workingChildren; 3866 var Tech = Component.getComponent('Tech'); 3867 3868 if (Array.isArray(children)) { 3869 workingChildren = children; 3870 } else { 3871 workingChildren = Object.keys(children); 3872 } 3873 3874 workingChildren // children that are in this.options_ but also in workingChildren would 3875 // give us extra children we do not want. So, we want to filter them out. 3876 .concat(Object.keys(this.options_).filter(function (child) { 3877 return !workingChildren.some(function (wchild) { 3878 if (typeof wchild === 'string') { 3879 return child === wchild; 3880 } 3881 3882 return child === wchild.name; 3883 }); 3884 })).map(function (child) { 3885 var name; 3886 var opts; 3887 3888 if (typeof child === 'string') { 3889 name = child; 3890 opts = children[name] || _this.options_[name] || {}; 3891 } else { 3892 name = child.name; 3893 opts = child; 3894 } 3895 3896 return { 3897 name: name, 3898 opts: opts 3899 }; 3900 }).filter(function (child) { 3901 // we have to make sure that child.name isn't in the techOrder since 3902 // techs are registerd as Components but can't aren't compatible 3903 // See https://github.com/videojs/video.js/issues/2772 3904 var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); 3905 return c && !Tech.isTech(c); 3906 }
vendor: 4,369 bytes, lines 3906-4040
3906).forEach(handleAdd); 3907 } 3908 } 3909 /** 3910 * Builds the default DOM class name. Should be overriden by sub-components. 3911 * 3912 * @return {string} 3913 * The DOM class name for this object. 3914 * 3915 * @abstract 3916 */ 3917 ; 3918 3919 _proto.buildCSSClass = function buildCSSClass() { 3920 // Child classes can include a function that does: 3921 // return 'CLASS NAME' + this._super(); 3922 return ''; 3923 } 3924 /** 3925 * Bind a listener to the component's ready state. 3926 * Different from event listeners in that if the ready event has already happened 3927 * it will trigger the function immediately. 3928 * 3929 * @return {Component} 3930 * Returns itself; method can be chained. 3931 */ 3932 ; 3933 3934 _proto.ready = function ready(fn, sync) { 3935 if (sync === void 0) { 3936 sync = false; 3937 } 3938 3939 if (!fn) { 3940 return; 3941 } 3942 3943 if (!this.isReady_) { 3944 this.readyQueue_ = this.readyQueue_ || []; 3945 this.readyQueue_.push(fn); 3946 return; 3947 } 3948 3949 if (sync) { 3950 fn.call(this); 3951 } else { 3952 // Call the function asynchronously by default for consistency 3953 this.setTimeout(fn, 1); 3954 } 3955 } 3956 /** 3957 * Trigger all the ready listeners for this `Component`. 3958 * 3959 * @fires Component#ready 3960 */ 3961 ; 3962 3963 _proto.triggerReady = function triggerReady() { 3964 this.isReady_ = true; // Ensure ready is triggered asynchronously 3965 3966 this.setTimeout(function () { 3967 var readyQueue = this.readyQueue_; // Reset Ready Queue 3968 3969 this.readyQueue_ = []; 3970 3971 if (readyQueue && readyQueue.length > 0) { 3972 readyQueue.forEach(function (fn) { 3973 fn.call(this); 3974 }, this); 3975 } // Allow for using event listeners also 3976 3977 /** 3978 * Triggered when a `Component` is ready. 3979 * 3980 * @event Component#ready 3981 * @type {EventTarget~Event} 3982 */ 3983 3984 3985 this.trigger('ready'); 3986 }, 1); 3987 } 3988 /** 3989 * Find a single DOM element matching a `selector`. This can be within the `Component`s 3990 * `contentEl()` or another custom context. 3991 * 3992 * @param {string} selector 3993 * A valid CSS selector, which will be passed to `querySelector`. 3994 * 3995 * @param {Element|string} [context=this.contentEl()] 3996 * A DOM element within which to query. Can also be a selector string in 3997 * which case the first matching element will get used as context. If 3998 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3999 * nothing it falls back to `document`. 4000 * 4001 * @return {Element|null} 4002 * the dom element that was found, or null 4003 * 4004 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 4005 */ 4006 ; 4007 4008 _proto.$ = function $$1(selector, context) { 4009 return $(selector, context || this.contentEl()); 4010 } 4011 /** 4012 * Finds all DOM element matching a `selector`. This can be within the `Component`s 4013 * `contentEl()` or another custom context. 4014 * 4015 * @param {string} selector 4016 * A valid CSS selector, which will be passed to `querySelectorAll`. 4017 * 4018 * @param {Element|string} [context=this.contentEl()] 4019 * A DOM element within which to query. Can also be a selector string in 4020 * which case the first matching element will get used as context. If 4021 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 4022 * nothing it falls back to `document`. 4023 * 4024 * @return {NodeList} 4025 * a list of dom elements that were found 4026 * 4027 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 4028 */ 4029 ; 4030 4031 _proto.$$ = function $$$1(selector, context) { 4032 return $$(selector, context || this.contentEl()); 4033 } 4034 /** 4035 * Check if a component's element has a CSS class name. 4036 * 4037 * @param {string} classToCheck 4038 * CSS class name to check. 4039 * 4040 * @return {boolean}
4041 * - True if the `Component` has the class. 4042 * - False if the `Component` does not have the class` 4043 */ 4044 ; 4045 4046 _proto.hasClass = function hasClass$1(classToCheck) { 4047 return hasClass(this.el_, classToCheck); 4048 } 4049 /** 4050 * Add a CSS class name to the `Component`s element. 4051 * 4052 * @param {string} classToAdd 4053 * CSS class name to add 4054 */ 4055 ; 4056 4057 _proto.addClass = function addClass$1(classToAdd) { 4058 addClass(this.el_, classToAdd); 4059 } 4060 /** 4061 * Remove a CSS class name from the `Component`s element. 4062 * 4063 * @param {string} classToRemove 4064 * CSS class name to remove 4065 */ 4066 ; 4067 4068 _proto.removeClass = function removeClass$1(classToRemove) { 4069 removeClass(this.el_, classToRemove); 4070 } 4071 /** 4072 * Add or remove a CSS class name from the component's element. 4073 * - `classToToggle` gets added when {@link Component#hasClass} would return false. 4074 * - `classToToggle` gets removed when {@link Component#hasClass} would return true. 4075 * 4076 * @param {string} classToToggle 4077 * The class to add or remove based on (@link Component#hasClass} 4078 * 4079 * @param {boolean|Dom~predicate} [predicate] 4080 * An {@link Dom~predicate} function or a boolean 4081 */ 4082 ; 4083 4084 _proto.toggleClass = function toggleClass$1(classToToggle, predicate) { 4085 toggleClass(this.el_, classToToggle, predicate); 4086 } 4087 /** 4088 * Show the `Component`s element if it is hidden by removing the 4089 * 'vjs-hidden' class name from it. 4090 */ 4091 ; 4092 4093 _proto.show = function show() { 4094 this.removeClass('vjs-hidden'); 4095 } 4096 /** 4097 * Hide the `Component`s element if it is currently showing by adding the 4098 * 'vjs-hidden` class name to it. 4099 */ 4100 ; 4101 4102 _proto.hide = function hide() { 4103 this.addClass('vjs-hidden'); 4104 } 4105 /** 4106 * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' 4107 * class name to it. Used during fadeIn/fadeOut. 4108 * 4109 * @private 4110 */ 4111 ; 4112 4113 _proto.lockShowing = function lockShowing() { 4114 this.addClass('vjs-lock-showing'); 4115 } 4116 /** 4117 * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' 4118 * class name from it. Used during fadeIn/fadeOut. 4119 * 4120 * @private 4121 */ 4122 ; 4123 4124 _proto.unlockShowing = function unlockShowing() { 4125 this.removeClass('vjs-lock-showing'); 4126 } 4127 /** 4128 * Get the value of an attribute on the `Component`s element. 4129 * 4130 * @param {string} attribute 4131 * Name of the attribute to get the value from. 4132 * 4133 * @return {string|null} 4134 * - The value of the attribute that was asked for. 4135 * - Can be an empty string on some browsers if the attribute does not exist 4136 * or has no value 4137 * - Most browsers will return null if the attibute does not exist or has 4138 * no value. 4139 * 4140 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} 4141 */ 4142 ; 4143 4144 _proto.getAttribute = function getAttribute$1(attribute) { 4145 return getAttribute(this.el_, attribute); 4146 } 4147 /** 4148 * Set the value of an attribute on the `Component`'s element 4149 * 4150 * @param {string} attribute 4151 * Name of the attribute to set. 4152 * 4153 * @param {string} value 4154 * Value to set the attribute to. 4155 * 4156 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} 4157 */ 4158 ; 4159 4160 _proto.setAttribute = function setAttribute$1(attribute, value) { 4161 setAttribute(this.el_, attribute, value); 4162 } 4163 /** 4164 * Remove an attribute from the `Component`s element. 4165 * 4166 * @param {string} attribute 4167 * Name of the attribute to remove. 4168 * 4169 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} 4170 */ 4171 ; 4172 4173 _proto.removeAttribute = function removeAttribute$1(attribute) { 4174 removeAttribute(this.el_, attribute); 4175 } 4176 /** 4177 * Get or set the width of the component based upon the CSS styles. 4178 * See {@link Component#dimension} for more detailed information. 4179 * 4180 * @param {number|string} [num] 4181 * The width that you want to set postfixed with '%', 'px' or nothing. 4182 * 4183 * @param {boolean} [skipListeners] 4184 * Skip the componentresize event trigger 4185 * 4186 * @return {number|string} 4187 * The width when getting, zero if there is no width. Can be a string 4188 * postpixed with '%' or 'px'. 4189 */ 4190 ; 4191 4192 _proto.width = function width(num, skipListeners) { 4193 return this.dimension('width', num, skipListeners); 4194 } 4195 /** 4196 * Get or set the height of the component based upon the CSS styles. 4197 * See {@link Component#dimension} for more detailed information. 4198 * 4199 * @param {number|string} [num] 4200 * The height that you want to set postfixed with '%', 'px' or nothing. 4201 * 4202 * @param {boolean} [skipListeners] 4203 * Skip the componentresize event trigger 4204 * 4205 * @return {number|string} 4206 * The width when getting, zero if there is no width. Can be a string 4207 * postpixed with '%' or 'px'. 4208 */ 4209 ; 4210 4211 _proto.height = function height(num, skipListeners) { 4212 return this.dimension('height', num, skipListeners); 4213 } 4214 /** 4215 * Set both the width and height of the `Component` element at the same time. 4216 * 4217 * @param {number|string} width 4218 * Width to set the `Component`s element to. 4219 * 4220 * @param {number|string} height 4221 * Height to set the `Component`s element to. 4222 */ 4223 ; 4224 4225 _proto.dimensions = function dimensions(width, height) { 4226 // Skip componentresize listeners on width for optimization 4227 this.width(width, true); 4228 this.height(height); 4229 } 4230 /** 4231 * Get or set width or height of the `Component` element. This is the shared code 4232 * for the {@link Component#width} and {@link Component#height}. 4233 * 4234 * Things to know: 4235 * - If the width or height in an number this will return the number postfixed with 'px'. 4236 * - If the width/height is a percent this will return the percent postfixed with '%' 4237 * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function 4238 * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. 4239 * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
4239sing-width/} 4240 * for more information 4241 * - If you want the computed style of the component, use {@link Component#currentWidth} 4242 * and {@link {Component#currentHeight} 4243 * 4244 * @fires Component#componentresize 4245 * 4246 * @param {string} widthOrHeight 4247 8 'width' or 'height' 4248 * 4249 * @param {number|string} [num] 4250 8 New dimension 4251 * 4252 * @param {boolean} [skipListeners] 4253 * Skip componentresize event trigger 4254 * 4255 * @return {number} 4256 * The dimension when getting or 0 if unset 4257 */ 4258 ; 4259 4260 _proto.dimension = function dimension(widthOrHeight, num, skipListeners) { 4261 if (num !== undefined) { 4262 // Set to zero if null or literally NaN (NaN !== NaN) 4263 if (num === null || num !== num) { 4264 num = 0; 4265 } // Check if using css width/height (% or px) and adjust 4266 4267 4268 if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { 4269 this.el_.style[widthOrHeight] = num; 4270 } else if (num === 'auto') { 4271 this.el_.style[widthOrHeight] = ''; 4272 } else { 4273 this.el_.style[widthOrHeight] = num + 'px'; 4274 } // skipListeners allows us to avoid triggering the resize event when setting both width and height 4275 4276 4277 if (!skipListeners) { 4278 /** 4279 * Triggered when a component is resized. 4280 * 4281 * @event Component#componentresize 4282 * @type {EventTarget~Event} 4283 */ 4284 this.trigger('componentresize'); 4285 } 4286 4287 return; 4288 } // Not setting a value, so getting it 4289 // Make sure element exists 4290 4291 4292 if (!this.el_) { 4293 return 0; 4294 } // Get dimension value from style 4295 4296 4297 var val = this.el_.style[widthOrHeight]; 4298 var pxIndex = val.indexOf('px'); 4299 4300 if (pxIndex !== -1) { 4301 // Return the pixel value with no 'px' 4302 return parseInt(val.slice(0, pxIndex), 10); 4303 } // No px so using % or no style was set, so falling back to offsetWidth/height 4304 // If component has display:none, offset will return 0 4305 // TODO: handle display:none and no dimension style using px 4306 4307 4308 return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); 4309 } 4310 /** 4311 * Get the computed width or the height of the component's element. 4312 * 4313 * Uses `window.getComputedStyle`. 4314 * 4315 * @param {string} widthOrHeight 4316 * A string containing 'width' or 'height'. Whichever one you want to get. 4317 * 4318 * @return {number} 4319 * The dimension that gets asked for or 0 if nothing was set 4320 * for that dimension. 4321 */ 4322 ; 4323 4324 _proto.currentDimension = function currentDimension(widthOrHeight) { 4325 var computedWidthOrHeight = 0; 4326 4327 if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { 4328 throw new Error('currentDimension only accepts width or height value'); 4329 } 4330 4331 computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer 4332 4333 computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying 4334 // and we want to check the offset values. 4335 // This code also runs wherever getComputedStyle doesn't exist. 4336 4337 if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) { 4338 var rule = "offset" + toTitleCase(widthOrHeight); 4339 computedWidthOrHeight = this.el_[rule]; 4340 } 4341 4342 return computedWidthOrHeight; 4343 } 4344 /** 4345 * An object that contains width and height values of the `Component`s 4346 * computed style. Uses `window.getComputedStyle`. 4347 * 4348 * @typedef {Object} Component~DimensionObject 4349 * 4350 * @property {number} width 4351 * The width of the `Component`s computed style. 4352 * 4353 * @property {number} height 4354 * The height of the `Component`s computed style. 4355 */ 4356 4357 /** 4358 * Get an object that contains computed width and height values of the 4359 * component's element. 4360 * 4361 * Uses `window.getComputedStyle`. 4362 * 4363 * @return {Component~DimensionObject} 4364 * The computed dimensions of the component's element. 4365 */ 4366 ; 4367 4368 _proto.currentDimensions = function currentDimensions() { 4369 return { 4370 width: this.currentDimension('width'), 4371 height: this.currentDimension('height') 4372 }; 4373 } 4374 /** 4375 * Get the computed width of the component's element. 4376 * 4377 * Uses `window.getComputedStyle`. 4378 * 4379 * @return {number} 4380 * The computed width of the component's element. 4381 */ 4382 ; 4383 4384 _proto.currentWidth = function currentWidth() { 4385 return this.currentDimension('width'); 4386 } 4387 /** 4388 * Get the computed height of the component's element. 4389 * 4390 * Uses `window.getComputedStyle`. 4391 * 4392 * @return {number} 4393 * The computed height of the component's element. 4394 */ 4395 ; 4396 4397 _proto.currentHeight = function currentHeight() { 4398 return this.currentDimension('height'); 4399 } 4400 /** 4401 * Set the focus to this component 4402 */ 4403 ; 4404 4405 _proto.focus = function focus() { 4406 this.el_.focus(); 4407 } 4408 /** 4409 * Remove the focus from this component 4410 */ 4411 ; 4412 4413 _proto.blur = function blur() { 4414 this.el_.blur(); 4415 } 4416 /** 4417 * When this Component receives a `keydown` event which it does not process, 4418 * it passes the event to the Player for handling. 4419 * 4420 * @param {EventTarget~Event} event
vendor: 11,043 bytes, lines 4421-4682
4421 * The `keydown` event that caused this function to be called. 4422 */ 4423 ; 4424 4425 _proto.handleKeyDown = function handleKeyDown(event) { 4426 if (this.player_) { 4427 // We only stop propagation here because we want unhandled events to fall 4428 // back to the browser. 4429 event.stopPropagation(); 4430 this.player_.handleKeyDown(event); 4431 } 4432 } 4433 /** 4434 * Many components used to have a `handleKeyPress` method, which was poorly 4435 * named because it listened to a `keydown` event. This method name now 4436 * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` 4437 * will not see their method calls stop working. 4438 * 4439 * @param {EventTarget~Event} event 4440 * The event that caused this function to be called. 4441 */ 4442 ; 4443 4444 _proto.handleKeyPress = function handleKeyPress(event) { 4445 this.handleKeyDown(event); 4446 } 4447 /** 4448 * Emit a 'tap' events when touch event support gets detected. This gets used to 4449 * support toggling the controls through a tap on the video. They get enabled 4450 * because every sub-component would have extra overhead otherwise. 4451 * 4452 * @private 4453 * @fires Component#tap 4454 * @listens Component#touchstart 4455 * @listens Component#touchmove 4456 * @listens Component#touchleave 4457 * @listens Component#touchcancel 4458 * @listens Component#touchend 4459 */ 4460 ; 4461 4462 _proto.emitTapEvents = function emitTapEvents() { 4463 // Track the start time so we can determine how long the touch lasted 4464 var touchStart = 0; 4465 var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap 4466 // Other popular libs use anywhere from 2 (hammer.js) to 15, 4467 // so 10 seems like a nice, round number. 4468 4469 var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap 4470 4471 var touchTimeThreshold = 200; 4472 var couldBeTap; 4473 this.on('touchstart', function (event) { 4474 // If more than one finger, don't consider treating this as a click 4475 if (event.touches.length === 1) { 4476 // Copy pageX/pageY from the object 4477 firstTouch = { 4478 pageX: event.touches[0].pageX, 4479 pageY: event.touches[0].pageY 4480 }; // Record start time so we can detect a tap vs. "touch and hold" 4481 4482 touchStart = window$3.performance.now(); // Reset couldBeTap tracking 4483 4484 couldBeTap = true; 4485 } 4486 }); 4487 this.on('touchmove', function (event) { 4488 // If more than one finger, don't consider treating this as a click 4489 if (event.touches.length > 1) { 4490 couldBeTap = false; 4491 } else if (firstTouch) { 4492 // Some devices will throw touchmoves for all but the slightest of taps. 4493 // So, if we moved only a small distance, this could still be a tap 4494 var xdiff = event.touches[0].pageX - firstTouch.pageX; 4495 var ydiff = event.touches[0].pageY - firstTouch.pageY; 4496 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); 4497 4498 if (touchDistance > tapMovementThreshold) { 4499 couldBeTap = false; 4500 } 4501 } 4502 }); 4503 4504 var noTap = function noTap() { 4505 couldBeTap = false; 4506 }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s 4507 4508 4509 this.on('touchleave', noTap); 4510 this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate 4511 // event 4512 4513 this.on('touchend', function (event) { 4514 firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen 4515 4516 if (couldBeTap === true) { 4517 // Measure how long the touch lasted 4518 var touchTime = window$3.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap 4519 4520 if (touchTime < touchTimeThreshold) { 4521 // Don't let browser turn this into a click 4522 event.preventDefault(); 4523 /** 4524 * Triggered when a `Component` is tapped. 4525 * 4526 * @event Component#tap 4527 * @type {EventTarget~Event} 4528 */ 4529 4530 this.trigger('tap'); // It may be good to copy the touchend event object and change the 4531 // type to tap, if the other event properties aren't exact after 4532 // Events.fixEvent runs (e.g. event.target) 4533 } 4534 } 4535 }); 4536 } 4537 /** 4538 * This function reports user activity whenever touch events happen. This can get 4539 * turned off by any sub-components that wants touch events to act another way. 4540 * 4541 * Report user touch activity when touch events occur. User activity gets used to 4542 * determine when controls should show/hide. It is simple when it comes to mouse 4543 * events, because any mouse event should show the controls. So we capture mouse 4544 * events that bubble up to the player and report activity when that happens. 4545 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player 4546 * controls. So touch events can't help us at the player level either. 4547 * 4548 * User activity gets checked asynchronously. So what could happen is a tap event 4549 * on the video turns the controls off. Then the `touchend` event bubbles up to 4550 * the player. Which, if it reported user activity, would turn the controls right 4551 * back on. We also don't want to completely block touch events from bubbling up. 4552 * Furthermore a `touchmove` event and anything other than a tap, should not turn 4553 * controls back on. 4554 * 4555 * @listens Component#touchstart 4556 * @listens Component#touchmove 4557 * @listens Component#touchend 4558 * @listens Component#touchcancel 4559 */ 4560 ; 4561 4562 _proto.enableTouchActivity = function enableTouchActivity() { 4563 // Don't continue if the root player doesn't support reporting user activity 4564 if (!this.player() || !this.player().reportUserActivity) { 4565 return; 4566 } // listener for reporting that the user is active 4567 4568 4569 var report = bind(this.player(), this.player().reportUserActivity); 4570 var touchHolding; 4571 this.on('touchstart', function () { 4572 report(); // For as long as the they are touching the device or have their mouse down, 4573 // we consider them active even if they're not moving their finger or mouse. 4574 // So we want to continue to update that they are active 4575 4576 this.clearInterval(touchHolding); // report at the same interval as activityCheck 4577 4578 touchHolding = this.setInterval(report, 250); 4579 }); 4580 4581 var touchEnd = function touchEnd(event) { 4582 report(); // stop the interval that maintains activity if the touch is holding 4583 4584 this.clearInterval(touchHolding); 4585 }; 4586 4587 this.on('touchmove', report); 4588 this.on('touchend', touchEnd); 4589 this.on('touchcancel', touchEnd); 4590 } 4591 /** 4592 * A callback that has no parameters and is bound into `Component`s context. 4593 * 4594 * @callback Component~GenericCallback 4595 * @this Component 4596 */ 4597 4598 /** 4599 * Creates a function that runs after an `x` millisecond timeout. This function is a 4600 * wrapper around `window.setTimeout`. There are a few reasons to use this one 4601 * instead though: 4602 * 1. It gets cleared via {@link Component#clearTimeout} when 4603 * {@link Component#dispose} gets called. 4604 * 2. The function callback will gets turned into a {@link Component~GenericCallback} 4605 * 4606 * > Note: You can't use `window.clearTimeout` on the id returned by this function. This 4607 * will cause its dispose listener not to get cleaned up! Please use 4608 * {@link Component#clearTimeout} or {@link Component#dispose} instead. 4609 * 4610 * @param {Component~GenericCallback} fn 4611 * The function that will be run after `timeout`. 4612 * 4613 * @param {number} timeout 4614 * Timeout in milliseconds to delay before executing the specified function. 4615 * 4616 * @return {number} 4617 * Returns a timeout ID that gets used to identify the timeout. It can also 4618 * get used in {@link Component#clearTimeout} to clear the timeout that 4619 * was set. 4620 * 4621 * @listens Component#dispose 4622 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} 4623 */ 4624 ; 4625 4626 _proto.setTimeout = function setTimeout(fn, timeout) { 4627 var _this2 = this; 4628 4629 // declare as variables so they are properly available in timeout function 4630 // eslint-disable-next-line 4631 var timeoutId; 4632 fn = bind(this, fn); 4633 this.clearTimersOnDispose_(); 4634 timeoutId = window$3.setTimeout(function () { 4635 if (_this2.setTimeoutIds_.has(timeoutId)) { 4636 _this2.setTimeoutIds_["delete"](timeoutId); 4637 } 4638 4639 fn(); 4640 }, timeout); 4641 this.setTimeoutIds_.add(timeoutId); 4642 return timeoutId; 4643 } 4644 /** 4645 * Clears a timeout that gets created via `window.setTimeout` or 4646 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} 4647 * use this function instead of `window.clearTimout`. If you don't your dispose 4648 * listener will not get cleaned up until {@link Component#dispose}! 4649 * 4650 * @param {number} timeoutId 4651 * The id of the timeout to clear. The return value of 4652 * {@link Component#setTimeout} or `window.setTimeout`. 4653 * 4654 * @return {number} 4655 * Returns the timeout id that was cleared. 4656 * 4657 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} 4658 */ 4659 ; 4660 4661 _proto.clearTimeout = function clearTimeout(timeoutId) { 4662 if (this.setTimeoutIds_.has(timeoutId)) { 4663 this.setTimeoutIds_["delete"](timeoutId); 4664 window$3.clearTimeout(timeoutId); 4665 } 4666 4667 return timeoutId; 4668 } 4669 /** 4670 * Creates a function that gets run every `x` milliseconds. This function is a wrapper 4671 * around `window.setInterval`. There are a few reasons to use this one instead though. 4672 * 1. It gets cleared via {@link Component#clearInterval} when 4673 * {@link Component#dispose} gets called. 4674 * 2. The function callback will be a {@link Component~GenericCallback} 4675 * 4676 * @param {Component~GenericCallback} fn 4677 * The function to run every `x` seconds. 4678 * 4679 * @param {number} interval 4680 * Execute the specified function every `x` milliseconds. 4681 * 4682 * @return {number}
4683 * Returns an id that can be used to identify the interval. It can also be be used in 4684 * {@link Component#clearInterval} to clear the interval. 4685 * 4686 * @listens Component#dispose 4687 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} 4688 */ 4689 ; 4690 4691 _proto.setInterval = function setInterval(fn, interval) { 4692 fn = bind(this, fn); 4693 this.clearTimersOnDispose_(); 4694 var intervalId = window$3.setInterval(fn, interval); 4695 this.setIntervalIds_.add(intervalId); 4696 return intervalId; 4697 } 4698 /** 4699 * Clears an interval that gets created via `window.setInterval` or 4700 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} 4701 * use this function instead of `window.clearInterval`. If you don't your dispose 4702 * listener will not get cleaned up until {@link Component#dispose}! 4703 * 4704 * @param {number} intervalId 4705 * The id of the interval to clear. The return value of 4706 * {@link Component#setInterval} or `window.setInterval`. 4707 * 4708 * @return {number} 4709 * Returns the interval id that was cleared. 4710 * 4711 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} 4712 */ 4713 ; 4714 4715 _proto.clearInterval = function clearInterval(intervalId) { 4716 if (this.setIntervalIds_.has(intervalId)) { 4717 this.setIntervalIds_["delete"](intervalId); 4718 window$3.clearInterval(intervalId); 4719 } 4720 4721 return intervalId; 4722 } 4723 /** 4724 * Queues up a callback to be passed to requestAnimationFrame (rAF), but 4725 * with a few extra bonuses: 4726 * 4727 * - Supports browsers that do not support rAF by falling back to 4728 * {@link Component#setTimeout}. 4729 * 4730 * - The callback is turned into a {@link Component~GenericCallback} (i.e. 4731 * bound to the component). 4732 * 4733 * - Automatic cancellation of the rAF callback is handled if the component 4734 * is disposed before it is called. 4735 * 4736 * @param {Component~GenericCallback} fn 4737 * A function that will be bound to this component and executed just 4738 * before the browser's next repaint. 4739 * 4740 * @return {number} 4741 * Returns an rAF ID that gets used to identify the timeout. It can 4742 * also be used in {@link Component#cancelAnimationFrame} to cancel 4743 * the animation frame callback. 4744 * 4745 * @listens Component#dispose 4746 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} 4747 */ 4748 ; 4749 4750 _proto.requestAnimationFrame = function requestAnimationFrame(fn) { 4751 var _this3 = this; 4752 4753 // Fall back to using a timer. 4754 if (!this.supportsRaf_) { 4755 return this.setTimeout(fn, 1000 / 60); 4756 } 4757 4758 this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function 4759 // eslint-disable-next-line 4760 4761 var id; 4762 fn = bind(this, fn); 4763 id = window$3.requestAnimationFrame(function () { 4764 if (_this3.rafIds_.has(id)) { 4765 _this3.rafIds_["delete"](id); 4766 } 4767 4768 fn(); 4769 }); 4770 this.rafIds_.add(id); 4771 return id; 4772 } 4773 /** 4774 * Cancels a queued callback passed to {@link Component#requestAnimationFrame} 4775 * (rAF). 4776 * 4777 * If you queue an rAF callback via {@link Component#requestAnimationFrame}, 4778 * use this function instead of `window.cancelAnimationFrame`. If you don't, 4779 * your dispose listener will not get cleaned up until {@link Component#dispose}! 4780 * 4781 * @param {number} id 4782 * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. 4783 * 4784 * @return {number} 4785 * Returns the rAF ID that was cleared. 4786 * 4787 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} 4788 */ 4789 ; 4790 4791 _proto.cancelAnimationFrame = function cancelAnimationFrame(id) { 4792 // Fall back to using a timer. 4793 if (!this.supportsRaf_) { 4794 return this.clearTimeout(id); 4795 } 4796 4797 if (this.rafIds_.has(id)) { 4798 this.rafIds_["delete"](id); 4799 window$3.cancelAnimationFrame(id); 4800 } 4801 4802 return id; 4803 } 4804 /** 4805 * A function to setup `requestAnimationFrame`, `setTimeout`, 4806 * and `setInterval`, clearing on dispose. 4807 * 4808 * > Previously each timer added and removed dispose listeners on it's own. 4809 * For better performance it was decided to batch them all, and use `Set`s 4810 * to track outstanding timer ids. 4811 * 4812 * @private 4813 */ 4814 ; 4815 4816 _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() { 4817 var _this4 = this; 4818 4819 if (this.clearingTimersOnDispose_) { 4820 return; 4821 } 4822 4823 this.clearingTimersOnDispose_ = true; 4824 this.one('dispose', function () { 4825 [['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEac
4825h(function (_ref) { 4826 var idName = _ref[0], 4827 cancelName = _ref[1]; 4828 4829 _this4[idName].forEach(_this4[cancelName], _this4); 4830 }); 4831 _this4.clearingTimersOnDispose_ = false; 4832 }); 4833 } 4834 /** 4835 * Register a `Component` with `videojs` given the name and the component. 4836 * 4837 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s 4838 * should be registered using {@link Tech.registerTech} or 4839 * {@link videojs:videojs.registerTech}. 4840 * 4841 * > NOTE: This function can also be seen on videojs as 4842 * {@link videojs:videojs.registerComponent}. 4843 * 4844 * @param {string} name 4845 * The name of the `Component` to register. 4846 * 4847 * @param {Component} ComponentToRegister 4848 * The `Component` class to register. 4849 * 4850 * @return {Component} 4851 * The `Component` that was registered. 4852 */ 4853 ; 4854 4855 Component.registerComponent = function registerComponent(name, ComponentToRegister) { 4856 if (typeof name !== 'string' || !name) { 4857 throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string."); 4858 } 4859 4860 var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered. 4861 4862 var isTech = Tech && Tech.isTech(ComponentToRegister); 4863 var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); 4864 4865 if (isTech || !isComp) { 4866 var reason; 4867 4868 if (isTech) { 4869 reason = 'techs must be registered using Tech.registerTech()'; 4870 } else { 4871 reason = 'must be a Component subclass'; 4872 } 4873 4874 throw new Error("Illegal component, \"" + name + "\"; " + reason + "."); 4875 } 4876 4877 name = toTitleCase(name); 4878 4879 if (!Component.components_) { 4880 Component.components_ = {}; 4881 } 4882 4883 var Player = Component.getComponent('Player'); 4884 4885 if (name === 'Player' && Player && Player.players) { 4886 var players = Player.players; 4887 var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be 4888 // in Players.players. So, we must loop through and verify that the value 4889 // for each item is not null. This allows registration of the Player component 4890 // after all players have been disposed or before any were created. 4891 4892 if (players && playerNames.length > 0 && playerNames.map(function (pname) { 4893 return players[pname]; 4894 }).every(Boolean)) { 4895 throw new Error('Can not register Player component after player has been created.'); 4896 } 4897 } 4898 4899 Component.components_[name] = ComponentToRegister; 4900 Component.components_[toLowerCase(name)] = ComponentToRegister; 4901 return ComponentToRegister; 4902 } 4903 /** 4904 * Get a `Component` based on the name it was registered with. 4905 * 4906 * @param {string} name 4907 * The Name of the component to get. 4908 * 4909 * @return {Component} 4910 * The `Component` that got registered under the given name. 4911 * 4912 * @deprecated In `videojs` 6 this will not return `Component`s that were not 4913 * registered using {@link Component.registerComponent}. Currently we 4914 * check the global `videojs` object for a `Component` name and 4915 * return that if it exists. 4916 */ 4917 ; 4918 4919 Component.getComponent = function getComponent(name) { 4920 if (!name || !Component.components_) { 4921 return; 4922 } 4923 4924 return Component.components_[name]; 4925 }; 4926 4927 return Component; 4928 }(); 4929 /** 4930 * Whether or not this component supports `requestAnimationFrame`. 4931 * 4932 * This is exposed primarily for testing purposes. 4933 * 4934 * @private 4935 * @type {Boolean} 4936 */ 4937 4938 4939 Component.prototype.supportsRaf_ = typeof window$3.requestAnimationFrame === 'function' && typeof window$3.cancelAnimationFrame === 'function'; 4940 Component.registerComponent('Component', Component); 4941 4942 function _assertThisInitialized(self) { 4943 if (self === void 0) { 4944 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 4945 } 4946 4947 return self; 4948 } 4949 4950 var assertThisInitialized = _assertThisInitialized; 4951 4952 var _typeof_1 = createCommonjsModule(function (module) { 4953 function _typeof(obj) { 4954 "@babel/helpers - typeof"; 4955 4956 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 4957 module.exports = _typeof = function _typeof(obj) { 4958 return typeof obj; 4959 }; 4960 } else { 4961 module.exports = _typeof = function _typeof(obj) { 4962 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 4963 }; 4964 } 4965 4966 return _typeof(obj); 4967 } 4968 4969 module.exports = _typeof; 4970 }); 4971 4972 var getPrototypeOf = createCommonjsModule(function (module) {
vendor: 23,458 bytes, lines 4973-5732
4973 function _getPrototypeOf(o) { 4974 module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 4975 return o.__proto__ || Object.getPrototypeOf(o); 4976 }; 4977 return _getPrototypeOf(o); 4978 } 4979 4980 module.exports = _getPrototypeOf; 4981 }); 4982 4983 function _inheritsLoose(subClass, superClass) { 4984 subClass.prototype = Object.create(superClass.prototype); 4985 subClass.prototype.constructor = subClass; 4986 subClass.__proto__ = superClass; 4987 } 4988 4989 var inheritsLoose = _inheritsLoose; 4990 4991 /** 4992 * @file browser.js 4993 * @module browser 4994 */ 4995 var USER_AGENT = window$3.navigator && window$3.navigator.userAgent || ''; 4996 var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); 4997 var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; 4998 /** 4999 * Whether or not this device is an iPod. 5000 * 5001 * @static 5002 * @const 5003 * @type {Boolean} 5004 */ 5005 5006 var IS_IPOD = /iPod/i.test(USER_AGENT); 5007 /** 5008 * The detected iOS version - or `null`. 5009 * 5010 * @static 5011 * @const 5012 * @type {string|null} 5013 */ 5014 5015 var IOS_VERSION = function () { 5016 var match = USER_AGENT.match(/OS (\d+)_/i); 5017 5018 if (match && match[1]) { 5019 return match[1]; 5020 } 5021 5022 return null; 5023 }(); 5024 /** 5025 * Whether or not this is an Android device. 5026 * 5027 * @static 5028 * @const 5029 * @type {Boolean} 5030 */ 5031 5032 var IS_ANDROID = /Android/i.test(USER_AGENT); 5033 /** 5034 * The detected Android version - or `null`. 5035 * 5036 * @static 5037 * @const 5038 * @type {number|string|null} 5039 */ 5040 5041 var ANDROID_VERSION = function () { 5042 // This matches Android Major.Minor.Patch versions 5043 // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned 5044 var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); 5045 5046 if (!match) { 5047 return null; 5048 } 5049 5050 var major = match[1] && parseFloat(match[1]); 5051 var minor = match[2] && parseFloat(match[2]); 5052 5053 if (major && minor) { 5054 return parseFloat(match[1] + '.' + match[2]); 5055 } else if (major) { 5056 return major; 5057 } 5058 5059 return null; 5060 }(); 5061 /** 5062 * Whether or not this is a native Android browser. 5063 * 5064 * @static 5065 * @const 5066 * @type {Boolean} 5067 */ 5068 5069 var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; 5070 /** 5071 * Whether or not this is Mozilla Firefox. 5072 * 5073 * @static 5074 * @const 5075 * @type {Boolean} 5076 */ 5077 5078 var IS_FIREFOX = /Firefox/i.test(USER_AGENT); 5079 /** 5080 * Whether or not this is Microsoft Edge. 5081 * 5082 * @static 5083 * @const 5084 * @type {Boolean} 5085 */ 5086 5087 var IS_EDGE = /Edg/i.test(USER_AGENT); 5088 /** 5089 * Whether or not this is Google Chrome. 5090 * 5091 * This will also be `true` for Chrome on iOS, which will have different support 5092 * as it is actually Safari under the hood. 5093 * 5094 * @static 5095 * @const 5096 * @type {Boolean} 5097 */ 5098 5099 var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); 5100 /** 5101 * The detected Google Chrome version - or `null`. 5102 * 5103 * @static 5104 * @const 5105 * @type {number|null} 5106 */ 5107 5108 var CHROME_VERSION = function () { 5109 var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); 5110 5111 if (match && match[2]) { 5112 return parseFloat(match[2]); 5113 } 5114 5115 return null; 5116 }(); 5117 /** 5118 * The detected Internet Explorer version - or `null`. 5119 * 5120 * @static 5121 * @const 5122 * @type {number|null} 5123 */ 5124 5125 var IE_VERSION = function () { 5126 var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); 5127 var version = result && parseFloat(result[1]); 5128 5129 if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { 5130 // IE 11 has a different user agent string than other IE versions 5131 version = 11.0; 5132 } 5133 5134 return version; 5135 }(); 5136 /** 5137 * Whether or not this is desktop Safari. 5138 * 5139 * @static 5140 * @const 5141 * @type {Boolean} 5142 */ 5143 5144 var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; 5145 /** 5146 * Whether or not this is a Windows machine. 5147 * 5148 * @static 5149 * @const 5150 * @type {Boolean} 5151 */ 5152 5153 var IS_WINDOWS = /Windows/i.test(USER_AGENT); 5154 /** 5155 * Whether or not this device is touch-enabled. 5156 * 5157 * @static 5158 * @const 5159 * @type {Boolean} 5160 */ 5161 5162 var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$3 || window$3.navigator.maxTouchPoints || window$3.DocumentTouch && window$3.document instanceof window$3.DocumentTouch); 5163 /** 5164 * Whether or not this device is an iPad. 5165 * 5166 * @static 5167 * @const 5168 * @type {Boolean} 5169 */ 5170 5171 var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT); 5172 /** 5173 * Whether or not this device is an iPhone. 5174 * 5175 * @static 5176 * @const 5177 * @type {Boolean} 5178 */ 5179 // The Facebook app's UIWebView identifies as both an iPhone and iPad, so 5180 // to identify iPhones, we need to exclude iPads. 5181 // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ 5182 5183 var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; 5184 /** 5185 * Whether or not this is an iOS device. 5186 * 5187 * @static 5188 * @const 5189 * @type {Boolean} 5190 */ 5191 5192 var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; 5193 /** 5194 * Whether or not this is any flavor of Safari - including iOS. 5195 * 5196 * @static 5197 * @const 5198 * @type {Boolean} 5199 */ 5200 5201 var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; 5202 5203 var browser = /*#__PURE__*/Object.freeze({ 5204 __proto__: null, 5205 IS_IPOD: IS_IPOD, 5206 IOS_VERSION: IOS_VERSION, 5207 IS_ANDROID: IS_ANDROID, 5208 ANDROID_VERSION: ANDROID_VERSION, 5209 IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, 5210 IS_FIREFOX: IS_FIREFOX, 5211 IS_EDGE: IS_EDGE, 5212 IS_CHROME: IS_CHROME, 5213 CHROME_VERSION: CHROME_VERSION, 5214 IE_VERSION: IE_VERSION, 5215 IS_SAFARI: IS_SAFARI, 5216 IS_WINDOWS: IS_WINDOWS, 5217 TOUCH_ENABLED: TOUCH_ENABLED, 5218 IS_IPAD: IS_IPAD, 5219 IS_IPHONE: IS_IPHONE, 5220 IS_IOS: IS_IOS, 5221 IS_ANY_SAFARI: IS_ANY_SAFARI 5222 }); 5223 5224 /** 5225 * @file time-ranges.js 5226 * @module time-ranges 5227 */ 5228 5229 /** 5230 * Returns the time for the specified index at the start or end 5231 * of a TimeRange object. 5232 * 5233 * @typedef {Function} TimeRangeIndex 5234 * 5235 * @param {number} [index=0] 5236 * The range number to return the time for. 5237 * 5238 * @return {number} 5239 * The time offset at the specified index. 5240 * 5241 * @deprecated The index argument must be provided. 5242 * In the future, leaving it out will throw an error. 5243 */ 5244 5245 /** 5246 * An object that contains ranges of time. 5247 * 5248 * @typedef {Object} TimeRange 5249 * 5250 * @property {number} length 5251 * The number of time ranges represented by this object. 5252 * 5253 * @property {module:time-ranges~TimeRangeIndex} start 5254 * Returns the time offset at which a specified time range begins. 5255 * 5256 * @property {module:time-ranges~TimeRangeIndex} end 5257 * Returns the time offset at which a specified time range ends. 5258 * 5259 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges 5260 */ 5261 5262 /** 5263 * Check if any of the time ranges are over the maximum index. 5264 * 5265 * @private 5266 * @param {string} fnName 5267 * The function name to use for logging 5268 * 5269 * @param {number} index 5270 * The index to check 5271 * 5272 * @param {number} maxIndex 5273 * The maximum possible index 5274 * 5275 * @throws {Error} if the timeRanges provided are over the maxIndex 5276 */ 5277 function rangeCheck(fnName, index, maxIndex) { 5278 if (typeof index !== 'number' || index < 0 || index > maxIndex) { 5279 throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ")."); 5280 } 5281 } 5282 /** 5283 * Get the time for the specified index at the start or end 5284 * of a TimeRange object. 5285 * 5286 * @private 5287 * @param {string} fnName 5288 * The function name to use for logging 5289 * 5290 * @param {string} valueIndex 5291 * The property that should be used to get the time. should be 5292 * 'start' or 'end' 5293 * 5294 * @param {Array} ranges 5295 * An array of time ranges 5296 * 5297 * @param {Array} [rangeIndex=0] 5298 * The index to start the search at 5299 * 5300 * @return {number} 5301 * The time that offset at the specified index. 5302 * 5303 * @deprecated rangeIndex must be set to a value, in the future this will throw an error. 5304 * @throws {Error} if rangeIndex is more than the length of ranges 5305 */ 5306 5307 5308 function getRange(fnName, valueIndex, ranges, rangeIndex) { 5309 rangeCheck(fnName, rangeIndex, ranges.length - 1); 5310 return ranges[rangeIndex][valueIndex]; 5311 } 5312 /** 5313 * Create a time range object given ranges of time. 5314 * 5315 * @private 5316 * @param {Array} [ranges] 5317 * An array of time ranges. 5318 */ 5319 5320 5321 function createTimeRangesObj(ranges) { 5322 if (ranges === undefined || ranges.length === 0) { 5323 return { 5324 length: 0, 5325 start: function start() { 5326 throw new Error('This TimeRanges object is empty'); 5327 }, 5328 end: function end() { 5329 throw new Error('This TimeRanges object is empty'); 5330 } 5331 }; 5332 } 5333 5334 return { 5335 length: ranges.length, 5336 start: getRange.bind(null, 'start', 0, ranges), 5337 end: getRange.bind(null, 'end', 1, ranges) 5338 }; 5339 } 5340 /** 5341 * Create a `TimeRange` object which mimics an 5342 * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. 5343 * 5344 * @param {number|Array[]} start 5345 * The start of a single range (a number) or an array of ranges (an 5346 * array of arrays of two numbers each). 5347 * 5348 * @param {number} end 5349 * The end of a single range. Cannot be used with the array form of 5350 * the `start` argument. 5351 */ 5352 5353 5354 function createTimeRanges(start, end) { 5355 if (Array.isArray(start)) { 5356 return createTimeRangesObj(start); 5357 } else if (start === undefined || end === undefined) { 5358 return createTimeRangesObj(); 5359 } 5360 5361 return createTimeRangesObj([[start, end]]); 5362 } 5363 5364 /** 5365 * @file buffer.js 5366 * @module buffer 5367 */ 5368 /** 5369 * Compute the percentage of the media that has been buffered. 5370 * 5371 * @param {TimeRange} buffered 5372 * The current `TimeRange` object representing buffered time ranges 5373 * 5374 * @param {number} duration 5375 * Total duration of the media 5376 * 5377 * @return {number} 5378 * Percent buffered of the total duration in decimal form. 5379 */ 5380 5381 function bufferedPercent(buffered, duration) { 5382 var bufferedDuration = 0; 5383 var start; 5384 var end; 5385 5386 if (!duration) { 5387 return 0; 5388 } 5389 5390 if (!buffered || !buffered.length) { 5391 buffered = createTimeRanges(0, 0); 5392 } 5393 5394 for (var i = 0; i < buffered.length; i++) { 5395 start = buffered.start(i); 5396 end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction 5397 5398 if (end > duration) { 5399 end = duration; 5400 } 5401 5402 bufferedDuration += end - start; 5403 } 5404 5405 return bufferedDuration / duration; 5406 } 5407 5408 /** 5409 * @file fullscreen-api.js 5410 * @module fullscreen-api 5411 * @private 5412 */ 5413 /** 5414 * Store the browser-specific methods for the fullscreen API. 5415 * 5416 * @type {Object} 5417 * @see [Specification]{@link https://fullscreen.spec.whatwg.org} 5418 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} 5419 */ 5420 5421 var FullscreenApi = { 5422 prefixed: true 5423 }; // browser API methods 5424 5425 var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit 5426 ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla 5427 ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft 5428 ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']]; 5429 var specApi = apiMap[0]; 5430 var browserApi; // determine the supported set of functions 5431 5432 for (var i = 0; i < apiMap.length; i++) { 5433 // check for exitFullscreen function 5434 if (apiMap[i][1] in document) { 5435 browserApi = apiMap[i]; 5436 break; 5437 } 5438 } // map the browser API names to the spec API names 5439 5440 5441 if (browserApi) { 5442 for (var _i = 0; _i < browserApi.length; _i++) { 5443 FullscreenApi[specApi[_i]] = browserApi[_i]; 5444 } 5445 5446 FullscreenApi.prefixed = browserApi[0] !== specApi[0]; 5447 } 5448 5449 /** 5450 * @file media-error.js 5451 */ 5452 /** 5453 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. 5454 * 5455 * @param {number|string|Object|MediaError} value 5456 * This can be of multiple types: 5457 * - number: should be a standard error code 5458 * - string: an error message (the code will be 0) 5459 * - Object: arbitrary properties 5460 * - `MediaError` (native): used to populate a video.js `MediaError` object 5461 * - `MediaError` (video.js): will return itself if it's already a 5462 * video.js `MediaError` object. 5463 * 5464 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} 5465 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} 5466 * 5467 * @class MediaError 5468 */ 5469 5470 function MediaError(value) { 5471 // Allow redundant calls to this constructor to avoid having `instanceof` 5472 // checks peppered around the code. 5473 if (value instanceof MediaError) { 5474 return value; 5475 } 5476 5477 if (typeof value === 'number') { 5478 this.code = value; 5479 } else if (typeof value === 'string') { 5480 // default code is zero, so this is a custom error 5481 this.message = value; 5482 } else if (isObject(value)) { 5483 // We assign the `code` property manually because native `MediaError` objects 5484 // do not expose it as an own/enumerable property of the object. 5485 if (typeof value.code === 'number') { 5486 this.code = value.code; 5487 } 5488 5489 assign(this, value); 5490 } 5491 5492 if (!this.message) { 5493 this.message = MediaError.defaultMessages[this.code] || ''; 5494 } 5495 } 5496 /** 5497 * The error code that refers two one of the defined `MediaError` types 5498 * 5499 * @type {Number} 5500 */ 5501 5502 5503 MediaError.prototype.code = 0; 5504 /** 5505 * An optional message that to show with the error. Message is not part of the HTML5 5506 * video spec but allows for more informative custom errors. 5507 * 5508 * @type {String} 5509 */ 5510 5511 MediaError.prototype.message = ''; 5512 /** 5513 * An optional status code that can be set by plugins to allow even more detail about 5514 * the error. For example a plugin might provide a specific HTTP status code and an 5515 * error message for that code. Then when the plugin gets that error this class will 5516 * know how to display an error message for it. This allows a custom message to show 5517 * up on the `Player` error overlay. 5518 * 5519 * @type {Array} 5520 */ 5521 5522 MediaError.prototype.status = null; 5523 /** 5524 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the 5525 * specification listed under {@link MediaError} for more information. 5526 * 5527 * @enum {array} 5528 * @readonly 5529 * @property {string} 0 - MEDIA_ERR_CUSTOM 5530 * @property {string} 1 - MEDIA_ERR_ABORTED 5531 * @property {string} 2 - MEDIA_ERR_NETWORK 5532 * @property {string} 3 - MEDIA_ERR_DECODE 5533 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED 5534 * @property {string} 5 - MEDIA_ERR_ENCRYPTED 5535 */ 5536 5537 MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; 5538 /** 5539 * The default `MediaError` messages based on the {@link MediaError.errorTypes}. 5540 * 5541 * @type {Array} 5542 * @constant 5543 */ 5544 5545 MediaError.defaultMessages = { 5546 1: 'You aborted the media playback', 5547 2: 'A network error caused the media download to fail part-way.', 5548 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 5549 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 5550 5: 'The media is encrypted and we do not have the keys to decrypt it.' 5551 }; // Add types as properties on MediaError 5552 // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 5553 5554 for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { 5555 MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance 5556 5557 MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; 5558 } // jsdocs for instance/static members added above 5559 5560 var tuple = SafeParseTuple; 5561 5562 function SafeParseTuple(obj, reviver) { 5563 var json; 5564 var error = null; 5565 5566 try { 5567 json = JSON.parse(obj, reviver); 5568 } catch (err) { 5569 error = err; 5570 } 5571 5572 return [error, json]; 5573 } 5574 5575 /** 5576 * Returns whether an object is `Promise`-like (i.e. has a `then` method). 5577 * 5578 * @param {Object} value 5579 * An object that may or may not be `Promise`-like. 5580 * 5581 * @return {boolean} 5582 * Whether or not the object is `Promise`-like. 5583 */ 5584 function isPromise(value) { 5585 return value !== undefined && value !== null && typeof value.then === 'function'; 5586 } 5587 /** 5588 * Silence a Promise-like object. 5589 * 5590 * This is useful for avoiding non-harmful, but potentially confusing "uncaught 5591 * play promise" rejection error messages. 5592 * 5593 * @param {Object} value 5594 * An object that may or may not be `Promise`-like. 5595 */ 5596 5597 function silencePromise(value) { 5598 if (isPromise(value)) { 5599 value.then(null, function (e) {}); 5600 } 5601 } 5602 5603 /** 5604 * @file text-track-list-converter.js Utilities for capturing text track state and 5605 * re-creating tracks based on a capture. 5606 * 5607 * @module text-track-list-converter 5608 */ 5609 5610 /** 5611 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that 5612 * represents the {@link TextTrack}'s state. 5613 * 5614 * @param {TextTrack} track 5615 * The text track to query. 5616 * 5617 * @return {Object} 5618 * A serializable javascript representation of the TextTrack. 5619 * @private 5620 */ 5621 var trackToJson_ = function trackToJson_(track) { 5622 var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { 5623 if (track[prop]) { 5624 acc[prop] = track[prop]; 5625 } 5626 5627 return acc; 5628 }, { 5629 cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { 5630 return { 5631 startTime: cue.startTime, 5632 endTime: cue.endTime, 5633 text: cue.text, 5634 id: cue.id 5635 }; 5636 }) 5637 }); 5638 return ret; 5639 }; 5640 /** 5641 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the 5642 * state of all {@link TextTrack}s currently configured. The return array is compatible with 5643 * {@link text-track-list-converter:jsonToTextTracks}. 5644 * 5645 * @param {Tech} tech 5646 * The tech object to query 5647 * 5648 * @return {Array} 5649 * A serializable javascript representation of the {@link Tech}s 5650 * {@link TextTrackList}. 5651 */ 5652 5653 5654 var textTracksToJson = function textTracksToJson(tech) { 5655 var trackEls = tech.$$('track'); 5656 var trackObjs = Array.prototype.map.call(trackEls, function (t) { 5657 return t.track; 5658 }); 5659 var tracks = Array.prototype.map.call(trackEls, function (trackEl) { 5660 var json = trackToJson_(trackEl.track); 5661 5662 if (trackEl.src) { 5663 json.src = trackEl.src; 5664 } 5665 5666 return json; 5667 }); 5668 return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { 5669 return trackObjs.indexOf(track) === -1; 5670 }).map(trackToJson_)); 5671 }; 5672 /** 5673 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript 5674 * object {@link TextTrack} representations. 5675 * 5676 * @param {Array} json 5677 * An array of `TextTrack` representation objects, like those that would be 5678 * produced by `textTracksToJson`. 5679 * 5680 * @param {Tech} tech 5681 * The `Tech` to create the `TextTrack`s on. 5682 */ 5683 5684 5685 var jsonToTextTracks = function jsonToTextTracks(json, tech) { 5686 json.forEach(function (track) { 5687 var addedTrack = tech.addRemoteTextTrack(track).track; 5688 5689 if (!track.src && track.cues) { 5690 track.cues.forEach(function (cue) { 5691 return addedTrack.addCue(cue); 5692 }); 5693 } 5694 }); 5695 return tech.textTracks(); 5696 }; 5697 5698 var textTrackConverter = { 5699 textTracksToJson: textTracksToJson, 5700 jsonToTextTracks: jsonToTextTracks, 5701 trackToJson_: trackToJson_ 5702 }; 5703 5704 var keycode = createCommonjsModule(function (module, exports) { 5705 // Source: http://jsfiddle.net/vWx8V/ 5706 // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes 5707 5708 /** 5709 * Conenience method returns corresponding value for given keyName or keyCode. 5710 * 5711 * @param {Mixed} keyCode {Number} or keyName {String} 5712 * @return {Mixed} 5713 * @api public 5714 */ 5715 function keyCode(searchInput) { 5716 // Keyboard Events 5717 if (searchInput && 'object' === typeof searchInput) { 5718 var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; 5719 if (hasKeyCode) searchInput = hasKeyCode; 5720 } // Numbers 5721 5722 5723 if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string) 5724 5725 var search = String(searchInput); // check codes 5726 5727 var foundNamedKey = codes[search.toLowerCase()]; 5728 if (foundNamedKey) return foundNamedKey; // check aliases 5729 5730 var foundNamedKey = aliases[search.toLowerCase()]; 5731 if (foundNamedKey) return foundNamedKey; // weird character? 5732
vendor: 2,236 bytes, lines 5733-5818
5733 if (search.length === 1) return search.charCodeAt(0); 5734 return undefined; 5735 } 5736 /** 5737 * Compares a keyboard event with a given keyCode or keyName. 5738 * 5739 * @param {Event} event Keyboard event that should be tested 5740 * @param {Mixed} keyCode {Number} or keyName {String} 5741 * @return {Boolean} 5742 * @api public 5743 */ 5744 5745 5746 keyCode.isEventKey = function isEventKey(event, nameOrCode) { 5747 if (event && 'object' === typeof event) { 5748 var keyCode = event.which || event.keyCode || event.charCode; 5749 5750 if (keyCode === null || keyCode === undefined) { 5751 return false; 5752 } 5753 5754 if (typeof nameOrCode === 'string') { 5755 // check codes 5756 var foundNamedKey = codes[nameOrCode.toLowerCase()]; 5757 5758 if (foundNamedKey) { 5759 return foundNamedKey === keyCode; 5760 } // check aliases 5761 5762 5763 var foundNamedKey = aliases[nameOrCode.toLowerCase()]; 5764 5765 if (foundNamedKey) { 5766 return foundNamedKey === keyCode; 5767 } 5768 } else if (typeof nameOrCode === 'number') { 5769 return nameOrCode === keyCode; 5770 } 5771 5772 return false; 5773 } 5774 }; 5775 5776 exports = module.exports = keyCode; 5777 /** 5778 * Get by name 5779 * 5780 * exports.code['enter'] // => 13 5781 */ 5782 5783 var codes = exports.code = exports.codes = { 5784 'backspace': 8, 5785 'tab': 9, 5786 'enter': 13, 5787 'shift': 16, 5788 'ctrl': 17, 5789 'alt': 18, 5790 'pause/break': 19, 5791 'caps lock': 20, 5792 'esc': 27, 5793 'space': 32, 5794 'page up': 33, 5795 'page down': 34, 5796 'end': 35, 5797 'home': 36, 5798 'left': 37, 5799 'up': 38, 5800 'right': 39, 5801 'down': 40, 5802 'insert': 45, 5803 'delete': 46, 5804 'command': 91, 5805 'left command': 91, 5806 'right command': 93, 5807 'numpad *': 106, 5808 'numpad +': 107, 5809 'numpad -': 109, 5810 'numpad .': 110, 5811 'numpad /': 111, 5812 'num lock': 144, 5813 'scroll lock': 145, 5814 'my computer': 182, 5815 'my calculator': 183, 5816 ';': 186, 5817 '=': 187, 5818 ',': 188,
5819 '-': 189, 5820 '.': 190, 5821 '/': 191, 5822 '`': 192, 5823 '[': 219, 5824 '\\': 220, 5825 ']': 221, 5826 "'": 222 5827 }; // Helper aliases 5828 5829 var aliases = exports.aliases = { 5830 'windows': 91, 5831 'â§': 16, 5832 'â¥': 18, 5833 'â': 17, 5834 'â': 91, 5835 'ctl': 17, 5836 'control': 17, 5837 'option': 18, 5838 'pause': 19, 5839 'break': 19, 5840 'caps': 20, 5841 'return': 13, 5842 'escape': 27, 5843 'spc': 32, 5844 'spacebar': 32, 5845 'pgup': 33, 5846 'pgdn': 34, 5847 'ins': 45, 5848 'del': 46, 5849 'cmd': 91 5850 }; 5851 /*! 5852 * Programatically add the following 5853 */ 5854 // lower case chars 5855 5856 for (i = 97; i < 123; i++) { 5857 codes[String.fromCharCode(i)] = i - 32; 5858 } // numbers 5859 5860 5861 for (var i = 48; i < 58; i++) { 5862 codes[i - 48] = i; 5863 } // function keys 5864 5865 5866 for (i = 1; i < 13; i++) { 5867 codes['f' + i] = i + 111; 5868 } // numpad keys 5869 5870 5871 for (i = 0; i < 10; i++) { 5872 codes['numpad ' + i] = i + 96; 5873 } 5874 /** 5875 * Get by code 5876 * 5877 * exports.name[13] // => 'Enter' 5878 */ 5879 5880 5881 var names = exports.names = exports.title = {}; // title for backward compat 5882 // Create reverse mapping 5883 5884 for (i in codes) { 5885 names[codes[i]] = i; 5886 } // Add aliases 5887 5888 5889 for (var alias in aliases) { 5890 codes[alias] = aliases[alias]; 5891 } 5892 }); 5893 var keycode_1 = keycode.code; 5894 var keycode_2 = keycode.codes; 5895 var keycode_3 = keycode.aliases; 5896 var keycode_4 = keycode.names; 5897 var keycode_5 = keycode.title; 5898 5899 var MODAL_CLASS_NAME = 'vjs-modal-dialog'; 5900 /** 5901 * The `ModalDialog` displays over the video and its controls, which blocks 5902 * interaction with the player until it is closed. 5903 * 5904 * Modal dialogs include a "Close" button and will close when that button 5905 * is activated - or when ESC is pressed anywhere. 5906 * 5907 * @extends Component 5908 */ 5909 5910 var ModalDialog = /*#__PURE__*/function (_Component) { 5911 inheritsLoose(ModalDialog, _Component); 5912 5913 /** 5914 * Create an instance of this class. 5915 * 5916 * @param {Player} player 5917 * The `Player` that this class should be attached to. 5918 * 5919 * @param {Object} [options] 5920 * The key/value store of player options. 5921 * 5922 * @param {Mixed} [options.content=undefined] 5923 * Provide customized content for this modal. 5924 * 5925 * @param {string} [options.description] 5926 * A text description for the modal, primarily for accessibility. 5927 * 5928 * @param {boolean} [options.fillAlways=false] 5929 * Normally, modals are automatically filled only the first time 5930 * they open. This tells the modal to refresh its content 5931 * every time it opens. 5932 * 5933 * @param {string} [options.label] 5934 * A text label for the modal, primarily for accessibility. 5935 * 5936 * @param {boolean} [options.pauseOnOpen=true] 5937 * If `true`, playback will will be paused if playing when 5938 * the modal opens, and resumed when it closes. 5939 * 5940 * @param {boolean} [options.temporary=true] 5941 * If `true`, the modal can only be opened once; it will be 5942 * disposed as soon as it's closed. 5943 * 5944 * @param {boolean} [options.uncloseable=false] 5945 * If `true`, the user will not be able to close the modal 5946 * through the UI in the normal ways. Programmatic closing is 5947 * still possible. 5948 */ 5949 function ModalDialog(player, options) { 5950 var _this; 5951 5952 _this = _Component.call(this, player, options) || this; 5953 _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; 5954 5955 _this.closeable(!_this.options_.uncloseable); 5956 5957 _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized 5958 // because we only want the contents of the modal in the contentEl 5959 // (not the UI elements like the close button). 5960 5961 5962 _this.contentEl_ = createEl('div', { 5963 className: MODAL_CLASS_NAME + "-content" 5964 }, { 5965 role: 'document' 5966 }); 5967 _this.descEl_ = createEl('p', { 5968 className: MODAL_CLASS_NAME + "-description vjs-control-text", 5969 id: _this.el().getAttribute('aria-describedby') 5970 }); 5971 textContent(_this.descEl_, _this.description()); 5972 5973 _this.el_.appendChild(_this.descEl_); 5974 5975 _this.el_.appendChild(_this.contentEl_); 5976 5977 return _this; 5978 } 5979 /** 5980 * Create the `ModalDialog`'s DOM element 5981 * 5982 * @return {Element} 5983 * The DOM element that gets created. 5984 */ 5985 5986 5987 var _proto = ModalDialog.prototype; 5988 5989 _proto.createEl = function createEl() { 5990 return _Component.prototype.createEl.call(this, 'div', { 5991 className: this.buildCSSClass(), 5992 tabIndex: -1 5993 }, { 5994 'aria-describedby': this.id() + "_description", 5995 'aria-hidden': 'true', 5996 'aria-label': this.label(), 5997 'role': 'dialog' 5998 }); 5999 }; 6000 6001 _proto.dispose = function dispose() { 6002 this.contentEl_ = null; 6003 this.descEl_ = null; 6004 this.previouslyActiveEl_ = null; 6005 6006 _Component.prototype.dispose.call(this); 6007 } 6008 /** 6009 * Builds the default DOM `className`. 6010 * 6011 * @return {string} 6012 * The DOM `className` for this object. 6013 */ 6014 ; 6015 6016 _proto.buildCSSClass = function buildCSSClass() { 6017 return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this); 6018 } 6019 /**
vendor: 10,167 bytes, lines 6020-6355
6020 * Returns the label string for this modal. Primarily used for accessibility. 6021 * 6022 * @return {string} 6023 * the localized or raw label of this modal. 6024 */ 6025 ; 6026 6027 _proto.label = function label() { 6028 return this.localize(this.options_.label || 'Modal Window'); 6029 } 6030 /** 6031 * Returns the description string for this modal. Primarily used for 6032 * accessibility. 6033 * 6034 * @return {string} 6035 * The localized or raw description of this modal. 6036 */ 6037 ; 6038 6039 _proto.description = function description() { 6040 var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable. 6041 6042 if (this.closeable()) { 6043 desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); 6044 } 6045 6046 return desc; 6047 } 6048 /** 6049 * Opens the modal. 6050 * 6051 * @fires ModalDialog#beforemodalopen 6052 * @fires ModalDialog#modalopen 6053 */ 6054 ; 6055 6056 _proto.open = function open() { 6057 if (!this.opened_) { 6058 var player = this.player(); 6059 /** 6060 * Fired just before a `ModalDialog` is opened. 6061 * 6062 * @event ModalDialog#beforemodalopen 6063 * @type {EventTarget~Event} 6064 */ 6065 6066 this.trigger('beforemodalopen'); 6067 this.opened_ = true; // Fill content if the modal has never opened before and 6068 // never been filled. 6069 6070 if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { 6071 this.fill(); 6072 } // If the player was playing, pause it and take note of its previously 6073 // playing state. 6074 6075 6076 this.wasPlaying_ = !player.paused(); 6077 6078 if (this.options_.pauseOnOpen && this.wasPlaying_) { 6079 player.pause(); 6080 } 6081 6082 this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled. 6083 6084 this.hadControls_ = player.controls(); 6085 player.controls(false); 6086 this.show(); 6087 this.conditionalFocus_(); 6088 this.el().setAttribute('aria-hidden', 'false'); 6089 /** 6090 * Fired just after a `ModalDialog` is opened. 6091 * 6092 * @event ModalDialog#modalopen 6093 * @type {EventTarget~Event} 6094 */ 6095 6096 this.trigger('modalopen'); 6097 this.hasBeenOpened_ = true; 6098 } 6099 } 6100 /** 6101 * If the `ModalDialog` is currently open or closed. 6102 * 6103 * @param {boolean} [value] 6104 * If given, it will open (`true`) or close (`false`) the modal. 6105 * 6106 * @return {boolean} 6107 * the current open state of the modaldialog 6108 */ 6109 ; 6110 6111 _proto.opened = function opened(value) { 6112 if (typeof value === 'boolean') { 6113 this[value ? 'open' : 'close'](); 6114 } 6115 6116 return this.opened_; 6117 } 6118 /** 6119 * Closes the modal, does nothing if the `ModalDialog` is 6120 * not open. 6121 * 6122 * @fires ModalDialog#beforemodalclose 6123 * @fires ModalDialog#modalclose 6124 */ 6125 ; 6126 6127 _proto.close = function close() { 6128 if (!this.opened_) { 6129 return; 6130 } 6131 6132 var player = this.player(); 6133 /** 6134 * Fired just before a `ModalDialog` is closed. 6135 * 6136 * @event ModalDialog#beforemodalclose 6137 * @type {EventTarget~Event} 6138 */ 6139 6140 this.trigger('beforemodalclose'); 6141 this.opened_ = false; 6142 6143 if (this.wasPlaying_ && this.options_.pauseOnOpen) { 6144 player.play(); 6145 } 6146 6147 this.off('keydown', this.handleKeyDown); 6148 6149 if (this.hadControls_) { 6150 player.controls(true); 6151 } 6152 6153 this.hide(); 6154 this.el().setAttribute('aria-hidden', 'true'); 6155 /** 6156 * Fired just after a `ModalDialog` is closed. 6157 * 6158 * @event ModalDialog#modalclose 6159 * @type {EventTarget~Event} 6160 */ 6161 6162 this.trigger('modalclose'); 6163 this.conditionalBlur_(); 6164 6165 if (this.options_.temporary) { 6166 this.dispose(); 6167 } 6168 } 6169 /** 6170 * Check to see if the `ModalDialog` is closeable via the UI. 6171 * 6172 * @param {boolean} [value] 6173 * If given as a boolean, it will set the `closeable` option. 6174 * 6175 * @return {boolean} 6176 * Returns the final value of the closable option. 6177 */ 6178 ; 6179 6180 _proto.closeable = function closeable(value) { 6181 if (typeof value === 'boolean') { 6182 var closeable = this.closeable_ = !!value; 6183 var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one. 6184 6185 if (closeable && !close) { 6186 // The close button should be a child of the modal - not its 6187 // content element, so temporarily change the content element. 6188 var temp = this.contentEl_; 6189 this.contentEl_ = this.el_; 6190 close = this.addChild('closeButton', { 6191 controlText: 'Close Modal Dialog' 6192 }); 6193 this.contentEl_ = temp; 6194 this.on(close, 'close', this.close); 6195 } // If this is being made uncloseable and has a close button, remove it. 6196 6197 6198 if (!closeable && close) { 6199 this.off(close, 'close', this.close); 6200 this.removeChild(close); 6201 close.dispose(); 6202 } 6203 } 6204 6205 return this.closeable_; 6206 } 6207 /** 6208 * Fill the modal's content element with the modal's "content" option. 6209 * The content element will be emptied before this change takes place. 6210 */ 6211 ; 6212 6213 _proto.fill = function fill() { 6214 this.fillWith(this.content()); 6215 } 6216 /** 6217 * Fill the modal's content element with arbitrary content. 6218 * The content element will be emptied before this change takes place. 6219 * 6220 * @fires ModalDialog#beforemodalfill 6221 * @fires ModalDialog#modalfill 6222 * 6223 * @param {Mixed} [content] 6224 * The same rules apply to this as apply to the `content` option. 6225 */ 6226 ; 6227 6228 _proto.fillWith = function fillWith(content) { 6229 var contentEl = this.contentEl(); 6230 var parentEl = contentEl.parentNode; 6231 var nextSiblingEl = contentEl.nextSibling; 6232 /** 6233 * Fired just before a `ModalDialog` is filled with content. 6234 * 6235 * @event ModalDialog#beforemodalfill 6236 * @type {EventTarget~Event} 6237 */ 6238 6239 this.trigger('beforemodalfill'); 6240 this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing 6241 // manipulation to avoid modifying the live DOM multiple times. 6242 6243 parentEl.removeChild(contentEl); 6244 this.empty(); 6245 insertContent(contentEl, content); 6246 /** 6247 * Fired just after a `ModalDialog` is filled with content. 6248 * 6249 * @event ModalDialog#modalfill 6250 * @type {EventTarget~Event} 6251 */ 6252 6253 this.trigger('modalfill'); // Re-inject the re-filled content element. 6254 6255 if (nextSiblingEl) { 6256 parentEl.insertBefore(contentEl, nextSiblingEl); 6257 } else { 6258 parentEl.appendChild(contentEl); 6259 } // make sure that the close button is last in the dialog DOM 6260 6261 6262 var closeButton = this.getChild('closeButton'); 6263 6264 if (closeButton) { 6265 parentEl.appendChild(closeButton.el_); 6266 } 6267 } 6268 /** 6269 * Empties the content element. This happens anytime the modal is filled. 6270 * 6271 * @fires ModalDialog#beforemodalempty 6272 * @fires ModalDialog#modalempty 6273 */ 6274 ; 6275 6276 _proto.empty = function empty() { 6277 /** 6278 * Fired just before a `ModalDialog` is emptied. 6279 * 6280 * @event ModalDialog#beforemodalempty 6281 * @type {EventTarget~Event} 6282 */ 6283 this.trigger('beforemodalempty'); 6284 emptyEl(this.contentEl()); 6285 /** 6286 * Fired just after a `ModalDialog` is emptied. 6287 * 6288 * @event ModalDialog#modalempty 6289 * @type {EventTarget~Event} 6290 */ 6291 6292 this.trigger('modalempty'); 6293 } 6294 /** 6295 * Gets or sets the modal content, which gets normalized before being 6296 * rendered into the DOM. 6297 * 6298 * This does not update the DOM or fill the modal, but it is called during 6299 * that process. 6300 * 6301 * @param {Mixed} [value] 6302 * If defined, sets the internal content value to be used on the 6303 * next call(s) to `fill`. This value is normalized before being 6304 * inserted. To "clear" the internal content value, pass `null`. 6305 * 6306 * @return {Mixed} 6307 * The current content of the modal dialog 6308 */ 6309 ; 6310 6311 _proto.content = function content(value) { 6312 if (typeof value !== 'undefined') { 6313 this.content_ = value; 6314 } 6315 6316 return this.content_; 6317 } 6318 /** 6319 * conditionally focus the modal dialog if focus was previously on the player. 6320 * 6321 * @private 6322 */ 6323 ; 6324 6325 _proto.conditionalFocus_ = function conditionalFocus_() { 6326 var activeEl = document.activeElement; 6327 var playerEl = this.player_.el_; 6328 this.previouslyActiveEl_ = null; 6329 6330 if (playerEl.contains(activeEl) || playerEl === activeEl) { 6331 this.previouslyActiveEl_ = activeEl; 6332 this.focus(); 6333 } 6334 } 6335 /** 6336 * conditionally blur the element and refocus the last focused element 6337 * 6338 * @private 6339 */ 6340 ; 6341 6342 _proto.conditionalBlur_ = function conditionalBlur_() { 6343 if (this.previouslyActiveEl_) { 6344 this.previouslyActiveEl_.focus(); 6345 this.previouslyActiveEl_ = null; 6346 } 6347 } 6348 /** 6349 * Keydown handler. Attached when modal is focused. 6350 * 6351 * @listens keydown 6352 */ 6353 ; 6354 6355 _proto.handleKeyDown = function handleKeyDown(event) {
6356 // Do not allow keydowns to reach out of the modal dialog. 6357 event.stopPropagation(); 6358 6359 if (keycode.isEventKey(event, 'Escape') && this.closeable()) { 6360 event.preventDefault(); 6361 this.close(); 6362 return; 6363 } // exit early if it isn't a tab key 6364 6365 6366 if (!keycode.isEventKey(event, 'Tab')) { 6367 return; 6368 } 6369 6370 var focusableEls = this.focusableEls_(); 6371 var activeEl = this.el_.querySelector(':focus'); 6372 var focusIndex; 6373 6374 for (var i = 0; i < focusableEls.length; i++) { 6375 if (activeEl === focusableEls[i]) { 6376 focusIndex = i; 6377 break; 6378 } 6379 } 6380 6381 if (document.activeElement === this.el_) { 6382 focusIndex = 0; 6383 } 6384 6385 if (event.shiftKey && focusIndex === 0) { 6386 focusableEls[focusableEls.length - 1].focus(); 6387 event.preventDefault(); 6388 } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { 6389 focusableEls[0].focus(); 6390 event.preventDefault(); 6391 } 6392 } 6393 /** 6394 * get all focusable elements 6395 * 6396 * @private 6397 */ 6398 ; 6399 6400 _proto.focusableEls_ = function focusableEls_() { 6401 var allChildren = this.el_.querySelectorAll('*'); 6402 return Array.prototype.filter.call(allChildren, function (child) { 6403 return (child instanceof window$3.HTMLAnchorElement || child instanceof window$3.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window$3.HTMLInputElement || child instanceof window$3.HTMLSelectElement || child instanceof window$3.HTMLTextAreaElement || child instanceof window$3.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window$3.HTMLIFrameElement || child instanceof window$3.HTMLObjectElement || child instanceof window$3.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); 6404 }); 6405 }; 6406 6407 return ModalDialog; 6408 }(Component); 6409 /** 6410 * Default options for `ModalDialog` default options. 6411 * 6412 * @type {Object} 6413 * @private 6414 */ 6415 6416 6417 ModalDialog.prototype.options_ = { 6418 pauseOnOpen: true, 6419 temporary: true 6420 }; 6421 Component.registerComponent('ModalDialog', ModalDialog); 6422 6423 /** 6424 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and 6425 * {@link VideoTrackList} 6426 * 6427 * @extends EventTarget 6428 */ 6429 6430 var TrackList = /*#__PURE__*/function (_EventTarget) { 6431 inheritsLoose(TrackList, _EventTarget); 6432 6433 /** 6434 * Create an instance of this class 6435 * 6436 * @param {Track[]} tracks 6437 * A list of tracks to initialize the list with. 6438 * 6439 * @abstract 6440 */ 6441 function TrackList(tracks) { 6442 var _this; 6443 6444 if (tracks === void 0) { 6445 tracks = []; 6446 } 6447 6448 _this = _EventTarget.call(this) || this; 6449 _this.tracks_ = []; 6450 /** 6451 * @memberof TrackList 6452 * @member {number} length 6453 * The current number of `Track`s in the this Trackist. 6454 * @instance 6455 */ 6456 6457 Object.defineProperty(assertThisInitialized(_this), 'length', { 6458 get: function get() { 6459 return this.tracks_.length; 6460 } 6461 }); 6462 6463 for (var i = 0; i < tracks.length; i++) { 6464 _this.addTrack(tracks[i]); 6465 } 6466 6467 return _this; 6468 } 6469 /** 6470 * Add a {@link Track} to the `TrackList` 6471 * 6472 * @param {Track} track 6473 * The audio, video, or text track to add to the list. 6474 * 6475 * @fires TrackList#addtrack 6476 */ 6477 6478 6479 var _proto = TrackList.prototype; 6480 6481 _proto.addTrack = function addTrack(track) { 6482 var index = this.tracks_.length; 6483 6484 if (!('' + index in this)) { 6485 Object.defineProperty(this, index, { 6486 get: function get() { 6487 return this.tracks_[index]; 6488 } 6489 }); 6490 } // Do not add duplicate tracks 6491 6492
6493 if (this.tracks_.indexOf(track) === -1) { 6494 this.tracks_.push(track); 6495 /** 6496 * Triggered when a track is added to a track list. 6497 * 6498 * @event TrackList#addtrack 6499 * @type {EventTarget~Event} 6500 * @property {Track} track 6501 * A reference to track that was added. 6502 */ 6503 6504 this.trigger({ 6505 track: track, 6506 type: 'addtrack', 6507 target: this 6508 }); 6509 } 6510 } 6511 /** 6512 * Remove a {@link Track} from the `TrackList` 6513 * 6514 * @param {Track} rtrack 6515 * The audio, video, or text track to remove from the list. 6516 * 6517 * @fires TrackList#removetrack 6518 */ 6519 ; 6520 6521 _proto.removeTrack = function removeTrack(rtrack) { 6522 var track; 6523 6524 for (var i = 0, l = this.length; i < l; i++) { 6525 if (this[i] === rtrack) { 6526 track = this[i]; 6527 6528 if (track.off) { 6529 track.off(); 6530 } 6531 6532 this.tracks_.splice(i, 1); 6533 break; 6534 } 6535 } 6536 6537 if (!track) { 6538 return; 6539 } 6540 /** 6541 * Triggered when a track is removed from track list. 6542 * 6543 * @event TrackList#removetrack 6544 * @type {EventTarget~Event} 6545 * @property {Track} track 6546 * A reference to track that was removed. 6547 */ 6548 6549 6550 this.trigger({ 6551 track: track, 6552 type: 'removetrack', 6553 target: this 6554 }); 6555 } 6556 /** 6557 * Get a Track from the TrackList by a tracks id 6558 * 6559 * @param {string} id - the id of the track to get 6560 * @method getTrackById 6561 * @return {Track} 6562 * @private 6563 */ 6564 ; 6565 6566 _proto.getTrackById = function getTrackById(id) { 6567 var result = null; 6568 6569 for (var i = 0, l = this.length; i < l; i++) { 6570 var track = this[i]; 6571 6572 if (track.id === id) { 6573 result = track; 6574 break; 6575 } 6576 } 6577 6578 return result; 6579 }; 6580 6581 return TrackList; 6582 }(EventTarget); 6583 /** 6584 * Triggered when a different track is selected/enabled. 6585 * 6586 * @event TrackList#change 6587 * @type {EventTarget~Event} 6588 */ 6589 6590 /** 6591 * Events that can be called with on + eventName. See {@link EventHandler}. 6592 * 6593 * @property {Object} TrackList#allowedEvents_ 6594 * @private 6595 */ 6596 6597 6598 TrackList.prototype.allowedEvents_ = { 6599 change: 'change', 6600 addtrack: 'addtrack', 6601 removetrack: 'removetrack' 6602 }; // emulate attribute EventHandler support to allow for feature detection 6603 6604 for (var event in TrackList.prototype.allowedEvents_) { 6605 TrackList.prototype['on' + event] = null; 6606 } 6607 6608 /** 6609 * Anywhere we call this function we diverge from the spec 6610 * as we only support one enabled audiotrack at a time 6611 * 6612 * @param {AudioTrackList} list 6613 * list to work on 6614 * 6615 * @param {AudioTrack} track 6616 * The track to skip 6617 * 6618 * @private 6619 */ 6620 6621 var disableOthers = function disableOthers(list, track) { 6622 for (var i = 0; i < list.length; i++) { 6623 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6624 continue; 6625 } // another audio track is enabled, disable it 6626 6627 6628 list[i].enabled = false; 6629 } 6630 }; 6631 /** 6632 * The current list of {@link AudioTrack} for a media file. 6633 * 6634 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} 6635 * @extends TrackList 6636 */ 6637 6638 6639 var AudioTrackList = /*#__PURE__*/function (_TrackList) { 6640 inheritsLoose(AudioTrackList, _TrackList); 6641 6642 /** 6643 * Create an instance of this class. 6644 * 6645 * @param {AudioTrack[]} [tracks=[]] 6646 * A list of `AudioTrack` to instantiate the list with. 6647 */ 6648 function AudioTrackList(tracks) { 6649 var _this; 6650 6651 if (tracks === void 0) { 6652 tracks = []; 6653 } 6654 6655 // make sure only 1 track is enabled 6656 // sorted from last index to first index 6657 for (var i = tracks.length - 1; i >= 0; i--) { 6658 if (tracks[i].enabled) { 6659 disableOthers(tracks, tracks[i]); 6660 break; 6661 } 6662 } 6663 6664 _this = _TrackList.call(this, tracks) || this; 6665 _this.changing_ = false;
6666 return _this; 6667 } 6668 /** 6669 * Add an {@link AudioTrack} to the `AudioTrackList`. 6670 * 6671 * @param {AudioTrack} track 6672 * The AudioTrack to add to the list 6673 * 6674 * @fires TrackList#addtrack 6675 */ 6676 6677 6678 var _proto = AudioTrackList.prototype; 6679 6680 _proto.addTrack = function addTrack(track) { 6681 var _this2 = this; 6682 6683 if (track.enabled) { 6684 disableOthers(this, track); 6685 } 6686 6687 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6688 6689 6690 if (!track.addEventListener) { 6691 return; 6692 } 6693 6694 track.enabledChange_ = function () { 6695 // when we are disabling other tracks (since we don't support 6696 // more than one track at a time) we will set changing_ 6697 // to true so that we don't trigger additional change events 6698 if (_this2.changing_) { 6699 return; 6700 } 6701 6702 _this2.changing_ = true; 6703 disableOthers(_this2, track); 6704 _this2.changing_ = false; 6705 6706 _this2.trigger('change'); 6707 }; 6708 /** 6709 * @listens AudioTrack#enabledchange 6710 * @fires TrackList#change 6711 */ 6712 6713 6714 track.addEventListener('enabledchange', track.enabledChange_); 6715 }; 6716 6717 _proto.removeTrack = function removeTrack(rtrack) { 6718 _TrackList.prototype.removeTrack.call(this, rtrack); 6719 6720 if (rtrack.removeEventListener && rtrack.enabledChange_) { 6721 rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); 6722 rtrack.enabledChange_ = null; 6723 } 6724 }; 6725 6726 return AudioTrackList; 6727 }(TrackList); 6728 6729 /** 6730 * Un-select all other {@link VideoTrack}s that are selected. 6731 * 6732 * @param {VideoTrackList} list 6733 * list to work on 6734 * 6735 * @param {VideoTrack} track 6736 * The track to skip 6737 * 6738 * @private 6739 */ 6740 6741 var disableOthers$1 = function disableOthers(list, track) { 6742 for (var i = 0; i < list.length; i++) { 6743 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6744 continue; 6745 } // another video track is enabled, disable it 6746 6747 6748 list[i].selected = false; 6749 } 6750 }; 6751 /** 6752 * The current list of {@link VideoTrack} for a video. 6753 * 6754 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} 6755 * @extends TrackList 6756 */ 6757 6758 6759 var VideoTrackList = /*#__PURE__*/function (_TrackList) { 6760 inheritsLoose(VideoTrackList, _TrackList); 6761 6762 /** 6763 * Create an instance of this class. 6764 * 6765 * @param {VideoTrack[]} [tracks=[]] 6766 * A list of `VideoTrack` to instantiate the list with. 6767 */ 6768 function VideoTrackList(tracks) { 6769 var _this; 6770 6771 if (tracks === void 0) { 6772 tracks = []; 6773 } 6774 6775 // make sure only 1 track is enabled 6776 // sorted from last index to first index 6777 for (var i = tracks.length - 1; i >= 0; i--) { 6778 if (tracks[i].selected) { 6779 disableOthers$1(tracks, tracks[i]); 6780 break; 6781 } 6782 } 6783 6784 _this = _TrackList.call(this, tracks) || this; 6785 _this.changing_ = false; 6786 /** 6787 * @member {number} VideoTrackList#selectedIndex 6788 * The current index of the selected {@link VideoTrack`}. 6789 */ 6790 6791 Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', { 6792 get: function get() { 6793 for (var _i = 0; _i < this.length; _i++) { 6794 if (this[_i].selected) { 6795 return _i; 6796 } 6797 } 6798 6799 return -1; 6800 }, 6801 set: function set() {} 6802 }); 6803 return _this; 6804 } 6805 /** 6806 * Add a {@link VideoTrack} to the `VideoTrackList`. 6807 * 6808 * @param {VideoTrack} track 6809 * The VideoTrack to add to the list 6810 * 6811 * @fires TrackList#addtrack 6812 */ 6813 6814 6815 var _proto = VideoTrackList.prototype; 6816 6817 _proto.addTrack = function addTrack(track) { 6818 var _this2 = this; 6819 6820 if (track.selected) { 6821 disableOthers$1(this, track); 6822 } 6823 6824 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6825 6826
6827 if (!track.addEventListener) { 6828 return; 6829 } 6830 6831 track.selectedChange_ = function () { 6832 if (_this2.changing_) { 6833 return; 6834 } 6835 6836 _this2.changing_ = true; 6837 disableOthers$1(_this2, track); 6838 _this2.changing_ = false; 6839 6840 _this2.trigger('change'); 6841 }; 6842 /** 6843 * @listens VideoTrack#selectedchange 6844 * @fires TrackList#change 6845 */ 6846 6847 6848 track.addEventListener('selectedchange', track.selectedChange_); 6849 }; 6850 6851 _proto.removeTrack = function removeTrack(rtrack) { 6852 _TrackList.prototype.removeTrack.call(this, rtrack); 6853 6854 if (rtrack.removeEventListener && rtrack.selectedChange_) { 6855 rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); 6856 rtrack.selectedChange_ = null; 6857 } 6858 }; 6859 6860 return VideoTrackList; 6861 }(TrackList); 6862 6863 /** 6864 * The current list of {@link TextTrack} for a media file. 6865 * 6866 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} 6867 * @extends TrackList 6868 */ 6869 6870 var TextTrackList = /*#__PURE__*/function (_TrackList) { 6871 inheritsLoose(TextTrackList, _TrackList); 6872 6873 function TextTrackList() { 6874 return _TrackList.apply(this, arguments) || this; 6875 } 6876 6877 var _proto = TextTrackList.prototype; 6878 6879 /** 6880 * Add a {@link TextTrack} to the `TextTrackList` 6881 * 6882 * @param {TextTrack} track 6883 * The text track to add to the list. 6884 * 6885 * @fires TrackList#addtrack 6886 */ 6887 _proto.addTrack = function addTrack(track) { 6888 var _this = this; 6889 6890 _TrackList.prototype.addTrack.call(this, track); 6891 6892 if (!this.queueChange_) { 6893 this.queueChange_ = function () { 6894 return _this.queueTrigger('change'); 6895 }; 6896 } 6897 6898 if (!this.triggerSelectedlanguagechange) { 6899 this.triggerSelectedlanguagechange_ = function () { 6900 return _this.trigger('selectedlanguagechange'); 6901 }; 6902 } 6903 /** 6904 * @listens TextTrack#modechange 6905 * @fires TrackList#change 6906 */ 6907 6908 6909 track.addEventListener('modechange', this.queueChange_); 6910 var nonLanguageTextTrackKind = ['metadata', 'chapters']; 6911 6912 if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { 6913 track.addEventListener('modechange', this.triggerSelectedlanguagechange_); 6914 } 6915 }; 6916 6917 _proto.removeTrack = function removeTrack(rtrack) { 6918 _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added 6919 6920 6921 if (rtrack.removeEventListener) { 6922 if (this.queueChange_) { 6923 rtrack.removeEventListener('modechange', this.queueChange_); 6924 } 6925 6926 if (this.selectedlanguagechange_) { 6927 rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); 6928 } 6929 } 6930 }; 6931 6932 return TextTrackList; 6933 }(TrackList); 6934 6935 /** 6936 * @file html-track-element-list.js 6937 */ 6938 6939 /** 6940 * The current list of {@link HtmlTrackElement}s. 6941 */ 6942 var HtmlTrackElementList = /*#__PURE__*/function () { 6943 /** 6944 * Create an instance of this class. 6945 * 6946 * @param {HtmlTrackElement[]} [tracks=[]] 6947 * A list of `HtmlTrackElement` to instantiate the list with. 6948 */ 6949 function HtmlTrackElementList(trackElements) { 6950 if (trackElements === void 0) { 6951 trackElements = []; 6952 } 6953 6954 this.trackElements_ = []; 6955 /** 6956 * @memberof HtmlTrackElementList 6957 * @member {number} length 6958 * The current number of `Track`s in the this Trackist. 6959 * @instance 6960 */ 6961 6962 Object.defineProperty(this, 'length', { 6963 get: function get() { 6964 return this.trackElements_.length; 6965 } 6966 }); 6967 6968 for (var i = 0, length = trackElements.length; i < length; i++) { 6969 this.addTrackElement_(trackElements[i]); 6970 } 6971 } 6972 /** 6973 * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` 6974 * 6975 * @param {HtmlTrackElement} trackElement 6976 * The track element to add to the list. 6977 * 6978 * @private 6979 */ 6980 6981 6982 var _proto = HtmlTrackElementList.prototype; 6983 6984 _proto.addTrackElement_ = function addTrackElement_(trackElement) { 6985 var index = this.trackElements_.length; 6986 6987 if (!('' + index in this)) { 6988 Object.defineProperty(this, index, { 6989 get: function get() { 6990 return this.trackElements_[index]; 6991 } 6992 }); 6993 } // Do not add duplicate elements 6994 6995 6996 if (this.trackElements_.indexOf(trackElement) === -1) {
6997 this.trackElements_.push(trackElement); 6998 } 6999 } 7000 /** 7001 * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an 7002 * {@link TextTrack}. 7003 * 7004 * @param {TextTrack} track 7005 * The track associated with a track element. 7006 * 7007 * @return {HtmlTrackElement|undefined} 7008 * The track element that was found or undefined. 7009 * 7010 * @private 7011 */ 7012 ; 7013 7014 _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { 7015 var trackElement_; 7016 7017 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 7018 if (track === this.trackElements_[i].track) { 7019 trackElement_ = this.trackElements_[i]; 7020 break; 7021 } 7022 } 7023 7024 return trackElement_; 7025 } 7026 /** 7027 * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` 7028 * 7029 * @param {HtmlTrackElement} trackElement 7030 * The track element to remove from the list. 7031 * 7032 * @private 7033 */ 7034 ; 7035 7036 _proto.removeTrackElement_ = function removeTrackElement_(trackElement) { 7037 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 7038 if (trackElement === this.trackElements_[i]) { 7039 if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { 7040 this.trackElements_[i].track.off(); 7041 } 7042 7043 if (typeof this.trackElements_[i].off === 'function') { 7044 this.trackElements_[i].off(); 7045 } 7046 7047 this.trackElements_.splice(i, 1); 7048 break; 7049 } 7050 } 7051 }; 7052 7053 return HtmlTrackElementList; 7054 }(); 7055 7056 /** 7057 * @file text-track-cue-list.js 7058 */ 7059 7060 /** 7061 * @typedef {Object} TextTrackCueList~TextTrackCue 7062 * 7063 * @property {string} id 7064 * The unique id for this text track cue 7065 * 7066 * @property {number} startTime 7067 * The start time for this text track cue 7068 * 7069 * @property {number} endTime 7070 * The end time for this text track cue 7071 * 7072 * @property {boolean} pauseOnExit 7073 * Pause when the end time is reached if true. 7074 * 7075 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} 7076 */ 7077 7078 /** 7079 * A List of TextTrackCues. 7080 * 7081 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} 7082 */ 7083 var TextTrackCueList = /*#__PURE__*/function () { 7084 /** 7085 * Create an instance of this class.. 7086 * 7087 * @param {Array} cues 7088 * A list of cues to be initialized with 7089 */ 7090 function TextTrackCueList(cues) { 7091 TextTrackCueList.prototype.setCues_.call(this, cues); 7092 /** 7093 * @memberof TextTrackCueList 7094 * @member {number} length 7095 * The current number of `TextTrackCue`s in the TextTrackCueList. 7096 * @instance 7097 */ 7098 7099 Object.defineProperty(this, 'length', { 7100 get: function get() { 7101 return this.length_; 7102 } 7103 }); 7104 } 7105 /** 7106 * A setter for cues in this list. Creates getters 7107 * an an index for the cues. 7108 * 7109 * @param {Array} cues 7110 * An array of cues to set 7111 * 7112 * @private 7113 */ 7114 7115 7116 var _proto = TextTrackCueList.prototype; 7117 7118 _proto.setCues_ = function setCues_(cues) { 7119 var oldLength = this.length || 0; 7120 var i = 0; 7121 var l = cues.length; 7122 this.cues_ = cues; 7123 this.length_ = cues.length; 7124 7125 var defineProp = function defineProp(index) { 7126 if (!('' + index in this)) { 7127 Object.defineProperty(this, '' + index, { 7128 get: function get() { 7129 return this.cues_[index]; 7130 } 7131 }); 7132 } 7133 }; 7134 7135 if (oldLength < l) { 7136 i = oldLength; 7137 7138 for (; i < l; i++) { 7139 defineProp.call(this, i); 7140 } 7141 } 7142 } 7143 /** 7144 * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. 7145 * 7146 * @param {string} id 7147 * The id of the cue that should be searched for. 7148 * 7149 * @return {TextTrackCueList~TextTrackCue|null} 7150 * A single cue or null if none was found. 7151 */ 7152 ; 7153 7154 _proto.getCueById = function getCueById(id) { 7155 var result = null; 7156 7157 for (var i = 0, l = this.length; i < l; i++) { 7158 var cue = this[i]; 7159 7160 if (cue.id === id) { 7161 result = cue; 7162 break; 7163 } 7164 } 7165 7166 return result; 7167 }; 7168 7169 return TextTrackCueList; 7170 }(); 7171 7172 /** 7173 * @file track-kinds.js 7174 */ 7175 7176 /** 7177 * All possible `VideoTrackKind`s 7178 * 7179 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind 7180 * @typedef VideoTrack~Kind 7181 * @enum 7182 */ 7183 var VideoTrackKind = { 7184 alternative: 'alternative', 7185 captions: 'captions', 7186 main: 'main', 7187 sign: 'sign', 7188 subtitles: 'subtitles', 7189 commentary: 'commentary' 7190 }; 7191 /** 7192 * All possible `AudioTrackKind`s 7193 * 7194 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind 7195 * @typedef AudioTrack~Kind 7196 * @enum 7197 */ 7198 7199 var AudioTrackKind = { 7200 'alternative': 'alternative',
7201 'descriptions': 'descriptions', 7202 'main': 'main', 7203 'main-desc': 'main-desc', 7204 'translation': 'translation', 7205 'commentary': 'commentary' 7206 }; 7207 /** 7208 * All possible `TextTrackKind`s 7209 * 7210 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind 7211 * @typedef TextTrack~Kind 7212 * @enum 7213 */ 7214 7215 var TextTrackKind = { 7216 subtitles: 'subtitles', 7217 captions: 'captions', 7218 descriptions: 'descriptions', 7219 chapters: 'chapters', 7220 metadata: 'metadata' 7221 }; 7222 /** 7223 * All possible `TextTrackMode`s 7224 * 7225 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode 7226 * @typedef TextTrack~Mode 7227 * @enum 7228 */ 7229 7230 var TextTrackMode = { 7231 disabled: 'disabled', 7232 hidden: 'hidden', 7233 showing: 'showing' 7234 }; 7235 7236 /** 7237 * A Track class that contains all of the common functionality for {@link AudioTrack}, 7238 * {@link VideoTrack}, and {@link TextTrack}. 7239 * 7240 * > Note: This class should not be used directly 7241 * 7242 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} 7243 * @extends EventTarget 7244 * @abstract 7245 */ 7246 7247 var Track = /*#__PURE__*/function (_EventTarget) { 7248 inheritsLoose(Track, _EventTarget); 7249 7250 /** 7251 * Create an instance of this class. 7252 * 7253 * @param {Object} [options={}] 7254 * Object of option names and values 7255 * 7256 * @param {string} [options.kind=''] 7257 * A valid kind for the track type you are creating. 7258 * 7259 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7260 * A unique id for this AudioTrack. 7261 * 7262 * @param {string} [options.label=''] 7263 * The menu label for this track. 7264 * 7265 * @param {string} [options.language=''] 7266 * A valid two character language code. 7267 * 7268 * @abstract 7269 */ 7270 function Track(options) { 7271 var _this; 7272 7273 if (options === void 0) { 7274 options = {}; 7275 } 7276 7277 _this = _EventTarget.call(this) || this; 7278 var trackProps = { 7279 id: options.id || 'vjs_track_' + newGUID(), 7280 kind: options.kind || '', 7281 label: options.label || '', 7282 language: options.language || '' 7283 }; 7284 /** 7285 * @memberof Track 7286 * @member {string} id 7287 * The id of this track. Cannot be changed after creation. 7288 * @instance 7289 * 7290 * @readonly 7291 */ 7292 7293 /** 7294 * @memberof Track 7295 * @member {string} kind 7296 * The kind of track that this is. Cannot be changed after creation. 7297 * @instance 7298 * 7299 * @readonly 7300 */ 7301 7302 /** 7303 * @memberof Track 7304 * @member {string} label 7305 * The label of this track. Cannot be changed after creation. 7306 * @instance 7307 * 7308 * @readonly 7309 */ 7310 7311 /** 7312 * @memberof Track 7313 * @member {string} language 7314 * The two letter language code for this track. Cannot be changed after 7315 * creation. 7316 * @instance 7317 * 7318 * @readonly 7319 */ 7320 7321 var _loop = function _loop(key) { 7322 Object.defineProperty(assertThisInitialized(_this), key, { 7323 get: function get() { 7324 return trackProps[key]; 7325 }, 7326 set: function set() {} 7327 }); 7328 }; 7329 7330 for (var key in trackProps) { 7331 _loop(key); 7332 } 7333 7334 return _this; 7335 } 7336 7337 return Track; 7338 }(EventTarget); 7339 7340 /** 7341 * @file url.js 7342 * @module url 7343 */ 7344 /** 7345 * @typedef {Object} url:URLObject 7346 * 7347 * @property {string} protocol 7348 * The protocol of the url that was parsed. 7349 * 7350 * @property {string} hostname 7351 * The hostname of the url that was parsed. 7352 * 7353 * @property {string} port 7354 * The port of the url that was parsed. 7355 * 7356 * @property {string} pathname 7357 * The pathname of the url that was parsed. 7358 * 7359 * @property {string} search 7360 * The search query of the url that was parsed. 7361 * 7362 * @property {string} hash 7363 * The hash of the url that was parsed. 7364 * 7365 * @property {string} host 7366 * The host of the url that was parsed. 7367 */ 7368 7369 /** 7370 * Resolve and parse the elements of a URL. 7371 * 7372 * @function 7373 * @param {String} url 7374 * The url to parse 7375 * 7376 * @return {url:URLObject} 7377 * An object of url details 7378 */ 7379 7380 var parseUrl = function parseUrl(url) { 7381 var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL 7382 7383 var a = document.createElement('a'); 7384 a.href = url; // IE8 (and 9?) Fix
7385 // ie8 doesn't parse the URL correctly until the anchor is actually 7386 // added to the body, and an innerHTML is needed to trigger the parsing 7387 7388 var addToBody = a.host === '' && a.protocol !== 'file:'; 7389 var div; 7390 7391 if (addToBody) { 7392 div = document.createElement('div'); 7393 div.innerHTML = "<a href=\"" + url + "\"></a>"; 7394 a = div.firstChild; // prevent the div from affecting layout 7395 7396 div.setAttribute('style', 'display:none; position:absolute;'); 7397 document.body.appendChild(div); 7398 } // Copy the specific URL properties to a new object 7399 // This is also needed for IE8 because the anchor loses its 7400 // properties when it's removed from the dom 7401 7402 7403 var details = {}; 7404 7405 for (var i = 0; i < props.length; i++) { 7406 details[props[i]] = a[props[i]]; 7407 } // IE9 adds the port to the host property unlike everyone else. If 7408 // a port identifier is added for standard ports, strip it. 7409 7410 7411 if (details.protocol === 'http:') { 7412 details.host = details.host.replace(/:80$/, ''); 7413 } 7414 7415 if (details.protocol === 'https:') { 7416 details.host = details.host.replace(/:443$/, ''); 7417 } 7418 7419 if (!details.protocol) { 7420 details.protocol = window$3.location.protocol; 7421 } 7422 7423 if (addToBody) { 7424 document.body.removeChild(div); 7425 } 7426 7427 return details; 7428 }; 7429 /** 7430 * Get absolute version of relative URL. Used to tell Flash the correct URL. 7431 * 7432 * @function 7433 * @param {string} url 7434 * URL to make absolute 7435 * 7436 * @return {string} 7437 * Absolute URL 7438 * 7439 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 7440 */ 7441 7442 var getAbsoluteURL = function getAbsoluteURL(url) { 7443 // Check if absolute URL 7444 if (!url.match(/^https?:\/\//)) { 7445 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 7446 var div = document.createElement('div'); 7447 div.innerHTML = "<a href=\"" + url + "\">x</a>"; 7448 url = div.firstChild.href; 7449 } 7450 7451 return url; 7452 }; 7453 /** 7454 * Returns the extension of the passed file name. It will return an empty string 7455 * if passed an invalid path. 7456 * 7457 * @function 7458 * @param {string} path 7459 * The fileName path like '/path/to/file.mp4' 7460 * 7461 * @return {string} 7462 * The extension in lower case or an empty string if no 7463 * extension could be found. 7464 */ 7465 7466 var getFileExtension = function getFileExtension(path) { 7467 if (typeof path === 'string') { 7468 var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/; 7469 var pathParts = splitPathRe.exec(path); 7470 7471 if (pathParts) { 7472 return pathParts.pop().toLowerCase(); 7473 } 7474 } 7475 7476 return ''; 7477 }; 7478 /** 7479 * Returns whether the url passed is a cross domain request or not. 7480 * 7481 * @function 7482 * @param {string} url 7483 * The url to check. 7484 * 7485 * @param {Object} [winLoc] 7486 * the domain to check the url against, defaults to window.location 7487 * 7488 * @param {string} [winLoc.protocol] 7489 * The window location protocol defaults to window.location.protocol 7490 * 7491 * @param {string} [winLoc.host] 7492 * The window location host defaults to window.location.host 7493 * 7494 * @return {boolean} 7495 * Whether it is a cross domain request or not. 7496 */ 7497 7498 var isCrossOrigin = function isCrossOrigin(url, winLoc) { 7499 if (winLoc === void 0) { 7500 winLoc = window$3.location; 7501 } 7502 7503 var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol 7504 7505 var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin 7506 // IE8 doesn't know location.origin, so we won't rely on it here 7507 7508 var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; 7509 return crossOrigin; 7510 }; 7511 7512 var Url = /*#__PURE__*/Object.freeze({ 7513 __proto__: null, 7514 parseUrl: parseUrl, 7515 getAbsoluteURL: getAbsoluteURL, 7516 getFileExtension: getFileExtension, 7517 isCrossOrigin: isCrossOrigin 7518 }); 7519 7520 var isFunction_1 = isFunction; 7521 var toString$1 = Object.prototype.toString; 7522 7523 function isFunction(fn) { 7524 var string = toString$1.call(fn); 7525 return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below 7526 fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt); 7527 } 7528 7529 /** 7530 * @license 7531 * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/> 7532 * Copyright (c) 2014 David Björklund 7533 * Available under the MIT license 7534 * <https://github.com/kesla/parse-headers/blob/master/LICENCE> 7535 */ 7536 7537 7538 var parseHeaders = function parseHeaders(headers) { 7539 var result = {}; 7540 7541 if (!headers) { 7542 return result; 7543 } 7544 7545 headers.trim().split('\n').forEach(function (row) { 7546 var index = row.indexOf(':'); 7547 var key = row.slice(0, index).trim().toLowerCase(); 7548 var value = row.slice(index + 1).trim(); 7549 7550 if (typeof result[key] === 'undefined') { 7551 result[key] = value; 7552 } else if (Array.isArray(result[key])) { 7553 result[key].push(value); 7554 } else { 7555 result[key] = [result[key], value]; 7556 } 7557 }); 7558 return result; 7559 }; 7560 7561 var xhr = createXHR; // Allow use of default import syntax in TypeScript 7562 7563 var default_1 = createXHR; 7564 createXHR.XMLHttpRequest = window$3.XMLHttpRequest || noop; 7565 createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$3.XDomainRequest;
vendor: 4,182 bytes, lines 7566-7724
7566 forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) { 7567 createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) { 7568 options = initParams(uri, options, callback); 7569 options.method = method.toUpperCase(); 7570 return _createXHR(options); 7571 }; 7572 }); 7573 7574 function forEachArray(array, iterator) { 7575 for (var i = 0; i < array.length; i++) { 7576 iterator(array[i]); 7577 } 7578 } 7579 7580 function isEmpty(obj) { 7581 for (var i in obj) { 7582 if (obj.hasOwnProperty(i)) return false; 7583 } 7584 7585 return true; 7586 } 7587 7588 function initParams(uri, options, callback) { 7589 var params = uri; 7590 7591 if (isFunction_1(options)) { 7592 callback = options; 7593 7594 if (typeof uri === "string") { 7595 params = { 7596 uri: uri 7597 }; 7598 } 7599 } else { 7600 params = _extends_1({}, options, { 7601 uri: uri 7602 }); 7603 } 7604 7605 params.callback = callback; 7606 return params; 7607 } 7608 7609 function createXHR(uri, options, callback) { 7610 options = initParams(uri, options, callback); 7611 return _createXHR(options); 7612 } 7613 7614 function _createXHR(options) { 7615 if (typeof options.callback === "undefined") { 7616 throw new Error("callback argument missing"); 7617 } 7618 7619 var called = false; 7620 7621 var callback = function cbOnce(err, response, body) { 7622 if (!called) { 7623 called = true; 7624 options.callback(err, response, body); 7625 } 7626 }; 7627 7628 function readystatechange() { 7629 if (xhr.readyState === 4) { 7630 setTimeout(loadFunc, 0); 7631 } 7632 } 7633 7634 function getBody() { 7635 // Chrome with requestType=blob throws errors arround when even testing access to responseText 7636 var body = undefined; 7637 7638 if (xhr.response) { 7639 body = xhr.response; 7640 } else { 7641 body = xhr.responseText || getXml(xhr); 7642 } 7643 7644 if (isJson) { 7645 try { 7646 body = JSON.parse(body); 7647 } catch (e) {} 7648 } 7649 7650 return body; 7651 } 7652 7653 function errorFunc(evt) { 7654 clearTimeout(timeoutTimer); 7655 7656 if (!(evt instanceof Error)) { 7657 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error")); 7658 } 7659 7660 evt.statusCode = 0; 7661 return callback(evt, failureResponse); 7662 } // will load the data & process the response in a special response object 7663 7664 7665 function loadFunc() { 7666 if (aborted) return; 7667 var status; 7668 clearTimeout(timeoutTimer); 7669 7670 if (options.useXDR && xhr.status === undefined) { 7671 //IE8 CORS GET successful response doesn't have a status field, but body is fine 7672 status = 200; 7673 } else { 7674 status = xhr.status === 1223 ? 204 : xhr.status; 7675 } 7676 7677 var response = failureResponse; 7678 var err = null; 7679 7680 if (status !== 0) { 7681 response = { 7682 body: getBody(), 7683 statusCode: status, 7684 method: method, 7685 headers: {}, 7686 url: uri, 7687 rawRequest: xhr 7688 }; 7689 7690 if (xhr.getAllResponseHeaders) { 7691 //remember xhr can in fact be XDR for CORS in IE 7692 response.headers = parseHeaders(xhr.getAllResponseHeaders()); 7693 } 7694 } else { 7695 err = new Error("Internal XMLHttpRequest Error"); 7696 } 7697 7698 return callback(err, response, response.body); 7699 } 7700 7701 var xhr = options.xhr || null; 7702 7703 if (!xhr) { 7704 if (options.cors || options.useXDR) { 7705 xhr = new createXHR.XDomainRequest(); 7706 } else { 7707 xhr = new createXHR.XMLHttpRequest(); 7708 } 7709 } 7710 7711 var key; 7712 var aborted; 7713 var uri = xhr.url = options.uri || options.url; 7714 var method = xhr.method = options.method || "GET"; 7715 var body = options.body || options.data; 7716 var headers = xhr.headers = options.headers || {}; 7717 var sync = !!options.sync; 7718 var isJson = false; 7719 var timeoutTimer; 7720 var failureResponse = { 7721 body: undefined, 7722 headers: {}, 7723 statusCode: 0, 7724 method: method,
vendor: 4,249 bytes, lines 7725-7856
7725 url: uri, 7726 rawRequest: xhr 7727 }; 7728 7729 if ("json" in options && options.json !== false) { 7730 isJson = true; 7731 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 7732 7733 if (method !== "GET" && method !== "HEAD") { 7734 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 7735 7736 body = JSON.stringify(options.json === true ? body : options.json); 7737 } 7738 } 7739 7740 xhr.onreadystatechange = readystatechange; 7741 xhr.onload = loadFunc; 7742 xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function. 7743 7744 xhr.onprogress = function () {// IE must die 7745 }; 7746 7747 xhr.onabort = function () { 7748 aborted = true; 7749 }; 7750 7751 xhr.ontimeout = errorFunc; 7752 xhr.open(method, uri, !sync, options.username, options.password); //has to be after open 7753 7754 if (!sync) { 7755 xhr.withCredentials = !!options.withCredentials; 7756 } // Cannot set timeout with sync request 7757 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 7758 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 7759 7760 7761 if (!sync && options.timeout > 0) { 7762 timeoutTimer = setTimeout(function () { 7763 if (aborted) return; 7764 aborted = true; //IE9 may still call readystatechange 7765 7766 xhr.abort("timeout"); 7767 var e = new Error("XMLHttpRequest timeout"); 7768 e.code = "ETIMEDOUT"; 7769 errorFunc(e); 7770 }, options.timeout); 7771 } 7772 7773 if (xhr.setRequestHeader) { 7774 for (key in headers) { 7775 if (headers.hasOwnProperty(key)) { 7776 xhr.setRequestHeader(key, headers[key]); 7777 } 7778 } 7779 } else if (options.headers && !isEmpty(options.headers)) { 7780 throw new Error("Headers cannot be set on an XDomainRequest object"); 7781 } 7782 7783 if ("responseType" in options) { 7784 xhr.responseType = options.responseType; 7785 } 7786 7787 if ("beforeSend" in options && typeof options.beforeSend === "function") { 7788 options.beforeSend(xhr); 7789 } // Microsoft Edge browser sends "undefined" when send is called with undefined value. 7790 // XMLHttpRequest spec says to pass null as body to indicate no body 7791 // See https://github.com/naugtur/xhr/issues/100. 7792 7793 7794 xhr.send(body || null); 7795 return xhr; 7796 } 7797 7798 function getXml(xhr) { 7799 // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" 7800 // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. 7801 try { 7802 if (xhr.responseType === "document") { 7803 return xhr.responseXML; 7804 } 7805 7806 var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 7807 7808 if (xhr.responseType === "" && !firefoxBugTakenEffect) { 7809 return xhr.responseXML; 7810 } 7811 } catch (e) {} 7812 7813 return null; 7814 } 7815 7816 function noop() {} 7817 xhr["default"] = default_1; 7818 7819 /** 7820 * Takes a webvtt file contents and parses it into cues 7821 * 7822 * @param {string} srcContent 7823 * webVTT file contents 7824 * 7825 * @param {TextTrack} track 7826 * TextTrack to add cues to. Cues come from the srcContent. 7827 * 7828 * @private 7829 */ 7830 7831 var parseCues = function parseCues(srcContent, track) { 7832 var parser = new window$3.WebVTT.Parser(window$3, window$3.vttjs, window$3.WebVTT.StringDecoder()); 7833 var errors = []; 7834 7835 parser.oncue = function (cue) { 7836 track.addCue(cue); 7837 }; 7838 7839 parser.onparsingerror = function (error) { 7840 errors.push(error); 7841 }; 7842 7843 parser.onflush = function () { 7844 track.trigger({ 7845 type: 'loadeddata', 7846 target: track 7847 }); 7848 }; 7849 7850 parser.parse(srcContent); 7851 7852 if (errors.length > 0) { 7853 if (window$3.console && window$3.console.groupCollapsed) { 7854 window$3.console.groupCollapsed("Text Track parsing errors for " + track.src); 7855 } 7856
7857 errors.forEach(function (error) { 7858 return log.error(error); 7859 }); 7860 7861 if (window$3.console && window$3.console.groupEnd) { 7862 window$3.console.groupEnd(); 7863 } 7864 } 7865 7866 parser.flush(); 7867 }; 7868 /** 7869 * Load a `TextTrack` from a specified url. 7870 * 7871 * @param {string} src 7872 * Url to load track from. 7873 * 7874 * @param {TextTrack} track 7875 * Track to add cues to. Comes from the content at the end of `url`. 7876 * 7877 * @private 7878 */ 7879 7880 7881 var loadTrack = function loadTrack(src, track) { 7882 var opts = { 7883 uri: src 7884 }; 7885 var crossOrigin = isCrossOrigin(src); 7886 7887 if (crossOrigin) { 7888 opts.cors = crossOrigin; 7889 } 7890 7891 xhr(opts, bind(this, function (err, response, responseBody) { 7892 if (err) { 7893 return log.error(err, response); 7894 } 7895 7896 track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 7897 // NOTE: this is only used for the alt/video.novtt.js build 7898 7899 if (typeof window$3.WebVTT !== 'function') { 7900 if (track.tech_) { 7901 // to prevent use before define eslint error, we define loadHandler 7902 // as a let here 7903 track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) { 7904 if (event.type === 'vttjserror') { 7905 log.error("vttjs failed to load, stopping trying to process " + track.src); 7906 return; 7907 } 7908 7909 return parseCues(responseBody, track); 7910 }); 7911 } 7912 } else { 7913 parseCues(responseBody, track); 7914 } 7915 })); 7916 }; 7917 /** 7918 * A representation of a single `TextTrack`. 7919 * 7920 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} 7921 * @extends Track 7922 */ 7923 7924 7925 var TextTrack = /*#__PURE__*/function (_Track) { 7926 inheritsLoose(TextTrack, _Track); 7927 7928 /** 7929 * Create an instance of this class. 7930 * 7931 * @param {Object} options={} 7932 * Object of option names and values 7933 * 7934 * @param {Tech} options.tech 7935 * A reference to the tech that owns this TextTrack. 7936 * 7937 * @param {TextTrack~Kind} [options.kind='subtitles'] 7938 * A valid text track kind. 7939 * 7940 * @param {TextTrack~Mode} [options.mode='disabled'] 7941 * A valid text track mode. 7942 * 7943 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7944 * A unique id for this TextTrack. 7945 * 7946 * @param {string} [options.label=''] 7947 * The menu label for this track. 7948 * 7949 * @param {string} [options.language=''] 7950 * A valid two character language code. 7951 * 7952 * @param {string} [options.srclang=''] 7953 * A valid two character language code. An alternative, but deprioritized 7954 * version of `options.language` 7955 * 7956 * @param {string} [options.src] 7957 * A url to TextTrack cues. 7958 * 7959 * @param {boolean} [options.default] 7960 * If this track should default to on or off. 7961 */ 7962 function TextTrack(options) { 7963 var _this; 7964 7965 if (options === void 0) { 7966 options = {}; 7967 } 7968 7969 if (!options.tech) { 7970 throw new Error('A tech was not provided.'); 7971 } 7972 7973 var settings = mergeOptions(options, { 7974 kind: TextTrackKind[options.kind] || 'subtitles', 7975 language: options.language || options.srclang || '' 7976 }); 7977 var mode = TextTrackMode[settings.mode] || 'disabled'; 7978 var default_ = settings["default"]; 7979 7980 if (settings.kind === 'metadata' || settings.kind === 'chapters') { 7981 mode = 'hidden'; 7982 } 7983 7984 _this = _Track.call(this, settings) || this; 7985 _this.tech_ = settings.tech; 7986 _this.cues_ = []; 7987 _this.activeCues_ = []; 7988 _this.preload_ = _this.tech_.preloadTextTracks !== false; 7989 var cues = new TextTrackCueList(_this.cues_); 7990 var activeCues = new TextTrackCueList(_this.activeCues_); 7991 var changed = false;
7992 var timeupdateHandler = bind(assertThisInitialized(_this), function () { 7993 // Accessing this.activeCues for the side-effects of updating itself 7994 // due to its nature as a getter function. Do not remove or cues will 7995 // stop updating! 7996 // Use the setter to prevent deletion from uglify (pure_getters rule) 7997 this.activeCues = this.activeCues; 7998 7999 if (changed) { 8000 this.trigger('cuechange'); 8001 changed = false; 8002 } 8003 }); 8004 8005 if (mode !== 'disabled') { 8006 _this.tech_.ready(function () { 8007 _this.tech_.on('timeupdate', timeupdateHandler); 8008 }, true); 8009 } 8010 8011 Object.defineProperties(assertThisInitialized(_this), { 8012 /** 8013 * @memberof TextTrack 8014 * @member {boolean} default 8015 * If this track was set to be on or off by default. Cannot be changed after 8016 * creation. 8017 * @instance 8018 * 8019 * @readonly 8020 */ 8021 "default": { 8022 get: function get() { 8023 return default_; 8024 }, 8025 set: function set() {} 8026 }, 8027 8028 /** 8029 * @memberof TextTrack 8030 * @member {string} mode 8031 * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will 8032 * not be set if setting to an invalid mode. 8033 * @instance 8034 * 8035 * @fires TextTrack#modechange 8036 */ 8037 mode: { 8038 get: function get() { 8039 return mode; 8040 }, 8041 set: function set(newMode) { 8042 var _this2 = this; 8043 8044 if (!TextTrackMode[newMode]) { 8045 return; 8046 } 8047 8048 mode = newMode; 8049 8050 if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) { 8051 // On-demand load. 8052 loadTrack(this.src, this); 8053 } 8054 8055 if (mode !== 'disabled') { 8056 this.tech_.ready(function () { 8057 _this2.tech_.on('timeupdate', timeupdateHandler); 8058 }, true); 8059 } else { 8060 this.tech_.off('timeupdate', timeupdateHandler); 8061 } 8062 /** 8063 * An event that fires when mode changes on this track. This allows 8064 * the TextTrackList that holds this track to act accordingly. 8065 * 8066 * > Note: This is not part of the spec! 8067 * 8068 * @event TextTrack#modechange 8069 * @type {EventTarget~Event} 8070 */ 8071 8072 8073 this.trigger('modechange'); 8074 } 8075 }, 8076 8077 /** 8078 * @memberof TextTrack 8079 * @member {TextTrackCueList} cues 8080 * The text track cue list for this TextTrack. 8081 * @instance 8082 */ 8083 cues: { 8084 get: function get() { 8085 if (!this.loaded_) { 8086 return null; 8087 } 8088 8089 return cues; 8090 }, 8091 set: function set() {} 8092 }, 8093 8094 /** 8095 * @memberof TextTrack 8096 * @member {TextTrackCueList} activeCues 8097 * The list text track cues that are currently active for this TextTrack. 8098 * @instance 8099 */ 8100 activeCues: { 8101 get: function get() { 8102 if (!this.loaded_) { 8103 return null; 8104 } // nothing to do 8105 8106 8107 if (this.cues.length === 0) { 8108 return activeCues; 8109 } 8110 8111 var ct = this.tech_.currentTime(); 8112 var active = []; 8113 8114 for (var i = 0, l = this.cues.length; i < l; i++) { 8115 var cue = this.cues[i]; 8116 8117 if (cue.startTime <= ct && cue.endTime >= ct) { 8118 active.push(cue); 8119 } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { 8120 active.push(cue); 8121 } 8122 } 8123 8124 changed = false;
8125 8126 if (active.length !== this.activeCues_.length) { 8127 changed = true; 8128 } else { 8129 for (var _i = 0; _i < active.length; _i++) { 8130 if (this.activeCues_.indexOf(active[_i]) === -1) { 8131 changed = true; 8132 } 8133 } 8134 } 8135 8136 this.activeCues_ = active; 8137 activeCues.setCues_(this.activeCues_); 8138 return activeCues; 8139 }, 8140 // /!\ Keep this setter empty (see the timeupdate handler above) 8141 set: function set() {} 8142 } 8143 }); 8144 8145 if (settings.src) { 8146 _this.src = settings.src; 8147 8148 if (!_this.preload_) { 8149 // Tracks will load on-demand. 8150 // Act like we're loaded for other purposes. 8151 _this.loaded_ = true; 8152 } 8153 8154 if (_this.preload_ || default_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') { 8155 loadTrack(_this.src, assertThisInitialized(_this)); 8156 } 8157 } else { 8158 _this.loaded_ = true; 8159 } 8160 8161 return _this; 8162 } 8163 /** 8164 * Add a cue to the internal list of cues. 8165 * 8166 * @param {TextTrack~Cue} cue 8167 * The cue to add to our internal list 8168 */ 8169 8170 8171 var _proto = TextTrack.prototype; 8172 8173 _proto.addCue = function addCue(originalCue) { 8174 var cue = originalCue; 8175 8176 if (window$3.vttjs && !(originalCue instanceof window$3.vttjs.VTTCue)) { 8177 cue = new window$3.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); 8178 8179 for (var prop in originalCue) { 8180 if (!(prop in cue)) { 8181 cue[prop] = originalCue[prop]; 8182 } 8183 } // make sure that `id` is copied over 8184 8185 8186 cue.id = originalCue.id; 8187 cue.originalCue_ = originalCue; 8188 } 8189 8190 var tracks = this.tech_.textTracks(); 8191 8192 for (var i = 0; i < tracks.length; i++) { 8193 if (tracks[i] !== this) { 8194 tracks[i].removeCue(cue); 8195 } 8196 } 8197 8198 this.cues_.push(cue); 8199 this.cues.setCues_(this.cues_); 8200 } 8201 /** 8202 * Remove a cue from our internal list 8203 * 8204 * @param {TextTrack~Cue} removeCue 8205 * The cue to remove from our internal list 8206 */ 8207 ; 8208 8209 _proto.removeCue = function removeCue(_removeCue) { 8210 var i = this.cues_.length; 8211 8212 while (i--) { 8213 var cue = this.cues_[i]; 8214 8215 if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { 8216 this.cues_.splice(i, 1); 8217 this.cues.setCues_(this.cues_); 8218 break; 8219 } 8220 } 8221 }; 8222 8223 return TextTrack; 8224 }(Track); 8225 /** 8226 * cuechange - One or more cues in the track have become active or stopped being active. 8227 */ 8228 8229 8230 TextTrack.prototype.allowedEvents_ = { 8231 cuechange: 'cuechange' 8232 }; 8233 8234 /** 8235 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} 8236 * only one `AudioTrack` in the list will be enabled at a time. 8237 * 8238 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} 8239 * @extends Track 8240 */ 8241 8242 var AudioTrack = /*#__PURE__*/function (_Track) { 8243 inheritsLoose(AudioTrack, _Track); 8244 8245 /** 8246 * Create an instance of this class. 8247 * 8248 * @param {Object} [options={}] 8249 * Object of option names and values 8250 * 8251 * @param {AudioTrack~Kind} [options.kind=''] 8252 * A valid audio track kind 8253 * 8254 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8255 * A unique id for this AudioTrack. 8256 * 8257 * @param {string} [options.label=''] 8258 * The menu label for this track. 8259 * 8260 * @param {string} [options.language=''] 8261 * A valid two character language code. 8262 * 8263 * @param {boolean} [options.enabled] 8264 * If this track is the one that is currently playing. If this track is part of 8265 * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. 8266 */ 8267 function AudioTrack(options) { 8268 var _this; 8269 8270 if (options === void 0) { 8271 options = {}; 8272 } 8273 8274 var settings = mergeOptions(options, { 8275 kind: AudioTrackKind[options.kind] || '' 8276 }); 8277 _this = _Track.call(this, settings) || this; 8278 var enabled = false;
8279 /** 8280 * @memberof AudioTrack 8281 * @member {boolean} enabled 8282 * If this `AudioTrack` is enabled or not. When setting this will 8283 * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. 8284 * @instance 8285 * 8286 * @fires VideoTrack#selectedchange 8287 */ 8288 8289 Object.defineProperty(assertThisInitialized(_this), 'enabled', { 8290 get: function get() { 8291 return enabled; 8292 }, 8293 set: function set(newEnabled) { 8294 // an invalid or unchanged value 8295 if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { 8296 return; 8297 } 8298 8299 enabled = newEnabled; 8300 /** 8301 * An event that fires when enabled changes on this track. This allows 8302 * the AudioTrackList that holds this track to act accordingly. 8303 * 8304 * > Note: This is not part of the spec! Native tracks will do 8305 * this internally without an event. 8306 * 8307 * @event AudioTrack#enabledchange 8308 * @type {EventTarget~Event} 8309 */ 8310 8311 this.trigger('enabledchange'); 8312 } 8313 }); // if the user sets this track to selected then 8314 // set selected to that true value otherwise 8315 // we keep it false 8316 8317 if (settings.enabled) { 8318 _this.enabled = settings.enabled; 8319 } 8320 8321 _this.loaded_ = true; 8322 return _this; 8323 } 8324 8325 return AudioTrack; 8326 }(Track); 8327 8328 /** 8329 * A representation of a single `VideoTrack`. 8330 * 8331 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} 8332 * @extends Track 8333 */ 8334 8335 var VideoTrack = /*#__PURE__*/function (_Track) { 8336 inheritsLoose(VideoTrack, _Track); 8337 8338 /** 8339 * Create an instance of this class. 8340 * 8341 * @param {Object} [options={}] 8342 * Object of option names and values 8343 * 8344 * @param {string} [options.kind=''] 8345 * A valid {@link VideoTrack~Kind} 8346 * 8347 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8348 * A unique id for this AudioTrack. 8349 * 8350 * @param {string} [options.label=''] 8351 * The menu label for this track. 8352 * 8353 * @param {string} [options.language=''] 8354 * A valid two character language code. 8355 * 8356 * @param {boolean} [options.selected] 8357 * If this track is the one that is currently playing. 8358 */ 8359 function VideoTrack(options) { 8360 var _this; 8361 8362 if (options === void 0) { 8363 options = {}; 8364 } 8365 8366 var settings = mergeOptions(options, { 8367 kind: VideoTrackKind[options.kind] || '' 8368 }); 8369 _this = _Track.call(this, settings) || this; 8370 var selected = false; 8371 /** 8372 * @memberof VideoTrack 8373 * @member {boolean} selected 8374 * If this `VideoTrack` is selected or not. When setting this will 8375 * fire {@link VideoTrack#selectedchange} if the state of selected changed. 8376 * @instance 8377 * 8378 * @fires VideoTrack#selectedchange 8379 */ 8380 8381 Object.defineProperty(assertThisInitialized(_this), 'selected', { 8382 get: function get() { 8383 return selected; 8384 }, 8385 set: function set(newSelected) { 8386 // an invalid or unchanged value 8387 if (typeof newSelected !== 'boolean' || newSelected === selected) { 8388 return; 8389 } 8390 8391 selected = newSelected; 8392 /** 8393 * An event that fires when selected changes on this track. This allows 8394 * the VideoTrackList that holds this track to act accordingly. 8395 * 8396 * > Note: This is not part of the spec! Native tracks will do 8397 * this internally without an event. 8398 * 8399 * @event VideoTrack#selectedchange 8400 * @type {EventTarget~Event} 8401 */ 8402 8403 this.trigger('selectedchange'); 8404 } 8405 }); // if the user sets this track to selected then 8406 // set selected to that true value otherwise 8407 // we keep it false 8408 8409 if (settings.selected) { 8410 _this.selected = settings.selected; 8411 } 8412 8413 return _this; 8414 } 8415 8416 return VideoTrack; 8417 }(Track); 8418 8419 /** 8420 * @memberof HTMLTrackElement 8421 * @typedef {HTMLTrackElement~ReadyState} 8422 * @enum {number} 8423 */ 8424 8425 var NONE = 0; 8426 var LOADING = 1; 8427 var LOADED = 2; 8428 var ERROR = 3; 8429 /** 8430 * A single track represented in the DOM. 8431 * 8432 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8433 * @extends EventTarget 8434 */ 8435 8436 var HTMLTrackElement = /*#__PURE__*/function (_EventTarget) { 8437 inheritsLoose(HTMLTrackElement, _EventTarget); 8438 8439 /** 8440 * Create an instance of this class. 8441 * 8442 * @param {Object} options={} 8443 * Object of option names and values 8444 * 8445 * @param {Tech} options.tech 8446 * A reference to the tech that owns this HTMLTrackElement. 8447 * 8448 * @param {TextTrack~Kind} [options.kind='subtitles'] 8449 * A valid text track kind. 8450 * 8451 * @param {TextTrack~Mode} [options.mode='disabled'] 8452 * A valid text track mode. 8453 * 8454 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8455 * A unique id for this TextTrack. 8456 * 8457 * @param {string} [options.label=''] 8458 * The menu label for this track. 8459 * 8460 * @param {string} [options.language=''] 8461 * A valid two character language code. 8462 * 8463 * @param {string} [options.srclang=''] 8464 * A valid two character language code. An alternative, but deprioritized 8465 * vesion of `options.language` 8466 * 8467 * @param {string} [options.src] 8468 * A url to TextTrack cues. 8469 * 8470 * @param {boolean} [options.default] 8471 * If this track should default to on or off. 8472 */ 8473 function HTMLTrackElement(options) { 8474 var _this; 8475 8476 if (options === void 0) { 8477 options = {}; 8478 } 8479 8480 _this = _EventTarget.call(this) || this; 8481 var readyState; 8482 var track = new TextTrack(options); 8483 _this.kind = track.kind; 8484 _this.src = track.src; 8485 _this.srclang = track.language; 8486 _this.label = track.label; 8487 _this["default"] = track["default"]; 8488 Object.defineProperties(assertThisInitialized(_this), { 8489 /** 8490 * @memberof HTMLTrackElement 8491 * @member {HTMLTrackElement~ReadyState} readyState 8492 * The current ready state of the track element. 8493 * @instance 8494 */ 8495 readyState: { 8496 get: function get() { 8497 return readyState; 8498 } 8499 }, 8500 8501 /** 8502 * @memberof HTMLTrackElement 8503 * @member {TextTrack} track 8504 * The underlying TextTrack object. 8505 * @instance 8506 * 8507 */ 8508 track: { 8509 get: function get() { 8510 return track; 8511 } 8512 } 8513 }); 8514 readyState = NONE; 8515 /** 8516 * @listens TextTrack#loadeddata 8517 * @fires HTMLTrackElement#load 8518 */ 8519 8520 track.addEventListener('loadeddata', function () { 8521 readyState = LOADED; 8522 8523 _this.trigger({ 8524 type: 'load', 8525 target: assertThisInitialized(_this) 8526 }); 8527 }); 8528 return _this; 8529 } 8530 8531 return HTMLTrackElement; 8532 }(EventTarget); 8533 8534 HTMLTrackElement.prototype.allowedEvents_ = { 8535 load: 'load' 8536 }; 8537 HTMLTrackElement.NONE = NONE; 8538 HTMLTrackElement.LOADING = LOADING; 8539 HTMLTrackElement.LOADED = LOADED; 8540 HTMLTrackElement.ERROR = ERROR; 8541 8542 /* 8543 * This file contains all track properties that are used in 8544 * player.js, tech.js, html5.js and possibly other techs in the future. 8545 */ 8546 8547 var NORMAL = { 8548 audio: { 8549 ListClass: AudioTrackList, 8550 TrackClass: AudioTrack, 8551 capitalName: 'Audio' 8552 }, 8553 video: { 8554 ListClass: VideoTrackList, 8555 TrackClass: VideoTrack, 8556 capitalName: 'Video' 8557 }, 8558 text: { 8559 ListClass: TextTrackList, 8560 TrackClass: TextTrack, 8561 capitalName: 'Text' 8562 } 8563 }; 8564 Object.keys(NORMAL).forEach(function (type) { 8565 NORMAL[type].getterName = type + "Tracks"; 8566 NORMAL[type].privateName = type + "Tracks_"; 8567 }); 8568 var REMOTE = { 8569 remoteText: { 8570 ListClass: TextTrackList, 8571 TrackClass: TextTrack, 8572 capitalName: 'RemoteText', 8573 getterName: 'remoteTextTracks', 8574 privateName: 'remoteTextTracks_' 8575 }, 8576 remoteTextEl: { 8577 ListClass: HtmlTrackElementList, 8578 TrackClass: HTMLTrackElement, 8579 capitalName: 'RemoteTextTrackEls', 8580 getterName: 'remoteTextTrackEls', 8581 privateName: 'remoteTextTrackEls_' 8582 } 8583 }; 8584 8585 var ALL = _extends_1({}, NORMAL, REMOTE); 8586 8587 REMOTE.names = Object.keys(REMOTE); 8588 NORMAL.names = Object.keys(NORMAL); 8589 ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); 8590 8591 /** 8592 * Copyright 2013 vtt.js Contributors 8593 * 8594 * Licensed under the Apache License, Version 2.0 (the "License");
8595 * you may not use this file except in compliance with the License. 8596 * You may obtain a copy of the License at 8597 * 8598 * http://www.apache.org/licenses/LICENSE-2.0 8599 * 8600 * Unless required by applicable law or agreed to in writing, software 8601 * distributed under the License is distributed on an "AS IS" BASIS, 8602 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8603 * See the License for the specific language governing permissions and 8604 * limitations under the License. 8605 */ 8606 8607 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 8608 8609 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 8610 8611 var _objCreate = Object.create || function () { 8612 function F() {} 8613 8614 return function (o) { 8615 if (arguments.length !== 1) { 8616 throw new Error('Object.create shim only accepts one parameter.'); 8617 } 8618 8619 F.prototype = o; 8620 return new F(); 8621 }; 8622 }(); // Creates a new ParserError object from an errorData object. The errorData 8623 // object should have default code and message properties. The default message 8624 // property can be overriden by passing in a message parameter. 8625 // See ParsingError.Errors below for acceptable errors. 8626 8627 8628 function ParsingError(errorData, message) { 8629 this.name = "ParsingError"; 8630 this.code = errorData.code; 8631 this.message = message || errorData.message; 8632 } 8633 8634 ParsingError.prototype = _objCreate(Error.prototype); 8635 ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors. 8636 8637 ParsingError.Errors = { 8638 BadSignature: { 8639 code: 0, 8640 message: "Malformed WebVTT signature." 8641 }, 8642 BadTimeStamp: { 8643 code: 1, 8644 message: "Malformed time stamp." 8645 } 8646 }; // Try to parse input as a time stamp. 8647 8648 function parseTimeStamp(input) { 8649 function computeSeconds(h, m, s, f) { 8650 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 8651 } 8652 8653 var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/); 8654 8655 if (!m) { 8656 return null; 8657 } 8658 8659 if (m[3]) { 8660 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 8661 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 8662 } else if (m[1] > 59) { 8663 // Timestamp takes the form of [hours]:[minutes].[milliseconds] 8664 // First position is hours as it's over 59. 8665 return computeSeconds(m[1], m[2], 0, m[4]); 8666 } else { 8667 // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 8668 return computeSeconds(0, m[1], m[2], m[4]); 8669 } 8670 } // A settings object holds key/value pairs and will ignore anything but the first 8671 // assignment to a specific key. 8672 8673 8674 function Settings() { 8675 this.values = _objCreate(null); 8676 } 8677 8678 Settings.prototype = { 8679 // Only accept the first assignment to any key. 8680 set: function set(k, v) { 8681 if (!this.get(k) && v !== "") { 8682 this.values[k] = v; 8683 } 8684 }, 8685 // Return the value for a key, or a default value. 8686 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 8687 // a number of possible default values as properties where 'defaultKey' is 8688 // the key of the property that will be chosen; otherwise it's assumed to be 8689 // a single value. 8690 get: function get(k, dflt, defaultKey) { 8691 if (defaultKey) { 8692 return this.has(k) ? this.values[k] : dflt[defaultKey]; 8693 } 8694 8695 return this.has(k) ? this.values[k] : dflt; 8696 }, 8697 // Check whether we have a value for a key. 8698 has: function has(k) { 8699 return k in this.values; 8700 }, 8701 // Accept a setting if its one of the given alternatives. 8702 alt: function alt(k, v, a) { 8703 for (var n = 0; n < a.length; ++n) { 8704 if (v === a[n]) { 8705 this.set(k, v); 8706 break; 8707 } 8708 } 8709 }, 8710 // Accept a setting if its a valid (signed) integer. 8711 integer: function integer(k, v) { 8712 if (/^-?\d+$/.test(v)) { 8713 // integer 8714 this.set(k, parseInt(v, 10)); 8715 } 8716 }, 8717 // Accept a setting if its a valid percentage. 8718 percent: function percent(k, v) { 8719 var m; 8720 8721 if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) { 8722 v = parseFloat(v); 8723 8724 if (v >= 0 && v <= 100) { 8725 this.set(k, v); 8726 return true; 8727 } 8728 } 8729 8730 return false;
vendor: 30,370 bytes, lines 8731-9592
8731 } 8732 }; // Helper function to parse input into groups separated by 'groupDelim', and 8733 // interprete each group as a key/value pair separated by 'keyValueDelim'. 8734 8735 function parseOptions(input, callback, keyValueDelim, groupDelim) { 8736 var groups = groupDelim ? input.split(groupDelim) : [input]; 8737 8738 for (var i in groups) { 8739 if (typeof groups[i] !== "string") { 8740 continue; 8741 } 8742 8743 var kv = groups[i].split(keyValueDelim); 8744 8745 if (kv.length !== 2) { 8746 continue; 8747 } 8748 8749 var k = kv[0]; 8750 var v = kv[1]; 8751 callback(k, v); 8752 } 8753 } 8754 8755 function parseCue(input, cue, regionList) { 8756 // Remember the original input if we need to throw an error. 8757 var oInput = input; // 4.1 WebVTT timestamp 8758 8759 function consumeTimeStamp() { 8760 var ts = parseTimeStamp(input); 8761 8762 if (ts === null) { 8763 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput); 8764 } // Remove time stamp from input. 8765 8766 8767 input = input.replace(/^[^\sa-zA-Z-]+/, ""); 8768 return ts; 8769 } // 4.4.2 WebVTT cue settings 8770 8771 8772 function consumeCueSettings(input, cue) { 8773 var settings = new Settings(); 8774 parseOptions(input, function (k, v) { 8775 switch (k) { 8776 case "region": 8777 // Find the last region we parsed with the same region id. 8778 for (var i = regionList.length - 1; i >= 0; i--) { 8779 if (regionList[i].id === v) { 8780 settings.set(k, regionList[i].region); 8781 break; 8782 } 8783 } 8784 8785 break; 8786 8787 case "vertical": 8788 settings.alt(k, v, ["rl", "lr"]); 8789 break; 8790 8791 case "line": 8792 var vals = v.split(","), 8793 vals0 = vals[0]; 8794 settings.integer(k, vals0); 8795 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 8796 settings.alt(k, vals0, ["auto"]); 8797 8798 if (vals.length === 2) { 8799 settings.alt("lineAlign", vals[1], ["start", "center", "end"]); 8800 } 8801 8802 break; 8803 8804 case "position": 8805 vals = v.split(","); 8806 settings.percent(k, vals[0]); 8807 8808 if (vals.length === 2) { 8809 settings.alt("positionAlign", vals[1], ["start", "center", "end"]); 8810 } 8811 8812 break; 8813 8814 case "size": 8815 settings.percent(k, v); 8816 break; 8817 8818 case "align": 8819 settings.alt(k, v, ["start", "center", "end", "left", "right"]); 8820 break; 8821 } 8822 }, /:/, /\s/); // Apply default values for any missing fields. 8823 8824 cue.region = settings.get("region", null); 8825 cue.vertical = settings.get("vertical", ""); 8826 8827 try { 8828 cue.line = settings.get("line", "auto"); 8829 } catch (e) {} 8830 8831 cue.lineAlign = settings.get("lineAlign", "start"); 8832 cue.snapToLines = settings.get("snapToLines", true); 8833 cue.size = settings.get("size", 100); // Safari still uses the old middle value and won't accept center 8834 8835 try { 8836 cue.align = settings.get("align", "center"); 8837 } catch (e) { 8838 cue.align = settings.get("align", "middle"); 8839 } 8840 8841 try { 8842 cue.position = settings.get("position", "auto"); 8843 } catch (e) { 8844 cue.position = settings.get("position", { 8845 start: 0, 8846 left: 0, 8847 center: 50, 8848 middle: 50, 8849 end: 100, 8850 right: 100 8851 }, cue.align); 8852 } 8853 8854 cue.positionAlign = settings.get("positionAlign", { 8855 start: "start", 8856 left: "start", 8857 center: "center", 8858 middle: "center", 8859 end: "end", 8860 right: "end" 8861 }, cue.align); 8862 } 8863 8864 function skipWhitespace() { 8865 input = input.replace(/^\s+/, ""); 8866 } // 4.1 WebVTT cue timings. 8867 8868 8869 skipWhitespace(); 8870 cue.startTime = consumeTimeStamp(); // (1) collect cue start time 8871 8872 skipWhitespace(); 8873 8874 if (input.substr(0, 3) !== "-->") { 8875 // (3) next characters must match "-->" 8876 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput); 8877 } 8878 8879 input = input.substr(3); 8880 skipWhitespace(); 8881 cue.endTime = consumeTimeStamp(); // (5) collect cue end time 8882 // 4.1 WebVTT cue settings list. 8883 8884 skipWhitespace(); 8885 consumeCueSettings(input, cue); 8886 } 8887 8888 var TEXTAREA_ELEMENT = document.createElement("textarea"); 8889 var TAG_NAME = { 8890 c: "span", 8891 i: "i", 8892 b: "b", 8893 u: "u", 8894 ruby: "ruby", 8895 rt: "rt", 8896 v: "span", 8897 lang: "span" 8898 }; // 5.1 default text color 8899 // 5.2 default text background color is equivalent to text color with bg_ prefix 8900 8901 var DEFAULT_COLOR_CLASS = { 8902 white: 'rgba(255,255,255,1)', 8903 lime: 'rgba(0,255,0,1)', 8904 cyan: 'rgba(0,255,255,1)', 8905 red: 'rgba(255,0,0,1)', 8906 yellow: 'rgba(255,255,0,1)', 8907 magenta: 'rgba(255,0,255,1)', 8908 blue: 'rgba(0,0,255,1)', 8909 black: 'rgba(0,0,0,1)' 8910 }; 8911 var TAG_ANNOTATION = { 8912 v: "title", 8913 lang: "lang" 8914 }; 8915 var NEEDS_PARENT = { 8916 rt: "ruby" 8917 }; // Parse content into a document fragment. 8918 8919 function parseContent(window, input) { 8920 function nextToken() { 8921 // Check for end-of-string. 8922 if (!input) { 8923 return null; 8924 } // Consume 'n' characters from the input. 8925 8926 8927 function consume(result) { 8928 input = input.substr(result.length); 8929 return result; 8930 } 8931 8932 var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return 8933 // the tag. 8934 8935 return consume(m[1] ? m[1] : m[2]); 8936 } 8937 8938 function unescape(s) { 8939 TEXTAREA_ELEMENT.innerHTML = s; 8940 s = TEXTAREA_ELEMENT.textContent; 8941 TEXTAREA_ELEMENT.textContent = ""; 8942 return s; 8943 } 8944 8945 function shouldAdd(current, element) { 8946 return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName; 8947 } // Create an element for this tag. 8948 8949 8950 function createElement(type, annotation) { 8951 var tagName = TAG_NAME[type]; 8952 8953 if (!tagName) { 8954 return null; 8955 } 8956 8957 var element = window.document.createElement(tagName); 8958 var name = TAG_ANNOTATION[type]; 8959 8960 if (name && annotation) { 8961 element[name] = annotation.trim(); 8962 } 8963 8964 return element; 8965 } 8966 8967 var rootDiv = window.document.createElement("div"), 8968 current = rootDiv, 8969 t, 8970 tagStack = []; 8971 8972 while ((t = nextToken()) !== null) { 8973 if (t[0] === '<') { 8974 if (t[1] === "/") { 8975 // If the closing tag matches, move back up to the parent node. 8976 if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 8977 tagStack.pop(); 8978 current = current.parentNode; 8979 } // Otherwise just ignore the end tag. 8980 8981 8982 continue; 8983 } 8984 8985 var ts = parseTimeStamp(t.substr(1, t.length - 2)); 8986 var node; 8987 8988 if (ts) { 8989 // Timestamps are lead nodes as well. 8990 node = window.document.createProcessingInstruction("timestamp", ts); 8991 current.appendChild(node); 8992 continue; 8993 } 8994 8995 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag. 8996 8997 if (!m) { 8998 continue; 8999 } // Try to construct an element, and ignore the tag if we couldn't. 9000 9001 9002 node = createElement(m[1], m[3]); 9003 9004 if (!node) { 9005 continue; 9006 } // Determine if the tag should be added based on the context of where it 9007 // is placed in the cuetext. 9008 9009 9010 if (!shouldAdd(current, node)) { 9011 continue; 9012 } // Set the class list (as a list of classes, separated by space). 9013 9014 9015 if (m[2]) { 9016 var classes = m[2].split('.'); 9017 classes.forEach(function (cl) { 9018 var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color 9019 9020 var colorName = bgColor ? cl.slice(3) : cl; 9021 9022 if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) { 9023 var propName = bgColor ? 'background-color' : 'color'; 9024 var propValue = DEFAULT_COLOR_CLASS[colorName]; 9025 node.style[propName] = propValue; 9026 } 9027 }); 9028 node.className = classes.join(' '); 9029 } // Append the node to the current node, and enter the scope of the new 9030 // node. 9031 9032 9033 tagStack.push(m[1]); 9034 current.appendChild(node); 9035 current = node; 9036 continue; 9037 } // Text nodes are leaf nodes. 9038 9039 9040 current.appendChild(window.document.createTextNode(unescape(t))); 9041 } 9042 9043 return rootDiv; 9044 } // This is a list of all the Unicode characters that have a strong 9045 // right-to-left category. What this means is that these characters are 9046 // written right-to-left for sure. It was generated by pulling all the strong 9047 // right-to-left characters out of the Unicode data table. That table can 9048 // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 9049 9050 9051 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]]; 9052 9053 function isStrongRTLChar(charCode) { 9054 for (var i = 0; i < strongRTLRanges.length; i++) { 9055 var currentRange = strongRTLRanges[i]; 9056 9057 if (charCode >= currentRange[0] && charCode <= currentRange[1]) { 9058 return true; 9059 } 9060 } 9061 9062 return false; 9063 } 9064 9065 function determineBidi(cueDiv) { 9066 var nodeStack = [], 9067 text = "", 9068 charCode; 9069 9070 if (!cueDiv || !cueDiv.childNodes) { 9071 return "ltr"; 9072 } 9073 9074 function pushNodes(nodeStack, node) { 9075 for (var i = node.childNodes.length - 1; i >= 0; i--) { 9076 nodeStack.push(node.childNodes[i]); 9077 } 9078 } 9079 9080 function nextTextNode(nodeStack) { 9081 if (!nodeStack || !nodeStack.length) { 9082 return null; 9083 } 9084 9085 var node = nodeStack.pop(), 9086 text = node.textContent || node.innerText; 9087 9088 if (text) { 9089 // TODO: This should match all unicode type B characters (paragraph 9090 // separator characters). See issue #115. 9091 var m = text.match(/^.*(\n|\r)/); 9092 9093 if (m) { 9094 nodeStack.length = 0; 9095 return m[0]; 9096 } 9097 9098 return text; 9099 } 9100 9101 if (node.tagName === "ruby") { 9102 return nextTextNode(nodeStack); 9103 } 9104 9105 if (node.childNodes) { 9106 pushNodes(nodeStack, node); 9107 return nextTextNode(nodeStack); 9108 } 9109 } 9110 9111 pushNodes(nodeStack, cueDiv); 9112 9113 while (text = nextTextNode(nodeStack)) { 9114 for (var i = 0; i < text.length; i++) { 9115 charCode = text.charCodeAt(i); 9116 9117 if (isStrongRTLChar(charCode)) { 9118 return "rtl"; 9119 } 9120 } 9121 } 9122 9123 return "ltr"; 9124 } 9125 9126 function computeLinePos(cue) { 9127 if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) { 9128 return cue.line; 9129 } 9130 9131 if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) { 9132 return -1; 9133 } 9134 9135 var track = cue.track, 9136 trackList = track.textTrackList, 9137 count = 0; 9138 9139 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 9140 if (trackList[i].mode === "showing") { 9141 count++; 9142 } 9143 } 9144 9145 return ++count * -1; 9146 } 9147 9148 function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the 9149 // div on 'this'. 9150 9151 9152 StyleBox.prototype.applyStyles = function (styles, div) { 9153 div = div || this.div; 9154 9155 for (var prop in styles) { 9156 if (styles.hasOwnProperty(prop)) { 9157 div.style[prop] = styles[prop]; 9158 } 9159 } 9160 }; 9161 9162 StyleBox.prototype.formatStyle = function (val, unit) { 9163 return val === 0 ? 0 : val + unit; 9164 }; // Constructs the computed display state of the cue (a div). Places the div 9165 // into the overlay which should be a block level element (usually a div). 9166 9167 9168 function CueStyleBox(window, cue, styleOptions) { 9169 StyleBox.call(this); 9170 this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 9171 // have inline positioning and will function as the cue background box. 9172 9173 this.cueDiv = parseContent(window, cue.text); 9174 var styles = { 9175 color: "rgba(255, 255, 255, 1)", 9176 backgroundColor: "rgba(0, 0, 0, 0.8)", 9177 position: "relative", 9178 left: 0, 9179 right: 0, 9180 top: 0, 9181 bottom: 0, 9182 display: "inline", 9183 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9184 unicodeBidi: "plaintext" 9185 }; 9186 this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue 9187 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 9188 // mirrors of them except middle instead of center on Safari. 9189 9190 this.div = window.document.createElement("div"); 9191 styles = { 9192 direction: determineBidi(this.cueDiv), 9193 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9194 unicodeBidi: "plaintext", 9195 textAlign: cue.align === "middle" ? "center" : cue.align, 9196 font: styleOptions.font, 9197 whiteSpace: "pre-line", 9198 position: "absolute" 9199 }; 9200 this.applyStyles(styles); 9201 this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text 9202 // position of the cue box. The reference edge will be resolved later when 9203 // the box orientation styles are applied. 9204 9205 var textPos = 0; 9206 9207 switch (cue.positionAlign) { 9208 case "start": 9209 textPos = cue.position; 9210 break; 9211 9212 case "center": 9213 textPos = cue.position - cue.size / 2; 9214 break; 9215 9216 case "end": 9217 textPos = cue.position - cue.size; 9218 break; 9219 } // Horizontal box orientation; textPos is the distance from the left edge of the 9220 // area to the left edge of the box and cue.size is the distance extending to 9221 // the right from there. 9222 9223 9224 if (cue.vertical === "") { 9225 this.applyStyles({ 9226 left: this.formatStyle(textPos, "%"), 9227 width: this.formatStyle(cue.size, "%") 9228 }); // Vertical box orientation; textPos is the distance from the top edge of the 9229 // area to the top edge of the box and cue.size is the height extending 9230 // downwards from there. 9231 } else { 9232 this.applyStyles({ 9233 top: this.formatStyle(textPos, "%"), 9234 height: this.formatStyle(cue.size, "%") 9235 }); 9236 } 9237 9238 this.move = function (box) { 9239 this.applyStyles({ 9240 top: this.formatStyle(box.top, "px"), 9241 bottom: this.formatStyle(box.bottom, "px"), 9242 left: this.formatStyle(box.left, "px"), 9243 right: this.formatStyle(box.right, "px"), 9244 height: this.formatStyle(box.height, "px"), 9245 width: this.formatStyle(box.width, "px") 9246 }); 9247 }; 9248 } 9249 9250 CueStyleBox.prototype = _objCreate(StyleBox.prototype); 9251 CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily 9252 // compute things with such as if it overlaps or intersects with another Element. 9253 // Can initialize it with either a StyleBox or another BoxPosition. 9254 9255 function BoxPosition(obj) { 9256 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 9257 // was passed in and we need to copy the results of 'getBoundingClientRect' 9258 // as the object returned is readonly. All co-ordinate values are in reference 9259 // to the viewport origin (top left). 9260 var lh, height, width, top; 9261 9262 if (obj.div) { 9263 height = obj.div.offsetHeight; 9264 width = obj.div.offsetWidth; 9265 top = obj.div.offsetTop; 9266 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); 9267 obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of 9268 // the inner div's lines. This could be due to bold text, etc, on some platforms. 9269 // In this case we should get the average line height and use that. This will 9270 // result in the desired behaviour. 9271 9272 lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0; 9273 } 9274 9275 this.left = obj.left; 9276 this.right = obj.right; 9277 this.top = obj.top || top; 9278 this.height = obj.height || height; 9279 this.bottom = obj.bottom || top + (obj.height || height); 9280 this.width = obj.width || width; 9281 this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 9282 } // Move the box along a particular axis. Optionally pass in an amount to move 9283 // the box. If no amount is passed then the default is the line height of the 9284 // box. 9285 9286 9287 BoxPosition.prototype.move = function (axis, toMove) { 9288 toMove = toMove !== undefined ? toMove : this.lineHeight; 9289 9290 switch (axis) { 9291 case "+x": 9292 this.left += toMove; 9293 this.right += toMove; 9294 break; 9295 9296 case "-x": 9297 this.left -= toMove; 9298 this.right -= toMove; 9299 break; 9300 9301 case "+y": 9302 this.top += toMove; 9303 this.bottom += toMove; 9304 break; 9305 9306 case "-y": 9307 this.top -= toMove; 9308 this.bottom -= toMove; 9309 break; 9310 } 9311 }; // Check if this box overlaps another box, b2. 9312 9313 9314 BoxPosition.prototype.overlaps = function (b2) { 9315 return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top; 9316 }; // Check if this box overlaps any other boxes in boxes. 9317 9318 9319 BoxPosition.prototype.overlapsAny = function (boxes) { 9320 for (var i = 0; i < boxes.length; i++) { 9321 if (this.overlaps(boxes[i])) { 9322 return true; 9323 } 9324 } 9325 9326 return false; 9327 }; // Check if this box is within another box. 9328 9329 9330 BoxPosition.prototype.within = function (container) { 9331 return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right; 9332 }; // Check if this box is entirely within the container or it is overlapping 9333 // on the edge opposite of the axis direction passed. For example, if "+x" is 9334 // passed and the box is overlapping on the left edge of the container, then 9335 // return true. 9336 9337 9338 BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) { 9339 switch (axis) { 9340 case "+x": 9341 return this.left < container.left; 9342 9343 case "-x": 9344 return this.right > container.right; 9345 9346 case "+y": 9347 return this.top < container.top; 9348 9349 case "-y": 9350 return this.bottom > container.bottom; 9351 } 9352 }; // Find the percentage of the area that this box is overlapping with another 9353 // box. 9354 9355 9356 BoxPosition.prototype.intersectPercentage = function (b2) { 9357 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 9358 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 9359 intersectArea = x * y; 9360 return intersectArea / (this.height * this.width); 9361 }; // Convert the positions from this box to CSS compatible positions using 9362 // the reference container's positions. This has to be done because this 9363 // box's positions are in reference to the viewport origin, whereas, CSS 9364 // values are in referecne to their respective edges. 9365 9366 9367 BoxPosition.prototype.toCSSCompatValues = function (reference) { 9368 return { 9369 top: this.top - reference.top, 9370 bottom: reference.bottom - this.bottom, 9371 left: this.left - reference.left, 9372 right: reference.right - this.right, 9373 height: this.height, 9374 width: this.width 9375 }; 9376 }; // Get an object that represents the box's position without anything extra. 9377 // Can pass a StyleBox, HTMLElement, or another BoxPositon. 9378 9379 9380 BoxPosition.getSimpleBoxPosition = function (obj) { 9381 var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; 9382 var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; 9383 var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; 9384 obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; 9385 var ret = { 9386 left: obj.left, 9387 right: obj.right, 9388 top: obj.top || top, 9389 height: obj.height || height, 9390 bottom: obj.bottom || top + (obj.height || height), 9391 width: obj.width || width 9392 }; 9393 return ret; 9394 }; // Move a StyleBox to its specified, or next best, position. The containerBox 9395 // is the box that contains the StyleBox, such as a div. boxPositions are 9396 // a list of other boxes that the styleBox can't overlap with. 9397 9398 9399 function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 9400 // Find the best position for a cue box, b, on the video. The axis parameter 9401 // is a list of axis, the order of which, it will move the box along. For example: 9402 // Passing ["+x", "-x"] will move the box first along the x axis in the positive 9403 // direction. If it doesn't find a good position for it there it will then move 9404 // it along the x axis in the negative direction. 9405 function findBestPosition(b, axis) { 9406 var bestPosition, 9407 specifiedPosition = new BoxPosition(b), 9408 percentage = 1; // Highest possible so the first thing we get is better. 9409 9410 for (var i = 0; i < axis.length; i++) { 9411 while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) { 9412 b.move(axis[i]); 9413 } // We found a spot where we aren't overlapping anything. This is our 9414 // best position. 9415 9416 9417 if (b.within(containerBox)) { 9418 return b; 9419 } 9420 9421 var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try 9422 // then remember this position as the best position. 9423 9424 if (percentage > p) { 9425 bestPosition = new BoxPosition(b); 9426 percentage = p; 9427 } // Reset the box position to the specified position. 9428 9429 9430 b = new BoxPosition(specifiedPosition); 9431 } 9432 9433 return bestPosition || specifiedPosition; 9434 } 9435 9436 var boxPosition = new BoxPosition(styleBox), 9437 cue = styleBox.cue, 9438 linePos = computeLinePos(cue), 9439 axis = []; // If we have a line number to align the cue to. 9440 9441 if (cue.snapToLines) { 9442 var size; 9443 9444 switch (cue.vertical) { 9445 case "": 9446 axis = ["+y", "-y"]; 9447 size = "height"; 9448 break; 9449 9450 case "rl": 9451 axis = ["+x", "-x"]; 9452 size = "width"; 9453 break; 9454 9455 case "lr": 9456 axis = ["-x", "+x"]; 9457 size = "width"; 9458 break; 9459 } 9460 9461 var step = boxPosition.lineHeight, 9462 position = step * Math.round(linePos), 9463 maxPosition = containerBox[size] + step, 9464 initialAxis = axis[0]; // If the specified intial position is greater then the max position then 9465 // clamp the box to the amount of steps it would take for the box to 9466 // reach the max position. 9467 9468 if (Math.abs(position) > maxPosition) { 9469 position = position < 0 ? -1 : 1; 9470 position *= Math.ceil(maxPosition / step) * step; 9471 } // If computed line position returns negative then line numbers are 9472 // relative to the bottom of the video instead of the top. Therefore, we 9473 // need to increase our initial position by the length or width of the 9474 // video, depending on the writing direction, and reverse our axis directions. 9475 9476 9477 if (linePos < 0) { 9478 position += cue.vertical === "" ? containerBox.height : containerBox.width; 9479 axis = axis.reverse(); 9480 } // Move the box to the specified position. This may not be its best 9481 // position. 9482 9483 9484 boxPosition.move(initialAxis, position); 9485 } else { 9486 // If we have a percentage line value for the cue. 9487 var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100; 9488 9489 switch (cue.lineAlign) { 9490 case "center": 9491 linePos -= calculatedPercentage / 2; 9492 break; 9493 9494 case "end": 9495 linePos -= calculatedPercentage; 9496 break; 9497 } // Apply initial line position to the cue box. 9498 9499 9500 switch (cue.vertical) { 9501 case "": 9502 styleBox.applyStyles({ 9503 top: styleBox.formatStyle(linePos, "%") 9504 }); 9505 break; 9506 9507 case "rl": 9508 styleBox.applyStyles({ 9509 left: styleBox.formatStyle(linePos, "%") 9510 }); 9511 break; 9512 9513 case "lr": 9514 styleBox.applyStyles({ 9515 right: styleBox.formatStyle(linePos, "%") 9516 }); 9517 break; 9518 } 9519 9520 axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning 9521 // to it. 9522 9523 boxPosition = new BoxPosition(styleBox); 9524 } 9525 9526 var bestPosition = findBestPosition(boxPosition, axis); 9527 styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 9528 } 9529 9530 function WebVTT$1() {} // Nothing 9531 // Helper to allow strings to be decoded instead of the default binary utf8 data. 9532 9533 9534 WebVTT$1.StringDecoder = function () { 9535 return { 9536 decode: function decode(data) { 9537 if (!data) { 9538 return ""; 9539 } 9540 9541 if (typeof data !== "string") { 9542 throw new Error("Error - expected string data."); 9543 } 9544 9545 return decodeURIComponent(encodeURIComponent(data)); 9546 } 9547 }; 9548 }; 9549 9550 WebVTT$1.convertCueToDOMTree = function (window, cuetext) { 9551 if (!window || !cuetext) { 9552 return null; 9553 } 9554 9555 return parseContent(window, cuetext); 9556 }; 9557 9558 var FONT_SIZE_PERCENT = 0.05; 9559 var FONT_STYLE = "sans-serif"; 9560 var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it. 9561 // @param overlay A block level element (usually a div) that the computed cues 9562 // and regions will be placed into. 9563 9564 WebVTT$1.processCues = function (window, cues, overlay) { 9565 if (!window || !cues || !overlay) { 9566 return null; 9567 } // Remove all previous children. 9568 9569 9570 while (overlay.firstChild) { 9571 overlay.removeChild(overlay.firstChild); 9572 } 9573 9574 var paddedOverlay = window.document.createElement("div"); 9575 paddedOverlay.style.position = "absolute"; 9576 paddedOverlay.style.left = "0"; 9577 paddedOverlay.style.right = "0"; 9578 paddedOverlay.style.top = "0"; 9579 paddedOverlay.style.bottom = "0"; 9580 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 9581 overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could 9582 // be the case if a cue's state has been changed since the last computation or 9583 // if it has not been computed yet. 9584 9585 function shouldCompute(cues) { 9586 for (var i = 0; i < cues.length; i++) { 9587 if (cues[i].hasBeenReset || !cues[i].displayState) { 9588 return true; 9589 } 9590 } 9591 9592 return false;
vendor: 5,444 bytes, lines 9593-9758
9593 } // We don't need to recompute the cues' display states. Just reuse them. 9594 9595 9596 if (!shouldCompute(cues)) { 9597 for (var i = 0; i < cues.length; i++) { 9598 paddedOverlay.appendChild(cues[i].displayState); 9599 } 9600 9601 return; 9602 } 9603 9604 var boxPositions = [], 9605 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 9606 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 9607 var styleOptions = { 9608 font: fontSize + "px " + FONT_STYLE 9609 }; 9610 9611 (function () { 9612 var styleBox, cue; 9613 9614 for (var i = 0; i < cues.length; i++) { 9615 cue = cues[i]; // Compute the intial position and styles of the cue div. 9616 9617 styleBox = new CueStyleBox(window, cue, styleOptions); 9618 paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. 9619 9620 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later 9621 // if we don't have too. 9622 9623 cue.displayState = styleBox.div; 9624 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 9625 } 9626 })(); 9627 }; 9628 9629 WebVTT$1.Parser = function (window, vttjs, decoder) { 9630 if (!decoder) { 9631 decoder = vttjs; 9632 vttjs = {}; 9633 } 9634 9635 if (!vttjs) { 9636 vttjs = {}; 9637 } 9638 9639 this.window = window; 9640 this.vttjs = vttjs; 9641 this.state = "INITIAL"; 9642 this.buffer = ""; 9643 this.decoder = decoder || new TextDecoder("utf8"); 9644 this.regionList = []; 9645 }; 9646 9647 WebVTT$1.Parser.prototype = { 9648 // If the error is a ParsingError then report it to the consumer if 9649 // possible. If it's not a ParsingError then throw it like normal. 9650 reportOrThrowError: function reportOrThrowError(e) { 9651 if (e instanceof ParsingError) { 9652 this.onparsingerror && this.onparsingerror(e); 9653 } else { 9654 throw e; 9655 } 9656 }, 9657 parse: function parse(data) { 9658 var self = this; // If there is no data then we won't decode it, but will just try to parse 9659 // whatever is in buffer already. This may occur in circumstances, for 9660 // example when flush() is called. 9661 9662 if (data) { 9663 // Try to decode the data that we received. 9664 self.buffer += self.decoder.decode(data, { 9665 stream: true 9666 }); 9667 } 9668 9669 function collectNextLine() { 9670 var buffer = self.buffer; 9671 var pos = 0; 9672 9673 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 9674 ++pos; 9675 } 9676 9677 var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below. 9678 9679 if (buffer[pos] === '\r') { 9680 ++pos; 9681 } 9682 9683 if (buffer[pos] === '\n') { 9684 ++pos; 9685 } 9686 9687 self.buffer = buffer.substr(pos); 9688 return line; 9689 } // 3.4 WebVTT region and WebVTT region settings syntax 9690 9691 9692 function parseRegion(input) { 9693 var settings = new Settings(); 9694 parseOptions(input, function (k, v) { 9695 switch (k) { 9696 case "id": 9697 settings.set(k, v); 9698 break; 9699 9700 case "width": 9701 settings.percent(k, v); 9702 break; 9703 9704 case "lines": 9705 settings.integer(k, v); 9706 break; 9707 9708 case "regionanchor": 9709 case "viewportanchor": 9710 var xy = v.split(','); 9711 9712 if (xy.length !== 2) { 9713 break; 9714 } // We have to make sure both x and y parse, so use a temporary 9715 // settings object here. 9716 9717 9718 var anchor = new Settings(); 9719 anchor.percent("x", xy[0]); 9720 anchor.percent("y", xy[1]); 9721 9722 if (!anchor.has("x") || !anchor.has("y")) { 9723 break; 9724 } 9725 9726 settings.set(k + "X", anchor.get("x")); 9727 settings.set(k + "Y", anchor.get("y")); 9728 break; 9729 9730 case "scroll": 9731 settings.alt(k, v, ["up"]); 9732 break; 9733 } 9734 }, /=/, /\s/); // Create the region, using default values for any values that were not 9735 // specified. 9736 9737 if (settings.has("id")) { 9738 var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); 9739 region.width = settings.get("width", 100); 9740 region.lines = settings.get("lines", 3); 9741 region.regionAnchorX = settings.get("regionanchorX", 0); 9742 region.regionAnchorY = settings.get("regionanchorY", 100); 9743 region.viewportAnchorX = settings.get("viewportanchorX", 0); 9744 region.viewportAnchorY = settings.get("viewportanchorY", 100); 9745 region.scroll = settings.get("scroll", ""); // Register the region. 9746 9747 self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that 9748 // reference it. 9749 9750 self.regionList.push({ 9751 id: settings.get("id"), 9752 region: region 9753 }); 9754 } 9755 } // draft-pantos-http-live-streaming-20 9756 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 9757 // 3.5 WebVTT 9758
vendor: 10,158 bytes, lines 9759-10092
9759 9760 function parseTimestampMap(input) { 9761 var settings = new Settings(); 9762 parseOptions(input, function (k, v) { 9763 switch (k) { 9764 case "MPEGT": 9765 settings.integer(k + 'S', v); 9766 break; 9767 9768 case "LOCA": 9769 settings.set(k + 'L', parseTimeStamp(v)); 9770 break; 9771 } 9772 }, /[^\d]:/, /,/); 9773 self.ontimestampmap && self.ontimestampmap({ 9774 "MPEGTS": settings.get("MPEGTS"), 9775 "LOCAL": settings.get("LOCAL") 9776 }); 9777 } // 3.2 WebVTT metadata header syntax 9778 9779 9780 function parseHeader(input) { 9781 if (input.match(/X-TIMESTAMP-MAP/)) { 9782 // This line contains HLS X-TIMESTAMP-MAP metadata 9783 parseOptions(input, function (k, v) { 9784 switch (k) { 9785 case "X-TIMESTAMP-MAP": 9786 parseTimestampMap(v); 9787 break; 9788 } 9789 }, /=/); 9790 } else { 9791 parseOptions(input, function (k, v) { 9792 switch (k) { 9793 case "Region": 9794 // 3.3 WebVTT region metadata header syntax 9795 parseRegion(v); 9796 break; 9797 } 9798 }, /:/); 9799 } 9800 } // 5.1 WebVTT file parsing. 9801 9802 9803 try { 9804 var line; 9805 9806 if (self.state === "INITIAL") { 9807 // We can't start parsing until we have the first line. 9808 if (!/\r\n|\n/.test(self.buffer)) { 9809 return this; 9810 } 9811 9812 line = collectNextLine(); 9813 var m = line.match(/^WEBVTT([ \t].*)?$/); 9814 9815 if (!m || !m[0]) { 9816 throw new ParsingError(ParsingError.Errors.BadSignature); 9817 } 9818 9819 self.state = "HEADER"; 9820 } 9821 9822 var alreadyCollectedLine = false; 9823 9824 while (self.buffer) { 9825 // We can't parse a line until we have the full line. 9826 if (!/\r\n|\n/.test(self.buffer)) { 9827 return this; 9828 } 9829 9830 if (!alreadyCollectedLine) { 9831 line = collectNextLine(); 9832 } else { 9833 alreadyCollectedLine = false; 9834 } 9835 9836 switch (self.state) { 9837 case "HEADER": 9838 // 13-18 - Allow a header (metadata) under the WEBVTT line. 9839 if (/:/.test(line)) { 9840 parseHeader(line); 9841 } else if (!line) { 9842 // An empty line terminates the header and starts the body (cues). 9843 self.state = "ID"; 9844 } 9845 9846 continue; 9847 9848 case "NOTE": 9849 // Ignore NOTE blocks. 9850 if (!line) { 9851 self.state = "ID"; 9852 } 9853 9854 continue; 9855 9856 case "ID": 9857 // Check for the start of NOTE blocks. 9858 if (/^NOTE($|[ \t])/.test(line)) { 9859 self.state = "NOTE"; 9860 break; 9861 } // 19-29 - Allow any number of line terminators, then initialize new cue values. 9862 9863 9864 if (!line) { 9865 continue; 9866 } 9867 9868 self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center 9869 9870 try { 9871 self.cue.align = "center"; 9872 } catch (e) { 9873 self.cue.align = "middle"; 9874 } 9875 9876 self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data. 9877 9878 if (line.indexOf("-->") === -1) { 9879 self.cue.id = line; 9880 continue; 9881 } 9882 9883 // Process line as start of a cue. 9884 9885 /*falls through*/ 9886 9887 case "CUE": 9888 // 40 - Collect cue timings and settings. 9889 try { 9890 parseCue(line, self.cue, self.regionList); 9891 } catch (e) { 9892 self.reportOrThrowError(e); // In case of an error ignore rest of the cue. 9893 9894 self.cue = null; 9895 self.state = "BADCUE"; 9896 continue; 9897 } 9898 9899 self.state = "CUETEXT"; 9900 continue; 9901 9902 case "CUETEXT": 9903 var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue. 9904 // 35 - If we have the special substring '-->' then report the cue, 9905 // but do not collect the line as we need to process the current 9906 // one as a new cue. 9907 9908 if (!line || hasSubstring && (alreadyCollectedLine = true)) { 9909 // We are done parsing self cue. 9910 self.oncue && self.oncue(self.cue); 9911 self.cue = null; 9912 self.state = "ID"; 9913 continue; 9914 } 9915 9916 if (self.cue.text) { 9917 self.cue.text += "\n"; 9918 } 9919 9920 self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n'); 9921 continue; 9922 9923 case "BADCUE": 9924 // BADCUE 9925 // 54-62 - Collect and discard the remaining cue. 9926 if (!line) { 9927 self.state = "ID"; 9928 } 9929 9930 continue; 9931 } 9932 } 9933 } catch (e) { 9934 self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have. 9935 9936 if (self.state === "CUETEXT" && self.cue && self.oncue) { 9937 self.oncue(self.cue); 9938 } 9939 9940 self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise 9941 // another exception occurred so enter BADCUE state. 9942 9943 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 9944 } 9945 9946 return this; 9947 }, 9948 flush: function flush() { 9949 var self = this; 9950 9951 try { 9952 // Finish decoding the stream. 9953 self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region. 9954 9955 if (self.cue || self.state === "HEADER") { 9956 self.buffer += "\n\n"; 9957 self.parse(); 9958 } // If we've flushed, parsed, and we're still on the INITIAL state then 9959 // that means we don't have enough of the stream to parse the first 9960 // line. 9961 9962 9963 if (self.state === "INITIAL") { 9964 throw new ParsingError(ParsingError.Errors.BadSignature); 9965 } 9966 } catch (e) { 9967 self.reportOrThrowError(e); 9968 } 9969 9970 self.onflush && self.onflush(); 9971 return this; 9972 } 9973 }; 9974 var vtt = WebVTT$1; 9975 9976 /** 9977 * Copyright 2013 vtt.js Contributors 9978 * 9979 * Licensed under the Apache License, Version 2.0 (the "License"); 9980 * you may not use this file except in compliance with the License. 9981 * You may obtain a copy of the License at 9982 * 9983 * http://www.apache.org/licenses/LICENSE-2.0 9984 * 9985 * Unless required by applicable law or agreed to in writing, software 9986 * distributed under the License is distributed on an "AS IS" BASIS, 9987 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9988 * See the License for the specific language governing permissions and 9989 * limitations under the License. 9990 */ 9991 var autoKeyword = "auto"; 9992 var directionSetting = { 9993 "": 1, 9994 "lr": 1, 9995 "rl": 1 9996 }; 9997 var alignSetting = { 9998 "start": 1, 9999 "center": 1, 10000 "end": 1, 10001 "left": 1, 10002 "right": 1, 10003 "auto": 1, 10004 "line-left": 1, 10005 "line-right": 1 10006 }; 10007 10008 function findDirectionSetting(value) { 10009 if (typeof value !== "string") { 10010 return false; 10011 } 10012 10013 var dir = directionSetting[value.toLowerCase()]; 10014 return dir ? value.toLowerCase() : false; 10015 } 10016 10017 function findAlignSetting(value) { 10018 if (typeof value !== "string") { 10019 return false; 10020 } 10021 10022 var align = alignSetting[value.toLowerCase()]; 10023 return align ? value.toLowerCase() : false; 10024 } 10025 10026 function VTTCue(startTime, endTime, text) { 10027 /** 10028 * Shim implementation specific properties. These properties are not in 10029 * the spec. 10030 */ 10031 // Lets us know when the VTTCue's data has changed in such a way that we need 10032 // to recompute its display state. This lets us compute its display state 10033 // lazily. 10034 this.hasBeenReset = false; 10035 /** 10036 * VTTCue and TextTrackCue properties 10037 * http://dev.w3.org/html5/webvtt/#vttcue-interface 10038 */ 10039 10040 var _id = ""; 10041 var _pauseOnExit = false; 10042 var _startTime = startTime; 10043 var _endTime = endTime; 10044 var _text = text; 10045 var _region = null; 10046 var _vertical = ""; 10047 var _snapToLines = true; 10048 var _line = "auto"; 10049 var _lineAlign = "start"; 10050 var _position = "auto"; 10051 var _positionAlign = "auto"; 10052 var _size = 100; 10053 var _align = "center"; 10054 Object.defineProperties(this, { 10055 "id": { 10056 enumerable: true, 10057 get: function get() { 10058 return _id; 10059 }, 10060 set: function set(value) { 10061 _id = "" + value; 10062 } 10063 }, 10064 "pauseOnExit": { 10065 enumerable: true, 10066 get: function get() { 10067 return _pauseOnExit; 10068 }, 10069 set: function set(value) { 10070 _pauseOnExit = !!value; 10071 } 10072 }, 10073 "startTime": { 10074 enumerable: true, 10075 get: function get() { 10076 return _startTime; 10077 }, 10078 set: function set(value) { 10079 if (typeof value !== "number") { 10080 throw new TypeError("Start time must be set to a number."); 10081 } 10082 10083 _startTime = value; 10084 this.hasBeenReset = true; 10085 } 10086 }, 10087 "endTime": { 10088 enumerable: true, 10089 get: function get() { 10090 return _endTime; 10091 }, 10092 set: function set(value) {
10093 if (typeof value !== "number") { 10094 throw new TypeError("End time must be set to a number."); 10095 } 10096 10097 _endTime = value; 10098 this.hasBeenReset = true; 10099 } 10100 }, 10101 "text": { 10102 enumerable: true, 10103 get: function get() { 10104 return _text; 10105 }, 10106 set: function set(value) { 10107 _text = "" + value; 10108 this.hasBeenReset = true; 10109 } 10110 }, 10111 "region": { 10112 enumerable: true, 10113 get: function get() { 10114 return _region; 10115 }, 10116 set: function set(value) { 10117 _region = value; 10118 this.hasBeenReset = true; 10119 } 10120 }, 10121 "vertical": { 10122 enumerable: true, 10123 get: function get() { 10124 return _vertical; 10125 }, 10126 set: function set(value) { 10127 var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. 10128 10129 if (setting === false) { 10130 throw new SyntaxError("Vertical: an invalid or illegal direction string was specified."); 10131 } 10132 10133 _vertical = setting; 10134 this.hasBeenReset = true; 10135 } 10136 }, 10137 "snapToLines": { 10138 enumerable: true, 10139 get: function get() { 10140 return _snapToLines; 10141 }, 10142 set: function set(value) { 10143 _snapToLines = !!value; 10144 this.hasBeenReset = true; 10145 } 10146 }, 10147 "line": { 10148 enumerable: true, 10149 get: function get() { 10150 return _line; 10151 }, 10152 set: function set(value) { 10153 if (typeof value !== "number" && value !== autoKeyword) { 10154 throw new SyntaxError("Line: an invalid number or illegal string was specified."); 10155 } 10156 10157 _line = value; 10158 this.hasBeenReset = true; 10159 } 10160 }, 10161 "lineAlign": { 10162 enumerable: true, 10163 get: function get() { 10164 return _lineAlign; 10165 }, 10166 set: function set(value) { 10167 var setting = findAlignSetting(value); 10168 10169 if (!setting) { 10170 console.warn("lineAlign: an invalid or illegal string was specified."); 10171 } else { 10172 _lineAlign = setting; 10173 this.hasBeenReset = true; 10174 } 10175 } 10176 }, 10177 "position": { 10178 enumerable: true, 10179 get: function get() { 10180 return _position; 10181 }, 10182 set: function set(value) { 10183 if (value < 0 || value > 100) { 10184 throw new Error("Position must be between 0 and 100."); 10185 } 10186 10187 _position = value; 10188 this.hasBeenReset = true; 10189 } 10190 }, 10191 "positionAlign": { 10192 enumerable: true, 10193 get: function get() { 10194 return _positionAlign; 10195 }, 10196 set: function set(value) { 10197 var setting = findAlignSetting(value); 10198 10199 if (!setting) { 10200 console.warn("positionAlign: an invalid or illegal string was specified."); 10201 } else { 10202 _positionAlign = setting; 10203 this.hasBeenReset = true; 10204 } 10205 } 10206 }, 10207 "size": { 10208 enumerable: true, 10209 get: function get() { 10210 return _size; 10211 }, 10212 set: function set(value) { 10213 if (value < 0 || value > 100) { 10214 throw new Error("Size must be between 0 and 100."); 10215 } 10216 10217 _size = value; 10218 this.hasBeenReset = true; 10219 } 10220 }, 10221 "align": { 10222 enumerable: true, 10223 get: function get() { 10224 return _align; 10225 }, 10226 set: function set(value) { 10227 var setting = findAlignSetting(value); 10228 10229 if (!setting) { 10230 throw new SyntaxError("align: an invalid or illegal alignment string was specified."); 10231 } 10232 10233 _align = setting; 10234 this.hasBeenReset = true; 10235 } 10236 } 10237 }); 10238 /** 10239 * Other <track> spec defined properties 10240 */ 10241 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state 10242 10243 this.displayState = undefined; 10244 } 10245 /** 10246 * VTTCue methods 10247 */ 10248 10249 10250 VTTCue.prototype.getCueAsHTML = function () { 10251 // Assume WebVTT.convertCueToDOMTree is on the global. 10252 return WebVTT.convertCueToDOMTree(window, this.text); 10253 }; 10254 10255 var vttcue = VTTCue; 10256 10257 /** 10258 * Copyright 2013 vtt.js Contributors 10259 * 10260 * Licensed under the Apache License, Version 2.0 (the "License");
10261 * you may not use this file except in compliance with the License. 10262 * You may obtain a copy of the License at 10263 * 10264 * http://www.apache.org/licenses/LICENSE-2.0 10265 * 10266 * Unless required by applicable law or agreed to in writing, software 10267 * distributed under the License is distributed on an "AS IS" BASIS, 10268 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10269 * See the License for the specific language governing permissions and 10270 * limitations under the License. 10271 */ 10272 var scrollSetting = { 10273 "": true, 10274 "up": true 10275 }; 10276 10277 function findScrollSetting(value) { 10278 if (typeof value !== "string") { 10279 return false; 10280 } 10281 10282 var scroll = scrollSetting[value.toLowerCase()]; 10283 return scroll ? value.toLowerCase() : false; 10284 } 10285 10286 function isValidPercentValue(value) { 10287 return typeof value === "number" && value >= 0 && value <= 100; 10288 } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 10289 10290 10291 function VTTRegion() { 10292 var _width = 100; 10293 var _lines = 3; 10294 var _regionAnchorX = 0; 10295 var _regionAnchorY = 100; 10296 var _viewportAnchorX = 0; 10297 var _viewportAnchorY = 100; 10298 var _scroll = ""; 10299 Object.defineProperties(this, { 10300 "width": { 10301 enumerable: true, 10302 get: function get() { 10303 return _width; 10304 }, 10305 set: function set(value) { 10306 if (!isValidPercentValue(value)) { 10307 throw new Error("Width must be between 0 and 100."); 10308 } 10309 10310 _width = value; 10311 } 10312 }, 10313 "lines": { 10314 enumerable: true, 10315 get: function get() { 10316 return _lines; 10317 }, 10318 set: function set(value) { 10319 if (typeof value !== "number") { 10320 throw new TypeError("Lines must be set to a number."); 10321 } 10322 10323 _lines = value; 10324 } 10325 }, 10326 "regionAnchorY": { 10327 enumerable: true, 10328 get: function get() { 10329 return _regionAnchorY; 10330 }, 10331 set: function set(value) { 10332 if (!isValidPercentValue(value)) { 10333 throw new Error("RegionAnchorX must be between 0 and 100."); 10334 } 10335 10336 _regionAnchorY = value; 10337 } 10338 }, 10339 "regionAnchorX": { 10340 enumerable: true, 10341 get: function get() { 10342 return _regionAnchorX; 10343 }, 10344 set: function set(value) { 10345 if (!isValidPercentValue(value)) { 10346 throw new Error("RegionAnchorY must be between 0 and 100."); 10347 } 10348 10349 _regionAnchorX = value; 10350 } 10351 }, 10352 "viewportAnchorY": { 10353 enumerable: true, 10354 get: function get() { 10355 return _viewportAnchorY; 10356 }, 10357 set: function set(value) { 10358 if (!isValidPercentValue(value)) { 10359 throw new Error("ViewportAnchorY must be between 0 and 100."); 10360 } 10361 10362 _viewportAnchorY = value; 10363 } 10364 }, 10365 "viewportAnchorX": { 10366 enumerable: true, 10367 get: function get() { 10368 return _viewportAnchorX; 10369 }, 10370 set: function set(value) { 10371 if (!isValidPercentValue(value)) { 10372 throw new Error("ViewportAnchorX must be between 0 and 100."); 10373 } 10374 10375 _viewportAnchorX = value; 10376 } 10377 }, 10378 "scroll": { 10379 enumerable: true, 10380 get: function get() { 10381 return _scroll; 10382 }, 10383 set: function set(value) { 10384 var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value. 10385 10386 if (setting === false) { 10387 console.warn("Scroll: an invalid or illegal string was specified."); 10388 } else { 10389 _scroll = setting; 10390 } 10391 } 10392 } 10393 }); 10394 } 10395 10396 var vttregion = VTTRegion; 10397 10398 var browserIndex = createCommonjsModule(function (module) { 10399 /** 10400 * Copyright 2013 vtt.js Contributors 10401 * 10402 * Licensed under the Apache License, Version 2.0 (the "License");
10403 * you may not use this file except in compliance with the License. 10404 * You may obtain a copy of the License at 10405 * 10406 * http://www.apache.org/licenses/LICENSE-2.0 10407 * 10408 * Unless required by applicable law or agreed to in writing, software 10409 * distributed under the License is distributed on an "AS IS" BASIS, 10410 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10411 * See the License for the specific language governing permissions and 10412 * limitations under the License. 10413 */ 10414 // Default exports for Node. Export the extended versions of VTTCue and 10415 // VTTRegion in Node since we likely want the capability to convert back and 10416 // forth between JSON. If we don't then it's not that big of a deal since we're 10417 // off browser. 10418 var vttjs = module.exports = { 10419 WebVTT: vtt, 10420 VTTCue: vttcue, 10421 VTTRegion: vttregion 10422 }; 10423 window$3.vttjs = vttjs; 10424 window$3.WebVTT = vttjs.WebVTT; 10425 var cueShim = vttjs.VTTCue; 10426 var regionShim = vttjs.VTTRegion; 10427 var nativeVTTCue = window$3.VTTCue; 10428 var nativeVTTRegion = window$3.VTTRegion; 10429 10430 vttjs.shim = function () { 10431 window$3.VTTCue = cueShim; 10432 window$3.VTTRegion = regionShim; 10433 }; 10434 10435 vttjs.restore = function () { 10436 window$3.VTTCue = nativeVTTCue; 10437 window$3.VTTRegion = nativeVTTRegion; 10438 }; 10439 10440 if (!window$3.VTTCue) { 10441 vttjs.shim(); 10442 } 10443 }); 10444 var browserIndex_1 = browserIndex.WebVTT; 10445 var browserIndex_2 = browserIndex.VTTCue; 10446 var browserIndex_3 = browserIndex.VTTRegion; 10447 10448 /** 10449 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string 10450 * that just contains the src url alone. 10451 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` 10452 * `var SourceString = 'http://example.com/some-video.mp4';` 10453 * 10454 * @typedef {Object|string} Tech~SourceObject 10455 * 10456 * @property {string} src 10457 * The url to the source 10458 * 10459 * @property {string} type 10460 * The mime type of the source 10461 */ 10462 10463 /** 10464 * A function used by {@link Tech} to create a new {@link TextTrack}. 10465 * 10466 * @private 10467 * 10468 * @param {Tech} self 10469 * An instance of the Tech class. 10470 * 10471 * @param {string} kind 10472 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10473 * 10474 * @param {string} [label] 10475 * Label to identify the text track 10476 * 10477 * @param {string} [language] 10478 * Two letter language abbreviation 10479 * 10480 * @param {Object} [options={}] 10481 * An object with additional text track options 10482 * 10483 * @return {TextTrack} 10484 * The text track that was created. 10485 */ 10486 10487 function createTrackHelper(self, kind, label, language, options) { 10488 if (options === void 0) { 10489 options = {}; 10490 } 10491 10492 var tracks = self.textTracks(); 10493 options.kind = kind; 10494 10495 if (label) { 10496 options.label = label; 10497 } 10498 10499 if (language) { 10500 options.language = language; 10501 } 10502 10503 options.tech = self; 10504 var track = new ALL.text.TrackClass(options); 10505 tracks.addTrack(track); 10506 return track; 10507 } 10508 /** 10509 * This is the base class for media playback technology controllers, such as 10510 * {@link Flash} and {@link HTML5} 10511 * 10512 * @extends Component 10513 */ 10514 10515 10516 var Tech = /*#__PURE__*/function (_Component) { 10517 inheritsLoose(Tech, _Component); 10518 10519 /** 10520 * Create an instance of this Tech. 10521 * 10522 * @param {Object} [options] 10523 * The key/value store of player options. 10524 * 10525 * @param {Component~ReadyCallback} ready 10526 * Callback function to call when the `HTML5` Tech is ready. 10527 */ 10528 function Tech(options, ready) { 10529 var _this; 10530 10531 if (options === void 0) { 10532 options = {}; 10533 } 10534 10535 if (ready === void 0) { 10536 ready = function ready() {}; 10537 } 10538 10539 // we don't want the tech to report user activity automatically.
vendor: 63,023 bytes, lines 10540-12628
10540 // This is done manually in addControlsListeners 10541 options.reportTouchActivity = false; 10542 _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to 10543 // implement a very limited played() 10544 10545 _this.hasStarted_ = false; 10546 10547 _this.on('playing', function () { 10548 this.hasStarted_ = true; 10549 }); 10550 10551 _this.on('loadstart', function () { 10552 this.hasStarted_ = false; 10553 }); 10554 10555 ALL.names.forEach(function (name) { 10556 var props = ALL[name]; 10557 10558 if (options && options[props.getterName]) { 10559 _this[props.privateName] = options[props.getterName]; 10560 } 10561 }); // Manually track progress in cases where the browser/flash player doesn't report it. 10562 10563 if (!_this.featuresProgressEvents) { 10564 _this.manualProgressOn(); 10565 } // Manually track timeupdates in cases where the browser/flash player doesn't report it. 10566 10567 10568 if (!_this.featuresTimeupdateEvents) { 10569 _this.manualTimeUpdatesOn(); 10570 } 10571 10572 ['Text', 'Audio', 'Video'].forEach(function (track) { 10573 if (options["native" + track + "Tracks"] === false) { 10574 _this["featuresNative" + track + "Tracks"] = false; 10575 } 10576 }); 10577 10578 if (options.nativeCaptions === false || options.nativeTextTracks === false) { 10579 _this.featuresNativeTextTracks = false; 10580 } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { 10581 _this.featuresNativeTextTracks = true; 10582 } 10583 10584 if (!_this.featuresNativeTextTracks) { 10585 _this.emulateTextTracks(); 10586 } 10587 10588 _this.preloadTextTracks = options.preloadTextTracks !== false; 10589 _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); 10590 10591 _this.initTrackListeners(); // Turn on component tap events only if not using native controls 10592 10593 10594 if (!options.nativeControlsForTouch) { 10595 _this.emitTapEvents(); 10596 } 10597 10598 if (_this.constructor) { 10599 _this.name_ = _this.constructor.name || 'Unknown Tech'; 10600 } 10601 10602 return _this; 10603 } 10604 /** 10605 * A special function to trigger source set in a way that will allow player 10606 * to re-trigger if the player or tech are not ready yet. 10607 * 10608 * @fires Tech#sourceset 10609 * @param {string} src The source string at the time of the source changing. 10610 */ 10611 10612 10613 var _proto = Tech.prototype; 10614 10615 _proto.triggerSourceset = function triggerSourceset(src) { 10616 var _this2 = this; 10617 10618 if (!this.isReady_) { 10619 // on initial ready we have to trigger source set 10620 // 1ms after ready so that player can watch for it. 10621 this.one('ready', function () { 10622 return _this2.setTimeout(function () { 10623 return _this2.triggerSourceset(src); 10624 }, 1); 10625 }); 10626 } 10627 /** 10628 * Fired when the source is set on the tech causing the media element 10629 * to reload. 10630 * 10631 * @see {@link Player#event:sourceset} 10632 * @event Tech#sourceset 10633 * @type {EventTarget~Event} 10634 */ 10635 10636 10637 this.trigger({ 10638 src: src, 10639 type: 'sourceset' 10640 }); 10641 } 10642 /* Fallbacks for unsupported event types 10643 ================================================================================ */ 10644 10645 /** 10646 * Polyfill the `progress` event for browsers that don't support it natively. 10647 * 10648 * @see {@link Tech#trackProgress} 10649 */ 10650 ; 10651 10652 _proto.manualProgressOn = function manualProgressOn() { 10653 this.on('durationchange', this.onDurationChange); 10654 this.manualProgress = true; // Trigger progress watching when a source begins loading 10655 10656 this.one('ready', this.trackProgress); 10657 } 10658 /** 10659 * Turn off the polyfill for `progress` events that was created in 10660 * {@link Tech#manualProgressOn} 10661 */ 10662 ; 10663 10664 _proto.manualProgressOff = function manualProgressOff() { 10665 this.manualProgress = false; 10666 this.stopTrackingProgress(); 10667 this.off('durationchange', this.onDurationChange); 10668 } 10669 /** 10670 * This is used to trigger a `progress` event when the buffered percent changes. It 10671 * sets an interval function that will be called every 500 milliseconds to check if the 10672 * buffer end percent has changed. 10673 * 10674 * > This function is called by {@link Tech#manualProgressOn} 10675 * 10676 * @param {EventTarget~Event} event 10677 * The `ready` event that caused this to run. 10678 * 10679 * @listens Tech#ready 10680 * @fires Tech#progress 10681 */ 10682 ; 10683 10684 _proto.trackProgress = function trackProgress(event) { 10685 this.stopTrackingProgress(); 10686 this.progressInterval = this.setInterval(bind(this, function () { 10687 // Don't trigger unless buffered amount is greater than last time 10688 var numBufferedPercent = this.bufferedPercent(); 10689 10690 if (this.bufferedPercent_ !== numBufferedPercent) { 10691 /** 10692 * See {@link Player#progress} 10693 * 10694 * @event Tech#progress 10695 * @type {EventTarget~Event} 10696 */ 10697 this.trigger('progress'); 10698 } 10699 10700 this.bufferedPercent_ = numBufferedPercent; 10701 10702 if (numBufferedPercent === 1) { 10703 this.stopTrackingProgress(); 10704 } 10705 }), 500); 10706 } 10707 /** 10708 * Update our internal duration on a `durationchange` event by calling 10709 * {@link Tech#duration}. 10710 * 10711 * @param {EventTarget~Event} event 10712 * The `durationchange` event that caused this to run. 10713 * 10714 * @listens Tech#durationchange 10715 */ 10716 ; 10717 10718 _proto.onDurationChange = function onDurationChange(event) { 10719 this.duration_ = this.duration(); 10720 } 10721 /** 10722 * Get and create a `TimeRange` object for buffering. 10723 * 10724 * @return {TimeRange} 10725 * The time range object that was created. 10726 */ 10727 ; 10728 10729 _proto.buffered = function buffered() { 10730 return createTimeRanges(0, 0); 10731 } 10732 /** 10733 * Get the percentage of the current video that is currently buffered. 10734 * 10735 * @return {number} 10736 * A number from 0 to 1 that represents the decimal percentage of the 10737 * video that is buffered. 10738 * 10739 */ 10740 ; 10741 10742 _proto.bufferedPercent = function bufferedPercent$1() { 10743 return bufferedPercent(this.buffered(), this.duration_); 10744 } 10745 /** 10746 * Turn off the polyfill for `progress` events that was created in 10747 * {@link Tech#manualProgressOn} 10748 * Stop manually tracking progress events by clearing the interval that was set in 10749 * {@link Tech#trackProgress}. 10750 */ 10751 ; 10752 10753 _proto.stopTrackingProgress = function stopTrackingProgress() { 10754 this.clearInterval(this.progressInterval); 10755 } 10756 /** 10757 * Polyfill the `timeupdate` event for browsers that don't support it. 10758 * 10759 * @see {@link Tech#trackCurrentTime} 10760 */ 10761 ; 10762 10763 _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() { 10764 this.manualTimeUpdates = true; 10765 this.on('play', this.trackCurrentTime); 10766 this.on('pause', this.stopTrackingCurrentTime); 10767 } 10768 /** 10769 * Turn off the polyfill for `timeupdate` events that was created in 10770 * {@link Tech#manualTimeUpdatesOn} 10771 */ 10772 ; 10773 10774 _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() { 10775 this.manualTimeUpdates = false; 10776 this.stopTrackingCurrentTime(); 10777 this.off('play', this.trackCurrentTime); 10778 this.off('pause', this.stopTrackingCurrentTime); 10779 } 10780 /** 10781 * Sets up an interval function to track current time and trigger `timeupdate` every 10782 * 250 milliseconds. 10783 * 10784 * @listens Tech#play 10785 * @triggers Tech#timeupdate 10786 */ 10787 ; 10788 10789 _proto.trackCurrentTime = function trackCurrentTime() { 10790 if (this.currentTimeInterval) { 10791 this.stopTrackingCurrentTime(); 10792 } 10793 10794 this.currentTimeInterval = this.setInterval(function () { 10795 /** 10796 * Triggered at an interval of 250ms to indicated that time is passing in the video. 10797 * 10798 * @event Tech#timeupdate 10799 * @type {EventTarget~Event} 10800 */ 10801 this.trigger({ 10802 type: 'timeupdate', 10803 target: this, 10804 manuallyTriggered: true 10805 }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 10806 }, 250); 10807 } 10808 /** 10809 * Stop the interval function created in {@link Tech#trackCurrentTime} so that the 10810 * `timeupdate` event is no longer triggered. 10811 * 10812 * @listens {Tech#pause} 10813 */ 10814 ; 10815 10816 _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() { 10817 this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen, 10818 // the progress bar won't make it all the way to the end 10819 10820 this.trigger({ 10821 type: 'timeupdate', 10822 target: this, 10823 manuallyTriggered: true 10824 }); 10825 } 10826 /** 10827 * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, 10828 * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. 10829 * 10830 * @fires Component#dispose 10831 */ 10832 ; 10833 10834 _proto.dispose = function dispose() { 10835 // clear out all tracks because we can't reuse them between techs 10836 this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking 10837 10838 if (this.manualProgress) { 10839 this.manualProgressOff(); 10840 } 10841 10842 if (this.manualTimeUpdates) { 10843 this.manualTimeUpdatesOff(); 10844 } 10845 10846 _Component.prototype.dispose.call(this); 10847 } 10848 /** 10849 * Clear out a single `TrackList` or an array of `TrackLists` given their names. 10850 * 10851 * > Note: Techs without source handlers should call this between sources for `video` 10852 * & `audio` tracks. You don't want to use them between tracks! 10853 * 10854 * @param {string[]|string} types 10855 * TrackList names to clear, valid names are `video`, `audio`, and 10856 * `text`. 10857 */ 10858 ; 10859 10860 _proto.clearTracks = function clearTracks(types) { 10861 var _this3 = this; 10862 10863 types = [].concat(types); // clear out all tracks because we can't reuse them between techs 10864 10865 types.forEach(function (type) { 10866 var list = _this3[type + "Tracks"]() || []; 10867 var i = list.length; 10868 10869 while (i--) { 10870 var track = list[i]; 10871 10872 if (type === 'text') { 10873 _this3.removeRemoteTextTrack(track); 10874 } 10875 10876 list.removeTrack(track); 10877 } 10878 }); 10879 } 10880 /** 10881 * Remove any TextTracks added via addRemoteTextTrack that are 10882 * flagged for automatic garbage collection 10883 */ 10884 ; 10885 10886 _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() { 10887 var list = this.autoRemoteTextTracks_ || []; 10888 var i = list.length; 10889 10890 while (i--) { 10891 var track = list[i]; 10892 this.removeRemoteTextTrack(track); 10893 } 10894 } 10895 /** 10896 * Reset the tech, which will removes all sources and reset the internal readyState. 10897 * 10898 * @abstract 10899 */ 10900 ; 10901 10902 _proto.reset = function reset() {} 10903 /** 10904 * Get or set an error on the Tech. 10905 * 10906 * @param {MediaError} [err] 10907 * Error to set on the Tech 10908 * 10909 * @return {MediaError|null} 10910 * The current error object on the tech, or null if there isn't one. 10911 */ 10912 ; 10913 10914 _proto.error = function error(err) { 10915 if (err !== undefined) { 10916 this.error_ = new MediaError(err); 10917 this.trigger('error'); 10918 } 10919 10920 return this.error_; 10921 } 10922 /** 10923 * Returns the `TimeRange`s that have been played through for the current source. 10924 * 10925 * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. 10926 * It only checks whether the source has played at all or not. 10927 * 10928 * @return {TimeRange} 10929 * - A single time range if this video has played 10930 * - An empty set of ranges if not. 10931 */ 10932 ; 10933 10934 _proto.played = function played() { 10935 if (this.hasStarted_) { 10936 return createTimeRanges(0, 0); 10937 } 10938 10939 return createTimeRanges(); 10940 } 10941 /** 10942 * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was 10943 * previously called. 10944 * 10945 * @fires Tech#timeupdate 10946 */ 10947 ; 10948 10949 _proto.setCurrentTime = function setCurrentTime() { 10950 // improve the accuracy of manual timeupdates 10951 if (this.manualTimeUpdates) { 10952 /** 10953 * A manual `timeupdate` event. 10954 * 10955 * @event Tech#timeupdate 10956 * @type {EventTarget~Event} 10957 */ 10958 this.trigger({ 10959 type: 'timeupdate', 10960 target: this, 10961 manuallyTriggered: true 10962 }); 10963 } 10964 } 10965 /** 10966 * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and 10967 * {@link TextTrackList} events. 10968 * 10969 * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. 10970 * 10971 * @fires Tech#audiotrackchange 10972 * @fires Tech#videotrackchange 10973 * @fires Tech#texttrackchange 10974 */ 10975 ; 10976 10977 _proto.initTrackListeners = function initTrackListeners() { 10978 var _this4 = this; 10979 10980 /** 10981 * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} 10982 * 10983 * @event Tech#audiotrackchange 10984 * @type {EventTarget~Event} 10985 */ 10986 10987 /** 10988 * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} 10989 * 10990 * @event Tech#videotrackchange 10991 * @type {EventTarget~Event} 10992 */ 10993 10994 /** 10995 * Triggered when tracks are added or removed on the Tech {@link TextTrackList} 10996 * 10997 * @event Tech#texttrackchange 10998 * @type {EventTarget~Event} 10999 */ 11000 NORMAL.names.forEach(function (name) { 11001 var props = NORMAL[name]; 11002 11003 var trackListChanges = function trackListChanges() { 11004 _this4.trigger(name + "trackchange"); 11005 }; 11006 11007 var tracks = _this4[props.getterName](); 11008 11009 tracks.addEventListener('removetrack', trackListChanges); 11010 tracks.addEventListener('addtrack', trackListChanges); 11011 11012 _this4.on('dispose', function () { 11013 tracks.removeEventListener('removetrack', trackListChanges); 11014 tracks.removeEventListener('addtrack', trackListChanges); 11015 }); 11016 }); 11017 } 11018 /** 11019 * Emulate TextTracks using vtt.js if necessary 11020 * 11021 * @fires Tech#vttjsloaded 11022 * @fires Tech#vttjserror 11023 */ 11024 ; 11025 11026 _proto.addWebVttScript_ = function addWebVttScript_() { 11027 var _this5 = this; 11028 11029 if (window$3.WebVTT) { 11030 return; 11031 } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system 11032 // signals that the Tech is ready at which point Tech.el_ is part of the DOM 11033 // before inserting the WebVTT script 11034 11035 11036 if (document.body.contains(this.el())) { 11037 // load via require if available and vtt.js script location was not passed in 11038 // as an option. novtt builds will turn the above require call into an empty object 11039 // which will cause this if check to always fail. 11040 if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { 11041 this.trigger('vttjsloaded'); 11042 return; 11043 } // load vtt.js via the script location option or the cdn of no location was 11044 // passed in 11045 11046 11047 var script = document.createElement('script'); 11048 script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; 11049 11050 script.onload = function () { 11051 /** 11052 * Fired when vtt.js is loaded. 11053 * 11054 * @event Tech#vttjsloaded 11055 * @type {EventTarget~Event} 11056 */ 11057 _this5.trigger('vttjsloaded'); 11058 }; 11059 11060 script.onerror = function () { 11061 /** 11062 * Fired when vtt.js was not loaded due to an error 11063 * 11064 * @event Tech#vttjsloaded 11065 * @type {EventTarget~Event} 11066 */ 11067 _this5.trigger('vttjserror'); 11068 }; 11069 11070 this.on('dispose', function () { 11071 script.onload = null; 11072 script.onerror = null; 11073 }); // but have not loaded yet and we set it to true before the inject so that 11074 // we don't overwrite the injected window.WebVTT if it loads right away 11075 11076 window$3.WebVTT = true; 11077 this.el().parentNode.appendChild(script); 11078 } else { 11079 this.ready(this.addWebVttScript_); 11080 } 11081 } 11082 /** 11083 * Emulate texttracks 11084 * 11085 */ 11086 ; 11087 11088 _proto.emulateTextTracks = function emulateTextTracks() { 11089 var _this6 = this; 11090 11091 var tracks = this.textTracks(); 11092 var remoteTracks = this.remoteTextTracks(); 11093 11094 var handleAddTrack = function handleAddTrack(e) { 11095 return tracks.addTrack(e.track); 11096 }; 11097 11098 var handleRemoveTrack = function handleRemoveTrack(e) { 11099 return tracks.removeTrack(e.track); 11100 }; 11101 11102 remoteTracks.on('addtrack', handleAddTrack); 11103 remoteTracks.on('removetrack', handleRemoveTrack); 11104 this.addWebVttScript_(); 11105 11106 var updateDisplay = function updateDisplay() { 11107 return _this6.trigger('texttrackchange'); 11108 }; 11109 11110 var textTracksChanges = function textTracksChanges() { 11111 updateDisplay(); 11112 11113 for (var i = 0; i < tracks.length; i++) { 11114 var track = tracks[i]; 11115 track.removeEventListener('cuechange', updateDisplay); 11116 11117 if (track.mode === 'showing') { 11118 track.addEventListener('cuechange', updateDisplay); 11119 } 11120 } 11121 }; 11122 11123 textTracksChanges(); 11124 tracks.addEventListener('change', textTracksChanges); 11125 tracks.addEventListener('addtrack', textTracksChanges); 11126 tracks.addEventListener('removetrack', textTracksChanges); 11127 this.on('dispose', function () { 11128 remoteTracks.off('addtrack', handleAddTrack); 11129 remoteTracks.off('removetrack', handleRemoveTrack); 11130 tracks.removeEventListener('change', textTracksChanges); 11131 tracks.removeEventListener('addtrack', textTracksChanges); 11132 tracks.removeEventListener('removetrack', textTracksChanges); 11133 11134 for (var i = 0; i < tracks.length; i++) { 11135 var track = tracks[i]; 11136 track.removeEventListener('cuechange', updateDisplay); 11137 } 11138 }); 11139 } 11140 /** 11141 * Create and returns a remote {@link TextTrack} object. 11142 * 11143 * @param {string} kind 11144 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 11145 * 11146 * @param {string} [label] 11147 * Label to identify the text track 11148 * 11149 * @param {string} [language] 11150 * Two letter language abbreviation 11151 * 11152 * @return {TextTrack} 11153 * The TextTrack that gets created. 11154 */ 11155 ; 11156 11157 _proto.addTextTrack = function addTextTrack(kind, label, language) { 11158 if (!kind) { 11159 throw new Error('TextTrack kind is required but was not provided'); 11160 } 11161 11162 return createTrackHelper(this, kind, label, language); 11163 } 11164 /** 11165 * Create an emulated TextTrack for use by addRemoteTextTrack 11166 * 11167 * This is intended to be overridden by classes that inherit from 11168 * Tech in order to create native or custom TextTracks. 11169 * 11170 * @param {Object} options 11171 * The object should contain the options to initialize the TextTrack with. 11172 * 11173 * @param {string} [options.kind] 11174 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 11175 * 11176 * @param {string} [options.label]. 11177 * Label to identify the text track 11178 * 11179 * @param {string} [options.language] 11180 * Two letter language abbreviation. 11181 * 11182 * @return {HTMLTrackElement} 11183 * The track element that gets created. 11184 */ 11185 ; 11186 11187 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 11188 var track = mergeOptions(options, { 11189 tech: this 11190 }); 11191 return new REMOTE.remoteTextEl.TrackClass(track); 11192 } 11193 /** 11194 * Creates a remote text track object and returns an html track element. 11195 * 11196 * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. 11197 * 11198 * @param {Object} options 11199 * See {@link Tech#createRemoteTextTrack} for more detailed properties. 11200 * 11201 * @param {boolean} [manualCleanup=true] 11202 * - When false: the TextTrack will be automatically removed from the video 11203 * element whenever the source changes 11204 * - When True: The TextTrack will have to be cleaned up manually 11205 * 11206 * @return {HTMLTrackElement} 11207 * An Html Track Element. 11208 * 11209 * @deprecated The default functionality for this function will be equivalent 11210 * to "manualCleanup=false" in the future. The manualCleanup parameter will 11211 * also be removed. 11212 */ 11213 ; 11214 11215 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 11216 var _this7 = this; 11217 11218 if (options === void 0) { 11219 options = {}; 11220 } 11221 11222 var htmlTrackElement = this.createRemoteTextTrack(options); 11223 11224 if (manualCleanup !== true && manualCleanup !== false) { 11225 // deprecation warning 11226 log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); 11227 manualCleanup = true; 11228 } // store HTMLTrackElement and TextTrack to remote list 11229 11230 11231 this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); 11232 this.remoteTextTracks().addTrack(htmlTrackElement.track); 11233 11234 if (manualCleanup !== true) { 11235 // create the TextTrackList if it doesn't exist 11236 this.ready(function () { 11237 return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); 11238 }); 11239 } 11240 11241 return htmlTrackElement; 11242 } 11243 /** 11244 * Remove a remote text track from the remote `TextTrackList`. 11245 * 11246 * @param {TextTrack} track 11247 * `TextTrack` to remove from the `TextTrackList` 11248 */ 11249 ; 11250 11251 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 11252 var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list 11253 11254 this.remoteTextTrackEls().removeTrackElement_(trackElement); 11255 this.remoteTextTracks().removeTrack(track); 11256 this.autoRemoteTextTracks_.removeTrack(track); 11257 } 11258 /** 11259 * Gets available media playback quality metrics as specified by the W3C's Media 11260 * Playback Quality API. 11261 * 11262 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 11263 * 11264 * @return {Object} 11265 * An object with supported media playback quality metrics 11266 * 11267 * @abstract 11268 */ 11269 ; 11270 11271 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 11272 return {}; 11273 } 11274 /** 11275 * Attempt to create a floating video window always on top of other windows 11276 * so that users may continue consuming media while they interact with other 11277 * content sites, or applications on their device. 11278 * 11279 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 11280 * 11281 * @return {Promise|undefined} 11282 * A promise with a Picture-in-Picture window if the browser supports 11283 * Promises (or one was passed in as an option). It returns undefined 11284 * otherwise. 11285 * 11286 * @abstract 11287 */ 11288 ; 11289 11290 _proto.requestPictureInPicture = function requestPictureInPicture() { 11291 var PromiseClass = this.options_.Promise || window$3.Promise; 11292 11293 if (PromiseClass) { 11294 return PromiseClass.reject(); 11295 } 11296 } 11297 /** 11298 * A method to set a poster from a `Tech`. 11299 * 11300 * @abstract 11301 */ 11302 ; 11303 11304 _proto.setPoster = function setPoster() {} 11305 /** 11306 * A method to check for the presence of the 'playsinline' <video> attribute. 11307 * 11308 * @abstract 11309 */ 11310 ; 11311 11312 _proto.playsinline = function playsinline() {} 11313 /** 11314 * A method to set or unset the 'playsinline' <video> attribute. 11315 * 11316 * @abstract 11317 */ 11318 ; 11319 11320 _proto.setPlaysinline = function setPlaysinline() {} 11321 /** 11322 * Attempt to force override of native audio tracks. 11323 * 11324 * @param {boolean} override - If set to true native audio will be overridden, 11325 * otherwise native audio will potentially be used. 11326 * 11327 * @abstract 11328 */ 11329 ; 11330 11331 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {} 11332 /** 11333 * Attempt to force override of native video tracks. 11334 * 11335 * @param {boolean} override - If set to true native video will be overridden, 11336 * otherwise native video will potentially be used. 11337 * 11338 * @abstract 11339 */ 11340 ; 11341 11342 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {} 11343 /* 11344 * Check if the tech can support the given mime-type. 11345 * 11346 * The base tech does not support any type, but source handlers might 11347 * overwrite this. 11348 * 11349 * @param {string} type 11350 * The mimetype to check for support 11351 * 11352 * @return {string} 11353 * 'probably', 'maybe', or empty string 11354 * 11355 * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType} 11356 * 11357 * @abstract 11358 */ 11359 ; 11360 11361 _proto.canPlayType = function canPlayType() { 11362 return ''; 11363 } 11364 /** 11365 * Check if the type is supported by this tech. 11366 * 11367 * The base tech does not support any type, but source handlers might 11368 * overwrite this. 11369 * 11370 * @param {string} type 11371 * The media type to check 11372 * @return {string} Returns the native video element's response 11373 */ 11374 ; 11375 11376 Tech.canPlayType = function canPlayType() { 11377 return ''; 11378 } 11379 /** 11380 * Check if the tech can support the given source 11381 * 11382 * @param {Object} srcObj 11383 * The source object 11384 * @param {Object} options 11385 * The options passed to the tech 11386 * @return {string} 'probably', 'maybe', or '' (empty string) 11387 */ 11388 ; 11389 11390 Tech.canPlaySource = function canPlaySource(srcObj, options) { 11391 return Tech.canPlayType(srcObj.type); 11392 } 11393 /* 11394 * Return whether the argument is a Tech or not. 11395 * Can be passed either a Class like `Html5` or a instance like `player.tech_` 11396 * 11397 * @param {Object} component 11398 * The item to check 11399 * 11400 * @return {boolean} 11401 * Whether it is a tech or not 11402 * - True if it is a tech 11403 * - False if it is not 11404 */ 11405 ; 11406 11407 Tech.isTech = function isTech(component) { 11408 return component.prototype instanceof Tech || component instanceof Tech || component === Tech; 11409 } 11410 /** 11411 * Registers a `Tech` into a shared list for videojs. 11412 * 11413 * @param {string} name 11414 * Name of the `Tech` to register. 11415 * 11416 * @param {Object} tech 11417 * The `Tech` class to register. 11418 */ 11419 ; 11420 11421 Tech.registerTech = function registerTech(name, tech) { 11422 if (!Tech.techs_) { 11423 Tech.techs_ = {}; 11424 } 11425 11426 if (!Tech.isTech(tech)) { 11427 throw new Error("Tech " + name + " must be a Tech"); 11428 } 11429 11430 if (!Tech.canPlayType) { 11431 throw new Error('Techs must have a static canPlayType method on them'); 11432 } 11433 11434 if (!Tech.canPlaySource) { 11435 throw new Error('Techs must have a static canPlaySource method on them'); 11436 } 11437 11438 name = toTitleCase(name); 11439 Tech.techs_[name] = tech; 11440 Tech.techs_[toLowerCase(name)] = tech; 11441 11442 if (name !== 'Tech') { 11443 // camel case the techName for use in techOrder 11444 Tech.defaultTechOrder_.push(name); 11445 } 11446 11447 return tech; 11448 } 11449 /** 11450 * Get a `Tech` from the shared list by name. 11451 * 11452 * @param {string} name 11453 * `camelCase` or `TitleCase` name of the Tech to get 11454 * 11455 * @return {Tech|undefined} 11456 * The `Tech` or undefined if there was no tech with the name requested. 11457 */ 11458 ; 11459 11460 Tech.getTech = function getTech(name) { 11461 if (!name) { 11462 return; 11463 } 11464 11465 if (Tech.techs_ && Tech.techs_[name]) { 11466 return Tech.techs_[name]; 11467 } 11468 11469 name = toTitleCase(name); 11470 11471 if (window$3 && window$3.videojs && window$3.videojs[name]) { 11472 log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)"); 11473 return window$3.videojs[name]; 11474 } 11475 }; 11476 11477 return Tech; 11478 }(Component); 11479 /** 11480 * Get the {@link VideoTrackList} 11481 * 11482 * @returns {VideoTrackList} 11483 * @method Tech.prototype.videoTracks 11484 */ 11485 11486 /** 11487 * Get the {@link AudioTrackList} 11488 * 11489 * @returns {AudioTrackList} 11490 * @method Tech.prototype.audioTracks 11491 */ 11492 11493 /** 11494 * Get the {@link TextTrackList} 11495 * 11496 * @returns {TextTrackList} 11497 * @method Tech.prototype.textTracks 11498 */ 11499 11500 /** 11501 * Get the remote element {@link TextTrackList} 11502 * 11503 * @returns {TextTrackList} 11504 * @method Tech.prototype.remoteTextTracks 11505 */ 11506 11507 /** 11508 * Get the remote element {@link HtmlTrackElementList} 11509 * 11510 * @returns {HtmlTrackElementList} 11511 * @method Tech.prototype.remoteTextTrackEls 11512 */ 11513 11514 11515 ALL.names.forEach(function (name) { 11516 var props = ALL[name]; 11517 11518 Tech.prototype[props.getterName] = function () { 11519 this[props.privateName] = this[props.privateName] || new props.ListClass(); 11520 return this[props.privateName]; 11521 }; 11522 }); 11523 /** 11524 * List of associated text tracks 11525 * 11526 * @type {TextTrackList} 11527 * @private 11528 * @property Tech#textTracks_ 11529 */ 11530 11531 /** 11532 * List of associated audio tracks. 11533 * 11534 * @type {AudioTrackList} 11535 * @private 11536 * @property Tech#audioTracks_ 11537 */ 11538 11539 /** 11540 * List of associated video tracks. 11541 * 11542 * @type {VideoTrackList} 11543 * @private 11544 * @property Tech#videoTracks_ 11545 */ 11546 11547 /** 11548 * Boolean indicating whether the `Tech` supports volume control. 11549 * 11550 * @type {boolean} 11551 * @default 11552 */ 11553 11554 Tech.prototype.featuresVolumeControl = true; 11555 /** 11556 * Boolean indicating whether the `Tech` supports muting volume. 11557 * 11558 * @type {bolean} 11559 * @default 11560 */ 11561 11562 Tech.prototype.featuresMuteControl = true; 11563 /** 11564 * Boolean indicating whether the `Tech` supports fullscreen resize control. 11565 * Resizing plugins using request fullscreen reloads the plugin 11566 * 11567 * @type {boolean} 11568 * @default 11569 */ 11570 11571 Tech.prototype.featuresFullscreenResize = false; 11572 /** 11573 * Boolean indicating whether the `Tech` supports changing the speed at which the video 11574 * plays. Examples: 11575 * - Set player to play 2x (twice) as fast 11576 * - Set player to play 0.5x (half) as fast 11577 * 11578 * @type {boolean} 11579 * @default 11580 */ 11581 11582 Tech.prototype.featuresPlaybackRate = false; 11583 /** 11584 * Boolean indicating whether the `Tech` supports the `progress` event. This is currently 11585 * not triggered by video-js-swf. This will be used to determine if 11586 * {@link Tech#manualProgressOn} should be called. 11587 * 11588 * @type {boolean} 11589 * @default 11590 */ 11591 11592 Tech.prototype.featuresProgressEvents = false; 11593 /** 11594 * Boolean indicating whether the `Tech` supports the `sourceset` event. 11595 * 11596 * A tech should set this to `true` and then use {@link Tech#triggerSourceset} 11597 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting 11598 * a new source. 11599 * 11600 * @type {boolean} 11601 * @default 11602 */ 11603 11604 Tech.prototype.featuresSourceset = false; 11605 /** 11606 * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently 11607 * not triggered by video-js-swf. This will be used to determine if 11608 * {@link Tech#manualTimeUpdates} should be called. 11609 * 11610 * @type {boolean} 11611 * @default 11612 */ 11613 11614 Tech.prototype.featuresTimeupdateEvents = false; 11615 /** 11616 * Boolean indicating whether the `Tech` supports the native `TextTrack`s. 11617 * This will help us integrate with native `TextTrack`s if the browser supports them. 11618 * 11619 * @type {boolean} 11620 * @default 11621 */ 11622 11623 Tech.prototype.featuresNativeTextTracks = false; 11624 /** 11625 * A functional mixin for techs that want to use the Source Handler pattern. 11626 * Source handlers are scripts for handling specific formats. 11627 * The source handler pattern is used for adaptive formats (HLS, DASH) that 11628 * manually load video data and feed it into a Source Buffer (Media Source Extensions) 11629 * Example: `Tech.withSourceHandlers.call(MyTech);` 11630 * 11631 * @param {Tech} _Tech 11632 * The tech to add source handler functions to. 11633 * 11634 * @mixes Tech~SourceHandlerAdditions 11635 */ 11636 11637 Tech.withSourceHandlers = function (_Tech) { 11638 /** 11639 * Register a source handler 11640 * 11641 * @param {Function} handler 11642 * The source handler class 11643 * 11644 * @param {number} [index] 11645 * Register it at the following index 11646 */ 11647 _Tech.registerSourceHandler = function (handler, index) { 11648 var handlers = _Tech.sourceHandlers; 11649 11650 if (!handlers) { 11651 handlers = _Tech.sourceHandlers = []; 11652 } 11653 11654 if (index === undefined) { 11655 // add to the end of the list 11656 index = handlers.length; 11657 } 11658 11659 handlers.splice(index, 0, handler); 11660 }; 11661 /** 11662 * Check if the tech can support the given type. Also checks the 11663 * Techs sourceHandlers. 11664 * 11665 * @param {string} type 11666 * The mimetype to check. 11667 * 11668 * @return {string} 11669 * 'probably', 'maybe', or '' (empty string) 11670 */ 11671 11672 11673 _Tech.canPlayType = function (type) { 11674 var handlers = _Tech.sourceHandlers || []; 11675 var can; 11676 11677 for (var i = 0; i < handlers.length; i++) { 11678 can = handlers[i].canPlayType(type); 11679 11680 if (can) { 11681 return can; 11682 } 11683 } 11684 11685 return ''; 11686 }; 11687 /** 11688 * Returns the first source handler that supports the source. 11689 * 11690 * TODO: Answer question: should 'probably' be prioritized over 'maybe' 11691 * 11692 * @param {Tech~SourceObject} source 11693 * The source object 11694 * 11695 * @param {Object} options 11696 * The options passed to the tech 11697 * 11698 * @return {SourceHandler|null} 11699 * The first source handler that supports the source or null if 11700 * no SourceHandler supports the source 11701 */ 11702 11703 11704 _Tech.selectSourceHandler = function (source, options) { 11705 var handlers = _Tech.sourceHandlers || []; 11706 var can; 11707 11708 for (var i = 0; i < handlers.length; i++) { 11709 can = handlers[i].canHandleSource(source, options); 11710 11711 if (can) { 11712 return handlers[i]; 11713 } 11714 } 11715 11716 return null; 11717 }; 11718 /** 11719 * Check if the tech can support the given source. 11720 * 11721 * @param {Tech~SourceObject} srcObj 11722 * The source object 11723 * 11724 * @param {Object} options 11725 * The options passed to the tech 11726 * 11727 * @return {string} 11728 * 'probably', 'maybe', or '' (empty string) 11729 */ 11730 11731 11732 _Tech.canPlaySource = function (srcObj, options) { 11733 var sh = _Tech.selectSourceHandler(srcObj, options); 11734 11735 if (sh) { 11736 return sh.canHandleSource(srcObj, options); 11737 } 11738 11739 return ''; 11740 }; 11741 /** 11742 * When using a source handler, prefer its implementation of 11743 * any function normally provided by the tech. 11744 */ 11745 11746 11747 var deferrable = ['seekable', 'seeking', 'duration']; 11748 /** 11749 * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable 11750 * function if it exists, with a fallback to the Techs seekable function. 11751 * 11752 * @method _Tech.seekable 11753 */ 11754 11755 /** 11756 * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration 11757 * function if it exists, otherwise it will fallback to the techs duration function. 11758 * 11759 * @method _Tech.duration 11760 */ 11761 11762 deferrable.forEach(function (fnName) { 11763 var originalFn = this[fnName]; 11764 11765 if (typeof originalFn !== 'function') { 11766 return; 11767 } 11768 11769 this[fnName] = function () { 11770 if (this.sourceHandler_ && this.sourceHandler_[fnName]) { 11771 return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); 11772 } 11773 11774 return originalFn.apply(this, arguments); 11775 }; 11776 }, _Tech.prototype); 11777 /** 11778 * Create a function for setting the source using a source object 11779 * and source handlers. 11780 * Should never be called unless a source handler was found. 11781 * 11782 * @param {Tech~SourceObject} source 11783 * A source object with src and type keys 11784 */ 11785 11786 _Tech.prototype.setSource = function (source) { 11787 var sh = _Tech.selectSourceHandler(source, this.options_); 11788 11789 if (!sh) { 11790 // Fall back to a native source hander when unsupported sources are 11791 // deliberately set 11792 if (_Tech.nativeSourceHandler) { 11793 sh = _Tech.nativeSourceHandler; 11794 } else { 11795 log.error('No source handler found for the current source.'); 11796 } 11797 } // Dispose any existing source handler 11798 11799 11800 this.disposeSourceHandler(); 11801 this.off('dispose', this.disposeSourceHandler); 11802 11803 if (sh !== _Tech.nativeSourceHandler) { 11804 this.currentSource_ = source; 11805 } 11806 11807 this.sourceHandler_ = sh.handleSource(source, this, this.options_); 11808 this.one('dispose', this.disposeSourceHandler); 11809 }; 11810 /** 11811 * Clean up any existing SourceHandlers and listeners when the Tech is disposed. 11812 * 11813 * @listens Tech#dispose 11814 */ 11815 11816 11817 _Tech.prototype.disposeSourceHandler = function () { 11818 // if we have a source and get another one 11819 // then we are loading something new 11820 // than clear all of our current tracks 11821 if (this.currentSource_) { 11822 this.clearTracks(['audio', 'video']); 11823 this.currentSource_ = null; 11824 } // always clean up auto-text tracks 11825 11826 11827 this.cleanupAutoTextTracks(); 11828 11829 if (this.sourceHandler_) { 11830 if (this.sourceHandler_.dispose) { 11831 this.sourceHandler_.dispose(); 11832 } 11833 11834 this.sourceHandler_ = null; 11835 } 11836 }; 11837 }; // The base Tech class needs to be registered as a Component. It is the only 11838 // Tech that can be registered as a Component. 11839 11840 11841 Component.registerComponent('Tech', Tech); 11842 Tech.registerTech('Tech', Tech); 11843 /** 11844 * A list of techs that should be added to techOrder on Players 11845 * 11846 * @private 11847 */ 11848 11849 Tech.defaultTechOrder_ = []; 11850 11851 /** 11852 * @file middleware.js 11853 * @module middleware 11854 */ 11855 var middlewares = {}; 11856 var middlewareInstances = {}; 11857 var TERMINATOR = {}; 11858 /** 11859 * A middleware object is a plain JavaScript object that has methods that 11860 * match the {@link Tech} methods found in the lists of allowed 11861 * {@link module:middleware.allowedGetters|getters}, 11862 * {@link module:middleware.allowedSetters|setters}, and 11863 * {@link module:middleware.allowedMediators|mediators}. 11864 * 11865 * @typedef {Object} MiddlewareObject 11866 */ 11867 11868 /** 11869 * A middleware factory function that should return a 11870 * {@link module:middleware~MiddlewareObject|MiddlewareObject}. 11871 * 11872 * This factory will be called for each player when needed, with the player 11873 * passed in as an argument. 11874 * 11875 * @callback MiddlewareFactory 11876 * @param {Player} player 11877 * A Video.js player. 11878 */ 11879 11880 /** 11881 * Define a middleware that the player should use by way of a factory function 11882 * that returns a middleware object. 11883 * 11884 * @param {string} type 11885 * The MIME type to match or `"*"` for all MIME types. 11886 * 11887 * @param {MiddlewareFactory} middleware 11888 * A middleware factory function that will be executed for 11889 * matching types. 11890 */ 11891 11892 function use(type, middleware) { 11893 middlewares[type] = middlewares[type] || []; 11894 middlewares[type].push(middleware); 11895 } 11896 /** 11897 * Asynchronously sets a source using middleware by recursing through any 11898 * matching middlewares and calling `setSource` on each, passing along the 11899 * previous returned value each time. 11900 * 11901 * @param {Player} player 11902 * A {@link Player} instance. 11903 * 11904 * @param {Tech~SourceObject} src 11905 * A source object. 11906 * 11907 * @param {Function} 11908 * The next middleware to run. 11909 */ 11910 11911 function setSource(player, src, next) { 11912 player.setTimeout(function () { 11913 return setSourceHelper(src, middlewares[src.type], next, player); 11914 }, 1); 11915 } 11916 /** 11917 * When the tech is set, passes the tech to each middleware's `setTech` method. 11918 * 11919 * @param {Object[]} middleware 11920 * An array of middleware instances. 11921 * 11922 * @param {Tech} tech 11923 * A Video.js tech. 11924 */ 11925 11926 function setTech(middleware, tech) { 11927 middleware.forEach(function (mw) { 11928 return mw.setTech && mw.setTech(tech); 11929 }); 11930 } 11931 /** 11932 * Calls a getter on the tech first, through each middleware 11933 * from right to left to the player. 11934 * 11935 * @param {Object[]} middleware 11936 * An array of middleware instances. 11937 * 11938 * @param {Tech} tech 11939 * The current tech. 11940 * 11941 * @param {string} method 11942 * A method name. 11943 * 11944 * @return {Mixed} 11945 * The final value from the tech after middleware has intercepted it. 11946 */ 11947 11948 function get(middleware, tech, method) { 11949 return middleware.reduceRight(middlewareIterator(method), tech[method]()); 11950 } 11951 /** 11952 * Takes the argument given to the player and calls the setter method on each 11953 * middleware from left to right to the tech. 11954 * 11955 * @param {Object[]} middleware 11956 * An array of middleware instances. 11957 * 11958 * @param {Tech} tech 11959 * The current tech. 11960 * 11961 * @param {string} method 11962 * A method name. 11963 * 11964 * @param {Mixed} arg 11965 * The value to set on the tech. 11966 * 11967 * @return {Mixed} 11968 * The return value of the `method` of the `tech`. 11969 */ 11970 11971 function set(middleware, tech, method, arg) { 11972 return tech[method](middleware.reduce(middlewareIterator(method), arg)); 11973 } 11974 /** 11975 * Takes the argument given to the player and calls the `call` version of the 11976 * method on each middleware from left to right. 11977 * 11978 * Then, call the passed in method on the tech and return the result unchanged 11979 * back to the player, through middleware, this time from right to left. 11980 * 11981 * @param {Object[]} middleware 11982 * An array of middleware instances. 11983 * 11984 * @param {Tech} tech 11985 * The current tech. 11986 * 11987 * @param {string} method 11988 * A method name. 11989 * 11990 * @param {Mixed} arg 11991 * The value to set on the tech. 11992 * 11993 * @return {Mixed} 11994 * The return value of the `method` of the `tech`, regardless of the 11995 * return values of middlewares. 11996 */ 11997 11998 function mediate(middleware, tech, method, arg) { 11999 if (arg === void 0) { 12000 arg = null; 12001 } 12002 12003 var callMethod = 'call' + toTitleCase(method); 12004 var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg); 12005 var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to 12006 // prevent confusion if a techs method actually returns null. 12007 12008 var returnValue = terminated ? null : tech[method](middlewareValue); 12009 executeRight(middleware, method, returnValue, terminated); 12010 return returnValue; 12011 } 12012 /** 12013 * Enumeration of allowed getters where the keys are method names. 12014 * 12015 * @type {Object} 12016 */ 12017 12018 var allowedGetters = { 12019 buffered: 1, 12020 currentTime: 1, 12021 duration: 1, 12022 muted: 1, 12023 played: 1, 12024 paused: 1, 12025 seekable: 1, 12026 volume: 1 12027 }; 12028 /** 12029 * Enumeration of allowed setters where the keys are method names. 12030 * 12031 * @type {Object} 12032 */ 12033 12034 var allowedSetters = { 12035 setCurrentTime: 1, 12036 setMuted: 1, 12037 setVolume: 1 12038 }; 12039 /** 12040 * Enumeration of allowed mediators where the keys are method names. 12041 * 12042 * @type {Object} 12043 */ 12044 12045 var allowedMediators = { 12046 play: 1, 12047 pause: 1 12048 }; 12049 12050 function middlewareIterator(method) { 12051 return function (value, mw) { 12052 // if the previous middleware terminated, pass along the termination 12053 if (value === TERMINATOR) { 12054 return TERMINATOR; 12055 } 12056 12057 if (mw[method]) { 12058 return mw[method](value); 12059 } 12060 12061 return value; 12062 }; 12063 } 12064 12065 function executeRight(mws, method, value, terminated) { 12066 for (var i = mws.length - 1; i >= 0; i--) { 12067 var mw = mws[i]; 12068 12069 if (mw[method]) { 12070 mw[method](terminated, value); 12071 } 12072 } 12073 } 12074 /** 12075 * Clear the middleware cache for a player. 12076 * 12077 * @param {Player} player 12078 * A {@link Player} instance. 12079 */ 12080 12081 12082 function clearCacheForPlayer(player) { 12083 middlewareInstances[player.id()] = null; 12084 } 12085 /** 12086 * { 12087 * [playerId]: [[mwFactory, mwInstance], ...] 12088 * } 12089 * 12090 * @private 12091 */ 12092 12093 function getOrCreateFactory(player, mwFactory) { 12094 var mws = middlewareInstances[player.id()]; 12095 var mw = null; 12096 12097 if (mws === undefined || mws === null) { 12098 mw = mwFactory(player); 12099 middlewareInstances[player.id()] = [[mwFactory, mw]]; 12100 return mw; 12101 } 12102 12103 for (var i = 0; i < mws.length; i++) { 12104 var _mws$i = mws[i], 12105 mwf = _mws$i[0], 12106 mwi = _mws$i[1]; 12107 12108 if (mwf !== mwFactory) { 12109 continue; 12110 } 12111 12112 mw = mwi; 12113 } 12114 12115 if (mw === null) { 12116 mw = mwFactory(player); 12117 mws.push([mwFactory, mw]); 12118 } 12119 12120 return mw; 12121 } 12122 12123 function setSourceHelper(src, middleware, next, player, acc, lastRun) { 12124 if (src === void 0) { 12125 src = {}; 12126 } 12127 12128 if (middleware === void 0) { 12129 middleware = []; 12130 } 12131 12132 if (acc === void 0) { 12133 acc = []; 12134 } 12135 12136 if (lastRun === void 0) { 12137 lastRun = false; 12138 } 12139 12140 var _middleware = middleware, 12141 mwFactory = _middleware[0], 12142 mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road 12143 12144 12145 if (typeof mwFactory === 'string') { 12146 setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw, 12147 // then call the mw's setSource method 12148 } else if (mwFactory) { 12149 var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware 12150 12151 if (!mw.setSource) { 12152 acc.push(mw); 12153 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12154 } 12155 12156 mw.setSource(assign({}, src), function (err, _src) { 12157 // something happened, try the next middleware on the current level 12158 // make sure to use the old src 12159 if (err) { 12160 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12161 } // we've succeeded, now we need to go deeper 12162 12163 12164 acc.push(mw); // if it's the same type, continue down the current chain 12165 // otherwise, we want to go down the new chain 12166 12167 setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); 12168 }); 12169 } else if (mwrest.length) { 12170 setSourceHelper(src, mwrest, next, player, acc, lastRun); 12171 } else if (lastRun) { 12172 next(src, acc); 12173 } else { 12174 setSourceHelper(src, middlewares['*'], next, player, acc, true); 12175 } 12176 } 12177 12178 /** 12179 * Mimetypes 12180 * 12181 * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm 12182 * @typedef Mimetypes~Kind 12183 * @enum 12184 */ 12185 12186 var MimetypesKind = { 12187 opus: 'video/ogg', 12188 ogv: 'video/ogg', 12189 mp4: 'video/mp4', 12190 mov: 'video/mp4', 12191 m4v: 'video/mp4', 12192 mkv: 'video/x-matroska', 12193 m4a: 'audio/mp4', 12194 mp3: 'audio/mpeg', 12195 aac: 'audio/aac', 12196 oga: 'audio/ogg', 12197 m3u8: 'application/x-mpegURL', 12198 jpg: 'image/jpeg', 12199 jpeg: 'image/jpeg', 12200 gif: 'image/gif', 12201 png: 'image/png', 12202 svg: 'image/svg+xml', 12203 webp: 'image/webp' 12204 }; 12205 /** 12206 * Get the mimetype of a given src url if possible 12207 * 12208 * @param {string} src 12209 * The url to the src 12210 * 12211 * @return {string} 12212 * return the mimetype if it was known or empty string otherwise 12213 */ 12214 12215 var getMimetype = function getMimetype(src) { 12216 if (src === void 0) { 12217 src = ''; 12218 } 12219 12220 var ext = getFileExtension(src); 12221 var mimetype = MimetypesKind[ext.toLowerCase()]; 12222 return mimetype || ''; 12223 }; 12224 /** 12225 * Find the mime type of a given source string if possible. Uses the player 12226 * source cache. 12227 * 12228 * @param {Player} player 12229 * The player object 12230 * 12231 * @param {string} src 12232 * The source string 12233 * 12234 * @return {string} 12235 * The type that was found 12236 */ 12237 12238 var findMimetype = function findMimetype(player, src) { 12239 if (!src) { 12240 return ''; 12241 } // 1. check for the type in the `source` cache 12242 12243 12244 if (player.cache_.source.src === src && player.cache_.source.type) { 12245 return player.cache_.source.type; 12246 } // 2. see if we have this source in our `currentSources` cache 12247 12248 12249 var matchingSources = player.cache_.sources.filter(function (s) { 12250 return s.src === src; 12251 }); 12252 12253 if (matchingSources.length) { 12254 return matchingSources[0].type; 12255 } // 3. look for the src url in source elements and use the type there 12256 12257 12258 var sources = player.$$('source'); 12259 12260 for (var i = 0; i < sources.length; i++) { 12261 var s = sources[i]; 12262 12263 if (s.type && s.src && s.src === src) { 12264 return s.type; 12265 } 12266 } // 4. finally fallback to our list of mime types based on src url extension 12267 12268 12269 return getMimetype(src); 12270 }; 12271 12272 /** 12273 * @module filter-source 12274 */ 12275 /** 12276 * Filter out single bad source objects or multiple source objects in an 12277 * array. Also flattens nested source object arrays into a 1 dimensional 12278 * array of source objects. 12279 * 12280 * @param {Tech~SourceObject|Tech~SourceObject[]} src 12281 * The src object to filter 12282 * 12283 * @return {Tech~SourceObject[]} 12284 * An array of sourceobjects containing only valid sources 12285 * 12286 * @private 12287 */ 12288 12289 var filterSource = function filterSource(src) { 12290 // traverse array 12291 if (Array.isArray(src)) { 12292 var newsrc = []; 12293 src.forEach(function (srcobj) { 12294 srcobj = filterSource(srcobj); 12295 12296 if (Array.isArray(srcobj)) { 12297 newsrc = newsrc.concat(srcobj); 12298 } else if (isObject(srcobj)) { 12299 newsrc.push(srcobj); 12300 } 12301 }); 12302 src = newsrc; 12303 } else if (typeof src === 'string' && src.trim()) { 12304 // convert string into object 12305 src = [fixSource({ 12306 src: src 12307 })]; 12308 } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { 12309 // src is already valid 12310 src = [fixSource(src)]; 12311 } else { 12312 // invalid source, turn it into an empty array 12313 src = []; 12314 } 12315 12316 return src; 12317 }; 12318 /** 12319 * Checks src mimetype, adding it when possible 12320 * 12321 * @param {Tech~SourceObject} src 12322 * The src object to check 12323 * @return {Tech~SourceObject} 12324 * src Object with known type 12325 */ 12326 12327 12328 function fixSource(src) { 12329 if (!src.type) { 12330 var mimetype = getMimetype(src.src); 12331 12332 if (mimetype) { 12333 src.type = mimetype; 12334 } 12335 } 12336 12337 return src; 12338 } 12339 12340 /** 12341 * The `MediaLoader` is the `Component` that decides which playback technology to load 12342 * when a player is initialized. 12343 * 12344 * @extends Component 12345 */ 12346 12347 var MediaLoader = /*#__PURE__*/function (_Component) { 12348 inheritsLoose(MediaLoader, _Component); 12349 12350 /** 12351 * Create an instance of this class. 12352 * 12353 * @param {Player} player 12354 * The `Player` that this class should attach to. 12355 * 12356 * @param {Object} [options] 12357 * The key/value store of player options. 12358 * 12359 * @param {Component~ReadyCallback} [ready] 12360 * The function that is run when this component is ready. 12361 */ 12362 function MediaLoader(player, options, ready) { 12363 var _this; 12364 12365 // MediaLoader has no element 12366 var options_ = mergeOptions({ 12367 createEl: false 12368 }, options); 12369 _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized, 12370 // load the first supported playback technology. 12371 12372 if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { 12373 for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { 12374 var techName = toTitleCase(j[i]); 12375 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 12376 // Remove once that deprecated behavior is removed. 12377 12378 if (!techName) { 12379 tech = Component.getComponent(techName); 12380 } // Check if the browser supports this technology 12381 12382 12383 if (tech && tech.isSupported()) { 12384 player.loadTech_(techName); 12385 break; 12386 } 12387 } 12388 } else { 12389 // Loop through playback technologies (HTML5, Flash) and check for support. 12390 // Then load the best source. 12391 // A few assumptions here: 12392 // All playback technologies respect preload false. 12393 player.src(options.playerOptions.sources); 12394 } 12395 12396 return _this; 12397 } 12398 12399 return MediaLoader; 12400 }(Component); 12401 12402 Component.registerComponent('MediaLoader', MediaLoader); 12403 12404 /** 12405 * Component which is clickable or keyboard actionable, but is not a 12406 * native HTML button. 12407 * 12408 * @extends Component 12409 */ 12410 12411 var ClickableComponent = /*#__PURE__*/function (_Component) { 12412 inheritsLoose(ClickableComponent, _Component); 12413 12414 /** 12415 * Creates an instance of this class. 12416 * 12417 * @param {Player} player 12418 * The `Player` that this class should be attached to. 12419 * 12420 * @param {Object} [options] 12421 * The key/value store of player options. 12422 * 12423 * @param {function} [options.clickHandler] 12424 * The function to call when the button is clicked / activated 12425 */ 12426 function ClickableComponent(player, options) { 12427 var _this; 12428 12429 _this = _Component.call(this, player, options) || this; 12430 12431 _this.emitTapEvents(); 12432 12433 _this.enable(); 12434 12435 return _this; 12436 } 12437 /** 12438 * Create the `ClickableComponent`s DOM element. 12439 * 12440 * @param {string} [tag=div] 12441 * The element's node type. 12442 * 12443 * @param {Object} [props={}] 12444 * An object of properties that should be set on the element. 12445 * 12446 * @param {Object} [attributes={}] 12447 * An object of attributes that should be set on the element. 12448 * 12449 * @return {Element} 12450 * The element that gets created. 12451 */ 12452 12453 12454 var _proto = ClickableComponent.prototype; 12455 12456 _proto.createEl = function createEl(tag, props, attributes) { 12457 if (tag === void 0) { 12458 tag = 'div'; 12459 } 12460 12461 if (props === void 0) { 12462 props = {}; 12463 } 12464 12465 if (attributes === void 0) { 12466 attributes = {}; 12467 } 12468 12469 props = assign({ 12470 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 12471 className: this.buildCSSClass(), 12472 tabIndex: 0 12473 }, props); 12474 12475 if (tag === 'button') { 12476 log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead."); 12477 } // Add ARIA attributes for clickable element which is not a native HTML button 12478 12479 12480 attributes = assign({ 12481 role: 'button' 12482 }, attributes); 12483 this.tabIndex_ = props.tabIndex; 12484 12485 var el = _Component.prototype.createEl.call(this, tag, props, attributes); 12486 12487 this.createControlTextEl(el); 12488 return el; 12489 }; 12490 12491 _proto.dispose = function dispose() { 12492 // remove controlTextEl_ on dispose 12493 this.controlTextEl_ = null; 12494 12495 _Component.prototype.dispose.call(this); 12496 } 12497 /** 12498 * Create a control text element on this `ClickableComponent` 12499 * 12500 * @param {Element} [el] 12501 * Parent element for the control text. 12502 * 12503 * @return {Element} 12504 * The control text element that gets created. 12505 */ 12506 ; 12507 12508 _proto.createControlTextEl = function createControlTextEl(el) { 12509 this.controlTextEl_ = createEl('span', { 12510 className: 'vjs-control-text' 12511 }, { 12512 // let the screen reader user know that the text of the element may change 12513 'aria-live': 'polite' 12514 }); 12515 12516 if (el) { 12517 el.appendChild(this.controlTextEl_); 12518 } 12519 12520 this.controlText(this.controlText_, el); 12521 return this.controlTextEl_; 12522 } 12523 /** 12524 * Get or set the localize text to use for the controls on the `ClickableComponent`. 12525 * 12526 * @param {string} [text] 12527 * Control text for element. 12528 * 12529 * @param {Element} [el=this.el()] 12530 * Element to set the title on. 12531 * 12532 * @return {string} 12533 * - The control text when getting 12534 */ 12535 ; 12536 12537 _proto.controlText = function controlText(text, el) { 12538 if (el === void 0) { 12539 el = this.el(); 12540 } 12541 12542 if (text === undefined) { 12543 return this.controlText_ || 'Need Text'; 12544 } 12545 12546 var localizedText = this.localize(text); 12547 this.controlText_ = text; 12548 textContent(this.controlTextEl_, localizedText); 12549 12550 if (!this.nonIconControl) { 12551 // Set title attribute if only an icon is shown 12552 el.setAttribute('title', localizedText); 12553 } 12554 } 12555 /** 12556 * Builds the default DOM `className`. 12557 * 12558 * @return {string} 12559 * The DOM `className` for this object. 12560 */ 12561 ; 12562 12563 _proto.buildCSSClass = function buildCSSClass() { 12564 return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this); 12565 } 12566 /** 12567 * Enable this `ClickableComponent` 12568 */ 12569 ; 12570 12571 _proto.enable = function enable() { 12572 if (!this.enabled_) { 12573 this.enabled_ = true; 12574 this.removeClass('vjs-disabled'); 12575 this.el_.setAttribute('aria-disabled', 'false'); 12576 12577 if (typeof this.tabIndex_ !== 'undefined') { 12578 this.el_.setAttribute('tabIndex', this.tabIndex_); 12579 } 12580 12581 this.on(['tap', 'click'], this.handleClick); 12582 this.on('keydown', this.handleKeyDown); 12583 } 12584 } 12585 /** 12586 * Disable this `ClickableComponent` 12587 */ 12588 ; 12589 12590 _proto.disable = function disable() { 12591 this.enabled_ = false; 12592 this.addClass('vjs-disabled'); 12593 this.el_.setAttribute('aria-disabled', 'true'); 12594 12595 if (typeof this.tabIndex_ !== 'undefined') { 12596 this.el_.removeAttribute('tabIndex'); 12597 } 12598 12599 this.off('mouseover', this.handleMouseOver); 12600 this.off('mouseout', this.handleMouseOut); 12601 this.off(['tap', 'click'], this.handleClick); 12602 this.off('keydown', this.handleKeyDown); 12603 } 12604 /** 12605 * Event handler that is called when a `ClickableComponent` receives a 12606 * `click` or `tap` event. 12607 * 12608 * @param {EventTarget~Event} event 12609 * The `tap` or `click` event that caused this function to be called. 12610 * 12611 * @listens tap 12612 * @listens click 12613 * @abstract 12614 */ 12615 ; 12616 12617 _proto.handleClick = function handleClick(event) { 12618 if (this.options_.clickHandler) { 12619 this.options_.clickHandler.call(this, arguments); 12620 } 12621 } 12622 /** 12623 * Event handler that is called when a `ClickableComponent` receives a 12624 * `keydown` event. 12625 * 12626 * By default, if the key is Space or Enter, it will trigger a `click` event. 12627 * 12628 * @param {EventTarget~Event} event
12629 * The `keydown` event that caused this function to be called. 12630 * 12631 * @listens keydown 12632 */ 12633 ; 12634 12635 _proto.handleKeyDown = function handleKeyDown(event) { 12636 // Support Space or Enter key operation to fire a click event. Also, 12637 // prevent the event from propagating through the DOM and triggering 12638 // Player hotkeys. 12639 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 12640 event.preventDefault(); 12641 event.stopPropagation(); 12642 this.trigger('click'); 12643 } else { 12644 // Pass keypress handling up for unsupported keys 12645 _Component.prototype.handleKeyDown.call(this, event); 12646 } 12647 }; 12648 12649 return ClickableComponent; 12650 }(Component); 12651 12652 Component.registerComponent('ClickableComponent', ClickableComponent); 12653 12654 /** 12655 * A `ClickableComponent` that handles showing the poster image for the player. 12656 * 12657 * @extends ClickableComponent 12658 */ 12659 12660 var PosterImage = /*#__PURE__*/function (_ClickableComponent) { 12661 inheritsLoose(PosterImage, _ClickableComponent); 12662 12663 /** 12664 * Create an instance of this class. 12665 * 12666 * @param {Player} player 12667 * The `Player` that this class should attach to. 12668 * 12669 * @param {Object} [options] 12670 * The key/value store of player options. 12671 */ 12672 function PosterImage(player, options) { 12673 var _this; 12674 12675 _this = _ClickableComponent.call(this, player, options) || this; 12676 12677 _this.update(); 12678 12679 player.on('posterchange', bind(assertThisInitialized(_this), _this.update)); 12680 return _this; 12681 } 12682 /** 12683 * Clean up and dispose of the `PosterImage`. 12684 */ 12685 12686 12687 var _proto = PosterImage.prototype; 12688 12689 _proto.dispose = function dispose() { 12690 this.player().off('posterchange', this.update); 12691 12692 _ClickableComponent.prototype.dispose.call(this); 12693 } 12694 /** 12695 * Create the `PosterImage`s DOM element. 12696 * 12697 * @return {Element} 12698 * The element that gets created. 12699 */ 12700 ; 12701 12702 _proto.createEl = function createEl$1() { 12703 var el = createEl('div', { 12704 className: 'vjs-poster', 12705 // Don't want poster to be tabbable. 12706 tabIndex: -1 12707 }); 12708 return el; 12709 } 12710 /** 12711 * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. 12712 * 12713 * @listens Player#posterchange 12714 * 12715 * @param {EventTarget~Event} [event] 12716 * The `Player#posterchange` event that triggered this function. 12717 */ 12718 ; 12719 12720 _proto.update = function update(event) { 12721 var url = this.player().poster(); 12722 this.setSrc(url); // If there's no poster source we should display:none on this component 12723 // so it's not still clickable or right-clickable 12724 12725 if (url) { 12726 this.show(); 12727 } else { 12728 this.hide(); 12729 } 12730 } 12731 /** 12732 * Set the source of the `PosterImage` depending on the display method. 12733 * 12734 * @param {string} url 12735 * The URL to the source for the `PosterImage`. 12736 */ 12737 ; 12738 12739 _proto.setSrc = function setSrc(url) { 12740 var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise 12741 // this will throw an extra error 12742 12743 if (url) { 12744 backgroundImage = "url(\"" + url + "\")"; 12745 } 12746 12747 this.el_.style.backgroundImage = backgroundImage; 12748 } 12749 /** 12750 * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See 12751 * {@link ClickableComponent#handleClick} for instances where this will be triggered. 12752 * 12753 * @listens tap 12754 * @listens click 12755 * @listens keydown 12756 * 12757 * @param {EventTarget~Event} event 12758 + The `click`, `tap` or `keydown` event that caused this function to be called. 12759 */ 12760 ; 12761 12762 _proto.handleClick = function handleClick(event) { 12763 // We don't want a click to trigger playback when controls are disabled 12764 if (!this.player_.controls()) { 12765 return; 12766 } 12767 12768 var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0; 12769 12770 if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where 12771 // calling .focus() on the video element causes the video to go black, 12772 // so we avoid it in that specific case 12773 !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) { 12774 this.player_.tech(true).focus(); 12775 } 12776 12777 if (this.player_.paused()) { 12778 silencePromise(this.player_.play()); 12779 } else { 12780 this.player_.pause(); 12781 } 12782 }; 12783 12784 return PosterImage; 12785 }(ClickableComponent); 12786 12787 Component.registerComponent('PosterImage', PosterImage); 12788 12789 var darkGray = '#222'; 12790 var lightGray = '#ccc'; 12791 var fontMap = { 12792 monospace: 'monospace', 12793 sansSerif: 'sans-serif', 12794 serif: 'serif', 12795 monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', 12796 monospaceSerif: '"Courier New", monospace', 12797 proportionalSansSerif: 'sans-serif', 12798 proportionalSerif: 'serif',
12799 casual: '"Comic Sans MS", Impact, fantasy', 12800 script: '"Monotype Corsiva", cursive', 12801 smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' 12802 }; 12803 /** 12804 * Construct an rgba color from a given hex color code. 12805 * 12806 * @param {number} color 12807 * Hex number for color, like #f0e or #f604e2. 12808 * 12809 * @param {number} opacity 12810 * Value for opacity, 0.0 - 1.0. 12811 * 12812 * @return {string} 12813 * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. 12814 */ 12815 12816 function constructColor(color, opacity) { 12817 var hex; 12818 12819 if (color.length === 4) { 12820 // color looks like "#f0e" 12821 hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; 12822 } else if (color.length === 7) { 12823 // color looks like "#f604e2" 12824 hex = color.slice(1); 12825 } else { 12826 throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.'); 12827 } 12828 12829 return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')'; 12830 } 12831 /** 12832 * Try to update the style of a DOM element. Some style changes will throw an error, 12833 * particularly in IE8. Those should be noops. 12834 * 12835 * @param {Element} el 12836 * The DOM element to be styled. 12837 * 12838 * @param {string} style 12839 * The CSS property on the element that should be styled. 12840 * 12841 * @param {string} rule 12842 * The style rule that should be applied to the property. 12843 * 12844 * @private 12845 */ 12846 12847 function tryUpdateStyle(el, style, rule) { 12848 try { 12849 el.style[style] = rule; 12850 } catch (e) { 12851 // Satisfies linter. 12852 return; 12853 } 12854 } 12855 /** 12856 * The component for displaying text track cues. 12857 * 12858 * @extends Component 12859 */ 12860 12861 12862 var TextTrackDisplay = /*#__PURE__*/function (_Component) { 12863 inheritsLoose(TextTrackDisplay, _Component); 12864 12865 /** 12866 * Creates an instance of this class. 12867 * 12868 * @param {Player} player 12869 * The `Player` that this class should be attached to. 12870 * 12871 * @param {Object} [options] 12872 * The key/value store of player options. 12873 * 12874 * @param {Component~ReadyCallback} [ready] 12875 * The function to call when `TextTrackDisplay` is ready. 12876 */ 12877 function TextTrackDisplay(player, options, ready) { 12878 var _this; 12879 12880 _this = _Component.call(this, player, options, ready) || this; 12881 var updateDisplayHandler = bind(assertThisInitialized(_this), _this.updateDisplay); 12882 player.on('loadstart', bind(assertThisInitialized(_this), _this.toggleDisplay)); 12883 player.on('texttrackchange', updateDisplayHandler); 12884 player.on('loadedmetadata', bind(assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error 12885 // if a track should show by default and the display hadn't loaded yet. 12886 // Should probably be moved to an external track loader when we support 12887 // tracks that don't need a display. 12888 12889 player.ready(bind(assertThisInitialized(_this), function () { 12890 if (player.tech_ && player.tech_.featuresNativeTextTracks) { 12891 this.hide(); 12892 return; 12893 } 12894 12895 player.on('fullscreenchange', updateDisplayHandler); 12896 player.on('playerresize', updateDisplayHandler); 12897 window$3.addEventListener('orientationchange', updateDisplayHandler); 12898 player.on('dispose', function () { 12899 return window$3.removeEventListener('orientationchange', updateDisplayHandler); 12900 }); 12901 var tracks = this.options_.playerOptions.tracks || []; 12902 12903 for (var i = 0; i < tracks.length; i++) { 12904 this.player_.addRemoteTextTrack(tracks[i], true); 12905 } 12906 12907 this.preselectTrack(); 12908 })); 12909 return _this; 12910 } 12911 /** 12912 * Preselect a track following this precedence: 12913 * - matches the previously selected {@link TextTrack}'s language and kind 12914 * - matches the previously selected {@link TextTrack}'s language only 12915 * - is the first default captions track 12916 * - is the first default descriptions track 12917 * 12918 * @listens Player#loadstart 12919 */ 12920 12921 12922 var _proto = TextTrackDisplay.prototype; 12923 12924 _proto.preselectTrack = function preselectTrack() { 12925 var modes = { 12926 captions: 1, 12927 subtitles: 1 12928 }; 12929 var trackList = this.player_.textTracks(); 12930 var userPref = this.player_.cache_.selectedLanguage; 12931 var firstDesc; 12932 var firstCaptions; 12933 var preferredTrack; 12934 12935 for (var i = 0; i < trackList.length; i++) { 12936 var track = trackList[i]; 12937 12938 if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) { 12939 // Always choose the track that matches both language and kind 12940 if (track.kind === userPref.kind) { 12941 preferredTrack = track; // or choose the first track that matches language 12942 } else if (!preferredTrack) { 12943 preferredTrack = track; 12944 } // clear everything if offTextTrackMenuItem was clicked 12945 12946 } else if (userPref && !userPref.enabled) { 12947 preferredTrack = null; 12948 firstDesc = null; 12949 firstCaptions = null; 12950 } else if (track["default"]) { 12951 if (track.kind === 'descriptions' && !firstDesc) { 12952 firstDesc = track; 12953 } else if (track.kind in modes && !firstCaptions) { 12954 firstCaptions = track; 12955 } 12956 } 12957 } // The preferredTrack matches the user preference and takes 12958 // precedence over all the other tracks. 12959 // So, display the preferredTrack before the first default track 12960 // and the subtitles/captions track before the descriptions track 12961 12962 12963 if (preferredTrack) { 12964 preferredTrack.mode = 'showing'; 12965 } else if (firstCaptions) { 12966 firstCaptions.mode = 'showing'; 12967 } else if (firstDesc) { 12968 firstDesc.mode = 'showing'; 12969 } 12970 } 12971 /** 12972 * Turn display of {@link TextTrack}'s from the current state into the other state. 12973 * There are only two states: 12974 * - 'shown' 12975 * - 'hidden' 12976 * 12977 * @listens Player#loadstart 12978 */ 12979 ; 12980 12981 _proto.toggleDisplay = function toggleDisplay() { 12982 if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { 12983 this.hide(); 12984 } else { 12985 this.show(); 12986 } 12987 } 12988 /** 12989 * Create the {@link Component}'s DOM element. 12990 * 12991 * @return {Element} 12992 * The element that was created. 12993 */ 12994 ; 12995 12996 _proto.createEl = function createEl() { 12997 return _Component.prototype.createEl.call(this, 'div', { 12998 className: 'vjs-text-track-display' 12999 }, { 13000 'aria-live': 'off', 13001 'aria-atomic': 'true' 13002 }); 13003 } 13004 /** 13005 * Clear all displayed {@link TextTrack}s. 13006 */ 13007 ; 13008 13009 _proto.clearDisplay = function clearDisplay() { 13010 if (typeof window$3.WebVTT === 'function') { 13011 window$3.WebVTT.processCues(window$3, [], this.el_); 13012 } 13013 } 13014 /** 13015 * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or 13016 * a {@link Player#fullscreenchange} is fired. 13017 * 13018 * @listens Player#texttrackchange 13019 * @listens Player#fullscreenchange 13020 */ 13021 ; 13022 13023 _proto.updateDisplay = function updateDisplay() { 13024 var tracks = this.player_.textTracks(); 13025 var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks; 13026 this.clearDisplay(); 13027 13028 if (allowMultipleShowingTracks) { 13029 var showingTracks = []; 13030 13031 for (var _i = 0; _i < tracks.length; ++_i) { 13032 var track = tracks[_i]; 13033 13034 if (track.mode !== 'showing') { 13035 continue; 13036 } 13037 13038 showingTracks.push(track); 13039 } 13040 13041 this.updateForTrack(showingTracks); 13042 return; 13043 } // Track display prioritization model: if multiple tracks are 'showing', 13044 // display the first 'subtitles' or 'captions' track which is 'showing', 13045 // otherwise display the first 'descriptions' track which is 'showing' 13046 13047 13048 var descriptionsTrack = null; 13049 var captionsSubtitlesTrack = null; 13050 var i = tracks.length; 13051 13052 while (i--) { 13053 var _track = tracks[i]; 13054 13055 if (_track.mode === 'showing') { 13056 if (_track.kind === 'descriptions') { 13057 descriptionsTrack = _track; 13058 } else { 13059 captionsSubtitlesTrack = _track; 13060 } 13061 } 13062 } 13063 13064 if (captionsSubtitlesTrack) { 13065 if (this.getAttribute('aria-live') !== 'off') { 13066 this.setAttribute('aria-live', 'off'); 13067 } 13068 13069 this.updateForTrack(captionsSubtitlesTrack); 13070 } else if (descriptionsTrack) { 13071 if (this.getAttribute('aria-live') !== 'assertive') { 13072 this.setAttribute('aria-live', 'assertive'); 13073 } 13074 13075 this.updateForTrack(descriptionsTrack); 13076 } 13077 } 13078 /** 13079 * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}. 13080 * 13081 * @param {TextTrack} track
13082 * Text track object containing active cues to style. 13083 */ 13084 ; 13085 13086 _proto.updateDisplayState = function updateDisplayState(track) { 13087 var overrides = this.player_.textTrackSettings.getValues(); 13088 var cues = track.activeCues; 13089 var i = cues.length; 13090 13091 while (i--) { 13092 var cue = cues[i]; 13093 13094 if (!cue) { 13095 continue; 13096 } 13097 13098 var cueDiv = cue.displayState; 13099 13100 if (overrides.color) { 13101 cueDiv.firstChild.style.color = overrides.color; 13102 } 13103 13104 if (overrides.textOpacity) { 13105 tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); 13106 } 13107 13108 if (overrides.backgroundColor) { 13109 cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; 13110 } 13111 13112 if (overrides.backgroundOpacity) { 13113 tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); 13114 } 13115 13116 if (overrides.windowColor) { 13117 if (overrides.windowOpacity) { 13118 tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); 13119 } else { 13120 cueDiv.style.backgroundColor = overrides.windowColor; 13121 } 13122 } 13123 13124 if (overrides.edgeStyle) { 13125 if (overrides.edgeStyle === 'dropshadow') { 13126 cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray; 13127 } else if (overrides.edgeStyle === 'raised') { 13128 cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray; 13129 } else if (overrides.edgeStyle === 'depressed') { 13130 cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray; 13131 } else if (overrides.edgeStyle === 'uniform') { 13132 cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray; 13133 } 13134 } 13135 13136 if (overrides.fontPercent && overrides.fontPercent !== 1) { 13137 var fontSize = window$3.parseFloat(cueDiv.style.fontSize); 13138 cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; 13139 cueDiv.style.height = 'auto'; 13140 cueDiv.style.top = 'auto'; 13141 cueDiv.style.bottom = '2px'; 13142 } 13143 13144 if (overrides.fontFamily && overrides.fontFamily !== 'default') { 13145 if (overrides.fontFamily === 'small-caps') { 13146 cueDiv.firstChild.style.fontVariant = 'small-caps'; 13147 } else { 13148 cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; 13149 } 13150 } 13151 } 13152 } 13153 /** 13154 * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}. 13155 * 13156 * @param {TextTrack|TextTrack[]} tracks 13157 * Text track object or text track array to be added to the list. 13158 */ 13159 ; 13160 13161 _proto.updateForTrack = function updateForTrack(tracks) { 13162 if (!Array.isArray(tracks)) { 13163 tracks = [tracks]; 13164 } 13165 13166 if (typeof window$3.WebVTT !== 'function' || tracks.every(function (track) { 13167 return !track.activeCues; 13168 })) { 13169 return; 13170 } 13171 13172 var cues = []; // push all active track cues 13173 13174 for (var i = 0; i < tracks.length; ++i) { 13175 var track = tracks[i]; 13176 13177 for (var j = 0; j < track.activeCues.length; ++j) { 13178 cues.push(track.activeCues[j]); 13179 } 13180 } // removes all cues before it processes new ones 13181 13182 13183 window$3.WebVTT.processCues(window$3, cues, this.el_); // add unique class to each language text track & add settings styling if necessary 13184 13185 for (var _i2 = 0; _i2 < tracks.length; ++_i2) { 13186 var _track2 = tracks[_i2]; 13187 13188 for (var _j = 0; _j < _track2.activeCues.length; ++_j) { 13189 var cueEl = _track2.activeCues[_j].displayState; 13190 addClass(cueEl, 'vjs-text-track-cue'); 13191 addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2)); 13192 } 13193 13194 if (this.player_.textTrackSettings) { 13195 this.updateDisplayState(_track2); 13196 } 13197 } 13198 }; 13199 13200 return TextTrackDisplay; 13201 }(Component); 13202 13203 Component.registerComponent('TextTrackDisplay', TextTrackDisplay); 13204 13205 /** 13206 * A loading spinner for use during waiting/loading events. 13207 * 13208 * @extends Component 13209 */ 13210 13211 var LoadingSpinner = /*#__PURE__*/function (_Component) { 13212 inheritsLoose(LoadingSpinner, _Component); 13213 13214 function LoadingSpinner() { 13215 return _Component.apply(this, arguments) || this; 13216 } 13217 13218 var _proto = LoadingSpinner.prototype; 13219 13220 /** 13221 * Create the `LoadingSpinner`s DOM element. 13222 * 13223 * @return {Element} 13224 * The dom element that gets created. 13225 */ 13226 _proto.createEl = function createEl$1() { 13227 var isAudio = this.player_.isAudio(); 13228 var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player'); 13229 var controlText = createEl('span', { 13230 className: 'vjs-control-text', 13231 innerHTML: this.localize('{1} is loading.', [playerType]) 13232 }); 13233 13234 var el = _Component.prototype.createEl.call(this, 'div', { 13235 className: 'vjs-loading-spinner', 13236 dir: 'ltr' 13237 }); 13238 13239 el.appendChild(controlText); 13240 return el; 13241 }; 13242 13243 return LoadingSpinner; 13244 }(Component); 13245 13246 Component.registerComponent('LoadingSpinner', LoadingSpinner); 13247 13248 /** 13249 * Base class for all buttons. 13250 * 13251 * @extends ClickableComponent 13252 */ 13253 13254 var Button = /*#__PURE__*/function (_ClickableComponent) { 13255 inheritsLoose(Button, _ClickableComponent); 13256 13257 function Button() { 13258 return _ClickableComponent.apply(this, arguments) || this; 13259 } 13260 13261 var _proto = Button.prototype; 13262 13263 /** 13264 * Create the `Button`s DOM element. 13265 * 13266 * @param {string} [tag="button"] 13267 * The element's node type. This argument is IGNORED: no matter what 13268 * is passed, it will always create a `button` element. 13269 * 13270 * @param {Object} [props={}] 13271 * An object of properties that should be set on the element. 13272 * 13273 * @param {Object} [attributes={}] 13274 * An object of attributes that should be set on the element. 13275 * 13276 * @return {Element} 13277 * The element that gets created. 13278 */ 13279 _proto.createEl = function createEl(tag, props, attributes) { 13280 if (props === void 0) { 13281 props = {}; 13282 } 13283 13284 if (attributes === void 0) { 13285 attributes = {}; 13286 } 13287 13288 tag = 'button'; 13289 props = assign({ 13290 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 13291 className: this.buildCSSClass() 13292 }, props); // Add attributes for button element 13293 13294 attributes = assign({ 13295 // Necessary since the default button type is "submit" 13296 type: 'button' 13297 }, attributes); 13298 var el = Component.prototype.createEl.call(this, tag, props, attributes); 13299 this.createControlTextEl(el); 13300 return el; 13301 } 13302 /** 13303 * Add a child `Component` inside of this `Button`. 13304 * 13305 * @param {string|Component} child 13306 * The name or instance of a child to add. 13307 * 13308 * @param {Object} [options={}] 13309 * The key/value store of options that will get passed to children of 13310 * the child. 13311 * 13312 * @return {Component} 13313 * The `Component` that gets added as a child. When using a string the 13314 * `Component` will get created by this process. 13315 * 13316 * @deprecated since version 5 13317 */ 13318 ; 13319 13320 _proto.addChild = function addChild(child, options) { 13321 if (options === void 0) { 13322 options = {}; 13323 } 13324 13325 var className = this.constructor.name; 13326 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 13327 13328 return Component.prototype.addChild.call(this, child, options); 13329 } 13330 /** 13331 * Enable the `Button` element so that it can be activated or clicked. Use this with 13332 * {@link Button#disable}. 13333 */ 13334 ; 13335 13336 _proto.enable = function enable() { 13337 _ClickableComponent.prototype.enable.call(this); 13338 13339 this.el_.removeAttribute('disabled'); 13340 } 13341 /** 13342 * Disable the `Button` element so that it cannot be activated or clicked. Use this with 13343 * {@link Button#enable}. 13344 */ 13345 ; 13346 13347 _proto.disable = function disable() { 13348 _ClickableComponent.prototype.disable.call(this); 13349 13350 this.el_.setAttribute('disabled', 'disabled'); 13351 } 13352 /** 13353 * This gets called when a `Button` has focus and `keydown` is triggered via a key 13354 * press. 13355 * 13356 * @param {EventTarget~Event} event 13357 * The event that caused this function to get called. 13358 * 13359 * @listens keydown 13360 */ 13361 ; 13362 13363 _proto.handleKeyDown = function handleKeyDown(event) { 13364 // Ignore Space or Enter key operation, which is handled by the browser for 13365 // a button - though not for its super class, ClickableComponent. Also, 13366 // prevent the event from propagating through the DOM and triggering Player 13367 // hotkeys. We do not preventDefault here because we _want_ the browser to 13368 // handle it. 13369 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 13370 event.stopPropagation(); 13371 return; 13372 } // Pass keypress handling up for unsupported keys 13373 13374 13375 _ClickableComponent.prototype.handleKeyDown.call(this, event); 13376 }; 13377 13378 return Button; 13379 }(ClickableComponent); 13380 13381 Component.registerComponent('Button', Button); 13382 13383 /** 13384 * The initial play button that shows before the video has played. The hiding of the 13385 * `BigPlayButton` get done via CSS and `Player` states. 13386 * 13387 * @extends Button 13388 */ 13389 13390 var BigPlayButton = /*#__PURE__*/function (_Button) { 13391 inheritsLoose(BigPlayButton, _Button); 13392 13393 function BigPlayButton(player, options) { 13394 var _this; 13395 13396 _this = _Button.call(this, player, options) || this; 13397 _this.mouseused_ = false;
13398 13399 _this.on('mousedown', _this.handleMouseDown); 13400 13401 return _this; 13402 } 13403 /** 13404 * Builds the default DOM `className`. 13405 * 13406 * @return {string} 13407 * The DOM `className` for this object. Always returns 'vjs-big-play-button'. 13408 */ 13409 13410 13411 var _proto = BigPlayButton.prototype; 13412 13413 _proto.buildCSSClass = function buildCSSClass() { 13414 return 'vjs-big-play-button'; 13415 } 13416 /** 13417 * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} 13418 * for more detailed information on what a click can be. 13419 * 13420 * @param {EventTarget~Event} event 13421 * The `keydown`, `tap`, or `click` event that caused this function to be 13422 * called. 13423 * 13424 * @listens tap 13425 * @listens click 13426 */ 13427 ; 13428 13429 _proto.handleClick = function handleClick(event) { 13430 var playPromise = this.player_.play(); // exit early if clicked via the mouse 13431 13432 if (this.mouseused_ && event.clientX && event.clientY) { 13433 var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0; 13434 silencePromise(playPromise); 13435 13436 if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where 13437 // calling .focus() on the video element causes the video to go black, 13438 // so we avoid it in that specific case 13439 !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) { 13440 this.player_.tech(true).focus(); 13441 } 13442 13443 return; 13444 } 13445 13446 var cb = this.player_.getChild('controlBar'); 13447 var playToggle = cb && cb.getChild('playToggle'); 13448 13449 if (!playToggle) { 13450 this.player_.tech(true).focus(); 13451 return; 13452 } 13453 13454 var playFocus = function playFocus() { 13455 return playToggle.focus(); 13456 }; 13457 13458 if (isPromise(playPromise)) { 13459 playPromise.then(playFocus, function () {}); 13460 } else { 13461 this.setTimeout(playFocus, 1); 13462 } 13463 }; 13464 13465 _proto.handleKeyDown = function handleKeyDown(event) { 13466 this.mouseused_ = false; 13467 13468 _Button.prototype.handleKeyDown.call(this, event); 13469 }; 13470 13471 _proto.handleMouseDown = function handleMouseDown(event) { 13472 this.mouseused_ = true; 13473 }; 13474 13475 return BigPlayButton; 13476 }(Button); 13477 /** 13478 * The text that should display over the `BigPlayButton`s controls. Added to for localization. 13479 * 13480 * @type {string} 13481 * @private 13482 */ 13483 13484 13485 BigPlayButton.prototype.controlText_ = 'Play Video'; 13486 Component.registerComponent('BigPlayButton', BigPlayButton); 13487 13488 /** 13489 * The `CloseButton` is a `{@link Button}` that fires a `close` event when 13490 * it gets clicked. 13491 * 13492 * @extends Button 13493 */ 13494 13495 var CloseButton = /*#__PURE__*/function (_Button) { 13496 inheritsLoose(CloseButton, _Button); 13497 13498 /** 13499 * Creates an instance of the this class. 13500 * 13501 * @param {Player} player 13502 * The `Player` that this class should be attached to. 13503 * 13504 * @param {Object} [options] 13505 * The key/value store of player options. 13506 */ 13507 function CloseButton(player, options) { 13508 var _this; 13509 13510 _this = _Button.call(this, player, options) || this; 13511 13512 _this.controlText(options && options.controlText || _this.localize('Close')); 13513 13514 return _this; 13515 } 13516 /** 13517 * Builds the default DOM `className`. 13518 * 13519 * @return {string} 13520 * The DOM `className` for this object. 13521 */ 13522 13523 13524 var _proto = CloseButton.prototype; 13525 13526 _proto.buildCSSClass = function buildCSSClass() { 13527 return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this); 13528 } 13529 /** 13530 * This gets called when a `CloseButton` gets clicked. See 13531 * {@link ClickableComponent#handleClick} for more information on when 13532 * this will be triggered 13533 * 13534 * @param {EventTarget~Event} event
13535 * The `keydown`, `tap`, or `click` event that caused this function to be 13536 * called. 13537 * 13538 * @listens tap 13539 * @listens click 13540 * @fires CloseButton#close 13541 */ 13542 ; 13543 13544 _proto.handleClick = function handleClick(event) { 13545 /** 13546 * Triggered when the a `CloseButton` is clicked. 13547 * 13548 * @event CloseButton#close 13549 * @type {EventTarget~Event} 13550 * 13551 * @property {boolean} [bubbles=false] 13552 * set to false so that the close event does not 13553 * bubble up to parents if there is no listener 13554 */ 13555 this.trigger({ 13556 type: 'close', 13557 bubbles: false 13558 }); 13559 } 13560 /** 13561 * Event handler that is called when a `CloseButton` receives a 13562 * `keydown` event. 13563 * 13564 * By default, if the key is Esc, it will trigger a `click` event. 13565 * 13566 * @param {EventTarget~Event} event 13567 * The `keydown` event that caused this function to be called. 13568 * 13569 * @listens keydown 13570 */ 13571 ; 13572 13573 _proto.handleKeyDown = function handleKeyDown(event) { 13574 // Esc button will trigger `click` event 13575 if (keycode.isEventKey(event, 'Esc')) { 13576 event.preventDefault(); 13577 event.stopPropagation(); 13578 this.trigger('click'); 13579 } else { 13580 // Pass keypress handling up for unsupported keys 13581 _Button.prototype.handleKeyDown.call(this, event); 13582 } 13583 }; 13584 13585 return CloseButton; 13586 }(Button); 13587 13588 Component.registerComponent('CloseButton', CloseButton); 13589 13590 /** 13591 * Button to toggle between play and pause. 13592 * 13593 * @extends Button 13594 */ 13595 13596 var PlayToggle = /*#__PURE__*/function (_Button) { 13597 inheritsLoose(PlayToggle, _Button); 13598 13599 /** 13600 * Creates an instance of this class. 13601 * 13602 * @param {Player} player 13603 * The `Player` that this class should be attached to. 13604 * 13605 * @param {Object} [options={}] 13606 * The key/value store of player options. 13607 */ 13608 function PlayToggle(player, options) { 13609 var _this; 13610 13611 if (options === void 0) { 13612 options = {}; 13613 } 13614 13615 _this = _Button.call(this, player, options) || this; // show or hide replay icon 13616 13617 options.replay = options.replay === undefined || options.replay; 13618 13619 _this.on(player, 'play', _this.handlePlay); 13620 13621 _this.on(player, 'pause', _this.handlePause); 13622 13623 if (options.replay) { 13624 _this.on(player, 'ended', _this.handleEnded); 13625 } 13626 13627 return _this; 13628 } 13629 /** 13630 * Builds the default DOM `className`. 13631 * 13632 * @return {string} 13633 * The DOM `className` for this object. 13634 */ 13635 13636 13637 var _proto = PlayToggle.prototype; 13638 13639 _proto.buildCSSClass = function buildCSSClass() { 13640 return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this); 13641 } 13642 /** 13643 * This gets called when an `PlayToggle` is "clicked". See 13644 * {@link ClickableComponent} for more detailed information on what a click can be. 13645 * 13646 * @param {EventTarget~Event} [event] 13647 * The `keydown`, `tap`, or `click` event that caused this function to be 13648 * called. 13649 * 13650 * @listens tap 13651 * @listens click 13652 */ 13653 ; 13654 13655 _proto.handleClick = function handleClick(event) { 13656 if (this.player_.paused()) { 13657 this.player_.play(); 13658 } else { 13659 this.player_.pause(); 13660 } 13661 } 13662 /** 13663 * This gets called once after the video has ended and the user seeks so that 13664 * we can change the replay button back to a play button. 13665 * 13666 * @param {EventTarget~Event} [event] 13667 * The event that caused this function to run. 13668 * 13669 * @listens Player#seeked 13670 */ 13671 ; 13672 13673 _proto.handleSeeked = function handleSeeked(event) { 13674 this.removeClass('vjs-ended'); 13675 13676 if (this.player_.paused()) { 13677 this.handlePause(event); 13678 } else { 13679 this.handlePlay(event); 13680 } 13681 } 13682 /** 13683 * Add the vjs-playing class to the element so it can change appearance. 13684 * 13685 * @param {EventTarget~Event} [event] 13686 * The event that caused this function to run. 13687 * 13688 * @listens Player#play 13689 */ 13690 ; 13691 13692 _proto.handlePlay = function handlePlay(event) { 13693 this.removeClass('vjs-ended'); 13694 this.removeClass('vjs-paused'); 13695 this.addClass('vjs-playing'); // change the button text to "Pause" 13696 13697 this.controlText('Pause'); 13698 } 13699 /** 13700 * Add the vjs-paused class to the element so it can change appearance. 13701 * 13702 * @param {EventTarget~Event} [event] 13703 * The event that caused this function to run. 13704 * 13705 * @listens Player#pause 13706 */ 13707 ; 13708 13709 _proto.handlePause = function handlePause(event) { 13710 this.removeClass('vjs-playing'); 13711 this.addClass('vjs-paused'); // change the button text to "Play" 13712 13713 this.controlText('Play'); 13714 } 13715 /** 13716 * Add the vjs-ended class to the element so it can change appearance 13717 * 13718 * @param {EventTarget~Event} [event] 13719 * The event that caused this function to run. 13720 * 13721 * @listens Player#ended 13722 */ 13723 ; 13724 13725 _proto.handleEnded = function handleEnded(event) { 13726 this.removeClass('vjs-playing'); 13727 this.addClass('vjs-ended'); // change the button text to "Replay" 13728 13729 this.controlText('Replay'); // on the next seek remove the replay button 13730 13731 this.one(this.player_, 'seeked', this.handleSeeked); 13732 }; 13733 13734 return PlayToggle; 13735 }(Button); 13736 /** 13737 * The text that should display over the `PlayToggle`s controls. Added for localization. 13738 * 13739 * @type {string} 13740 * @private 13741 */ 13742 13743 13744 PlayToggle.prototype.controlText_ = 'Play'; 13745 Component.registerComponent('PlayToggle', PlayToggle); 13746 13747 /** 13748 * @file format-time.js 13749 * @module format-time 13750 */ 13751 13752 /** 13753 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in 13754 * seconds) will force a number of leading zeros to cover the length of the 13755 * guide. 13756 * 13757 * @private 13758 * @param {number} seconds 13759 * Number of seconds to be turned into a string 13760 * 13761 * @param {number} guide 13762 * Number (in seconds) to model the string after 13763 * 13764 * @return {string} 13765 * Time formatted as H:MM:SS or M:SS 13766 */ 13767 var defaultImplementation = function defaultImplementation(seconds, guide) { 13768 seconds = seconds < 0 ? 0 : seconds; 13769 var s = Math.floor(seconds % 60); 13770 var m = Math.floor(seconds / 60 % 60); 13771 var h = Math.floor(seconds / 3600); 13772 var gm = Math.floor(guide / 60 % 60); 13773 var gh = Math.floor(guide / 3600); // handle invalid times 13774 13775 if (isNaN(seconds) || seconds === Infinity) { 13776 // '-' is false for all relational operators (e.g. <, >
13776=) so this setting 13777 // will add the minimum number of fields specified by the guide 13778 h = m = s = '-'; 13779 } // Check if we need to show hours 13780 13781 13782 h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero. 13783 // Always show at least one digit of minutes. 13784 13785 m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds 13786 13787 s = s < 10 ? '0' + s : s; 13788 return h + m + s; 13789 }; // Internal pointer to the current implementation. 13790 13791 13792 var implementation = defaultImplementation; 13793 /** 13794 * Replaces the default formatTime implementation with a custom implementation. 13795 * 13796 * @param {Function} customImplementation 13797 * A function which will be used in place of the default formatTime 13798 * implementation. Will receive the current time in seconds and the 13799 * guide (in seconds) as arguments. 13800 */ 13801 13802 function setFormatTime(customImplementation) { 13803 implementation = customImplementation; 13804 } 13805 /** 13806 * Resets formatTime to the default implementation. 13807 */ 13808 13809 function resetFormatTime() { 13810 implementation = defaultImplementation; 13811 } 13812 /** 13813 * Delegates to either the default time formatting function or a custom 13814 * function supplied via `setFormatTime`. 13815 * 13816 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a 13817 * guide (in seconds) will force a number of leading zeros to cover the 13818 * length of the guide. 13819 * 13820 * @static 13821 * @example formatTime(125, 600) === "02:05" 13822 * @param {number} seconds 13823 * Number of seconds to be turned into a string 13824 * 13825 * @param {number} guide 13826 * Number (in seconds) to model the string after 13827 * 13828 * @return {string} 13829 * Time formatted as H:MM:SS or M:SS 13830 */ 13831 13832 function formatTime(seconds, guide) { 13833 if (guide === void 0) { 13834 guide = seconds; 13835 } 13836 13837 return implementation(seconds, guide); 13838 } 13839 13840 /** 13841 * Displays time information about the video 13842 * 13843 * @extends Component 13844 */ 13845 13846 var TimeDisplay = /*#__PURE__*/function (_Component) { 13847 inheritsLoose(TimeDisplay, _Component); 13848 13849 /** 13850 * Creates an instance of this class. 13851 * 13852 * @param {Player} player 13853 * The `Player` that this class should be attached to. 13854 * 13855 * @param {Object} [options] 13856 * The key/value store of player options. 13857 */ 13858 function TimeDisplay(player, options) { 13859 var _this; 13860 13861 _this = _Component.call(this, player, options) || this; 13862 13863 _this.on(player, ['timeupdate', 'ended'], _this.updateContent); 13864 13865 _this.updateTextNode_(); 13866 13867 return _this; 13868 } 13869 /** 13870 * Create the `Component`'s DOM element 13871 * 13872 * @return {Element} 13873 * The element that was created. 13874 */ 13875 13876 13877 var _proto = TimeDisplay.prototype; 13878 13879 _proto.createEl = function createEl$1() { 13880 var className = this.buildCSSClass(); 13881 13882 var el = _Component.prototype.createEl.call(this, 'div', { 13883 className: className + " vjs-time-control vjs-control", 13884 innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>" 13885 }); 13886 13887 this.contentEl_ = createEl('span', { 13888 className: className + "-display" 13889 }, { 13890 // tell screen readers not to automatically read the time as it changes 13891 'aria-live': 'off', 13892 // span elements have no implicit role, but some screen readers (notably VoiceOver) 13893 // treat them as a break between items in the DOM when using arrow keys 13894 // (or left-to-right swipes on iOS) to read contents of a page. Using 13895 // role='presentation' causes VoiceOver to NOT treat this span as a break. 13896 'role': 'presentation' 13897 }); 13898 el.appendChild(this.contentEl_); 13899 return el; 13900 }; 13901 13902 _proto.dispose = function dispose() { 13903 this.contentEl_ = null; 13904 this.textNode_ = null; 13905 13906 _Component.prototype.dispose.call(this); 13907 } 13908 /** 13909 * Updates the time display text node with a new time 13910 * 13911 * @param {number} [time=0] the time to update to 13912 * 13913 * @private 13914 */ 13915 ; 13916 13917 _proto.updateTextNode_ = function updateTextNode_(time) { 13918 var _this2 = this; 13919 13920 if (time === void 0) { 13921 time = 0; 13922 } 13923 13924 time = formatTime(time); 13925 13926 if (this.formattedTime_ === time) { 13927 return; 13928 } 13929 13930 this.formattedTime_ = time; 13931 this.requestAnimationFrame(function () { 13932 if (!_this2.contentEl_) { 13933 return; 13934 } 13935 13936 var oldNode = _this2.textNode_; 13937 _this2.textNode_ = document.createTextNode(_this2.formattedTime_); 13938 13939 if (!_this2.textNode_) { 13940 return; 13941 } 13942 13943 if (oldNode) { 13944 _this2.contentEl_.replaceChild(_this2.textNode_, oldNode); 13945 } else { 13946 _this2.contentEl_.appendChild(_this2.textNode_); 13947 } 13948 }); 13949 } 13950 /** 13951 * To be filled out in the child class, should update the displayed time 13952 * in accordance with the fact that the current time has changed. 13953 * 13954 * @param {EventTarget~Event} [event] 13955 * The `timeupdate` event that caused this to run. 13956 * 13957 * @listens Player#timeupdate 13958 */ 13959 ; 13960 13961 _proto.updateContent = function updateContent(event) {}; 13962 13963 return TimeDisplay; 13964 }(Component); 13965 /** 13966 * The text that is added to the `TimeDisplay` for screen reader users. 13967 * 13968 * @type {string} 13969 * @private 13970 */ 13971 13972 13973 TimeDisplay.prototype.labelText_ = 'Time'; 13974 /** 13975 * The text that should display over the `TimeDisplay`s controls. Added to for localization. 13976 * 13977 * @type {string} 13978 * @private 13979 *
13980 * @deprecated in v7; controlText_ is not used in non-active display Components 13981 */ 13982 13983 TimeDisplay.prototype.controlText_ = 'Time'; 13984 Component.registerComponent('TimeDisplay', TimeDisplay); 13985 13986 /** 13987 * Displays the current time 13988 * 13989 * @extends Component 13990 */ 13991 13992 var CurrentTimeDisplay = /*#__PURE__*/function (_TimeDisplay) { 13993 inheritsLoose(CurrentTimeDisplay, _TimeDisplay); 13994 13995 function CurrentTimeDisplay() { 13996 return _TimeDisplay.apply(this, arguments) || this; 13997 } 13998 13999 var _proto = CurrentTimeDisplay.prototype; 14000 14001 /** 14002 * Builds the default DOM `className`. 14003 * 14004 * @return {string} 14005 * The DOM `className` for this object. 14006 */ 14007 _proto.buildCSSClass = function buildCSSClass() { 14008 return 'vjs-current-time'; 14009 } 14010 /** 14011 * Update current time display 14012 * 14013 * @param {EventTarget~Event} [event] 14014 * The `timeupdate` event that caused this function to run. 14015 * 14016 * @listens Player#timeupdate 14017 */ 14018 ; 14019 14020 _proto.updateContent = function updateContent(event) { 14021 // Allows for smooth scrubbing, when player can't keep up. 14022 var time; 14023 14024 if (this.player_.ended()) { 14025 time = this.player_.duration(); 14026 } else { 14027 time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 14028 } 14029 14030 this.updateTextNode_(time); 14031 }; 14032 14033 return CurrentTimeDisplay; 14034 }(TimeDisplay); 14035 /** 14036 * The text that is added to the `CurrentTimeDisplay` for screen reader users. 14037 * 14038 * @type {string} 14039 * @private 14040 */ 14041 14042 14043 CurrentTimeDisplay.prototype.labelText_ = 'Current Time'; 14044 /** 14045 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization. 14046 * 14047 * @type {string} 14048 * @private 14049 * 14050 * @deprecated in v7; controlText_ is not used in non-active display Components 14051 */ 14052 14053 CurrentTimeDisplay.prototype.controlText_ = 'Current Time'; 14054 Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); 14055 14056 /** 14057 * Displays the duration 14058 * 14059 * @extends Component 14060 */ 14061 14062 var DurationDisplay = /*#__PURE__*/function (_TimeDisplay) { 14063 inheritsLoose(DurationDisplay, _TimeDisplay); 14064 14065 /** 14066 * Creates an instance of this class. 14067 * 14068 * @param {Player} player 14069 * The `Player` that this class should be attached to. 14070 * 14071 * @param {Object} [options] 14072 * The key/value store of player options. 14073 */ 14074 function DurationDisplay(player, options) { 14075 var _this; 14076 14077 _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes, 14078 // as they should always display the changed duration as 14079 // it has changed 14080 14081 _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded, 14082 // but the durationchange on the user agent will not fire. 14083 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 14084 14085 14086 _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those 14087 // listeners could have broken dependent applications/libraries. These 14088 // can likely be removed for 7.0. 14089 14090 14091 _this.on(player, 'loadedmetadata', _this.updateContent); 14092 14093 return _this; 14094 } 14095 /** 14096 * Builds the default DOM `className`. 14097 * 14098 * @return {string} 14099 * The DOM `className` for this object. 14100 */ 14101 14102 14103 var _proto = DurationDisplay.prototype; 14104 14105 _proto.buildCSSClass = function buildCSSClass() { 14106 return 'vjs-duration'; 14107 } 14108 /** 14109 * Update duration time display. 14110 * 14111 * @param {EventTarget~Event} [event] 14112 * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused 14113 * this function to be called. 14114 * 14115 * @listens Player#durationchange 14116 * @listens Player#timeupdate 14117 * @listens Player#loadedmetadata 14118 */ 14119 ; 14120 14121 _proto.updateContent = function updateContent(event) { 14122 var duration = this.player_.duration(); 14123 this.updateTextNode_(duration); 14124 }; 14125 14126 return DurationDisplay; 14127 }(TimeDisplay); 14128 /** 14129 * The text that is added to the `DurationDisplay` for screen reader users. 14130 * 14131 * @type {string} 14132 * @private 14133 */ 14134 14135 14136 DurationDisplay.prototype.labelText_ = 'Duration'; 14137 /** 14138 * The text that should display over the `DurationDisplay`s controls. Added to for localization. 14139 * 14140 * @type {string} 14141 * @private 14142 *
14143 * @deprecated in v7; controlText_ is not used in non-active display Components 14144 */ 14145 14146 DurationDisplay.prototype.controlText_ = 'Duration'; 14147 Component.registerComponent('DurationDisplay', DurationDisplay); 14148 14149 /** 14150 * The separator between the current time and duration. 14151 * Can be hidden if it's not needed in the design. 14152 * 14153 * @extends Component 14154 */ 14155 14156 var TimeDivider = /*#__PURE__*/function (_Component) { 14157 inheritsLoose(TimeDivider, _Component); 14158 14159 function TimeDivider() { 14160 return _Component.apply(this, arguments) || this; 14161 } 14162 14163 var _proto = TimeDivider.prototype; 14164 14165 /** 14166 * Create the component's DOM element 14167 * 14168 * @return {Element} 14169 * The element that was created. 14170 */ 14171 _proto.createEl = function createEl() { 14172 return _Component.prototype.createEl.call(this, 'div', { 14173 className: 'vjs-time-control vjs-time-divider', 14174 innerHTML: '<div><span>/</span></div>' 14175 }, { 14176 // this element and its contents can be hidden from assistive techs since 14177 // it is made extraneous by the announcement of the control text 14178 // for the current time and duration displays 14179 'aria-hidden': true 14180 }); 14181 }; 14182 14183 return TimeDivider; 14184 }(Component); 14185 14186 Component.registerComponent('TimeDivider', TimeDivider); 14187 14188 /** 14189 * Displays the time left in the video 14190 * 14191 * @extends Component 14192 */ 14193 14194 var RemainingTimeDisplay = /*#__PURE__*/function (_TimeDisplay) { 14195 inheritsLoose(RemainingTimeDisplay, _TimeDisplay); 14196 14197 /** 14198 * Creates an instance of this class. 14199 * 14200 * @param {Player} player 14201 * The `Player` that this class should be attached to. 14202 * 14203 * @param {Object} [options] 14204 * The key/value store of player options. 14205 */ 14206 function RemainingTimeDisplay(player, options) { 14207 var _this; 14208 14209 _this = _TimeDisplay.call(this, player, options) || this; 14210 14211 _this.on(player, 'durationchange', _this.updateContent); 14212 14213 return _this; 14214 } 14215 /** 14216 * Builds the default DOM `className`. 14217 * 14218 * @return {string} 14219 * The DOM `className` for this object. 14220 */ 14221 14222 14223 var _proto = RemainingTimeDisplay.prototype; 14224 14225 _proto.buildCSSClass = function buildCSSClass() { 14226 return 'vjs-remaining-time'; 14227 } 14228 /** 14229 * Create the `Component`'s DOM element with the "minus" characted prepend to the time 14230 * 14231 * @return {Element} 14232 * The element that was created. 14233 */ 14234 ; 14235 14236 _proto.createEl = function createEl$1() { 14237 var el = _TimeDisplay.prototype.createEl.call(this); 14238 14239 el.insertBefore(createEl('span', {}, { 14240 'aria-hidden': true 14241 }, '-'), this.contentEl_); 14242 return el; 14243 } 14244 /** 14245 * Update remaining time display. 14246 * 14247 * @param {EventTarget~Event} [event] 14248 * The `timeupdate` or `durationchange` event that caused this to run. 14249 * 14250 * @listens Player#timeupdate 14251 * @listens Player#durationchange 14252 */ 14253 ; 14254 14255 _proto.updateContent = function updateContent(event) { 14256 if (typeof this.player_.duration() !== 'number') { 14257 return; 14258 } 14259 14260 var time; // @deprecated We should only use remainingTimeDisplay 14261 // as of video.js 7 14262 14263 if (this.player_.ended()) { 14264 time = 0; 14265 } else if (this.player_.remainingTimeDisplay) { 14266 time = this.player_.remainingTimeDisplay(); 14267 } else { 14268 time = this.player_.remainingTime(); 14269 } 14270 14271 this.updateTextNode_(time); 14272 }; 14273 14274 return RemainingTimeDisplay; 14275 }(TimeDisplay); 14276 /** 14277 * The text that is added to the `RemainingTimeDisplay` for screen reader users. 14278 * 14279 * @type {string} 14280 * @private 14281 */ 14282 14283 14284 RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time'; 14285 /** 14286 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization. 14287 * 14288 * @type {string} 14289 * @private 14290 *
14291 * @deprecated in v7; controlText_ is not used in non-active display Components 14292 */ 14293 14294 RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time'; 14295 Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); 14296 14297 /** 14298 * Displays the live indicator when duration is Infinity. 14299 * 14300 * @extends Component 14301 */ 14302 14303 var LiveDisplay = /*#__PURE__*/function (_Component) { 14304 inheritsLoose(LiveDisplay, _Component); 14305 14306 /** 14307 * Creates an instance of this class. 14308 * 14309 * @param {Player} player 14310 * The `Player` that this class should be attached to. 14311 * 14312 * @param {Object} [options] 14313 * The key/value store of player options. 14314 */ 14315 function LiveDisplay(player, options) { 14316 var _this; 14317 14318 _this = _Component.call(this, player, options) || this; 14319 14320 _this.updateShowing(); 14321 14322 _this.on(_this.player(), 'durationchange', _this.updateShowing); 14323 14324 return _this; 14325 } 14326 /** 14327 * Create the `Component`'s DOM element 14328 * 14329 * @return {Element} 14330 * The element that was created. 14331 */ 14332 14333 14334 var _proto = LiveDisplay.prototype; 14335 14336 _proto.createEl = function createEl$1() { 14337 var el = _Component.prototype.createEl.call(this, 'div', { 14338 className: 'vjs-live-control vjs-control' 14339 }); 14340 14341 this.contentEl_ = createEl('div', { 14342 className: 'vjs-live-display', 14343 innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE') 14344 }, { 14345 'aria-live': 'off' 14346 }); 14347 el.appendChild(this.contentEl_); 14348 return el; 14349 }; 14350 14351 _proto.dispose = function dispose() { 14352 this.contentEl_ = null; 14353 14354 _Component.prototype.dispose.call(this); 14355 } 14356 /** 14357 * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide 14358 * it accordingly 14359 * 14360 * @param {EventTarget~Event} [event] 14361 * The {@link Player#durationchange} event that caused this function to run. 14362 * 14363 * @listens Player#durationchange 14364 */ 14365 ; 14366 14367 _proto.updateShowing = function updateShowing(event) { 14368 if (this.player().duration() === Infinity) { 14369 this.show(); 14370 } else { 14371 this.hide(); 14372 } 14373 }; 14374 14375 return LiveDisplay; 14376 }(Component); 14377 14378 Component.registerComponent('LiveDisplay', LiveDisplay); 14379 14380 /** 14381 * Displays the live indicator when duration is Infinity. 14382 * 14383 * @extends Component 14384 */ 14385 14386 var SeekToLive = /*#__PURE__*/function (_Button) { 14387 inheritsLoose(SeekToLive, _Button); 14388 14389 /** 14390 * Creates an instance of this class. 14391 * 14392 * @param {Player} player 14393 * The `Player` that this class should be attached to. 14394 * 14395 * @param {Object} [options] 14396 * The key/value store of player options. 14397 */ 14398 function SeekToLive(player, options) { 14399 var _this; 14400 14401 _this = _Button.call(this, player, options) || this; 14402 14403 _this.updateLiveEdgeStatus(); 14404 14405 if (_this.player_.liveTracker) { 14406 _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus); 14407 } 14408 14409 return _this; 14410 } 14411 /** 14412 * Create the `Component`'s DOM element 14413 * 14414 * @return {Element} 14415 * The element that was created. 14416 */ 14417 14418 14419 var _proto = SeekToLive.prototype; 14420 14421 _proto.createEl = function createEl$1() { 14422 var el = _Button.prototype.createEl.call(this, 'button', { 14423 className: 'vjs-seek-to-live-control vjs-control' 14424 }); 14425 14426 this.textEl_ = createEl('span', { 14427 className: 'vjs-seek-to-live-text', 14428 innerHTML: this.localize('LIVE') 14429 }, { 14430 'aria-hidden': 'true' 14431 }); 14432 el.appendChild(this.textEl_); 14433 return el; 14434 } 14435 /** 14436 * Update the state of this button if we are at the live edge 14437 * or not 14438 */ 14439 ; 14440 14441 _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus() { 14442 // default to live edge 14443 if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) { 14444 this.setAttribute('aria-disabled', true); 14445 this.addClass('vjs-at-live-edge'); 14446 this.controlText('Seek to live, currently playing live'); 14447 } else { 14448 this.setAttribute('aria-disabled', false); 14449 this.removeClass('vjs-at-live-edge'); 14450 this.controlText('Seek to live, currently behind live'); 14451 } 14452 } 14453 /** 14454 * On click bring us as near to the live point as possible. 14455 * This requires that we wait for the next `live-seekable-change` 14456 * event which will happen every segment length seconds. 14457 */ 14458 ; 14459 14460 _proto.handleClick = function handleClick() { 14461 this.player_.liveTracker.seekToLiveEdge(); 14462 } 14463 /**
14464 * Dispose of the element and stop tracking 14465 */ 14466 ; 14467 14468 _proto.dispose = function dispose() { 14469 if (this.player_.liveTracker) { 14470 this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus); 14471 } 14472 14473 this.textEl_ = null; 14474 14475 _Button.prototype.dispose.call(this); 14476 }; 14477 14478 return SeekToLive; 14479 }(Button); 14480 14481 SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live'; 14482 Component.registerComponent('SeekToLive', SeekToLive); 14483 14484 /** 14485 * Keep a number between a min and a max value 14486 * 14487 * @param {number} number 14488 * The number to clamp 14489 * 14490 * @param {number} min 14491 * The minimum value 14492 * @param {number} max 14493 * The maximum value 14494 * 14495 * @return {number} 14496 * the clamped number 14497 */ 14498 var clamp = function clamp(number, min, max) { 14499 number = Number(number); 14500 return Math.min(max, Math.max(min, isNaN(number) ? min : number)); 14501 }; 14502 14503 /** 14504 * The base functionality for a slider. Can be vertical or horizontal. 14505 * For instance the volume bar or the seek bar on a video is a slider. 14506 * 14507 * @extends Component 14508 */ 14509 14510 var Slider = /*#__PURE__*/function (_Component) { 14511 inheritsLoose(Slider, _Component); 14512 14513 /** 14514 * Create an instance of this class 14515 * 14516 * @param {Player} player 14517 * The `Player` that this class should be attached to. 14518 * 14519 * @param {Object} [options] 14520 * The key/value store of player options. 14521 */ 14522 function Slider(player, options) { 14523 var _this; 14524 14525 _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for 14526 14527 _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type 14528 14529 _this.vertical(!!_this.options_.vertical); 14530 14531 _this.enable(); 14532 14533 return _this; 14534 } 14535 /** 14536 * Are controls are currently enabled for this slider or not. 14537 * 14538 * @return {boolean} 14539 * true if controls are enabled, false otherwise 14540 */ 14541 14542 14543 var _proto = Slider.prototype; 14544 14545 _proto.enabled = function enabled() { 14546 return this.enabled_; 14547 } 14548 /** 14549 * Enable controls for this slider if they are disabled 14550 */ 14551 ; 14552 14553 _proto.enable = function enable() { 14554 if (this.enabled()) { 14555 return; 14556 } 14557 14558 this.on('mousedown', this.handleMouseDown); 14559 this.on('touchstart', this.handleMouseDown); 14560 this.on('keydown', this.handleKeyDown); 14561 this.on('click', this.handleClick); // TODO: deprecated, controlsvisible does not seem to be fired 14562 14563 this.on(this.player_, 'controlsvisible', this.update); 14564 14565 if (this.playerEvent) { 14566 this.on(this.player_, this.playerEvent, this.update); 14567 } 14568 14569 this.removeClass('disabled'); 14570 this.setAttribute('tabindex', 0); 14571 this.enabled_ = true; 14572 } 14573 /** 14574 * Disable controls for this slider if they are enabled 14575 */ 14576 ; 14577 14578 _proto.disable = function disable() { 14579 if (!this.enabled()) { 14580 return; 14581 } 14582 14583 var doc = this.bar.el_.ownerDocument; 14584 this.off('mousedown', this.handleMouseDown); 14585 this.off('touchstart', this.handleMouseDown); 14586 this.off('keydown', this.handleKeyDown); 14587 this.off('click', this.handleClick); 14588 this.off(this.player_, 'controlsvisible', this.update); 14589 this.off(doc, 'mousemove', this.handleMouseMove); 14590 this.off(doc, 'mouseup', this.handleMouseUp); 14591 this.off(doc, 'touchmove', this.handleMouseMove); 14592 this.off(doc, 'touchend', this.handleMouseUp); 14593 this.removeAttribute('tabindex'); 14594 this.addClass('disabled'); 14595 14596 if (this.playerEvent) { 14597 this.off(this.player_, this.playerEvent, this.update); 14598 } 14599 14600 this.enabled_ = false; 14601 } 14602 /** 14603 * Create the `Slider`s DOM element. 14604 * 14605 * @param {string} type 14606 * Type of element to create. 14607 * 14608 * @param {Object} [props={}] 14609 * List of properties in Object form. 14610 * 14611 * @param {Object} [attributes={}] 14612 * list of attributes in Object form. 14613 * 14614 * @return {Element} 14615 * The element that gets created. 14616 */ 14617 ; 14618 14619 _proto.createEl = function createEl(type, props, attributes) { 14620 if (props === void 0) { 14621 props = {}; 14622 } 14623 14624 if (attributes === void 0) { 14625 attributes = {}; 14626 } 14627 14628 // Add the slider element class to all sub classes 14629 props.className = props.className + ' vjs-slider'; 14630 props = assign({ 14631 tabIndex: 0 14632 }, props); 14633 attributes = assign({ 14634 'role': 'slider', 14635 'aria-valuenow': 0, 14636 'aria-valuemin': 0, 14637 'aria-valuemax': 100, 14638 'tabIndex': 0 14639 }, attributes); 14640 return _Component.prototype.createEl.call(this, type, props, attributes); 14641 } 14642 /** 14643 * Handle `mousedown` or `touchstart` events on the `Slider`. 14644 * 14645 * @param {EventTarget~Event} event 14646 * `mousedown` or `touchstart` event that triggered this function 14647 * 14648 * @listens mousedown 14649 * @listens touchstart 14650 * @fires Slider#slideractive 14651 */ 14652 ; 14653 14654 _proto.handleMouseDown = function handleMouseDown(event) { 14655 var doc = this.bar.el_.ownerDocument; 14656 14657 if (event.type === 'mousedown') { 14658 event.preventDefault(); 14659 } // Do not call preventDefault() on touchstart in Chrome 14660 // to avoid console warnings. Use a 'touch-action: none' style 14661 // instead to prevent unintented scrolling. 14662 // https://developers.google.com/web/updates/2017/01/scrolling-intervention 14663 14664 14665 if (event.type === 'touchstart' && !IS_CHROME) { 14666 event.preventDefault(); 14667 } 14668 14669 blockTextSelection(); 14670 this.addClass('vjs-sliding'); 14671 /**
14672 * Triggered when the slider is in an active state 14673 * 14674 * @event Slider#slideractive 14675 * @type {EventTarget~Event} 14676 */ 14677 14678 this.trigger('slideractive'); 14679 this.on(doc, 'mousemove', this.handleMouseMove); 14680 this.on(doc, 'mouseup', this.handleMouseUp); 14681 this.on(doc, 'touchmove', this.handleMouseMove); 14682 this.on(doc, 'touchend', this.handleMouseUp); 14683 this.handleMouseMove(event); 14684 } 14685 /** 14686 * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. 14687 * The `mousemove` and `touchmove` events will only only trigger this function during 14688 * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and 14689 * {@link Slider#handleMouseUp}. 14690 * 14691 * @param {EventTarget~Event} event 14692 * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered 14693 * this function 14694 * 14695 * @listens mousemove 14696 * @listens touchmove 14697 */ 14698 ; 14699 14700 _proto.handleMouseMove = function handleMouseMove(event) {} 14701 /** 14702 * Handle `mouseup` or `touchend` events on the `Slider`. 14703 * 14704 * @param {EventTarget~Event} event 14705 * `mouseup` or `touchend` event that triggered this function. 14706 * 14707 * @listens touchend 14708 * @listens mouseup 14709 * @fires Slider#sliderinactive 14710 */ 14711 ; 14712 14713 _proto.handleMouseUp = function handleMouseUp() { 14714 var doc = this.bar.el_.ownerDocument; 14715 unblockTextSelection(); 14716 this.removeClass('vjs-sliding'); 14717 /** 14718 * Triggered when the slider is no longer in an active state. 14719 * 14720 * @event Slider#sliderinactive 14721 * @type {EventTarget~Event} 14722 */ 14723 14724 this.trigger('sliderinactive'); 14725 this.off(doc, 'mousemove', this.handleMouseMove); 14726 this.off(doc, 'mouseup', this.handleMouseUp); 14727 this.off(doc, 'touchmove', this.handleMouseMove); 14728 this.off(doc, 'touchend', this.handleMouseUp); 14729 this.update(); 14730 } 14731 /** 14732 * Update the progress bar of the `Slider`. 14733 * 14734 * @return {number} 14735 * The percentage of progress the progress bar represents as a 14736 * number from 0 to 1. 14737 */ 14738 ; 14739 14740 _proto.update = function update() { 14741 var _this2 = this; 14742 14743 // In VolumeBar init we have a setTimeout for update that pops and update 14744 // to the end of the execution stack. The player is destroyed before then 14745 // update will cause an error 14746 // If there's no bar... 14747 if (!this.el_ || !this.bar) { 14748 return; 14749 } // clamp progress between 0 and 1 14750 // and only round to four decimal places, as we round to two below 14751 14752 14753 var progress = this.getProgress(); 14754 14755 if (progress === this.progress_) { 14756 return progress; 14757 } 14758 14759 this.progress_ = progress; 14760 this.requestAnimationFrame(function () { 14761 // Set the new bar width or height 14762 var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value 14763 14764 _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%'; 14765 }); 14766 return progress; 14767 } 14768 /** 14769 * Get the percentage of the bar that should be filled 14770 * but clamped and rounded. 14771 * 14772 * @return {number} 14773 * percentage filled that the slider is 14774 */ 14775 ; 14776 14777 _proto.getProgress = function getProgress() { 14778 return Number(clamp(this.getPercent(), 0, 1).toFixed(4)); 14779 } 14780 /** 14781 * Calculate distance for slider 14782 * 14783 * @param {EventTarget~Event} event 14784 * The event that caused this function to run. 14785 * 14786 * @return {number} 14787 * The current position of the Slider. 14788 * - position.x for vertical `Slider`s 14789 * - position.y for horizontal `Slider`s 14790 */ 14791 ; 14792 14793 _proto.calculateDistance = function calculateDistance(event) { 14794 var position = getPointerPosition(this.el_, event); 14795 14796 if (this.vertical()) { 14797 return position.y; 14798 } 14799 14800 return position.x; 14801 } 14802 /** 14803 * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down 14804 * arrow keys. This function will only be called when the slider has focus. See 14805 * {@link Slider#handleFocus} and {@link Slider#handleBlur}. 14806 * 14807 * @param {EventTarget~Event} event 14808 * the `keydown` event that caused this function to run. 14809 * 14810 * @listens keydown 14811 */ 14812 ; 14813 14814 _proto.handleKeyDown = function handleKeyDown(event) { 14815 // Left and Down Arrows 14816 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 14817 event.preventDefault(); 14818 event.stopPropagation(); 14819 this.stepBack(); // Up and Right Arrows 14820 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 14821 event.preventDefault(); 14822 event.stopPropagation(); 14823 this.stepForward(); 14824 } else { 14825 // Pass keydown handling up for unsupported keys 14826 _Component.prototype.handleKeyDown.call(this, event); 14827 } 14828 } 14829 /** 14830 * Listener for click events on slider, used to prevent clicks 14831 * from bubbling up to parent elements like button menus. 14832 * 14833 * @param {Object} event 14834 * Event that caused this object to run 14835 */ 14836 ; 14837 14838 _proto.handleClick = function handleClick(event) { 14839 event.stopPropagation(); 14840 event.preventDefault(); 14841 } 14842 /** 14843 * Get/set if slider is horizontal for vertical 14844 * 14845 * @param {boolean} [bool] 14846 * - true if slider is vertical, 14847 * - false is horizontal 14848 * 14849 * @return {boolean} 14850 * - true if slider is vertical, and getting 14851 * - false if the slider is horizontal, and getting 14852 */ 14853 ; 14854 14855 _proto.vertical = function vertical(bool) { 14856 if (bool === undefined) { 14857 return this.vertical_ || false; 14858 } 14859 14860 this.vertical_ = !!bool; 14861 14862 if (this.vertical_) { 14863 this.addClass('vjs-slider-vertical'); 14864 } else { 14865 this.addClass('vjs-slider-horizontal'); 14866 } 14867 }; 14868 14869 return Slider; 14870 }(Component); 14871 14872 Component.registerComponent('Slider', Slider); 14873 14874 var percentify = function percentify(time, end) { 14875 return clamp(time / end * 100, 0, 100).toFixed(2) + '%'; 14876 }; 14877 /** 14878 * Shows loading progress 14879 * 14880 * @extends Component 14881 */ 14882 14883 14884 var LoadProgressBar = /*#__PURE__*/function (_Component) { 14885 inheritsLoose(LoadProgressBar, _Component); 14886 14887 /** 14888 * Creates an instance of this class. 14889 * 14890 * @param {Player} player 14891 * The `Player` that this class should be attached to. 14892 * 14893 * @param {Object} [options] 14894 * The key/value store of player options. 14895 */ 14896 function LoadProgressBar(player, options) { 14897 var _this; 14898 14899 _this = _Component.call(this, player, options) || this; 14900 _this.partEls_ = []; 14901 14902 _this.on(player, 'progress', _this.update); 14903 14904 return _this; 14905 } 14906 /** 14907 * Create the `Component`'s DOM element 14908 * 14909 * @return {Element} 14910 * The element that was created. 14911 */ 14912 14913 14914 var _proto = LoadProgressBar.prototype; 14915 14916 _proto.createEl = function createEl$1() { 14917 var el = _Component.prototype.createEl.call(this, 'div', { 14918 className: 'vjs-load-progress' 14919 }); 14920 14921 var wrapper = createEl('span', { 14922 className: 'vjs-control-text' 14923 }); 14924 var loadedText = createEl('span', { 14925 textContent: this.localize('Loaded') 14926 }); 14927 var separator = document.createTextNode(': '); 14928 this.percentageEl_ = createEl('span', { 14929 className: 'vjs-control-text-loaded-percentage', 14930 textContent: '0%' 14931 }); 14932 el.appendChild(wrapper); 14933 wrapper.appendChild(loadedText); 14934 wrapper.appendChild(separator); 14935 wrapper.appendChild(this.percentageEl_); 14936 return el; 14937 }; 14938 14939 _proto.dispose = function dispose() { 14940 this.partEls_ = null; 14941 this.percentageEl_ = null; 14942 14943 _Component.prototype.dispose.call(this); 14944 } 14945 /** 14946 * Update progress bar 14947 * 14948 * @param {EventTarget~Event} [event] 14949 * The `progress` event that caused this function to run. 14950 * 14951 * @listens Player#progress 14952 */ 14953 ; 14954 14955 _proto.update = function update(event) { 14956 var _this2 = this; 14957 14958 this.requestAnimationFrame(function () { 14959 var liveTracker = _this2.player_.liveTracker; 14960 14961 var buffered = _this2.player_.buffered(); 14962 14963 var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration(); 14964 14965 var bufferedEnd = _this2.player_.bufferedEnd(); 14966 14967 var children = _this2.partEls_; 14968 var percent = percentify(bufferedEnd, duration); 14969 14970 if (_this2.percent_ !== percent) { 14971 // update the width of the progress bar 14972 _this2.el_.style.width = percent; // update the control-text 14973 14974 textContent(_this2.percentageEl_, percent); 14975 _this2.percent_ = percent; 14976 } // add child elements to represent the individual buffered time ranges 14977 14978 14979 for (var i = 0; i < buffered.length; i++) { 14980 var start = buffered.start(i); 14981 var end = buffered.end(i); 14982 var part = children[i]; 14983 14984 if (!part) { 14985 part = _this2.el_.appendChild(createEl()); 14986 children[i] = part;
14987 } // only update if changed 14988 14989 14990 if (part.dataset.start === start && part.dataset.end === end) { 14991 continue; 14992 } 14993 14994 part.dataset.start = start; 14995 part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd) 14996 14997 part.style.left = percentify(start, bufferedEnd); 14998 part.style.width = percentify(end - start, bufferedEnd); 14999 } // remove unused buffered range elements 15000 15001 15002 for (var _i = children.length; _i > buffered.length; _i--) { 15003 _this2.el_.removeChild(children[_i - 1]); 15004 } 15005 15006 children.length = buffered.length; 15007 }); 15008 }; 15009 15010 return LoadProgressBar; 15011 }(Component); 15012 15013 Component.registerComponent('LoadProgressBar', LoadProgressBar); 15014 15015 /** 15016 * Time tooltips display a time above the progress bar. 15017 * 15018 * @extends Component 15019 */ 15020 15021 var TimeTooltip = /*#__PURE__*/function (_Component) { 15022 inheritsLoose(TimeTooltip, _Component); 15023 15024 /** 15025 * Creates an instance of this class. 15026 * 15027 * @param {Player} player 15028 * The {@link Player} that this class should be attached to. 15029 * 15030 * @param {Object} [options] 15031 * The key/value store of player options. 15032 */ 15033 function TimeTooltip(player, options) { 15034 var _this; 15035 15036 _this = _Component.call(this, player, options) || this; 15037 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15038 return _this; 15039 } 15040 /** 15041 * Create the time tooltip DOM element 15042 * 15043 * @return {Element} 15044 * The element that was created. 15045 */ 15046 15047 15048 var _proto = TimeTooltip.prototype; 15049 15050 _proto.createEl = function createEl() { 15051 return _Component.prototype.createEl.call(this, 'div', { 15052 className: 'vjs-time-tooltip' 15053 }, { 15054 'aria-hidden': 'true' 15055 }); 15056 } 15057 /** 15058 * Updates the position of the time tooltip relative to the `SeekBar`. 15059 * 15060 * @param {Object} seekBarRect 15061 * The `ClientRect` for the {@link SeekBar} element. 15062 * 15063 * @param {number} seekBarPoint 15064 * A number from 0 to 1, representing a horizontal reference point 15065 * from the left edge of the {@link SeekBar} 15066 */ 15067 ; 15068 15069 _proto.update = function update(seekBarRect, seekBarPoint, content) { 15070 var tooltipRect = getBoundingClientRect(this.el_); 15071 var playerRect = getBoundingClientRect(this.player_.el()); 15072 var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available 15073 // for example, if the player isn't in the DOM for testing 15074 15075 if (!playerRect || !tooltipRect) { 15076 return; 15077 } // This is the space left of the `seekBarPoint` available within the bounds 15078 // of the player. We calculate any gap between the left edge of the player 15079 // and the left edge of the `SeekBar` and add the number of pixels in the 15080 // `SeekBar` before hitting the `seekBarPoint` 15081 15082 15083 var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds 15084 // of the player. We calculate the number of pixels from the `seekBarPoint` 15085 // to the right edge of the `SeekBar` and add to that any gap between the 15086 // right edge of the `SeekBar` and the player. 15087 15088 var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled 15089 // further to the right to center it over the `seekBarPoint`. 15090 15091 var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on 15092 // the results of the space calculations above. 15093 15094 if (spaceLeftOfPoint < pullTooltipBy) { 15095 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 15096 }
15096 else if (spaceRightOfPoint < pullTooltipBy) { 15097 pullTooltipBy = spaceRightOfPoint; 15098 } // Due to the imprecision of decimal/ratio based calculations and varying 15099 // rounding behaviors, there are cases where the spacing adjustment is off 15100 // by a pixel or two. This adds insurance to these calculations. 15101 15102 15103 if (pullTooltipBy < 0) { 15104 pullTooltipBy = 0; 15105 } else if (pullTooltipBy > tooltipRect.width) { 15106 pullTooltipBy = tooltipRect.width; 15107 } 15108 15109 this.el_.style.right = "-" + pullTooltipBy + "px"; 15110 this.write(content); 15111 } 15112 /** 15113 * Write the time to the tooltip DOM element. 15114 * 15115 * @param {string} content 15116 * The formatted time for the tooltip. 15117 */ 15118 ; 15119 15120 _proto.write = function write(content) { 15121 textContent(this.el_, content); 15122 } 15123 /** 15124 * Updates the position of the time tooltip relative to the `SeekBar`. 15125 * 15126 * @param {Object} seekBarRect 15127 * The `ClientRect` for the {@link SeekBar} element. 15128 * 15129 * @param {number} seekBarPoint 15130 * A number from 0 to 1, representing a horizontal reference point 15131 * from the left edge of the {@link SeekBar} 15132 * 15133 * @param {number} time 15134 * The time to update the tooltip to, not used during live playback 15135 * 15136 * @param {Function} cb 15137 * A function that will be called during the request animation frame 15138 * for tooltips that need to do additional animations from the default 15139 */ 15140 ; 15141 15142 _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) { 15143 var _this2 = this; 15144 15145 // If there is an existing rAF ID, cancel it so we don't over-queue. 15146 if (this.rafId_) { 15147 this.cancelAnimationFrame(this.rafId_); 15148 } 15149 15150 this.rafId_ = this.requestAnimationFrame(function () { 15151 var content; 15152 15153 var duration = _this2.player_.duration(); 15154 15155 if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) { 15156 var liveWindow = _this2.player_.liveTracker.liveWindow(); 15157 15158 var secondsBehind = liveWindow - seekBarPoint * liveWindow; 15159 content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow); 15160 } else { 15161 content = formatTime(time, duration); 15162 } 15163 15164 _this2.update(seekBarRect, seekBarPoint, content); 15165 15166 if (cb) { 15167 cb(); 15168 } 15169 }); 15170 }; 15171 15172 return TimeTooltip; 15173 }(Component); 15174 15175 Component.registerComponent('TimeTooltip', TimeTooltip); 15176 15177 /** 15178 * Used by {@link SeekBar} to display media playback progress as part of the 15179 * {@link ProgressControl}. 15180 * 15181 * @extends Component 15182 */ 15183 15184 var PlayProgressBar = /*#__PURE__*/function (_Component) { 15185 inheritsLoose(PlayProgressBar, _Component); 15186 15187 /** 15188 * Creates an instance of this class. 15189 * 15190 * @param {Player} player 15191 * The {@link Player} that this class should be attached to. 15192 * 15193 * @param {Object} [options] 15194 * The key/value store of player options. 15195 */ 15196 function PlayProgressBar(player, options) { 15197 var _this; 15198 15199 _this = _Component.call(this, player, options) || this; 15200 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15201 return _this; 15202 } 15203 /** 15204 * Create the the DOM element for this class. 15205 * 15206 * @return {Element} 15207 * The element that was created. 15208 */ 15209 15210 15211 var _proto = PlayProgressBar.prototype; 15212 15213 _proto.createEl = function createEl() { 15214 return _Component.prototype.createEl.call(this, 'div', { 15215 className: 'vjs-play-progress vjs-slider-bar' 15216 }, { 15217 'aria-hidden': 'true' 15218 }); 15219 } 15220 /** 15221 * Enqueues updates to its own DOM as well as the DOM of its 15222 * {@link TimeTooltip} child. 15223 * 15224 * @param {Object} seekBarRect 15225 * The `ClientRect` for the {@link SeekBar} element. 15226 * 15227 * @param {number} seekBarPoint 15228 * A number from 0 to 1, representing a horizontal reference point 15229 * from the left edge of the {@link SeekBar} 15230 */ 15231 ; 15232 15233 _proto.update = function update(seekBarRect, seekBarPoint) { 15234 var timeTooltip = this.getChild('timeTooltip'); 15235 15236 if (!timeTooltip) { 15237 return; 15238 } 15239 15240 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 15241 timeTooltip.updateTime(seekBarRect, seekBarPoint, time); 15242 }; 15243 15244 return PlayProgressBar; 15245 }(Component); 15246 /** 15247 * Default options for {@link PlayProgressBar}. 15248 * 15249 * @type {Object} 15250 * @private 15251 */ 15252 15253 15254 PlayProgressBar.prototype.options_ = { 15255 children: [] 15256 }; // Time tooltips should not be added to a player on mobile devices 15257 15258 if (!IS_IOS && !IS_ANDROID) { 15259 PlayProgressBar.prototype.options_.children.push('timeTooltip'); 15260 } 15261 15262 Component.registerComponent('PlayProgressBar', PlayProgressBar); 15263 15264 /** 15265 * The {@link MouseTimeDisplay} component tracks mouse movement over the 15266 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} 15267 * indicating the time which is represented by a given point in the 15268 * {@link ProgressControl}. 15269 * 15270 * @extends Component 15271 */ 15272 15273 var MouseTimeDisplay = /*#__PURE__*/function (_Component) { 15274 inheritsLoose(MouseTimeDisplay, _Component); 15275 15276 /** 15277 * Creates an instance of this class. 15278 * 15279 * @param {Player} player 15280 * The {@link Player} that this class should be attached to. 15281 * 15282 * @param {Object} [options] 15283 * The key/value store of player options. 15284 */ 15285 function MouseTimeDisplay(player, options) { 15286 var _this; 15287 15288 _this = _Component.call(this, player, options) || this; 15289 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15290 return _this; 15291 } 15292 /** 15293 * Create the DOM element for this class. 15294 * 15295 * @return {Element} 15296 * The element that was created. 15297 */ 15298 15299 15300 var _proto = MouseTimeDisplay.prototype; 15301 15302 _proto.createEl = function createEl() { 15303 return _Component.prototype.createEl.call(this, 'div', { 15304 className: 'vjs-mouse-display' 15305 }); 15306 } 15307 /** 15308 * Enqueues updates to its own DOM as well as the DOM of its 15309 * {@link TimeTooltip} child. 15310 * 15311 * @param {Object} seekBarRect 15312 * The `ClientRect` for the {@link SeekBar} element. 15313 * 15314 * @param {number} seekBarPoint 15315 * A number from 0 to 1, representing a horizontal reference point 15316 * from the left edge of the {@link SeekBar} 15317 */ 15318 ; 15319 15320 _proto.update = function update(seekBarRect, seekBarPoint) { 15321 var _this2 = this; 15322 15323 var time = seekBarPoint * this.player_.duration(); 15324 this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () { 15325 _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px"; 15326 }); 15327 }; 15328 15329 return MouseTimeDisplay; 15330 }(Component); 15331 /** 15332 * Default options for `MouseTimeDisplay` 15333 * 15334 * @type {Object} 15335 * @private 15336 */ 15337 15338 15339 MouseTimeDisplay.prototype.options_ = { 15340 children: ['timeTooltip'] 15341 }; 15342 Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); 15343 15344 var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline. 15345 15346 var PAGE_KEY_MULTIPLIER = 12; 15347 /** 15348 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} 15349 * as its `bar`. 15350 * 15351 * @extends Slider 15352 */ 15353
15354 var SeekBar = /*#__PURE__*/function (_Slider) { 15355 inheritsLoose(SeekBar, _Slider); 15356 15357 /** 15358 * Creates an instance of this class. 15359 * 15360 * @param {Player} player 15361 * The `Player` that this class should be attached to. 15362 * 15363 * @param {Object} [options] 15364 * The key/value store of player options. 15365 */ 15366 function SeekBar(player, options) { 15367 var _this; 15368 15369 _this = _Slider.call(this, player, options) || this; 15370 15371 _this.setEventHandlers_(); 15372 15373 return _this; 15374 } 15375 /** 15376 * Sets the event handlers 15377 * 15378 * @private 15379 */ 15380 15381 15382 var _proto = SeekBar.prototype; 15383 15384 _proto.setEventHandlers_ = function setEventHandlers_() { 15385 this.update_ = bind(this, this.update); 15386 this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL); 15387 this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update); 15388 15389 if (this.player_.liveTracker) { 15390 this.on(this.player_.liveTracker, 'liveedgechange', this.update); 15391 } // when playing, let's ensure we smoothly update the play progress bar 15392 // via an interval 15393 15394 15395 this.updateInterval = null; 15396 this.on(this.player_, ['playing'], this.enableInterval_); 15397 this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden, 15398 // also, this causes the CPU to spike and eventually crash the page on IE11. 15399 15400 if ('hidden' in document && 'visibilityState' in document) { 15401 this.on(document, 'visibilitychange', this.toggleVisibility_); 15402 } 15403 }; 15404 15405 _proto.toggleVisibility_ = function toggleVisibility_(e) { 15406 if (document.hidden) { 15407 this.disableInterval_(e); 15408 } else { 15409 this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP 15410 15411 this.update(); 15412 } 15413 }; 15414 15415 _proto.enableInterval_ = function enableInterval_() { 15416 if (this.updateInterval) { 15417 return; 15418 } 15419 15420 this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL); 15421 }; 15422 15423 _proto.disableInterval_ = function disableInterval_(e) { 15424 if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e && e.type !== 'ended') { 15425 return; 15426 } 15427 15428 if (!this.updateInterval) { 15429 return; 15430 } 15431 15432 this.clearInterval(this.updateInterval); 15433 this.updateInterval = null; 15434 } 15435 /** 15436 * Create the `Component`'s DOM element 15437 * 15438 * @return {Element} 15439 * The element that was created. 15440 */ 15441 ; 15442 15443 _proto.createEl = function createEl() { 15444 return _Slider.prototype.createEl.call(this, 'div', { 15445 className: 'vjs-progress-holder' 15446 }, { 15447 'aria-label': this.localize('Progress Bar') 15448 }); 15449 } 15450 /** 15451 * This function updates the play progress bar and accessibility 15452 * attributes to whatever is passed in. 15453 * 15454 * @param {EventTarget~Event} [event] 15455 * The `timeupdate` or `ended` event that caused this to run. 15456 * 15457 * @listens Player#timeupdate 15458 * 15459 * @return {number} 15460 * The current percent at a number from 0-1 15461 */ 15462 ; 15463 15464 _proto.update = function update(event) { 15465 var _this2 = this; 15466 15467 var percent = _Slider.prototype.update.call(this); 15468 15469 this.requestAnimationFrame(function () { 15470 var currentTime = _this2.player_.ended() ? _this2.player_.duration() : _this2.getCurrentTime_(); 15471 var liveTracker = _this2.player_.liveTracker; 15472 15473 var duration = _this2.player_.duration(); 15474 15475 if (liveTracker && liveTracker.isLive()) { 15476 duration = _this2.player_.liveTracker.liveCurrentTime(); 15477 } 15478 15479 if (_this2.percent_ !== percent) { 15480 // machine readable value of progress bar (percentage complete) 15481 _this2.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); 15482 15483 _this2.percent_ = percent; 15484 } 15485 15486 if (_this2.currentTime_ !== currentTime || _this2.duration_ !== duration) { 15487 // human readable value of progress bar (time complete) 15488 _this2.el_.setAttribute('aria-valuetext', _this2.localize('progress bar timing: currentTime={1} duration={2}
15488', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); 15489 15490 _this2.currentTime_ = currentTime; 15491 _this2.duration_ = duration; 15492 } // update the progress bar time tooltip with the current time 15493 15494 15495 if (_this2.bar) { 15496 _this2.bar.update(getBoundingClientRect(_this2.el()), _this2.getProgress()); 15497 } 15498 }); 15499 return percent; 15500 } 15501 /** 15502 * Get the value of current time but allows for smooth scrubbing, 15503 * when player can't keep up. 15504 * 15505 * @return {number} 15506 * The current time value to display 15507 * 15508 * @private 15509 */ 15510 ; 15511 15512 _proto.getCurrentTime_ = function getCurrentTime_() { 15513 return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 15514 } 15515 /** 15516 * Get the percentage of media played so far. 15517 * 15518 * @return {number} 15519 * The percentage of media played so far (0 to 1). 15520 */ 15521 ; 15522 15523 _proto.getPercent = function getPercent() { 15524 var currentTime = this.getCurrentTime_(); 15525 var percent; 15526 var liveTracker = this.player_.liveTracker; 15527 15528 if (liveTracker && liveTracker.isLive()) { 15529 percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge 15530 15531 if (liveTracker.atLiveEdge()) { 15532 percent = 1; 15533 } 15534 } else { 15535 percent = currentTime / this.player_.duration(); 15536 } 15537 15538 return percent; 15539 } 15540 /** 15541 * Handle mouse down on seek bar 15542 * 15543 * @param {EventTarget~Event} event 15544 * The `mousedown` event that caused this to run. 15545 * 15546 * @listens mousedown 15547 */ 15548 ; 15549 15550 _proto.handleMouseDown = function handleMouseDown(event) { 15551 if (!isSingleLeftClick(event)) { 15552 return; 15553 } // Stop event propagation to prevent double fire in progress-control.js 15554 15555 15556 event.stopPropagation(); 15557 this.player_.scrubbing(true); 15558 this.videoWasPlaying = !this.player_.paused(); 15559 this.player_.pause(); 15560 15561 _Slider.prototype.handleMouseDown.call(this, event); 15562 } 15563 /** 15564 * Handle mouse move on seek bar 15565 * 15566 * @param {EventTarget~Event} event 15567 * The `mousemove` event that caused this to run. 15568 * 15569 * @listens mousemove 15570 */ 15571 ; 15572 15573 _proto.handleMouseMove = function handleMouseMove(event) { 15574 if (!isSingleLeftClick(event)) { 15575 return; 15576 } 15577 15578 var newTime; 15579 var distance = this.calculateDistance(event); 15580 var liveTracker = this.player_.liveTracker; 15581 15582 if (!liveTracker || !liveTracker.isLive()) { 15583 newTime = distance * this.player_.duration(); // Don't let video end while scrubbing. 15584 15585 if (newTime === this.player_.duration()) { 15586 newTime = newTime - 0.1; 15587 } 15588 } else { 15589 if (distance >= 0.99) { 15590 liveTracker.seekToLiveEdge(); 15591 return; 15592 } 15593 15594 var seekableStart = liveTracker.seekableStart(); 15595 var seekableEnd = liveTracker.liveCurrentTime(); 15596 newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing. 15597 15598 if (newTime >= seekableEnd) { 15599 newTime = seekableEnd; 15600 } // Compensate for precision differences so that currentTime is not less 15601 // than seekable start 15602 15603 15604 if (newTime <= seekableStart) { 15605 newTime = seekableStart + 0.1; 15606 } // On android seekableEnd can be Infinity sometimes, 15607 // this will cause newTime to be Infinity, which is 15608 // not a valid currentTime. 15609 15610 15611 if (newTime === Infinity) { 15612 return; 15613 } 15614 } // Set new time (tell player to seek to new time) 15615 15616 15617 this.player_.currentTime(newTime); 15618 }; 15619 15620 _proto.enable = function enable() { 15621 _Slider.prototype.enable.call(this); 15622 15623 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 15624 15625 if (!mouseTimeDisplay) { 15626 return; 15627 } 15628 15629 mouseTimeDisplay.show(); 15630 }; 15631 15632 _proto.disable = function disable() { 15633 _Slider.prototype.disable.call(this); 15634 15635 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 15636 15637 if (!mouseTimeDisplay) { 15638 return; 15639 } 15640 15641 mouseTimeDisplay.hide(); 15642 } 15643 /** 15644 * Handle mouse up on seek bar 15645 * 15646 * @param {EventTarget~Event} event 15647 * The `mouseup` event that caused this to run. 15648 * 15649 * @listens mouseup 15650 */ 15651 ; 15652 15653 _proto.handleMouseUp = function handleMouseUp(event) { 15654 _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js 15655 15656 15657 if (event) { 15658 event.stopPropagation(); 15659 } 15660 15661 this.player_.scrubbing(false); 15662 /** 15663 * Trigger timeupdate because we're done seeking and the time has changed. 15664 * This is particularly useful for if the player is paused to time the time displays. 15665 * 15666 * @event Tech#timeupdate 15667 * @type {EventTarget~Event} 15668 */ 15669 15670 this.player_.trigger({ 15671 type: 'timeupdate', 15672 target: this, 15673 manuallyTriggered: true 15674 }); 15675 15676 if (this.videoWasPlaying) { 15677 silencePromise(this.player_.play()); 15678 } else { 15679 // We're done seeking and the time has changed. 15680 // If the player is paused, make sure we display the correct time on the seek bar. 15681 this.update_(); 15682 } 15683 } 15684 /** 15685 * Move more quickly fast forward for keyboard-only users 15686 */ 15687 ; 15688 15689 _proto.stepForward = function stepForward() { 15690 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); 15691 } 15692 /** 15693 * Move more quickly rewind for keyboard-only users 15694 */ 15695 ; 15696 15697 _proto.stepBack = function stepBack() { 15698 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); 15699 } 15700 /** 15701 * Toggles the playback state of the player 15702 * This gets called when enter or space is used on the seekbar 15703 * 15704 * @param {EventTarget~Event} event
15705 * The `keydown` event that caused this function to be called 15706 * 15707 */ 15708 ; 15709 15710 _proto.handleAction = function handleAction(event) { 15711 if (this.player_.paused()) { 15712 this.player_.play(); 15713 } else { 15714 this.player_.pause(); 15715 } 15716 } 15717 /** 15718 * Called when this SeekBar has focus and a key gets pressed down. 15719 * Supports the following keys: 15720 * 15721 * Space or Enter key fire a click event 15722 * Home key moves to start of the timeline 15723 * End key moves to end of the timeline 15724 * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline 15725 * PageDown key moves back a larger step than ArrowDown 15726 * PageUp key moves forward a large step 15727 * 15728 * @param {EventTarget~Event} event 15729 * The `keydown` event that caused this function to be called. 15730 * 15731 * @listens keydown 15732 */ 15733 ; 15734 15735 _proto.handleKeyDown = function handleKeyDown(event) { 15736 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 15737 event.preventDefault(); 15738 event.stopPropagation(); 15739 this.handleAction(event); 15740 } else if (keycode.isEventKey(event, 'Home')) { 15741 event.preventDefault(); 15742 event.stopPropagation(); 15743 this.player_.currentTime(0); 15744 } else if (keycode.isEventKey(event, 'End')) { 15745 event.preventDefault(); 15746 event.stopPropagation(); 15747 this.player_.currentTime(this.player_.duration()); 15748 } else if (/^[0-9]$/.test(keycode(event))) { 15749 event.preventDefault(); 15750 event.stopPropagation(); 15751 var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0; 15752 this.player_.currentTime(this.player_.duration() * gotoFraction); 15753 } else if (keycode.isEventKey(event, 'PgDn')) { 15754 event.preventDefault(); 15755 event.stopPropagation(); 15756 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER); 15757 } else if (keycode.isEventKey(event, 'PgUp')) { 15758 event.preventDefault(); 15759 event.stopPropagation(); 15760 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER); 15761 } else { 15762 // Pass keydown handling up for unsupported keys 15763 _Slider.prototype.handleKeyDown.call(this, event); 15764 } 15765 }; 15766 15767 _proto.dispose = function dispose() { 15768 this.disableInterval_(); 15769 this.off(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update); 15770 15771 if (this.player_.liveTracker) { 15772 this.on(this.player_.liveTracker, 'liveedgechange', this.update); 15773 } 15774 15775 this.off(this.player_, ['playing'], this.enableInterval_); 15776 this.off(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden, 15777 // also, this causes the CPU to spike and eventually crash the page on IE11. 15778 15779 if ('hidden' in document && 'visibilityState' in document) { 15780 this.off(document, 'visibilitychange', this.toggleVisibility_); 15781 } 15782 15783 _Slider.prototype.dispose.call(this); 15784 }; 15785 15786 return SeekBar; 15787 }(Slider); 15788 /** 15789 * Default options for the `SeekBar` 15790 * 15791 * @type {Object} 15792 * @private 15793 */ 15794 15795 15796 SeekBar.prototype.options_ = { 15797 children: ['loadProgressBar', 'playProgressBar'], 15798 barName: 'playProgressBar' 15799 }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices 15800 15801 if (!IS_IOS && !IS_ANDROID) { 15802 SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); 15803 } 15804 15805 Component.registerComponent('SeekBar', SeekBar); 15806 15807 /** 15808 * The Progress Control component contains the seek bar, load progress, 15809 * and play progress. 15810 * 15811 * @extends Component 15812 */ 15813 15814 var ProgressControl = /*#__PURE__*/function (_Component) { 15815 inheritsLoose(ProgressControl, _Component); 15816 15817 /** 15818 * Creates an instance of this class. 15819 * 15820 * @param {Player} player 15821 * The `Player` that this class should be attached to. 15822 * 15823 * @param {Object} [options] 15824 * The key/value store of player options. 15825 */ 15826 function ProgressControl(player, options) { 15827 var _this; 15828 15829 _this = _Component.call(this, player, options) || this; 15830 _this.handleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 15831 _this.throttledHandleMouseSeek = throttle(bind(assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL); 15832 15833 _this.enable(); 15834 15835 return _this; 15836 } 15837 /** 15838 * Create the `Component`'s DOM element 15839 * 15840 * @return {Element} 15841 * The element that was created. 15842 */ 15843 15844 15845 var _proto = ProgressControl.prototype; 15846 15847 _proto.createEl = function createEl() { 15848 return _Component.prototype.createEl.call(this, 'div', { 15849 className: 'vjs-progress-control vjs-control' 15850 }); 15851 } 15852 /** 15853 * When the mouse moves over the `ProgressControl`, the pointer position 15854 * gets passed down to the `MouseTimeDisplay` component. 15855 * 15856 * @param {EventTarget~Event} event 15857 * The `mousemove` event that caused this function to run. 15858 * 15859 * @listen mousemove 15860 */ 15861 ; 15862 15863 _proto.handleMouseMove = function handleMouseMove(event) { 15864 var seekBar = this.getChild('seekBar'); 15865 15866 if (!seekBar) { 15867 return; 15868 } 15869 15870 var playProgressBar = seekBar.getChild('playProgressBar'); 15871 var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); 15872 15873 if (!playProgressBar && !mouseTimeDisplay) { 15874 return; 15875 } 15876 15877 var seekBarEl = seekBar.el(); 15878 var seekBarRect = getBoundingClientRect(seekBarEl); 15879 var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means 15880 // that it's possible to trigger this behavior outside the boundaries of 15881 // the `SeekBar`. This ensures we stay within it at all times. 15882 15883 seekBarPoint = clamp(0, 1, seekBarPoint); 15884 15885 if (mouseTimeDisplay) { 15886 mouseTimeDisplay.update(seekBarRect, seekBarPoint); 15887 } 15888 15889 if (playProgressBar) { 15890 playProgressBar.update(seekBarRect, seekBar.getProgress()); 15891 } 15892 } 15893 /** 15894 * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. 15895 * 15896 * @method ProgressControl#throttledHandleMouseSeek 15897 * @param {EventTarget~Event} event 15898 * The `mousemove` event that caused this function to run. 15899 * 15900 * @listen mousemove 15901 * @listen touchmove 15902 */ 15903 15904 /** 15905 * Handle `mousemove` or `touchmove` events on the `ProgressControl`. 15906 * 15907 * @param {EventTarget~Event} event 15908 * `mousedown` or `touchstart` event that triggered this function 15909 * 15910 * @listens mousemove 15911 * @listens touchmove 15912 */ 15913 ; 15914 15915 _proto.handleMouseSeek = function handleMouseSeek(event) { 15916 var seekBar = this.getChild('seekBar'); 15917 15918 if (seekBar) { 15919 seekBar.handleMouseMove(event); 15920 } 15921 } 15922 /**
15923 * Are controls are currently enabled for this progress control. 15924 * 15925 * @return {boolean} 15926 * true if controls are enabled, false otherwise 15927 */ 15928 ; 15929 15930 _proto.enabled = function enabled() { 15931 return this.enabled_; 15932 } 15933 /** 15934 * Disable all controls on the progress control and its children 15935 */ 15936 ; 15937 15938 _proto.disable = function disable() { 15939 this.children().forEach(function (child) { 15940 return child.disable && child.disable(); 15941 }); 15942 15943 if (!this.enabled()) { 15944 return; 15945 } 15946 15947 this.off(['mousedown', 'touchstart'], this.handleMouseDown); 15948 this.off(this.el_, 'mousemove', this.handleMouseMove); 15949 this.handleMouseUp(); 15950 this.addClass('disabled'); 15951 this.enabled_ = false; 15952 } 15953 /** 15954 * Enable all controls on the progress control and its children 15955 */ 15956 ; 15957 15958 _proto.enable = function enable() { 15959 this.children().forEach(function (child) { 15960 return child.enable && child.enable(); 15961 }); 15962 15963 if (this.enabled()) { 15964 return; 15965 } 15966 15967 this.on(['mousedown', 'touchstart'], this.handleMouseDown); 15968 this.on(this.el_, 'mousemove', this.handleMouseMove); 15969 this.removeClass('disabled'); 15970 this.enabled_ = true; 15971 } 15972 /** 15973 * Handle `mousedown` or `touchstart` events on the `ProgressControl`. 15974 * 15975 * @param {EventTarget~Event} event 15976 * `mousedown` or `touchstart` event that triggered this function 15977 * 15978 * @listens mousedown 15979 * @listens touchstart 15980 */ 15981 ; 15982 15983 _proto.handleMouseDown = function handleMouseDown(event) { 15984 var doc = this.el_.ownerDocument; 15985 var seekBar = this.getChild('seekBar'); 15986 15987 if (seekBar) { 15988 seekBar.handleMouseDown(event); 15989 } 15990 15991 this.on(doc, 'mousemove', this.throttledHandleMouseSeek); 15992 this.on(doc, 'touchmove', this.throttledHandleMouseSeek); 15993 this.on(doc, 'mouseup', this.handleMouseUp); 15994 this.on(doc, 'touchend', this.handleMouseUp); 15995 } 15996 /** 15997 * Handle `mouseup` or `touchend` events on the `ProgressControl`. 15998 * 15999 * @param {EventTarget~Event} event 16000 * `mouseup` or `touchend` event that triggered this function. 16001 * 16002 * @listens touchend 16003 * @listens mouseup 16004 */ 16005 ; 16006 16007 _proto.handleMouseUp = function handleMouseUp(event) { 16008 var doc = this.el_.ownerDocument; 16009 var seekBar = this.getChild('seekBar'); 16010 16011 if (seekBar) { 16012 seekBar.handleMouseUp(event); 16013 } 16014 16015 this.off(doc, 'mousemove', this.throttledHandleMouseSeek); 16016 this.off(doc, 'touchmove', this.throttledHandleMouseSeek); 16017 this.off(doc, 'mouseup', this.handleMouseUp); 16018 this.off(doc, 'touchend', this.handleMouseUp); 16019 }; 16020 16021 return ProgressControl; 16022 }(Component); 16023 /** 16024 * Default options for `ProgressControl` 16025 * 16026 * @type {Object} 16027 * @private 16028 */ 16029 16030 16031 ProgressControl.prototype.options_ = { 16032 children: ['seekBar'] 16033 }; 16034 Component.registerComponent('ProgressControl', ProgressControl); 16035 16036 /** 16037 * Toggle Picture-in-Picture mode 16038 * 16039 * @extends Button 16040 */ 16041 16042 var PictureInPictureToggle = /*#__PURE__*/function (_Button) { 16043 inheritsLoose(PictureInPictureToggle, _Button); 16044 16045 /** 16046 * Creates an instance of this class. 16047 * 16048 * @param {Player} player 16049 * The `Player` that this class should be attached to. 16050 * 16051 * @param {Object} [options] 16052 * The key/value store of player options. 16053 * 16054 * @listens Player#enterpictureinpicture 16055 * @listens Player#leavepictureinpicture 16056 */ 16057 function PictureInPictureToggle(player, options) { 16058 var _this; 16059 16060 _this = _Button.call(this, player, options) || this; 16061 16062 _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], _this.handlePictureInPictureChange); // TODO: Activate button on player loadedmetadata event. 16063 // TODO: Deactivate button on player emptied event. 16064 // TODO: Deactivate button if disablepictureinpicture attribute is present. 16065 16066 16067 if (!document.pictureInPictureEnabled) { 16068 _this.disable(); 16069 } 16070 16071 return _this; 16072 } 16073 /** 16074 * Builds the default DOM `className`. 16075 * 16076 * @return {string} 16077 * The DOM `className` for this object. 16078 */ 16079 16080 16081 var _proto = PictureInPictureToggle.prototype; 16082 16083 _proto.buildCSSClass = function buildCSSClass() { 16084 return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this); 16085 } 16086 /** 16087 * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly. 16088 * 16089 * @param {EventTarget~Event} [event] 16090 * The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be 16091 * called. 16092 * 16093 * @listens Player#enterpictureinpicture 16094 * @listens Player#leavepictureinpicture 16095 */ 16096 ; 16097 16098 _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) { 16099 if (this.player_.isInPictureInPicture()) { 16100 this.controlText('Exit Picture-in-Picture'); 16101 } else { 16102 this.controlText('Picture-in-Picture'); 16103 } 16104 } 16105 /** 16106 * This gets called when an `PictureInPictureToggle` is "clicked". See 16107 * {@link ClickableComponent} for more detailed information on what a click can be. 16108 * 16109 * @param {EventTarget~Event} [event]
16110 * The `keydown`, `tap`, or `click` event that caused this function to be 16111 * called. 16112 * 16113 * @listens tap 16114 * @listens click 16115 */ 16116 ; 16117 16118 _proto.handleClick = function handleClick(event) { 16119 if (!this.player_.isInPictureInPicture()) { 16120 this.player_.requestPictureInPicture(); 16121 } else { 16122 this.player_.exitPictureInPicture(); 16123 } 16124 }; 16125 16126 return PictureInPictureToggle; 16127 }(Button); 16128 /** 16129 * The text that should display over the `PictureInPictureToggle`s controls. Added for localization. 16130 * 16131 * @type {string} 16132 * @private 16133 */ 16134 16135 16136 PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture'; 16137 Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle); 16138 16139 /** 16140 * Toggle fullscreen video 16141 * 16142 * @extends Button 16143 */ 16144 16145 var FullscreenToggle = /*#__PURE__*/function (_Button) { 16146 inheritsLoose(FullscreenToggle, _Button); 16147 16148 /** 16149 * Creates an instance of this class. 16150 * 16151 * @param {Player} player 16152 * The `Player` that this class should be attached to. 16153 * 16154 * @param {Object} [options] 16155 * The key/value store of player options. 16156 */ 16157 function FullscreenToggle(player, options) { 16158 var _this; 16159 16160 _this = _Button.call(this, player, options) || this; 16161 16162 _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); 16163 16164 if (document[player.fsApi_.fullscreenEnabled] === false) { 16165 _this.disable(); 16166 } 16167 16168 return _this; 16169 } 16170 /** 16171 * Builds the default DOM `className`. 16172 * 16173 * @return {string} 16174 * The DOM `className` for this object. 16175 */ 16176 16177 16178 var _proto = FullscreenToggle.prototype; 16179 16180 _proto.buildCSSClass = function buildCSSClass() { 16181 return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this); 16182 } 16183 /** 16184 * Handles fullscreenchange on the player and change control text accordingly. 16185 * 16186 * @param {EventTarget~Event} [event] 16187 * The {@link Player#fullscreenchange} event that caused this function to be 16188 * called. 16189 * 16190 * @listens Player#fullscreenchange 16191 */ 16192 ; 16193 16194 _proto.handleFullscreenChange = function handleFullscreenChange(event) { 16195 if (this.player_.isFullscreen()) { 16196 this.controlText('Non-Fullscreen'); 16197 } else { 16198 this.controlText('Fullscreen'); 16199 } 16200 } 16201 /** 16202 * This gets called when an `FullscreenToggle` is "clicked". See 16203 * {@link ClickableComponent} for more detailed information on what a click can be. 16204 * 16205 * @param {EventTarget~Event} [event] 16206 * The `keydown`, `tap`, or `click` event that caused this function to be 16207 * called. 16208 * 16209 * @listens tap 16210 * @listens click 16211 */ 16212 ; 16213 16214 _proto.handleClick = function handleClick(event) { 16215 if (!this.player_.isFullscreen()) { 16216 this.player_.requestFullscreen(); 16217 } else { 16218 this.player_.exitFullscreen(); 16219 } 16220 }; 16221 16222 return FullscreenToggle; 16223 }(Button); 16224 /** 16225 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 16226 * 16227 * @type {string} 16228 * @private 16229 */ 16230 16231 16232 FullscreenToggle.prototype.controlText_ = 'Fullscreen'; 16233 Component.registerComponent('FullscreenToggle', FullscreenToggle); 16234 16235 /** 16236 * Check if volume control is supported and if it isn't hide the 16237 * `Component` that was passed using the `vjs-hidden` class. 16238 * 16239 * @param {Component} self 16240 * The component that should be hidden if volume is unsupported 16241 * 16242 * @param {Player} player 16243 * A reference to the player 16244 * 16245 * @private 16246 */ 16247 var checkVolumeSupport = function checkVolumeSupport(self, player) { 16248 // hide volume controls when they're not supported by the current tech 16249 if (player.tech_ && !player.tech_.featuresVolumeControl) { 16250 self.addClass('vjs-hidden'); 16251 } 16252 16253 self.on(player, 'loadstart', function () { 16254 if (!player.tech_.featuresVolumeControl) { 16255 self.addClass('vjs-hidden'); 16256 } else { 16257 self.removeClass('vjs-hidden'); 16258 } 16259 }); 16260 }; 16261 16262 /** 16263 * Shows volume level 16264 * 16265 * @extends Component 16266 */ 16267 16268 var VolumeLevel = /*#__PURE__*/function (_Component) { 16269 inheritsLoose(VolumeLevel, _Component); 16270 16271 function VolumeLevel() { 16272 return _Component.apply(this, arguments) || this; 16273 } 16274 16275 var _proto = VolumeLevel.prototype; 16276 16277 /** 16278 * Create the `Component`'s DOM element 16279 * 16280 * @return {Element} 16281 * The element that was created. 16282 */ 16283 _proto.createEl = function createEl() { 16284 return _Component.prototype.createEl.call(this, 'div', { 16285 className: 'vjs-volume-level', 16286 innerHTML: '<span class="vjs-control-text"></span>' 16287 }); 16288 }; 16289 16290 return VolumeLevel; 16291 }(Component); 16292 16293 Component.registerComponent('VolumeLevel', VolumeLevel); 16294 16295 /** 16296 * The bar that contains the volume level and can be clicked on to adjust the level 16297 * 16298 * @extends Slider 16299 */ 16300
vendor: 11,935 bytes, lines 16301-16714
16301 var VolumeBar = /*#__PURE__*/function (_Slider) { 16302 inheritsLoose(VolumeBar, _Slider); 16303 16304 /** 16305 * Creates an instance of this class. 16306 * 16307 * @param {Player} player 16308 * The `Player` that this class should be attached to. 16309 * 16310 * @param {Object} [options] 16311 * The key/value store of player options. 16312 */ 16313 function VolumeBar(player, options) { 16314 var _this; 16315 16316 _this = _Slider.call(this, player, options) || this; 16317 16318 _this.on('slideractive', _this.updateLastVolume_); 16319 16320 _this.on(player, 'volumechange', _this.updateARIAAttributes); 16321 16322 player.ready(function () { 16323 return _this.updateARIAAttributes(); 16324 }); 16325 return _this; 16326 } 16327 /** 16328 * Create the `Component`'s DOM element 16329 * 16330 * @return {Element} 16331 * The element that was created. 16332 */ 16333 16334 16335 var _proto = VolumeBar.prototype; 16336 16337 _proto.createEl = function createEl() { 16338 return _Slider.prototype.createEl.call(this, 'div', { 16339 className: 'vjs-volume-bar vjs-slider-bar' 16340 }, { 16341 'aria-label': this.localize('Volume Level'), 16342 'aria-live': 'polite' 16343 }); 16344 } 16345 /** 16346 * Handle mouse down on volume bar 16347 * 16348 * @param {EventTarget~Event} event 16349 * The `mousedown` event that caused this to run. 16350 * 16351 * @listens mousedown 16352 */ 16353 ; 16354 16355 _proto.handleMouseDown = function handleMouseDown(event) { 16356 if (!isSingleLeftClick(event)) { 16357 return; 16358 } 16359 16360 _Slider.prototype.handleMouseDown.call(this, event); 16361 } 16362 /** 16363 * Handle movement events on the {@link VolumeMenuButton}. 16364 * 16365 * @param {EventTarget~Event} event 16366 * The event that caused this function to run. 16367 * 16368 * @listens mousemove 16369 */ 16370 ; 16371 16372 _proto.handleMouseMove = function handleMouseMove(event) { 16373 if (!isSingleLeftClick(event)) { 16374 return; 16375 } 16376 16377 this.checkMuted(); 16378 this.player_.volume(this.calculateDistance(event)); 16379 } 16380 /** 16381 * If the player is muted unmute it. 16382 */ 16383 ; 16384 16385 _proto.checkMuted = function checkMuted() { 16386 if (this.player_.muted()) { 16387 this.player_.muted(false); 16388 } 16389 } 16390 /** 16391 * Get percent of volume level 16392 * 16393 * @return {number} 16394 * Volume level percent as a decimal number. 16395 */ 16396 ; 16397 16398 _proto.getPercent = function getPercent() { 16399 if (this.player_.muted()) { 16400 return 0; 16401 } 16402 16403 return this.player_.volume(); 16404 } 16405 /** 16406 * Increase volume level for keyboard users 16407 */ 16408 ; 16409 16410 _proto.stepForward = function stepForward() { 16411 this.checkMuted(); 16412 this.player_.volume(this.player_.volume() + 0.1); 16413 } 16414 /** 16415 * Decrease volume level for keyboard users 16416 */ 16417 ; 16418 16419 _proto.stepBack = function stepBack() { 16420 this.checkMuted(); 16421 this.player_.volume(this.player_.volume() - 0.1); 16422 } 16423 /** 16424 * Update ARIA accessibility attributes 16425 * 16426 * @param {EventTarget~Event} [event] 16427 * The `volumechange` event that caused this function to run. 16428 * 16429 * @listens Player#volumechange 16430 */ 16431 ; 16432 16433 _proto.updateARIAAttributes = function updateARIAAttributes(event) { 16434 var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); 16435 this.el_.setAttribute('aria-valuenow', ariaValue); 16436 this.el_.setAttribute('aria-valuetext', ariaValue + '%'); 16437 } 16438 /** 16439 * Returns the current value of the player volume as a percentage 16440 * 16441 * @private 16442 */ 16443 ; 16444 16445 _proto.volumeAsPercentage_ = function volumeAsPercentage_() { 16446 return Math.round(this.player_.volume() * 100); 16447 } 16448 /** 16449 * When user starts dragging the VolumeBar, store the volume and listen for 16450 * the end of the drag. When the drag ends, if the volume was set to zero, 16451 * set lastVolume to the stored volume. 16452 * 16453 * @listens slideractive 16454 * @private 16455 */ 16456 ; 16457 16458 _proto.updateLastVolume_ = function updateLastVolume_() { 16459 var _this2 = this; 16460 16461 var volumeBeforeDrag = this.player_.volume(); 16462 this.one('sliderinactive', function () { 16463 if (_this2.player_.volume() === 0) { 16464 _this2.player_.lastVolume_(volumeBeforeDrag); 16465 } 16466 }); 16467 }; 16468 16469 return VolumeBar; 16470 }(Slider); 16471 /** 16472 * Default options for the `VolumeBar` 16473 * 16474 * @type {Object} 16475 * @private 16476 */ 16477 16478 16479 VolumeBar.prototype.options_ = { 16480 children: ['volumeLevel'], 16481 barName: 'volumeLevel' 16482 }; 16483 /** 16484 * Call the update event for this Slider when this event happens on the player. 16485 * 16486 * @type {string} 16487 */ 16488 16489 VolumeBar.prototype.playerEvent = 'volumechange'; 16490 Component.registerComponent('VolumeBar', VolumeBar); 16491 16492 /** 16493 * The component for controlling the volume level 16494 * 16495 * @extends Component 16496 */ 16497 16498 var VolumeControl = /*#__PURE__*/function (_Component) { 16499 inheritsLoose(VolumeControl, _Component); 16500 16501 /** 16502 * Creates an instance of this class. 16503 * 16504 * @param {Player} player 16505 * The `Player` that this class should be attached to. 16506 * 16507 * @param {Object} [options={}] 16508 * The key/value store of player options. 16509 */ 16510 function VolumeControl(player, options) { 16511 var _this; 16512 16513 if (options === void 0) { 16514 options = {}; 16515 } 16516 16517 options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if 16518 // the VolumeBar is turned on. 16519 16520 if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { 16521 options.volumeBar = options.volumeBar || {}; 16522 options.volumeBar.vertical = options.vertical; 16523 } 16524 16525 _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing 16526 16527 checkVolumeSupport(assertThisInitialized(_this), player); 16528 _this.throttledHandleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 16529 16530 _this.on('mousedown', _this.handleMouseDown); 16531 16532 _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and 16533 // is dragging) or in focus we do not want to hide the VolumeBar 16534 16535 16536 _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { 16537 _this.volumeBar.addClass('vjs-slider-active'); 16538 16539 _this.addClass('vjs-slider-active'); 16540 16541 _this.trigger('slideractive'); 16542 }); 16543 16544 _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { 16545 _this.volumeBar.removeClass('vjs-slider-active'); 16546 16547 _this.removeClass('vjs-slider-active'); 16548 16549 _this.trigger('sliderinactive'); 16550 }); 16551 16552 return _this; 16553 } 16554 /** 16555 * Create the `Component`'s DOM element 16556 * 16557 * @return {Element} 16558 * The element that was created. 16559 */ 16560 16561 16562 var _proto = VolumeControl.prototype; 16563 16564 _proto.createEl = function createEl() { 16565 var orientationClass = 'vjs-volume-horizontal'; 16566 16567 if (this.options_.vertical) { 16568 orientationClass = 'vjs-volume-vertical'; 16569 } 16570 16571 return _Component.prototype.createEl.call(this, 'div', { 16572 className: "vjs-volume-control vjs-control " + orientationClass 16573 }); 16574 } 16575 /** 16576 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 16577 * 16578 * @param {EventTarget~Event} event 16579 * `mousedown` or `touchstart` event that triggered this function 16580 * 16581 * @listens mousedown 16582 * @listens touchstart 16583 */ 16584 ; 16585 16586 _proto.handleMouseDown = function handleMouseDown(event) { 16587 var doc = this.el_.ownerDocument; 16588 this.on(doc, 'mousemove', this.throttledHandleMouseMove); 16589 this.on(doc, 'touchmove', this.throttledHandleMouseMove); 16590 this.on(doc, 'mouseup', this.handleMouseUp); 16591 this.on(doc, 'touchend', this.handleMouseUp); 16592 } 16593 /** 16594 * Handle `mouseup` or `touchend` events on the `VolumeControl`. 16595 * 16596 * @param {EventTarget~Event} event 16597 * `mouseup` or `touchend` event that triggered this function. 16598 * 16599 * @listens touchend 16600 * @listens mouseup 16601 */ 16602 ; 16603 16604 _proto.handleMouseUp = function handleMouseUp(event) { 16605 var doc = this.el_.ownerDocument; 16606 this.off(doc, 'mousemove', this.throttledHandleMouseMove); 16607 this.off(doc, 'touchmove', this.throttledHandleMouseMove); 16608 this.off(doc, 'mouseup', this.handleMouseUp); 16609 this.off(doc, 'touchend', this.handleMouseUp); 16610 } 16611 /** 16612 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 16613 * 16614 * @param {EventTarget~Event} event 16615 * `mousedown` or `touchstart` event that triggered this function 16616 * 16617 * @listens mousedown 16618 * @listens touchstart 16619 */ 16620 ; 16621 16622 _proto.handleMouseMove = function handleMouseMove(event) { 16623 this.volumeBar.handleMouseMove(event); 16624 }; 16625 16626 return VolumeControl; 16627 }(Component); 16628 /** 16629 * Default options for the `VolumeControl` 16630 * 16631 * @type {Object} 16632 * @private 16633 */ 16634 16635 16636 VolumeControl.prototype.options_ = { 16637 children: ['volumeBar'] 16638 }; 16639 Component.registerComponent('VolumeControl', VolumeControl); 16640 16641 /** 16642 * Check if muting volume is supported and if it isn't hide the mute toggle 16643 * button. 16644 * 16645 * @param {Component} self 16646 * A reference to the mute toggle button 16647 * 16648 * @param {Player} player 16649 * A reference to the player 16650 * 16651 * @private 16652 */ 16653 var checkMuteSupport = function checkMuteSupport(self, player) { 16654 // hide mute toggle button if it's not supported by the current tech 16655 if (player.tech_ && !player.tech_.featuresMuteControl) { 16656 self.addClass('vjs-hidden'); 16657 } 16658 16659 self.on(player, 'loadstart', function () { 16660 if (!player.tech_.featuresMuteControl) { 16661 self.addClass('vjs-hidden'); 16662 } else { 16663 self.removeClass('vjs-hidden'); 16664 } 16665 }); 16666 }; 16667 16668 /** 16669 * A button component for muting the audio. 16670 * 16671 * @extends Button 16672 */ 16673 16674 var MuteToggle = /*#__PURE__*/function (_Button) { 16675 inheritsLoose(MuteToggle, _Button); 16676 16677 /** 16678 * Creates an instance of this class. 16679 * 16680 * @param {Player} player 16681 * The `Player` that this class should be attached to. 16682 * 16683 * @param {Object} [options] 16684 * The key/value store of player options. 16685 */ 16686 function MuteToggle(player, options) { 16687 var _this; 16688 16689 _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing 16690 16691 checkMuteSupport(assertThisInitialized(_this), player); 16692 16693 _this.on(player, ['loadstart', 'volumechange'], _this.update); 16694 16695 return _this; 16696 } 16697 /** 16698 * Builds the default DOM `className`. 16699 * 16700 * @return {string} 16701 * The DOM `className` for this object. 16702 */ 16703 16704 16705 var _proto = MuteToggle.prototype; 16706 16707 _proto.buildCSSClass = function buildCSSClass() { 16708 return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this); 16709 } 16710 /** 16711 * This gets called when an `MuteToggle` is "clicked". See 16712 * {@link ClickableComponent} for more detailed information on what a click can be. 16713 * 16714 * @param {EventTarget~Event} [event]
16715 * The `keydown`, `tap`, or `click` event that caused this function to be 16716 * called. 16717 * 16718 * @listens tap 16719 * @listens click 16720 */ 16721 ; 16722 16723 _proto.handleClick = function handleClick(event) { 16724 var vol = this.player_.volume(); 16725 var lastVolume = this.player_.lastVolume_(); 16726 16727 if (vol === 0) { 16728 var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; 16729 this.player_.volume(volumeToSet); 16730 this.player_.muted(false); 16731 } else { 16732 this.player_.muted(this.player_.muted() ? false : true); 16733 } 16734 } 16735 /** 16736 * Update the `MuteToggle` button based on the state of `volume` and `muted` 16737 * on the player. 16738 * 16739 * @param {EventTarget~Event} [event] 16740 * The {@link Player#loadstart} event if this function was called 16741 * through an event. 16742 * 16743 * @listens Player#loadstart 16744 * @listens Player#volumechange 16745 */ 16746 ; 16747 16748 _proto.update = function update(event) { 16749 this.updateIcon_(); 16750 this.updateControlText_(); 16751 } 16752 /** 16753 * Update the appearance of the `MuteToggle` icon. 16754 * 16755 * Possible states (given `level` variable below): 16756 * - 0: crossed out 16757 * - 1: zero bars of volume 16758 * - 2: one bar of volume 16759 * - 3: two bars of volume 16760 * 16761 * @private 16762 */ 16763 ; 16764 16765 _proto.updateIcon_ = function updateIcon_() { 16766 var vol = this.player_.volume(); 16767 var level = 3; // in iOS when a player is loaded with muted attribute 16768 // and volume is changed with a native mute button 16769 // we want to make sure muted state is updated 16770 16771 if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) { 16772 this.player_.muted(this.player_.tech_.el_.muted); 16773 } 16774 16775 if (vol === 0 || this.player_.muted()) { 16776 level = 0; 16777 } else if (vol < 0.33) { 16778 level = 1; 16779 } else if (vol < 0.67) { 16780 level = 2; 16781 } // TODO improve muted icon classes 16782 16783 16784 for (var i = 0; i < 4; i++) { 16785 removeClass(this.el_, "vjs-vol-" + i); 16786 } 16787 16788 addClass(this.el_, "vjs-vol-" + level); 16789 } 16790 /** 16791 * If `muted` has changed on the player, update the control text 16792 * (`title` attribute on `vjs-mute-control` element and content of 16793 * `vjs-control-text` element). 16794 * 16795 * @private 16796 */ 16797 ; 16798 16799 _proto.updateControlText_ = function updateControlText_() { 16800 var soundOff = this.player_.muted() || this.player_.volume() === 0; 16801 var text = soundOff ? 'Unmute' : 'Mute'; 16802 16803 if (this.controlText() !== text) { 16804 this.controlText(text); 16805 } 16806 }; 16807 16808 return MuteToggle; 16809 }(Button); 16810 /** 16811 * The text that should display over the `MuteToggle`s controls. Added for localization. 16812 * 16813 * @type {string} 16814 * @private 16815 */ 16816 16817 16818 MuteToggle.prototype.controlText_ = 'Mute'; 16819 Component.registerComponent('MuteToggle', MuteToggle); 16820 16821 /** 16822 * A Component to contain the MuteToggle and VolumeControl so that 16823 * they can work together. 16824 * 16825 * @extends Component 16826 */ 16827 16828 var VolumePanel = /*#__PURE__*/function (_Component) { 16829 inheritsLoose(VolumePanel, _Component); 16830 16831 /** 16832 * Creates an instance of this class. 16833 * 16834 * @param {Player} player 16835 * The `Player` that this class should be attached to. 16836 * 16837 * @param {Object} [options={}] 16838 * The key/value store of player options. 16839 */ 16840 function VolumePanel(player, options) { 16841 var _this; 16842 16843 if (options === void 0) { 16844 options = {}; 16845 } 16846 16847 if (typeof options.inline !== 'undefined') { 16848 options.inline = options.inline; 16849 } else { 16850 options.inline = true; 16851 } // pass the inline option down to the VolumeControl as vertical if 16852 // the VolumeControl is on. 16853 16854 16855 if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { 16856 options.volumeControl = options.volumeControl || {}; 16857 options.volumeControl.vertical = !options.inline; 16858 } 16859 16860 _this = _Component.call(this, player, options) || this; 16861 16862 _this.on(player, ['loadstart'], _this.volumePanelState_); 16863
16864 _this.on(_this.muteToggle, 'keyup', _this.handleKeyPress); 16865 16866 _this.on(_this.volumeControl, 'keyup', _this.handleVolumeControlKeyUp); 16867 16868 _this.on('keydown', _this.handleKeyPress); 16869 16870 _this.on('mouseover', _this.handleMouseOver); 16871 16872 _this.on('mouseout', _this.handleMouseOut); // while the slider is active (the mouse has been pressed down and 16873 // is dragging) we do not want to hide the VolumeBar 16874 16875 16876 _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); 16877 16878 _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); 16879 16880 return _this; 16881 } 16882 /** 16883 * Add vjs-slider-active class to the VolumePanel 16884 * 16885 * @listens VolumeControl#slideractive 16886 * @private 16887 */ 16888 16889 16890 var _proto = VolumePanel.prototype; 16891 16892 _proto.sliderActive_ = function sliderActive_() { 16893 this.addClass('vjs-slider-active'); 16894 } 16895 /** 16896 * Removes vjs-slider-active class to the VolumePanel 16897 * 16898 * @listens VolumeControl#sliderinactive 16899 * @private 16900 */ 16901 ; 16902 16903 _proto.sliderInactive_ = function sliderInactive_() { 16904 this.removeClass('vjs-slider-active'); 16905 } 16906 /** 16907 * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel 16908 * depending on MuteToggle and VolumeControl state 16909 * 16910 * @listens Player#loadstart 16911 * @private 16912 */ 16913 ; 16914 16915 _proto.volumePanelState_ = function volumePanelState_() { 16916 // hide volume panel if neither volume control or mute toggle 16917 // are displayed 16918 if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) { 16919 this.addClass('vjs-hidden'); 16920 } // if only mute toggle is visible we don't want 16921 // volume panel expanding when hovered or active 16922 16923 16924 if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) { 16925 this.addClass('vjs-mute-toggle-only'); 16926 } 16927 } 16928 /** 16929 * Create the `Component`'s DOM element 16930 * 16931 * @return {Element} 16932 * The element that was created. 16933 */ 16934 ; 16935 16936 _proto.createEl = function createEl() { 16937 var orientationClass = 'vjs-volume-panel-horizontal'; 16938 16939 if (!this.options_.inline) { 16940 orientationClass = 'vjs-volume-panel-vertical'; 16941 } 16942 16943 return _Component.prototype.createEl.call(this, 'div', { 16944 className: "vjs-volume-panel vjs-control " + orientationClass 16945 }); 16946 } 16947 /** 16948 * Dispose of the `volume-panel` and all child components. 16949 */ 16950 ; 16951 16952 _proto.dispose = function dispose() { 16953 this.handleMouseOut(); 16954 16955 _Component.prototype.dispose.call(this); 16956 } 16957 /** 16958 * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes 16959 * the volume panel and sets focus on `MuteToggle`. 16960 * 16961 * @param {EventTarget~Event} event 16962 * The `keyup` event that caused this function to be called. 16963 * 16964 * @listens keyup 16965 */ 16966 ; 16967 16968 _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) { 16969 if (keycode.isEventKey(event, 'Esc')) { 16970 this.muteToggle.focus(); 16971 } 16972 } 16973 /** 16974 * This gets called when a `VolumePanel` gains hover via a `mouseover` event. 16975 * Turns on listening for `mouseover` event. When they happen it 16976 * calls `this.handleMouseOver`. 16977 * 16978 * @param {EventTarget~Event} event 16979 * The `mouseover` event that caused this function to be called. 16980 * 16981 * @listens mouseover 16982 */ 16983 ; 16984 16985 _proto.handleMouseOver = function handleMouseOver(event) { 16986 this.addClass('vjs-hover'); 16987 on(document, 'keyup', bind(this, this.handleKeyPress)); 16988 } 16989 /** 16990 * This gets called when a `VolumePanel` gains hover via a `mouseout` event. 16991 * Turns on listening for `mouseout` event. When they happen it 16992 * calls `this.handleMouseOut`. 16993 * 16994 * @param {EventTarget~Event} event 16995 * The `mouseout` event that caused this function to be called. 16996 * 16997 * @listens mouseout 16998 */ 16999 ; 17000 17001 _proto.handleMouseOut = function handleMouseOut(event) { 17002 this.removeClass('vjs-hover'); 17003 off(document, 'keyup', bind(this, this.handleKeyPress)); 17004 } 17005 /** 17006 * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`, 17007 * looking for ESC, which hides the `VolumeControl`. 17008 * 17009 * @param {EventTarget~Event} event 17010 * The keypress that triggered this event. 17011 * 17012 * @listens keydown | keyup 17013 */ 17014 ; 17015 17016 _proto.handleKeyPress = function handleKeyPress(event) { 17017 if (keycode.isEventKey(event, 'Esc')) { 17018 this.handleMouseOut(); 17019 } 17020 }; 17021 17022 return VolumePanel; 17023 }(Component); 17024 /** 17025 * Default options for the `VolumeControl` 17026 * 17027 * @type {Object} 17028 * @private 17029 */ 17030 17031 17032 VolumePanel.prototype.options_ = { 17033 children: ['muteToggle', 'volumeControl'] 17034 }; 17035 Component.registerComponent('VolumePanel', VolumePanel); 17036 17037 /** 17038 * The Menu component is used to build popup menus, including subtitle and 17039 * captions selection menus. 17040 * 17041 * @extends Component 17042 */ 17043 17044 var Menu = /*#__PURE__*/function (_Component) { 17045 inheritsLoose(Menu, _Component); 17046 17047 /** 17048 * Create an instance of this class. 17049 * 17050 * @param {Player} player 17051 * the player that this component should attach to 17052 * 17053 * @param {Object} [options] 17054 * Object of option names and values 17055 * 17056 */ 17057 function Menu(player, options) { 17058 var _this; 17059 17060 _this = _Component.call(this, player, options) || this; 17061 17062 if (options) { 17063 _this.menuButton_ = options.menuButton; 17064 } 17065 17066 _this.focusedChild_ = -1; 17067 17068 _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container. 17069 17070 17071 _this.boundHandleBlur_ = bind(assertThisInitialized(_this), _this.handleBlur); 17072 _this.boundHandleTapClick_ = bind(assertThisInitialized(_this), _this.handleTapClick); 17073 return _this; 17074 } 17075 /** 17076 * Add event listeners to the {@link MenuItem}. 17077 * 17078 * @param {Object} component 17079 * The instance of the `MenuItem` to add listeners to. 17080 * 17081 */ 17082 17083
17084 var _proto = Menu.prototype; 17085 17086 _proto.addEventListenerForItem = function addEventListenerForItem(component) { 17087 if (!(component instanceof Component)) { 17088 return; 17089 } 17090 17091 this.on(component, 'blur', this.boundHandleBlur_); 17092 this.on(component, ['tap', 'click'], this.boundHandleTapClick_); 17093 } 17094 /** 17095 * Remove event listeners from the {@link MenuItem}. 17096 * 17097 * @param {Object} component 17098 * The instance of the `MenuItem` to remove listeners. 17099 * 17100 */ 17101 ; 17102 17103 _proto.removeEventListenerForItem = function removeEventListenerForItem(component) { 17104 if (!(component instanceof Component)) { 17105 return; 17106 } 17107 17108 this.off(component, 'blur', this.boundHandleBlur_); 17109 this.off(component, ['tap', 'click'], this.boundHandleTapClick_); 17110 } 17111 /** 17112 * This method will be called indirectly when the component has been added 17113 * before the component adds to the new menu instance by `addItem`. 17114 * In this case, the original menu instance will remove the component 17115 * by calling `removeChild`. 17116 * 17117 * @param {Object} component 17118 * The instance of the `MenuItem` 17119 */ 17120 ; 17121 17122 _proto.removeChild = function removeChild(component) { 17123 if (typeof component === 'string') { 17124 component = this.getChild(component); 17125 } 17126 17127 this.removeEventListenerForItem(component); 17128 17129 _Component.prototype.removeChild.call(this, component); 17130 } 17131 /** 17132 * Add a {@link MenuItem} to the menu. 17133 * 17134 * @param {Object|string} component 17135 * The name or instance of the `MenuItem` to add. 17136 * 17137 */ 17138 ; 17139 17140 _proto.addItem = function addItem(component) { 17141 var childComponent = this.addChild(component); 17142 17143 if (childComponent) { 17144 this.addEventListenerForItem(childComponent); 17145 } 17146 } 17147 /** 17148 * Create the `Menu`s DOM element. 17149 * 17150 * @return {Element} 17151 * the element that was created 17152 */ 17153 ; 17154 17155 _proto.createEl = function createEl$1() { 17156 var contentElType = this.options_.contentElType || 'ul'; 17157 this.contentEl_ = createEl(contentElType, { 17158 className: 'vjs-menu-content' 17159 }); 17160 this.contentEl_.setAttribute('role', 'menu'); 17161 17162 var el = _Component.prototype.createEl.call(this, 'div', { 17163 append: this.contentEl_, 17164 className: 'vjs-menu' 17165 }); 17166 17167 el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons, 17168 // where a click on the parent is significant 17169 17170 on(el, 'click', function (event) { 17171 event.preventDefault(); 17172 event.stopImmediatePropagation(); 17173 }); 17174 return el; 17175 }; 17176 17177 _proto.dispose = function dispose() { 17178 this.contentEl_ = null; 17179 this.boundHandleBlur_ = null; 17180 this.boundHandleTapClick_ = null; 17181 17182 _Component.prototype.dispose.call(this); 17183 } 17184 /** 17185 * Called when a `MenuItem` loses focus. 17186 * 17187 * @param {EventTarget~Event} event 17188 * The `blur` event that caused this function to be called. 17189 * 17190 * @listens blur 17191 */ 17192 ; 17193 17194 _proto.handleBlur = function handleBlur(event) { 17195 var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu 17196 17197 if (!this.children().some(function (element) { 17198 return element.el() === relatedTarget; 17199 })) { 17200 var btn = this.menuButton_; 17201 17202 if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) { 17203 btn.unpressButton(); 17204 } 17205 } 17206 } 17207 /** 17208 * Called when a `MenuItem` gets clicked or tapped. 17209 * 17210 * @param {EventTarget~Event} event 17211 * The `click` or `tap` event that caused this function to be called. 17212 * 17213 * @listens click,tap 17214 */ 17215 ; 17216 17217 _proto.handleTapClick = function handleTapClick(event) { 17218 // Unpress the associated MenuButton, and move focus back to it 17219 if (this.menuButton_) { 17220 this.menuButton_.unpressButton(); 17221 var childComponents = this.children(); 17222 17223 if (!Array.isArray(childComponents)) { 17224 return; 17225 } 17226 17227 var foundComponent = childComponents.filter(function (component) { 17228 return component.el() === event.target; 17229 })[0]; 17230 17231 if (!foundComponent) { 17232 return; 17233 } // don't focus menu button if item is a caption settings item 17234 // because focus will move elsewhere 17235 17236 17237 if (foundComponent.name() !== 'CaptionSettingsMenuItem') { 17238 this.menuButton_.focus(); 17239 } 17240 } 17241 } 17242 /** 17243 * Handle a `keydown` event on this menu. This listener is added in the constructor. 17244 * 17245 * @param {EventTarget~Event} event 17246 * A `keydown` event that happened on the menu. 17247 * 17248 * @listens keydown 17249 */ 17250 ; 17251 17252 _proto.handleKeyDown = function handleKeyDown(event) { 17253 // Left and Down Arrows 17254 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 17255 event.preventDefault(); 17256 event.stopPropagation(); 17257 this.stepForward(); // Up and Right Arrows 17258 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 17259 event.preventDefault(); 17260 event.stopPropagation(); 17261 this.stepBack(); 17262 } 17263 } 17264 /** 17265 * Move to next (lower) menu item for keyboard users. 17266 */ 17267 ; 17268 17269 _proto.stepForward = function stepForward() {
vendor: 8,057 bytes, lines 17270-17557
17270 var stepChild = 0; 17271 17272 if (this.focusedChild_ !== undefined) { 17273 stepChild = this.focusedChild_ + 1; 17274 } 17275 17276 this.focus(stepChild); 17277 } 17278 /** 17279 * Move to previous (higher) menu item for keyboard users. 17280 */ 17281 ; 17282 17283 _proto.stepBack = function stepBack() { 17284 var stepChild = 0; 17285 17286 if (this.focusedChild_ !== undefined) { 17287 stepChild = this.focusedChild_ - 1; 17288 } 17289 17290 this.focus(stepChild); 17291 } 17292 /** 17293 * Set focus on a {@link MenuItem} in the `Menu`. 17294 * 17295 * @param {Object|string} [item=0] 17296 * Index of child item set focus on. 17297 */ 17298 ; 17299 17300 _proto.focus = function focus(item) { 17301 if (item === void 0) { 17302 item = 0; 17303 } 17304 17305 var children = this.children().slice(); 17306 var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); 17307 17308 if (haveTitle) { 17309 children.shift(); 17310 } 17311 17312 if (children.length > 0) { 17313 if (item < 0) { 17314 item = 0; 17315 } else if (item >= children.length) { 17316 item = children.length - 1; 17317 } 17318 17319 this.focusedChild_ = item; 17320 children[item].el_.focus(); 17321 } 17322 }; 17323 17324 return Menu; 17325 }(Component); 17326 17327 Component.registerComponent('Menu', Menu); 17328 17329 /** 17330 * A `MenuButton` class for any popup {@link Menu}. 17331 * 17332 * @extends Component 17333 */ 17334 17335 var MenuButton = /*#__PURE__*/function (_Component) { 17336 inheritsLoose(MenuButton, _Component); 17337 17338 /** 17339 * Creates an instance of this class. 17340 * 17341 * @param {Player} player 17342 * The `Player` that this class should be attached to. 17343 * 17344 * @param {Object} [options={}] 17345 * The key/value store of player options. 17346 */ 17347 function MenuButton(player, options) { 17348 var _this; 17349 17350 if (options === void 0) { 17351 options = {}; 17352 } 17353 17354 _this = _Component.call(this, player, options) || this; 17355 _this.menuButton_ = new Button(player, options); 17356 17357 _this.menuButton_.controlText(_this.controlText_); 17358 17359 _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper 17360 17361 17362 var buttonClass = Button.prototype.buildCSSClass(); 17363 _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; 17364 17365 _this.menuButton_.removeClass('vjs-control'); 17366 17367 _this.addChild(_this.menuButton_); 17368 17369 _this.update(); 17370 17371 _this.enabled_ = true; 17372 17373 _this.on(_this.menuButton_, 'tap', _this.handleClick); 17374 17375 _this.on(_this.menuButton_, 'click', _this.handleClick); 17376 17377 _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown); 17378 17379 _this.on(_this.menuButton_, 'mouseenter', function () { 17380 _this.addClass('vjs-hover'); 17381 17382 _this.menu.show(); 17383 17384 on(document, 'keyup', bind(assertThisInitialized(_this), _this.handleMenuKeyUp)); 17385 }); 17386 17387 _this.on('mouseleave', _this.handleMouseLeave); 17388 17389 _this.on('keydown', _this.handleSubmenuKeyDown); 17390 17391 return _this; 17392 } 17393 /** 17394 * Update the menu based on the current state of its items. 17395 */ 17396 17397 17398 var _proto = MenuButton.prototype; 17399 17400 _proto.update = function update() { 17401 var menu = this.createMenu(); 17402 17403 if (this.menu) { 17404 this.menu.dispose(); 17405 this.removeChild(this.menu); 17406 } 17407 17408 this.menu = menu; 17409 this.addChild(menu); 17410 /** 17411 * Track the state of the menu button 17412 * 17413 * @type {Boolean} 17414 * @private 17415 */ 17416 17417 this.buttonPressed_ = false; 17418 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 17419 17420 if (this.items && this.items.length <= this.hideThreshold_) { 17421 this.hide(); 17422 } else { 17423 this.show(); 17424 } 17425 } 17426 /** 17427 * Create the menu and add all items to it. 17428 * 17429 * @return {Menu} 17430 * The constructed menu 17431 */ 17432 ; 17433 17434 _proto.createMenu = function createMenu() { 17435 var menu = new Menu(this.player_, { 17436 menuButton: this 17437 }); 17438 /** 17439 * Hide the menu if the number of items is less than or equal to this threshold. This defaults 17440 * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list 17441 * it here because every time we run `createMenu` we need to reset the value. 17442 * 17443 * @protected 17444 * @type {Number} 17445 */ 17446 17447 this.hideThreshold_ = 0; // Add a title list item to the top 17448 17449 if (this.options_.title) { 17450 var titleEl = createEl('li', { 17451 className: 'vjs-menu-title', 17452 innerHTML: toTitleCase(this.options_.title), 17453 tabIndex: -1 17454 }); 17455 this.hideThreshold_ += 1; 17456 var titleComponent = new Component(this.player_, { 17457 el: titleEl 17458 }); 17459 menu.addItem(titleComponent); 17460 } 17461 17462 this.items = this.createItems(); 17463 17464 if (this.items) { 17465 // Add menu items to the menu 17466 for (var i = 0; i < this.items.length; i++) { 17467 menu.addItem(this.items[i]); 17468 } 17469 } 17470 17471 return menu; 17472 } 17473 /** 17474 * Create the list of menu items. Specific to each subclass. 17475 * 17476 * @abstract 17477 */ 17478 ; 17479 17480 _proto.createItems = function createItems() {} 17481 /** 17482 * Create the `MenuButtons`s DOM element. 17483 * 17484 * @return {Element} 17485 * The element that gets created. 17486 */ 17487 ; 17488 17489 _proto.createEl = function createEl() { 17490 return _Component.prototype.createEl.call(this, 'div', { 17491 className: this.buildWrapperCSSClass() 17492 }, {}); 17493 } 17494 /** 17495 * Allow sub components to stack CSS class names for the wrapper element 17496 * 17497 * @return {string} 17498 * The constructed wrapper DOM `className` 17499 */ 17500 ; 17501 17502 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 17503 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 17504 17505 if (this.options_.inline === true) { 17506 menuButtonClass += '-inline'; 17507 } else { 17508 menuButtonClass += '-popup'; 17509 } // TODO: Fix the CSS so that this isn't necessary 17510 17511 17512 var buttonClass = Button.prototype.buildCSSClass(); 17513 return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this); 17514 } 17515 /** 17516 * Builds the default DOM `className`. 17517 * 17518 * @return {string} 17519 * The DOM `className` for this object. 17520 */ 17521 ; 17522 17523 _proto.buildCSSClass = function buildCSSClass() { 17524 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 17525 17526 if (this.options_.inline === true) { 17527 menuButtonClass += '-inline'; 17528 } else { 17529 menuButtonClass += '-popup'; 17530 } 17531 17532 return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this); 17533 } 17534 /** 17535 * Get or set the localized control text that will be used for accessibility. 17536 * 17537 * > NOTE: This will come from the internal `menuButton_` element. 17538 * 17539 * @param {string} [text] 17540 * Control text for element. 17541 * 17542 * @param {Element} [el=this.menuButton_.el()] 17543 * Element to set the title on. 17544 * 17545 * @return {string} 17546 * - The control text when getting 17547 */ 17548 ; 17549 17550 _proto.controlText = function controlText(text, el) { 17551 if (el === void 0) { 17552 el = this.menuButton_.el(); 17553 } 17554 17555 return this.menuButton_.controlText(text, el); 17556 } 17557 /**
17558 * Dispose of the `menu-button` and all child components. 17559 */ 17560 ; 17561 17562 _proto.dispose = function dispose() { 17563 this.handleMouseLeave(); 17564 17565 _Component.prototype.dispose.call(this); 17566 } 17567 /** 17568 * Handle a click on a `MenuButton`. 17569 * See {@link ClickableComponent#handleClick} for instances where this is called. 17570 * 17571 * @param {EventTarget~Event} event 17572 * The `keydown`, `tap`, or `click` event that caused this function to be 17573 * called. 17574 * 17575 * @listens tap 17576 * @listens click 17577 */ 17578 ; 17579 17580 _proto.handleClick = function handleClick(event) { 17581 if (this.buttonPressed_) { 17582 this.unpressButton(); 17583 } else { 17584 this.pressButton(); 17585 } 17586 } 17587 /** 17588 * Handle `mouseleave` for `MenuButton`. 17589 * 17590 * @param {EventTarget~Event} event 17591 * The `mouseleave` event that caused this function to be called. 17592 * 17593 * @listens mouseleave 17594 */ 17595 ; 17596 17597 _proto.handleMouseLeave = function handleMouseLeave(event) { 17598 this.removeClass('vjs-hover'); 17599 off(document, 'keyup', bind(this, this.handleMenuKeyUp)); 17600 } 17601 /** 17602 * Set the focus to the actual button, not to this element 17603 */ 17604 ; 17605 17606 _proto.focus = function focus() { 17607 this.menuButton_.focus(); 17608 } 17609 /** 17610 * Remove the focus from the actual button, not this element 17611 */ 17612 ; 17613 17614 _proto.blur = function blur() { 17615 this.menuButton_.blur(); 17616 } 17617 /** 17618 * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See 17619 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 17620 * 17621 * @param {EventTarget~Event} event 17622 * The `keydown` event that caused this function to be called. 17623 * 17624 * @listens keydown 17625 */ 17626 ; 17627 17628 _proto.handleKeyDown = function handleKeyDown(event) { 17629 // Escape or Tab unpress the 'button' 17630 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17631 if (this.buttonPressed_) { 17632 this.unpressButton(); 17633 } // Don't preventDefault for Tab key - we still want to lose focus 17634 17635 17636 if (!keycode.isEventKey(event, 'Tab')) { 17637 event.preventDefault(); // Set focus back to the menu button's button 17638 17639 this.menuButton_.focus(); 17640 } // Up Arrow or Down Arrow also 'press' the button to open the menu 17641 17642 } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) { 17643 if (!this.buttonPressed_) { 17644 event.preventDefault(); 17645 this.pressButton(); 17646 } 17647 } 17648 } 17649 /** 17650 * Handle a `keyup` event on a `MenuButton`. The listener for this is added in 17651 * the constructor. 17652 * 17653 * @param {EventTarget~Event} event 17654 * Key press event 17655 * 17656 * @listens keyup 17657 */ 17658 ; 17659 17660 _proto.handleMenuKeyUp = function handleMenuKeyUp(event) { 17661 // Escape hides popup menu 17662 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17663 this.removeClass('vjs-hover'); 17664 } 17665 } 17666 /** 17667 * This method name now delegates to `handleSubmenuKeyDown`. This means 17668 * anyone calling `handleSubmenuKeyPress` will not see their method calls 17669 * stop working. 17670 * 17671 * @param {EventTarget~Event} event 17672 * The event that caused this function to be called. 17673 */ 17674 ; 17675 17676 _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { 17677 this.handleSubmenuKeyDown(event); 17678 } 17679 /** 17680 * Handle a `keydown` event on a sub-menu. The listener for this is added in 17681 * the constructor. 17682 * 17683 * @param {EventTarget~Event} event 17684 * Key press event 17685 * 17686 * @listens keydown 17687 */ 17688 ; 17689 17690 _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) { 17691 // Escape or Tab unpress the 'button' 17692 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17693 if (this.buttonPressed_) { 17694 this.unpressButton(); 17695 } // Don't preventDefault for Tab key - we still want to lose focus 17696 17697 17698 if (!keycode.isEventKey(event, 'Tab')) { 17699 event.preventDefault(); // Set focus back to the menu button's button 17700 17701 this.menuButton_.focus(); 17702 } 17703 } 17704 } 17705 /** 17706 * Put the current `MenuButton` into a pressed state. 17707 */ 17708 ; 17709 17710 _proto.pressButton = function pressButton() { 17711 if (this.enabled_) { 17712 this.buttonPressed_ = true; 17713 this.menu.show(); 17714 this.menu.lockShowing(); 17715 this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in 17716 // undesired scrolling behavior when the player is in an iframe 17717 17718 if (IS_IOS && isInFrame()) { 17719 // Return early so that the menu isn't focused 17720 return; 17721 } 17722 17723 this.menu.focus(); 17724 } 17725 } 17726 /** 17727 * Take the current `MenuButton` out of a pressed state. 17728 */ 17729 ; 17730 17731 _proto.unpressButton = function unpressButton() { 17732 if (this.enabled_) { 17733 this.buttonPressed_ = false;
17734 this.menu.unlockShowing(); 17735 this.menu.hide(); 17736 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 17737 } 17738 } 17739 /** 17740 * Disable the `MenuButton`. Don't allow it to be clicked. 17741 */ 17742 ; 17743 17744 _proto.disable = function disable() { 17745 this.unpressButton(); 17746 this.enabled_ = false; 17747 this.addClass('vjs-disabled'); 17748 this.menuButton_.disable(); 17749 } 17750 /** 17751 * Enable the `MenuButton`. Allow it to be clicked. 17752 */ 17753 ; 17754 17755 _proto.enable = function enable() { 17756 this.enabled_ = true; 17757 this.removeClass('vjs-disabled'); 17758 this.menuButton_.enable(); 17759 }; 17760 17761 return MenuButton; 17762 }(Component); 17763 17764 Component.registerComponent('MenuButton', MenuButton); 17765 17766 /** 17767 * The base class for buttons that toggle specific track types (e.g. subtitles). 17768 * 17769 * @extends MenuButton 17770 */ 17771 17772 var TrackButton = /*#__PURE__*/function (_MenuButton) { 17773 inheritsLoose(TrackButton, _MenuButton); 17774 17775 /** 17776 * Creates an instance of this class. 17777 * 17778 * @param {Player} player 17779 * The `Player` that this class should be attached to. 17780 * 17781 * @param {Object} [options] 17782 * The key/value store of player options. 17783 */ 17784 function TrackButton(player, options) { 17785 var _this; 17786 17787 var tracks = options.tracks; 17788 _this = _MenuButton.call(this, player, options) || this; 17789 17790 if (_this.items.length <= 1) { 17791 _this.hide(); 17792 } 17793 17794 if (!tracks) { 17795 return assertThisInitialized(_this); 17796 } 17797 17798 var updateHandler = bind(assertThisInitialized(_this), _this.update); 17799 tracks.addEventListener('removetrack', updateHandler); 17800 tracks.addEventListener('addtrack', updateHandler); 17801 17802 _this.player_.on('ready', updateHandler); 17803 17804 _this.player_.on('dispose', function () { 17805 tracks.removeEventListener('removetrack', updateHandler); 17806 tracks.removeEventListener('addtrack', updateHandler); 17807 }); 17808 17809 return _this; 17810 } 17811 17812 return TrackButton; 17813 }(MenuButton); 17814 17815 Component.registerComponent('TrackButton', TrackButton); 17816 17817 /** 17818 * @file menu-keys.js 17819 */ 17820 17821 /** 17822 * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`) 17823 * Note that 'Enter' and 'Space' are not included here (otherwise they would 17824 * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable) 17825 * @typedef MenuKeys 17826 * @array 17827 */ 17828 var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left']; 17829 17830 /** 17831 * The component for a menu item. `<li>` 17832 * 17833 * @extends ClickableComponent 17834 */ 17835 17836 var MenuItem = /*#__PURE__*/function (_ClickableComponent) { 17837 inheritsLoose(MenuItem, _ClickableComponent); 17838 17839 /** 17840 * Creates an instance of the this class. 17841 * 17842 * @param {Player} player 17843 * The `Player` that this class should be attached to. 17844 * 17845 * @param {Object} [options={}] 17846 * The key/value store of player options. 17847 * 17848 */ 17849 function MenuItem(player, options) { 17850 var _this; 17851 17852 _this = _ClickableComponent.call(this, player, options) || this; 17853 _this.selectable = options.selectable; 17854 _this.isSelected_ = options.selected || false; 17855 _this.multiSelectable = options.multiSelectable; 17856 17857 _this.selected(_this.isSelected_); 17858 17859 if (_this.selectable) { 17860 if (_this.multiSelectable) { 17861 _this.el_.setAttribute('role', 'menuitemcheckbox'); 17862 } else { 17863 _this.el_.setAttribute('role', 'menuitemradio'); 17864 } 17865 } else { 17866 _this.el_.setAttribute('role', 'menuitem'); 17867 } 17868 17869 return _this; 17870 } 17871 /** 17872 * Create the `MenuItem's DOM element 17873 * 17874 * @param {string} [type=li] 17875 * Element's node type, not actually used, always set to `li`. 17876 * 17877 * @param {Object} [props={}] 17878 * An object of properties that should be set on the element 17879 * 17880 * @param {Object} [attrs={}] 17881 * An object of attributes that should be set on the element 17882 * 17883 * @return {Element} 17884 * The element that gets created. 17885 */ 17886 17887
17888 var _proto = MenuItem.prototype; 17889 17890 _proto.createEl = function createEl(type, props, attrs) { 17891 // The control is textual, not just an icon 17892 this.nonIconControl = true; 17893 return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ 17894 className: 'vjs-menu-item', 17895 innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>", 17896 tabIndex: -1 17897 }, props), attrs); 17898 } 17899 /** 17900 * Ignore keys which are used by the menu, but pass any other ones up. See 17901 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 17902 * 17903 * @param {EventTarget~Event} event 17904 * The `keydown` event that caused this function to be called. 17905 * 17906 * @listens keydown 17907 */ 17908 ; 17909 17910 _proto.handleKeyDown = function handleKeyDown(event) { 17911 if (!MenuKeys.some(function (key) { 17912 return keycode.isEventKey(event, key); 17913 })) { 17914 // Pass keydown handling up for unused keys 17915 _ClickableComponent.prototype.handleKeyDown.call(this, event); 17916 } 17917 } 17918 /** 17919 * Any click on a `MenuItem` puts it into the selected state. 17920 * See {@link ClickableComponent#handleClick} for instances where this is called. 17921 * 17922 * @param {EventTarget~Event} event 17923 * The `keydown`, `tap`, or `click` event that caused this function to be 17924 * called. 17925 * 17926 * @listens tap 17927 * @listens click 17928 */ 17929 ; 17930 17931 _proto.handleClick = function handleClick(event) { 17932 this.selected(true); 17933 } 17934 /** 17935 * Set the state for this menu item as selected or not. 17936 * 17937 * @param {boolean} selected 17938 * if the menu item is selected or not 17939 */ 17940 ; 17941 17942 _proto.selected = function selected(_selected) { 17943 if (this.selectable) { 17944 if (_selected) { 17945 this.addClass('vjs-selected'); 17946 this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers, 17947 // so indicate selected state to screen reader in the control text. 17948 17949 this.controlText(', selected'); 17950 this.isSelected_ = true; 17951 } else { 17952 this.removeClass('vjs-selected'); 17953 this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader 17954 17955 this.controlText(''); 17956 this.isSelected_ = false; 17957 } 17958 } 17959 }; 17960 17961 return MenuItem; 17962 }(ClickableComponent); 17963 17964 Component.registerComponent('MenuItem', MenuItem); 17965 17966 /** 17967 * The specific menu item type for selecting a language within a text track kind 17968 * 17969 * @extends MenuItem 17970 */ 17971 17972 var TextTrackMenuItem = /*#__PURE__*/function (_MenuItem) { 17973 inheritsLoose(TextTrackMenuItem, _MenuItem); 17974 17975 /** 17976 * Creates an instance of this class. 17977 * 17978 * @param {Player} player 17979 * The `Player` that this class should be attached to. 17980 * 17981 * @param {Object} [options] 17982 * The key/value store of player options. 17983 */ 17984 function TextTrackMenuItem(player, options) { 17985 var _this; 17986 17987 var track = options.track; 17988 var tracks = player.textTracks(); // Modify options for parent MenuItem class's init. 17989 17990 options.label = track.label || track.language || 'Unknown'; 17991 options.selected = track.mode === 'showing'; 17992 _this = _MenuItem.call(this, player, options) || this; 17993 _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter 17994 // out empty kinds. 17995 17996 _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean); 17997 17998 var changeHandler = function changeHandler() { 17999 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 18000 args[_key] = arguments[_key]; 18001 } 18002 18003 _this.handleTracksChange.apply(assertThisInitialized(_this), args); 18004 }; 18005 18006 var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() { 18007 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 18008 args[_key2] = arguments[_key2]; 18009 } 18010 18011 _this.handleSelectedLanguageChange.apply(assertThisInitialized(_this), args); 18012 }; 18013 18014 player.on(['loadstart', 'texttrackchange'], changeHandler); 18015 tracks.addEventListener('change', changeHandler); 18016 tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 18017 18018 _this.on('dispose', function () { 18019 player.off(['loadstart', 'texttrackchange'], changeHandler); 18020 tracks.removeEventListener('change', changeHandler); 18021 tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 18022 }); // iOS7 doesn't dispatch change events to TextTrackLists when an 18023 // associated track's mode changes. Without something like 18024 // Object.observe() (also not present on iOS7), it's not 18025 // possible to detect changes to the mode attribute and polyfill 18026 // the change event. As a poor substitute, we manually dispatch 18027 // change events whenever the controls modify the mode. 18028 18029 18030 if (tracks.onchange === undefined) { 18031 var event; 18032 18033 _this.on(['tap', 'click'], function () { 18034 if (typeof window$3.Event !== 'object') { 18035 // Android 2.3 throws an Illegal Constructor error for window.Event 18036 try { 18037 event = new window$3.Event('change'); 18038 } catch (err) {// continue regardless of error 18039 } 18040 } 18041 18042 if (!event) { 18043 event = document.createEvent('Event'); 18044 event.initEvent('change', true, true); 18045 } 18046 18047 tracks.dispatchEvent(event); 18048 }); 18049 } // set the default state based on current tracks 18050 18051
vendor: 9,010 bytes, lines 18052-18333
18052 _this.handleTracksChange(); 18053 18054 return _this; 18055 } 18056 /** 18057 * This gets called when an `TextTrackMenuItem` is "clicked". See 18058 * {@link ClickableComponent} for more detailed information on what a click can be. 18059 * 18060 * @param {EventTarget~Event} event 18061 * The `keydown`, `tap`, or `click` event that caused this function to be 18062 * called. 18063 * 18064 * @listens tap 18065 * @listens click 18066 */ 18067 18068 18069 var _proto = TextTrackMenuItem.prototype; 18070 18071 _proto.handleClick = function handleClick(event) { 18072 var referenceTrack = this.track; 18073 var tracks = this.player_.textTracks(); 18074 18075 _MenuItem.prototype.handleClick.call(this, event); 18076 18077 if (!tracks) { 18078 return; 18079 } 18080 18081 for (var i = 0; i < tracks.length; i++) { 18082 var track = tracks[i]; // If the track from the text tracks list is not of the right kind, 18083 // skip it. We do not want to affect tracks of incompatible kind(s). 18084 18085 if (this.kinds.indexOf(track.kind) === -1) { 18086 continue; 18087 } // If this text track is the component's track and it is not showing, 18088 // set it to showing. 18089 18090 18091 if (track === referenceTrack) { 18092 if (track.mode !== 'showing') { 18093 track.mode = 'showing'; 18094 } // If this text track is not the component's track and it is not 18095 // disabled, set it to disabled. 18096 18097 } else if (track.mode !== 'disabled') { 18098 track.mode = 'disabled'; 18099 } 18100 } 18101 } 18102 /** 18103 * Handle text track list change 18104 * 18105 * @param {EventTarget~Event} event 18106 * The `change` event that caused this function to be called. 18107 * 18108 * @listens TextTrackList#change 18109 */ 18110 ; 18111 18112 _proto.handleTracksChange = function handleTracksChange(event) { 18113 var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause 18114 // screen readers to read the appended control text unnecessarily 18115 18116 if (shouldBeSelected !== this.isSelected_) { 18117 this.selected(shouldBeSelected); 18118 } 18119 }; 18120 18121 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 18122 if (this.track.mode === 'showing') { 18123 var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language 18124 18125 if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { 18126 return; 18127 } 18128 18129 this.player_.cache_.selectedLanguage = { 18130 enabled: true, 18131 language: this.track.language, 18132 kind: this.track.kind 18133 }; 18134 } 18135 }; 18136 18137 _proto.dispose = function dispose() { 18138 // remove reference to track object on dispose 18139 this.track = null; 18140 18141 _MenuItem.prototype.dispose.call(this); 18142 }; 18143 18144 return TextTrackMenuItem; 18145 }(MenuItem); 18146 18147 Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); 18148 18149 /** 18150 * A special menu item for turning of a specific type of text track 18151 * 18152 * @extends TextTrackMenuItem 18153 */ 18154 18155 var OffTextTrackMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) { 18156 inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem); 18157 18158 /** 18159 * Creates an instance of this class. 18160 * 18161 * @param {Player} player 18162 * The `Player` that this class should be attached to. 18163 * 18164 * @param {Object} [options] 18165 * The key/value store of player options. 18166 */ 18167 function OffTextTrackMenuItem(player, options) { 18168 // Create pseudo track info 18169 // Requires options['kind'] 18170 options.track = { 18171 player: player, 18172 // it is no longer necessary to store `kind` or `kinds` on the track itself 18173 // since they are now stored in the `kinds` property of all instances of 18174 // TextTrackMenuItem, but this will remain for backwards compatibility 18175 kind: options.kind, 18176 kinds: options.kinds, 18177 "default": false, 18178 mode: 'disabled' 18179 }; 18180 18181 if (!options.kinds) { 18182 options.kinds = [options.kind]; 18183 } 18184 18185 if (options.label) { 18186 options.track.label = options.label; 18187 } else { 18188 options.track.label = options.kinds.join(' and ') + ' off'; 18189 } // MenuItem is selectable 18190 18191 18192 options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 18193 18194 options.multiSelectable = false; 18195 return _TextTrackMenuItem.call(this, player, options) || this; 18196 } 18197 /** 18198 * Handle text track change 18199 * 18200 * @param {EventTarget~Event} event 18201 * The event that caused this function to run 18202 */ 18203 18204 18205 var _proto = OffTextTrackMenuItem.prototype; 18206 18207 _proto.handleTracksChange = function handleTracksChange(event) { 18208 var tracks = this.player().textTracks(); 18209 var shouldBeSelected = true; 18210 18211 for (var i = 0, l = tracks.length; i < l; i++) { 18212 var track = tracks[i]; 18213 18214 if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { 18215 shouldBeSelected = false; 18216 break; 18217 } 18218 } // Prevent redundant selected() calls because they may cause 18219 // screen readers to read the appended control text unnecessarily 18220 18221 18222 if (shouldBeSelected !== this.isSelected_) { 18223 this.selected(shouldBeSelected); 18224 } 18225 }; 18226 18227 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 18228 var tracks = this.player().textTracks(); 18229 var allHidden = true; 18230 18231 for (var i = 0, l = tracks.length; i < l; i++) { 18232 var track = tracks[i]; 18233 18234 if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { 18235 allHidden = false; 18236 break; 18237 } 18238 } 18239 18240 if (allHidden) { 18241 this.player_.cache_.selectedLanguage = { 18242 enabled: false 18243 }; 18244 } 18245 }; 18246 18247 return OffTextTrackMenuItem; 18248 }(TextTrackMenuItem); 18249 18250 Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); 18251 18252 /** 18253 * The base class for buttons that toggle specific text track types (e.g. subtitles) 18254 * 18255 * @extends MenuButton 18256 */ 18257 18258 var TextTrackButton = /*#__PURE__*/function (_TrackButton) { 18259 inheritsLoose(TextTrackButton, _TrackButton); 18260 18261 /** 18262 * Creates an instance of this class. 18263 * 18264 * @param {Player} player 18265 * The `Player` that this class should be attached to. 18266 * 18267 * @param {Object} [options={}] 18268 * The key/value store of player options. 18269 */ 18270 function TextTrackButton(player, options) { 18271 if (options === void 0) { 18272 options = {}; 18273 } 18274 18275 options.tracks = player.textTracks(); 18276 return _TrackButton.call(this, player, options) || this; 18277 } 18278 /** 18279 * Create a menu item for each text track 18280 * 18281 * @param {TextTrackMenuItem[]} [items=[]] 18282 * Existing array of items to use during creation 18283 * 18284 * @return {TextTrackMenuItem[]} 18285 * Array of menu items that were created 18286 */ 18287 18288 18289 var _proto = TextTrackButton.prototype; 18290 18291 _proto.createItems = function createItems(items, TrackMenuItem) { 18292 if (items === void 0) { 18293 items = []; 18294 } 18295 18296 if (TrackMenuItem === void 0) { 18297 TrackMenuItem = TextTrackMenuItem; 18298 } 18299 18300 // Label is an override for the [track] off label 18301 // USed to localise captions/subtitles 18302 var label; 18303 18304 if (this.label_) { 18305 label = this.label_ + " off"; 18306 } // Add an OFF menu item to turn all tracks off 18307 18308 18309 items.push(new OffTextTrackMenuItem(this.player_, { 18310 kinds: this.kinds_, 18311 kind: this.kind_, 18312 label: label 18313 })); 18314 this.hideThreshold_ += 1; 18315 var tracks = this.player_.textTracks(); 18316 18317 if (!Array.isArray(this.kinds_)) { 18318 this.kinds_ = [this.kind_]; 18319 } 18320 18321 for (var i = 0; i < tracks.length; i++) { 18322 var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label 18323 18324 if (this.kinds_.indexOf(track.kind) > -1) { 18325 var item = new TrackMenuItem(this.player_, { 18326 track: track, 18327 kinds: this.kinds_, 18328 kind: this.kind_, 18329 // MenuItem is selectable 18330 selectable: true, 18331 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 18332 multiSelectable: false 18333 });
18334 item.addClass("vjs-" + track.kind + "-menu-item"); 18335 items.push(item); 18336 } 18337 } 18338 18339 return items; 18340 }; 18341 18342 return TextTrackButton; 18343 }(TrackButton); 18344 18345 Component.registerComponent('TextTrackButton', TextTrackButton); 18346 18347 /** 18348 * The chapter track menu item 18349 * 18350 * @extends MenuItem 18351 */ 18352 18353 var ChaptersTrackMenuItem = /*#__PURE__*/function (_MenuItem) { 18354 inheritsLoose(ChaptersTrackMenuItem, _MenuItem); 18355 18356 /** 18357 * Creates an instance of this class. 18358 * 18359 * @param {Player} player 18360 * The `Player` that this class should be attached to. 18361 * 18362 * @param {Object} [options] 18363 * The key/value store of player options. 18364 */ 18365 function ChaptersTrackMenuItem(player, options) { 18366 var _this; 18367 18368 var track = options.track; 18369 var cue = options.cue; 18370 var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init. 18371 18372 options.selectable = true; 18373 options.multiSelectable = false; 18374 options.label = cue.text; 18375 options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; 18376 _this = _MenuItem.call(this, player, options) || this; 18377 _this.track = track; 18378 _this.cue = cue; 18379 track.addEventListener('cuechange', bind(assertThisInitialized(_this), _this.update)); 18380 return _this; 18381 } 18382 /** 18383 * This gets called when an `ChaptersTrackMenuItem` is "clicked". See 18384 * {@link ClickableComponent} for more detailed information on what a click can be. 18385 * 18386 * @param {EventTarget~Event} [event] 18387 * The `keydown`, `tap`, or `click` event that caused this function to be 18388 * called. 18389 * 18390 * @listens tap 18391 * @listens click 18392 */ 18393 18394 18395 var _proto = ChaptersTrackMenuItem.prototype; 18396 18397 _proto.handleClick = function handleClick(event) { 18398 _MenuItem.prototype.handleClick.call(this); 18399 18400 this.player_.currentTime(this.cue.startTime); 18401 this.update(this.cue.startTime); 18402 } 18403 /** 18404 * Update chapter menu item 18405 * 18406 * @param {EventTarget~Event} [event] 18407 * The `cuechange` event that caused this function to run. 18408 * 18409 * @listens TextTrack#cuechange 18410 */ 18411 ; 18412 18413 _proto.update = function update(event) { 18414 var cue = this.cue; 18415 var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime); 18416 18417 this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); 18418 }; 18419 18420 return ChaptersTrackMenuItem; 18421 }(MenuItem); 18422 18423 Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); 18424 18425 /** 18426 * The button component for toggling and selecting chapters 18427 * Chapters act much differently than other text tracks 18428 * Cues are navigation vs. other tracks of alternative languages 18429 * 18430 * @extends TextTrackButton 18431 */ 18432 18433 var ChaptersButton = /*#__PURE__*/function (_TextTrackButton) { 18434 inheritsLoose(ChaptersButton, _TextTrackButton); 18435 18436 /** 18437 * Creates an instance of this class. 18438 * 18439 * @param {Player} player 18440 * The `Player` that this class should be attached to. 18441 * 18442 * @param {Object} [options] 18443 * The key/value store of player options. 18444 * 18445 * @param {Component~ReadyCallback} [ready] 18446 * The function to call when this function is ready. 18447 */ 18448 function ChaptersButton(player, options, ready) { 18449 return _TextTrackButton.call(this, player, options, ready) || this; 18450 } 18451 /** 18452 * Builds the default DOM `className`. 18453 * 18454 * @return {string} 18455 * The DOM `className` for this object. 18456 */ 18457 18458 18459 var _proto = ChaptersButton.prototype; 18460 18461 _proto.buildCSSClass = function buildCSSClass() { 18462 return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18463 }; 18464 18465 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18466 return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18467 } 18468 /** 18469 * Update the menu based on the current state of its items. 18470 * 18471 * @param {EventTarget~Event} [event] 18472 * An event that triggered this function to run. 18473 * 18474 * @listens TextTrackList#addtrack 18475 * @listens TextTrackList#removetrack 18476 * @listens TextTrackList#change 18477 */ 18478 ; 18479 18480 _proto.update = function update(event) { 18481 if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
vendor: 6,005 bytes, lines 18482-18690
18482 this.setTrack(this.findChaptersTrack()); 18483 } 18484 18485 _TextTrackButton.prototype.update.call(this); 18486 } 18487 /** 18488 * Set the currently selected track for the chapters button. 18489 * 18490 * @param {TextTrack} track 18491 * The new track to select. Nothing will change if this is the currently selected 18492 * track. 18493 */ 18494 ; 18495 18496 _proto.setTrack = function setTrack(track) { 18497 if (this.track_ === track) { 18498 return; 18499 } 18500 18501 if (!this.updateHandler_) { 18502 this.updateHandler_ = this.update.bind(this); 18503 } // here this.track_ refers to the old track instance 18504 18505 18506 if (this.track_) { 18507 var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 18508 18509 if (remoteTextTrackEl) { 18510 remoteTextTrackEl.removeEventListener('load', this.updateHandler_); 18511 } 18512 18513 this.track_ = null; 18514 } 18515 18516 this.track_ = track; // here this.track_ refers to the new track instance 18517 18518 if (this.track_) { 18519 this.track_.mode = 'hidden'; 18520 18521 var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 18522 18523 if (_remoteTextTrackEl) { 18524 _remoteTextTrackEl.addEventListener('load', this.updateHandler_); 18525 } 18526 } 18527 } 18528 /** 18529 * Find the track object that is currently in use by this ChaptersButton 18530 * 18531 * @return {TextTrack|undefined} 18532 * The current track or undefined if none was found. 18533 */ 18534 ; 18535 18536 _proto.findChaptersTrack = function findChaptersTrack() { 18537 var tracks = this.player_.textTracks() || []; 18538 18539 for (var i = tracks.length - 1; i >= 0; i--) { 18540 // We will always choose the last track as our chaptersTrack 18541 var track = tracks[i]; 18542 18543 if (track.kind === this.kind_) { 18544 return track; 18545 } 18546 } 18547 } 18548 /** 18549 * Get the caption for the ChaptersButton based on the track label. This will also 18550 * use the current tracks localized kind as a fallback if a label does not exist. 18551 * 18552 * @return {string} 18553 * The tracks current label or the localized track kind. 18554 */ 18555 ; 18556 18557 _proto.getMenuCaption = function getMenuCaption() { 18558 if (this.track_ && this.track_.label) { 18559 return this.track_.label; 18560 } 18561 18562 return this.localize(toTitleCase(this.kind_)); 18563 } 18564 /** 18565 * Create menu from chapter track 18566 * 18567 * @return {Menu} 18568 * New menu for the chapter buttons 18569 */ 18570 ; 18571 18572 _proto.createMenu = function createMenu() { 18573 this.options_.title = this.getMenuCaption(); 18574 return _TextTrackButton.prototype.createMenu.call(this); 18575 } 18576 /** 18577 * Create a menu item for each text track 18578 * 18579 * @return {TextTrackMenuItem[]} 18580 * Array of menu items 18581 */ 18582 ; 18583 18584 _proto.createItems = function createItems() { 18585 var items = []; 18586 18587 if (!this.track_) { 18588 return items; 18589 } 18590 18591 var cues = this.track_.cues; 18592 18593 if (!cues) { 18594 return items; 18595 } 18596 18597 for (var i = 0, l = cues.length; i < l; i++) { 18598 var cue = cues[i]; 18599 var mi = new ChaptersTrackMenuItem(this.player_, { 18600 track: this.track_, 18601 cue: cue 18602 }); 18603 items.push(mi); 18604 } 18605 18606 return items; 18607 }; 18608 18609 return ChaptersButton; 18610 }(TextTrackButton); 18611 /** 18612 * `kind` of TextTrack to look for to associate it with this menu. 18613 * 18614 * @type {string} 18615 * @private 18616 */ 18617 18618 18619 ChaptersButton.prototype.kind_ = 'chapters'; 18620 /** 18621 * The text that should display over the `ChaptersButton`s controls. Added for localization. 18622 * 18623 * @type {string} 18624 * @private 18625 */ 18626 18627 ChaptersButton.prototype.controlText_ = 'Chapters'; 18628 Component.registerComponent('ChaptersButton', ChaptersButton); 18629 18630 /** 18631 * The button component for toggling and selecting descriptions 18632 * 18633 * @extends TextTrackButton 18634 */ 18635 18636 var DescriptionsButton = /*#__PURE__*/function (_TextTrackButton) { 18637 inheritsLoose(DescriptionsButton, _TextTrackButton); 18638 18639 /** 18640 * Creates an instance of this class. 18641 * 18642 * @param {Player} player 18643 * The `Player` that this class should be attached to. 18644 * 18645 * @param {Object} [options] 18646 * The key/value store of player options. 18647 * 18648 * @param {Component~ReadyCallback} [ready] 18649 * The function to call when this component is ready. 18650 */ 18651 function DescriptionsButton(player, options, ready) { 18652 var _this; 18653 18654 _this = _TextTrackButton.call(this, player, options, ready) || this; 18655 var tracks = player.textTracks(); 18656 var changeHandler = bind(assertThisInitialized(_this), _this.handleTracksChange); 18657 tracks.addEventListener('change', changeHandler); 18658 18659 _this.on('dispose', function () { 18660 tracks.removeEventListener('change', changeHandler); 18661 }); 18662 18663 return _this; 18664 } 18665 /** 18666 * Handle text track change 18667 * 18668 * @param {EventTarget~Event} event 18669 * The event that caused this function to run 18670 * 18671 * @listens TextTrackList#change 18672 */ 18673 18674 18675 var _proto = DescriptionsButton.prototype; 18676 18677 _proto.handleTracksChange = function handleTracksChange(event) { 18678 var tracks = this.player().textTracks(); 18679 var disabled = false; // Check whether a track of a different kind is showing 18680 18681 for (var i = 0, l = tracks.length; i < l; i++) { 18682 var track = tracks[i]; 18683 18684 if (track.kind !== this.kind_ && track.mode === 'showing') { 18685 disabled = true; 18686 break; 18687 } 18688 } // If another track is showing, disable this menu button 18689 18690
18691 if (disabled) { 18692 this.disable(); 18693 } else { 18694 this.enable(); 18695 } 18696 } 18697 /** 18698 * Builds the default DOM `className`. 18699 * 18700 * @return {string} 18701 * The DOM `className` for this object. 18702 */ 18703 ; 18704 18705 _proto.buildCSSClass = function buildCSSClass() { 18706 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18707 }; 18708 18709 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18710 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18711 }; 18712 18713 return DescriptionsButton; 18714 }(TextTrackButton); 18715 /** 18716 * `kind` of TextTrack to look for to associate it with this menu. 18717 * 18718 * @type {string} 18719 * @private 18720 */ 18721 18722 18723 DescriptionsButton.prototype.kind_ = 'descriptions'; 18724 /** 18725 * The text that should display over the `DescriptionsButton`s controls. Added for localization. 18726 * 18727 * @type {string} 18728 * @private 18729 */ 18730 18731 DescriptionsButton.prototype.controlText_ = 'Descriptions'; 18732 Component.registerComponent('DescriptionsButton', DescriptionsButton); 18733 18734 /** 18735 * The button component for toggling and selecting subtitles 18736 * 18737 * @extends TextTrackButton 18738 */ 18739 18740 var SubtitlesButton = /*#__PURE__*/function (_TextTrackButton) { 18741 inheritsLoose(SubtitlesButton, _TextTrackButton); 18742 18743 /** 18744 * Creates an instance of this class. 18745 * 18746 * @param {Player} player 18747 * The `Player` that this class should be attached to. 18748 * 18749 * @param {Object} [options] 18750 * The key/value store of player options. 18751 * 18752 * @param {Component~ReadyCallback} [ready] 18753 * The function to call when this component is ready. 18754 */ 18755 function SubtitlesButton(player, options, ready) { 18756 return _TextTrackButton.call(this, player, options, ready) || this; 18757 } 18758 /** 18759 * Builds the default DOM `className`. 18760 * 18761 * @return {string} 18762 * The DOM `className` for this object. 18763 */ 18764 18765 18766 var _proto = SubtitlesButton.prototype; 18767 18768 _proto.buildCSSClass = function buildCSSClass() { 18769 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18770 }; 18771 18772 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18773 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18774 }; 18775 18776 return SubtitlesButton; 18777 }(TextTrackButton); 18778 /** 18779 * `kind` of TextTrack to look for to associate it with this menu. 18780 * 18781 * @type {string} 18782 * @private 18783 */ 18784 18785 18786 SubtitlesButton.prototype.kind_ = 'subtitles'; 18787 /** 18788 * The text that should display over the `SubtitlesButton`s controls. Added for localization. 18789 * 18790 * @type {string} 18791 * @private 18792 */ 18793 18794 SubtitlesButton.prototype.controlText_ = 'Subtitles'; 18795 Component.registerComponent('SubtitlesButton', SubtitlesButton); 18796 18797 /** 18798 * The menu item for caption track settings menu 18799 * 18800 * @extends TextTrackMenuItem 18801 */ 18802 18803 var CaptionSettingsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) { 18804 inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem); 18805 18806 /** 18807 * Creates an instance of this class. 18808 * 18809 * @param {Player} player 18810 * The `Player` that this class should be attached to. 18811 * 18812 * @param {Object} [options] 18813 * The key/value store of player options. 18814 */ 18815 function CaptionSettingsMenuItem(player, options) { 18816 var _this; 18817 18818 options.track = { 18819 player: player, 18820 kind: options.kind, 18821 label: options.kind + ' settings', 18822 selectable: false, 18823 "default": false, 18824 mode: 'disabled' 18825 }; // CaptionSettingsMenuItem has no concept of 'selected' 18826 18827 options.selectable = false; 18828 options.name = 'CaptionSettingsMenuItem'; 18829 _this = _TextTrackMenuItem.call(this, player, options) || this; 18830 18831 _this.addClass('vjs-texttrack-settings'); 18832 18833 _this.controlText(', opens ' + options.kind + ' settings dialog'); 18834 18835 return _this; 18836 } 18837 /** 18838 * This gets called when an `CaptionSettingsMenuItem` is "clicked". See 18839 * {@link ClickableComponent} for more detailed information on what a click can be. 18840 * 18841 * @param {EventTarget~Event} [event]
18842 * The `keydown`, `tap`, or `click` event that caused this function to be 18843 * called. 18844 * 18845 * @listens tap 18846 * @listens click 18847 */ 18848 18849 18850 var _proto = CaptionSettingsMenuItem.prototype; 18851 18852 _proto.handleClick = function handleClick(event) { 18853 this.player().getChild('textTrackSettings').open(); 18854 }; 18855 18856 return CaptionSettingsMenuItem; 18857 }(TextTrackMenuItem); 18858 18859 Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); 18860 18861 /** 18862 * The button component for toggling and selecting captions 18863 * 18864 * @extends TextTrackButton 18865 */ 18866 18867 var CaptionsButton = /*#__PURE__*/function (_TextTrackButton) { 18868 inheritsLoose(CaptionsButton, _TextTrackButton); 18869 18870 /** 18871 * Creates an instance of this class. 18872 * 18873 * @param {Player} player 18874 * The `Player` that this class should be attached to. 18875 * 18876 * @param {Object} [options] 18877 * The key/value store of player options. 18878 * 18879 * @param {Component~ReadyCallback} [ready] 18880 * The function to call when this component is ready. 18881 */ 18882 function CaptionsButton(player, options, ready) { 18883 return _TextTrackButton.call(this, player, options, ready) || this; 18884 } 18885 /** 18886 * Builds the default DOM `className`. 18887 * 18888 * @return {string} 18889 * The DOM `className` for this object. 18890 */ 18891 18892 18893 var _proto = CaptionsButton.prototype; 18894 18895 _proto.buildCSSClass = function buildCSSClass() { 18896 return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18897 }; 18898 18899 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18900 return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18901 } 18902 /** 18903 * Create caption menu items 18904 * 18905 * @return {CaptionSettingsMenuItem[]} 18906 * The array of current menu items. 18907 */ 18908 ; 18909 18910 _proto.createItems = function createItems() { 18911 var items = []; 18912 18913 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 18914 items.push(new CaptionSettingsMenuItem(this.player_, { 18915 kind: this.kind_ 18916 })); 18917 this.hideThreshold_ += 1; 18918 } 18919 18920 return _TextTrackButton.prototype.createItems.call(this, items); 18921 }; 18922 18923 return CaptionsButton; 18924 }(TextTrackButton); 18925 /** 18926 * `kind` of TextTrack to look for to associate it with this menu. 18927 * 18928 * @type {string} 18929 * @private 18930 */ 18931 18932 18933 CaptionsButton.prototype.kind_ = 'captions'; 18934 /** 18935 * The text that should display over the `CaptionsButton`s controls. Added for localization. 18936 * 18937 * @type {string} 18938 * @private 18939 */ 18940 18941 CaptionsButton.prototype.controlText_ = 'Captions'; 18942 Component.registerComponent('CaptionsButton', CaptionsButton); 18943 18944 /** 18945 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles 18946 * in the SubsCapsMenu. 18947 * 18948 * @extends TextTrackMenuItem 18949 */ 18950 18951 var SubsCapsMenuItem = /*#__PURE__*/function (_TextTrackMenuItem) { 18952 inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem); 18953 18954 function SubsCapsMenuItem() { 18955 return _TextTrackMenuItem.apply(this, arguments) || this; 18956 } 18957 18958 var _proto = SubsCapsMenuItem.prototype; 18959 18960 _proto.createEl = function createEl(type, props, attrs) { 18961 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 18962 18963 if (this.options_.track.kind === 'captions') { 18964 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n "; 18965 } 18966 18967 innerHTML += '</span>'; 18968 18969 var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({ 18970 innerHTML: innerHTML 18971 }, props), attrs); 18972 18973 return el; 18974 }; 18975 18976 return SubsCapsMenuItem; 18977 }(TextTrackMenuItem); 18978 18979 Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); 18980 18981 /** 18982 * The button component for toggling and selecting captions and/or subtitles 18983 * 18984 * @extends TextTrackButton 18985 */ 18986 18987 var SubsCapsButton = /*#__PURE__*/function (_TextTrackButton) {
vendor: 18,171 bytes, lines 18988-19603
18988 inheritsLoose(SubsCapsButton, _TextTrackButton); 18989 18990 function SubsCapsButton(player, options) { 18991 var _this; 18992 18993 if (options === void 0) { 18994 options = {}; 18995 } 18996 18997 _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for 18998 // "captions and subtitles" other locales use "subtitles" 18999 19000 _this.label_ = 'subtitles'; 19001 19002 if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { 19003 _this.label_ = 'captions'; 19004 } 19005 19006 _this.menuButton_.controlText(toTitleCase(_this.label_)); 19007 19008 return _this; 19009 } 19010 /** 19011 * Builds the default DOM `className`. 19012 * 19013 * @return {string} 19014 * The DOM `className` for this object. 19015 */ 19016 19017 19018 var _proto = SubsCapsButton.prototype; 19019 19020 _proto.buildCSSClass = function buildCSSClass() { 19021 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19022 }; 19023 19024 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19025 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19026 } 19027 /** 19028 * Create caption/subtitles menu items 19029 * 19030 * @return {CaptionSettingsMenuItem[]} 19031 * The array of current menu items. 19032 */ 19033 ; 19034 19035 _proto.createItems = function createItems() { 19036 var items = []; 19037 19038 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 19039 items.push(new CaptionSettingsMenuItem(this.player_, { 19040 kind: this.label_ 19041 })); 19042 this.hideThreshold_ += 1; 19043 } 19044 19045 items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); 19046 return items; 19047 }; 19048 19049 return SubsCapsButton; 19050 }(TextTrackButton); 19051 /** 19052 * `kind`s of TextTrack to look for to associate it with this menu. 19053 * 19054 * @type {array} 19055 * @private 19056 */ 19057 19058 19059 SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; 19060 /** 19061 * The text that should display over the `SubsCapsButton`s controls. 19062 * 19063 * 19064 * @type {string} 19065 * @private 19066 */ 19067 19068 SubsCapsButton.prototype.controlText_ = 'Subtitles'; 19069 Component.registerComponent('SubsCapsButton', SubsCapsButton); 19070 19071 /** 19072 * An {@link AudioTrack} {@link MenuItem} 19073 * 19074 * @extends MenuItem 19075 */ 19076 19077 var AudioTrackMenuItem = /*#__PURE__*/function (_MenuItem) { 19078 inheritsLoose(AudioTrackMenuItem, _MenuItem); 19079 19080 /** 19081 * Creates an instance of this class. 19082 * 19083 * @param {Player} player 19084 * The `Player` that this class should be attached to. 19085 * 19086 * @param {Object} [options] 19087 * The key/value store of player options. 19088 */ 19089 function AudioTrackMenuItem(player, options) { 19090 var _this; 19091 19092 var track = options.track; 19093 var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init. 19094 19095 options.label = track.label || track.language || 'Unknown'; 19096 options.selected = track.enabled; 19097 _this = _MenuItem.call(this, player, options) || this; 19098 _this.track = track; 19099 19100 _this.addClass("vjs-" + track.kind + "-menu-item"); 19101 19102 var changeHandler = function changeHandler() { 19103 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 19104 args[_key] = arguments[_key]; 19105 } 19106 19107 _this.handleTracksChange.apply(assertThisInitialized(_this), args); 19108 }; 19109 19110 tracks.addEventListener('change', changeHandler); 19111 19112 _this.on('dispose', function () { 19113 tracks.removeEventListener('change', changeHandler); 19114 }); 19115 19116 return _this; 19117 } 19118 19119 var _proto = AudioTrackMenuItem.prototype; 19120 19121 _proto.createEl = function createEl(type, props, attrs) { 19122 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 19123 19124 if (this.options_.track.kind === 'main-desc') { 19125 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n "; 19126 } 19127 19128 innerHTML += '</span>'; 19129 19130 var el = _MenuItem.prototype.createEl.call(this, type, assign({ 19131 innerHTML: innerHTML 19132 }, props), attrs); 19133 19134 return el; 19135 } 19136 /** 19137 * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} 19138 * for more detailed information on what a click can be. 19139 * 19140 * @param {EventTarget~Event} [event] 19141 * The `keydown`, `tap`, or `click` event that caused this function to be 19142 * called. 19143 * 19144 * @listens tap 19145 * @listens click 19146 */ 19147 ; 19148 19149 _proto.handleClick = function handleClick(event) { 19150 var tracks = this.player_.audioTracks(); 19151 19152 _MenuItem.prototype.handleClick.call(this, event); 19153 19154 for (var i = 0; i < tracks.length; i++) { 19155 var track = tracks[i]; 19156 track.enabled = track === this.track; 19157 } 19158 } 19159 /** 19160 * Handle any {@link AudioTrack} change. 19161 * 19162 * @param {EventTarget~Event} [event] 19163 * The {@link AudioTrackList#change} event that caused this to run. 19164 * 19165 * @listens AudioTrackList#change 19166 */ 19167 ; 19168 19169 _proto.handleTracksChange = function handleTracksChange(event) { 19170 this.selected(this.track.enabled); 19171 }; 19172 19173 return AudioTrackMenuItem; 19174 }(MenuItem); 19175 19176 Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); 19177 19178 /** 19179 * The base class for buttons that toggle specific {@link AudioTrack} types. 19180 * 19181 * @extends TrackButton 19182 */ 19183 19184 var AudioTrackButton = /*#__PURE__*/function (_TrackButton) { 19185 inheritsLoose(AudioTrackButton, _TrackButton); 19186 19187 /** 19188 * Creates an instance of this class. 19189 * 19190 * @param {Player} player 19191 * The `Player` that this class should be attached to. 19192 * 19193 * @param {Object} [options={}] 19194 * The key/value store of player options. 19195 */ 19196 function AudioTrackButton(player, options) { 19197 if (options === void 0) { 19198 options = {}; 19199 } 19200 19201 options.tracks = player.audioTracks(); 19202 return _TrackButton.call(this, player, options) || this; 19203 } 19204 /** 19205 * Builds the default DOM `className`. 19206 * 19207 * @return {string} 19208 * The DOM `className` for this object. 19209 */ 19210 19211 19212 var _proto = AudioTrackButton.prototype; 19213 19214 _proto.buildCSSClass = function buildCSSClass() { 19215 return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this); 19216 }; 19217 19218 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19219 return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this); 19220 } 19221 /** 19222 * Create a menu item for each audio track 19223 * 19224 * @param {AudioTrackMenuItem[]} [items=[]] 19225 * An array of existing menu items to use. 19226 * 19227 * @return {AudioTrackMenuItem[]} 19228 * An array of menu items 19229 */ 19230 ; 19231 19232 _proto.createItems = function createItems(items) { 19233 if (items === void 0) { 19234 items = []; 19235 } 19236 19237 // if there's only one audio track, there no point in showing it 19238 this.hideThreshold_ = 1; 19239 var tracks = this.player_.audioTracks(); 19240 19241 for (var i = 0; i < tracks.length; i++) { 19242 var track = tracks[i]; 19243 items.push(new AudioTrackMenuItem(this.player_, { 19244 track: track, 19245 // MenuItem is selectable 19246 selectable: true, 19247 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 19248 multiSelectable: false 19249 })); 19250 } 19251 19252 return items; 19253 }; 19254 19255 return AudioTrackButton; 19256 }(TrackButton); 19257 /** 19258 * The text that should display over the `AudioTrackButton`s controls. Added for localization. 19259 * 19260 * @type {string} 19261 * @private 19262 */ 19263 19264 19265 AudioTrackButton.prototype.controlText_ = 'Audio Track'; 19266 Component.registerComponent('AudioTrackButton', AudioTrackButton); 19267 19268 /** 19269 * The specific menu item type for selecting a playback rate. 19270 * 19271 * @extends MenuItem 19272 */ 19273 19274 var PlaybackRateMenuItem = /*#__PURE__*/function (_MenuItem) { 19275 inheritsLoose(PlaybackRateMenuItem, _MenuItem); 19276 19277 /** 19278 * Creates an instance of this class. 19279 * 19280 * @param {Player} player 19281 * The `Player` that this class should be attached to. 19282 * 19283 * @param {Object} [options] 19284 * The key/value store of player options. 19285 */ 19286 function PlaybackRateMenuItem(player, options) { 19287 var _this; 19288 19289 var label = options.rate; 19290 var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init. 19291 19292 options.label = label; 19293 options.selected = rate === 1; 19294 options.selectable = true; 19295 options.multiSelectable = false; 19296 _this = _MenuItem.call(this, player, options) || this; 19297 _this.label = label; 19298 _this.rate = rate; 19299 19300 _this.on(player, 'ratechange', _this.update); 19301 19302 return _this; 19303 } 19304 /** 19305 * This gets called when an `PlaybackRateMenuItem` is "clicked". See 19306 * {@link ClickableComponent} for more detailed information on what a click can be. 19307 * 19308 * @param {EventTarget~Event} [event] 19309 * The `keydown`, `tap`, or `click` event that caused this function to be 19310 * called. 19311 * 19312 * @listens tap 19313 * @listens click 19314 */ 19315 19316 19317 var _proto = PlaybackRateMenuItem.prototype; 19318 19319 _proto.handleClick = function handleClick(event) { 19320 _MenuItem.prototype.handleClick.call(this); 19321 19322 this.player().playbackRate(this.rate); 19323 } 19324 /** 19325 * Update the PlaybackRateMenuItem when the playbackrate changes. 19326 * 19327 * @param {EventTarget~Event} [event] 19328 * The `ratechange` event that caused this function to run. 19329 * 19330 * @listens Player#ratechange 19331 */ 19332 ; 19333 19334 _proto.update = function update(event) { 19335 this.selected(this.player().playbackRate() === this.rate); 19336 }; 19337 19338 return PlaybackRateMenuItem; 19339 }(MenuItem); 19340 /** 19341 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. 19342 * 19343 * @type {string} 19344 * @private 19345 */ 19346 19347 19348 PlaybackRateMenuItem.prototype.contentElType = 'button'; 19349 Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); 19350 19351 /** 19352 * The component for controlling the playback rate. 19353 * 19354 * @extends MenuButton 19355 */ 19356 19357 var PlaybackRateMenuButton = /*#__PURE__*/function (_MenuButton) { 19358 inheritsLoose(PlaybackRateMenuButton, _MenuButton); 19359 19360 /** 19361 * Creates an instance of this class. 19362 * 19363 * @param {Player} player 19364 * The `Player` that this class should be attached to. 19365 * 19366 * @param {Object} [options] 19367 * The key/value store of player options. 19368 */ 19369 function PlaybackRateMenuButton(player, options) { 19370 var _this; 19371 19372 _this = _MenuButton.call(this, player, options) || this; 19373 19374 _this.updateVisibility(); 19375 19376 _this.updateLabel(); 19377 19378 _this.on(player, 'loadstart', _this.updateVisibility); 19379 19380 _this.on(player, 'ratechange', _this.updateLabel); 19381 19382 return _this; 19383 } 19384 /** 19385 * Create the `Component`'s DOM element 19386 * 19387 * @return {Element} 19388 * The element that was created. 19389 */ 19390 19391 19392 var _proto = PlaybackRateMenuButton.prototype; 19393 19394 _proto.createEl = function createEl$1() { 19395 var el = _MenuButton.prototype.createEl.call(this); 19396 19397 this.labelEl_ = createEl('div', { 19398 className: 'vjs-playback-rate-value', 19399 innerHTML: '1x' 19400 }); 19401 el.appendChild(this.labelEl_); 19402 return el; 19403 }; 19404 19405 _proto.dispose = function dispose() { 19406 this.labelEl_ = null; 19407 19408 _MenuButton.prototype.dispose.call(this); 19409 } 19410 /** 19411 * Builds the default DOM `className`. 19412 * 19413 * @return {string} 19414 * The DOM `className` for this object. 19415 */ 19416 ; 19417 19418 _proto.buildCSSClass = function buildCSSClass() { 19419 return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this); 19420 }; 19421 19422 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19423 return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this); 19424 } 19425 /** 19426 * Create the playback rate menu 19427 * 19428 * @return {Menu} 19429 * Menu object populated with {@link PlaybackRateMenuItem}s 19430 */ 19431 ; 19432 19433 _proto.createMenu = function createMenu() { 19434 var menu = new Menu(this.player()); 19435 var rates = this.playbackRates(); 19436 19437 if (rates) { 19438 for (var i = rates.length - 1; i >= 0; i--) { 19439 menu.addChild(new PlaybackRateMenuItem(this.player(), { 19440 rate: rates[i] + 'x' 19441 })); 19442 } 19443 } 19444 19445 return menu; 19446 } 19447 /** 19448 * Updates ARIA accessibility attributes 19449 */ 19450 ; 19451 19452 _proto.updateARIAAttributes = function updateARIAAttributes() { 19453 // Current playback rate 19454 this.el().setAttribute('aria-valuenow', this.player().playbackRate()); 19455 } 19456 /** 19457 * This gets called when an `PlaybackRateMenuButton` is "clicked". See 19458 * {@link ClickableComponent} for more detailed information on what a click can be. 19459 * 19460 * @param {EventTarget~Event} [event] 19461 * The `keydown`, `tap`, or `click` event that caused this function to be 19462 * called. 19463 * 19464 * @listens tap 19465 * @listens click 19466 */ 19467 ; 19468 19469 _proto.handleClick = function handleClick(event) { 19470 // select next rate option 19471 var currentRate = this.player().playbackRate(); 19472 var rates = this.playbackRates(); // this will select first one if the last one currently selected 19473 19474 var newRate = rates[0]; 19475 19476 for (var i = 0; i < rates.length; i++) { 19477 if (rates[i] > currentRate) { 19478 newRate = rates[i]; 19479 break; 19480 } 19481 } 19482 19483 this.player().playbackRate(newRate); 19484 } 19485 /** 19486 * Get possible playback rates 19487 * 19488 * @return {Array} 19489 * All possible playback rates 19490 */ 19491 ; 19492 19493 _proto.playbackRates = function playbackRates() { 19494 return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; 19495 } 19496 /** 19497 * Get whether playback rates is supported by the tech 19498 * and an array of playback rates exists 19499 * 19500 * @return {boolean} 19501 * Whether changing playback rate is supported 19502 */ 19503 ; 19504 19505 _proto.playbackRateSupported = function playbackRateSupported() { 19506 return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; 19507 } 19508 /** 19509 * Hide playback rate controls when they're no playback rate options to select 19510 * 19511 * @param {EventTarget~Event} [event] 19512 * The event that caused this function to run. 19513 * 19514 * @listens Player#loadstart 19515 */ 19516 ; 19517 19518 _proto.updateVisibility = function updateVisibility(event) { 19519 if (this.playbackRateSupported()) { 19520 this.removeClass('vjs-hidden'); 19521 } else { 19522 this.addClass('vjs-hidden'); 19523 } 19524 } 19525 /** 19526 * Update button label when rate changed 19527 * 19528 * @param {EventTarget~Event} [event] 19529 * The event that caused this function to run. 19530 * 19531 * @listens Player#ratechange 19532 */ 19533 ; 19534 19535 _proto.updateLabel = function updateLabel(event) { 19536 if (this.playbackRateSupported()) { 19537 this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; 19538 } 19539 }; 19540 19541 return PlaybackRateMenuButton; 19542 }(MenuButton); 19543 /** 19544 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 19545 * 19546 * @type {string} 19547 * @private 19548 */ 19549 19550 19551 PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; 19552 Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); 19553 19554 /** 19555 * Just an empty spacer element that can be used as an append point for plugins, etc. 19556 * Also can be used to create space between elements when necessary. 19557 * 19558 * @extends Component 19559 */ 19560 19561 var Spacer = /*#__PURE__*/function (_Component) { 19562 inheritsLoose(Spacer, _Component); 19563 19564 function Spacer() { 19565 return _Component.apply(this, arguments) || this; 19566 } 19567 19568 var _proto = Spacer.prototype; 19569 19570 /** 19571 * Builds the default DOM `className`. 19572 * 19573 * @return {string} 19574 * The DOM `className` for this object. 19575 */ 19576 _proto.buildCSSClass = function buildCSSClass() { 19577 return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this); 19578 } 19579 /** 19580 * Create the `Component`'s DOM element 19581 * 19582 * @return {Element} 19583 * The element that was created. 19584 */ 19585 ; 19586 19587 _proto.createEl = function createEl() { 19588 return _Component.prototype.createEl.call(this, 'div', { 19589 className: this.buildCSSClass() 19590 }); 19591 }; 19592 19593 return Spacer; 19594 }(Component); 19595 19596 Component.registerComponent('Spacer', Spacer); 19597 19598 /** 19599 * Spacer specifically meant to be used as an insertion point for new plugins, etc. 19600 * 19601 * @extends Spacer 19602 */ 19603
19604 var CustomControlSpacer = /*#__PURE__*/function (_Spacer) { 19605 inheritsLoose(CustomControlSpacer, _Spacer); 19606 19607 function CustomControlSpacer() { 19608 return _Spacer.apply(this, arguments) || this; 19609 } 19610 19611 var _proto = CustomControlSpacer.prototype; 19612 19613 /** 19614 * Builds the default DOM `className`. 19615 * 19616 * @return {string} 19617 * The DOM `className` for this object. 19618 */ 19619 _proto.buildCSSClass = function buildCSSClass() { 19620 return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this); 19621 } 19622 /** 19623 * Create the `Component`'s DOM element 19624 * 19625 * @return {Element} 19626 * The element that was created. 19627 */ 19628 ; 19629 19630 _proto.createEl = function createEl() { 19631 var el = _Spacer.prototype.createEl.call(this, { 19632 className: this.buildCSSClass() 19633 }); // No-flex/table-cell mode requires there be some content 19634 // in the cell to fill the remaining space of the table. 19635 19636 19637 el.innerHTML = "\xA0"; 19638 return el; 19639 }; 19640 19641 return CustomControlSpacer; 19642 }(Spacer); 19643 19644 Component.registerComponent('CustomControlSpacer', CustomControlSpacer); 19645 19646 /** 19647 * Container of main controls. 19648 * 19649 * @extends Component 19650 */ 19651 19652 var ControlBar = /*#__PURE__*/function (_Component) { 19653 inheritsLoose(ControlBar, _Component); 19654 19655 function ControlBar() { 19656 return _Component.apply(this, arguments) || this; 19657 } 19658 19659 var _proto = ControlBar.prototype; 19660 19661 /** 19662 * Create the `Component`'s DOM element 19663 * 19664 * @return {Element} 19665 * The element that was created. 19666 */ 19667 _proto.createEl = function createEl() { 19668 return _Component.prototype.createEl.call(this, 'div', { 19669 className: 'vjs-control-bar', 19670 dir: 'ltr' 19671 }); 19672 }; 19673 19674 return ControlBar; 19675 }(Component); 19676 /** 19677 * Default options for `ControlBar` 19678 * 19679 * @type {Object} 19680 * @private 19681 */ 19682 19683 19684 ControlBar.prototype.options_ = { 19685 children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] 19686 }; 19687 19688 if ('exitPictureInPicture' in document) { 19689 ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle'); 19690 } 19691 19692 Component.registerComponent('ControlBar', ControlBar); 19693 19694 /** 19695 * A display that indicates an error has occurred. This means that the video 19696 * is unplayable. 19697 * 19698 * @extends ModalDialog 19699 */ 19700 19701 var ErrorDisplay = /*#__PURE__*/function (_ModalDialog) { 19702 inheritsLoose(ErrorDisplay, _ModalDialog); 19703 19704 /** 19705 * Creates an instance of this class. 19706 * 19707 * @param {Player} player 19708 * The `Player` that this class should be attached to. 19709 * 19710 * @param {Object} [options] 19711 * The key/value store of player options. 19712 */ 19713 function ErrorDisplay(player, options) { 19714 var _this; 19715 19716 _this = _ModalDialog.call(this, player, options) || this; 19717 19718 _this.on(player, 'error', _this.open); 19719 19720 return _this; 19721 } 19722 /** 19723 * Builds the default DOM `className`. 19724 * 19725 * @return {string} 19726 * The DOM `className` for this object. 19727 * 19728 * @deprecated Since version 5. 19729 */ 19730 19731 19732 var _proto = ErrorDisplay.prototype; 19733 19734 _proto.buildCSSClass = function buildCSSClass() { 19735 return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this); 19736 } 19737 /** 19738 * Gets the localized error message based on the `Player`s error. 19739 * 19740 * @return {string} 19741 * The `Player`s error message localized or an empty string. 19742 */ 19743 ; 19744 19745 _proto.content = function content() { 19746 var error = this.player().error(); 19747 return error ? this.localize(error.message) : ''; 19748 }; 19749 19750 return ErrorDisplay; 19751 }(ModalDialog); 19752 /** 19753 * The default options for an `ErrorDisplay`. 19754 * 19755 * @private 19756 */ 19757 19758 19759 ErrorDisplay.prototype.options_ = _extends_1({}, ModalDialog.prototype.options_, {
vendor: 16,614 bytes, lines 19760-20251
19760 pauseOnOpen: false, 19761 fillAlways: true, 19762 temporary: false, 19763 uncloseable: true 19764 }); 19765 Component.registerComponent('ErrorDisplay', ErrorDisplay); 19766 19767 var LOCAL_STORAGE_KEY = 'vjs-text-track-settings'; 19768 var COLOR_BLACK = ['#000', 'Black']; 19769 var COLOR_BLUE = ['#00F', 'Blue']; 19770 var COLOR_CYAN = ['#0FF', 'Cyan']; 19771 var COLOR_GREEN = ['#0F0', 'Green']; 19772 var COLOR_MAGENTA = ['#F0F', 'Magenta']; 19773 var COLOR_RED = ['#F00', 'Red']; 19774 var COLOR_WHITE = ['#FFF', 'White']; 19775 var COLOR_YELLOW = ['#FF0', 'Yellow']; 19776 var OPACITY_OPAQUE = ['1', 'Opaque']; 19777 var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; 19778 var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component. 19779 // 19780 // Possible keys include: 19781 // 19782 // `default`: 19783 // The default option index. Only needs to be provided if not zero. 19784 // `parser`: 19785 // A function which is used to parse the value from the selected option in 19786 // a customized way. 19787 // `selector`: 19788 // The selector used to find the associated <select> element. 19789 19790 var selectConfigs = { 19791 backgroundColor: { 19792 selector: '.vjs-bg-color > select', 19793 id: 'captions-background-color-%s', 19794 label: 'Color', 19795 options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 19796 }, 19797 backgroundOpacity: { 19798 selector: '.vjs-bg-opacity > select', 19799 id: 'captions-background-opacity-%s', 19800 label: 'Transparency', 19801 options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] 19802 }, 19803 color: { 19804 selector: '.vjs-fg-color > select', 19805 id: 'captions-foreground-color-%s', 19806 label: 'Color', 19807 options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 19808 }, 19809 edgeStyle: { 19810 selector: '.vjs-edge-style > select', 19811 id: '%s', 19812 label: 'Text Edge Style', 19813 options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] 19814 }, 19815 fontFamily: { 19816 selector: '.vjs-font-family > select', 19817 id: 'captions-font-family-%s', 19818 label: 'Font Family', 19819 options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] 19820 }, 19821 fontPercent: { 19822 selector: '.vjs-font-percent > select', 19823 id: 'captions-font-size-%s', 19824 label: 'Font Size', 19825 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%']], 19826 "default": 2, 19827 parser: function parser(v) { 19828 return v === '1.00' ? null : Number(v); 19829 } 19830 }, 19831 textOpacity: { 19832 selector: '.vjs-text-opacity > select', 19833 id: 'captions-foreground-opacity-%s', 19834 label: 'Transparency', 19835 options: [OPACITY_OPAQUE, OPACITY_SEMI] 19836 }, 19837 // Options for this object are defined below. 19838 windowColor: { 19839 selector: '.vjs-window-color > select', 19840 id: 'captions-window-color-%s', 19841 label: 'Color' 19842 }, 19843 // Options for this object are defined below. 19844 windowOpacity: { 19845 selector: '.vjs-window-opacity > select', 19846 id: 'captions-window-opacity-%s', 19847 label: 'Transparency', 19848 options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] 19849 } 19850 }; 19851 selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; 19852 /** 19853 * Get the actual value of an option. 19854 * 19855 * @param {string} value 19856 * The value to get 19857 * 19858 * @param {Function} [parser] 19859 * Optional function to adjust the value. 19860 * 19861 * @return {Mixed} 19862 * - Will be `undefined` if no value exists 19863 * - Will be `undefined` if the given value is "none". 19864 * - Will be the actual value otherwise. 19865 * 19866 * @private 19867 */ 19868 19869 function parseOptionValue(value, parser) { 19870 if (parser) { 19871 value = parser(value); 19872 } 19873 19874 if (value && value !== 'none') { 19875 return value; 19876 } 19877 } 19878 /** 19879 * Gets the value of the selected <option> element within a <select> element. 19880 * 19881 * @param {Element} el 19882 * the element to look in 19883 * 19884 * @param {Function} [parser] 19885 * Optional function to adjust the value. 19886 * 19887 * @return {Mixed} 19888 * - Will be `undefined` if no value exists 19889 * - Will be `undefined` if the given value is "none". 19890 * - Will be the actual value otherwise. 19891 * 19892 * @private 19893 */ 19894 19895 19896 function getSelectedOptionValue(el, parser) { 19897 var value = el.options[el.options.selectedIndex].value; 19898 return parseOptionValue(value, parser); 19899 } 19900 /** 19901 * Sets the selected <option> element within a <select> element based on a 19902 * given value. 19903 * 19904 * @param {Element} el 19905 * The element to look in. 19906 * 19907 * @param {string} value 19908 * the property to look on. 19909 * 19910 * @param {Function} [parser] 19911 * Optional function to adjust the value before comparing. 19912 * 19913 * @private 19914 */ 19915 19916 19917 function setSelectedOption(el, value, parser) { 19918 if (!value) { 19919 return; 19920 } 19921 19922 for (var i = 0; i < el.options.length; i++) { 19923 if (parseOptionValue(el.options[i].value, parser) === value) { 19924 el.selectedIndex = i; 19925 break; 19926 } 19927 } 19928 } 19929 /** 19930 * Manipulate Text Tracks settings. 19931 * 19932 * @extends ModalDialog 19933 */ 19934 19935 19936 var TextTrackSettings = /*#__PURE__*/function (_ModalDialog) { 19937 inheritsLoose(TextTrackSettings, _ModalDialog); 19938 19939 /** 19940 * Creates an instance of this class. 19941 * 19942 * @param {Player} player 19943 * The `Player` that this class should be attached to. 19944 * 19945 * @param {Object} [options] 19946 * The key/value store of player options. 19947 */ 19948 function TextTrackSettings(player, options) { 19949 var _this; 19950 19951 options.temporary = false; 19952 _this = _ModalDialog.call(this, player, options) || this; 19953 _this.updateDisplay = bind(assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it 19954 19955 _this.fill(); 19956 19957 _this.hasBeenOpened_ = _this.hasBeenFilled_ = true; 19958 _this.endDialog = createEl('p', { 19959 className: 'vjs-control-text', 19960 textContent: _this.localize('End of dialog window.') 19961 }); 19962 19963 _this.el().appendChild(_this.endDialog); 19964 19965 _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options 19966 19967 19968 if (options.persistTextTrackSettings === undefined) { 19969 _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings; 19970 } 19971 19972 _this.on(_this.$('.vjs-done-button'), 'click', function () { 19973 _this.saveSettings(); 19974 19975 _this.close(); 19976 }); 19977 19978 _this.on(_this.$('.vjs-default-button'), 'click', function () { 19979 _this.setDefaults(); 19980 19981 _this.updateDisplay(); 19982 }); 19983 19984 each(selectConfigs, function (config) { 19985 _this.on(_this.$(config.selector), 'change', _this.updateDisplay); 19986 }); 19987 19988 if (_this.options_.persistTextTrackSettings) { 19989 _this.restoreSettings(); 19990 } 19991 19992 return _this; 19993 } 19994 19995 var _proto = TextTrackSettings.prototype; 19996 19997 _proto.dispose = function dispose() { 19998 this.endDialog = null; 19999 20000 _ModalDialog.prototype.dispose.call(this); 20001 } 20002 /** 20003 * Create a <select> element with configured options. 20004 * 20005 * @param {string} key 20006 * Configuration key to use during creation. 20007 * 20008 * @return {string} 20009 * An HTML string. 20010 * 20011 * @private 20012 */ 20013 ; 20014 20015 _proto.createElSelect_ = function createElSelect_(key, legendId, type) { 20016 var _this2 = this; 20017 20018 if (legendId === void 0) { 20019 legendId = ''; 20020 } 20021 20022 if (type === void 0) { 20023 type = 'label'; 20024 } 20025 20026 var config = selectConfigs[key]; 20027 var id = config.id.replace('%s', this.id_); 20028 var selectLabelledbyIds = [legendId, id].join(' ').trim(); 20029 return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) { 20030 var optionId = id + '-' + o[1].replace(/\W+/g, ''); 20031 return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join(''); 20032 })).concat('</select>').join(''); 20033 } 20034 /** 20035 * Create foreground color element for the component 20036 * 20037 * @return {string} 20038 * An HTML string. 20039 * 20040 * @private 20041 */ 20042 ; 20043 20044 _proto.createElFgColor_ = function createElFgColor_() { 20045 var legendId = "captions-text-legend-" + this.id_; 20046 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(''); 20047 } 20048 /** 20049 * Create background color element for the component 20050 * 20051 * @return {string} 20052 * An HTML string. 20053 * 20054 * @private 20055 */ 20056 ; 20057 20058 _proto.createElBgColor_ = function createElBgColor_() { 20059 var legendId = "captions-background-" + this.id_; 20060 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(''); 20061 } 20062 /** 20063 * Create window color element for the component 20064 * 20065 * @return {string} 20066 * An HTML string. 20067 * 20068 * @private 20069 */ 20070 ; 20071 20072 _proto.createElWinColor_ = function createElWinColor_() { 20073 var legendId = "captions-window-" + this.id_; 20074 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(''); 20075 } 20076 /** 20077 * Create color elements for the component 20078 * 20079 * @return {Element} 20080 * The element that was created 20081 * 20082 * @private 20083 */ 20084 ; 20085 20086 _proto.createElColors_ = function createElColors_() { 20087 return createEl('div', { 20088 className: 'vjs-track-settings-colors', 20089 innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') 20090 }); 20091 } 20092 /** 20093 * Create font elements for the component 20094 * 20095 * @return {Element} 20096 * The element that was created. 20097 * 20098 * @private 20099 */ 20100 ; 20101 20102 _proto.createElFont_ = function createElFont_() { 20103 return createEl('div', { 20104 className: 'vjs-track-settings-font', 20105 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('') 20106 }); 20107 } 20108 /** 20109 * Create controls for the component 20110 * 20111 * @return {Element} 20112 * The element that was created. 20113 * 20114 * @private 20115 */ 20116 ; 20117 20118 _proto.createElControls_ = function createElControls_() { 20119 var defaultsDescription = this.localize('restore all settings to the default values'); 20120 return createEl('div', { 20121 className: 'vjs-track-settings-controls', 20122 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('') 20123 }); 20124 }; 20125 20126 _proto.content = function content() { 20127 return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; 20128 }; 20129 20130 _proto.label = function label() { 20131 return this.localize('Caption Settings Dialog'); 20132 }; 20133 20134 _proto.description = function description() { 20135 return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); 20136 }; 20137 20138 _proto.buildCSSClass = function buildCSSClass() { 20139 return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; 20140 } 20141 /** 20142 * Gets an object of text track settings (or null). 20143 * 20144 * @return {Object} 20145 * An object with config values parsed from the DOM or localStorage. 20146 */ 20147 ; 20148 20149 _proto.getValues = function getValues() { 20150 var _this3 = this; 20151 20152 return reduce(selectConfigs, function (accum, config, key) { 20153 var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); 20154 20155 if (value !== undefined) { 20156 accum[key] = value; 20157 } 20158 20159 return accum; 20160 }, {}); 20161 } 20162 /** 20163 * Sets text track settings from an object of values. 20164 * 20165 * @param {Object} values 20166 * An object with config values parsed from the DOM or localStorage. 20167 */ 20168 ; 20169 20170 _proto.setValues = function setValues(values) { 20171 var _this4 = this; 20172 20173 each(selectConfigs, function (config, key) { 20174 setSelectedOption(_this4.$(config.selector), values[key], config.parser); 20175 }); 20176 } 20177 /** 20178 * Sets all `<select>` elements to their default values. 20179 */ 20180 ; 20181 20182 _proto.setDefaults = function setDefaults() { 20183 var _this5 = this; 20184 20185 each(selectConfigs, function (config) { 20186 var index = config.hasOwnProperty('default') ? config["default"] : 0; 20187 _this5.$(config.selector).selectedIndex = index; 20188 }); 20189 } 20190 /** 20191 * Restore texttrack settings from localStorage 20192 */ 20193 ; 20194 20195 _proto.restoreSettings = function restoreSettings() { 20196 var values; 20197 20198 try { 20199 values = JSON.parse(window$3.localStorage.getItem(LOCAL_STORAGE_KEY)); 20200 } catch (err) { 20201 log.warn(err); 20202 } 20203 20204 if (values) { 20205 this.setValues(values); 20206 } 20207 } 20208 /** 20209 * Save text track settings to localStorage 20210 */ 20211 ; 20212 20213 _proto.saveSettings = function saveSettings() { 20214 if (!this.options_.persistTextTrackSettings) { 20215 return; 20216 } 20217 20218 var values = this.getValues(); 20219 20220 try { 20221 if (Object.keys(values).length) { 20222 window$3.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values)); 20223 } else { 20224 window$3.localStorage.removeItem(LOCAL_STORAGE_KEY); 20225 } 20226 } catch (err) { 20227 log.warn(err); 20228 } 20229 } 20230 /** 20231 * Update display of text track settings 20232 */ 20233 ; 20234 20235 _proto.updateDisplay = function updateDisplay() { 20236 var ttDisplay = this.player_.getChild('textTrackDisplay'); 20237 20238 if (ttDisplay) { 20239 ttDisplay.updateDisplay(); 20240 } 20241 } 20242 /** 20243 * conditionally blur the element and refocus the captions button 20244 * 20245 * @private 20246 */ 20247 ; 20248 20249 _proto.conditionalBlur_ = function conditionalBlur_() { 20250 this.previouslyActiveEl_ = null; 20251 var cb = this.player_.controlBar;
20252 var subsCapsBtn = cb && cb.subsCapsButton; 20253 var ccBtn = cb && cb.captionsButton; 20254 20255 if (subsCapsBtn) { 20256 subsCapsBtn.focus(); 20257 } else if (ccBtn) { 20258 ccBtn.focus(); 20259 } 20260 }; 20261 20262 return TextTrackSettings; 20263 }(ModalDialog); 20264 20265 Component.registerComponent('TextTrackSettings', TextTrackSettings); 20266 20267 /** 20268 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions. 20269 * 20270 * 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}. 20271 * 20272 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option. 20273 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below. 20274 * @example <caption>How to disable the resize manager</caption> 20275 * const player = videojs('#vid', { 20276 * resizeManager: false 20277 * }); 20278 * 20279 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification} 20280 * 20281 * @extends Component 20282 */ 20283 20284 var ResizeManager = /*#__PURE__*/function (_Component) { 20285 inheritsLoose(ResizeManager, _Component); 20286 20287 /** 20288 * Create the ResizeManager. 20289 * 20290 * @param {Object} player 20291 * The `Player` that this class should be attached to. 20292 * 20293 * @param {Object} [options] 20294 * The key/value store of ResizeManager options. 20295 * 20296 * @param {Object} [options.ResizeObserver] 20297 * A polyfill for ResizeObserver can be passed in here. 20298 * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback. 20299 */ 20300 function ResizeManager(player, options) { 20301 var _this; 20302 20303 var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$3.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver 20304 20305 if (options.ResizeObserver === null) { 20306 RESIZE_OBSERVER_AVAILABLE = false; 20307 } // Only create an element when ResizeObserver isn't available 20308 20309 20310 var options_ = mergeOptions({ 20311 createEl: !RESIZE_OBSERVER_AVAILABLE, 20312 reportTouchActivity: false 20313 }, options); 20314 _this = _Component.call(this, player, options_) || this; 20315 _this.ResizeObserver = options.ResizeObserver || window$3.ResizeObserver; 20316 _this.loadListener_ = null; 20317 _this.resizeObserver_ = null; 20318 _this.debouncedHandler_ = debounce(function () { 20319 _this.resizeHandler(); 20320 }, 100, false, assertThisInitialized(_this)); 20321 20322 if (RESIZE_OBSERVER_AVAILABLE) { 20323 _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_); 20324 20325 _this.resizeObserver_.observe(player.el()); 20326 } else { 20327 _this.loadListener_ = function () { 20328 if (!_this.el_ || !_this.el_.contentWindow) { 20329 return; 20330 } 20331 20332 var debouncedHandler_ = _this.debouncedHandler_; 20333 20334 var unloadListener_ = _this.unloadListener_ = function () { 20335 off(this, 'resize', debouncedHandler_); 20336 off(this, 'unload', unloadListener_); 20337 unloadListener_ = null; 20338 }; // safari and edge can unload the iframe before resizemanager dispose 20339 // we have to dispose of event handlers correctly before that happens 20340 20341 20342 on(_this.el_.contentWindow, 'unload', unloadListener_); 20343 on(_this.el_.contentWindow, 'resize', debouncedHandler_); 20344 }; 20345 20346 _this.one('load', _this.loadListener_); 20347 } 20348 20349 return _this; 20350 } 20351 20352 var _proto = ResizeManager.prototype; 20353 20354 _proto.createEl = function createEl() { 20355 return _Component.prototype.createEl.call(this, 'iframe', { 20356 className: 'vjs-resize-manager', 20357 tabIndex: -1 20358 }, { 20359 'aria-hidden': 'true' 20360 }); 20361 } 20362 /** 20363 * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver 20364 * 20365 * @fires Player#playerresize 20366 */ 20367 ; 20368 20369 _proto.resizeHandler = function resizeHandler() { 20370 /** 20371 * Called when the player size has changed 20372 * 20373 * @event Player#playerresize 20374 * @type {EventTarget~Event} 20375 */ 20376 // make sure player is still around to trigger 20377 // prevents this from causing an error after dispose 20378 if (!this.player_ || !this.player_.trigger) { 20379 return; 20380 } 20381 20382 this.player_.trigger('playerresize'); 20383 }; 20384 20385 _proto.dispose = function dispose() { 20386 if (this.debouncedHandler_) { 20387 this.debouncedHandler_.cancel(); 20388 } 20389 20390 if (this.resizeObserver_) { 20391 if (this.player_.el()) { 20392 this.resizeObserver_.unobserve(this.player_.el()); 20393 } 20394 20395 this.resizeObserver_.disconnect(); 20396 } 20397 20398 if (this.loadListener_) { 20399 this.off('load', this.loadListener_); 20400 } 20401 20402 if (this.el_ && this.el_.contentWindow && this.unloadListener_) { 20403 this.unloadListener_.call(this.el_.contentWindow); 20404 } 20405 20406 this.ResizeObserver = null; 20407 this.resizeObserver = null; 20408 this.debouncedHandler_ = null; 20409 this.loadListener_ = null; 20410 20411 _Component.prototype.dispose.call(this); 20412 }; 20413 20414 return ResizeManager; 20415 }(Component); 20416 20417 Component.registerComponent('ResizeManager', ResizeManager); 20418 20419 var defaults = { 20420 trackingThreshold: 30, 20421 liveTolerance: 15 20422 }; 20423 /* 20424 track when we are at the live edge, and other helpers for live playback */ 20425 20426 /** 20427 * A class for checking live current time and determining when the player 20428 * is at or behind the live edge. 20429 */ 20430 20431 var LiveTracker = /*#__PURE__*/function (_Component) { 20432 inheritsLoose(LiveTracker, _Component); 20433 20434 /** 20435 * Creates an instance of this class. 20436 * 20437 * @param {Player} player 20438 * The `Player` that this class should be attached to. 20439 * 20440 * @param {Object} [options] 20441 * The key/value store of player options. 20442 * 20443 * @param {number} [options.trackingThreshold=30] 20444 * Number of seconds of live window (seekableEnd - seekableStart) that 20445 * media needs to have before the liveui will be shown. 20446 * 20447 * @param {number} [options.liveTolerance=15] 20448 * Number of seconds behind live that we have to be 20449 * before we will be considered non-live. Note that this will only 20450 * be used when playing at the live edge. This allows large seekable end 20451 * changes to not effect wether we are live or not. 20452 */ 20453 function LiveTracker(player, options) { 20454 var _this; 20455 20456 // LiveTracker does not need an element 20457 var options_ = mergeOptions(defaults, options, { 20458 createEl: false 20459 }); 20460 _this = _Component.call(this, player, options_) || this; 20461 20462 _this.reset_(); 20463 20464 _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden, 20465 // also, tracking when the document is hidden can 20466 // cause the CPU to spike and eventually crash the page on IE11. 20467 20468 20469 if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) { 20470 _this.on(document, 'visibilitychange', _this.handleVisibilityChange); 20471 } 20472 20473 return _this; 20474 } 20475 /** 20476 * toggle tracking based on document visiblility 20477 */ 20478 20479 20480 var _proto = LiveTracker.prototype; 20481 20482 _proto.handleVisibilityChange = function handleVisibilityChange() { 20483 if (this.player_.duration() !== Infinity) { 20484 return; 20485 } 20486 20487 if (document.hidden) { 20488 this.stopTracking(); 20489 } else { 20490 this.startTracking(); 20491 } 20492 } 20493 /** 20494 * all the functionality for tracking when seek end changes 20495 * and for tracking how far past seek end we should be 20496 */ 20497 ; 20498 20499 _proto.trackLive_ = function trackLive_() { 20500 var seekable = this.player_.seekable();
20500 // skip undefined seekable 20501 20502 if (!seekable || !seekable.length) { 20503 return; 20504 } 20505 20506 var newTime = Number(window$3.performance.now().toFixed(4)); 20507 var deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000; 20508 this.lastTime_ = newTime; 20509 this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime; 20510 var liveCurrentTime = this.liveCurrentTime(); 20511 var currentTime = this.player_.currentTime(); // we are behind live if any are true 20512 // 1. the player is paused 20513 // 2. the user seeked to a location 2 seconds away from live 20514 // 3. the difference between live and current time is greater 20515 // liveTolerance which defaults to 15s 20516 20517 var isBehind = this.player_.paused() || this.seekedBehindLive_ || Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance; // we cannot be behind if 20518 // 1. until we have not seen a timeupdate yet 20519 // 2. liveCurrentTime is Infinity, which happens on Android 20520 20521 if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) { 20522 isBehind = false; 20523 } 20524 20525 if (isBehind !== this.behindLiveEdge_) { 20526 this.behindLiveEdge_ = isBehind; 20527 this.trigger('liveedgechange'); 20528 } 20529 } 20530 /** 20531 * handle a durationchange event on the player 20532 * and start/stop tracking accordingly. 20533 */ 20534 ; 20535 20536 _proto.handleDurationchange = function handleDurationchange() { 20537 if (this.player_.duration() === Infinity && this.liveWindow() >= this.options_.trackingThreshold) { 20538 if (this.player_.options_.liveui) { 20539 this.player_.addClass('vjs-liveui'); 20540 } 20541 20542 this.startTracking(); 20543 } else { 20544 this.player_.removeClass('vjs-liveui'); 20545 this.stopTracking(); 20546 } 20547 } 20548 /** 20549 * start tracking live playback 20550 */ 20551 ; 20552 20553 _proto.startTracking = function startTracking() { 20554 if (this.isTracking()) { 20555 return; 20556 } // If we haven't seen a timeupdate, we need to check whether playback 20557 // began before this component started tracking. This can happen commonly 20558 // when using autoplay. 20559 20560 20561 if (!this.timeupdateSeen_) { 20562 this.timeupdateSeen_ = this.player_.hasStarted(); 20563 } 20564 20565 this.trackingInterval_ = this.setInterval(this.trackLive_, UPDATE_REFRESH_INTERVAL); 20566 this.trackLive_(); 20567 this.on(this.player_, ['play', 'pause'], this.trackLive_); 20568 20569 if (!this.timeupdateSeen_) { 20570 this.one(this.player_, 'play', this.handlePlay); 20571 this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate); 20572 } else { 20573 this.on(this.player_, 'seeked', this.handleSeeked); 20574 } 20575 } 20576 /** 20577 * handle the first timeupdate on the player if it wasn't already playing 20578 * when live tracker started tracking. 20579 */ 20580 ; 20581 20582 _proto.handleFirstTimeupdate = function handleFirstTimeupdate() { 20583 this.timeupdateSeen_ = true; 20584 this.on(this.player_, 'seeked', this.handleSeeked); 20585 } 20586 /** 20587 * Keep track of what time a seek starts, and listen for seeked 20588 * to find where a seek ends. 20589 */ 20590 ; 20591 20592 _proto.handleSeeked = function handleSeeked() { 20593 var timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime()); 20594 this.seekedBehindLive_ = this.skipNextSeeked_ ? false : timeDiff > 2; 20595 this.skipNextSeeked_ = false; 20596 this.trackLive_(); 20597 } 20598 /** 20599 * handle the first play on the player, and make sure that we seek 20600 * right to the live edge. 20601 */ 20602 ; 20603 20604 _proto.handlePlay = function handlePlay() { 20605 this.one(this.player_, 'timeupdate', this.seekToLiveEdge); 20606 } 20607 /** 20608 * Stop tracking, and set all internal variables to 20609 * their initial value. 20610 */ 20611 ; 20612 20613 _proto.reset_ = function reset_() { 20614 this.lastTime_ = -1; 20615 this.pastSeekEnd_ = 0; 20616 this.lastSeekEnd_ = -1; 20617 this.behindLiveEdge_ = true; 20618 this.timeupdateSeen_ = false;
20619 this.seekedBehindLive_ = false; 20620 this.skipNextSeeked_ = false; 20621 this.clearInterval(this.trackingInterval_); 20622 this.trackingInterval_ = null; 20623 this.off(this.player_, ['play', 'pause'], this.trackLive_); 20624 this.off(this.player_, 'seeked', this.handleSeeked); 20625 this.off(this.player_, 'play', this.handlePlay); 20626 this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate); 20627 this.off(this.player_, 'timeupdate', this.seekToLiveEdge); 20628 } 20629 /** 20630 * stop tracking live playback 20631 */ 20632 ; 20633 20634 _proto.stopTracking = function stopTracking() { 20635 if (!this.isTracking()) { 20636 return; 20637 } 20638 20639 this.reset_(); 20640 this.trigger('liveedgechange'); 20641 } 20642 /** 20643 * A helper to get the player seekable end 20644 * so that we don't have to null check everywhere 20645 * 20646 * @return {number} 20647 * The furthest seekable end or Infinity. 20648 */ 20649 ; 20650 20651 _proto.seekableEnd = function seekableEnd() { 20652 var seekable = this.player_.seekable(); 20653 var seekableEnds = []; 20654 var i = seekable ? seekable.length : 0; 20655 20656 while (i--) { 20657 seekableEnds.push(seekable.end(i)); 20658 } // grab the furthest seekable end after sorting, or if there are none 20659 // default to Infinity 20660 20661 20662 return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity; 20663 } 20664 /** 20665 * A helper to get the player seekable start 20666 * so that we don't have to null check everywhere 20667 * 20668 * @return {number} 20669 * The earliest seekable start or 0. 20670 */ 20671 ; 20672 20673 _proto.seekableStart = function seekableStart() { 20674 var seekable = this.player_.seekable(); 20675 var seekableStarts = []; 20676 var i = seekable ? seekable.length : 0; 20677 20678 while (i--) { 20679 seekableStarts.push(seekable.start(i)); 20680 } // grab the first seekable start after sorting, or if there are none 20681 // default to 0 20682 20683 20684 return seekableStarts.length ? seekableStarts.sort()[0] : 0; 20685 } 20686 /** 20687 * Get the live time window aka 20688 * the amount of time between seekable start and 20689 * live current time. 20690 * 20691 * @return {number} 20692 * The amount of seconds that are seekable in 20693 * the live video. 20694 */ 20695 ; 20696 20697 _proto.liveWindow = function liveWindow() { 20698 var liveCurrentTime = this.liveCurrentTime(); 20699 20700 if (liveCurrentTime === Infinity) { 20701 return Infinity; 20702 } 20703 20704 return liveCurrentTime - this.seekableStart(); 20705 } 20706 /** 20707 * Determines if the player is live, only checks if this component 20708 * is tracking live playback or not 20709 * 20710 * @return {boolean} 20711 * Wether liveTracker is tracking 20712 */ 20713 ; 20714 20715 _proto.isLive = function isLive() { 20716 return this.isTracking(); 20717 } 20718 /** 20719 * Determines if currentTime is at the live edge and won't fall behind 20720 * on each seekableendchange 20721 * 20722 * @return {boolean} 20723 * Wether playback is at the live edge 20724 */ 20725 ; 20726 20727 _proto.atLiveEdge = function atLiveEdge() { 20728 return !this.behindLiveEdge(); 20729 } 20730 /** 20731 * get what we expect the live current time to be 20732 * 20733 * @return {number} 20734 * The expected live current time 20735 */ 20736 ; 20737 20738 _proto.liveCurrentTime = function liveCurrentTime() { 20739 return this.pastSeekEnd() + this.seekableEnd(); 20740 } 20741 /** 20742 * The number of seconds that have occured after seekable end 20743 * changed. This will be reset to 0 once seekable end changes. 20744 * 20745 * @return {number} 20746 * Seconds past the current seekable end 20747 */ 20748 ; 20749 20750 _proto.pastSeekEnd = function pastSeekEnd() { 20751 var seekableEnd = this.seekableEnd(); 20752 20753 if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) { 20754 this.pastSeekEnd_ = 0; 20755 } 20756 20757 this.lastSeekEnd_ = seekableEnd; 20758 return this.pastSeekEnd_; 20759 } 20760 /** 20761 * If we are currently behind the live edge, aka currentTime will be 20762 * behind on a seekableendchange 20763 * 20764 * @return {boolean} 20765 * If we are behind the live edge 20766 */ 20767 ; 20768 20769 _proto.behindLiveEdge = function behindLiveEdge() { 20770 return this.behindLiveEdge_; 20771 } 20772 /** 20773 * Wether live tracker is currently tracking or not. 20774 */ 20775 ; 20776 20777 _proto.isTracking = function isTracking() { 20778 return typeof this.trackingInterval_ === 'number'; 20779 } 20780 /** 20781 * Seek to the live edge if we are behind the live edge 20782 */ 20783 ; 20784 20785 _proto.seekToLiveEdge = function seekToLiveEdge() { 20786 this.seekedBehindLive_ = false;
vendor: 35,079 bytes, lines 20787-21791
20787 20788 if (this.atLiveEdge()) { 20789 return; 20790 } // skipNextSeeked_ 20791 20792 20793 this.skipNextSeeked_ = true; 20794 this.player_.currentTime(this.liveCurrentTime()); 20795 } 20796 /** 20797 * Dispose of liveTracker 20798 */ 20799 ; 20800 20801 _proto.dispose = function dispose() { 20802 this.off(document, 'visibilitychange', this.handleVisibilityChange); 20803 this.stopTracking(); 20804 20805 _Component.prototype.dispose.call(this); 20806 }; 20807 20808 return LiveTracker; 20809 }(Component); 20810 20811 Component.registerComponent('LiveTracker', LiveTracker); 20812 20813 /** 20814 * This function is used to fire a sourceset when there is something 20815 * similar to `mediaEl.load()` being called. It will try to find the source via 20816 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset` 20817 * with the source that was found or empty string if we cannot know. If it cannot 20818 * find a source then `sourceset` will not be fired. 20819 * 20820 * @param {Html5} tech 20821 * The tech object that sourceset was setup on 20822 * 20823 * @return {boolean} 20824 * returns false if the sourceset was not fired and true otherwise. 20825 */ 20826 20827 var sourcesetLoad = function sourcesetLoad(tech) { 20828 var el = tech.el(); // if `el.src` is set, that source will be loaded. 20829 20830 if (el.hasAttribute('src')) { 20831 tech.triggerSourceset(el.src); 20832 return true; 20833 } 20834 /** 20835 * Since there isn't a src property on the media element, source elements will be used for 20836 * implementing the source selection algorithm. This happens asynchronously and 20837 * for most cases were there is more than one source we cannot tell what source will 20838 * be loaded, without re-implementing the source selection algorithm. At this time we are not 20839 * going to do that. There are three special cases that we do handle here though: 20840 * 20841 * 1. If there are no sources, do not fire `sourceset`. 20842 * 2. If there is only one `<source>` with a `src` property/attribute that is our `src` 20843 * 3. If there is more than one `<source>` but all of them have the same `src` url. 20844 * That will be our src. 20845 */ 20846 20847 20848 var sources = tech.$$('source'); 20849 var srcUrls = []; 20850 var src = ''; // if there are no sources, do not fire sourceset 20851 20852 if (!sources.length) { 20853 return false; 20854 } // only count valid/non-duplicate source elements 20855 20856 20857 for (var i = 0; i < sources.length; i++) { 20858 var url = sources[i].src; 20859 20860 if (url && srcUrls.indexOf(url) === -1) { 20861 srcUrls.push(url); 20862 } 20863 } // there were no valid sources 20864 20865 20866 if (!srcUrls.length) { 20867 return false; 20868 } // there is only one valid source element url 20869 // use that 20870 20871 20872 if (srcUrls.length === 1) { 20873 src = srcUrls[0]; 20874 } 20875 20876 tech.triggerSourceset(src); 20877 return true; 20878 }; 20879 /** 20880 * our implementation of an `innerHTML` descriptor for browsers 20881 * that do not have one. 20882 */ 20883 20884 20885 var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', { 20886 get: function get() { 20887 return this.cloneNode(true).innerHTML; 20888 }, 20889 set: function set(v) { 20890 // make a dummy node to use innerHTML on 20891 var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided 20892 20893 dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy 20894 20895 var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy 20896 // to the document fragment 20897 20898 while (dummy.childNodes.length) { 20899 docFrag.appendChild(dummy.childNodes[0]); 20900 } // remove content 20901 20902 20903 this.innerText = ''; // now we add all of that html in one by appending the 20904 // document fragment. This is how innerHTML does it. 20905 20906 window$3.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would 20907 20908 return this.innerHTML; 20909 } 20910 }); 20911 /** 20912 * Get a property descriptor given a list of priorities and the 20913 * property to get. 20914 */ 20915 20916 var getDescriptor = function getDescriptor(priority, prop) { 20917 var descriptor = {}; 20918 20919 for (var i = 0; i < priority.length; i++) { 20920 descriptor = Object.getOwnPropertyDescriptor(priority[i], prop); 20921 20922 if (descriptor && descriptor.set && descriptor.get) { 20923 break; 20924 } 20925 } 20926 20927 descriptor.enumerable = true; 20928 descriptor.configurable = true; 20929 return descriptor; 20930 }; 20931 20932 var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) { 20933 return getDescriptor([tech.el(), window$3.HTMLMediaElement.prototype, window$3.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML'); 20934 }; 20935 /** 20936 * Patches browser internal functions so that we can tell synchronously 20937 * if a `<source>` was appended to the media element. For some reason this 20938 * causes a `sourceset` if the the media element is ready and has no source. 20939 * This happens when: 20940 * - The page has just loaded and the media element does not have a source. 20941 * - The media element was emptied of all sources, then `load()` was called. 20942 * 20943 * It does this by patching the following functions/properties when they are supported: 20944 * 20945 * - `append()` - can be used to add a `<source>` element to the media element 20946 * - `appendChild()` - can be used to add a `<source>` element to the media element 20947 * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element 20948 * - `innerHTML` - can be used to add a `<source>` element to the media element 20949 * 20950 * @param {Html5} tech 20951 * The tech object that sourceset is being setup on. 20952 */ 20953 20954 20955 var firstSourceWatch = function firstSourceWatch(tech) { 20956 var el = tech.el(); // make sure firstSourceWatch isn't setup twice. 20957 20958 if (el.resetSourceWatch_) { 20959 return; 20960 } 20961 20962 var old = {}; 20963 var innerDescriptor = getInnerHTMLDescriptor(tech); 20964 20965 var appendWrapper = function appendWrapper(appendFn) { 20966 return function () { 20967 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 20968 args[_key] = arguments[_key]; 20969 } 20970 20971 var retval = appendFn.apply(el, args); 20972 sourcesetLoad(tech); 20973 return retval; 20974 }; 20975 }; 20976 20977 ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) { 20978 if (!el[k]) { 20979 return; 20980 } // store the old function 20981 20982 20983 old[k] = el[k]; // call the old function with a sourceset if a source 20984 // was loaded 20985 20986 el[k] = appendWrapper(old[k]); 20987 }); 20988 Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, { 20989 set: appendWrapper(innerDescriptor.set) 20990 })); 20991 20992 el.resetSourceWatch_ = function () { 20993 el.resetSourceWatch_ = null; 20994 Object.keys(old).forEach(function (k) { 20995 el[k] = old[k]; 20996 }); 20997 Object.defineProperty(el, 'innerHTML', innerDescriptor); 20998 }; // on the first sourceset, we need to revert our changes 20999 21000 21001 tech.one('sourceset', el.resetSourceWatch_); 21002 }; 21003 /** 21004 * our implementation of a `src` descriptor for browsers 21005 * that do not have one. 21006 */ 21007 21008 21009 var srcDescriptorPolyfill = Object.defineProperty({}, 'src', { 21010 get: function get() { 21011 if (this.hasAttribute('src')) { 21012 return getAbsoluteURL(window$3.Element.prototype.getAttribute.call(this, 'src')); 21013 } 21014 21015 return ''; 21016 }, 21017 set: function set(v) { 21018 window$3.Element.prototype.setAttribute.call(this, 'src', v); 21019 return v; 21020 } 21021 }); 21022 21023 var getSrcDescriptor = function getSrcDescriptor(tech) { 21024 return getDescriptor([tech.el(), window$3.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src'); 21025 }; 21026 /** 21027 * setup `sourceset` handling on the `Html5` tech. This function 21028 * patches the following element properties/functions: 21029 * 21030 * - `src` - to determine when `src` is set 21031 * - `setAttribute()` - to determine when `src` is set 21032 * - `load()` - this re-triggers the source selection algorithm, and can 21033 * cause a sourceset. 21034 * 21035 * If there is no source when we are adding `sourceset` support or during a `load()` 21036 * we also patch the functions listed in `firstSourceWatch`. 21037 * 21038 * @param {Html5} tech 21039 * The tech to patch 21040 */ 21041 21042 21043 var setupSourceset = function setupSourceset(tech) { 21044 if (!tech.featuresSourceset) { 21045 return; 21046 } 21047 21048 var el = tech.el(); // make sure sourceset isn't setup twice. 21049 21050 if (el.resetSourceset_) { 21051 return; 21052 } 21053 21054 var srcDescriptor = getSrcDescriptor(tech); 21055 var oldSetAttribute = el.setAttribute; 21056 var oldLoad = el.load; 21057 Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, { 21058 set: function set(v) { 21059 var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src 21060 21061 tech.triggerSourceset(el.src); 21062 return retval; 21063 } 21064 })); 21065 21066 el.setAttribute = function (n, v) { 21067 var retval = oldSetAttribute.call(el, n, v); 21068 21069 if (/src/i.test(n)) { 21070 tech.triggerSourceset(el.src); 21071 } 21072 21073 return retval; 21074 }; 21075 21076 el.load = function () { 21077 var retval = oldLoad.call(el); // if load was called, but there was no source to fire 21078 // sourceset on. We have to watch for a source append 21079 // as that can trigger a `sourceset` when the media element 21080 // has no source 21081 21082 if (!sourcesetLoad(tech)) { 21083 tech.triggerSourceset(''); 21084 firstSourceWatch(tech); 21085 } 21086 21087 return retval; 21088 }; 21089 21090 if (el.currentSrc) { 21091 tech.triggerSourceset(el.currentSrc); 21092 } else if (!sourcesetLoad(tech)) { 21093 firstSourceWatch(tech); 21094 } 21095 21096 el.resetSourceset_ = function () { 21097 el.resetSourceset_ = null; 21098 el.load = oldLoad; 21099 el.setAttribute = oldSetAttribute; 21100 Object.defineProperty(el, 'src', srcDescriptor); 21101 21102 if (el.resetSourceWatch_) { 21103 el.resetSourceWatch_(); 21104 } 21105 }; 21106 }; 21107 21108 /** 21109 * Object.defineProperty but "lazy", which means that the value is only set after 21110 * it retrieved the first time, rather than being set right away. 21111 * 21112 * @param {Object} obj the object to set the property on 21113 * @param {string} key the key for the property to set 21114 * @param {Function} getValue the function used to get the value when it is needed. 21115 * @param {boolean} setter wether a setter shoould be allowed or not 21116 */ 21117 var defineLazyProperty = function defineLazyProperty(obj, key, getValue, setter) { 21118 if (setter === void 0) { 21119 setter = true; 21120 } 21121 21122 var set = function set(value) { 21123 return Object.defineProperty(obj, key, { 21124 value: value, 21125 enumerable: true, 21126 writable: true 21127 }); 21128 }; 21129 21130 var options = { 21131 configurable: true, 21132 enumerable: true, 21133 get: function get() { 21134 var value = getValue(); 21135 set(value); 21136 return value; 21137 } 21138 }; 21139 21140 if (setter) { 21141 options.set = set; 21142 } 21143 21144 return Object.defineProperty(obj, key, options); 21145 }; 21146 21147 /** 21148 * HTML5 Media Controller - Wrapper for HTML5 Media API 21149 * 21150 * @mixes Tech~SourceHandlerAdditions 21151 * @extends Tech 21152 */ 21153 21154 var Html5 = /*#__PURE__*/function (_Tech) { 21155 inheritsLoose(Html5, _Tech); 21156 21157 /** 21158 * Create an instance of this Tech. 21159 * 21160 * @param {Object} [options] 21161 * The key/value store of player options. 21162 * 21163 * @param {Component~ReadyCallback} ready 21164 * Callback function to call when the `HTML5` Tech is ready. 21165 */ 21166 function Html5(options, ready) { 21167 var _this; 21168 21169 _this = _Tech.call(this, options, ready) || this; 21170 var source = options.source; 21171 var crossoriginTracks = false; // Set the source if one is provided 21172 // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) 21173 // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source 21174 // anyway so the error gets fired. 21175 21176 if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) { 21177 _this.setSource(source); 21178 } else { 21179 _this.handleLateInit_(_this.el_); 21180 } // setup sourceset after late sourceset/init 21181 21182 21183 if (options.enableSourceset) { 21184 _this.setupSourcesetHandling_(); 21185 } 21186 21187 if (_this.el_.hasChildNodes()) { 21188 var nodes = _this.el_.childNodes; 21189 var nodesLength = nodes.length; 21190 var removeNodes = []; 21191 21192 while (nodesLength--) { 21193 var node = nodes[nodesLength]; 21194 var nodeName = node.nodeName.toLowerCase(); 21195 21196 if (nodeName === 'track') { 21197 if (!_this.featuresNativeTextTracks) { 21198 // Empty video tag tracks so the built-in player doesn't use them also. 21199 // This may not be fast enough to stop HTML5 browsers from reading the tags 21200 // so we'll need to turn off any default tracks if we're manually doing 21201 // captions and subtitles. videoElement.textTracks 21202 removeNodes.push(node); 21203 } else { 21204 // store HTMLTrackElement and TextTrack to remote list 21205 _this.remoteTextTrackEls().addTrackElement_(node); 21206 21207 _this.remoteTextTracks().addTrack(node.track); 21208 21209 _this.textTracks().addTrack(node.track); 21210 21211 if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) { 21212 crossoriginTracks = true; 21213 } 21214 } 21215 } 21216 } 21217 21218 for (var i = 0; i < removeNodes.length; i++) { 21219 _this.el_.removeChild(removeNodes[i]); 21220 } 21221 } 21222 21223 _this.proxyNativeTracks_(); 21224 21225 if (_this.featuresNativeTextTracks && crossoriginTracks) { 21226 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.'); 21227 } // prevent iOS Safari from disabling metadata text tracks during native playback 21228 21229 21230 _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used 21231 // Our goal should be to get the custom controls on mobile solid everywhere 21232 // so we can remove this all together. Right now this will block custom 21233 // controls on touch enabled laptops like the Chrome Pixel 21234 21235 21236 if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { 21237 _this.setControls(true); 21238 } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` 21239 // into a `fullscreenchange` event 21240 21241 21242 _this.proxyWebkitFullscreen_(); 21243 21244 _this.triggerReady(); 21245 21246 return _this; 21247 } 21248 /** 21249 * Dispose of `HTML5` media element and remove all tracks. 21250 */ 21251 21252 21253 var _proto = Html5.prototype; 21254 21255 _proto.dispose = function dispose() { 21256 if (this.el_ && this.el_.resetSourceset_) { 21257 this.el_.resetSourceset_(); 21258 } 21259 21260 Html5.disposeMediaElement(this.el_); 21261 this.options_ = null; // tech will handle clearing of the emulated track list 21262 21263 _Tech.prototype.dispose.call(this); 21264 } 21265 /** 21266 * Modify the media element so that we can detect when 21267 * the source is changed. Fires `sourceset` just after the source has changed 21268 */ 21269 ; 21270 21271 _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() { 21272 setupSourceset(this); 21273 } 21274 /** 21275 * When a captions track is enabled in the iOS Safari native player, all other 21276 * tracks are disabled (including metadata tracks), which nulls all of their 21277 * associated cue points. This will restore metadata tracks to their pre-fullscreen 21278 * state in those cases so that cue points are not needlessly lost. 21279 * 21280 * @private 21281 */ 21282 ; 21283 21284 _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() { 21285 var textTracks = this.textTracks(); 21286 var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state 21287 21288 var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() { 21289 metadataTracksPreFullscreenState = []; 21290 21291 for (var i = 0; i < textTracks.length; i++) { 21292 var track = textTracks[i]; 21293 21294 if (track.kind === 'metadata') { 21295 metadataTracksPreFullscreenState.push({ 21296 track: track, 21297 storedMode: track.mode 21298 }); 21299 } 21300 } 21301 }; // snapshot each metadata track's initial state, and update the snapshot 21302 // each time there is a track 'change' event 21303 21304 21305 takeMetadataTrackSnapshot(); 21306 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 21307 this.on('dispose', function () { 21308 return textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 21309 }); 21310 21311 var restoreTrackMode = function restoreTrackMode() { 21312 for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) { 21313 var storedTrack = metadataTracksPreFullscreenState[i]; 21314 21315 if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) { 21316 storedTrack.track.mode = storedTrack.storedMode; 21317 } 21318 } // we only want this handler to be executed on the first 'change' event 21319 21320 21321 textTracks.removeEventListener('change', restoreTrackMode); 21322 }; // when we enter fullscreen playback, stop updating the snapshot and 21323 // restore all track modes to their pre-fullscreen state 21324 21325 21326 this.on('webkitbeginfullscreen', function () { 21327 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed 21328 21329 textTracks.removeEventListener('change', restoreTrackMode); 21330 textTracks.addEventListener('change', restoreTrackMode); 21331 }); // start updating the snapshot again after leaving fullscreen 21332 21333 this.on('webkitendfullscreen', function () { 21334 // remove the listener before adding it just in case it wasn't previously removed 21335 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 21336 textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback 21337 21338 textTracks.removeEventListener('change', restoreTrackMode); 21339 }); 21340 } 21341 /** 21342 * Attempt to force override of tracks for the given type 21343 * 21344 * @param {string} type - Track type to override, possible values include 'Audio', 21345 * 'Video', and 'Text'. 21346 * @param {boolean} override - If set to true native audio/video will be overridden, 21347 * otherwise native audio/video will potentially be used. 21348 * @private 21349 */ 21350 ; 21351 21352 _proto.overrideNative_ = function overrideNative_(type, override) { 21353 var _this2 = this; 21354 21355 // If there is no behavioral change don't add/remove listeners 21356 if (override !== this["featuresNative" + type + "Tracks"]) { 21357 return; 21358 } 21359 21360 var lowerCaseType = type.toLowerCase(); 21361 21362 if (this[lowerCaseType + "TracksListeners_"]) { 21363 Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) { 21364 var elTracks = _this2.el()[lowerCaseType + "Tracks"]; 21365 21366 elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]); 21367 }); 21368 } 21369 21370 this["featuresNative" + type + "Tracks"] = !override; 21371 this[lowerCaseType + "TracksListeners_"] = null; 21372 this.proxyNativeTracksForType_(lowerCaseType); 21373 } 21374 /** 21375 * Attempt to force override of native audio tracks. 21376 * 21377 * @param {boolean} override - If set to true native audio will be overridden, 21378 * otherwise native audio will potentially be used. 21379 */ 21380 ; 21381 21382 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) { 21383 this.overrideNative_('Audio', override); 21384 } 21385 /** 21386 * Attempt to force override of native video tracks. 21387 * 21388 * @param {boolean} override - If set to true native video will be overridden, 21389 * otherwise native video will potentially be used. 21390 */ 21391 ; 21392 21393 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) { 21394 this.overrideNative_('Video', override); 21395 } 21396 /** 21397 * Proxy native track list events for the given type to our track 21398 * lists if the browser we are playing in supports that type of track list. 21399 * 21400 * @param {string} name - Track type; values include 'audio', 'video', and 'text' 21401 * @private 21402 */ 21403 ; 21404 21405 _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) { 21406 var _this3 = this; 21407 21408 var props = NORMAL[name]; 21409 var elTracks = this.el()[props.getterName]; 21410 var techTracks = this[props.getterName](); 21411 21412 if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) { 21413 return; 21414 } 21415 21416 var listeners = { 21417 change: function change(e) { 21418 var event = { 21419 type: 'change', 21420 target: techTracks, 21421 currentTarget: techTracks, 21422 srcElement: techTracks 21423 }; 21424 techTracks.trigger(event); // if we are a text track change event, we should also notify the 21425 // remote text track list. This can potentially cause a false positive 21426 // if we were to get a change event on a non-remote track and 21427 // we triggered the event on the remote text track list which doesn't 21428 // contain that track. However, best practices mean looping through the 21429 // list of tracks and searching for the appropriate mode value, so, 21430 // this shouldn't pose an issue 21431 21432 if (name === 'text') { 21433 _this3[REMOTE.remoteText.getterName]().trigger(event); 21434 } 21435 }, 21436 addtrack: function addtrack(e) { 21437 techTracks.addTrack(e.track); 21438 }, 21439 removetrack: function removetrack(e) { 21440 techTracks.removeTrack(e.track); 21441 } 21442 }; 21443 21444 var removeOldTracks = function removeOldTracks() { 21445 var removeTracks = []; 21446 21447 for (var i = 0; i < techTracks.length; i++) { 21448 var found = false; 21449 21450 for (var j = 0; j < elTracks.length; j++) { 21451 if (elTracks[j] === techTracks[i]) { 21452 found = true; 21453 break; 21454 } 21455 } 21456 21457 if (!found) { 21458 removeTracks.push(techTracks[i]); 21459 } 21460 } 21461 21462 while (removeTracks.length) { 21463 techTracks.removeTrack(removeTracks.shift()); 21464 } 21465 }; 21466 21467 this[props.getterName + 'Listeners_'] = listeners; 21468 Object.keys(listeners).forEach(function (eventName) { 21469 var listener = listeners[eventName]; 21470 elTracks.addEventListener(eventName, listener); 21471 21472 _this3.on('dispose', function (e) { 21473 return elTracks.removeEventListener(eventName, listener); 21474 }); 21475 }); // Remove (native) tracks that are not used anymore 21476 21477 this.on('loadstart', removeOldTracks); 21478 this.on('dispose', function (e) { 21479 return _this3.off('loadstart', removeOldTracks); 21480 }); 21481 } 21482 /** 21483 * Proxy all native track list events to our track lists if the browser we are playing 21484 * in supports that type of track list. 21485 * 21486 * @private 21487 */ 21488 ; 21489 21490 _proto.proxyNativeTracks_ = function proxyNativeTracks_() { 21491 var _this4 = this; 21492 21493 NORMAL.names.forEach(function (name) { 21494 _this4.proxyNativeTracksForType_(name); 21495 }); 21496 } 21497 /** 21498 * Create the `Html5` Tech's DOM element. 21499 * 21500 * @return {Element} 21501 * The element that gets created. 21502 */ 21503 ; 21504 21505 _proto.createEl = function createEl() { 21506 var el = this.options_.tag; // Check if this browser supports moving the element into the box. 21507 // On the iPhone video will break if you move the element, 21508 // So we have to create a brand new element. 21509 // If we ingested the player div, we do not need to move the media element. 21510 21511 if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { 21512 // If the original tag is still there, clone and remove it. 21513 if (el) { 21514 var clone = el.cloneNode(true); 21515 21516 if (el.parentNode) { 21517 el.parentNode.insertBefore(clone, el); 21518 } 21519 21520 Html5.disposeMediaElement(el); 21521 el = clone; 21522 } else { 21523 el = document.createElement('video'); // determine if native controls should be used 21524 21525 var tagAttributes = this.options_.tag && getAttributes(this.options_.tag); 21526 var attributes = mergeOptions({}, tagAttributes); 21527 21528 if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { 21529 delete attributes.controls; 21530 } 21531 21532 setAttributes(el, assign(attributes, { 21533 id: this.options_.techId, 21534 "class": 'vjs-tech' 21535 })); 21536 } 21537 21538 el.playerId = this.options_.playerId; 21539 } 21540 21541 if (typeof this.options_.preload !== 'undefined') { 21542 setAttribute(el, 'preload', this.options_.preload); 21543 } // Update specific tag settings, in case they were overridden 21544 // `autoplay` has to be *last* so that `muted` and `playsinline` are present 21545 // when iOS/Safari or other browsers attempt to autoplay. 21546 21547 21548 var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay']; 21549 21550 for (var i = 0; i < settingsAttrs.length; i++) { 21551 var attr = settingsAttrs[i]; 21552 var value = this.options_[attr]; 21553 21554 if (typeof value !== 'undefined') { 21555 if (value) { 21556 setAttribute(el, attr, attr); 21557 } else { 21558 removeAttribute(el, attr); 21559 } 21560 21561 el[attr] = value; 21562 } 21563 } 21564 21565 return el; 21566 } 21567 /** 21568 * This will be triggered if the loadstart event has already fired, before videojs was 21569 * ready. Two known examples of when this can happen are: 21570 * 1. If we're loading the playback object after it has started loading 21571 * 2. The media is already playing the (often with autoplay on) then 21572 * 21573 * This function will fire another loadstart so that videojs can catchup. 21574 * 21575 * @fires Tech#loadstart 21576 * 21577 * @return {undefined} 21578 * returns nothing. 21579 */ 21580 ; 21581 21582 _proto.handleLateInit_ = function handleLateInit_(el) { 21583 if (el.networkState === 0 || el.networkState === 3) { 21584 // The video element hasn't started loading the source yet 21585 // or didn't find a source 21586 return; 21587 } 21588 21589 if (el.readyState === 0) { 21590 // NetworkState is set synchronously BUT loadstart is fired at the 21591 // end of the current stack, usually before setInterval(fn, 0). 21592 // So at this point we know loadstart may have already fired or is 21593 // about to fire, and either way the player hasn't seen it yet. 21594 // We don't want to fire loadstart prematurely here and cause a 21595 // double loadstart so we'll wait and see if it happens between now 21596 // and the next loop, and fire it if not. 21597 // HOWEVER, we also want to make sure it fires before loadedmetadata 21598 // which could also happen between now and the next loop, so we'll 21599 // watch for that also. 21600 var loadstartFired = false; 21601 21602 var setLoadstartFired = function setLoadstartFired() { 21603 loadstartFired = true; 21604 }; 21605 21606 this.on('loadstart', setLoadstartFired); 21607 21608 var triggerLoadstart = function triggerLoadstart() { 21609 // We did miss the original loadstart. Make sure the player 21610 // sees loadstart before loadedmetadata 21611 if (!loadstartFired) { 21612 this.trigger('loadstart'); 21613 } 21614 }; 21615 21616 this.on('loadedmetadata', triggerLoadstart); 21617 this.ready(function () { 21618 this.off('loadstart', setLoadstartFired); 21619 this.off('loadedmetadata', triggerLoadstart); 21620 21621 if (!loadstartFired) { 21622 // We did miss the original native loadstart. Fire it now. 21623 this.trigger('loadstart'); 21624 } 21625 }); 21626 return; 21627 } // From here on we know that loadstart already fired and we missed it. 21628 // The other readyState events aren't as much of a problem if we double 21629 // them, so not going to go to as much trouble as loadstart to prevent 21630 // that unless we find reason to. 21631 21632 21633 var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater 21634 21635 eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater 21636 21637 if (el.readyState >= 2) { 21638 eventsToTrigger.push('loadeddata'); 21639 } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater 21640 21641 21642 if (el.readyState >= 3) { 21643 eventsToTrigger.push('canplay'); 21644 } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) 21645 21646 21647 if (el.readyState >= 4) { 21648 eventsToTrigger.push('canplaythrough'); 21649 } // We still need to give the player time to add event listeners 21650 21651 21652 this.ready(function () { 21653 eventsToTrigger.forEach(function (type) { 21654 this.trigger(type); 21655 }, this); 21656 }); 21657 } 21658 /** 21659 * Set current time for the `HTML5` tech. 21660 * 21661 * @param {number} seconds 21662 * Set the current time of the media to this. 21663 */ 21664 ; 21665 21666 _proto.setCurrentTime = function setCurrentTime(seconds) { 21667 try { 21668 this.el_.currentTime = seconds; 21669 } catch (e) { 21670 log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady); 21671 } 21672 } 21673 /** 21674 * Get the current duration of the HTML5 media element. 21675 * 21676 * @return {number} 21677 * The duration of the media or 0 if there is no duration. 21678 */ 21679 ; 21680 21681 _proto.duration = function duration() { 21682 var _this5 = this; 21683 21684 // Android Chrome will report duration as Infinity for VOD HLS until after 21685 // playback has started, which triggers the live display erroneously. 21686 // Return NaN if playback has not started and trigger a durationupdate once 21687 // the duration can be reliably known. 21688 if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) { 21689 // Wait for the first `timeupdate` with currentTime > 0 - there may be 21690 // several with 0 21691 var checkProgress = function checkProgress() { 21692 if (_this5.el_.currentTime > 0) { 21693 // Trigger durationchange for genuinely live video 21694 if (_this5.el_.duration === Infinity) { 21695 _this5.trigger('durationchange'); 21696 } 21697 21698 _this5.off('timeupdate', checkProgress); 21699 } 21700 }; 21701 21702 this.on('timeupdate', checkProgress); 21703 return NaN; 21704 } 21705 21706 return this.el_.duration || NaN; 21707 } 21708 /** 21709 * Get the current width of the HTML5 media element. 21710 * 21711 * @return {number} 21712 * The width of the HTML5 media element. 21713 */ 21714 ; 21715 21716 _proto.width = function width() { 21717 return this.el_.offsetWidth; 21718 } 21719 /** 21720 * Get the current height of the HTML5 media element. 21721 * 21722 * @return {number} 21723 * The height of the HTML5 media element. 21724 */ 21725 ; 21726 21727 _proto.height = function height() { 21728 return this.el_.offsetHeight; 21729 } 21730 /** 21731 * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into 21732 * `fullscreenchange` event. 21733 * 21734 * @private 21735 * @fires fullscreenchange 21736 * @listens webkitendfullscreen 21737 * @listens webkitbeginfullscreen 21738 * @listens webkitbeginfullscreen 21739 */ 21740 ; 21741 21742 _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { 21743 var _this6 = this; 21744 21745 if (!('webkitDisplayingFullscreen' in this.el_)) { 21746 return; 21747 } 21748 21749 var endFn = function endFn() { 21750 this.trigger('fullscreenchange', { 21751 isFullscreen: false 21752 }); 21753 }; 21754 21755 var beginFn = function beginFn() { 21756 if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') { 21757 this.one('webkitendfullscreen', endFn); 21758 this.trigger('fullscreenchange', { 21759 isFullscreen: true, 21760 // set a flag in case another tech triggers fullscreenchange 21761 nativeIOSFullscreen: true 21762 }); 21763 } 21764 }; 21765 21766 this.on('webkitbeginfullscreen', beginFn); 21767 this.on('dispose', function () { 21768 _this6.off('webkitbeginfullscreen', beginFn); 21769 21770 _this6.off('webkitendfullscreen', endFn); 21771 }); 21772 } 21773 /** 21774 * Check if fullscreen is supported on the current playback device. 21775 * 21776 * @return {boolean} 21777 * - True if fullscreen is supported. 21778 * - False if fullscreen is not supported. 21779 */ 21780 ; 21781 21782 _proto.supportsFullScreen = function supportsFullScreen() { 21783 if (typeof this.el_.webkitEnterFullScreen === 'function') { 21784 var userAgent = window$3.navigator && window$3.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard 21785 21786 if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { 21787 return true; 21788 } 21789 } 21790 21791 return false;
21792 } 21793 /** 21794 * Request that the `HTML5` Tech enter fullscreen. 21795 */ 21796 ; 21797 21798 _proto.enterFullScreen = function enterFullScreen() { 21799 var video = this.el_; 21800 21801 if (video.paused && video.networkState <= video.HAVE_METADATA) { 21802 // attempt to prime the video element for programmatic access 21803 // this isn't necessary on the desktop but shouldn't hurt 21804 silencePromise(this.el_.play()); // playing and pausing synchronously during the transition to fullscreen 21805 // can get iOS ~6.1 devices into a play/pause loop 21806 21807 this.setTimeout(function () { 21808 video.pause(); 21809 21810 try { 21811 video.webkitEnterFullScreen(); 21812 } catch (e) { 21813 this.trigger('fullscreenerror', e); 21814 } 21815 }, 0); 21816 } else { 21817 try { 21818 video.webkitEnterFullScreen(); 21819 } catch (e) { 21820 this.trigger('fullscreenerror', e); 21821 } 21822 } 21823 } 21824 /** 21825 * Request that the `HTML5` Tech exit fullscreen. 21826 */ 21827 ; 21828 21829 _proto.exitFullScreen = function exitFullScreen() { 21830 if (!this.el_.webkitDisplayingFullscreen) { 21831 this.trigger('fullscreenerror', new Error('The video is not fullscreen')); 21832 return; 21833 } 21834 21835 this.el_.webkitExitFullScreen(); 21836 } 21837 /** 21838 * Create a floating video window always on top of other windows so that users may 21839 * continue consuming media while they interact with other content sites, or 21840 * applications on their device. 21841 * 21842 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 21843 * 21844 * @return {Promise} 21845 * A promise with a Picture-in-Picture window. 21846 */ 21847 ; 21848 21849 _proto.requestPictureInPicture = function requestPictureInPicture() { 21850 return this.el_.requestPictureInPicture(); 21851 } 21852 /** 21853 * A getter/setter for the `Html5` Tech's source object. 21854 * > Note: Please use {@link Html5#setSource} 21855 * 21856 * @param {Tech~SourceObject} [src] 21857 * The source object you want to set on the `HTML5` techs element. 21858 * 21859 * @return {Tech~SourceObject|undefined} 21860 * - The current source object when a source is not passed in. 21861 * - undefined when setting 21862 * 21863 * @deprecated Since version 5. 21864 */ 21865 ; 21866 21867 _proto.src = function src(_src) { 21868 if (_src === undefined) { 21869 return this.el_.src; 21870 } // Setting src through `src` instead of `setSrc` will be deprecated 21871 21872 21873 this.setSrc(_src); 21874 } 21875 /** 21876 * Reset the tech by removing all sources and then calling 21877 * {@link Html5.resetMediaElement}. 21878 */ 21879 ; 21880 21881 _proto.reset = function reset() { 21882 Html5.resetMediaElement(this.el_); 21883 } 21884 /** 21885 * Get the current source on the `HTML5` Tech. Falls back to returning the source from 21886 * the HTML5 media element. 21887 * 21888 * @return {Tech~SourceObject} 21889 * The current source object from the HTML5 tech. With a fallback to the 21890 * elements source. 21891 */ 21892 ; 21893 21894 _proto.currentSrc = function currentSrc() { 21895 if (this.currentSource_) { 21896 return this.currentSource_.src; 21897 } 21898 21899 return this.el_.currentSrc; 21900 } 21901 /** 21902 * Set controls attribute for the HTML5 media Element. 21903 * 21904 * @param {string} val 21905 * Value to set the controls attribute to 21906 */ 21907 ; 21908 21909 _proto.setControls = function setControls(val) { 21910 this.el_.controls = !!val; 21911 } 21912 /** 21913 * Create and returns a remote {@link TextTrack} object. 21914 * 21915 * @param {string} kind 21916 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 21917 * 21918 * @param {string} [label] 21919 * Label to identify the text track 21920 * 21921 * @param {string} [language] 21922 * Two letter language abbreviation 21923 * 21924 * @return {TextTrack} 21925 * The TextTrack that gets created. 21926 */ 21927 ; 21928 21929 _proto.addTextTrack = function addTextTrack(kind, label, language) { 21930 if (!this.featuresNativeTextTracks) { 21931 return _Tech.prototype.addTextTrack.call(this, kind, label, language); 21932 } 21933 21934 return this.el_.addTextTrack(kind, label, language); 21935 } 21936 /** 21937 * Creates either native TextTrack or an emulated TextTrack depending 21938 * on the value of `featuresNativeTextTracks` 21939 * 21940 * @param {Object} options 21941 * The object should contain the options to initialize the TextTrack with. 21942 * 21943 * @param {string} [options.kind] 21944 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 21945 * 21946 * @param {string} [options.label] 21947 * Label to identify the text track 21948 * 21949 * @param {string} [options.language] 21950 * Two letter language abbreviation. 21951 * 21952 * @param {boolean} [options.default] 21953 * Default this track to on. 21954 * 21955 * @param {string} [options.id] 21956 * The internal id to assign this track. 21957 * 21958 * @param {string} [options.src] 21959 * A source url for the track. 21960 * 21961 * @return {HTMLTrackElement} 21962 * The track element that gets created. 21963 */ 21964 ; 21965 21966 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 21967 if (!this.featuresNativeTextTracks) { 21968 return _Tech.prototype.createRemoteTextTrack.call(this, options); 21969 } 21970 21971 var htmlTrackElement = document.createElement('track'); 21972 21973 if (options.kind) { 21974 htmlTrackElement.kind = options.kind; 21975 } 21976 21977 if (options.label) { 21978 htmlTrackElement.label = options.label; 21979 } 21980 21981 if (options.language || options.srclang) { 21982 htmlTrackElement.srclang = options.language || options.srclang; 21983 } 21984 21985 if (options["default"]) { 21986 htmlTrackElement["default"] = options["default"]; 21987 } 21988 21989 if (options.id) { 21990 htmlTrackElement.id = options.id; 21991 } 21992 21993 if (options.src) { 21994 htmlTrackElement.src = options.src; 21995 } 21996 21997 return htmlTrackElement; 21998 } 21999 /** 22000 * Creates a remote text track object and returns an html track element. 22001 * 22002 * @param {Object} options The object should contain values for 22003 * kind, language, label, and src (location of the WebVTT file) 22004 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 22005 * automatically removed from the video element whenever the source changes 22006 * @return {HTMLTrackElement} An Html Track Element. 22007 * This can be an emulated {@link HTMLTrackElement} or a native one. 22008 * @deprecated The default value of the "manualCleanup" parameter will default 22009 * to "false" in upcoming versions of Video.js 22010 */ 22011 ; 22012 22013 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 22014 var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); 22015 22016 if (this.featuresNativeTextTracks) { 22017 this.el().appendChild(htmlTrackElement); 22018 } 22019 22020 return htmlTrackElement; 22021 } 22022 /** 22023 * Remove remote `TextTrack` from `TextTrackList` object 22024 * 22025 * @param {TextTrack} track 22026 * `TextTrack` object to remove 22027 */ 22028 ; 22029 22030 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 22031 _Tech.prototype.removeRemoteTextTrack.call(this, track); 22032 22033 if (this.featuresNativeTextTracks) { 22034 var tracks = this.$$('track'); 22035 var i = tracks.length; 22036 22037 while (i--) { 22038 if (track === tracks[i] || track === tracks[i].track) { 22039 this.el().removeChild(tracks[i]); 22040 } 22041 } 22042 } 22043 } 22044 /** 22045 * Gets available media playback quality metrics as specified by the W3C's Media 22046 * Playback Quality API. 22047 * 22048 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 22049 * 22050 * @return {Object} 22051 * An object with supported media playback quality metrics 22052 */ 22053 ; 22054 22055 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 22056 if (typeof this.el().getVideoPlaybackQuality === 'function') { 22057 return this.el().getVideoPlaybackQuality(); 22058 } 22059 22060 var videoPlaybackQuality = {}; 22061 22062 if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { 22063 videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; 22064 videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; 22065 } 22066 22067 if (window$3.performance && typeof window$3.performance.now === 'function') { 22068 videoPlaybackQuality.creationTime = window$3.performance.now(); 22069 } else if (window$3.performance && window$3.performance.timing && typeof window$3.performance.timing.navigationStart === 'number') { 22070 videoPlaybackQuality.creationTime = window$3.Date.now() - window$3.performance.timing.navigationStart; 22071 } 22072 22073 return videoPlaybackQuality; 22074 }; 22075 22076 return Html5; 22077 }(Tech); 22078 /* HTML5 Support Testing ---------------------------------------------------- */ 22079 22080 /** 22081 * Element for testing browser HTML5 media capabilities 22082 * 22083 * @type {Element} 22084 * @constant 22085 * @private 22086 */ 22087 22088 22089 defineLazyProperty(Html5, 'TEST_VID', function () { 22090 if (!isReal()) { 22091 return; 22092 } 22093 22094 var video = document.createElement('video'); 22095 var track = document.createElement('track'); 22096 track.kind = 'captions'; 22097 track.srclang = 'en'; 22098 track.label = 'English'; 22099 video.appendChild(track); 22100 return video; 22101 }); 22102 /** 22103 * Check if HTML5 media is supported by this browser/device. 22104 * 22105 * @return {boolean} 22106 * - True if HTML5 media is supported. 22107 * - False if HTML5 media is not supported. 22108 */ 22109 22110 Html5.isSupported = function () {
vendor: 4,248 bytes, lines 22111-22254
22111 // IE with no Media Player is a LIAR! (#984) 22112 try { 22113 Html5.TEST_VID.volume = 0.5; 22114 } catch (e) { 22115 return false; 22116 } 22117 22118 return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); 22119 }; 22120 /** 22121 * Check if the tech can support the given type 22122 * 22123 * @param {string} type 22124 * The mimetype to check 22125 * @return {string} 'probably', 'maybe', or '' (empty string) 22126 */ 22127 22128 22129 Html5.canPlayType = function (type) { 22130 return Html5.TEST_VID.canPlayType(type); 22131 }; 22132 /** 22133 * Check if the tech can support the given source 22134 * 22135 * @param {Object} srcObj 22136 * The source object 22137 * @param {Object} options 22138 * The options passed to the tech 22139 * @return {string} 'probably', 'maybe', or '' (empty string) 22140 */ 22141 22142 22143 Html5.canPlaySource = function (srcObj, options) { 22144 return Html5.canPlayType(srcObj.type); 22145 }; 22146 /** 22147 * Check if the volume can be changed in this browser/device. 22148 * Volume cannot be changed in a lot of mobile devices. 22149 * Specifically, it can't be changed from 1 on iOS. 22150 * 22151 * @return {boolean} 22152 * - True if volume can be controlled 22153 * - False otherwise 22154 */ 22155 22156 22157 Html5.canControlVolume = function () { 22158 // IE will error if Windows Media Player not installed #3315 22159 try { 22160 var volume = Html5.TEST_VID.volume; 22161 Html5.TEST_VID.volume = volume / 2 + 0.1; 22162 return volume !== Html5.TEST_VID.volume; 22163 } catch (e) { 22164 return false; 22165 } 22166 }; 22167 /** 22168 * Check if the volume can be muted in this browser/device. 22169 * Some devices, e.g. iOS, don't allow changing volume 22170 * but permits muting/unmuting. 22171 * 22172 * @return {bolean} 22173 * - True if volume can be muted 22174 * - False otherwise 22175 */ 22176 22177 22178 Html5.canMuteVolume = function () { 22179 try { 22180 var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always 22181 // work, so we want to set both property and attribute 22182 22183 Html5.TEST_VID.muted = !muted; 22184 22185 if (Html5.TEST_VID.muted) { 22186 setAttribute(Html5.TEST_VID, 'muted', 'muted'); 22187 } else { 22188 removeAttribute(Html5.TEST_VID, 'muted', 'muted'); 22189 } 22190 22191 return muted !== Html5.TEST_VID.muted; 22192 } catch (e) { 22193 return false; 22194 } 22195 }; 22196 /** 22197 * Check if the playback rate can be changed in this browser/device. 22198 * 22199 * @return {boolean} 22200 * - True if playback rate can be controlled 22201 * - False otherwise 22202 */ 22203 22204 22205 Html5.canControlPlaybackRate = function () { 22206 // Playback rate API is implemented in Android Chrome, but doesn't do anything 22207 // https://github.com/videojs/video.js/issues/3180 22208 if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) { 22209 return false; 22210 } // IE will error if Windows Media Player not installed #3315 22211 22212 22213 try { 22214 var playbackRate = Html5.TEST_VID.playbackRate; 22215 Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; 22216 return playbackRate !== Html5.TEST_VID.playbackRate; 22217 } catch (e) { 22218 return false; 22219 } 22220 }; 22221 /** 22222 * Check if we can override a video/audio elements attributes, with 22223 * Object.defineProperty. 22224 * 22225 * @return {boolean} 22226 * - True if builtin attributes can be overridden 22227 * - False otherwise 22228 */ 22229 22230 22231 Html5.canOverrideAttributes = function () { 22232 // if we cannot overwrite the src/innerHTML property, there is no support 22233 // iOS 7 safari for instance cannot do this. 22234 try { 22235 var noop = function noop() {}; 22236 22237 Object.defineProperty(document.createElement('video'), 'src', { 22238 get: noop, 22239 set: noop 22240 }); 22241 Object.defineProperty(document.createElement('audio'), 'src', { 22242 get: noop, 22243 set: noop 22244 }); 22245 Object.defineProperty(document.createElement('video'), 'innerHTML', { 22246 get: noop, 22247 set: noop 22248 }); 22249 Object.defineProperty(document.createElement('audio'), 'innerHTML', { 22250 get: noop, 22251 set: noop 22252 }); 22253 } catch (e) { 22254 return false;
22255 } 22256 22257 return true; 22258 }; 22259 /** 22260 * Check to see if native `TextTrack`s are supported by this browser/device. 22261 * 22262 * @return {boolean} 22263 * - True if native `TextTrack`s are supported. 22264 * - False otherwise 22265 */ 22266 22267 22268 Html5.supportsNativeTextTracks = function () { 22269 return IS_ANY_SAFARI || IS_IOS && IS_CHROME; 22270 }; 22271 /** 22272 * Check to see if native `VideoTrack`s are supported by this browser/device 22273 * 22274 * @return {boolean} 22275 * - True if native `VideoTrack`s are supported. 22276 * - False otherwise 22277 */ 22278 22279 22280 Html5.supportsNativeVideoTracks = function () { 22281 return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); 22282 }; 22283 /** 22284 * Check to see if native `AudioTrack`s are supported by this browser/device 22285 * 22286 * @return {boolean} 22287 * - True if native `AudioTrack`s are supported. 22288 * - False otherwise 22289 */ 22290 22291 22292 Html5.supportsNativeAudioTracks = function () { 22293 return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); 22294 }; 22295 /** 22296 * An array of events available on the Html5 tech. 22297 * 22298 * @private 22299 * @type {Array} 22300 */ 22301 22302 22303 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']; 22304 /** 22305 * Boolean indicating whether the `Tech` supports volume control. 22306 * 22307 * @type {boolean} 22308 * @default {@link Html5.canControlVolume} 22309 */ 22310 22311 /** 22312 * Boolean indicating whether the `Tech` supports muting volume. 22313 * 22314 * @type {bolean} 22315 * @default {@link Html5.canMuteVolume} 22316 */ 22317 22318 /** 22319 * Boolean indicating whether the `Tech` supports changing the speed at which the media 22320 * plays. Examples: 22321 * - Set player to play 2x (twice) as fast 22322 * - Set player to play 0.5x (half) as fast 22323 * 22324 * @type {boolean} 22325 * @default {@link Html5.canControlPlaybackRate} 22326 */ 22327 22328 /** 22329 * Boolean indicating whether the `Tech` supports the `sourceset` event. 22330 * 22331 * @type {boolean} 22332 * @default 22333 */ 22334 22335 /** 22336 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. 22337 * 22338 * @type {boolean} 22339 * @default {@link Html5.supportsNativeTextTracks} 22340 */ 22341 22342 /** 22343 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. 22344 * 22345 * @type {boolean} 22346 * @default {@link Html5.supportsNativeVideoTracks} 22347 */ 22348 22349 /** 22350 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. 22351 * 22352 * @type {boolean} 22353 * @default {@link Html5.supportsNativeAudioTracks} 22354 */ 22355 22356 [['featuresVolumeControl', 'canControlVolume'], ['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) { 22357 var key = _ref[0], 22358 fn = _ref[1]; 22359 defineLazyProperty(Html5.prototype, key, function () { 22360 return Html5[fn](); 22361 }, true); 22362 }); 22363 /** 22364 * Boolean indicating whether the `HTML5` tech currently supports the media element 22365 * moving in the DOM. iOS breaks if you move the media element, so this is set this to 22366 * false there. Everywhere else this should be true. 22367 * 22368 * @type {boolean} 22369 * @default 22370 */ 22371 22372 Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed. 22373 // Is this true? 22374 22375 /** 22376 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize 22377 * when going into fullscreen. 22378 * 22379 * @type {boolean} 22380 * @default 22381 */ 22382 22383 Html5.prototype.featuresFullscreenResize = true; 22384 /** 22385 * Boolean indicating whether the `HTML5` tech currently supports the progress event. 22386 * If this is false, manual `progress` events will be triggered instead. 22387 * 22388 * @type {boolean} 22389 * @default 22390 */ 22391 22392 Html5.prototype.featuresProgressEvents = true; 22393 /** 22394 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. 22395 * If this is false, manual `timeupdate` events will be triggered instead. 22396 * 22397 * @default 22398 */ 22399 22400 Html5.prototype.featuresTimeupdateEvents = true; // HTML5 Feature detection and Device Fixes --------------------------------- // 22401 22402 var canPlayType; 22403 22404 Html5.patchCanPlayType = function () { 22405 // Android 4.0 and above can play HLS to some extent but it reports being unable to do so 22406 // Firefox and Chrome report correctly 22407 if (ANDROID_VERSION >
vendor: 15,215 bytes, lines 22407-22799
22407= 4.0 && !IS_FIREFOX && !IS_CHROME) { 22408 canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; 22409 22410 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 22411 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 22412 22413 if (type && mpegurlRE.test(type)) { 22414 return 'maybe'; 22415 } 22416 22417 return canPlayType.call(this, type); 22418 }; 22419 } 22420 }; 22421 22422 Html5.unpatchCanPlayType = function () { 22423 var r = Html5.TEST_VID.constructor.prototype.canPlayType; 22424 22425 if (canPlayType) { 22426 Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; 22427 } 22428 22429 return r; 22430 }; // by default, patch the media element 22431 22432 22433 Html5.patchCanPlayType(); 22434 22435 Html5.disposeMediaElement = function (el) { 22436 if (!el) { 22437 return; 22438 } 22439 22440 if (el.parentNode) { 22441 el.parentNode.removeChild(el); 22442 } // remove any child track or source nodes to prevent their loading 22443 22444 22445 while (el.hasChildNodes()) { 22446 el.removeChild(el.firstChild); 22447 } // remove any src reference. not setting `src=''` because that causes a warning 22448 // in firefox 22449 22450 22451 el.removeAttribute('src'); // force the media element to update its loading state by calling load() 22452 // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) 22453 22454 if (typeof el.load === 'function') { 22455 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 22456 (function () { 22457 try { 22458 el.load(); 22459 } catch (e) {// not supported 22460 } 22461 })(); 22462 } 22463 }; 22464 22465 Html5.resetMediaElement = function (el) { 22466 if (!el) { 22467 return; 22468 } 22469 22470 var sources = el.querySelectorAll('source'); 22471 var i = sources.length; 22472 22473 while (i--) { 22474 el.removeChild(sources[i]); 22475 } // remove any src reference. 22476 // not setting `src=''` because that throws an error 22477 22478 22479 el.removeAttribute('src'); 22480 22481 if (typeof el.load === 'function') { 22482 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 22483 (function () { 22484 try { 22485 el.load(); 22486 } catch (e) {// satisfy linter 22487 } 22488 })(); 22489 } 22490 }; 22491 /* Native HTML5 element property wrapping ----------------------------------- */ 22492 // Wrap native boolean attributes with getters that check both property and attribute 22493 // The list is as followed: 22494 // muted, defaultMuted, autoplay, controls, loop, playsinline 22495 22496 22497 [ 22498 /** 22499 * Get the value of `muted` from the media element. `muted` indicates 22500 * that the volume for the media should be set to silent. This does not actually change 22501 * the `volume` attribute. 22502 * 22503 * @method Html5#muted 22504 * @return {boolean} 22505 * - True if the value of `volume` should be ignored and the audio set to silent. 22506 * - False if the value of `volume` should be used. 22507 * 22508 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 22509 */ 22510 'muted', 22511 /** 22512 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates 22513 * whether the media should start muted or not. Only changes the default state of the 22514 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the 22515 * current state. 22516 * 22517 * @method Html5#defaultMuted 22518 * @return {boolean} 22519 * - The value of `defaultMuted` from the media element. 22520 * - True indicates that the media should start muted. 22521 * - False indicates that the media should not start muted 22522 * 22523 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 22524 */ 22525 'defaultMuted', 22526 /** 22527 * Get the value of `autoplay` from the media element. `autoplay` indicates 22528 * that the media should start to play as soon as the page is ready. 22529 * 22530 * @method Html5#autoplay 22531 * @return {boolean} 22532 * - The value of `autoplay` from the media element. 22533 * - True indicates that the media should start as soon as the page loads. 22534 * - False indicates that the media should not start as soon as the page loads. 22535 * 22536 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 22537 */ 22538 'autoplay', 22539 /** 22540 * Get the value of `controls` from the media element. `controls` indicates 22541 * whether the native media controls should be shown or hidden. 22542 * 22543 * @method Html5#controls 22544 * @return {boolean} 22545 * - The value of `controls` from the media element. 22546 * - True indicates that native controls should be showing. 22547 * - False indicates that native controls should be hidden. 22548 * 22549 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} 22550 */ 22551 'controls', 22552 /** 22553 * Get the value of `loop` from the media element. `loop` indicates 22554 * that the media should return to the start of the media and continue playing once 22555 * it reaches the end. 22556 * 22557 * @method Html5#loop 22558 * @return {boolean} 22559 * - The value of `loop` from the media element. 22560 * - True indicates that playback should seek back to start once 22561 * the end of a media is reached. 22562 * - False indicates that playback should not loop back to the start when the 22563 * end of the media is reached. 22564 * 22565 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 22566 */ 22567 'loop', 22568 /** 22569 * Get the value of `playsinline` from the media element. `playsinline` indicates 22570 * to the browser that non-fullscreen playback is preferred when fullscreen 22571 * playback is the native default, such as in iOS Safari. 22572 * 22573 * @method Html5#playsinline 22574 * @return {boolean} 22575 * - The value of `playsinline` from the media element. 22576 * - True indicates that the media should play inline. 22577 * - False indicates that the media should not play inline. 22578 * 22579 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 22580 */ 22581 'playsinline'].forEach(function (prop) { 22582 Html5.prototype[prop] = function () { 22583 return this.el_[prop] || this.el_.hasAttribute(prop); 22584 }; 22585 }); // Wrap native boolean attributes with setters that set both property and attribute 22586 // The list is as followed: 22587 // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline 22588 // setControls is special-cased above 22589 22590 [ 22591 /** 22592 * Set the value of `muted` on the media element. `muted` indicates that the current 22593 * audio level should be silent. 22594 * 22595 * @method Html5#setMuted 22596 * @param {boolean} muted 22597 * - True if the audio should be set to silent 22598 * - False otherwise 22599 * 22600 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 22601 */ 22602 'muted', 22603 /** 22604 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current 22605 * audio level should be silent, but will only effect the muted level on intial playback.. 22606 * 22607 * @method Html5.prototype.setDefaultMuted 22608 * @param {boolean} defaultMuted 22609 * - True if the audio should be set to silent 22610 * - False otherwise 22611 * 22612 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 22613 */ 22614 'defaultMuted', 22615 /** 22616 * Set the value of `autoplay` on the media element. `autoplay` indicates 22617 * that the media should start to play as soon as the page is ready. 22618 * 22619 * @method Html5#setAutoplay 22620 * @param {boolean} autoplay 22621 * - True indicates that the media should start as soon as the page loads. 22622 * - False indicates that the media should not start as soon as the page loads. 22623 * 22624 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 22625 */ 22626 'autoplay', 22627 /** 22628 * Set the value of `loop` on the media element. `loop` indicates 22629 * that the media should return to the start of the media and continue playing once 22630 * it reaches the end. 22631 * 22632 * @method Html5#setLoop 22633 * @param {boolean} loop 22634 * - True indicates that playback should seek back to start once 22635 * the end of a media is reached. 22636 * - False indicates that playback should not loop back to the start when the 22637 * end of the media is reached. 22638 * 22639 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 22640 */ 22641 'loop', 22642 /** 22643 * Set the value of `playsinline` from the media element. `playsinline` indicates 22644 * to the browser that non-fullscreen playback is preferred when fullscreen 22645 * playback is the native default, such as in iOS Safari. 22646 * 22647 * @method Html5#setPlaysinline 22648 * @param {boolean} playsinline 22649 * - True indicates that the media should play inline. 22650 * - False indicates that the media should not play inline. 22651 * 22652 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 22653 */ 22654 'playsinline'].forEach(function (prop) { 22655 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 22656 this.el_[prop] = v; 22657 22658 if (v) { 22659 this.el_.setAttribute(prop, prop); 22660 } else { 22661 this.el_.removeAttribute(prop); 22662 } 22663 }; 22664 }); // Wrap native properties with a getter 22665 // The list is as followed 22666 // paused, currentTime, buffered, volume, poster, preload, error, seeking 22667 // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState 22668 // readyState, videoWidth, videoHeight, crossOrigin 22669 22670 [ 22671 /** 22672 * Get the value of `paused` from the media element. `paused` indicates whether the media element 22673 * is currently paused or not. 22674 * 22675 * @method Html5#paused 22676 * @return {boolean} 22677 * The value of `paused` from the media element. 22678 * 22679 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} 22680 */ 22681 'paused', 22682 /** 22683 * Get the value of `currentTime` from the media element. `currentTime` indicates 22684 * the current second that the media is at in playback. 22685 * 22686 * @method Html5#currentTime 22687 * @return {number} 22688 * The value of `currentTime` from the media element. 22689 * 22690 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} 22691 */ 22692 'currentTime', 22693 /** 22694 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` 22695 * object that represents the parts of the media that are already downloaded and 22696 * available for playback. 22697 * 22698 * @method Html5#buffered 22699 * @return {TimeRange} 22700 * The value of `buffered` from the media element. 22701 * 22702 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} 22703 */ 22704 'buffered', 22705 /** 22706 * Get the value of `volume` from the media element. `volume` indicates 22707 * the current playback volume of audio for a media. `volume` will be a value from 0 22708 * (silent) to 1 (loudest and default). 22709 * 22710 * @method Html5#volume 22711 * @return {number} 22712 * The value of `volume` from the media element. Value will be between 0-1. 22713 * 22714 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 22715 */ 22716 'volume', 22717 /** 22718 * Get the value of `poster` from the media element. `poster` indicates 22719 * that the url of an image file that can/will be shown when no media data is available. 22720 * 22721 * @method Html5#poster 22722 * @return {string} 22723 * The value of `poster` from the media element. Value will be a url to an 22724 * image. 22725 * 22726 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} 22727 */ 22728 'poster', 22729 /** 22730 * Get the value of `preload` from the media element. `preload` indicates 22731 * what should download before the media is interacted with. It can have the following 22732 * values: 22733 * - none: nothing should be downloaded 22734 * - metadata: poster and the first few frames of the media may be downloaded to get 22735 * media dimensions and other metadata 22736 * - auto: allow the media and metadata for the media to be downloaded before 22737 * interaction 22738 * 22739 * @method Html5#preload 22740 * @return {string} 22741 * The value of `preload` from the media element. Will be 'none', 'metadata', 22742 * or 'auto'. 22743 * 22744 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 22745 */ 22746 'preload', 22747 /** 22748 * Get the value of the `error` from the media element. `error` indicates any 22749 * MediaError that may have occurred during playback. If error returns null there is no 22750 * current error. 22751 * 22752 * @method Html5#error 22753 * @return {MediaError|null} 22754 * The value of `error` from the media element. Will be `MediaError` if there 22755 * is a current error and null otherwise. 22756 * 22757 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} 22758 */ 22759 'error', 22760 /** 22761 * Get the value of `seeking` from the media element. `seeking` indicates whether the 22762 * media is currently seeking to a new position or not. 22763 * 22764 * @method Html5#seeking 22765 * @return {boolean} 22766 * - The value of `seeking` from the media element. 22767 * - True indicates that the media is currently seeking to a new position. 22768 * - False indicates that the media is not seeking to a new position at this time. 22769 * 22770 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} 22771 */ 22772 'seeking', 22773 /** 22774 * Get the value of `seekable` from the media element. `seekable` returns a 22775 * `TimeRange` object indicating ranges of time that can currently be `seeked` to. 22776 * 22777 * @method Html5#seekable 22778 * @return {TimeRange} 22779 * The value of `seekable` from the media element. A `TimeRange` object 22780 * indicating the current ranges of time that can be seeked to. 22781 * 22782 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} 22783 */ 22784 'seekable', 22785 /** 22786 * Get the value of `ended` from the media element. `ended` indicates whether 22787 * the media has reached the end or not. 22788 * 22789 * @method Html5#ended 22790 * @return {boolean} 22791 * - The value of `ended` from the media element. 22792 * - True indicates that the media has ended. 22793 * - False indicates that the media has not ended. 22794 * 22795 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} 22796 */ 22797 'ended', 22798 /** 22799 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
22800 * the rate at which the media is currently playing back. Examples: 22801 * - if playbackRate is set to 2, media will play twice as fast. 22802 * - if playbackRate is set to 0.5, media will play half as fast. 22803 * 22804 * @method Html5#playbackRate 22805 * @return {number} 22806 * The value of `playbackRate` from the media element. A number indicating 22807 * the current playback speed of the media, where 1 is normal speed. 22808 * 22809 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22810 */ 22811 'playbackRate', 22812 /** 22813 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates 22814 * the rate at which the media is currently playing back. This value will not indicate the current 22815 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. 22816 * 22817 * Examples: 22818 * - if defaultPlaybackRate is set to 2, media will play twice as fast. 22819 * - if defaultPlaybackRate is set to 0.5, media will play half as fast. 22820 * 22821 * @method Html5.prototype.defaultPlaybackRate 22822 * @return {number} 22823 * The value of `defaultPlaybackRate` from the media element. A number indicating 22824 * the current playback speed of the media, where 1 is normal speed. 22825 * 22826 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22827 */ 22828 'defaultPlaybackRate', 22829 /** 22830 * Get the value of `played` from the media element. `played` returns a `TimeRange` 22831 * object representing points in the media timeline that have been played. 22832 * 22833 * @method Html5#played 22834 * @return {TimeRange} 22835 * The value of `played` from the media element. A `TimeRange` object indicating 22836 * the ranges of time that have been played. 22837 * 22838 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} 22839 */ 22840 'played', 22841 /** 22842 * Get the value of `networkState` from the media element. `networkState` indicates 22843 * the current network state. It returns an enumeration from the following list: 22844 * - 0: NETWORK_EMPTY 22845 * - 1: NETWORK_IDLE 22846 * - 2: NETWORK_LOADING 22847 * - 3: NETWORK_NO_SOURCE 22848 * 22849 * @method Html5#networkState 22850 * @return {number} 22851 * The value of `networkState` from the media element. This will be a number 22852 * from the list in the description. 22853 * 22854 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} 22855 */ 22856 'networkState', 22857 /** 22858 * Get the value of `readyState` from the media element. `readyState` indicates 22859 * the current state of the media element. It returns an enumeration from the 22860 * following list: 22861 * - 0: HAVE_NOTHING 22862 * - 1: HAVE_METADATA 22863 * - 2: HAVE_CURRENT_DATA 22864 * - 3: HAVE_FUTURE_DATA 22865 * - 4: HAVE_ENOUGH_DATA 22866 * 22867 * @method Html5#readyState 22868 * @return {number} 22869 * The value of `readyState` from the media element. This will be a number 22870 * from the list in the description. 22871 * 22872 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} 22873 */ 22874 'readyState', 22875 /** 22876 * Get the value of `videoWidth` from the video element. `videoWidth` indicates 22877 * the current width of the video in css pixels. 22878 * 22879 * @method Html5#videoWidth 22880 * @return {number} 22881 * The value of `videoWidth` from the video element. This will be a number 22882 * in css pixels. 22883 * 22884 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 22885 */ 22886 'videoWidth', 22887 /** 22888 * Get the value of `videoHeight` from the video element. `videoHeight` indicates 22889 * the current height of the video in css pixels. 22890 * 22891 * @method Html5#videoHeight 22892 * @return {number} 22893 * The value of `videoHeight` from the video element. This will be a number 22894 * in css pixels. 22895 * 22896 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 22897 */ 22898 'videoHeight', 22899 /** 22900 * Get the value of `crossOrigin` from the media element. `crossOrigin` indicates 22901 * to the browser that should sent the cookies along with the requests for the 22902 * different assets/playlists 22903 * 22904 * @method Html5#crossOrigin 22905 * @return {string} 22906 * - anonymous indicates that the media should not sent cookies. 22907 * - use-credentials indicates that the media should sent cookies along the requests. 22908 * 22909 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin} 22910 */ 22911 'crossOrigin'].forEach(function (prop) { 22912 Html5.prototype[prop] = function () { 22913 return this.el_[prop]; 22914 }; 22915 }); // Wrap native properties with a setter in this format: 22916 // set + toTitleCase(name) 22917 // The list is as follows: 22918 // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate, setCrossOrigin 22919 22920 [ 22921 /** 22922 * Set the value of `volume` on the media element. `volume` indicates the current 22923 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and 22924 * so on. 22925 * 22926 * @method Html5#setVolume 22927 * @param {number} percentAsDecimal 22928 * The volume percent as a decimal. Valid range is from 0-1. 22929 * 22930 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 22931 */ 22932 'volume', 22933 /** 22934 * Set the value of `src` on the media element. `src` indicates the current 22935 * {@link Tech~SourceObject} for the media. 22936 * 22937 * @method Html5#setSrc 22938 * @param {Tech~SourceObject} src 22939 * The source object to set as the current source. 22940 * 22941 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} 22942 */ 22943 'src', 22944 /** 22945 * Set the value of `poster` on the media element. `poster` is the url to 22946 * an image file that can/will be shown when no media data is available. 22947 * 22948 * @method Html5#setPoster 22949 * @param {string} poster 22950 * The url to an image that should be used as the `poster` for the media 22951 * element. 22952 * 22953 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} 22954 */ 22955 'poster', 22956 /** 22957 * Set the value of `preload` on the media element. `preload` indicates 22958 * what should download before the media is interacted with. It can have the following 22959 * values: 22960 * - none: nothing should be downloaded 22961 * - metadata: poster and the first few frames of the media may be downloaded to get 22962 * media dimensions and other metadata 22963 * - auto: allow the media and metadata for the media to be downloaded before 22964 * interaction 22965 * 22966 * @method Html5#setPreload 22967 * @param {string} preload 22968 * The value of `preload` to set on the media element. Must be 'none', 'metadata', 22969 * or 'auto'. 22970 * 22971 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 22972 */ 22973 'preload', 22974 /** 22975 * Set the value of `playbackRate` on the media element. `playbackRate` indicates 22976 * the rate at which the media should play back. Examples: 22977 * - if playbackRate is set to 2, media will play twice as fast. 22978 * - if playbackRate is set to 0.5, media will play half as fast. 22979 * 22980 * @method Html5#setPlaybackRate 22981 * @return {number} 22982 * The value of `playbackRate` from the media element. A number indicating 22983 * the current playback speed of the media, where 1 is normal speed. 22984 * 22985 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22986 */ 22987 'playbackRate', 22988 /** 22989 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates 22990 * the rate at which the media should play back upon initial startup. Changing this value 22991 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. 22992 * 22993 * Example Values: 22994 * - if playbackRate is set to 2, media will play twice as fast. 22995 * - if playbackRate is set to 0.5, media will play half as fast. 22996 * 22997 * @method Html5.prototype.setDefaultPlaybackRate 22998 * @return {number} 22999 * The value of `defaultPlaybackRate` from the media element. A number indicating 23000 * the current playback speed of the media, where 1 is normal speed. 23001 * 23002 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} 23003 */ 23004 'defaultPlaybackRate', 23005 /** 23006 * Set the value of `crossOrigin` from the media element. `crossOrigin` indicates 23007 * to the browser that should sent the cookies along with the requests for the 23008 * different assets/playlists 23009 * 23010 * @method Html5#setCrossOrigin 23011 * @param {string} crossOrigin 23012 * - anonymous indicates that the media should not sent cookies. 23013 * - use-credentials indicates that the media should sent cookies along the requests. 23014 * 23015 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-media-crossorigin} 23016 */ 23017 'crossOrigin'].forEach(function (prop) { 23018 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 23019 this.el_[prop] = v; 23020 }; 23021 }); // wrap native functions with a function 23022 // The list is as follows: 23023 // pause, load, play 23024 23025 [ 23026 /** 23027 * A wrapper around the media elements `pause` function. This will call the `HTML5` 23028 * media elements `pause` function. 23029 * 23030 * @method Html5#pause 23031 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} 23032 */ 23033 'pause', 23034 /** 23035 * A wrapper around the media elements `load` function. This will call the `HTML5`s 23036 * media element `load` function. 23037 * 23038 * @method Html5#load 23039 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} 23040 */ 23041 'load', 23042 /** 23043 * A wrapper around the media elements `play` function. This will call the `HTML5`s 23044 * media element `play` function. 23045 * 23046 * @method Html5#play 23047 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} 23048 */ 23049 'play'].forEach(function (prop) { 23050 Html5.prototype[prop] = function () { 23051 return this.el_[prop](); 23052 }; 23053 }); 23054 Tech.withSourceHandlers(Html5); 23055 /** 23056 * Native source handler for Html5, simply passes the source to the media element. 23057 * 23058 * @property {Tech~SourceObject} source 23059 * The source object 23060 * 23061 * @property {Html5} tech 23062 * The instance of the HTML5 tech. 23063 */ 23064 23065 Html5.nativeSourceHandler = {}; 23066 /** 23067 * Check if the media element can play the given mime type. 23068 * 23069 * @param {string} type 23070 * The mimetype to check 23071 * 23072 * @return {string} 23073 * 'probably', 'maybe', or '' (empty string) 23074 */ 23075 23076 Html5.nativeSourceHandler.canPlayType = function (type) { 23077 // IE without MediaPlayer throws an error (#519)
23078 try { 23079 return Html5.TEST_VID.canPlayType(type); 23080 } catch (e) { 23081 return ''; 23082 } 23083 }; 23084 /** 23085 * Check if the media element can handle a source natively. 23086 * 23087 * @param {Tech~SourceObject} source 23088 * The source object 23089 * 23090 * @param {Object} [options] 23091 * Options to be passed to the tech. 23092 * 23093 * @return {string} 23094 * 'probably', 'maybe', or '' (empty string). 23095 */ 23096 23097 23098 Html5.nativeSourceHandler.canHandleSource = function (source, options) { 23099 // If a type was provided we should rely on that 23100 if (source.type) { 23101 return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]' 23102 } else if (source.src) { 23103 var ext = getFileExtension(source.src); 23104 return Html5.nativeSourceHandler.canPlayType("video/" + ext); 23105 } 23106 23107 return ''; 23108 }; 23109 /** 23110 * Pass the source to the native media element. 23111 * 23112 * @param {Tech~SourceObject} source 23113 * The source object 23114 * 23115 * @param {Html5} tech 23116 * The instance of the Html5 tech 23117 * 23118 * @param {Object} [options] 23119 * The options to pass to the source 23120 */ 23121 23122 23123 Html5.nativeSourceHandler.handleSource = function (source, tech, options) { 23124 tech.setSrc(source.src); 23125 }; 23126 /** 23127 * A noop for the native dispose function, as cleanup is not needed. 23128 */ 23129 23130 23131 Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler 23132 23133 23134 Html5.registerSourceHandler(Html5.nativeSourceHandler); 23135 Tech.registerTech('Html5', Html5); 23136 23137 // on the player when they happen 23138 23139 var TECH_EVENTS_RETRIGGER = [ 23140 /** 23141 * Fired while the user agent is downloading media data. 23142 * 23143 * @event Player#progress 23144 * @type {EventTarget~Event} 23145 */ 23146 23147 /** 23148 * Retrigger the `progress` event that was triggered by the {@link Tech}. 23149 * 23150 * @private 23151 * @method Player#handleTechProgress_ 23152 * @fires Player#progress 23153 * @listens Tech#progress 23154 */ 23155 'progress', 23156 /** 23157 * Fires when the loading of an audio/video is aborted. 23158 * 23159 * @event Player#abort 23160 * @type {EventTarget~Event} 23161 */ 23162 23163 /** 23164 * Retrigger the `abort` event that was triggered by the {@link Tech}. 23165 * 23166 * @private 23167 * @method Player#handleTechAbort_ 23168 * @fires Player#abort 23169 * @listens Tech#abort 23170 */ 23171 'abort', 23172 /** 23173 * Fires when the browser is intentionally not getting media data. 23174 * 23175 * @event Player#suspend 23176 * @type {EventTarget~Event} 23177 */ 23178 23179 /** 23180 * Retrigger the `suspend` event that was triggered by the {@link Tech}. 23181 * 23182 * @private 23183 * @method Player#handleTechSuspend_ 23184 * @fires Player#suspend 23185 * @listens Tech#suspend 23186 */ 23187 'suspend', 23188 /** 23189 * Fires when the current playlist is empty. 23190 * 23191 * @event Player#emptied 23192 * @type {EventTarget~Event} 23193 */ 23194 23195 /** 23196 * Retrigger the `emptied` event that was triggered by the {@link Tech}. 23197 * 23198 * @private 23199 * @method Player#handleTechEmptied_ 23200 * @fires Player#emptied 23201 * @listens Tech#emptied 23202 */ 23203 'emptied', 23204 /** 23205 * Fires when the browser is trying to get media data, but data is not available. 23206 * 23207 * @event Player#stalled 23208 * @type {EventTarget~Event} 23209 */ 23210 23211 /** 23212 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 23213 * 23214 * @private 23215 * @method Player#handleTechStalled_ 23216 * @fires Player#stalled 23217 * @listens Tech#stalled 23218 */ 23219 'stalled', 23220 /** 23221 * Fires when the browser has loaded meta data for the audio/video. 23222 * 23223 * @event Player#loadedmetadata 23224 * @type {EventTarget~Event} 23225 */ 23226 23227 /** 23228 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 23229 * 23230 * @private 23231 * @method Player#handleTechLoadedmetadata_ 23232 * @fires Player#loadedmetadata 23233 * @listens Tech#loadedmetadata 23234 */ 23235 'loadedmetadata', 23236 /** 23237 * Fires when the browser has loaded the current frame of the audio/video. 23238 * 23239 * @event Player#loadeddata 23240 * @type {event} 23241 */ 23242 23243 /** 23244 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. 23245 * 23246 * @private 23247 * @method Player#handleTechLoaddeddata_ 23248 * @fires Player#loadeddata 23249 * @listens Tech#loadeddata 23250 */ 23251 'loadeddata', 23252 /** 23253 * Fires when the current playback position has changed. 23254 * 23255 * @event Player#timeupdate 23256 * @type {event} 23257 */ 23258 23259 /** 23260 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. 23261 * 23262 * @private 23263 * @method Player#handleTechTimeUpdate_ 23264 * @fires Player#timeupdate 23265 * @listens Tech#timeupdate 23266 */ 23267 'timeupdate', 23268 /** 23269 * Fires when the video's intrinsic dimensions change 23270 * 23271 * @event Player#resize 23272 * @type {event} 23273 */ 23274 23275 /** 23276 * Retrigger the `resize` event that was triggered by the {@link Tech}. 23277 * 23278 * @private 23279 * @method Player#handleTechResize_ 23280 * @fires Player#resize 23281 * @listens Tech#resize 23282 */ 23283 'resize', 23284 /** 23285 * Fires when the volume has been changed 23286 * 23287 * @event Player#volumechange 23288 * @type {event} 23289 */ 23290 23291 /** 23292 * Retrigger the `volumechange` event that was triggered by the {@link Tech}. 23293 * 23294 * @private 23295 * @method Player#handleTechVolumechange_ 23296 * @fires Player#volumechange 23297 * @listens Tech#volumechange 23298 */ 23299 'volumechange', 23300 /** 23301 * Fires when the text track has been changed 23302 * 23303 * @event Player#texttrackchange 23304 * @type {event} 23305 */ 23306 23307 /** 23308 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. 23309 * 23310 * @private 23311 * @method Player#handleTechTexttrackchange_ 23312 * @fires Player#texttrackchange 23313 * @listens Tech#texttrackchange 23314 */ 23315 'texttrackchange']; // events to queue when playback rate is zero 23316 // this is a hash for the sole purpose of mapping non-camel-cased event names 23317 // to camel-cased function names 23318 23319 var TECH_EVENTS_QUEUE = { 23320 canplay: 'CanPlay', 23321 canplaythrough: 'CanPlayThrough', 23322 playing: 'Playing', 23323 seeked: 'Seeked' 23324 }; 23325 var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge']; 23326 var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny 23327 // grep: vjs-layout-x-small 23328 // grep: vjs-layout-small 23329 // grep: vjs-layout-medium 23330 // grep: vjs-layout-large 23331 // grep: vjs-layout-x-large 23332 // grep: vjs-layout-huge 23333
23334 BREAKPOINT_ORDER.forEach(function (k) { 23335 var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k; 23336 BREAKPOINT_CLASSES[k] = "vjs-layout-" + v; 23337 }); 23338 var DEFAULT_BREAKPOINTS = { 23339 tiny: 210, 23340 xsmall: 320, 23341 small: 425, 23342 medium: 768, 23343 large: 1440, 23344 xlarge: 2560, 23345 huge: Infinity 23346 }; 23347 /** 23348 * An instance of the `Player` class is created when any of the Video.js setup methods 23349 * are used to initialize a video. 23350 * 23351 * After an instance has been created it can be accessed globally in two ways: 23352 * 1. By calling `videojs('example_video_1');` 23353 * 2. By using it directly via `videojs.players.example_video_1;` 23354 * 23355 * @extends Component 23356 */ 23357 23358 var Player = /*#__PURE__*/function (_Component) { 23359 inheritsLoose(Player, _Component); 23360 23361 /** 23362 * Create an instance of this class. 23363 * 23364 * @param {Element} tag 23365 * The original video DOM element used for configuring options. 23366 * 23367 * @param {Object} [options] 23368 * Object of option names and values. 23369 * 23370 * @param {Component~ReadyCallback} [ready] 23371 * Ready callback function. 23372 */ 23373 function Player(tag, options, ready) { 23374 var _this; 23375 23376 // Make sure tag ID exists 23377 tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options 23378 // The options argument overrides options set in the video tag 23379 // which overrides globally set options. 23380 // This latter part coincides with the load order 23381 // (tag must exist before Player) 23382 23383 options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up 23384 // player properties first, and can't use `this` before `super()` 23385 23386 options.initChildren = false; // Same with creating the element 23387 23388 options.createEl = false; // don't auto mixin the evented mixin 23389 23390 options.evented = false; // we don't want the player to report touch activity on itself 23391 // see enableTouchActivity in Component 23392 23393 options.reportTouchActivity = false; // If language is not set, get the closest lang attribute 23394 23395 if (!options.language) { 23396 if (typeof tag.closest === 'function') { 23397 var closest = tag.closest('[lang]'); 23398 23399 if (closest && closest.getAttribute) { 23400 options.language = closest.getAttribute('lang'); 23401 } 23402 } else { 23403 var element = tag; 23404 23405 while (element && element.nodeType === 1) { 23406 if (getAttributes(element).hasOwnProperty('lang')) { 23407 options.language = element.getAttribute('lang'); 23408 break; 23409 } 23410 23411 element = element.parentNode; 23412 } 23413 } 23414 } // Run base component initializing with new options 23415 23416 23417 _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners. 23418 23419 _this.boundDocumentFullscreenChange_ = bind(assertThisInitialized(_this), _this.documentFullscreenChange_); 23420 _this.boundFullWindowOnEscKey_ = bind(assertThisInitialized(_this), _this.fullWindowOnEscKey); // default isFullscreen_ to false 23421 23422 _this.isFullscreen_ = false; // create logger 23423 23424 _this.log = createLogger$1(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests 23425 23426 _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster 23427 23428 _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero 23429 // and a seek is happening 23430 23431 _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously 23432 23433 _this.isReady_ = false; // Init state hasStarted_ 23434 23435 _this.hasStarted_ = false; // Init state userActive_ 23436 23437 _this.userActive_ = false; // if the global option object was accidentally blown away by 23438 // someone, bail early with an informative error 23439 23440 if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { 23441 throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); 23442 } // Store the original tag used to set options 23443 23444
23445 _this.tag = tag; // Store the tag attributes used to restore html5 element 23446 23447 _this.tagAttributes = tag && getAttributes(tag); // Update current language 23448 23449 _this.language(_this.options_.language); // Update Supported Languages 23450 23451 23452 if (options.languages) { 23453 // Normalise player option languages to lowercase 23454 var languagesToLower = {}; 23455 Object.getOwnPropertyNames(options.languages).forEach(function (name) { 23456 languagesToLower[name.toLowerCase()] = options.languages[name]; 23457 }); 23458 _this.languages_ = languagesToLower; 23459 } else { 23460 _this.languages_ = Player.prototype.options_.languages; 23461 } 23462 23463 _this.resetCache_(); // Set poster 23464 23465 23466 _this.poster_ = options.poster || ''; // Set controls 23467 23468 _this.controls_ = !!options.controls; // Original tag settings stored in options 23469 // now remove immediately so native controls don't flash. 23470 // May be turned back on by HTML5 tech if nativeControlsForTouch is true 23471 23472 tag.controls = false; 23473 tag.removeAttribute('controls'); 23474 _this.changingSrc_ = false; 23475 _this.playCallbacks_ = []; 23476 _this.playTerminatedQueue_ = []; // the attribute overrides the option 23477 23478 if (tag.hasAttribute('autoplay')) { 23479 _this.autoplay(true); 23480 } else { 23481 // otherwise use the setter to validate and 23482 // set the correct value. 23483 _this.autoplay(_this.options_.autoplay); 23484 } // check plugins 23485 23486 23487 if (options.plugins) { 23488 Object.keys(options.plugins).forEach(function (name) { 23489 if (typeof _this[name] !== 'function') { 23490 throw new Error("plugin \"" + name + "\" does not exist"); 23491 } 23492 }); 23493 } 23494 /* 23495 * Store the internal state of scrubbing 23496 * 23497 * @private 23498 * @return {Boolean} True if the user is scrubbing 23499 */ 23500 23501 23502 _this.scrubbing_ = false; 23503 _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus. 23504 23505 evented(assertThisInitialized(_this), { 23506 eventBusKey: 'el_' 23507 }); // listen to document and player fullscreenchange handlers so we receive those events 23508 // before a user can receive them so we can update isFullscreen appropriately. 23509 // make sure that we listen to fullscreenchange events before everything else to make sure that 23510 // our isFullscreen method is updated properly for internal components as well as external. 23511 23512 if (_this.fsApi_.requestFullscreen) { 23513 on(document, _this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_); 23514 23515 _this.on(_this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_); 23516 } 23517 23518 if (_this.fluid_) { 23519 _this.on('playerreset', _this.updateStyleEl_); 23520 } // We also want to pass the original player options to each component and plugin 23521 // as well so they don't need to reach back into the player for options later. 23522 // We also need to do another copy of this.options_ so we don't end up with 23523 // an infinite loop. 23524 23525 23526 var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins 23527 23528 if (options.plugins) { 23529 Object.keys(options.plugins).forEach(function (name) { 23530 _this[name](options.plugins[name]); 23531 }); 23532 } 23533 23534 _this.options_.playerOptions = playerOptionsCopy; 23535 _this.middleware_ = []; 23536 23537 _this.initChildren(); // Set isAudio based on whether or not an audio tag was used 23538 23539 23540 _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially 23541 // set because the element doesn't exist yet. 23542 23543 23544 if (_this.controls()) { 23545 _this.addClass('vjs-controls-enabled'); 23546 } else { 23547 _this.addClass('vjs-controls-disabled'); 23548 } // Set ARIA label and region role depending on player type 23549 23550 23551 _this.el_.setAttribute('role', 'region'); 23552 23553 if (_this.isAudio()) { 23554 _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); 23555 } else { 23556 _this.el_.setAttribute('aria-label', _this.localize('Video Player')); 23557 } 23558 23559 if (_this.isAudio()) { 23560 _this.addClass('vjs-audio'); 23561 } 23562 23563 if (_this.flexNotSupported_()) { 23564 _this.addClass('vjs-no-flex'); 23565 } // TODO: Make this smarter. Toggle user state between touching/mousing 23566 // using events, since devices can have both touch and mouse events. 23567 // TODO: Make this check be performed again when the window switches between monitors 23568 // (See https://github.com/videojs/video.js/issues/5683) 23569 23570 23571 if (TOUCH_ENABLED) { 23572 _this.addClass('vjs-touch-enabled'); 23573 } // iOS Safari has broken hover handling 23574 23575 23576 if (!IS_IOS) { 23577 _this.addClass('vjs-workinghover'); 23578 } // Make player easily findable by ID 23579 23580 23581 Player.players[_this.id_] = assertThisInitialized(_this); // Add a major version class to aid css in plugins 23582 23583 var majorVersion = version.split('.')[0]; 23584 23585 _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components 23586 // like the control bar show themselves if needed 23587 23588
vendor: 1,787 bytes, lines 23589-23651
23589 _this.userActive(true); 23590 23591 _this.reportUserActivity(); 23592 23593 _this.one('play', _this.listenForUserActivity_); 23594 23595 _this.on('stageclick', _this.handleStageClick_); 23596 23597 _this.on('keydown', _this.handleKeyDown); 23598 23599 _this.breakpoints(_this.options_.breakpoints); 23600 23601 _this.responsive(_this.options_.responsive); 23602 23603 return _this; 23604 } 23605 /** 23606 * Destroys the video player and does any necessary cleanup. 23607 * 23608 * This is especially helpful if you are dynamically adding and removing videos 23609 * to/from the DOM. 23610 * 23611 * @fires Player#dispose 23612 */ 23613 23614 23615 var _proto = Player.prototype; 23616 23617 _proto.dispose = function dispose() { 23618 var _this2 = this; 23619 23620 /** 23621 * Called when the player is being disposed of. 23622 * 23623 * @event Player#dispose 23624 * @type {EventTarget~Event} 23625 */ 23626 this.trigger('dispose'); // prevent dispose from being called twice 23627 23628 this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is 23629 23630 off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_); 23631 off(document, 'keydown', this.boundFullWindowOnEscKey_); 23632 23633 if (this.styleEl_ && this.styleEl_.parentNode) { 23634 this.styleEl_.parentNode.removeChild(this.styleEl_); 23635 this.styleEl_ = null; 23636 } // Kill reference to this player 23637 23638 23639 Player.players[this.id_] = null; 23640 23641 if (this.tag && this.tag.player) { 23642 this.tag.player = null; 23643 } 23644 23645 if (this.el_ && this.el_.player) { 23646 this.el_.player = null; 23647 } 23648 23649 if (this.tech_) { 23650 this.tech_.dispose(); 23651 this.isPosterFromTech_ = false;
23652 this.poster_ = ''; 23653 } 23654 23655 if (this.playerElIngest_) { 23656 this.playerElIngest_ = null; 23657 } 23658 23659 if (this.tag) { 23660 this.tag = null; 23661 } 23662 23663 clearCacheForPlayer(this); // remove all event handlers for track lists 23664 // all tracks and track listeners are removed on 23665 // tech dispose 23666 23667 ALL.names.forEach(function (name) { 23668 var props = ALL[name]; 23669 23670 var list = _this2[props.getterName](); // if it is not a native list 23671 // we have to manually remove event listeners 23672 23673 23674 if (list && list.off) { 23675 list.off(); 23676 } 23677 }); // the actual .el_ is removed here 23678 23679 _Component.prototype.dispose.call(this); 23680 } 23681 /** 23682 * Create the `Player`'s DOM element. 23683 * 23684 * @return {Element} 23685 * The DOM element that gets created. 23686 */ 23687 ; 23688 23689 _proto.createEl = function createEl() { 23690 var tag = this.tag; 23691 var el; 23692 var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); 23693 var divEmbed = this.tag.tagName.toLowerCase() === 'video-js'; 23694 23695 if (playerElIngest) { 23696 el = this.el_ = tag.parentNode; 23697 } else if (!divEmbed) { 23698 el = this.el_ = _Component.prototype.createEl.call(this, 'div'); 23699 } // Copy over all the attributes from the tag, including ID and class 23700 // ID will now reference player box, not the video tag 23701 23702 23703 var attrs = getAttributes(tag); 23704 23705 if (divEmbed) { 23706 el = this.el_ = tag; 23707 tag = this.tag = document.createElement('video'); 23708 23709 while (el.children.length) { 23710 tag.appendChild(el.firstChild); 23711 } 23712 23713 if (!hasClass(el, 'video-js')) { 23714 addClass(el, 'video-js'); 23715 } 23716 23717 el.appendChild(tag); 23718 playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element 23719 // to our new `video` element. This will move things like 23720 // `src` or `controls` that were set via js before the player 23721 // was initialized. 23722 23723 Object.keys(el).forEach(function (k) { 23724 try { 23725 tag[k] = el[k]; 23726 } catch (e) {// we got a a property like outerHTML which we can't actually copy, ignore it 23727 } 23728 }); 23729 } // set tabindex to -1 to remove the video element from the focus order 23730 23731 23732 tag.setAttribute('tabindex', '-1'); 23733 attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and 23734 // for the same issue with Chrome (on Windows) with JAWS. 23735 // See https://github.com/FreedomScientific/VFO-standards-support/issues/78 23736 // Note that we can't detect if JAWS is being used, but this ARIA attribute 23737 // doesn't change behavior of IE11 or Chrome if JAWS is not being used 23738 23739 if (IE_VERSION || IS_CHROME && IS_WINDOWS) { 23740 tag.setAttribute('role', 'application'); 23741 attrs.role = 'application'; 23742 } // Remove width/height attrs from tag so CSS can make it 100% width/height 23743 23744 23745 tag.removeAttribute('width'); 23746 tag.removeAttribute('height'); 23747 23748 if ('width' in attrs) { 23749 delete attrs.width; 23750 } 23751 23752 if ('height' in attrs) { 23753 delete attrs.height; 23754 } 23755 23756 Object.getOwnPropertyNames(attrs).forEach(function (attr) { 23757 // don't copy over the class attribute to the player element when we're in a div embed 23758 // the class is already set up properly in the divEmbed case 23759 // and we want to make sure that the `video-js` class doesn't get lost 23760 if (!(divEmbed && attr === 'class')) { 23761 el.setAttribute(attr, attrs[attr]); 23762 } 23763 23764 if (divEmbed) { 23765 tag.setAttribute(attr, attrs[attr]); 23766 } 23767 }); // Update tag id/class for use as HTML5 playback tech 23768 // Might think we should do this after embedding in container so .vjs-tech class 23769 // doesn't flash 100% width/height, but class only applies with .video-js parent 23770 23771 tag.playerId = tag.id; 23772 tag.id += '_html5_api'; 23773 tag.className = 'vjs-tech'; // Make player findable on elements 23774 23775 tag.player = el.player = this; // Default state of video is paused 23776 23777 this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height 23778 // of the player in a way that's still overrideable by CSS, just like the 23779 // video element 23780 23781 if (window$3.VIDEOJS_NO_DYNAMIC_STYLE !== true) { 23782 this.styleEl_ = createStyleElement('vjs-styles-dimensions'); 23783 var defaultsStyleEl = $('.vjs-styles-defaults'); 23784 var head = $('head'); 23785 head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); 23786 } 23787 23788 this.fill_ = false;
23789 this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el 23790 23791 this.width(this.options_.width); 23792 this.height(this.options_.height); 23793 this.fill(this.options_.fill); 23794 this.fluid(this.options_.fluid); 23795 this.aspectRatio(this.options_.aspectRatio); // support both crossOrigin and crossorigin to reduce confusion and issues around the name 23796 23797 this.crossOrigin(this.options_.crossOrigin || this.options_.crossorigin); // Hide any links within the video/audio tag, 23798 // because IE doesn't hide them completely from screen readers. 23799 23800 var links = tag.getElementsByTagName('a'); 23801 23802 for (var i = 0; i < links.length; i++) { 23803 var linkEl = links.item(i); 23804 addClass(linkEl, 'vjs-hidden'); 23805 linkEl.setAttribute('hidden', 'hidden'); 23806 } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so 23807 // keep track of the original for later so we can know if the source originally failed 23808 23809 23810 tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container 23811 23812 if (tag.parentNode && !playerElIngest) { 23813 tag.parentNode.insertBefore(el, tag); 23814 } // insert the tag as the first child of the player element 23815 // then manually add it to the children array so that this.addChild 23816 // will work properly for other components 23817 // 23818 // Breaks iPhone, fixed in HTML5 setup. 23819 23820 23821 prependTo(tag, el); 23822 this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player 23823 // if it's been set to something different to the doc 23824 23825 this.el_.setAttribute('lang', this.language_); 23826 this.el_ = el; 23827 return el; 23828 } 23829 /** 23830 * Get or set the `Player`'s crossOrigin option. For the HTML5 player, this 23831 * sets the `crossOrigin` property on the `<video>` tag to control the CORS 23832 * behavior. 23833 * 23834 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin} 23835 * 23836 * @param {string} [value] 23837 * The value to set the `Player`'s crossOrigin to. If an argument is 23838 * given, must be one of `anonymous` or `use-credentials`. 23839 * 23840 * @return {string|undefined} 23841 * - The current crossOrigin value of the `Player` when getting. 23842 * - undefined when setting 23843 */ 23844 ; 23845 23846 _proto.crossOrigin = function crossOrigin(value) { 23847 if (!value) { 23848 return this.techGet_('crossOrigin'); 23849 } 23850 23851 if (value !== 'anonymous' && value !== 'use-credentials') { 23852 log.warn("crossOrigin must be \"anonymous\" or \"use-credentials\", given \"" + value + "\""); 23853 return; 23854 } 23855 23856 this.techCall_('setCrossOrigin', value); 23857 return; 23858 } 23859 /** 23860 * A getter/setter for the `Player`'s width. Returns the player's configured value. 23861 * To get the current width use `currentWidth()`. 23862 * 23863 * @param {number} [value] 23864 * The value to set the `Player`'s width to. 23865 * 23866 * @return {number} 23867 * The current width of the `Player` when getting. 23868 */ 23869 ; 23870 23871 _proto.width = function width(value) { 23872 return this.dimension('width', value); 23873 } 23874 /** 23875 * A getter/setter for the `Player`'s height. Returns the player's configured value. 23876 * To get the current height use `currentheight()`. 23877 * 23878 * @param {number} [value] 23879 * The value to set the `Player`'s heigth to. 23880 * 23881 * @return {number} 23882 * The current height of the `Player` when getting. 23883 */ 23884 ; 23885 23886 _proto.height = function height(value) { 23887 return this.dimension('height', value); 23888 } 23889 /** 23890 * A getter/setter for the `Player`'s width & height. 23891 * 23892 * @param {string} dimension 23893 * This string can be: 23894 * - 'width' 23895 * - 'height' 23896 * 23897 * @param {number} [value] 23898 * Value for dimension specified in the first argument. 23899 * 23900 * @return {number} 23901 * The dimension arguments value when getting (width/height). 23902 */ 23903 ; 23904 23905 _proto.dimension = function dimension(_dimension, value) { 23906 var privDimension = _dimension + '_'; 23907 23908 if (value === undefined) { 23909 return this[privDimension] || 0; 23910 } 23911 23912 if (value === '' || value === 'auto') { 23913 // If an empty string is given, reset the dimension to be automatic 23914 this[privDimension] = undefined; 23915 this.updateStyleEl_(); 23916 return; 23917 } 23918 23919 var parsedVal = parseFloat(value); 23920 23921 if (isNaN(parsedVal)) { 23922 log.error("Improper value \"" + value + "\" supplied for for " + _dimension); 23923 return; 23924 } 23925 23926 this[privDimension] = parsedVal; 23927 this.updateStyleEl_(); 23928 } 23929 /** 23930 * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. 23931 * 23932 * Turning this on will turn off fill mode. 23933 * 23934 * @param {boolean} [bool] 23935 * - A value of true adds the class. 23936 * - A value of false removes the class. 23937 * - No value will be a getter. 23938 * 23939 * @return {boolean|undefined} 23940 * - The value of fluid when getting. 23941 * - `undefined` when setting. 23942 */ 23943 ; 23944 23945 _proto.fluid = function fluid(bool) { 23946 if (bool === undefined) { 23947 return !!this.fluid_; 23948 } 23949 23950 this.fluid_ = !!bool; 23951 23952 if (isEvented(this)) { 23953 this.off('playerreset', this.updateStyleEl_); 23954 } 23955 23956 if (bool) { 23957 this.addClass('vjs-fluid'); 23958 this.fill(false); 23959 addEventedCallback(function () { 23960 this.on('playerreset', this.updateStyleEl_); 23961 }); 23962 } else { 23963 this.removeClass('vjs-fluid'); 23964 } 23965 23966 this.updateStyleEl_(); 23967 } 23968 /** 23969 * A getter/setter/toggler for the vjs-fill `className` on the `Player`. 23970 * 23971 * Turning this on will turn off fluid mode. 23972 * 23973 * @param {boolean} [bool] 23974 * - A value of true adds the class. 23975 * - A value of false removes the class. 23976 * - No value will be a getter. 23977 * 23978 * @return {boolean|undefined} 23979 * - The value of fluid when getting. 23980 * - `undefined` when setting. 23981 */ 23982 ; 23983 23984 _proto.fill = function fill(bool) { 23985 if (bool === undefined) { 23986 return !!this.fill_; 23987 } 23988 23989 this.fill_ = !!bool; 23990 23991 if (bool) { 23992 this.addClass('vjs-fill'); 23993 this.fluid(false); 23994 } else { 23995 this.removeClass('vjs-fill'); 23996 } 23997 } 23998 /** 23999 * Get/Set the aspect ratio 24000 * 24001 * @param {string} [ratio] 24002 * Aspect ratio for player 24003 * 24004 * @return {string|undefined} 24005 * returns the current aspect ratio when getting 24006 */ 24007 24008 /** 24009 * A getter/setter for the `Player`'s aspect ratio. 24010 * 24011 * @param {string} [ratio] 24012 * The value to set the `Player`'s aspect ratio to. 24013 * 24014 * @return {string|undefined} 24015 * - The current aspect ratio of the `Player` when getting. 24016 * - undefined when setting 24017 */ 24018 ; 24019 24020 _proto.aspectRatio = function aspectRatio(ratio) { 24021 if (ratio === undefined) { 24022 return this.aspectRatio_; 24023 } // Check for width:height format 24024 24025 24026 if (!/^\d+\:\d+$/.test(ratio)) { 24027 throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); 24028 } 24029 24030 this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode, 24031 // because in fixed mode you could calculate width and height yourself. 24032 24033 this.fluid(true); 24034 this.updateStyleEl_(); 24035 } 24036 /** 24037 * Update styles of the `Player` element (height, width and aspect ratio). 24038 * 24039 * @private 24040 * @listens Tech#loadedmetadata 24041 */ 24042 ; 24043 24044 _proto.updateStyleEl_ = function updateStyleEl_() { 24045 if (window$3.VIDEOJS_NO_DYNAMIC_STYLE === true) { 24046 var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; 24047 24048 var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; 24049 24050 var techEl = this.tech_ && this.tech_.el(); 24051 24052 if (techEl) { 24053 if (_width >= 0) { 24054 techEl.width = _width; 24055 } 24056 24057 if (_height >= 0) {
vendor: 8,735 bytes, lines 24058-24262
24058 techEl.height = _height; 24059 } 24060 } 24061 24062 return; 24063 } 24064 24065 var width; 24066 var height; 24067 var aspectRatio; 24068 var idClass; // The aspect ratio is either used directly or to calculate width and height. 24069 24070 if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { 24071 // Use any aspectRatio that's been specifically set 24072 aspectRatio = this.aspectRatio_; 24073 } else if (this.videoWidth() > 0) { 24074 // Otherwise try to get the aspect ratio from the video metadata 24075 aspectRatio = this.videoWidth() + ':' + this.videoHeight(); 24076 } else { 24077 // Or use a default. The video element's is 2:1, but 16:9 is more common. 24078 aspectRatio = '16:9'; 24079 } // Get the ratio as a decimal we can use to calculate dimensions 24080 24081 24082 var ratioParts = aspectRatio.split(':'); 24083 var ratioMultiplier = ratioParts[1] / ratioParts[0]; 24084 24085 if (this.width_ !== undefined) { 24086 // Use any width that's been specifically set 24087 width = this.width_; 24088 } else if (this.height_ !== undefined) { 24089 // Or calulate the width from the aspect ratio if a height has been set 24090 width = this.height_ / ratioMultiplier; 24091 } else { 24092 // Or use the video's metadata, or use the video el's default of 300 24093 width = this.videoWidth() || 300; 24094 } 24095 24096 if (this.height_ !== undefined) { 24097 // Use any height that's been specifically set 24098 height = this.height_; 24099 } else { 24100 // Otherwise calculate the height from the ratio and the width 24101 height = width * ratioMultiplier; 24102 } // Ensure the CSS class is valid by starting with an alpha character 24103 24104 24105 if (/^[^a-zA-Z]/.test(this.id())) { 24106 idClass = 'dimensions-' + this.id(); 24107 } else { 24108 idClass = this.id() + '-dimensions'; 24109 } // Ensure the right class is still on the player for the style element 24110 24111 24112 this.addClass(idClass); 24113 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 "); 24114 } 24115 /** 24116 * Load/Create an instance of playback {@link Tech} including element 24117 * and API methods. Then append the `Tech` element in `Player` as a child. 24118 * 24119 * @param {string} techName 24120 * name of the playback technology 24121 * 24122 * @param {string} source 24123 * video source 24124 * 24125 * @private 24126 */ 24127 ; 24128 24129 _proto.loadTech_ = function loadTech_(techName, source) { 24130 var _this3 = this; 24131 24132 // Pause and remove current playback technology 24133 if (this.tech_) { 24134 this.unloadTech_(); 24135 } 24136 24137 var titleTechName = toTitleCase(techName); 24138 var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech 24139 24140 if (titleTechName !== 'Html5' && this.tag) { 24141 Tech.getTech('Html5').disposeMediaElement(this.tag); 24142 this.tag.player = null; 24143 this.tag = null; 24144 } 24145 24146 this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously 24147 24148 this.isReady_ = false; // if autoplay is a string we pass false to the tech 24149 // because the player is going to handle autoplay on `loadstart` 24150 24151 var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use. 24152 24153 var techOptions = { 24154 source: source, 24155 autoplay: autoplay, 24156 'nativeControlsForTouch': this.options_.nativeControlsForTouch, 24157 'playerId': this.id(), 24158 'techId': this.id() + "_" + camelTechName + "_api", 24159 'playsinline': this.options_.playsinline, 24160 'preload': this.options_.preload, 24161 'loop': this.options_.loop, 24162 'muted': this.options_.muted, 24163 'poster': this.poster(), 24164 'language': this.language(), 24165 'playerElIngest': this.playerElIngest_ || false, 24166 'vtt.js': this.options_['vtt.js'], 24167 'canOverridePoster': !!this.options_.techCanOverridePoster, 24168 'enableSourceset': this.options_.enableSourceset, 24169 'Promise': this.options_.Promise 24170 }; 24171 ALL.names.forEach(function (name) { 24172 var props = ALL[name]; 24173 techOptions[props.getterName] = _this3[props.privateName]; 24174 }); 24175 assign(techOptions, this.options_[titleTechName]); 24176 assign(techOptions, this.options_[camelTechName]); 24177 assign(techOptions, this.options_[techName.toLowerCase()]); 24178 24179 if (this.tag) { 24180 techOptions.tag = this.tag; 24181 } 24182 24183 if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { 24184 techOptions.startTime = this.cache_.currentTime; 24185 } // Initialize tech instance 24186 24187 24188 var TechClass = Tech.getTech(techName); 24189 24190 if (!TechClass) { 24191 throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'"); 24192 } 24193 24194 this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async 24195 24196 this.tech_.ready(bind(this, this.handleTechReady_), true); 24197 textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player 24198 24199 TECH_EVENTS_RETRIGGER.forEach(function (event) { 24200 _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]); 24201 }); 24202 Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) { 24203 _this3.on(_this3.tech_, event, function (eventObj) { 24204 if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) { 24205 _this3.queuedCallbacks_.push({ 24206 callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3), 24207 event: eventObj 24208 }); 24209 24210 return; 24211 } 24212 24213 _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj); 24214 }); 24215 }); 24216 this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); 24217 this.on(this.tech_, 'sourceset', this.handleTechSourceset_); 24218 this.on(this.tech_, 'waiting', this.handleTechWaiting_); 24219 this.on(this.tech_, 'ended', this.handleTechEnded_); 24220 this.on(this.tech_, 'seeking', this.handleTechSeeking_); 24221 this.on(this.tech_, 'play', this.handleTechPlay_); 24222 this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); 24223 this.on(this.tech_, 'pause', this.handleTechPause_); 24224 this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); 24225 this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); 24226 this.on(this.tech_, 'fullscreenerror', this.handleTechFullscreenError_); 24227 this.on(this.tech_, 'enterpictureinpicture', this.handleTechEnterPictureInPicture_); 24228 this.on(this.tech_, 'leavepictureinpicture', this.handleTechLeavePictureInPicture_); 24229 this.on(this.tech_, 'error', this.handleTechError_); 24230 this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); 24231 this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); 24232 this.on(this.tech_, 'textdata', this.handleTechTextData_); 24233 this.on(this.tech_, 'ratechange', this.handleTechRateChange_); 24234 this.usingNativeControls(this.techGet_('controls')); 24235 24236 if (this.controls() && !this.usingNativeControls()) { 24237 this.addTechControlsListeners_(); 24238 } // Add the tech element in the DOM if it was not already there 24239 // Make sure to not insert the original video element if using Html5 24240 24241 24242 if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { 24243 prependTo(this.tech_.el(), this.el()); 24244 } // Get rid of the original video tag reference after the first tech is loaded 24245 24246 24247 if (this.tag) { 24248 this.tag.player = null; 24249 this.tag = null; 24250 } 24251 } 24252 /** 24253 * Unload and dispose of the current playback {@link Tech}. 24254 * 24255 * @private 24256 */ 24257 ; 24258 24259 _proto.unloadTech_ = function unloadTech_() { 24260 var _this4 = this; 24261 24262 // Save the current text tracks so that we can reuse the same text tracks with the next tech
24263 ALL.names.forEach(function (name) { 24264 var props = ALL[name]; 24265 _this4[props.privateName] = _this4[props.getterName](); 24266 }); 24267 this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_); 24268 this.isReady_ = false; 24269 this.tech_.dispose(); 24270 this.tech_ = false; 24271 24272 if (this.isPosterFromTech_) { 24273 this.poster_ = ''; 24274 this.trigger('posterchange'); 24275 } 24276 24277 this.isPosterFromTech_ = false; 24278 } 24279 /** 24280 * Return a reference to the current {@link Tech}. 24281 * It will print a warning by default about the danger of using the tech directly 24282 * but any argument that is passed in will silence the warning. 24283 * 24284 * @param {*} [safety] 24285 * Anything passed in to silence the warning 24286 * 24287 * @return {Tech} 24288 * The Tech 24289 */ 24290 ; 24291 24292 _proto.tech = function tech(safety) { 24293 if (safety === undefined) { 24294 log.warn('Using the tech directly can be dangerous. I hope you know
24294what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n'); 24295 } 24296 24297 return this.tech_; 24298 } 24299 /** 24300 * Set up click and touch listeners for the playback element 24301 * 24302 * - On desktops: a click on the video itself will toggle playback 24303 * - On mobile devices: a click on the video toggles controls 24304 * which is done by toggling the user state between active and 24305 * inactive 24306 * - A tap can signal that a user has become active or has become inactive 24307 * e.g. a quick tap on an iPhone movie should reveal the controls. Another 24308 * quick tap should hide them again (signaling the user is in an inactive 24309 * viewing state) 24310 * - In addition to this, we still want the user to be considered inactive after 24311 * a few seconds of inactivity. 24312 * 24313 * > Note: the only part of iOS interaction we can't mimic with this setup 24314 * is a touch and hold on the video element counting as activity in order to 24315 * keep the controls showing, but that shouldn't be an issue. A touch and hold 24316 * on any controls will still keep the user active 24317 * 24318 * @private 24319 */ 24320 ; 24321 24322 _proto.addTechControlsListeners_ = function addTechControlsListeners_() { 24323 // Make sure to remove all the previous listeners in case we are called multiple times. 24324 this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do 24325 // trigger mousedown/up. 24326 // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object 24327 // Any touch events are set to block the mousedown event from happening 24328 24329 this.on(this.tech_, 'mouseup', this.handleTechClick_); 24330 this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event 24331 // so we'll check if the controls were already showing before reporting user 24332 // activity 24333 24334 this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); 24335 this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); 24336 this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap 24337 // listener cancels out any reportedUserActivity when setting userActive(false) 24338 24339 this.on(this.tech_, 'tap', this.handleTechTap_); 24340 } 24341 /** 24342 * Remove the listeners used for click and tap controls. This is needed for 24343 * toggling to controls disabled, where a tap/touch should do nothing. 24344 * 24345 * @private 24346 */ 24347 ; 24348 24349 _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() { 24350 // We don't want to just use `this.off()` because there might be other needed 24351 // listeners added by techs that extend this. 24352 this.off(this.tech_, 'tap', this.handleTechTap_); 24353 this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); 24354 this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); 24355 this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); 24356 this.off(this.tech_, 'mouseup', this.handleTechClick_); 24357 this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_); 24358 } 24359 /** 24360 * Player waits for the tech to be ready 24361 * 24362 * @private 24363 */ 24364 ; 24365 24366 _proto.handleTechReady_ = function handleTechReady_() { 24367 this.triggerReady(); // Keep the same volume as before 24368 24369 if (this.cache_.volume) { 24370 this.techCall_('setVolume', this.cache_.volume); 24371 } // Look if the tech found a higher resolution poster while loading 24372 24373 24374 this.handleTechPosterChange_(); // Update the duration if available 24375 24376 this.handleTechDurationChange_(); 24377 } 24378 /** 24379 * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This 24380 * function will also trigger {@link Player#firstplay} if it is the first loadstart 24381 * for a video. 24382 * 24383 * @fires Player#loadstart 24384 * @fires Player#firstplay 24385 * @listens Tech#loadstart 24386 * @private 24387 */ 24388 ; 24389 24390 _proto.handleTechLoadStart_ = function handleTechLoadStart_() { 24391 // TODO: Update to use `emptied` event instead. See #1277. 24392 this.removeClass('vjs-ended'); 24393 this.removeClass('vjs-seeking'); // reset the error state 24394 24395 this.error(null); // Update the duration 24396 24397 this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now. 24398 // The firstplay event relies on both the play and loadstart events 24399 // which can happen in any order for a new source 24400 24401 if (!this.paused()) { 24402 /** 24403 * Fired when the user agent begins looking for media data 24404 * 24405 * @event Player#loadstart 24406 * @type {EventTarget~Event} 24407 */ 24408 this.trigger('loadstart'); 24409 this.trigger('firstplay'); 24410 } else { 24411 // reset the hasStarted state 24412 this.hasStarted(false); 24413 this.trigger('loadstart'); 24414 } // autoplay happens after loadstart for the browser, 24415 // so we mimic that behavior 24416 24417 24418 this.manualAutoplay_(this.autoplay()); 24419 } 24420 /** 24421 * Handle autoplay string values, rather than the typical boolean 24422 * values that should be handled by the tech. Note that this is not 24423 * part of any specification. Valid values and what they do can be 24424 * found on the autoplay getter at Player#autoplay() 24425 */ 24426 ; 24427 24428 _proto.manualAutoplay_ = function manualAutoplay_(type) { 24429 var _this5 = this; 24430 24431 if (!this.tech_ || typeof type !== 'string') { 24432 return; 24433 } 24434 24435 var muted = function muted() { 24436 var previouslyMuted = _this5.muted(); 24437 24438 _this5.muted(true); 24439 24440 var restoreMuted = function restoreMuted() { 24441 _this5.muted(previouslyMuted); 24442 }; // restore muted on play terminatation 24443 24444 24445 _this5.playTerminatedQueue_.push(restoreMuted); 24446 24447 var mutedPromise = _this5.play(); 24448 24449 if (!isPromise(mutedPromise)) { 24450 return; 24451 } 24452 24453 return mutedPromise["catch"](restoreMuted); 24454 }; 24455 24456 var promise; // if muted defaults to true 24457 // the only thing we can do is call play 24458 24459 if (type === 'any' && this.muted() !== true) { 24460 promise = this.play(); 24461
vendor: 14,193 bytes, lines 24462-24890
24462 if (isPromise(promise)) { 24463 promise = promise["catch"](muted); 24464 } 24465 } else if (type === 'muted' && this.muted() !== true) { 24466 promise = muted(); 24467 } else { 24468 promise = this.play(); 24469 } 24470 24471 if (!isPromise(promise)) { 24472 return; 24473 } 24474 24475 return promise.then(function () { 24476 _this5.trigger({ 24477 type: 'autoplay-success', 24478 autoplay: type 24479 }); 24480 })["catch"](function (e) { 24481 _this5.trigger({ 24482 type: 'autoplay-failure', 24483 autoplay: type 24484 }); 24485 }); 24486 } 24487 /** 24488 * Update the internal source caches so that we return the correct source from 24489 * `src()`, `currentSource()`, and `currentSources()`. 24490 * 24491 * > Note: `currentSources` will not be updated if the source that is passed in exists 24492 * in the current `currentSources` cache. 24493 * 24494 * 24495 * @param {Tech~SourceObject} srcObj 24496 * A string or object source to update our caches to. 24497 */ 24498 ; 24499 24500 _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) { 24501 if (srcObj === void 0) { 24502 srcObj = ''; 24503 } 24504 24505 var src = srcObj; 24506 var type = ''; 24507 24508 if (typeof src !== 'string') { 24509 src = srcObj.src; 24510 type = srcObj.type; 24511 } // make sure all the caches are set to default values 24512 // to prevent null checking 24513 24514 24515 this.cache_.source = this.cache_.source || {}; 24516 this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in 24517 24518 if (src && !type) { 24519 type = findMimetype(this, src); 24520 } // update `currentSource` cache always 24521 24522 24523 this.cache_.source = mergeOptions({}, srcObj, { 24524 src: src, 24525 type: type 24526 }); 24527 var matchingSources = this.cache_.sources.filter(function (s) { 24528 return s.src && s.src === src; 24529 }); 24530 var sourceElSources = []; 24531 var sourceEls = this.$$('source'); 24532 var matchingSourceEls = []; 24533 24534 for (var i = 0; i < sourceEls.length; i++) { 24535 var sourceObj = getAttributes(sourceEls[i]); 24536 sourceElSources.push(sourceObj); 24537 24538 if (sourceObj.src && sourceObj.src === src) { 24539 matchingSourceEls.push(sourceObj.src); 24540 } 24541 } // if we have matching source els but not matching sources 24542 // the current source cache is not up to date 24543 24544 24545 if (matchingSourceEls.length && !matchingSources.length) { 24546 this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the 24547 // sources cache to the `currentSource` cache 24548 } else if (!matchingSources.length) { 24549 this.cache_.sources = [this.cache_.source]; 24550 } // update the tech `src` cache 24551 24552 24553 this.cache_.src = src; 24554 } 24555 /** 24556 * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech} 24557 * causing the media element to reload. 24558 * 24559 * It will fire for the initial source and each subsequent source. 24560 * This event is a custom event from Video.js and is triggered by the {@link Tech}. 24561 * 24562 * The event object for this event contains a `src` property that will contain the source 24563 * that was available when the event was triggered. This is generally only necessary if Video.js 24564 * is switching techs while the source was being changed. 24565 * 24566 * It is also fired when `load` is called on the player (or media element) 24567 * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`} 24568 * says that the resource selection algorithm needs to be aborted and restarted. 24569 * In this case, it is very likely that the `src` property will be set to the 24570 * empty string `""` to indicate we do not know what the source will be but 24571 * that it is changing. 24572 * 24573 * *This event is currently still experimental and may change in minor releases.* 24574 * __To use this, pass `enableSourceset` option to the player.__ 24575 * 24576 * @event Player#sourceset 24577 * @type {EventTarget~Event} 24578 * @prop {string} src 24579 * The source url available when the `sourceset` was triggered. 24580 * It will be an empty string if we cannot know what the source is 24581 * but know that the source will change. 24582 */ 24583 24584 /** 24585 * Retrigger the `sourceset` event that was triggered by the {@link Tech}. 24586 * 24587 * @fires Player#sourceset 24588 * @listens Tech#sourceset 24589 * @private 24590 */ 24591 ; 24592 24593 _proto.handleTechSourceset_ = function handleTechSourceset_(event) { 24594 var _this6 = this; 24595 24596 // only update the source cache when the source 24597 // was not updated using the player api 24598 if (!this.changingSrc_) { 24599 var updateSourceCaches = function updateSourceCaches(src) { 24600 return _this6.updateSourceCaches_(src); 24601 }; 24602 24603 var playerSrc = this.currentSource().src; 24604 var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob 24605 24606 if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) { 24607 // if both the tech source and the player source were updated we assume 24608 // something like @videojs/http-streaming did the sourceset and skip updating the source cache. 24609 if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) { 24610 updateSourceCaches = function updateSourceCaches() {}; 24611 } 24612 } // update the source to the intial source right away 24613 // in some cases this will be empty string 24614 24615 24616 updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string 24617 // wait for a `loadstart` to update the cache to `currentSrc`. 24618 // If a sourceset happens before a `loadstart`, we reset the state 24619 24620 if (!event.src) { 24621 this.tech_.any(['sourceset', 'loadstart'], function (e) { 24622 // if a sourceset happens before a `loadstart` there 24623 // is nothing to do as this `handleTechSourceset_` 24624 // will be called again and this will be handled there. 24625 if (e.type === 'sourceset') { 24626 return; 24627 } 24628 24629 var techSrc = _this6.techGet('currentSrc'); 24630 24631 _this6.lastSource_.tech = techSrc; 24632 24633 _this6.updateSourceCaches_(techSrc); 24634 }); 24635 } 24636 } 24637 24638 this.lastSource_ = { 24639 player: this.currentSource().src, 24640 tech: event.src 24641 }; 24642 this.trigger({ 24643 src: event.src, 24644 type: 'sourceset' 24645 }); 24646 } 24647 /** 24648 * Add/remove the vjs-has-started class 24649 * 24650 * @fires Player#firstplay 24651 * 24652 * @param {boolean} request 24653 * - true: adds the class 24654 * - false: remove the class 24655 * 24656 * @return {boolean} 24657 * the boolean value of hasStarted_ 24658 */ 24659 ; 24660 24661 _proto.hasStarted = function hasStarted(request) { 24662 if (request === undefined) { 24663 // act as getter, if we have no request to change 24664 return this.hasStarted_; 24665 } 24666 24667 if (request === this.hasStarted_) { 24668 return; 24669 } 24670 24671 this.hasStarted_ = request; 24672 24673 if (this.hasStarted_) { 24674 this.addClass('vjs-has-started'); 24675 this.trigger('firstplay'); 24676 } else { 24677 this.removeClass('vjs-has-started'); 24678 } 24679 } 24680 /** 24681 * Fired whenever the media begins or resumes playback 24682 * 24683 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} 24684 * @fires Player#play 24685 * @listens Tech#play 24686 * @private 24687 */ 24688 ; 24689 24690 _proto.handleTechPlay_ = function handleTechPlay_() { 24691 this.removeClass('vjs-ended'); 24692 this.removeClass('vjs-paused'); 24693 this.addClass('vjs-playing'); // hide the poster when the user hits play 24694 24695 this.hasStarted(true); 24696 /** 24697 * Triggered whenever an {@link Tech#play} event happens. Indicates that 24698 * playback has started or resumed. 24699 * 24700 * @event Player#play 24701 * @type {EventTarget~Event} 24702 */ 24703 24704 this.trigger('play'); 24705 } 24706 /** 24707 * Retrigger the `ratechange` event that was triggered by the {@link Tech}. 24708 * 24709 * If there were any events queued while the playback rate was zero, fire 24710 * those events now. 24711 * 24712 * @private 24713 * @method Player#handleTechRateChange_ 24714 * @fires Player#ratechange 24715 * @listens Tech#ratechange 24716 */ 24717 ; 24718 24719 _proto.handleTechRateChange_ = function handleTechRateChange_() { 24720 if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) { 24721 this.queuedCallbacks_.forEach(function (queued) { 24722 return queued.callback(queued.event); 24723 }); 24724 this.queuedCallbacks_ = []; 24725 } 24726 24727 this.cache_.lastPlaybackRate = this.tech_.playbackRate(); 24728 /** 24729 * Fires when the playing speed of the audio/video is changed 24730 * 24731 * @event Player#ratechange 24732 * @type {event} 24733 */ 24734 24735 this.trigger('ratechange'); 24736 } 24737 /** 24738 * Retrigger the `waiting` event that was triggered by the {@link Tech}. 24739 * 24740 * @fires Player#waiting 24741 * @listens Tech#waiting 24742 * @private 24743 */ 24744 ; 24745 24746 _proto.handleTechWaiting_ = function handleTechWaiting_() { 24747 var _this7 = this; 24748 24749 this.addClass('vjs-waiting'); 24750 /** 24751 * A readyState change on the DOM element has caused playback to stop. 24752 * 24753 * @event Player#waiting 24754 * @type {EventTarget~Event} 24755 */ 24756 24757 this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent 24758 // premature removal of the waiting class, wait for the time to change. 24759 24760 var timeWhenWaiting = this.currentTime(); 24761 24762 var timeUpdateListener = function timeUpdateListener() { 24763 if (timeWhenWaiting !== _this7.currentTime()) { 24764 _this7.removeClass('vjs-waiting'); 24765 24766 _this7.off('timeupdate', timeUpdateListener); 24767 } 24768 }; 24769 24770 this.on('timeupdate', timeUpdateListener); 24771 } 24772 /** 24773 * Retrigger the `canplay` event that was triggered by the {@link Tech}. 24774 * > Note: This is not consistent between browsers. See #1351 24775 * 24776 * @fires Player#canplay 24777 * @listens Tech#canplay 24778 * @private 24779 */ 24780 ; 24781 24782 _proto.handleTechCanPlay_ = function handleTechCanPlay_() { 24783 this.removeClass('vjs-waiting'); 24784 /** 24785 * The media has a readyState of HAVE_FUTURE_DATA or greater. 24786 * 24787 * @event Player#canplay 24788 * @type {EventTarget~Event} 24789 */ 24790 24791 this.trigger('canplay'); 24792 } 24793 /** 24794 * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. 24795 * 24796 * @fires Player#canplaythrough 24797 * @listens Tech#canplaythrough 24798 * @private 24799 */ 24800 ; 24801 24802 _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { 24803 this.removeClass('vjs-waiting'); 24804 /** 24805 * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the 24806 * entire media file can be played without buffering. 24807 * 24808 * @event Player#canplaythrough 24809 * @type {EventTarget~Event} 24810 */ 24811 24812 this.trigger('canplaythrough'); 24813 } 24814 /** 24815 * Retrigger the `playing` event that was triggered by the {@link Tech}. 24816 * 24817 * @fires Player#playing 24818 * @listens Tech#playing 24819 * @private 24820 */ 24821 ; 24822 24823 _proto.handleTechPlaying_ = function handleTechPlaying_() { 24824 this.removeClass('vjs-waiting'); 24825 /** 24826 * The media is no longer blocked from playback, and has started playing. 24827 * 24828 * @event Player#playing 24829 * @type {EventTarget~Event} 24830 */ 24831 24832 this.trigger('playing'); 24833 } 24834 /** 24835 * Retrigger the `seeking` event that was triggered by the {@link Tech}. 24836 * 24837 * @fires Player#seeking 24838 * @listens Tech#seeking 24839 * @private 24840 */ 24841 ; 24842 24843 _proto.handleTechSeeking_ = function handleTechSeeking_() { 24844 this.addClass('vjs-seeking'); 24845 /** 24846 * Fired whenever the player is jumping to a new time 24847 * 24848 * @event Player#seeking 24849 * @type {EventTarget~Event} 24850 */ 24851 24852 this.trigger('seeking'); 24853 } 24854 /** 24855 * Retrigger the `seeked` event that was triggered by the {@link Tech}. 24856 * 24857 * @fires Player#seeked 24858 * @listens Tech#seeked 24859 * @private 24860 */ 24861 ; 24862 24863 _proto.handleTechSeeked_ = function handleTechSeeked_() { 24864 this.removeClass('vjs-seeking'); 24865 this.removeClass('vjs-ended'); 24866 /** 24867 * Fired when the player has finished jumping to a new time 24868 * 24869 * @event Player#seeked 24870 * @type {EventTarget~Event} 24871 */ 24872 24873 this.trigger('seeked'); 24874 } 24875 /** 24876 * Retrigger the `firstplay` event that was triggered by the {@link Tech}. 24877 * 24878 * @fires Player#firstplay 24879 * @listens Tech#firstplay 24880 * @deprecated As of 6.0 firstplay event is deprecated. 24881 * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. 24882 * @private 24883 */ 24884 ; 24885 24886 _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() { 24887 // If the first starttime attribute is specified 24888 // then we will start at the given offset in seconds 24889 if (this.options_.starttime) { 24890 log.warn('Passing the `starttime` option to the player will be deprecated
24890in 6.0'); 24891 this.currentTime(this.options_.starttime); 24892 } 24893 24894 this.addClass('vjs-has-started'); 24895 /** 24896 * Fired the first time a video is played. Not part of the HLS spec, and this is 24897 * probably not the best implementation yet, so use sparingly. If you don't have a 24898 * reason to prevent playback, use `myPlayer.one('play');` instead. 24899 * 24900 * @event Player#firstplay 24901 * @deprecated As of 6.0 firstplay event is deprecated. 24902 * @type {EventTarget~Event} 24903 */ 24904 24905 this.trigger('firstplay'); 24906 } 24907 /** 24908 * Retrigger the `pause` event that was triggered by the {@link Tech}. 24909 * 24910 * @fires Player#pause 24911 * @listens Tech#pause 24912 * @private 24913 */ 24914 ; 24915 24916 _proto.handleTechPause_ = function handleTechPause_() { 24917 this.removeClass('vjs-playing'); 24918 this.addClass('vjs-paused'); 24919 /** 24920 * Fired whenever the media has been paused 24921 * 24922 * @event Player#pause 24923 * @type {EventTarget~Event} 24924 */ 24925 24926 this.trigger('pause'); 24927 } 24928 /** 24929 * Retrigger the `ended` event that was triggered by the {@link Tech}. 24930 * 24931 * @fires Player#ended 24932 * @listens Tech#ended 24933 * @private 24934 */ 24935 ; 24936 24937 _proto.handleTechEnded_ = function handleTechEnded_() { 24938 this.addClass('vjs-ended'); 24939 24940 if (this.options_.loop) { 24941 this.currentTime(0); 24942 this.play(); 24943 } else if (!this.paused()) { 24944 this.pause(); 24945 } 24946 /** 24947 * Fired when the end of the media resource is reached (currentTime == duration) 24948 * 24949 * @event Player#ended 24950 * @type {EventTarget~Event} 24951 */ 24952 24953 24954 this.trigger('ended'); 24955 } 24956 /** 24957 * Fired when the duration of the media resource is first known or changed 24958 * 24959 * @listens Tech#durationchange 24960 * @private 24961 */ 24962 ; 24963 24964 _proto.handleTechDurationChange_ = function handleTechDurationChange_() { 24965 this.duration(this.techGet_('duration')); 24966 } 24967 /** 24968 * Handle a click on the media element to play/pause 24969 * 24970 * @param {EventTarget~Event} event 24971 * the event that caused this function to trigger 24972 * 24973 * @listens Tech#mouseup 24974 * @private 24975 */ 24976 ; 24977 24978 _proto.handleTechClick_ = function handleTechClick_(event) { 24979 if (!isSingleLeftClick(event)) { 24980 return; 24981 } // When controls are disabled a click should not toggle playback because 24982 // the click is considered a control 24983 24984 24985 if (!this.controls_) { 24986 return; 24987 } 24988 24989 if (this.paused()) { 24990 silencePromise(this.play()); 24991 } else { 24992 this.pause(); 24993 } 24994 } 24995 /** 24996 * Handle a double-click on the media element to enter/exit fullscreen 24997 * 24998 * @param {EventTarget~Event} event 24999 * the event that caused this function to trigger 25000 * 25001 * @listens Tech#dblclick 25002 * @private 25003 */ 25004 ; 25005 25006 _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) { 25007 if (!this.controls_) { 25008 return; 25009 } // we do not want to toggle fullscreen state 25010 // when double-clicking inside a control bar or a modal 25011 25012 25013 var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) { 25014 return el.contains(event.target); 25015 }); 25016 25017 if (!inAllowedEls) { 25018 /* 25019 * options.userActions.doubleClick 25020 * 25021 * If `undefined` or `true`, double-click toggles fullscreen if controls are present 25022 * Set to `false` to disable double-click handling 25023 * Set to a function to substitute an external double-click handler 25024 */ 25025 if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) { 25026 if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') { 25027 this.options_.userActions.doubleClick.call(this, event); 25028 } else if (this.isFullscreen()) { 25029 this.exitFullscreen(); 25030 } else { 25031 this.requestFullscreen(); 25032 } 25033 } 25034 } 25035 } 25036 /** 25037 * Handle a tap on the media element. It will toggle the user 25038 * activity state, which hides and shows the controls. 25039 * 25040 * @listens Tech#tap 25041 * @private 25042 */ 25043 ; 25044 25045 _proto.handleTechTap_ = function handleTechTap_() { 25046 this.userActive(!this.userActive()); 25047 } 25048 /** 25049 * Handle touch to start 25050 * 25051 * @listens Tech#touchstart 25052 * @private 25053 */ 25054 ; 25055 25056 _proto.handleTechTouchStart_ = function handleTechTouchStart_() { 25057 this.userWasActive = this.userActive(); 25058 } 25059 /** 25060 * Handle touch to move 25061 * 25062 * @listens Tech#touchmove 25063 * @private 25064 */ 25065 ; 25066 25067 _proto.handleTechTouchMove_ = function handleTechTouchMove_() { 25068 if (this.userWasActive) { 25069 this.reportUserActivity(); 25070 } 25071 } 25072 /** 25073 * Handle touch to end 25074 * 25075 * @param {EventTarget~Event} event 25076 * the touchend event that triggered 25077 * this function 25078 * 25079 * @listens Tech#touchend 25080 * @private 25081 */ 25082 ; 25083 25084 _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { 25085 // Stop the mouse events from also happening 25086 event.preventDefault(); 25087 } 25088 /** 25089 * native click events on the SWF aren't triggered on IE11, Win8.1RT 25090 * use stageclick events triggered from inside the SWF instead 25091 * 25092 * @private 25093 * @listens stageclick 25094 */ 25095 ; 25096 25097 _proto.handleStageClick_ = function handleStageClick_() { 25098 this.reportUserActivity(); 25099 } 25100 /** 25101 * @private 25102 */ 25103 ; 25104 25105 _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() { 25106 if (this.isFullscreen()) { 25107 this.addClass('vjs-fullscreen'); 25108 } else { 25109 this.removeClass('vjs-fullscreen'); 25110 } 25111 } 25112 /** 25113 * when the document fschange event triggers it calls this 25114 */ 25115 ; 25116 25117 _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) { 25118 var targetPlayer = e.target.player; // if another player was fullscreen 25119 // do a null check for targetPlayer because older firefox's would put document as e.target 25120 25121 if (targetPlayer && targetPlayer !== this) { 25122 return; 25123 } 25124 25125 var el = this.el(); 25126 var isFs = document[this.fsApi_.fullscreenElement] === el; 25127 25128 if (!isFs && el.matches) { 25129 isFs = el.matches(':' + this.fsApi_.fullscreen); 25130 } else if (!isFs && el.msMatchesSelector) { 25131 isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen); 25132 } 25133 25134 this.isFullscreen(isFs); 25135 } 25136 /** 25137 * Handle Tech Fullscreen Change 25138 * 25139 * @param {EventTarget~Event} event 25140 * the fullscreenchange event that triggered this function 25141 * 25142 * @param {Object} data 25143 * the data that was sent with the event 25144 * 25145 * @private 25146 * @listens Tech#fullscreenchange 25147 * @fires Player#fullscreenchange 25148 */ 25149 ; 25150 25151 _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { 25152 if (data) { 25153 if (data.nativeIOSFullscreen) { 25154 this.toggleClass('vjs-ios-native-fs'); 25155 } 25156 25157 this.isFullscreen(data.isFullscreen); 25158 } 25159 }; 25160 25161 _proto.handleTechFullscreenError_ = function handleTechFullscreenError_(event, err) { 25162 this.trigger('fullscreenerror', err); 25163 } 25164 /** 25165 * @private 25166 */ 25167 ; 25168 25169 _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() { 25170 if (this.isInPictureInPicture()) { 25171 this.addClass('vjs-picture-in-picture'); 25172 } else { 25173 this.removeClass('vjs-picture-in-picture'); 25174 } 25175 } 25176 /** 25177 * Handle Tech Enter Picture-in-Picture. 25178 * 25179 * @param {EventTarget~Event} event 25180 * the enterpictureinpicture event that triggered this function 25181 * 25182 * @private 25183 * @listens Tech#enterpictureinpicture 25184 */ 25185 ; 25186 25187 _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) { 25188 this.isInPictureInPicture(true); 25189 } 25190 /** 25191 * Handle Tech Leave Picture-in-Picture. 25192 * 25193 * @param {EventTarget~Event} event 25194 * the leavepictureinpicture event that triggered this function 25195 * 25196 * @private 25197 * @listens Tech#leavepictureinpicture 25198 */ 25199 ; 25200 25201 _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) { 25202 this.isInPictureInPicture(false); 25203 } 25204 /** 25205 * Fires when an error occurred during the loading of an audio/video. 25206 * 25207 * @private 25208 * @listens Tech#error 25209 */ 25210 ; 25211 25212 _proto.handleTechError_ = function handleTechError_() { 25213 var error = this.tech_.error(); 25214 this.error(error); 25215 } 25216 /** 25217 * Retrigger the `textdata` event that was triggered by the {@link Tech}. 25218 * 25219 * @fires Player#textdata 25220 * @listens Tech#textdata 25221 * @private 25222 */ 25223 ; 25224 25225 _proto.handleTechTextData_ = function handleTechTextData_() { 25226 var data = null; 25227 25228 if (arguments.length > 1) { 25229 data = arguments[1]; 25230 } 25231 /** 25232 * Fires when we get a textdata event from tech 25233 * 25234 * @event Player#textdata 25235 * @type {EventTarget~Event} 25236 */ 25237 25238 25239 this.trigger('textdata', data); 25240 } 25241 /** 25242 * Get object for cached values. 25243 * 25244 * @return {Object} 25245 * get the current object cache 25246 */ 25247 ; 25248 25249 _proto.getCache = function getCache() { 25250 return this.cache_; 25251 } 25252 /**
25253 * Resets the internal cache object. 25254 * 25255 * Using this function outside the player constructor or reset method may 25256 * have unintended side-effects. 25257 * 25258 * @private 25259 */ 25260 ; 25261 25262 _proto.resetCache_ = function resetCache_() { 25263 this.cache_ = { 25264 // Right now, the currentTime is not _really_ cached because it is always 25265 // retrieved from the tech (see: currentTime). However, for completeness, 25266 // we set it to zero here to ensure that if we do start actually caching 25267 // it, we reset it along with everything else. 25268 currentTime: 0, 25269 initTime: 0, 25270 inactivityTimeout: this.options_.inactivityTimeout, 25271 duration: NaN, 25272 lastVolume: 1, 25273 lastPlaybackRate: this.defaultPlaybackRate(), 25274 media: null, 25275 src: '', 25276 source: {}, 25277 sources: [], 25278 volume: 1 25279 }; 25280 } 25281 /** 25282 * Pass values to the playback tech 25283 * 25284 * @param {string} [method] 25285 * the method to call 25286 * 25287 * @param {Object} arg 25288 * the argument to pass 25289 * 25290 * @private 25291 */ 25292 ; 25293 25294 _proto.techCall_ = function techCall_(method, arg) { 25295 // If it's not ready yet, call method when it is 25296 this.ready(function () { 25297 if (method in allowedSetters) { 25298 return set(this.middleware_, this.tech_, method, arg); 25299 } else if (method in allowedMediators) { 25300 return mediate(this.middleware_, this.tech_, method, arg); 25301 } 25302 25303 try { 25304 if (this.tech_) { 25305 this.tech_[method](arg); 25306 } 25307 } catch (e) { 25308 log(e); 25309 throw e; 25310 } 25311 }, true); 25312 } 25313 /** 25314 * Get calls can't wait for the tech, and sometimes don't need to. 25315 * 25316 * @param {string} method 25317 * Tech method 25318 * 25319 * @return {Function|undefined} 25320 * the method or undefined 25321 * 25322 * @private 25323 */ 25324 ; 25325 25326 _proto.techGet_ = function techGet_(method) { 25327 if (!this.tech_ || !this.tech_.isReady_) { 25328 return; 25329 } 25330 25331 if (method in allowedGetters) { 25332 return get(this.middleware_, this.tech_, method); 25333 } else if (method in allowedMediators) { 25334 return mediate(this.middleware_, this.tech_, method); 25335 } // Flash likes to die and reload when you hide or reposition it. 25336 // In these cases the object methods go away and we get errors. 25337 // When that happens we'll catch the errors and inform tech that it's not ready any more. 25338 25339 25340 try { 25341 return this.tech_[method](); 25342 } catch (e) { 25343 // When building additional tech libs, an expected method may not be defined yet 25344 if (this.tech_[method] === undefined) { 25345 log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e); 25346 throw e; 25347 } // When a method isn't available on the object it throws a TypeError 25348 25349 25350 if (e.name === 'TypeError') { 25351 log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e); 25352 this.tech_.isReady_ = false; 25353 throw e; 25354 } // If error unknown, just log and throw 25355 25356 25357 log(e); 25358 throw e; 25359 } 25360 } 25361 /** 25362 * Attempt to begin playback at the first opportunity. 25363 * 25364 * @return {Promise|undefined} 25365 * Returns a promise if the browser supports Promises (or one 25366 * was passed in as an option). This promise will be resolved on 25367 * the return value of play. If this is undefined it will fulfill the 25368 * promise chain otherwise the promise chain will be fulfilled when 25369 * the promise from play is fulfilled. 25370 */ 25371 ; 25372 25373 _proto.play = function play() { 25374 var _this8 = this; 25375 25376 var PromiseClass = this.options_.Promise || window$3.Promise; 25377 25378 if (PromiseClass) { 25379 return new PromiseClass(function (resolve) { 25380 _this8.play_(resolve); 25381 }); 25382 } 25383 25384 return this.play_(); 25385 } 25386 /** 25387 * The actual logic for play, takes a callback that will be resolved on the 25388 * return value of play. This allows us to resolve to the play promise if there 25389 * is one on modern browsers. 25390 * 25391 * @private 25392 * @param {Function} [callback] 25393 * The callback that should be called when the techs play is actually called 25394 */ 25395 ; 25396 25397 _proto.play_ = function play_(callback) { 25398 var _this9 = this; 25399 25400 if (callback === void 0) { 25401 callback = silencePromise; 25402 } 25403 25404 this.playCallbacks_.push(callback);
25405 var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function 25406 25407 if (this.waitToPlay_) { 25408 this.off(['ready', 'loadstart'], this.waitToPlay_); 25409 this.waitToPlay_ = null; 25410 } // if the player/tech is not ready or the src itself is not ready 25411 // queue up a call to play on `ready` or `loadstart` 25412 25413 25414 if (!this.isReady_ || !isSrcReady) { 25415 this.waitToPlay_ = function (e) { 25416 _this9.play_(); 25417 }; 25418 25419 this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod 25420 // in that case, we need to prime the video element by calling load so it'll be ready in time 25421 25422 if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) { 25423 this.load(); 25424 } 25425 25426 return; 25427 } // If the player/tech is ready and we have a source, we can attempt playback. 25428 25429 25430 var val = this.techGet_('play'); // play was terminated if the returned value is null 25431 25432 if (val === null) { 25433 this.runPlayTerminatedQueue_(); 25434 } else { 25435 this.runPlayCallbacks_(val); 25436 } 25437 } 25438 /** 25439 * These functions will be run when if play is terminated. If play 25440 * runPlayCallbacks_ is run these function will not be run. This allows us 25441 * to differenciate between a terminated play and an actual call to play. 25442 */ 25443 ; 25444 25445 _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() { 25446 var queue = this.playTerminatedQueue_.slice(0); 25447 this.playTerminatedQueue_ = []; 25448 queue.forEach(function (q) { 25449 q(); 25450 }); 25451 } 25452 /** 25453 * When a callback to play is delayed we have to run these 25454 * callbacks when play is actually called on the tech. This function 25455 * runs the callbacks that were delayed and accepts the return value 25456 * from the tech. 25457 * 25458 * @param {undefined|Promise} val 25459 * The return value from the tech. 25460 */ 25461 ; 25462 25463 _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) { 25464 var callbacks = this.playCallbacks_.slice(0); 25465 this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play 25466 25467 this.playTerminatedQueue_ = []; 25468 callbacks.forEach(function (cb) { 25469 cb(val); 25470 }); 25471 } 25472 /** 25473 * Pause the video playback 25474 * 25475 * @return {Player} 25476 * A reference to the player object this function was called on 25477 */ 25478 ; 25479 25480 _proto.pause = function pause() { 25481 this.techCall_('pause'); 25482 } 25483 /** 25484 * Check if the player is paused or has yet to play 25485 * 25486 * @return {boolean} 25487 * - false: if the media is currently playing 25488 * - true: if media is not currently playing 25489 */ 25490 ; 25491 25492 _proto.paused = function paused() { 25493 // The initial state of paused should be true (in Safari it's actually false) 25494 return this.techGet_('paused') === false ? false : true; 25495 } 25496 /** 25497 * Get a TimeRange object representing the current ranges of time that the user 25498 * has played. 25499 * 25500 * @return {TimeRange} 25501 * A time range object that represents all the increments of time that have 25502 * been played. 25503 */ 25504 ; 25505 25506 _proto.played = function played() { 25507 return this.techGet_('played') || createTimeRanges(0, 0); 25508 } 25509 /** 25510 * Returns whether or not the user is "scrubbing". Scrubbing is 25511 * when the user has clicked the progress bar handle and is 25512 * dragging it along the progress bar. 25513 * 25514 * @param {boolean} [isScrubbing] 25515 * whether the user is or is not scrubbing 25516 * 25517 * @return {boolean} 25518 * The value of scrubbing when getting 25519 */ 25520 ; 25521 25522 _proto.scrubbing = function scrubbing(isScrubbing) { 25523 if (typeof isScrubbing === 'undefined') { 25524 return this.scrubbing_; 25525 } 25526 25527 this.scrubbing_ = !!isScrubbing; 25528 25529 if (isScrubbing) { 25530 this.addClass('vjs-scrubbing'); 25531 } else { 25532 this.removeClass('vjs-scrubbing'); 25533 } 25534 } 25535 /** 25536 * Get or set the current time (in seconds) 25537 * 25538 * @param {number|string} [seconds] 25539 * The time to seek to in seconds 25540 * 25541 * @return {number} 25542 * - the current time in seconds when getting 25543 */ 25544 ; 25545 25546 _proto.currentTime = function currentTime(seconds) { 25547 if (typeof seconds !== 'undefined') { 25548 if (seconds < 0) { 25549 seconds = 0; 25550 } 25551 25552 if (!this.isReady_ || this.changingSrc_ || !this.tech_ || !this.tech_.isReady_) { 25553 this.cache_.initTime = seconds; 25554 this.off('canplay', this.applyInitTime_); 25555 this.one('canplay', this.applyInitTime_); 25556 return; 25557 } 25558 25559 this.techCall_('setCurrentTime', seconds); 25560 this.cache_.initTime = 0; 25561 return; 25562 } // cache last currentTime and return. default to 0 seconds 25563 // 25564 // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
vendor: 10,254 bytes, lines 25565-25880
25565 // currentTime when scrubbing, but may not provide much performance benefit afterall. 25566 // Should be tested. Also something has to read the actual current time or the cache will 25567 // never get updated. 25568 25569 25570 this.cache_.currentTime = this.techGet_('currentTime') || 0; 25571 return this.cache_.currentTime; 25572 } 25573 /** 25574 * Apply the value of initTime stored in cache as currentTime. 25575 * 25576 * @private 25577 */ 25578 ; 25579 25580 _proto.applyInitTime_ = function applyInitTime_() { 25581 this.currentTime(this.cache_.initTime); 25582 } 25583 /** 25584 * Normally gets the length in time of the video in seconds; 25585 * in all but the rarest use cases an argument will NOT be passed to the method 25586 * 25587 * > **NOTE**: The video must have started loading before the duration can be 25588 * known, and in the case of Flash, may not be known until the video starts 25589 * playing. 25590 * 25591 * @fires Player#durationchange 25592 * 25593 * @param {number} [seconds] 25594 * The duration of the video to set in seconds 25595 * 25596 * @return {number} 25597 * - The duration of the video in seconds when getting 25598 */ 25599 ; 25600 25601 _proto.duration = function duration(seconds) { 25602 if (seconds === undefined) { 25603 // return NaN if the duration is not known 25604 return this.cache_.duration !== undefined ? this.cache_.duration : NaN; 25605 } 25606 25607 seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live 25608 25609 if (seconds < 0) { 25610 seconds = Infinity; 25611 } 25612 25613 if (seconds !== this.cache_.duration) { 25614 // Cache the last set value for optimized scrubbing (esp. Flash) 25615 this.cache_.duration = seconds; 25616 25617 if (seconds === Infinity) { 25618 this.addClass('vjs-live'); 25619 } else { 25620 this.removeClass('vjs-live'); 25621 } 25622 25623 if (!isNaN(seconds)) { 25624 // Do not fire durationchange unless the duration value is known. 25625 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 25626 25627 /** 25628 * @event Player#durationchange 25629 * @type {EventTarget~Event} 25630 */ 25631 this.trigger('durationchange'); 25632 } 25633 } 25634 } 25635 /** 25636 * Calculates how much time is left in the video. Not part 25637 * of the native video API. 25638 * 25639 * @return {number} 25640 * The time remaining in seconds 25641 */ 25642 ; 25643 25644 _proto.remainingTime = function remainingTime() { 25645 return this.duration() - this.currentTime(); 25646 } 25647 /** 25648 * A remaining time function that is intented to be used when 25649 * the time is to be displayed directly to the user. 25650 * 25651 * @return {number} 25652 * The rounded time remaining in seconds 25653 */ 25654 ; 25655 25656 _proto.remainingTimeDisplay = function remainingTimeDisplay() { 25657 return Math.floor(this.duration()) - Math.floor(this.currentTime()); 25658 } // 25659 // Kind of like an array of portions of the video that have been downloaded. 25660 25661 /** 25662 * Get a TimeRange object with an array of the times of the video 25663 * that have been downloaded. If you just want the percent of the 25664 * video that's been downloaded, use bufferedPercent. 25665 * 25666 * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} 25667 * 25668 * @return {TimeRange} 25669 * A mock TimeRange object (following HTML spec) 25670 */ 25671 ; 25672 25673 _proto.buffered = function buffered() { 25674 var buffered = this.techGet_('buffered'); 25675 25676 if (!buffered || !buffered.length) { 25677 buffered = createTimeRanges(0, 0); 25678 } 25679 25680 return buffered; 25681 } 25682 /** 25683 * Get the percent (as a decimal) of the video that's been downloaded. 25684 * This method is not a part of the native HTML video API. 25685 * 25686 * @return {number} 25687 * A decimal between 0 and 1 representing the percent 25688 * that is buffered 0 being 0% and 1 being 100% 25689 */ 25690 ; 25691 25692 _proto.bufferedPercent = function bufferedPercent$1() { 25693 return bufferedPercent(this.buffered(), this.duration()); 25694 } 25695 /** 25696 * Get the ending time of the last buffered time range 25697 * This is used in the progress bar to encapsulate all time ranges. 25698 * 25699 * @return {number} 25700 * The end of the last buffered time range 25701 */ 25702 ; 25703 25704 _proto.bufferedEnd = function bufferedEnd() { 25705 var buffered = this.buffered(); 25706 var duration = this.duration(); 25707 var end = buffered.end(buffered.length - 1); 25708 25709 if (end > duration) { 25710 end = duration; 25711 } 25712 25713 return end; 25714 } 25715 /** 25716 * Get or set the current volume of the media 25717 * 25718 * @param {number} [percentAsDecimal] 25719 * The new volume as a decimal percent: 25720 * - 0 is muted/0%/off 25721 * - 1.0 is 100%/full 25722 * - 0.5 is half volume or 50% 25723 * 25724 * @return {number} 25725 * The current volume as a percent when getting 25726 */ 25727 ; 25728 25729 _proto.volume = function volume(percentAsDecimal) { 25730 var vol; 25731 25732 if (percentAsDecimal !== undefined) { 25733 // Force value to between 0 and 1 25734 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); 25735 this.cache_.volume = vol; 25736 this.techCall_('setVolume', vol); 25737 25738 if (vol > 0) { 25739 this.lastVolume_(vol); 25740 } 25741 25742 return; 25743 } // Default to 1 when returning current volume. 25744 25745 25746 vol = parseFloat(this.techGet_('volume')); 25747 return isNaN(vol) ? 1 : vol; 25748 } 25749 /** 25750 * Get the current muted state, or turn mute on or off 25751 * 25752 * @param {boolean} [muted] 25753 * - true to mute 25754 * - false to unmute 25755 * 25756 * @return {boolean} 25757 * - true if mute is on and getting 25758 * - false if mute is off and getting 25759 */ 25760 ; 25761 25762 _proto.muted = function muted(_muted) { 25763 if (_muted !== undefined) { 25764 this.techCall_('setMuted', _muted); 25765 return; 25766 } 25767 25768 return this.techGet_('muted') || false; 25769 } 25770 /** 25771 * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted 25772 * indicates the state of muted on initial playback. 25773 * 25774 * ```js 25775 * var myPlayer = videojs('some-player-id'); 25776 * 25777 * myPlayer.src("http://www.example.com/path/to/video.mp4"); 25778 * 25779 * // get, should be false 25780 * console.log(myPlayer.defaultMuted()); 25781 * // set to true 25782 * myPlayer.defaultMuted(true); 25783 * // get should be true 25784 * console.log(myPlayer.defaultMuted()); 25785 * ``` 25786 * 25787 * @param {boolean} [defaultMuted] 25788 * - true to mute 25789 * - false to unmute 25790 * 25791 * @return {boolean|Player} 25792 * - true if defaultMuted is on and getting 25793 * - false if defaultMuted is off and getting 25794 * - A reference to the current player when setting 25795 */ 25796 ; 25797 25798 _proto.defaultMuted = function defaultMuted(_defaultMuted) { 25799 if (_defaultMuted !== undefined) { 25800 return this.techCall_('setDefaultMuted', _defaultMuted); 25801 } 25802 25803 return this.techGet_('defaultMuted') || false; 25804 } 25805 /** 25806 * Get the last volume, or set it 25807 * 25808 * @param {number} [percentAsDecimal] 25809 * The new last volume as a decimal percent: 25810 * - 0 is muted/0%/off 25811 * - 1.0 is 100%/full 25812 * - 0.5 is half volume or 50% 25813 * 25814 * @return {number} 25815 * the current value of lastVolume as a percent when getting 25816 * 25817 * @private 25818 */ 25819 ; 25820 25821 _proto.lastVolume_ = function lastVolume_(percentAsDecimal) { 25822 if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { 25823 this.cache_.lastVolume = percentAsDecimal; 25824 return; 25825 } 25826 25827 return this.cache_.lastVolume; 25828 } 25829 /** 25830 * Check if current tech can support native fullscreen 25831 * (e.g. with built in controls like iOS, so not our flash swf) 25832 * 25833 * @return {boolean} 25834 * if native fullscreen is supported 25835 */ 25836 ; 25837 25838 _proto.supportsFullScreen = function supportsFullScreen() { 25839 return this.techGet_('supportsFullScreen') || false; 25840 } 25841 /** 25842 * Check if the player is in fullscreen mode or tell the player that it 25843 * is or is not in fullscreen mode. 25844 * 25845 * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official 25846 * property and instead document.fullscreenElement is used. But isFullscreen is 25847 * still a valuable property for internal player workings. 25848 * 25849 * @param {boolean} [isFS] 25850 * Set the players current fullscreen state 25851 * 25852 * @return {boolean} 25853 * - true if fullscreen is on and getting 25854 * - false if fullscreen is off and getting 25855 */ 25856 ; 25857 25858 _proto.isFullscreen = function isFullscreen(isFS) { 25859 if (isFS !== undefined) { 25860 var oldValue = this.isFullscreen_; 25861 this.isFullscreen_ = Boolean(isFS); // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange 25862 // this is the only place where we trigger fullscreenchange events for older browsers 25863 // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well 25864 25865 if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) { 25866 /** 25867 * @event Player#fullscreenchange 25868 * @type {EventTarget~Event} 25869 */ 25870 this.trigger('fullscreenchange'); 25871 } 25872 25873 this.toggleFullscreenClass_(); 25874 return; 25875 } 25876 25877 return this.isFullscreen_; 25878 } 25879 /** 25880 * Increase the size of the video to full screen
25881 * In some browsers, full screen is not supported natively, so it enters 25882 * "full window mode", where the video fills the browser window. 25883 * In browsers and devices that support native full screen, sometimes the 25884 * browser's default controls will be shown, and not the Video.js custom skin. 25885 * This includes most mobile devices (iOS, Android) and older versions of 25886 * Safari. 25887 * 25888 * @param {Object} [fullscreenOptions] 25889 * Override the player fullscreen options 25890 * 25891 * @fires Player#fullscreenchange 25892 */ 25893 ; 25894 25895 _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) { 25896 var PromiseClass = this.options_.Promise || window$3.Promise; 25897 25898 if (PromiseClass) { 25899 var self = this; 25900 return new PromiseClass(function (resolve, reject) { 25901 function offHandler() { 25902 self.off('fullscreenerror', errorHandler); 25903 self.off('fullscreenchange', changeHandler); 25904 } 25905 25906 function changeHandler() { 25907 offHandler(); 25908 resolve(); 25909 } 25910 25911 function errorHandler(e, err) { 25912 offHandler(); 25913 reject(err); 25914 } 25915 25916 self.one('fullscreenchange', changeHandler); 25917 self.one('fullscreenerror', errorHandler); 25918 var promise = self.requestFullscreenHelper_(fullscreenOptions); 25919 25920 if (promise) { 25921 promise.then(offHandler, offHandler); 25922 return promise; 25923 } 25924 }); 25925 } 25926 25927 return this.requestFullscreenHelper_(); 25928 }; 25929 25930 _proto.requestFullscreenHelper_ = function requestFullscreenHelper_(fullscreenOptions) { 25931 var _this10 = this; 25932 25933 var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers. 25934 // Use defaults or player configured option unless passed directly to this method. 25935 25936 if (!this.fsApi_.prefixed) { 25937 fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {}; 25938 25939 if (fullscreenOptions !== undefined) { 25940 fsOptions = fullscreenOptions; 25941 } 25942 } // This method works as follows: 25943 // 1. if a fullscreen api is available, use it 25944 // 1. call requestFullscreen with potential options 25945 // 2. if we got a promise from above, use it to update isFullscreen() 25946 // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it. 25947 // This is particularly used for iPhone, older iPads, and non-safari browser on iOS. 25948 // 3. otherwise, use "fullWindow" mode 25949 25950 25951 if (this.fsApi_.requestFullscreen) { 25952 var promise = this.el_[this.fsApi_.requestFullscreen](fsOptions); 25953 25954 if (promise) { 25955 promise.then(function () { 25956 return _this10.isFullscreen(true); 25957 }, function () { 25958 return _this10.isFullscreen(false); 25959 }); 25960 } 25961 25962 return promise; 25963 } else if (this.tech_.supportsFullScreen()) { 25964 // we can't take the video.js controls fullscreen but we can go fullscreen 25965 // with native controls 25966 this.techCall_('enterFullScreen'); 25967 } else { 25968 // fullscreen isn't supported so we'll just stretch the video element to 25969 // fill the viewport 25970 this.enterFullWindow(); 25971 } 25972 } 25973 /** 25974 * Return the video to its normal size after having been in full screen mode 25975 * 25976 * @fires Player#fullscreenchange 25977 */ 25978 ; 25979 25980 _proto.exitFullscreen = function exitFullscreen() { 25981 var PromiseClass = this.options_.Promise || window$3.Promise; 25982 25983 if (PromiseClass) { 25984 var self = this; 25985 return new PromiseClass(function (resolve, reject) { 25986 function offHandler() { 25987 self.off('fullscreenerror', errorHandler); 25988 self.off('fullscreenchange', changeHandler); 25989 } 25990 25991 function changeHandler() { 25992 offHandler(); 25993 resolve(); 25994 } 25995 25996 function errorHandler(e, err) { 25997 offHandler(); 25998 reject(err); 25999 } 26000 26001 self.one('fullscreenchange', changeHandler); 26002 self.one('fullscreenerror', errorHandler); 26003 var promise = self.exitFullscreenHelper_(); 26004 26005 if (promise) { 26006 promise.then(offHandler, offHandler); 26007 return promise; 26008 } 26009 }); 26010 } 26011 26012 return this.exitFullscreenHelper_(); 26013 }; 26014 26015 _proto.exitFullscreenHelper_ = function exitFullscreenHelper_() { 26016 var _this11 = this; 26017 26018 if (this.fsApi_.requestFullscreen) { 26019 var promise = document[this.fsApi_.exitFullscreen](); 26020 26021 if (promise) { 26022 promise.then(function () { 26023 return _this11.isFullscreen(false); 26024 }); 26025 } 26026 26027 return promise; 26028 } else if (this.tech_.supportsFullScreen()) { 26029 this.techCall_('exitFullScreen'); 26030 } else { 26031 this.exitFullWindow(); 26032 } 26033 } 26034 /** 26035 * When fullscreen isn't supported we can stretch the 26036 * video container to as wide as the browser will let us. 26037 * 26038 * @fires Player#enterFullWindow 26039 */ 26040 ; 26041 26042 _proto.enterFullWindow = function enterFullWindow() { 26043 this.isFullscreen(true); 26044 this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off 26045 26046 this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen 26047 26048 on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars 26049 26050 document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles 26051 26052 addClass(document.body, 'vjs-full-window'); 26053 /** 26054 * @event Player#enterFullWindow 26055 * @type {EventTarget~Event} 26056 */ 26057 26058 this.trigger('enterFullWindow'); 26059 } 26060 /** 26061 * Check for call to either exit full window or 26062 * full screen on ESC key 26063 * 26064 * @param {string} event 26065 * Event to check for key press 26066 */ 26067 ; 26068 26069 _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) { 26070 if (keycode.isEventKey(event, 'Esc')) { 26071 if (this.isFullscreen() === true) { 26072 this.exitFullscreen(); 26073 } else { 26074 this.exitFullWindow(); 26075 } 26076 } 26077 } 26078 /** 26079 * Exit full window 26080 * 26081 * @fires Player#exitFullWindow 26082 */ 26083 ; 26084 26085 _proto.exitFullWindow = function exitFullWindow() { 26086 this.isFullscreen(false); 26087 this.isFullWindow = false;
26088 off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars. 26089 26090 document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles 26091 26092 removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes 26093 // this.positionAll(); 26094 26095 /** 26096 * @event Player#exitFullWindow 26097 * @type {EventTarget~Event} 26098 */ 26099 26100 this.trigger('exitFullWindow'); 26101 } 26102 /** 26103 * Check if the player is in Picture-in-Picture mode or tell the player that it 26104 * is or is not in Picture-in-Picture mode. 26105 * 26106 * @param {boolean} [isPiP] 26107 * Set the players current Picture-in-Picture state 26108 * 26109 * @return {boolean} 26110 * - true if Picture-in-Picture is on and getting 26111 * - false if Picture-in-Picture is off and getting 26112 */ 26113 ; 26114 26115 _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) { 26116 if (isPiP !== undefined) { 26117 this.isInPictureInPicture_ = !!isPiP; 26118 this.togglePictureInPictureClass_(); 26119 return; 26120 } 26121 26122 return !!this.isInPictureInPicture_; 26123 } 26124 /** 26125 * Create a floating video window always on top of other windows so that users may 26126 * continue consuming media while they interact with other content sites, or 26127 * applications on their device. 26128 * 26129 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 26130 * 26131 * @fires Player#enterpictureinpicture 26132 * 26133 * @return {Promise} 26134 * A promise with a Picture-in-Picture window. 26135 */ 26136 ; 26137 26138 _proto.requestPictureInPicture = function requestPictureInPicture() { 26139 if ('pictureInPictureEnabled' in document) { 26140 /** 26141 * This event fires when the player enters picture in picture mode 26142 * 26143 * @event Player#enterpictureinpicture 26144 * @type {EventTarget~Event} 26145 */ 26146 return this.techGet_('requestPictureInPicture'); 26147 } 26148 } 26149 /** 26150 * Exit Picture-in-Picture mode. 26151 * 26152 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 26153 * 26154 * @fires Player#leavepictureinpicture 26155 * 26156 * @return {Promise} 26157 * A promise. 26158 */ 26159 ; 26160 26161 _proto.exitPictureInPicture = function exitPictureInPicture() { 26162 if ('pictureInPictureEnabled' in document) { 26163 /** 26164 * This event fires when the player leaves picture in picture mode 26165 * 26166 * @event Player#leavepictureinpicture 26167 * @type {EventTarget~Event} 26168 */ 26169 return document.exitPictureInPicture(); 26170 } 26171 } 26172 /** 26173 * Called when this Player has focus and a key gets pressed down, or when 26174 * any Component of this player receives a key press that it doesn't handle. 26175 * This allows player-wide hotkeys (either as defined below, or optionally 26176 * by an external function). 26177 * 26178 * @param {EventTarget~Event} event 26179 * The `keydown` event that caused this function to be called. 26180 * 26181 * @listens keydown 26182 */ 26183 ; 26184 26185 _proto.handleKeyDown = function handleKeyDown(event) { 26186 var userActions = this.options_.userActions; // Bail out if hotkeys are not configured. 26187 26188 if (!userActions || !userActions.hotkeys) { 26189 return; 26190 } // Function that determines whether or not to exclude an element from 26191 // hotkeys handling. 26192 26193 26194 var excludeElement = function excludeElement(el) { 26195 var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements. 26196 26197 if (el.isContentEditable) { 26198 return true; 26199 } // Inputs matching these types will still trigger hotkey handling as 26200 // they are not text inputs. 26201 26202 26203 var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit']; 26204 26205 if (tagName === 'input') { 26206 return allowedInputTypes.indexOf(el.type) === -1; 26207 } // The final test is by tag name. These tags will be excluded entirely. 26208 26209 26210 var excludedTags = ['textarea']; 26211 return excludedTags.indexOf(tagName) !== -1; 26212 }; // Bail out if the user is focused on an interactive form element. 26213 26214 26215 if (excludeElement(this.el_.ownerDocument.activeElement)) { 26216 return; 26217 } 26218 26219 if (typeof userActions.hotkeys === 'function') { 26220 userActions.hotkeys.call(this, event); 26221 } else { 26222 this.handleHotkeys(event); 26223 } 26224 } 26225 /** 26226 * Called when this Player receives a hotkey keydown event. 26227 * Supported player-wide hotkeys are: 26228 * 26229 * f - toggle fullscreen 26230 * m - toggle mute 26231 * k or Space - toggle play/pause 26232 * 26233 * @param {EventTarget~Event} event
26234 * The `keydown` event that caused this function to be called. 26235 */ 26236 ; 26237 26238 _proto.handleHotkeys = function handleHotkeys(event) { 26239 var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set 26240 26241 var _hotkeys$fullscreenKe = hotkeys.fullscreenKey, 26242 fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) { 26243 return keycode.isEventKey(keydownEvent, 'f'); 26244 } : _hotkeys$fullscreenKe, 26245 _hotkeys$muteKey = hotkeys.muteKey, 26246 muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) { 26247 return keycode.isEventKey(keydownEvent, 'm'); 26248 } : _hotkeys$muteKey, 26249 _hotkeys$playPauseKey = hotkeys.playPauseKey, 26250 playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) { 26251 return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'); 26252 } : _hotkeys$playPauseKey; 26253 26254 if (fullscreenKey.call(this, event)) { 26255 event.preventDefault(); 26256 event.stopPropagation(); 26257 var FSToggle = Component.getComponent('FullscreenToggle'); 26258 26259 if (document[this.fsApi_.fullscreenEnabled] !== false) { 26260 FSToggle.prototype.handleClick.call(this, event); 26261 } 26262 } else if (muteKey.call(this, event)) { 26263 event.preventDefault(); 26264 event.stopPropagation(); 26265 var MuteToggle = Component.getComponent('MuteToggle'); 26266 MuteToggle.prototype.handleClick.call(this, event); 26267 } else if (playPauseKey.call(this, event)) { 26268 event.preventDefault(); 26269 event.stopPropagation(); 26270 var PlayToggle = Component.getComponent('PlayToggle'); 26271 PlayToggle.prototype.handleClick.call(this, event); 26272 } 26273 } 26274 /** 26275 * Check whether the player can play a given mimetype 26276 * 26277 * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype 26278 * 26279 * @param {string} type 26280 * The mimetype to check 26281 * 26282 * @return {string} 26283 * 'probably', 'maybe', or '' (empty string) 26284 */ 26285 ; 26286 26287 _proto.canPlayType = function canPlayType(type) { 26288 var can; // Loop through each playback technology in the options order 26289 26290 for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { 26291 var techName = j[i]; 26292 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 26293 // Remove once that deprecated behavior is removed. 26294 26295 if (!tech) { 26296 tech = Component.getComponent(techName); 26297 } // Check if the current tech is defined before continuing 26298 26299 26300 if (!tech) { 26301 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 26302 continue; 26303 } // Check if the browser supports this technology 26304 26305 26306 if (tech.isSupported()) { 26307 can = tech.canPlayType(type); 26308 26309 if (can) { 26310 return can; 26311 } 26312 } 26313 } 26314 26315 return ''; 26316 } 26317 /** 26318 * Select source based on tech-order or source-order 26319 * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, 26320 * defaults to tech-order selection 26321 * 26322 * @param {Array} sources 26323 * The sources for a media asset 26324 * 26325 * @return {Object|boolean} 26326 * Object of source and tech order or false 26327 */ 26328 ; 26329 26330 _proto.selectSource = function selectSource(sources) { 26331 var _this12 = this; 26332 26333 // Get only the techs specified in `techOrder` that exist and are supported by the 26334 // current platform 26335 var techs = this.options_.techOrder.map(function (techName) { 26336 return [techName, Tech.getTech(techName)]; 26337 }).filter(function (_ref) { 26338 var techName = _ref[0], 26339 tech = _ref[1]; 26340 26341 // Check if the current tech is defined before continuing 26342 if (tech) { 26343 // Check if the browser supports this technology 26344 return tech.isSupported(); 26345 } 26346 26347 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 26348 return false;
vendor: 4,460 bytes, lines 26349-26470
26349 }); // Iterate over each `innerArray` element once per `outerArray` element and execute 26350 // `tester` with both. If `tester` returns a non-falsy value, exit early and return 26351 // that value. 26352 26353 var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { 26354 var found; 26355 outerArray.some(function (outerChoice) { 26356 return innerArray.some(function (innerChoice) { 26357 found = tester(outerChoice, innerChoice); 26358 26359 if (found) { 26360 return true; 26361 } 26362 }); 26363 }); 26364 return found; 26365 }; 26366 26367 var foundSourceAndTech; 26368 26369 var flip = function flip(fn) { 26370 return function (a, b) { 26371 return fn(b, a); 26372 }; 26373 }; 26374 26375 var finder = function finder(_ref2, source) { 26376 var techName = _ref2[0], 26377 tech = _ref2[1]; 26378 26379 if (tech.canPlaySource(source, _this12.options_[techName.toLowerCase()])) { 26380 return { 26381 source: source, 26382 tech: techName 26383 }; 26384 } 26385 }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources 26386 // to select from them based on their priority. 26387 26388 26389 if (this.options_.sourceOrder) { 26390 // Source-first ordering 26391 foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); 26392 } else { 26393 // Tech-first ordering 26394 foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); 26395 } 26396 26397 return foundSourceAndTech || false; 26398 } 26399 /** 26400 * Get or set the video source. 26401 * 26402 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 26403 * A SourceObject, an array of SourceObjects, or a string referencing 26404 * a URL to a media source. It is _highly recommended_ that an object 26405 * or array of objects is used here, so that source selection 26406 * algorithms can take the `type` into account. 26407 * 26408 * If not provided, this method acts as a getter. 26409 * 26410 * @return {string|undefined} 26411 * If the `source` argument is missing, returns the current source 26412 * URL. Otherwise, returns nothing/undefined. 26413 */ 26414 ; 26415 26416 _proto.src = function src(source) { 26417 var _this13 = this; 26418 26419 // getter usage 26420 if (typeof source === 'undefined') { 26421 return this.cache_.src || ''; 26422 } // filter out invalid sources and turn our source into 26423 // an array of source objects 26424 26425 26426 var sources = filterSource(source); // if a source was passed in then it is invalid because 26427 // it was filtered to a zero length Array. So we have to 26428 // show an error 26429 26430 if (!sources.length) { 26431 this.setTimeout(function () { 26432 this.error({ 26433 code: 4, 26434 message: this.localize(this.options_.notSupportedMessage) 26435 }); 26436 }, 0); 26437 return; 26438 } // intial sources 26439 26440 26441 this.changingSrc_ = true; 26442 this.cache_.sources = sources; 26443 this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware 26444 26445 setSource(this, sources[0], function (middlewareSource, mws) { 26446 _this13.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since 26447 // the source that is selected could be out of order from the cache update above this callback. 26448 26449 _this13.cache_.sources = sources; 26450 26451 _this13.updateSourceCaches_(middlewareSource); 26452 26453 var err = _this13.src_(middlewareSource); 26454 26455 if (err) { 26456 if (sources.length > 1) { 26457 return _this13.src(sources.slice(1)); 26458 } 26459 26460 _this13.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers 26461 26462 _this13.setTimeout(function () { 26463 this.error({ 26464 code: 4, 26465 message: this.localize(this.options_.notSupportedMessage) 26466 }); 26467 }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it 26468 // this needs a better comment about why this is needed 26469 26470
26471 _this13.triggerReady(); 26472 26473 return; 26474 } 26475 26476 setTech(mws, _this13.tech_); 26477 }); 26478 } 26479 /** 26480 * Set the source object on the tech, returns a boolean that indicates whether 26481 * there is a tech that can play the source or not 26482 * 26483 * @param {Tech~SourceObject} source 26484 * The source object to set on the Tech 26485 * 26486 * @return {boolean} 26487 * - True if there is no Tech to playback this source 26488 * - False otherwise 26489 * 26490 * @private 26491 */ 26492 ; 26493 26494 _proto.src_ = function src_(source) { 26495 var _this14 = this; 26496 26497 var sourceTech = this.selectSource([source]); 26498 26499 if (!sourceTech) { 26500 return true; 26501 } 26502 26503 if (!titleCaseEquals(sourceTech.tech, this.techName_)) { 26504 this.changingSrc_ = true; // load this technology with the chosen source 26505 26506 this.loadTech_(sourceTech.tech, sourceTech.source); 26507 this.tech_.ready(function () { 26508 _this14.changingSrc_ = false; 26509 }); 26510 return false; 26511 } // wait until the tech is ready to set the source 26512 // and set it synchronously if possible (#2326) 26513 26514 26515 this.ready(function () { 26516 // The setSource tech method was added with source handlers 26517 // so older techs won't support it 26518 // We need to check the direct prototype for the case where subclasses 26519 // of the tech do not support source handlers 26520 if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { 26521 this.techCall_('setSource', source); 26522 } else { 26523 this.techCall_('src', source.src); 26524 } 26525 26526 this.changingSrc_ = false; 26527 }, true); 26528 return false; 26529 } 26530 /** 26531 * Begin loading the src data. 26532 */ 26533 ; 26534 26535 _proto.load = function load() { 26536 this.techCall_('load'); 26537 } 26538 /** 26539 * Reset the player. Loads the first tech in the techOrder, 26540 * removes all the text tracks in the existing `tech`, 26541 * and calls `reset` on the `tech`. 26542 */ 26543 ; 26544 26545 _proto.reset = function reset() { 26546 var _this15 = this; 26547 26548 var PromiseClass = this.options_.Promise || window$3.Promise; 26549 26550 if (this.paused() || !PromiseClass) { 26551 this.doReset_(); 26552 } else { 26553 var playPromise = this.play(); 26554 silencePromise(playPromise.then(function () { 26555 return _this15.doReset_(); 26556 })); 26557 } 26558 }; 26559 26560 _proto.doReset_ = function doReset_() { 26561 if (this.tech_) { 26562 this.tech_.clearTracks('text'); 26563 } 26564 26565 this.resetCache_(); 26566 this.poster(''); 26567 this.loadTech_(this.options_.techOrder[0], null); 26568 this.techCall_('reset'); 26569 this.resetControlBarUI_(); 26570 26571 if (isEvented(this)) { 26572 this.trigger('playerreset'); 26573 } 26574 } 26575 /** 26576 * Reset Control Bar's UI by calling sub-methods that reset 26577 * all of Control Bar's components 26578 */ 26579 ; 26580 26581 _proto.resetControlBarUI_ = function resetControlBarUI_() { 26582 this.resetProgressBar_(); 26583 this.resetPlaybackRate_(); 26584 this.resetVolumeBar_(); 26585 } 26586 /** 26587 * Reset tech's progress so progress bar is reset in the UI 26588 */ 26589 ; 26590 26591 _proto.resetProgressBar_ = function resetProgressBar_() { 26592 this.currentTime(0); 26593 var _this$controlBar = this.controlBar, 26594 durationDisplay = _this$controlBar.durationDisplay, 26595 remainingTimeDisplay = _this$controlBar.remainingTimeDisplay; 26596 26597 if (durationDisplay) { 26598 durationDisplay.updateContent(); 26599 } 26600 26601 if (remainingTimeDisplay) { 26602 remainingTimeDisplay.updateContent(); 26603 } 26604 } 26605 /** 26606 * Reset Playback ratio 26607 */ 26608 ; 26609 26610 _proto.resetPlaybackRate_ = function resetPlaybackRate_() { 26611 this.playbackRate(this.defaultPlaybackRate()); 26612 this.handleTechRateChange_(); 26613 } 26614 /** 26615 * Reset Volume bar 26616 */ 26617 ; 26618 26619 _proto.resetVolumeBar_ = function resetVolumeBar_() { 26620 this.volume(1.0); 26621 this.trigger('volumechange'); 26622 } 26623 /** 26624 * Returns all of the current source objects. 26625 * 26626 * @return {Tech~SourceObject[]} 26627 * The current source objects 26628 */ 26629 ; 26630 26631 _proto.currentSources = function currentSources() { 26632 var source = this.currentSource(); 26633 var sources = []; // assume `{}` or `{ src }` 26634 26635 if (Object.keys(source).length !== 0) { 26636 sources.push(source); 26637 } 26638 26639 return this.cache_.sources || sources; 26640 } 26641 /** 26642 * Returns the current source object. 26643 * 26644 * @return {Tech~SourceObject} 26645 * The current source object 26646 */ 26647 ; 26648 26649 _proto.currentSource = function currentSource() { 26650 return this.cache_.source || {}; 26651 } 26652 /** 26653 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 26654 * Can be used in conjunction with `currentType` to assist in rebuilding the current source object. 26655 * 26656 * @return {string} 26657 * The current source 26658 */ 26659 ; 26660 26661 _proto.currentSrc = function currentSrc() { 26662 return this.currentSource() && this.currentSource().src || ''; 26663 } 26664 /** 26665 * Get the current source type e.g. video/mp4 26666 * This can allow you rebuild the current source object so that you could load the same 26667 * source and tech later 26668 * 26669 * @return {string} 26670 * The source MIME type 26671 */ 26672 ; 26673 26674 _proto.currentType = function currentType() { 26675 return this.currentSource() && this.currentSource().type || ''; 26676 } 26677 /** 26678 * Get or set the preload attribute 26679 * 26680 * @param {boolean} [value] 26681 * - true means that we should preload 26682 * - false means that we should not preload 26683 * 26684 * @return {string} 26685 * The preload attribute value when getting 26686 */ 26687 ; 26688 26689 _proto.preload = function preload(value) { 26690 if (value !== undefined) { 26691 this.techCall_('setPreload', value); 26692 this.options_.preload = value; 26693 return; 26694 } 26695 26696 return this.techGet_('preload'); 26697 } 26698 /** 26699 * Get or set the autoplay option. When this is a boolean it will 26700 * modify the attribute on the tech. When this is a string the attribute on 26701 * the tech will be removed and `Player` will handle autoplay on loadstarts. 26702 * 26703 * @param {boolean|string} [value] 26704 * - true: autoplay using the browser behavior 26705 * - false: do not autoplay 26706 * - 'play': call play() on every loadstart 26707 * - 'muted': call muted() then play() on every loadstart 26708 * - 'any': call play() on every loadstart. if that fails call muted() then play(). 26709 * - *: values other than those listed here will be set `autoplay` to true 26710 * 26711 * @return {boolean|string} 26712 * The current value of autoplay when getting 26713 */ 26714 ; 26715 26716 _proto.autoplay = function autoplay(value) { 26717 // getter usage 26718 if (value === undefined) { 26719 return this.options_.autoplay || false; 26720 } 26721 26722 var techAutoplay; // if the value is a valid string set it to that 26723 26724 if (typeof value === 'string' && /(any|play|muted)/.test(value)) { 26725 this.options_.autoplay = value; 26726 this.manualAutoplay_(value); 26727 techAutoplay = false;
vendor: 26,217 bytes, lines 26727-27517
26727 // any falsy value sets autoplay to false in the browser, 26728 // lets do the same 26729 } else if (!value) { 26730 this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true 26731 } else { 26732 this.options_.autoplay = true; 26733 } 26734 26735 techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up 26736 // a setAutoplay call on tech ready. We do this because the 26737 // autoplay option will be passed in the constructor and we 26738 // do not need to set it twice 26739 26740 if (this.tech_) { 26741 this.techCall_('setAutoplay', techAutoplay); 26742 } 26743 } 26744 /** 26745 * Set or unset the playsinline attribute. 26746 * Playsinline tells the browser that non-fullscreen playback is preferred. 26747 * 26748 * @param {boolean} [value] 26749 * - true means that we should try to play inline by default 26750 * - false means that we should use the browser's default playback mode, 26751 * which in most cases is inline. iOS Safari is a notable exception 26752 * and plays fullscreen by default. 26753 * 26754 * @return {string|Player} 26755 * - the current value of playsinline 26756 * - the player when setting 26757 * 26758 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 26759 */ 26760 ; 26761 26762 _proto.playsinline = function playsinline(value) { 26763 if (value !== undefined) { 26764 this.techCall_('setPlaysinline', value); 26765 this.options_.playsinline = value; 26766 return this; 26767 } 26768 26769 return this.techGet_('playsinline'); 26770 } 26771 /** 26772 * Get or set the loop attribute on the video element. 26773 * 26774 * @param {boolean} [value] 26775 * - true means that we should loop the video 26776 * - false means that we should not loop the video 26777 * 26778 * @return {boolean} 26779 * The current value of loop when getting 26780 */ 26781 ; 26782 26783 _proto.loop = function loop(value) { 26784 if (value !== undefined) { 26785 this.techCall_('setLoop', value); 26786 this.options_.loop = value; 26787 return; 26788 } 26789 26790 return this.techGet_('loop'); 26791 } 26792 /** 26793 * Get or set the poster image source url 26794 * 26795 * @fires Player#posterchange 26796 * 26797 * @param {string} [src] 26798 * Poster image source URL 26799 * 26800 * @return {string} 26801 * The current value of poster when getting 26802 */ 26803 ; 26804 26805 _proto.poster = function poster(src) { 26806 if (src === undefined) { 26807 return this.poster_; 26808 } // The correct way to remove a poster is to set as an empty string 26809 // other falsey values will throw errors 26810 26811 26812 if (!src) { 26813 src = ''; 26814 } 26815 26816 if (src === this.poster_) { 26817 return; 26818 } // update the internal poster variable 26819 26820 26821 this.poster_ = src; // update the tech's poster 26822 26823 this.techCall_('setPoster', src); 26824 this.isPosterFromTech_ = false; // alert components that the poster has been set 26825 26826 /** 26827 * This event fires when the poster image is changed on the player. 26828 * 26829 * @event Player#posterchange 26830 * @type {EventTarget~Event} 26831 */ 26832 26833 this.trigger('posterchange'); 26834 } 26835 /** 26836 * Some techs (e.g. YouTube) can provide a poster source in an 26837 * asynchronous way. We want the poster component to use this 26838 * poster source so that it covers up the tech's controls. 26839 * (YouTube's play button). However we only want to use this 26840 * source if the player user hasn't set a poster through 26841 * the normal APIs. 26842 * 26843 * @fires Player#posterchange 26844 * @listens Tech#posterchange 26845 * @private 26846 */ 26847 ; 26848 26849 _proto.handleTechPosterChange_ = function handleTechPosterChange_() { 26850 if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) { 26851 var newPoster = this.tech_.poster() || ''; 26852 26853 if (newPoster !== this.poster_) { 26854 this.poster_ = newPoster; 26855 this.isPosterFromTech_ = true; // Let components know the poster has changed 26856 26857 this.trigger('posterchange'); 26858 } 26859 } 26860 } 26861 /** 26862 * Get or set whether or not the controls are showing. 26863 * 26864 * @fires Player#controlsenabled 26865 * 26866 * @param {boolean} [bool] 26867 * - true to turn controls on 26868 * - false to turn controls off 26869 * 26870 * @return {boolean} 26871 * The current value of controls when getting 26872 */ 26873 ; 26874 26875 _proto.controls = function controls(bool) { 26876 if (bool === undefined) { 26877 return !!this.controls_; 26878 } 26879 26880 bool = !!bool; // Don't trigger a change event unless it actually changed 26881 26882 if (this.controls_ === bool) { 26883 return; 26884 } 26885 26886 this.controls_ = bool; 26887 26888 if (this.usingNativeControls()) { 26889 this.techCall_('setControls', bool); 26890 } 26891 26892 if (this.controls_) { 26893 this.removeClass('vjs-controls-disabled'); 26894 this.addClass('vjs-controls-enabled'); 26895 /** 26896 * @event Player#controlsenabled 26897 * @type {EventTarget~Event} 26898 */ 26899 26900 this.trigger('controlsenabled'); 26901 26902 if (!this.usingNativeControls()) { 26903 this.addTechControlsListeners_(); 26904 } 26905 } else { 26906 this.removeClass('vjs-controls-enabled'); 26907 this.addClass('vjs-controls-disabled'); 26908 /** 26909 * @event Player#controlsdisabled 26910 * @type {EventTarget~Event} 26911 */ 26912 26913 this.trigger('controlsdisabled'); 26914 26915 if (!this.usingNativeControls()) { 26916 this.removeTechControlsListeners_(); 26917 } 26918 } 26919 } 26920 /** 26921 * Toggle native controls on/off. Native controls are the controls built into 26922 * devices (e.g. default iPhone controls), Flash, or other techs 26923 * (e.g. Vimeo Controls) 26924 * **This should only be set by the current tech, because only the tech knows 26925 * if it can support native controls** 26926 * 26927 * @fires Player#usingnativecontrols 26928 * @fires Player#usingcustomcontrols 26929 * 26930 * @param {boolean} [bool] 26931 * - true to turn native controls on 26932 * - false to turn native controls off 26933 * 26934 * @return {boolean} 26935 * The current value of native controls when getting 26936 */ 26937 ; 26938 26939 _proto.usingNativeControls = function usingNativeControls(bool) { 26940 if (bool === undefined) { 26941 return !!this.usingNativeControls_; 26942 } 26943 26944 bool = !!bool; // Don't trigger a change event unless it actually changed 26945 26946 if (this.usingNativeControls_ === bool) { 26947 return; 26948 } 26949 26950 this.usingNativeControls_ = bool; 26951 26952 if (this.usingNativeControls_) { 26953 this.addClass('vjs-using-native-controls'); 26954 /** 26955 * player is using the native device controls 26956 * 26957 * @event Player#usingnativecontrols 26958 * @type {EventTarget~Event} 26959 */ 26960 26961 this.trigger('usingnativecontrols'); 26962 } else { 26963 this.removeClass('vjs-using-native-controls'); 26964 /** 26965 * player is using the custom HTML controls 26966 * 26967 * @event Player#usingcustomcontrols 26968 * @type {EventTarget~Event} 26969 */ 26970 26971 this.trigger('usingcustomcontrols'); 26972 } 26973 } 26974 /** 26975 * Set or get the current MediaError 26976 * 26977 * @fires Player#error 26978 * 26979 * @param {MediaError|string|number} [err] 26980 * A MediaError or a string/number to be turned 26981 * into a MediaError 26982 * 26983 * @return {MediaError|null} 26984 * The current MediaError when getting (or null) 26985 */ 26986 ; 26987 26988 _proto.error = function error(err) { 26989 if (err === undefined) { 26990 return this.error_ || null; 26991 } // Suppress the first error message for no compatible source until 26992 // user interaction 26993 26994 26995 if (this.options_.suppressNotSupportedError && err && err.code === 4) { 26996 var triggerSuppressedError = function triggerSuppressedError() { 26997 this.error(err); 26998 }; 26999 27000 this.options_.suppressNotSupportedError = false; 27001 this.any(['click', 'touchstart'], triggerSuppressedError); 27002 this.one('loadstart', function () { 27003 this.off(['click', 'touchstart'], triggerSuppressedError); 27004 }); 27005 return; 27006 } // restoring to default 27007 27008 27009 if (err === null) { 27010 this.error_ = err; 27011 this.removeClass('vjs-error'); 27012 27013 if (this.errorDisplay) { 27014 this.errorDisplay.close(); 27015 } 27016 27017 return; 27018 } 27019 27020 this.error_ = new MediaError(err); // add the vjs-error classname to the player 27021 27022 this.addClass('vjs-error'); // log the name of the error type and any message 27023 // IE11 logs "[object object]" and required you to expand message to see error object 27024 27025 log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_); 27026 /** 27027 * @event Player#error 27028 * @type {EventTarget~Event} 27029 */ 27030 27031 this.trigger('error'); 27032 return; 27033 } 27034 /** 27035 * Report user activity 27036 * 27037 * @param {Object} event 27038 * Event object 27039 */ 27040 ; 27041 27042 _proto.reportUserActivity = function reportUserActivity(event) { 27043 this.userActivity_ = true; 27044 } 27045 /** 27046 * Get/set if user is active 27047 * 27048 * @fires Player#useractive 27049 * @fires Player#userinactive 27050 * 27051 * @param {boolean} [bool] 27052 * - true if the user is active 27053 * - false if the user is inactive 27054 * 27055 * @return {boolean} 27056 * The current value of userActive when getting 27057 */ 27058 ; 27059 27060 _proto.userActive = function userActive(bool) { 27061 if (bool === undefined) { 27062 return this.userActive_; 27063 } 27064 27065 bool = !!bool; 27066 27067 if (bool === this.userActive_) { 27068 return; 27069 } 27070 27071 this.userActive_ = bool; 27072 27073 if (this.userActive_) { 27074 this.userActivity_ = true; 27075 this.removeClass('vjs-user-inactive'); 27076 this.addClass('vjs-user-active'); 27077 /** 27078 * @event Player#useractive 27079 * @type {EventTarget~Event} 27080 */ 27081 27082 this.trigger('useractive'); 27083 return; 27084 } // Chrome/Safari/IE have bugs where when you change the cursor it can 27085 // trigger a mousemove event. This causes an issue when you're hiding 27086 // the cursor when the user is inactive, and a mousemove signals user 27087 // activity. Making it impossible to go into inactive mode. Specifically 27088 // this happens in fullscreen when we really need to hide the cursor. 27089 // 27090 // When this gets resolved in ALL browsers it can be removed 27091 // https://code.google.com/p/chromium/issues/detail?id=103041 27092 27093 27094 if (this.tech_) { 27095 this.tech_.one('mousemove', function (e) { 27096 e.stopPropagation(); 27097 e.preventDefault(); 27098 }); 27099 } 27100 27101 this.userActivity_ = false; 27102 this.removeClass('vjs-user-active'); 27103 this.addClass('vjs-user-inactive'); 27104 /** 27105 * @event Player#userinactive 27106 * @type {EventTarget~Event} 27107 */ 27108 27109 this.trigger('userinactive'); 27110 } 27111 /** 27112 * Listen for user activity based on timeout value 27113 * 27114 * @private 27115 */ 27116 ; 27117 27118 _proto.listenForUserActivity_ = function listenForUserActivity_() { 27119 var mouseInProgress; 27120 var lastMoveX; 27121 var lastMoveY; 27122 var handleActivity = bind(this, this.reportUserActivity); 27123 27124 var handleMouseMove = function handleMouseMove(e) { 27125 // #1068 - Prevent mousemove spamming 27126 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 27127 if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { 27128 lastMoveX = e.screenX; 27129 lastMoveY = e.screenY; 27130 handleActivity(); 27131 } 27132 }; 27133 27134 var handleMouseDown = function handleMouseDown() { 27135 handleActivity(); // For as long as the they are touching the device or have their mouse down, 27136 // we consider them active even if they're not moving their finger or mouse. 27137 // So we want to continue to update that they are active 27138 27139 this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time 27140 // as the activityCheck interval (250) should ensure we never miss the 27141 // next activityCheck 27142 27143 mouseInProgress = this.setInterval(handleActivity, 250); 27144 }; 27145 27146 var handleMouseUpAndMouseLeave = function handleMouseUpAndMouseLeave(event) { 27147 handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down 27148 27149 this.clearInterval(mouseInProgress); 27150 }; // Any mouse movement will be considered user activity 27151 27152 27153 this.on('mousedown', handleMouseDown); 27154 this.on('mousemove', handleMouseMove); 27155 this.on('mouseup', handleMouseUpAndMouseLeave); 27156 this.on('mouseleave', handleMouseUpAndMouseLeave); 27157 var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed) 27158 // controlBar would no longer be hidden by default timeout. 27159 27160 if (controlBar && !IS_IOS && !IS_ANDROID) { 27161 controlBar.on('mouseenter', function (event) { 27162 this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout; 27163 this.player().options_.inactivityTimeout = 0; 27164 }); 27165 controlBar.on('mouseleave', function (event) { 27166 this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout; 27167 }); 27168 } // Listen for keyboard navigation 27169 // Shouldn't need to use inProgress interval because of key repeat 27170 27171 27172 this.on('keydown', handleActivity); 27173 this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into 27174 // the mousemove/touchmove function itself, to prevent performance degradation. 27175 // `this.reportUserActivity` simply sets this.userActivity_ to true, which 27176 // then gets picked up by this loop 27177 // http://ejohn.org/blog/learning-from-twitter/ 27178 27179 var inactivityTimeout; 27180 this.setInterval(function () { 27181 // Check to see if mouse/touch activity has happened 27182 if (!this.userActivity_) { 27183 return; 27184 } // Reset the activity tracker 27185 27186 27187 this.userActivity_ = false; // If the user state was inactive, set the state to active 27188 27189 this.userActive(true); // Clear any existing inactivity timeout to start the timer over 27190 27191 this.clearTimeout(inactivityTimeout); 27192 var timeout = this.options_.inactivityTimeout; 27193 27194 if (timeout <= 0) { 27195 return; 27196 } // In <timeout> milliseconds, if no more activity has occurred the 27197 // user will be considered inactive 27198 27199 27200 inactivityTimeout = this.setTimeout(function () { 27201 // Protect against the case where the inactivityTimeout can trigger just 27202 // before the next user activity is picked up by the activity check loop 27203 // causing a flicker 27204 if (!this.userActivity_) { 27205 this.userActive(false); 27206 } 27207 }, timeout); 27208 }, 250); 27209 } 27210 /** 27211 * Gets or sets the current playback rate. A playback rate of 27212 * 1.0 represents normal speed and 0.5 would indicate half-speed 27213 * playback, for instance. 27214 * 27215 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate 27216 * 27217 * @param {number} [rate] 27218 * New playback rate to set. 27219 * 27220 * @return {number} 27221 * The current playback rate when getting or 1.0 27222 */ 27223 ; 27224 27225 _proto.playbackRate = function playbackRate(rate) { 27226 if (rate !== undefined) { 27227 // NOTE: this.cache_.lastPlaybackRate is set from the tech handler 27228 // that is registered above 27229 this.techCall_('setPlaybackRate', rate); 27230 return; 27231 } 27232 27233 if (this.tech_ && this.tech_.featuresPlaybackRate) { 27234 return this.cache_.lastPlaybackRate || this.techGet_('playbackRate'); 27235 } 27236 27237 return 1.0; 27238 } 27239 /** 27240 * Gets or sets the current default playback rate. A default playback rate of 27241 * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. 27242 * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not 27243 * not the current playbackRate. 27244 * 27245 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate 27246 * 27247 * @param {number} [rate] 27248 * New default playback rate to set. 27249 * 27250 * @return {number|Player} 27251 * - The default playback rate when getting or 1.0 27252 * - the player when setting 27253 */ 27254 ; 27255 27256 _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) { 27257 if (rate !== undefined) { 27258 return this.techCall_('setDefaultPlaybackRate', rate); 27259 } 27260 27261 if (this.tech_ && this.tech_.featuresPlaybackRate) { 27262 return this.techGet_('defaultPlaybackRate'); 27263 } 27264 27265 return 1.0; 27266 } 27267 /** 27268 * Gets or sets the audio flag 27269 * 27270 * @param {boolean} bool 27271 * - true signals that this is an audio player 27272 * - false signals that this is not an audio player 27273 * 27274 * @return {boolean} 27275 * The current value of isAudio when getting 27276 */ 27277 ; 27278 27279 _proto.isAudio = function isAudio(bool) { 27280 if (bool !== undefined) { 27281 this.isAudio_ = !!bool; 27282 return; 27283 } 27284 27285 return !!this.isAudio_; 27286 } 27287 /** 27288 * A helper method for adding a {@link TextTrack} to our 27289 * {@link TextTrackList}. 27290 * 27291 * In addition to the W3C settings we allow adding additional info through options. 27292 * 27293 * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack 27294 * 27295 * @param {string} [kind] 27296 * the kind of TextTrack you are adding 27297 * 27298 * @param {string} [label] 27299 * the label to give the TextTrack label 27300 * 27301 * @param {string} [language] 27302 * the language to set on the TextTrack 27303 * 27304 * @return {TextTrack|undefined} 27305 * the TextTrack that was added or undefined 27306 * if there is no tech 27307 */ 27308 ; 27309 27310 _proto.addTextTrack = function addTextTrack(kind, label, language) { 27311 if (this.tech_) { 27312 return this.tech_.addTextTrack(kind, label, language); 27313 } 27314 } 27315 /** 27316 * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. 27317 * When manualCleanup is set to false, the track will be automatically removed 27318 * on source changes. 27319 * 27320 * @param {Object} options 27321 * Options to pass to {@link HTMLTrackElement} during creation. See 27322 * {@link HTMLTrackElement} for object properties that you should use. 27323 * 27324 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 27325 * removed on a source change 27326 * 27327 * @return {HtmlTrackElement} 27328 * the HTMLTrackElement that was created and added 27329 * to the HtmlTrackElementList and the remote 27330 * TextTrackList 27331 * 27332 * @deprecated The default value of the "manualCleanup" parameter will default 27333 * to "false" in upcoming versions of Video.js 27334 */ 27335 ; 27336 27337 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 27338 if (this.tech_) { 27339 return this.tech_.addRemoteTextTrack(options, manualCleanup); 27340 } 27341 } 27342 /** 27343 * Remove a remote {@link TextTrack} from the respective 27344 * {@link TextTrackList} and {@link HtmlTrackElementList}. 27345 * 27346 * @param {Object} track 27347 * Remote {@link TextTrack} to remove 27348 * 27349 * @return {undefined} 27350 * does not return anything 27351 */ 27352 ; 27353 27354 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) { 27355 if (obj === void 0) { 27356 obj = {}; 27357 } 27358 27359 var _obj = obj, 27360 track = _obj.track; 27361 27362 if (!track) { 27363 track = obj; 27364 } // destructure the input into an object with a track argument, defaulting to arguments[0] 27365 // default the whole argument to an empty object if nothing was passed in 27366 27367 27368 if (this.tech_) { 27369 return this.tech_.removeRemoteTextTrack(track); 27370 } 27371 } 27372 /** 27373 * Gets available media playback quality metrics as specified by the W3C's Media 27374 * Playback Quality API. 27375 * 27376 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 27377 * 27378 * @return {Object|undefined} 27379 * An object with supported media playback quality metrics or undefined if there 27380 * is no tech or the tech does not support it. 27381 */ 27382 ; 27383 27384 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 27385 return this.techGet_('getVideoPlaybackQuality'); 27386 } 27387 /** 27388 * Get video width 27389 * 27390 * @return {number} 27391 * current video width 27392 */ 27393 ; 27394 27395 _proto.videoWidth = function videoWidth() { 27396 return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; 27397 } 27398 /** 27399 * Get video height 27400 * 27401 * @return {number} 27402 * current video height 27403 */ 27404 ; 27405 27406 _proto.videoHeight = function videoHeight() { 27407 return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; 27408 } 27409 /** 27410 * The player's language code 27411 * NOTE: The language should be set in the player options if you want the 27412 * the controls to be built with a specific language. Changing the language 27413 * later will not update controls text. 27414 * 27415 * @param {string} [code] 27416 * the language code to set the player to 27417 * 27418 * @return {string} 27419 * The current language code when getting 27420 */ 27421 ; 27422 27423 _proto.language = function language(code) { 27424 if (code === undefined) { 27425 return this.language_; 27426 } 27427 27428 this.language_ = String(code).toLowerCase(); 27429 } 27430 /** 27431 * Get the player's language dictionary 27432 * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time 27433 * Languages specified directly in the player options have precedence 27434 * 27435 * @return {Array} 27436 * An array of of supported languages 27437 */ 27438 ; 27439 27440 _proto.languages = function languages() { 27441 return mergeOptions(Player.prototype.options_.languages, this.languages_); 27442 } 27443 /** 27444 * returns a JavaScript object reperesenting the current track 27445 * information. **DOES not return it as JSON** 27446 * 27447 * @return {Object} 27448 * Object representing the current of track info 27449 */ 27450 ; 27451 27452 _proto.toJSON = function toJSON() { 27453 var options = mergeOptions(this.options_); 27454 var tracks = options.tracks; 27455 options.tracks = []; 27456 27457 for (var i = 0; i < tracks.length; i++) { 27458 var track = tracks[i]; // deep merge tracks and null out player so no circular references 27459 27460 track = mergeOptions(track); 27461 track.player = undefined; 27462 options.tracks[i] = track; 27463 } 27464 27465 return options; 27466 } 27467 /** 27468 * Creates a simple modal dialog (an instance of the {@link ModalDialog} 27469 * component) that immediately overlays the player with arbitrary 27470 * content and removes itself when closed. 27471 * 27472 * @param {string|Function|Element|Array|null} content 27473 * Same as {@link ModalDialog#content}'s param of the same name. 27474 * The most straight-forward usage is to provide a string or DOM 27475 * element. 27476 * 27477 * @param {Object} [options] 27478 * Extra options which will be passed on to the {@link ModalDialog}. 27479 * 27480 * @return {ModalDialog} 27481 * the {@link ModalDialog} that was created 27482 */ 27483 ; 27484 27485 _proto.createModal = function createModal(content, options) { 27486 var _this16 = this; 27487 27488 options = options || {}; 27489 options.content = content || ''; 27490 var modal = new ModalDialog(this, options); 27491 this.addChild(modal); 27492 modal.on('dispose', function () { 27493 _this16.removeChild(modal); 27494 }); 27495 modal.open(); 27496 return modal; 27497 } 27498 /** 27499 * Change breakpoint classes when the player resizes. 27500 * 27501 * @private 27502 */ 27503 ; 27504 27505 _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() { 27506 if (!this.responsive()) { 27507 return; 27508 } 27509 27510 var currentBreakpoint = this.currentBreakpoint(); 27511 var currentWidth = this.currentWidth(); 27512 27513 for (var i = 0; i < BREAKPOINT_ORDER.length; i++) { 27514 var candidateBreakpoint = BREAKPOINT_ORDER[i]; 27515 var maxWidth = this.breakpoints_[candidateBreakpoint]; 27516 27517 if (currentWidth <= maxWidth) {
27518 // The current breakpoint did not change, nothing to do. 27519 if (currentBreakpoint === candidateBreakpoint) { 27520 return; 27521 } // Only remove a class if there is a current breakpoint. 27522 27523 27524 if (currentBreakpoint) { 27525 this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]); 27526 } 27527 27528 this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]); 27529 this.breakpoint_ = candidateBreakpoint; 27530 break; 27531 } 27532 } 27533 } 27534 /** 27535 * Removes the current breakpoint. 27536 * 27537 * @private 27538 */ 27539 ; 27540 27541 _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() { 27542 var className = this.currentBreakpointClass(); 27543 this.breakpoint_ = ''; 27544 27545 if (className) { 27546 this.removeClass(className); 27547 } 27548 } 27549 /** 27550 * Get or set breakpoints on the player. 27551 * 27552 * Calling this method with an object or `true` will remove any previous 27553 * custom breakpoints and start from the defaults again. 27554 * 27555 * @param {Object|boolean} [breakpoints] 27556 * If an object is given, it can be used to provide custom 27557 * breakpoints. If `true` is given, will set default breakpoints. 27558 * If this argument is not given, will simply return the current 27559 * breakpoints. 27560 * 27561 * @param {number} [breakpoints.tiny] 27562 * The maximum width for the "vjs-layout-tiny" class. 27563 * 27564 * @param {number} [breakpoints.xsmall] 27565 * The maximum width for the "vjs-layout-x-small" class. 27566 * 27567 * @param {number} [breakpoints.small] 27568 * The maximum width for the "vjs-layout-small" class. 27569 * 27570 * @param {number} [breakpoints.medium] 27571 * The maximum width for the "vjs-layout-medium" class. 27572 * 27573 * @param {number} [breakpoints.large] 27574 * The maximum width for the "vjs-layout-large" class. 27575 * 27576 * @param {number} [breakpoints.xlarge] 27577 * The maximum width for the "vjs-layout-x-large" class. 27578 * 27579 * @param {number} [breakpoints.huge] 27580 * The maximum width for the "vjs-layout-huge" class. 27581 * 27582 * @return {Object} 27583 * An object mapping breakpoint names to maximum width values. 27584 */ 27585 ; 27586 27587 _proto.breakpoints = function breakpoints(_breakpoints) { 27588 // Used as a getter. 27589 if (_breakpoints === undefined) { 27590 return assign(this.breakpoints_); 27591 } 27592 27593 this.breakpoint_ = ''; 27594 this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently 27595 // selected breakpoint. 27596 27597 this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning. 27598 27599 return assign(this.breakpoints_); 27600 } 27601 /** 27602 * Get or set a flag indicating whether or not this player should adjust 27603 * its UI based on its dimensions. 27604 * 27605 * @param {boolean} value 27606 * Should be `true` if the player should adjust its UI based on its 27607 * dimensions; otherwise, should be `false`. 27608 * 27609 * @return {boolean} 27610 * Will be `true` if this player should adjust its UI based on its 27611 * dimensions; otherwise, will be `false`. 27612 */ 27613 ; 27614 27615 _proto.responsive = function responsive(value) { 27616 // Used as a getter. 27617 if (value === undefined) { 27618 return this.responsive_; 27619 } 27620 27621 value = Boolean(value); 27622 var current = this.responsive_; // Nothing changed. 27623 27624 if (value === current) { 27625 return; 27626 } // The value actually changed, set it. 27627 27628 27629 this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the 27630 // player is now responsive. 27631 27632 if (value) { 27633 this.on('playerresize', this.updateCurrentBreakpoint_); 27634 this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive. 27635 } else { 27636 this.off('playerresize', this.updateCurrentBreakpoint_); 27637 this.removeCurrentBreakpoint_(); 27638 } 27639 27640 return value; 27641 } 27642 /** 27643 * Get current breakpoint name, if any. 27644 * 27645 * @return {string} 27646 * If there is currently a breakpoint set, returns a the key from the 27647 * breakpoints object matching it. Otherwise, returns an empty string. 27648 */ 27649 ; 27650 27651 _proto.currentBreakpoint = function currentBreakpoint() { 27652 return this.breakpoint_; 27653 } 27654 /** 27655 * Get the current breakpoint class name. 27656 * 27657 * @return {string} 27658 * The matching class name (e.g. `"vjs-layout-tiny"` or 27659 * `"vjs-layout-large"`) for the current breakpoint. Empty string if 27660 * there is no current breakpoint. 27661 */ 27662 ; 27663 27664 _proto.currentBreakpointClass = function currentBreakpointClass() { 27665 return BREAKPOINT_CLASSES[this.breakpoint_] || ''; 27666 } 27667 /** 27668 * An object that describes a single piece of media. 27669 * 27670 * Properties that are not part of this type description will be retained; so, 27671 * this can be viewed as a generic metadata storage mechanism as well. 27672 * 27673 * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface} 27674 * @typedef {Object} Player~MediaObject 27675 * 27676 * @property {string} [album] 27677 * Unused, except if this object is passed to the `MediaSession` 27678 * API. 27679 * 27680 * @property {string} [artist] 27681 * Unused, except if this object is passed to the `MediaSession` 27682 * API. 27683 * 27684 * @property {Object[]} [artwork] 27685 * Unused, except if this object is passed to the `MediaSession` 27686 * API. If not specified, will be populated via the `poster`, if 27687 * available. 27688 * 27689 * @property {string} [poster] 27690 * URL to an image that will display before playback. 27691 * 27692 * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src] 27693 * A single source object, an array of source objects, or a string
27694 * referencing a URL to a media source. It is _highly recommended_ 27695 * that an object or array of objects is used here, so that source 27696 * selection algorithms can take the `type` into account. 27697 * 27698 * @property {string} [title] 27699 * Unused, except if this object is passed to the `MediaSession` 27700 * API. 27701 * 27702 * @property {Object[]} [textTracks] 27703 * An array of objects to be used to create text tracks, following 27704 * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}. 27705 * For ease of removal, these will be created as "remote" text 27706 * tracks and set to automatically clean up on source changes. 27707 * 27708 * These objects may have properties like `src`, `kind`, `label`, 27709 * and `language`, see {@link Tech#createRemoteTextTrack}. 27710 */ 27711 27712 /** 27713 * Populate the player using a {@link Player~MediaObject|MediaObject}. 27714 * 27715 * @param {Player~MediaObject} media 27716 * A media object. 27717 * 27718 * @param {Function} ready 27719 * A callback to be called when the player is ready. 27720 */ 27721 ; 27722 27723 _proto.loadMedia = function loadMedia(media, ready) { 27724 var _this17 = this; 27725 27726 if (!media || typeof media !== 'object') { 27727 return; 27728 } 27729 27730 this.reset(); // Clone the media object so it cannot be mutated from outside. 27731 27732 this.cache_.media = mergeOptions(media); 27733 var _this$cache_$media = this.cache_.media, 27734 artwork = _this$cache_$media.artwork, 27735 poster = _this$cache_$media.poster, 27736 src = _this$cache_$media.src, 27737 textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`. 27738 27739 if (!artwork && poster) { 27740 this.cache_.media.artwork = [{ 27741 src: poster, 27742 type: getMimetype(poster) 27743 }]; 27744 } 27745 27746 if (src) { 27747 this.src(src); 27748 } 27749 27750 if (poster) { 27751 this.poster(poster); 27752 } 27753 27754 if (Array.isArray(textTracks)) { 27755 textTracks.forEach(function (tt) { 27756 return _this17.addRemoteTextTrack(tt, false); 27757 }); 27758 } 27759 27760 this.ready(ready); 27761 } 27762 /** 27763 * Get a clone of the current {@link Player~MediaObject} for this player. 27764 * 27765 * If the `loadMedia` method has not been used, will attempt to return a 27766 * {@link Player~MediaObject} based on the current state of the player. 27767 * 27768 * @return {Player~MediaObject} 27769 */ 27770 ; 27771 27772 _proto.getMedia = function getMedia() { 27773 if (!this.cache_.media) { 27774 var poster = this.poster(); 27775 var src = this.currentSources(); 27776 var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) { 27777 return { 27778 kind: tt.kind, 27779 label: tt.label, 27780 language: tt.language, 27781 src: tt.src 27782 }; 27783 }); 27784 var media = { 27785 src: src, 27786 textTracks: textTracks 27787 }; 27788 27789 if (poster) { 27790 media.poster = poster; 27791 media.artwork = [{ 27792 src: media.poster, 27793 type: getMimetype(media.poster) 27794 }]; 27795 } 27796 27797 return media; 27798 } 27799 27800 return mergeOptions(this.cache_.media); 27801 } 27802 /** 27803 * Gets tag settings 27804 * 27805 * @param {Element} tag 27806 * The player tag 27807 * 27808 * @return {Object} 27809 * An object containing all of the settings 27810 * for a player tag 27811 */ 27812 ; 27813 27814 Player.getTagSettings = function getTagSettings(tag) { 27815 var baseOptions = { 27816 sources: [], 27817 tracks: [] 27818 }; 27819 var tagOptions = getAttributes(tag); 27820 var dataSetup = tagOptions['data-setup']; 27821 27822 if (hasClass(tag, 'vjs-fill')) { 27823 tagOptions.fill = true; 27824 } 27825 27826 if (hasClass(tag, 'vjs-fluid')) { 27827 tagOptions.fluid = true; 27828 } // Check if data-setup attr exists. 27829 27830 27831 if (dataSetup !== null) { 27832 // Parse options JSON 27833 // If empty string, make it a parsable json object. 27834 var _safeParseTuple = tuple(dataSetup || '{}'), 27835 err = _safeParseTuple[0], 27836 data = _safeParseTuple[1]; 27837 27838 if (err) { 27839 log.error(err); 27840 } 27841 27842 assign(tagOptions, data); 27843 } 27844 27845 assign(baseOptions, tagOptions); // Get tag children settings 27846 27847 if (tag.hasChildNodes()) { 27848 var children = tag.childNodes; 27849 27850 for (var i = 0, j = children.length; i < j; i++) { 27851 var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ 27852 27853 var childName = child.nodeName.toLowerCase(); 27854 27855 if (childName === 'source') { 27856 baseOptions.sources.push(getAttributes(child)); 27857 } else if (childName === 'track') { 27858 baseOptions.tracks.push(getAttributes(child)); 27859 } 27860 } 27861 } 27862 27863 return baseOptions; 27864 } 27865 /** 27866 * Determine whether or not flexbox is supported 27867 * 27868 * @return {boolean} 27869 * - true if flexbox is supported 27870 * - false if flexbox is not supported 27871 */ 27872 ; 27873 27874 _proto.flexNotSupported_ = function flexNotSupported_() { 27875 var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more 27876 // common flex features that we can rely on when checking for flex support. 27877 27878 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 27879 'msFlexOrder' in elem.style); 27880 }; 27881 27882 return Player; 27883 }(Component); 27884 /** 27885 * Get the {@link VideoTrackList} 27886 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist 27887 * 27888 * @return {VideoTrackList} 27889 * the current video track list 27890 * 27891 * @method Player.prototype.videoTracks 27892 */ 27893 27894 /** 27895 * Get the {@link AudioTrackList} 27896 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist 27897 * 27898 * @return {AudioTrackList} 27899 * the current audio track list 27900 * 27901 * @method Player.prototype.audioTracks 27902 */ 27903 27904 /** 27905 * Get the {@link TextTrackList} 27906 * 27907 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks 27908 * 27909 * @return {TextTrackList} 27910 * the current text track list 27911 * 27912 * @method Player.prototype.textTracks 27913 */ 27914 27915 /** 27916 * Get the remote {@link TextTrackList} 27917 * 27918 * @return {TextTrackList} 27919 * The current remote text track list 27920 * 27921 * @method Player.prototype.remoteTextTracks 27922 */ 27923 27924 /** 27925 * Get the remote {@link HtmlTrackElementList} tracks. 27926 * 27927 * @return {HtmlTrackElementList} 27928 * The current remote text track element list 27929 * 27930 * @method Player.prototype.remoteTextTrackEls 27931 */ 27932 27933
vendor: 4,629 bytes, lines 27934-28059
27934 ALL.names.forEach(function (name) { 27935 var props = ALL[name]; 27936 27937 Player.prototype[props.getterName] = function () { 27938 if (this.tech_) { 27939 return this.tech_[props.getterName](); 27940 } // if we have not yet loadTech_, we create {video,audio,text}Tracks_ 27941 // these will be passed to the tech during loading 27942 27943 27944 this[props.privateName] = this[props.privateName] || new props.ListClass(); 27945 return this[props.privateName]; 27946 }; 27947 }); 27948 /** 27949 * Get or set the `Player`'s crossorigin option. For the HTML5 player, this 27950 * sets the `crossOrigin` property on the `<video>` tag to control the CORS 27951 * behavior. 27952 * 27953 * @see [Video Element Attributes]{@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-crossorigin} 27954 * 27955 * @param {string} [value] 27956 * The value to set the `Player`'s crossorigin to. If an argument is 27957 * given, must be one of `anonymous` or `use-credentials`. 27958 * 27959 * @return {string|undefined} 27960 * - The current crossorigin value of the `Player` when getting. 27961 * - undefined when setting 27962 */ 27963 27964 Player.prototype.crossorigin = Player.prototype.crossOrigin; 27965 /** 27966 * Global enumeration of players. 27967 * 27968 * The keys are the player IDs and the values are either the {@link Player} 27969 * instance or `null` for disposed players. 27970 * 27971 * @type {Object} 27972 */ 27973 27974 Player.players = {}; 27975 var navigator = window$3.navigator; 27976 /* 27977 * Player instance options, surfaced using options 27978 * options = Player.prototype.options_ 27979 * Make changes in options, not here. 27980 * 27981 * @type {Object} 27982 * @private 27983 */ 27984 27985 Player.prototype.options_ = { 27986 // Default order of fallback technology 27987 techOrder: Tech.defaultTechOrder_, 27988 html5: {}, 27989 flash: {}, 27990 // default inactivity timeout 27991 inactivityTimeout: 2000, 27992 // default playback rates 27993 playbackRates: [], 27994 // Add playback rate selection by adding rates 27995 // 'playbackRates': [0.5, 1, 1.5, 2], 27996 liveui: false, 27997 // Included control sets 27998 children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'], 27999 language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', 28000 // locales and their language translations 28001 languages: {}, 28002 // Default message to show when a video cannot be played. 28003 notSupportedMessage: 'No compatible source was found for this media.', 28004 fullscreen: { 28005 options: { 28006 navigationUI: 'hide' 28007 } 28008 }, 28009 breakpoints: {}, 28010 responsive: false 28011 }; 28012 [ 28013 /** 28014 * Returns whether or not the player is in the "ended" state. 28015 * 28016 * @return {Boolean} True if the player is in the ended state, false if not. 28017 * @method Player#ended 28018 */ 28019 'ended', 28020 /** 28021 * Returns whether or not the player is in the "seeking" state. 28022 * 28023 * @return {Boolean} True if the player is in the seeking state, false if not. 28024 * @method Player#seeking 28025 */ 28026 'seeking', 28027 /** 28028 * Returns the TimeRanges of the media that are currently available 28029 * for seeking to. 28030 * 28031 * @return {TimeRanges} the seekable intervals of the media timeline 28032 * @method Player#seekable 28033 */ 28034 'seekable', 28035 /** 28036 * Returns the current state of network activity for the element, from 28037 * the codes in the list below. 28038 * - NETWORK_EMPTY (numeric value 0) 28039 * The element has not yet been initialised. All attributes are in 28040 * their initial states. 28041 * - NETWORK_IDLE (numeric value 1) 28042 * The element's resource selection algorithm is active and has 28043 * selected a resource, but it is not actually using the network at 28044 * this time. 28045 * - NETWORK_LOADING (numeric value 2) 28046 * The user agent is actively trying to download data. 28047 * - NETWORK_NO_SOURCE (numeric value 3) 28048 * The element's resource selection algorithm is active, but it has 28049 * not yet found a resource to use. 28050 * 28051 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states 28052 * @return {number} the current network activity state 28053 * @method Player#networkState 28054 */ 28055 'networkState', 28056 /** 28057 * Returns a value that expresses the current state of the element 28058 * with respect to rendering the current playback position, from the 28059 * codes in the list below.
28060 * - HAVE_NOTHING (numeric value 0) 28061 * No information regarding the media resource is available. 28062 * - HAVE_METADATA (numeric value 1) 28063 * Enough of the resource has been obtained that the duration of the 28064 * resource is available. 28065 * - HAVE_CURRENT_DATA (numeric value 2) 28066 * Data for the immediate current playback position is available. 28067 * - HAVE_FUTURE_DATA (numeric value 3) 28068 * Data for the immediate current playback position is available, as 28069 * well as enough data for the user agent to advance the current 28070 * playback position in the direction of playback. 28071 * - HAVE_ENOUGH_DATA (numeric value 4) 28072 * The user agent estimates that enough data is available for 28073 * playback to proceed uninterrupted. 28074 * 28075 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate 28076 * @return {number} the current playback rendering state 28077 * @method Player#readyState 28078 */ 28079 'readyState'].forEach(function (fn) { 28080 Player.prototype[fn] = function () { 28081 return this.techGet_(fn); 28082 }; 28083 }); 28084 TECH_EVENTS_RETRIGGER.forEach(function (event) { 28085 Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () { 28086 return this.trigger(event); 28087 }; 28088 }); 28089 /** 28090 * Fired when the player has initial duration and dimension information 28091 * 28092 * @event Player#loadedmetadata 28093 * @type {EventTarget~Event} 28094 */ 28095 28096 /** 28097 * Fired when the player has downloaded data at the current playback position 28098 * 28099 * @event Player#loadeddata 28100 * @type {EventTarget~Event} 28101 */ 28102 28103 /** 28104 * Fired when the current playback position has changed * 28105 * During playback this is fired every 15-250 milliseconds, depending on the 28106 * playback technology in use. 28107 * 28108 * @event Player#timeupdate 28109 * @type {EventTarget~Event} 28110 */ 28111 28112 /** 28113 * Fired when the volume changes 28114 * 28115 * @event Player#volumechange 28116 * @type {EventTarget~Event} 28117 */ 28118 28119 /** 28120 * Reports whether or not a player has a plugin available. 28121 * 28122 * This does not report whether or not the plugin has ever been initialized 28123 * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. 28124 * 28125 * @method Player#hasPlugin 28126 * @param {string} name 28127 * The name of a plugin. 28128 * 28129 * @return {boolean} 28130 * Whether or not this player has the requested plugin available. 28131 */ 28132 28133 /** 28134 * Reports whether or not a player is using a plugin by name. 28135 * 28136 * For basic plugins, this only reports whether the plugin has _ever_ been 28137 * initialized on this player. 28138 * 28139 * @method Player#usingPlugin 28140 * @param {string} name 28141 * The name of a plugin. 28142 * 28143 * @return {boolean} 28144 * Whether or not this player is using the requested plugin. 28145 */ 28146 28147 Component.registerComponent('Player', Player); 28148 28149 var setPrototypeOf = createCommonjsModule(function (module) { 28150 function _setPrototypeOf(o, p) { 28151 module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 28152 o.__proto__ = p; 28153 return o; 28154 }; 28155 28156 return _setPrototypeOf(o, p); 28157 } 28158 28159 module.exports = _setPrototypeOf; 28160 }); 28161 28162 function _isNativeReflectConstruct() { 28163 if (typeof Reflect === "undefined" || !Reflect.construct) return false; 28164 if (Reflect.construct.sham) return false; 28165 if (typeof Proxy === "function") return true; 28166 28167 try { 28168 Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 28169 return true; 28170 } catch (e) { 28171 return false; 28172 } 28173 } 28174 28175 var isNativeReflectConstruct = _isNativeReflectConstruct; 28176 28177 var construct = createCommonjsModule(function (module) { 28178 function _construct(Parent, args, Class) { 28179 if (isNativeReflectConstruct()) { 28180 module.exports = _construct = Reflect.construct; 28181 } else { 28182 module.exports = _construct = function _construct(Parent, args, Class) { 28183 var a = [null]; 28184 a.push.apply(a, args); 28185 var Constructor = Function.bind.apply(Parent, a); 28186 var instance = new Constructor(); 28187 if (Class) setPrototypeOf(instance, Class.prototype); 28188 return instance; 28189 }; 28190 } 28191 28192 return _construct.apply(null, arguments); 28193 } 28194 28195 module.exports = _construct; 28196 }); 28197 28198 /** 28199 * The base plugin name. 28200 * 28201 * @private 28202 * @constant 28203 * @type {string} 28204 */ 28205
vendor: 13,112 bytes, lines 28206-28610
28206 var BASE_PLUGIN_NAME = 'plugin'; 28207 /** 28208 * The key on which a player's active plugins cache is stored. 28209 * 28210 * @private 28211 * @constant 28212 * @type {string} 28213 */ 28214 28215 var PLUGIN_CACHE_KEY = 'activePlugins_'; 28216 /** 28217 * Stores registered plugins in a private space. 28218 * 28219 * @private 28220 * @type {Object} 28221 */ 28222 28223 var pluginStorage = {}; 28224 /** 28225 * Reports whether or not a plugin has been registered. 28226 * 28227 * @private 28228 * @param {string} name 28229 * The name of a plugin. 28230 * 28231 * @return {boolean} 28232 * Whether or not the plugin has been registered. 28233 */ 28234 28235 var pluginExists = function pluginExists(name) { 28236 return pluginStorage.hasOwnProperty(name); 28237 }; 28238 /** 28239 * Get a single registered plugin by name. 28240 * 28241 * @private 28242 * @param {string} name 28243 * The name of a plugin. 28244 * 28245 * @return {Function|undefined} 28246 * The plugin (or undefined). 28247 */ 28248 28249 28250 var getPlugin = function getPlugin(name) { 28251 return pluginExists(name) ? pluginStorage[name] : undefined; 28252 }; 28253 /** 28254 * Marks a plugin as "active" on a player. 28255 * 28256 * Also, ensures that the player has an object for tracking active plugins. 28257 * 28258 * @private 28259 * @param {Player} player 28260 * A Video.js player instance. 28261 * 28262 * @param {string} name 28263 * The name of a plugin. 28264 */ 28265 28266 28267 var markPluginAsActive = function markPluginAsActive(player, name) { 28268 player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; 28269 player[PLUGIN_CACHE_KEY][name] = true; 28270 }; 28271 /** 28272 * Triggers a pair of plugin setup events. 28273 * 28274 * @private 28275 * @param {Player} player 28276 * A Video.js player instance. 28277 * 28278 * @param {Plugin~PluginEventHash} hash 28279 * A plugin event hash. 28280 * 28281 * @param {boolean} [before] 28282 * If true, prefixes the event name with "before". In other words, 28283 * use this to trigger "beforepluginsetup" instead of "pluginsetup". 28284 */ 28285 28286 28287 var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { 28288 var eventName = (before ? 'before' : '') + 'pluginsetup'; 28289 player.trigger(eventName, hash); 28290 player.trigger(eventName + ':' + hash.name, hash); 28291 }; 28292 /** 28293 * Takes a basic plugin function and returns a wrapper function which marks 28294 * on the player that the plugin has been activated. 28295 * 28296 * @private 28297 * @param {string} name 28298 * The name of the plugin. 28299 * 28300 * @param {Function} plugin 28301 * The basic plugin. 28302 * 28303 * @return {Function} 28304 * A wrapper function for the given plugin. 28305 */ 28306 28307 28308 var createBasicPlugin = function createBasicPlugin(name, plugin) { 28309 var basicPluginWrapper = function basicPluginWrapper() { 28310 // We trigger the "beforepluginsetup" and "pluginsetup" events on the player 28311 // regardless, but we want the hash to be consistent with the hash provided 28312 // for advanced plugins. 28313 // 28314 // The only potentially counter-intuitive thing here is the `instance` in 28315 // the "pluginsetup" event is the value returned by the `plugin` function. 28316 triggerSetupEvent(this, { 28317 name: name, 28318 plugin: plugin, 28319 instance: null 28320 }, true); 28321 var instance = plugin.apply(this, arguments); 28322 markPluginAsActive(this, name); 28323 triggerSetupEvent(this, { 28324 name: name, 28325 plugin: plugin, 28326 instance: instance 28327 }); 28328 return instance; 28329 }; 28330 28331 Object.keys(plugin).forEach(function (prop) { 28332 basicPluginWrapper[prop] = plugin[prop]; 28333 }); 28334 return basicPluginWrapper; 28335 }; 28336 /** 28337 * Takes a plugin sub-class and returns a factory function for generating 28338 * instances of it. 28339 * 28340 * This factory function will replace itself with an instance of the requested 28341 * sub-class of Plugin. 28342 * 28343 * @private 28344 * @param {string} name 28345 * The name of the plugin. 28346 * 28347 * @param {Plugin} PluginSubClass 28348 * The advanced plugin. 28349 * 28350 * @return {Function} 28351 */ 28352 28353 28354 var createPluginFactory = function createPluginFactory(name, PluginSubClass) { 28355 // Add a `name` property to the plugin prototype so that each plugin can 28356 // refer to itself by name. 28357 PluginSubClass.prototype.name = name; 28358 return function () { 28359 triggerSetupEvent(this, { 28360 name: name, 28361 plugin: PluginSubClass, 28362 instance: null 28363 }, true); 28364 28365 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 28366 args[_key] = arguments[_key]; 28367 } 28368 28369 var instance = construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance. 28370 28371 28372 this[name] = function () { 28373 return instance; 28374 }; 28375 28376 triggerSetupEvent(this, instance.getEventHash()); 28377 return instance; 28378 }; 28379 }; 28380 /** 28381 * Parent class for all advanced plugins. 28382 * 28383 * @mixes module:evented~EventedMixin 28384 * @mixes module:stateful~StatefulMixin 28385 * @fires Player#beforepluginsetup 28386 * @fires Player#beforepluginsetup:$name 28387 * @fires Player#pluginsetup 28388 * @fires Player#pluginsetup:$name 28389 * @listens Player#dispose 28390 * @throws {Error} 28391 * If attempting to instantiate the base {@link Plugin} class 28392 * directly instead of via a sub-class. 28393 */ 28394 28395 28396 var Plugin = /*#__PURE__*/function () { 28397 /** 28398 * Creates an instance of this class. 28399 * 28400 * Sub-classes should call `super` to ensure plugins are properly initialized. 28401 * 28402 * @param {Player} player 28403 * A Video.js player instance. 28404 */ 28405 function Plugin(player) { 28406 if (this.constructor === Plugin) { 28407 throw new Error('Plugin must be sub-classed; not directly instantiated.'); 28408 } 28409 28410 this.player = player; // Make this object evented, but remove the added `trigger` method so we 28411 // use the prototype version instead. 28412 28413 evented(this); 28414 delete this.trigger; 28415 stateful(this, this.constructor.defaultState); 28416 markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind 28417 // it later easily. 28418 28419 this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin. 28420 28421 player.on('dispose', this.dispose); 28422 } 28423 /** 28424 * Get the version of the plugin that was set on <pluginName>.VERSION 28425 */ 28426 28427 28428 var _proto = Plugin.prototype; 28429 28430 _proto.version = function version() { 28431 return this.constructor.VERSION; 28432 } 28433 /** 28434 * Each event triggered by plugins includes a hash of additional data with 28435 * conventional properties. 28436 * 28437 * This returns that object or mutates an existing hash. 28438 * 28439 * @param {Object} [hash={}] 28440 * An object to be used as event an event hash. 28441 * 28442 * @return {Plugin~PluginEventHash} 28443 * An event hash object with provided properties mixed-in. 28444 */ 28445 ; 28446 28447 _proto.getEventHash = function getEventHash(hash) { 28448 if (hash === void 0) { 28449 hash = {}; 28450 } 28451 28452 hash.name = this.name; 28453 hash.plugin = this.constructor; 28454 hash.instance = this; 28455 return hash; 28456 } 28457 /** 28458 * Triggers an event on the plugin object and overrides 28459 * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. 28460 * 28461 * @param {string|Object} event 28462 * An event type or an object with a type property. 28463 * 28464 * @param {Object} [hash={}] 28465 * Additional data hash to merge with a 28466 * {@link Plugin~PluginEventHash|PluginEventHash}. 28467 * 28468 * @return {boolean} 28469 * Whether or not default was prevented. 28470 */ 28471 ; 28472 28473 _proto.trigger = function trigger$1(event, hash) { 28474 if (hash === void 0) { 28475 hash = {}; 28476 } 28477 28478 return trigger(this.eventBusEl_, event, this.getEventHash(hash)); 28479 } 28480 /** 28481 * Handles "statechanged" events on the plugin. No-op by default, override by 28482 * subclassing. 28483 * 28484 * @abstract 28485 * @param {Event} e 28486 * An event object provided by a "statechanged" event. 28487 * 28488 * @param {Object} e.changes 28489 * An object describing changes that occurred with the "statechanged" 28490 * event. 28491 */ 28492 ; 28493 28494 _proto.handleStateChanged = function handleStateChanged(e) {} 28495 /** 28496 * Disposes a plugin. 28497 * 28498 * Subclasses can override this if they want, but for the sake of safety, 28499 * it's probably best to subscribe the "dispose" event. 28500 * 28501 * @fires Plugin#dispose 28502 */ 28503 ; 28504 28505 _proto.dispose = function dispose() { 28506 var name = this.name, 28507 player = this.player; 28508 /** 28509 * Signals that a advanced plugin is about to be disposed. 28510 * 28511 * @event Plugin#dispose 28512 * @type {EventTarget~Event} 28513 */ 28514 28515 this.trigger('dispose'); 28516 this.off(); 28517 player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up 28518 // references between the player and the plugin instance and nulling out 28519 // the plugin's state and replacing methods with a function that throws. 28520 28521 player[PLUGIN_CACHE_KEY][name] = false; 28522 this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory 28523 // function, so that the plugin is ready to be set up again. 28524 28525 player[name] = createPluginFactory(name, pluginStorage[name]); 28526 } 28527 /** 28528 * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). 28529 * 28530 * @param {string|Function} plugin 28531 * If a string, matches the name of a plugin. If a function, will be 28532 * tested directly. 28533 * 28534 * @return {boolean} 28535 * Whether or not a plugin is a basic plugin. 28536 */ 28537 ; 28538 28539 Plugin.isBasic = function isBasic(plugin) { 28540 var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; 28541 return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); 28542 } 28543 /** 28544 * Register a Video.js plugin. 28545 * 28546 * @param {string} name 28547 * The name of the plugin to be registered. Must be a string and 28548 * must not match an existing plugin or a method on the `Player` 28549 * prototype. 28550 * 28551 * @param {Function} plugin 28552 * A sub-class of `Plugin` or a function for basic plugins. 28553 * 28554 * @return {Function} 28555 * For advanced plugins, a factory function for that plugin. For 28556 * basic plugins, a wrapper function that initializes the plugin. 28557 */ 28558 ; 28559 28560 Plugin.registerPlugin = function registerPlugin(name, plugin) { 28561 if (typeof name !== 'string') { 28562 throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + "."); 28563 } 28564 28565 if (pluginExists(name)) { 28566 log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!"); 28567 } else if (Player.prototype.hasOwnProperty(name)) { 28568 throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!"); 28569 } 28570 28571 if (typeof plugin !== 'function') { 28572 throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + "."); 28573 } 28574 28575 pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for 28576 // the base Plugin class). 28577 28578 if (name !== BASE_PLUGIN_NAME) { 28579 if (Plugin.isBasic(plugin)) { 28580 Player.prototype[name] = createBasicPlugin(name, plugin); 28581 } else { 28582 Player.prototype[name] = createPluginFactory(name, plugin); 28583 } 28584 } 28585 28586 return plugin; 28587 } 28588 /** 28589 * De-register a Video.js plugin. 28590 * 28591 * @param {string} name 28592 * The name of the plugin to be de-registered. Must be a string that 28593 * matches an existing plugin. 28594 * 28595 * @throws {Error} 28596 * If an attempt is made to de-register the base plugin. 28597 */ 28598 ; 28599 28600 Plugin.deregisterPlugin = function deregisterPlugin(name) { 28601 if (name === BASE_PLUGIN_NAME) { 28602 throw new Error('Cannot de-register base plugin.'); 28603 } 28604 28605 if (pluginExists(name)) { 28606 delete pluginStorage[name]; 28607 delete Player.prototype[name]; 28608 } 28609 } 28610 /**
28611 * Gets an object containing multiple Video.js plugins. 28612 * 28613 * @param {Array} [names] 28614 * If provided, should be an array of plugin names. Defaults to _all_ 28615 * plugin names. 28616 * 28617 * @return {Object|undefined} 28618 * An object containing plugin(s) associated with their name(s) or 28619 * `undefined` if no matching plugins exist). 28620 */ 28621 ; 28622 28623 Plugin.getPlugins = function getPlugins(names) { 28624 if (names === void 0) { 28625 names = Object.keys(pluginStorage); 28626 } 28627 28628 var result; 28629 names.forEach(function (name) { 28630 var plugin = getPlugin(name); 28631 28632 if (plugin) { 28633 result = result || {}; 28634 result[name] = plugin; 28635 } 28636 }); 28637 return result; 28638 } 28639 /** 28640 * Gets a plugin's version, if available 28641 * 28642 * @param {string} name 28643 * The name of a plugin. 28644 * 28645 * @return {string} 28646 * The plugin's version or an empty string. 28647 */ 28648 ; 28649 28650 Plugin.getPluginVersion = function getPluginVersion(name) { 28651 var plugin = getPlugin(name); 28652 return plugin && plugin.VERSION || ''; 28653 }; 28654 28655 return Plugin; 28656 }(); 28657 /** 28658 * Gets a plugin by name if it exists. 28659 * 28660 * @static 28661 * @method getPlugin 28662 * @memberOf Plugin 28663 * @param {string} name 28664 * The name of a plugin. 28665 * 28666 * @returns {Function|undefined} 28667 * The plugin (or `undefined`). 28668 */ 28669 28670 28671 Plugin.getPlugin = getPlugin; 28672 /** 28673 * The name of the base plugin class as it is registered. 28674 * 28675 * @type {string} 28676 */ 28677 28678 Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; 28679 Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); 28680 /** 28681 * Documented in player.js 28682 * 28683 * @ignore 28684 */ 28685 28686 Player.prototype.usingPlugin = function (name) { 28687 return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; 28688 }; 28689 /** 28690 * Documented in player.js 28691 * 28692 * @ignore 28693 */ 28694 28695 28696 Player.prototype.hasPlugin = function (name) { 28697 return !!pluginExists(name); 28698 }; 28699 /** 28700 * Signals that a plugin is about to be set up on a player. 28701 * 28702 * @event Player#beforepluginsetup 28703 * @type {Plugin~PluginEventHash} 28704 */ 28705 28706 /** 28707 * Signals that a plugin is about to be set up on a player - by name. The name 28708 * is the name of the plugin. 28709 * 28710 * @event Player#beforepluginsetup:$name 28711 * @type {Plugin~PluginEventHash} 28712 */ 28713 28714 /** 28715 * Signals that a plugin has just been set up on a player. 28716 * 28717 * @event Player#pluginsetup 28718 * @type {Plugin~PluginEventHash} 28719 */ 28720 28721 /** 28722 * Signals that a plugin has just been set up on a player - by name. The name 28723 * is the name of the plugin. 28724 * 28725 * @event Player#pluginsetup:$name 28726 * @type {Plugin~PluginEventHash} 28727 */ 28728 28729 /** 28730 * @typedef {Object} Plugin~PluginEventHash 28731 * 28732 * @property {string} instance 28733 * For basic plugins, the return value of the plugin function. For 28734 * advanced plugins, the plugin instance on which the event is fired. 28735 * 28736 * @property {string} name 28737 * The name of the plugin. 28738 * 28739 * @property {string} plugin 28740 * For basic plugins, the plugin function. For advanced plugins, the 28741 * plugin class/constructor. 28742 */ 28743 28744 function _inherits(subClass, superClass) { 28745 if (typeof superClass !== "function" && superClass !== null) { 28746 throw new TypeError("Super expression must either be null or a function"); 28747 } 28748 28749 subClass.prototype = Object.create(superClass && superClass.prototype, { 28750 constructor: { 28751 value: subClass, 28752 writable: true, 28753 configurable: true 28754 } 28755 }); 28756 if (superClass) setPrototypeOf(subClass, superClass); 28757 } 28758 28759 var inherits = _inherits; 28760 28761 /** 28762 * @file extend.js 28763 * @module extend 28764 */ 28765 /** 28766 * Used to subclass an existing class by emulating ES subclassing using the 28767 * `extends` keyword. 28768 * 28769 * @function 28770 * @example 28771 * var MyComponent = videojs.extend(videojs.getComponent('Component'), { 28772 * myCustomMethod: function() { 28773 * // Do things in my method. 28774 * } 28775 * }); 28776 * 28777 * @param {Function} superClass 28778 * The class to inherit from 28779 * 28780 * @param {Object} [subClassMethods={}] 28781 * Methods of the new class 28782 * 28783 * @return {Function} 28784 * The new class with subClassMethods that inherited superClass. 28785 */ 28786 28787 var extend = function extend(superClass, subClassMethods) { 28788 if (subClassMethods === void 0) { 28789 subClassMethods = {}; 28790 } 28791 28792 var subClass = function subClass() { 28793 superClass.apply(this, arguments); 28794 }; 28795 28796 var methods = {}; 28797 28798 if (typeof subClassMethods === 'object') { 28799 if (subClassMethods.constructor !== Object.prototype.constructor) { 28800 subClass = subClassMethods.constructor; 28801 } 28802 28803 methods = subClassMethods; 28804 } else if (typeof subClassMethods === 'function') { 28805 subClass = subClassMethods; 28806 } 28807 28808 inherits(subClass, superClass); // this is needed for backward-compatibility and node compatibility. 28809 28810 28811 if (superClass) { 28812 subClass.super_ = superClass; 28813 } // Extend subObj's prototype with functions and other properties from props 28814 28815 28816 for (var name in methods) { 28817 if (methods.hasOwnProperty(name)) { 28818 subClass.prototype[name] = methods[name]; 28819 } 28820 } 28821 28822 return subClass; 28823 }; 28824 28825 /** 28826 * @file video.js 28827 * @module videojs 28828 */ 28829 /** 28830 * Normalize an `id` value by trimming off a leading `#` 28831 * 28832 * @private 28833 * @param {string} id 28834 * A string, maybe with a leading `#`. 28835 * 28836 * @return {string} 28837 * The string, without any leading `#`. 28838 */ 28839 28840 var normalizeId = function normalizeId(id) { 28841 return id.indexOf('#') === 0 ? id.slice(1) : id; 28842 }; 28843 /** 28844 * The `videojs()` function doubles as the main function for users to create a 28845 * {@link Player} instance as well as the main library namespace. 28846 * 28847 * It can also be used as a getter for a pre-existing {@link Player} instance. 28848 * However, we _strongly_ recommend using `videojs.getPlayer()` for this 28849 * purpose because it avoids any potential for unintended initialization. 28850 * 28851 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 28852 * of our JSDoc template, we cannot properly document this as both a function 28853 * and a namespace, so its function signature is documented here. 28854 * 28855 * #### Arguments 28856 * ##### id 28857 * string|Element, **required** 28858 * 28859 * Video element or video element ID. 28860 * 28861 * ##### options 28862 * Object, optional 28863 * 28864 * Options object for providing settings. 28865 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 28866 * 28867 * ##### ready 28868 * {@link Component~ReadyCallback}, optional 28869 * 28870 * A function to be called when the {@link Player} and {@link Tech} are ready. 28871 * 28872 * #### Return Value 28873 * 28874 * The `videojs()` function returns a {@link Player} instance. 28875 * 28876 * @namespace 28877 * 28878 * @borrows AudioTrack as AudioTrack 28879 * @borrows Component.getComponent as getComponent 28880 * @borrows module:computed-style~computedStyle as computedStyle 28881 * @borrows module:events.on as on 28882 * @borrows module:events.one as one 28883 * @borrows module:events.off as off 28884 * @borrows module:events.trigger as trigger 28885 * @borrows EventTarget as EventTarget 28886 * @borrows module:extend~extend as extend 28887 * @borrows module:fn.bind as bind 28888 * @borrows module:format-time.formatTime as formatTime 28889 * @borrows module:format-time.resetFormatTime as resetFormatTime 28890 * @borrows module:format-time.setFormatTime as setFormatTime 28891 * @borrows module:merge-options.mergeOptions as mergeOptions 28892 * @borrows module:middleware.use as use 28893 * @borrows Player.players as players 28894 * @borrows Plugin.registerPlugin as registerPlugin 28895 * @borrows Plugin.deregisterPlugin as deregisterPlugin 28896 * @borrows Plugin.getPlugins as getPlugins 28897 * @borrows Plugin.getPlugin as getPlugin 28898 * @borrows Plugin.getPluginVersion as getPluginVersion 28899 * @borrows Tech.getTech as getTech 28900 * @borrows Tech.registerTech as registerTech 28901 * @borrows TextTrack as TextTrack 28902 * @borrows module:time-ranges.createTimeRanges as createTimeRange 28903 * @borrows module:time-ranges.createTimeRanges as createTimeRanges 28904 * @borrows module:url.isCrossOrigin as isCrossOrigin 28905 * @borrows module:url.parseUrl as parseUrl 28906 * @borrows VideoTrack as VideoTrack 28907 * 28908 * @param {string|Element}
28908 id 28909 * Video element or video element ID. 28910 * 28911 * @param {Object} [options] 28912 * Options object for providing settings. 28913 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 28914 * 28915 * @param {Component~ReadyCallback} [ready] 28916 * A function to be called when the {@link Player} and {@link Tech} are 28917 * ready. 28918 * 28919 * @return {Player} 28920 * The `videojs()` function returns a {@link Player|Player} instance. 28921 */ 28922 28923 28924 function videojs$1(id, options, ready) { 28925 var player = videojs$1.getPlayer(id); 28926 28927 if (player) { 28928 if (options) { 28929 log.warn("Player \"" + id + "\" is already initialised. Options will not be applied."); 28930 } 28931 28932 if (ready) { 28933 player.ready(ready); 28934 } 28935 28936 return player; 28937 } 28938 28939 var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id; 28940 28941 if (!isEl(el)) { 28942 throw new TypeError('The element or ID supplied is not valid. (videojs)'); 28943 } // document.body.contains(el) will only check if el is contained within that one document. 28944 // This causes problems for elements in iframes. 28945 // Instead, use the element's ownerDocument instead of the global document. 28946 // This will make sure that the element is indeed in the dom of that document. 28947 // Additionally, check that the document in question has a default view. 28948 // If the document is no longer attached to the dom, the defaultView of the document will be null. 28949 28950 28951 if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) { 28952 log.warn('The element supplied is not included in the DOM'); 28953 } 28954 28955 options = options || {}; 28956 videojs$1.hooks('beforesetup').forEach(function (hookFunction) { 28957 var opts = hookFunction(el, mergeOptions(options)); 28958 28959 if (!isObject(opts) || Array.isArray(opts)) { 28960 log.error('please return an object in beforesetup hooks'); 28961 return; 28962 } 28963 28964 options = mergeOptions(options, opts); 28965 }); // We get the current "Player" component here in case an integration has 28966 // replaced it with a custom player. 28967 28968 var PlayerComponent = Component.getComponent('Player'); 28969 player = new PlayerComponent(el, options, ready); 28970 videojs$1.hooks('setup').forEach(function (hookFunction) { 28971 return hookFunction(player); 28972 }); 28973 return player; 28974 } 28975 /** 28976 * An Object that contains lifecycle hooks as keys which point to an array 28977 * of functions that are run when a lifecycle is triggered 28978 * 28979 * @private 28980 */ 28981 28982 28983 videojs$1.hooks_ = {}; 28984 /** 28985 * Get a list of hooks for a specific lifecycle 28986 * 28987 * @param {string} type 28988 * the lifecyle to get hooks from 28989 * 28990 * @param {Function|Function[]} [fn] 28991 * Optionally add a hook (or hooks) to the lifecycle that your are getting. 28992 * 28993 * @return {Array} 28994 * an array of hooks, or an empty array if there are none. 28995 */ 28996 28997 videojs$1.hooks = function (type, fn) { 28998 videojs$1.hooks_[type] = videojs$1.hooks_[type] || []; 28999 29000 if (fn) { 29001 videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn); 29002 } 29003 29004 return videojs$1.hooks_[type]; 29005 }; 29006 /** 29007 * Add a function hook to a specific videojs lifecycle. 29008 * 29009 * @param {string} type 29010 * the lifecycle to hook the function to. 29011 * 29012 * @param {Function|Function[]} 29013 * The function or array of functions to attach. 29014 */ 29015 29016 29017 videojs$1.hook = function (type, fn) { 29018 videojs$1.hooks(type, fn); 29019 }; 29020 /** 29021 * Add a function hook that will only run once to a specific videojs lifecycle. 29022 * 29023 * @param {string} type 29024 * the lifecycle to hook the function to. 29025 * 29026 * @param {Function|Function[]} 29027 * The function or array of functions to attach. 29028 */ 29029 29030 29031 videojs$1.hookOnce = function (type, fn) { 29032 videojs$1.hooks(type, [].concat(fn).map(function (original) { 29033 var wrapper = function wrapper() { 29034 videojs$1.removeHook(type, wrapper); 29035 return original.apply(void 0, arguments); 29036 }; 29037 29038 return wrapper; 29039 })); 29040 }; 29041 /** 29042 * Remove a hook from a specific videojs lifecycle. 29043 * 29044 * @param {string} type 29045 * the lifecycle that the function hooked to 29046 * 29047 * @param {Function} fn 29048 * The hooked function to remove 29049 * 29050 * @return {boolean} 29051 * The function that was removed or undef 29052 */ 29053 29054
vendor: 9,401 bytes, lines 29055-29351
29055 videojs$1.removeHook = function (type, fn) { 29056 var index = videojs$1.hooks(type).indexOf(fn); 29057 29058 if (index <= -1) { 29059 return false; 29060 } 29061 29062 videojs$1.hooks_[type] = videojs$1.hooks_[type].slice(); 29063 videojs$1.hooks_[type].splice(index, 1); 29064 return true; 29065 }; // Add default styles 29066 29067 29068 if (window$3.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) { 29069 var style = $('.vjs-styles-defaults'); 29070 29071 if (!style) { 29072 style = createStyleElement('vjs-styles-defaults'); 29073 var head = $('head'); 29074 29075 if (head) { 29076 head.insertBefore(style, head.firstChild); 29077 } 29078 29079 setTextContent(style, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n "); 29080 } 29081 } // Run Auto-load players 29082 // You have to wait at least once in case this script is loaded after your 29083 // video in the DOM (weird behavior only with minified version) 29084 29085 29086 autoSetupTimeout(1, videojs$1); 29087 /** 29088 * Current Video.js version. Follows [semantic versioning](https://semver.org/). 29089 * 29090 * @type {string} 29091 */ 29092 29093 videojs$1.VERSION = version; 29094 /** 29095 * The global options object. These are the settings that take effect 29096 * if no overrides are specified when the player is created. 29097 * 29098 * @type {Object} 29099 */ 29100 29101 videojs$1.options = Player.prototype.options_; 29102 /** 29103 * Get an object with the currently created players, keyed by player ID 29104 * 29105 * @return {Object} 29106 * The created players 29107 */ 29108 29109 videojs$1.getPlayers = function () { 29110 return Player.players; 29111 }; 29112 /** 29113 * Get a single player based on an ID or DOM element. 29114 * 29115 * This is useful if you want to check if an element or ID has an associated 29116 * Video.js player, but not create one if it doesn't. 29117 * 29118 * @param {string|Element} id 29119 * An HTML element - `<video>`, `<audio>`, or `<video-js>` - 29120 * or a string matching the `id` of such an element. 29121 * 29122 * @return {Player|undefined} 29123 * A player instance or `undefined` if there is no player instance 29124 * matching the argument. 29125 */ 29126 29127 29128 videojs$1.getPlayer = function (id) { 29129 var players = Player.players; 29130 var tag; 29131 29132 if (typeof id === 'string') { 29133 var nId = normalizeId(id); 29134 var player = players[nId]; 29135 29136 if (player) { 29137 return player; 29138 } 29139 29140 tag = $('#' + nId); 29141 } else { 29142 tag = id; 29143 } 29144 29145 if (isEl(tag)) { 29146 var _tag = tag, 29147 _player = _tag.player, 29148 playerId = _tag.playerId; // Element may have a `player` property referring to an already created 29149 // player instance. If so, return that. 29150 29151 if (_player || players[playerId]) { 29152 return _player || players[playerId]; 29153 } 29154 } 29155 }; 29156 /** 29157 * Returns an array of all current players. 29158 * 29159 * @return {Array} 29160 * An array of all players. The array will be in the order that 29161 * `Object.keys` provides, which could potentially vary between 29162 * JavaScript engines. 29163 * 29164 */ 29165 29166 29167 videojs$1.getAllPlayers = function () { 29168 return (// Disposed players leave a key with a `null` value, so we need to make sure 29169 // we filter those out. 29170 Object.keys(Player.players).map(function (k) { 29171 return Player.players[k]; 29172 }).filter(Boolean) 29173 ); 29174 }; 29175 29176 videojs$1.players = Player.players; 29177 videojs$1.getComponent = Component.getComponent; 29178 /** 29179 * Register a component so it can referred to by name. Used when adding to other 29180 * components, either through addChild `component.addChild('myComponent')` or through 29181 * default children options `{ children: ['myComponent'] }`. 29182 * 29183 * > NOTE: You could also just initialize the component before adding. 29184 * `component.addChild(new MyComponent());` 29185 * 29186 * @param {string} name 29187 * The class name of the component 29188 * 29189 * @param {Component} comp 29190 * The component class 29191 * 29192 * @return {Component} 29193 * The newly registered component 29194 */ 29195 29196 videojs$1.registerComponent = function (name, comp) { 29197 if (Tech.isTech(comp)) { 29198 log.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)"); 29199 } 29200 29201 Component.registerComponent.call(Component, name, comp); 29202 }; 29203 29204 videojs$1.getTech = Tech.getTech; 29205 videojs$1.registerTech = Tech.registerTech; 29206 videojs$1.use = use; 29207 /** 29208 * An object that can be returned by a middleware to signify 29209 * that the middleware is being terminated. 29210 * 29211 * @type {object} 29212 * @property {object} middleware.TERMINATOR 29213 */ 29214 29215 Object.defineProperty(videojs$1, 'middleware', { 29216 value: {}, 29217 writeable: false, 29218 enumerable: true 29219 }); 29220 Object.defineProperty(videojs$1.middleware, 'TERMINATOR', { 29221 value: TERMINATOR, 29222 writeable: false, 29223 enumerable: true 29224 }); 29225 /** 29226 * A reference to the {@link module:browser|browser utility module} as an object. 29227 * 29228 * @type {Object} 29229 * @see {@link module:browser|browser} 29230 */ 29231 29232 videojs$1.browser = browser; 29233 /** 29234 * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only 29235 * included for backward-compatibility with 4.x. 29236 * 29237 * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead. 29238 * @type {boolean} 29239 */ 29240 29241 videojs$1.TOUCH_ENABLED = TOUCH_ENABLED; 29242 videojs$1.extend = extend; 29243 videojs$1.mergeOptions = mergeOptions; 29244 videojs$1.bind = bind; 29245 videojs$1.registerPlugin = Plugin.registerPlugin; 29246 videojs$1.deregisterPlugin = Plugin.deregisterPlugin; 29247 /** 29248 * Deprecated method to register a plugin with Video.js 29249 * 29250 * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead 29251 * 29252 * @param {string} name 29253 * The plugin name 29254 * 29255 * @param {Plugin|Function} plugin 29256 * The plugin sub-class or function 29257 */ 29258 29259 videojs$1.plugin = function (name, plugin) { 29260 log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead'); 29261 return Plugin.registerPlugin(name, plugin); 29262 }; 29263 29264 videojs$1.getPlugins = Plugin.getPlugins; 29265 videojs$1.getPlugin = Plugin.getPlugin; 29266 videojs$1.getPluginVersion = Plugin.getPluginVersion; 29267 /** 29268 * Adding languages so that they're available to all players. 29269 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });` 29270 * 29271 * @param {string} code 29272 * The language code or dictionary property 29273 * 29274 * @param {Object} data 29275 * The data values to be translated 29276 * 29277 * @return {Object} 29278 * The resulting language dictionary object 29279 */ 29280 29281 videojs$1.addLanguage = function (code, data) { 29282 var _mergeOptions; 29283 29284 code = ('' + code).toLowerCase(); 29285 videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions)); 29286 return videojs$1.options.languages[code]; 29287 }; 29288 /** 29289 * A reference to the {@link module:log|log utility module} as an object. 29290 * 29291 * @type {Function} 29292 * @see {@link module:log|log} 29293 */ 29294 29295 29296 videojs$1.log = log; 29297 videojs$1.createLogger = createLogger$1; 29298 videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges; 29299 videojs$1.formatTime = formatTime; 29300 videojs$1.setFormatTime = setFormatTime; 29301 videojs$1.resetFormatTime = resetFormatTime; 29302 videojs$1.parseUrl = parseUrl; 29303 videojs$1.isCrossOrigin = isCrossOrigin; 29304 videojs$1.EventTarget = EventTarget; 29305 videojs$1.on = on; 29306 videojs$1.one = one; 29307 videojs$1.off = off; 29308 videojs$1.trigger = trigger; 29309 /** 29310 * A cross-browser XMLHttpRequest wrapper. 29311 * 29312 * @function 29313 * @param {Object} options 29314 * Settings for the request. 29315 * 29316 * @return {XMLHttpRequest|XDomainRequest} 29317 * The request object. 29318 * 29319 * @see https://github.com/Raynos/xhr 29320 */ 29321 29322 videojs$1.xhr = xhr; 29323 videojs$1.TextTrack = TextTrack; 29324 videojs$1.AudioTrack = AudioTrack; 29325 videojs$1.VideoTrack = VideoTrack; 29326 ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) { 29327 videojs$1[k] = function () { 29328 log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead"); 29329 return Dom[k].apply(null, arguments); 29330 }; 29331 }); 29332 videojs$1.computedStyle = computedStyle; 29333 /** 29334 * A reference to the {@link module:dom|DOM utility module} as an object. 29335 * 29336 * @type {Object} 29337 * @see {@link module:dom|dom} 29338 */ 29339 29340 videojs$1.dom = Dom; 29341 /** 29342 * A reference to the {@link module:url|URL utility module} as an object. 29343 * 29344 * @type {Object} 29345 * @see {@link module:url|url} 29346 */ 29347 29348 videojs$1.url = Url; 29349 videojs$1.defineLazyProperty = defineLazyProperty; 29350 29351 var urlToolkit = createCommonjsModule(function (module, exports
vendor: 4,856 bytes, lines 29351-29458
29351) { 29352 // see https://tools.ietf.org/html/rfc1808 29353 29354 /* jshint ignore:start */ 29355 (function (root) { 29356 /* jshint ignore:end */ 29357 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; 29358 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; 29359 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 29360 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; 29361 var URLToolkit = { 29362 // jshint ignore:line 29363 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 29364 // E.g 29365 // With opts.alwaysNormalize = false (default, spec compliant) 29366 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 29367 // With opts.alwaysNormalize = true (not spec compliant) 29368 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 29369 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 29370 opts = opts || {}; // remove any remaining space and CRLF 29371 29372 baseURL = baseURL.trim(); 29373 relativeURL = relativeURL.trim(); 29374 29375 if (!relativeURL) { 29376 // 2a) If the embedded URL is entirely empty, it inherits the 29377 // entire base URL (i.e., is set equal to the base URL) 29378 // and we are done. 29379 if (!opts.alwaysNormalize) { 29380 return baseURL; 29381 } 29382 29383 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 29384 29385 if (!basePartsForNormalise) { 29386 throw new Error('Error trying to parse base URL.'); 29387 } 29388 29389 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 29390 return URLToolkit.buildURLFromParts(basePartsForNormalise); 29391 } 29392 29393 var relativeParts = URLToolkit.parseURL(relativeURL); 29394 29395 if (!relativeParts) { 29396 throw new Error('Error trying to parse relative URL.'); 29397 } 29398 29399 if (relativeParts.scheme) { 29400 // 2b) If the embedded URL starts with a scheme name, it is 29401 // interpreted as an absolute URL and we are done. 29402 if (!opts.alwaysNormalize) { 29403 return relativeURL; 29404 } 29405 29406 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 29407 return URLToolkit.buildURLFromParts(relativeParts); 29408 } 29409 29410 var baseParts = URLToolkit.parseURL(baseURL); 29411 29412 if (!baseParts) { 29413 throw new Error('Error trying to parse base URL.'); 29414 } 29415 29416 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 29417 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 29418 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 29419 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 29420 baseParts.netLoc = pathParts[1]; 29421 baseParts.path = pathParts[2]; 29422 } 29423 29424 if (baseParts.netLoc && !baseParts.path) { 29425 baseParts.path = '/'; 29426 } 29427 29428 var builtParts = { 29429 // 2c) Otherwise, the embedded URL inherits the scheme of 29430 // the base URL. 29431 scheme: baseParts.scheme, 29432 netLoc: relativeParts.netLoc, 29433 path: null, 29434 params: relativeParts.params, 29435 query: relativeParts.query, 29436 fragment: relativeParts.fragment 29437 }; 29438 29439 if (!relativeParts.netLoc) { 29440 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 29441 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 29442 // (if any) of the base URL. 29443 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 29444 // path is not relative and we skip to Step 7. 29445 29446 if (relativeParts.path[0] !== '/') { 29447 if (!relativeParts.path) { 29448 // 5) If the embedded URL path is empty (and not preceded by a 29449 // slash), then the embedded URL inherits the base URL path 29450 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 29451 // step 7; otherwise, it inherits the <params> of the base 29452 // URL (if any) and 29453 29454 if (!relativeParts.params) { 29455 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 29456 // step 7; otherwise, it inherits the <query> of the base 29457 // URL (if any) and we skip to step 7. 29458
29459 if (!relativeParts.query) { 29460 builtParts.query = baseParts.query; 29461 } 29462 } 29463 } else { 29464 // 6) The last segment of the base URL's path (anything 29465 // following the rightmost slash "/", or the entire path if no 29466 // slash is present) is removed and the embedded URL's path is 29467 // appended in its place. 29468 var baseURLPath = baseParts.path; 29469 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 29470 builtParts.path = URLToolkit.normalizePath(newPath); 29471 } 29472 } 29473 } 29474 29475 if (builtParts.path === null) { 29476 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 29477 } 29478 29479 return URLToolkit.buildURLFromParts(builtParts); 29480 }, 29481 parseURL: function parseURL(url) { 29482 var parts = URL_REGEX.exec(url); 29483 29484 if (!parts) { 29485 return null; 29486 } 29487 29488 return { 29489 scheme: parts[1] || '', 29490 netLoc: parts[2] || '', 29491 path: parts[3] || '', 29492 params: parts[4] || '', 29493 query: parts[5] || '', 29494 fragment: parts[6] || '' 29495 }; 29496 }, 29497 normalizePath: function normalizePath(path) { 29498 // The following operations are 29499 // then applied, in order, to the new path: 29500 // 6a) All occurrences of "./", where "." is a complete path 29501 // segment, are removed. 29502 // 6b) If the path ends with "." as a complete path segment, 29503 // that "." is removed. 29504 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 29505 // complete path segment not equal to "..", are removed. 29506 // Removal of these path segments is performed iteratively, 29507 // removing the leftmost matching pattern on each iteration, 29508 // until no matching pattern remains. 29509 // 6d) If the path ends with "<segment>/..", where <segment> is a 29510 // complete path segment not equal to "..", that 29511 // "<segment>/.." is removed. 29512 29513 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line 29514 29515 29516 return path.split('').reverse().join(''); 29517 }, 29518 buildURLFromParts: function buildURLFromParts(parts) { 29519 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 29520 } 29521 }; 29522 /* jshint ignore:start */ 29523 29524 module.exports = URLToolkit; 29525 })(); 29526 /* jshint ignore:end */ 29527 29528 }); 29529 29530 /*! @name m3u8-parser @version 4.4.0 @license Apache-2.0 */ 29531 29532 function _extends() { 29533 _extends = Object.assign || function (target) { 29534 for (var i = 1; i < arguments.length; i++) { 29535 var source = arguments[i]; 29536 29537 for (var key in source) { 29538 if (Object.prototype.hasOwnProperty.call(source, key)) { 29539 target[key] = source[key]; 29540 } 29541 } 29542 } 29543 29544 return target; 29545 }; 29546 29547 return _extends.apply(this, arguments); 29548 } 29549 29550 function _inheritsLoose$1(subClass, superClass) { 29551 subClass.prototype = Object.create(superClass.prototype); 29552 subClass.prototype.constructor = subClass; 29553 subClass.__proto__ = superClass; 29554 } 29555 29556 function _assertThisInitialized$1(self) { 29557 if (self === void 0) { 29558 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 29559 } 29560 29561 return self; 29562 } 29563 /** 29564 * @file stream.js 29565 */ 29566 29567 /** 29568 * A lightweight readable stream implementation that handles event dispatching. 29569 * 29570 * @class Stream 29571 */ 29572 29573 29574 var Stream = /*#__PURE__*/function () { 29575 function Stream() { 29576 this.listeners = {}; 29577 } 29578 /** 29579 * Add a listener for a specified event type. 29580 * 29581 * @param {string} type the event name 29582 * @param {Function} listener the callback to be invoked when an event of 29583 * the specified type occurs 29584 */ 29585 29586 29587 var _proto = Stream.prototype; 29588 29589 _proto.on = function on(type, listener) { 29590 if (!this.listeners[type]) { 29591 this.listeners[type] = []; 29592 } 29593 29594 this.listeners[type].push(listener); 29595 } 29596 /** 29597 * Remove a listener for a specified event type. 29598 * 29599 * @param {string} type the event name 29600 * @param {Function} listener a function previously registered for this 29601 * type of event through `on` 29602 * @return {boolean} if we could turn it off or not 29603 */ 29604 ; 29605 29606 _proto.off = function off(type, listener) { 29607 if (!this.listeners[type]) { 29608 return false;
vendor: 40,377 bytes, lines 29609-30892
29609 } 29610 29611 var index = this.listeners[type].indexOf(listener); 29612 this.listeners[type].splice(index, 1); 29613 return index > -1; 29614 } 29615 /** 29616 * Trigger an event of the specified type on this stream. Any additional 29617 * arguments to this function are passed as parameters to event listeners. 29618 * 29619 * @param {string} type the event name 29620 */ 29621 ; 29622 29623 _proto.trigger = function trigger(type) { 29624 var callbacks = this.listeners[type]; 29625 var i; 29626 var length; 29627 var args; 29628 29629 if (!callbacks) { 29630 return; 29631 } // Slicing the arguments on every invocation of this method 29632 // can add a significant amount of overhead. Avoid the 29633 // intermediate object creation for the common case of a 29634 // single callback argument 29635 29636 29637 if (arguments.length === 2) { 29638 length = callbacks.length; 29639 29640 for (i = 0; i < length; ++i) { 29641 callbacks[i].call(this, arguments[1]); 29642 } 29643 } else { 29644 args = Array.prototype.slice.call(arguments, 1); 29645 length = callbacks.length; 29646 29647 for (i = 0; i < length; ++i) { 29648 callbacks[i].apply(this, args); 29649 } 29650 } 29651 } 29652 /** 29653 * Destroys the stream and cleans up. 29654 */ 29655 ; 29656 29657 _proto.dispose = function dispose() { 29658 this.listeners = {}; 29659 } 29660 /** 29661 * Forwards all `data` events on this stream to the destination stream. The 29662 * destination stream should provide a method `push` to receive the data 29663 * events as they arrive. 29664 * 29665 * @param {Stream} destination the stream that will receive all `data` events 29666 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 29667 */ 29668 ; 29669 29670 _proto.pipe = function pipe(destination) { 29671 this.on('data', function (data) { 29672 destination.push(data); 29673 }); 29674 }; 29675 29676 return Stream; 29677 }(); 29678 /** 29679 * A stream that buffers string input and generates a `data` event for each 29680 * line. 29681 * 29682 * @class LineStream 29683 * @extends Stream 29684 */ 29685 29686 29687 var LineStream = /*#__PURE__*/function (_Stream) { 29688 _inheritsLoose$1(LineStream, _Stream); 29689 29690 function LineStream() { 29691 var _this; 29692 29693 _this = _Stream.call(this) || this; 29694 _this.buffer = ''; 29695 return _this; 29696 } 29697 /** 29698 * Add new data to be parsed. 29699 * 29700 * @param {string} data the text to process 29701 */ 29702 29703 29704 var _proto = LineStream.prototype; 29705 29706 _proto.push = function push(data) { 29707 var nextNewline; 29708 this.buffer += data; 29709 nextNewline = this.buffer.indexOf('\n'); 29710 29711 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 29712 this.trigger('data', this.buffer.substring(0, nextNewline)); 29713 this.buffer = this.buffer.substring(nextNewline + 1); 29714 } 29715 }; 29716 29717 return LineStream; 29718 }(Stream); 29719 /** 29720 * "forgiving" attribute list psuedo-grammar: 29721 * attributes -> keyvalue (',' keyvalue)* 29722 * keyvalue -> key '=' value 29723 * key -> [^=]* 29724 * value -> '"' [^"]* '"' | [^,]* 29725 */ 29726 29727 29728 var attributeSeparator = function attributeSeparator() { 29729 var key = '[^=]*'; 29730 var value = '"[^"]*"|[^,]*'; 29731 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 29732 return new RegExp('(?:^|,)(' + keyvalue + ')'); 29733 }; 29734 /** 29735 * Parse attributes from a line given the separator 29736 * 29737 * @param {string} attributes the attribute line to parse 29738 */ 29739 29740 29741 var parseAttributes = function parseAttributes(attributes) { 29742 // split the string using attributes as the separator 29743 var attrs = attributes.split(attributeSeparator()); 29744 var result = {}; 29745 var i = attrs.length; 29746 var attr; 29747 29748 while (i--) { 29749 // filter out unmatched portions of the string 29750 if (attrs[i] === '') { 29751 continue; 29752 } // split the key and value 29753 29754 29755 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value 29756 29757 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 29758 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 29759 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 29760 result[attr[0]] = attr[1]; 29761 } 29762 29763 return result; 29764 }; 29765 /** 29766 * A line-level M3U8 parser event stream. It expects to receive input one 29767 * line at a time and performs a context-free parse of its contents. A stream 29768 * interpretation of a manifest can be useful if the manifest is expected to 29769 * be too large to fit comfortably into memory or the entirety of the input 29770 * is not immediately available. Otherwise, it's probably much easier to work 29771 * with a regular `Parser` object. 29772 * 29773 * Produces `data` events with an object that captures the parser's 29774 * interpretation of the input. That object has a property `tag` that is one 29775 * of `uri`, `comment`, or `tag`. URIs only have a single additional 29776 * property, `line`, which captures the entirety of the input without 29777 * interpretation. Comments similarly have a single additional property 29778 * `text` which is the input without the leading `#`. 29779 * 29780 * Tags always have a property `tagType` which is the lower-cased version of 29781 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 29782 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 29783 * tags are given the tag type `unknown` and a single additional property 29784 * `data` with the remainder of the input. 29785 * 29786 * @class ParseStream 29787 * @extends Stream 29788 */ 29789 29790 29791 var ParseStream = /*#__PURE__*/function (_Stream) { 29792 _inheritsLoose$1(ParseStream, _Stream); 29793 29794 function ParseStream() { 29795 var _this; 29796 29797 _this = _Stream.call(this) || this; 29798 _this.customParsers = []; 29799 _this.tagMappers = []; 29800 return _this; 29801 } 29802 /** 29803 * Parses an additional line of input. 29804 * 29805 * @param {string} line a single line of an M3U8 file to parse 29806 */ 29807 29808 29809 var _proto = ParseStream.prototype; 29810 29811 _proto.push = function push(line) { 29812 var _this2 = this; 29813 29814 var match; 29815 var event; // strip whitespace 29816 29817 line = line.trim(); 29818 29819 if (line.length === 0) { 29820 // ignore empty lines 29821 return; 29822 } // URIs 29823 29824 29825 if (line[0] !== '#') { 29826 this.trigger('data', { 29827 type: 'uri', 29828 uri: line 29829 }); 29830 return; 29831 } // map tags 29832 29833 29834 var newLines = this.tagMappers.reduce(function (acc, mapper) { 29835 var mappedLine = mapper(line); // skip if unchanged 29836 29837 if (mappedLine === line) { 29838 return acc; 29839 } 29840 29841 return acc.concat([mappedLine]); 29842 }, [line]); 29843 newLines.forEach(function (newLine) { 29844 for (var i = 0; i < _this2.customParsers.length; i++) { 29845 if (_this2.customParsers[i].call(_this2, newLine)) { 29846 return; 29847 } 29848 } // Comments 29849 29850 29851 if (newLine.indexOf('#EXT') !== 0) { 29852 _this2.trigger('data', { 29853 type: 'comment', 29854 text: newLine.slice(1) 29855 }); 29856 29857 return; 29858 } // strip off any carriage returns here so the regex matching 29859 // doesn't have to account for them. 29860 29861 29862 newLine = newLine.replace('\r', ''); // Tags 29863 29864 match = /^#EXTM3U/.exec(newLine); 29865 29866 if (match) { 29867 _this2.trigger('data', { 29868 type: 'tag', 29869 tagType: 'm3u' 29870 }); 29871 29872 return; 29873 } 29874 29875 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine); 29876 29877 if (match) { 29878 event = { 29879 type: 'tag', 29880 tagType: 'inf' 29881 }; 29882 29883 if (match[1]) { 29884 event.duration = parseFloat(match[1]); 29885 } 29886 29887 if (match[2]) { 29888 event.title = match[2]; 29889 } 29890 29891 _this2.trigger('data', event); 29892 29893 return; 29894 } 29895 29896 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine); 29897 29898 if (match) { 29899 event = { 29900 type: 'tag', 29901 tagType: 'targetduration' 29902 }; 29903 29904 if (match[1]) { 29905 event.duration = parseInt(match[1], 10); 29906 } 29907 29908 _this2.trigger('data', event); 29909 29910 return; 29911 } 29912 29913 match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine); 29914 29915 if (match) { 29916 event = { 29917 type: 'tag', 29918 tagType: 'totalduration' 29919 }; 29920 29921 if (match[1]) { 29922 event.duration = parseInt(match[1], 10); 29923 } 29924 29925 _this2.trigger('data', event); 29926 29927 return; 29928 } 29929 29930 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine); 29931 29932 if (match) { 29933 event = { 29934 type: 'tag', 29935 tagType: 'version' 29936 }; 29937 29938 if (match[1]) { 29939 event.version = parseInt(match[1], 10); 29940 } 29941 29942 _this2.trigger('data', event); 29943 29944 return; 29945 } 29946 29947 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 29948 29949 if (match) { 29950 event = { 29951 type: 'tag', 29952 tagType: 'media-sequence' 29953 }; 29954 29955 if (match[1]) { 29956 event.number = parseInt(match[1], 10); 29957 } 29958 29959 _this2.trigger('data', event); 29960 29961 return; 29962 } 29963 29964 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 29965 29966 if (match) { 29967 event = { 29968 type: 'tag', 29969 tagType: 'discontinuity-sequence' 29970 }; 29971 29972 if (match[1]) { 29973 event.number = parseInt(match[1], 10); 29974 } 29975 29976 _this2.trigger('data', event); 29977 29978 return; 29979 } 29980 29981 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine); 29982 29983 if (match) { 29984 event = { 29985 type: 'tag', 29986 tagType: 'playlist-type' 29987 }; 29988 29989 if (match[1]) { 29990 event.playlistType = match[1]; 29991 } 29992 29993 _this2.trigger('data', event); 29994 29995 return; 29996 } 29997 29998 match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine); 29999 30000 if (match) { 30001 event = { 30002 type: 'tag', 30003 tagType: 'byterange' 30004 }; 30005 30006 if (match[1]) { 30007 event.length = parseInt(match[1], 10); 30008 } 30009 30010 if (match[2]) { 30011 event.offset = parseInt(match[2], 10); 30012 } 30013 30014 _this2.trigger('data', event); 30015 30016 return; 30017 } 30018 30019 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine); 30020 30021 if (match) { 30022 event = { 30023 type: 'tag', 30024 tagType: 'allow-cache' 30025 }; 30026 30027 if (match[1]) { 30028 event.allowed = !/NO/.test(match[1]); 30029 } 30030 30031 _this2.trigger('data', event); 30032 30033 return; 30034 } 30035 30036 match = /^#EXT-X-MAP:?(.*)$/.exec(newLine); 30037 30038 if (match) { 30039 event = { 30040 type: 'tag', 30041 tagType: 'map' 30042 }; 30043 30044 if (match[1]) { 30045 var attributes = parseAttributes(match[1]); 30046 30047 if (attributes.URI) { 30048 event.uri = attributes.URI; 30049 } 30050 30051 if (attributes.BYTERANGE) { 30052 var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'), 30053 length = _attributes$BYTERANGE[0], 30054 offset = _attributes$BYTERANGE[1]; 30055 30056 event.byterange = {}; 30057 30058 if (length) { 30059 event.byterange.length = parseInt(length, 10); 30060 } 30061 30062 if (offset) { 30063 event.byterange.offset = parseInt(offset, 10); 30064 } 30065 } 30066 } 30067 30068 _this2.trigger('data', event); 30069 30070 return; 30071 } 30072 30073 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine); 30074 30075 if (match) { 30076 event = { 30077 type: 'tag', 30078 tagType: 'stream-inf' 30079 }; 30080 30081 if (match[1]) { 30082 event.attributes = parseAttributes(match[1]); 30083 30084 if (event.attributes.RESOLUTION) { 30085 var split = event.attributes.RESOLUTION.split('x'); 30086 var resolution = {}; 30087 30088 if (split[0]) { 30089 resolution.width = parseInt(split[0], 10); 30090 } 30091 30092 if (split[1]) { 30093 resolution.height = parseInt(split[1], 10); 30094 } 30095 30096 event.attributes.RESOLUTION = resolution; 30097 } 30098 30099 if (event.attributes.BANDWIDTH) { 30100 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 30101 } 30102 30103 if (event.attributes['PROGRAM-ID']) { 30104 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 30105 } 30106 } 30107 30108 _this2.trigger('data', event); 30109 30110 return; 30111 } 30112 30113 match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine); 30114 30115 if (match) { 30116 event = { 30117 type: 'tag', 30118 tagType: 'media' 30119 }; 30120 30121 if (match[1]) { 30122 event.attributes = parseAttributes(match[1]); 30123 } 30124 30125 _this2.trigger('data', event); 30126 30127 return; 30128 } 30129 30130 match = /^#EXT-X-ENDLIST/.exec(newLine); 30131 30132 if (match) { 30133 _this2.trigger('data', { 30134 type: 'tag', 30135 tagType: 'endlist' 30136 }); 30137 30138 return; 30139 } 30140 30141 match = /^#EXT-X-DISCONTINUITY/.exec(newLine); 30142 30143 if (match) { 30144 _this2.trigger('data', { 30145 type: 'tag', 30146 tagType: 'discontinuity' 30147 }); 30148 30149 return; 30150 } 30151 30152 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine); 30153 30154 if (match) { 30155 event = { 30156 type: 'tag', 30157 tagType: 'program-date-time' 30158 }; 30159 30160 if (match[1]) { 30161 event.dateTimeString = match[1]; 30162 event.dateTimeObject = new Date(match[1]); 30163 } 30164 30165 _this2.trigger('data', event); 30166 30167 return; 30168 } 30169 30170 match = /^#EXT-X-KEY:?(.*)$/.exec(newLine); 30171 30172 if (match) { 30173 event = { 30174 type: 'tag', 30175 tagType: 'key' 30176 }; 30177 30178 if (match[1]) { 30179 event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array 30180 30181 if (event.attributes.IV) { 30182 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 30183 event.attributes.IV = event.attributes.IV.substring(2); 30184 } 30185 30186 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 30187 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 30188 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 30189 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 30190 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 30191 event.attributes.IV = new Uint32Array(event.attributes.IV); 30192 } 30193 } 30194 30195 _this2.trigger('data', event); 30196 30197 return; 30198 } 30199 30200 match = /^#EXT-X-START:?(.*)$/.exec(newLine); 30201 30202 if (match) { 30203 event = { 30204 type: 'tag', 30205 tagType: 'start' 30206 }; 30207 30208 if (match[1]) { 30209 event.attributes = parseAttributes(match[1]); 30210 event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']); 30211 event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE); 30212 } 30213 30214 _this2.trigger('data', event); 30215 30216 return; 30217 } 30218 30219 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine); 30220 30221 if (match) { 30222 event = { 30223 type: 'tag', 30224 tagType: 'cue-out-cont' 30225 }; 30226 30227 if (match[1]) { 30228 event.data = match[1]; 30229 } else { 30230 event.data = ''; 30231 } 30232 30233 _this2.trigger('data', event); 30234 30235 return; 30236 } 30237 30238 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine); 30239 30240 if (match) { 30241 event = { 30242 type: 'tag', 30243 tagType: 'cue-out' 30244 }; 30245 30246 if (match[1]) { 30247 event.data = match[1]; 30248 } else { 30249 event.data = ''; 30250 } 30251 30252 _this2.trigger('data', event); 30253 30254 return; 30255 } 30256 30257 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine); 30258 30259 if (match) { 30260 event = { 30261 type: 'tag', 30262 tagType: 'cue-in' 30263 }; 30264 30265 if (match[1]) { 30266 event.data = match[1]; 30267 } else { 30268 event.data = ''; 30269 } 30270 30271 _this2.trigger('data', event); 30272 30273 return; 30274 } // unknown tag type 30275 30276 30277 _this2.trigger('data', { 30278 type: 'tag', 30279 data: newLine.slice(4) 30280 }); 30281 }); 30282 } 30283 /** 30284 * Add a parser for custom headers 30285 * 30286 * @param {Object} options a map of options for the added parser 30287 * @param {RegExp} options.expression a regular expression to match the custom header 30288 * @param {string} options.customType the custom type to register to the output 30289 * @param {Function} [options.dataParser] function to parse the line into an object 30290 * @param {boolean} [options.segment] should tag data be attached to the segment object 30291 */ 30292 ; 30293 30294 _proto.addParser = function addParser(_ref) { 30295 var _this3 = this; 30296 30297 var expression = _ref.expression, 30298 customType = _ref.customType, 30299 dataParser = _ref.dataParser, 30300 segment = _ref.segment; 30301 30302 if (typeof dataParser !== 'function') { 30303 dataParser = function dataParser(line) { 30304 return line; 30305 }; 30306 } 30307 30308 this.customParsers.push(function (line) { 30309 var match = expression.exec(line); 30310 30311 if (match) { 30312 _this3.trigger('data', { 30313 type: 'custom', 30314 data: dataParser(line), 30315 customType: customType, 30316 segment: segment 30317 }); 30318 30319 return true; 30320 } 30321 }); 30322 } 30323 /** 30324 * Add a custom header mapper 30325 * 30326 * @param {Object} options 30327 * @param {RegExp} options.expression a regular expression to match the custom header 30328 * @param {Function} options.map function to translate tag into a different tag 30329 */ 30330 ; 30331 30332 _proto.addTagMapper = function addTagMapper(_ref2) { 30333 var expression = _ref2.expression, 30334 map = _ref2.map; 30335 30336 var mapFn = function mapFn(line) { 30337 if (expression.test(line)) { 30338 return map(line); 30339 } 30340 30341 return line; 30342 }; 30343 30344 this.tagMappers.push(mapFn); 30345 }; 30346 30347 return ParseStream; 30348 }(Stream); 30349 30350 function decodeB64ToUint8Array(b64Text) { 30351 var decodedString = window$3.atob(b64Text || ''); 30352 var array = new Uint8Array(decodedString.length); 30353 30354 for (var i = 0; i < decodedString.length; i++) { 30355 array[i] = decodedString.charCodeAt(i); 30356 } 30357 30358 return array; 30359 } 30360 /** 30361 * A parser for M3U8 files. The current interpretation of the input is 30362 * exposed as a property `manifest` on parser objects. It's just two lines to 30363 * create and parse a manifest once you have the contents available as a string: 30364 * 30365 * ```js 30366 * var parser = new m3u8.Parser(); 30367 * parser.push(xhr.responseText); 30368 * ``` 30369 * 30370 * New input can later be applied to update the manifest object by calling 30371 * `push` again. 30372 * 30373 * The parser attempts to create a usable manifest object even if the 30374 * underlying input is somewhat nonsensical. It emits `info` and `warning` 30375 * events during the parse if it encounters input that seems invalid or 30376 * requires some property of the manifest object to be defaulted. 30377 * 30378 * @class Parser 30379 * @extends Stream 30380 */ 30381 30382 30383 var Parser = /*#__PURE__*/function (_Stream) { 30384 _inheritsLoose$1(Parser, _Stream); 30385 30386 function Parser() { 30387 var _this; 30388 30389 _this = _Stream.call(this) || this; 30390 _this.lineStream = new LineStream(); 30391 _this.parseStream = new ParseStream(); 30392 30393 _this.lineStream.pipe(_this.parseStream); 30394 /* eslint-disable consistent-this */ 30395 30396 30397 var self = _assertThisInitialized$1(_this); 30398 /* eslint-enable consistent-this */ 30399 30400 30401 var uris = []; 30402 var currentUri = {}; // if specified, the active EXT-X-MAP definition 30403 30404 var currentMap; // if specified, the active decryption key 30405 30406 var _key; 30407 30408 var noop = function noop() {}; 30409 30410 var defaultMediaGroups = { 30411 'AUDIO': {}, 30412 'VIDEO': {}, 30413 'CLOSED-CAPTIONS': {}, 30414 'SUBTITLES': {} 30415 }; // This is the Widevine UUID from DASH IF IOP. The same exact string is 30416 // used in MPDs with Widevine encrypted streams. 30417 30418 var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities 30419 30420 var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data 30421 30422 _this.manifest = { 30423 allowCache: true, 30424 discontinuityStarts: [], 30425 segments: [] 30426 }; // update the manifest with the m3u8 entry from the parse stream 30427 30428 _this.parseStream.on('data', function (entry) { 30429 var mediaGroup; 30430 var rendition; 30431 ({ 30432 tag: function tag() { 30433 // switch based on the tag type 30434 (({ 30435 'allow-cache': function allowCache() { 30436 this.manifest.allowCache = entry.allowed; 30437 30438 if (!('allowed' in entry)) { 30439 this.trigger('info', { 30440 message: 'defaulting allowCache to YES' 30441 }); 30442 this.manifest.allowCache = true; 30443 } 30444 }, 30445 byterange: function byterange() { 30446 var byterange = {}; 30447 30448 if ('length' in entry) { 30449 currentUri.byterange = byterange; 30450 byterange.length = entry.length; 30451 30452 if (!('offset' in entry)) { 30453 this.trigger('info', { 30454 message: 'defaulting offset to zero' 30455 }); 30456 entry.offset = 0; 30457 } 30458 } 30459 30460 if ('offset' in entry) { 30461 currentUri.byterange = byterange; 30462 byterange.offset = entry.offset; 30463 } 30464 }, 30465 endlist: function endlist() { 30466 this.manifest.endList = true; 30467 }, 30468 inf: function inf() { 30469 if (!('mediaSequence' in this.manifest)) { 30470 this.manifest.mediaSequence = 0; 30471 this.trigger('info', { 30472 message: 'defaulting media sequence to zero' 30473 }); 30474 } 30475 30476 if (!('discontinuitySequence' in this.manifest)) { 30477 this.manifest.discontinuitySequence = 0; 30478 this.trigger('info', { 30479 message: 'defaulting discontinuity sequence to zero' 30480 }); 30481 } 30482 30483 if (entry.duration > 0) { 30484 currentUri.duration = entry.duration; 30485 } 30486 30487 if (entry.duration === 0) { 30488 currentUri.duration = 0.01; 30489 this.trigger('info', { 30490 message: 'updating zero segment duration to a small value' 30491 }); 30492 } 30493 30494 this.manifest.segments = uris; 30495 }, 30496 key: function key() { 30497 if (!entry.attributes) { 30498 this.trigger('warn', { 30499 message: 'ignoring key declaration without attribute list' 30500 }); 30501 return; 30502 } // clear the active encryption key 30503 30504 30505 if (entry.attributes.METHOD === 'NONE') { 30506 _key = null; 30507 return; 30508 } 30509 30510 if (!entry.attributes.URI) { 30511 this.trigger('warn', { 30512 message: 'ignoring key declaration without URI' 30513 }); 30514 return; 30515 } // check if the content is encrypted for Widevine 30516 // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf 30517 30518 30519 if (entry.attributes.KEYFORMAT === widevineUuid) { 30520 var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC']; 30521 30522 if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) { 30523 this.trigger('warn', { 30524 message: 'invalid key method provided for Widevine' 30525 }); 30526 return; 30527 } 30528 30529 if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') { 30530 this.trigger('warn', { 30531 message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead' 30532 }); 30533 } 30534 30535 if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') { 30536 this.trigger('warn', { 30537 message: 'invalid key URI provided for Widevine' 30538 }); 30539 return; 30540 } 30541 30542 if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) { 30543 this.trigger('warn', { 30544 message: 'invalid key ID provided for Widevine' 30545 }); 30546 return; 30547 } // if Widevine key attributes are valid, store them as `contentProtection` 30548 // on the manifest to emulate Widevine tag structure in a DASH mpd 30549 30550 30551 this.manifest.contentProtection = { 30552 'com.widevine.alpha': { 30553 attributes: { 30554 schemeIdUri: entry.attributes.KEYFORMAT, 30555 // remove '0x' from the key id string 30556 keyId: entry.attributes.KEYID.substring(2) 30557 }, 30558 // decode the base64-encoded PSSH box 30559 pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1]) 30560 } 30561 }; 30562 return; 30563 } 30564 30565 if (!entry.attributes.METHOD) { 30566 this.trigger('warn', { 30567 message: 'defaulting key method to AES-128' 30568 }); 30569 } // setup an encryption key for upcoming segments 30570 30571 30572 _key = { 30573 method: entry.attributes.METHOD || 'AES-128', 30574 uri: entry.attributes.URI 30575 }; 30576 30577 if (typeof entry.attributes.IV !== 'undefined') { 30578 _key.iv = entry.attributes.IV; 30579 } 30580 }, 30581 'media-sequence': function mediaSequence() { 30582 if (!isFinite(entry.number)) { 30583 this.trigger('warn', { 30584 message: 'ignoring invalid media sequence: ' + entry.number 30585 }); 30586 return; 30587 } 30588 30589 this.manifest.mediaSequence = entry.number; 30590 }, 30591 'discontinuity-sequence': function discontinuitySequence() { 30592 if (!isFinite(entry.number)) { 30593 this.trigger('warn', { 30594 message: 'ignoring invalid discontinuity sequence: ' + entry.number 30595 }); 30596 return; 30597 } 30598 30599 this.manifest.discontinuitySequence = entry.number; 30600 currentTimeline = entry.number; 30601 }, 30602 'playlist-type': function playlistType() { 30603 if (!/VOD|EVENT/.test(entry.playlistType)) { 30604 this.trigger('warn', { 30605 message: 'ignoring unknown playlist type: ' + entry.playlist 30606 }); 30607 return; 30608 } 30609 30610 this.manifest.playlistType = entry.playlistType; 30611 }, 30612 map: function map() { 30613 currentMap = {}; 30614 30615 if (entry.uri) { 30616 currentMap.uri = entry.uri; 30617 } 30618 30619 if (entry.byterange) { 30620 currentMap.byterange = entry.byterange; 30621 } 30622 }, 30623 'stream-inf': function streamInf() { 30624 this.manifest.playlists = uris; 30625 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 30626 30627 if (!entry.attributes) { 30628 this.trigger('warn', { 30629 message: 'ignoring empty stream-inf attributes' 30630 }); 30631 return; 30632 } 30633 30634 if (!currentUri.attributes) { 30635 currentUri.attributes = {}; 30636 } 30637 30638 _extends(currentUri.attributes, entry.attributes); 30639 }, 30640 media: function media() { 30641 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 30642 30643 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 30644 this.trigger('warn', { 30645 message: 'ignoring incomplete or missing media group' 30646 }); 30647 return; 30648 } // find the media group, creating defaults as necessary 30649 30650 30651 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 30652 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 30653 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata 30654 30655 rendition = { 30656 "default": /yes/i.test(entry.attributes.DEFAULT) 30657 }; 30658 30659 if (rendition["default"]) { 30660 rendition.autoselect = true; 30661 } else { 30662 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 30663 } 30664 30665 if (entry.attributes.LANGUAGE) { 30666 rendition.language = entry.attributes.LANGUAGE; 30667 } 30668 30669 if (entry.attributes.URI) { 30670 rendition.uri = entry.attributes.URI; 30671 } 30672 30673 if (entry.attributes['INSTREAM-ID']) { 30674 rendition.instreamId = entry.attributes['INSTREAM-ID']; 30675 } 30676 30677 if (entry.attributes.CHARACTERISTICS) { 30678 rendition.characteristics = entry.attributes.CHARACTERISTICS; 30679 } 30680 30681 if (entry.attributes.FORCED) { 30682 rendition.forced = /yes/i.test(entry.attributes.FORCED); 30683 } // insert the new rendition 30684 30685 30686 mediaGroup[entry.attributes.NAME] = rendition; 30687 }, 30688 discontinuity: function discontinuity() { 30689 currentTimeline += 1; 30690 currentUri.discontinuity = true; 30691 this.manifest.discontinuityStarts.push(uris.length); 30692 }, 30693 'program-date-time': function programDateTime() { 30694 if (typeof this.manifest.dateTimeString === 'undefined') { 30695 // PROGRAM-DATE-TIME is a media-segment tag, but for backwards 30696 // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag 30697 // to the manifest object 30698 // TODO: Consider removing this in future major version 30699 this.manifest.dateTimeString = entry.dateTimeString; 30700 this.manifest.dateTimeObject = entry.dateTimeObject; 30701 } 30702 30703 currentUri.dateTimeString = entry.dateTimeString; 30704 currentUri.dateTimeObject = entry.dateTimeObject; 30705 }, 30706 targetduration: function targetduration() { 30707 if (!isFinite(entry.duration) || entry.duration < 0) { 30708 this.trigger('warn', { 30709 message: 'ignoring invalid target duration: ' + entry.duration 30710 }); 30711 return; 30712 } 30713 30714 this.manifest.targetDuration = entry.duration; 30715 }, 30716 totalduration: function totalduration() { 30717 if (!isFinite(entry.duration) || entry.duration < 0) { 30718 this.trigger('warn', { 30719 message: 'ignoring invalid total duration: ' + entry.duration 30720 }); 30721 return; 30722 } 30723 30724 this.manifest.totalDuration = entry.duration; 30725 }, 30726 start: function start() { 30727 if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) { 30728 this.trigger('warn', { 30729 message: 'ignoring start declaration without appropriate attribute list' 30730 }); 30731 return; 30732 } 30733 30734 this.manifest.start = { 30735 timeOffset: entry.attributes['TIME-OFFSET'], 30736 precise: entry.attributes.PRECISE 30737 }; 30738 }, 30739 'cue-out': function cueOut() { 30740 currentUri.cueOut = entry.data; 30741 }, 30742 'cue-out-cont': function cueOutCont() { 30743 currentUri.cueOutCont = entry.data; 30744 }, 30745 'cue-in': function cueIn() { 30746 currentUri.cueIn = entry.data; 30747 } 30748 })[entry.tagType] || noop).call(self); 30749 }, 30750 uri: function uri() { 30751 currentUri.uri = entry.uri; 30752 uris.push(currentUri); // if no explicit duration was declared, use the target duration 30753 30754 if (this.manifest.targetDuration && !('duration' in currentUri)) { 30755 this.trigger('warn', { 30756 message: 'defaulting segment duration to the target duration' 30757 }); 30758 currentUri.duration = this.manifest.targetDuration; 30759 } // annotate with encryption information, if necessary 30760 30761 30762 if (_key) { 30763 currentUri.key = _key; 30764 } 30765 30766 currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary 30767 30768 if (currentMap) { 30769 currentUri.map = currentMap; 30770 } // prepare for the next URI 30771 30772 30773 currentUri = {}; 30774 }, 30775 comment: function comment() {// comments are not important for playback 30776 }, 30777 custom: function custom() { 30778 // if this is segment-level data attach the output to the segment 30779 if (entry.segment) { 30780 currentUri.custom = currentUri.custom || {}; 30781 currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object 30782 } else { 30783 this.manifest.custom = this.manifest.custom || {}; 30784 this.manifest.custom[entry.customType] = entry.data; 30785 } 30786 } 30787 })[entry.type].call(self); 30788 }); 30789 30790 return _this; 30791 } 30792 /** 30793 * Parse the input string and update the manifest object. 30794 * 30795 * @param {string} chunk a potentially incomplete portion of the manifest 30796 */ 30797 30798 30799 var _proto = Parser.prototype; 30800 30801 _proto.push = function push(chunk) { 30802 this.lineStream.push(chunk); 30803 } 30804 /** 30805 * Flush any remaining input. This can be handy if the last line of an M3U8 30806 * manifest did not contain a trailing newline but the file has been 30807 * completely received. 30808 */ 30809 ; 30810 30811 _proto.end = function end() { 30812 // flush any buffered input 30813 this.lineStream.push('\n'); 30814 } 30815 /** 30816 * Add an additional parser for non-standard tags 30817 * 30818 * @param {Object} options a map of options for the added parser 30819 * @param {RegExp} options.expression a regular expression to match the custom header 30820 * @param {string} options.type the type to register to the output 30821 * @param {Function} [options.dataParser] function to parse the line into an object 30822 * @param {boolean} [options.segment] should tag data be attached to the segment object 30823 */ 30824 ; 30825 30826 _proto.addParser = function addParser(options) { 30827 this.parseStream.addParser(options); 30828 } 30829 /** 30830 * Add a custom header mapper 30831 * 30832 * @param {Object} options 30833 * @param {RegExp} options.expression a regular expression to match the custom header 30834 * @param {Function} options.map function to translate tag into a different tag 30835 */ 30836 ; 30837 30838 _proto.addTagMapper = function addTagMapper(options) { 30839 this.parseStream.addTagMapper(options); 30840 }; 30841 30842 return Parser; 30843 }(Stream); 30844 30845 function _interopDefault(ex) { 30846 return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex; 30847 } 30848 30849 var URLToolkit = _interopDefault(urlToolkit); 30850 30851 var window$1 = _interopDefault(window$3); 30852 30853 var resolveUrl = function resolveUrl(baseUrl, relativeUrl) { 30854 // return early if we don't need to resolve 30855 if (/^[a-z]+:/i.test(relativeUrl)) { 30856 return relativeUrl; 30857 } // if the base URL is relative then combine with the current location 30858 30859 30860 if (!/\/\//i.test(baseUrl)) { 30861 baseUrl = URLToolkit.buildAbsoluteURL(window$1.location && window$1.location.href || '', baseUrl); 30862 } 30863 30864 return URLToolkit.buildAbsoluteURL(baseUrl, relativeUrl); 30865 }; 30866 30867 var resolveUrl_1 = resolveUrl; 30868 30869 function _interopDefault$1(ex) { 30870 return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex; 30871 } 30872 30873 var window$2 = _interopDefault$1(window$3); 30874 30875 var atob = function atob(s) { 30876 return window$2.atob ? window$2.atob(s) : Buffer.from(s, 'base64').toString('binary'); 30877 }; 30878 30879 function decodeB64ToUint8Array$1(b64Text) { 30880 var decodedString = atob(b64Text); 30881 var array = new Uint8Array(decodedString.length); 30882 30883 for (var i = 0; i < decodedString.length; i++) { 30884 array[i] = decodedString.charCodeAt(i); 30885 } 30886 30887 return array; 30888 } 30889 30890 var decodeB64ToUint8Array_1 = decodeB64ToUint8Array$1; 30891 30892 //[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6
vendor: 5,975 bytes, lines 30892-31041
30892] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] 30893 //[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] 30894 //[5] Name ::= NameStartChar (NameChar)* 30895 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 30896 30897 var nameChar = new RegExp("[\\-\\.0-9" + nameStartChar.source.slice(1, -1) + "\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"); 30898 var tagNamePattern = new RegExp('^' + nameStartChar.source + nameChar.source + '*(?:\:' + nameStartChar.source + nameChar.source + '*)?$'); //var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/ 30899 //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(',') 30900 //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE 30901 //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE 30902 30903 var S_TAG = 0; //tag name offerring 30904 30905 var S_ATTR = 1; //attr name offerring 30906 30907 var S_ATTR_SPACE = 2; //attr name end and space offer 30908 30909 var S_EQ = 3; //=space? 30910 30911 var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only) 30912 30913 var S_ATTR_END = 5; //attr value end and no space(quot end) 30914 30915 var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer) 30916 30917 var S_TAG_CLOSE = 7; //closed el<el /> 30918 30919 function XMLReader() {} 30920 30921 XMLReader.prototype = { 30922 parse: function parse(source, defaultNSMap, entityMap) { 30923 var domBuilder = this.domBuilder; 30924 domBuilder.startDocument(); 30925 30926 _copy(defaultNSMap, defaultNSMap = {}); 30927 30928 _parse(source, defaultNSMap, entityMap, domBuilder, this.errorHandler); 30929 30930 domBuilder.endDocument(); 30931 } 30932 }; 30933 30934 function _parse(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) { 30935 function fixedFromCharCode(code) { 30936 // String.prototype.fromCharCode does not supports 30937 // > 2 bytes unicode chars directly 30938 if (code > 0xffff) { 30939 code -= 0x10000; 30940 var surrogate1 = 0xd800 + (code >> 10), 30941 surrogate2 = 0xdc00 + (code & 0x3ff); 30942 return String.fromCharCode(surrogate1, surrogate2); 30943 } else { 30944 return String.fromCharCode(code); 30945 } 30946 } 30947 30948 function entityReplacer(a) { 30949 var k = a.slice(1, -1); 30950 30951 if (k in entityMap) { 30952 return entityMap[k]; 30953 } else if (k.charAt(0) === '#') { 30954 return fixedFromCharCode(parseInt(k.substr(1).replace('x', '0x'))); 30955 } else { 30956 errorHandler.error('entity not found:' + a); 30957 return a; 30958 } 30959 } 30960 30961 function appendText(end) { 30962 //has some bugs 30963 if (end > start) { 30964 var xt = source.substring(start, end).replace(/&#?\w+;/g, entityReplacer); 30965 locator && position(start); 30966 domBuilder.characters(xt, 0, end - start); 30967 start = end; 30968 } 30969 } 30970 30971 function position(p, m) { 30972 while (p >= lineEnd && (m = linePattern.exec(source))) { 30973 lineStart = m.index; 30974 lineEnd = lineStart + m[0].length; 30975 locator.lineNumber++; //console.log('line++:',locator,startPos,endPos) 30976 } 30977 30978 locator.columnNumber = p - lineStart + 1; 30979 } 30980 30981 var lineStart = 0; 30982 var lineEnd = 0; 30983 var linePattern = /.*(?:\r\n?|\n)|.*$/g; 30984 var locator = domBuilder.locator; 30985 var parseStack = [{ 30986 currentNSMap: defaultNSMapCopy 30987 }]; 30988 var closeMap = {}; 30989 var start = 0; 30990 30991 while (true) { 30992 try { 30993 var tagStart = source.indexOf('<', start); 30994 30995 if (tagStart < 0) { 30996 if (!source.substr(start).match(/^\s*$/)) { 30997 var doc = domBuilder.doc; 30998 var text = doc.createTextNode(source.substr(start)); 30999 doc.appendChild(text); 31000 domBuilder.currentElement = text; 31001 } 31002 31003 return; 31004 } 31005 31006 if (tagStart > start) { 31007 appendText(tagStart); 31008 } 31009 31010 switch (source.charAt(tagStart + 1)) { 31011 case '/': 31012 var end = source.indexOf('>', tagStart + 3); 31013 var tagName = source.substring(tagStart + 2, end); 31014 var config = parseStack.pop(); 31015 31016 if (end < 0) { 31017 tagName = source.substring(tagStart + 2).replace(/[\s<].*/, ''); //console.error('#@@@@@@'+tagName) 31018 31019 errorHandler.error("end tag name: " + tagName + ' is not complete:' + config.tagName); 31020 end = tagStart + 1 + tagName.length; 31021 } else if (tagName.match(/\s</)) { 31022 tagName = tagName.replace(/[\s<].*/, ''); 31023 errorHandler.error("end tag name: " + tagName + ' maybe not complete'); 31024 end = tagStart + 1 + tagName.length; 31025 } //console.error(parseStack.length,parseStack) 31026 //console.error(config); 31027 31028 31029 var localNSMap = config.localNSMap; 31030 var endMatch = config.tagName == tagName; 31031 var endIgnoreCaseMach = endMatch || config.tagName && config.tagName.toLowerCase() == tagName.toLowerCase(); 31032 31033 if (endIgnoreCaseMach) { 31034 domBuilder.endElement(config.uri, config.localName, tagName); 31035 31036 if (localNSMap) { 31037 for (var prefix in localNSMap) { 31038 domBuilder.endPrefixMapping(prefix); 31039 } 31040 } 31041
vendor: 14,537 bytes, lines 31042-31510
31042 if (!endMatch) { 31043 errorHandler.fatalError("end tag name: " + tagName + ' is not match the current start tagName:' + config.tagName); 31044 } 31045 } else { 31046 parseStack.push(config); 31047 } 31048 31049 end++; 31050 break; 31051 // end elment 31052 31053 case '?': 31054 // <?...?> 31055 locator && position(tagStart); 31056 end = parseInstruction(source, tagStart, domBuilder); 31057 break; 31058 31059 case '!': 31060 // <!doctype,<![CDATA,<!-- 31061 locator && position(tagStart); 31062 end = parseDCC(source, tagStart, domBuilder, errorHandler); 31063 break; 31064 31065 default: 31066 locator && position(tagStart); 31067 var el = new ElementAttributes(); 31068 var currentNSMap = parseStack[parseStack.length - 1].currentNSMap; //elStartEnd 31069 31070 var end = parseElementStartPart(source, tagStart, el, currentNSMap, entityReplacer, errorHandler); 31071 var len = el.length; 31072 31073 if (!el.closed && fixSelfClosed(source, end, el.tagName, closeMap)) { 31074 el.closed = true; 31075 31076 if (!entityMap.nbsp) { 31077 errorHandler.warning('unclosed xml attribute'); 31078 } 31079 } 31080 31081 if (locator && len) { 31082 var locator2 = copyLocator(locator, {}); //try{//attribute position fixed 31083 31084 for (var i = 0; i < len; i++) { 31085 var a = el[i]; 31086 position(a.offset); 31087 a.locator = copyLocator(locator, {}); 31088 } //}catch(e){console.error('@@@@@'+e)} 31089 31090 31091 domBuilder.locator = locator2; 31092 31093 if (appendElement(el, domBuilder, currentNSMap)) { 31094 parseStack.push(el); 31095 } 31096 31097 domBuilder.locator = locator; 31098 } else { 31099 if (appendElement(el, domBuilder, currentNSMap)) { 31100 parseStack.push(el); 31101 } 31102 } 31103 31104 if (el.uri === 'http://www.w3.org/1999/xhtml' && !el.closed) { 31105 end = parseHtmlSpecialContent(source, end, el.tagName, entityReplacer, domBuilder); 31106 } else { 31107 end++; 31108 } 31109 31110 } 31111 } catch (e) { 31112 errorHandler.error('element parse error: ' + e); //errorHandler.error('element parse error: '+e); 31113 31114 end = -1; //throw e; 31115 } 31116 31117 if (end > start) { 31118 start = end; 31119 } else { 31120 //TODO: è¿éæå¯è½saxåéï¼æä½ç½®é误é£é© 31121 appendText(Math.max(tagStart, start) + 1); 31122 } 31123 } 31124 } 31125 31126 function copyLocator(f, t) { 31127 t.lineNumber = f.lineNumber; 31128 t.columnNumber = f.columnNumber; 31129 return t; 31130 } 31131 /** 31132 * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack); 31133 * @return end of the elementStartPart(end of elementEndPart for selfClosed el) 31134 */ 31135 31136 31137 function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler) { 31138 var attrName; 31139 var value; 31140 var p = ++start; 31141 var s = S_TAG; //status 31142 31143 while (true) { 31144 var c = source.charAt(p); 31145 31146 switch (c) { 31147 case '=': 31148 if (s === S_ATTR) { 31149 //attrName 31150 attrName = source.slice(start, p); 31151 s = S_EQ; 31152 } else if (s === S_ATTR_SPACE) { 31153 s = S_EQ; 31154 } else { 31155 //fatalError: equal must after attrName or space after attrName 31156 throw new Error('attribute equal must after attrName'); 31157 } 31158 31159 break; 31160 31161 case '\'': 31162 case '"': 31163 if (s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE 31164 ) { 31165 //equal 31166 if (s === S_ATTR) { 31167 errorHandler.warning('attribute value must after "="'); 31168 attrName = source.slice(start, p); 31169 } 31170 31171 start = p + 1; 31172 p = source.indexOf(c, start); 31173 31174 if (p > 0) { 31175 value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); 31176 el.add(attrName, value, start - 1); 31177 s = S_ATTR_END; 31178 } else { 31179 //fatalError: no end quot match 31180 throw new Error('attribute value no end \'' + c + '\' match'); 31181 } 31182 } else if (s == S_ATTR_NOQUOT_VALUE) { 31183 value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); //console.log(attrName,value,start,p) 31184 31185 el.add(attrName, value, start); //console.dir(el) 31186 31187 errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!'); 31188 start = p + 1; 31189 s = S_ATTR_END; 31190 } else { 31191 //fatalError: no equal before 31192 throw new Error('attribute value must after "="'); 31193 } 31194 31195 break; 31196 31197 case '/': 31198 switch (s) { 31199 case S_TAG: 31200 el.setTagName(source.slice(start, p)); 31201 31202 case S_ATTR_END: 31203 case S_TAG_SPACE: 31204 case S_TAG_CLOSE: 31205 s = S_TAG_CLOSE; 31206 el.closed = true; 31207 31208 case S_ATTR_NOQUOT_VALUE: 31209 case S_ATTR: 31210 case S_ATTR_SPACE: 31211 break; 31212 //case S_EQ: 31213 31214 default: 31215 throw new Error("attribute invalid close char('/')"); 31216 } 31217 31218 break; 31219 31220 case '': 31221 //end document 31222 //throw new Error('unexpected end of input') 31223 errorHandler.error('unexpected end of input'); 31224 31225 if (s == S_TAG) { 31226 el.setTagName(source.slice(start, p)); 31227 } 31228 31229 return p; 31230 31231 case '>': 31232 switch (s) { 31233 case S_TAG: 31234 el.setTagName(source.slice(start, p)); 31235 31236 case S_ATTR_END: 31237 case S_TAG_SPACE: 31238 case S_TAG_CLOSE: 31239 break; 31240 //normal 31241 31242 case S_ATTR_NOQUOT_VALUE: //Compatible state 31243 31244 case S_ATTR: 31245 value = source.slice(start, p); 31246 31247 if (value.slice(-1) === '/') { 31248 el.closed = true; 31249 value = value.slice(0, -1); 31250 } 31251 31252 case S_ATTR_SPACE: 31253 if (s === S_ATTR_SPACE) { 31254 value = attrName; 31255 } 31256 31257 if (s == S_ATTR_NOQUOT_VALUE) { 31258 errorHandler.warning('attribute "' + value + '" missed quot(")!!'); 31259 el.add(attrName, value.replace(/&#?\w+;/g, entityReplacer), start); 31260 } else { 31261 if (currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !value.match(/^(?:disabled|checked|selected)$/i)) { 31262 errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!'); 31263 } 31264 31265 el.add(value, value, start); 31266 } 31267 31268 break; 31269 31270 case S_EQ: 31271 throw new Error('attribute value missed!!'); 31272 } // console.log(tagName,tagNamePattern,tagNamePattern.test(tagName)) 31273 31274 31275 return p; 31276 31277 /*xml space '\x20' | #x9 | #xD | #xA; */ 31278 31279 case "\x80": 31280 c = ' '; 31281 31282 default: 31283 if (c <= ' ') { 31284 //space 31285 switch (s) { 31286 case S_TAG: 31287 el.setTagName(source.slice(start, p)); //tagName 31288 31289 s = S_TAG_SPACE; 31290 break; 31291 31292 case S_ATTR: 31293 attrName = source.slice(start, p); 31294 s = S_ATTR_SPACE; 31295 break; 31296 31297 case S_ATTR_NOQUOT_VALUE: 31298 var value = source.slice(start, p).replace(/&#?\w+;/g, entityReplacer); 31299 errorHandler.warning('attribute "' + value + '" missed quot(")!!'); 31300 el.add(attrName, value, start); 31301 31302 case S_ATTR_END: 31303 s = S_TAG_SPACE; 31304 break; 31305 //case S_TAG_SPACE: 31306 //case S_EQ: 31307 //case S_ATTR_SPACE: 31308 // void();break; 31309 //case S_TAG_CLOSE: 31310 //ignore warning 31311 } 31312 } else { 31313 //not space 31314 //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE 31315 //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE 31316 switch (s) { 31317 //case S_TAG:void();break; 31318 //case S_ATTR:void();break; 31319 //case S_ATTR_NOQUOT_VALUE:void();break; 31320 case S_ATTR_SPACE: 31321 var tagName = el.tagName; 31322 31323 if (currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !attrName.match(/^(?:disabled|checked|selected)$/i)) { 31324 errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!'); 31325 } 31326 31327 el.add(attrName, attrName, start); 31328 start = p; 31329 s = S_ATTR; 31330 break; 31331 31332 case S_ATTR_END: 31333 errorHandler.warning('attribute space is required"' + attrName + '"!!'); 31334 31335 case S_TAG_SPACE: 31336 s = S_ATTR; 31337 start = p; 31338 break; 31339 31340 case S_EQ: 31341 s = S_ATTR_NOQUOT_VALUE; 31342 start = p; 31343 break; 31344 31345 case S_TAG_CLOSE: 31346 throw new Error("elements closed character '/' and '>' must be connected to"); 31347 } 31348 } 31349 31350 } //end outer switch 31351 //console.log('p++',p) 31352 31353 31354 p++; 31355 } 31356 } 31357 /** 31358 * @return true if has new namespace define 31359 */ 31360 31361 31362 function appendElement(el, domBuilder, currentNSMap) { 31363 var tagName = el.tagName; 31364 var localNSMap = null; //var currentNSMap = parseStack[parseStack.length-1].currentNSMap; 31365 31366 var i = el.length; 31367 31368 while (i--) { 31369 var a = el[i]; 31370 var qName = a.qName; 31371 var value = a.value; 31372 var nsp = qName.indexOf(':'); 31373 31374 if (nsp > 0) { 31375 var prefix = a.prefix = qName.slice(0, nsp); 31376 var localName = qName.slice(nsp + 1); 31377 var nsPrefix = prefix === 'xmlns' && localName; 31378 } else { 31379 localName = qName; 31380 prefix = null; 31381 nsPrefix = qName === 'xmlns' && ''; 31382 } //can not set prefix,because prefix !== '' 31383 31384 31385 a.localName = localName; //prefix == null for no ns prefix attribute 31386 31387 if (nsPrefix !== false) { 31388 //hack!! 31389 if (localNSMap == null) { 31390 localNSMap = {}; //console.log(currentNSMap,0) 31391 31392 _copy(currentNSMap, currentNSMap = {}); //console.log(currentNSMap,1) 31393 31394 } 31395 31396 currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; 31397 a.uri = 'http://www.w3.org/2000/xmlns/'; 31398 domBuilder.startPrefixMapping(nsPrefix, value); 31399 } 31400 } 31401 31402 var i = el.length; 31403 31404 while (i--) { 31405 a = el[i]; 31406 var prefix = a.prefix; 31407 31408 if (prefix) { 31409 //no prefix attribute has no namespace 31410 if (prefix === 'xml') { 31411 a.uri = 'http://www.w3.org/XML/1998/namespace'; 31412 } 31413 31414 if (prefix !== 'xmlns') { 31415 a.uri = currentNSMap[prefix || '']; //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)} 31416 } 31417 } 31418 } 31419 31420 var nsp = tagName.indexOf(':'); 31421 31422 if (nsp > 0) { 31423 prefix = el.prefix = tagName.slice(0, nsp); 31424 localName = el.localName = tagName.slice(nsp + 1); 31425 } else { 31426 prefix = null; //important!! 31427 31428 localName = el.localName = tagName; 31429 } //no prefix element has default namespace 31430 31431 31432 var ns = el.uri = currentNSMap[prefix || '']; 31433 domBuilder.startElement(ns, localName, tagName, el); //endPrefixMapping and startPrefixMapping have not any help for dom builder 31434 //localNSMap = null 31435 31436 if (el.closed) { 31437 domBuilder.endElement(ns, localName, tagName); 31438 31439 if (localNSMap) { 31440 for (prefix in localNSMap) { 31441 domBuilder.endPrefixMapping(prefix); 31442 } 31443 } 31444 } else { 31445 el.currentNSMap = currentNSMap; 31446 el.localNSMap = localNSMap; //parseStack.push(el); 31447 31448 return true; 31449 } 31450 } 31451 31452 function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) { 31453 if (/^(?:script|textarea)$/i.test(tagName)) { 31454 var elEndStart = source.indexOf('</' + tagName + '>', elStartEnd); 31455 var text = source.substring(elStartEnd + 1, elEndStart); 31456 31457 if (/[&<]/.test(text)) { 31458 if (/^script$/i.test(tagName)) { 31459 //if(!/\]\]>/.test(text)){ 31460 //lexHandler.startCDATA(); 31461 domBuilder.characters(text, 0, text.length); //lexHandler.endCDATA(); 31462 31463 return elEndStart; //} 31464 } //}else{//text area 31465 31466 31467 text = text.replace(/&#?\w+;/g, entityReplacer); 31468 domBuilder.characters(text, 0, text.length); 31469 return elEndStart; //} 31470 } 31471 } 31472 31473 return elStartEnd + 1; 31474 } 31475 31476 function fixSelfClosed(source, elStartEnd, tagName, closeMap) { 31477 //if(tagName in closeMap){ 31478 var pos = closeMap[tagName]; 31479 31480 if (pos == null) { 31481 //console.log(tagName) 31482 pos = source.lastIndexOf('</' + tagName + '>'); 31483 31484 if (pos < elStartEnd) { 31485 //å¿è®°éå 31486 pos = source.lastIndexOf('</' + tagName); 31487 } 31488 31489 closeMap[tagName] = pos; 31490 } 31491 31492 return pos < elStartEnd; //} 31493 } 31494 31495 function _copy(source, target) { 31496 for (var n in source) { 31497 target[n] = source[n]; 31498 } 31499 } 31500 31501 function parseDCC(source, start, domBuilder, errorHandler) { 31502 //sure start with '<!' 31503 var next = source.charAt(start + 2); 31504 31505 switch (next) { 31506 case '-': 31507 if (source.charAt(start + 3) === '-') { 31508 var end = source.indexOf('-->', start + 4); //append comment source.substring(4,end)//<!-- 31509 31510 if (end > start) {
31511 domBuilder.comment(source, start + 4, end - start - 4); 31512 return end + 3; 31513 } else { 31514 errorHandler.error("Unclosed comment"); 31515 return -1; 31516 } 31517 } else { 31518 //error 31519 return -1; 31520 } 31521 31522 default: 31523 if (source.substr(start + 3, 6) == 'CDATA[') { 31524 var end = source.indexOf(']]>', start + 9); 31525 domBuilder.startCDATA(); 31526 domBuilder.characters(source, start + 9, end - start - 9); 31527 domBuilder.endCDATA(); 31528 return end + 3; 31529 } //<!DOCTYPE 31530 //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId) 31531 31532 31533 var matchs = split(source, start); 31534 var len = matchs.length; 31535 31536 if (len > 1 && /!doctype/i.test(matchs[0][0])) { 31537 var name = matchs[1][0]; 31538 var pubid = len > 3 && /^public$/i.test(matchs[2][0]) && matchs[3][0]; 31539 var sysid = len > 4 && matchs[4][0]; 31540 var lastMatch = matchs[len - 1]; 31541 domBuilder.startDTD(name, pubid && pubid.replace(/^(['"])(.*?)\1$/, '$2'), sysid && sysid.replace(/^(['"])(.*?)\1$/, '$2')); 31542 domBuilder.endDTD(); 31543 return lastMatch.index + lastMatch[0].length; 31544 } 31545 31546 } 31547 31548 return -1; 31549 } 31550 31551 function parseInstruction(source, start, domBuilder) { 31552 var end = source.indexOf('?>', start); 31553 31554 if (end) { 31555 var match = source.substring(start, end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/); 31556 31557 if (match) { 31558 var len = match[0].length; 31559 domBuilder.processingInstruction(match[1], match[2]); 31560 return end + 2; 31561 } else { 31562 //error 31563 return -1; 31564 } 31565 } 31566 31567 return -1; 31568 } 31569 /** 31570 * @param source 31571 */ 31572 31573 31574 function ElementAttributes(source) {} 31575 31576 ElementAttributes.prototype = { 31577 setTagName: function setTagName(tagName) { 31578 if (!tagNamePattern.test(tagName)) { 31579 throw new Error('invalid tagName:' + tagName); 31580 } 31581 31582 this.tagName = tagName; 31583 }, 31584 add: function add(qName, value, offset) { 31585 if (!tagNamePattern.test(qName)) { 31586 throw new Error('invalid attribute:' + qName); 31587 } 31588 31589 this[this.length++] = { 31590 qName: qName, 31591 value: value, 31592 offset: offset 31593 }; 31594 }, 31595 length: 0, 31596 getLocalName: function getLocalName(i) { 31597 return this[i].localName; 31598 }, 31599 getLocator: function getLocator(i) { 31600 return this[i].locator; 31601 }, 31602 getQName: function getQName(i) { 31603 return this[i].qName; 31604 }, 31605 getURI: function getURI(i) { 31606 return this[i].uri; 31607 }, 31608 getValue: function getValue(i) { 31609 return this[i].value; 31610 } // ,getIndex:function(uri, localName)){ 31611 // if(localName){ 31612 // 31613 // }else{ 31614 // var qName = uri 31615 // } 31616 // }, 31617 // getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))}, 31618 // getType:function(uri,localName){} 31619 // getType:function(i){}, 31620 31621 }; 31622 31623 function _set_proto_(thiz, parent) { 31624 thiz.__proto__ = parent; 31625 return thiz; 31626 } 31627 31628 if (!(_set_proto_({}, _set_proto_.prototype) instanceof _set_proto_)) { 31629 _set_proto_ = function _set_proto_(thiz, parent) { 31630 function p() {} 31631 p.prototype = parent; 31632 p = new p(); 31633 31634 for (parent in thiz) { 31635 p[parent] = thiz[parent]; 31636 } 31637 31638 return p; 31639 }; 31640 } 31641 31642 function split(source, start) { 31643 var match; 31644 var buf = []; 31645 var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g; 31646 reg.lastIndex = start; 31647 reg.exec(source); //skip < 31648 31649 while (match = reg.exec(source)) { 31650 buf.push(match); 31651 if (match[1]) return buf; 31652 } 31653 } 31654 31655 var XMLReader_1 = XMLReader; 31656 var sax = { 31657 XMLReader: XMLReader_1 31658 }; 31659 31660 /* 31661 * DOM Level 2 31662 * Object DOMException 31663 * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html 31664 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html 31665 */ 31666 function copy(src, dest) { 31667 for (var p in src) { 31668 dest[p] = src[p]; 31669 } 31670 } 31671 /** 31672 ^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));? 31673 ^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));? 31674 */ 31675 31676 31677 function _extends$1(Class, Super) { 31678 var pt = Class.prototype; 31679 31680 if (Object.create) { 31681 var ppt = Object.create(Super.prototype); 31682 pt.__proto__ = ppt; 31683 } 31684 31685 if (!(pt instanceof Super)) { 31686 var t = function t() {}; 31687 t.prototype = Super.prototype; 31688 t = new t(); 31689 copy(pt, t); 31690 Class.prototype = pt = t; 31691 } 31692 31693 if (pt.constructor != Class) { 31694 if (typeof Class != 'function') { 31695 console.error("unknow Class:" + Class); 31696 } 31697 31698 pt.constructor = Class; 31699 } 31700 } 31701 31702 var htmlns = 'http://www.w3.org/1999/xhtml'; // Node Types 31703 31704 var NodeType = {};
vendor: 7,802 bytes, lines 31705-31915
31705 var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; 31706 var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; 31707 var TEXT_NODE = NodeType.TEXT_NODE = 3; 31708 var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; 31709 var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; 31710 var ENTITY_NODE = NodeType.ENTITY_NODE = 6; 31711 var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; 31712 var COMMENT_NODE = NodeType.COMMENT_NODE = 8; 31713 var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; 31714 var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; 31715 var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; 31716 var NOTATION_NODE = NodeType.NOTATION_NODE = 12; // ExceptionCode 31717 31718 var ExceptionCode = {}; 31719 var ExceptionMessage = {}; 31720 var INDEX_SIZE_ERR = ExceptionCode.INDEX_SIZE_ERR = (ExceptionMessage[1] = "Index size error", 1); 31721 var DOMSTRING_SIZE_ERR = ExceptionCode.DOMSTRING_SIZE_ERR = (ExceptionMessage[2] = "DOMString size error", 2); 31722 var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = (ExceptionMessage[3] = "Hierarchy request error", 3); 31723 var WRONG_DOCUMENT_ERR = ExceptionCode.WRONG_DOCUMENT_ERR = (ExceptionMessage[4] = "Wrong document", 4); 31724 var INVALID_CHARACTER_ERR = ExceptionCode.INVALID_CHARACTER_ERR = (ExceptionMessage[5] = "Invalid character", 5); 31725 var NO_DATA_ALLOWED_ERR = ExceptionCode.NO_DATA_ALLOWED_ERR = (ExceptionMessage[6] = "No data allowed", 6); 31726 var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = (ExceptionMessage[7] = "No modification allowed", 7); 31727 var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = (ExceptionMessage[8] = "Not found", 8); 31728 var NOT_SUPPORTED_ERR = ExceptionCode.NOT_SUPPORTED_ERR = (ExceptionMessage[9] = "Not supported", 9); 31729 var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = (ExceptionMessage[10] = "Attribute in use", 10); //level2 31730 31731 var INVALID_STATE_ERR = ExceptionCode.INVALID_STATE_ERR = (ExceptionMessage[11] = "Invalid state", 11); 31732 var SYNTAX_ERR = ExceptionCode.SYNTAX_ERR = (ExceptionMessage[12] = "Syntax error", 12); 31733 var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = (ExceptionMessage[13] = "Invalid modification", 13); 31734 var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = (ExceptionMessage[14] = "Invalid namespace", 14); 31735 var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = (ExceptionMessage[15] = "Invalid access", 15); 31736 31737 function DOMException(code, message) { 31738 if (message instanceof Error) { 31739 var error = message; 31740 } else { 31741 error = this; 31742 Error.call(this, ExceptionMessage[code]); 31743 this.message = ExceptionMessage[code]; 31744 if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException); 31745 } 31746 31747 error.code = code; 31748 if (message) this.message = this.message + ": " + message; 31749 return error; 31750 } 31751 DOMException.prototype = Error.prototype; 31752 copy(ExceptionCode, DOMException); 31753 /** 31754 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 31755 * 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. 31756 * The items in the NodeList are accessible via an integral index, starting from 0. 31757 */ 31758 31759 function NodeList() {} 31760 NodeList.prototype = { 31761 /** 31762 * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive. 31763 * @standard level1 31764 */ 31765 length: 0, 31766 31767 /** 31768 * 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. 31769 * @standard level1 31770 * @param index unsigned long 31771 * Index into the collection. 31772 * @return Node 31773 * The node at the indexth position in the NodeList, or null if that is not a valid index. 31774 */ 31775 item: function item(index) { 31776 return this[index] || null; 31777 }, 31778 toString: function toString(isHTML, nodeFilter) { 31779 for (var buf = [], i = 0; i < this.length; i++) { 31780 serializeToString(this[i], buf, isHTML, nodeFilter); 31781 } 31782 31783 return buf.join(''); 31784 } 31785 }; 31786 31787 function LiveNodeList(node, refresh) { 31788 this._node = node; 31789 this._refresh = refresh; 31790 31791 _updateLiveList(this); 31792 } 31793 31794 function _updateLiveList(list) { 31795 var inc = list._node._inc || list._node.ownerDocument._inc; 31796 31797 if (list._inc != inc) { 31798 var ls = list._refresh(list._node); //console.log(ls.length) 31799 31800 31801 __set__(list, 'length', ls.length); 31802 31803 copy(ls, list); 31804 list._inc = inc; 31805 } 31806 } 31807 31808 LiveNodeList.prototype.item = function (i) { 31809 _updateLiveList(this); 31810 31811 return this[i]; 31812 }; 31813 31814 _extends$1(LiveNodeList, NodeList); 31815 /** 31816 * 31817 * 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. 31818 * NamedNodeMap objects in the DOM are live. 31819 * used for attributes or DocumentType entities 31820 */ 31821 31822 31823 function NamedNodeMap() {} 31824 31825 function _findNodeIndex(list, node) { 31826 var i = list.length; 31827 31828 while (i--) { 31829 if (list[i] === node) { 31830 return i; 31831 } 31832 } 31833 } 31834 31835 function _addNamedNode(el, list, newAttr, oldAttr) { 31836 if (oldAttr) { 31837 list[_findNodeIndex(list, oldAttr)] = newAttr; 31838 } else { 31839 list[list.length++] = newAttr; 31840 } 31841 31842 if (el) { 31843 newAttr.ownerElement = el; 31844 var doc = el.ownerDocument; 31845 31846 if (doc) { 31847 oldAttr && _onRemoveAttribute(doc, el, oldAttr); 31848 31849 _onAddAttribute(doc, el, newAttr); 31850 } 31851 } 31852 } 31853 31854 function _removeNamedNode(el, list, attr) { 31855 //console.log('remove attr:'+attr) 31856 var i = _findNodeIndex(list, attr); 31857 31858 if (i >= 0) { 31859 var lastIndex = list.length - 1; 31860 31861 while (i < lastIndex) { 31862 list[i] = list[++i]; 31863 } 31864 31865 list.length = lastIndex; 31866 31867 if (el) { 31868 var doc = el.ownerDocument; 31869 31870 if (doc) { 31871 _onRemoveAttribute(doc, el, attr); 31872 31873 attr.ownerElement = null; 31874 } 31875 } 31876 } else { 31877 throw DOMException(NOT_FOUND_ERR, new Error(el.tagName + '@' + attr)); 31878 } 31879 } 31880 31881 NamedNodeMap.prototype = { 31882 length: 0, 31883 item: NodeList.prototype.item, 31884 getNamedItem: function getNamedItem(key) { 31885 // if(key.indexOf(':')>0 || key == 'xmlns'){ 31886 // return null; 31887 // } 31888 //console.log() 31889 var i = this.length; 31890 31891 while (i--) { 31892 var attr = this[i]; //console.log(attr.nodeName,key) 31893 31894 if (attr.nodeName == key) { 31895 return attr; 31896 } 31897 } 31898 }, 31899 setNamedItem: function setNamedItem(attr) { 31900 var el = attr.ownerElement; 31901 31902 if (el && el != this._ownerElement) { 31903 throw new DOMException(INUSE_ATTRIBUTE_ERR); 31904 } 31905 31906 var oldAttr = this.getNamedItem(attr.nodeName); 31907 31908 _addNamedNode(this._ownerElement, this, attr, oldAttr); 31909 31910 return oldAttr; 31911 }, 31912 31913 /* returns Node */ 31914 setNamedItemNS: function setNamedItemNS(attr) { 31915 // raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ER
vendor: 20,382 bytes, lines 31915-32594
31915R,INUSE_ATTRIBUTE_ERR 31916 var el = attr.ownerElement, 31917 oldAttr; 31918 31919 if (el && el != this._ownerElement) { 31920 throw new DOMException(INUSE_ATTRIBUTE_ERR); 31921 } 31922 31923 oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName); 31924 31925 _addNamedNode(this._ownerElement, this, attr, oldAttr); 31926 31927 return oldAttr; 31928 }, 31929 31930 /* returns Node */ 31931 removeNamedItem: function removeNamedItem(key) { 31932 var attr = this.getNamedItem(key); 31933 31934 _removeNamedNode(this._ownerElement, this, attr); 31935 31936 return attr; 31937 }, 31938 // raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR 31939 //for level2 31940 removeNamedItemNS: function removeNamedItemNS(namespaceURI, localName) { 31941 var attr = this.getNamedItemNS(namespaceURI, localName); 31942 31943 _removeNamedNode(this._ownerElement, this, attr); 31944 31945 return attr; 31946 }, 31947 getNamedItemNS: function getNamedItemNS(namespaceURI, localName) { 31948 var i = this.length; 31949 31950 while (i--) { 31951 var node = this[i]; 31952 31953 if (node.localName == localName && node.namespaceURI == namespaceURI) { 31954 return node; 31955 } 31956 } 31957 31958 return null; 31959 } 31960 }; 31961 /** 31962 * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 31963 */ 31964 31965 function DOMImplementation( 31966 /* Object */ 31967 features) { 31968 this._features = {}; 31969 31970 if (features) { 31971 for (var feature in features) { 31972 this._features = features[feature]; 31973 } 31974 } 31975 } 31976 DOMImplementation.prototype = { 31977 hasFeature: function hasFeature( 31978 /* string */ 31979 feature, 31980 /* string */ 31981 version) { 31982 var versions = this._features[feature.toLowerCase()]; 31983 31984 if (versions && (!version || version in versions)) { 31985 return true; 31986 } else { 31987 return false; 31988 } 31989 }, 31990 // Introduced in DOM Level 2: 31991 createDocument: function createDocument(namespaceURI, qualifiedName, doctype) { 31992 // raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR,WRONG_DOCUMENT_ERR 31993 var doc = new Document(); 31994 doc.implementation = this; 31995 doc.childNodes = new NodeList(); 31996 doc.doctype = doctype; 31997 31998 if (doctype) { 31999 doc.appendChild(doctype); 32000 } 32001 32002 if (qualifiedName) { 32003 var root = doc.createElementNS(namespaceURI, qualifiedName); 32004 doc.appendChild(root); 32005 } 32006 32007 return doc; 32008 }, 32009 // Introduced in DOM Level 2: 32010 createDocumentType: function createDocumentType(qualifiedName, publicId, systemId) { 32011 // raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR 32012 var node = new DocumentType(); 32013 node.name = qualifiedName; 32014 node.nodeName = qualifiedName; 32015 node.publicId = publicId; 32016 node.systemId = systemId; // Introduced in DOM Level 2: 32017 //readonly attribute DOMString internalSubset; 32018 //TODO:.. 32019 // readonly attribute NamedNodeMap entities; 32020 // readonly attribute NamedNodeMap notations; 32021 32022 return node; 32023 } 32024 }; 32025 /** 32026 * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 32027 */ 32028 32029 function Node() {} 32030 Node.prototype = { 32031 firstChild: null, 32032 lastChild: null, 32033 previousSibling: null, 32034 nextSibling: null, 32035 attributes: null, 32036 parentNode: null, 32037 childNodes: null, 32038 ownerDocument: null, 32039 nodeValue: null, 32040 namespaceURI: null, 32041 prefix: null, 32042 localName: null, 32043 // Modified in DOM Level 2: 32044 insertBefore: function insertBefore(newChild, refChild) { 32045 //raises 32046 return _insertBefore(this, newChild, refChild); 32047 }, 32048 replaceChild: function replaceChild(newChild, oldChild) { 32049 //raises 32050 this.insertBefore(newChild, oldChild); 32051 32052 if (oldChild) { 32053 this.removeChild(oldChild); 32054 } 32055 }, 32056 removeChild: function removeChild(oldChild) { 32057 return _removeChild(this, oldChild); 32058 }, 32059 appendChild: function appendChild(newChild) { 32060 return this.insertBefore(newChild, null); 32061 }, 32062 hasChildNodes: function hasChildNodes() { 32063 return this.firstChild != null; 32064 }, 32065 cloneNode: function cloneNode(deep) { 32066 return _cloneNode(this.ownerDocument || this, this, deep); 32067 }, 32068 // Modified in DOM Level 2: 32069 normalize: function normalize() { 32070 var child = this.firstChild; 32071 32072 while (child) { 32073 var next = child.nextSibling; 32074 32075 if (next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE) { 32076 this.removeChild(next); 32077 child.appendData(next.data); 32078 } else { 32079 child.normalize(); 32080 child = next; 32081 } 32082 } 32083 }, 32084 // Introduced in DOM Level 2: 32085 isSupported: function isSupported(feature, version) { 32086 return this.ownerDocument.implementation.hasFeature(feature, version); 32087 }, 32088 // Introduced in DOM Level 2: 32089 hasAttributes: function hasAttributes() { 32090 return this.attributes.length > 0; 32091 }, 32092 lookupPrefix: function lookupPrefix(namespaceURI) { 32093 var el = this; 32094 32095 while (el) { 32096 var map = el._nsMap; //console.dir(map) 32097 32098 if (map) { 32099 for (var n in map) { 32100 if (map[n] == namespaceURI) { 32101 return n; 32102 } 32103 } 32104 } 32105 32106 el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode; 32107 } 32108 32109 return null; 32110 }, 32111 // Introduced in DOM Level 3: 32112 lookupNamespaceURI: function lookupNamespaceURI(prefix) { 32113 var el = this; 32114 32115 while (el) { 32116 var map = el._nsMap; //console.dir(map) 32117 32118 if (map) { 32119 if (prefix in map) { 32120 return map[prefix]; 32121 } 32122 } 32123 32124 el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode; 32125 } 32126 32127 return null; 32128 }, 32129 // Introduced in DOM Level 3: 32130 isDefaultNamespace: function isDefaultNamespace(namespaceURI) { 32131 var prefix = this.lookupPrefix(namespaceURI); 32132 return prefix == null; 32133 } 32134 }; 32135 32136 function _xmlEncoder(c) { 32137 return c == '<' && '<' || c == '>' && '>' || c == '&' && '&' || c == '"' && '"' || '&#' + c.charCodeAt() + ';'; 32138 } 32139 32140 copy(NodeType, Node); 32141 copy(NodeType, Node.prototype); 32142 /** 32143 * @param callback return true for continue,false for break 32144 * @return boolean true: break visit; 32145 */ 32146 32147 function _visitNode(node, callback) { 32148 if (callback(node)) { 32149 return true; 32150 } 32151 32152 if (node = node.firstChild) { 32153 do { 32154 if (_visitNode(node, callback)) { 32155 return true; 32156 } 32157 } while (node = node.nextSibling); 32158 } 32159 } 32160 32161 function Document() {} 32162 32163 function _onAddAttribute(doc, el, newAttr) { 32164 doc && doc._inc++; 32165 var ns = newAttr.namespaceURI; 32166 32167 if (ns == 'http://www.w3.org/2000/xmlns/') { 32168 //update namespace 32169 el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value; 32170 } 32171 } 32172 32173 function _onRemoveAttribute(doc, el, newAttr, remove) { 32174 doc && doc._inc++; 32175 var ns = newAttr.namespaceURI; 32176 32177 if (ns == 'http://www.w3.org/2000/xmlns/') { 32178 //update namespace 32179 delete el._nsMap[newAttr.prefix ? newAttr.localName : '']; 32180 } 32181 } 32182 32183 function _onUpdateChild(doc, el, newChild) { 32184 if (doc && doc._inc) { 32185 doc._inc++; //update childNodes 32186 32187 var cs = el.childNodes; 32188 32189 if (newChild) { 32190 cs[cs.length++] = newChild; 32191 } else { 32192 //console.log(1) 32193 var child = el.firstChild; 32194 var i = 0; 32195 32196 while (child) { 32197 cs[i++] = child; 32198 child = child.nextSibling; 32199 } 32200 32201 cs.length = i; 32202 } 32203 } 32204 } 32205 /** 32206 * attributes; 32207 * children; 32208 * 32209 * writeable properties: 32210 * nodeValue,Attr:value,CharacterData:data 32211 * prefix 32212 */ 32213 32214 32215 function _removeChild(parentNode, child) { 32216 var previous = child.previousSibling; 32217 var next = child.nextSibling; 32218 32219 if (previous) { 32220 previous.nextSibling = next; 32221 } else { 32222 parentNode.firstChild = next; 32223 } 32224 32225 if (next) { 32226 next.previousSibling = previous; 32227 } else { 32228 parentNode.lastChild = previous; 32229 } 32230 32231 _onUpdateChild(parentNode.ownerDocument, parentNode); 32232 32233 return child; 32234 } 32235 /** 32236 * preformance key(refChild == null) 32237 */ 32238 32239 32240 function _insertBefore(parentNode, newChild, nextChild) { 32241 var cp = newChild.parentNode; 32242 32243 if (cp) { 32244 cp.removeChild(newChild); //remove and update 32245 } 32246 32247 if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) { 32248 var newFirst = newChild.firstChild; 32249 32250 if (newFirst == null) { 32251 return newChild; 32252 } 32253 32254 var newLast = newChild.lastChild; 32255 } else { 32256 newFirst = newLast = newChild; 32257 } 32258 32259 var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild; 32260 newFirst.previousSibling = pre; 32261 newLast.nextSibling = nextChild; 32262 32263 if (pre) { 32264 pre.nextSibling = newFirst; 32265 } else { 32266 parentNode.firstChild = newFirst; 32267 } 32268 32269 if (nextChild == null) { 32270 parentNode.lastChild = newLast; 32271 } else { 32272 nextChild.previousSibling = newLast; 32273 } 32274 32275 do { 32276 newFirst.parentNode = parentNode; 32277 } while (newFirst !== newLast && (newFirst = newFirst.nextSibling)); 32278 32279 _onUpdateChild(parentNode.ownerDocument || parentNode, parentNode); //console.log(parentNode.lastChild.nextSibling == null) 32280 32281 32282 if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) { 32283 newChild.firstChild = newChild.lastChild = null; 32284 } 32285 32286 return newChild; 32287 } 32288 32289 function _appendSingleChild(parentNode, newChild) { 32290 var cp = newChild.parentNode; 32291 32292 if (cp) { 32293 var pre = parentNode.lastChild; 32294 cp.removeChild(newChild); //remove and update 32295 32296 var pre = parentNode.lastChild; 32297 } 32298 32299 var pre = parentNode.lastChild; 32300 newChild.parentNode = parentNode; 32301 newChild.previousSibling = pre; 32302 newChild.nextSibling = null; 32303 32304 if (pre) { 32305 pre.nextSibling = newChild; 32306 } else { 32307 parentNode.firstChild = newChild; 32308 } 32309 32310 parentNode.lastChild = newChild; 32311 32312 _onUpdateChild(parentNode.ownerDocument, parentNode, newChild); 32313 32314 return newChild; //console.log("__aa",parentNode.lastChild.nextSibling == null) 32315 } 32316 32317 Document.prototype = { 32318 //implementation : null, 32319 nodeName: '#document', 32320 nodeType: DOCUMENT_NODE, 32321 doctype: null, 32322 documentElement: null, 32323 _inc: 1, 32324 insertBefore: function insertBefore(newChild, refChild) { 32325 //raises 32326 if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) { 32327 var child = newChild.firstChild; 32328 32329 while (child) { 32330 var next = child.nextSibling; 32331 this.insertBefore(child, refChild); 32332 child = next; 32333 } 32334 32335 return newChild; 32336 } 32337 32338 if (this.documentElement == null && newChild.nodeType == ELEMENT_NODE) { 32339 this.documentElement = newChild; 32340 } 32341 32342 return _insertBefore(this, newChild, refChild), newChild.ownerDocument = this, newChild; 32343 }, 32344 removeChild: function removeChild(oldChild) { 32345 if (this.documentElement == oldChild) { 32346 this.documentElement = null; 32347 } 32348 32349 return _removeChild(this, oldChild); 32350 }, 32351 // Introduced in DOM Level 2: 32352 importNode: function importNode(importedNode, deep) { 32353 return _importNode(this, importedNode, deep); 32354 }, 32355 // Introduced in DOM Level 2: 32356 getElementById: function getElementById(id) { 32357 var rtv = null; 32358 32359 _visitNode(this.documentElement, function (node) { 32360 if (node.nodeType == ELEMENT_NODE) { 32361 if (node.getAttribute('id') == id) { 32362 rtv = node; 32363 return true; 32364 } 32365 } 32366 }); 32367 32368 return rtv; 32369 }, 32370 //document factory method: 32371 createElement: function createElement(tagName) { 32372 var node = new Element(); 32373 node.ownerDocument = this; 32374 node.nodeName = tagName; 32375 node.tagName = tagName; 32376 node.childNodes = new NodeList(); 32377 var attrs = node.attributes = new NamedNodeMap(); 32378 attrs._ownerElement = node; 32379 return node; 32380 }, 32381 createDocumentFragment: function createDocumentFragment() { 32382 var node = new DocumentFragment(); 32383 node.ownerDocument = this; 32384 node.childNodes = new NodeList(); 32385 return node; 32386 }, 32387 createTextNode: function createTextNode(data) { 32388 var node = new Text(); 32389 node.ownerDocument = this; 32390 node.appendData(data); 32391 return node; 32392 }, 32393 createComment: function createComment(data) { 32394 var node = new Comment(); 32395 node.ownerDocument = this; 32396 node.appendData(data); 32397 return node; 32398 }, 32399 createCDATASection: function createCDATASection(data) { 32400 var node = new CDATASection(); 32401 node.ownerDocument = this; 32402 node.appendData(data); 32403 return node; 32404 }, 32405 createProcessingInstruction: function createProcessingInstruction(target, data) { 32406 var node = new ProcessingInstruction(); 32407 node.ownerDocument = this; 32408 node.tagName = node.target = target; 32409 node.nodeValue = node.data = data; 32410 return node; 32411 }, 32412 createAttribute: function createAttribute(name) { 32413 var node = new Attr(); 32414 node.ownerDocument = this; 32415 node.name = name; 32416 node.nodeName = name; 32417 node.localName = name; 32418 node.specified = true; 32419 return node; 32420 }, 32421 createEntityReference: function createEntityReference(name) { 32422 var node = new EntityReference(); 32423 node.ownerDocument = this; 32424 node.nodeName = name; 32425 return node; 32426 }, 32427 // Introduced in DOM Level 2: 32428 createElementNS: function createElementNS(namespaceURI, qualifiedName) { 32429 var node = new Element(); 32430 var pl = qualifiedName.split(':'); 32431 var attrs = node.attributes = new NamedNodeMap(); 32432 node.childNodes = new NodeList(); 32433 node.ownerDocument = this; 32434 node.nodeName = qualifiedName; 32435 node.tagName = qualifiedName; 32436 node.namespaceURI = namespaceURI; 32437 32438 if (pl.length == 2) { 32439 node.prefix = pl[0]; 32440 node.localName = pl[1]; 32441 } else { 32442 //el.prefix = null; 32443 node.localName = qualifiedName; 32444 } 32445 32446 attrs._ownerElement = node; 32447 return node; 32448 }, 32449 // Introduced in DOM Level 2: 32450 createAttributeNS: function createAttributeNS(namespaceURI, qualifiedName) { 32451 var node = new Attr(); 32452 var pl = qualifiedName.split(':'); 32453 node.ownerDocument = this; 32454 node.nodeName = qualifiedName; 32455 node.name = qualifiedName; 32456 node.namespaceURI = namespaceURI; 32457 node.specified = true; 32458 32459 if (pl.length == 2) { 32460 node.prefix = pl[0]; 32461 node.localName = pl[1]; 32462 } else { 32463 //el.prefix = null; 32464 node.localName = qualifiedName; 32465 } 32466 32467 return node; 32468 } 32469 }; 32470 32471 _extends$1(Document, Node); 32472 32473 function Element() { 32474 this._nsMap = {}; 32475 } 32476 Element.prototype = { 32477 nodeType: ELEMENT_NODE, 32478 hasAttribute: function hasAttribute(name) { 32479 return this.getAttributeNode(name) != null; 32480 }, 32481 getAttribute: function getAttribute(name) { 32482 var attr = this.getAttributeNode(name); 32483 return attr && attr.value || ''; 32484 }, 32485 getAttributeNode: function getAttributeNode(name) { 32486 return this.attributes.getNamedItem(name); 32487 }, 32488 setAttribute: function setAttribute(name, value) { 32489 var attr = this.ownerDocument.createAttribute(name); 32490 attr.value = attr.nodeValue = "" + value; 32491 this.setAttributeNode(attr); 32492 }, 32493 removeAttribute: function removeAttribute(name) { 32494 var attr = this.getAttributeNode(name); 32495 attr && this.removeAttributeNode(attr); 32496 }, 32497 //four real opeartion method 32498 appendChild: function appendChild(newChild) { 32499 if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) { 32500 return this.insertBefore(newChild, null); 32501 } else { 32502 return _appendSingleChild(this, newChild); 32503 } 32504 }, 32505 setAttributeNode: function setAttributeNode(newAttr) { 32506 return this.attributes.setNamedItem(newAttr); 32507 }, 32508 setAttributeNodeNS: function setAttributeNodeNS(newAttr) { 32509 return this.attributes.setNamedItemNS(newAttr); 32510 }, 32511 removeAttributeNode: function removeAttributeNode(oldAttr) { 32512 //console.log(this == oldAttr.ownerElement) 32513 return this.attributes.removeNamedItem(oldAttr.nodeName); 32514 }, 32515 //get real attribute name,and remove it by removeAttributeNode 32516 removeAttributeNS: function removeAttributeNS(namespaceURI, localName) { 32517 var old = this.getAttributeNodeNS(namespaceURI, localName); 32518 old && this.removeAttributeNode(old); 32519 }, 32520 hasAttributeNS: function hasAttributeNS(namespaceURI, localName) { 32521 return this.getAttributeNodeNS(namespaceURI, localName) != null; 32522 }, 32523 getAttributeNS: function getAttributeNS(namespaceURI, localName) { 32524 var attr = this.getAttributeNodeNS(namespaceURI, localName); 32525 return attr && attr.value || ''; 32526 }, 32527 setAttributeNS: function setAttributeNS(namespaceURI, qualifiedName, value) { 32528 var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); 32529 attr.value = attr.nodeValue = "" + value; 32530 this.setAttributeNode(attr); 32531 }, 32532 getAttributeNodeNS: function getAttributeNodeNS(namespaceURI, localName) { 32533 return this.attributes.getNamedItemNS(namespaceURI, localName); 32534 }, 32535 getElementsByTagName: function getElementsByTagName(tagName) { 32536 return new LiveNodeList(this, function (base) { 32537 var ls = []; 32538 32539 _visitNode(base, function (node) { 32540 if (node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)) { 32541 ls.push(node); 32542 } 32543 }); 32544 32545 return ls; 32546 }); 32547 }, 32548 getElementsByTagNameNS: function getElementsByTagNameNS(namespaceURI, localName) { 32549 return new LiveNodeList(this, function (base) { 32550 var ls = []; 32551 32552 _visitNode(base, function (node) { 32553 if (node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)) { 32554 ls.push(node); 32555 } 32556 }); 32557 32558 return ls; 32559 }); 32560 } 32561 }; 32562 Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; 32563 Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; 32564 32565 _extends$1(Element, Node); 32566 32567 function Attr() {} 32568 Attr.prototype.nodeType = ATTRIBUTE_NODE; 32569 32570 _extends$1(Attr, Node); 32571 32572 function CharacterData() {} 32573 CharacterData.prototype = { 32574 data: '', 32575 substringData: function substringData(offset, count) { 32576 return this.data.substring(offset, offset + count); 32577 }, 32578 appendData: function appendData(text) { 32579 text = this.data + text; 32580 this.nodeValue = this.data = text; 32581 this.length = text.length; 32582 }, 32583 insertData: function insertData(offset, text) { 32584 this.replaceData(offset, 0, text); 32585 }, 32586 appendChild: function appendChild(newChild) { 32587 throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]); 32588 }, 32589 deleteData: function deleteData(offset, count) { 32590 this.replaceData(offset, count, ""); 32591 }, 32592 replaceData: function replaceData(offset, count, text) { 32593 var start = this.data.substring(0, offset); 32594 var end = this.data.substring(offset + count);
vendor: 9,620 bytes, lines 32595-32926
32595 text = start + text + end; 32596 this.nodeValue = this.data = text; 32597 this.length = text.length; 32598 } 32599 }; 32600 32601 _extends$1(CharacterData, Node); 32602 32603 function Text() {} 32604 Text.prototype = { 32605 nodeName: "#text", 32606 nodeType: TEXT_NODE, 32607 splitText: function splitText(offset) { 32608 var text = this.data; 32609 var newText = text.substring(offset); 32610 text = text.substring(0, offset); 32611 this.data = this.nodeValue = text; 32612 this.length = text.length; 32613 var newNode = this.ownerDocument.createTextNode(newText); 32614 32615 if (this.parentNode) { 32616 this.parentNode.insertBefore(newNode, this.nextSibling); 32617 } 32618 32619 return newNode; 32620 } 32621 }; 32622 32623 _extends$1(Text, CharacterData); 32624 32625 function Comment() {} 32626 Comment.prototype = { 32627 nodeName: "#comment", 32628 nodeType: COMMENT_NODE 32629 }; 32630 32631 _extends$1(Comment, CharacterData); 32632 32633 function CDATASection() {} 32634 CDATASection.prototype = { 32635 nodeName: "#cdata-section", 32636 nodeType: CDATA_SECTION_NODE 32637 }; 32638 32639 _extends$1(CDATASection, CharacterData); 32640 32641 function DocumentType() {} 32642 DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; 32643 32644 _extends$1(DocumentType, Node); 32645 32646 function Notation() {} 32647 Notation.prototype.nodeType = NOTATION_NODE; 32648 32649 _extends$1(Notation, Node); 32650 32651 function Entity() {} 32652 Entity.prototype.nodeType = ENTITY_NODE; 32653 32654 _extends$1(Entity, Node); 32655 32656 function EntityReference() {} 32657 EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; 32658 32659 _extends$1(EntityReference, Node); 32660 32661 function DocumentFragment() {} 32662 DocumentFragment.prototype.nodeName = "#document-fragment"; 32663 DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; 32664 32665 _extends$1(DocumentFragment, Node); 32666 32667 function ProcessingInstruction() {} 32668 32669 ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; 32670 32671 _extends$1(ProcessingInstruction, Node); 32672 32673 function XMLSerializer() {} 32674 32675 XMLSerializer.prototype.serializeToString = function (node, isHtml, nodeFilter) { 32676 return nodeSerializeToString.call(node, isHtml, nodeFilter); 32677 }; 32678 32679 Node.prototype.toString = nodeSerializeToString; 32680 32681 function nodeSerializeToString(isHtml, nodeFilter) { 32682 var buf = []; 32683 var refNode = this.nodeType == 9 ? this.documentElement : this; 32684 var prefix = refNode.prefix; 32685 var uri = refNode.namespaceURI; 32686 32687 if (uri && prefix == null) { 32688 //console.log(prefix) 32689 var prefix = refNode.lookupPrefix(uri); 32690 32691 if (prefix == null) { 32692 //isHTML = true; 32693 var visibleNamespaces = [{ 32694 namespace: uri, 32695 prefix: null 32696 } //{namespace:uri,prefix:''} 32697 ]; 32698 } 32699 } 32700 32701 serializeToString(this, buf, isHtml, nodeFilter, visibleNamespaces); //console.log('###',this.nodeType,uri,prefix,buf.join('')) 32702 32703 return buf.join(''); 32704 } 32705 32706 function needNamespaceDefine(node, isHTML, visibleNamespaces) { 32707 var prefix = node.prefix || ''; 32708 var uri = node.namespaceURI; 32709 32710 if (!prefix && !uri) { 32711 return false; 32712 } 32713 32714 if (prefix === "xml" && uri === "http://www.w3.org/XML/1998/namespace" || uri == 'http://www.w3.org/2000/xmlns/') { 32715 return false; 32716 } 32717 32718 var i = visibleNamespaces.length; //console.log('@@@@',node.tagName,prefix,uri,visibleNamespaces) 32719 32720 while (i--) { 32721 var ns = visibleNamespaces[i]; // get namespace prefix 32722 //console.log(node.nodeType,node.tagName,ns.prefix,prefix) 32723 32724 if (ns.prefix == prefix) { 32725 return ns.namespace != uri; 32726 } 32727 } //console.log(isHTML,uri,prefix=='') 32728 //if(isHTML && prefix ==null && uri == 'http://www.w3.org/1999/xhtml'){ 32729 // return false; 32730 //} 32731 //node.flag = '11111' 32732 //console.error(3,true,node.flag,node.prefix,node.namespaceURI) 32733 32734 32735 return true; 32736 } 32737 32738 function serializeToString(node, buf, isHTML, nodeFilter, visibleNamespaces) { 32739 if (nodeFilter) { 32740 node = nodeFilter(node); 32741 32742 if (node) { 32743 if (typeof node == 'string') { 32744 buf.push(node); 32745 return; 32746 } 32747 } else { 32748 return; 32749 } //buf.sort.apply(attrs, attributeSorter); 32750 32751 } 32752 32753 switch (node.nodeType) { 32754 case ELEMENT_NODE: 32755 if (!visibleNamespaces) visibleNamespaces = []; 32756 var startVisibleNamespaces = visibleNamespaces.length; 32757 var attrs = node.attributes; 32758 var len = attrs.length; 32759 var child = node.firstChild; 32760 var nodeName = node.tagName; 32761 isHTML = htmlns === node.namespaceURI || isHTML; 32762 buf.push('<', nodeName); 32763 32764 for (var i = 0; i < len; i++) { 32765 // add namespaces for attributes 32766 var attr = attrs.item(i); 32767 32768 if (attr.prefix == 'xmlns') { 32769 visibleNamespaces.push({ 32770 prefix: attr.localName, 32771 namespace: attr.value 32772 }); 32773 } else if (attr.nodeName == 'xmlns') { 32774 visibleNamespaces.push({ 32775 prefix: '', 32776 namespace: attr.value 32777 }); 32778 } 32779 } 32780 32781 for (var i = 0; i < len; i++) { 32782 var attr = attrs.item(i); 32783 32784 if (needNamespaceDefine(attr, isHTML, visibleNamespaces)) { 32785 var prefix = attr.prefix || ''; 32786 var uri = attr.namespaceURI; 32787 var ns = prefix ? ' xmlns:' + prefix : " xmlns"; 32788 buf.push(ns, '="', uri, '"'); 32789 visibleNamespaces.push({ 32790 prefix: prefix, 32791 namespace: uri 32792 }); 32793 } 32794 32795 serializeToString(attr, buf, isHTML, nodeFilter, visibleNamespaces); 32796 } // add namespace for current node 32797 32798 32799 if (needNamespaceDefine(node, isHTML, visibleNamespaces)) { 32800 var prefix = node.prefix || ''; 32801 var uri = node.namespaceURI; 32802 var ns = prefix ? ' xmlns:' + prefix : " xmlns"; 32803 buf.push(ns, '="', uri, '"'); 32804 visibleNamespaces.push({ 32805 prefix: prefix, 32806 namespace: uri 32807 }); 32808 } 32809 32810 if (child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)) { 32811 buf.push('>'); //if is cdata child node 32812 32813 if (isHTML && /^script$/i.test(nodeName)) { 32814 while (child) { 32815 if (child.data) { 32816 buf.push(child.data); 32817 } else { 32818 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces); 32819 } 32820 32821 child = child.nextSibling; 32822 } 32823 } else { 32824 while (child) { 32825 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces); 32826 child = child.nextSibling; 32827 } 32828 } 32829 32830 buf.push('</', nodeName, '>'); 32831 } else { 32832 buf.push('/>'); 32833 } // remove added visible namespaces 32834 //visibleNamespaces.length = startVisibleNamespaces; 32835 32836 32837 return; 32838 32839 case DOCUMENT_NODE: 32840 case DOCUMENT_FRAGMENT_NODE: 32841 var child = node.firstChild; 32842 32843 while (child) { 32844 serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces); 32845 child = child.nextSibling; 32846 } 32847 32848 return; 32849 32850 case ATTRIBUTE_NODE: 32851 return buf.push(' ', node.name, '="', node.value.replace(/[<&"]/g, _xmlEncoder), '"'); 32852 32853 case TEXT_NODE: 32854 return buf.push(node.data.replace(/[<&]/g, _xmlEncoder)); 32855 32856 case CDATA_SECTION_NODE: 32857 return buf.push('<![CDATA[', node.data, ']]>'); 32858 32859 case COMMENT_NODE: 32860 return buf.push("<!--", node.data, "-->"); 32861 32862 case DOCUMENT_TYPE_NODE: 32863 var pubid = node.publicId; 32864 var sysid = node.systemId; 32865 buf.push('<!DOCTYPE ', node.name); 32866 32867 if (pubid) { 32868 buf.push(' PUBLIC "', pubid); 32869 32870 if (sysid && sysid != '.') { 32871 buf.push('" "', sysid); 32872 } 32873 32874 buf.push('">'); 32875 } else if (sysid && sysid != '.') { 32876 buf.push(' SYSTEM "', sysid, '">'); 32877 } else { 32878 var sub = node.internalSubset; 32879 32880 if (sub) { 32881 buf.push(" [", sub, "]"); 32882 } 32883 32884 buf.push(">"); 32885 } 32886 32887 return; 32888 32889 case PROCESSING_INSTRUCTION_NODE: 32890 return buf.push("<?", node.target, " ", node.data, "?>"); 32891 32892 case ENTITY_REFERENCE_NODE: 32893 return buf.push('&', node.nodeName, ';'); 32894 //case ENTITY_NODE: 32895 //case NOTATION_NODE: 32896 32897 default: 32898 buf.push('??', node.nodeName); 32899 } 32900 } 32901 32902 function _importNode(doc, node, deep) { 32903 var node2; 32904 32905 switch (node.nodeType) { 32906 case ELEMENT_NODE: 32907 node2 = node.cloneNode(false); 32908 node2.ownerDocument = doc; 32909 //var attrs = node2.attributes; 32910 //var len = attrs.length; 32911 //for(var i=0;i<len;i++){ 32912 //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep)); 32913 //} 32914 32915 case DOCUMENT_FRAGMENT_NODE: 32916 break; 32917 32918 case ATTRIBUTE_NODE: 32919 deep = true; 32920 break; 32921 //case ENTITY_REFERENCE_NODE: 32922 //case PROCESSING_INSTRUCTION_NODE: 32923 ////case TEXT_NODE: 32924 //case CDATA_SECTION_NODE: 32925 //case COMMENT_NODE: 32926 // deep = false;
32927 // break; 32928 //case DOCUMENT_NODE: 32929 //case DOCUMENT_TYPE_NODE: 32930 //cannot be imported. 32931 //case ENTITY_NODE: 32932 //case NOTATION_NODEï¼ 32933 //can not hit in level3 32934 //default:throw e; 32935 } 32936 32937 if (!node2) { 32938 node2 = node.cloneNode(false); //false 32939 } 32940 32941 node2.ownerDocument = doc; 32942 node2.parentNode = null; 32943 32944 if (deep) { 32945 var child = node.firstChild; 32946 32947 while (child) { 32948 node2.appendChild(_importNode(doc, child, deep)); 32949 child = child.nextSibling; 32950 } 32951 } 32952 32953 return node2; 32954 } // 32955 //var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1, 32956 // attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,}; 32957 32958 32959 function _cloneNode(doc, node, deep) { 32960 var node2 = new node.constructor(); 32961 32962 for (var n in node) { 32963 var v = node[n]; 32964 32965 if (typeof v != 'object') { 32966 if (v != node2[n]) { 32967 node2[n] = v; 32968 } 32969 } 32970 } 32971 32972 if (node.childNodes) { 32973 node2.childNodes = new NodeList(); 32974 } 32975 32976 node2.ownerDocument = doc; 32977 32978 switch (node2.nodeType) { 32979 case ELEMENT_NODE: 32980 var attrs = node.attributes; 32981 var attrs2 = node2.attributes = new NamedNodeMap(); 32982 var len = attrs.length; 32983 attrs2._ownerElement = node2; 32984 32985 for (var i = 0; i < len; i++) { 32986 node2.setAttributeNode(_cloneNode(doc, attrs.item(i), true)); 32987 } 32988 32989 break; 32990 32991 case ATTRIBUTE_NODE: 32992 deep = true; 32993 } 32994 32995 if (deep) { 32996 var child = node.firstChild; 32997 32998 while (child) { 32999 node2.appendChild(_cloneNode(doc, child, deep)); 33000 child = child.nextSibling; 33001 } 33002 } 33003 33004 return node2; 33005 } 33006 33007 function __set__(object, key, value) { 33008 object[key] = value; 33009 } //do dynamic 33010 33011 33012 try { 33013 if (Object.defineProperty) { 33014 var getTextContent = function getTextContent(node) { 33015 switch (node.nodeType) { 33016 case ELEMENT_NODE: 33017 case DOCUMENT_FRAGMENT_NODE: 33018 var buf = []; 33019 node = node.firstChild; 33020 33021 while (node) { 33022 if (node.nodeType !== 7 && node.nodeType !== 8) { 33023 buf.push(getTextContent(node)); 33024 } 33025 33026 node = node.nextSibling; 33027 } 33028 33029 return buf.join(''); 33030 33031 default: 33032 return node.nodeValue; 33033 } 33034 }; 33035 33036 Object.defineProperty(LiveNodeList.prototype, 'length', { 33037 get: function get() { 33038 _updateLiveList(this); 33039 33040 return this.$$length; 33041 } 33042 }); 33043 Object.defineProperty(Node.prototype, 'textContent', { 33044 get: function get() { 33045 return getTextContent(this); 33046 }, 33047 set: function set(data) { 33048 switch (this.nodeType) { 33049 case ELEMENT_NODE: 33050 case DOCUMENT_FRAGMENT_NODE: 33051 while (this.firstChild) { 33052 this.removeChild(this.firstChild); 33053 } 33054 33055 if (data || String(data)) { 33056 this.appendChild(this.ownerDocument.createTextNode(data)); 33057 } 33058 33059 break; 33060 33061 default: 33062 //TODO: 33063 this.data = data; 33064 this.value = data; 33065 this.nodeValue = data; 33066 } 33067 } 33068 }); 33069 33070 __set__ = function __set__(object, key, value) { 33071 //console.log(value) 33072 object['$$' + key] = value; 33073 }; 33074 } 33075 } catch (e) {} //ie8 33076 //if(typeof require == 'function'){ 33077 33078 33079 var DOMImplementation_1 = DOMImplementation; 33080 var XMLSerializer_1 = XMLSerializer; //} 33081 33082 var dom = { 33083 DOMImplementation: DOMImplementation_1, 33084 XMLSerializer: XMLSerializer_1 33085 }; 33086 33087 var domParser = createCommonjsModule(function (module, exports) { 33088 function DOMParser(options) { 33089 this.options = options || { 33090 locator: {} 33091 }; 33092 } 33093 33094 DOMParser.prototype.parseFromString = function (source, mimeType) { 33095 var options = this.options; 33096 var sax = new XMLReader(); 33097 var domBuilder = options.domBuilder || new DOMHandler(); //contentHandler and LexicalHandler 33098 33099 var errorHandler = options.errorHandler; 33100 var locator = options.locator; 33101 var defaultNSMap = options.xmlns || {}; 33102 var entityMap = { 33103 'lt': '<', 33104 'gt': '>', 33105 'amp': '&', 33106 'quot': '"', 33107 'apos': "'" 33108 }; 33109 33110 if (locator) { 33111 domBuilder.setDocumentLocator(locator); 33112 } 33113 33114 sax.errorHandler = buildErrorHandler(errorHandler, domBuilder, locator); 33115 sax.domBuilder = options.domBuilder || domBuilder; 33116 33117 if (/\/x?html?$/.test(mimeType)) { 33118 entityMap.nbsp = '\xa0'; 33119 entityMap.copy = '\xa9'; 33120 defaultNSMap[''] = 'http://www.w3.org/1999/xhtml'; 33121 } 33122 33123 defaultNSMap.xml = defaultNSMap.xml || 'http://www.w3.org/XML/1998/namespace'; 33124
vendor: 5,143 bytes, lines 33125-33272
33125 if (source) { 33126 sax.parse(source, defaultNSMap, entityMap); 33127 } else { 33128 sax.errorHandler.error("invalid doc source"); 33129 } 33130 33131 return domBuilder.doc; 33132 }; 33133 33134 function buildErrorHandler(errorImpl, domBuilder, locator) { 33135 if (!errorImpl) { 33136 if (domBuilder instanceof DOMHandler) { 33137 return domBuilder; 33138 } 33139 33140 errorImpl = domBuilder; 33141 } 33142 33143 var errorHandler = {}; 33144 var isCallback = errorImpl instanceof Function; 33145 locator = locator || {}; 33146 33147 function build(key) { 33148 var fn = errorImpl[key]; 33149 33150 if (!fn && isCallback) { 33151 fn = errorImpl.length == 2 ? function (msg) { 33152 errorImpl(key, msg); 33153 } : errorImpl; 33154 } 33155 33156 errorHandler[key] = fn && function (msg) { 33157 fn('[xmldom ' + key + ']\t' + msg + _locator(locator)); 33158 } || function () {}; 33159 } 33160 33161 build('warning'); 33162 build('error'); 33163 build('fatalError'); 33164 return errorHandler; 33165 } //console.log('#\n\n\n\n\n\n\n####') 33166 33167 /** 33168 * +ContentHandler+ErrorHandler 33169 * +LexicalHandler+EntityResolver2 33170 * -DeclHandler-DTDHandler 33171 * 33172 * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler 33173 * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 33174 * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html 33175 */ 33176 33177 33178 function DOMHandler() { 33179 this.cdata = false; 33180 } 33181 33182 function position(locator, node) { 33183 node.lineNumber = locator.lineNumber; 33184 node.columnNumber = locator.columnNumber; 33185 } 33186 /** 33187 * @see org.xml.sax.ContentHandler#startDocument 33188 * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html 33189 */ 33190 33191 33192 DOMHandler.prototype = { 33193 startDocument: function startDocument() { 33194 this.doc = new DOMImplementation().createDocument(null, null, null); 33195 33196 if (this.locator) { 33197 this.doc.documentURI = this.locator.systemId; 33198 } 33199 }, 33200 startElement: function startElement(namespaceURI, localName, qName, attrs) { 33201 var doc = this.doc; 33202 var el = doc.createElementNS(namespaceURI, qName || localName); 33203 var len = attrs.length; 33204 appendElement(this, el); 33205 this.currentElement = el; 33206 this.locator && position(this.locator, el); 33207 33208 for (var i = 0; i < len; i++) { 33209 var namespaceURI = attrs.getURI(i); 33210 var value = attrs.getValue(i); 33211 var qName = attrs.getQName(i); 33212 var attr = doc.createAttributeNS(namespaceURI, qName); 33213 this.locator && position(attrs.getLocator(i), attr); 33214 attr.value = attr.nodeValue = value; 33215 el.setAttributeNode(attr); 33216 } 33217 }, 33218 endElement: function endElement(namespaceURI, localName, qName) { 33219 var current = this.currentElement; 33220 var tagName = current.tagName; 33221 this.currentElement = current.parentNode; 33222 }, 33223 startPrefixMapping: function startPrefixMapping(prefix, uri) {}, 33224 endPrefixMapping: function endPrefixMapping(prefix) {}, 33225 processingInstruction: function processingInstruction(target, data) { 33226 var ins = this.doc.createProcessingInstruction(target, data); 33227 this.locator && position(this.locator, ins); 33228 appendElement(this, ins); 33229 }, 33230 ignorableWhitespace: function ignorableWhitespace(ch, start, length) {}, 33231 characters: function characters(chars, start, length) { 33232 chars = _toString.apply(this, arguments); //console.log(chars) 33233 33234 if (chars) { 33235 if (this.cdata) { 33236 var charNode = this.doc.createCDATASection(chars); 33237 } else { 33238 var charNode = this.doc.createTextNode(chars); 33239 } 33240 33241 if (this.currentElement) { 33242 this.currentElement.appendChild(charNode); 33243 } else if (/^\s*$/.test(chars)) { 33244 this.doc.appendChild(charNode); //process xml 33245 } 33246 33247 this.locator && position(this.locator, charNode); 33248 } 33249 }, 33250 skippedEntity: function skippedEntity(name) {}, 33251 endDocument: function endDocument() { 33252 this.doc.normalize(); 33253 }, 33254 setDocumentLocator: function setDocumentLocator(locator) { 33255 if (this.locator = locator) { 33256 // && !('lineNumber' in locator)){ 33257 locator.lineNumber = 0; 33258 } 33259 }, 33260 //LexicalHandler 33261 comment: function comment(chars, start, length) { 33262 chars = _toString.apply(this, arguments); 33263 var comm = this.doc.createComment(chars); 33264 this.locator && position(this.locator, comm); 33265 appendElement(this, comm); 33266 }, 33267 startCDATA: function startCDATA() { 33268 //used in characters() methods 33269 this.cdata = true; 33270 }, 33271 endCDATA: function endCDATA() { 33272 this.cdata = false;
vendor: 4,078 bytes, lines 33273-33376
33273 }, 33274 startDTD: function startDTD(name, publicId, systemId) { 33275 var impl = this.doc.implementation; 33276 33277 if (impl && impl.createDocumentType) { 33278 var dt = impl.createDocumentType(name, publicId, systemId); 33279 this.locator && position(this.locator, dt); 33280 appendElement(this, dt); 33281 } 33282 }, 33283 33284 /** 33285 * @see org.xml.sax.ErrorHandler 33286 * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html 33287 */ 33288 warning: function warning(error) { 33289 console.warn('[xmldom warning]\t' + error, _locator(this.locator)); 33290 }, 33291 error: function error(_error) { 33292 console.error('[xmldom error]\t' + _error, _locator(this.locator)); 33293 }, 33294 fatalError: function fatalError(error) { 33295 console.error('[xmldom fatalError]\t' + error, _locator(this.locator)); 33296 throw error; 33297 } 33298 }; 33299 33300 function _locator(l) { 33301 if (l) { 33302 return '\n@' + (l.systemId || '') + '#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']'; 33303 } 33304 } 33305 33306 function _toString(chars, start, length) { 33307 if (typeof chars == 'string') { 33308 return chars.substr(start, length); 33309 } else { 33310 //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") 33311 if (chars.length >= start + length || start) { 33312 return new java.lang.String(chars, start, length) + ''; 33313 } 33314 33315 return chars; 33316 } 33317 } 33318 /* 33319 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html 33320 * used method of org.xml.sax.ext.LexicalHandler: 33321 * #comment(chars, start, length) 33322 * #startCDATA() 33323 * #endCDATA() 33324 * #startDTD(name, publicId, systemId) 33325 * 33326 * 33327 * IGNORED method of org.xml.sax.ext.LexicalHandler: 33328 * #endDTD() 33329 * #startEntity(name) 33330 * #endEntity(name) 33331 * 33332 * 33333 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html 33334 * IGNORED method of org.xml.sax.ext.DeclHandler 33335 * #attributeDecl(eName, aName, type, mode, value) 33336 * #elementDecl(name, model) 33337 * #externalEntityDecl(name, publicId, systemId) 33338 * #internalEntityDecl(name, value) 33339 * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html 33340 * IGNORED method of org.xml.sax.EntityResolver2 33341 * #resolveEntity(String name,String publicId,String baseURI,String systemId) 33342 * #resolveEntity(publicId, systemId) 33343 * #getExternalSubset(name, baseURI) 33344 * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html 33345 * IGNORED method of org.xml.sax.DTDHandler 33346 * #notationDecl(name, publicId, systemId) {}; 33347 * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; 33348 */ 33349 33350 33351 "endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g, function (key) { 33352 DOMHandler.prototype[key] = function () { 33353 return null; 33354 }; 33355 }); 33356 /* 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 */ 33357 33358 function appendElement(hander, node) { 33359 if (!hander.currentElement) { 33360 hander.doc.appendChild(node); 33361 } else { 33362 hander.currentElement.appendChild(node); 33363 } 33364 } //appendChild and setAttributeNS are preformance key 33365 //if(typeof require == 'function'){ 33366 33367 33368 var XMLReader = sax.XMLReader; 33369 var DOMImplementation = exports.DOMImplementation = dom.DOMImplementation; 33370 exports.XMLSerializer = dom.XMLSerializer; 33371 exports.DOMParser = DOMParser; //} 33372 }); 33373 var domParser_1 = domParser.DOMImplementation; 33374 var domParser_2 = domParser.XMLSerializer; 33375 var domParser_3 = domParser.DOMParser; 33376
vendor: 6,869 bytes, lines 33377-33569
33377 /*! @name mpd-parser @version 0.10.0 @license Apache-2.0 */ 33378 33379 var isObject$1 = function isObject(obj) { 33380 return !!obj && typeof obj === 'object'; 33381 }; 33382 33383 var merge = function merge() { 33384 for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) { 33385 objects[_key] = arguments[_key]; 33386 } 33387 33388 return objects.reduce(function (result, source) { 33389 Object.keys(source).forEach(function (key) { 33390 if (Array.isArray(result[key]) && Array.isArray(source[key])) { 33391 result[key] = result[key].concat(source[key]); 33392 } else if (isObject$1(result[key]) && isObject$1(source[key])) { 33393 result[key] = merge(result[key], source[key]); 33394 } else { 33395 result[key] = source[key]; 33396 } 33397 }); 33398 return result; 33399 }, {}); 33400 }; 33401 33402 var values = function values(o) { 33403 return Object.keys(o).map(function (k) { 33404 return o[k]; 33405 }); 33406 }; 33407 33408 var range = function range(start, end) { 33409 var result = []; 33410 33411 for (var i = start; i < end; i++) { 33412 result.push(i); 33413 } 33414 33415 return result; 33416 }; 33417 33418 var flatten = function flatten(lists) { 33419 return lists.reduce(function (x, y) { 33420 return x.concat(y); 33421 }, []); 33422 }; 33423 33424 var from = function from(list) { 33425 if (!list.length) { 33426 return []; 33427 } 33428 33429 var result = []; 33430 33431 for (var i = 0; i < list.length; i++) { 33432 result.push(list[i]); 33433 } 33434 33435 return result; 33436 }; 33437 33438 var findIndexes = function findIndexes(l, key) { 33439 return l.reduce(function (a, e, i) { 33440 if (e[key]) { 33441 a.push(i); 33442 } 33443 33444 return a; 33445 }, []); 33446 }; 33447 33448 var errors = { 33449 INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD', 33450 DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST', 33451 DASH_INVALID_XML: 'DASH_INVALID_XML', 33452 NO_BASE_URL: 'NO_BASE_URL', 33453 MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', 33454 SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED', 33455 UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME' 33456 }; 33457 /** 33458 * @typedef {Object} SingleUri 33459 * @property {string} uri - relative location of segment 33460 * @property {string} resolvedUri - resolved location of segment 33461 * @property {Object} byterange - Object containing information on how to make byte range 33462 * requests following byte-range-spec per RFC2616. 33463 * @property {String} byterange.length - length of range request 33464 * @property {String} byterange.offset - byte offset of range request 33465 * 33466 * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 33467 */ 33468 33469 /** 33470 * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object 33471 * that conforms to how m3u8-parser is structured 33472 * 33473 * @see https://github.com/videojs/m3u8-parser 33474 * 33475 * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes 33476 * @param {string} source - source url for segment 33477 * @param {string} range - optional range used for range calls, 33478 * follows RFC 2616, Clause 14.35.1 33479 * @return {SingleUri} full segment information transformed into a format similar 33480 * to m3u8-parser 33481 */ 33482 33483 var urlTypeToSegment = function urlTypeToSegment(_ref) { 33484 var _ref$baseUrl = _ref.baseUrl, 33485 baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl, 33486 _ref$source = _ref.source, 33487 source = _ref$source === void 0 ? '' : _ref$source, 33488 _ref$range = _ref.range, 33489 range = _ref$range === void 0 ? '' : _ref$range, 33490 _ref$indexRange = _ref.indexRange, 33491 indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange; 33492 var segment = { 33493 uri: source, 33494 resolvedUri: resolveUrl_1(baseUrl || '', source) 33495 }; 33496 33497 if (range || indexRange) { 33498 var rangeStr = range ? range : indexRange; 33499 var ranges = rangeStr.split('-'); 33500 var startRange = parseInt(ranges[0], 10); 33501 var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to 33502 // RFC 2616, Clause 14.35.1 33503 33504 segment.byterange = { 33505 length: endRange - startRange + 1, 33506 offset: startRange 33507 }; 33508 } 33509 33510 return segment; 33511 }; 33512 33513 var byteRangeToString = function byteRangeToString(byterange) { 33514 // `endRange` is one less than `offset + length` because the HTTP range 33515 // header uses inclusive ranges 33516 var endRange = byterange.offset + byterange.length - 1; 33517 return byterange.offset + "-" + endRange; 33518 }; 33519 /** 33520 * Functions for calculating the range of available segments in static and dynamic 33521 * manifests. 33522 */ 33523 33524 33525 var segmentRange = { 33526 /** 33527 * Returns the entire range of available segments for a static MPD 33528 * 33529 * @param {Object} attributes 33530 * Inheritied MPD attributes 33531 * @return {{ start: number, end: number }} 33532 * The start and end numbers for available segments 33533 */ 33534 "static": function _static(attributes) { 33535 var duration = attributes.duration, 33536 _attributes$timescale = attributes.timescale, 33537 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 33538 sourceDuration = attributes.sourceDuration; 33539 return { 33540 start: 0, 33541 end: Math.ceil(sourceDuration / (duration / timescale)) 33542 }; 33543 }, 33544 33545 /** 33546 * Returns the current live window range of available segments for a dynamic MPD 33547 * 33548 * @param {Object} attributes 33549 * Inheritied MPD attributes 33550 * @return {{ start: number, end: number }} 33551 * The start and end numbers for available segments 33552 */ 33553 dynamic: function dynamic(attributes) { 33554 var NOW = attributes.NOW, 33555 clientOffset = attributes.clientOffset, 33556 availabilityStartTime = attributes.availabilityStartTime, 33557 _attributes$timescale2 = attributes.timescale, 33558 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 33559 duration = attributes.duration, 33560 _attributes$start = attributes.start, 33561 start = _attributes$start === void 0 ? 0 : _attributes$start, 33562 _attributes$minimumUp = attributes.minimumUpdatePeriod, 33563 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp, 33564 _attributes$timeShift = attributes.timeShiftBufferDepth, 33565 timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift; 33566 var now = (NOW + clientOffset) / 1000; 33567 var periodStartWC = availabilityStartTime + start; 33568 var periodEndWC = now + minimumUpdatePeriod; 33569 var periodDuration = periodEndWC - periodStartWC;
33570 var segmentCount = Math.ceil(periodDuration * timescale / duration); 33571 var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration); 33572 var availableEnd = Math.floor((now - periodStartWC) * timescale / duration); 33573 return { 33574 start: Math.max(0, availableStart), 33575 end: Math.min(segmentCount, availableEnd) 33576 }; 33577 } 33578 }; 33579 /** 33580 * Maps a range of numbers to objects with information needed to build the corresponding 33581 * segment list 33582 * 33583 * @name toSegmentsCallback 33584 * @function 33585 * @param {number} number 33586 * Number of the segment 33587 * @param {number} index 33588 * Index of the number in the range list 33589 * @return {{ number: Number, duration: Number, timeline: Number, time: Number }} 33590 * Object with segment timing and duration info 33591 */ 33592 33593 /** 33594 * Returns a callback for Array.prototype.map for mapping a range of numbers to 33595 * information needed to build the segment list. 33596 * 33597 * @param {Object} attributes 33598 * Inherited MPD attributes 33599 * @return {toSegmentsCallback} 33600 * Callback map function 33601 */ 33602 33603 var toSegments = function toSegments(attributes) { 33604 return function (number, index) { 33605 var duration = attributes.duration, 33606 _attributes$timescale3 = attributes.timescale, 33607 timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3, 33608 periodIndex = attributes.periodIndex, 33609 _attributes$startNumb = attributes.startNumber, 33610 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb; 33611 return { 33612 number: startNumber + number, 33613 duration: duration / timescale, 33614 timeline: periodIndex, 33615 time: index * duration 33616 }; 33617 }; 33618 }; 33619 /** 33620 * Returns a list of objects containing segment timing and duration info used for 33621 * building the list of segments. This uses the @duration attribute specified 33622 * in the MPD manifest to derive the range of segments. 33623 * 33624 * @param {Object} attributes 33625 * Inherited MPD attributes 33626 * @return {{number: number, duration: number, time: number, timeline: number}[]} 33627 * List of Objects with segment timing and duration info 33628 */ 33629 33630 33631 var parseByDuration = function parseByDuration(attributes) { 33632 var _attributes$type = attributes.type, 33633 type = _attributes$type === void 0 ? 'static' : _attributes$type, 33634 duration = attributes.duration, 33635 _attributes$timescale4 = attributes.timescale, 33636 timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4, 33637 sourceDuration = attributes.sourceDuration; 33638 33639 var _segmentRange$type = segmentRange[type](attributes), 33640 start = _segmentRange$type.start, 33641 end = _segmentRange$type.end; 33642 33643 var segments = range(start, end).map(toSegments(attributes)); 33644 33645 if (type === 'static') { 33646 var index = segments.length - 1; // final segment may be less than full segment duration 33647 33648 segments[index].duration = sourceDuration - duration / timescale * index; 33649 } 33650 33651 return segments; 33652 }; 33653 /** 33654 * Translates SegmentBase into a set of segments. 33655 * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 33656 * node should be translated into segment. 33657 * 33658 * @param {Object} attributes 33659 * Object containing all inherited attributes from parent elements with attribute 33660 * names as keys 33661 * @return {Object.<Array>} list of segments 33662 */ 33663 33664 33665 var segmentsFromBase = function segmentsFromBase(attributes) { 33666 var baseUrl = attributes.baseUrl, 33667 _attributes$initializ = attributes.initialization, 33668 initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ, 33669 sourceDuration = attributes.sourceDuration, 33670 _attributes$timescale = attributes.timescale, 33671 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 33672 _attributes$indexRang = attributes.indexRange, 33673 indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang, 33674 duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) 33675 33676 if (!baseUrl) { 33677 throw new Error(errors.NO_BASE_URL); 33678 } 33679 33680 var initSegment = urlTypeToSegment({ 33681 baseUrl: baseUrl, 33682 source: initialization.sourceURL, 33683 range: initialization.range 33684 }); 33685 var segment = urlTypeToSegment({ 33686 baseUrl: baseUrl, 33687 source: baseUrl, 33688 indexRange: indexRange 33689 }); 33690 segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source 33691 // (since SegmentBase is only for one total segment) 33692 33693 if (duration) { 33694 var segmentTimeInfo = parseByDuration(attributes); 33695 33696 if (segmentTimeInfo.length) { 33697 segment.duration = segmentTimeInfo[0].duration; 33698 segment.timeline = segmentTimeInfo[0].timeline; 33699 } 33700 } else if (sourceDuration) { 33701 segment.duration = sourceDuration / timescale; 33702 segment.timeline = 0; 33703 } // This is used for mediaSequence 33704 33705 33706 segment.number = 0; 33707 return [segment]; 33708 }; 33709 /** 33710 * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist 33711 * according to the sidx information given. 33712 * 33713 * playlist.sidx has metadadata about the sidx where-as the sidx param 33714 * is the parsed sidx box itself. 33715 * 33716 * @param {Object} playlist the playlist to update the sidx information for 33717 * @param {Object} sidx the parsed sidx box 33718 * @return {Object} the playlist object with the updated sidx information 33719 */ 33720 33721 33722 var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) { 33723 // Retain init segment information 33724 var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing 33725 33726 var sourceDuration = playlist.sidx.duration; // Retain source timeline 33727 33728 var timeline = playlist.timeline || 0; 33729 var sidxByteRange = playlist.sidx.byterange; 33730 var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx 33731 33732 var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes 33733 33734 var mediaReferences = sidx.references.filter(function (r) { 33735 return r.referenceType !== 1; 33736 }); 33737 var segments = []; // firstOffset is the offset from the end of the sidx box 33738 33739 var startIndex = sidxEnd + sidx.firstOffset; 33740 33741 for (var i = 0; i < mediaReferences.length; i++) { 33742 var reference = sidx.references[i]; // size of the referenced (sub)segment 33743 33744 var size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale 33745 // this will be converted to seconds when generating segments 33746 33747 var duration = reference.subsegmentDuration; // should be an inclusive range 33748 33749 var endIndex = startIndex + size - 1; 33750 var indexRange = startIndex + "-" + endIndex; 33751 var attributes = { 33752 baseUrl: baseUrl, 33753 timescale: timescale, 33754 timeline: timeline, 33755 // this is used in parseByDuration 33756 periodIndex: timeline, 33757 duration: duration, 33758 sourceDuration: sourceDuration, 33759 indexRange: indexRange 33760 }; 33761 var segment = segmentsFromBase(attributes)[0]; 33762 33763 if (initSegment) { 33764 segment.map = initSegment; 33765 } 33766 33767 segments.push(segment); 33768 startIndex += size; 33769 } 33770 33771 playlist.segments = segments; 33772 return playlist; 33773 }; 33774 33775 var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) { 33776 var mergedPlaylists = values(playlists.reduce(function (acc, playlist) { 33777 // assuming playlist IDs are the same across periods 33778 // TODO: handle multiperiod where representation sets are not the same 33779 // across periods 33780 var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first 33781 33782 if (acc[name]) { 33783 var _acc$name$segments; // first segment of subsequent periods signal a discontinuity 33784 33785 33786 if (playlist.segments[0]) { 33787 playlist.segments[0].discontinuity = true; 33788 } 33789 33790 (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content 33791 // has the same contentProtection 33792 33793 33794 if (playlist.attributes.contentProtection) { 33795 acc[name].attributes.contentProtection = playlist.attributes.contentProtection; 33796 } 33797 } else { 33798 // first Period 33799 acc[name] = playlist; 33800 } 33801 33802 return acc; 33803 }, {})); 33804 return mergedPlaylists.map(function (playlist) { 33805 playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity'); 33806 return playlist; 33807 }); 33808 }; 33809 33810 var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) { 33811 if (sidxMapping === void 0) { 33812 sidxMapping = {}; 33813 } 33814 33815 if (!Object.keys(sidxMapping).length) { 33816 return playlists; 33817 } 33818 33819 for (var i in playlists) { 33820 var playlist = playlists[i]; 33821 33822 if (!playlist.sidx) { 33823 continue; 33824 } 33825 33826 var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange); 33827 var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx; 33828 33829 if (playlist.sidx && sidxMatch) { 33830 addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri); 33831 } 33832 } 33833 33834 return playlists; 33835 }; 33836 33837 var formatAudioPlaylist = function formatAudioPlaylist(_ref) { 33838 var _attributes; 33839 33840 var attributes = _ref.attributes, 33841 segments = _ref.segments, 33842 sidx = _ref.sidx; 33843 var playlist = { 33844 attributes: (_attributes = { 33845 NAME: attributes.id, 33846 BANDWIDTH: attributes.bandwidth, 33847 CODECS: attributes.codecs 33848 }, _attributes['PROGRAM-ID'] = 1, _attributes), 33849 uri: '', 33850 endList: (attributes.type || 'static') === 'static', 33851 timeline: attributes.periodIndex, 33852 resolvedUri: '', 33853 targetDuration: attributes.duration, 33854 segments: segments, 33855 mediaSequence: segments.length ? segments[0].number : 1 33856 }; 33857 33858 if (attributes.contentProtection) { 33859 playlist.contentProtection = attributes.contentProtection; 33860 } 33861 33862 if (sidx) { 33863 playlist.sidx = sidx; 33864 } 33865 33866 return playlist; 33867 }; 33868 33869 var formatVttPlaylist = function formatVttPlaylist(_ref2) { 33870 var _attributes2; 33871 33872 var attributes = _ref2.attributes, 33873 segments = _ref2.segments; 33874 33875 if (typeof segments === 'undefined') { 33876 // vtt tracks may use single file in BaseURL 33877 segments = [{ 33878 uri: attributes.baseUrl, 33879 timeline: attributes.periodIndex, 33880 resolvedUri: attributes.baseUrl || '', 33881 duration: attributes.sourceDuration, 33882 number: 0 33883 }]; // targetDuration should be the same duration as the only segment 33884 33885 attributes.duration = attributes.sourceDuration; 33886 } 33887 33888 return { 33889 attributes: (_attributes2 = { 33890 NAME: attributes.id, 33891 BANDWIDTH: attributes.bandwidth 33892 }, _attributes2['PROGRAM-ID'] = 1, _attributes2), 33893 uri: '', 33894 endList: (attributes.type || 'static') === 'static', 33895 timeline: attributes.periodIndex, 33896 resolvedUri: attributes.baseUrl || '', 33897 targetDuration: attributes.duration, 33898 segments: segments, 33899 mediaSequence: segments.length ? segments[0].number : 1 33900 }; 33901 }; 33902 33903 var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) { 33904 if (sidxMapping === void 0) { 33905 sidxMapping = {}; 33906 } 33907 33908 var mainPlaylist; 33909 var formattedPlaylists = playlists.reduce(function (a, playlist) { 33910 var role = playlist.attributes.role && playlist.attributes.role.value || ''; 33911 var language = playlist.attributes.lang || ''; 33912 var label = 'main'; 33913 33914 if (language) { 33915 var roleLabel = role ? " (" + role + ")" : ''; 33916 label = "" + playlist.attributes.lang + roleLabel; 33917 } // skip if we already have the highest quality audio for a language 33918 33919 33920 if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) { 33921 return a; 33922 } 33923 33924 a[label] = { 33925 language: language, 33926 autoselect: true, 33927 "default": role === 'main', 33928 playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping), 33929 uri: '' 33930 }; 33931 33932 if (typeof mainPlaylist === 'undefined' && role === 'main') { 33933 mainPlaylist = playlist; 33934 mainPlaylist["default"] = true; 33935 } 33936 33937 return a; 33938 }, {}); // if no playlists have role "main", mark the first as main 33939 33940 if (!mainPlaylist) { 33941 var firstLabel = Object.keys(formattedPlaylists)[0]; 33942 formattedPlaylists[firstLabel]["default"] = true; 33943 } 33944 33945 return formattedPlaylists; 33946 }; 33947 33948 var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) { 33949 if (sidxMapping === void 0) { 33950 sidxMapping = {}; 33951 } 33952 33953 return playlists.reduce(function (a, playlist) { 33954 var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles 33955 33956 if (a[label]) { 33957 return a; 33958 } 33959 33960 a[label] = { 33961 language: label, 33962 "default": false, 33963 autoselect: false, 33964 playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping), 33965 uri: '' 33966 }; 33967 return a; 33968 }, {}); 33969 }; 33970 33971 var formatVideoPlaylist = function formatVideoPlaylist(_ref3) { 33972 var _attributes3; 33973 33974 var attributes = _ref3.attributes, 33975 segments = _ref3.segments, 33976 sidx = _ref3.sidx; 33977 var playlist = { 33978 attributes: (_attributes3 = { 33979 NAME: attributes.id, 33980 AUDIO: 'audio', 33981 SUBTITLES: 'subs', 33982 RESOLUTION: { 33983 width: attributes.width, 33984 height: attributes.height 33985 }, 33986 CODECS: attributes.codecs, 33987 BANDWIDTH: attributes.bandwidth 33988 }, _attributes3['PROGRAM-ID'] = 1, _attributes3), 33989 uri: '', 33990 endList: (attributes.type || 'static') === 'static', 33991 timeline: attributes.periodIndex, 33992 resolvedUri: '', 33993 targetDuration: attributes.duration, 33994 segments: segments, 33995 mediaSequence: segments.length ? segments[0].number : 1 33996 }; 33997 33998 if (attributes.contentProtection) { 33999 playlist.contentProtection = attributes.contentProtection; 34000 } 34001 34002 if (sidx) { 34003 playlist.sidx = sidx; 34004 } 34005 34006 return playlist; 34007 }; 34008 34009 var toM3u8 = function toM3u8(dashPlaylists, sidxMapping) { 34010 var _mediaGroups; 34011 34012 if (sidxMapping === void 0) { 34013 sidxMapping = {}; 34014 } 34015 34016 if (!dashPlaylists.length) { 34017 return {}; 34018 } // grab all master attributes 34019 34020 34021 var _dashPlaylists$0$attr = dashPlaylists[0].attributes, 34022 duration = _dashPlaylists$0$attr.sourceDuration, 34023 _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.type, 34024 type = _dashPlaylists$0$attr2 === void 0 ? 'static' : _dashPlaylists$0$attr2, 34025 suggestedPresentationDelay = _dashPlaylists$0$attr.suggestedPresentationDelay, 34026 _dashPlaylists$0$attr3 = _dashPlaylists$0$attr.minimumUpdatePeriod, 34027 minimumUpdatePeriod = _dashPlaylists$0$attr3 === void 0 ? 0 : _dashPlaylists$0$attr3; 34028 34029 var videoOnly = function videoOnly(_ref4) { 34030 var attributes = _ref4.attributes; 34031 return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video'; 34032 }; 34033 34034 var audioOnly = function audioOnly(_ref5) { 34035 var attributes = _ref5.attributes; 34036 return attrib
vendor: 14,003 bytes, lines 34036-34398
34036utes.mimeType === 'audio/mp4' || attributes.contentType === 'audio'; 34037 }; 34038 34039 var vttOnly = function vttOnly(_ref6) { 34040 var attributes = _ref6.attributes; 34041 return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text'; 34042 }; 34043 34044 var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist); 34045 var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly)); 34046 var vttPlaylists = dashPlaylists.filter(vttOnly); 34047 var master = { 34048 allowCache: true, 34049 discontinuityStarts: [], 34050 segments: [], 34051 endList: true, 34052 mediaGroups: (_mediaGroups = { 34053 AUDIO: {}, 34054 VIDEO: {} 34055 }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups), 34056 uri: '', 34057 duration: duration, 34058 playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping), 34059 minimumUpdatePeriod: minimumUpdatePeriod * 1000 34060 }; 34061 34062 if (type === 'dynamic') { 34063 master.suggestedPresentationDelay = suggestedPresentationDelay; 34064 } 34065 34066 if (audioPlaylists.length) { 34067 master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping); 34068 } 34069 34070 if (vttPlaylists.length) { 34071 master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping); 34072 } 34073 34074 return master; 34075 }; 34076 /** 34077 * Calculates the R (repetition) value for a live stream (for the final segment 34078 * in a manifest where the r value is negative 1) 34079 * 34080 * @param {Object} attributes 34081 * Object containing all inherited attributes from parent elements with attribute 34082 * names as keys 34083 * @param {number} time 34084 * current time (typically the total time up until the final segment) 34085 * @param {number} duration 34086 * duration property for the given <S /> 34087 * 34088 * @return {number} 34089 * R value to reach the end of the given period 34090 */ 34091 34092 34093 var getLiveRValue = function getLiveRValue(attributes, time, duration) { 34094 var NOW = attributes.NOW, 34095 clientOffset = attributes.clientOffset, 34096 availabilityStartTime = attributes.availabilityStartTime, 34097 _attributes$timescale = attributes.timescale, 34098 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 34099 _attributes$start = attributes.start, 34100 start = _attributes$start === void 0 ? 0 : _attributes$start, 34101 _attributes$minimumUp = attributes.minimumUpdatePeriod, 34102 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp; 34103 var now = (NOW + clientOffset) / 1000; 34104 var periodStartWC = availabilityStartTime + start; 34105 var periodEndWC = now + minimumUpdatePeriod; 34106 var periodDuration = periodEndWC - periodStartWC; 34107 return Math.ceil((periodDuration * timescale - time) / duration); 34108 }; 34109 /** 34110 * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment 34111 * timing and duration 34112 * 34113 * @param {Object} attributes 34114 * Object containing all inherited attributes from parent elements with attribute 34115 * names as keys 34116 * @param {Object[]} segmentTimeline 34117 * List of objects representing the attributes of each S element contained within 34118 * 34119 * @return {{number: number, duration: number, time: number, timeline: number}[]} 34120 * List of Objects with segment timing and duration info 34121 */ 34122 34123 34124 var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) { 34125 var _attributes$type = attributes.type, 34126 type = _attributes$type === void 0 ? 'static' : _attributes$type, 34127 _attributes$minimumUp2 = attributes.minimumUpdatePeriod, 34128 minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2, 34129 _attributes$media = attributes.media, 34130 media = _attributes$media === void 0 ? '' : _attributes$media, 34131 sourceDuration = attributes.sourceDuration, 34132 _attributes$timescale2 = attributes.timescale, 34133 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 34134 _attributes$startNumb = attributes.startNumber, 34135 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb, 34136 timeline = attributes.periodIndex; 34137 var segments = []; 34138 var time = -1; 34139 34140 for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { 34141 var S = segmentTimeline[sIndex]; 34142 var duration = S.d; 34143 var repeat = S.r || 0; 34144 var segmentTime = S.t || 0; 34145 34146 if (time < 0) { 34147 // first segment 34148 time = segmentTime; 34149 } 34150 34151 if (segmentTime && segmentTime > time) { 34152 // discontinuity 34153 // TODO: How to handle this type of discontinuity 34154 // timeline++ here would treat it like HLS discontuity and content would 34155 // get appended without gap 34156 // E.G. 34157 // <S t="0" d="1" /> 34158 // <S d="1" /> 34159 // <S d="1" /> 34160 // <S t="5" d="1" /> 34161 // would have $Time$ values of [0, 1, 2, 5] 34162 // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) 34163 // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) 34164 // does the value of sourceDuration consider this when calculating arbitrary 34165 // negative @r repeat value? 34166 // E.G. Same elements as above with this added at the end 34167 // <S d="1" r="-1" /> 34168 // with a sourceDuration of 10 34169 // Would the 2 gaps be included in the time duration calculations resulting in 34170 // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments 34171 // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? 34172 time = segmentTime; 34173 } 34174 34175 var count = void 0; 34176 34177 if (repeat < 0) { 34178 var nextS = sIndex + 1; 34179 34180 if (nextS === segmentTimeline.length) { 34181 // last segment 34182 if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) { 34183 count = getLiveRValue(attributes, time, duration); 34184 } else { 34185 // TODO: This may be incorrect depending on conclusion of TODO above 34186 count = (sourceDuration * timescale - time) / duration; 34187 } 34188 } else { 34189 count = (segmentTimeline[nextS].t - time) / duration; 34190 } 34191 } else { 34192 count = repeat + 1; 34193 } 34194 34195 var end = startNumber + segments.length + count; 34196 var number = startNumber + segments.length; 34197 34198 while (number < end) { 34199 segments.push({ 34200 number: number, 34201 duration: duration / timescale, 34202 time: time, 34203 timeline: timeline 34204 }); 34205 time += duration; 34206 number++; 34207 } 34208 } 34209 34210 return segments; 34211 }; 34212 34213 var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; 34214 /** 34215 * Replaces template identifiers with corresponding values. To be used as the callback 34216 * for String.prototype.replace 34217 * 34218 * @name replaceCallback 34219 * @function 34220 * @param {string} match 34221 * Entire match of identifier 34222 * @param {string} identifier 34223 * Name of matched identifier 34224 * @param {string} format 34225 * Format tag string. Its presence indicates that padding is expected 34226 * @param {string} width 34227 * Desired length of the replaced value. Values less than this width shall be left 34228 * zero padded 34229 * @return {string} 34230 * Replacement for the matched identifier 34231 */ 34232 34233 /** 34234 * Returns a function to be used as a callback for String.prototype.replace to replace 34235 * template identifiers 34236 * 34237 * @param {Obect} values 34238 * Object containing values that shall be used to replace known identifiers 34239 * @param {number} values.RepresentationID 34240 * Value of the Representation@id attribute 34241 * @param {number} values.Number 34242 * Number of the corresponding segment 34243 * @param {number} values.Bandwidth 34244 * Value of the Representation@bandwidth attribute. 34245 * @param {number} values.Time 34246 * Timestamp value of the corresponding segment 34247 * @return {replaceCallback} 34248 * Callback to be used with String.prototype.replace to replace identifiers 34249 */ 34250 34251 var identifierReplacement = function identifierReplacement(values) { 34252 return function (match, identifier, format, width) { 34253 if (match === '$$') { 34254 // escape sequence 34255 return '$'; 34256 } 34257 34258 if (typeof values[identifier] === 'undefined') { 34259 return match; 34260 } 34261 34262 var value = '' + values[identifier]; 34263 34264 if (identifier === 'RepresentationID') { 34265 // Format tag shall not be present with RepresentationID 34266 return value; 34267 } 34268 34269 if (!format) { 34270 width = 1; 34271 } else { 34272 width = parseInt(width, 10); 34273 } 34274 34275 if (value.length >= width) { 34276 return value; 34277 } 34278 34279 return "" + new Array(width - value.length + 1).join('0') + value; 34280 }; 34281 }; 34282 /** 34283 * Constructs a segment url from a template string 34284 * 34285 * @param {string} url 34286 * Template string to construct url from 34287 * @param {Obect} values 34288 * Object containing values that shall be used to replace known identifiers 34289 * @param {number} values.RepresentationID 34290 * Value of the Representation@id attribute 34291 * @param {number} values.Number 34292 * Number of the corresponding segment 34293 * @param {number} values.Bandwidth 34294 * Value of the Representation@bandwidth attribute. 34295 * @param {number} values.Time 34296 * Timestamp value of the corresponding segment 34297 * @return {string} 34298 * Segment url with identifiers replaced 34299 */ 34300 34301 34302 var constructTemplateUrl = function constructTemplateUrl(url, values) { 34303 return url.replace(identifierPattern, identifierReplacement(values)); 34304 }; 34305 /** 34306 * Generates a list of objects containing timing and duration information about each 34307 * segment needed to generate segment uris and the complete segment object 34308 * 34309 * @param {Object} attributes 34310 * Object containing all inherited attributes from parent elements with attribute 34311 * names as keys 34312 * @param {Object[]|undefined} segmentTimeline 34313 * List of objects representing the attributes of each S element contained within 34314 * the SegmentTimeline element 34315 * @return {{number: number, duration: number, time: number, timeline: number}[]} 34316 * List of Objects with segment timing and duration info 34317 */ 34318 34319 34320 var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) { 34321 if (!attributes.duration && !segmentTimeline) { 34322 // if neither @duration or SegmentTimeline are present, then there shall be exactly 34323 // one media segment 34324 return [{ 34325 number: attributes.startNumber || 1, 34326 duration: attributes.sourceDuration, 34327 time: 0, 34328 timeline: attributes.periodIndex 34329 }]; 34330 } 34331 34332 if (attributes.duration) { 34333 return parseByDuration(attributes); 34334 } 34335 34336 return parseByTimeline(attributes, segmentTimeline); 34337 }; 34338 /** 34339 * Generates a list of segments using information provided by the SegmentTemplate element 34340 * 34341 * @param {Object} attributes 34342 * Object containing all inherited attributes from parent elements with attribute 34343 * names as keys 34344 * @param {Object[]|undefined} segmentTimeline 34345 * List of objects representing the attributes of each S element contained within 34346 * the SegmentTimeline element 34347 * @return {Object[]} 34348 * List of segment objects 34349 */ 34350 34351 34352 var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) { 34353 var templateValues = { 34354 RepresentationID: attributes.id, 34355 Bandwidth: attributes.bandwidth || 0 34356 }; 34357 var _attributes$initializ = attributes.initialization, 34358 initialization = _attributes$initializ === void 0 ? { 34359 sourceURL: '', 34360 range: '' 34361 } : _attributes$initializ; 34362 var mapSegment = urlTypeToSegment({ 34363 baseUrl: attributes.baseUrl, 34364 source: constructTemplateUrl(initialization.sourceURL, templateValues), 34365 range: initialization.range 34366 }); 34367 var segments = parseTemplateInfo(attributes, segmentTimeline); 34368 return segments.map(function (segment) { 34369 templateValues.Number = segment.number; 34370 templateValues.Time = segment.time; 34371 var uri = constructTemplateUrl(attributes.media || '', templateValues); 34372 return { 34373 uri: uri, 34374 timeline: segment.timeline, 34375 duration: segment.duration, 34376 resolvedUri: resolveUrl_1(attributes.baseUrl || '', uri), 34377 map: mapSegment, 34378 number: segment.number 34379 }; 34380 }); 34381 }; 34382 /** 34383 * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14) 34384 * to an object that matches the output of a segment in videojs/mpd-parser 34385 * 34386 * @param {Object} attributes 34387 * Object containing all inherited attributes from parent elements with attribute 34388 * names as keys 34389 * @param {Object} segmentUrl 34390 * <SegmentURL> node to translate into a segment object 34391 * @return {Object} translated segment object 34392 */ 34393 34394 34395 var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) { 34396 var baseUrl = attributes.baseUrl, 34397 _attributes$initializ = attributes.initialization, 34398 initialization = _attributes$initializ === void 0 ? {}
34398 : _attributes$initializ; 34399 var initSegment = urlTypeToSegment({ 34400 baseUrl: baseUrl, 34401 source: initialization.sourceURL, 34402 range: initialization.range 34403 }); 34404 var segment = urlTypeToSegment({ 34405 baseUrl: baseUrl, 34406 source: segmentUrl.media, 34407 range: segmentUrl.mediaRange 34408 }); 34409 segment.map = initSegment; 34410 return segment; 34411 }; 34412 /** 34413 * Generates a list of segments using information provided by the SegmentList element 34414 * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 34415 * node should be translated into segment. 34416 * 34417 * @param {Object} attributes 34418 * Object containing all inherited attributes from parent elements with attribute 34419 * names as keys 34420 * @param {Object[]|undefined} segmentTimeline 34421 * List of objects representing the attributes of each S element contained within 34422 * the SegmentTimeline element 34423 * @return {Object.<Array>} list of segments 34424 */ 34425 34426
34427 var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) { 34428 var duration = attributes.duration, 34429 _attributes$segmentUr = attributes.segmentUrls, 34430 segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR 34431 // if both SegmentTimeline and @duration are defined, it is outside of spec. 34432 34433 if (!duration && !segmentTimeline || duration && segmentTimeline) { 34434 throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); 34435 } 34436 34437 var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) { 34438 return SegmentURLToSegmentObject(attributes, segmentUrlObject); 34439 }); 34440 var segmentTimeInfo; 34441 34442 if (duration) { 34443 segmentTimeInfo = parseByDuration(attributes); 34444 } 34445 34446 if (segmentTimeline) { 34447 segmentTimeInfo = parseByTimeline(attributes, segmentTimeline); 34448 } 34449 34450 var segments = segmentTimeInfo.map(function (segmentTime, index) { 34451 if (segmentUrlMap[index]) { 34452 var segment = segmentUrlMap[index]; 34453 segment.timeline = segmentTime.timeline; 34454 segment.duration = segmentTime.duration; 34455 segment.number = segmentTime.number; 34456 return segment; 34457 } // Since we're mapping we should get rid of any blank segments (in case 34458 // the given SegmentTimeline is handling for more elements than we have 34459 // SegmentURLs for). 34460 34461 }).filter(function (segment) { 34462 return segment; 34463 }); 34464 return segments; 34465 }; 34466 34467 var generateSegments = function generateSegments(_ref) { 34468 var attributes = _ref.attributes, 34469 segmentInfo = _ref.segmentInfo; 34470 var segmentAttributes; 34471 var segmentsFn; 34472 34473 if (segmentInfo.template) { 34474 segmentsFn = segmentsFromTemplate; 34475 segmentAttributes = merge(attributes, segmentInfo.template); 34476 } else if (segmentInfo.base) { 34477 segmentsFn = segmentsFromBase; 34478 segmentAttributes = merge(attributes, segmentInfo.base); 34479 } else if (segmentInfo.list) { 34480 segmentsFn = segmentsFromList; 34481 segmentAttributes = merge(attributes, segmentInfo.list); 34482 } 34483 34484 var segmentsInfo = { 34485 attributes: attributes 34486 }; 34487 34488 if (!segmentsFn) { 34489 return segmentsInfo; 34490 } 34491 34492 var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which 34493 // must be in seconds. Since we've generated the segment list, we no longer need 34494 // @duration to be in @timescale units, so we can convert it here. 34495 34496 if (segmentAttributes.duration) { 34497 var _segmentAttributes = segmentAttributes, 34498 duration = _segmentAttributes.duration, 34499 _segmentAttributes$ti = _segmentAttributes.timescale, 34500 timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti; 34501 segmentAttributes.duration = duration / timescale; 34502 } else if (segments.length) { 34503 // if there is no @duration attribute, use the largest segment duration as 34504 // as target duration 34505 segmentAttributes.duration = segments.reduce(function (max, segment) { 34506 return Math.max(max, Math.ceil(segment.duration)); 34507 }, 0); 34508 } else { 34509 segmentAttributes.duration = 0; 34510 } 34511 34512 segmentsInfo.attributes = segmentAttributes; 34513 segmentsInfo.segments = segments; // This is a sidx box without actual segment information 34514 34515 if (segmentInfo.base && segmentAttributes.indexRange) { 34516 segmentsInfo.sidx = segments[0]; 34517 segmentsInfo.segments = []; 34518 } 34519 34520 return segmentsInfo; 34521 }; 34522 34523 var toPlaylists = function toPlaylists(representations) { 34524 return representations.map(generateSegments); 34525 }; 34526 34527 var findChildren = function findChildren(element, name) { 34528 return from(element.childNodes).filter(function (_ref) { 34529 var tagName = _ref.tagName; 34530 return tagName === name; 34531 }); 34532 }; 34533 34534 var getContent = function getContent(element) { 34535 return element.textContent.trim(); 34536 }; 34537 34538 var parseDuration = function parseDuration(str) { 34539 var SECONDS_IN_YEAR = 365 * 24 * 60 * 60; 34540 var SECONDS_IN_MONTH = 30 * 24 * 60 * 60; 34541 var SECONDS_IN_DAY = 24 * 60 * 60; 34542 var SECONDS_IN_HOUR = 60 * 60; 34543 var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S 34544 34545 var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/; 34546 var match = durationRegex.exec(str); 34547 34548 if (!match) { 34549 return 0; 34550 } 34551 34552 var _match$slice = match.slice(1), 34553 year = _match$slice[0],
vendor: 13,483 bytes, lines 34554-34938
34554 month = _match$slice[1], 34555 day = _match$slice[2], 34556 hour = _match$slice[3], 34557 minute = _match$slice[4], 34558 second = _match$slice[5]; 34559 34560 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); 34561 }; 34562 34563 var parseDate = function parseDate(str) { 34564 // Date format without timezone according to ISO 8601 34565 // YYY-MM-DDThh:mm:ss.ssssss 34566 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 34567 // expressed by ending with 'Z' 34568 34569 if (dateRegex.test(str)) { 34570 str += 'Z'; 34571 } 34572 34573 return Date.parse(str); 34574 }; 34575 34576 var parsers = { 34577 /** 34578 * Specifies the duration of the entire Media Presentation. Format is a duration string 34579 * as specified in ISO 8601 34580 * 34581 * @param {string} value 34582 * value of attribute as a string 34583 * @return {number} 34584 * The duration in seconds 34585 */ 34586 mediaPresentationDuration: function mediaPresentationDuration(value) { 34587 return parseDuration(value); 34588 }, 34589 34590 /** 34591 * Specifies the Segment availability start time for all Segments referred to in this 34592 * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability 34593 * time. Format is a date string as specified in ISO 8601 34594 * 34595 * @param {string} value 34596 * value of attribute as a string 34597 * @return {number} 34598 * The date as seconds from unix epoch 34599 */ 34600 availabilityStartTime: function availabilityStartTime(value) { 34601 return parseDate(value) / 1000; 34602 }, 34603 34604 /** 34605 * Specifies the smallest period between potential changes to the MPD. Format is a 34606 * duration string as specified in ISO 8601 34607 * 34608 * @param {string} value 34609 * value of attribute as a string 34610 * @return {number} 34611 * The duration in seconds 34612 */ 34613 minimumUpdatePeriod: function minimumUpdatePeriod(value) { 34614 return parseDuration(value); 34615 }, 34616 34617 /** 34618 * Specifies the suggested presentation delay. Format is a 34619 * duration string as specified in ISO 8601 34620 * 34621 * @param {string} value 34622 * value of attribute as a string 34623 * @return {number} 34624 * The duration in seconds 34625 */ 34626 suggestedPresentationDelay: function suggestedPresentationDelay(value) { 34627 return parseDuration(value); 34628 }, 34629 34630 /** 34631 * specifices the type of mpd. Can be either "static" or "dynamic" 34632 * 34633 * @param {string} value 34634 * value of attribute as a string 34635 * 34636 * @return {string} 34637 * The type as a string 34638 */ 34639 type: function type(value) { 34640 return value; 34641 }, 34642 34643 /** 34644 * Specifies the duration of the smallest time shifting buffer for any Representation 34645 * in the MPD. Format is a duration string as specified in ISO 8601 34646 * 34647 * @param {string} value 34648 * value of attribute as a string 34649 * @return {number} 34650 * The duration in seconds 34651 */ 34652 timeShiftBufferDepth: function timeShiftBufferDepth(value) { 34653 return parseDuration(value); 34654 }, 34655 34656 /** 34657 * Specifies the PeriodStart time of the Period relative to the availabilityStarttime. 34658 * Format is a duration string as specified in ISO 8601 34659 * 34660 * @param {string} value 34661 * value of attribute as a string 34662 * @return {number} 34663 * The duration in seconds 34664 */ 34665 start: function start(value) { 34666 return parseDuration(value); 34667 }, 34668 34669 /** 34670 * Specifies the width of the visual presentation 34671 * 34672 * @param {string} value 34673 * value of attribute as a string 34674 * @return {number} 34675 * The parsed width 34676 */ 34677 width: function width(value) { 34678 return parseInt(value, 10); 34679 }, 34680 34681 /** 34682 * Specifies the height of the visual presentation 34683 * 34684 * @param {string} value 34685 * value of attribute as a string 34686 * @return {number} 34687 * The parsed height 34688 */ 34689 height: function height(value) { 34690 return parseInt(value, 10); 34691 }, 34692 34693 /** 34694 * Specifies the bitrate of the representation 34695 * 34696 * @param {string} value 34697 * value of attribute as a string 34698 * @return {number} 34699 * The parsed bandwidth 34700 */ 34701 bandwidth: function bandwidth(value) { 34702 return parseInt(value, 10); 34703 }, 34704 34705 /** 34706 * Specifies the number of the first Media Segment in this Representation in the Period 34707 * 34708 * @param {string} value 34709 * value of attribute as a string 34710 * @return {number} 34711 * The parsed number 34712 */ 34713 startNumber: function startNumber(value) { 34714 return parseInt(value, 10); 34715 }, 34716 34717 /** 34718 * Specifies the timescale in units per seconds 34719 * 34720 * @param {string} value 34721 * value of attribute as a string 34722 * @return {number} 34723 * The aprsed timescale 34724 */ 34725 timescale: function timescale(value) { 34726 return parseInt(value, 10); 34727 }, 34728 34729 /** 34730 * Specifies the constant approximate Segment duration 34731 * NOTE: The <Period> element also contains an @duration attribute. This duration 34732 * specifies the duration of the Period. This attribute is currently not 34733 * supported by the rest of the parser, however we still check for it to prevent 34734 * errors. 34735 * 34736 * @param {string} value 34737 * value of attribute as a string 34738 * @return {number} 34739 * The parsed duration 34740 */ 34741 duration: function duration(value) { 34742 var parsedValue = parseInt(value, 10); 34743 34744 if (isNaN(parsedValue)) { 34745 return parseDuration(value); 34746 } 34747 34748 return parsedValue; 34749 }, 34750 34751 /** 34752 * Specifies the Segment duration, in units of the value of the @timescale. 34753 * 34754 * @param {string} value 34755 * value of attribute as a string 34756 * @return {number} 34757 * The parsed duration 34758 */ 34759 d: function d(value) { 34760 return parseInt(value, 10); 34761 }, 34762 34763 /** 34764 * Specifies the MPD start time, in @timescale units, the first Segment in the series 34765 * starts relative to the beginning of the Period 34766 * 34767 * @param {string} value 34768 * value of attribute as a string 34769 * @return {number} 34770 * The parsed time 34771 */ 34772 t: function t(value) { 34773 return parseInt(value, 10); 34774 }, 34775 34776 /** 34777 * Specifies the repeat count of the number of following contiguous Segments with the 34778 * same duration expressed by the value of @d 34779 * 34780 * @param {string} value 34781 * value of attribute as a string 34782 * @return {number} 34783 * The parsed number 34784 */ 34785 r: function r(value) { 34786 return parseInt(value, 10); 34787 }, 34788 34789 /** 34790 * Default parser for all other attributes. Acts as a no-op and just returns the value 34791 * as a string 34792 * 34793 * @param {string} value 34794 * value of attribute as a string 34795 * @return {string} 34796 * Unparsed value 34797 */ 34798 DEFAULT: function DEFAULT(value) { 34799 return value; 34800 } 34801 }; 34802 /** 34803 * Gets all the attributes and values of the provided node, parses attributes with known 34804 * types, and returns an object with attribute names mapped to values. 34805 * 34806 * @param {Node} el 34807 * The node to parse attributes from 34808 * @return {Object} 34809 * Object with all attributes of el parsed 34810 */ 34811 34812 var parseAttributes$1 = function parseAttributes(el) { 34813 if (!(el && el.attributes)) { 34814 return {}; 34815 } 34816 34817 return from(el.attributes).reduce(function (a, e) { 34818 var parseFn = parsers[e.name] || parsers.DEFAULT; 34819 a[e.name] = parseFn(e.value); 34820 return a; 34821 }, {}); 34822 }; 34823 34824 var keySystemsMap = { 34825 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', 34826 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', 34827 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready', 34828 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime' 34829 }; 34830 /** 34831 * Builds a list of urls that is the product of the reference urls and BaseURL values 34832 * 34833 * @param {string[]} referenceUrls 34834 * List of reference urls to resolve to 34835 * @param {Node[]} baseUrlElements 34836 * List of BaseURL nodes from the mpd 34837 * @return {string[]} 34838 * List of resolved urls 34839 */ 34840 34841 var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) { 34842 if (!baseUrlElements.length) { 34843 return referenceUrls; 34844 } 34845 34846 return flatten(referenceUrls.map(function (reference) { 34847 return baseUrlElements.map(function (baseUrlElement) { 34848 return resolveUrl_1(reference, getContent(baseUrlElement)); 34849 }); 34850 })); 34851 }; 34852 /** 34853 * Contains all Segment information for its containing AdaptationSet 34854 * 34855 * @typedef {Object} SegmentInformation 34856 * @property {Object|undefined} template 34857 * Contains the attributes for the SegmentTemplate node 34858 * @property {Object[]|undefined} timeline 34859 * Contains a list of atrributes for each S node within the SegmentTimeline node 34860 * @property {Object|undefined} list 34861 * Contains the attributes for the SegmentList node 34862 * @property {Object|undefined} base 34863 * Contains the attributes for the SegmentBase node 34864 */ 34865 34866 /** 34867 * Returns all available Segment information contained within the AdaptationSet node 34868 * 34869 * @param {Node} adaptationSet 34870 * The AdaptationSet node to get Segment information from 34871 * @return {SegmentInformation} 34872 * The Segment information contained within the provided AdaptationSet 34873 */ 34874 34875 34876 var getSegmentInformation = function getSegmentInformation(adaptationSet) { 34877 var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; 34878 var segmentList = findChildren(adaptationSet, 'SegmentList')[0]; 34879 var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) { 34880 return merge({ 34881 tag: 'SegmentURL' 34882 }, parseAttributes$1(s)); 34883 }); 34884 var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; 34885 var segmentTimelineParentNode = segmentList || segmentTemplate; 34886 var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0]; 34887 var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate; 34888 var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both 34889 // @initialization and an <Initialization> node. @initialization can be templated, 34890 // while the node can have a url and range specified. If the <SegmentTemplate> has 34891 // both @initialization and an <Initialization> subelement we opt to override with 34892 // the node, as this interaction is not defined in the spec. 34893 34894 var template = segmentTemplate && parseAttributes$1(segmentTemplate); 34895 34896 if (template && segmentInitialization) { 34897 template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization); 34898 } else if (template && template.initialization) { 34899 // If it is @initialization we convert it to an object since this is the format that 34900 // later functions will rely on for the initialization segment. This is only valid 34901 // for <SegmentTemplate> 34902 template.initialization = { 34903 sourceURL: template.initialization 34904 }; 34905 } 34906 34907 var segmentInfo = { 34908 template: template, 34909 timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) { 34910 return parseAttributes$1(s); 34911 }), 34912 list: segmentList && merge(parseAttributes$1(segmentList), { 34913 segmentUrls: segmentUrls, 34914 initialization: parseAttributes$1(segmentInitialization) 34915 }), 34916 base: segmentBase && merge(parseAttributes$1(segmentBase), { 34917 initialization: parseAttributes$1(segmentInitialization) 34918 }) 34919 }; 34920 Object.keys(segmentInfo).forEach(function (key) { 34921 if (!segmentInfo[key]) { 34922 delete segmentInfo[key]; 34923 } 34924 }); 34925 return segmentInfo; 34926 }; 34927 /** 34928 * Contains Segment information and attributes needed to construct a Playlist object 34929 * from a Representation 34930 * 34931 * @typedef {Object} RepresentationInformation 34932 * @property {SegmentInformation} segmentInfo 34933 * Segment information for this Representation 34934 * @property {Object} attributes 34935 * Inherited attributes for this Representation 34936 */ 34937 34938 /**
34939 * Maps a Representation node to an object containing Segment information and attributes 34940 * 34941 * @name inheritBaseUrlsCallback 34942 * @function 34943 * @param {Node} representation 34944 * Representation node from the mpd 34945 * @return {RepresentationInformation} 34946 * Representation information needed to construct a Playlist object 34947 */ 34948 34949 /** 34950 * Returns a callback for Array.prototype.map for mapping Representation nodes to 34951 * Segment information and attributes using inherited BaseURL nodes. 34952 * 34953 * @param {Object} adaptationSetAttributes 34954 * Contains attributes inherited by the AdaptationSet 34955 * @param {string[]} adaptationSetBaseUrls 34956 * Contains list of resolved base urls inherited by the AdaptationSet 34957 * @param {SegmentInformation} adaptationSetSegmentInfo 34958 * Contains Segment information for the AdaptationSet 34959 * @return {inheritBaseUrlsCallback} 34960 * Callback map function 34961 */ 34962 34963 34964 var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) { 34965 return function (representation) { 34966 var repBaseUrlElements = findChildren(representation, 'BaseURL'); 34967 var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements); 34968 var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation)); 34969 var representationSegmentInfo = getSegmentInformation(representation); 34970 return repBaseUrls.map(function (baseUrl) { 34971 return { 34972 segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo), 34973 attributes: merge(attributes, { 34974 baseUrl: baseUrl 34975 }) 34976 }; 34977 }); 34978 }; 34979 }; 34980 /** 34981 * Tranforms a series of content protection nodes to
34982 * an object containing pssh data by key system 34983 * 34984 * @param {Node[]} contentProtectionNodes 34985 * Content protection nodes 34986 * @return {Object} 34987 * Object containing pssh data by key system 34988 */ 34989 34990 34991 var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) { 34992 return contentProtectionNodes.reduce(function (acc, node) { 34993 var attributes = parseAttributes$1(node); 34994 var keySystem = keySystemsMap[attributes.schemeIdUri]; 34995 34996 if (keySystem) { 34997 acc[keySystem] = { 34998 attributes: attributes 34999 }; 35000 var psshNode = findChildren(node, 'cenc:pssh')[0]; 35001 35002 if (psshNode) { 35003 var pssh = getContent(psshNode); 35004 var psshBuffer = pssh && decodeB64ToUint8Array_1(pssh); 35005 acc[keySystem].pssh = psshBuffer; 35006 } 35007 } 35008 35009 return acc; 35010 }, {}); 35011 }; 35012 /** 35013 * Maps an AdaptationSet node to a list of Representation information objects 35014 * 35015 * @name toRepresentationsCallback 35016 * @function 35017 * @param {Node} adaptationSet 35018 * AdaptationSet node from the mpd 35019 * @return {RepresentationInformation[]} 35020 * List of objects containing Representaion information 35021 */ 35022 35023 /** 35024 * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of 35025 * Representation information objects 35026 * 35027 * @param {Object} periodAttributes 35028 * Contains attributes inherited by the Period 35029 * @param {string[]} periodBaseUrls 35030 * Contains list of resolved base urls inherited by the Period 35031 * @param {string[]} periodSegmentInfo 35032 * Contains Segment Information at the period level 35033 * @return {toRepresentationsCallback} 35034 * Callback map function 35035 */ 35036 35037 35038 var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) { 35039 return function (adaptationSet) { 35040 var adaptationSetAttributes = parseAttributes$1(adaptationSet); 35041 var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL')); 35042 var role = findChildren(adaptationSet, 'Role')[0]; 35043 var roleAttributes = { 35044 role: parseAttributes$1(role) 35045 }; 35046 var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes); 35047 var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection')); 35048 35049 if (Object.keys(contentProtection).length) { 35050 attrs = merge(attrs, { 35051 contentProtection: contentProtection 35052 }); 35053 } 35054 35055 var segmentInfo = getSegmentInformation(adaptationSet); 35056 var representations = findChildren(adaptationSet, 'Representation'); 35057 var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo); 35058 return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo))); 35059 }; 35060 }; 35061 /** 35062 * Maps an Period node to a list of Representation inforamtion objects for all 35063 * AdaptationSet nodes contained within the Period 35064 * 35065 * @name toAdaptationSetsCallback 35066 * @function 35067 * @param {Node} period 35068 * Period node from the mpd 35069 * @param {number} periodIndex 35070 * Index of the Period within the mpd 35071 * @return {RepresentationInformation[]} 35072 * List of objects containing Representaion information 35073 */ 35074 35075 /** 35076 * Returns a callback for Array.prototype.map for mapping Period nodes to a list of 35077 * Representation information objects 35078 * 35079 * @param {Object} mpdAttributes 35080 * Contains attributes inherited by the mpd 35081 * @param {string[]} mpdBaseUrls 35082 * Contains list of resolved base urls inherited by the mpd 35083 * @return {toAdaptationSetsCallback} 35084 * Callback map function 35085 */ 35086 35087 35088 var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) { 35089 return function (period, index) { 35090 var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL')); 35091 var periodAtt = parseAttributes$1(period); 35092 var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number 35093 35094 var periodIndex = window$3.isNaN(parsedPeriodId) ? index : parsedPeriodId; 35095 var periodAttributes = merge(mpdAttributes, { 35096 periodIndex: periodIndex 35097 }); 35098 var adaptationSets = findChildren(period, 'AdaptationSet'); 35099 var periodSegmentInfo = getSegmentInformation(period); 35100 return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo))); 35101 }; 35102 }; 35103 /** 35104 * Traverses the mpd xml tree to generate a list of Representation information objects 35105 * that have inherited attributes from parent nodes 35106 * 35107 * @param {Node} mpd 35108 * The root node of the mpd 35109 * @param {Object} options 35110 * Available options for inheritAttributes 35111 * @param {string} options.manifestUri 35112 * The uri source of the mpd 35113 * @param {number} options.NOW 35114 * Current time per DASH IOP. Default is current time in ms since epoch 35115 * @param {number} options.clientOffset 35116 * Client time difference from NOW (in milliseconds) 35117 * @return {RepresentationInformation[]} 35118 * List of objects containing Representation information 35119 */ 35120 35121 35122 var inheritAttributes = function inheritAttributes(mpd, options) { 35123 if (options === void 0) { 35124 options = {}; 35125 } 35126 35127 var _options = options, 35128 _options$manifestUri = _options.manifestUri, 35129 manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri, 35130 _options$NOW = _options.NOW, 35131 NOW = _options$NOW === void 0 ? Date.now() : _options$NOW, 35132 _options$clientOffset = _options.clientOffset, 35133 clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset;
vendor: 7,437 bytes, lines 35134-35372
35134 var periods = findChildren(mpd, 'Period'); 35135 35136 if (!periods.length) { 35137 throw new Error(errors.INVALID_NUMBER_OF_PERIOD); 35138 } 35139 35140 var mpdAttributes = parseAttributes$1(mpd); 35141 var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); 35142 mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0; 35143 mpdAttributes.NOW = NOW; 35144 mpdAttributes.clientOffset = clientOffset; 35145 return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))); 35146 }; 35147 35148 var stringToMpdXml = function stringToMpdXml(manifestString) { 35149 if (manifestString === '') { 35150 throw new Error(errors.DASH_EMPTY_MANIFEST); 35151 } 35152 35153 var parser = new domParser_3(); 35154 var xml = parser.parseFromString(manifestString, 'application/xml'); 35155 var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null; 35156 35157 if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) { 35158 throw new Error(errors.DASH_INVALID_XML); 35159 } 35160 35161 return mpd; 35162 }; 35163 /** 35164 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 35165 * 35166 * @param {string} mpd 35167 * XML string of the MPD manifest 35168 * @return {Object|null} 35169 * Attributes of UTCTiming node specified in the manifest. Null if none found 35170 */ 35171 35172 35173 var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) { 35174 var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0]; 35175 35176 if (!UTCTimingNode) { 35177 return null; 35178 } 35179 35180 var attributes = parseAttributes$1(UTCTimingNode); 35181 35182 switch (attributes.schemeIdUri) { 35183 case 'urn:mpeg:dash:utc:http-head:2014': 35184 case 'urn:mpeg:dash:utc:http-head:2012': 35185 attributes.method = 'HEAD'; 35186 break; 35187 35188 case 'urn:mpeg:dash:utc:http-xsdate:2014': 35189 case 'urn:mpeg:dash:utc:http-iso:2014': 35190 case 'urn:mpeg:dash:utc:http-xsdate:2012': 35191 case 'urn:mpeg:dash:utc:http-iso:2012': 35192 attributes.method = 'GET'; 35193 break; 35194 35195 case 'urn:mpeg:dash:utc:direct:2014': 35196 case 'urn:mpeg:dash:utc:direct:2012': 35197 attributes.method = 'DIRECT'; 35198 attributes.value = Date.parse(attributes.value); 35199 break; 35200 35201 case 'urn:mpeg:dash:utc:http-ntp:2014': 35202 case 'urn:mpeg:dash:utc:ntp:2014': 35203 case 'urn:mpeg:dash:utc:sntp:2014': 35204 default: 35205 throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME); 35206 } 35207 35208 return attributes; 35209 }; 35210 35211 var parse = function parse(manifestString, options) { 35212 if (options === void 0) { 35213 options = {}; 35214 } 35215 35216 return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)), options.sidxMapping); 35217 }; 35218 /** 35219 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 35220 * 35221 * @param {string} manifestString 35222 * XML string of the MPD manifest 35223 * @return {Object|null} 35224 * Attributes of UTCTiming node specified in the manifest. Null if none found 35225 */ 35226 35227 35228 var parseUTCTiming = function parseUTCTiming(manifestString) { 35229 return parseUTCTimingScheme(stringToMpdXml(manifestString)); 35230 }; 35231 35232 /** 35233 * mux.js 35234 * 35235 * Copyright (c) Brightcove 35236 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 35237 */ 35238 var toUnsigned = function toUnsigned(value) { 35239 return value >>> 0; 35240 }; 35241 35242 var toHexString = function toHexString(value) { 35243 return ('00' + value.toString(16)).slice(-2); 35244 }; 35245 35246 var bin = { 35247 toUnsigned: toUnsigned, 35248 toHexString: toHexString 35249 }; 35250 35251 var inspectMp4, 35252 _textifyMp, 35253 toUnsigned$1 = bin.toUnsigned, 35254 parseMp4Date = function parseMp4Date(seconds) { 35255 return new Date(seconds * 1000 - 2082844800000); 35256 }, 35257 parseSampleFlags = function parseSampleFlags(flags) { 35258 return { 35259 isLeading: (flags[0] & 0x0c) >>> 2, 35260 dependsOn: flags[0] & 0x03, 35261 isDependedOn: (flags[1] & 0xc0) >>> 6, 35262 hasRedundancy: (flags[1] & 0x30) >>> 4, 35263 paddingValue: (flags[1] & 0x0e) >>> 1, 35264 isNonSyncSample: flags[1] & 0x01, 35265 degradationPriority: flags[2] << 8 | flags[3] 35266 }; 35267 }, 35268 35269 /** 35270 * Returns the string representation of an ASCII encoded four byte buffer. 35271 * @param buffer {Uint8Array} a four-byte buffer to translate 35272 * @return {string} the corresponding string 35273 */ 35274 parseType = function parseType(buffer) { 35275 var result = ''; 35276 result += String.fromCharCode(buffer[0]); 35277 result += String.fromCharCode(buffer[1]); 35278 result += String.fromCharCode(buffer[2]); 35279 result += String.fromCharCode(buffer[3]); 35280 return result; 35281 }, 35282 // Find the data for a box specified by its path 35283 findBox = function findBox(data, path) { 35284 var results = [], 35285 i, 35286 size, 35287 type, 35288 end, 35289 subresults; 35290 35291 if (!path.length) { 35292 // short-circuit the search for empty paths 35293 return null; 35294 } 35295 35296 for (i = 0; i < data.byteLength;) { 35297 size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]); 35298 type = parseType(data.subarray(i + 4, i + 8)); 35299 end = size > 1 ? i + size : data.byteLength; 35300 35301 if (type === path[0]) { 35302 if (path.length === 1) { 35303 // this is the end of the path and we've found the box we were 35304 // looking for 35305 results.push(data.subarray(i + 8, end)); 35306 } else { 35307 // recursively search for the next box along the path 35308 subresults = findBox(data.subarray(i + 8, end), path.slice(1)); 35309 35310 if (subresults.length) { 35311 results = results.concat(subresults); 35312 } 35313 } 35314 } 35315 35316 i = end; 35317 } // we've finished searching all of data 35318 35319 35320 return results; 35321 }, 35322 nalParse = function nalParse(avcStream) { 35323 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 35324 result = [], 35325 i, 35326 length; 35327 35328 for (i = 0; i + 4 < avcStream.length; i += length) { 35329 length = avcView.getUint32(i); 35330 i += 4; // bail if this doesn't appear to be an H264 stream 35331 35332 if (length <= 0) { 35333 result.push('<span style=\'color:red;\'>MALFORMED DATA</span>'); 35334 continue; 35335 } 35336 35337 switch (avcStream[i] & 0x1F) { 35338 case 0x01: 35339 result.push('slice_layer_without_partitioning_rbsp'); 35340 break; 35341 35342 case 0x05: 35343 result.push('slice_layer_without_partitioning_rbsp_idr'); 35344 break; 35345 35346 case 0x06: 35347 result.push('sei_rbsp'); 35348 break; 35349 35350 case 0x07: 35351 result.push('seq_parameter_set_rbsp'); 35352 break; 35353 35354 case 0x08: 35355 result.push('pic_parameter_set_rbsp'); 35356 break; 35357 35358 case 0x09: 35359 result.push('access_unit_delimiter_rbsp'); 35360 break; 35361 35362 default: 35363 result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F); 35364 break; 35365 } 35366 } 35367 35368 return result; 35369 }, 35370 // registry of handlers for individual mp4 box types 35371 parse$1 = { 35372 // codingname, not a first-class box type. stsd entries share the
vendor: 6,898 bytes, lines 35373-35573
35373 // same format as real boxes so the parsing infrastructure can be 35374 // shared 35375 avc1: function avc1(data) { 35376 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 35377 return { 35378 dataReferenceIndex: view.getUint16(6), 35379 width: view.getUint16(24), 35380 height: view.getUint16(26), 35381 horizresolution: view.getUint16(28) + view.getUint16(30) / 16, 35382 vertresolution: view.getUint16(32) + view.getUint16(34) / 16, 35383 frameCount: view.getUint16(40), 35384 depth: view.getUint16(74), 35385 config: inspectMp4(data.subarray(78, data.byteLength)) 35386 }; 35387 }, 35388 avcC: function avcC(data) { 35389 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35390 result = { 35391 configurationVersion: data[0], 35392 avcProfileIndication: data[1], 35393 profileCompatibility: data[2], 35394 avcLevelIndication: data[3], 35395 lengthSizeMinusOne: data[4] & 0x03, 35396 sps: [], 35397 pps: [] 35398 }, 35399 numOfSequenceParameterSets = data[5] & 0x1f, 35400 numOfPictureParameterSets, 35401 nalSize, 35402 offset, 35403 i; // iterate past any SPSs 35404 35405 offset = 6; 35406 35407 for (i = 0; i < numOfSequenceParameterSets; i++) { 35408 nalSize = view.getUint16(offset); 35409 offset += 2; 35410 result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 35411 offset += nalSize; 35412 } // iterate past any PPSs 35413 35414 35415 numOfPictureParameterSets = data[offset]; 35416 offset++; 35417 35418 for (i = 0; i < numOfPictureParameterSets; i++) { 35419 nalSize = view.getUint16(offset); 35420 offset += 2; 35421 result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 35422 offset += nalSize; 35423 } 35424 35425 return result; 35426 }, 35427 btrt: function btrt(data) { 35428 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 35429 return { 35430 bufferSizeDB: view.getUint32(0), 35431 maxBitrate: view.getUint32(4), 35432 avgBitrate: view.getUint32(8) 35433 }; 35434 }, 35435 esds: function esds(data) { 35436 return { 35437 version: data[0], 35438 flags: new Uint8Array(data.subarray(1, 4)), 35439 esId: data[6] << 8 | data[7], 35440 streamPriority: data[8] & 0x1f, 35441 decoderConfig: { 35442 objectProfileIndication: data[11], 35443 streamType: data[12] >>> 2 & 0x3f, 35444 bufferSize: data[13] << 16 | data[14] << 8 | data[15], 35445 maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19], 35446 avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23], 35447 decoderConfigDescriptor: { 35448 tag: data[24], 35449 length: data[25], 35450 audioObjectType: data[26] >>> 3 & 0x1f, 35451 samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01, 35452 channelConfiguration: data[27] >>> 3 & 0x0f 35453 } 35454 } 35455 }; 35456 }, 35457 ftyp: function ftyp(data) { 35458 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35459 result = { 35460 majorBrand: parseType(data.subarray(0, 4)), 35461 minorVersion: view.getUint32(4), 35462 compatibleBrands: [] 35463 }, 35464 i = 8; 35465 35466 while (i < data.byteLength) { 35467 result.compatibleBrands.push(parseType(data.subarray(i, i + 4))); 35468 i += 4; 35469 } 35470 35471 return result; 35472 }, 35473 dinf: function dinf(data) { 35474 return { 35475 boxes: inspectMp4(data) 35476 }; 35477 }, 35478 dref: function dref(data) { 35479 return { 35480 version: data[0], 35481 flags: new Uint8Array(data.subarray(1, 4)), 35482 dataReferences: inspectMp4(data.subarray(8)) 35483 }; 35484 }, 35485 hdlr: function hdlr(data) { 35486 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35487 result = { 35488 version: view.getUint8(0), 35489 flags: new Uint8Array(data.subarray(1, 4)), 35490 handlerType: parseType(data.subarray(8, 12)), 35491 name: '' 35492 }, 35493 i = 8; // parse out the name field 35494 35495 for (i = 24; i < data.byteLength; i++) { 35496 if (data[i] === 0x00) { 35497 // the name field is null-terminated 35498 i++; 35499 break; 35500 } 35501 35502 result.name += String.fromCharCode(data[i]); 35503 } // decode UTF-8 to javascript's internal representation 35504 // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html 35505 35506 35507 result.name = decodeURIComponent(escape(result.name)); 35508 return result; 35509 }, 35510 mdat: function mdat(data) { 35511 return { 35512 byteLength: data.byteLength, 35513 nals: nalParse(data) 35514 }; 35515 }, 35516 mdhd: function mdhd(data) { 35517 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35518 i = 4, 35519 language, 35520 result = { 35521 version: view.getUint8(0), 35522 flags: new Uint8Array(data.subarray(1, 4)), 35523 language: '' 35524 }; 35525 35526 if (result.version === 1) { 35527 i += 4; 35528 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 35529 35530 i += 8; 35531 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 35532 35533 i += 4; 35534 result.timescale = view.getUint32(i); 35535 i += 8; 35536 result.duration = view.getUint32(i); // truncating top 4 bytes 35537 } else { 35538 result.creationTime = parseMp4Date(view.getUint32(i)); 35539 i += 4; 35540 result.modificationTime = parseMp4Date(view.getUint32(i)); 35541 i += 4; 35542 result.timescale = view.getUint32(i); 35543 i += 4; 35544 result.duration = view.getUint32(i); 35545 } 35546 35547 i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields 35548 // each field is the packed difference between its ASCII value and 0x60 35549 35550 language = view.getUint16(i); 35551 result.language += String.fromCharCode((language >> 10) + 0x60); 35552 result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60); 35553 result.language += String.fromCharCode((language & 0x1f) + 0x60); 35554 return result; 35555 }, 35556 mdia: function mdia(data) { 35557 return { 35558 boxes: inspectMp4(data) 35559 }; 35560 }, 35561 mfhd: function mfhd(data) { 35562 return { 35563 version: data[0], 35564 flags: new Uint8Array(data.subarray(1, 4)), 35565 sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] 35566 }; 35567 }, 35568 minf: function minf(data) { 35569 return { 35570 boxes: inspectMp4(data) 35571 }; 35572 }, 35573 // codingname, not a first-class box type. stsd entries share the
vendor: 26,181 bytes, lines 35574-36333
35574 // same format as real boxes so the parsing infrastructure can be 35575 // shared 35576 mp4a: function mp4a(data) { 35577 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35578 result = { 35579 // 6 bytes reserved 35580 dataReferenceIndex: view.getUint16(6), 35581 // 4 + 4 bytes reserved 35582 channelcount: view.getUint16(16), 35583 samplesize: view.getUint16(18), 35584 // 2 bytes pre_defined 35585 // 2 bytes reserved 35586 samplerate: view.getUint16(24) + view.getUint16(26) / 65536 35587 }; // if there are more bytes to process, assume this is an ISO/IEC 35588 // 14496-14 MP4AudioSampleEntry and parse the ESDBox 35589 35590 if (data.byteLength > 28) { 35591 result.streamDescriptor = inspectMp4(data.subarray(28))[0]; 35592 } 35593 35594 return result; 35595 }, 35596 moof: function moof(data) { 35597 return { 35598 boxes: inspectMp4(data) 35599 }; 35600 }, 35601 moov: function moov(data) { 35602 return { 35603 boxes: inspectMp4(data) 35604 }; 35605 }, 35606 mvex: function mvex(data) { 35607 return { 35608 boxes: inspectMp4(data) 35609 }; 35610 }, 35611 mvhd: function mvhd(data) { 35612 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35613 i = 4, 35614 result = { 35615 version: view.getUint8(0), 35616 flags: new Uint8Array(data.subarray(1, 4)) 35617 }; 35618 35619 if (result.version === 1) { 35620 i += 4; 35621 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 35622 35623 i += 8; 35624 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 35625 35626 i += 4; 35627 result.timescale = view.getUint32(i); 35628 i += 8; 35629 result.duration = view.getUint32(i); // truncating top 4 bytes 35630 } else { 35631 result.creationTime = parseMp4Date(view.getUint32(i)); 35632 i += 4; 35633 result.modificationTime = parseMp4Date(view.getUint32(i)); 35634 i += 4; 35635 result.timescale = view.getUint32(i); 35636 i += 4; 35637 result.duration = view.getUint32(i); 35638 } 35639 35640 i += 4; // convert fixed-point, base 16 back to a number 35641 35642 result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16; 35643 i += 4; 35644 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 35645 i += 2; 35646 i += 2; 35647 i += 2 * 4; 35648 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 35649 i += 9 * 4; 35650 i += 6 * 4; 35651 result.nextTrackId = view.getUint32(i); 35652 return result; 35653 }, 35654 pdin: function pdin(data) { 35655 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 35656 return { 35657 version: view.getUint8(0), 35658 flags: new Uint8Array(data.subarray(1, 4)), 35659 rate: view.getUint32(4), 35660 initialDelay: view.getUint32(8) 35661 }; 35662 }, 35663 sdtp: function sdtp(data) { 35664 var result = { 35665 version: data[0], 35666 flags: new Uint8Array(data.subarray(1, 4)), 35667 samples: [] 35668 }, 35669 i; 35670 35671 for (i = 4; i < data.byteLength; i++) { 35672 result.samples.push({ 35673 dependsOn: (data[i] & 0x30) >> 4, 35674 isDependedOn: (data[i] & 0x0c) >> 2, 35675 hasRedundancy: data[i] & 0x03 35676 }); 35677 } 35678 35679 return result; 35680 }, 35681 sidx: function sidx(data) { 35682 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35683 result = { 35684 version: data[0], 35685 flags: new Uint8Array(data.subarray(1, 4)), 35686 references: [], 35687 referenceId: view.getUint32(4), 35688 timescale: view.getUint32(8), 35689 earliestPresentationTime: view.getUint32(12), 35690 firstOffset: view.getUint32(16) 35691 }, 35692 referenceCount = view.getUint16(22), 35693 i; 35694 35695 for (i = 24; referenceCount; i += 12, referenceCount--) { 35696 result.references.push({ 35697 referenceType: (data[i] & 0x80) >>> 7, 35698 referencedSize: view.getUint32(i) & 0x7FFFFFFF, 35699 subsegmentDuration: view.getUint32(i + 4), 35700 startsWithSap: !!(data[i + 8] & 0x80), 35701 sapType: (data[i + 8] & 0x70) >>> 4, 35702 sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF 35703 }); 35704 } 35705 35706 return result; 35707 }, 35708 smhd: function smhd(data) { 35709 return { 35710 version: data[0], 35711 flags: new Uint8Array(data.subarray(1, 4)), 35712 balance: data[4] + data[5] / 256 35713 }; 35714 }, 35715 stbl: function stbl(data) { 35716 return { 35717 boxes: inspectMp4(data) 35718 }; 35719 }, 35720 stco: function stco(data) { 35721 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35722 result = { 35723 version: data[0], 35724 flags: new Uint8Array(data.subarray(1, 4)), 35725 chunkOffsets: [] 35726 }, 35727 entryCount = view.getUint32(4), 35728 i; 35729 35730 for (i = 8; entryCount; i += 4, entryCount--) { 35731 result.chunkOffsets.push(view.getUint32(i)); 35732 } 35733 35734 return result; 35735 }, 35736 stsc: function stsc(data) { 35737 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35738 entryCount = view.getUint32(4), 35739 result = { 35740 version: data[0], 35741 flags: new Uint8Array(data.subarray(1, 4)), 35742 sampleToChunks: [] 35743 }, 35744 i; 35745 35746 for (i = 8; entryCount; i += 12, entryCount--) { 35747 result.sampleToChunks.push({ 35748 firstChunk: view.getUint32(i), 35749 samplesPerChunk: view.getUint32(i + 4), 35750 sampleDescriptionIndex: view.getUint32(i + 8) 35751 }); 35752 } 35753 35754 return result; 35755 }, 35756 stsd: function stsd(data) { 35757 return { 35758 version: data[0], 35759 flags: new Uint8Array(data.subarray(1, 4)), 35760 sampleDescriptions: inspectMp4(data.subarray(8)) 35761 }; 35762 }, 35763 stsz: function stsz(data) { 35764 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35765 result = { 35766 version: data[0], 35767 flags: new Uint8Array(data.subarray(1, 4)), 35768 sampleSize: view.getUint32(4), 35769 entries: [] 35770 }, 35771 i; 35772 35773 for (i = 12; i < data.byteLength; i += 4) { 35774 result.entries.push(view.getUint32(i)); 35775 } 35776 35777 return result; 35778 }, 35779 stts: function stts(data) { 35780 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35781 result = { 35782 version: data[0], 35783 flags: new Uint8Array(data.subarray(1, 4)), 35784 timeToSamples: [] 35785 }, 35786 entryCount = view.getUint32(4), 35787 i; 35788 35789 for (i = 8; entryCount; i += 8, entryCount--) { 35790 result.timeToSamples.push({ 35791 sampleCount: view.getUint32(i), 35792 sampleDelta: view.getUint32(i + 4) 35793 }); 35794 } 35795 35796 return result; 35797 }, 35798 styp: function styp(data) { 35799 return parse$1.ftyp(data); 35800 }, 35801 tfdt: function tfdt(data) { 35802 var result = { 35803 version: data[0], 35804 flags: new Uint8Array(data.subarray(1, 4)), 35805 baseMediaDecodeTime: toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]) 35806 }; 35807 35808 if (result.version === 1) { 35809 result.baseMediaDecodeTime *= Math.pow(2, 32); 35810 result.baseMediaDecodeTime += toUnsigned$1(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]); 35811 } 35812 35813 return result; 35814 }, 35815 tfhd: function tfhd(data) { 35816 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35817 result = { 35818 version: data[0], 35819 flags: new Uint8Array(data.subarray(1, 4)), 35820 trackId: view.getUint32(4) 35821 }, 35822 baseDataOffsetPresent = result.flags[2] & 0x01, 35823 sampleDescriptionIndexPresent = result.flags[2] & 0x02, 35824 defaultSampleDurationPresent = result.flags[2] & 0x08, 35825 defaultSampleSizePresent = result.flags[2] & 0x10, 35826 defaultSampleFlagsPresent = result.flags[2] & 0x20, 35827 durationIsEmpty = result.flags[0] & 0x010000, 35828 defaultBaseIsMoof = result.flags[0] & 0x020000, 35829 i; 35830 i = 8; 35831 35832 if (baseDataOffsetPresent) { 35833 i += 4; // truncate top 4 bytes 35834 // FIXME: should we read the full 64 bits? 35835 35836 result.baseDataOffset = view.getUint32(12); 35837 i += 4; 35838 } 35839 35840 if (sampleDescriptionIndexPresent) { 35841 result.sampleDescriptionIndex = view.getUint32(i); 35842 i += 4; 35843 } 35844 35845 if (defaultSampleDurationPresent) { 35846 result.defaultSampleDuration = view.getUint32(i); 35847 i += 4; 35848 } 35849 35850 if (defaultSampleSizePresent) { 35851 result.defaultSampleSize = view.getUint32(i); 35852 i += 4; 35853 } 35854 35855 if (defaultSampleFlagsPresent) { 35856 result.defaultSampleFlags = view.getUint32(i); 35857 } 35858 35859 if (durationIsEmpty) { 35860 result.durationIsEmpty = true; 35861 } 35862 35863 if (!baseDataOffsetPresent && defaultBaseIsMoof) { 35864 result.baseDataOffsetIsMoof = true; 35865 } 35866 35867 return result; 35868 }, 35869 tkhd: function tkhd(data) { 35870 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35871 i = 4, 35872 result = { 35873 version: view.getUint8(0), 35874 flags: new Uint8Array(data.subarray(1, 4)) 35875 }; 35876 35877 if (result.version === 1) { 35878 i += 4; 35879 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 35880 35881 i += 8; 35882 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 35883 35884 i += 4; 35885 result.trackId = view.getUint32(i); 35886 i += 4; 35887 i += 8; 35888 result.duration = view.getUint32(i); // truncating top 4 bytes 35889 } else { 35890 result.creationTime = parseMp4Date(view.getUint32(i)); 35891 i += 4; 35892 result.modificationTime = parseMp4Date(view.getUint32(i)); 35893 i += 4; 35894 result.trackId = view.getUint32(i); 35895 i += 4; 35896 i += 4; 35897 result.duration = view.getUint32(i); 35898 } 35899 35900 i += 4; 35901 i += 2 * 4; 35902 result.layer = view.getUint16(i); 35903 i += 2; 35904 result.alternateGroup = view.getUint16(i); 35905 i += 2; // convert fixed-point, base 16 back to a number 35906 35907 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 35908 i += 2; 35909 i += 2; 35910 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 35911 i += 9 * 4; 35912 result.width = view.getUint16(i) + view.getUint16(i + 2) / 16; 35913 i += 4; 35914 result.height = view.getUint16(i) + view.getUint16(i + 2) / 16; 35915 return result; 35916 }, 35917 traf: function traf(data) { 35918 return { 35919 boxes: inspectMp4(data) 35920 }; 35921 }, 35922 trak: function trak(data) { 35923 return { 35924 boxes: inspectMp4(data) 35925 }; 35926 }, 35927 trex: function trex(data) { 35928 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 35929 return { 35930 version: data[0], 35931 flags: new Uint8Array(data.subarray(1, 4)), 35932 trackId: view.getUint32(4), 35933 defaultSampleDescriptionIndex: view.getUint32(8), 35934 defaultSampleDuration: view.getUint32(12), 35935 defaultSampleSize: view.getUint32(16), 35936 sampleDependsOn: data[20] & 0x03, 35937 sampleIsDependedOn: (data[21] & 0xc0) >> 6, 35938 sampleHasRedundancy: (data[21] & 0x30) >> 4, 35939 samplePaddingValue: (data[21] & 0x0e) >> 1, 35940 sampleIsDifferenceSample: !!(data[21] & 0x01), 35941 sampleDegradationPriority: view.getUint16(22) 35942 }; 35943 }, 35944 trun: function trun(data) { 35945 var result = { 35946 version: data[0], 35947 flags: new Uint8Array(data.subarray(1, 4)), 35948 samples: [] 35949 }, 35950 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 35951 // Flag interpretation 35952 dataOffsetPresent = result.flags[2] & 0x01, 35953 // compare with 2nd byte of 0x1 35954 firstSampleFlagsPresent = result.flags[2] & 0x04, 35955 // compare with 2nd byte of 0x4 35956 sampleDurationPresent = result.flags[1] & 0x01, 35957 // compare with 2nd byte of 0x100 35958 sampleSizePresent = result.flags[1] & 0x02, 35959 // compare with 2nd byte of 0x200 35960 sampleFlagsPresent = result.flags[1] & 0x04, 35961 // compare with 2nd byte of 0x400 35962 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, 35963 // compare with 2nd byte of 0x800 35964 sampleCount = view.getUint32(4), 35965 offset = 8, 35966 sample; 35967 35968 if (dataOffsetPresent) { 35969 // 32 bit signed integer 35970 result.dataOffset = view.getInt32(offset); 35971 offset += 4; 35972 } // Overrides the flags for the first sample only. The order of 35973 // optional values will be: duration, size, compositionTimeOffset 35974 35975 35976 if (firstSampleFlagsPresent && sampleCount) { 35977 sample = { 35978 flags: parseSampleFlags(data.subarray(offset, offset + 4)) 35979 }; 35980 offset += 4; 35981 35982 if (sampleDurationPresent) { 35983 sample.duration = view.getUint32(offset); 35984 offset += 4; 35985 } 35986 35987 if (sampleSizePresent) { 35988 sample.size = view.getUint32(offset); 35989 offset += 4; 35990 } 35991 35992 if (sampleCompositionTimeOffsetPresent) { 35993 // Note: this should be a signed int if version is 1 35994 sample.compositionTimeOffset = view.getUint32(offset); 35995 offset += 4; 35996 } 35997 35998 result.samples.push(sample); 35999 sampleCount--; 36000 } 36001 36002 while (sampleCount--) { 36003 sample = {}; 36004 36005 if (sampleDurationPresent) { 36006 sample.duration = view.getUint32(offset); 36007 offset += 4; 36008 } 36009 36010 if (sampleSizePresent) { 36011 sample.size = view.getUint32(offset); 36012 offset += 4; 36013 } 36014 36015 if (sampleFlagsPresent) { 36016 sample.flags = parseSampleFlags(data.subarray(offset, offset + 4)); 36017 offset += 4; 36018 } 36019 36020 if (sampleCompositionTimeOffsetPresent) { 36021 // Note: this should be a signed int if version is 1 36022 sample.compositionTimeOffset = view.getUint32(offset); 36023 offset += 4; 36024 } 36025 36026 result.samples.push(sample); 36027 } 36028 36029 return result; 36030 }, 36031 'url ': function url(data) { 36032 return { 36033 version: data[0], 36034 flags: new Uint8Array(data.subarray(1, 4)) 36035 }; 36036 }, 36037 vmhd: function vmhd(data) { 36038 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 36039 return { 36040 version: data[0], 36041 flags: new Uint8Array(data.subarray(1, 4)), 36042 graphicsmode: view.getUint16(4), 36043 opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)]) 36044 }; 36045 } 36046 }; 36047 /** 36048 * Return a javascript array of box objects parsed from an ISO base 36049 * media file. 36050 * @param data {Uint8Array} the binary data of the media to be inspected 36051 * @return {array} a javascript array of potentially nested box objects 36052 */ 36053 36054 36055 inspectMp4 = function inspectMp4(data) { 36056 var i = 0, 36057 result = [], 36058 view, 36059 size, 36060 type, 36061 end, 36062 box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API 36063 36064 var ab = new ArrayBuffer(data.length); 36065 var v = new Uint8Array(ab); 36066 36067 for (var z = 0; z < data.length; ++z) { 36068 v[z] = data[z]; 36069 } 36070 36071 view = new DataView(ab); 36072 36073 while (i < data.byteLength) { 36074 // parse box data 36075 size = view.getUint32(i); 36076 type = parseType(data.subarray(i + 4, i + 8)); 36077 end = size > 1 ? i + size : data.byteLength; // parse type-specific data 36078 36079 box = (parse$1[type] || function (data) { 36080 return { 36081 data: data 36082 }; 36083 })(data.subarray(i + 8, end)); 36084 36085 box.size = size; 36086 box.type = type; // store this box and move to the next 36087 36088 result.push(box); 36089 i = end; 36090 } 36091 36092 return result; 36093 }; 36094 /** 36095 * Returns a textual representation of the javascript represtentation 36096 * of an MP4 file. You can use it as an alternative to 36097 * JSON.stringify() to compare inspected MP4s. 36098 * @param inspectedMp4 {array} the parsed array of boxes in an MP4 36099 * file 36100 * @param depth {number} (optional) the number of ancestor boxes of 36101 * the elements of inspectedMp4. Assumed to be zero if unspecified. 36102 * @return {string} a text representation of the parsed MP4 36103 */ 36104 36105 36106 _textifyMp = function textifyMp4(inspectedMp4, depth) { 36107 var indent; 36108 depth = depth || 0; 36109 indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes 36110 36111 return inspectedMp4.map(function (box, index) { 36112 // list the box type first at the current indentation level 36113 return indent + box.type + '\n' + // the type is already included and handle child boxes separately 36114 Object.keys(box).filter(function (key) { 36115 return key !== 'type' && key !== 'boxes'; // output all the box properties 36116 }).map(function (key) { 36117 var prefix = indent + ' ' + key + ': ', 36118 value = box[key]; // print out raw bytes as hexademical 36119 36120 if (value instanceof Uint8Array || value instanceof Uint32Array) { 36121 var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (_byte) { 36122 return ' ' + ('00' + _byte.toString(16)).slice(-2); 36123 }).join('').match(/.{1,24}/g); 36124 36125 if (!bytes) { 36126 return prefix + '<>'; 36127 } 36128 36129 if (bytes.length === 1) { 36130 return prefix + '<' + bytes.join('').slice(1) + '>'; 36131 } 36132 36133 return prefix + '<\n' + bytes.map(function (line) { 36134 return indent + ' ' + line; 36135 }).join('\n') + '\n' + indent + ' >'; 36136 } // stringify generic objects 36137 36138 36139 return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) { 36140 if (index === 0) { 36141 return line; 36142 } 36143 36144 return indent + ' ' + line; 36145 }).join('\n'); 36146 }).join('\n') + ( // recursively textify the child boxes 36147 box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : ''); 36148 }).join('\n'); 36149 }; 36150 36151 var mp4Inspector = { 36152 inspect: inspectMp4, 36153 textify: _textifyMp, 36154 parseType: parseType, 36155 findBox: findBox, 36156 parseTraf: parse$1.traf, 36157 parseTfdt: parse$1.tfdt, 36158 parseHdlr: parse$1.hdlr, 36159 parseTfhd: parse$1.tfhd, 36160 parseTrun: parse$1.trun, 36161 parseSidx: parse$1.sidx 36162 }; 36163 36164 var toUnsigned$2 = bin.toUnsigned; 36165 var toHexString$1 = bin.toHexString; 36166 var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks; 36167 /** 36168 * Parses an MP4 initialization segment and extracts the timescale 36169 * values for any declared tracks. Timescale values indicate the 36170 * number of clock ticks per second to assume for time-based values 36171 * elsewhere in the MP4. 36172 * 36173 * To determine the start time of an MP4, you need two pieces of 36174 * information: the timescale unit and the earliest base media decode 36175 * time. Multiple timescales can be specified within an MP4 but the 36176 * base media decode time is always expressed in the timescale from 36177 * the media header box for the track: 36178 * ``` 36179 * moov > trak > mdia > mdhd.timescale 36180 * ``` 36181 * @param init {Uint8Array} the bytes of the init segment 36182 * @return {object} a hash of track ids to timescale values or null if 36183 * the init segment is malformed. 36184 */ 36185 36186 timescale = function timescale(init) { 36187 var result = {}, 36188 traks = mp4Inspector.findBox(init, ['moov', 'trak']); // mdhd timescale 36189 36190 return traks.reduce(function (result, trak) { 36191 var tkhd, version, index, id, mdhd; 36192 tkhd = mp4Inspector.findBox(trak, ['tkhd'])[0]; 36193 36194 if (!tkhd) { 36195 return null; 36196 } 36197 36198 version = tkhd[0]; 36199 index = version === 0 ? 12 : 20; 36200 id = toUnsigned$2(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]); 36201 mdhd = mp4Inspector.findBox(trak, ['mdia', 'mdhd'])[0]; 36202 36203 if (!mdhd) { 36204 return null; 36205 } 36206 36207 version = mdhd[0]; 36208 index = version === 0 ? 12 : 20; 36209 result[id] = toUnsigned$2(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 36210 return result; 36211 }, result); 36212 }; 36213 /** 36214 * Determine the base media decode start time, in seconds, for an MP4 36215 * fragment. If multiple fragments are specified, the earliest time is 36216 * returned. 36217 * 36218 * The base media decode time can be parsed from track fragment 36219 * metadata: 36220 * ``` 36221 * moof > traf > tfdt.baseMediaDecodeTime 36222 * ``` 36223 * It requires the timescale value from the mdhd to interpret. 36224 * 36225 * @param timescale {object} a hash of track ids to timescale values. 36226 * @return {number} the earliest base media decode start time for the 36227 * fragment, in seconds 36228 */ 36229 36230 36231 startTime = function startTime(timescale, fragment) { 36232 var trafs, baseTimes, result; // we need info from two childrend of each track fragment box 36233 36234 trafs = mp4Inspector.findBox(fragment, ['moof', 'traf']); // determine the start times for each track 36235 36236 baseTimes = [].concat.apply([], trafs.map(function (traf) { 36237 return mp4Inspector.findBox(traf, ['tfhd']).map(function (tfhd) { 36238 var id, scale, baseTime; // get the track id from the tfhd 36239 36240 id = toUnsigned$2(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified 36241 36242 scale = timescale[id] || 90e3; // get the base media decode time from the tfdt 36243 36244 baseTime = mp4Inspector.findBox(traf, ['tfdt']).map(function (tfdt) { 36245 var version, result; 36246 version = tfdt[0]; 36247 result = toUnsigned$2(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]); 36248 36249 if (version === 1) { 36250 result *= Math.pow(2, 32); 36251 result += toUnsigned$2(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]); 36252 } 36253 36254 return result; 36255 })[0]; 36256 baseTime = baseTime || Infinity; // convert base time to seconds 36257 36258 return baseTime / scale; 36259 }); 36260 })); // return the minimum 36261 36262 result = Math.min.apply(null, baseTimes); 36263 return isFinite(result) ? result : 0; 36264 }; 36265 /** 36266 * Determine the composition start, in seconds, for an MP4 36267 * fragment. 36268 * 36269 * The composition start time of a fragment can be calculated using the base 36270 * media decode time, composition time offset, and timescale, as follows: 36271 * 36272 * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale 36273 * 36274 * All of the aforementioned information is contained within a media fragment's 36275 * `traf` box, except for timescale info, which comes from the initialization 36276 * segment, so a track id (also contained within a `traf`) is also necessary to 36277 * associate it with a timescale 36278 * 36279 * 36280 * @param timescales {object} - a hash of track ids to timescale values. 36281 * @param fragment {Unit8Array} - the bytes of a media segment 36282 * @return {number} the composition start time for the fragment, in seconds 36283 **/ 36284 36285 36286 compositionStartTime = function compositionStartTime(timescales, fragment) { 36287 var trafBoxes = mp4Inspector.findBox(fragment, ['moof', 'traf']); 36288 var baseMediaDecodeTime = 0; 36289 var compositionTimeOffset = 0; 36290 var trackId; 36291 36292 if (trafBoxes && trafBoxes.length) { 36293 // The spec states that track run samples contained within a `traf` box are contiguous, but 36294 // it does not explicitly state whether the `traf` boxes themselves are contiguous. 36295 // We will assume that they are, so we only need the first to calculate start time. 36296 var parsedTraf = mp4Inspector.parseTraf(trafBoxes[0]); 36297 36298 for (var i = 0; i < parsedTraf.boxes.length; i++) { 36299 if (parsedTraf.boxes[i].type === 'tfhd') { 36300 trackId = parsedTraf.boxes[i].trackId; 36301 } else if (parsedTraf.boxes[i].type === 'tfdt') { 36302 baseMediaDecodeTime = parsedTraf.boxes[i].baseMediaDecodeTime; 36303 } else if (parsedTraf.boxes[i].type === 'trun' && parsedTraf.boxes[i].samples.length) { 36304 compositionTimeOffset = parsedTraf.boxes[i].samples[0].compositionTimeOffset || 0; 36305 } 36306 } 36307 } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was 36308 // specified. 36309 36310 36311 var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds 36312 36313 return (baseMediaDecodeTime + compositionTimeOffset) / timescale; 36314 }; 36315 /** 36316 * Find the trackIds of the video tracks in this source. 36317 * Found by parsing the Handler Reference and Track Header Boxes: 36318 * moov > trak > mdia > hdlr 36319 * moov > trak > tkhd 36320 * 36321 * @param {Uint8Array} init - The bytes of the init segment for this source 36322 * @return {Number[]} A list of trackIds 36323 * 36324 * @see ISO-BMFF-12/2015, Section 8.4.3 36325 **/ 36326 36327 36328 getVideoTrackIds = function getVideoTrackIds(init) { 36329 var traks = mp4Inspector.findBox(init, ['moov', 'trak']); 36330 var videoTrackIds = []; 36331 traks.forEach(function (trak) { 36332 var hdlrs = mp4Inspector.findBox(trak, ['mdia', 'hdlr']); 36333 var tkhds = mp4Inspector.findBox(trak, ['tkhd']);
vendor: 7,252 bytes, lines 36334-36540
36334 hdlrs.forEach(function (hdlr, index) { 36335 var handlerType = mp4Inspector.parseType(hdlr.subarray(8, 12)); 36336 var tkhd = tkhds[index]; 36337 var view; 36338 var version; 36339 var trackId; 36340 36341 if (handlerType === 'vide') { 36342 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 36343 version = view.getUint8(0); 36344 trackId = version === 0 ? view.getUint32(12) : view.getUint32(20); 36345 videoTrackIds.push(trackId); 36346 } 36347 }); 36348 }); 36349 return videoTrackIds; 36350 }; 36351 /** 36352 * Get all the video, audio, and hint tracks from a non fragmented 36353 * mp4 segment 36354 */ 36355 36356 36357 getTracks = function getTracks(init) { 36358 var traks = mp4Inspector.findBox(init, ['moov', 'trak']); 36359 var tracks = []; 36360 traks.forEach(function (trak) { 36361 var track = {}; 36362 var tkhd = mp4Inspector.findBox(trak, ['tkhd'])[0]; 36363 var view, version; // id 36364 36365 if (tkhd) { 36366 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 36367 version = view.getUint8(0); 36368 track.id = version === 0 ? view.getUint32(12) : view.getUint32(20); 36369 } 36370 36371 var hdlr = mp4Inspector.findBox(trak, ['mdia', 'hdlr'])[0]; // type 36372 36373 if (hdlr) { 36374 var type = mp4Inspector.parseType(hdlr.subarray(8, 12)); 36375 36376 if (type === 'vide') { 36377 track.type = 'video'; 36378 } else if (type === 'soun') { 36379 track.type = 'audio'; 36380 } else { 36381 track.type = type; 36382 } 36383 } // codec 36384 36385 36386 var stsd = mp4Inspector.findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; 36387 36388 if (stsd) { 36389 var sampleDescriptions = stsd.subarray(8); // gives the codec type string 36390 36391 track.codec = mp4Inspector.parseType(sampleDescriptions.subarray(4, 8)); 36392 var codecBox = mp4Inspector.findBox(sampleDescriptions, [track.codec])[0]; 36393 var codecConfig, codecConfigType; 36394 36395 if (codecBox) { 36396 // https://tools.ietf.org/html/rfc6381#section-3.3 36397 if (/^[a-z]vc[1-9]$/i.test(track.codec)) { 36398 // we don't need anything but the "config" parameter of the 36399 // avc1 codecBox 36400 codecConfig = codecBox.subarray(78); 36401 codecConfigType = mp4Inspector.parseType(codecConfig.subarray(4, 8)); 36402 36403 if (codecConfigType === 'avcC' && codecConfig.length > 11) { 36404 track.codec += '.'; // left padded with zeroes for single digit hex 36405 // profile idc 36406 36407 track.codec += toHexString$1(codecConfig[9]); // the byte containing the constraint_set flags 36408 36409 track.codec += toHexString$1(codecConfig[10]); // level idc 36410 36411 track.codec += toHexString$1(codecConfig[11]); 36412 } else { 36413 // TODO: show a warning that we couldn't parse the codec 36414 // and are using the default 36415 track.codec = 'avc1.4d400d'; 36416 } 36417 } else if (/^mp4[a,v]$/i.test(track.codec)) { 36418 // we do not need anything but the streamDescriptor of the mp4a codecBox 36419 codecConfig = codecBox.subarray(28); 36420 codecConfigType = mp4Inspector.parseType(codecConfig.subarray(4, 8)); 36421 36422 if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) { 36423 track.codec += '.' + toHexString$1(codecConfig[19]); // this value is only a single digit 36424 36425 track.codec += '.' + toHexString$1(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, ''); 36426 } else { 36427 // TODO: show a warning that we couldn't parse the codec 36428 // and are using the default 36429 track.codec = 'mp4a.40.2'; 36430 } 36431 } 36432 } 36433 } 36434 36435 var mdhd = mp4Inspector.findBox(trak, ['mdia', 'mdhd'])[0]; 36436 36437 if (mdhd && tkhd) { 36438 var index = version === 0 ? 12 : 20; 36439 track.timescale = toUnsigned$2(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 36440 } 36441 36442 tracks.push(track); 36443 }); 36444 return tracks; 36445 }; 36446 36447 var probe = { 36448 // export mp4 inspector's findBox and parseType for backwards compatibility 36449 findBox: mp4Inspector.findBox, 36450 parseType: mp4Inspector.parseType, 36451 timescale: timescale, 36452 startTime: startTime, 36453 compositionStartTime: compositionStartTime, 36454 videoTrackIds: getVideoTrackIds, 36455 tracks: getTracks 36456 }; 36457 36458 /** 36459 * mux.js 36460 * 36461 * Copyright (c) Brightcove 36462 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 36463 * 36464 * Reads in-band caption information from a video elementary 36465 * stream. Captions must follow the CEA-708 standard for injection 36466 * into an MPEG-2 transport streams. 36467 * @see https://en.wikipedia.org/wiki/CEA-708 36468 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 36469 */ 36470 // payload type field to indicate how they are to be 36471 // interpreted. CEAS-708 caption content is always transmitted with 36472 // payload type 0x04. 36473 36474 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 36475 RBSP_TRAILING_BITS = 128; 36476 /** 36477 * Parse a supplemental enhancement information (SEI) NAL unit. 36478 * Stops parsing once a message of type ITU T T35 has been found. 36479 * 36480 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 36481 * @return {object} the parsed SEI payload 36482 * @see Rec. ITU-T H.264, 7.3.2.3.1 36483 */ 36484 36485 var parseSei = function parseSei(bytes) { 36486 var i = 0, 36487 result = { 36488 payloadType: -1, 36489 payloadSize: 0 36490 }, 36491 payloadType = 0, 36492 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 36493 36494 while (i < bytes.byteLength) { 36495 // stop once we have hit the end of the sei_rbsp 36496 if (bytes[i] === RBSP_TRAILING_BITS) { 36497 break; 36498 } // Parse payload type 36499 36500 36501 while (bytes[i] === 0xFF) { 36502 payloadType += 255; 36503 i++; 36504 } 36505 36506 payloadType += bytes[i++]; // Parse payload size 36507 36508 while (bytes[i] === 0xFF) { 36509 payloadSize += 255; 36510 i++; 36511 } 36512 36513 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 36514 // there can only ever be one caption message in a frame's sei 36515 36516 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 36517 result.payloadType = payloadType; 36518 result.payloadSize = payloadSize; 36519 result.payload = bytes.subarray(i, i + payloadSize); 36520 break; 36521 } // skip the payload and parse the next message 36522 36523 36524 i += payloadSize; 36525 payloadType = 0; 36526 payloadSize = 0; 36527 } 36528 36529 return result; 36530 }; // see ANSI/SCTE 128-1 (2013), section 8.1 36531 36532 36533 var parseUserData = function parseUserData(sei) { 36534 // itu_t_t35_contry_code must be 181 (United States) for 36535 // captions 36536 if (sei.payload[0] !== 181) { 36537 return null; 36538 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 36539 36540
vendor: 4,193 bytes, lines 36541-36680
36541 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 36542 return null; 36543 } // the user_identifier should be "GA94" to indicate ATSC1 data 36544 36545 36546 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 36547 return null; 36548 } // finally, user_data_type_code should be 0x03 for caption data 36549 36550 36551 if (sei.payload[7] !== 0x03) { 36552 return null; 36553 } // return the user_data_type_structure and strip the trailing 36554 // marker bits 36555 36556 36557 return sei.payload.subarray(8, sei.payload.length - 1); 36558 }; // see CEA-708-D, section 4.4 36559 36560 36561 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 36562 var results = [], 36563 i, 36564 count, 36565 offset, 36566 data; // if this is just filler, return immediately 36567 36568 if (!(userData[0] & 0x40)) { 36569 return results; 36570 } // parse out the cc_data_1 and cc_data_2 fields 36571 36572 36573 count = userData[0] & 0x1f; 36574 36575 for (i = 0; i < count; i++) { 36576 offset = i * 3; 36577 data = { 36578 type: userData[offset + 2] & 0x03, 36579 pts: pts 36580 }; // capture cc data when cc_valid is 1 36581 36582 if (userData[offset + 2] & 0x04) { 36583 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 36584 results.push(data); 36585 } 36586 } 36587 36588 return results; 36589 }; 36590 36591 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 36592 var length = data.byteLength, 36593 emulationPreventionBytesPositions = [], 36594 i = 1, 36595 newLength, 36596 newData; // Find all `Emulation Prevention Bytes` 36597 36598 while (i < length - 2) { 36599 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 36600 emulationPreventionBytesPositions.push(i + 2); 36601 i += 2; 36602 } else { 36603 i++; 36604 } 36605 } // If no Emulation Prevention Bytes were found just return the original 36606 // array 36607 36608 36609 if (emulationPreventionBytesPositions.length === 0) { 36610 return data; 36611 } // Create a new array to hold the NAL unit data 36612 36613 36614 newLength = length - emulationPreventionBytesPositions.length; 36615 newData = new Uint8Array(newLength); 36616 var sourceIndex = 0; 36617 36618 for (i = 0; i < newLength; sourceIndex++, i++) { 36619 if (sourceIndex === emulationPreventionBytesPositions[0]) { 36620 // Skip this byte 36621 sourceIndex++; // Remove this position index 36622 36623 emulationPreventionBytesPositions.shift(); 36624 } 36625 36626 newData[i] = data[sourceIndex]; 36627 } 36628 36629 return newData; 36630 }; // exports 36631 36632 36633 var captionPacketParser = { 36634 parseSei: parseSei, 36635 parseUserData: parseUserData, 36636 parseCaptionPackets: parseCaptionPackets, 36637 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 36638 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 36639 }; 36640 36641 /** 36642 * mux.js 36643 * 36644 * Copyright (c) Brightcove 36645 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 36646 * 36647 * A lightweight readable stream implemention that handles event dispatching. 36648 * Objects that inherit from streams should call init in their constructors. 36649 */ 36650 36651 var Stream$1 = function Stream() { 36652 this.init = function () { 36653 var listeners = {}; 36654 /** 36655 * Add a listener for a specified event type. 36656 * @param type {string} the event name 36657 * @param listener {function} the callback to be invoked when an event of 36658 * the specified type occurs 36659 */ 36660 36661 this.on = function (type, listener) { 36662 if (!listeners[type]) { 36663 listeners[type] = []; 36664 } 36665 36666 listeners[type] = listeners[type].concat(listener); 36667 }; 36668 /** 36669 * Remove a listener for a specified event type. 36670 * @param type {string} the event name 36671 * @param listener {function} a function previously registered for this 36672 * type of event through `on` 36673 */ 36674 36675 36676 this.off = function (type, listener) { 36677 var index; 36678 36679 if (!listeners[type]) { 36680 return false;
vendor: 6,151 bytes, lines 36681-36855
36681 } 36682 36683 index = listeners[type].indexOf(listener); 36684 listeners[type] = listeners[type].slice(); 36685 listeners[type].splice(index, 1); 36686 return index > -1; 36687 }; 36688 /** 36689 * Trigger an event of the specified type on this stream. Any additional 36690 * arguments to this function are passed as parameters to event listeners. 36691 * @param type {string} the event name 36692 */ 36693 36694 36695 this.trigger = function (type) { 36696 var callbacks, i, length, args; 36697 callbacks = listeners[type]; 36698 36699 if (!callbacks) { 36700 return; 36701 } // Slicing the arguments on every invocation of this method 36702 // can add a significant amount of overhead. Avoid the 36703 // intermediate object creation for the common case of a 36704 // single callback argument 36705 36706 36707 if (arguments.length === 2) { 36708 length = callbacks.length; 36709 36710 for (i = 0; i < length; ++i) { 36711 callbacks[i].call(this, arguments[1]); 36712 } 36713 } else { 36714 args = []; 36715 i = arguments.length; 36716 36717 for (i = 1; i < arguments.length; ++i) { 36718 args.push(arguments[i]); 36719 } 36720 36721 length = callbacks.length; 36722 36723 for (i = 0; i < length; ++i) { 36724 callbacks[i].apply(this, args); 36725 } 36726 } 36727 }; 36728 /** 36729 * Destroys the stream and cleans up. 36730 */ 36731 36732 36733 this.dispose = function () { 36734 listeners = {}; 36735 }; 36736 }; 36737 }; 36738 /** 36739 * Forwards all `data` events on this stream to the destination stream. The 36740 * destination stream should provide a method `push` to receive the data 36741 * events as they arrive. 36742 * @param destination {stream} the stream that will receive all `data` events 36743 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 36744 * when the current stream emits a 'done' event 36745 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 36746 */ 36747 36748 36749 Stream$1.prototype.pipe = function (destination) { 36750 this.on('data', function (data) { 36751 destination.push(data); 36752 }); 36753 this.on('done', function (flushSource) { 36754 destination.flush(flushSource); 36755 }); 36756 this.on('partialdone', function (flushSource) { 36757 destination.partialFlush(flushSource); 36758 }); 36759 this.on('endedtimeline', function (flushSource) { 36760 destination.endTimeline(flushSource); 36761 }); 36762 this.on('reset', function (flushSource) { 36763 destination.reset(flushSource); 36764 }); 36765 return destination; 36766 }; // Default stream functions that are expected to be overridden to perform 36767 // actual work. These are provided by the prototype as a sort of no-op 36768 // implementation so that we don't have to check for their existence in the 36769 // `pipe` function above. 36770 36771 36772 Stream$1.prototype.push = function (data) { 36773 this.trigger('data', data); 36774 }; 36775 36776 Stream$1.prototype.flush = function (flushSource) { 36777 this.trigger('done', flushSource); 36778 }; 36779 36780 Stream$1.prototype.partialFlush = function (flushSource) { 36781 this.trigger('partialdone', flushSource); 36782 }; 36783 36784 Stream$1.prototype.endTimeline = function (flushSource) { 36785 this.trigger('endedtimeline', flushSource); 36786 }; 36787 36788 Stream$1.prototype.reset = function (flushSource) { 36789 this.trigger('reset', flushSource); 36790 }; 36791 36792 var stream = Stream$1; 36793 36794 // Link To Transport 36795 // ----------------- 36796 36797 36798 var CaptionStream = function CaptionStream() { 36799 CaptionStream.prototype.init.call(this); 36800 this.captionPackets_ = []; 36801 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 36802 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 36803 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 36804 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 36805 ]; 36806 this.reset(); // forward data and done events from CCs to this CaptionStream 36807 36808 this.ccStreams_.forEach(function (cc) { 36809 cc.on('data', this.trigger.bind(this, 'data')); 36810 cc.on('partialdone', this.trigger.bind(this, 'partialdone')); 36811 cc.on('done', this.trigger.bind(this, 'done')); 36812 }, this); 36813 }; 36814 36815 CaptionStream.prototype = new stream(); 36816 36817 CaptionStream.prototype.push = function (event) { 36818 var sei, userData, newCaptionPackets; // only examine SEI NALs 36819 36820 if (event.nalUnitType !== 'sei_rbsp') { 36821 return; 36822 } // parse the sei 36823 36824 36825 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 36826 36827 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 36828 return; 36829 } // parse out the user data payload 36830 36831 36832 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 36833 36834 if (!userData) { 36835 return; 36836 } // Sometimes, the same segment # will be downloaded twice. To stop the 36837 // caption data from being processed twice, we track the latest dts we've 36838 // received and ignore everything with a dts before that. However, since 36839 // data for a specific dts can be split across packets on either side of 36840 // a segment boundary, we need to make sure we *don't* ignore the packets 36841 // from the *next* segment that have dts === this.latestDts_. By constantly 36842 // tracking the number of packets received with dts === this.latestDts_, we 36843 // know how many should be ignored once we start receiving duplicates. 36844 36845 36846 if (event.dts < this.latestDts_) { 36847 // We've started getting older data, so set the flag. 36848 this.ignoreNextEqualDts_ = true; 36849 return; 36850 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 36851 this.numSameDts_--; 36852 36853 if (!this.numSameDts_) { 36854 // We've received the last duplicate packet, time to start processing again 36855 this.ignoreNextEqualDts_ = false;
vendor: 6,896 bytes, lines 36856-37102
36856 } 36857 36858 return; 36859 } // parse out CC data packets and save them for later 36860 36861 36862 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 36863 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 36864 36865 if (this.latestDts_ !== event.dts) { 36866 this.numSameDts_ = 0; 36867 } 36868 36869 this.numSameDts_++; 36870 this.latestDts_ = event.dts; 36871 }; 36872 36873 CaptionStream.prototype.flushCCStreams = function (flushType) { 36874 this.ccStreams_.forEach(function (cc) { 36875 return flushType === 'flush' ? cc.flush() : cc.partialFlush(); 36876 }, this); 36877 }; 36878 36879 CaptionStream.prototype.flushStream = function (flushType) { 36880 // make sure we actually parsed captions before proceeding 36881 if (!this.captionPackets_.length) { 36882 this.flushCCStreams(flushType); 36883 return; 36884 } // In Chrome, the Array#sort function is not stable so add a 36885 // presortIndex that we can use to ensure we get a stable-sort 36886 36887 36888 this.captionPackets_.forEach(function (elem, idx) { 36889 elem.presortIndex = idx; 36890 }); // sort caption byte-pairs based on their PTS values 36891 36892 this.captionPackets_.sort(function (a, b) { 36893 if (a.pts === b.pts) { 36894 return a.presortIndex - b.presortIndex; 36895 } 36896 36897 return a.pts - b.pts; 36898 }); 36899 this.captionPackets_.forEach(function (packet) { 36900 if (packet.type < 2) { 36901 // Dispatch packet to the right Cea608Stream 36902 this.dispatchCea608Packet(packet); 36903 } // this is where an 'else' would go for a dispatching packets 36904 // to a theoretical Cea708Stream that handles SERVICEn data 36905 36906 }, this); 36907 this.captionPackets_.length = 0; 36908 this.flushCCStreams(flushType); 36909 }; 36910 36911 CaptionStream.prototype.flush = function () { 36912 return this.flushStream('flush'); 36913 }; // Only called if handling partial data 36914 36915 36916 CaptionStream.prototype.partialFlush = function () { 36917 return this.flushStream('partialFlush'); 36918 }; 36919 36920 CaptionStream.prototype.reset = function () { 36921 this.latestDts_ = null; 36922 this.ignoreNextEqualDts_ = false; 36923 this.numSameDts_ = 0; 36924 this.activeCea608Channel_ = [null, null]; 36925 this.ccStreams_.forEach(function (ccStream) { 36926 ccStream.reset(); 36927 }); 36928 }; // From the CEA-608 spec: 36929 36930 /* 36931 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed 36932 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is 36933 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair 36934 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the 36935 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) 36936 * to switch to captioning or Text. 36937 */ 36938 // With that in mind, we ignore any data between an XDS control code and a 36939 // subsequent closed-captioning control code. 36940 36941 36942 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 36943 // NOTE: packet.type is the CEA608 field 36944 if (this.setsTextOrXDSActive(packet)) { 36945 this.activeCea608Channel_[packet.type] = null; 36946 } else if (this.setsChannel1Active(packet)) { 36947 this.activeCea608Channel_[packet.type] = 0; 36948 } else if (this.setsChannel2Active(packet)) { 36949 this.activeCea608Channel_[packet.type] = 1; 36950 } 36951 36952 if (this.activeCea608Channel_[packet.type] === null) { 36953 // If we haven't received anything to set the active channel, or the 36954 // packets are Text/XDS data, discard the data; we don't want jumbled 36955 // captions 36956 return; 36957 } 36958 36959 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 36960 }; 36961 36962 CaptionStream.prototype.setsChannel1Active = function (packet) { 36963 return (packet.ccData & 0x7800) === 0x1000; 36964 }; 36965 36966 CaptionStream.prototype.setsChannel2Active = function (packet) { 36967 return (packet.ccData & 0x7800) === 0x1800; 36968 }; 36969 36970 CaptionStream.prototype.setsTextOrXDSActive = function (packet) { 36971 return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a; 36972 }; // ---------------------- 36973 // Session to Application 36974 // ---------------------- 36975 // This hash maps non-ASCII, special, and extended character codes to their 36976 // proper Unicode equivalent. The first keys that are only a single byte 36977 // are the non-standard ASCII characters, which simply map the CEA608 byte 36978 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 36979 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 36980 // can be performed regardless of the field and data channel on which the 36981 // character code was received. 36982 36983 36984 var CHARACTER_TRANSLATION = { 36985 0x2a: 0xe1, 36986 // á 36987 0x5c: 0xe9, 36988 // é 36989 0x5e: 0xed, 36990 // à 36991 0x5f: 0xf3, 36992 // ó 36993 0x60: 0xfa, 36994 // ú 36995 0x7b: 0xe7, 36996 // ç 36997 0x7c: 0xf7, 36998 // ÷ 36999 0x7d: 0xd1, 37000 // à 37001 0x7e: 0xf1, 37002 // ñ 37003 0x7f: 0x2588, 37004 // â 37005 0x0130: 0xae, 37006 // ® 37007 0x0131: 0xb0, 37008 // ° 37009 0x0132: 0xbd, 37010 // ½ 37011 0x0133: 0xbf, 37012 // ¿ 37013 0x0134: 0x2122, 37014 // ⢠37015 0x0135: 0xa2, 37016 // ¢ 37017 0x0136: 0xa3, 37018 // £ 37019 0x0137: 0x266a, 37020 // ⪠37021 0x0138: 0xe0, 37022 // à 37023 0x0139: 0xa0, 37024 // 37025 0x013a: 0xe8, 37026 // è 37027 0x013b: 0xe2, 37028 // â 37029 0x013c: 0xea, 37030 // ê 37031 0x013d: 0xee, 37032 // î 37033 0x013e: 0xf4, 37034 // ô 37035 0x013f: 0xfb, 37036 // û 37037 0x0220: 0xc1, 37038 // à 37039 0x0221: 0xc9, 37040 // à 37041 0x0222: 0xd3, 37042 // à 37043 0x0223: 0xda, 37044 // à 37045 0x0224: 0xdc, 37046 // à 37047 0x0225: 0xfc, 37048 // ü 37049 0x0226: 0x2018, 37050 // â 37051 0x0227: 0xa1, 37052 // ¡ 37053 0x0228: 0x2a, 37054 // * 37055 0x0229: 0x27, 37056 // ' 37057 0x022a: 0x2014, 37058 // â 37059 0x022b: 0xa9, 37060 // © 37061 0x022c: 0x2120, 37062 // â 37063 0x022d: 0x2022, 37064 // ⢠37065 0x022e: 0x201c, 37066 // â 37067 0x022f: 0x201d, 37068 // â 37069 0x0230: 0xc0, 37070 // à 37071 0x0231: 0xc2, 37072 // à 37073 0x0232: 0xc7, 37074 // à 37075 0x0233: 0xc8, 37076 // à 37077 0x0234: 0xca, 37078 // à 37079 0x0235: 0xcb, 37080 // à 37081 0x0236: 0xeb, 37082 // ë 37083 0x0237: 0xce, 37084 // à 37085 0x0238: 0xcf, 37086 // à 37087 0x0239: 0xef, 37088 // ï 37089 0x023a: 0xd4, 37090 // à 37091 0x023b: 0xd9, 37092 // à 37093 0x023c: 0xf9, 37094 // ù 37095 0x023d: 0xdb, 37096 // à 37097 0x023e: 0xab, 37098 // « 37099 0x023f: 0xbb, 37100 // » 37101 0x0320: 0xc3, 37102 // Ã
vendor: 19,499 bytes, lines 37103-37635
37103 0x0321: 0xe3, 37104 // ã 37105 0x0322: 0xcd, 37106 // à 37107 0x0323: 0xcc, 37108 // à 37109 0x0324: 0xec, 37110 // ì 37111 0x0325: 0xd2, 37112 // à 37113 0x0326: 0xf2, 37114 // ò 37115 0x0327: 0xd5, 37116 // à 37117 0x0328: 0xf5, 37118 // õ 37119 0x0329: 0x7b, 37120 // { 37121 0x032a: 0x7d, 37122 // } 37123 0x032b: 0x5c, 37124 // \ 37125 0x032c: 0x5e, 37126 // ^ 37127 0x032d: 0x5f, 37128 // _ 37129 0x032e: 0x7c, 37130 // | 37131 0x032f: 0x7e, 37132 // ~ 37133 0x0330: 0xc4, 37134 // à 37135 0x0331: 0xe4, 37136 // ä 37137 0x0332: 0xd6, 37138 // à 37139 0x0333: 0xf6, 37140 // ö 37141 0x0334: 0xdf, 37142 // à 37143 0x0335: 0xa5, 37144 // Â¥ 37145 0x0336: 0xa4, 37146 // ¤ 37147 0x0337: 0x2502, 37148 // â 37149 0x0338: 0xc5, 37150 // à 37151 0x0339: 0xe5, 37152 // Ã¥ 37153 0x033a: 0xd8, 37154 // à 37155 0x033b: 0xf8, 37156 // ø 37157 0x033c: 0x250c, 37158 // â 37159 0x033d: 0x2510, 37160 // â 37161 0x033e: 0x2514, 37162 // â 37163 0x033f: 0x2518 // â 37164 37165 }; 37166 37167 var getCharFromCode = function getCharFromCode(code) { 37168 if (code === null) { 37169 return ''; 37170 } 37171 37172 code = CHARACTER_TRANSLATION[code] || code; 37173 return String.fromCharCode(code); 37174 }; // the index of the last row in a CEA-608 display buffer 37175 37176 37177 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 37178 // getting it through bit logic. 37179 37180 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 37181 // cells. The "bottom" row is the last element in the outer array. 37182 37183 var createDisplayBuffer = function createDisplayBuffer() { 37184 var result = [], 37185 i = BOTTOM_ROW + 1; 37186 37187 while (i--) { 37188 result.push(''); 37189 } 37190 37191 return result; 37192 }; 37193 37194 var Cea608Stream = function Cea608Stream(field, dataChannel) { 37195 Cea608Stream.prototype.init.call(this); 37196 this.field_ = field || 0; 37197 this.dataChannel_ = dataChannel || 0; 37198 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 37199 this.setConstants(); 37200 this.reset(); 37201 37202 this.push = function (packet) { 37203 var data, swap, char0, char1, text; // remove the parity bits 37204 37205 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 37206 37207 if (data === this.lastControlCode_) { 37208 this.lastControlCode_ = null; 37209 return; 37210 } // Store control codes 37211 37212 37213 if ((data & 0xf000) === 0x1000) { 37214 this.lastControlCode_ = data; 37215 } else if (data !== this.PADDING_) { 37216 this.lastControlCode_ = null; 37217 } 37218 37219 char0 = data >>> 8; 37220 char1 = data & 0xff; 37221 37222 if (data === this.PADDING_) { 37223 return; 37224 } else if (data === this.RESUME_CAPTION_LOADING_) { 37225 this.mode_ = 'popOn'; 37226 } else if (data === this.END_OF_CAPTION_) { 37227 // If an EOC is received while in paint-on mode, the displayed caption 37228 // text should be swapped to non-displayed memory as if it was a pop-on 37229 // caption. Because of that, we should explicitly switch back to pop-on 37230 // mode 37231 this.mode_ = 'popOn'; 37232 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 37233 37234 this.flushDisplayed(packet.pts); // flip memory 37235 37236 swap = this.displayed_; 37237 this.displayed_ = this.nonDisplayed_; 37238 this.nonDisplayed_ = swap; // start measuring the time to display the caption 37239 37240 this.startPts_ = packet.pts; 37241 } else if (data === this.ROLL_UP_2_ROWS_) { 37242 this.rollUpRows_ = 2; 37243 this.setRollUp(packet.pts); 37244 } else if (data === this.ROLL_UP_3_ROWS_) { 37245 this.rollUpRows_ = 3; 37246 this.setRollUp(packet.pts); 37247 } else if (data === this.ROLL_UP_4_ROWS_) { 37248 this.rollUpRows_ = 4; 37249 this.setRollUp(packet.pts); 37250 } else if (data === this.CARRIAGE_RETURN_) { 37251 this.clearFormatting(packet.pts); 37252 this.flushDisplayed(packet.pts); 37253 this.shiftRowsUp_(); 37254 this.startPts_ = packet.pts; 37255 } else if (data === this.BACKSPACE_) { 37256 if (this.mode_ === 'popOn') { 37257 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 37258 } else { 37259 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 37260 } 37261 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 37262 this.flushDisplayed(packet.pts); 37263 this.displayed_ = createDisplayBuffer(); 37264 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 37265 this.nonDisplayed_ = createDisplayBuffer(); 37266 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 37267 if (this.mode_ !== 'paintOn') { 37268 // NOTE: This should be removed when proper caption positioning is 37269 // implemented 37270 this.flushDisplayed(packet.pts); 37271 this.displayed_ = createDisplayBuffer(); 37272 } 37273 37274 this.mode_ = 'paintOn'; 37275 this.startPts_ = packet.pts; // Append special characters to caption text 37276 } else if (this.isSpecialCharacter(char0, char1)) { 37277 // Bitmask char0 so that we can apply character transformations 37278 // regardless of field and data channel. 37279 // Then byte-shift to the left and OR with char1 so we can pass the 37280 // entire character code to `getCharFromCode`. 37281 char0 = (char0 & 0x03) << 8; 37282 text = getCharFromCode(char0 | char1); 37283 this[this.mode_](packet.pts, text); 37284 this.column_++; // Append extended characters to caption text 37285 } else if (this.isExtCharacter(char0, char1)) { 37286 // Extended characters always follow their "non-extended" equivalents. 37287 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 37288 // decoders are supposed to drop the "è", while compliant decoders 37289 // backspace the "e" and insert "è". 37290 // Delete the previous character 37291 if (this.mode_ === 'popOn') { 37292 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 37293 } else { 37294 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 37295 } // Bitmask char0 so that we can apply character transformations 37296 // regardless of field and data channel. 37297 // Then byte-shift to the left and OR with char1 so we can pass the 37298 // entire character code to `getCharFromCode`. 37299 37300 37301 char0 = (char0 & 0x03) << 8; 37302 text = getCharFromCode(char0 | char1); 37303 this[this.mode_](packet.pts, text); 37304 this.column_++; // Process mid-row codes 37305 } else if (this.isMidRowCode(char0, char1)) { 37306 // Attributes are not additive, so clear all formatting 37307 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 37308 // should be replaced with spaces, so add one now 37309 37310 this[this.mode_](packet.pts, ' '); 37311 this.column_++; 37312 37313 if ((char1 & 0xe) === 0xe) { 37314 this.addFormatting(packet.pts, ['i']); 37315 } 37316 37317 if ((char1 & 0x1) === 0x1) { 37318 this.addFormatting(packet.pts, ['u']); 37319 } // Detect offset control codes and adjust cursor 37320 37321 } else if (this.isOffsetControlCode(char0, char1)) { 37322 // Cursor position is set by indent PAC (see below) in 4-column 37323 // increments, with an additional offset code of 1-3 to reach any 37324 // of the 32 columns specified by CEA-608. So all we need to do 37325 // here is increment the column cursor by the given offset. 37326 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 37327 } else if (this.isPAC(char0, char1)) { 37328 // There's no logic for PAC -> row mapping, so we have to just 37329 // find the row code in an array and use its index :( 37330 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 37331 37332 if (this.mode_ === 'rollUp') { 37333 // This implies that the base row is incorrectly set. 37334 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 37335 // of roll-up rows set. 37336 if (row - this.rollUpRows_ + 1 < 0) { 37337 row = this.rollUpRows_ - 1; 37338 } 37339 37340 this.setRollUp(packet.pts, row); 37341 } 37342 37343 if (row !== this.row_) { 37344 // formatting is only persistent for current row 37345 this.clearFormatting(packet.pts); 37346 this.row_ = row; 37347 } // All PACs can apply underline, so detect and apply 37348 // (All odd-numbered second bytes set underline) 37349 37350 37351 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 37352 this.addFormatting(packet.pts, ['u']); 37353 } 37354 37355 if ((data & 0x10) === 0x10) { 37356 // We've got an indent level code. Each successive even number 37357 // increments the column cursor by 4, so we can get the desired 37358 // column position by bit-shifting to the right (to get n/2) 37359 // and multiplying by 4. 37360 this.column_ = ((data & 0xe) >> 1) * 4; 37361 } 37362 37363 if (this.isColorPAC(char1)) { 37364 // it's a color code, though we only support white, which 37365 // can be either normal or italicized. white italics can be 37366 // either 0x4e or 0x6e depending on the row, so we just 37367 // bitwise-and with 0xe to see if italics should be turned on 37368 if ((char1 & 0xe) === 0xe) { 37369 this.addFormatting(packet.pts, ['i']); 37370 } 37371 } // We have a normal character in char0, and possibly one in char1 37372 37373 } else if (this.isNormalChar(char0)) { 37374 if (char1 === 0x00) { 37375 char1 = null; 37376 } 37377 37378 text = getCharFromCode(char0); 37379 text += getCharFromCode(char1); 37380 this[this.mode_](packet.pts, text); 37381 this.column_ += text.length; 37382 } // finish data processing 37383 37384 }; 37385 }; 37386 37387 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 37388 // display buffer 37389 37390 Cea608Stream.prototype.flushDisplayed = function (pts) { 37391 var content = this.displayed_ // remove spaces from the start and end of the string 37392 .map(function (row) { 37393 try { 37394 return row.trim(); 37395 } catch (e) { 37396 // Ordinarily, this shouldn't happen. However, caption 37397 // parsing errors should not throw exceptions and 37398 // break playback. 37399 // eslint-disable-next-line no-console 37400 console.error('Skipping malformed caption.'); 37401 return ''; 37402 } 37403 }) // combine all text rows to display in one cue 37404 .join('\n') // and remove blank rows from the start and end, but not the middle 37405 .replace(/^\n+|\n+$/g, ''); 37406 37407 if (content.length) { 37408 this.trigger('data', { 37409 startPts: this.startPts_, 37410 endPts: pts, 37411 text: content, 37412 stream: this.name_ 37413 }); 37414 } 37415 }; 37416 /** 37417 * Zero out the data, used for startup and on seek 37418 */ 37419 37420 37421 Cea608Stream.prototype.reset = function () { 37422 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 37423 // actually display captions. If a caption is shifted to a row 37424 // with a lower index than this, it is cleared from the display 37425 // buffer 37426 37427 this.topRow_ = 0; 37428 this.startPts_ = 0; 37429 this.displayed_ = createDisplayBuffer(); 37430 this.nonDisplayed_ = createDisplayBuffer(); 37431 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 37432 37433 this.column_ = 0; 37434 this.row_ = BOTTOM_ROW; 37435 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 37436 37437 this.formatting_ = []; 37438 }; 37439 /** 37440 * Sets up control code and related constants for this instance 37441 */ 37442 37443 37444 Cea608Stream.prototype.setConstants = function () { 37445 // The following attributes have these uses: 37446 // ext_ : char0 for mid-row codes, and the base for extended 37447 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 37448 // extended codes) 37449 // control_: char0 for control codes, except byte-shifted to the 37450 // left so that we can do this.control_ | CONTROL_CODE 37451 // offset_: char0 for tab offset codes 37452 // 37453 // It's also worth noting that control codes, and _only_ control codes, 37454 // differ between field 1 and field2. Field 2 control codes are always 37455 // their field 1 value plus 1. That's why there's the "| field" on the 37456 // control value. 37457 if (this.dataChannel_ === 0) { 37458 this.BASE_ = 0x10; 37459 this.EXT_ = 0x11; 37460 this.CONTROL_ = (0x14 | this.field_) << 8; 37461 this.OFFSET_ = 0x17; 37462 } else if (this.dataChannel_ === 1) { 37463 this.BASE_ = 0x18; 37464 this.EXT_ = 0x19; 37465 this.CONTROL_ = (0x1c | this.field_) << 8; 37466 this.OFFSET_ = 0x1f; 37467 } // Constants for the LSByte command codes recognized by Cea608Stream. This 37468 // list is not exhaustive. For a more comprehensive listing and semantics see 37469 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 37470 // Padding 37471 37472 37473 this.PADDING_ = 0x0000; // Pop-on Mode 37474 37475 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 37476 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 37477 37478 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 37479 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 37480 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 37481 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 37482 37483 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 37484 37485 this.BACKSPACE_ = this.CONTROL_ | 0x21; 37486 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 37487 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 37488 }; 37489 /** 37490 * Detects if the 2-byte packet data is a special character 37491 * 37492 * Special characters have a second byte in the range 0x30 to 0x3f, 37493 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 37494 * data channel 2). 37495 * 37496 * @param {Integer} char0 The first byte 37497 * @param {Integer} char1 The second byte 37498 * @return {Boolean} Whether the 2 bytes are an special character 37499 */ 37500 37501 37502 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 37503 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 37504 }; 37505 /** 37506 * Detects if the 2-byte packet data is an extended character 37507 * 37508 * Extended characters have a second byte in the range 0x20 to 0x3f, 37509 * with the first byte being 0x12 or 0x13 (for data channel 1) or 37510 * 0x1a or 0x1b (for data channel 2). 37511 * 37512 * @param {Integer} char0 The first byte 37513 * @param {Integer} char1 The second byte 37514 * @return {Boolean} Whether the 2 bytes are an extended character 37515 */ 37516 37517 37518 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 37519 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 37520 }; 37521 /** 37522 * Detects if the 2-byte packet is a mid-row code 37523 * 37524 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 37525 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 37526 * channel 2). 37527 * 37528 * @param {Integer} char0 The first byte 37529 * @param {Integer} char1 The second byte 37530 * @return {Boolean} Whether the 2 bytes are a mid-row code 37531 */ 37532 37533 37534 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 37535 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 37536 }; 37537 /** 37538 * Detects if the 2-byte packet is an offset control code 37539 * 37540 * Offset control codes have a second byte in the range 0x21 to 0x23, 37541 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 37542 * data channel 2). 37543 * 37544 * @param {Integer} char0 The first byte 37545 * @param {Integer} char1 The second byte 37546 * @return {Boolean} Whether the 2 bytes are an offset control code 37547 */ 37548 37549 37550 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 37551 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 37552 }; 37553 /** 37554 * Detects if the 2-byte packet is a Preamble Address Code 37555 * 37556 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 37557 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 37558 * range 0x40 to 0x7f. 37559 * 37560 * @param {Integer} char0 The first byte 37561 * @param {Integer} char1 The second byte 37562 * @return {Boolean} Whether the 2 bytes are a PAC 37563 */ 37564 37565 37566 Cea608Stream.prototype.isPAC = function (char0, char1) { 37567 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 37568 }; 37569 /** 37570 * Detects if a packet's second byte is in the range of a PAC color code 37571 * 37572 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 37573 * 0x60 to 0x6f. 37574 * 37575 * @param {Integer} char1 The second byte 37576 * @return {Boolean} Whether the byte is a color PAC 37577 */ 37578 37579 37580 Cea608Stream.prototype.isColorPAC = function (char1) { 37581 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 37582 }; 37583 /** 37584 * Detects if a single byte is in the range of a normal character 37585 * 37586 * Normal text bytes are in the range 0x20 to 0x7f. 37587 * 37588 * @param {Integer} char The byte 37589 * @return {Boolean} Whether the byte is a normal character 37590 */ 37591 37592 37593 Cea608Stream.prototype.isNormalChar = function (_char) { 37594 return _char >= 0x20 && _char <= 0x7f; 37595 }; 37596 /** 37597 * Configures roll-up 37598 * 37599 * @param {Integer} pts Current PTS 37600 * @param {Integer} newBaseRow Used by PACs to slide the current window to 37601 * a new position 37602 */ 37603 37604 37605 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 37606 // Reset the base row to the bottom row when switching modes 37607 if (this.mode_ !== 'rollUp') { 37608 this.row_ = BOTTOM_ROW; 37609 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 37610 37611 this.flushDisplayed(pts); 37612 this.nonDisplayed_ = createDisplayBuffer(); 37613 this.displayed_ = createDisplayBuffer(); 37614 } 37615 37616 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 37617 // move currently displayed captions (up or down) to the new base row 37618 for (var i = 0; i < this.rollUpRows_; i++) { 37619 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 37620 this.displayed_[this.row_ - i] = ''; 37621 } 37622 } 37623 37624 if (newBaseRow === undefined) { 37625 newBaseRow = this.row_; 37626 } 37627 37628 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 37629 }; // Adds the opening HTML tag for the passed character to the caption text, 37630 // and keeps track of it for later closing 37631 37632 37633 Cea608Stream.prototype.addFormatting = function (pts, format) { 37634 this.formatting_ = this.formatting_.concat(format); 37635 var text = format.reduce(function (text, format) {
vendor: 3,099 bytes, lines 37636-37736
37636 return text + '<' + format + '>'; 37637 }, ''); 37638 this[this.mode_](pts, text); 37639 }; // Adds HTML closing tags for current formatting to caption text and 37640 // clears remembered formatting 37641 37642 37643 Cea608Stream.prototype.clearFormatting = function (pts) { 37644 if (!this.formatting_.length) { 37645 return; 37646 } 37647 37648 var text = this.formatting_.reverse().reduce(function (text, format) { 37649 return text + '</' + format + '>'; 37650 }, ''); 37651 this.formatting_ = []; 37652 this[this.mode_](pts, text); 37653 }; // Mode Implementations 37654 37655 37656 Cea608Stream.prototype.popOn = function (pts, text) { 37657 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 37658 37659 baseRow += text; 37660 this.nonDisplayed_[this.row_] = baseRow; 37661 }; 37662 37663 Cea608Stream.prototype.rollUp = function (pts, text) { 37664 var baseRow = this.displayed_[this.row_]; 37665 baseRow += text; 37666 this.displayed_[this.row_] = baseRow; 37667 }; 37668 37669 Cea608Stream.prototype.shiftRowsUp_ = function () { 37670 var i; // clear out inactive rows 37671 37672 for (i = 0; i < this.topRow_; i++) { 37673 this.displayed_[i] = ''; 37674 } 37675 37676 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 37677 this.displayed_[i] = ''; 37678 } // shift displayed rows up 37679 37680 37681 for (i = this.topRow_; i < this.row_; i++) { 37682 this.displayed_[i] = this.displayed_[i + 1]; 37683 } // clear out the bottom row 37684 37685 37686 this.displayed_[this.row_] = ''; 37687 }; 37688 37689 Cea608Stream.prototype.paintOn = function (pts, text) { 37690 var baseRow = this.displayed_[this.row_]; 37691 baseRow += text; 37692 this.displayed_[this.row_] = baseRow; 37693 }; // exports 37694 37695 37696 var captionStream = { 37697 CaptionStream: CaptionStream, 37698 Cea608Stream: Cea608Stream 37699 }; 37700 37701 var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes; 37702 var CaptionStream$1 = captionStream.CaptionStream; 37703 /** 37704 * Maps an offset in the mdat to a sample based on the the size of the samples. 37705 * Assumes that `parseSamples` has been called first. 37706 * 37707 * @param {Number} offset - The offset into the mdat 37708 * @param {Object[]} samples - An array of samples, parsed using `parseSamples` 37709 * @return {?Object} The matching sample, or null if no match was found. 37710 * 37711 * @see ISO-BMFF-12/2015, Section 8.8.8 37712 **/ 37713 37714 var mapToSample = function mapToSample(offset, samples) { 37715 var approximateOffset = offset; 37716 37717 for (var i = 0; i < samples.length; i++) { 37718 var sample = samples[i]; 37719 37720 if (approximateOffset < sample.size) { 37721 return sample; 37722 } 37723 37724 approximateOffset -= sample.size; 37725 } 37726 37727 return null; 37728 }; 37729 /** 37730 * Finds SEI nal units contained in a Media Data Box. 37731 * Assumes that `parseSamples` has been called first. 37732 * 37733 * @param {Uint8Array} avcStream - The bytes of the mdat 37734 * @param {Object[]} samples - The samples parsed out by `parseSamples` 37735 * @param {Number} trackId - The trackId of this video track 37736 * @return {Object[]}
vendor: 8,256 bytes, lines 37736-37961
37736 seiNals - the parsed SEI NALUs found. 37737 * The contents of the seiNal should match what is expected by 37738 * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts) 37739 * 37740 * @see ISO-BMFF-12/2015, Section 8.1.1 37741 * @see Rec. ITU-T H.264, 7.3.2.3.1 37742 **/ 37743 37744 37745 var findSeiNals = function findSeiNals(avcStream, samples, trackId) { 37746 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 37747 result = [], 37748 seiNal, 37749 i, 37750 length, 37751 lastMatchedSample; 37752 37753 for (i = 0; i + 4 < avcStream.length; i += length) { 37754 length = avcView.getUint32(i); 37755 i += 4; // Bail if this doesn't appear to be an H264 stream 37756 37757 if (length <= 0) { 37758 continue; 37759 } 37760 37761 switch (avcStream[i] & 0x1F) { 37762 case 0x06: 37763 var data = avcStream.subarray(i + 1, i + 1 + length); 37764 var matchingSample = mapToSample(i, samples); 37765 seiNal = { 37766 nalUnitType: 'sei_rbsp', 37767 size: length, 37768 data: data, 37769 escapedRBSP: discardEmulationPreventionBytes$1(data), 37770 trackId: trackId 37771 }; 37772 37773 if (matchingSample) { 37774 seiNal.pts = matchingSample.pts; 37775 seiNal.dts = matchingSample.dts; 37776 lastMatchedSample = matchingSample; 37777 } else { 37778 // If a matching sample cannot be found, use the last 37779 // sample's values as they should be as close as possible 37780 seiNal.pts = lastMatchedSample.pts; 37781 seiNal.dts = lastMatchedSample.dts; 37782 } 37783 37784 result.push(seiNal); 37785 break; 37786 } 37787 } 37788 37789 return result; 37790 }; 37791 /** 37792 * Parses sample information out of Track Run Boxes and calculates 37793 * the absolute presentation and decode timestamps of each sample. 37794 * 37795 * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed 37796 * @param {Number} baseMediaDecodeTime - base media decode time from tfdt 37797 @see ISO-BMFF-12/2015, Section 8.8.12 37798 * @param {Object} tfhd - The parsed Track Fragment Header 37799 * @see inspect.parseTfhd 37800 * @return {Object[]} the parsed samples 37801 * 37802 * @see ISO-BMFF-12/2015, Section 8.8.8 37803 **/ 37804 37805 37806 var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) { 37807 var currentDts = baseMediaDecodeTime; 37808 var defaultSampleDuration = tfhd.defaultSampleDuration || 0; 37809 var defaultSampleSize = tfhd.defaultSampleSize || 0; 37810 var trackId = tfhd.trackId; 37811 var allSamples = []; 37812 truns.forEach(function (trun) { 37813 // Note: We currently do not parse the sample table as well 37814 // as the trun. It's possible some sources will require this. 37815 // moov > trak > mdia > minf > stbl 37816 var trackRun = mp4Inspector.parseTrun(trun); 37817 var samples = trackRun.samples; 37818 samples.forEach(function (sample) { 37819 if (sample.duration === undefined) { 37820 sample.duration = defaultSampleDuration; 37821 } 37822 37823 if (sample.size === undefined) { 37824 sample.size = defaultSampleSize; 37825 } 37826 37827 sample.trackId = trackId; 37828 sample.dts = currentDts; 37829 37830 if (sample.compositionTimeOffset === undefined) { 37831 sample.compositionTimeOffset = 0; 37832 } 37833 37834 sample.pts = currentDts + sample.compositionTimeOffset; 37835 currentDts += sample.duration; 37836 }); 37837 allSamples = allSamples.concat(samples); 37838 }); 37839 return allSamples; 37840 }; 37841 /** 37842 * Parses out caption nals from an FMP4 segment's video tracks. 37843 * 37844 * @param {Uint8Array} segment - The bytes of a single segment 37845 * @param {Number} videoTrackId - The trackId of a video track in the segment 37846 * @return {Object.<Number, Object[]>} A mapping of video trackId to 37847 * a list of seiNals found in that track 37848 **/ 37849 37850 37851 var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) { 37852 // To get the samples 37853 var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units 37854 37855 var mdats = probe.findBox(segment, ['mdat']); 37856 var captionNals = {}; 37857 var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs 37858 37859 mdats.forEach(function (mdat, index) { 37860 var matchingTraf = trafs[index]; 37861 mdatTrafPairs.push({ 37862 mdat: mdat, 37863 traf: matchingTraf 37864 }); 37865 }); 37866 mdatTrafPairs.forEach(function (pair) { 37867 var mdat = pair.mdat; 37868 var traf = pair.traf; 37869 var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf 37870 37871 var headerInfo = mp4Inspector.parseTfhd(tfhd[0]); 37872 var trackId = headerInfo.trackId; 37873 var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf 37874 37875 var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0; 37876 var truns = probe.findBox(traf, ['trun']); 37877 var samples; 37878 var seiNals; // Only parse video data for the chosen video track 37879 37880 if (videoTrackId === trackId && truns.length > 0) { 37881 samples = parseSamples(truns, baseMediaDecodeTime, headerInfo); 37882 seiNals = findSeiNals(mdat, samples, trackId); 37883 37884 if (!captionNals[trackId]) { 37885 captionNals[trackId] = []; 37886 } 37887 37888 captionNals[trackId] = captionNals[trackId].concat(seiNals); 37889 } 37890 }); 37891 return captionNals; 37892 }; 37893 /** 37894 * Parses out inband captions from an MP4 container and returns 37895 * caption objects that can be used by WebVTT and the TextTrack API. 37896 * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue 37897 * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack 37898 * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first 37899 * 37900 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 37901 * @param {Number} trackId - The id of the video track to parse 37902 * @param {Number} timescale - The timescale for the video track from the init segment 37903 * 37904 * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks 37905 * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds 37906 * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds 37907 * @return {String} parsedCaptions[].text - The visible content of the caption 37908 **/ 37909 37910 37911 var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) { 37912 var seiNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 37913 37914 if (trackId === null) { 37915 return null; 37916 } 37917 37918 seiNals = parseCaptionNals(segment, trackId); 37919 return { 37920 seiNals: seiNals[trackId], 37921 timescale: timescale 37922 }; 37923 }; 37924 /** 37925 * Converts SEI NALUs into captions that can be used by video.js 37926 **/ 37927 37928 37929 var CaptionParser = function CaptionParser() { 37930 var isInitialized = false; 37931 var captionStream; // Stores segments seen before trackId and timescale are set 37932 37933 var segmentCache; // Stores video track ID of the track being parsed 37934 37935 var trackId; // Stores the timescale of the track being parsed 37936 37937 var timescale; // Stores captions parsed so far 37938 37939 var parsedCaptions; // Stores whether we are receiving partial data or not 37940 37941 var parsingPartial; 37942 /** 37943 * A method to indicate whether a CaptionParser has been initalized 37944 * @returns {Boolean} 37945 **/ 37946 37947 this.isInitialized = function () { 37948 return isInitialized; 37949 }; 37950 /** 37951 * Initializes the underlying CaptionStream, SEI NAL parsing 37952 * and management, and caption collection 37953 **/ 37954 37955 37956 this.init = function (options) { 37957 captionStream = new CaptionStream$1(); 37958 isInitialized = true; 37959 parsingPartial = options ? options.isPartial : false; // Collect dispatched captions 37960 37961 captionStream.on('data', function (event) {
vendor: 4,700 bytes, lines 37962-38111
37962 // Convert to seconds in the source's timescale 37963 event.startTime = event.startPts / timescale; 37964 event.endTime = event.endPts / timescale; 37965 parsedCaptions.captions.push(event); 37966 parsedCaptions.captionStreams[event.stream] = true; 37967 }); 37968 }; 37969 /** 37970 * Determines if a new video track will be selected 37971 * or if the timescale changed 37972 * @return {Boolean} 37973 **/ 37974 37975 37976 this.isNewInit = function (videoTrackIds, timescales) { 37977 if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) { 37978 return false; 37979 } 37980 37981 return trackId !== videoTrackIds[0] || timescale !== timescales[trackId]; 37982 }; 37983 /** 37984 * Parses out SEI captions and interacts with underlying 37985 * CaptionStream to return dispatched captions 37986 * 37987 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 37988 * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment 37989 * @param {Object.<Number, Number>} timescales - The timescales found in the init segment 37990 * @see parseEmbeddedCaptions 37991 * @see m2ts/caption-stream.js 37992 **/ 37993 37994 37995 this.parse = function (segment, videoTrackIds, timescales) { 37996 var parsedData; 37997 37998 if (!this.isInitialized()) { 37999 return null; // This is not likely to be a video segment 38000 } else if (!videoTrackIds || !timescales) { 38001 return null; 38002 } else if (this.isNewInit(videoTrackIds, timescales)) { 38003 // Use the first video track only as there is no 38004 // mechanism to switch to other video tracks 38005 trackId = videoTrackIds[0]; 38006 timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment 38007 // data until we have one. 38008 // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 38009 } else if (trackId === null || !timescale) { 38010 segmentCache.push(segment); 38011 return null; 38012 } // Now that a timescale and trackId is set, parse cached segments 38013 38014 38015 while (segmentCache.length > 0) { 38016 var cachedSegment = segmentCache.shift(); 38017 this.parse(cachedSegment, videoTrackIds, timescales); 38018 } 38019 38020 parsedData = parseEmbeddedCaptions(segment, trackId, timescale); 38021 38022 if (parsedData === null || !parsedData.seiNals) { 38023 return null; 38024 } 38025 38026 this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched 38027 38028 this.flushStream(); 38029 return parsedCaptions; 38030 }; 38031 /** 38032 * Pushes SEI NALUs onto CaptionStream 38033 * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals` 38034 * Assumes that `parseCaptionNals` has been called first 38035 * @see m2ts/caption-stream.js 38036 **/ 38037 38038 38039 this.pushNals = function (nals) { 38040 if (!this.isInitialized() || !nals || nals.length === 0) { 38041 return null; 38042 } 38043 38044 nals.forEach(function (nal) { 38045 captionStream.push(nal); 38046 }); 38047 }; 38048 /** 38049 * Flushes underlying CaptionStream to dispatch processed, displayable captions 38050 * @see m2ts/caption-stream.js 38051 **/ 38052 38053 38054 this.flushStream = function () { 38055 if (!this.isInitialized()) { 38056 return null; 38057 } 38058 38059 if (!parsingPartial) { 38060 captionStream.flush(); 38061 } else { 38062 captionStream.partialFlush(); 38063 } 38064 }; 38065 /** 38066 * Reset caption buckets for new data 38067 **/ 38068 38069 38070 this.clearParsedCaptions = function () { 38071 parsedCaptions.captions = []; 38072 parsedCaptions.captionStreams = {}; 38073 }; 38074 /** 38075 * Resets underlying CaptionStream 38076 * @see m2ts/caption-stream.js 38077 **/ 38078 38079 38080 this.resetCaptionStream = function () { 38081 if (!this.isInitialized()) { 38082 return null; 38083 } 38084 38085 captionStream.reset(); 38086 }; 38087 /** 38088 * Convenience method to clear all captions flushed from the 38089 * CaptionStream and still being parsed 38090 * @see m2ts/caption-stream.js 38091 **/ 38092 38093 38094 this.clearAllCaptions = function () { 38095 this.clearParsedCaptions(); 38096 this.resetCaptionStream(); 38097 }; 38098 /** 38099 * Reset caption parser 38100 **/ 38101 38102 38103 this.reset = function () { 38104 segmentCache = []; 38105 trackId = null; 38106 timescale = null; 38107 38108 if (!parsedCaptions) { 38109 parsedCaptions = { 38110 captions: [], 38111 // CC1, CC2, CC3, CC4
vendor: 7,627 bytes, lines 38112-38345
38112 captionStreams: {} 38113 }; 38114 } else { 38115 this.clearParsedCaptions(); 38116 } 38117 38118 this.resetCaptionStream(); 38119 }; 38120 38121 this.reset(); 38122 }; 38123 38124 var captionParser = CaptionParser; 38125 38126 /** 38127 * mux.js 38128 * 38129 * Copyright (c) Brightcove 38130 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 38131 */ 38132 38133 var streamTypes = { 38134 H264_STREAM_TYPE: 0x1B, 38135 ADTS_STREAM_TYPE: 0x0F, 38136 METADATA_STREAM_TYPE: 0x15 38137 }; 38138 38139 var MAX_TS = 8589934592; 38140 var RO_THRESH = 4294967296; 38141 var TYPE_SHARED = 'shared'; 38142 38143 var handleRollover = function handleRollover(value, reference) { 38144 var direction = 1; 38145 38146 if (value > reference) { 38147 // If the current timestamp value is greater than our reference timestamp and we detect a 38148 // timestamp rollover, this means the roll over is happening in the opposite direction. 38149 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 38150 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 38151 // rollover point. In loading this segment, the timestamp values will be very large, 38152 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 38153 // the time stamp to be `value - 2^33`. 38154 direction = -1; 38155 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 38156 // cause an incorrect adjustment. 38157 38158 38159 while (Math.abs(reference - value) > RO_THRESH) { 38160 value += direction * MAX_TS; 38161 } 38162 38163 return value; 38164 }; 38165 38166 var TimestampRolloverStream = function TimestampRolloverStream(type) { 38167 var lastDTS, referenceDTS; 38168 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 38169 // video and audio. We could use `undefined` here, but having a string 38170 // makes debugging a little clearer. 38171 38172 this.type_ = type || TYPE_SHARED; 38173 38174 this.push = function (data) { 38175 // Any "shared" rollover streams will accept _all_ data. Otherwise, 38176 // streams will only accept data that matches their type. 38177 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 38178 return; 38179 } 38180 38181 if (referenceDTS === undefined) { 38182 referenceDTS = data.dts; 38183 } 38184 38185 data.dts = handleRollover(data.dts, referenceDTS); 38186 data.pts = handleRollover(data.pts, referenceDTS); 38187 lastDTS = data.dts; 38188 this.trigger('data', data); 38189 }; 38190 38191 this.flush = function () { 38192 referenceDTS = lastDTS; 38193 this.trigger('done'); 38194 }; 38195 38196 this.endTimeline = function () { 38197 this.flush(); 38198 this.trigger('endedtimeline'); 38199 }; 38200 38201 this.discontinuity = function () { 38202 referenceDTS = void 0; 38203 lastDTS = void 0; 38204 }; 38205 38206 this.reset = function () { 38207 this.discontinuity(); 38208 this.trigger('reset'); 38209 }; 38210 }; 38211 38212 TimestampRolloverStream.prototype = new stream(); 38213 var timestampRolloverStream = { 38214 TimestampRolloverStream: TimestampRolloverStream, 38215 handleRollover: handleRollover 38216 }; 38217 38218 var parsePid = function parsePid(packet) { 38219 var pid = packet[1] & 0x1f; 38220 pid <<= 8; 38221 pid |= packet[2]; 38222 return pid; 38223 }; 38224 38225 var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) { 38226 return !!(packet[1] & 0x40); 38227 }; 38228 38229 var parseAdaptionField = function parseAdaptionField(packet) { 38230 var offset = 0; // if an adaption field is present, its length is specified by the 38231 // fifth byte of the TS packet header. The adaptation field is 38232 // used to add stuffing to PES packets that don't fill a complete 38233 // TS packet, and to specify some forms of timing and control data 38234 // that we do not currently use. 38235 38236 if ((packet[3] & 0x30) >>> 4 > 0x01) { 38237 offset += packet[4] + 1; 38238 } 38239 38240 return offset; 38241 }; 38242 38243 var parseType$1 = function parseType(packet, pmtPid) { 38244 var pid = parsePid(packet); 38245 38246 if (pid === 0) { 38247 return 'pat'; 38248 } else if (pid === pmtPid) { 38249 return 'pmt'; 38250 } else if (pmtPid) { 38251 return 'pes'; 38252 } 38253 38254 return null; 38255 }; 38256 38257 var parsePat = function parsePat(packet) { 38258 var pusi = parsePayloadUnitStartIndicator(packet); 38259 var offset = 4 + parseAdaptionField(packet); 38260 38261 if (pusi) { 38262 offset += packet[offset] + 1; 38263 } 38264 38265 return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; 38266 }; 38267 38268 var parsePmt = function parsePmt(packet) { 38269 var programMapTable = {}; 38270 var pusi = parsePayloadUnitStartIndicator(packet); 38271 var payloadOffset = 4 + parseAdaptionField(packet); 38272 38273 if (pusi) { 38274 payloadOffset += packet[payloadOffset] + 1; 38275 } // PMTs can be sent ahead of the time when they should actually 38276 // take effect. We don't believe this should ever be the case 38277 // for HLS but we'll ignore "forward" PMT declarations if we see 38278 // them. Future PMT declarations have the current_next_indicator 38279 // set to zero. 38280 38281 38282 if (!(packet[payloadOffset + 5] & 0x01)) { 38283 return; 38284 } 38285 38286 var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section 38287 38288 sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; 38289 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 38290 // long the program info descriptors are 38291 38292 programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table 38293 38294 var offset = 12 + programInfoLength; 38295 38296 while (offset < tableEnd) { 38297 var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type 38298 38299 programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry 38300 // skip past the elementary stream descriptors, if present 38301 38302 offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; 38303 } 38304 38305 return programMapTable; 38306 }; 38307 38308 var parsePesType = function parsePesType(packet, programMapTable) { 38309 var pid = parsePid(packet); 38310 var type = programMapTable[pid]; 38311 38312 switch (type) { 38313 case streamTypes.H264_STREAM_TYPE: 38314 return 'video'; 38315 38316 case streamTypes.ADTS_STREAM_TYPE: 38317 return 'audio'; 38318 38319 case streamTypes.METADATA_STREAM_TYPE: 38320 return 'timed-metadata'; 38321 38322 default: 38323 return null; 38324 } 38325 }; 38326 38327 var parsePesTime = function parsePesTime(packet) { 38328 var pusi = parsePayloadUnitStartIndicator(packet); 38329 38330 if (!pusi) { 38331 return null; 38332 } 38333 38334 var offset = 4 + parseAdaptionField(packet); 38335 38336 if (offset >= packet.byteLength) { 38337 // From the H 222.0 MPEG-TS spec 38338 // "For transport stream packets carrying PES packets, stuffing is needed when there 38339 // is insufficient PES packet data to completely fill the transport stream packet 38340 // payload bytes. Stuffing is accomplished by defining an adaptation field longer than 38341 // the sum of the lengths of the data elements in it, so that the payload bytes 38342 // remaining after the adaptation field exactly accommodates the available PES packet 38343 // data." 38344 // 38345 // If the offset is >
vendor: 6,005 bytes, lines 38345-38522
38345= the length of the packet, then the packet contains no data 38346 // and instead is just adaption field stuffing bytes 38347 return null; 38348 } 38349 38350 var pes = null; 38351 var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value 38352 // and a DTS value. Determine what combination of values is 38353 // available to work with. 38354 38355 ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 38356 // performs all bitwise operations on 32-bit integers but javascript 38357 // supports a much greater range (52-bits) of integer using standard 38358 // mathematical operations. 38359 // We construct a 31-bit value using bitwise operators over the 31 38360 // most significant bits and then multiply by 4 (equal to a left-shift 38361 // of 2) before we add the final 2 least significant bits of the 38362 // timestamp (equal to an OR.) 38363 38364 if (ptsDtsFlags & 0xC0) { 38365 pes = {}; // the PTS and DTS are not written out directly. For information 38366 // on how they are encoded, see 38367 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 38368 38369 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; 38370 pes.pts *= 4; // Left shift by 2 38371 38372 pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs 38373 38374 pes.dts = pes.pts; 38375 38376 if (ptsDtsFlags & 0x40) { 38377 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; 38378 pes.dts *= 4; // Left shift by 2 38379 38380 pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs 38381 } 38382 } 38383 38384 return pes; 38385 }; 38386 38387 var parseNalUnitType = function parseNalUnitType(type) { 38388 switch (type) { 38389 case 0x05: 38390 return 'slice_layer_without_partitioning_rbsp_idr'; 38391 38392 case 0x06: 38393 return 'sei_rbsp'; 38394 38395 case 0x07: 38396 return 'seq_parameter_set_rbsp'; 38397 38398 case 0x08: 38399 return 'pic_parameter_set_rbsp'; 38400 38401 case 0x09: 38402 return 'access_unit_delimiter_rbsp'; 38403 38404 default: 38405 return null; 38406 } 38407 }; 38408 38409 var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) { 38410 var offset = 4 + parseAdaptionField(packet); 38411 var frameBuffer = packet.subarray(offset); 38412 var frameI = 0; 38413 var frameSyncPoint = 0; 38414 var foundKeyFrame = false; 38415 var nalType; // advance the sync point to a NAL start, if necessary 38416 38417 for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { 38418 if (frameBuffer[frameSyncPoint + 2] === 1) { 38419 // the sync point is properly aligned 38420 frameI = frameSyncPoint + 5; 38421 break; 38422 } 38423 } 38424 38425 while (frameI < frameBuffer.byteLength) { 38426 // look at the current byte to determine if we've hit the end of 38427 // a NAL unit boundary 38428 switch (frameBuffer[frameI]) { 38429 case 0: 38430 // skip past non-sync sequences 38431 if (frameBuffer[frameI - 1] !== 0) { 38432 frameI += 2; 38433 break; 38434 } else if (frameBuffer[frameI - 2] !== 0) { 38435 frameI++; 38436 break; 38437 } 38438 38439 if (frameSyncPoint + 3 !== frameI - 2) { 38440 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 38441 38442 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 38443 foundKeyFrame = true; 38444 } 38445 } // drop trailing zeroes 38446 38447 38448 do { 38449 frameI++; 38450 } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); 38451 38452 frameSyncPoint = frameI - 2; 38453 frameI += 3; 38454 break; 38455 38456 case 1: 38457 // skip past non-sync sequences 38458 if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) { 38459 frameI += 3; 38460 break; 38461 } 38462 38463 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 38464 38465 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 38466 foundKeyFrame = true; 38467 } 38468 38469 frameSyncPoint = frameI - 2; 38470 frameI += 3; 38471 break; 38472 38473 default: 38474 // the current byte isn't a one or zero, so it cannot be part 38475 // of a sync sequence 38476 frameI += 3; 38477 break; 38478 } 38479 } 38480 38481 frameBuffer = frameBuffer.subarray(frameSyncPoint); 38482 frameI -= frameSyncPoint; 38483 frameSyncPoint = 0; // parse the final nal 38484 38485 if (frameBuffer && frameBuffer.byteLength > 3) { 38486 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 38487 38488 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 38489 foundKeyFrame = true; 38490 } 38491 } 38492 38493 return foundKeyFrame; 38494 }; 38495 38496 var probe$1 = { 38497 parseType: parseType$1, 38498 parsePat: parsePat, 38499 parsePmt: parsePmt, 38500 parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, 38501 parsePesType: parsePesType, 38502 parsePesTime: parsePesTime, 38503 videoPacketContainsKeyFrame: videoPacketContainsKeyFrame 38504 }; 38505 38506 /** 38507 * mux.js 38508 * 38509 * Copyright (c) Brightcove 38510 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 38511 * 38512 * Utilities to detect basic properties and metadata about Aac data. 38513 */ 38514 38515 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 38516 38517 var isLikelyAacData = function isLikelyAacData(data) { 38518 if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) { 38519 return true; 38520 } 38521 38522 return false;
vendor: 5,804 bytes, lines 38523-38700
38523 }; 38524 38525 var parseSyncSafeInteger = function parseSyncSafeInteger(data) { 38526 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 38527 }; // return a percent-encoded representation of the specified byte range 38528 // @see http://en.wikipedia.org/wiki/Percent-encoding 38529 38530 38531 var percentEncode = function percentEncode(bytes, start, end) { 38532 var i, 38533 result = ''; 38534 38535 for (i = start; i < end; i++) { 38536 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 38537 } 38538 38539 return result; 38540 }; // return the string representation of the specified byte range, 38541 // interpreted as ISO-8859-1. 38542 38543 38544 var parseIso88591 = function parseIso88591(bytes, start, end) { 38545 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 38546 }; 38547 38548 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 38549 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 38550 flags = header[byteIndex + 5], 38551 footerPresent = (flags & 16) >> 4; 38552 38553 if (footerPresent) { 38554 return returnSize + 20; 38555 } 38556 38557 return returnSize + 10; 38558 }; 38559 38560 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 38561 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 38562 middle = header[byteIndex + 4] << 3, 38563 highTwo = header[byteIndex + 3] & 0x3 << 11; 38564 return highTwo | middle | lowThree; 38565 }; 38566 38567 var parseType$2 = function parseType(header, byteIndex) { 38568 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 38569 return 'timed-metadata'; 38570 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 38571 return 'audio'; 38572 } 38573 38574 return null; 38575 }; 38576 38577 var parseSampleRate = function parseSampleRate(packet) { 38578 var i = 0; 38579 38580 while (i + 5 < packet.length) { 38581 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 38582 // If a valid header was not found, jump one forward and attempt to 38583 // find a valid ADTS header starting at the next byte 38584 i++; 38585 continue; 38586 } 38587 38588 return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2]; 38589 } 38590 38591 return null; 38592 }; 38593 38594 var parseAacTimestamp = function parseAacTimestamp(packet) { 38595 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 38596 38597 frameStart = 10; 38598 38599 if (packet[5] & 0x40) { 38600 // advance the frame start past the extended header 38601 frameStart += 4; // header size field 38602 38603 frameStart += parseSyncSafeInteger(packet.subarray(10, 14)); 38604 } // parse one or more ID3 frames 38605 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 38606 38607 38608 do { 38609 // determine the number of bytes in this frame 38610 frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8)); 38611 38612 if (frameSize < 1) { 38613 return null; 38614 } 38615 38616 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 38617 38618 if (frameHeader === 'PRIV') { 38619 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 38620 38621 for (var i = 0; i < frame.byteLength; i++) { 38622 if (frame[i] === 0) { 38623 var owner = parseIso88591(frame, 0, i); 38624 38625 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 38626 var d = frame.subarray(i + 1); 38627 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 38628 size *= 4; 38629 size += d[7] & 0x03; 38630 return size; 38631 } 38632 38633 break; 38634 } 38635 } 38636 } 38637 38638 frameStart += 10; // advance past the frame header 38639 38640 frameStart += frameSize; // advance past the frame body 38641 } while (frameStart < packet.byteLength); 38642 38643 return null; 38644 }; 38645 38646 var utils = { 38647 isLikelyAacData: isLikelyAacData, 38648 parseId3TagSize: parseId3TagSize, 38649 parseAdtsSize: parseAdtsSize, 38650 parseType: parseType$2, 38651 parseSampleRate: parseSampleRate, 38652 parseAacTimestamp: parseAacTimestamp 38653 }; 38654 38655 /** 38656 * mux.js 38657 * 38658 * Copyright (c) Brightcove 38659 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 38660 */ 38661 var ONE_SECOND_IN_TS = 90000, 38662 // 90kHz clock 38663 secondsToVideoTs, 38664 secondsToAudioTs, 38665 videoTsToSeconds, 38666 audioTsToSeconds, 38667 audioTsToVideoTs, 38668 videoTsToAudioTs, 38669 metadataTsToSeconds; 38670 38671 secondsToVideoTs = function secondsToVideoTs(seconds) { 38672 return seconds * ONE_SECOND_IN_TS; 38673 }; 38674 38675 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 38676 return seconds * sampleRate; 38677 }; 38678 38679 videoTsToSeconds = function videoTsToSeconds(timestamp) { 38680 return timestamp / ONE_SECOND_IN_TS; 38681 }; 38682 38683 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 38684 return timestamp / sampleRate; 38685 }; 38686 38687 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 38688 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 38689 }; 38690 38691 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 38692 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 38693 }; 38694 /** 38695 * Adjust ID3 tag or caption timing information by the timeline pts values 38696 * (if keepOriginalTimestamps is false) and convert to seconds 38697 */ 38698 38699 38700 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts,
vendor: 14,915 bytes, lines 38700-39157
38700keepOriginalTimestamps) { 38701 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 38702 }; 38703 38704 var clock = { 38705 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 38706 secondsToVideoTs: secondsToVideoTs, 38707 secondsToAudioTs: secondsToAudioTs, 38708 videoTsToSeconds: videoTsToSeconds, 38709 audioTsToSeconds: audioTsToSeconds, 38710 audioTsToVideoTs: audioTsToVideoTs, 38711 videoTsToAudioTs: videoTsToAudioTs, 38712 metadataTsToSeconds: metadataTsToSeconds 38713 }; 38714 38715 var handleRollover$1 = timestampRolloverStream.handleRollover; 38716 var probe$2 = {}; 38717 probe$2.ts = probe$1; 38718 probe$2.aac = utils; 38719 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; 38720 var MP2T_PACKET_LENGTH = 188, 38721 // bytes 38722 SYNC_BYTE = 0x47; 38723 /** 38724 * walks through segment data looking for pat and pmt packets to parse out 38725 * program map table information 38726 */ 38727 38728 var parsePsi_ = function parsePsi_(bytes, pmt) { 38729 var startIndex = 0, 38730 endIndex = MP2T_PACKET_LENGTH, 38731 packet, 38732 type; 38733 38734 while (endIndex < bytes.byteLength) { 38735 // Look for a pair of start and end sync bytes in the data.. 38736 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 38737 // We found a packet 38738 packet = bytes.subarray(startIndex, endIndex); 38739 type = probe$2.ts.parseType(packet, pmt.pid); 38740 38741 switch (type) { 38742 case 'pat': 38743 if (!pmt.pid) { 38744 pmt.pid = probe$2.ts.parsePat(packet); 38745 } 38746 38747 break; 38748 38749 case 'pmt': 38750 if (!pmt.table) { 38751 pmt.table = probe$2.ts.parsePmt(packet); 38752 } 38753 38754 break; 38755 } // Found the pat and pmt, we can stop walking the segment 38756 38757 38758 if (pmt.pid && pmt.table) { 38759 return; 38760 } 38761 38762 startIndex += MP2T_PACKET_LENGTH; 38763 endIndex += MP2T_PACKET_LENGTH; 38764 continue; 38765 } // If we get here, we have somehow become de-synchronized and we need to step 38766 // forward one byte at a time until we find a pair of sync bytes that denote 38767 // a packet 38768 38769 38770 startIndex++; 38771 endIndex++; 38772 } 38773 }; 38774 /** 38775 * walks through the segment data from the start and end to get timing information 38776 * for the first and last audio pes packets 38777 */ 38778 38779 38780 var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) { 38781 var startIndex = 0, 38782 endIndex = MP2T_PACKET_LENGTH, 38783 packet, 38784 type, 38785 pesType, 38786 pusi, 38787 parsed; 38788 var endLoop = false; // Start walking from start of segment to get first audio packet 38789 38790 while (endIndex <= bytes.byteLength) { 38791 // Look for a pair of start and end sync bytes in the data.. 38792 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 38793 // We found a packet 38794 packet = bytes.subarray(startIndex, endIndex); 38795 type = probe$2.ts.parseType(packet, pmt.pid); 38796 38797 switch (type) { 38798 case 'pes': 38799 pesType = probe$2.ts.parsePesType(packet, pmt.table); 38800 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 38801 38802 if (pesType === 'audio' && pusi) { 38803 parsed = probe$2.ts.parsePesTime(packet); 38804 38805 if (parsed) { 38806 parsed.type = 'audio'; 38807 result.audio.push(parsed); 38808 endLoop = true; 38809 } 38810 } 38811 38812 break; 38813 } 38814 38815 if (endLoop) { 38816 break; 38817 } 38818 38819 startIndex += MP2T_PACKET_LENGTH; 38820 endIndex += MP2T_PACKET_LENGTH; 38821 continue; 38822 } // If we get here, we have somehow become de-synchronized and we need to step 38823 // forward one byte at a time until we find a pair of sync bytes that denote 38824 // a packet 38825 38826 38827 startIndex++; 38828 endIndex++; 38829 } // Start walking from end of segment to get last audio packet 38830 38831 38832 endIndex = bytes.byteLength; 38833 startIndex = endIndex - MP2T_PACKET_LENGTH; 38834 endLoop = false; 38835 38836 while (startIndex >= 0) { 38837 // Look for a pair of start and end sync bytes in the data.. 38838 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 38839 // We found a packet 38840 packet = bytes.subarray(startIndex, endIndex); 38841 type = probe$2.ts.parseType(packet, pmt.pid); 38842 38843 switch (type) { 38844 case 'pes': 38845 pesType = probe$2.ts.parsePesType(packet, pmt.table); 38846 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 38847 38848 if (pesType === 'audio' && pusi) { 38849 parsed = probe$2.ts.parsePesTime(packet); 38850 38851 if (parsed) { 38852 parsed.type = 'audio'; 38853 result.audio.push(parsed); 38854 endLoop = true; 38855 } 38856 } 38857 38858 break; 38859 } 38860 38861 if (endLoop) { 38862 break; 38863 } 38864 38865 startIndex -= MP2T_PACKET_LENGTH; 38866 endIndex -= MP2T_PACKET_LENGTH; 38867 continue; 38868 } // If we get here, we have somehow become de-synchronized and we need to step 38869 // forward one byte at a time until we find a pair of sync bytes that denote 38870 // a packet 38871 38872 38873 startIndex--; 38874 endIndex--; 38875 } 38876 }; 38877 /** 38878 * walks through the segment data from the start and end to get timing information 38879 * for the first and last video pes packets as well as timing information for the first 38880 * key frame. 38881 */ 38882 38883 38884 var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) { 38885 var startIndex = 0, 38886 endIndex = MP2T_PACKET_LENGTH, 38887 packet, 38888 type, 38889 pesType, 38890 pusi, 38891 parsed, 38892 frame, 38893 i, 38894 pes; 38895 var endLoop = false; 38896 var currentFrame = { 38897 data: [], 38898 size: 0 38899 }; // Start walking from start of segment to get first video packet 38900 38901 while (endIndex < bytes.byteLength) { 38902 // Look for a pair of start and end sync bytes in the data.. 38903 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 38904 // We found a packet 38905 packet = bytes.subarray(startIndex, endIndex); 38906 type = probe$2.ts.parseType(packet, pmt.pid); 38907 38908 switch (type) { 38909 case 'pes': 38910 pesType = probe$2.ts.parsePesType(packet, pmt.table); 38911 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 38912 38913 if (pesType === 'video') { 38914 if (pusi && !endLoop) { 38915 parsed = probe$2.ts.parsePesTime(packet); 38916 38917 if (parsed) { 38918 parsed.type = 'video'; 38919 result.video.push(parsed); 38920 endLoop = true; 38921 } 38922 } 38923 38924 if (!result.firstKeyFrame) { 38925 if (pusi) { 38926 if (currentFrame.size !== 0) { 38927 frame = new Uint8Array(currentFrame.size); 38928 i = 0; 38929 38930 while (currentFrame.data.length) { 38931 pes = currentFrame.data.shift(); 38932 frame.set(pes, i); 38933 i += pes.byteLength; 38934 } 38935 38936 if (probe$2.ts.videoPacketContainsKeyFrame(frame)) { 38937 var firstKeyFrame = probe$2.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting 38938 // the keyframe seems to work fine with HLS playback 38939 // and definitely preferable to a crash with TypeError... 38940 38941 if (firstKeyFrame) { 38942 result.firstKeyFrame = firstKeyFrame; 38943 result.firstKeyFrame.type = 'video'; 38944 } else { 38945 // eslint-disable-next-line 38946 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.'); 38947 } 38948 } 38949 38950 currentFrame.size = 0; 38951 } 38952 } 38953 38954 currentFrame.data.push(packet); 38955 currentFrame.size += packet.byteLength; 38956 } 38957 } 38958 38959 break; 38960 } 38961 38962 if (endLoop && result.firstKeyFrame) { 38963 break; 38964 } 38965 38966 startIndex += MP2T_PACKET_LENGTH; 38967 endIndex += MP2T_PACKET_LENGTH; 38968 continue; 38969 } // If we get here, we have somehow become de-synchronized and we need to step 38970 // forward one byte at a time until we find a pair of sync bytes that denote 38971 // a packet 38972 38973 38974 startIndex++; 38975 endIndex++; 38976 } // Start walking from end of segment to get last video packet 38977 38978 38979 endIndex = bytes.byteLength; 38980 startIndex = endIndex - MP2T_PACKET_LENGTH; 38981 endLoop = false; 38982 38983 while (startIndex >= 0) { 38984 // Look for a pair of start and end sync bytes in the data.. 38985 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 38986 // We found a packet 38987 packet = bytes.subarray(startIndex, endIndex); 38988 type = probe$2.ts.parseType(packet, pmt.pid); 38989 38990 switch (type) { 38991 case 'pes': 38992 pesType = probe$2.ts.parsePesType(packet, pmt.table); 38993 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 38994 38995 if (pesType === 'video' && pusi) { 38996 parsed = probe$2.ts.parsePesTime(packet); 38997 38998 if (parsed) { 38999 parsed.type = 'video'; 39000 result.video.push(parsed); 39001 endLoop = true; 39002 } 39003 } 39004 39005 break; 39006 } 39007 39008 if (endLoop) { 39009 break; 39010 } 39011 39012 startIndex -= MP2T_PACKET_LENGTH; 39013 endIndex -= MP2T_PACKET_LENGTH; 39014 continue; 39015 } // If we get here, we have somehow become de-synchronized and we need to step 39016 // forward one byte at a time until we find a pair of sync bytes that denote 39017 // a packet 39018 39019 39020 startIndex--; 39021 endIndex--; 39022 } 39023 }; 39024 /** 39025 * Adjusts the timestamp information for the segment to account for 39026 * rollover and convert to seconds based on pes packet timescale (90khz clock) 39027 */ 39028 39029 39030 var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) { 39031 if (segmentInfo.audio && segmentInfo.audio.length) { 39032 var audioBaseTimestamp = baseTimestamp; 39033 39034 if (typeof audioBaseTimestamp === 'undefined') { 39035 audioBaseTimestamp = segmentInfo.audio[0].dts; 39036 } 39037 39038 segmentInfo.audio.forEach(function (info) { 39039 info.dts = handleRollover$1(info.dts, audioBaseTimestamp); 39040 info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds 39041 39042 info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; 39043 info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; 39044 }); 39045 } 39046 39047 if (segmentInfo.video && segmentInfo.video.length) { 39048 var videoBaseTimestamp = baseTimestamp; 39049 39050 if (typeof videoBaseTimestamp === 'undefined') { 39051 videoBaseTimestamp = segmentInfo.video[0].dts; 39052 } 39053 39054 segmentInfo.video.forEach(function (info) { 39055 info.dts = handleRollover$1(info.dts, videoBaseTimestamp); 39056 info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds 39057 39058 info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; 39059 info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; 39060 }); 39061 39062 if (segmentInfo.firstKeyFrame) { 39063 var frame = segmentInfo.firstKeyFrame; 39064 frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp); 39065 frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds 39066 39067 frame.dtsTime = frame.dts / ONE_SECOND_IN_TS$1; 39068 frame.ptsTime = frame.dts / ONE_SECOND_IN_TS$1; 39069 } 39070 } 39071 }; 39072 /** 39073 * inspects the aac data stream for start and end time information 39074 */ 39075 39076 39077 var inspectAac_ = function inspectAac_(bytes) { 39078 var endLoop = false, 39079 audioCount = 0, 39080 sampleRate = null, 39081 timestamp = null, 39082 frameSize = 0, 39083 byteIndex = 0, 39084 packet; 39085 39086 while (bytes.length - byteIndex >= 3) { 39087 var type = probe$2.aac.parseType(bytes, byteIndex); 39088 39089 switch (type) { 39090 case 'timed-metadata': 39091 // Exit early because we don't have enough to parse 39092 // the ID3 tag header 39093 if (bytes.length - byteIndex < 10) { 39094 endLoop = true; 39095 break; 39096 } 39097 39098 frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 39099 // to emit a full packet 39100 39101 if (frameSize > bytes.length) { 39102 endLoop = true; 39103 break; 39104 } 39105 39106 if (timestamp === null) { 39107 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 39108 timestamp = probe$2.aac.parseAacTimestamp(packet); 39109 } 39110 39111 byteIndex += frameSize; 39112 break; 39113 39114 case 'audio': 39115 // Exit early because we don't have enough to parse 39116 // the ADTS frame header 39117 if (bytes.length - byteIndex < 7) { 39118 endLoop = true; 39119 break; 39120 } 39121 39122 frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 39123 // to emit a full packet 39124 39125 if (frameSize > bytes.length) { 39126 endLoop = true; 39127 break; 39128 } 39129 39130 if (sampleRate === null) { 39131 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 39132 sampleRate = probe$2.aac.parseSampleRate(packet); 39133 } 39134 39135 audioCount++; 39136 byteIndex += frameSize; 39137 break; 39138 39139 default: 39140 byteIndex++; 39141 break; 39142 } 39143 39144 if (endLoop) { 39145 return null; 39146 } 39147 } 39148 39149 if (sampleRate === null || timestamp === null) { 39150 return null; 39151 } 39152 39153 var audioTimescale = ONE_SECOND_IN_TS$1 / sampleRate; 39154 var result = { 39155 audio: [{ 39156 type: 'audio', 39157 dts: timestamp,
vendor: 77,433 bytes, lines 39158-41467
39158 pts: timestamp 39159 }, { 39160 type: 'audio', 39161 dts: timestamp + audioCount * 1024 * audioTimescale, 39162 pts: timestamp + audioCount * 1024 * audioTimescale 39163 }] 39164 }; 39165 return result; 39166 }; 39167 /** 39168 * inspects the transport stream segment data for start and end time information 39169 * of the audio and video tracks (when present) as well as the first key frame's 39170 * start time. 39171 */ 39172 39173 39174 var inspectTs_ = function inspectTs_(bytes) { 39175 var pmt = { 39176 pid: null, 39177 table: null 39178 }; 39179 var result = {}; 39180 parsePsi_(bytes, pmt); 39181 39182 for (var pid in pmt.table) { 39183 if (pmt.table.hasOwnProperty(pid)) { 39184 var type = pmt.table[pid]; 39185 39186 switch (type) { 39187 case streamTypes.H264_STREAM_TYPE: 39188 result.video = []; 39189 parseVideoPes_(bytes, pmt, result); 39190 39191 if (result.video.length === 0) { 39192 delete result.video; 39193 } 39194 39195 break; 39196 39197 case streamTypes.ADTS_STREAM_TYPE: 39198 result.audio = []; 39199 parseAudioPes_(bytes, pmt, result); 39200 39201 if (result.audio.length === 0) { 39202 delete result.audio; 39203 } 39204 39205 break; 39206 } 39207 } 39208 } 39209 39210 return result; 39211 }; 39212 /** 39213 * Inspects segment byte data and returns an object with start and end timing information 39214 * 39215 * @param {Uint8Array} bytes The segment byte data 39216 * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame 39217 * timestamps for rollover. This value must be in 90khz clock. 39218 * @return {Object} Object containing start and end frame timing info of segment. 39219 */ 39220 39221 39222 var inspect = function inspect(bytes, baseTimestamp) { 39223 var isAacData = probe$2.aac.isLikelyAacData(bytes); 39224 var result; 39225 39226 if (isAacData) { 39227 result = inspectAac_(bytes); 39228 } else { 39229 result = inspectTs_(bytes); 39230 } 39231 39232 if (!result || !result.audio && !result.video) { 39233 return null; 39234 } 39235 39236 adjustTimestamp_(result, baseTimestamp); 39237 return result; 39238 }; 39239 39240 var tsInspector = { 39241 inspect: inspect, 39242 parseAudioPes_: parseAudioPes_ 39243 }; 39244 39245 /* 39246 * pkcs7.pad 39247 * https://github.com/brightcove/pkcs7 39248 * 39249 * Copyright (c) 2014 Brightcove 39250 * Licensed under the apache2 license. 39251 */ 39252 /** 39253 * Returns the subarray of a Uint8Array without PKCS#7 padding. 39254 * @param padded {Uint8Array} unencrypted bytes that have been padded 39255 * @return {Uint8Array} the unpadded bytes 39256 * @see http://tools.ietf.org/html/rfc5652 39257 */ 39258 39259 function unpad(padded) { 39260 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 39261 } 39262 39263 var classCallCheck = function classCallCheck(instance, Constructor) { 39264 if (!(instance instanceof Constructor)) { 39265 throw new TypeError("Cannot call a class as a function"); 39266 } 39267 }; 39268 39269 var createClass = function () { 39270 function defineProperties(target, props) { 39271 for (var i = 0; i < props.length; i++) { 39272 var descriptor = props[i]; 39273 descriptor.enumerable = descriptor.enumerable || false; 39274 descriptor.configurable = true; 39275 if ("value" in descriptor) descriptor.writable = true; 39276 Object.defineProperty(target, descriptor.key, descriptor); 39277 } 39278 } 39279 39280 return function (Constructor, protoProps, staticProps) { 39281 if (protoProps) defineProperties(Constructor.prototype, protoProps); 39282 if (staticProps) defineProperties(Constructor, staticProps); 39283 return Constructor; 39284 }; 39285 }(); 39286 39287 var inherits$1 = function inherits(subClass, superClass) { 39288 if (typeof superClass !== "function" && superClass !== null) { 39289 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 39290 } 39291 39292 subClass.prototype = Object.create(superClass && superClass.prototype, { 39293 constructor: { 39294 value: subClass, 39295 enumerable: false, 39296 writable: true, 39297 configurable: true 39298 } 39299 }); 39300 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 39301 }; 39302 39303 var possibleConstructorReturn = function possibleConstructorReturn(self, call) { 39304 if (!self) { 39305 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 39306 } 39307 39308 return call && (typeof call === "object" || typeof call === "function") ? call : self; 39309 }; 39310 /** 39311 * @file aes.js 39312 * 39313 * This file contains an adaptation of the AES decryption algorithm 39314 * from the Standford Javascript Cryptography Library. That work is 39315 * covered by the following copyright and permissions notice: 39316 * 39317 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 39318 * All rights reserved. 39319 * 39320 * Redistribution and use in source and binary forms, with or without 39321 * modification, are permitted provided that the following conditions are 39322 * met: 39323 * 39324 * 1. Redistributions of source code must retain the above copyright 39325 * notice, this list of conditions and the following disclaimer. 39326 * 39327 * 2. Redistributions in binary form must reproduce the above 39328 * copyright notice, this list of conditions and the following 39329 * disclaimer in the documentation and/or other materials provided 39330 * with the distribution. 39331 * 39332 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 39333 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 39334 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 39335 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 39336 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 39337 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39338 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 39339 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 39340 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 39341 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 39342 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39343 * 39344 * The views and conclusions contained in the software and documentation 39345 * are those of the authors and should not be interpreted as representing 39346 * official policies, either expressed or implied, of the authors. 39347 */ 39348 39349 /** 39350 * Expand the S-box tables. 39351 * 39352 * @private 39353 */ 39354 39355 39356 var precompute = function precompute() { 39357 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 39358 var encTable = tables[0]; 39359 var decTable = tables[1]; 39360 var sbox = encTable[4]; 39361 var sboxInv = decTable[4]; 39362 var i = void 0; 39363 var x = void 0; 39364 var xInv = void 0; 39365 var d = []; 39366 var th = []; 39367 var x2 = void 0; 39368 var x4 = void 0; 39369 var x8 = void 0; 39370 var s = void 0; 39371 var tEnc = void 0; 39372 var tDec = void 0; // Compute double and third tables 39373 39374 for (i = 0; i < 256; i++) { 39375 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 39376 } 39377 39378 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 39379 // Compute sbox 39380 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 39381 s = s >> 8 ^ s & 255 ^ 99; 39382 sbox[x] = s; 39383 sboxInv[s] = x; // Compute MixColumns 39384 39385 x8 = d[x4 = d[x2 = d[x]]]; 39386 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 39387 tEnc = d[s] * 0x101 ^ s * 0x1010100; 39388 39389 for (i = 0; i < 4; i++) { 39390 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 39391 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 39392 } 39393 } // Compactify. Considerable speedup on Firefox. 39394 39395 39396 for (i = 0; i < 5; i++) { 39397 encTable[i] = encTable[i].slice(0); 39398 decTable[i] = decTable[i].slice(0); 39399 } 39400 39401 return tables; 39402 }; 39403 39404 var aesTables = null; 39405 /** 39406 * Schedule out an AES key for both encryption and decryption. This 39407 * is a low-level class. Use a cipher mode to do bulk encryption. 39408 * 39409 * @class AES 39410 * @param key {Array} The key as an array of 4, 6 or 8 words. 39411 */ 39412 39413 var AES = function () { 39414 function AES(key) { 39415 classCallCheck(this, AES); 39416 /** 39417 * The expanded S-box and inverse S-box tables. These will be computed 39418 * on the client so that we don't have to send them down the wire. 39419 * 39420 * There are two tables, _tables[0] is for encryption and 39421 * _tables[1] is for decryption. 39422 * 39423 * The first 4 sub-tables are the expanded S-box with MixColumns. The 39424 * last (_tables[01][4]) is the S-box itself. 39425 * 39426 * @private 39427 */ 39428 // if we have yet to precompute the S-box tables 39429 // do so now 39430 39431 if (!aesTables) { 39432 aesTables = precompute(); 39433 } // then make a copy of that object for use 39434 39435 39436 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()]]; 39437 var i = void 0; 39438 var j = void 0; 39439 var tmp = void 0; 39440 var encKey = void 0; 39441 var decKey = void 0; 39442 var sbox = this._tables[0][4]; 39443 var decTable = this._tables[1]; 39444 var keyLen = key.length; 39445 var rcon = 1; 39446 39447 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 39448 throw new Error('Invalid aes key size'); 39449 } 39450 39451 encKey = key.slice(0); 39452 decKey = []; 39453 this._key = [encKey, decKey]; // schedule encryption keys 39454 39455 for (i = keyLen; i < 4 * keyLen + 28; i++) { 39456 tmp = encKey[i - 1]; // apply sbox 39457 39458 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 39459 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 39460 39461 if (i % keyLen === 0) { 39462 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 39463 rcon = rcon << 1 ^ (rcon >> 7) * 283; 39464 } 39465 } 39466 39467 encKey[i] = encKey[i - keyLen] ^ tmp; 39468 } // schedule decryption keys 39469 39470 39471 for (j = 0; i; j++, i--) { 39472 tmp = encKey[j & 3 ? i : i - 4]; 39473 39474 if (i <= 4 || j < 4) { 39475 decKey[j] = tmp; 39476 } else { 39477 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 39478 } 39479 } 39480 } 39481 /** 39482 * Decrypt 16 bytes, specified as four 32-bit words. 39483 * 39484 * @param {Number} encrypted0 the first word to decrypt 39485 * @param {Number} encrypted1 the second word to decrypt 39486 * @param {Number} encrypted2 the third word to decrypt 39487 * @param {Number} encrypted3 the fourth word to decrypt 39488 * @param {Int32Array} out the array to write the decrypted words 39489 * into 39490 * @param {Number} offset the offset into the output array to start 39491 * writing results 39492 * @return {Array} The plaintext. 39493 */ 39494 39495 39496 AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 39497 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 39498 39499 var a = encrypted0 ^ key[0]; 39500 var b = encrypted3 ^ key[1]; 39501 var c = encrypted2 ^ key[2]; 39502 var d = encrypted1 ^ key[3]; 39503 var a2 = void 0; 39504 var b2 = void 0; 39505 var c2 = void 0; // key.length === 2 ? 39506 39507 var nInnerRounds = key.length / 4 - 2; 39508 var i = void 0; 39509 var kIndex = 4; 39510 var table = this._tables[1]; // load up the tables 39511 39512 var table0 = table[0]; 39513 var table1 = table[1]; 39514 var table2 = table[2]; 39515 var table3 = table[3]; 39516 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 39517 39518 for (i = 0; i < nInnerRounds; i++) { 39519 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 39520 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 39521 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 39522 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 39523 kIndex += 4; 39524 a = a2; 39525 b = b2; 39526 c = c2; 39527 } // Last round. 39528 39529 39530 for (i = 0; i < 4; i++) { 39531 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 39532 a2 = a; 39533 a = b; 39534 b = c; 39535 c = d; 39536 d = a2; 39537 } 39538 }; 39539 39540 return AES; 39541 }(); 39542 /** 39543 * @file stream.js 39544 */ 39545 39546 /** 39547 * A lightweight readable stream implemention that handles event dispatching. 39548 * 39549 * @class Stream 39550 */ 39551 39552 39553 var Stream$2 = function () { 39554 function Stream() { 39555 classCallCheck(this, Stream); 39556 this.listeners = {}; 39557 } 39558 /** 39559 * Add a listener for a specified event type. 39560 * 39561 * @param {String} type the event name 39562 * @param {Function} listener the callback to be invoked when an event of 39563 * the specified type occurs 39564 */ 39565 39566 39567 Stream.prototype.on = function on(type, listener) { 39568 if (!this.listeners[type]) { 39569 this.listeners[type] = []; 39570 } 39571 39572 this.listeners[type].push(listener); 39573 }; 39574 /** 39575 * Remove a listener for a specified event type. 39576 * 39577 * @param {String} type the event name 39578 * @param {Function} listener a function previously registered for this 39579 * type of event through `on` 39580 * @return {Boolean} if we could turn it off or not 39581 */ 39582 39583 39584 Stream.prototype.off = function off(type, listener) { 39585 if (!this.listeners[type]) { 39586 return false; 39587 } 39588 39589 var index = this.listeners[type].indexOf(listener); 39590 this.listeners[type].splice(index, 1); 39591 return index > -1; 39592 }; 39593 /** 39594 * Trigger an event of the specified type on this stream. Any additional 39595 * arguments to this function are passed as parameters to event listeners. 39596 * 39597 * @param {String} type the event name 39598 */ 39599 39600 39601 Stream.prototype.trigger = function trigger(type) { 39602 var callbacks = this.listeners[type]; 39603 39604 if (!callbacks) { 39605 return; 39606 } // Slicing the arguments on every invocation of this method 39607 // can add a significant amount of overhead. Avoid the 39608 // intermediate object creation for the common case of a 39609 // single callback argument 39610 39611 39612 if (arguments.length === 2) { 39613 var length = callbacks.length; 39614 39615 for (var i = 0; i < length; ++i) { 39616 callbacks[i].call(this, arguments[1]); 39617 } 39618 } else { 39619 var args = Array.prototype.slice.call(arguments, 1); 39620 var _length = callbacks.length; 39621 39622 for (var _i = 0; _i < _length; ++_i) { 39623 callbacks[_i].apply(this, args); 39624 } 39625 } 39626 }; 39627 /** 39628 * Destroys the stream and cleans up. 39629 */ 39630 39631 39632 Stream.prototype.dispose = function dispose() { 39633 this.listeners = {}; 39634 }; 39635 /** 39636 * Forwards all `data` events on this stream to the destination stream. The 39637 * destination stream should provide a method `push` to receive the data 39638 * events as they arrive. 39639 * 39640 * @param {Stream} destination the stream that will receive all `data` events 39641 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 39642 */ 39643 39644 39645 Stream.prototype.pipe = function pipe(destination) { 39646 this.on('data', function (data) { 39647 destination.push(data); 39648 }); 39649 }; 39650 39651 return Stream; 39652 }(); 39653 /** 39654 * @file async-stream.js 39655 */ 39656 39657 /** 39658 * A wrapper around the Stream class to use setTiemout 39659 * and run stream "jobs" Asynchronously 39660 * 39661 * @class AsyncStream 39662 * @extends Stream 39663 */ 39664 39665 39666 var AsyncStream = function (_Stream) { 39667 inherits$1(AsyncStream, _Stream); 39668 39669 function AsyncStream() { 39670 classCallCheck(this, AsyncStream); 39671 39672 var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2)); 39673 39674 _this.jobs = []; 39675 _this.delay = 1; 39676 _this.timeout_ = null; 39677 return _this; 39678 } 39679 /** 39680 * process an async job 39681 * 39682 * @private 39683 */ 39684 39685 39686 AsyncStream.prototype.processJob_ = function processJob_() { 39687 this.jobs.shift()(); 39688 39689 if (this.jobs.length) { 39690 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 39691 } else { 39692 this.timeout_ = null; 39693 } 39694 }; 39695 /** 39696 * push a job into the stream 39697 * 39698 * @param {Function} job the job to push into the stream 39699 */ 39700 39701 39702 AsyncStream.prototype.push = function push(job) { 39703 this.jobs.push(job); 39704 39705 if (!this.timeout_) { 39706 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 39707 } 39708 }; 39709 39710 return AsyncStream; 39711 }(Stream$2); 39712 /** 39713 * @file decrypter.js 39714 * 39715 * An asynchronous implementation of AES-128 CBC decryption with 39716 * PKCS#7 padding. 39717 */ 39718 39719 /** 39720 * Convert network-order (big-endian) bytes into their little-endian 39721 * representation. 39722 */ 39723 39724 39725 var ntoh = function ntoh(word) { 39726 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 39727 }; 39728 /** 39729 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 39730 * 39731 * @param {Uint8Array} encrypted the encrypted bytes 39732 * @param {Uint32Array} key the bytes of the decryption key 39733 * @param {Uint32Array} initVector the initialization vector (IV) to 39734 * use for the first round of CBC. 39735 * @return {Uint8Array} the decrypted bytes 39736 * 39737 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 39738 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 39739 * @see https://tools.ietf.org/html/rfc2315 39740 */ 39741 39742 39743 var decrypt = function decrypt(encrypted, key, initVector) { 39744 // word-level access to the encrypted bytes 39745 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 39746 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 39747 39748 var decrypted = new Uint8Array(encrypted.byteLength); 39749 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 39750 // decrypted data 39751 39752 var init0 = void 0; 39753 var init1 = void 0; 39754 var init2 = void 0; 39755 var init3 = void 0; 39756 var encrypted0 = void 0; 39757 var encrypted1 = void 0; 39758 var encrypted2 = void 0; 39759 var encrypted3 = void 0; // iteration variable 39760 39761 var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the 39762 // passed-in reference and easier access 39763 39764 init0 = initVector[0]; 39765 init1 = initVector[1]; 39766 init2 = initVector[2]; 39767 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 39768 // to each decrypted block 39769 39770 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 39771 // convert big-endian (network order) words into little-endian 39772 // (javascript order) 39773 encrypted0 = ntoh(encrypted32[wordIx]); 39774 encrypted1 = ntoh(encrypted32[wordIx + 1]); 39775 encrypted2 = ntoh(encrypted32[wordIx + 2]); 39776 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 39777 39778 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 39779 // plaintext 39780 39781 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 39782 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 39783 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 39784 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 39785 39786 init0 = encrypted0; 39787 init1 = encrypted1; 39788 init2 = encrypted2; 39789 init3 = encrypted3; 39790 } 39791 39792 return decrypted; 39793 }; 39794 /** 39795 * The `Decrypter` class that manages decryption of AES 39796 * data through `AsyncStream` objects and the `decrypt` 39797 * function 39798 * 39799 * @param {Uint8Array} encrypted the encrypted bytes 39800 * @param {Uint32Array} key the bytes of the decryption key 39801 * @param {Uint32Array} initVector the initialization vector (IV) to 39802 * @param {Function} done the function to run when done 39803 * @class Decrypter 39804 */ 39805 39806 39807 var Decrypter = function () { 39808 function Decrypter(encrypted, key, initVector, done) { 39809 classCallCheck(this, Decrypter); 39810 var step = Decrypter.STEP; 39811 var encrypted32 = new Int32Array(encrypted.buffer); 39812 var decrypted = new Uint8Array(encrypted.byteLength); 39813 var i = 0; 39814 this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously 39815 39816 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 39817 39818 for (i = step; i < encrypted32.length; i += step) { 39819 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 39820 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 39821 } // invoke the done() callback when everything is finished 39822 39823 39824 this.asyncStream_.push(function () { 39825 // remove pkcs#7 padding from the decrypted bytes 39826 done(null, unpad(decrypted)); 39827 }); 39828 } 39829 /** 39830 * a getter for step the maximum number of bytes to process at one time 39831 * 39832 * @return {Number} the value of step 32000 39833 */ 39834 39835 /** 39836 * @private 39837 */ 39838 39839 39840 Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 39841 return function () { 39842 var bytes = decrypt(encrypted, key, initVector); 39843 decrypted.set(bytes, encrypted.byteOffset); 39844 }; 39845 }; 39846 39847 createClass(Decrypter, null, [{ 39848 key: 'STEP', 39849 get: function get$$1() { 39850 // 4 * 8000; 39851 return 32000; 39852 } 39853 }]); 39854 return Decrypter; 39855 }(); 39856 39857 /** 39858 * @videojs/http-streaming 39859 * @version 1.13.2 39860 * @copyright 2020 Brightcove, Inc 39861 * @license Apache-2.0 39862 */ 39863 /** 39864 * @file resolve-url.js - Handling how URLs are resolved and manipulated 39865 */ 39866 39867 var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) { 39868 // return early if we don't need to resolve 39869 if (/^[a-z]+:/i.test(relativeURL)) { 39870 return relativeURL; 39871 } // if the base URL is relative then combine with the current location 39872 39873 39874 if (!/\/\//i.test(baseURL)) { 39875 baseURL = urlToolkit.buildAbsoluteURL(window$3.location.href, baseURL); 39876 } 39877 39878 return urlToolkit.buildAbsoluteURL(baseURL, relativeURL); 39879 }; 39880 /** 39881 * Checks whether xhr request was redirected and returns correct url depending 39882 * on `handleManifestRedirects` option 39883 * 39884 * @api private 39885 * 39886 * @param {String} url - an url being requested 39887 * @param {XMLHttpRequest} req - xhr request result 39888 * 39889 * @return {String} 39890 */ 39891 39892 39893 var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) { 39894 // To understand how the responseURL below is set and generated: 39895 // - https://fetch.spec.whatwg.org/#concept-response-url 39896 // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling 39897 if (handleManifestRedirect && req.responseURL && url !== req.responseURL) { 39898 return req.responseURL; 39899 } 39900 39901 return url; 39902 }; 39903 39904 var classCallCheck$1 = function classCallCheck(instance, Constructor) { 39905 if (!(instance instanceof Constructor)) { 39906 throw new TypeError("Cannot call a class as a function"); 39907 } 39908 }; 39909 39910 var createClass$1 = function () { 39911 function defineProperties(target, props) { 39912 for (var i = 0; i < props.length; i++) { 39913 var descriptor = props[i]; 39914 descriptor.enumerable = descriptor.enumerable || false; 39915 descriptor.configurable = true; 39916 if ("value" in descriptor) descriptor.writable = true; 39917 Object.defineProperty(target, descriptor.key, descriptor); 39918 } 39919 } 39920 39921 return function (Constructor, protoProps, staticProps) { 39922 if (protoProps) defineProperties(Constructor.prototype, protoProps); 39923 if (staticProps) defineProperties(Constructor, staticProps); 39924 return Constructor; 39925 }; 39926 }(); 39927 39928 var get$1 = function get(object, property, receiver) { 39929 if (object === null) object = Function.prototype; 39930 var desc = Object.getOwnPropertyDescriptor(object, property); 39931 39932 if (desc === undefined) { 39933 var parent = Object.getPrototypeOf(object); 39934 39935 if (parent === null) { 39936 return undefined; 39937 } else { 39938 return get(parent, property, receiver); 39939 } 39940 } else if ("value" in desc) { 39941 return desc.value; 39942 } else { 39943 var getter = desc.get; 39944 39945 if (getter === undefined) { 39946 return undefined; 39947 } 39948 39949 return getter.call(receiver); 39950 } 39951 }; 39952 39953 var inherits$2 = function inherits(subClass, superClass) { 39954 if (typeof superClass !== "function" && superClass !== null) { 39955 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 39956 } 39957 39958 subClass.prototype = Object.create(superClass && superClass.prototype, { 39959 constructor: { 39960 value: subClass, 39961 enumerable: false, 39962 writable: true, 39963 configurable: true 39964 } 39965 }); 39966 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 39967 }; 39968 39969 var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) { 39970 if (!self) { 39971 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 39972 } 39973 39974 return call && (typeof call === "object" || typeof call === "function") ? call : self; 39975 }; 39976 39977 var slicedToArray = function () { 39978 function sliceIterator(arr, i) { 39979 var _arr = []; 39980 var _n = true; 39981 var _d = false; 39982 var _e = undefined; 39983 39984 try { 39985 for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 39986 _arr.push(_s.value); 39987 39988 if (i && _arr.length === i) break; 39989 } 39990 } catch (err) { 39991 _d = true; 39992 _e = err; 39993 } finally { 39994 try { 39995 if (!_n && _i["return"]) _i["return"](); 39996 } finally { 39997 if (_d) throw _e; 39998 } 39999 } 40000 40001 return _arr; 40002 } 40003 40004 return function (arr, i) { 40005 if (Array.isArray(arr)) { 40006 return arr; 40007 } else if (Symbol.iterator in Object(arr)) { 40008 return sliceIterator(arr, i); 40009 } else { 40010 throw new TypeError("Invalid attempt to destructure non-iterable instance"); 40011 } 40012 }; 40013 }(); 40014 /** 40015 * @file playlist-loader.js 40016 * 40017 * A state machine that manages the loading, caching, and updating of 40018 * M3U8 playlists. 40019 * 40020 */ 40021 40022 40023 var mergeOptions$1 = videojs$1.mergeOptions, 40024 EventTarget$1 = videojs$1.EventTarget, 40025 log$1 = videojs$1.log; 40026 /** 40027 * Loops through all supported media groups in master and calls the provided 40028 * callback for each group 40029 * 40030 * @param {Object} master 40031 * The parsed master manifest object 40032 * @param {Function} callback 40033 * Callback to call for each media group 40034 */ 40035 40036 var forEachMediaGroup = function forEachMediaGroup(master, callback) { 40037 ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) { 40038 for (var groupKey in master.mediaGroups[mediaType]) { 40039 for (var labelKey in master.mediaGroups[mediaType][groupKey]) { 40040 var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey]; 40041 callback(mediaProperties, mediaType, groupKey, labelKey); 40042 } 40043 } 40044 }); 40045 }; 40046 /** 40047 * Returns a new array of segments that is the result of merging 40048 * properties from an older list of segments onto an updated 40049 * list. No properties on the updated playlist will be overridden. 40050 * 40051 * @param {Array} original the outdated list of segments 40052 * @param {Array} update the updated list of segments 40053 * @param {Number=} offset the index of the first update 40054 * segment in the original segment list. For non-live playlists, 40055 * this should always be zero and does not need to be 40056 * specified. For live playlists, it should be the difference 40057 * between the media sequence numbers in the original and updated 40058 * playlists. 40059 * @return a list of merged segment objects 40060 */ 40061 40062 40063 var updateSegments = function updateSegments(original, update, offset) { 40064 var result = update.slice(); 40065 offset = offset || 0; 40066 var length = Math.min(original.length, update.length + offset); 40067 40068 for (var i = offset; i < length; i++) { 40069 result[i - offset] = mergeOptions$1(original[i], result[i - offset]); 40070 } 40071 40072 return result; 40073 }; 40074 40075 var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) { 40076 if (!segment.resolvedUri) { 40077 segment.resolvedUri = resolveUrl$1(baseUri, segment.uri); 40078 } 40079 40080 if (segment.key && !segment.key.resolvedUri) { 40081 segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri); 40082 } 40083 40084 if (segment.map && !segment.map.resolvedUri) { 40085 segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri); 40086 } 40087 }; 40088 /** 40089 * Returns a new master playlist that is the result of merging an 40090 * updated media playlist into the original version. If the 40091 * updated media playlist does not match any of the playlist 40092 * entries in the original master playlist, null is returned. 40093 * 40094 * @param {Object} master a parsed master M3U8 object 40095 * @param {Object} media a parsed media M3U8 object 40096 * @return {Object} a new object that represents the original 40097 * master playlist with the updated media playlist merged in, or 40098 * null if the merge produced no change. 40099 */ 40100 40101 40102 var updateMaster = function updateMaster(master, media) { 40103 var result = mergeOptions$1(master, {}); 40104 var playlist = result.playlists[media.id]; 40105 40106 if (!playlist) { 40107 return null; 40108 } // consider the playlist unchanged if the number of segments is equal, the media 40109 // sequence number is unchanged, and this playlist hasn't become the end of the playlist 40110 40111 40112 if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) { 40113 return null; 40114 } 40115 40116 var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists 40117 40118 if (playlist.segments) { 40119 mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); 40120 } // resolve any segment URIs to prevent us from having to do it later 40121 40122 40123 mergedPlaylist.segments.forEach(function (segment) { 40124 resolveSegmentUris(segment, mergedPlaylist.resolvedUri); 40125 }); // TODO Right now in the playlists array there are two references to each playlist, one 40126 // that is referenced by index, and one by URI. The index reference may no longer be 40127 // necessary. 40128 40129 for (var i = 0; i < result.playlists.length; i++) { 40130 if (result.playlists[i].id === media.id) { 40131 result.playlists[i] = mergedPlaylist; 40132 } 40133 } 40134 40135 result.playlists[media.id] = mergedPlaylist; // URI reference added for backwards compatibility 40136 40137 result.playlists[media.uri] = mergedPlaylist; 40138 return result; 40139 }; 40140 40141 var createPlaylistID = function createPlaylistID(index, uri) { 40142 return index + '-' + uri; 40143 }; 40144 40145 var setupMediaPlaylists = function setupMediaPlaylists(master) { 40146 // setup by-URI lookups and resolve media playlist URIs 40147 var i = master.playlists.length; 40148 40149 while (i--) { 40150 var playlist = master.playlists[i]; 40151 playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri); 40152 playlist.id = createPlaylistID(i, playlist.uri); 40153 master.playlists[playlist.id] = playlist; // URI reference added for backwards compatibility 40154 40155 master.playlists[playlist.uri] = playlist; 40156 40157 if (!playlist.attributes) { 40158 // Although the spec states an #EXT-X-STREAM-INF tag MUST have a 40159 // BANDWIDTH attribute, we can play the stream without it. This means a poorly 40160 // formatted master playlist may not have an attribute list. An attributes 40161 // property is added here to prevent undefined references when we encounter 40162 // this scenario. 40163 playlist.attributes = {}; 40164 log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.'); 40165 } 40166 } 40167 }; 40168 40169 var resolveMediaGroupUris = function resolveMediaGroupUris(master) { 40170 forEachMediaGroup(master, function (properties) { 40171 if (properties.uri) { 40172 properties.resolvedUri = resolveUrl$1(master.uri, properties.uri); 40173 } 40174 }); 40175 }; 40176 /** 40177 * Calculates the time to wait before refreshing a live playlist 40178 * 40179 * @param {Object} media 40180 * The current media 40181 * @param {Boolean} update 40182 * True if there were any updates from the last refresh, false otherwise 40183 * @return {Number} 40184 * The time in ms to wait before refreshing the live playlist 40185 */ 40186 40187 40188 var refreshDelay = function refreshDelay(media, update) { 40189 var lastSegment = media.segments[media.segments.length - 1]; 40190 var delay = void 0; 40191 40192 if (update && lastSegment && lastSegment.duration) { 40193 delay = lastSegment.duration * 1000; 40194 } else { 40195 // if the playlist is unchanged since the last reload or last segment duration 40196 // cannot be determined, try again after half the target duration 40197 delay = (media.targetDuration || 10) * 500; 40198 } 40199 40200 return delay; 40201 }; 40202 /** 40203 * Load a playlist from a remote location 40204 * 40205 * @class PlaylistLoader 40206 * @extends Stream 40207 * @param {String} srcUrl the url to start with 40208 * @param {Boolean} withCredentials the withCredentials xhr option 40209 * @constructor 40210 */ 40211 40212 40213 var PlaylistLoader = function (_EventTarget) { 40214 inherits$2(PlaylistLoader, _EventTarget); 40215 40216 function PlaylistLoader(srcUrl, hls) { 40217 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 40218 classCallCheck$1(this, PlaylistLoader); 40219 40220 var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this)); 40221 40222 var _options$withCredenti = options.withCredentials, 40223 withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti, 40224 _options$handleManife = options.handleManifestRedirects, 40225 handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife; 40226 _this.srcUrl = srcUrl; 40227 _this.hls_ = hls; 40228 _this.withCredentials = withCredentials; 40229 _this.handleManifestRedirects = handleManifestRedirects; 40230 var hlsOptions = hls.options_; 40231 _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || []; 40232 _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || []; 40233 40234 if (!_this.srcUrl) { 40235 throw new Error('A non-empty playlist URL is required'); 40236 } // initialize the loader state 40237 40238 40239 _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout 40240 40241 _this.on('mediaupdatetimeout', function () { 40242 if (_this.state !== 'HAVE_METADATA') { 40243 // only refresh the media playlist if no other activity is going on 40244 return; 40245 } 40246 40247 _this.state = 'HAVE_CURRENT_METADATA'; 40248 _this.request = _this.hls_.xhr({ 40249 uri: resolveUrl$1(_this.master.uri, _this.media().uri), 40250 withCredentials: _this.withCredentials 40251 }, function (error, req) { 40252 // disposed 40253 if (!_this.request) { 40254 return; 40255 } 40256 40257 if (error) { 40258 return _this.playlistRequestError(_this.request, _this.media(), 'HAVE_METADATA'); 40259 } 40260 40261 _this.haveMetadata(_this.request, _this.media().uri, _this.media().id); 40262 }); 40263 }); 40264 40265 return _this; 40266 } 40267 40268 createClass$1(PlaylistLoader, [{ 40269 key: 'playlistRequestError', 40270 value: function playlistRequestError(xhr, playlist, startingState) { 40271 var uri = playlist.uri, 40272 id = playlist.id; // any in-flight request is now finished 40273 40274 this.request = null; 40275 40276 if (startingState) { 40277 this.state = startingState; 40278 } 40279 40280 this.error = { 40281 playlist: this.master.playlists[id], 40282 status: xhr.status, 40283 message: 'HLS playlist request error at URL: ' + uri + '.', 40284 responseText: xhr.responseText, 40285 code: xhr.status >= 500 ? 4 : 2 40286 }; 40287 this.trigger('error'); 40288 } // update the playlist loader's state in response to a new or 40289 // updated playlist. 40290 40291 }, { 40292 key: 'haveMetadata', 40293 value: function haveMetadata(xhr, url, id) { 40294 var _this2 = this; // any in-flight request is now finished 40295 40296 40297 this.request = null; 40298 this.state = 'HAVE_METADATA'; 40299 var parser = new Parser(); // adding custom tag parsers 40300 40301 this.customTagParsers.forEach(function (customParser) { 40302 return parser.addParser(customParser); 40303 }); // adding custom tag mappers 40304 40305 this.customTagMappers.forEach(function (mapper) { 40306 return parser.addTagMapper(mapper); 40307 }); 40308 parser.push(xhr.responseText); 40309 parser.end(); 40310 parser.manifest.uri = url; 40311 parser.manifest.id = id; // m3u8-parser does not attach an attributes property to media playlists so make 40312 // sure that the property is attached to avoid undefined reference errors 40313 40314 parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master 40315 40316 var update = updateMaster(this.master, parser.manifest); 40317 this.targetDuration = parser.manifest.targetDuration; 40318 40319 if (update) { 40320 this.master = update; 40321 this.media_ = this.master.playlists[id]; 40322 } else { 40323 this.trigger('playlistunchanged'); 40324 } // refresh live playlists after a target duration passes 40325 40326 40327 if (!this.media().endList) { 40328 window$3.clearTimeout(this.mediaUpdateTimeout); 40329 this.mediaUpdateTimeout = window$3.setTimeout(function () { 40330 _this2.trigger('mediaupdatetimeout'); 40331 }, refreshDelay(this.media(), !!update)); 40332 } 40333 40334 this.trigger('loadedplaylist'); 40335 } 40336 /** 40337 * Abort any outstanding work and clean up. 40338 */ 40339 40340 }, { 40341 key: 'dispose', 40342 value: function dispose() { 40343 this.trigger('dispose'); 40344 this.stopRequest(); 40345 window$3.clearTimeout(this.mediaUpdateTimeout); 40346 window$3.clearTimeout(this.finalRenditionTimeout); 40347 this.off(); 40348 } 40349 }, { 40350 key: 'stopRequest', 40351 value: function stopRequest() { 40352 if (this.request) { 40353 var oldRequest = this.request; 40354 this.request = null; 40355 oldRequest.onreadystatechange = null; 40356 oldRequest.abort(); 40357 } 40358 } 40359 /** 40360 * When called without any arguments, returns the currently 40361 * active media playlist. When called with a single argument, 40362 * triggers the playlist loader to asynchronously switch to the 40363 * specified media playlist. Calling this method while the 40364 * loader is in the HAVE_NOTHING causes an error to be emitted 40365 * but otherwise has no effect. 40366 * 40367 * @param {Object=} playlist the parsed media playlist 40368 * object to switch to 40369 * @param {Boolean=} is this the last available playlist 40370 * 40371 * @return {Playlist} the current loaded media 40372 */ 40373 40374 }, { 40375 key: 'media', 40376 value: function media(playlist, isFinalRendition) { 40377 var _this3 = this; // getter 40378 40379 40380 if (!playlist) { 40381 return this.media_; 40382 } // setter 40383 40384 40385 if (this.state === 'HAVE_NOTHING') { 40386 throw new Error('Cannot switch media playlist from ' + this.state); 40387 } // find the playlist object if the target playlist has been 40388 // specified by URI 40389 40390 40391 if (typeof playlist === 'string') { 40392 if (!this.master.playlists[playlist]) { 40393 throw new Error('Unknown playlist URI: ' + playlist); 40394 } 40395 40396 playlist = this.master.playlists[playlist]; 40397 } 40398 40399 window$3.clearTimeout(this.finalRenditionTimeout); 40400 40401 if (isFinalRendition) { 40402 var delay = playlist.targetDuration / 2 * 1000 || 5 * 1000; 40403 this.finalRenditionTimeout = window$3.setTimeout(this.media.bind(this, playlist, false), delay); 40404 return; 40405 } 40406 40407 var startingState = this.state; 40408 var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to fully loaded playlists immediately 40409 40410 if (this.master.playlists[playlist.id].endList) { 40411 // abort outstanding playlist requests 40412 if (this.request) { 40413 this.request.onreadystatechange = null; 40414 this.request.abort(); 40415 this.request = null; 40416 } 40417 40418 this.state = 'HAVE_METADATA'; 40419 this.media_ = playlist; // trigger media change if the active media has been updated 40420 40421 if (mediaChange) { 40422 this.trigger('mediachanging'); 40423 this.trigger('mediachange'); 40424 } 40425 40426 return; 40427 } // switching to the active playlist is a no-op 40428 40429 40430 if (!mediaChange) { 40431 return; 40432 } 40433 40434 this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request 40435 40436 if (this.request) { 40437 if (playlist.resolvedUri === this.request.url) { 40438 // requesting to switch to the same playlist multiple times 40439 // has no effect after the first 40440 return; 40441 } 40442 40443 this.request.onreadystatechange = null; 40444 this.request.abort(); 40445 this.request = null; 40446 } // request the new playlist 40447 40448 40449 if (this.media_) { 40450 this.trigger('mediachanging'); 40451 } 40452 40453 this.request = this.hls_.xhr({ 40454 uri: playlist.resolvedUri, 40455 withCredentials: this.withCredentials 40456 }, function (error, req) { 40457 // disposed 40458 if (!_this3.request) { 40459 return; 40460 } 40461 40462 playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req); 40463 40464 if (error) { 40465 return _this3.playlistRequestError(_this3.request, playlist, startingState); 40466 } 40467 40468 _this3.haveMetadata(req, playlist.uri, playlist.id); // fire loadedmetadata the first time a media playlist is loaded 40469 40470 40471 if (startingState === 'HAVE_MASTER') { 40472 _this3.trigger('loadedmetadata'); 40473 } else { 40474 _this3.trigger('mediachange'); 40475 } 40476 }); 40477 } 40478 /** 40479 * pause loading of the playlist 40480 */ 40481 40482 }, { 40483 key: 'pause', 40484 value: function pause() { 40485 this.stopRequest(); 40486 window$3.clearTimeout(this.mediaUpdateTimeout); 40487 40488 if (this.state === 'HAVE_NOTHING') { 40489 // If we pause the loader before any data has been retrieved, its as if we never 40490 // started, so reset to an unstarted state. 40491 this.started = false; 40492 } // Need to restore state now that no activity is happening 40493 40494 40495 if (this.state === 'SWITCHING_MEDIA') { 40496 // if the loader was in the process of switching media, it should either return to 40497 // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media 40498 // playlist yet. This is determined by the existence of loader.media_ 40499 if (this.media_) { 40500 this.state = 'HAVE_METADATA'; 40501 } else { 40502 this.state = 'HAVE_MASTER'; 40503 } 40504 } else if (this.state === 'HAVE_CURRENT_METADATA') { 40505 this.state = 'HAVE_METADATA'; 40506 } 40507 } 40508 /** 40509 * start loading of the playlist 40510 */ 40511 40512 }, { 40513 key: 'load', 40514 value: function load(isFinalRendition) { 40515 var _this4 = this; 40516 40517 window$3.clearTimeout(this.mediaUpdateTimeout); 40518 var media = this.media(); 40519 40520 if (isFinalRendition) { 40521 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 40522 this.mediaUpdateTimeout = window$3.setTimeout(function () { 40523 return _this4.load(); 40524 }, delay); 40525 return; 40526 } 40527 40528 if (!this.started) { 40529 this.start(); 40530 return; 40531 } 40532 40533 if (media && !media.endList) { 40534 this.trigger('mediaupdatetimeout'); 40535 } else { 40536 this.trigger('loadedplaylist'); 40537 } 40538 } 40539 /** 40540 * start loading of the playlist 40541 */ 40542 40543 }, { 40544 key: 'start', 40545 value: function start() { 40546 var _this5 = this; 40547 40548 this.started = true; // request the specified URL 40549 40550 this.request = this.hls_.xhr({ 40551 uri: this.srcUrl, 40552 withCredentials: this.withCredentials 40553 }, function (error, req) { 40554 // disposed 40555 if (!_this5.request) { 40556 return; 40557 } // clear the loader's request reference 40558 40559 40560 _this5.request = null; 40561 40562 if (error) { 40563 _this5.error = { 40564 status: req.status, 40565 message: 'HLS playlist request error at URL: ' + _this5.srcUrl + '.', 40566 responseText: req.responseText, 40567 // MEDIA_ERR_NETWORK 40568 code: 2 40569 }; 40570 40571 if (_this5.state === 'HAVE_NOTHING') { 40572 _this5.started = false; 40573 } 40574 40575 return _this5.trigger('error'); 40576 } 40577 40578 var parser = new Parser(); // adding custom tag parsers 40579 40580 _this5.customTagParsers.forEach(function (customParser) { 40581 return parser.addParser(customParser); 40582 }); // adding custom tag mappers 40583 40584 40585 _this5.customTagMappers.forEach(function (mapper) { 40586 return parser.addTagMapper(mapper); 40587 }); 40588 40589 parser.push(req.responseText); 40590 parser.end(); 40591 _this5.state = 'HAVE_MASTER'; 40592 _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req); 40593 parser.manifest.uri = _this5.srcUrl; // loaded a master playlist 40594 40595 if (parser.manifest.playlists) { 40596 _this5.master = parser.manifest; 40597 setupMediaPlaylists(_this5.master); 40598 resolveMediaGroupUris(_this5.master); 40599 40600 _this5.trigger('loadedplaylist'); 40601 40602 if (!_this5.request) { 40603 // no media playlist was specifically selected so start 40604 // from the first listed one 40605 _this5.media(parser.manifest.playlists[0]); 40606 } 40607 40608 return; 40609 } 40610 40611 var id = createPlaylistID(0, _this5.srcUrl); // loaded a media playlist 40612 // infer a master playlist if none was previously requested 40613 40614 _this5.master = { 40615 mediaGroups: { 40616 'AUDIO': {}, 40617 'VIDEO': {}, 40618 'CLOSED-CAPTIONS': {}, 40619 'SUBTITLES': {} 40620 }, 40621 uri: window$3.location.href, 40622 playlists: [{ 40623 uri: _this5.srcUrl, 40624 id: id, 40625 resolvedUri: _this5.srcUrl, 40626 // m3u8-parser does not attach an attributes property to media playlists so make 40627 // sure that the property is attached to avoid undefined reference errors 40628 attributes: {} 40629 }] 40630 }; 40631 _this5.master.playlists[id] = _this5.master.playlists[0]; // URI reference added for backwards compatibility 40632 40633 _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0]; 40634 40635 _this5.haveMetadata(req, _this5.srcUrl, id); 40636 40637 return _this5.trigger('loadedmetadata'); 40638 }); 40639 } 40640 }]); 40641 return PlaylistLoader; 40642 }(EventTarget$1); 40643 /** 40644 * @file playlist.js 40645 * 40646 * Playlist related utilities. 40647 */ 40648 40649 40650 var createTimeRange = videojs$1.createTimeRange; 40651 /** 40652 * walk backward until we find a duration we can use 40653 * or return a failure 40654 * 40655 * @param {Playlist} playlist the playlist to walk through 40656 * @param {Number} endSequence the mediaSequence to stop walking on 40657 */ 40658 40659 var backwardDuration = function backwardDuration(playlist, endSequence) { 40660 var result = 0; 40661 var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following 40662 // the interval, use it 40663 40664 var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline 40665 // information that is earlier than endSequence 40666 40667 if (segment) { 40668 if (typeof segment.start !== 'undefined') { 40669 return { 40670 result: segment.start, 40671 precise: true 40672 }; 40673 } 40674 40675 if (typeof segment.end !== 'undefined') { 40676 return { 40677 result: segment.end - segment.duration, 40678 precise: true 40679 }; 40680 } 40681 } 40682 40683 while (i--) { 40684 segment = playlist.segments[i]; 40685 40686 if (typeof segment.end !== 'undefined') { 40687 return { 40688 result: result + segment.end, 40689 precise: true 40690 }; 40691 } 40692 40693 result += segment.duration; 40694 40695 if (typeof segment.start !== 'undefined') { 40696 return { 40697 result: result + segment.start, 40698 precise: true 40699 }; 40700 } 40701 } 40702 40703 return { 40704 result: result, 40705 precise: false 40706 }; 40707 }; 40708 /** 40709 * walk forward until we find a duration we can use 40710 * or return a failure 40711 * 40712 * @param {Playlist} playlist the playlist to walk through 40713 * @param {Number} endSequence the mediaSequence to stop walking on 40714 */ 40715 40716 40717 var forwardDuration = function forwardDuration(playlist, endSequence) { 40718 var result = 0; 40719 var segment = void 0; 40720 var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline 40721 // information 40722 40723 for (; i < playlist.segments.length; i++) { 40724 segment = playlist.segments[i]; 40725 40726 if (typeof segment.start !== 'undefined') { 40727 return { 40728 result: segment.start - result, 40729 precise: true 40730 }; 40731 } 40732 40733 result += segment.duration; 40734 40735 if (typeof segment.end !== 'undefined') { 40736 return { 40737 result: segment.end - result, 40738 precise: true 40739 }; 40740 } 40741 } // indicate we didn't find a useful duration estimate 40742 40743 40744 return { 40745 result: -1, 40746 precise: false 40747 }; 40748 }; 40749 /** 40750 * Calculate the media duration from the segments associated with a 40751 * playlist. The duration of a subinterval of the available segments 40752 * may be calculated by specifying an end index. 40753 * 40754 * @param {Object} playlist a media playlist object 40755 * @param {Number=} endSequence an exclusive upper boundary 40756 * for the playlist. Defaults to playlist length. 40757 * @param {Number} expired the amount of time that has dropped 40758 * off the front of the playlist in a live scenario 40759 * @return {Number} the duration between the first available segment 40760 * and end index. 40761 */ 40762 40763 40764 var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 40765 var backward = void 0; 40766 var forward = void 0; 40767 40768 if (typeof endSequence === 'undefined') { 40769 endSequence = playlist.mediaSequence + playlist.segments.length; 40770 } 40771 40772 if (endSequence < playlist.mediaSequence) { 40773 return 0; 40774 } // do a backward walk to estimate the duration 40775 40776 40777 backward = backwardDuration(playlist, endSequence); 40778 40779 if (backward.precise) { 40780 // if we were able to base our duration estimate on timing 40781 // information provided directly from the Media Source, return 40782 // it 40783 return backward.result; 40784 } // walk forward to see if a precise duration estimate can be made 40785 // that way 40786 40787 40788 forward = forwardDuration(playlist, endSequence); 40789 40790 if (forward.precise) { 40791 // we found a segment that has been buffered and so it's 40792 // position is known precisely 40793 return forward.result; 40794 } // return the less-precise, playlist-based duration estimate 40795 40796 40797 return backward.result + expired; 40798 }; 40799 /** 40800 * Calculates the duration of a playlist. If a start and end index 40801 * are specified, the duration will be for the subset of the media 40802 * timeline between those two indices. The total duration for live 40803 * playlists is always Infinity. 40804 * 40805 * @param {Object} playlist a media playlist object 40806 * @param {Number=} endSequence an exclusive upper 40807 * boundary for the playlist. Defaults to the playlist media 40808 * sequence number plus its length. 40809 * @param {Number=} expired the amount of time that has 40810 * dropped off the front of the playlist in a live scenario 40811 * @return {Number} the duration between the start index and end 40812 * index. 40813 */ 40814 40815 40816 var duration = function duration(playlist, endSequence, expired) { 40817 if (!playlist) { 40818 return 0; 40819 } 40820 40821 if (typeof expired !== 'number') { 40822 expired = 0; 40823 } // if a slice of the total duration is not requested, use 40824 // playlist-level duration indicators when they're present 40825 40826 40827 if (typeof endSequence === 'undefined') { 40828 // if present, use the duration specified in the playlist 40829 if (playlist.totalDuration) { 40830 return playlist.totalDuration; 40831 } // duration should be Infinity for live playlists 40832 40833 40834 if (!playlist.endList) { 40835 return window$3.Infinity; 40836 } 40837 } // calculate the total duration based on the segment durations 40838 40839 40840 return intervalDuration(playlist, endSequence, expired); 40841 }; 40842 /** 40843 * Calculate the time between two indexes in the current playlist 40844 * neight the start- nor the end-index need to be within the current 40845 * playlist in which case, the targetDuration of the playlist is used 40846 * to approximate the durations of the segments 40847 * 40848 * @param {Object} playlist a media playlist object 40849 * @param {Number} startIndex 40850 * @param {Number} endIndex 40851 * @return {Number} the number of seconds between startIndex and endIndex 40852 */ 40853 40854 40855 var sumDurations = function sumDurations(playlist, startIndex, endIndex) { 40856 var durations = 0; 40857 40858 if (startIndex > endIndex) { 40859 var _ref = [endIndex, startIndex]; 40860 startIndex = _ref[0]; 40861 endIndex = _ref[1]; 40862 } 40863 40864 if (startIndex < 0) { 40865 for (var i = startIndex; i < Math.min(0, endIndex); i++) { 40866 durations += playlist.targetDuration; 40867 } 40868 40869 startIndex = 0; 40870 } 40871 40872 for (var _i = startIndex; _i < endIndex; _i++) { 40873 durations += playlist.segments[_i].duration; 40874 } 40875 40876 return durations; 40877 }; 40878 /** 40879 * Determines the media index of the segment corresponding to the safe edge of the live 40880 * window which is the duration of the last segment plus 2 target durations from the end 40881 * of the playlist. 40882 * 40883 * A liveEdgePadding can be provided which will be used instead of calculating the safe live edge. 40884 * This corresponds to suggestedPresentationDelay in DASH manifests. 40885 * 40886 * @param {Object} playlist 40887 * a media playlist object 40888 * @param {Number} [liveEdgePadding] 40889 * A number in seconds indicating how far from the end we want to be. 40890 * If provided, this value is used instead of calculating the safe live index from the target durations. 40891 * Corresponds to suggestedPresentationDelay in DASH manifests. 40892 * @return {Number} 40893 * The media index of the segment at the safe live point. 0 if there is no "safe" 40894 * point. 40895 * @function safeLiveIndex 40896 */ 40897 40898 40899 var safeLiveIndex = function safeLiveIndex(playlist, liveEdgePadding) { 40900 if (!playlist.segments.length) { 40901 return 0; 40902 } 40903 40904 var i = playlist.segments.length; 40905 var lastSegmentDuration = playlist.segments[i - 1].duration || playlist.targetDuration; 40906 var safeDistance = typeof liveEdgePadding === 'number' ? liveEdgePadding : lastSegmentDuration + playlist.targetDuration * 2; 40907 40908 if (safeDistance === 0) { 40909 return i; 40910 } 40911 40912 var distanceFromEnd = 0; 40913 40914 while (i--) { 40915 distanceFromEnd += playlist.segments[i].duration; 40916 40917 if (distanceFromEnd >= safeDistance) { 40918 break; 40919 } 40920 } 40921 40922 return Math.max(0, i); 40923 }; 40924 /** 40925 * Calculates the playlist end time 40926 * 40927 * @param {Object} playlist a media playlist object 40928 * @param {Number=} expired the amount of time that has 40929 * dropped off the front of the playlist in a live scenario 40930 * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the 40931 * playlist end calculation should consider the safe live end 40932 * (truncate the playlist end by three segments). This is normally 40933 * used for calculating the end of the playlist's seekable range. 40934 * This takes into account the value of liveEdgePadding. 40935 * Setting liveEdgePadding to 0 is equivalent to setting this to false. 40936 * @param {Number} liveEdgePadding a number indicating how far from the end of the playlist we should be in seconds. 40937 * If this is provided, it is used in the safe live end calculation. 40938 * Setting useSafeLiveEnd=false or liveEdgePadding=0 are equivalent. 40939 * Corresponds to suggestedPresentationDelay in DASH manifests. 40940 * @returns {Number} the end time of playlist 40941 * @function playlistEnd 40942 */ 40943 40944 40945 var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding) { 40946 if (!playlist || !playlist.segments) { 40947 return null; 40948 } 40949 40950 if (playlist.endList) { 40951 return duration(playlist); 40952 } 40953 40954 if (expired === null) { 40955 return null; 40956 } 40957 40958 expired = expired || 0; 40959 var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist, liveEdgePadding) : playlist.segments.length; 40960 return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); 40961 }; 40962 /** 40963 * Calculates the interval of time that is currently seekable in a 40964 * playlist. The returned time ranges are relative to the earliest 40965 * moment in the specified playlist that is still available. A full 40966 * seekable implementation for live streams would need to offset 40967 * these values by the duration of content that has expired from the 40968 * stream. 40969 * 40970 * @param {Object} playlist a media playlist object 40971 * dropped off the front of the playlist in a live scenario 40972 * @param {Number=} expired the amount of time that has 40973 * dropped off the front of the playlist in a live scenario 40974 * @param {Number} liveEdgePadding how far from the end of the playlist we should be in seconds. 40975 * Corresponds to suggestedPresentationDelay in DASH manifests. 40976 * @return {TimeRanges} the periods of time that are valid targets 40977 * for seeking 40978 */ 40979 40980 40981 var seekable = function seekable(playlist, expired, liveEdgePadding) { 40982 var useSafeLiveEnd = true; 40983 var seekableStart = expired || 0; 40984 var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding); 40985 40986 if (seekableEnd === null) { 40987 return createTimeRange(); 40988 } 40989 40990 return createTimeRange(seekableStart, seekableEnd); 40991 }; 40992 40993 var isWholeNumber = function isWholeNumber(num) { 40994 return num - Math.floor(num) === 0; 40995 }; 40996 40997 var roundSignificantDigit = function roundSignificantDigit(increment, num) { 40998 // If we have a whole number, just add 1 to it 40999 if (isWholeNumber(num)) { 41000 return num + increment * 0.1; 41001 } 41002 41003 var numDecimalDigits = num.toString().split('.')[1].length; 41004 41005 for (var i = 1; i <= numDecimalDigits; i++) { 41006 var scale = Math.pow(10, i); 41007 var temp = num * scale; 41008 41009 if (isWholeNumber(temp) || i === numDecimalDigits) { 41010 return (temp + increment) / scale; 41011 } 41012 } 41013 }; 41014 41015 var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1); 41016 var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1); 41017 /** 41018 * Determine the index and estimated starting time of the segment that 41019 * contains a specified playback position in a media playlist. 41020 * 41021 * @param {Object} playlist the media playlist to query 41022 * @param {Number} currentTime The number of seconds since the earliest 41023 * possible position to determine the containing segment for 41024 * @param {Number} startIndex 41025 * @param {Number} startTime 41026 * @return {Object} 41027 */ 41028 41029 var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) { 41030 var i = void 0; 41031 var segment = void 0; 41032 var numSegments = playlist.segments.length; 41033 var time = currentTime - startTime; 41034 41035 if (time < 0) { 41036 // Walk backward from startIndex in the playlist, adding durations 41037 // until we find a segment that contains `time` and return it 41038 if (startIndex > 0) { 41039 for (i = startIndex - 1; i >= 0; i--) { 41040 segment = playlist.segments[i]; 41041 time += floorLeastSignificantDigit(segment.duration); 41042 41043 if (time > 0) { 41044 return { 41045 mediaIndex: i, 41046 startTime: startTime - sumDurations(playlist, startIndex, i) 41047 }; 41048 } 41049 } 41050 } // We were unable to find a good segment within the playlist 41051 // so select the first segment 41052 41053 41054 return { 41055 mediaIndex: 0, 41056 startTime: currentTime 41057 }; 41058 } // When startIndex is negative, we first walk forward to first segment 41059 // adding target durations. If we "run out of time" before getting to 41060 // the first segment, return the first segment 41061 41062 41063 if (startIndex < 0) { 41064 for (i = startIndex; i < 0; i++) { 41065 time -= playlist.targetDuration; 41066 41067 if (time < 0) { 41068 return { 41069 mediaIndex: 0, 41070 startTime: currentTime 41071 }; 41072 } 41073 } 41074 41075 startIndex = 0; 41076 } // Walk forward from startIndex in the playlist, subtracting durations 41077 // until we find a segment that contains `time` and return it 41078 41079 41080 for (i = startIndex; i < numSegments; i++) { 41081 segment = playlist.segments[i]; 41082 time -= ceilLeastSignificantDigit(segment.duration); 41083 41084 if (time < 0) { 41085 return { 41086 mediaIndex: i, 41087 startTime: startTime + sumDurations(playlist, startIndex, i) 41088 }; 41089 } 41090 } // We are out of possible candidates so load the last one... 41091 41092 41093 return { 41094 mediaIndex: numSegments - 1, 41095 startTime: currentTime 41096 }; 41097 }; 41098 /** 41099 * Check whether the playlist is blacklisted or not. 41100 * 41101 * @param {Object} playlist the media playlist object 41102 * @return {boolean} whether the playlist is blacklisted or not 41103 * @function isBlacklisted 41104 */ 41105 41106 41107 var isBlacklisted = function isBlacklisted(playlist) { 41108 return playlist.excludeUntil && playlist.excludeUntil > Date.now(); 41109 }; 41110 /** 41111 * Check whether the playlist is compatible with current playback configuration or has 41112 * been blacklisted permanently for being incompatible. 41113 * 41114 * @param {Object} playlist the media playlist object 41115 * @return {boolean} whether the playlist is incompatible or not 41116 * @function isIncompatible 41117 */ 41118 41119 41120 var isIncompatible = function isIncompatible(playlist) { 41121 return playlist.excludeUntil && playlist.excludeUntil === Infinity; 41122 }; 41123 /** 41124 * Check whether the playlist is enabled or not. 41125 * 41126 * @param {Object} playlist the media playlist object 41127 * @return {boolean} whether the playlist is enabled or not 41128 * @function isEnabled 41129 */ 41130 41131 41132 var isEnabled = function isEnabled(playlist) { 41133 var blacklisted = isBlacklisted(playlist); 41134 return !playlist.disabled && !blacklisted; 41135 }; 41136 /** 41137 * Check whether the playlist has been manually disabled through the representations api. 41138 * 41139 * @param {Object} playlist the media playlist object 41140 * @return {boolean} whether the playlist is disabled manually or not 41141 * @function isDisabled 41142 */ 41143 41144 41145 var isDisabled = function isDisabled(playlist) { 41146 return playlist.disabled; 41147 }; 41148 /** 41149 * Returns whether the current playlist is an AES encrypted HLS stream 41150 * 41151 * @return {Boolean} true if it's an AES encrypted HLS stream 41152 */ 41153 41154 41155 var isAes = function isAes(media) { 41156 for (var i = 0; i < media.segments.length; i++) { 41157 if (media.segments[i].key) { 41158 return true; 41159 } 41160 } 41161 41162 return false; 41163 }; 41164 /** 41165 * Returns whether the current playlist contains fMP4 41166 * 41167 * @return {Boolean} true if the playlist contains fMP4 41168 */ 41169 41170 41171 var isFmp4 = function isFmp4(media) { 41172 for (var i = 0; i < media.segments.length; i++) { 41173 if (media.segments[i].map) { 41174 return true; 41175 } 41176 } 41177 41178 return false; 41179 }; 41180 /** 41181 * Checks if the playlist has a value for the specified attribute 41182 * 41183 * @param {String} attr 41184 * Attribute to check for 41185 * @param {Object} playlist 41186 * The media playlist object 41187 * @return {Boolean} 41188 * Whether the playlist contains a value for the attribute or not 41189 * @function hasAttribute 41190 */ 41191 41192 41193 var hasAttribute = function hasAttribute(attr, playlist) { 41194 return playlist.attributes && playlist.attributes[attr]; 41195 }; 41196 /** 41197 * Estimates the time required to complete a segment download from the specified playlist 41198 * 41199 * @param {Number} segmentDuration 41200 * Duration of requested segment 41201 * @param {Number} bandwidth 41202 * Current measured bandwidth of the player 41203 * @param {Object} playlist 41204 * The media playlist object 41205 * @param {Number=} bytesReceived 41206 * Number of bytes already received for the request. Defaults to 0 41207 * @return {Number|NaN} 41208 * The estimated time to request the segment. NaN if bandwidth information for 41209 * the given playlist is unavailable 41210 * @function estimateSegmentRequestTime 41211 */ 41212 41213 41214 var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) { 41215 var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; 41216 41217 if (!hasAttribute('BANDWIDTH', playlist)) { 41218 return NaN; 41219 } 41220 41221 var size = segmentDuration * playlist.attributes.BANDWIDTH; 41222 return (size - bytesReceived * 8) / bandwidth; 41223 }; 41224 /* 41225 * Returns whether the current playlist is the lowest rendition 41226 * 41227 * @return {Boolean} true if on lowest rendition 41228 */ 41229 41230 41231 var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) { 41232 if (master.playlists.length === 1) { 41233 return true; 41234 } 41235 41236 var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; 41237 return master.playlists.filter(function (playlist) { 41238 if (!isEnabled(playlist)) { 41239 return false; 41240 } 41241 41242 return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth; 41243 }).length === 0; 41244 }; // exports 41245 41246 41247 var Playlist = { 41248 duration: duration, 41249 seekable: seekable, 41250 safeLiveIndex: safeLiveIndex, 41251 getMediaInfoForTime: getMediaInfoForTime, 41252 isEnabled: isEnabled, 41253 isDisabled: isDisabled, 41254 isBlacklisted: isBlacklisted, 41255 isIncompatible: isIncompatible, 41256 playlistEnd: playlistEnd, 41257 isAes: isAes, 41258 isFmp4: isFmp4, 41259 hasAttribute: hasAttribute, 41260 estimateSegmentRequestTime: estimateSegmentRequestTime, 41261 isLowestEnabledRendition: isLowestEnabledRendition 41262 }; 41263 /** 41264 * @file xhr.js 41265 */ 41266 41267 var videojsXHR = videojs$1.xhr, 41268 mergeOptions$1$1 = videojs$1.mergeOptions; 41269 41270 var xhrFactory = function xhrFactory() { 41271 var xhr = function XhrFunction(options, callback) { 41272 // Add a default timeout for all hls requests 41273 options = mergeOptions$1$1({ 41274 timeout: 45e3 41275 }, options); // Allow an optional user-specified function to modify the option 41276 // object before we construct the xhr request 41277 41278 var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest; 41279 41280 if (beforeRequest && typeof beforeRequest === 'function') { 41281 var newOptions = beforeRequest(options); 41282 41283 if (newOptions) { 41284 options = newOptions; 41285 } 41286 } 41287 41288 var request = videojsXHR(options, function (error, response) { 41289 var reqResponse = request.response; 41290 41291 if (!error && reqResponse) { 41292 request.responseTime = Date.now(); 41293 request.roundTripTime = request.responseTime - request.requestTime; 41294 request.bytesReceived = reqResponse.byteLength || reqResponse.length; 41295 41296 if (!request.bandwidth) { 41297 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 41298 } 41299 } 41300 41301 if (response.headers) { 41302 request.responseHeaders = response.headers; 41303 } // videojs.xhr now uses a specific code on the error 41304 // object to signal that a request has timed out instead 41305 // of setting a boolean on the request object 41306 41307 41308 if (error && error.code === 'ETIMEDOUT') { 41309 request.timedout = true; 41310 } // videojs.xhr no longer considers status codes outside of 200 and 0 41311 // (for file uris) to be errors, but the old XHR did, so emulate that 41312 // behavior. Status 206 may be used in response to byterange requests. 41313 41314 41315 if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 41316 error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))); 41317 } 41318 41319 callback(error, request); 41320 }); 41321 var originalAbort = request.abort; 41322 41323 request.abort = function () { 41324 request.aborted = true; 41325 return originalAbort.apply(request, arguments); 41326 }; 41327 41328 request.uri = options.uri; 41329 request.requestTime = Date.now(); 41330 return request; 41331 }; 41332 41333 return xhr; 41334 }; 41335 /** 41336 * Turns segment byterange into a string suitable for use in 41337 * HTTP Range requests 41338 * 41339 * @param {Object} byterange - an object with two values defining the start and end 41340 * of a byte-range 41341 */ 41342 41343 41344 var byterangeStr = function byterangeStr(byterange) { 41345 var byterangeStart = void 0; 41346 var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range 41347 // header uses inclusive ranges 41348 41349 byterangeEnd = byterange.offset + byterange.length - 1; 41350 byterangeStart = byterange.offset; 41351 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 41352 }; 41353 /** 41354 * Defines headers for use in the xhr request for a particular segment. 41355 * 41356 * @param {Object} segment - a simplified copy of the segmentInfo object 41357 * from SegmentLoader 41358 */ 41359 41360 41361 var segmentXhrHeaders = function segmentXhrHeaders(segment) { 41362 var headers = {}; 41363 41364 if (segment.byterange) { 41365 headers.Range = byterangeStr(segment.byterange); 41366 } 41367 41368 return headers; 41369 }; 41370 /** 41371 * @file bin-utils.js 41372 */ 41373 41374 /** 41375 * convert a TimeRange to text 41376 * 41377 * @param {TimeRange} range the timerange to use for conversion 41378 * @param {Number} i the iterator on the range to convert 41379 */ 41380 41381 41382 var textRange = function textRange(range, i) { 41383 return range.start(i) + '-' + range.end(i); 41384 }; 41385 /** 41386 * format a number as hex string 41387 * 41388 * @param {Number} e The number 41389 * @param {Number} i the iterator 41390 */ 41391 41392 41393 var formatHexString = function formatHexString(e, i) { 41394 var value = e.toString(16); 41395 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 41396 }; 41397 41398 var formatAsciiString = function formatAsciiString(e) { 41399 if (e >= 0x20 && e < 0x7e) { 41400 return String.fromCharCode(e); 41401 } 41402 41403 return '.'; 41404 }; 41405 /** 41406 * Creates an object for sending to a web worker modifying properties that are TypedArrays 41407 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 41408 * 41409 * @param {Object} message 41410 * Object of properties and values to send to the web worker 41411 * @return {Object} 41412 * Modified message with TypedArray values expanded 41413 * @function createTransferableMessage 41414 */ 41415 41416 41417 var createTransferableMessage = function createTransferableMessage(message) { 41418 var transferable = {}; 41419 Object.keys(message).forEach(function (key) { 41420 var value = message[key]; 41421 41422 if (ArrayBuffer.isView(value)) { 41423 transferable[key] = { 41424 bytes: value.buffer, 41425 byteOffset: value.byteOffset, 41426 byteLength: value.byteLength 41427 }; 41428 } else { 41429 transferable[key] = value; 41430 } 41431 }); 41432 return transferable; 41433 }; 41434 /** 41435 * Returns a unique string identifier for a media initialization 41436 * segment. 41437 */ 41438 41439 41440 var initSegmentId = function initSegmentId(initSegment) { 41441 var byterange = initSegment.byterange || { 41442 length: Infinity, 41443 offset: 0 41444 }; 41445 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 41446 }; 41447 /** 41448 * Returns a unique string identifier for a media segment key. 41449 */ 41450 41451 41452 var segmentKeyId = function segmentKeyId(key) { 41453 return key.resolvedUri; 41454 }; 41455 /** 41456 * utils to help dump binary data to the console 41457 */ 41458 41459 41460 var hexDump = function hexDump(data) { 41461 var bytes = Array.prototype.slice.call(data); 41462 var step = 16; 41463 var result = ''; 41464 var hex = void 0; 41465 var ascii = void 0; 41466 41467 for (var j = 0;
41467 j < bytes.length / step; j++) { 41468 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 41469 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 41470 result += hex + ' ' + ascii + '\n'; 41471 } 41472 41473 return result; 41474 }; 41475 41476 var tagDump = function tagDump(_ref) { 41477 var bytes = _ref.bytes; 41478 return hexDump(bytes); 41479 }; 41480 41481 var textRanges = function textRanges(ranges) { 41482 var result = ''; 41483 var i = void 0; 41484 41485 for (i = 0; i < ranges.length; i++) { 41486 result += textRange(ranges, i) + ' '; 41487 } 41488 41489 return result; 41490 }; 41491 41492 var utils$1 = /*#__PURE__*/Object.freeze({ 41493 createTransferableMessage: createTransferableMessage, 41494 initSegmentId: initSegmentId, 41495 segmentKeyId: segmentKeyId, 41496 hexDump: hexDump, 41497 tagDump: tagDump, 41498 textRanges: textRanges 41499 }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux 41500 // Add 25% to the segment duration to account for small discrepencies in segment timing. 41501 // 25% was arbitrarily chosen, and may need to be refined over time. 41502 41503 var SEGMENT_END_FUDGE_PERCENT = 0.25; 41504 /** 41505 * Converts a player time (any time that can be gotten/set from player.currentTime(), 41506 * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a 41507 * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)). 41508 * 41509 * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor 41510 * point" (a point where we have a mapping from program time to player time, with player 41511 * time being the post transmux start of the segment). 41512 * 41513 * For more details, see [this doc](../../docs/program-time-from-player-time.md). 41514 * 41515 * @param {Number} playerTime the player time 41516 * @param {Object} segment the segment which contains the player time 41517 * @return {Date} program time 41518 */ 41519 41520 var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) { 41521 if (!segment.dateTimeObject) { 41522 // Can't convert without an "anchor point" for the program time (i.e., a time that can 41523 // be used to map the start of a segment with a real world time). 41524 return null; 41525 } 41526 41527 var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds; 41528 var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended 41529 41530 var startOfSegment = transmuxedStart + transmuxerPrependedSeconds; 41531 var offsetFromSegmentStart = playerTime - startOfSegment; 41532 return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000); 41533 }; 41534 41535 var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) { 41536 return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds; 41537 }; 41538 /** 41539 * Finds a segment that contains the time requested given as an ISO-8601 string. The 41540 * returned segment might be an estimate or an accurate match. 41541 * 41542 * @param {String} programTime The ISO-8601 programTime to find a match for 41543 * @param {Object} playlist A playlist object to search within 41544 */ 41545 41546 41547 var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) { 41548 // Assumptions: 41549 // - verifyProgramDateTimeTags has already been run 41550 // - live streams have been started 41551 var dateTimeObject = void 0; 41552 41553 try { 41554 dateTimeObject = new Date(programTime); 41555 } catch (e) { 41556 return null; 41557 } 41558 41559 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 41560 return null; 41561 } 41562 41563 var segment = playlist.segments[0]; 41564 41565 if (dateTimeObject < segment.dateTimeObject) { 41566 // Requested time is before stream start. 41567 return null; 41568 } 41569 41570 for (var i = 0; i < playlist.segments.length - 1; i++) { 41571 segment = playlist.segments[i]; 41572 var nextSegmentStart = playlist.segments[i + 1].dateTimeObject; 41573 41574 if (dateTimeObject < nextSegmentStart) { 41575 break; 41576 } 41577 } 41578 41579 var lastSegment = playlist.segments[playlist.segments.length - 1]; 41580 var lastSegmentStart = lastSegment.dateTimeObject; 41581 var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT; 41582 var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000); 41583 41584 if (dateTimeObject > lastSegmentEnd) { 41585 // Beyond the end of the stream, or our best guess of the end of the stream. 41586 return null; 41587 } 41588 41589 if (dateTimeObject > lastSegmentStart) { 41590 segment = lastSegment; 41591 } 41592 41593 return { 41594 segment: segment, 41595 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)), 41596 // Although, given that all segments have accurate date time objects, the segment 41597 // selected should be accurate, unless the video has been transmuxed at some point 41598 // (determined by the presence of the videoTimingInfo object), the segment's "player 41599 // time" (the start time in the player) can't be considered accurate. 41600 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 41601 }; 41602 }; 41603 /** 41604 * Finds a segment that contains the given player time(in seconds). 41605 * 41606 * @param {Number} time The player time to find a match for 41607 * @param {Object} playlist A playlist object to search within 41608 */ 41609 41610 41611 var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) { 41612 // Assumptions: 41613 // - there will always be a segment.duration 41614 // - we can start from zero
41615 // - segments are in time order 41616 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 41617 return null; 41618 } 41619 41620 var segmentEnd = 0; 41621 var segment = void 0; 41622 41623 for (var i = 0; i < playlist.segments.length; i++) { 41624 segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and 41625 // should contain the most accurate values we have for the segment's player times. 41626 // 41627 // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall 41628 // back to an estimate based on the manifest derived (inaccurate) segment.duration, to 41629 // calculate an end value. 41630 41631 segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration; 41632 41633 if (time <= segmentEnd) { 41634 break; 41635 } 41636 } 41637 41638 var lastSegment = playlist.segments[playlist.segments.length - 1]; 41639 41640 if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) { 41641 // The time requested is beyond the stream end. 41642 return null; 41643 } 41644 41645 if (time > segmentEnd) { 41646 // The time is within or beyond the last segment. 41647 // 41648 // Check to see if the time is beyond a reasonable guess of the end of the stream. 41649 if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) { 41650 // Technically, because the duration value is only an estimate, the time may still 41651 // exist in the last segment, however, there isn't enough information to make even 41652 // a reasonable estimate. 41653 return null; 41654 } 41655 41656 segment = lastSegment; 41657 } 41658 41659 return { 41660 segment: segment, 41661 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration, 41662 // Because videoTimingInfo is only set after transmux, it is the only way to get 41663 // accurate timing values. 41664 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 41665 }; 41666 }; 41667 /** 41668 * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds. 41669 * If the offset returned is positive, the programTime occurs after the 41670 * comparisonTimestamp. 41671 * If the offset is negative, the programTime occurs before the comparisonTimestamp. 41672 * 41673 * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against 41674 * @param {String} programTime The programTime as an ISO-8601 string 41675 * @return {Number} offset 41676 */ 41677 41678 41679 var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) { 41680 var segmentDateTime = void 0; 41681 var programDateTime = void 0; 41682 41683 try { 41684 segmentDateTime = new Date(comparisonTimeStamp); 41685 programDateTime = new Date(programTime); 41686 } catch (e) {// TODO handle error 41687 } 41688 41689 var segmentTimeEpoch = segmentDateTime.getTime(); 41690 var programTimeEpoch = programDateTime.getTime(); 41691 return (programTimeEpoch - segmentTimeEpoch) / 1000; 41692 }; 41693 /** 41694 * Checks that all segments in this playlist have programDateTime tags. 41695 * 41696 * @param {Object} playlist A playlist object 41697 */ 41698 41699 41700 var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) { 41701 if (!playlist.segments || playlist.segments.length === 0) { 41702 return false; 41703 } 41704 41705 for (var i = 0; i < playlist.segments.length; i++) { 41706 var segment = playlist.segments[i]; 41707 41708 if (!segment.dateTimeObject) { 41709 return false; 41710 } 41711 } 41712 41713 return true; 41714 }; 41715 /** 41716 * Returns the programTime of the media given a playlist and a playerTime. 41717 * The playlist must have programDateTime tags for a programDateTime tag to be returned. 41718 * If the segments containing the time requested have not been buffered yet, an estimate 41719 * may be returned to the callback. 41720 * 41721 * @param {Object} args 41722 * @param {Object} args.playlist A playlist object to search within 41723 * @param {Number} time A playerTime in seconds 41724 * @param {Function} callback(err, programTime) 41725 * @returns {String} err.message A detailed error message 41726 * @returns {Object} programTime 41727 * @returns {Number} programTime.mediaSeconds The streamTime in seconds 41728 * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String 41729 */ 41730 41731 41732 var getProgramTime = function getProgramTime(_ref) { 41733 var playlist = _ref.playlist, 41734 _ref$time = _ref.time, 41735 time = _ref$time === undefined ? undefined : _ref$time, 41736 callback = _ref.callback; 41737 41738 if (!callback) { 41739 throw new Error('getProgramTime: callback must be provided'); 41740 } 41741 41742 if (!playlist || time === undefined) { 41743 return callback({ 41744 message: 'getProgramTime: playlist and time must be provided' 41745 }); 41746 } 41747 41748 var matchedSegment = findSegmentForPlayerTime(time, playlist); 41749 41750 if (!matchedSegment) { 41751 return callback({ 41752 message: 'valid programTime was not found' 41753 }); 41754 } 41755 41756 if (matchedSegment.type === 'estimate') { 41757 return callback({ 41758 message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again', 41759 seekTime: matchedSegment.estimatedStart 41760 }); 41761 } 41762 41763 var programTimeObject = { 41764 mediaSeconds: time 41765 }; 41766 var programTime = playerTimeToProgramTime(time, matchedSegment.segment); 41767 41768 if (programTime) { 41769 programTimeObject.programDateTime = programTime.toISOString(); 41770 } 41771 41772 return callback(null, programTimeObject); 41773 }; 41774 /** 41775 * Seeks in the player to a time that matches the given programTime ISO-8601 string. 41776 * 41777 * @param {Object} args 41778 * @param {String} args.programTime A programTime to seek to as an ISO-8601 String 41779 * @param {Object} args.playlist A playlist to look within 41780 * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2. 41781 * @param {Function} args.seekTo A method to perform a seek 41782 * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true. 41783 * @param {Object} args.tech The tech to seek on 41784 * @param {Function} args.callback(err, newTime) A callback to return the new time to 41785 * @returns {String} err.message A detailed error message 41786 * @returns {Number} newTime The exact time that was seeked to in seconds 41787 */ 41788 41789 41790 var seekToProgramTime = function seekToProgramTime(_ref2) { 41791 var programTime = _ref2.programTime, 41792 playlist = _ref2.playlist, 41793 _ref2$retryCount = _ref2.retryCount, 41794 retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount, 41795 seekTo = _ref2.seekTo, 41796 _ref2$pauseAfterSeek = _ref2.pauseAfterSeek, 41797 pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek, 41798 tech = _ref2.tech, 41799 callback = _ref2.callback; 41800 41801 if (!callback) { 41802 throw new Error('seekToProgramTime: callback must be provided'); 41803 } 41804 41805 if (typeof programTime === 'undefined' || !playlist || !seekTo) { 41806 return callback({ 41807 message: 'seekToProgramTime: programTime, seekTo and playlist must be provided' 41808 }); 41809 } 41810 41811 if (!playlist.endList && !tech.hasStarted_) { 41812 return callback({ 41813 message: 'player must be playing a live stream to start buffering' 41814 }); 41815 } 41816 41817 if (!verifyProgramDateTimeTags(playlist)) { 41818 return callback({ 41819 message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri 41820 }); 41821 } 41822 41823 var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match 41824 41825 if (!matchedSegment) { 41826 return callback({ 41827 message: programTime + ' was not found in the stream' 41828 }); 41829 } 41830 41831 var segment = matchedSegment.segment; 41832 var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime); 41833 41834 if (matchedSegment.type === 'estimate') { 41835 // we've run out of retries 41836 if (retryCount === 0) { 41837 return callback({ 41838 message: programTime + ' is not buffered yet. Try again' 41839 }); 41840 } 41841 41842 seekTo(matchedSegment.estimatedStart + mediaOffset); 41843 tech.one('seeked', function () { 41844 seekToProgramTime({ 41845 programTime: programTime, 41846 playlist: playlist, 41847 retryCount: retryCount - 1, 41848 seekTo: seekTo, 41849 pauseAfterSeek: pauseAfterSeek, 41850 tech: tech, 41851 callback: callback 41852 }); 41853 }); 41854 return; 41855 } // Since the segment.start value is determined from the buffered end or ending time 41856 // of the prior segment, the seekToTime doesn't need to account for any transmuxer 41857 // modifications. 41858 41859 41860 var seekToTime = segment.start + mediaOffset; 41861 41862 var seekedCallback = function seekedCallback() { 41863 return callback(null, tech.currentTime()); 41864 }; // listen for seeked event 41865 41866 41867 tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state 41868 41869 if (pauseAfterSeek) { 41870 tech.pause(); 41871 } 41872 41873 seekTo(seekToTime); 41874 }; 41875 /** 41876 * ranges 41877 * 41878 * Utilities for working with TimeRanges. 41879 * 41880 */ 41881 // Fudge factor to account for TimeRanges rounding 41882 41883 41884 var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range 41885 // can be misleading because of precision differences or when the current media has poorly 41886 // aligned audio and video, which can cause values to be slightly off from what you would 41887 // expect. This value is what we consider to be safe to use in such comparisons to account 41888 // for these scenarios. 41889 41890 var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3; 41891 41892 var filterRanges = function filterRanges(timeRanges, predicate) { 41893 var results = []; 41894 var i = void 0; 41895 41896 if (timeRanges && timeRanges.length) { 41897 // Search for ranges that match the predicate 41898 for (i = 0; i < timeRanges.length; i++) { 41899 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 41900 results.push([timeRanges.start(i), timeRanges.end(i)]); 41901 } 41902 } 41903 } 41904 41905 return videojs$1.createTimeRanges(results); 41906 }; 41907 /** 41908 * Attempts to find the buffered TimeRange that contains the specified 41909 * time. 41910 * @param {TimeRanges} buffered - the TimeRanges object to query 41911 * @param {number} time - the time to filter on. 41912 * @returns {TimeRanges} a new TimeRanges object 41913 */ 41914 41915 41916 var findRange = function findRange(buffered, time) { 41917 return filterRanges(buffered, function (start, end) { 41918 return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time; 41919 }); 41920 }; 41921 /** 41922 * Returns the TimeRanges that begin later than the specified time. 41923 * @param {TimeRanges} timeRanges - the TimeRanges object to query 41924 * @param {number} time - the time to filter on. 41925 * @returns {TimeRanges} a new TimeRanges object. 41926 */ 41927 41928 41929 var findNextRange = function findNextRange(timeRanges, time) { 41930 return filterRanges(timeRanges, function (start) { 41931 return start - TIME_FUDGE_FACTOR >= time; 41932 }); 41933 }; 41934 /** 41935 * Returns gaps within a list of TimeRanges 41936 * @param {TimeRanges} buffered - the TimeRanges object 41937 * @return {TimeRanges} a TimeRanges object of gaps 41938 */ 41939 41940 41941 var findGaps = function findGaps(buffered) { 41942 if (buffered.length < 2) { 41943 return videojs$1.createTimeRanges(); 41944 } 41945 41946 var ranges = []; 41947 41948 for (var i = 1; i < buffered.length; i++) { 41949 var start = buffered.end(i - 1); 41950 var end = buffered.start(i); 41951 ranges.push([start, end]); 41952 } 41953 41954 return videojs$1.createTimeRanges(ranges); 41955 }; 41956 /** 41957 * Gets a human readable string for a TimeRange 41958 * 41959 * @param {TimeRange} range 41960 * @returns {String} a human readable string 41961 */ 41962 41963 41964 var printableRange = function printableRange(range) { 41965 var strArr = []; 41966 41967 if (!range || !range.length) { 41968 return ''; 41969 } 41970 41971 for (var i = 0; i < range.length; i++) { 41972 strArr.push(range.start(i) + ' => ' + range.end(i)); 41973 } 41974 41975 return strArr.join(', '); 41976 }; 41977 /** 41978 * Calculates the amount of time left in seconds until the player hits the end of the 41979 * buffer and causes a rebuffer 41980 * 41981 * @param {TimeRange} buffered 41982 * The state of the buffer 41983 * @param {Numnber} currentTime 41984 * The current time of the player 41985 * @param {Number} playbackRate 41986 * The current playback rate of the player. Defaults to 1. 41987 * @return {Number} 41988 * Time until the player has to start rebuffering in seconds. 41989 * @function timeUntilRebuffer 41990 */ 41991 41992 41993 var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) { 41994 var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; 41995 var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0; 41996 return (bufferedEnd - currentTime) / playbackRate; 41997 }; 41998 /** 41999 * Converts a TimeRanges object into an array representation 42000 * @param {TimeRanges} timeRanges 42001 * @returns {Array} 42002 */ 42003 42004 42005 var timeRangesToArray = function timeRangesToArray(timeRanges) { 42006 var timeRangesList = []; 42007 42008 for (var i = 0; i < timeRanges.length; i++) { 42009 timeRangesList.push({ 42010 start: timeRanges.start(i), 42011 end: timeRanges.end(i) 42012 }); 42013 } 42014 42015 return timeRangesList; 42016 }; 42017 /** 42018 * @file create-text-tracks-if-necessary.js 42019 */ 42020 42021 /** 42022 * Create text tracks on video.js if they exist on a segment. 42023 * 42024 * @param {Object} sourceBuffer the VSB or FSB 42025 * @param {Object}
vendor: 17,660 bytes, lines 42025-42554
42025 mediaSource the HTML media source 42026 * @param {Object} segment the segment that may contain the text track 42027 * @private 42028 */ 42029 42030 42031 var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) { 42032 var player = mediaSource.player_; // create an in-band caption track if one is present in the segment 42033 42034 if (segment.captions && segment.captions.length) { 42035 if (!sourceBuffer.inbandTextTracks_) { 42036 sourceBuffer.inbandTextTracks_ = {}; 42037 } 42038 42039 for (var trackId in segment.captionStreams) { 42040 if (!sourceBuffer.inbandTextTracks_[trackId]) { 42041 player.tech_.trigger({ 42042 type: 'usage', 42043 name: 'hls-608' 42044 }); 42045 var track = player.textTracks().getTrackById(trackId); 42046 42047 if (track) { 42048 // Resuse an existing track with a CC# id because this was 42049 // very likely created by videojs-contrib-hls from information 42050 // in the m3u8 for us to use 42051 sourceBuffer.inbandTextTracks_[trackId] = track; 42052 } else { 42053 // Otherwise, create a track with the default `CC#` label and 42054 // without a language 42055 sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({ 42056 kind: 'captions', 42057 id: trackId, 42058 label: trackId 42059 }, false).track; 42060 } 42061 } 42062 } 42063 } 42064 42065 if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) { 42066 sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({ 42067 kind: 'metadata', 42068 label: 'Timed Metadata' 42069 }, false).track; 42070 sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType; 42071 } 42072 }; 42073 /** 42074 * @file remove-cues-from-track.js 42075 */ 42076 42077 /** 42078 * Remove cues from a track on video.js. 42079 * 42080 * @param {Double} start start of where we should remove the cue 42081 * @param {Double} end end of where the we should remove the cue 42082 * @param {Object} track the text track to remove the cues from 42083 * @private 42084 */ 42085 42086 42087 var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 42088 var i = void 0; 42089 var cue = void 0; 42090 42091 if (!track) { 42092 return; 42093 } 42094 42095 if (!track.cues) { 42096 return; 42097 } 42098 42099 i = track.cues.length; 42100 42101 while (i--) { 42102 cue = track.cues[i]; // Remove any overlapping cue 42103 42104 if (cue.startTime <= end && cue.endTime >= start) { 42105 track.removeCue(cue); 42106 } 42107 } 42108 }; 42109 /** 42110 * @file add-text-track-data.js 42111 */ 42112 42113 /** 42114 * Define properties on a cue for backwards compatability, 42115 * but warn the user that the way that they are using it 42116 * is depricated and will be removed at a later date. 42117 * 42118 * @param {Cue} cue the cue to add the properties on 42119 * @private 42120 */ 42121 42122 42123 var deprecateOldCue = function deprecateOldCue(cue) { 42124 Object.defineProperties(cue.frame, { 42125 id: { 42126 get: function get() { 42127 videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 42128 return cue.value.key; 42129 } 42130 }, 42131 value: { 42132 get: function get() { 42133 videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 42134 return cue.value.data; 42135 } 42136 }, 42137 privateData: { 42138 get: function get() { 42139 videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 42140 return cue.value.data; 42141 } 42142 } 42143 }); 42144 }; 42145 42146 var durationOfVideo = function durationOfVideo(duration) { 42147 var dur = void 0; 42148 42149 if (isNaN(duration) || Math.abs(duration) === Infinity) { 42150 dur = Number.MAX_VALUE; 42151 } else { 42152 dur = duration; 42153 } 42154 42155 return dur; 42156 }; 42157 /** 42158 * Add text track data to a source handler given the captions and 42159 * metadata from the buffer. 42160 * 42161 * @param {Object} sourceHandler the virtual source buffer 42162 * @param {Array} captionArray an array of caption data 42163 * @param {Array} metadataArray an array of meta data 42164 * @private 42165 */ 42166 42167 42168 var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) { 42169 var Cue = window$3.WebKitDataCue || window$3.VTTCue; 42170 42171 if (captionArray) { 42172 captionArray.forEach(function (caption) { 42173 var track = caption.stream; 42174 this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text)); 42175 }, sourceHandler); 42176 } 42177 42178 if (metadataArray) { 42179 var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration); 42180 metadataArray.forEach(function (metadata) { 42181 var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN, 42182 // ignore this bit of metadata. 42183 // This likely occurs when you have an non-timed ID3 tag like TIT2, 42184 // which is the "Title/Songname/Content description" frame 42185 42186 if (typeof time !== 'number' || window$3.isNaN(time) || time < 0 || !(time < Infinity)) { 42187 return; 42188 } 42189 42190 metadata.frames.forEach(function (frame) { 42191 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 42192 cue.frame = frame; 42193 cue.value = frame; 42194 deprecateOldCue(cue); 42195 this.metadataTrack_.addCue(cue); 42196 }, this); 42197 }, sourceHandler); // Updating the metadeta cues so that 42198 // the endTime of each cue is the startTime of the next cue 42199 // the endTime of last cue is the duration of the video 42200 42201 if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) { 42202 var cues = sourceHandler.metadataTrack_.cues; 42203 var cuesArray = []; // Create a copy of the TextTrackCueList... 42204 // ...disregarding cues with a falsey value 42205 42206 for (var i = 0; i < cues.length; i++) { 42207 if (cues[i]) { 42208 cuesArray.push(cues[i]); 42209 } 42210 } // Group cues by their startTime value 42211 42212 42213 var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) { 42214 var timeSlot = obj[cue.startTime] || []; 42215 timeSlot.push(cue); 42216 obj[cue.startTime] = timeSlot; 42217 return obj; 42218 }, {}); // Sort startTimes by ascending order 42219 42220 var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) { 42221 return Number(a) - Number(b); 42222 }); // Map each cue group's endTime to the next group's startTime 42223 42224 sortedStartTimes.forEach(function (startTime, idx) { 42225 var cueGroup = cuesGroupedByStartTime[startTime]; 42226 var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime 42227 42228 cueGroup.forEach(function (cue) { 42229 cue.endTime = nextTime; 42230 }); 42231 }); 42232 } 42233 } 42234 }; 42235 42236 var win = typeof window !== 'undefined' ? window : {}, 42237 TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(), 42238 SCRIPT_TYPE = 'application/javascript', 42239 BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder, 42240 URL = win.URL || win.webkitURL || URL && URL.msURL, 42241 Worker = win.Worker; 42242 /** 42243 * Returns a wrapper around Web Worker code that is constructible. 42244 * 42245 * @function shimWorker 42246 * 42247 * @param { String } filename The name of the file 42248 * @param { Function } fn Function wrapping the code of the worker 42249 */ 42250 42251 function shimWorker(filename, fn) { 42252 return function ShimWorker(forceFallback) { 42253 var o = this; 42254 42255 if (!fn) { 42256 return new Worker(filename); 42257 } else if (Worker && !forceFallback) { 42258 // Convert the function's inner code to a string to construct the worker 42259 var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1), 42260 objURL = createSourceObject(source); 42261 this[TARGET] = new Worker(objURL); 42262 wrapTerminate(this[TARGET], objURL); 42263 return this[TARGET]; 42264 } else { 42265 var selfShim = { 42266 postMessage: function postMessage(m) { 42267 if (o.onmessage) { 42268 setTimeout(function () { 42269 o.onmessage({ 42270 data: m, 42271 target: selfShim 42272 }); 42273 }); 42274 } 42275 } 42276 }; 42277 fn.call(selfShim); 42278 42279 this.postMessage = function (m) { 42280 setTimeout(function () { 42281 selfShim.onmessage({ 42282 data: m, 42283 target: o 42284 }); 42285 }); 42286 }; 42287 42288 this.isThisThread = true; 42289 } 42290 }; 42291 } // Test Worker capabilities 42292 42293 42294 if (Worker) { 42295 var testWorker, 42296 objURL = createSourceObject('self.onmessage = function () {}'), 42297 testArray = new Uint8Array(1); 42298 42299 try { 42300 testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it 42301 42302 testWorker.postMessage(testArray, [testArray.buffer]); 42303 } catch (e) { 42304 Worker = null; 42305 } finally { 42306 URL.revokeObjectURL(objURL); 42307 42308 if (testWorker) { 42309 testWorker.terminate(); 42310 } 42311 } 42312 } 42313 42314 function createSourceObject(str) { 42315 try { 42316 return URL.createObjectURL(new Blob([str], { 42317 type: SCRIPT_TYPE 42318 })); 42319 } catch (e) { 42320 var blob = new BlobBuilder(); 42321 blob.append(str); 42322 return URL.createObjectURL(blob.getBlob(type)); 42323 } 42324 } 42325 42326 function wrapTerminate(worker, objURL) { 42327 if (!worker || !objURL) return; 42328 var term = worker.terminate; 42329 worker.objURL = objURL; 42330 42331 worker.terminate = function () { 42332 if (worker.objURL) URL.revokeObjectURL(worker.objURL); 42333 term.call(worker); 42334 }; 42335 } 42336 42337 var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) { 42338 var self = this; 42339 42340 var transmuxerWorker = function () { 42341 /** 42342 * mux.js 42343 * 42344 * Copyright (c) Brightcove 42345 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 42346 * 42347 * A lightweight readable stream implemention that handles event dispatching. 42348 * Objects that inherit from streams should call init in their constructors. 42349 */ 42350 var Stream = function Stream() { 42351 this.init = function () { 42352 var listeners = {}; 42353 /** 42354 * Add a listener for a specified event type. 42355 * @param type {string} the event name 42356 * @param listener {function} the callback to be invoked when an event of 42357 * the specified type occurs 42358 */ 42359 42360 this.on = function (type, listener) { 42361 if (!listeners[type]) { 42362 listeners[type] = []; 42363 } 42364 42365 listeners[type] = listeners[type].concat(listener); 42366 }; 42367 /** 42368 * Remove a listener for a specified event type. 42369 * @param type {string} the event name 42370 * @param listener {function} a function previously registered for this 42371 * type of event through `on` 42372 */ 42373 42374 42375 this.off = function (type, listener) { 42376 var index; 42377 42378 if (!listeners[type]) { 42379 return false; 42380 } 42381 42382 index = listeners[type].indexOf(listener); 42383 listeners[type] = listeners[type].slice(); 42384 listeners[type].splice(index, 1); 42385 return index > -1; 42386 }; 42387 /** 42388 * Trigger an event of the specified type on this stream. Any additional 42389 * arguments to this function are passed as parameters to event listeners. 42390 * @param type {string} the event name 42391 */ 42392 42393 42394 this.trigger = function (type) { 42395 var callbacks, i, length, args; 42396 callbacks = listeners[type]; 42397 42398 if (!callbacks) { 42399 return; 42400 } // Slicing the arguments on every invocation of this method 42401 // can add a significant amount of overhead. Avoid the 42402 // intermediate object creation for the common case of a 42403 // single callback argument 42404 42405 42406 if (arguments.length === 2) { 42407 length = callbacks.length; 42408 42409 for (i = 0; i < length; ++i) { 42410 callbacks[i].call(this, arguments[1]); 42411 } 42412 } else { 42413 args = []; 42414 i = arguments.length; 42415 42416 for (i = 1; i < arguments.length; ++i) { 42417 args.push(arguments[i]); 42418 } 42419 42420 length = callbacks.length; 42421 42422 for (i = 0; i < length; ++i) { 42423 callbacks[i].apply(this, args); 42424 } 42425 } 42426 }; 42427 /** 42428 * Destroys the stream and cleans up. 42429 */ 42430 42431 42432 this.dispose = function () { 42433 listeners = {}; 42434 }; 42435 }; 42436 }; 42437 /** 42438 * Forwards all `data` events on this stream to the destination stream. The 42439 * destination stream should provide a method `push` to receive the data 42440 * events as they arrive. 42441 * @param destination {stream} the stream that will receive all `data` events 42442 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 42443 * when the current stream emits a 'done' event 42444 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 42445 */ 42446 42447 42448 Stream.prototype.pipe = function (destination) { 42449 this.on('data', function (data) { 42450 destination.push(data); 42451 }); 42452 this.on('done', function (flushSource) { 42453 destination.flush(flushSource); 42454 }); 42455 this.on('partialdone', function (flushSource) { 42456 destination.partialFlush(flushSource); 42457 }); 42458 this.on('endedtimeline', function (flushSource) { 42459 destination.endTimeline(flushSource); 42460 }); 42461 this.on('reset', function (flushSource) { 42462 destination.reset(flushSource); 42463 }); 42464 return destination; 42465 }; // Default stream functions that are expected to be overridden to perform 42466 // actual work. These are provided by the prototype as a sort of no-op 42467 // implementation so that we don't have to check for their existence in the 42468 // `pipe` function above. 42469 42470 42471 Stream.prototype.push = function (data) { 42472 this.trigger('data', data); 42473 }; 42474 42475 Stream.prototype.flush = function (flushSource) { 42476 this.trigger('done', flushSource); 42477 }; 42478 42479 Stream.prototype.partialFlush = function (flushSource) { 42480 this.trigger('partialdone', flushSource); 42481 }; 42482 42483 Stream.prototype.endTimeline = function (flushSource) { 42484 this.trigger('endedtimeline', flushSource); 42485 }; 42486 42487 Stream.prototype.reset = function (flushSource) { 42488 this.trigger('reset', flushSource); 42489 }; 42490 42491 var stream = Stream; 42492 /** 42493 * mux.js 42494 * 42495 * Copyright (c) Brightcove 42496 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 42497 * 42498 * Functions that generate fragmented MP4s suitable for use with Media 42499 * Source Extensions. 42500 */ 42501 42502 var UINT32_MAX = Math.pow(2, 32) - 1; 42503 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 42504 42505 (function () { 42506 var i; 42507 types = { 42508 avc1: [], 42509 // codingname 42510 avcC: [], 42511 btrt: [], 42512 dinf: [], 42513 dref: [], 42514 esds: [], 42515 ftyp: [], 42516 hdlr: [], 42517 mdat: [], 42518 mdhd: [], 42519 mdia: [], 42520 mfhd: [], 42521 minf: [], 42522 moof: [], 42523 moov: [], 42524 mp4a: [], 42525 // codingname 42526 mvex: [], 42527 mvhd: [], 42528 pasp: [], 42529 sdtp: [], 42530 smhd: [], 42531 stbl: [], 42532 stco: [], 42533 stsc: [], 42534 stsd: [], 42535 stsz: [], 42536 stts: [], 42537 styp: [], 42538 tfdt: [], 42539 tfhd: [], 42540 traf: [], 42541 trak: [], 42542 trun: [], 42543 trex: [], 42544 tkhd: [], 42545 vmhd: [] 42546 }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 42547 // don't throw an error 42548 42549 if (typeof Uint8Array === 'undefined') { 42550 return; 42551 } 42552 42553 for (i in types) { 42554 if (types.hasOwnProperty(i)) {
vendor: 16,384 bytes, lines 42555-42923
42555 types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; 42556 } 42557 } 42558 42559 MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]); 42560 AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]); 42561 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 42562 VIDEO_HDLR = new Uint8Array([0x00, // version 0 42563 0x00, 0x00, 0x00, // flags 42564 0x00, 0x00, 0x00, 0x00, // pre_defined 42565 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 42566 0x00, 0x00, 0x00, 0x00, // reserved 42567 0x00, 0x00, 0x00, 0x00, // reserved 42568 0x00, 0x00, 0x00, 0x00, // reserved 42569 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 42570 ]); 42571 AUDIO_HDLR = new Uint8Array([0x00, // version 0 42572 0x00, 0x00, 0x00, // flags 42573 0x00, 0x00, 0x00, 0x00, // pre_defined 42574 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 42575 0x00, 0x00, 0x00, 0x00, // reserved 42576 0x00, 0x00, 0x00, 0x00, // reserved 42577 0x00, 0x00, 0x00, 0x00, // reserved 42578 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 42579 ]); 42580 HDLR_TYPES = { 42581 video: VIDEO_HDLR, 42582 audio: AUDIO_HDLR 42583 }; 42584 DREF = new Uint8Array([0x00, // version 0 42585 0x00, 0x00, 0x00, // flags 42586 0x00, 0x00, 0x00, 0x01, // entry_count 42587 0x00, 0x00, 0x00, 0x0c, // entry_size 42588 0x75, 0x72, 0x6c, 0x20, // 'url' type 42589 0x00, // version 0 42590 0x00, 0x00, 0x01 // entry_flags 42591 ]); 42592 SMHD = new Uint8Array([0x00, // version 42593 0x00, 0x00, 0x00, // flags 42594 0x00, 0x00, // balance, 0 means centered 42595 0x00, 0x00 // reserved 42596 ]); 42597 STCO = new Uint8Array([0x00, // version 42598 0x00, 0x00, 0x00, // flags 42599 0x00, 0x00, 0x00, 0x00 // entry_count 42600 ]); 42601 STSC = STCO; 42602 STSZ = new Uint8Array([0x00, // version 42603 0x00, 0x00, 0x00, // flags 42604 0x00, 0x00, 0x00, 0x00, // sample_size 42605 0x00, 0x00, 0x00, 0x00 // sample_count 42606 ]); 42607 STTS = STCO; 42608 VMHD = new Uint8Array([0x00, // version 42609 0x00, 0x00, 0x01, // flags 42610 0x00, 0x00, // graphicsmode 42611 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor 42612 ]); 42613 })(); 42614 42615 box = function box(type) { 42616 var payload = [], 42617 size = 0, 42618 i, 42619 result, 42620 view; 42621 42622 for (i = 1; i < arguments.length; i++) { 42623 payload.push(arguments[i]); 42624 } 42625 42626 i = payload.length; // calculate the total size we need to allocate 42627 42628 while (i--) { 42629 size += payload[i].byteLength; 42630 } 42631 42632 result = new Uint8Array(size + 8); 42633 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 42634 view.setUint32(0, result.byteLength); 42635 result.set(type, 4); // copy the payload into the result 42636 42637 for (i = 0, size = 8; i < payload.length; i++) { 42638 result.set(payload[i], size); 42639 size += payload[i].byteLength; 42640 } 42641 42642 return result; 42643 }; 42644 42645 dinf = function dinf() { 42646 return box(types.dinf, box(types.dref, DREF)); 42647 }; 42648 42649 esds = function esds(track) { 42650 return box(types.esds, new Uint8Array([0x00, // version 42651 0x00, 0x00, 0x00, // flags 42652 // ES_Descriptor 42653 0x03, // tag, ES_DescrTag 42654 0x19, // length 42655 0x00, 0x00, // ES_ID 42656 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 42657 // DecoderConfigDescriptor 42658 0x04, // tag, DecoderConfigDescrTag 42659 0x11, // length 42660 0x40, // object type 42661 0x15, // streamType 42662 0x00, 0x06, 0x00, // bufferSizeDB 42663 0x00, 0x00, 0xda, 0xc0, // maxBitrate 42664 0x00, 0x00, 0xda, 0xc0, // avgBitrate 42665 // DecoderSpecificInfo 42666 0x05, // tag, DecoderSpecificInfoTag 42667 0x02, // length 42668 // ISO/IEC 14496-3, AudioSpecificConfig 42669 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 42670 track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig 42671 ])); 42672 }; 42673 42674 ftyp = function ftyp() { 42675 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 42676 }; 42677 42678 hdlr = function hdlr(type) { 42679 return box(types.hdlr, HDLR_TYPES[type]); 42680 }; 42681 42682 mdat = function mdat(data) { 42683 return box(types.mdat, data); 42684 }; 42685 42686 mdhd = function mdhd(track) { 42687 var result = new Uint8Array([0x00, // version 0 42688 0x00, 0x00, 0x00, // flags 42689 0x00, 0x00, 0x00, 0x02, // creation_time 42690 0x00, 0x00, 0x00, 0x03, // modification_time 42691 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 42692 track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration 42693 0x55, 0xc4, // 'und' language (undetermined) 42694 0x00, 0x00]); // Use the sample rate from the track metadata, when it is 42695 // defined. The sample rate can be parsed out of an ADTS header, for 42696 // instance. 42697 42698 if (track.samplerate) { 42699 result[12] = track.samplerate >>> 24 & 0xFF; 42700 result[13] = track.samplerate >>> 16 & 0xFF; 42701 result[14] = track.samplerate >>> 8 & 0xFF; 42702 result[15] = track.samplerate & 0xFF; 42703 } 42704 42705 return box(types.mdhd, result); 42706 }; 42707 42708 mdia = function mdia(track) { 42709 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 42710 }; 42711 42712 mfhd = function mfhd(sequenceNumber) { 42713 return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags 42714 (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number 42715 ])); 42716 }; 42717 42718 minf = function minf(track) { 42719 return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track)); 42720 }; 42721 42722 moof = function moof(sequenceNumber, tracks) { 42723 var trackFragments = [], 42724 i = tracks.length; // build traf boxes for each track fragment 42725 42726 while (i--) { 42727 trackFragments[i] = traf(tracks[i]); 42728 } 42729 42730 return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments)); 42731 }; 42732 /** 42733 * Returns a movie box. 42734 * @param tracks {array} the tracks associated with this movie 42735 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 42736 */ 42737 42738 42739 moov = function moov(tracks) { 42740 var i = tracks.length, 42741 boxes = []; 42742 42743 while (i--) { 42744 boxes[i] = trak(tracks[i]); 42745 } 42746 42747 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 42748 }; 42749 42750 mvex = function mvex(tracks) { 42751 var i = tracks.length, 42752 boxes = []; 42753 42754 while (i--) { 42755 boxes[i] = trex(tracks[i]); 42756 } 42757 42758 return box.apply(null, [types.mvex].concat(boxes)); 42759 }; 42760 42761 mvhd = function mvhd(duration) { 42762 var bytes = new Uint8Array([0x00, // version 0 42763 0x00, 0x00, 0x00, // flags 42764 0x00, 0x00, 0x00, 0x01, // creation_time 42765 0x00, 0x00, 0x00, 0x02, // modification_time 42766 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 42767 (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration 42768 0x00, 0x01, 0x00, 0x00, // 1.0 rate 42769 0x01, 0x00, // 1.0 volume 42770 0x00, 0x00, // reserved 42771 0x00, 0x00, 0x00, 0x00, // reserved 42772 0x00, 0x00, 0x00, 0x00, // reserved 42773 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 42774 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 42775 0xff, 0xff, 0xff, 0xff // next_track_ID 42776 ]); 42777 return box(types.mvhd, bytes); 42778 }; 42779 42780 sdtp = function sdtp(track) { 42781 var samples = track.samples || [], 42782 bytes = new Uint8Array(4 + samples.length), 42783 flags, 42784 i; // leave the full box header (4 bytes) all zero 42785 // write the sample table 42786 42787 for (i = 0; i < samples.length; i++) { 42788 flags = samples[i].flags; 42789 bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; 42790 } 42791 42792 return box(types.sdtp, bytes); 42793 }; 42794 42795 stbl = function stbl(track) { 42796 return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO)); 42797 }; 42798 42799 (function () { 42800 var videoSample, audioSample; 42801 42802 stsd = function stsd(track) { 42803 return box(types.stsd, new Uint8Array([0x00, // version 0 42804 0x00, 0x00, 0x00, // flags 42805 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track)); 42806 }; 42807 42808 videoSample = function videoSample(track) { 42809 var sps = track.sps || [], 42810 pps = track.pps || [], 42811 sequenceParameterSets = [], 42812 pictureParameterSets = [], 42813 i, 42814 avc1Box; // assemble the SPSs 42815 42816 for (i = 0; i < sps.length; i++) { 42817 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 42818 sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength 42819 42820 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 42821 } // assemble the PPSs 42822 42823 42824 for (i = 0; i < pps.length; i++) { 42825 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 42826 pictureParameterSets.push(pps[i].byteLength & 0xFF); 42827 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 42828 } 42829 42830 avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 42831 0x00, 0x01, // data_reference_index 42832 0x00, 0x00, // pre_defined 42833 0x00, 0x00, // reserved 42834 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 42835 (track.width & 0xff00) >> 8, track.width & 0xff, // width 42836 (track.height & 0xff00) >> 8, track.height & 0xff, // height 42837 0x00, 0x48, 0x00, 0x00, // horizresolution 42838 0x00, 0x48, 0x00, 0x00, // vertresolution 42839 0x00, 0x00, 0x00, 0x00, // reserved 42840 0x00, 0x01, // frame_count 42841 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 42842 0x00, 0x18, // depth = 24 42843 0x11, 0x11 // pre_defined = -1 42844 ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion 42845 track.profileIdc, // AVCProfileIndication 42846 track.profileCompatibility, // profile_compatibility 42847 track.levelIdc, // AVCLevelIndication 42848 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 42849 ].concat([sps.length], // numOfSequenceParameterSets 42850 sequenceParameterSets, // "SPS" 42851 [pps.length], // numOfPictureParameterSets 42852 pictureParameterSets // "PPS" 42853 ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 42854 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 42855 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate 42856 ]))]; 42857 42858 if (track.sarRatio) { 42859 var hSpacing = track.sarRatio[0], 42860 vSpacing = track.sarRatio[1]; 42861 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]))); 42862 } 42863 42864 return box.apply(null, avc1Box); 42865 }; 42866 42867 audioSample = function audioSample(track) { 42868 return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12 42869 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 42870 0x00, 0x01, // data_reference_index 42871 // AudioSampleEntry, ISO/IEC 14496-12 42872 0x00, 0x00, 0x00, 0x00, // reserved 42873 0x00, 0x00, 0x00, 0x00, // reserved 42874 (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount 42875 (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize 42876 0x00, 0x00, // pre_defined 42877 0x00, 0x00, // reserved 42878 (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16 42879 // MP4AudioSampleEntry, ISO/IEC 14496-14 42880 ]), esds(track)); 42881 }; 42882 })(); 42883 42884 tkhd = function tkhd(track) { 42885 var result = new Uint8Array([0x00, // version 0 42886 0x00, 0x00, 0x07, // flags 42887 0x00, 0x00, 0x00, 0x00, // creation_time 42888 0x00, 0x00, 0x00, 0x00, // modification_time 42889 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 42890 0x00, 0x00, 0x00, 0x00, // reserved 42891 (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration 42892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 42893 0x00, 0x00, // layer 42894 0x00, 0x00, // alternate_group 42895 0x01, 0x00, // non-audio track volume 42896 0x00, 0x00, // reserved 42897 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 42898 (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width 42899 (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height 42900 ]); 42901 return box(types.tkhd, result); 42902 }; 42903 /** 42904 * Generate a track fragment (traf) box. A traf box collects metadata 42905 * about tracks in a movie fragment (moof) box. 42906 */ 42907 42908 42909 traf = function traf(track) { 42910 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 42911 trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0 42912 0x00, 0x00, 0x3a, // flags 42913 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 42914 0x00, 0x00, 0x00, 0x01, // sample_description_index 42915 0x00, 0x00, 0x00, 0x00, // default_sample_duration 42916 0x00, 0x00, 0x00, 0x00, // default_sample_size 42917 0x00, 0x00, 0x00, 0x00 // default_sample_flags 42918 ])); 42919 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 42920 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 42921 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1 42922 0x00, 0x00, 0x00, // flags 42923
vendor: 9,302 bytes, lines 42923-43128
42923 // baseMediaDecodeTime 42924 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 42925 // the containing moof to the first payload byte of the associated 42926 // mdat 42927 42928 dataOffset = 32 + // tfhd 42929 20 + // tfdt 42930 8 + // traf header 42931 16 + // mfhd 42932 8 + // moof header 42933 8; // mdat header 42934 // audio tracks require less metadata 42935 42936 if (track.type === 'audio') { 42937 trackFragmentRun = trun(track, dataOffset); 42938 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun); 42939 } // video tracks should contain an independent and disposable samples 42940 // box (sdtp) 42941 // generate one and adjust offsets to match 42942 42943 42944 sampleDependencyTable = sdtp(track); 42945 trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset); 42946 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable); 42947 }; 42948 /** 42949 * Generate a track box. 42950 * @param track {object} a track definition 42951 * @return {Uint8Array} the track box 42952 */ 42953 42954 42955 trak = function trak(track) { 42956 track.duration = track.duration || 0xffffffff; 42957 return box(types.trak, tkhd(track), mdia(track)); 42958 }; 42959 42960 trex = function trex(track) { 42961 var result = new Uint8Array([0x00, // version 0 42962 0x00, 0x00, 0x00, // flags 42963 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 42964 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 42965 0x00, 0x00, 0x00, 0x00, // default_sample_duration 42966 0x00, 0x00, 0x00, 0x00, // default_sample_size 42967 0x00, 0x01, 0x00, 0x01 // default_sample_flags 42968 ]); // the last two bytes of default_sample_flags is the sample 42969 // degradation priority, a hint about the importance of this sample 42970 // relative to others. Lower the degradation priority for all sample 42971 // types other than video. 42972 42973 if (track.type !== 'video') { 42974 result[result.length - 1] = 0x00; 42975 } 42976 42977 return box(types.trex, result); 42978 }; 42979 42980 (function () { 42981 var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a 42982 // duration is present for the first sample, it will be present for 42983 // all subsequent samples. 42984 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 42985 42986 trunHeader = function trunHeader(samples, offset) { 42987 var durationPresent = 0, 42988 sizePresent = 0, 42989 flagsPresent = 0, 42990 compositionTimeOffset = 0; // trun flag constants 42991 42992 if (samples.length) { 42993 if (samples[0].duration !== undefined) { 42994 durationPresent = 0x1; 42995 } 42996 42997 if (samples[0].size !== undefined) { 42998 sizePresent = 0x2; 42999 } 43000 43001 if (samples[0].flags !== undefined) { 43002 flagsPresent = 0x4; 43003 } 43004 43005 if (samples[0].compositionTimeOffset !== undefined) { 43006 compositionTimeOffset = 0x8; 43007 } 43008 } 43009 43010 return [0x00, // version 0 43011 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags 43012 (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count 43013 (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset 43014 ]; 43015 }; 43016 43017 videoTrun = function videoTrun(track, offset) { 43018 var bytes, samples, sample, i; 43019 samples = track.samples || []; 43020 offset += 8 + 12 + 16 * samples.length; 43021 bytes = trunHeader(samples, offset); 43022 43023 for (i = 0; i < samples.length; i++) { 43024 sample = samples[i]; 43025 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 43026 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size 43027 sample.flags.isLeading << 2 | sample.flags.dependsOn, sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample, sample.flags.degradationPriority & 0xF0 << 8, sample.flags.degradationPriority & 0x0F, // sample_flags 43028 (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset 43029 ]); 43030 } 43031 43032 return box(types.trun, new Uint8Array(bytes)); 43033 }; 43034 43035 audioTrun = function audioTrun(track, offset) { 43036 var bytes, samples, sample, i; 43037 samples = track.samples || []; 43038 offset += 8 + 12 + 8 * samples.length; 43039 bytes = trunHeader(samples, offset); 43040 43041 for (i = 0; i < samples.length; i++) { 43042 sample = samples[i]; 43043 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 43044 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size 43045 } 43046 43047 return box(types.trun, new Uint8Array(bytes)); 43048 }; 43049 43050 trun = function trun(track, offset) { 43051 if (track.type === 'audio') { 43052 return audioTrun(track, offset); 43053 } 43054 43055 return videoTrun(track, offset); 43056 }; 43057 })(); 43058 43059 var mp4Generator = { 43060 ftyp: ftyp, 43061 mdat: mdat, 43062 moof: moof, 43063 moov: moov, 43064 initSegment: function initSegment(tracks) { 43065 var fileType = ftyp(), 43066 movie = moov(tracks), 43067 result; 43068 result = new Uint8Array(fileType.byteLength + movie.byteLength); 43069 result.set(fileType); 43070 result.set(movie, fileType.byteLength); 43071 return result; 43072 } 43073 }; 43074 /** 43075 * mux.js 43076 * 43077 * Copyright (c) Brightcove 43078 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43079 */ 43080 // Convert an array of nal units into an array of frames with each frame being 43081 // composed of the nal units that make up that frame 43082 // Also keep track of cummulative data about the frame from the nal units such 43083 // as the frame duration, starting pts, etc. 43084 43085 var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) { 43086 var i, 43087 currentNal, 43088 currentFrame = [], 43089 frames = []; // TODO added for LHLS, make sure this is OK 43090 43091 frames.byteLength = 0; 43092 frames.nalCount = 0; 43093 frames.duration = 0; 43094 currentFrame.byteLength = 0; 43095 43096 for (i = 0; i < nalUnits.length; i++) { 43097 currentNal = nalUnits[i]; // Split on 'aud'-type nal units 43098 43099 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 43100 // Since the very first nal unit is expected to be an AUD 43101 // only push to the frames array when currentFrame is not empty 43102 if (currentFrame.length) { 43103 currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK 43104 43105 frames.byteLength += currentFrame.byteLength; 43106 frames.nalCount += currentFrame.length; 43107 frames.duration += currentFrame.duration; 43108 frames.push(currentFrame); 43109 } 43110 43111 currentFrame = [currentNal]; 43112 currentFrame.byteLength = currentNal.data.byteLength; 43113 currentFrame.pts = currentNal.pts; 43114 currentFrame.dts = currentNal.dts; 43115 } else { 43116 // Specifically flag key frames for ease of use later 43117 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 43118 currentFrame.keyFrame = true; 43119 } 43120 43121 currentFrame.duration = currentNal.dts - currentFrame.dts; 43122 currentFrame.byteLength += currentNal.data.byteLength; 43123 currentFrame.push(currentNal); 43124 } 43125 } // For the last frame, use the duration of the previous frame if we 43126 // have nothing better to go on 43127 43128
vendor: 4,818 bytes, lines 43129-43257
43129 if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) { 43130 currentFrame.duration = frames[frames.length - 1].duration; 43131 } // Push the final frame 43132 // TODO added for LHLS, make sure this is OK 43133 43134 43135 frames.byteLength += currentFrame.byteLength; 43136 frames.nalCount += currentFrame.length; 43137 frames.duration += currentFrame.duration; 43138 frames.push(currentFrame); 43139 return frames; 43140 }; // Convert an array of frames into an array of Gop with each Gop being composed 43141 // of the frames that make up that Gop 43142 // Also keep track of cummulative data about the Gop from the frames such as the 43143 // Gop duration, starting pts, etc. 43144 43145 43146 var groupFramesIntoGops = function groupFramesIntoGops(frames) { 43147 var i, 43148 currentFrame, 43149 currentGop = [], 43150 gops = []; // We must pre-set some of the values on the Gop since we 43151 // keep running totals of these values 43152 43153 currentGop.byteLength = 0; 43154 currentGop.nalCount = 0; 43155 currentGop.duration = 0; 43156 currentGop.pts = frames[0].pts; 43157 currentGop.dts = frames[0].dts; // store some metadata about all the Gops 43158 43159 gops.byteLength = 0; 43160 gops.nalCount = 0; 43161 gops.duration = 0; 43162 gops.pts = frames[0].pts; 43163 gops.dts = frames[0].dts; 43164 43165 for (i = 0; i < frames.length; i++) { 43166 currentFrame = frames[i]; 43167 43168 if (currentFrame.keyFrame) { 43169 // Since the very first frame is expected to be an keyframe 43170 // only push to the gops array when currentGop is not empty 43171 if (currentGop.length) { 43172 gops.push(currentGop); 43173 gops.byteLength += currentGop.byteLength; 43174 gops.nalCount += currentGop.nalCount; 43175 gops.duration += currentGop.duration; 43176 } 43177 43178 currentGop = [currentFrame]; 43179 currentGop.nalCount = currentFrame.length; 43180 currentGop.byteLength = currentFrame.byteLength; 43181 currentGop.pts = currentFrame.pts; 43182 currentGop.dts = currentFrame.dts; 43183 currentGop.duration = currentFrame.duration; 43184 } else { 43185 currentGop.duration += currentFrame.duration; 43186 currentGop.nalCount += currentFrame.length; 43187 currentGop.byteLength += currentFrame.byteLength; 43188 currentGop.push(currentFrame); 43189 } 43190 } 43191 43192 if (gops.length && currentGop.duration <= 0) { 43193 currentGop.duration = gops[gops.length - 1].duration; 43194 } 43195 43196 gops.byteLength += currentGop.byteLength; 43197 gops.nalCount += currentGop.nalCount; 43198 gops.duration += currentGop.duration; // push the final Gop 43199 43200 gops.push(currentGop); 43201 return gops; 43202 }; 43203 /* 43204 * Search for the first keyframe in the GOPs and throw away all frames 43205 * until that keyframe. Then extend the duration of the pulled keyframe 43206 * and pull the PTS and DTS of the keyframe so that it covers the time 43207 * range of the frames that were disposed. 43208 * 43209 * @param {Array} gops video GOPs 43210 * @returns {Array} modified video GOPs 43211 */ 43212 43213 43214 var extendFirstKeyFrame = function extendFirstKeyFrame(gops) { 43215 var currentGop; 43216 43217 if (!gops[0][0].keyFrame && gops.length > 1) { 43218 // Remove the first GOP 43219 currentGop = gops.shift(); 43220 gops.byteLength -= currentGop.byteLength; 43221 gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the 43222 // first gop to cover the time period of the 43223 // frames we just removed 43224 43225 gops[0][0].dts = currentGop.dts; 43226 gops[0][0].pts = currentGop.pts; 43227 gops[0][0].duration += currentGop.duration; 43228 } 43229 43230 return gops; 43231 }; 43232 /** 43233 * Default sample object 43234 * see ISO/IEC 14496-12:2012, section 8.6.4.3 43235 */ 43236 43237 43238 var createDefaultSample = function createDefaultSample() { 43239 return { 43240 size: 0, 43241 flags: { 43242 isLeading: 0, 43243 dependsOn: 1, 43244 isDependedOn: 0, 43245 hasRedundancy: 0, 43246 degradationPriority: 0, 43247 isNonSyncSample: 1 43248 } 43249 }; 43250 }; 43251 /* 43252 * Collates information from a video frame into an object for eventual 43253 * entry into an MP4 sample table. 43254 * 43255 * @param {Object} frame the video frame 43256 * @param {Number} dataOffset the byte offset to position the sample 43257 * @return {Object}
vendor: 7,934 bytes, lines 43257-43467
43257 object containing sample table info for a frame 43258 */ 43259 43260 43261 var sampleForFrame = function sampleForFrame(frame, dataOffset) { 43262 var sample = createDefaultSample(); 43263 sample.dataOffset = dataOffset; 43264 sample.compositionTimeOffset = frame.pts - frame.dts; 43265 sample.duration = frame.duration; 43266 sample.size = 4 * frame.length; // Space for nal unit size 43267 43268 sample.size += frame.byteLength; 43269 43270 if (frame.keyFrame) { 43271 sample.flags.dependsOn = 2; 43272 sample.flags.isNonSyncSample = 0; 43273 } 43274 43275 return sample; 43276 }; // generate the track's sample table from an array of gops 43277 43278 43279 var generateSampleTable = function generateSampleTable(gops, baseDataOffset) { 43280 var h, 43281 i, 43282 sample, 43283 currentGop, 43284 currentFrame, 43285 dataOffset = baseDataOffset || 0, 43286 samples = []; 43287 43288 for (h = 0; h < gops.length; h++) { 43289 currentGop = gops[h]; 43290 43291 for (i = 0; i < currentGop.length; i++) { 43292 currentFrame = currentGop[i]; 43293 sample = sampleForFrame(currentFrame, dataOffset); 43294 dataOffset += sample.size; 43295 samples.push(sample); 43296 } 43297 } 43298 43299 return samples; 43300 }; // generate the track's raw mdat data from an array of gops 43301 43302 43303 var concatenateNalData = function concatenateNalData(gops) { 43304 var h, 43305 i, 43306 j, 43307 currentGop, 43308 currentFrame, 43309 currentNal, 43310 dataOffset = 0, 43311 nalsByteLength = gops.byteLength, 43312 numberOfNals = gops.nalCount, 43313 totalByteLength = nalsByteLength + 4 * numberOfNals, 43314 data = new Uint8Array(totalByteLength), 43315 view = new DataView(data.buffer); // For each Gop.. 43316 43317 for (h = 0; h < gops.length; h++) { 43318 currentGop = gops[h]; // For each Frame.. 43319 43320 for (i = 0; i < currentGop.length; i++) { 43321 currentFrame = currentGop[i]; // For each NAL.. 43322 43323 for (j = 0; j < currentFrame.length; j++) { 43324 currentNal = currentFrame[j]; 43325 view.setUint32(dataOffset, currentNal.data.byteLength); 43326 dataOffset += 4; 43327 data.set(currentNal.data, dataOffset); 43328 dataOffset += currentNal.data.byteLength; 43329 } 43330 } 43331 } 43332 43333 return data; 43334 }; // generate the track's sample table from a frame 43335 43336 43337 var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) { 43338 var sample, 43339 dataOffset = baseDataOffset || 0, 43340 samples = []; 43341 sample = sampleForFrame(frame, dataOffset); 43342 samples.push(sample); 43343 return samples; 43344 }; // generate the track's raw mdat data from a frame 43345 43346 43347 var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) { 43348 var i, 43349 currentNal, 43350 dataOffset = 0, 43351 nalsByteLength = frame.byteLength, 43352 numberOfNals = frame.length, 43353 totalByteLength = nalsByteLength + 4 * numberOfNals, 43354 data = new Uint8Array(totalByteLength), 43355 view = new DataView(data.buffer); // For each NAL.. 43356 43357 for (i = 0; i < frame.length; i++) { 43358 currentNal = frame[i]; 43359 view.setUint32(dataOffset, currentNal.data.byteLength); 43360 dataOffset += 4; 43361 data.set(currentNal.data, dataOffset); 43362 dataOffset += currentNal.data.byteLength; 43363 } 43364 43365 return data; 43366 }; 43367 43368 var frameUtils = { 43369 groupNalsIntoFrames: groupNalsIntoFrames, 43370 groupFramesIntoGops: groupFramesIntoGops, 43371 extendFirstKeyFrame: extendFirstKeyFrame, 43372 generateSampleTable: generateSampleTable, 43373 concatenateNalData: concatenateNalData, 43374 generateSampleTableForFrame: generateSampleTableForFrame, 43375 concatenateNalDataForFrame: concatenateNalDataForFrame 43376 }; 43377 /** 43378 * mux.js 43379 * 43380 * Copyright (c) Brightcove 43381 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43382 */ 43383 43384 var highPrefix = [33, 16, 5, 32, 164, 27]; 43385 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 43386 43387 var zeroFill = function zeroFill(count) { 43388 var a = []; 43389 43390 while (count--) { 43391 a.push(0); 43392 } 43393 43394 return a; 43395 }; 43396 43397 var makeTable = function makeTable(metaTable) { 43398 return Object.keys(metaTable).reduce(function (obj, key) { 43399 obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) { 43400 return arr.concat(part); 43401 }, [])); 43402 return obj; 43403 }, {}); 43404 }; // Frames-of-silence to use for filling in missing AAC frames 43405 43406 43407 var coneOfSilence = { 43408 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 43409 88200: [highPrefix, [231], zeroFill(170), [56]], 43410 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 43411 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 43412 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 43413 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 43414 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 43415 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 43416 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]], 43417 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]], 43418 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 43419 }; 43420 var silence = makeTable(coneOfSilence); 43421 /** 43422 * mux.js 43423 * 43424 * Copyright (c) Brightcove 43425 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43426 */ 43427 43428 var ONE_SECOND_IN_TS = 90000, 43429 // 90kHz clock 43430 secondsToVideoTs, 43431 secondsToAudioTs, 43432 videoTsToSeconds, 43433 audioTsToSeconds, 43434 audioTsToVideoTs, 43435 videoTsToAudioTs, 43436 metadataTsToSeconds; 43437 43438 secondsToVideoTs = function secondsToVideoTs(seconds) { 43439 return seconds * ONE_SECOND_IN_TS; 43440 }; 43441 43442 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 43443 return seconds * sampleRate; 43444 }; 43445 43446 videoTsToSeconds = function videoTsToSeconds(timestamp) { 43447 return timestamp / ONE_SECOND_IN_TS; 43448 }; 43449 43450 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 43451 return timestamp / sampleRate; 43452 }; 43453 43454 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 43455 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 43456 }; 43457 43458 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 43459 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 43460 }; 43461 /** 43462 * Adjust ID3 tag or caption timing information by the timeline pts values 43463 * (if keepOriginalTimestamps is false) and convert to seconds 43464 */ 43465 43466 43467 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts,
vendor: 8,925 bytes, lines 43467-43706
43467keepOriginalTimestamps) { 43468 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 43469 }; 43470 43471 var clock = { 43472 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 43473 secondsToVideoTs: secondsToVideoTs, 43474 secondsToAudioTs: secondsToAudioTs, 43475 videoTsToSeconds: videoTsToSeconds, 43476 audioTsToSeconds: audioTsToSeconds, 43477 audioTsToVideoTs: audioTsToVideoTs, 43478 videoTsToAudioTs: videoTsToAudioTs, 43479 metadataTsToSeconds: metadataTsToSeconds 43480 }; 43481 /** 43482 * mux.js 43483 * 43484 * Copyright (c) Brightcove 43485 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43486 */ 43487 43488 /** 43489 * Sum the `byteLength` properties of the data in each AAC frame 43490 */ 43491 43492 var sumFrameByteLengths = function sumFrameByteLengths(array) { 43493 var i, 43494 currentObj, 43495 sum = 0; // sum the byteLength's all each nal unit in the frame 43496 43497 for (i = 0; i < array.length; i++) { 43498 currentObj = array[i]; 43499 sum += currentObj.data.byteLength; 43500 } 43501 43502 return sum; 43503 }; // Possibly pad (prefix) the audio track with silence if appending this track 43504 // would lead to the introduction of a gap in the audio buffer 43505 43506 43507 var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) { 43508 var baseMediaDecodeTimeTs, 43509 frameDuration = 0, 43510 audioGapDuration = 0, 43511 audioFillFrameCount = 0, 43512 audioFillDuration = 0, 43513 silentFrame, 43514 i, 43515 firstFrame; 43516 43517 if (!frames.length) { 43518 return; 43519 } 43520 43521 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills 43522 43523 frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024)); 43524 43525 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 43526 // insert the shortest possible amount (audio gap or audio to video gap) 43527 audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap 43528 43529 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 43530 audioFillDuration = audioFillFrameCount * frameDuration; 43531 } // don't attempt to fill gaps smaller than a single frame or larger 43532 // than a half second 43533 43534 43535 if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) { 43536 return; 43537 } 43538 43539 silentFrame = silence[track.samplerate]; 43540 43541 if (!silentFrame) { 43542 // we don't have a silent frame pregenerated for the sample rate, so use a frame 43543 // from the content instead 43544 silentFrame = frames[0].data; 43545 } 43546 43547 for (i = 0; i < audioFillFrameCount; i++) { 43548 firstFrame = frames[0]; 43549 frames.splice(0, 0, { 43550 data: silentFrame, 43551 dts: firstFrame.dts - frameDuration, 43552 pts: firstFrame.pts - frameDuration 43553 }); 43554 } 43555 43556 track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 43557 }; // If the audio segment extends before the earliest allowed dts 43558 // value, remove AAC frames until starts at or after the earliest 43559 // allowed DTS so that we don't end up with a negative baseMedia- 43560 // DecodeTime for the audio track 43561 43562 43563 var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) { 43564 if (track.minSegmentDts >= earliestAllowedDts) { 43565 return adtsFrames; 43566 } // We will need to recalculate the earliest segment Dts 43567 43568 43569 track.minSegmentDts = Infinity; 43570 return adtsFrames.filter(function (currentFrame) { 43571 // If this is an allowed frame, keep it and record it's Dts 43572 if (currentFrame.dts >= earliestAllowedDts) { 43573 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 43574 track.minSegmentPts = track.minSegmentDts; 43575 return true; 43576 } // Otherwise, discard it 43577 43578 43579 return false; 43580 }); 43581 }; // generate the track's raw mdat data from an array of frames 43582 43583 43584 var generateSampleTable$1 = function generateSampleTable(frames) { 43585 var i, 43586 currentFrame, 43587 samples = []; 43588 43589 for (i = 0; i < frames.length; i++) { 43590 currentFrame = frames[i]; 43591 samples.push({ 43592 size: currentFrame.data.byteLength, 43593 duration: 1024 // For AAC audio, all samples contain 1024 samples 43594 43595 }); 43596 } 43597 43598 return samples; 43599 }; // generate the track's sample table from an array of frames 43600 43601 43602 var concatenateFrameData = function concatenateFrameData(frames) { 43603 var i, 43604 currentFrame, 43605 dataOffset = 0, 43606 data = new Uint8Array(sumFrameByteLengths(frames)); 43607 43608 for (i = 0; i < frames.length; i++) { 43609 currentFrame = frames[i]; 43610 data.set(currentFrame.data, dataOffset); 43611 dataOffset += currentFrame.data.byteLength; 43612 } 43613 43614 return data; 43615 }; 43616 43617 var audioFrameUtils = { 43618 prefixWithSilence: prefixWithSilence, 43619 trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts, 43620 generateSampleTable: generateSampleTable$1, 43621 concatenateFrameData: concatenateFrameData 43622 }; 43623 /** 43624 * mux.js 43625 * 43626 * Copyright (c) Brightcove 43627 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43628 */ 43629 43630 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; 43631 /** 43632 * Store information about the start and end of the track and the 43633 * duration for each frame/sample we process in order to calculate 43634 * the baseMediaDecodeTime 43635 */ 43636 43637 var collectDtsInfo = function collectDtsInfo(track, data) { 43638 if (typeof data.pts === 'number') { 43639 if (track.timelineStartInfo.pts === undefined) { 43640 track.timelineStartInfo.pts = data.pts; 43641 } 43642 43643 if (track.minSegmentPts === undefined) { 43644 track.minSegmentPts = data.pts; 43645 } else { 43646 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 43647 } 43648 43649 if (track.maxSegmentPts === undefined) { 43650 track.maxSegmentPts = data.pts; 43651 } else { 43652 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 43653 } 43654 } 43655 43656 if (typeof data.dts === 'number') { 43657 if (track.timelineStartInfo.dts === undefined) { 43658 track.timelineStartInfo.dts = data.dts; 43659 } 43660 43661 if (track.minSegmentDts === undefined) { 43662 track.minSegmentDts = data.dts; 43663 } else { 43664 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 43665 } 43666 43667 if (track.maxSegmentDts === undefined) { 43668 track.maxSegmentDts = data.dts; 43669 } else { 43670 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 43671 } 43672 } 43673 }; 43674 /** 43675 * Clear values used to calculate the baseMediaDecodeTime between 43676 * tracks 43677 */ 43678 43679 43680 var clearDtsInfo = function clearDtsInfo(track) { 43681 delete track.minSegmentDts; 43682 delete track.maxSegmentDts; 43683 delete track.minSegmentPts; 43684 delete track.maxSegmentPts; 43685 }; 43686 /** 43687 * Calculate the track's baseMediaDecodeTime based on the earliest 43688 * DTS the transmuxer has ever seen and the minimum DTS for the 43689 * current track 43690 * @param track {object} track metadata configuration 43691 * @param keepOriginalTimestamps {boolean} If true, keep the timestamps 43692 * in the source; false to adjust the first segment to start at 0. 43693 */ 43694 43695 43696 var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) { 43697 var baseMediaDecodeTime, 43698 scale, 43699 minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero. 43700 43701 if (!keepOriginalTimestamps) { 43702 minSegmentDts -= track.timelineStartInfo.dts; 43703 } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 43704 // we want the start of the first segment to be placed 43705 43706
vendor: 9,973 bytes, lines 43707-43972
43707 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first 43708 43709 baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative 43710 43711 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 43712 43713 if (track.type === 'audio') { 43714 // Audio has a different clock equal to the sampling_rate so we need to 43715 // scale the PTS values into the clock rate of the track 43716 scale = track.samplerate / ONE_SECOND_IN_TS$1; 43717 baseMediaDecodeTime *= scale; 43718 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 43719 } 43720 43721 return baseMediaDecodeTime; 43722 }; 43723 43724 var trackDecodeInfo = { 43725 clearDtsInfo: clearDtsInfo, 43726 calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime, 43727 collectDtsInfo: collectDtsInfo 43728 }; 43729 /** 43730 * mux.js 43731 * 43732 * Copyright (c) Brightcove 43733 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43734 * 43735 * Reads in-band caption information from a video elementary 43736 * stream. Captions must follow the CEA-708 standard for injection 43737 * into an MPEG-2 transport streams. 43738 * @see https://en.wikipedia.org/wiki/CEA-708 43739 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 43740 */ 43741 // Supplemental enhancement information (SEI) NAL units have a 43742 // payload type field to indicate how they are to be 43743 // interpreted. CEAS-708 caption content is always transmitted with 43744 // payload type 0x04. 43745 43746 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 43747 RBSP_TRAILING_BITS = 128; 43748 /** 43749 * Parse a supplemental enhancement information (SEI) NAL unit. 43750 * Stops parsing once a message of type ITU T T35 has been found. 43751 * 43752 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 43753 * @return {object} the parsed SEI payload 43754 * @see Rec. ITU-T H.264, 7.3.2.3.1 43755 */ 43756 43757 var parseSei = function parseSei(bytes) { 43758 var i = 0, 43759 result = { 43760 payloadType: -1, 43761 payloadSize: 0 43762 }, 43763 payloadType = 0, 43764 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 43765 43766 while (i < bytes.byteLength) { 43767 // stop once we have hit the end of the sei_rbsp 43768 if (bytes[i] === RBSP_TRAILING_BITS) { 43769 break; 43770 } // Parse payload type 43771 43772 43773 while (bytes[i] === 0xFF) { 43774 payloadType += 255; 43775 i++; 43776 } 43777 43778 payloadType += bytes[i++]; // Parse payload size 43779 43780 while (bytes[i] === 0xFF) { 43781 payloadSize += 255; 43782 i++; 43783 } 43784 43785 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 43786 // there can only ever be one caption message in a frame's sei 43787 43788 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 43789 result.payloadType = payloadType; 43790 result.payloadSize = payloadSize; 43791 result.payload = bytes.subarray(i, i + payloadSize); 43792 break; 43793 } // skip the payload and parse the next message 43794 43795 43796 i += payloadSize; 43797 payloadType = 0; 43798 payloadSize = 0; 43799 } 43800 43801 return result; 43802 }; // see ANSI/SCTE 128-1 (2013), section 8.1 43803 43804 43805 var parseUserData = function parseUserData(sei) { 43806 // itu_t_t35_contry_code must be 181 (United States) for 43807 // captions 43808 if (sei.payload[0] !== 181) { 43809 return null; 43810 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 43811 43812 43813 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 43814 return null; 43815 } // the user_identifier should be "GA94" to indicate ATSC1 data 43816 43817 43818 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 43819 return null; 43820 } // finally, user_data_type_code should be 0x03 for caption data 43821 43822 43823 if (sei.payload[7] !== 0x03) { 43824 return null; 43825 } // return the user_data_type_structure and strip the trailing 43826 // marker bits 43827 43828 43829 return sei.payload.subarray(8, sei.payload.length - 1); 43830 }; // see CEA-708-D, section 4.4 43831 43832 43833 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 43834 var results = [], 43835 i, 43836 count, 43837 offset, 43838 data; // if this is just filler, return immediately 43839 43840 if (!(userData[0] & 0x40)) { 43841 return results; 43842 } // parse out the cc_data_1 and cc_data_2 fields 43843 43844 43845 count = userData[0] & 0x1f; 43846 43847 for (i = 0; i < count; i++) { 43848 offset = i * 3; 43849 data = { 43850 type: userData[offset + 2] & 0x03, 43851 pts: pts 43852 }; // capture cc data when cc_valid is 1 43853 43854 if (userData[offset + 2] & 0x04) { 43855 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 43856 results.push(data); 43857 } 43858 } 43859 43860 return results; 43861 }; 43862 43863 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 43864 var length = data.byteLength, 43865 emulationPreventionBytesPositions = [], 43866 i = 1, 43867 newLength, 43868 newData; // Find all `Emulation Prevention Bytes` 43869 43870 while (i < length - 2) { 43871 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 43872 emulationPreventionBytesPositions.push(i + 2); 43873 i += 2; 43874 } else { 43875 i++; 43876 } 43877 } // If no Emulation Prevention Bytes were found just return the original 43878 // array 43879 43880 43881 if (emulationPreventionBytesPositions.length === 0) { 43882 return data; 43883 } // Create a new array to hold the NAL unit data 43884 43885 43886 newLength = length - emulationPreventionBytesPositions.length; 43887 newData = new Uint8Array(newLength); 43888 var sourceIndex = 0; 43889 43890 for (i = 0; i < newLength; sourceIndex++, i++) { 43891 if (sourceIndex === emulationPreventionBytesPositions[0]) { 43892 // Skip this byte 43893 sourceIndex++; // Remove this position index 43894 43895 emulationPreventionBytesPositions.shift(); 43896 } 43897 43898 newData[i] = data[sourceIndex]; 43899 } 43900 43901 return newData; 43902 }; // exports 43903 43904 43905 var captionPacketParser = { 43906 parseSei: parseSei, 43907 parseUserData: parseUserData, 43908 parseCaptionPackets: parseCaptionPackets, 43909 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 43910 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 43911 }; // ----------------- 43912 // Link To Transport 43913 // ----------------- 43914 43915 var CaptionStream = function CaptionStream() { 43916 CaptionStream.prototype.init.call(this); 43917 this.captionPackets_ = []; 43918 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 43919 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 43920 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 43921 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 43922 ]; 43923 this.reset(); // forward data and done events from CCs to this CaptionStream 43924 43925 this.ccStreams_.forEach(function (cc) { 43926 cc.on('data', this.trigger.bind(this, 'data')); 43927 cc.on('partialdone', this.trigger.bind(this, 'partialdone')); 43928 cc.on('done', this.trigger.bind(this, 'done')); 43929 }, this); 43930 }; 43931 43932 CaptionStream.prototype = new stream(); 43933 43934 CaptionStream.prototype.push = function (event) { 43935 var sei, userData, newCaptionPackets; // only examine SEI NALs 43936 43937 if (event.nalUnitType !== 'sei_rbsp') { 43938 return; 43939 } // parse the sei 43940 43941 43942 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 43943 43944 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 43945 return; 43946 } // parse out the user data payload 43947 43948 43949 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 43950 43951 if (!userData) { 43952 return; 43953 } // Sometimes, the same segment # will be downloaded twice. To stop the 43954 // caption data from being processed twice, we track the latest dts we've 43955 // received and ignore everything with a dts before that. However, since 43956 // data for a specific dts can be split across packets on either side of 43957 // a segment boundary, we need to make sure we *don't* ignore the packets 43958 // from the *next* segment that have dts === this.latestDts_. By constantly 43959 // tracking the number of packets received with dts === this.latestDts_, we 43960 // know how many should be ignored once we start receiving duplicates. 43961 43962 43963 if (event.dts < this.latestDts_) { 43964 // We've started getting older data, so set the flag. 43965 this.ignoreNextEqualDts_ = true; 43966 return; 43967 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 43968 this.numSameDts_--; 43969 43970 if (!this.numSameDts_) { 43971 // We've received the last duplicate packet, time to start processing again 43972 this.ignoreNextEqualDts_ = false;
vendor: 6,564 bytes, lines 43973-44159
43973 } 43974 43975 return; 43976 } // parse out CC data packets and save them for later 43977 43978 43979 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 43980 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 43981 43982 if (this.latestDts_ !== event.dts) { 43983 this.numSameDts_ = 0; 43984 } 43985 43986 this.numSameDts_++; 43987 this.latestDts_ = event.dts; 43988 }; 43989 43990 CaptionStream.prototype.flushCCStreams = function (flushType) { 43991 this.ccStreams_.forEach(function (cc) { 43992 return flushType === 'flush' ? cc.flush() : cc.partialFlush(); 43993 }, this); 43994 }; 43995 43996 CaptionStream.prototype.flushStream = function (flushType) { 43997 // make sure we actually parsed captions before proceeding 43998 if (!this.captionPackets_.length) { 43999 this.flushCCStreams(flushType); 44000 return; 44001 } // In Chrome, the Array#sort function is not stable so add a 44002 // presortIndex that we can use to ensure we get a stable-sort 44003 44004 44005 this.captionPackets_.forEach(function (elem, idx) { 44006 elem.presortIndex = idx; 44007 }); // sort caption byte-pairs based on their PTS values 44008 44009 this.captionPackets_.sort(function (a, b) { 44010 if (a.pts === b.pts) { 44011 return a.presortIndex - b.presortIndex; 44012 } 44013 44014 return a.pts - b.pts; 44015 }); 44016 this.captionPackets_.forEach(function (packet) { 44017 if (packet.type < 2) { 44018 // Dispatch packet to the right Cea608Stream 44019 this.dispatchCea608Packet(packet); 44020 } // this is where an 'else' would go for a dispatching packets 44021 // to a theoretical Cea708Stream that handles SERVICEn data 44022 44023 }, this); 44024 this.captionPackets_.length = 0; 44025 this.flushCCStreams(flushType); 44026 }; 44027 44028 CaptionStream.prototype.flush = function () { 44029 return this.flushStream('flush'); 44030 }; // Only called if handling partial data 44031 44032 44033 CaptionStream.prototype.partialFlush = function () { 44034 return this.flushStream('partialFlush'); 44035 }; 44036 44037 CaptionStream.prototype.reset = function () { 44038 this.latestDts_ = null; 44039 this.ignoreNextEqualDts_ = false; 44040 this.numSameDts_ = 0; 44041 this.activeCea608Channel_ = [null, null]; 44042 this.ccStreams_.forEach(function (ccStream) { 44043 ccStream.reset(); 44044 }); 44045 }; // From the CEA-608 spec: 44046 44047 /* 44048 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed 44049 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is 44050 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair 44051 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the 44052 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) 44053 * to switch to captioning or Text. 44054 */ 44055 // With that in mind, we ignore any data between an XDS control code and a 44056 // subsequent closed-captioning control code. 44057 44058 44059 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 44060 // NOTE: packet.type is the CEA608 field 44061 if (this.setsTextOrXDSActive(packet)) { 44062 this.activeCea608Channel_[packet.type] = null; 44063 } else if (this.setsChannel1Active(packet)) { 44064 this.activeCea608Channel_[packet.type] = 0; 44065 } else if (this.setsChannel2Active(packet)) { 44066 this.activeCea608Channel_[packet.type] = 1; 44067 } 44068 44069 if (this.activeCea608Channel_[packet.type] === null) { 44070 // If we haven't received anything to set the active channel, or the 44071 // packets are Text/XDS data, discard the data; we don't want jumbled 44072 // captions 44073 return; 44074 } 44075 44076 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 44077 }; 44078 44079 CaptionStream.prototype.setsChannel1Active = function (packet) { 44080 return (packet.ccData & 0x7800) === 0x1000; 44081 }; 44082 44083 CaptionStream.prototype.setsChannel2Active = function (packet) { 44084 return (packet.ccData & 0x7800) === 0x1800; 44085 }; 44086 44087 CaptionStream.prototype.setsTextOrXDSActive = function (packet) { 44088 return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a; 44089 }; // ---------------------- 44090 // Session to Application 44091 // ---------------------- 44092 // This hash maps non-ASCII, special, and extended character codes to their 44093 // proper Unicode equivalent. The first keys that are only a single byte 44094 // are the non-standard ASCII characters, which simply map the CEA608 byte 44095 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 44096 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 44097 // can be performed regardless of the field and data channel on which the 44098 // character code was received. 44099 44100 44101 var CHARACTER_TRANSLATION = { 44102 0x2a: 0xe1, 44103 // á 44104 0x5c: 0xe9, 44105 // é 44106 0x5e: 0xed, 44107 // à 44108 0x5f: 0xf3, 44109 // ó 44110 0x60: 0xfa, 44111 // ú 44112 0x7b: 0xe7, 44113 // ç 44114 0x7c: 0xf7, 44115 // ÷ 44116 0x7d: 0xd1, 44117 // à 44118 0x7e: 0xf1, 44119 // ñ 44120 0x7f: 0x2588, 44121 // â 44122 0x0130: 0xae, 44123 // ® 44124 0x0131: 0xb0, 44125 // ° 44126 0x0132: 0xbd, 44127 // ½ 44128 0x0133: 0xbf, 44129 // ¿ 44130 0x0134: 0x2122, 44131 // ⢠44132 0x0135: 0xa2, 44133 // ¢ 44134 0x0136: 0xa3, 44135 // £ 44136 0x0137: 0x266a, 44137 // ⪠44138 0x0138: 0xe0, 44139 // à 44140 0x0139: 0xa0, 44141 // 44142 0x013a: 0xe8, 44143 // è 44144 0x013b: 0xe2, 44145 // â 44146 0x013c: 0xea, 44147 // ê 44148 0x013d: 0xee, 44149 // î 44150 0x013e: 0xf4, 44151 // ô 44152 0x013f: 0xfb, 44153 // û 44154 0x0220: 0xc1, 44155 // à 44156 0x0221: 0xc9, 44157 // à 44158 0x0222: 0xd3, 44159 // Ã
vendor: 26,695 bytes, lines 44160-44866
44160 0x0223: 0xda, 44161 // à 44162 0x0224: 0xdc, 44163 // à 44164 0x0225: 0xfc, 44165 // ü 44166 0x0226: 0x2018, 44167 // â 44168 0x0227: 0xa1, 44169 // ¡ 44170 0x0228: 0x2a, 44171 // * 44172 0x0229: 0x27, 44173 // ' 44174 0x022a: 0x2014, 44175 // â 44176 0x022b: 0xa9, 44177 // © 44178 0x022c: 0x2120, 44179 // â 44180 0x022d: 0x2022, 44181 // ⢠44182 0x022e: 0x201c, 44183 // â 44184 0x022f: 0x201d, 44185 // â 44186 0x0230: 0xc0, 44187 // à 44188 0x0231: 0xc2, 44189 // à 44190 0x0232: 0xc7, 44191 // à 44192 0x0233: 0xc8, 44193 // à 44194 0x0234: 0xca, 44195 // à 44196 0x0235: 0xcb, 44197 // à 44198 0x0236: 0xeb, 44199 // ë 44200 0x0237: 0xce, 44201 // à 44202 0x0238: 0xcf, 44203 // à 44204 0x0239: 0xef, 44205 // ï 44206 0x023a: 0xd4, 44207 // à 44208 0x023b: 0xd9, 44209 // à 44210 0x023c: 0xf9, 44211 // ù 44212 0x023d: 0xdb, 44213 // à 44214 0x023e: 0xab, 44215 // « 44216 0x023f: 0xbb, 44217 // » 44218 0x0320: 0xc3, 44219 // à 44220 0x0321: 0xe3, 44221 // ã 44222 0x0322: 0xcd, 44223 // à 44224 0x0323: 0xcc, 44225 // à 44226 0x0324: 0xec, 44227 // ì 44228 0x0325: 0xd2, 44229 // à 44230 0x0326: 0xf2, 44231 // ò 44232 0x0327: 0xd5, 44233 // à 44234 0x0328: 0xf5, 44235 // õ 44236 0x0329: 0x7b, 44237 // { 44238 0x032a: 0x7d, 44239 // } 44240 0x032b: 0x5c, 44241 // \ 44242 0x032c: 0x5e, 44243 // ^ 44244 0x032d: 0x5f, 44245 // _ 44246 0x032e: 0x7c, 44247 // | 44248 0x032f: 0x7e, 44249 // ~ 44250 0x0330: 0xc4, 44251 // à 44252 0x0331: 0xe4, 44253 // ä 44254 0x0332: 0xd6, 44255 // à 44256 0x0333: 0xf6, 44257 // ö 44258 0x0334: 0xdf, 44259 // à 44260 0x0335: 0xa5, 44261 // Â¥ 44262 0x0336: 0xa4, 44263 // ¤ 44264 0x0337: 0x2502, 44265 // â 44266 0x0338: 0xc5, 44267 // à 44268 0x0339: 0xe5, 44269 // Ã¥ 44270 0x033a: 0xd8, 44271 // à 44272 0x033b: 0xf8, 44273 // ø 44274 0x033c: 0x250c, 44275 // â 44276 0x033d: 0x2510, 44277 // â 44278 0x033e: 0x2514, 44279 // â 44280 0x033f: 0x2518 // â 44281 44282 }; 44283 44284 var getCharFromCode = function getCharFromCode(code) { 44285 if (code === null) { 44286 return ''; 44287 } 44288 44289 code = CHARACTER_TRANSLATION[code] || code; 44290 return String.fromCharCode(code); 44291 }; // the index of the last row in a CEA-608 display buffer 44292 44293 44294 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 44295 // getting it through bit logic. 44296 44297 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 44298 // cells. The "bottom" row is the last element in the outer array. 44299 44300 var createDisplayBuffer = function createDisplayBuffer() { 44301 var result = [], 44302 i = BOTTOM_ROW + 1; 44303 44304 while (i--) { 44305 result.push(''); 44306 } 44307 44308 return result; 44309 }; 44310 44311 var Cea608Stream = function Cea608Stream(field, dataChannel) { 44312 Cea608Stream.prototype.init.call(this); 44313 this.field_ = field || 0; 44314 this.dataChannel_ = dataChannel || 0; 44315 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 44316 this.setConstants(); 44317 this.reset(); 44318 44319 this.push = function (packet) { 44320 var data, swap, char0, char1, text; // remove the parity bits 44321 44322 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 44323 44324 if (data === this.lastControlCode_) { 44325 this.lastControlCode_ = null; 44326 return; 44327 } // Store control codes 44328 44329 44330 if ((data & 0xf000) === 0x1000) { 44331 this.lastControlCode_ = data; 44332 } else if (data !== this.PADDING_) { 44333 this.lastControlCode_ = null; 44334 } 44335 44336 char0 = data >>> 8; 44337 char1 = data & 0xff; 44338 44339 if (data === this.PADDING_) { 44340 return; 44341 } else if (data === this.RESUME_CAPTION_LOADING_) { 44342 this.mode_ = 'popOn'; 44343 } else if (data === this.END_OF_CAPTION_) { 44344 // If an EOC is received while in paint-on mode, the displayed caption 44345 // text should be swapped to non-displayed memory as if it was a pop-on 44346 // caption. Because of that, we should explicitly switch back to pop-on 44347 // mode 44348 this.mode_ = 'popOn'; 44349 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 44350 44351 this.flushDisplayed(packet.pts); // flip memory 44352 44353 swap = this.displayed_; 44354 this.displayed_ = this.nonDisplayed_; 44355 this.nonDisplayed_ = swap; // start measuring the time to display the caption 44356 44357 this.startPts_ = packet.pts; 44358 } else if (data === this.ROLL_UP_2_ROWS_) { 44359 this.rollUpRows_ = 2; 44360 this.setRollUp(packet.pts); 44361 } else if (data === this.ROLL_UP_3_ROWS_) { 44362 this.rollUpRows_ = 3; 44363 this.setRollUp(packet.pts); 44364 } else if (data === this.ROLL_UP_4_ROWS_) { 44365 this.rollUpRows_ = 4; 44366 this.setRollUp(packet.pts); 44367 } else if (data === this.CARRIAGE_RETURN_) { 44368 this.clearFormatting(packet.pts); 44369 this.flushDisplayed(packet.pts); 44370 this.shiftRowsUp_(); 44371 this.startPts_ = packet.pts; 44372 } else if (data === this.BACKSPACE_) { 44373 if (this.mode_ === 'popOn') { 44374 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 44375 } else { 44376 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 44377 } 44378 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 44379 this.flushDisplayed(packet.pts); 44380 this.displayed_ = createDisplayBuffer(); 44381 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 44382 this.nonDisplayed_ = createDisplayBuffer(); 44383 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 44384 if (this.mode_ !== 'paintOn') { 44385 // NOTE: This should be removed when proper caption positioning is 44386 // implemented 44387 this.flushDisplayed(packet.pts); 44388 this.displayed_ = createDisplayBuffer(); 44389 } 44390 44391 this.mode_ = 'paintOn'; 44392 this.startPts_ = packet.pts; // Append special characters to caption text 44393 } else if (this.isSpecialCharacter(char0, char1)) { 44394 // Bitmask char0 so that we can apply character transformations 44395 // regardless of field and data channel. 44396 // Then byte-shift to the left and OR with char1 so we can pass the 44397 // entire character code to `getCharFromCode`. 44398 char0 = (char0 & 0x03) << 8; 44399 text = getCharFromCode(char0 | char1); 44400 this[this.mode_](packet.pts, text); 44401 this.column_++; // Append extended characters to caption text 44402 } else if (this.isExtCharacter(char0, char1)) { 44403 // Extended characters always follow their "non-extended" equivalents. 44404 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 44405 // decoders are supposed to drop the "è", while compliant decoders 44406 // backspace the "e" and insert "è". 44407 // Delete the previous character 44408 if (this.mode_ === 'popOn') { 44409 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 44410 } else { 44411 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 44412 } // Bitmask char0 so that we can apply character transformations 44413 // regardless of field and data channel. 44414 // Then byte-shift to the left and OR with char1 so we can pass the 44415 // entire character code to `getCharFromCode`. 44416 44417 44418 char0 = (char0 & 0x03) << 8; 44419 text = getCharFromCode(char0 | char1); 44420 this[this.mode_](packet.pts, text); 44421 this.column_++; // Process mid-row codes 44422 } else if (this.isMidRowCode(char0, char1)) { 44423 // Attributes are not additive, so clear all formatting 44424 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 44425 // should be replaced with spaces, so add one now 44426 44427 this[this.mode_](packet.pts, ' '); 44428 this.column_++; 44429 44430 if ((char1 & 0xe) === 0xe) { 44431 this.addFormatting(packet.pts, ['i']); 44432 } 44433 44434 if ((char1 & 0x1) === 0x1) { 44435 this.addFormatting(packet.pts, ['u']); 44436 } // Detect offset control codes and adjust cursor 44437 44438 } else if (this.isOffsetControlCode(char0, char1)) { 44439 // Cursor position is set by indent PAC (see below) in 4-column 44440 // increments, with an additional offset code of 1-3 to reach any 44441 // of the 32 columns specified by CEA-608. So all we need to do 44442 // here is increment the column cursor by the given offset. 44443 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 44444 } else if (this.isPAC(char0, char1)) { 44445 // There's no logic for PAC -> row mapping, so we have to just 44446 // find the row code in an array and use its index :( 44447 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 44448 44449 if (this.mode_ === 'rollUp') { 44450 // This implies that the base row is incorrectly set. 44451 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 44452 // of roll-up rows set. 44453 if (row - this.rollUpRows_ + 1 < 0) { 44454 row = this.rollUpRows_ - 1; 44455 } 44456 44457 this.setRollUp(packet.pts, row); 44458 } 44459 44460 if (row !== this.row_) { 44461 // formatting is only persistent for current row 44462 this.clearFormatting(packet.pts); 44463 this.row_ = row; 44464 } // All PACs can apply underline, so detect and apply 44465 // (All odd-numbered second bytes set underline) 44466 44467 44468 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 44469 this.addFormatting(packet.pts, ['u']); 44470 } 44471 44472 if ((data & 0x10) === 0x10) { 44473 // We've got an indent level code. Each successive even number 44474 // increments the column cursor by 4, so we can get the desired 44475 // column position by bit-shifting to the right (to get n/2) 44476 // and multiplying by 4. 44477 this.column_ = ((data & 0xe) >> 1) * 4; 44478 } 44479 44480 if (this.isColorPAC(char1)) { 44481 // it's a color code, though we only support white, which 44482 // can be either normal or italicized. white italics can be 44483 // either 0x4e or 0x6e depending on the row, so we just 44484 // bitwise-and with 0xe to see if italics should be turned on 44485 if ((char1 & 0xe) === 0xe) { 44486 this.addFormatting(packet.pts, ['i']); 44487 } 44488 } // We have a normal character in char0, and possibly one in char1 44489 44490 } else if (this.isNormalChar(char0)) { 44491 if (char1 === 0x00) { 44492 char1 = null; 44493 } 44494 44495 text = getCharFromCode(char0); 44496 text += getCharFromCode(char1); 44497 this[this.mode_](packet.pts, text); 44498 this.column_ += text.length; 44499 } // finish data processing 44500 44501 }; 44502 }; 44503 44504 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 44505 // display buffer 44506 44507 Cea608Stream.prototype.flushDisplayed = function (pts) { 44508 var content = this.displayed_ // remove spaces from the start and end of the string 44509 .map(function (row) { 44510 try { 44511 return row.trim(); 44512 } catch (e) { 44513 // Ordinarily, this shouldn't happen. However, caption 44514 // parsing errors should not throw exceptions and 44515 // break playback. 44516 // eslint-disable-next-line no-console 44517 console.error('Skipping malformed caption.'); 44518 return ''; 44519 } 44520 }) // combine all text rows to display in one cue 44521 .join('\n') // and remove blank rows from the start and end, but not the middle 44522 .replace(/^\n+|\n+$/g, ''); 44523 44524 if (content.length) { 44525 this.trigger('data', { 44526 startPts: this.startPts_, 44527 endPts: pts, 44528 text: content, 44529 stream: this.name_ 44530 }); 44531 } 44532 }; 44533 /** 44534 * Zero out the data, used for startup and on seek 44535 */ 44536 44537 44538 Cea608Stream.prototype.reset = function () { 44539 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 44540 // actually display captions. If a caption is shifted to a row 44541 // with a lower index than this, it is cleared from the display 44542 // buffer 44543 44544 this.topRow_ = 0; 44545 this.startPts_ = 0; 44546 this.displayed_ = createDisplayBuffer(); 44547 this.nonDisplayed_ = createDisplayBuffer(); 44548 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 44549 44550 this.column_ = 0; 44551 this.row_ = BOTTOM_ROW; 44552 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 44553 44554 this.formatting_ = []; 44555 }; 44556 /** 44557 * Sets up control code and related constants for this instance 44558 */ 44559 44560 44561 Cea608Stream.prototype.setConstants = function () { 44562 // The following attributes have these uses: 44563 // ext_ : char0 for mid-row codes, and the base for extended 44564 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 44565 // extended codes) 44566 // control_: char0 for control codes, except byte-shifted to the 44567 // left so that we can do this.control_ | CONTROL_CODE 44568 // offset_: char0 for tab offset codes 44569 // 44570 // It's also worth noting that control codes, and _only_ control codes, 44571 // differ between field 1 and field2. Field 2 control codes are always 44572 // their field 1 value plus 1. That's why there's the "| field" on the 44573 // control value. 44574 if (this.dataChannel_ === 0) { 44575 this.BASE_ = 0x10; 44576 this.EXT_ = 0x11; 44577 this.CONTROL_ = (0x14 | this.field_) << 8; 44578 this.OFFSET_ = 0x17; 44579 } else if (this.dataChannel_ === 1) { 44580 this.BASE_ = 0x18; 44581 this.EXT_ = 0x19; 44582 this.CONTROL_ = (0x1c | this.field_) << 8; 44583 this.OFFSET_ = 0x1f; 44584 } // Constants for the LSByte command codes recognized by Cea608Stream. This 44585 // list is not exhaustive. For a more comprehensive listing and semantics see 44586 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 44587 // Padding 44588 44589 44590 this.PADDING_ = 0x0000; // Pop-on Mode 44591 44592 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 44593 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 44594 44595 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 44596 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 44597 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 44598 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 44599 44600 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 44601 44602 this.BACKSPACE_ = this.CONTROL_ | 0x21; 44603 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 44604 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 44605 }; 44606 /** 44607 * Detects if the 2-byte packet data is a special character 44608 * 44609 * Special characters have a second byte in the range 0x30 to 0x3f, 44610 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 44611 * data channel 2). 44612 * 44613 * @param {Integer} char0 The first byte 44614 * @param {Integer} char1 The second byte 44615 * @return {Boolean} Whether the 2 bytes are an special character 44616 */ 44617 44618 44619 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 44620 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 44621 }; 44622 /** 44623 * Detects if the 2-byte packet data is an extended character 44624 * 44625 * Extended characters have a second byte in the range 0x20 to 0x3f, 44626 * with the first byte being 0x12 or 0x13 (for data channel 1) or 44627 * 0x1a or 0x1b (for data channel 2). 44628 * 44629 * @param {Integer} char0 The first byte 44630 * @param {Integer} char1 The second byte 44631 * @return {Boolean} Whether the 2 bytes are an extended character 44632 */ 44633 44634 44635 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 44636 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 44637 }; 44638 /** 44639 * Detects if the 2-byte packet is a mid-row code 44640 * 44641 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 44642 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 44643 * channel 2). 44644 * 44645 * @param {Integer} char0 The first byte 44646 * @param {Integer} char1 The second byte 44647 * @return {Boolean} Whether the 2 bytes are a mid-row code 44648 */ 44649 44650 44651 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 44652 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 44653 }; 44654 /** 44655 * Detects if the 2-byte packet is an offset control code 44656 * 44657 * Offset control codes have a second byte in the range 0x21 to 0x23, 44658 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 44659 * data channel 2). 44660 * 44661 * @param {Integer} char0 The first byte 44662 * @param {Integer} char1 The second byte 44663 * @return {Boolean} Whether the 2 bytes are an offset control code 44664 */ 44665 44666 44667 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 44668 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 44669 }; 44670 /** 44671 * Detects if the 2-byte packet is a Preamble Address Code 44672 * 44673 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 44674 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 44675 * range 0x40 to 0x7f. 44676 * 44677 * @param {Integer} char0 The first byte 44678 * @param {Integer} char1 The second byte 44679 * @return {Boolean} Whether the 2 bytes are a PAC 44680 */ 44681 44682 44683 Cea608Stream.prototype.isPAC = function (char0, char1) { 44684 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 44685 }; 44686 /** 44687 * Detects if a packet's second byte is in the range of a PAC color code 44688 * 44689 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 44690 * 0x60 to 0x6f. 44691 * 44692 * @param {Integer} char1 The second byte 44693 * @return {Boolean} Whether the byte is a color PAC 44694 */ 44695 44696 44697 Cea608Stream.prototype.isColorPAC = function (char1) { 44698 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 44699 }; 44700 /** 44701 * Detects if a single byte is in the range of a normal character 44702 * 44703 * Normal text bytes are in the range 0x20 to 0x7f. 44704 * 44705 * @param {Integer} char The byte 44706 * @return {Boolean} Whether the byte is a normal character 44707 */ 44708 44709 44710 Cea608Stream.prototype.isNormalChar = function (_char) { 44711 return _char >= 0x20 && _char <= 0x7f; 44712 }; 44713 /** 44714 * Configures roll-up 44715 * 44716 * @param {Integer} pts Current PTS 44717 * @param {Integer} newBaseRow Used by PACs to slide the current window to 44718 * a new position 44719 */ 44720 44721 44722 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 44723 // Reset the base row to the bottom row when switching modes 44724 if (this.mode_ !== 'rollUp') { 44725 this.row_ = BOTTOM_ROW; 44726 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 44727 44728 this.flushDisplayed(pts); 44729 this.nonDisplayed_ = createDisplayBuffer(); 44730 this.displayed_ = createDisplayBuffer(); 44731 } 44732 44733 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 44734 // move currently displayed captions (up or down) to the new base row 44735 for (var i = 0; i < this.rollUpRows_; i++) { 44736 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 44737 this.displayed_[this.row_ - i] = ''; 44738 } 44739 } 44740 44741 if (newBaseRow === undefined) { 44742 newBaseRow = this.row_; 44743 } 44744 44745 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 44746 }; // Adds the opening HTML tag for the passed character to the caption text, 44747 // and keeps track of it for later closing 44748 44749 44750 Cea608Stream.prototype.addFormatting = function (pts, format) { 44751 this.formatting_ = this.formatting_.concat(format); 44752 var text = format.reduce(function (text, format) { 44753 return text + '<' + format + '>'; 44754 }, ''); 44755 this[this.mode_](pts, text); 44756 }; // Adds HTML closing tags for current formatting to caption text and 44757 // clears remembered formatting 44758 44759 44760 Cea608Stream.prototype.clearFormatting = function (pts) { 44761 if (!this.formatting_.length) { 44762 return; 44763 } 44764 44765 var text = this.formatting_.reverse().reduce(function (text, format) { 44766 return text + '</' + format + '>'; 44767 }, ''); 44768 this.formatting_ = []; 44769 this[this.mode_](pts, text); 44770 }; // Mode Implementations 44771 44772 44773 Cea608Stream.prototype.popOn = function (pts, text) { 44774 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 44775 44776 baseRow += text; 44777 this.nonDisplayed_[this.row_] = baseRow; 44778 }; 44779 44780 Cea608Stream.prototype.rollUp = function (pts, text) { 44781 var baseRow = this.displayed_[this.row_]; 44782 baseRow += text; 44783 this.displayed_[this.row_] = baseRow; 44784 }; 44785 44786 Cea608Stream.prototype.shiftRowsUp_ = function () { 44787 var i; // clear out inactive rows 44788 44789 for (i = 0; i < this.topRow_; i++) { 44790 this.displayed_[i] = ''; 44791 } 44792 44793 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 44794 this.displayed_[i] = ''; 44795 } // shift displayed rows up 44796 44797 44798 for (i = this.topRow_; i < this.row_; i++) { 44799 this.displayed_[i] = this.displayed_[i + 1]; 44800 } // clear out the bottom row 44801 44802 44803 this.displayed_[this.row_] = ''; 44804 }; 44805 44806 Cea608Stream.prototype.paintOn = function (pts, text) { 44807 var baseRow = this.displayed_[this.row_]; 44808 baseRow += text; 44809 this.displayed_[this.row_] = baseRow; 44810 }; // exports 44811 44812 44813 var captionStream = { 44814 CaptionStream: CaptionStream, 44815 Cea608Stream: Cea608Stream 44816 }; 44817 /** 44818 * mux.js 44819 * 44820 * Copyright (c) Brightcove 44821 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 44822 */ 44823 44824 var streamTypes = { 44825 H264_STREAM_TYPE: 0x1B, 44826 ADTS_STREAM_TYPE: 0x0F, 44827 METADATA_STREAM_TYPE: 0x15 44828 }; 44829 var MAX_TS = 8589934592; 44830 var RO_THRESH = 4294967296; 44831 var TYPE_SHARED = 'shared'; 44832 44833 var handleRollover = function handleRollover(value, reference) { 44834 var direction = 1; 44835 44836 if (value > reference) { 44837 // If the current timestamp value is greater than our reference timestamp and we detect a 44838 // timestamp rollover, this means the roll over is happening in the opposite direction. 44839 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 44840 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 44841 // rollover point. In loading this segment, the timestamp values will be very large, 44842 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 44843 // the time stamp to be `value - 2^33`. 44844 direction = -1; 44845 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 44846 // cause an incorrect adjustment. 44847 44848 44849 while (Math.abs(reference - value) > RO_THRESH) { 44850 value += direction * MAX_TS; 44851 } 44852 44853 return value; 44854 }; 44855 44856 var TimestampRolloverStream = function TimestampRolloverStream(type) { 44857 var lastDTS, referenceDTS; 44858 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 44859 // video and audio. We could use `undefined` here, but having a string 44860 // makes debugging a little clearer. 44861 44862 this.type_ = type || TYPE_SHARED; 44863 44864 this.push = function (data) { 44865 // Any "shared" rollover streams will accept _all_ data. Otherwise, 44866 // streams will only accept data that matches their type.
vendor: 12,471 bytes, lines 44867-45195
44867 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 44868 return; 44869 } 44870 44871 if (referenceDTS === undefined) { 44872 referenceDTS = data.dts; 44873 } 44874 44875 data.dts = handleRollover(data.dts, referenceDTS); 44876 data.pts = handleRollover(data.pts, referenceDTS); 44877 lastDTS = data.dts; 44878 this.trigger('data', data); 44879 }; 44880 44881 this.flush = function () { 44882 referenceDTS = lastDTS; 44883 this.trigger('done'); 44884 }; 44885 44886 this.endTimeline = function () { 44887 this.flush(); 44888 this.trigger('endedtimeline'); 44889 }; 44890 44891 this.discontinuity = function () { 44892 referenceDTS = void 0; 44893 lastDTS = void 0; 44894 }; 44895 44896 this.reset = function () { 44897 this.discontinuity(); 44898 this.trigger('reset'); 44899 }; 44900 }; 44901 44902 TimestampRolloverStream.prototype = new stream(); 44903 var timestampRolloverStream = { 44904 TimestampRolloverStream: TimestampRolloverStream, 44905 handleRollover: handleRollover 44906 }; 44907 44908 var percentEncode = function percentEncode(bytes, start, end) { 44909 var i, 44910 result = ''; 44911 44912 for (i = start; i < end; i++) { 44913 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 44914 } 44915 44916 return result; 44917 }, 44918 // return the string representation of the specified byte range, 44919 // interpreted as UTf-8. 44920 parseUtf8 = function parseUtf8(bytes, start, end) { 44921 return decodeURIComponent(percentEncode(bytes, start, end)); 44922 }, 44923 // return the string representation of the specified byte range, 44924 // interpreted as ISO-8859-1. 44925 parseIso88591 = function parseIso88591(bytes, start, end) { 44926 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 44927 }, 44928 parseSyncSafeInteger = function parseSyncSafeInteger(data) { 44929 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 44930 }, 44931 tagParsers = { 44932 TXXX: function TXXX(tag) { 44933 var i; 44934 44935 if (tag.data[0] !== 3) { 44936 // ignore frames with unrecognized character encodings 44937 return; 44938 } 44939 44940 for (i = 1; i < tag.data.length; i++) { 44941 if (tag.data[i] === 0) { 44942 // parse the text fields 44943 tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value 44944 44945 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 44946 break; 44947 } 44948 } 44949 44950 tag.data = tag.value; 44951 }, 44952 WXXX: function WXXX(tag) { 44953 var i; 44954 44955 if (tag.data[0] !== 3) { 44956 // ignore frames with unrecognized character encodings 44957 return; 44958 } 44959 44960 for (i = 1; i < tag.data.length; i++) { 44961 if (tag.data[i] === 0) { 44962 // parse the description and URL fields 44963 tag.description = parseUtf8(tag.data, 1, i); 44964 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 44965 break; 44966 } 44967 } 44968 }, 44969 PRIV: function PRIV(tag) { 44970 var i; 44971 44972 for (i = 0; i < tag.data.length; i++) { 44973 if (tag.data[i] === 0) { 44974 // parse the description and URL fields 44975 tag.owner = parseIso88591(tag.data, 0, i); 44976 break; 44977 } 44978 } 44979 44980 tag.privateData = tag.data.subarray(i + 1); 44981 tag.data = tag.privateData; 44982 } 44983 }, 44984 _MetadataStream; 44985 44986 _MetadataStream = function MetadataStream(options) { 44987 var settings = { 44988 debug: !!(options && options.debug), 44989 // the bytes of the program-level descriptor field in MP2T 44990 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 44991 // program element descriptors" 44992 descriptor: options && options.descriptor 44993 }, 44994 // the total size in bytes of the ID3 tag being parsed 44995 tagSize = 0, 44996 // tag data that is not complete enough to be parsed 44997 buffer = [], 44998 // the total number of bytes currently in the buffer 44999 bufferSize = 0, 45000 i; 45001 45002 _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type 45003 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 45004 45005 45006 this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16); 45007 45008 if (settings.descriptor) { 45009 for (i = 0; i < settings.descriptor.length; i++) { 45010 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 45011 } 45012 } 45013 45014 this.push = function (chunk) { 45015 var tag, frameStart, frameSize, frame, i, frameHeader; 45016 45017 if (chunk.type !== 'timed-metadata') { 45018 return; 45019 } // if data_alignment_indicator is set in the PES header, 45020 // we must have the start of a new ID3 tag. Assume anything 45021 // remaining in the buffer was malformed and throw it out 45022 45023 45024 if (chunk.dataAlignmentIndicator) { 45025 bufferSize = 0; 45026 buffer.length = 0; 45027 } // ignore events that don't look like ID3 data 45028 45029 45030 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))) { 45031 if (settings.debug) { 45032 // eslint-disable-next-line no-console 45033 console.log('Skipping unrecognized metadata packet'); 45034 } 45035 45036 return; 45037 } // add this chunk to the data we've collected so far 45038 45039 45040 buffer.push(chunk); 45041 bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header 45042 45043 if (buffer.length === 1) { 45044 // the frame size is transmitted as a 28-bit integer in the 45045 // last four bytes of the ID3 header. 45046 // The most significant bit of each byte is dropped and the 45047 // results concatenated to recover the actual value. 45048 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more 45049 // convenient for our comparisons to include it 45050 45051 tagSize += 10; 45052 } // if the entire frame has not arrived, wait for more data 45053 45054 45055 if (bufferSize < tagSize) { 45056 return; 45057 } // collect the entire frame so it can be parsed 45058 45059 45060 tag = { 45061 data: new Uint8Array(tagSize), 45062 frames: [], 45063 pts: buffer[0].pts, 45064 dts: buffer[0].dts 45065 }; 45066 45067 for (i = 0; i < tagSize;) { 45068 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 45069 i += buffer[0].data.byteLength; 45070 bufferSize -= buffer[0].data.byteLength; 45071 buffer.shift(); 45072 } // find the start of the first frame and the end of the tag 45073 45074 45075 frameStart = 10; 45076 45077 if (tag.data[5] & 0x40) { 45078 // advance the frame start past the extended header 45079 frameStart += 4; // header size field 45080 45081 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end 45082 45083 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 45084 } // parse one or more ID3 frames 45085 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 45086 45087 45088 do { 45089 // determine the number of bytes in this frame 45090 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 45091 45092 if (frameSize < 1) { 45093 // eslint-disable-next-line no-console 45094 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 45095 } 45096 45097 frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]); 45098 frame = { 45099 id: frameHeader, 45100 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 45101 }; 45102 frame.key = frame.id; 45103 45104 if (tagParsers[frame.id]) { 45105 tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start 45106 // time for raw AAC data 45107 45108 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 45109 var d = frame.data, 45110 size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 45111 size *= 4; 45112 size += d[7] & 0x03; 45113 frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based 45114 // on the value of this frame 45115 // we couldn't have known the appropriate pts and dts before 45116 // parsing this ID3 tag so set those values now 45117 45118 if (tag.pts === undefined && tag.dts === undefined) { 45119 tag.pts = frame.timeStamp; 45120 tag.dts = frame.timeStamp; 45121 } 45122 45123 this.trigger('timestamp', frame); 45124 } 45125 } 45126 45127 tag.frames.push(frame); 45128 frameStart += 10; // advance past the frame header 45129 45130 frameStart += frameSize; // advance past the frame body 45131 } while (frameStart < tagSize); 45132 45133 this.trigger('data', tag); 45134 }; 45135 }; 45136 45137 _MetadataStream.prototype = new stream(); 45138 var metadataStream = _MetadataStream; 45139 var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types 45140 45141 var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants 45142 45143 45144 var MP2T_PACKET_LENGTH = 188, 45145 // bytes 45146 SYNC_BYTE = 0x47; 45147 /** 45148 * Splits an incoming stream of binary data into MPEG-2 Transport 45149 * Stream packets. 45150 */ 45151 45152 _TransportPacketStream = function TransportPacketStream() { 45153 var buffer = new Uint8Array(MP2T_PACKET_LENGTH), 45154 bytesInBuffer = 0; 45155 45156 _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream. 45157 45158 /** 45159 * Split a stream of data into M2TS packets 45160 **/ 45161 45162 45163 this.push = function (bytes) { 45164 var startIndex = 0, 45165 endIndex = MP2T_PACKET_LENGTH, 45166 everything; // If there are bytes remaining from the last segment, prepend them to the 45167 // bytes that were pushed in 45168 45169 if (bytesInBuffer) { 45170 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 45171 everything.set(buffer.subarray(0, bytesInBuffer)); 45172 everything.set(bytes, bytesInBuffer); 45173 bytesInBuffer = 0; 45174 } else { 45175 everything = bytes; 45176 } // While we have enough data for a packet 45177 45178 45179 while (endIndex < everything.byteLength) { 45180 // Look for a pair of start and end sync bytes in the data.. 45181 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 45182 // We found a packet so emit it and jump one whole packet forward in 45183 // the stream 45184 this.trigger('data', everything.subarray(startIndex, endIndex)); 45185 startIndex += MP2T_PACKET_LENGTH; 45186 endIndex += MP2T_PACKET_LENGTH; 45187 continue; 45188 } // If we get here, we have somehow become de-synchronized and we need to step 45189 // forward one byte at a time until we find a pair of sync bytes that denote 45190 // a packet 45191 45192 45193 startIndex++; 45194 endIndex++; 45195 } // If there was some data left over at the end of the segment that couldn't
vendor: 7,518 bytes, lines 45196-45372
45196 // possibly be a whole packet, keep it because it might be the start of a packet 45197 // that continues in the next segment 45198 45199 45200 if (startIndex < everything.byteLength) { 45201 buffer.set(everything.subarray(startIndex), 0); 45202 bytesInBuffer = everything.byteLength - startIndex; 45203 } 45204 }; 45205 /** 45206 * Passes identified M2TS packets to the TransportParseStream to be parsed 45207 **/ 45208 45209 45210 this.flush = function () { 45211 // If the buffer contains a whole packet when we are being flushed, emit it 45212 // and empty the buffer. Otherwise hold onto the data because it may be 45213 // important for decoding the next segment 45214 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 45215 this.trigger('data', buffer); 45216 bytesInBuffer = 0; 45217 } 45218 45219 this.trigger('done'); 45220 }; 45221 45222 this.endTimeline = function () { 45223 this.flush(); 45224 this.trigger('endedtimeline'); 45225 }; 45226 45227 this.reset = function () { 45228 bytesInBuffer = 0; 45229 this.trigger('reset'); 45230 }; 45231 }; 45232 45233 _TransportPacketStream.prototype = new stream(); 45234 /** 45235 * Accepts an MP2T TransportPacketStream and emits data events with parsed 45236 * forms of the individual transport stream packets. 45237 */ 45238 45239 _TransportParseStream = function TransportParseStream() { 45240 var parsePsi, parsePat, parsePmt, self; 45241 45242 _TransportParseStream.prototype.init.call(this); 45243 45244 self = this; 45245 this.packetsWaitingForPmt = []; 45246 this.programMapTable = undefined; 45247 45248 parsePsi = function parsePsi(payload, psi) { 45249 var offset = 0; // PSI packets may be split into multiple sections and those 45250 // sections may be split into multiple packets. If a PSI 45251 // section starts in this packet, the payload_unit_start_indicator 45252 // will be true and the first byte of the payload will indicate 45253 // the offset from the current position to the start of the 45254 // section. 45255 45256 if (psi.payloadUnitStartIndicator) { 45257 offset += payload[offset] + 1; 45258 } 45259 45260 if (psi.type === 'pat') { 45261 parsePat(payload.subarray(offset), psi); 45262 } else { 45263 parsePmt(payload.subarray(offset), psi); 45264 } 45265 }; 45266 45267 parsePat = function parsePat(payload, pat) { 45268 pat.section_number = payload[7]; // eslint-disable-line camelcase 45269 45270 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 45271 // skip the PSI header and parse the first PMT entry 45272 45273 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 45274 pat.pmtPid = self.pmtPid; 45275 }; 45276 /** 45277 * Parse out the relevant fields of a Program Map Table (PMT). 45278 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 45279 * packet. The first byte in this array should be the table_id 45280 * field. 45281 * @param pmt {object} the object that should be decorated with 45282 * fields parsed from the PMT. 45283 */ 45284 45285 45286 parsePmt = function parsePmt(payload, pmt) { 45287 var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually 45288 // take effect. We don't believe this should ever be the case 45289 // for HLS but we'll ignore "forward" PMT declarations if we see 45290 // them. Future PMT declarations have the current_next_indicator 45291 // set to zero. 45292 45293 if (!(payload[5] & 0x01)) { 45294 return; 45295 } // overwrite any existing program map table 45296 45297 45298 self.programMapTable = { 45299 video: null, 45300 audio: null, 45301 'timed-metadata': {} 45302 }; // the mapping table ends at the end of the current section 45303 45304 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 45305 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 45306 // long the program info descriptors are 45307 45308 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table 45309 45310 offset = 12 + programInfoLength; 45311 45312 while (offset < tableEnd) { 45313 var streamType = payload[offset]; 45314 var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types 45315 // TODO: should this be done for metadata too? for now maintain behavior of 45316 // multiple metadata streams 45317 45318 if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) { 45319 self.programMapTable.video = pid; 45320 } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) { 45321 self.programMapTable.audio = pid; 45322 } else if (streamType === streamTypes.METADATA_STREAM_TYPE) { 45323 // map pid to stream type for metadata streams 45324 self.programMapTable['timed-metadata'][pid] = streamType; 45325 } // move to the next table entry 45326 // skip past the elementary stream descriptors, if present 45327 45328 45329 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 45330 } // record the map on the packet as well 45331 45332 45333 pmt.programMapTable = self.programMapTable; 45334 }; 45335 /** 45336 * Deliver a new MP2T packet to the next stream in the pipeline. 45337 */ 45338 45339 45340 this.push = function (packet) { 45341 var result = {}, 45342 offset = 4; 45343 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1] 45344 45345 result.pid = packet[1] & 0x1f; 45346 result.pid <<= 8; 45347 result.pid |= packet[2]; // if an adaption field is present, its length is specified by the 45348 // fifth byte of the TS packet header. The adaptation field is 45349 // used to add stuffing to PES packets that don't fill a complete 45350 // TS packet, and to specify some forms of timing and control data 45351 // that we do not currently use. 45352 45353 if ((packet[3] & 0x30) >>> 4 > 0x01) { 45354 offset += packet[offset] + 1; 45355 } // parse the rest of the packet based on the type 45356 45357 45358 if (result.pid === 0) { 45359 result.type = 'pat'; 45360 parsePsi(packet.subarray(offset), result); 45361 this.trigger('data', result); 45362 } else if (result.pid === this.pmtPid) { 45363 result.type = 'pmt'; 45364 parsePsi(packet.subarray(offset), result); 45365 this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now 45366 45367 while (this.packetsWaitingForPmt.length) { 45368 this.processPes_.apply(this, this.packetsWaitingForPmt.shift()); 45369 } 45370 } else if (this.programMapTable === undefined) { 45371 // When we have not seen a PMT yet, defer further processing of 45372 // PES packets until one has been parsed
vendor: 4,086 bytes, lines 45373-45463
45373 this.packetsWaitingForPmt.push([packet, offset, result]); 45374 } else { 45375 this.processPes_(packet, offset, result); 45376 } 45377 }; 45378 45379 this.processPes_ = function (packet, offset, result) { 45380 // set the appropriate stream type 45381 if (result.pid === this.programMapTable.video) { 45382 result.streamType = streamTypes.H264_STREAM_TYPE; 45383 } else if (result.pid === this.programMapTable.audio) { 45384 result.streamType = streamTypes.ADTS_STREAM_TYPE; 45385 } else { 45386 // if not video or audio, it is timed-metadata or unknown 45387 // if unknown, streamType will be undefined 45388 result.streamType = this.programMapTable['timed-metadata'][result.pid]; 45389 } 45390 45391 result.type = 'pes'; 45392 result.data = packet.subarray(offset); 45393 this.trigger('data', result); 45394 }; 45395 }; 45396 45397 _TransportParseStream.prototype = new stream(); 45398 _TransportParseStream.STREAM_TYPES = { 45399 h264: 0x1b, 45400 adts: 0x0f 45401 }; 45402 /** 45403 * Reconsistutes program elementary stream (PES) packets from parsed 45404 * transport stream packets. That is, if you pipe an 45405 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 45406 * events will be events which capture the bytes for individual PES 45407 * packets plus relevant metadata that has been extracted from the 45408 * container. 45409 */ 45410 45411 _ElementaryStream = function ElementaryStream() { 45412 var self = this, 45413 // PES packet fragments 45414 video = { 45415 data: [], 45416 size: 0 45417 }, 45418 audio = { 45419 data: [], 45420 size: 0 45421 }, 45422 timedMetadata = { 45423 data: [], 45424 size: 0 45425 }, 45426 programMapTable, 45427 parsePes = function parsePes(payload, pes) { 45428 var ptsDtsFlags; // get the packet length, this will be 0 for video 45429 45430 pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe 45431 45432 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value 45433 // and a DTS value. Determine what combination of values is 45434 // available to work with. 45435 45436 ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 45437 // performs all bitwise operations on 32-bit integers but javascript 45438 // supports a much greater range (52-bits) of integer using standard 45439 // mathematical operations. 45440 // We construct a 31-bit value using bitwise operators over the 31 45441 // most significant bits and then multiply by 4 (equal to a left-shift 45442 // of 2) before we add the final 2 least significant bits of the 45443 // timestamp (equal to an OR.) 45444 45445 if (ptsDtsFlags & 0xC0) { 45446 // the PTS and DTS are not written out directly. For information 45447 // on how they are encoded, see 45448 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 45449 pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3; 45450 pes.pts *= 4; // Left shift by 2 45451 45452 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 45453 45454 pes.dts = pes.pts; 45455 45456 if (ptsDtsFlags & 0x40) { 45457 pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3; 45458 pes.dts *= 4; // Left shift by 2 45459 45460 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 45461 } 45462 } // the data section starts immediately after the PES header. 45463 // pes_header_data_length specifies the number of header bytes
vendor: 5,896 bytes, lines 45464-45631
45464 // that follow the last byte of the field. 45465 45466 45467 pes.data = payload.subarray(9 + payload[8]); 45468 }, 45469 45470 /** 45471 * Pass completely parsed PES packets to the next stream in the pipeline 45472 **/ 45473 flushStream = function flushStream(stream$$1, type, forceFlush) { 45474 var packetData = new Uint8Array(stream$$1.size), 45475 event = { 45476 type: type 45477 }, 45478 i = 0, 45479 offset = 0, 45480 packetFlushable = false, 45481 fragment; // do nothing if there is not enough buffered data for a complete 45482 // PES header 45483 45484 if (!stream$$1.data.length || stream$$1.size < 9) { 45485 return; 45486 } 45487 45488 event.trackId = stream$$1.data[0].pid; // reassemble the packet 45489 45490 for (i = 0; i < stream$$1.data.length; i++) { 45491 fragment = stream$$1.data[i]; 45492 packetData.set(fragment.data, offset); 45493 offset += fragment.data.byteLength; 45494 } // parse assembled packet's PES header 45495 45496 45497 parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length 45498 // check that there is enough stream data to fill the packet 45499 45500 packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right 45501 45502 if (forceFlush || packetFlushable) { 45503 stream$$1.size = 0; 45504 stream$$1.data.length = 0; 45505 } // only emit packets that are complete. this is to avoid assembling 45506 // incomplete PES packets due to poor segmentation 45507 45508 45509 if (packetFlushable) { 45510 self.trigger('data', event); 45511 } 45512 }; 45513 45514 _ElementaryStream.prototype.init.call(this); 45515 /** 45516 * Identifies M2TS packet types and parses PES packets using metadata 45517 * parsed from the PMT 45518 **/ 45519 45520 45521 this.push = function (data) { 45522 ({ 45523 pat: function pat() {// we have to wait for the PMT to arrive as well before we 45524 // have any meaningful metadata 45525 }, 45526 pes: function pes() { 45527 var stream$$1, streamType; 45528 45529 switch (data.streamType) { 45530 case streamTypes.H264_STREAM_TYPE: 45531 case streamTypes.H264_STREAM_TYPE: 45532 stream$$1 = video; 45533 streamType = 'video'; 45534 break; 45535 45536 case streamTypes.ADTS_STREAM_TYPE: 45537 stream$$1 = audio; 45538 streamType = 'audio'; 45539 break; 45540 45541 case streamTypes.METADATA_STREAM_TYPE: 45542 stream$$1 = timedMetadata; 45543 streamType = 'timed-metadata'; 45544 break; 45545 45546 default: 45547 // ignore unknown stream types 45548 return; 45549 } // if a new packet is starting, we can flush the completed 45550 // packet 45551 45552 45553 if (data.payloadUnitStartIndicator) { 45554 flushStream(stream$$1, streamType, true); 45555 } // buffer this fragment until we are sure we've received the 45556 // complete payload 45557 45558 45559 stream$$1.data.push(data); 45560 stream$$1.size += data.data.byteLength; 45561 }, 45562 pmt: function pmt() { 45563 var event = { 45564 type: 'metadata', 45565 tracks: [] 45566 }; 45567 programMapTable = data.programMapTable; // translate audio and video streams to tracks 45568 45569 if (programMapTable.video !== null) { 45570 event.tracks.push({ 45571 timelineStartInfo: { 45572 baseMediaDecodeTime: 0 45573 }, 45574 id: +programMapTable.video, 45575 codec: 'avc', 45576 type: 'video' 45577 }); 45578 } 45579 45580 if (programMapTable.audio !== null) { 45581 event.tracks.push({ 45582 timelineStartInfo: { 45583 baseMediaDecodeTime: 0 45584 }, 45585 id: +programMapTable.audio, 45586 codec: 'adts', 45587 type: 'audio' 45588 }); 45589 } 45590 45591 self.trigger('data', event); 45592 } 45593 })[data.type](); 45594 }; 45595 45596 this.reset = function () { 45597 video.size = 0; 45598 video.data.length = 0; 45599 audio.size = 0; 45600 audio.data.length = 0; 45601 this.trigger('reset'); 45602 }; 45603 /** 45604 * Flush any remaining input. Video PES packets may be of variable 45605 * length. Normally, the start of a new video packet can trigger the 45606 * finalization of the previous packet. That is not possible if no 45607 * more video is forthcoming, however. In that case, some other 45608 * mechanism (like the end of the file) has to be employed. When it is 45609 * clear that no additional data is forthcoming, calling this method 45610 * will flush the buffered packets. 45611 */ 45612 45613 45614 this.flushStreams_ = function () { 45615 // !!THIS ORDER IS IMPORTANT!! 45616 // video first then audio 45617 flushStream(video, 'video'); 45618 flushStream(audio, 'audio'); 45619 flushStream(timedMetadata, 'timed-metadata'); 45620 }; 45621 45622 this.flush = function () { 45623 this.flushStreams_(); 45624 this.trigger('done'); 45625 }; 45626 }; 45627 45628 _ElementaryStream.prototype = new stream(); 45629 var m2ts = { 45630 PAT_PID: 0x0000, 45631 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
vendor: 4,688 bytes, lines 45632-45743
45632 TransportPacketStream: _TransportPacketStream, 45633 TransportParseStream: _TransportParseStream, 45634 ElementaryStream: _ElementaryStream, 45635 TimestampRolloverStream: TimestampRolloverStream$1, 45636 CaptionStream: captionStream.CaptionStream, 45637 Cea608Stream: captionStream.Cea608Stream, 45638 MetadataStream: metadataStream 45639 }; 45640 45641 for (var type in streamTypes) { 45642 if (streamTypes.hasOwnProperty(type)) { 45643 m2ts[type] = streamTypes[type]; 45644 } 45645 } 45646 45647 var m2ts_1 = m2ts; 45648 var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS; 45649 45650 var _AdtsStream; 45651 45652 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 45653 /* 45654 * Accepts a ElementaryStream and emits data events with parsed 45655 * AAC Audio Frames of the individual packets. Input audio in ADTS 45656 * format is unpacked and re-emitted as AAC frames. 45657 * 45658 * @see http://wiki.multimedia.cx/index.php?title=ADTS 45659 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 45660 */ 45661 45662 _AdtsStream = function AdtsStream(handlePartialSegments) { 45663 var buffer, 45664 frameNum = 0; 45665 45666 _AdtsStream.prototype.init.call(this); 45667 45668 this.push = function (packet) { 45669 var i = 0, 45670 frameLength, 45671 protectionSkipBytes, 45672 frameEnd, 45673 oldBuffer, 45674 sampleCount, 45675 adtsFrameDuration; 45676 45677 if (!handlePartialSegments) { 45678 frameNum = 0; 45679 } 45680 45681 if (packet.type !== 'audio') { 45682 // ignore non-audio data 45683 return; 45684 } // Prepend any data in the buffer to the input data so that we can parse 45685 // aac frames the cross a PES packet boundary 45686 45687 45688 if (buffer) { 45689 oldBuffer = buffer; 45690 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 45691 buffer.set(oldBuffer); 45692 buffer.set(packet.data, oldBuffer.byteLength); 45693 } else { 45694 buffer = packet.data; 45695 } // unpack any ADTS frames which have been fully received 45696 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 45697 45698 45699 while (i + 5 < buffer.length) { 45700 // Look for the start of an ADTS header.. 45701 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 45702 // If a valid header was not found, jump one forward and attempt to 45703 // find a valid ADTS header starting at the next byte 45704 i++; 45705 continue; 45706 } // The protection skip bit tells us if we have 2 bytes of CRC data at the 45707 // end of the ADTS header 45708 45709 45710 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the 45711 // end of the sync sequence 45712 45713 frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5; 45714 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 45715 adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 45716 frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return 45717 // and wait for more data 45718 45719 if (buffer.byteLength < frameEnd) { 45720 return; 45721 } // Otherwise, deliver the complete AAC frame 45722 45723 45724 this.trigger('data', { 45725 pts: packet.pts + frameNum * adtsFrameDuration, 45726 dts: packet.dts + frameNum * adtsFrameDuration, 45727 sampleCount: sampleCount, 45728 audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1, 45729 channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6, 45730 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 45731 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 45732 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 45733 samplesize: 16, 45734 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 45735 }); 45736 frameNum++; // If the buffer is empty, clear it and return 45737 45738 if (buffer.byteLength === frameEnd) { 45739 buffer = undefined; 45740 return; 45741 } // Remove the finished frame from the buffer and start the process again 45742 45743
vendor: 4,881 bytes, lines 45744-45898
45744 buffer = buffer.subarray(frameEnd); 45745 } 45746 }; 45747 45748 this.flush = function () { 45749 frameNum = 0; 45750 this.trigger('done'); 45751 }; 45752 45753 this.reset = function () { 45754 buffer = void 0; 45755 this.trigger('reset'); 45756 }; 45757 45758 this.endTimeline = function () { 45759 buffer = void 0; 45760 this.trigger('endedtimeline'); 45761 }; 45762 }; 45763 45764 _AdtsStream.prototype = new stream(); 45765 var adts = _AdtsStream; 45766 /** 45767 * mux.js 45768 * 45769 * Copyright (c) Brightcove 45770 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 45771 */ 45772 45773 var ExpGolomb; 45774 /** 45775 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 45776 * scheme used by h264. 45777 */ 45778 45779 ExpGolomb = function ExpGolomb(workingData) { 45780 var // the number of bytes left to examine in workingData 45781 workingBytesAvailable = workingData.byteLength, 45782 // the current word being examined 45783 workingWord = 0, 45784 // :uint 45785 // the number of bits left to examine in the current word 45786 workingBitsAvailable = 0; // :uint; 45787 // ():uint 45788 45789 this.length = function () { 45790 return 8 * workingBytesAvailable; 45791 }; // ():uint 45792 45793 45794 this.bitsAvailable = function () { 45795 return 8 * workingBytesAvailable + workingBitsAvailable; 45796 }; // ():void 45797 45798 45799 this.loadWord = function () { 45800 var position = workingData.byteLength - workingBytesAvailable, 45801 workingBytes = new Uint8Array(4), 45802 availableBytes = Math.min(4, workingBytesAvailable); 45803 45804 if (availableBytes === 0) { 45805 throw new Error('no bytes available'); 45806 } 45807 45808 workingBytes.set(workingData.subarray(position, position + availableBytes)); 45809 workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed 45810 45811 workingBitsAvailable = availableBytes * 8; 45812 workingBytesAvailable -= availableBytes; 45813 }; // (count:int):void 45814 45815 45816 this.skipBits = function (count) { 45817 var skipBytes; // :int 45818 45819 if (workingBitsAvailable > count) { 45820 workingWord <<= count; 45821 workingBitsAvailable -= count; 45822 } else { 45823 count -= workingBitsAvailable; 45824 skipBytes = Math.floor(count / 8); 45825 count -= skipBytes * 8; 45826 workingBytesAvailable -= skipBytes; 45827 this.loadWord(); 45828 workingWord <<= count; 45829 workingBitsAvailable -= count; 45830 } 45831 }; // (size:int):uint 45832 45833 45834 this.readBits = function (size) { 45835 var bits = Math.min(workingBitsAvailable, size), 45836 // :uint 45837 valu = workingWord >>> 32 - bits; // :uint 45838 // if size > 31, handle error 45839 45840 workingBitsAvailable -= bits; 45841 45842 if (workingBitsAvailable > 0) { 45843 workingWord <<= bits; 45844 } else if (workingBytesAvailable > 0) { 45845 this.loadWord(); 45846 } 45847 45848 bits = size - bits; 45849 45850 if (bits > 0) { 45851 return valu << bits | this.readBits(bits); 45852 } 45853 45854 return valu; 45855 }; // ():uint 45856 45857 45858 this.skipLeadingZeros = function () { 45859 var leadingZeroCount; // :uint 45860 45861 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 45862 if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) { 45863 // the first bit of working word is 1 45864 workingWord <<= leadingZeroCount; 45865 workingBitsAvailable -= leadingZeroCount; 45866 return leadingZeroCount; 45867 } 45868 } // we exhausted workingWord and still have not found a 1 45869 45870 45871 this.loadWord(); 45872 return leadingZeroCount + this.skipLeadingZeros(); 45873 }; // ():void 45874 45875 45876 this.skipUnsignedExpGolomb = function () { 45877 this.skipBits(1 + this.skipLeadingZeros()); 45878 }; // ():void 45879 45880 45881 this.skipExpGolomb = function () { 45882 this.skipBits(1 + this.skipLeadingZeros()); 45883 }; // ():uint 45884 45885 45886 this.readUnsignedExpGolomb = function () { 45887 var clz = this.skipLeadingZeros(); // :uint 45888 45889 return this.readBits(clz + 1) - 1; 45890 }; // ():int 45891 45892 45893 this.readExpGolomb = function () { 45894 var valu = this.readUnsignedExpGolomb(); // :int 45895 45896 if (0x01 & valu) { 45897 // the number is odd if the low order bit is set 45898 return 1 + valu >>> 1;
vendor: 5,389 bytes, lines 45898-46077
45898 // add 1 to make it even, and divide by 2 45899 } 45900 45901 return -1 * (valu >>> 1); // divide by two then make it negative 45902 }; // Some convenience functions 45903 // :Boolean 45904 45905 45906 this.readBoolean = function () { 45907 return this.readBits(1) === 1; 45908 }; // ():int 45909 45910 45911 this.readUnsignedByte = function () { 45912 return this.readBits(8); 45913 }; 45914 45915 this.loadWord(); 45916 }; 45917 45918 var expGolomb = ExpGolomb; 45919 45920 var _H264Stream, _NalByteStream; 45921 45922 var PROFILES_WITH_OPTIONAL_SPS_DATA; 45923 /** 45924 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 45925 */ 45926 45927 _NalByteStream = function NalByteStream() { 45928 var syncPoint = 0, 45929 i, 45930 buffer; 45931 45932 _NalByteStream.prototype.init.call(this); 45933 /* 45934 * Scans a byte stream and triggers a data event with the NAL units found. 45935 * @param {Object} data Event received from H264Stream 45936 * @param {Uint8Array} data.data The h264 byte stream to be scanned 45937 * 45938 * @see H264Stream.push 45939 */ 45940 45941 45942 this.push = function (data) { 45943 var swapBuffer; 45944 45945 if (!buffer) { 45946 buffer = data.data; 45947 } else { 45948 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 45949 swapBuffer.set(buffer); 45950 swapBuffer.set(data.data, buffer.byteLength); 45951 buffer = swapBuffer; 45952 } 45953 45954 var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B 45955 // scan for NAL unit boundaries 45956 // a match looks like this: 45957 // 0 0 1 .. NAL .. 0 0 1 45958 // ^ sync point ^ i 45959 // or this: 45960 // 0 0 1 .. NAL .. 0 0 0 45961 // ^ sync point ^ i 45962 // advance the sync point to a NAL start, if necessary 45963 45964 for (; syncPoint < len - 3; syncPoint++) { 45965 if (buffer[syncPoint + 2] === 1) { 45966 // the sync point is properly aligned 45967 i = syncPoint + 5; 45968 break; 45969 } 45970 } 45971 45972 while (i < len) { 45973 // look at the current byte to determine if we've hit the end of 45974 // a NAL unit boundary 45975 switch (buffer[i]) { 45976 case 0: 45977 // skip past non-sync sequences 45978 if (buffer[i - 1] !== 0) { 45979 i += 2; 45980 break; 45981 } else if (buffer[i - 2] !== 0) { 45982 i++; 45983 break; 45984 } // deliver the NAL unit if it isn't empty 45985 45986 45987 if (syncPoint + 3 !== i - 2) { 45988 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 45989 } // drop trailing zeroes 45990 45991 45992 do { 45993 i++; 45994 } while (buffer[i] !== 1 && i < len); 45995 45996 syncPoint = i - 2; 45997 i += 3; 45998 break; 45999 46000 case 1: 46001 // skip past non-sync sequences 46002 if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) { 46003 i += 3; 46004 break; 46005 } // deliver the NAL unit 46006 46007 46008 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 46009 syncPoint = i - 2; 46010 i += 3; 46011 break; 46012 46013 default: 46014 // the current byte isn't a one or zero, so it cannot be part 46015 // of a sync sequence 46016 i += 3; 46017 break; 46018 } 46019 } // filter out the NAL units that were delivered 46020 46021 46022 buffer = buffer.subarray(syncPoint); 46023 i -= syncPoint; 46024 syncPoint = 0; 46025 }; 46026 46027 this.reset = function () { 46028 buffer = null; 46029 syncPoint = 0; 46030 this.trigger('reset'); 46031 }; 46032 46033 this.flush = function () { 46034 // deliver the last buffered NAL unit 46035 if (buffer && buffer.byteLength > 3) { 46036 this.trigger('data', buffer.subarray(syncPoint + 3)); 46037 } // reset the stream state 46038 46039 46040 buffer = null; 46041 syncPoint = 0; 46042 this.trigger('done'); 46043 }; 46044 46045 this.endTimeline = function () { 46046 this.flush(); 46047 this.trigger('endedtimeline'); 46048 }; 46049 }; 46050 46051 _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS 46052 // see Recommendation ITU-T H.264 (4/2013), 46053 // 7.3.2.1.1 Sequence parameter set data syntax 46054 46055 PROFILES_WITH_OPTIONAL_SPS_DATA = { 46056 100: true, 46057 110: true, 46058 122: true, 46059 244: true, 46060 44: true, 46061 83: true, 46062 86: true, 46063 118: true, 46064 128: true, 46065 138: true, 46066 139: true, 46067 134: true 46068 }; 46069 /** 46070 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 46071 * events. 46072 */ 46073 46074 _H264Stream = function H264Stream() { 46075 var nalByteStream = new _NalByteStream(), 46076 self, 46077 trackId,
46078 currentPts, 46079 currentDts, 46080 discardEmulationPreventionBytes, 46081 readSequenceParameterSet, 46082 skipScalingList; 46083 46084 _H264Stream.prototype.init.call(this); 46085 46086 self = this; 46087 /* 46088 * Pushes a packet from a stream onto the NalByteStream 46089 * 46090 * @param {Object} packet - A packet received from a stream 46091 * @param {Uint8Array} packet.data - The raw bytes of the packet 46092 * @param {Number} packet.dts - Decode timestamp of the packet 46093 * @param {Number} packet.pts - Presentation timestamp of the packet 46094 * @param {Number} packet.trackId - The id of the h264 track this packet came from 46095 * @param {('video'|'audio')} packet.type - The type of packet 46096 * 46097 */ 46098 46099 this.push = function (packet) { 46100 if (packet.type !== 'video') { 46101 return; 46102 } 46103 46104 trackId = packet.trackId; 46105 currentPts = packet.pts; 46106 currentDts = packet.dts; 46107 nalByteStream.push(packet); 46108 }; 46109 /* 46110 * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps 46111 * for the NALUs to the next stream component. 46112 * Also, preprocess caption and sequence parameter NALUs. 46113 * 46114 * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push` 46115 * @see NalByteStream.push 46116 */ 46117 46118 46119 nalByteStream.on('data', function (data) { 46120 var event = { 46121 trackId: trackId,
vendor: 12,978 bytes, lines 46122-46488
46122 pts: currentPts, 46123 dts: currentDts, 46124 data: data 46125 }; 46126 46127 switch (data[0] & 0x1f) { 46128 case 0x05: 46129 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 46130 break; 46131 46132 case 0x06: 46133 event.nalUnitType = 'sei_rbsp'; 46134 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 46135 break; 46136 46137 case 0x07: 46138 event.nalUnitType = 'seq_parameter_set_rbsp'; 46139 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 46140 event.config = readSequenceParameterSet(event.escapedRBSP); 46141 break; 46142 46143 case 0x08: 46144 event.nalUnitType = 'pic_parameter_set_rbsp'; 46145 break; 46146 46147 case 0x09: 46148 event.nalUnitType = 'access_unit_delimiter_rbsp'; 46149 break; 46150 } // This triggers data on the H264Stream 46151 46152 46153 self.trigger('data', event); 46154 }); 46155 nalByteStream.on('done', function () { 46156 self.trigger('done'); 46157 }); 46158 nalByteStream.on('partialdone', function () { 46159 self.trigger('partialdone'); 46160 }); 46161 nalByteStream.on('reset', function () { 46162 self.trigger('reset'); 46163 }); 46164 nalByteStream.on('endedtimeline', function () { 46165 self.trigger('endedtimeline'); 46166 }); 46167 46168 this.flush = function () { 46169 nalByteStream.flush(); 46170 }; 46171 46172 this.partialFlush = function () { 46173 nalByteStream.partialFlush(); 46174 }; 46175 46176 this.reset = function () { 46177 nalByteStream.reset(); 46178 }; 46179 46180 this.endTimeline = function () { 46181 nalByteStream.endTimeline(); 46182 }; 46183 /** 46184 * Advance the ExpGolomb decoder past a scaling list. The scaling 46185 * list is optionally transmitted as part of a sequence parameter 46186 * set and is not relevant to transmuxing. 46187 * @param count {number} the number of entries in this scaling list 46188 * @param expGolombDecoder {object} an ExpGolomb pointed to the 46189 * start of a scaling list 46190 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 46191 */ 46192 46193 46194 skipScalingList = function skipScalingList(count, expGolombDecoder) { 46195 var lastScale = 8, 46196 nextScale = 8, 46197 j, 46198 deltaScale; 46199 46200 for (j = 0; j < count; j++) { 46201 if (nextScale !== 0) { 46202 deltaScale = expGolombDecoder.readExpGolomb(); 46203 nextScale = (lastScale + deltaScale + 256) % 256; 46204 } 46205 46206 lastScale = nextScale === 0 ? lastScale : nextScale; 46207 } 46208 }; 46209 /** 46210 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 46211 * Sequence Payload" 46212 * @param data {Uint8Array} the bytes of a RBSP from a NAL 46213 * unit 46214 * @return {Uint8Array} the RBSP without any Emulation 46215 * Prevention Bytes 46216 */ 46217 46218 46219 discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 46220 var length = data.byteLength, 46221 emulationPreventionBytesPositions = [], 46222 i = 1, 46223 newLength, 46224 newData; // Find all `Emulation Prevention Bytes` 46225 46226 while (i < length - 2) { 46227 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 46228 emulationPreventionBytesPositions.push(i + 2); 46229 i += 2; 46230 } else { 46231 i++; 46232 } 46233 } // If no Emulation Prevention Bytes were found just return the original 46234 // array 46235 46236 46237 if (emulationPreventionBytesPositions.length === 0) { 46238 return data; 46239 } // Create a new array to hold the NAL unit data 46240 46241 46242 newLength = length - emulationPreventionBytesPositions.length; 46243 newData = new Uint8Array(newLength); 46244 var sourceIndex = 0; 46245 46246 for (i = 0; i < newLength; sourceIndex++, i++) { 46247 if (sourceIndex === emulationPreventionBytesPositions[0]) { 46248 // Skip this byte 46249 sourceIndex++; // Remove this position index 46250 46251 emulationPreventionBytesPositions.shift(); 46252 } 46253 46254 newData[i] = data[sourceIndex]; 46255 } 46256 46257 return newData; 46258 }; 46259 /** 46260 * Read a sequence parameter set and return some interesting video 46261 * properties. A sequence parameter set is the H264 metadata that 46262 * describes the properties of upcoming video frames. 46263 * @param data {Uint8Array} the bytes of a sequence parameter set 46264 * @return {object} an object with configuration parsed from the 46265 * sequence parameter set, including the dimensions of the 46266 * associated video frames. 46267 */ 46268 46269 46270 readSequenceParameterSet = function readSequenceParameterSet(data) { 46271 var frameCropLeftOffset = 0, 46272 frameCropRightOffset = 0, 46273 frameCropTopOffset = 0, 46274 frameCropBottomOffset = 0, 46275 sarScale = 1, 46276 expGolombDecoder, 46277 profileIdc, 46278 levelIdc, 46279 profileCompatibility, 46280 chromaFormatIdc, 46281 picOrderCntType, 46282 numRefFramesInPicOrderCntCycle, 46283 picWidthInMbsMinus1, 46284 picHeightInMapUnitsMinus1, 46285 frameMbsOnlyFlag, 46286 scalingListCount, 46287 sarRatio, 46288 aspectRatioIdc, 46289 i; 46290 expGolombDecoder = new expGolomb(data); 46291 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 46292 46293 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 46294 46295 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 46296 46297 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 46298 // some profiles have more optional data we don't need 46299 46300 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 46301 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 46302 46303 if (chromaFormatIdc === 3) { 46304 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 46305 } 46306 46307 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 46308 46309 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 46310 46311 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 46312 46313 if (expGolombDecoder.readBoolean()) { 46314 // seq_scaling_matrix_present_flag 46315 scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; 46316 46317 for (i = 0; i < scalingListCount; i++) { 46318 if (expGolombDecoder.readBoolean()) { 46319 // seq_scaling_list_present_flag[ i ] 46320 if (i < 6) { 46321 skipScalingList(16, expGolombDecoder); 46322 } else { 46323 skipScalingList(64, expGolombDecoder); 46324 } 46325 } 46326 } 46327 } 46328 } 46329 46330 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 46331 46332 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 46333 46334 if (picOrderCntType === 0) { 46335 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 46336 } else if (picOrderCntType === 1) { 46337 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 46338 46339 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 46340 46341 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 46342 46343 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 46344 46345 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 46346 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 46347 } 46348 } 46349 46350 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 46351 46352 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 46353 46354 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 46355 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 46356 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 46357 46358 if (frameMbsOnlyFlag === 0) { 46359 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 46360 } 46361 46362 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 46363 46364 if (expGolombDecoder.readBoolean()) { 46365 // frame_cropping_flag 46366 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 46367 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 46368 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 46369 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 46370 } 46371 46372 if (expGolombDecoder.readBoolean()) { 46373 // vui_parameters_present_flag 46374 if (expGolombDecoder.readBoolean()) { 46375 // aspect_ratio_info_present_flag 46376 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 46377 46378 switch (aspectRatioIdc) { 46379 case 1: 46380 sarRatio = [1, 1]; 46381 break; 46382 46383 case 2: 46384 sarRatio = [12, 11]; 46385 break; 46386 46387 case 3: 46388 sarRatio = [10, 11]; 46389 break; 46390 46391 case 4: 46392 sarRatio = [16, 11]; 46393 break; 46394 46395 case 5: 46396 sarRatio = [40, 33]; 46397 break; 46398 46399 case 6: 46400 sarRatio = [24, 11]; 46401 break; 46402 46403 case 7: 46404 sarRatio = [20, 11]; 46405 break; 46406 46407 case 8: 46408 sarRatio = [32, 11]; 46409 break; 46410 46411 case 9: 46412 sarRatio = [80, 33]; 46413 break; 46414 46415 case 10: 46416 sarRatio = [18, 11]; 46417 break; 46418 46419 case 11: 46420 sarRatio = [15, 11]; 46421 break; 46422 46423 case 12: 46424 sarRatio = [64, 33]; 46425 break; 46426 46427 case 13: 46428 sarRatio = [160, 99]; 46429 break; 46430 46431 case 14: 46432 sarRatio = [4, 3]; 46433 break; 46434 46435 case 15: 46436 sarRatio = [3, 2]; 46437 break; 46438 46439 case 16: 46440 sarRatio = [2, 1]; 46441 break; 46442 46443 case 255: 46444 { 46445 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()]; 46446 break; 46447 } 46448 } 46449 46450 if (sarRatio) { 46451 sarScale = sarRatio[0] / sarRatio[1]; 46452 } 46453 } 46454 } 46455 46456 return { 46457 profileIdc: profileIdc, 46458 levelIdc: levelIdc, 46459 profileCompatibility: profileCompatibility, 46460 width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 46461 height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2, 46462 sarRatio: sarRatio 46463 }; 46464 }; 46465 }; 46466 46467 _H264Stream.prototype = new stream(); 46468 var h264 = { 46469 H264Stream: _H264Stream, 46470 NalByteStream: _NalByteStream 46471 }; 46472 /** 46473 * mux.js 46474 * 46475 * Copyright (c) Brightcove 46476 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 46477 * 46478 * Utilities to detect basic properties and metadata about Aac data. 46479 */ 46480 46481 var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 46482 46483 var isLikelyAacData = function isLikelyAacData(data) { 46484 if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) { 46485 return true; 46486 } 46487 46488 return false;
vendor: 7,716 bytes, lines 46489-46695
46489 }; 46490 46491 var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) { 46492 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 46493 }; // return a percent-encoded representation of the specified byte range 46494 // @see http://en.wikipedia.org/wiki/Percent-encoding 46495 46496 46497 var percentEncode$1 = function percentEncode(bytes, start, end) { 46498 var i, 46499 result = ''; 46500 46501 for (i = start; i < end; i++) { 46502 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 46503 } 46504 46505 return result; 46506 }; // return the string representation of the specified byte range, 46507 // interpreted as ISO-8859-1. 46508 46509 46510 var parseIso88591$1 = function parseIso88591(bytes, start, end) { 46511 return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line 46512 }; 46513 46514 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 46515 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 46516 flags = header[byteIndex + 5], 46517 footerPresent = (flags & 16) >> 4; 46518 46519 if (footerPresent) { 46520 return returnSize + 20; 46521 } 46522 46523 return returnSize + 10; 46524 }; 46525 46526 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 46527 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 46528 middle = header[byteIndex + 4] << 3, 46529 highTwo = header[byteIndex + 3] & 0x3 << 11; 46530 return highTwo | middle | lowThree; 46531 }; 46532 46533 var parseType = function parseType(header, byteIndex) { 46534 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 46535 return 'timed-metadata'; 46536 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 46537 return 'audio'; 46538 } 46539 46540 return null; 46541 }; 46542 46543 var parseSampleRate = function parseSampleRate(packet) { 46544 var i = 0; 46545 46546 while (i + 5 < packet.length) { 46547 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 46548 // If a valid header was not found, jump one forward and attempt to 46549 // find a valid ADTS header starting at the next byte 46550 i++; 46551 continue; 46552 } 46553 46554 return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2]; 46555 } 46556 46557 return null; 46558 }; 46559 46560 var parseAacTimestamp = function parseAacTimestamp(packet) { 46561 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 46562 46563 frameStart = 10; 46564 46565 if (packet[5] & 0x40) { 46566 // advance the frame start past the extended header 46567 frameStart += 4; // header size field 46568 46569 frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14)); 46570 } // parse one or more ID3 frames 46571 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 46572 46573 46574 do { 46575 // determine the number of bytes in this frame 46576 frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8)); 46577 46578 if (frameSize < 1) { 46579 return null; 46580 } 46581 46582 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 46583 46584 if (frameHeader === 'PRIV') { 46585 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 46586 46587 for (var i = 0; i < frame.byteLength; i++) { 46588 if (frame[i] === 0) { 46589 var owner = parseIso88591$1(frame, 0, i); 46590 46591 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 46592 var d = frame.subarray(i + 1); 46593 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 46594 size *= 4; 46595 size += d[7] & 0x03; 46596 return size; 46597 } 46598 46599 break; 46600 } 46601 } 46602 } 46603 46604 frameStart += 10; // advance past the frame header 46605 46606 frameStart += frameSize; // advance past the frame body 46607 } while (frameStart < packet.byteLength); 46608 46609 return null; 46610 }; 46611 46612 var utils = { 46613 isLikelyAacData: isLikelyAacData, 46614 parseId3TagSize: parseId3TagSize, 46615 parseAdtsSize: parseAdtsSize, 46616 parseType: parseType, 46617 parseSampleRate: parseSampleRate, 46618 parseAacTimestamp: parseAacTimestamp 46619 }; // Constants 46620 46621 var _AacStream; 46622 /** 46623 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 46624 */ 46625 46626 46627 _AacStream = function AacStream() { 46628 var everything = new Uint8Array(), 46629 timeStamp = 0; 46630 46631 _AacStream.prototype.init.call(this); 46632 46633 this.setTimestamp = function (timestamp) { 46634 timeStamp = timestamp; 46635 }; 46636 46637 this.push = function (bytes) { 46638 var frameSize = 0, 46639 byteIndex = 0, 46640 bytesLeft, 46641 chunk, 46642 packet, 46643 tempLength; // If there are bytes remaining from the last segment, prepend them to the 46644 // bytes that were pushed in 46645 46646 if (everything.length) { 46647 tempLength = everything.length; 46648 everything = new Uint8Array(bytes.byteLength + tempLength); 46649 everything.set(everything.subarray(0, tempLength)); 46650 everything.set(bytes, tempLength); 46651 } else { 46652 everything = bytes; 46653 } 46654 46655 while (everything.length - byteIndex >= 3) { 46656 if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) { 46657 // Exit early because we don't have enough to parse 46658 // the ID3 tag header 46659 if (everything.length - byteIndex < 10) { 46660 break; 46661 } // check framesize 46662 46663 46664 frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 46665 // to emit a full packet 46666 // Add to byteIndex to support multiple ID3 tags in sequence 46667 46668 if (byteIndex + frameSize > everything.length) { 46669 break; 46670 } 46671 46672 chunk = { 46673 type: 'timed-metadata', 46674 data: everything.subarray(byteIndex, byteIndex + frameSize) 46675 }; 46676 this.trigger('data', chunk); 46677 byteIndex += frameSize; 46678 continue; 46679 } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) { 46680 // Exit early because we don't have enough to parse 46681 // the ADTS frame header 46682 if (everything.length - byteIndex < 7) { 46683 break; 46684 } 46685 46686 frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 46687 // to emit a full packet 46688 46689 if (byteIndex + frameSize > everything.length) { 46690 break; 46691 } 46692 46693 packet = { 46694 type: 'audio', 46695 data: everything.subarray(byteIndex, byteIndex + frameSize),
vendor: 10,030 bytes, lines 46696-46956
46696 pts: timeStamp, 46697 dts: timeStamp 46698 }; 46699 this.trigger('data', packet); 46700 byteIndex += frameSize; 46701 continue; 46702 } 46703 46704 byteIndex++; 46705 } 46706 46707 bytesLeft = everything.length - byteIndex; 46708 46709 if (bytesLeft > 0) { 46710 everything = everything.subarray(byteIndex); 46711 } else { 46712 everything = new Uint8Array(); 46713 } 46714 }; 46715 46716 this.reset = function () { 46717 everything = new Uint8Array(); 46718 this.trigger('reset'); 46719 }; 46720 46721 this.endTimeline = function () { 46722 everything = new Uint8Array(); 46723 this.trigger('endedtimeline'); 46724 }; 46725 }; 46726 46727 _AacStream.prototype = new stream(); 46728 var aac = _AacStream; 46729 var H264Stream = h264.H264Stream; 46730 var isLikelyAacData$1 = utils.isLikelyAacData; 46731 var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS; // constants 46732 46733 var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize']; 46734 var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio']; // object types 46735 46736 var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream; 46737 /** 46738 * Compare two arrays (even typed) for same-ness 46739 */ 46740 46741 46742 var arrayEquals = function arrayEquals(a, b) { 46743 var i; 46744 46745 if (a.length !== b.length) { 46746 return false; 46747 } // compare the value of each element in the array 46748 46749 46750 for (i = 0; i < a.length; i++) { 46751 if (a[i] !== b[i]) { 46752 return false; 46753 } 46754 } 46755 46756 return true; 46757 }; 46758 46759 var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) { 46760 var ptsOffsetFromDts = startPts - startDts, 46761 decodeDuration = endDts - startDts, 46762 presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment, 46763 // however, the player time values will reflect a start from the baseMediaDecodeTime. 46764 // In order to provide relevant values for the player times, base timing info on the 46765 // baseMediaDecodeTime and the DTS and PTS durations of the segment. 46766 46767 return { 46768 start: { 46769 dts: baseMediaDecodeTime, 46770 pts: baseMediaDecodeTime + ptsOffsetFromDts 46771 }, 46772 end: { 46773 dts: baseMediaDecodeTime + decodeDuration, 46774 pts: baseMediaDecodeTime + presentationDuration 46775 }, 46776 prependedContentDuration: prependedContentDuration, 46777 baseMediaDecodeTime: baseMediaDecodeTime 46778 }; 46779 }; 46780 /** 46781 * Constructs a single-track, ISO BMFF media segment from AAC data 46782 * events. The output of this stream can be fed to a SourceBuffer 46783 * configured with a suitable initialization segment. 46784 * @param track {object} track metadata configuration 46785 * @param options {object} transmuxer options object 46786 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 46787 * in the source; false to adjust the first segment to start at 0. 46788 */ 46789 46790 46791 _AudioSegmentStream = function AudioSegmentStream(track, options) { 46792 var adtsFrames = [], 46793 sequenceNumber = 0, 46794 earliestAllowedDts = 0, 46795 audioAppendStartTs = 0, 46796 videoBaseMediaDecodeTime = Infinity; 46797 options = options || {}; 46798 46799 _AudioSegmentStream.prototype.init.call(this); 46800 46801 this.push = function (data) { 46802 trackDecodeInfo.collectDtsInfo(track, data); 46803 46804 if (track) { 46805 AUDIO_PROPERTIES.forEach(function (prop) { 46806 track[prop] = data[prop]; 46807 }); 46808 } // buffer audio data until end() is called 46809 46810 46811 adtsFrames.push(data); 46812 }; 46813 46814 this.setEarliestDts = function (earliestDts) { 46815 earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; 46816 }; 46817 46818 this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) { 46819 videoBaseMediaDecodeTime = baseMediaDecodeTime; 46820 }; 46821 46822 this.setAudioAppendStart = function (timestamp) { 46823 audioAppendStartTs = timestamp; 46824 }; 46825 46826 this.flush = function () { 46827 var frames, moof, mdat, boxes, frameDuration; // return early if no audio data has been observed 46828 46829 if (adtsFrames.length === 0) { 46830 this.trigger('done', 'AudioSegmentStream'); 46831 return; 46832 } 46833 46834 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts); 46835 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 46836 audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to 46837 // samples (that is, adts frames) in the audio data 46838 46839 track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat 46840 46841 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames)); 46842 adtsFrames = []; 46843 moof = mp4Generator.moof(sequenceNumber, [track]); 46844 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time 46845 46846 sequenceNumber++; 46847 boxes.set(moof); 46848 boxes.set(mdat, moof.byteLength); 46849 trackDecodeInfo.clearDtsInfo(track); 46850 frameDuration = Math.ceil(ONE_SECOND_IN_TS$3 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with 46851 // tests) on adding the timingInfo event. However, it seems unlikely that there's a 46852 // valid use-case where an init segment/data should be triggered without associated 46853 // frames. Leaving for now, but should be looked into. 46854 46855 if (frames.length) { 46856 this.trigger('timingInfo', { 46857 start: frames[0].pts, 46858 end: frames[0].pts + frames.length * frameDuration 46859 }); 46860 } 46861 46862 this.trigger('data', { 46863 track: track, 46864 boxes: boxes 46865 }); 46866 this.trigger('done', 'AudioSegmentStream'); 46867 }; 46868 46869 this.reset = function () { 46870 trackDecodeInfo.clearDtsInfo(track); 46871 adtsFrames = []; 46872 this.trigger('reset'); 46873 }; 46874 }; 46875 46876 _AudioSegmentStream.prototype = new stream(); 46877 /** 46878 * Constructs a single-track, ISO BMFF media segment from H264 data 46879 * events. The output of this stream can be fed to a SourceBuffer 46880 * configured with a suitable initialization segment. 46881 * @param track {object} track metadata configuration 46882 * @param options {object} transmuxer options object 46883 * @param options.alignGopsAtEnd {boolean} If true, start from the end of the 46884 * gopsToAlignWith list when attempting to align gop pts 46885 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 46886 * in the source; false to adjust the first segment to start at 0. 46887 */ 46888 46889 _VideoSegmentStream = function VideoSegmentStream(track, options) { 46890 var sequenceNumber = 0, 46891 nalUnits = [], 46892 gopsToAlignWith = [], 46893 config, 46894 pps; 46895 options = options || {}; 46896 46897 _VideoSegmentStream.prototype.init.call(this); 46898 46899 delete track.minPTS; 46900 this.gopCache_ = []; 46901 /** 46902 * Constructs a ISO BMFF segment given H264 nalUnits 46903 * @param {Object} nalUnit A data event representing a nalUnit 46904 * @param {String} nalUnit.nalUnitType 46905 * @param {Object} nalUnit.config Properties for a mp4 track 46906 * @param {Uint8Array} nalUnit.data The nalUnit bytes 46907 * @see lib/codecs/h264.js 46908 **/ 46909 46910 this.push = function (nalUnit) { 46911 trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config 46912 46913 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 46914 config = nalUnit.config; 46915 track.sps = [nalUnit.data]; 46916 VIDEO_PROPERTIES.forEach(function (prop) { 46917 track[prop] = config[prop]; 46918 }, this); 46919 } 46920 46921 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) { 46922 pps = nalUnit.data; 46923 track.pps = [nalUnit.data]; 46924 } // buffer video until flush() is called 46925 46926 46927 nalUnits.push(nalUnit); 46928 }; 46929 /** 46930 * Pass constructed ISO BMFF track and boxes on to the 46931 * next stream in the pipeline 46932 **/ 46933 46934 46935 this.flush = function () { 46936 var frames, 46937 gopForFusion, 46938 gops, 46939 moof, 46940 mdat, 46941 boxes, 46942 prependedContentDuration = 0, 46943 firstGop, 46944 lastGop; // Throw away nalUnits at the start of the byte stream until 46945 // we find the first AUD 46946 46947 while (nalUnits.length) { 46948 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 46949 break; 46950 } 46951 46952 nalUnits.shift(); 46953 } // Return early if no video data has been observed 46954 46955 46956 if (nalUnits.length === 0) {
vendor: 10,988 bytes, lines 46957-47212
46957 this.resetStream_(); 46958 this.trigger('done', 'VideoSegmentStream'); 46959 return; 46960 } // Organize the raw nal-units into arrays that represent 46961 // higher-level constructs such as frames and gops 46962 // (group-of-pictures) 46963 46964 46965 frames = frameUtils.groupNalsIntoFrames(nalUnits); 46966 gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have 46967 // a problem since MSE (on Chrome) requires a leading keyframe. 46968 // 46969 // We have two approaches to repairing this situation: 46970 // 1) GOP-FUSION: 46971 // This is where we keep track of the GOPS (group-of-pictures) 46972 // from previous fragments and attempt to find one that we can 46973 // prepend to the current fragment in order to create a valid 46974 // fragment. 46975 // 2) KEYFRAME-PULLING: 46976 // Here we search for the first keyframe in the fragment and 46977 // throw away all the frames between the start of the fragment 46978 // and that keyframe. We then extend the duration and pull the 46979 // PTS of the keyframe forward so that it covers the time range 46980 // of the frames that were disposed of. 46981 // 46982 // #1 is far prefereable over #2 which can cause "stuttering" but 46983 // requires more things to be just right. 46984 46985 if (!gops[0][0].keyFrame) { 46986 // Search for a gop for fusion from our gopCache 46987 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 46988 46989 if (gopForFusion) { 46990 // in order to provide more accurate timing information about the segment, save 46991 // the number of seconds prepended to the original segment due to GOP fusion 46992 prependedContentDuration = gopForFusion.duration; 46993 gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the 46994 // new gop at the beginning 46995 46996 gops.byteLength += gopForFusion.byteLength; 46997 gops.nalCount += gopForFusion.nalCount; 46998 gops.pts = gopForFusion.pts; 46999 gops.dts = gopForFusion.dts; 47000 gops.duration += gopForFusion.duration; 47001 } else { 47002 // If we didn't find a candidate gop fall back to keyframe-pulling 47003 gops = frameUtils.extendFirstKeyFrame(gops); 47004 } 47005 } // Trim gops to align with gopsToAlignWith 47006 47007 47008 if (gopsToAlignWith.length) { 47009 var alignedGops; 47010 47011 if (options.alignGopsAtEnd) { 47012 alignedGops = this.alignGopsAtEnd_(gops); 47013 } else { 47014 alignedGops = this.alignGopsAtStart_(gops); 47015 } 47016 47017 if (!alignedGops) { 47018 // save all the nals in the last GOP into the gop cache 47019 this.gopCache_.unshift({ 47020 gop: gops.pop(), 47021 pps: track.pps, 47022 sps: track.sps 47023 }); // Keep a maximum of 6 GOPs in the cache 47024 47025 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 47026 47027 nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith 47028 47029 this.resetStream_(); 47030 this.trigger('done', 'VideoSegmentStream'); 47031 return; 47032 } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct 47033 // when recalculated before sending off to CoalesceStream 47034 47035 47036 trackDecodeInfo.clearDtsInfo(track); 47037 gops = alignedGops; 47038 } 47039 47040 trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to 47041 // samples (that is, frames) in the video data 47042 47043 track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat 47044 47045 mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops)); 47046 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 47047 this.trigger('processedGopsInfo', gops.map(function (gop) { 47048 return { 47049 pts: gop.pts, 47050 dts: gop.dts, 47051 byteLength: gop.byteLength 47052 }; 47053 })); 47054 firstGop = gops[0]; 47055 lastGop = gops[gops.length - 1]; 47056 this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); 47057 this.trigger('timingInfo', { 47058 start: gops[0].pts, 47059 end: gops[gops.length - 1].pts + gops[gops.length - 1].duration 47060 }); // save all the nals in the last GOP into the gop cache 47061 47062 this.gopCache_.unshift({ 47063 gop: gops.pop(), 47064 pps: track.pps, 47065 sps: track.sps 47066 }); // Keep a maximum of 6 GOPs in the cache 47067 47068 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 47069 47070 nalUnits = []; 47071 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 47072 this.trigger('timelineStartInfo', track.timelineStartInfo); 47073 moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of 47074 // throwing away hundreds of media segment fragments 47075 47076 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time 47077 47078 sequenceNumber++; 47079 boxes.set(moof); 47080 boxes.set(mdat, moof.byteLength); 47081 this.trigger('data', { 47082 track: track, 47083 boxes: boxes 47084 }); 47085 this.resetStream_(); // Continue with the flush process now 47086 47087 this.trigger('done', 'VideoSegmentStream'); 47088 }; 47089 47090 this.reset = function () { 47091 this.resetStream_(); 47092 nalUnits = []; 47093 this.gopCache_.length = 0; 47094 gopsToAlignWith.length = 0; 47095 this.trigger('reset'); 47096 }; 47097 47098 this.resetStream_ = function () { 47099 trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments 47100 // for instance, when we are rendition switching 47101 47102 config = undefined; 47103 pps = undefined; 47104 }; // Search for a candidate Gop for gop-fusion from the gop cache and 47105 // return it or return null if no good candidate was found 47106 47107 47108 this.getGopForFusion_ = function (nalUnit) { 47109 var halfSecond = 45000, 47110 // Half-a-second in a 90khz clock 47111 allowableOverlap = 10000, 47112 // About 3 frames @ 30fps 47113 nearestDistance = Infinity, 47114 dtsDistance, 47115 nearestGopObj, 47116 currentGop, 47117 currentGopObj, 47118 i; // Search for the GOP nearest to the beginning of this nal unit 47119 47120 for (i = 0; i < this.gopCache_.length; i++) { 47121 currentGopObj = this.gopCache_[i]; 47122 currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS 47123 47124 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 47125 continue; 47126 } // Reject Gops that would require a negative baseMediaDecodeTime 47127 47128 47129 if (currentGop.dts < track.timelineStartInfo.dts) { 47130 continue; 47131 } // The distance between the end of the gop and the start of the nalUnit 47132 47133 47134 dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within 47135 // a half-second of the nal unit 47136 47137 if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) { 47138 // Always use the closest GOP we found if there is more than 47139 // one candidate 47140 if (!nearestGopObj || nearestDistance > dtsDistance) { 47141 nearestGopObj = currentGopObj; 47142 nearestDistance = dtsDistance; 47143 } 47144 } 47145 } 47146 47147 if (nearestGopObj) { 47148 return nearestGopObj.gop; 47149 } 47150 47151 return null; 47152 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 47153 // of gopsToAlignWith starting from the START of the list 47154 47155 47156 this.alignGopsAtStart_ = function (gops) { 47157 var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops; 47158 byteLength = gops.byteLength; 47159 nalCount = gops.nalCount; 47160 duration = gops.duration; 47161 alignIndex = gopIndex = 0; 47162 47163 while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) { 47164 align = gopsToAlignWith[alignIndex]; 47165 gop = gops[gopIndex]; 47166 47167 if (align.pts === gop.pts) { 47168 break; 47169 } 47170 47171 if (gop.pts > align.pts) { 47172 // this current gop starts after the current gop we want to align on, so increment 47173 // align index 47174 alignIndex++; 47175 continue; 47176 } // current gop starts before the current gop we want to align on. so increment gop 47177 // index 47178 47179 47180 gopIndex++; 47181 byteLength -= gop.byteLength; 47182 nalCount -= gop.nalCount; 47183 duration -= gop.duration; 47184 } 47185 47186 if (gopIndex === 0) { 47187 // no gops to trim 47188 return gops; 47189 } 47190 47191 if (gopIndex === gops.length) { 47192 // all gops trimmed, skip appending all gops 47193 return null; 47194 } 47195 47196 alignedGops = gops.slice(gopIndex); 47197 alignedGops.byteLength = byteLength; 47198 alignedGops.duration = duration; 47199 alignedGops.nalCount = nalCount; 47200 alignedGops.pts = alignedGops[0].pts; 47201 alignedGops.dts = alignedGops[0].dts; 47202 return alignedGops; 47203 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 47204 // of gopsToAlignWith starting from the END of the list 47205 47206 47207 this.alignGopsAtEnd_ = function (gops) { 47208 var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound; 47209 alignIndex = gopsToAlignWith.length - 1; 47210 gopIndex = gops.length - 1; 47211 alignEndIndex = null; 47212 matchFound = false;
vendor: 4,591 bytes, lines 47213-47341
47213 47214 while (alignIndex >= 0 && gopIndex >= 0) { 47215 align = gopsToAlignWith[alignIndex]; 47216 gop = gops[gopIndex]; 47217 47218 if (align.pts === gop.pts) { 47219 matchFound = true; 47220 break; 47221 } 47222 47223 if (align.pts > gop.pts) { 47224 alignIndex--; 47225 continue; 47226 } 47227 47228 if (alignIndex === gopsToAlignWith.length - 1) { 47229 // gop.pts is greater than the last alignment candidate. If no match is found 47230 // by the end of this loop, we still want to append gops that come after this 47231 // point 47232 alignEndIndex = gopIndex; 47233 } 47234 47235 gopIndex--; 47236 } 47237 47238 if (!matchFound && alignEndIndex === null) { 47239 return null; 47240 } 47241 47242 var trimIndex; 47243 47244 if (matchFound) { 47245 trimIndex = gopIndex; 47246 } else { 47247 trimIndex = alignEndIndex; 47248 } 47249 47250 if (trimIndex === 0) { 47251 return gops; 47252 } 47253 47254 var alignedGops = gops.slice(trimIndex); 47255 var metadata = alignedGops.reduce(function (total, gop) { 47256 total.byteLength += gop.byteLength; 47257 total.duration += gop.duration; 47258 total.nalCount += gop.nalCount; 47259 return total; 47260 }, { 47261 byteLength: 0, 47262 duration: 0, 47263 nalCount: 0 47264 }); 47265 alignedGops.byteLength = metadata.byteLength; 47266 alignedGops.duration = metadata.duration; 47267 alignedGops.nalCount = metadata.nalCount; 47268 alignedGops.pts = alignedGops[0].pts; 47269 alignedGops.dts = alignedGops[0].dts; 47270 return alignedGops; 47271 }; 47272 47273 this.alignGopsWith = function (newGopsToAlignWith) { 47274 gopsToAlignWith = newGopsToAlignWith; 47275 }; 47276 }; 47277 47278 _VideoSegmentStream.prototype = new stream(); 47279 /** 47280 * A Stream that can combine multiple streams (ie. audio & video) 47281 * into a single output segment for MSE. Also supports audio-only 47282 * and video-only streams. 47283 * @param options {object} transmuxer options object 47284 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 47285 * in the source; false to adjust the first segment to start at media timeline start. 47286 */ 47287 47288 _CoalesceStream = function CoalesceStream(options, metadataStream) { 47289 // Number of Tracks per output segment 47290 // If greater than 1, we combine multiple 47291 // tracks into a single segment 47292 this.numberOfTracks = 0; 47293 this.metadataStream = metadataStream; 47294 options = options || {}; 47295 47296 if (typeof options.remux !== 'undefined') { 47297 this.remuxTracks = !!options.remux; 47298 } else { 47299 this.remuxTracks = true; 47300 } 47301 47302 if (typeof options.keepOriginalTimestamps === 'boolean') { 47303 this.keepOriginalTimestamps = options.keepOriginalTimestamps; 47304 } else { 47305 this.keepOriginalTimestamps = false; 47306 } 47307 47308 this.pendingTracks = []; 47309 this.videoTrack = null; 47310 this.pendingBoxes = []; 47311 this.pendingCaptions = []; 47312 this.pendingMetadata = []; 47313 this.pendingBytes = 0; 47314 this.emittedTracks = 0; 47315 47316 _CoalesceStream.prototype.init.call(this); // Take output from multiple 47317 47318 47319 this.push = function (output) { 47320 // buffer incoming captions until the associated video segment 47321 // finishes 47322 if (output.text) { 47323 return this.pendingCaptions.push(output); 47324 } // buffer incoming id3 tags until the final flush 47325 47326 47327 if (output.frames) { 47328 return this.pendingMetadata.push(output); 47329 } // Add this track to the list of pending tracks and store 47330 // important information required for the construction of 47331 // the final segment 47332 47333 47334 this.pendingTracks.push(output.track); 47335 this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome? 47336 // We unshift audio and push video because 47337 // as of Chrome 75 when switching from 47338 // one init segment to another if the video 47339 // mdat does not appear after the audio mdat 47340 // only audio will play for the duration of our transmux. 47341
vendor: 18,043 bytes, lines 47342-47729
47342 if (output.track.type === 'video') { 47343 this.videoTrack = output.track; 47344 this.pendingBoxes.push(output.boxes); 47345 } 47346 47347 if (output.track.type === 'audio') { 47348 this.audioTrack = output.track; 47349 this.pendingBoxes.unshift(output.boxes); 47350 } 47351 }; 47352 }; 47353 47354 _CoalesceStream.prototype = new stream(); 47355 47356 _CoalesceStream.prototype.flush = function (flushSource) { 47357 var offset = 0, 47358 event = { 47359 captions: [], 47360 captionStreams: {}, 47361 metadata: [], 47362 info: {} 47363 }, 47364 caption, 47365 id3, 47366 initSegment, 47367 timelineStartPts = 0, 47368 i; 47369 47370 if (this.pendingTracks.length < this.numberOfTracks) { 47371 if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') { 47372 // Return because we haven't received a flush from a data-generating 47373 // portion of the segment (meaning that we have only recieved meta-data 47374 // or captions.) 47375 return; 47376 } else if (this.remuxTracks) { 47377 // Return until we have enough tracks from the pipeline to remux (if we 47378 // are remuxing audio and video into a single MP4) 47379 return; 47380 } else if (this.pendingTracks.length === 0) { 47381 // In the case where we receive a flush without any data having been 47382 // received we consider it an emitted track for the purposes of coalescing 47383 // `done` events. 47384 // We do this for the case where there is an audio and video track in the 47385 // segment but no audio data. (seen in several playlists with alternate 47386 // audio tracks and no audio present in the main TS segments.) 47387 this.emittedTracks++; 47388 47389 if (this.emittedTracks >= this.numberOfTracks) { 47390 this.trigger('done'); 47391 this.emittedTracks = 0; 47392 } 47393 47394 return; 47395 } 47396 } 47397 47398 if (this.videoTrack) { 47399 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 47400 VIDEO_PROPERTIES.forEach(function (prop) { 47401 event.info[prop] = this.videoTrack[prop]; 47402 }, this); 47403 } else if (this.audioTrack) { 47404 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 47405 AUDIO_PROPERTIES.forEach(function (prop) { 47406 event.info[prop] = this.audioTrack[prop]; 47407 }, this); 47408 } 47409 47410 if (this.videoTrack || this.audioTrack) { 47411 if (this.pendingTracks.length === 1) { 47412 event.type = this.pendingTracks[0].type; 47413 } else { 47414 event.type = 'combined'; 47415 } 47416 47417 this.emittedTracks += this.pendingTracks.length; 47418 initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment 47419 47420 event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov 47421 // and track definitions 47422 47423 event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats 47424 47425 event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together 47426 47427 for (i = 0; i < this.pendingBoxes.length; i++) { 47428 event.data.set(this.pendingBoxes[i], offset); 47429 offset += this.pendingBoxes[i].byteLength; 47430 } // Translate caption PTS times into second offsets to match the 47431 // video timeline for the segment, and add track info 47432 47433 47434 for (i = 0; i < this.pendingCaptions.length; i++) { 47435 caption = this.pendingCaptions[i]; 47436 caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps); 47437 caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps); 47438 event.captionStreams[caption.stream] = true; 47439 event.captions.push(caption); 47440 } // Translate ID3 frame PTS times into second offsets to match the 47441 // video timeline for the segment 47442 47443 47444 for (i = 0; i < this.pendingMetadata.length; i++) { 47445 id3 = this.pendingMetadata[i]; 47446 id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps); 47447 event.metadata.push(id3); 47448 } // We add this to every single emitted segment even though we only need 47449 // it for the first 47450 47451 47452 event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state 47453 47454 this.pendingTracks.length = 0; 47455 this.videoTrack = null; 47456 this.pendingBoxes.length = 0; 47457 this.pendingCaptions.length = 0; 47458 this.pendingBytes = 0; 47459 this.pendingMetadata.length = 0; // Emit the built segment 47460 // We include captions and ID3 tags for backwards compatibility, 47461 // ideally we should send only video and audio in the data event 47462 47463 this.trigger('data', event); // Emit each caption to the outside world 47464 // Ideally, this would happen immediately on parsing captions, 47465 // but we need to ensure that video data is sent back first 47466 // so that caption timing can be adjusted to match video timing 47467 47468 for (i = 0; i < event.captions.length; i++) { 47469 caption = event.captions[i]; 47470 this.trigger('caption', caption); 47471 } // Emit each id3 tag to the outside world 47472 // Ideally, this would happen immediately on parsing the tag, 47473 // but we need to ensure that video data is sent back first 47474 // so that ID3 frame timing can be adjusted to match video timing 47475 47476 47477 for (i = 0; i < event.metadata.length; i++) { 47478 id3 = event.metadata[i]; 47479 this.trigger('id3Frame', id3); 47480 } 47481 } // Only emit `done` if all tracks have been flushed and emitted 47482 47483 47484 if (this.emittedTracks >= this.numberOfTracks) { 47485 this.trigger('done'); 47486 this.emittedTracks = 0; 47487 } 47488 }; 47489 47490 _CoalesceStream.prototype.setRemux = function (val) { 47491 this.remuxTracks = val; 47492 }; 47493 /** 47494 * A Stream that expects MP2T binary data as input and produces 47495 * corresponding media segments, suitable for use with Media Source 47496 * Extension (MSE) implementations that support the ISO BMFF byte 47497 * stream format, like Chrome. 47498 */ 47499 47500 47501 _Transmuxer = function Transmuxer(options) { 47502 var self = this, 47503 hasFlushed = true, 47504 videoTrack, 47505 audioTrack; 47506 47507 _Transmuxer.prototype.init.call(this); 47508 47509 options = options || {}; 47510 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 47511 this.transmuxPipeline_ = {}; 47512 47513 this.setupAacPipeline = function () { 47514 var pipeline = {}; 47515 this.transmuxPipeline_ = pipeline; 47516 pipeline.type = 'aac'; 47517 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 47518 47519 pipeline.aacStream = new aac(); 47520 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 47521 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 47522 pipeline.adtsStream = new adts(); 47523 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 47524 pipeline.headOfPipeline = pipeline.aacStream; 47525 pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 47526 pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); 47527 pipeline.metadataStream.on('timestamp', function (frame) { 47528 pipeline.aacStream.setTimestamp(frame.timeStamp); 47529 }); 47530 pipeline.aacStream.on('data', function (data) { 47531 if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { 47532 audioTrack = audioTrack || { 47533 timelineStartInfo: { 47534 baseMediaDecodeTime: self.baseMediaDecodeTime 47535 }, 47536 codec: 'adts', 47537 type: 'audio' 47538 }; // hook up the audio segment stream to the first track with aac data 47539 47540 pipeline.coalesceStream.numberOfTracks++; 47541 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 47542 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 47543 47544 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 47545 } // emit pmt info 47546 47547 47548 self.trigger('trackinfo', { 47549 hasAudio: !!audioTrack, 47550 hasVideo: !!videoTrack 47551 }); 47552 }); // Re-emit any data coming from the coalesce stream to the outside world 47553 47554 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 47555 47556 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 47557 }; 47558 47559 this.setupTsPipeline = function () { 47560 var pipeline = {}; 47561 this.transmuxPipeline_ = pipeline; 47562 pipeline.type = 'ts'; 47563 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 47564 47565 pipeline.packetStream = new m2ts_1.TransportPacketStream(); 47566 pipeline.parseStream = new m2ts_1.TransportParseStream(); 47567 pipeline.elementaryStream = new m2ts_1.ElementaryStream(); 47568 pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream(); 47569 pipeline.adtsStream = new adts(); 47570 pipeline.h264Stream = new H264Stream(); 47571 pipeline.captionStream = new m2ts_1.CaptionStream(); 47572 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 47573 pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams 47574 47575 pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!! 47576 // demux the streams 47577 47578 pipeline.timestampRolloverStream.pipe(pipeline.h264Stream); 47579 pipeline.timestampRolloverStream.pipe(pipeline.adtsStream); 47580 pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream 47581 47582 pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream); 47583 pipeline.elementaryStream.on('data', function (data) { 47584 var i; 47585 47586 if (data.type === 'metadata') { 47587 i = data.tracks.length; // scan the tracks listed in the metadata 47588 47589 while (i--) { 47590 if (!videoTrack && data.tracks[i].type === 'video') { 47591 videoTrack = data.tracks[i]; 47592 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 47593 } else if (!audioTrack && data.tracks[i].type === 'audio') { 47594 audioTrack = data.tracks[i]; 47595 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 47596 } 47597 } // hook up the video segment stream to the first track with h264 data 47598 47599 47600 if (videoTrack && !pipeline.videoSegmentStream) { 47601 pipeline.coalesceStream.numberOfTracks++; 47602 pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options); 47603 pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) { 47604 // When video emits timelineStartInfo data after a flush, we forward that 47605 // info to the AudioSegmentStream, if it exists, because video timeline 47606 // data takes precedence. 47607 if (audioTrack) { 47608 audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the 47609 // very earliest DTS we have seen in video because Chrome will 47610 // interpret any video track with a baseMediaDecodeTime that is 47611 // non-zero as a gap. 47612 47613 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); 47614 } 47615 }); 47616 pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo')); 47617 pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo')); 47618 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) { 47619 if (audioTrack) { 47620 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 47621 } 47622 }); 47623 pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline 47624 47625 pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream); 47626 } 47627 47628 if (audioTrack && !pipeline.audioSegmentStream) { 47629 // hook up the audio segment stream to the first track with aac data 47630 pipeline.coalesceStream.numberOfTracks++; 47631 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 47632 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 47633 47634 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 47635 } // emit pmt info 47636 47637 47638 self.trigger('trackinfo', { 47639 hasAudio: !!audioTrack, 47640 hasVideo: !!videoTrack 47641 }); 47642 } 47643 }); // Re-emit any data coming from the coalesce stream to the outside world 47644 47645 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 47646 pipeline.coalesceStream.on('id3Frame', function (id3Frame) { 47647 id3Frame.dispatchType = pipeline.metadataStream.dispatchType; 47648 self.trigger('id3Frame', id3Frame); 47649 }); 47650 pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline 47651 47652 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 47653 }; // hook up the segment streams once track metadata is delivered 47654 47655 47656 this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { 47657 var pipeline = this.transmuxPipeline_; 47658 47659 if (!options.keepOriginalTimestamps) { 47660 this.baseMediaDecodeTime = baseMediaDecodeTime; 47661 } 47662 47663 if (audioTrack) { 47664 audioTrack.timelineStartInfo.dts = undefined; 47665 audioTrack.timelineStartInfo.pts = undefined; 47666 trackDecodeInfo.clearDtsInfo(audioTrack); 47667 47668 if (!options.keepOriginalTimestamps) { 47669 audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 47670 } 47671 47672 if (pipeline.audioTimestampRolloverStream) { 47673 pipeline.audioTimestampRolloverStream.discontinuity(); 47674 } 47675 } 47676 47677 if (videoTrack) { 47678 if (pipeline.videoSegmentStream) { 47679 pipeline.videoSegmentStream.gopCache_ = []; 47680 } 47681 47682 videoTrack.timelineStartInfo.dts = undefined; 47683 videoTrack.timelineStartInfo.pts = undefined; 47684 trackDecodeInfo.clearDtsInfo(videoTrack); 47685 pipeline.captionStream.reset(); 47686 47687 if (!options.keepOriginalTimestamps) { 47688 videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 47689 } 47690 } 47691 47692 if (pipeline.timestampRolloverStream) { 47693 pipeline.timestampRolloverStream.discontinuity(); 47694 } 47695 }; 47696 47697 this.setAudioAppendStart = function (timestamp) { 47698 if (audioTrack) { 47699 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 47700 } 47701 }; 47702 47703 this.setRemux = function (val) { 47704 var pipeline = this.transmuxPipeline_; 47705 options.remux = val; 47706 47707 if (pipeline && pipeline.coalesceStream) { 47708 pipeline.coalesceStream.setRemux(val); 47709 } 47710 }; 47711 47712 this.alignGopsWith = function (gopsToAlignWith) { 47713 if (videoTrack && this.transmuxPipeline_.videoSegmentStream) { 47714 this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith); 47715 } 47716 }; // feed incoming data to the front of the parsing pipeline 47717 47718 47719 this.push = function (data) { 47720 if (hasFlushed) { 47721 var isAac = isLikelyAacData$1(data); 47722 47723 if (isAac && this.transmuxPipeline_.type !== 'aac') { 47724 this.setupAacPipeline(); 47725 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 47726 this.setupTsPipeline(); 47727 } 47728 47729 hasFlushed = false;
vendor: 35,594 bytes, lines 47730-48759
47730 } 47731 47732 this.transmuxPipeline_.headOfPipeline.push(data); 47733 }; // flush any buffered data 47734 47735 47736 this.flush = function () { 47737 hasFlushed = true; // Start at the top of the pipeline and flush all pending work 47738 47739 this.transmuxPipeline_.headOfPipeline.flush(); 47740 }; 47741 47742 this.endTimeline = function () { 47743 this.transmuxPipeline_.headOfPipeline.endTimeline(); 47744 }; 47745 47746 this.reset = function () { 47747 if (this.transmuxPipeline_.headOfPipeline) { 47748 this.transmuxPipeline_.headOfPipeline.reset(); 47749 } 47750 }; // Caption data has to be reset when seeking outside buffered range 47751 47752 47753 this.resetCaptions = function () { 47754 if (this.transmuxPipeline_.captionStream) { 47755 this.transmuxPipeline_.captionStream.reset(); 47756 } 47757 }; 47758 }; 47759 47760 _Transmuxer.prototype = new stream(); 47761 var transmuxer = { 47762 Transmuxer: _Transmuxer, 47763 VideoSegmentStream: _VideoSegmentStream, 47764 AudioSegmentStream: _AudioSegmentStream, 47765 AUDIO_PROPERTIES: AUDIO_PROPERTIES, 47766 VIDEO_PROPERTIES: VIDEO_PROPERTIES, 47767 // exported for testing 47768 generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo 47769 }; 47770 47771 var classCallCheck = function classCallCheck(instance, Constructor) { 47772 if (!(instance instanceof Constructor)) { 47773 throw new TypeError("Cannot call a class as a function"); 47774 } 47775 }; 47776 47777 var createClass = function () { 47778 function defineProperties(target, props) { 47779 for (var i = 0; i < props.length; i++) { 47780 var descriptor = props[i]; 47781 descriptor.enumerable = descriptor.enumerable || false; 47782 descriptor.configurable = true; 47783 if ("value" in descriptor) descriptor.writable = true; 47784 Object.defineProperty(target, descriptor.key, descriptor); 47785 } 47786 } 47787 47788 return function (Constructor, protoProps, staticProps) { 47789 if (protoProps) defineProperties(Constructor.prototype, protoProps); 47790 if (staticProps) defineProperties(Constructor, staticProps); 47791 return Constructor; 47792 }; 47793 }(); 47794 /** 47795 * @file transmuxer-worker.js 47796 */ 47797 47798 /** 47799 * Re-emits transmuxer events by converting them into messages to the 47800 * world outside the worker. 47801 * 47802 * @param {Object} transmuxer the transmuxer to wire events on 47803 * @private 47804 */ 47805 47806 47807 var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer$$1) { 47808 transmuxer$$1.on('data', function (segment) { 47809 // transfer ownership of the underlying ArrayBuffer 47810 // instead of doing a copy to save memory 47811 // ArrayBuffers are transferable but generic TypedArrays are not 47812 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 47813 var initArray = segment.initSegment; 47814 segment.initSegment = { 47815 data: initArray.buffer, 47816 byteOffset: initArray.byteOffset, 47817 byteLength: initArray.byteLength 47818 }; 47819 var typedArray = segment.data; 47820 segment.data = typedArray.buffer; 47821 self.postMessage({ 47822 action: 'data', 47823 segment: segment, 47824 byteOffset: typedArray.byteOffset, 47825 byteLength: typedArray.byteLength 47826 }, [segment.data]); 47827 }); 47828 47829 if (transmuxer$$1.captionStream) { 47830 transmuxer$$1.captionStream.on('data', function (caption) { 47831 self.postMessage({ 47832 action: 'caption', 47833 data: caption 47834 }); 47835 }); 47836 } 47837 47838 transmuxer$$1.on('done', function (data) { 47839 self.postMessage({ 47840 action: 'done' 47841 }); 47842 }); 47843 transmuxer$$1.on('gopInfo', function (gopInfo) { 47844 self.postMessage({ 47845 action: 'gopInfo', 47846 gopInfo: gopInfo 47847 }); 47848 }); 47849 transmuxer$$1.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) { 47850 self.postMessage({ 47851 action: 'videoSegmentTimingInfo', 47852 videoSegmentTimingInfo: videoSegmentTimingInfo 47853 }); 47854 }); 47855 }; 47856 /** 47857 * All incoming messages route through this hash. If no function exists 47858 * to handle an incoming message, then we ignore the message. 47859 * 47860 * @class MessageHandlers 47861 * @param {Object} options the options to initialize with 47862 */ 47863 47864 47865 var MessageHandlers = function () { 47866 function MessageHandlers(self, options) { 47867 classCallCheck(this, MessageHandlers); 47868 this.options = options || {}; 47869 this.self = self; 47870 this.init(); 47871 } 47872 /** 47873 * initialize our web worker and wire all the events. 47874 */ 47875 47876 47877 createClass(MessageHandlers, [{ 47878 key: 'init', 47879 value: function init() { 47880 if (this.transmuxer) { 47881 this.transmuxer.dispose(); 47882 } 47883 47884 this.transmuxer = new transmuxer.Transmuxer(this.options); 47885 wireTransmuxerEvents(this.self, this.transmuxer); 47886 } 47887 /** 47888 * Adds data (a ts segment) to the start of the transmuxer pipeline for 47889 * processing. 47890 * 47891 * @param {ArrayBuffer} data data to push into the muxer 47892 */ 47893 47894 }, { 47895 key: 'push', 47896 value: function push(data) { 47897 // Cast array buffer to correct type for transmuxer 47898 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 47899 this.transmuxer.push(segment); 47900 } 47901 /** 47902 * Recreate the transmuxer so that the next segment added via `push` 47903 * start with a fresh transmuxer. 47904 */ 47905 47906 }, { 47907 key: 'reset', 47908 value: function reset() { 47909 this.init(); 47910 } 47911 /** 47912 * Set the value that will be used as the `baseMediaDecodeTime` time for the 47913 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 47914 * set relative to the first based on the PTS values. 47915 * 47916 * @param {Object} data used to set the timestamp offset in the muxer 47917 */ 47918 47919 }, { 47920 key: 'setTimestampOffset', 47921 value: function setTimestampOffset(data) { 47922 var timestampOffset = data.timestampOffset || 0; 47923 this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000)); 47924 } 47925 }, { 47926 key: 'setAudioAppendStart', 47927 value: function setAudioAppendStart(data) { 47928 this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000)); 47929 } 47930 /** 47931 * Forces the pipeline to finish processing the last segment and emit it's 47932 * results. 47933 * 47934 * @param {Object} data event data, not really used 47935 */ 47936 47937 }, { 47938 key: 'flush', 47939 value: function flush(data) { 47940 this.transmuxer.flush(); 47941 } 47942 }, { 47943 key: 'resetCaptions', 47944 value: function resetCaptions() { 47945 this.transmuxer.resetCaptions(); 47946 } 47947 }, { 47948 key: 'alignGopsWith', 47949 value: function alignGopsWith(data) { 47950 this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice()); 47951 } 47952 }]); 47953 return MessageHandlers; 47954 }(); 47955 /** 47956 * Our web wroker interface so that things can talk to mux.js 47957 * that will be running in a web worker. the scope is passed to this by 47958 * webworkify. 47959 * 47960 * @param {Object} self the scope for the web worker 47961 */ 47962 47963 47964 var TransmuxerWorker = function TransmuxerWorker(self) { 47965 self.onmessage = function (event) { 47966 if (event.data.action === 'init' && event.data.options) { 47967 this.messageHandlers = new MessageHandlers(self, event.data.options); 47968 return; 47969 } 47970 47971 if (!this.messageHandlers) { 47972 this.messageHandlers = new MessageHandlers(self); 47973 } 47974 47975 if (event.data && event.data.action && event.data.action !== 'init') { 47976 if (this.messageHandlers[event.data.action]) { 47977 this.messageHandlers[event.data.action](event.data); 47978 } 47979 } 47980 }; 47981 }; 47982 47983 var transmuxerWorker = new TransmuxerWorker(self); 47984 return transmuxerWorker; 47985 }(); 47986 }); 47987 /** 47988 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to 47989 * codec strings, or translating codec strings into objects that can be examined. 47990 */ 47991 // Default codec parameters if none were provided for video and/or audio 47992 47993 var defaultCodecs = { 47994 videoCodec: 'avc1', 47995 videoObjectTypeIndicator: '.4d400d', 47996 // AAC-LC 47997 audioProfile: '2' 47998 }; 47999 /** 48000 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 48001 * `avc1.<hhhhhh>` 48002 * 48003 * @param {Array} codecs an array of codec strings to fix 48004 * @return {Array} the translated codec array 48005 * @private 48006 */ 48007 48008 var translateLegacyCodecs = function translateLegacyCodecs(codecs) { 48009 return codecs.map(function (codec) { 48010 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 48011 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 48012 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 48013 return 'avc1.' + profileHex + '00' + avcLevelHex; 48014 }); 48015 }); 48016 }; 48017 /** 48018 * Parses a codec string to retrieve the number of codecs specified, 48019 * the video codec and object type indicator, and the audio profile. 48020 */ 48021 48022 48023 var parseCodecs = function parseCodecs() { 48024 var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 48025 var result = { 48026 codecCount: 0 48027 }; 48028 var parsed = void 0; 48029 result.codecCount = codecs.split(',').length; 48030 result.codecCount = result.codecCount || 2; // parse the video codec 48031 48032 parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs); 48033 48034 if (parsed) { 48035 result.videoCodec = parsed[2]; 48036 result.videoObjectTypeIndicator = parsed[3]; 48037 } // parse the last field of the audio codec 48038 48039 48040 result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs); 48041 result.audioProfile = result.audioProfile && result.audioProfile[2]; 48042 return result; 48043 }; 48044 /** 48045 * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the 48046 * standard `avc1.<hhhhhh>`. 48047 * 48048 * @param codecString {String} the codec string 48049 * @return {String} the codec string with old apple-style codecs replaced 48050 * 48051 * @private 48052 */ 48053 48054 48055 var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) { 48056 return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) { 48057 return translateLegacyCodecs([match])[0]; 48058 }); 48059 }; 48060 /** 48061 * Build a media mime-type string from a set of parameters 48062 * @param {String} type either 'audio' or 'video' 48063 * @param {String} container either 'mp2t' or 'mp4' 48064 * @param {Array} codecs an array of codec strings to add 48065 * @return {String} a valid media mime-type 48066 */ 48067 48068 48069 var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) { 48070 // The codecs array is filtered so that falsey values are 48071 // dropped and don't cause Array#join to create spurious 48072 // commas 48073 return type + '/' + container + '; codecs="' + codecs.filter(function (c) { 48074 return !!c; 48075 }).join(', ') + '"'; 48076 }; 48077 /** 48078 * Returns the type container based on information in the playlist 48079 * @param {Playlist} media the current media playlist 48080 * @return {String} a valid media container type 48081 */ 48082 48083 48084 var getContainerType = function getContainerType(media) { 48085 // An initialization segment means the media playlist is an iframe 48086 // playlist or is using the mp4 container. We don't currently 48087 // support iframe playlists, so assume this is signalling mp4 48088 // fragments. 48089 if (media.segments && media.segments.length && media.segments[0].map) { 48090 return 'mp4'; 48091 } 48092 48093 return 'mp2t'; 48094 }; 48095 /** 48096 * Returns a set of codec strings parsed from the playlist or the default 48097 * codec strings if no codecs were specified in the playlist 48098 * @param {Playlist} media the current media playlist 48099 * @return {Object} an object with the video and audio codecs 48100 */ 48101 48102 48103 var getCodecs = function getCodecs(media) { 48104 // if the codecs were explicitly specified, use them instead of the 48105 // defaults 48106 var mediaAttributes = media.attributes || {}; 48107 48108 if (mediaAttributes.CODECS) { 48109 return parseCodecs(mediaAttributes.CODECS); 48110 } 48111 48112 return defaultCodecs; 48113 }; 48114 48115 var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) { 48116 if (!master.mediaGroups.AUDIO || !audioGroupId) { 48117 return null; 48118 } 48119 48120 var audioGroup = master.mediaGroups.AUDIO[audioGroupId]; 48121 48122 if (!audioGroup) { 48123 return null; 48124 } 48125 48126 for (var name in audioGroup) { 48127 var audioType = audioGroup[name]; 48128 48129 if (audioType["default"] && audioType.playlists) { 48130 // codec should be the same for all playlists within the audio type 48131 return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile; 48132 } 48133 } 48134 48135 return null; 48136 }; 48137 /** 48138 * Calculates the MIME type strings for a working configuration of 48139 * SourceBuffers to play variant streams in a master playlist. If 48140 * there is no possible working configuration, an empty array will be 48141 * returned. 48142 * 48143 * @param master {Object} the m3u8 object for the master playlist 48144 * @param media {Object} the m3u8 object for the variant playlist 48145 * @return {Array} the MIME type strings. If the array has more than 48146 * one entry, the first element should be applied to the video 48147 * SourceBuffer and the second to the audio SourceBuffer. 48148 * 48149 * @private 48150 */ 48151 48152 48153 var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) { 48154 var containerType = getContainerType(media); 48155 var codecInfo = getCodecs(media); 48156 var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video) 48157 48158 var isMuxed = true; 48159 var isMaat = false; 48160 48161 if (!media) { 48162 // Not enough information 48163 return []; 48164 } 48165 48166 if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) { 48167 var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario 48168 48169 if (audioGroup) { 48170 isMaat = true; // Start with the everything demuxed then... 48171 48172 isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri) 48173 48174 for (var groupId in audioGroup) { 48175 // either a uri is present (if the case of HLS and an external playlist), or 48176 // playlists is present (in the case of DASH where we don't have external audio 48177 // playlists) 48178 if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) { 48179 isMuxed = true; 48180 break; 48181 } 48182 } 48183 } 48184 } // HLS with multiple-audio tracks must always get an audio codec. 48185 // Put another way, there is no way to have a video-only multiple-audio HLS! 48186 48187 48188 if (isMaat && !codecInfo.audioProfile) { 48189 if (!isMuxed) { 48190 // It is possible for codecs to be specified on the audio media group playlist but 48191 // not on the rendition playlist. This is mostly the case for DASH, where audio and 48192 // video are always separate (and separately specified). 48193 codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO); 48194 } 48195 48196 if (!codecInfo.audioProfile) { 48197 videojs$1.log.warn('Multiple audio tracks present but no audio codec string is specified. ' + 'Attempting to use the default audio codec (mp4a.40.2)'); 48198 codecInfo.audioProfile = defaultCodecs.audioProfile; 48199 } 48200 } // Generate the final codec strings from the codec object generated above 48201 48202 48203 var codecStrings = {}; 48204 48205 if (codecInfo.videoCodec) { 48206 codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator; 48207 } 48208 48209 if (codecInfo.audioProfile) { 48210 codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile; 48211 } // Finally, make and return an array with proper mime-types depending on 48212 // the configuration 48213 48214 48215 var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]); 48216 var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]); 48217 var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]); 48218 48219 if (isMaat) { 48220 if (!isMuxed && codecStrings.video) { 48221 return [justVideo, justAudio]; 48222 } 48223 48224 if (!isMuxed && !codecStrings.video) { 48225 // There is no muxed content and no video codec string, so this is an audio only 48226 // stream with alternate audio. 48227 return [justAudio, justAudio]; 48228 } // There exists the possiblity that this will return a `video/container` 48229 // mime-type for the first entry in the array even when there is only audio. 48230 // This doesn't appear to be a problem and simplifies the code. 48231 48232 48233 return [bothVideoAudio, justAudio]; 48234 } // If there is no video codec at all, always just return a single 48235 // audio/<container> mime-type 48236 48237 48238 if (!codecStrings.video) { 48239 return [justAudio]; 48240 } // When not using separate audio media groups, audio and video is 48241 // *always* muxed 48242 48243 48244 return [bothVideoAudio]; 48245 }; 48246 /** 48247 * Parse a content type header into a type and parameters 48248 * object 48249 * 48250 * @param {String} type the content type header 48251 * @return {Object} the parsed content-type 48252 * @private 48253 */ 48254 48255 48256 var parseContentType = function parseContentType(type) { 48257 var object = { 48258 type: '', 48259 parameters: {} 48260 }; 48261 var parameters = type.trim().split(';'); // first parameter should always be content-type 48262 48263 object.type = parameters.shift().trim(); 48264 parameters.forEach(function (parameter) { 48265 var pair = parameter.trim().split('='); 48266 48267 if (pair.length > 1) { 48268 var name = pair[0].replace(/"/g, '').trim(); 48269 var value = pair[1].replace(/"/g, '').trim(); 48270 object.parameters[name] = value; 48271 } 48272 }); 48273 return object; 48274 }; 48275 /** 48276 * Check if a codec string refers to an audio codec. 48277 * 48278 * @param {String} codec codec string to check 48279 * @return {Boolean} if this is an audio codec 48280 * @private 48281 */ 48282 48283 48284 var isAudioCodec = function isAudioCodec(codec) { 48285 return /mp4a\.\d+.\d+/i.test(codec); 48286 }; 48287 /** 48288 * Check if a codec string refers to a video codec. 48289 * 48290 * @param {String} codec codec string to check 48291 * @return {Boolean} if this is a video codec 48292 * @private 48293 */ 48294 48295 48296 var isVideoCodec = function isVideoCodec(codec) { 48297 return /avc1\.[\da-f]+/i.test(codec); 48298 }; 48299 /** 48300 * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in 48301 * front of current time. 48302 * 48303 * @param {Array} buffer 48304 * The current buffer of gop information 48305 * @param {Number} currentTime 48306 * The current time 48307 * @param {Double} mapping 48308 * Offset to map display time to stream presentation time 48309 * @return {Array} 48310 * List of gops considered safe to append over 48311 */ 48312 48313 48314 var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) { 48315 if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) { 48316 return []; 48317 } // pts value for current time + 3 seconds to give a bit more wiggle room 48318 48319 48320 var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000); 48321 var i = void 0; 48322 48323 for (i = 0; i < buffer.length; i++) { 48324 if (buffer[i].pts > currentTimePts) { 48325 break; 48326 } 48327 } 48328 48329 return buffer.slice(i); 48330 }; 48331 /** 48332 * Appends gop information (timing and byteLength) received by the transmuxer for the 48333 * gops appended in the last call to appendBuffer 48334 * 48335 * @param {Array} buffer 48336 * The current buffer of gop information 48337 * @param {Array} gops 48338 * List of new gop information 48339 * @param {boolean} replace 48340 * If true, replace the buffer with the new gop information. If false, append the 48341 * new gop information to the buffer in the right location of time. 48342 * @return {Array} 48343 * Updated list of gop information 48344 */ 48345 48346 48347 var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) { 48348 if (!gops.length) { 48349 return buffer; 48350 } 48351 48352 if (replace) { 48353 // If we are in safe append mode, then completely overwrite the gop buffer 48354 // with the most recent appeneded data. This will make sure that when appending 48355 // future segments, we only try to align with gops that are both ahead of current 48356 // time and in the last segment appended. 48357 return gops.slice(); 48358 } 48359 48360 var start = gops[0].pts; 48361 var i = 0; 48362 48363 for (i; i < buffer.length; i++) { 48364 if (buffer[i].pts >= start) { 48365 break; 48366 } 48367 } 48368 48369 return buffer.slice(0, i).concat(gops); 48370 }; 48371 /** 48372 * Removes gop information in buffer that overlaps with provided start and end 48373 * 48374 * @param {Array} buffer 48375 * The current buffer of gop information 48376 * @param {Double} start 48377 * position to start the remove at 48378 * @param {Double} end 48379 * position to end the remove at 48380 * @param {Double} mapping 48381 * Offset to map display time to stream presentation time 48382 */ 48383 48384 48385 var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) { 48386 var startPts = Math.ceil((start - mapping) * 90000); 48387 var endPts = Math.ceil((end - mapping) * 90000); 48388 var updatedBuffer = buffer.slice(); 48389 var i = buffer.length; 48390 48391 while (i--) { 48392 if (buffer[i].pts <= endPts) { 48393 break; 48394 } 48395 } 48396 48397 if (i === -1) { 48398 // no removal because end of remove range is before start of buffer 48399 return updatedBuffer; 48400 } 48401 48402 var j = i + 1; 48403 48404 while (j--) { 48405 if (buffer[j].pts <= startPts) { 48406 break; 48407 } 48408 } // clamp remove range start to 0 index 48409 48410 48411 j = Math.max(j, 0); 48412 updatedBuffer.splice(j, i - j + 1); 48413 return updatedBuffer; 48414 }; 48415 48416 var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) { 48417 var start = null; 48418 var end = null; 48419 var arity = 0; 48420 var extents = []; 48421 var ranges = []; // neither buffer has been created yet 48422 48423 if (!videoBuffer && !audioBuffer) { 48424 return videojs$1.createTimeRange(); 48425 } // only one buffer is configured 48426 48427 48428 if (!videoBuffer) { 48429 return audioBuffer.buffered; 48430 } 48431 48432 if (!audioBuffer) { 48433 return videoBuffer.buffered; 48434 } // both buffers are configured 48435 48436 48437 if (audioDisabled) { 48438 return videoBuffer.buffered; 48439 } // both buffers are empty 48440 48441 48442 if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) { 48443 return videojs$1.createTimeRange(); 48444 } // Handle the case where we have both buffers and create an 48445 // intersection of the two 48446 48447 48448 var videoBuffered = videoBuffer.buffered; 48449 var audioBuffered = audioBuffer.buffered; 48450 var count = videoBuffered.length; // A) Gather up all start and end times 48451 48452 while (count--) { 48453 extents.push({ 48454 time: videoBuffered.start(count), 48455 type: 'start' 48456 }); 48457 extents.push({ 48458 time: videoBuffered.end(count), 48459 type: 'end' 48460 }); 48461 } 48462 48463 count = audioBuffered.length; 48464 48465 while (count--) { 48466 extents.push({ 48467 time: audioBuffered.start(count), 48468 type: 'start' 48469 }); 48470 extents.push({ 48471 time: audioBuffered.end(count), 48472 type: 'end' 48473 }); 48474 } // B) Sort them by time 48475 48476 48477 extents.sort(function (a, b) { 48478 return a.time - b.time; 48479 }); // C) Go along one by one incrementing arity for start and decrementing 48480 // arity for ends 48481 48482 for (count = 0; count < extents.length; count++) { 48483 if (extents[count].type === 'start') { 48484 arity++; // D) If arity is ever incremented to 2 we are entering an 48485 // overlapping range 48486 48487 if (arity === 2) { 48488 start = extents[count].time; 48489 } 48490 } else if (extents[count].type === 'end') { 48491 arity--; // E) If arity is ever decremented to 1 we leaving an 48492 // overlapping range 48493 48494 if (arity === 1) { 48495 end = extents[count].time; 48496 } 48497 } // F) Record overlapping ranges 48498 48499 48500 if (start !== null && end !== null) { 48501 ranges.push([start, end]); 48502 start = null; 48503 end = null; 48504 } 48505 } 48506 48507 return videojs$1.createTimeRanges(ranges); 48508 }; 48509 /** 48510 * @file virtual-source-buffer.js 48511 */ 48512 48513 48514 var ONE_SECOND_IN_TS$2 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the 48515 // state of the `updating` property manually. We have to do this because 48516 // Firefox changes `updating` to false long before triggering `updateend` 48517 // events and that was causing strange problems in videojs-contrib-hls 48518 48519 var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) { 48520 var sourceBuffer = mediaSource.addSourceBuffer(mimeType); 48521 var wrapper = Object.create(null); 48522 wrapper.updating = false; 48523 wrapper.realBuffer_ = sourceBuffer; 48524 48525 var _loop = function _loop(key) { 48526 if (typeof sourceBuffer[key] === 'function') { 48527 wrapper[key] = function () { 48528 return sourceBuffer[key].apply(sourceBuffer, arguments); 48529 }; 48530 } else if (typeof wrapper[key] === 'undefined') { 48531 Object.defineProperty(wrapper, key, { 48532 get: function get$$1() { 48533 return sourceBuffer[key]; 48534 }, 48535 set: function set$$1(v) { 48536 return sourceBuffer[key] = v; 48537 } 48538 }); 48539 } 48540 }; 48541 48542 for (var key in sourceBuffer) { 48543 _loop(key); 48544 } 48545 48546 return wrapper; 48547 }; 48548 /** 48549 * VirtualSourceBuffers exist so that we can transmux non native formats 48550 * into a native format, but keep the same api as a native source buffer. 48551 * It creates a transmuxer, that works in its own thread (a web worker) and 48552 * that transmuxer muxes the data into a native format. VirtualSourceBuffer will 48553 * then send all of that data to the naive sourcebuffer so that it is 48554 * indestinguishable from a natively supported format. 48555 * 48556 * @param {HtmlMediaSource} mediaSource the parent mediaSource 48557 * @param {Array} codecs array of codecs that we will be dealing with 48558 * @class VirtualSourceBuffer 48559 * @extends video.js.EventTarget 48560 */ 48561 48562 48563 var VirtualSourceBuffer = function (_videojs$EventTarget) { 48564 inherits$2(VirtualSourceBuffer, _videojs$EventTarget); 48565 48566 function VirtualSourceBuffer(mediaSource, codecs) { 48567 classCallCheck$1(this, VirtualSourceBuffer); 48568 48569 var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget)); 48570 48571 _this.timestampOffset_ = 0; 48572 _this.pendingBuffers_ = []; 48573 _this.bufferUpdating_ = false; 48574 _this.mediaSource_ = mediaSource; 48575 _this.codecs_ = codecs; 48576 _this.audioCodec_ = null; 48577 _this.videoCodec_ = null; 48578 _this.audioDisabled_ = false; 48579 _this.appendAudioInitSegment_ = true; 48580 _this.gopBuffer_ = []; 48581 _this.timeMapping_ = 0; 48582 _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11; 48583 var options = { 48584 remux: false, 48585 alignGopsAtEnd: _this.safeAppend_ 48586 }; 48587 48588 _this.codecs_.forEach(function (codec) { 48589 if (isAudioCodec(codec)) { 48590 _this.audioCodec_ = codec; 48591 } else if (isVideoCodec(codec)) { 48592 _this.videoCodec_ = codec; 48593 } 48594 }); // append muxed segments to their respective native buffers as 48595 // soon as they are available 48596 48597 48598 _this.transmuxer_ = new TransmuxWorker(); 48599 48600 _this.transmuxer_.postMessage({ 48601 action: 'init', 48602 options: options 48603 }); 48604 48605 _this.transmuxer_.onmessage = function (event) { 48606 if (event.data.action === 'data') { 48607 return _this.data_(event); 48608 } 48609 48610 if (event.data.action === 'done') { 48611 return _this.done_(event); 48612 } 48613 48614 if (event.data.action === 'gopInfo') { 48615 return _this.appendGopInfo_(event); 48616 } 48617 48618 if (event.data.action === 'videoSegmentTimingInfo') { 48619 return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo); 48620 } 48621 }; // this timestampOffset is a property with the side-effect of resetting 48622 // baseMediaDecodeTime in the transmuxer on the setter 48623 48624 48625 Object.defineProperty(_this, 'timestampOffset', { 48626 get: function get$$1() { 48627 return this.timestampOffset_; 48628 }, 48629 set: function set$$1(val) { 48630 if (typeof val === 'number' && val >= 0) { 48631 this.timestampOffset_ = val; 48632 this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline 48633 48634 this.gopBuffer_.length = 0; 48635 this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to 48636 // the desired timestampOffset for the next segment 48637 48638 this.transmuxer_.postMessage({ 48639 action: 'setTimestampOffset', 48640 timestampOffset: val 48641 }); 48642 } 48643 } 48644 }); // setting the append window affects both source buffers 48645 48646 Object.defineProperty(_this, 'appendWindowStart', { 48647 get: function get$$1() { 48648 return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart; 48649 }, 48650 set: function set$$1(start) { 48651 if (this.videoBuffer_) { 48652 this.videoBuffer_.appendWindowStart = start; 48653 } 48654 48655 if (this.audioBuffer_) { 48656 this.audioBuffer_.appendWindowStart = start; 48657 } 48658 } 48659 }); // this buffer is "updating" if either of its native buffers are 48660 48661 Object.defineProperty(_this, 'updating', { 48662 get: function get$$1() { 48663 return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating); 48664 } 48665 }); // the buffered property is the intersection of the buffered 48666 // ranges of the native source buffers 48667 48668 Object.defineProperty(_this, 'buffered', { 48669 get: function get$$1() { 48670 return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_); 48671 } 48672 }); 48673 return _this; 48674 } 48675 /** 48676 * When we get a data event from the transmuxer 48677 * we call this function and handle the data that 48678 * was sent to us 48679 * 48680 * @private 48681 * @param {Event} event the data event from the transmuxer 48682 */ 48683 48684 48685 createClass$1(VirtualSourceBuffer, [{ 48686 key: 'data_', 48687 value: function data_(event) { 48688 var segment = event.data.segment; // Cast ArrayBuffer to TypedArray 48689 48690 segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength); 48691 segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength); 48692 createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array 48693 48694 this.pendingBuffers_.push(segment); 48695 return; 48696 } 48697 /** 48698 * When we get a done event from the transmuxer 48699 * we call this function and we process all 48700 * of the pending data that we have been saving in the 48701 * data_ function 48702 * 48703 * @private 48704 * @param {Event} event the done event from the transmuxer 48705 */ 48706 48707 }, { 48708 key: 'done_', 48709 value: function done_(event) { 48710 // Don't process and append data if the mediaSource is closed 48711 if (this.mediaSource_.readyState === 'closed') { 48712 this.pendingBuffers_.length = 0; 48713 return; 48714 } // All buffers should have been flushed from the muxer 48715 // start processing anything we have received 48716 48717 48718 this.processPendingSegments_(); 48719 return; 48720 } 48721 }, { 48722 key: 'videoSegmentTimingInfo_', 48723 value: function videoSegmentTimingInfo_(timingInfo) { 48724 var timingInfoInSeconds = { 48725 start: { 48726 decode: timingInfo.start.dts / ONE_SECOND_IN_TS$2, 48727 presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$2 48728 }, 48729 end: { 48730 decode: timingInfo.end.dts / ONE_SECOND_IN_TS$2, 48731 presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$2 48732 }, 48733 baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$2 48734 }; 48735 48736 if (timingInfo.prependedContentDuration) { 48737 timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$2; 48738 } 48739 48740 this.trigger({ 48741 type: 'videoSegmentTimingInfo', 48742 videoSegmentTimingInfo: timingInfoInSeconds 48743 }); 48744 } 48745 /** 48746 * Create our internal native audio/video source buffers and add 48747 * event handlers to them with the following conditions: 48748 * 1. they do not already exist on the mediaSource 48749 * 2. this VSB has a codec for them 48750 * 48751 * @private 48752 */ 48753 48754 }, { 48755 key: 'createRealSourceBuffers_', 48756 value: function createRealSourceBuffers_() { 48757 var _this2 = this; 48758 48759 var types = ['audio', 'video'];
48760 types.forEach(function (type) { 48761 // Don't create a SourceBuffer of this type if we don't have a 48762 // codec for it 48763 if (!_this2[type + 'Codec_']) { 48764 return; 48765 } // Do nothing if a SourceBuffer of this type already exists 48766 48767 48768 if (_this2[type + 'Buffer_']) { 48769 return; 48770 } 48771 48772 var buffer = null; // If the mediasource already has a SourceBuffer for the codec 48773 // use that 48774 48775 if (_this2.mediaSource_[type + 'Buffer_']) { 48776 buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled 48777 // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier 48778 // than createRealSourceBuffers_ is called to create the second 48779 // VirtualSourceBuffer because that happens as a side-effect of 48780 // videojs-contrib-hls starting the audioSegmentLoader. As a result, 48781 // the audioBuffer is essentially "ownerless" and no one will toggle
vendor: 30,123 bytes, lines 48782-49589
48782 // the `updating` state back to false once the `updateend` event is received 48783 // 48784 // Setting `updating` to false manually will work around this 48785 // situation and allow work to continue 48786 48787 buffer.updating = false; 48788 } else { 48789 var codecProperty = type + 'Codec_'; 48790 var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"'; 48791 buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType); 48792 _this2.mediaSource_[type + 'Buffer_'] = buffer; 48793 } 48794 48795 _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer 48796 48797 ['update', 'updatestart', 'updateend'].forEach(function (event) { 48798 buffer.addEventListener(event, function () { 48799 // if audio is disabled 48800 if (type === 'audio' && _this2.audioDisabled_) { 48801 return; 48802 } 48803 48804 if (event === 'updateend') { 48805 _this2[type + 'Buffer_'].updating = false; 48806 } 48807 48808 var shouldTrigger = types.every(function (t) { 48809 // skip checking audio's updating status if audio 48810 // is not enabled 48811 if (t === 'audio' && _this2.audioDisabled_) { 48812 return true; 48813 } // if the other type is updating we don't trigger 48814 48815 48816 if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) { 48817 return false; 48818 } 48819 48820 return true; 48821 }); 48822 48823 if (shouldTrigger) { 48824 return _this2.trigger(event); 48825 } 48826 }); 48827 }); 48828 }); 48829 } 48830 /** 48831 * Emulate the native mediasource function, but our function will 48832 * send all of the proposed segments to the transmuxer so that we 48833 * can transmux them before we append them to our internal 48834 * native source buffers in the correct format. 48835 * 48836 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 48837 * @param {Uint8Array} segment the segment to append to the buffer 48838 */ 48839 48840 }, { 48841 key: 'appendBuffer', 48842 value: function appendBuffer(segment) { 48843 // Start the internal "updating" state 48844 this.bufferUpdating_ = true; 48845 48846 if (this.audioBuffer_ && this.audioBuffer_.buffered.length) { 48847 var audioBuffered = this.audioBuffer_.buffered; 48848 this.transmuxer_.postMessage({ 48849 action: 'setAudioAppendStart', 48850 appendStart: audioBuffered.end(audioBuffered.length - 1) 48851 }); 48852 } 48853 48854 if (this.videoBuffer_) { 48855 this.transmuxer_.postMessage({ 48856 action: 'alignGopsWith', 48857 gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_) 48858 }); 48859 } 48860 48861 this.transmuxer_.postMessage({ 48862 action: 'push', 48863 // Send the typed-array of data as an ArrayBuffer so that 48864 // it can be sent as a "Transferable" and avoid the costly 48865 // memory copy 48866 data: segment.buffer, 48867 // To recreate the original typed-array, we need information 48868 // about what portion of the ArrayBuffer it was a view into 48869 byteOffset: segment.byteOffset, 48870 byteLength: segment.byteLength 48871 }, [segment.buffer]); 48872 this.transmuxer_.postMessage({ 48873 action: 'flush' 48874 }); 48875 } 48876 /** 48877 * Appends gop information (timing and byteLength) received by the transmuxer for the 48878 * gops appended in the last call to appendBuffer 48879 * 48880 * @param {Event} event 48881 * The gopInfo event from the transmuxer 48882 * @param {Array} event.data.gopInfo 48883 * List of gop info to append 48884 */ 48885 48886 }, { 48887 key: 'appendGopInfo_', 48888 value: function appendGopInfo_(event) { 48889 this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_); 48890 } 48891 /** 48892 * Emulate the native mediasource function and remove parts 48893 * of the buffer from any of our internal buffers that exist 48894 * 48895 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 48896 * @param {Double} start position to start the remove at 48897 * @param {Double} end position to end the remove at 48898 */ 48899 48900 }, { 48901 key: 'remove', 48902 value: function remove(start, end) { 48903 if (this.videoBuffer_) { 48904 this.videoBuffer_.updating = true; 48905 this.videoBuffer_.remove(start, end); 48906 this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_); 48907 } 48908 48909 if (!this.audioDisabled_ && this.audioBuffer_) { 48910 this.audioBuffer_.updating = true; 48911 this.audioBuffer_.remove(start, end); 48912 } // Remove Metadata Cues (id3) 48913 48914 48915 removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions 48916 48917 if (this.inbandTextTracks_) { 48918 for (var track in this.inbandTextTracks_) { 48919 removeCuesFromTrack(start, end, this.inbandTextTracks_[track]); 48920 } 48921 } 48922 } 48923 /** 48924 * Process any segments that the muxer has output 48925 * Concatenate segments together based on type and append them into 48926 * their respective sourceBuffers 48927 * 48928 * @private 48929 */ 48930 48931 }, { 48932 key: 'processPendingSegments_', 48933 value: function processPendingSegments_() { 48934 var sortedSegments = { 48935 video: { 48936 segments: [], 48937 bytes: 0 48938 }, 48939 audio: { 48940 segments: [], 48941 bytes: 0 48942 }, 48943 captions: [], 48944 metadata: [] 48945 }; 48946 48947 if (!this.pendingBuffers_.length) { 48948 // We are no longer in the internal "updating" state 48949 this.trigger('updateend'); 48950 this.bufferUpdating_ = false; 48951 return; 48952 } // Sort segments into separate video/audio arrays and 48953 // keep track of their total byte lengths 48954 48955 48956 sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) { 48957 var type = segment.type; 48958 var data = segment.data; 48959 var initSegment = segment.initSegment; 48960 segmentObj[type].segments.push(data); 48961 segmentObj[type].bytes += data.byteLength; 48962 segmentObj[type].initSegment = initSegment; // Gather any captions into a single array 48963 48964 if (segment.captions) { 48965 segmentObj.captions = segmentObj.captions.concat(segment.captions); 48966 } 48967 48968 if (segment.info) { 48969 segmentObj[type].info = segment.info; 48970 } // Gather any metadata into a single array 48971 48972 48973 if (segment.metadata) { 48974 segmentObj.metadata = segmentObj.metadata.concat(segment.metadata); 48975 } 48976 48977 return segmentObj; 48978 }, sortedSegments); // Create the real source buffers if they don't exist by now since we 48979 // finally are sure what tracks are contained in the source 48980 48981 if (!this.videoBuffer_ && !this.audioBuffer_) { 48982 // Remove any codecs that may have been specified by default but 48983 // are no longer applicable now 48984 if (sortedSegments.video.bytes === 0) { 48985 this.videoCodec_ = null; 48986 } 48987 48988 if (sortedSegments.audio.bytes === 0) { 48989 this.audioCodec_ = null; 48990 } 48991 48992 this.createRealSourceBuffers_(); 48993 } 48994 48995 if (sortedSegments.audio.info) { 48996 this.mediaSource_.trigger({ 48997 type: 'audioinfo', 48998 info: sortedSegments.audio.info 48999 }); 49000 } 49001 49002 if (sortedSegments.video.info) { 49003 this.mediaSource_.trigger({ 49004 type: 'videoinfo', 49005 info: sortedSegments.video.info 49006 }); 49007 } 49008 49009 if (this.appendAudioInitSegment_) { 49010 if (!this.audioDisabled_ && this.audioBuffer_) { 49011 sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment); 49012 sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength; 49013 } 49014 49015 this.appendAudioInitSegment_ = false; 49016 } 49017 49018 var triggerUpdateend = false; // Merge multiple video and audio segments into one and append 49019 49020 if (this.videoBuffer_ && sortedSegments.video.bytes) { 49021 sortedSegments.video.segments.unshift(sortedSegments.video.initSegment); 49022 sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength; 49023 this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_); 49024 } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) { 49025 // The transmuxer did not return any bytes of video, meaning it was all trimmed 49026 // for gop alignment. Since we have a video buffer and audio is disabled, updateend 49027 // will never be triggered by this source buffer, which will cause contrib-hls 49028 // to be stuck forever waiting for updateend. If audio is not disabled, updateend 49029 // will be triggered by the audio buffer, which will be sent upwards since the video 49030 // buffer will not be in an updating state. 49031 triggerUpdateend = true; 49032 } // Add text-track data for all 49033 49034 49035 addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata); 49036 49037 if (!this.audioDisabled_ && this.audioBuffer_) { 49038 this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_); 49039 } 49040 49041 this.pendingBuffers_.length = 0; 49042 49043 if (triggerUpdateend) { 49044 this.trigger('updateend'); 49045 } // We are no longer in the internal "updating" state 49046 49047 49048 this.bufferUpdating_ = false; 49049 } 49050 /** 49051 * Combine all segments into a single Uint8Array and then append them 49052 * to the destination buffer 49053 * 49054 * @param {Object} segmentObj 49055 * @param {SourceBuffer} destinationBuffer native source buffer to append data to 49056 * @private 49057 */ 49058 49059 }, { 49060 key: 'concatAndAppendSegments_', 49061 value: function concatAndAppendSegments_(segmentObj, destinationBuffer) { 49062 var offset = 0; 49063 var tempBuffer = void 0; 49064 49065 if (segmentObj.bytes) { 49066 tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array 49067 49068 segmentObj.segments.forEach(function (segment) { 49069 tempBuffer.set(segment, offset); 49070 offset += segment.byteLength; 49071 }); 49072 49073 try { 49074 destinationBuffer.updating = true; 49075 destinationBuffer.appendBuffer(tempBuffer); 49076 } catch (error) { 49077 if (this.mediaSource_.player_) { 49078 this.mediaSource_.player_.error({ 49079 code: -3, 49080 type: 'APPEND_BUFFER_ERR', 49081 message: error.message, 49082 originalError: error 49083 }); 49084 } 49085 } 49086 } 49087 } 49088 /** 49089 * Emulate the native mediasource function. abort any soureBuffer 49090 * actions and throw out any un-appended data. 49091 * 49092 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 49093 */ 49094 49095 }, { 49096 key: 'abort', 49097 value: function abort() { 49098 if (this.videoBuffer_) { 49099 this.videoBuffer_.abort(); 49100 } 49101 49102 if (!this.audioDisabled_ && this.audioBuffer_) { 49103 this.audioBuffer_.abort(); 49104 } 49105 49106 if (this.transmuxer_) { 49107 this.transmuxer_.postMessage({ 49108 action: 'reset' 49109 }); 49110 } 49111 49112 this.pendingBuffers_.length = 0; 49113 this.bufferUpdating_ = false; 49114 } 49115 }, { 49116 key: 'dispose', 49117 value: function dispose() { 49118 if (this.transmuxer_) { 49119 this.transmuxer_.terminate(); 49120 } 49121 49122 this.trigger('dispose'); 49123 this.off(); 49124 } 49125 }]); 49126 return VirtualSourceBuffer; 49127 }(videojs$1.EventTarget); 49128 /** 49129 * @file html-media-source.js 49130 */ 49131 49132 /** 49133 * Our MediaSource implementation in HTML, mimics native 49134 * MediaSource where/if possible. 49135 * 49136 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 49137 * @class HtmlMediaSource 49138 * @extends videojs.EventTarget 49139 */ 49140 49141 49142 var HtmlMediaSource = function (_videojs$EventTarget) { 49143 inherits$2(HtmlMediaSource, _videojs$EventTarget); 49144 49145 function HtmlMediaSource() { 49146 classCallCheck$1(this, HtmlMediaSource); 49147 49148 var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this)); 49149 49150 var property = void 0; 49151 _this.nativeMediaSource_ = new window$3.MediaSource(); // delegate to the native MediaSource's methods by default 49152 49153 for (property in _this.nativeMediaSource_) { 49154 if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') { 49155 _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_); 49156 } 49157 } // emulate `duration` and `seekable` until seeking can be 49158 // handled uniformly for live streams 49159 // see https://github.com/w3c/media-source/issues/5 49160 49161 49162 _this.duration_ = NaN; 49163 Object.defineProperty(_this, 'duration', { 49164 get: function get$$1() { 49165 if (this.duration_ === Infinity) { 49166 return this.duration_; 49167 } 49168 49169 return this.nativeMediaSource_.duration; 49170 }, 49171 set: function set$$1(duration) { 49172 this.duration_ = duration; 49173 49174 if (duration !== Infinity) { 49175 this.nativeMediaSource_.duration = duration; 49176 return; 49177 } 49178 } 49179 }); 49180 Object.defineProperty(_this, 'seekable', { 49181 get: function get$$1() { 49182 if (this.duration_ === Infinity) { 49183 return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]); 49184 } 49185 49186 return this.nativeMediaSource_.seekable; 49187 } 49188 }); 49189 Object.defineProperty(_this, 'readyState', { 49190 get: function get$$1() { 49191 return this.nativeMediaSource_.readyState; 49192 } 49193 }); 49194 Object.defineProperty(_this, 'activeSourceBuffers', { 49195 get: function get$$1() { 49196 return this.activeSourceBuffers_; 49197 } 49198 }); // the list of virtual and native SourceBuffers created by this 49199 // MediaSource 49200 49201 _this.sourceBuffers = []; 49202 _this.activeSourceBuffers_ = []; 49203 /** 49204 * update the list of active source buffers based upon various 49205 * imformation from HLS and video.js 49206 * 49207 * @private 49208 */ 49209 49210 _this.updateActiveSourceBuffers_ = function () { 49211 // Retain the reference but empty the array 49212 _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will 49213 // be disabled based on the codec of the source buffer 49214 49215 if (_this.sourceBuffers.length === 1) { 49216 var sourceBuffer = _this.sourceBuffers[0]; 49217 sourceBuffer.appendAudioInitSegment_ = true; 49218 sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_; 49219 49220 _this.activeSourceBuffers_.push(sourceBuffer); 49221 49222 return; 49223 } // There are 2 source buffers, a combined (possibly video only) source buffer and 49224 // and an audio only source buffer. 49225 // By default, the audio in the combined virtual source buffer is enabled 49226 // and the audio-only source buffer (if it exists) is disabled. 49227 49228 49229 var disableCombined = false; 49230 var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects? 49231 // safari may do something like this 49232 49233 for (var i = 0; i < _this.player_.audioTracks().length; i++) { 49234 var track = _this.player_.audioTracks()[i]; 49235 49236 if (track.enabled && track.kind !== 'main') { 49237 // The enabled track is an alternate audio track so disable the audio in 49238 // the combined source buffer and enable the audio-only source buffer. 49239 disableCombined = true; 49240 disableAudioOnly = false; 49241 break; 49242 } 49243 } 49244 49245 _this.sourceBuffers.forEach(function (sourceBuffer, index) { 49246 /* eslinst-disable */ 49247 // TODO once codecs are required, we can switch to using the codecs to determine 49248 // what stream is the video stream, rather than relying on videoTracks 49249 49250 /* eslinst-enable */ 49251 sourceBuffer.appendAudioInitSegment_ = true; 49252 49253 if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 49254 // combined 49255 sourceBuffer.audioDisabled_ = disableCombined; 49256 } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) { 49257 // If the "combined" source buffer is video only, then we do not want 49258 // disable the audio-only source buffer (this is mostly for demuxed 49259 // audio and video hls) 49260 sourceBuffer.audioDisabled_ = true; 49261 disableAudioOnly = false; 49262 } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 49263 // audio only 49264 // In the case of audio only with alternate audio and disableAudioOnly is true 49265 // this means we want to disable the audio on the alternate audio sourcebuffer 49266 // but not the main "combined" source buffer. The "combined" source buffer is 49267 // always at index 0, so this ensures audio won't be disabled in both source 49268 // buffers. 49269 sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly; 49270 49271 if (sourceBuffer.audioDisabled_) { 49272 return; 49273 } 49274 } 49275 49276 _this.activeSourceBuffers_.push(sourceBuffer); 49277 }); 49278 }; 49279 49280 _this.onPlayerMediachange_ = function () { 49281 _this.sourceBuffers.forEach(function (sourceBuffer) { 49282 sourceBuffer.appendAudioInitSegment_ = true; 49283 }); 49284 }; 49285 49286 _this.onHlsReset_ = function () { 49287 _this.sourceBuffers.forEach(function (sourceBuffer) { 49288 if (sourceBuffer.transmuxer_) { 49289 sourceBuffer.transmuxer_.postMessage({ 49290 action: 'resetCaptions' 49291 }); 49292 } 49293 }); 49294 }; 49295 49296 _this.onHlsSegmentTimeMapping_ = function (event) { 49297 _this.sourceBuffers.forEach(function (buffer) { 49298 return buffer.timeMapping_ = event.mapping; 49299 }); 49300 }; // Re-emit MediaSource events on the polyfill 49301 49302 49303 ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) { 49304 this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this)); 49305 }, _this); // capture the associated player when the MediaSource is 49306 // successfully attached 49307 49308 _this.on('sourceopen', function (event) { 49309 // Get the player this MediaSource is attached to 49310 var video = document.querySelector('[src="' + _this.url_ + '"]'); 49311 49312 if (!video) { 49313 return; 49314 } 49315 49316 _this.player_ = videojs$1(video.parentNode); 49317 49318 if (!_this.player_) { 49319 return; 49320 } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader 49321 // resets its state and flushes the buffer 49322 49323 49324 _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main 49325 // SegmentLoader inspects an MTS segment and has an accurate stream to display 49326 // time mapping 49327 49328 49329 _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_); 49330 49331 if (_this.player_.audioTracks && _this.player_.audioTracks()) { 49332 _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_); 49333 49334 _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_); 49335 49336 _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_); 49337 } 49338 49339 _this.player_.on('mediachange', _this.onPlayerMediachange_); 49340 }); 49341 49342 _this.on('sourceended', function (event) { 49343 var duration = durationOfVideo(_this.duration); 49344 49345 for (var i = 0; i < _this.sourceBuffers.length; i++) { 49346 var sourcebuffer = _this.sourceBuffers[i]; 49347 var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues; 49348 49349 if (cues && cues.length) { 49350 cues[cues.length - 1].endTime = duration; 49351 } 49352 } 49353 }); // explicitly terminate any WebWorkers that were created 49354 // by SourceHandlers 49355 49356 49357 _this.on('sourceclose', function (event) { 49358 this.sourceBuffers.forEach(function (sourceBuffer) { 49359 if (sourceBuffer.transmuxer_) { 49360 sourceBuffer.transmuxer_.terminate(); 49361 } 49362 }); 49363 this.sourceBuffers.length = 0; 49364 49365 if (!this.player_) { 49366 return; 49367 } 49368 49369 if (this.player_.audioTracks && this.player_.audioTracks()) { 49370 this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_); 49371 this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_); 49372 this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_); 49373 } // We can only change this if the player hasn't been disposed of yet 49374 // because `off` eventually tries to use the el_ property. If it has 49375 // been disposed of, then don't worry about it because there are no 49376 // event handlers left to unbind anyway 49377 49378 49379 if (this.player_.el_) { 49380 this.player_.off('mediachange', this.onPlayerMediachange_); 49381 } 49382 49383 if (this.player_.tech_ && this.player_.tech_.el_) { 49384 this.player_.tech_.off('hls-reset', this.onHlsReset_); 49385 this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_); 49386 } 49387 }); 49388 49389 return _this; 49390 } 49391 /** 49392 * Add a range that that can now be seeked to. 49393 * 49394 * @param {Double} start where to start the addition 49395 * @param {Double} end where to end the addition 49396 * @private 49397 */ 49398 49399 49400 createClass$1(HtmlMediaSource, [{ 49401 key: 'addSeekableRange_', 49402 value: function addSeekableRange_(start, end) { 49403 var error = void 0; 49404 49405 if (this.duration !== Infinity) { 49406 error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity'); 49407 error.name = 'InvalidStateError'; 49408 error.code = 11; 49409 throw error; 49410 } 49411 49412 if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) { 49413 this.nativeMediaSource_.duration = end; 49414 } 49415 } 49416 /** 49417 * Add a source buffer to the media source. 49418 * 49419 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 49420 * @param {String} type the content-type of the content 49421 * @return {Object} the created source buffer 49422 */ 49423 49424 }, { 49425 key: 'addSourceBuffer', 49426 value: function addSourceBuffer(type) { 49427 var buffer = void 0; 49428 var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport 49429 // stream segments into fragmented MP4s 49430 49431 if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) { 49432 var codecs = []; 49433 49434 if (parsedType.parameters && parsedType.parameters.codecs) { 49435 codecs = parsedType.parameters.codecs.split(','); 49436 codecs = translateLegacyCodecs(codecs); 49437 codecs = codecs.filter(function (codec) { 49438 return isAudioCodec(codec) || isVideoCodec(codec); 49439 }); 49440 } 49441 49442 if (codecs.length === 0) { 49443 codecs = ['avc1.4d400d', 'mp4a.40.2']; 49444 } 49445 49446 buffer = new VirtualSourceBuffer(this, codecs); 49447 49448 if (this.sourceBuffers.length !== 0) { 49449 // If another VirtualSourceBuffer already exists, then we are creating a 49450 // SourceBuffer for an alternate audio track and therefore we know that 49451 // the source has both an audio and video track. 49452 // That means we should trigger the manual creation of the real 49453 // SourceBuffers instead of waiting for the transmuxer to return data 49454 this.sourceBuffers[0].createRealSourceBuffers_(); 49455 buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if 49456 // a second source buffer is ever created 49457 49458 this.sourceBuffers[0].audioDisabled_ = true; 49459 } 49460 } else { 49461 // delegate to the native implementation 49462 buffer = this.nativeMediaSource_.addSourceBuffer(type); 49463 } 49464 49465 this.sourceBuffers.push(buffer); 49466 return buffer; 49467 } 49468 }, { 49469 key: 'dispose', 49470 value: function dispose() { 49471 this.trigger('dispose'); 49472 this.off(); 49473 this.sourceBuffers.forEach(function (buffer) { 49474 if (buffer.dispose) { 49475 buffer.dispose(); 49476 } 49477 }); 49478 this.sourceBuffers.length = 0; 49479 } 49480 }]); 49481 return HtmlMediaSource; 49482 }(videojs$1.EventTarget); 49483 /** 49484 * @file videojs-contrib-media-sources.js 49485 */ 49486 49487 49488 var urlCount = 0; // ------------ 49489 // Media Source 49490 // ------------ 49491 // store references to the media sources so they can be connected 49492 // to a video element (a swf object) 49493 // TODO: can we store this somewhere local to this module? 49494 49495 videojs$1.mediaSources = {}; 49496 /** 49497 * Provide a method for a swf object to notify JS that a 49498 * media source is now open. 49499 * 49500 * @param {String} msObjectURL string referencing the MSE Object URL 49501 * @param {String} swfId the swf id 49502 */ 49503 49504 var open = function open(msObjectURL, swfId) { 49505 var mediaSource = videojs$1.mediaSources[msObjectURL]; 49506 49507 if (mediaSource) { 49508 mediaSource.trigger({ 49509 type: 'sourceopen', 49510 swfId: swfId 49511 }); 49512 } else { 49513 throw new Error('Media Source not found (Video.js)'); 49514 } 49515 }; 49516 /** 49517 * Check to see if the native MediaSource object exists and supports 49518 * an MP4 container with both H.264 video and AAC-LC audio. 49519 * 49520 * @return {Boolean} if native media sources are supported 49521 */ 49522 49523 49524 var supportsNativeMediaSources = function supportsNativeMediaSources() { 49525 return !!window$3.MediaSource && !!window$3.MediaSource.isTypeSupported && window$3.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"'); 49526 }; 49527 /** 49528 * An emulation of the MediaSource API so that we can support 49529 * native and non-native functionality. returns an instance of 49530 * HtmlMediaSource. 49531 * 49532 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource 49533 */ 49534 49535 49536 var MediaSource = function MediaSource() { 49537 this.MediaSource = { 49538 open: open, 49539 supportsNativeMediaSources: supportsNativeMediaSources 49540 }; 49541 49542 if (supportsNativeMediaSources()) { 49543 return new HtmlMediaSource(); 49544 } 49545 49546 throw new Error('Cannot use create a virtual MediaSource for this video'); 49547 }; 49548 49549 MediaSource.open = open; 49550 MediaSource.supportsNativeMediaSources = supportsNativeMediaSources; 49551 /** 49552 * A wrapper around the native URL for our MSE object 49553 * implementation, this object is exposed under videojs.URL 49554 * 49555 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL 49556 */ 49557 49558 var URL$1 = { 49559 /** 49560 * A wrapper around the native createObjectURL for our objects. 49561 * This function maps a native or emulated mediaSource to a blob 49562 * url so that it can be loaded into video.js 49563 * 49564 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL 49565 * @param {MediaSource} object the object to create a blob url to 49566 */ 49567 createObjectURL: function createObjectURL(object) { 49568 var objectUrlPrefix = 'blob:vjs-media-source/'; 49569 var url = void 0; // use the native MediaSource to generate an object URL 49570 49571 if (object instanceof HtmlMediaSource) { 49572 url = window$3.URL.createObjectURL(object.nativeMediaSource_); 49573 object.url_ = url; 49574 return url; 49575 } // if the object isn't an emulated MediaSource, delegate to the 49576 // native implementation 49577 49578 49579 if (!(object instanceof HtmlMediaSource)) { 49580 url = window$3.URL.createObjectURL(object); 49581 object.url_ = url; 49582 return url; 49583 } // build a URL that can be used to map back to the emulated 49584 // MediaSource 49585 49586 49587 url = objectUrlPrefix + urlCount; 49588 urlCount++; // setup the mapping back to object 49589
49590 videojs$1.mediaSources[url] = object; 49591 return url; 49592 } 49593 }; 49594 videojs$1.MediaSource = MediaSource; 49595 videojs$1.URL = URL$1; 49596 var EventTarget$1$1 = videojs$1.EventTarget, 49597 mergeOptions$2 = videojs$1.mergeOptions; 49598 /** 49599 * Returns a new master manifest that is the result of merging an updated master manifest 49600 * into the original version. 49601 * 49602 * @param {Object} oldMaster 49603 * The old parsed mpd object 49604 * @param {Object} newMaster 49605 * The updated parsed mpd object 49606 * @return {Object} 49607 * A new object representing the original master manifest with the updated media 49608 * playlists merged in 49609 */ 49610 49611 var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) { 49612 var noChanges = void 0; 49613 var update = mergeOptions$2(oldMaster, { 49614 // These are top level properties that can be updated 49615 duration: newMaster.duration, 49616 minimumUpdatePeriod: newMaster.minimumUpdatePeriod 49617 }); // First update the playlists in playlist list 49618 49619 for (var i = 0; i < newMaster.playlists.length; i++) { 49620 var playlistUpdate = updateMaster(update, newMaster.playlists[i]); 49621 49622 if (playlistUpdate) { 49623 update = playlistUpdate; 49624 } else { 49625 noChanges = true; 49626 } 49627 } // Then update media group playlists 49628 49629 49630 forEachMediaGroup(newMaster, function (properties, type, group, label) { 49631 if (properties.playlists && properties.playlists.length) { 49632 var id = properties.playlists[0].id; 49633 49634 var _playlistUpdate = updateMaster(update, properties.playlists[0]); 49635 49636 if (_playlistUpdate) { 49637 update = _playlistUpdate; // update the playlist reference within media groups 49638 49639 update.mediaGroups[type][group][label].playlists[0] = update.playlists[id]; 49640 noChanges = false; 49641 } 49642 } 49643 }); 49644 49645 if (noChanges) { 49646 return null; 49647 } 49648 49649 return update; 49650 }; 49651 49652 var generateSidxKey = function generateSidxKey(sidxInfo) { 49653 // should be non-inclusive 49654 var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1; 49655 return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd; 49656 }; // SIDX should be equivalent if the URI and byteranges of the SIDX match. 49657 // If the SIDXs have maps, the two maps should match, 49658 // both `a` and `b` missing SIDXs is considered matching. 49659 // If `a` or `b` but not both have a map, they aren't matching. 49660 49661 49662 var equivalentSidx = function equivalentSidx(a, b) { 49663 var neitherMap = Boolean(!a.map && !b.map); 49664 var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length); 49665 return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length; 49666 }; // exported for testing 49667 49668 49669 var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) { 49670 var newSidxMapping = {}; 49671 49672 for (var id in playlists) { 49673 var playlist = playlists[id]; 49674 var currentSidxInfo = playlist.sidx; 49675 49676 if (currentSidxInfo) { 49677 var key = generateSidxKey(currentSidxInfo); 49678 49679 if (!oldSidxMapping[key]) { 49680 break; 49681 } 49682 49683 var savedSidxInfo = oldSidxMapping[key].sidxInfo; 49684 49685 if (equivalentSidx(savedSidxInfo, currentSidxInfo)) { 49686 newSidxMapping[key] = oldSidxMapping[key]; 49687 } 49688 } 49689 } 49690 49691 return newSidxMapping; 49692 }; 49693 /** 49694 * A function that filters out changed items as they need to be requested separately. 49695 * 49696 * The method is exported for testing 49697 * 49698 * @param {Object} masterXml the mpd XML 49699 * @param {string} srcUrl the mpd url 49700 * @param {Date} clientOffset a time difference between server and client (passed through and not used) 49701 * @param {Object} oldSidxMapping the SIDX to compare against 49702 */ 49703 49704
49705 var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) { 49706 // Don't pass current sidx mapping 49707 var master = parse(masterXml, { 49708 manifestUri: srcUrl, 49709 clientOffset: clientOffset 49710 }); 49711 var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping); 49712 var mediaGroupSidx = videoSidx; 49713 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 49714 if (properties.playlists && properties.playlists.length) { 49715 var playlists = properties.playlists; 49716 mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping)); 49717 } 49718 }); 49719 return mediaGroupSidx; 49720 }; // exported for testing 49721 49722 49723 var requestSidx_ = function requestSidx_(sidxRange, playlist, xhr, options, finishProcessingFn) { 49724 var sidxInfo = { 49725 // resolve the segment URL relative to the playlist 49726 uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri), 49727 // resolvedUri: sidxRange.resolvedUri, 49728 byterange: sidxRange.byterange, 49729 // the segment's playlist 49730 playlist: playlist 49731 }; 49732 var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, { 49733 responseType: 'arraybuffer', 49734 headers: segmentXhrHeaders(sidxInfo) 49735 }); 49736 return xhr(sidxRequestOptions, finishProcessingFn); 49737 }; 49738 49739 var DashPlaylistLoader = function (_EventTarget) { 49740 inherits$2(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent 49741 // playlist loader setups from media groups will expect to be able to pass a playlist 49742 // (since there aren't external URLs to media playlists with DASH) 49743 49744 function DashPlaylistLoader(srcUrlOrPlaylist, hls) { 49745 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 49746 var masterPlaylistLoader = arguments[3]; 49747 classCallCheck$1(this, DashPlaylistLoader); 49748 49749 var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this)); 49750 49751 var _options$withCredenti = options.withCredentials, 49752 withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti, 49753 _options$handleManife = options.handleManifestRedirects, 49754 handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife; 49755 _this.hls_ = hls; 49756 _this.withCredentials = withCredentials; 49757 _this.handleManifestRedirects = handleManifestRedirects; 49758 49759 if (!srcUrlOrPlaylist) { 49760 throw new Error('A non-empty playlist URL or playlist is required'); 49761 } // event naming? 49762 49763 49764 _this.on('minimumUpdatePeriod', function () { 49765 _this.refreshXml_(); 49766 }); // live playlist staleness timeout 49767 49768 49769 _this.on('mediaupdatetimeout', function () { 49770 _this.refreshMedia_(_this.media().id); 49771 }); 49772 49773 _this.state = 'HAVE_NOTHING'; 49774 _this.loadedPlaylists_ = {}; // initialize the loader state 49775 // The masterPlaylistLoader will be created with a string 49776 49777 if (typeof srcUrlOrPlaylist === 'string') { 49778 _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes 49779 // once multi-period is refactored 49780 49781 _this.sidxMapping_ = {}; 49782 return possibleConstructorReturn$1(_this); 49783 } 49784 49785 _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist); 49786 49787 return _this; 49788 } 49789 49790 createClass$1(DashPlaylistLoader, [{ 49791 key: 'setupChildLoader', 49792 value: function setupChildLoader(masterPlaylistLoader, playlist) { 49793 this.masterPlaylistLoader_ = masterPlaylistLoader; 49794 this.childPlaylist_ = playlist; 49795 } 49796 }, { 49797 key: 'dispose', 49798 value: function dispose() { 49799 this.trigger('dispose'); 49800 this.stopRequest(); 49801 this.loadedPlaylists_ = {}; 49802 window$3.clearTimeout(this.minimumUpdatePeriodTimeout_); 49803 window$3.clearTimeout(this.mediaRequest_); 49804 window$3.clearTimeout(this.mediaUpdateTimeout); 49805 this.off(); 49806 } 49807 }, { 49808 key: 'hasPendingRequest', 49809 value: function hasPendingRequest() { 49810 return this.request || this.mediaRequest_; 49811 } 49812 }, { 49813 key: 'stopRequest', 49814 value: function stopRequest() { 49815 if (this.request) { 49816 var oldRequest = this.request; 49817 this.request = null; 49818 oldRequest.onreadystatechange = null; 49819 oldRequest.abort(); 49820 } 49821 } 49822 }, { 49823 key: 'sidxRequestFinished_', 49824 value: function sidxRequestFinished_(playlist, master, startingState, doneFn) { 49825 var _this2 = this; 49826 49827 return function (err, request) { 49828 // disposed 49829 if (!_this2.request) { 49830 return; 49831 } // pending request is cleared 49832 49833 49834 _this2.request = null; 49835 49836 if (err) { 49837 _this2.error = { 49838 status: request.status, 49839 message: 'DASH playlist request error at URL: ' + playlist.uri, 49840 response: request.response, 49841 // MEDIA_ERR_NETWORK 49842 code: 2 49843 }; 49844 49845 if (startingState) { 49846 _this2.state = startingState; 49847 } 49848 49849 _this2.trigger('error'); 49850 49851 return doneFn(master, null); 49852 } 49853 49854 var bytes = new Uint8Array(request.response); 49855 var sidx = mp4Inspector.parseSidx(bytes.subarray(8)); 49856 return doneFn(master, sidx); 49857 }; 49858 } 49859 }, { 49860 key: 'media', 49861 value: function media(playlist) { 49862 var _this3 = this; // getter 49863 49864 49865 if (!playlist) { 49866 return this.media_; 49867 } // setter 49868 49869 49870 if (this.state === 'HAVE_NOTHING') { 49871 throw new Error('Cannot switch media playlist from ' + this.state); 49872 } 49873 49874 var startingState = this.state; // find the playlist object if the target playlist has been specified by URI 49875 49876 if (typeof playlist === 'string') { 49877 if (!this.master.playlists[playlist]) { 49878 throw new Error('Unknown playlist URI: ' + playlist); 49879 } 49880 49881 playlist = this.master.playlists[playlist]; 49882 } 49883 49884 var mediaChange = !this.media_ || playlist.id !== this.media_.id; // switch to previously loaded playlists immediately 49885 49886 if (mediaChange && this.loadedPlaylists_[playlist.id] && this.loadedPlaylists_[playlist.id].endList) { 49887 this.state = 'HAVE_METADATA'; 49888 this.media_ = playlist; // trigger media change if the active media has been updated 49889 49890 if (mediaChange) { 49891 this.trigger('mediachanging'); 49892 this.trigger('mediachange'); 49893 } 49894 49895 return; 49896 } // switching to the active playlist is a no-op 49897 49898 49899 if (!mediaChange) { 49900 return; 49901 } // switching from an already loaded playlist 49902 49903 49904 if (this.media_) { 49905 this.trigger('mediachanging'); 49906 } 49907 49908 if (!playlist.sidx) { 49909 // Continue asynchronously if there is no sidx 49910 // wait one tick to allow haveMaster to run first on a child loader 49911 this.mediaRequest_ = window$3.setTimeout(this.haveMetadata.bind(this, { 49912 startingState: startingState, 49913 playlist: playlist 49914 }), 0); // exit early and don't do sidx work 49915 49916 return; 49917 } // we have sidx mappings 49918 49919 49920 var oldMaster = void 0; 49921 var sidxMapping = void 0; // sidxMapping is used when parsing the masterXml, so store 49922 // it on the masterPlaylistLoader 49923 49924 if (this.masterPlaylistLoader_) { 49925 oldMaster = this.masterPlaylistLoader_.master; 49926 sidxMapping = this.masterPlaylistLoader_.sidxMapping_; 49927 } else { 49928 oldMaster = this.master; 49929 sidxMapping = this.sidxMapping_; 49930 } 49931 49932 var sidxKey = generateSidxKey(playlist.sidx); 49933 sidxMapping[sidxKey] = { 49934 sidxInfo: playlist.sidx 49935 }; 49936 this.request = requestSidx_(playlist.sidx, playlist, this.hls_.xhr, { 49937 handleManifestRedirects: this.handleManifestRedirects 49938 }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) { 49939 if (!newMaster || !sidx) { 49940 throw new Error('failed to request sidx'); 49941 } // update loader's sidxMapping with parsed sidx box 49942 49943 49944 sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata 49945 49946 _this3.haveMetadata({ 49947 startingState: startingState, 49948 playlist: newMaster.playlists[playlist.id] 49949 }); 49950 })); 49951 } 49952 }, { 49953 key: 'haveMetadata', 49954 value: function haveMetadata(_ref) { 49955 var startingState = _ref.startingState, 49956 playlist = _ref.playlist; 49957 this.state = 'HAVE_METADATA'; 49958 this.loadedPlaylists_[playlist.id] = playlist; 49959 this.mediaRequest_ = null; // This will trigger loadedplaylist 49960 49961 this.refreshMedia_(playlist.id);
vendor: 10,724 bytes, lines 49961-50253
49961 // fire loadedmetadata the first time a media playlist is loaded 49962 // to resolve setup of media groups 49963 49964 if (startingState === 'HAVE_MASTER') { 49965 this.trigger('loadedmetadata'); 49966 } else { 49967 // trigger media change if the active media has been updated 49968 this.trigger('mediachange'); 49969 } 49970 } 49971 }, { 49972 key: 'pause', 49973 value: function pause() { 49974 this.stopRequest(); 49975 window$3.clearTimeout(this.mediaUpdateTimeout); 49976 window$3.clearTimeout(this.minimumUpdatePeriodTimeout_); 49977 49978 if (this.state === 'HAVE_NOTHING') { 49979 // If we pause the loader before any data has been retrieved, its as if we never 49980 // started, so reset to an unstarted state. 49981 this.started = false; 49982 } 49983 } 49984 }, { 49985 key: 'load', 49986 value: function load(isFinalRendition) { 49987 var _this4 = this; 49988 49989 window$3.clearTimeout(this.mediaUpdateTimeout); 49990 window$3.clearTimeout(this.minimumUpdatePeriodTimeout_); 49991 var media = this.media(); 49992 49993 if (isFinalRendition) { 49994 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 49995 this.mediaUpdateTimeout = window$3.setTimeout(function () { 49996 return _this4.load(); 49997 }, delay); 49998 return; 49999 } // because the playlists are internal to the manifest, load should either load the 50000 // main manifest, or do nothing but trigger an event 50001 50002 50003 if (!this.started) { 50004 this.start(); 50005 return; 50006 } 50007 50008 if (media && !media.endList) { 50009 this.trigger('mediaupdatetimeout'); 50010 } else { 50011 this.trigger('loadedplaylist'); 50012 } 50013 } 50014 /** 50015 * Parses the master xml string and updates playlist uri references 50016 * 50017 * @return {Object} 50018 * The parsed mpd manifest object 50019 */ 50020 50021 }, { 50022 key: 'parseMasterXml', 50023 value: function parseMasterXml() { 50024 var master = parse(this.masterXml_, { 50025 manifestUri: this.srcUrl, 50026 clientOffset: this.clientOffset_, 50027 sidxMapping: this.sidxMapping_ 50028 }); 50029 master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH 50030 // but reference playlists by their URI throughout the project 50031 // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes). 50032 50033 for (var i = 0; i < master.playlists.length; i++) { 50034 var phonyUri = 'placeholder-uri-' + i; 50035 master.playlists[i].uri = phonyUri; 50036 } // set up phony URIs for the media group playlists since we won't have external 50037 // URIs for DASH but reference playlists by their URI throughout the project 50038 50039 50040 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 50041 if (properties.playlists && properties.playlists.length) { 50042 var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey; 50043 50044 var id = createPlaylistID(0, _phonyUri); 50045 properties.playlists[0].uri = _phonyUri; 50046 properties.playlists[0].id = id; // setup ID and URI references (URI for backwards compatibility) 50047 50048 master.playlists[id] = properties.playlists[0]; 50049 master.playlists[_phonyUri] = properties.playlists[0]; 50050 } 50051 }); 50052 setupMediaPlaylists(master); 50053 resolveMediaGroupUris(master); 50054 return master; 50055 } 50056 }, { 50057 key: 'start', 50058 value: function start() { 50059 var _this5 = this; 50060 50061 this.started = true; // We don't need to request the master manifest again 50062 // Call this asynchronously to match the xhr request behavior below 50063 50064 if (this.masterPlaylistLoader_) { 50065 this.mediaRequest_ = window$3.setTimeout(this.haveMaster_.bind(this), 0); 50066 return; 50067 } // request the specified URL 50068 50069 50070 this.request = this.hls_.xhr({ 50071 uri: this.srcUrl, 50072 withCredentials: this.withCredentials 50073 }, function (error, req) { 50074 // disposed 50075 if (!_this5.request) { 50076 return; 50077 } // clear the loader's request reference 50078 50079 50080 _this5.request = null; 50081 50082 if (error) { 50083 _this5.error = { 50084 status: req.status, 50085 message: 'DASH playlist request error at URL: ' + _this5.srcUrl, 50086 responseText: req.responseText, 50087 // MEDIA_ERR_NETWORK 50088 code: 2 50089 }; 50090 50091 if (_this5.state === 'HAVE_NOTHING') { 50092 _this5.started = false; 50093 } 50094 50095 return _this5.trigger('error'); 50096 } 50097 50098 _this5.masterXml_ = req.responseText; 50099 50100 if (req.responseHeaders && req.responseHeaders.date) { 50101 _this5.masterLoaded_ = Date.parse(req.responseHeaders.date); 50102 } else { 50103 _this5.masterLoaded_ = Date.now(); 50104 } 50105 50106 _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req); 50107 50108 _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5)); 50109 }); 50110 } 50111 /** 50112 * Parses the master xml for UTCTiming node to sync the client clock to the server 50113 * clock. If the UTCTiming node requires a HEAD or GET request, that request is made. 50114 * 50115 * @param {Function} done 50116 * Function to call when clock sync has completed 50117 */ 50118 50119 }, { 50120 key: 'syncClientServerClock_', 50121 value: function syncClientServerClock_(done) { 50122 var _this6 = this; 50123 50124 var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the 50125 // server clock 50126 50127 if (utcTiming === null) { 50128 this.clientOffset_ = this.masterLoaded_ - Date.now(); 50129 return done(); 50130 } 50131 50132 if (utcTiming.method === 'DIRECT') { 50133 this.clientOffset_ = utcTiming.value - Date.now(); 50134 return done(); 50135 } 50136 50137 this.request = this.hls_.xhr({ 50138 uri: resolveUrl$1(this.srcUrl, utcTiming.value), 50139 method: utcTiming.method, 50140 withCredentials: this.withCredentials 50141 }, function (error, req) { 50142 // disposed 50143 if (!_this6.request) { 50144 return; 50145 } 50146 50147 if (error) { 50148 // sync request failed, fall back to using date header from mpd 50149 // TODO: log warning 50150 _this6.clientOffset_ = _this6.masterLoaded_ - Date.now(); 50151 return done(); 50152 } 50153 50154 var serverTime = void 0; 50155 50156 if (utcTiming.method === 'HEAD') { 50157 if (!req.responseHeaders || !req.responseHeaders.date) { 50158 // expected date header not preset, fall back to using date header from mpd 50159 // TODO: log warning 50160 serverTime = _this6.masterLoaded_; 50161 } else { 50162 serverTime = Date.parse(req.responseHeaders.date); 50163 } 50164 } else { 50165 serverTime = Date.parse(req.responseText); 50166 } 50167 50168 _this6.clientOffset_ = serverTime - Date.now(); 50169 done(); 50170 }); 50171 } 50172 }, { 50173 key: 'haveMaster_', 50174 value: function haveMaster_() { 50175 this.state = 'HAVE_MASTER'; // clear media request 50176 50177 this.mediaRequest_ = null; 50178 50179 if (!this.masterPlaylistLoader_) { 50180 this.master = this.parseMasterXml(); // We have the master playlist at this point, so 50181 // trigger this to allow MasterPlaylistController 50182 // to make an initial playlist selection 50183 50184 this.trigger('loadedplaylist'); 50185 } else if (!this.media_) { 50186 // no media playlist was specifically selected so select 50187 // the one the child playlist loader was created with 50188 this.media(this.childPlaylist_); 50189 } 50190 } 50191 /** 50192 * Handler for after client/server clock synchronization has happened. Sets up 50193 * xml refresh timer if specificed by the manifest. 50194 */ 50195 50196 }, { 50197 key: 'onClientServerClockSync_', 50198 value: function onClientServerClockSync_() { 50199 var _this7 = this; 50200 50201 this.haveMaster_(); 50202 50203 if (!this.hasPendingRequest() && !this.media_) { 50204 this.media(this.master.playlists[0]); 50205 } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not 50206 // be refreshed when this is the case. The inter-op guide says that when the 50207 // minimumUpdatePeriod is 0, the manifest should outline all currently available 50208 // segments, but future segments may require an update. I think a good solution 50209 // would be to update the manifest at the same rate that the media playlists 50210 // are "refreshed", i.e. every targetDuration. 50211 50212 50213 if (this.master && this.master.minimumUpdatePeriod) { 50214 this.minimumUpdatePeriodTimeout_ = window$3.setTimeout(function () { 50215 _this7.trigger('minimumUpdatePeriod'); 50216 }, this.master.minimumUpdatePeriod); 50217 } 50218 } 50219 /** 50220 * Sends request to refresh the master xml and updates the parsed master manifest 50221 * TODO: Does the client offset need to be recalculated when the xml is refreshed? 50222 */ 50223 50224 }, { 50225 key: 'refreshXml_', 50226 value: function refreshXml_() { 50227 var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when 50228 // sidx is implemented 50229 50230 50231 this.request = this.hls_.xhr({ 50232 uri: this.srcUrl, 50233 withCredentials: this.withCredentials 50234 }, function (error, req) { 50235 // disposed 50236 if (!_this8.request) { 50237 return; 50238 } // clear the loader's request reference 50239 50240 50241 _this8.request = null; 50242 50243 if (error) { 50244 _this8.error = { 50245 status: req.status, 50246 message: 'DASH playlist request error at URL: ' + _this8.srcUrl, 50247 responseText: req.responseText, 50248 // MEDIA_ERR_NETWORK 50249 code: 2 50250 }; 50251 50252 if (_this8.state === 'HAVE_NOTHING') { 50253 _this8.started = false;
50254 } 50255 50256 return _this8.trigger('error'); 50257 } 50258 50259 _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping 50260 50261 _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_); 50262 50263 var master = _this8.parseMasterXml(); 50264 50265 var updatedMaster = updateMaster$1(_this8.master, master); 50266 50267 var currentSidxInfo = _this8.media().sidx; 50268 50269 if (updatedMaster) { 50270 if (currentSidxInfo) { 50271 var sidxKey = generateSidxKey(currentSidxInfo); // the sidx was updated, so the previous mapping was removed 50272 50273 if (!_this8.sidxMapping_[sidxKey]) { 50274 var playlist = _this8.media(); 50275 50276 _this8.request = requestSidx_(playlist.sidx, playlist, _this8.hls_.xhr, { 50277 handleManifestRedirects: _this8.handleManifestRedirects 50278 }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) { 50279 if (!newMaster || !sidx) { 50280 throw new Error('failed to request sidx on minimumUpdatePeriod'); 50281 } // update loader's sidxMapping with parsed sidx box 50282 50283 50284 _this8.sidxMapping_[sidxKey].sidx = sidx; 50285 _this8.minimumUpdatePeriodTimeout_ = window$3.setTimeout(function () { 50286 _this8.trigger('minimumUpdatePeriod'); 50287 }, _this8.master.minimumUpdatePeriod); // TODO: do we need to reload the current playlist? 50288 50289 _this8.refreshMedia_(_this8.media().id); 50290 50291 return; 50292 })); 50293 } 50294 } else { 50295 _this8.master = updatedMaster; 50296 } 50297 } 50298 50299 _this8.minimumUpdatePeriodTimeout_ = window$3.setTimeout(function () { 50300 _this8.trigger('minimumUpdatePeriod'); 50301 }, _this8.master.minimumUpdatePeriod); 50302 }); 50303 } 50304 /** 50305 * Refreshes the media playlist by re-parsing the master xml and updating playlist 50306 * references. If this is an alternate loader, the updated parsed manifest is retrieved 50307 * from the master loader. 50308 */ 50309 50310 }, { 50311 key: 'refreshMedia_', 50312 value: function refreshMedia_(mediaID) { 50313 var _this9 = this; 50314 50315 if (!mediaID) { 50316 throw new Error('refreshMedia_ must take a media id'); 50317 } 50318 50319 var oldMaster = void 0; 50320 var newMaster = void 0; 50321 50322 if (this.masterPlaylistLoader_) { 50323 oldMaster = this.masterPlaylistLoader_.master; 50324 newMaster = this.masterPlaylistLoader_.parseMasterXml(); 50325 } else { 50326 oldMaster = this.master; 50327 newMaster = this.parseMasterXml(); 50328 } 50329 50330 var updatedMaster = updateMaster$1(oldMaster, newMaster); 50331 50332 if (updatedMaster) { 50333 if (this.masterPlaylistLoader_) { 50334 this.masterPlaylistLoader_.master = updatedMaster; 50335 } else { 50336 this.master = updatedMaster; 50337 } 50338 50339 this.media_ = updatedMaster.playlists[mediaID]; 50340 } else { 50341 this.media_ = newMaster.playlists[mediaID]; 50342 this.trigger('playlistunchanged'); 50343 } 50344 50345 if (!this.media().endList) { 50346 this.mediaUpdateTimeout = window$3.setTimeout(function () { 50347 _this9.trigger('mediaupdatetimeout'); 50348 }, refreshDelay(this.media(), !!updatedMaster)); 50349 } 50350 50351 this.trigger('loadedplaylist'); 50352 } 50353 }]); 50354 return DashPlaylistLoader; 50355 }(EventTarget$1$1); 50356 50357 var logger = function logger(source) { 50358 if (videojs$1.log.debug) { 50359 return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >'); 50360 } 50361 50362 return function () {}; 50363 }; 50364 50365 function noop$1() {} 50366 /** 50367 * @file source-updater.js 50368 */ 50369 50370 /** 50371 * A queue of callbacks to be serialized and applied when a
50372 * MediaSource and its associated SourceBuffers are not in the 50373 * updating state. It is used by the segment loader to update the 50374 * underlying SourceBuffers when new data is loaded, for instance. 50375 * 50376 * @class SourceUpdater 50377 * @param {MediaSource} mediaSource the MediaSource to create the 50378 * SourceBuffer from 50379 * @param {String} mimeType the desired MIME type of the underlying 50380 * SourceBuffer 50381 * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is 50382 * added to the media source 50383 */ 50384 50385 50386 var SourceUpdater = function () { 50387 function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) { 50388 classCallCheck$1(this, SourceUpdater); 50389 this.callbacks_ = []; 50390 this.pendingCallback_ = null; 50391 this.timestampOffset_ = 0; 50392 this.mediaSource = mediaSource; 50393 this.processedAppend_ = false; 50394 this.type_ = type; 50395 this.mimeType_ = mimeType; 50396 this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']'); 50397 50398 if (mediaSource.readyState === 'closed') { 50399 mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter)); 50400 } else { 50401 this.createSourceBuffer_(mimeType, sourceBufferEmitter); 50402 } 50403 } 50404 50405 createClass$1(SourceUpdater, [{ 50406 key: 'createSourceBuffer_', 50407 value: function createSourceBuffer_(mimeType, sourceBufferEmitter) { 50408 var _this = this; 50409 50410 this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType); 50411 this.logger_('created SourceBuffer'); 50412 50413 if (sourceBufferEmitter) { 50414 sourceBufferEmitter.trigger('sourcebufferadded'); 50415 50416 if (this.mediaSource.sourceBuffers.length < 2) { 50417 // There's another source buffer we must wait for before we can start updating 50418 // our own (or else we can get into a bad state, i.e., appending video/audio data 50419 // before the other video/audio source buffer is available and leading to a video 50420 // or audio only buffer). 50421 sourceBufferEmitter.on('sourcebufferadded', function () { 50422 _this.start_(); 50423 }); 50424 return; 50425 } 50426 } 50427 50428 this.start_(); 50429 } 50430 }, { 50431 key: 'start_', 50432 value: function start_() { 50433 var _this2 = this; 50434 50435 this.started_ = true; // run completion handlers and process callbacks as updateend 50436 // events fire 50437 50438 this.onUpdateendCallback_ = function () { 50439 var pendingCallback = _this2.pendingCallback_; 50440 _this2.pendingCallback_ = null; 50441 _this2.sourceBuffer_.removing = false; 50442 50443 _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']'); 50444 50445 if (pendingCallback) { 50446 pendingCallback(); 50447 } 50448 50449 _this2.runCallback_(); 50450 }; 50451 50452 this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_); 50453 this.runCallback_(); 50454 } 50455 /** 50456 * Aborts the current segment and resets the segment parser. 50457 * 50458 * @param {Function} done function to call when done 50459 * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void 50460 */ 50461 50462 }, { 50463 key: 'abort', 50464 value: function abort(done) { 50465 var _this3 = this; 50466 50467 if (this.processedAppend_) { 50468 this.queueCallback_(function () { 50469 _this3.sourceBuffer_.abort(); 50470 }, done); 50471 } 50472 } 50473 /** 50474 * Queue an update to append an ArrayBuffer. 50475 * 50476 * @param {ArrayBuffer} bytes 50477 * @param {Function} done the function to call when done 50478 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 50479 */ 50480 50481 }, { 50482 key: 'appendBuffer', 50483 value: function appendBuffer(config, done) { 50484 var _this4 = this; 50485 50486 this.processedAppend_ = true; 50487 this.queueCallback_(function () { 50488 if (config.videoSegmentTimingInfoCallback) { 50489 _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); 50490 } 50491 50492 _this4.sourceBuffer_.appendBuffer(config.bytes); 50493 }, function () { 50494 if (config.videoSegmentTimingInfoCallback) { 50495 _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); 50496 } 50497 50498 done(); 50499 }); 50500 } 50501 /** 50502 * Indicates what TimeRanges are buffered in the managed SourceBuffer. 50503 * 50504 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered 50505 */ 50506 50507 }, { 50508 key: 'buffered', 50509 value: function buffered() { 50510 if (!this.sourceBuffer_) { 50511 return videojs$1.createTimeRanges(); 50512 } 50513 50514 return this.sourceBuffer_.buffered; 50515 } 50516 /** 50517 * Queue an update to remove a time range from the buffer. 50518 * 50519 * @param {Number} start where to start the removal 50520 * @param {Number} end where to end the removal 50521 * @param {Function} [done=noop] optional callback to be executed when the remove 50522 * operation is complete 50523 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 50524 */ 50525 50526 }, { 50527 key: 'remove', 50528 value: function remove(start, end) { 50529 var _this5 = this; 50530 50531 var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1; 50532 50533 if (this.processedAppend_) { 50534 this.queueCallback_(function () { 50535 _this5.logger_('remove [' + start + ' => ' + end + ']'); 50536 50537 _this5.sourceBuffer_.removing = true; 50538 50539 _this5.sourceBuffer_.remove(start, end); 50540 }, done); 50541 } 50542 } 50543 /** 50544 * Whether the underlying sourceBuffer is updating or not 50545 * 50546 * @return {Boolean} the updating status of the SourceBuffer 50547 */ 50548 50549 }, { 50550 key: 'updating', 50551 value: function updating() { 50552 // we are updating if the sourcebuffer is updating or 50553 return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending c
vendor: 8,227 bytes, lines 50553-50813
50553allback that is not our internal noop 50554 !!this.pendingCallback_ && this.pendingCallback_ !== noop$1; 50555 } 50556 /** 50557 * Set/get the timestampoffset on the SourceBuffer 50558 * 50559 * @return {Number} the timestamp offset 50560 */ 50561 50562 }, { 50563 key: 'timestampOffset', 50564 value: function timestampOffset(offset) { 50565 var _this6 = this; 50566 50567 if (typeof offset !== 'undefined') { 50568 this.queueCallback_(function () { 50569 _this6.sourceBuffer_.timestampOffset = offset; 50570 50571 _this6.runCallback_(); 50572 }); 50573 this.timestampOffset_ = offset; 50574 } 50575 50576 return this.timestampOffset_; 50577 } 50578 /** 50579 * Queue a callback to run 50580 */ 50581 50582 }, { 50583 key: 'queueCallback_', 50584 value: function queueCallback_(callback, done) { 50585 this.callbacks_.push([callback.bind(this), done]); 50586 this.runCallback_(); 50587 } 50588 /** 50589 * Run a queued callback 50590 */ 50591 50592 }, { 50593 key: 'runCallback_', 50594 value: function runCallback_() { 50595 var callbacks = void 0; 50596 50597 if (!this.updating() && this.callbacks_.length && this.started_) { 50598 callbacks = this.callbacks_.shift(); 50599 this.pendingCallback_ = callbacks[1]; 50600 callbacks[0](); 50601 } 50602 } 50603 /** 50604 * dispose of the source updater and the underlying sourceBuffer 50605 */ 50606 50607 }, { 50608 key: 'dispose', 50609 value: function dispose() { 50610 var _this7 = this; 50611 50612 var disposeFn = function disposeFn() { 50613 if (_this7.sourceBuffer_ && _this7.mediaSource.readyState === 'open') { 50614 _this7.sourceBuffer_.abort(); 50615 } 50616 50617 _this7.sourceBuffer_.removeEventListener('updateend', disposeFn); 50618 }; 50619 50620 this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_); 50621 50622 if (this.sourceBuffer_.removing) { 50623 this.sourceBuffer_.addEventListener('updateend', disposeFn); 50624 } else { 50625 disposeFn(); 50626 } 50627 } 50628 }]); 50629 return SourceUpdater; 50630 }(); 50631 50632 var Config = { 50633 GOAL_BUFFER_LENGTH: 30, 50634 MAX_GOAL_BUFFER_LENGTH: 60, 50635 GOAL_BUFFER_LENGTH_RATE: 1, 50636 // 0.5 MB/s 50637 INITIAL_BANDWIDTH: 4194304, 50638 // A fudge factor to apply to advertised playlist bitrates to account for 50639 // temporary flucations in client bandwidth 50640 BANDWIDTH_VARIANCE: 1.2, 50641 // How much of the buffer must be filled before we consider upswitching 50642 BUFFER_LOW_WATER_LINE: 0, 50643 MAX_BUFFER_LOW_WATER_LINE: 30, 50644 BUFFER_LOW_WATER_LINE_RATE: 1 50645 }; 50646 var REQUEST_ERRORS = { 50647 FAILURE: 2, 50648 TIMEOUT: -101, 50649 ABORTED: -102 50650 }; 50651 /** 50652 * Abort all requests 50653 * 50654 * @param {Object} activeXhrs - an object that tracks all XHR requests 50655 */ 50656 50657 var abortAll = function abortAll(activeXhrs) { 50658 activeXhrs.forEach(function (xhr) { 50659 xhr.abort(); 50660 }); 50661 }; 50662 /** 50663 * Gather important bandwidth stats once a request has completed 50664 * 50665 * @param {Object} request - the XHR request from which to gather stats 50666 */ 50667 50668 50669 var getRequestStats = function getRequestStats(request) { 50670 return { 50671 bandwidth: request.bandwidth, 50672 bytesReceived: request.bytesReceived || 0, 50673 roundTripTime: request.roundTripTime || 0 50674 }; 50675 }; 50676 /** 50677 * If possible gather bandwidth stats as a request is in 50678 * progress 50679 * 50680 * @param {Event} progressEvent - an event object from an XHR's progress event 50681 */ 50682 50683 50684 var getProgressStats = function getProgressStats(progressEvent) { 50685 var request = progressEvent.target; 50686 var roundTripTime = Date.now() - request.requestTime; 50687 var stats = { 50688 bandwidth: Infinity, 50689 bytesReceived: 0, 50690 roundTripTime: roundTripTime || 0 50691 }; 50692 stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok 50693 // because we should only use bandwidth stats on progress to determine when 50694 // abort a request early due to insufficient bandwidth 50695 50696 stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000); 50697 return stats; 50698 }; 50699 /** 50700 * Handle all error conditions in one place and return an object 50701 * with all the information 50702 * 50703 * @param {Error|null} error - if non-null signals an error occured with the XHR 50704 * @param {Object} request - the XHR request that possibly generated the error 50705 */ 50706 50707 50708 var handleErrors = function handleErrors(error, request) { 50709 if (request.timedout) { 50710 return { 50711 status: request.status, 50712 message: 'HLS request timed-out at URL: ' + request.uri, 50713 code: REQUEST_ERRORS.TIMEOUT, 50714 xhr: request 50715 }; 50716 } 50717 50718 if (request.aborted) { 50719 return { 50720 status: request.status, 50721 message: 'HLS request aborted at URL: ' + request.uri, 50722 code: REQUEST_ERRORS.ABORTED, 50723 xhr: request 50724 }; 50725 } 50726 50727 if (error) { 50728 return { 50729 status: request.status, 50730 message: 'HLS request errored at URL: ' + request.uri, 50731 code: REQUEST_ERRORS.FAILURE, 50732 xhr: request 50733 }; 50734 } 50735 50736 return null; 50737 }; 50738 /** 50739 * Handle responses for key data and convert the key data to the correct format 50740 * for the decryption step later 50741 * 50742 * @param {Object} segment - a simplified copy of the segmentInfo object 50743 * from SegmentLoader 50744 * @param {Function} finishProcessingFn - a callback to execute to continue processing 50745 * this request 50746 */ 50747 50748 50749 var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) { 50750 return function (error, request) { 50751 var response = request.response; 50752 var errorObj = handleErrors(error, request); 50753 50754 if (errorObj) { 50755 return finishProcessingFn(errorObj, segment); 50756 } 50757 50758 if (response.byteLength !== 16) { 50759 return finishProcessingFn({ 50760 status: request.status, 50761 message: 'Invalid HLS key at URL: ' + request.uri, 50762 code: REQUEST_ERRORS.FAILURE, 50763 xhr: request 50764 }, segment); 50765 } 50766 50767 var view = new DataView(response); 50768 segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 50769 return finishProcessingFn(null, segment); 50770 }; 50771 }; 50772 /** 50773 * Handle init-segment responses 50774 * 50775 * @param {Object} segment - a simplified copy of the segmentInfo object 50776 * from SegmentLoader 50777 * @param {Function} finishProcessingFn - a callback to execute to continue processing 50778 * this request 50779 */ 50780 50781 50782 var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) { 50783 return function (error, request) { 50784 var response = request.response; 50785 var errorObj = handleErrors(error, request); 50786 50787 if (errorObj) { 50788 return finishProcessingFn(errorObj, segment); 50789 } // stop processing if received empty content 50790 50791 50792 if (response.byteLength === 0) { 50793 return finishProcessingFn({ 50794 status: request.status, 50795 message: 'Empty HLS segment content at URL: ' + request.uri, 50796 code: REQUEST_ERRORS.FAILURE, 50797 xhr: request 50798 }, segment); 50799 } 50800 50801 segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet 50802 50803 if (captionParser && !captionParser.isInitialized()) { 50804 captionParser.init(); 50805 } 50806 50807 segment.map.timescales = probe.timescale(segment.map.bytes); 50808 segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes); 50809 return finishProcessingFn(null, segment); 50810 }; 50811 }; 50812 /** 50813 * Response handler for segment-requests being sure to set the correct
50814 * property depending on whether the segment is encryped or not 50815 * Also records and keeps track of stats that are used for ABR purposes 50816 * 50817 * @param {Object} segment - a simplified copy of the segmentInfo object 50818 * from SegmentLoader 50819 * @param {Function} finishProcessingFn - a callback to execute to continue processing 50820 * this request 50821 */ 50822 50823 50824 var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) { 50825 return function (error, request) { 50826 var response = request.response; 50827 var errorObj = handleErrors(error, request); 50828 var parsed = void 0; 50829 50830 if (errorObj) { 50831 return finishProcessingFn(errorObj, segment); 50832 } // stop processing if received empty content 50833 50834 50835 if (response.byteLength === 0) { 50836 return finishProcessingFn({ 50837 status: request.status, 50838 message: 'Empty HLS segment content at URL: ' + request.uri, 50839 code: REQUEST_ERRORS.FAILURE, 50840 xhr: request 50841 }, segment); 50842 } 50843 50844 segment.stats = getRequestStats(request); 50845 50846 if (segment.key) { 50847 segment.encryptedBytes = new Uint8Array(request.response); 50848 } else { 50849 segment.bytes = new Uint8Array(request.response); 50850 } // This is likely an FMP4 and has the init segment. 50851 // Run through the CaptionParser in case there are captions. 50852 50853 50854 if (captionParser && segment.map && segment.map.bytes) { 50855 // Initialize CaptionParser if it hasn't been yet 50856 if (!captionParser.isInitialized()) { 50857 captionParser.init(); 50858 } 50859 50860 parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales); 50861 50862 if (parsed && parsed.captions) { 50863 segment.captionStreams = parsed.captionStreams; 50864 segment.fmp4Captions = parsed.captions; 50865 } 50866 } 50867 50868 return finishProcessingFn(null, segment); 50869 }; 50870 }; 50871 /** 50872 * Decrypt the segment via the decryption web worker 50873 * 50874 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 50875 * @param {Object} segment - a simplified copy of the segmentInfo object 50876 * from SegmentLoader 50877 * @param {Function} doneFn - a callback that is executed after decryption has completed 50878 */ 50879 50880 50881 var decryptSegment = function decryptSegment(decrypter, segment, doneFn) { 50882 var decryptionHandler = function decryptionHandler(event) { 50883 if (event.data.source === segment.requestId) { 50884 decrypter.removeEventListener('message', decryptionHandler); 50885 var decrypted = event.data.decrypted; 50886 segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength); 50887 return doneFn(null, segment); 50888 } 50889 }; 50890 50891 decrypter.addEventListener('message', decryptionHandler); 50892 var keyBytes = void 0; 50893 50894 if (segment.key.bytes.slice) { 50895 keyBytes = segment.key.bytes.slice(); 50896 } else { 50897 keyBytes = new Uint32Array(Array.prototype.slice.call(segment.key.bytes)); 50898 } // this is an encrypted segment 50899 // incrementally decrypt the segment 50900 50901 50902 decrypter.postMessage(createTransferableMessage({ 50903 source: segment.requestId, 50904 encrypted: segment.encryptedBytes, 50905 key: keyBytes, 50906 iv: segment.key.iv 50907 }), [segment.encryptedBytes.buffer, keyBytes.buffer]); 50908 }; 50909 /** 50910 * This function waits for all XHRs to finish (with either success or failure) 50911 * before continueing processing via it's callback. The function gathers errors 50912 * from each request into a single errors array so that the error status for 50913 * each request can be examined later. 50914 * 50915 * @param {Object} activeXhrs - an object that tracks all XHR requests 50916 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 50917 * @param {Function} doneFn - a callback that is executed after all resources have been 50918 * downloaded and any decryption completed 50919 */ 50920 50921 50922 var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) { 50923 var count = 0; 50924 var didError = false;
50925 return function (error, segment) { 50926 if (didError) { 50927 return; 50928 } 50929 50930 if (error) { 50931 didError = true; // If there are errors, we have to abort any outstanding requests 50932 50933 abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we 50934 // handle the aborted events from those requests, there are some cases where we may 50935 // never get an aborted event. For instance, if the network connection is lost and 50936 // there were two requests, the first may have triggered an error immediately, while 50937 // the second request remains unsent. In that case, the aborted algorithm will not 50938 // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method 50939 // 50940 // We also can't rely on the ready state of the XHR, since the request that 50941 // triggered the connection error may also show as a ready state of 0 (unsent). 50942 // Therefore, we have to finish this group of requests immediately after the first 50943 // seen error. 50944 50945 return doneFn(error, segment); 50946 } 50947 50948 count += 1; 50949 50950 if (count === activeXhrs.length) { 50951 // Keep track of when *all* of the requests have completed 50952 segment.endOfAllRequests = Date.now(); 50953 50954 if (segment.encryptedBytes) { 50955 return decryptSegment(decrypter, segment, doneFn); 50956 } // Otherwise, everything is ready just continue 50957 50958 50959 return doneFn(null, segment); 50960 } 50961 }; 50962 }; 50963 /** 50964 * Simple progress event callback handler that gathers some stats before 50965 * executing a provided callback with the `segment` object 50966 * 50967 * @param {Object} segment - a simplified copy of the segmentInfo object 50968 * from SegmentLoader 50969 * @param {Function} progressFn - a callback that is executed each time a progress event 50970 * is received 50971 * @param {Event} event - the progress event object from XMLHttpRequest 50972 */ 50973 50974 50975 var handleProgress = function handleProgress(segment, progressFn) { 50976 return function (event) { 50977 segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data 50978 50979 if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) { 50980 segment.stats.firstBytesReceivedAt = Date.now(); 50981 } 50982 50983 return progressFn(event, segment); 50984 }; 50985 }; 50986 /** 50987 * Load all resources and does any processing necessary for a media-segment 50988 * 50989 * Features: 50990 * decrypts the media-segment if it has a key uri and an iv 50991 * aborts *all* requests if *any* one request fails 50992 * 50993 * The segment object, at minimum, has the following format: 50994 * { 50995 * resolvedUri: String, 50996 * [byterange]: { 50997 * offset: Number, 50998 * length: Number 50999 * }, 51000 * [key]: { 51001 * resolvedUri: String 51002 * [byterange]: { 51003 * offset: Number, 51004 * length: Number 51005 * }, 51006 * iv: { 51007 * bytes: Uint32Array 51008 * } 51009 * }, 51010 * [map]: { 51011 * resolvedUri: String, 51012 * [byterange]: { 51013 * offset: Number, 51014 * length: Number 51015 * }, 51016 * [bytes]: Uint8Array 51017 * } 51018 * } 51019 * ...where [name] denotes optional properties 51020 * 51021 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js 51022 * @param {Object} xhrOptions - the base options to provide to all xhr requests 51023 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 51024 * decryption routines 51025 * @param {Object} segment - a simplified copy of the segmentInfo object 51026 * from SegmentLoader 51027 * @param {Function} progressFn - a callback that receives progress events from the main 51028 * segment's xhr request 51029 * @param {Function} doneFn - a callback that is executed only once all requests have 51030 * succeeded or failed 51031 * @returns {Function} a function that, when invoked, immediately aborts all 51032 * outstanding requests 51033 */ 51034 51035 51036 var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) { 51037 var activeXhrs = [];
vendor: 172,335 bytes, lines 51038-55523
51038 var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key 51039 51040 if (segment.key && !segment.key.bytes) { 51041 var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, { 51042 uri: segment.key.resolvedUri, 51043 responseType: 'arraybuffer' 51044 }); 51045 var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn); 51046 var keyXhr = xhr(keyRequestOptions, keyRequestCallback); 51047 activeXhrs.push(keyXhr); 51048 } // optionally, request the associated media init segment 51049 51050 51051 if (segment.map && !segment.map.bytes) { 51052 var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, { 51053 uri: segment.map.resolvedUri, 51054 responseType: 'arraybuffer', 51055 headers: segmentXhrHeaders(segment.map) 51056 }); 51057 var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn); 51058 var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); 51059 activeXhrs.push(initSegmentXhr); 51060 } 51061 51062 var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, { 51063 uri: segment.resolvedUri, 51064 responseType: 'arraybuffer', 51065 headers: segmentXhrHeaders(segment) 51066 }); 51067 var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn); 51068 var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); 51069 segmentXhr.addEventListener('progress', handleProgress(segment, progressFn)); 51070 activeXhrs.push(segmentXhr); 51071 return function () { 51072 return abortAll(activeXhrs); 51073 }; 51074 }; // Utilities 51075 51076 /** 51077 * Returns the CSS value for the specified property on an element 51078 * using `getComputedStyle`. Firefox has a long-standing issue where 51079 * getComputedStyle() may return null when running in an iframe with 51080 * `display: none`. 51081 * 51082 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 51083 * @param {HTMLElement} el the htmlelement to work on 51084 * @param {string} the proprety to get the style for 51085 */ 51086 51087 51088 var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 51089 var result = void 0; 51090 51091 if (!el) { 51092 return ''; 51093 } 51094 51095 result = window$3.getComputedStyle(el); 51096 51097 if (!result) { 51098 return ''; 51099 } 51100 51101 return result[property]; 51102 }; 51103 /** 51104 * Resuable stable sort function 51105 * 51106 * @param {Playlists} array 51107 * @param {Function} sortFn Different comparators 51108 * @function stableSort 51109 */ 51110 51111 51112 var stableSort = function stableSort(array, sortFn) { 51113 var newArray = array.slice(); 51114 array.sort(function (left, right) { 51115 var cmp = sortFn(left, right); 51116 51117 if (cmp === 0) { 51118 return newArray.indexOf(left) - newArray.indexOf(right); 51119 } 51120 51121 return cmp; 51122 }); 51123 }; 51124 /** 51125 * A comparator function to sort two playlist object by bandwidth. 51126 * 51127 * @param {Object} left a media playlist object 51128 * @param {Object} right a media playlist object 51129 * @return {Number} Greater than zero if the bandwidth attribute of 51130 * left is greater than the corresponding attribute of right. Less 51131 * than zero if the bandwidth of right is greater than left and 51132 * exactly zero if the two are equal. 51133 */ 51134 51135 51136 var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) { 51137 var leftBandwidth = void 0; 51138 var rightBandwidth = void 0; 51139 51140 if (left.attributes.BANDWIDTH) { 51141 leftBandwidth = left.attributes.BANDWIDTH; 51142 } 51143 51144 leftBandwidth = leftBandwidth || window$3.Number.MAX_VALUE; 51145 51146 if (right.attributes.BANDWIDTH) { 51147 rightBandwidth = right.attributes.BANDWIDTH; 51148 } 51149 51150 rightBandwidth = rightBandwidth || window$3.Number.MAX_VALUE; 51151 return leftBandwidth - rightBandwidth; 51152 }; 51153 /** 51154 * A comparator function to sort two playlist object by resolution (width). 51155 * @param {Object} left a media playlist object 51156 * @param {Object} right a media playlist object 51157 * @return {Number} Greater than zero if the resolution.width attribute of 51158 * left is greater than the corresponding attribute of right. Less 51159 * than zero if the resolution.width of right is greater than left and 51160 * exactly zero if the two are equal. 51161 */ 51162 51163 51164 var comparePlaylistResolution = function comparePlaylistResolution(left, right) { 51165 var leftWidth = void 0; 51166 var rightWidth = void 0; 51167 51168 if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 51169 leftWidth = left.attributes.RESOLUTION.width; 51170 } 51171 51172 leftWidth = leftWidth || window$3.Number.MAX_VALUE; 51173 51174 if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 51175 rightWidth = right.attributes.RESOLUTION.width; 51176 } 51177 51178 rightWidth = rightWidth || window$3.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 51179 // have the same media dimensions/ resolution 51180 51181 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 51182 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 51183 } 51184 51185 return leftWidth - rightWidth; 51186 }; 51187 /** 51188 * Chooses the appropriate media playlist based on bandwidth and player size 51189 * 51190 * @param {Object} master 51191 * Object representation of the master manifest 51192 * @param {Number} playerBandwidth 51193 * Current calculated bandwidth of the player 51194 * @param {Number} playerWidth 51195 * Current width of the player element (should account for the device pixel ratio) 51196 * @param {Number} playerHeight 51197 * Current height of the player element (should account for the device pixel ratio) 51198 * @param {Boolean} limitRenditionByPlayerDimensions 51199 * True if the player width and height should be used during the selection, false otherwise 51200 * @return {Playlist} the highest bitrate playlist less than the 51201 * currently detected bandwidth, accounting for some amount of 51202 * bandwidth variance 51203 */ 51204 51205 51206 var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) { 51207 // convert the playlists to an intermediary representation to make comparisons easier 51208 var sortedPlaylistReps = master.playlists.map(function (playlist) { 51209 var width = void 0; 51210 var height = void 0; 51211 var bandwidth = void 0; 51212 width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width; 51213 height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height; 51214 bandwidth = playlist.attributes.BANDWIDTH; 51215 bandwidth = bandwidth || window$3.Number.MAX_VALUE; 51216 return { 51217 bandwidth: bandwidth, 51218 width: width, 51219 height: height, 51220 playlist: playlist 51221 }; 51222 }); 51223 stableSort(sortedPlaylistReps, function (left, right) { 51224 return left.bandwidth - right.bandwidth; 51225 }); // filter out any playlists that have been excluded due to 51226 // incompatible configurations 51227 51228 sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) { 51229 return !Playlist.isIncompatible(rep.playlist); 51230 }); // filter out any playlists that have been disabled manually through the representations 51231 // api or blacklisted temporarily due to playback errors. 51232 51233 var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 51234 return Playlist.isEnabled(rep.playlist); 51235 }); 51236 51237 if (!enabledPlaylistReps.length) { 51238 // if there are no enabled playlists, then they have all been blacklisted or disabled 51239 // by the user through the representations api. In this case, ignore blacklisting and 51240 // fallback to what the user wants by using playlists the user has not disabled. 51241 enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 51242 return !Playlist.isDisabled(rep.playlist); 51243 }); 51244 } // filter out any variant that has greater effective bitrate 51245 // than the current estimated bandwidth 51246 51247 51248 var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) { 51249 return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth; 51250 }); 51251 var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth 51252 // and then taking the very first element 51253 51254 var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) { 51255 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 51256 })[0]; // if we're not going to limit renditions by player size, make an early decision. 51257 51258 if (limitRenditionByPlayerDimensions === false) { 51259 var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 51260 51261 return _chosenRep ? _chosenRep.playlist : null; 51262 } // filter out playlists without resolution information 51263 51264 51265 var haveResolution = bandwidthPlaylistReps.filter(function (rep) { 51266 return rep.width && rep.height; 51267 }); // sort variants by resolution 51268 51269 stableSort(haveResolution, function (left, right) { 51270 return left.width - right.width; 51271 }); // if we have the exact resolution as the player use it 51272 51273 var resolutionBestRepList = haveResolution.filter(function (rep) { 51274 return rep.width === playerWidth && rep.height === playerHeight; 51275 }); 51276 highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution 51277 51278 var resolutionBestRep = resolutionBestRepList.filter(function (rep) { 51279 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 51280 })[0]; 51281 var resolutionPlusOneList = void 0; 51282 var resolutionPlusOneSmallest = void 0; 51283 var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player 51284 // if there is no match of exact resolution 51285 51286 if (!resolutionBestRep) { 51287 resolutionPlusOneList = haveResolution.filter(function (rep) { 51288 return rep.width > playerWidth || rep.height > playerHeight; 51289 }); // find all the variants have the same smallest resolution 51290 51291 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) { 51292 return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height; 51293 }); // ensure that we also pick the highest bandwidth variant that 51294 // is just-larger-than the video player 51295 51296 highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1]; 51297 resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) { 51298 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 51299 })[0]; 51300 } // fallback chain of variants 51301 51302 51303 var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 51304 return chosenRep ? chosenRep.playlist : null; 51305 }; // Playlist Selectors 51306 51307 /** 51308 * Chooses the appropriate media playlist based on the most recent 51309 * bandwidth estimate and the player size. 51310 * 51311 * Expects to be called within the context of an instance of HlsHandler 51312 * 51313 * @return {Playlist} the highest bitrate playlist less than the 51314 * currently detected bandwidth, accounting for some amount of 51315 * bandwidth variance 51316 */ 51317 51318 51319 var lastBandwidthSelector = function lastBandwidthSelector() { 51320 var pixelRatio = this.useDevicePixelRatio ? window$3.devicePixelRatio || 1 : 1; 51321 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); 51322 }; 51323 /** 51324 * Chooses the appropriate media playlist based on the potential to rebuffer 51325 * 51326 * @param {Object} settings 51327 * Object of information required to use this selector 51328 * @param {Object} settings.master 51329 * Object representation of the master manifest 51330 * @param {Number} settings.currentTime 51331 * The current time of the player 51332 * @param {Number} settings.bandwidth 51333 * Current measured bandwidth 51334 * @param {Number} settings.duration 51335 * Duration of the media 51336 * @param {Number} settings.segmentDuration 51337 * Segment duration to be used in round trip time calculations 51338 * @param {Number} settings.timeUntilRebuffer 51339 * Time left in seconds until the player has to rebuffer 51340 * @param {Number} settings.currentTimeline 51341 * The current timeline segments are being loaded from 51342 * @param {SyncController} settings.syncController 51343 * SyncController for determining if we have a sync point for a given playlist 51344 * @return {Object|null} 51345 * {Object} return.playlist 51346 * The highest bandwidth playlist with the least amount of rebuffering 51347 * {Number} return.rebufferingImpact 51348 * The amount of time in seconds switching to this playlist will rebuffer. A 51349 * negative value means that switching will cause zero rebuffering. 51350 */ 51351 51352 51353 var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) { 51354 var master = settings.master, 51355 currentTime = settings.currentTime, 51356 bandwidth = settings.bandwidth, 51357 duration$$1 = settings.duration, 51358 segmentDuration = settings.segmentDuration, 51359 timeUntilRebuffer = settings.timeUntilRebuffer, 51360 currentTimeline = settings.currentTimeline, 51361 syncController = settings.syncController; // filter out any playlists that have been excluded due to 51362 // incompatible configurations 51363 51364 var compatiblePlaylists = master.playlists.filter(function (playlist) { 51365 return !Playlist.isIncompatible(playlist); 51366 }); // filter out any playlists that have been disabled manually through the representations 51367 // api or blacklisted temporarily due to playback errors. 51368 51369 var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled); 51370 51371 if (!enabledPlaylists.length) { 51372 // if there are no enabled playlists, then they have all been blacklisted or disabled 51373 // by the user through the representations api. In this case, ignore blacklisting and 51374 // fallback to what the user wants by using playlists the user has not disabled. 51375 enabledPlaylists = compatiblePlaylists.filter(function (playlist) { 51376 return !Playlist.isDisabled(playlist); 51377 }); 51378 } 51379 51380 var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH')); 51381 var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) { 51382 var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a 51383 // sync request first. This will double the request time 51384 51385 var numRequests = syncPoint ? 1 : 2; 51386 var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist); 51387 var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer; 51388 return { 51389 playlist: playlist, 51390 rebufferingImpact: rebufferingImpact 51391 }; 51392 }); 51393 var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) { 51394 return estimate.rebufferingImpact <= 0; 51395 }); // Sort by bandwidth DESC 51396 51397 stableSort(noRebufferingPlaylists, function (a, b) { 51398 return comparePlaylistBandwidth(b.playlist, a.playlist); 51399 }); 51400 51401 if (noRebufferingPlaylists.length) { 51402 return noRebufferingPlaylists[0]; 51403 } 51404 51405 stableSort(rebufferingEstimates, function (a, b) { 51406 return a.rebufferingImpact - b.rebufferingImpact; 51407 }); 51408 return rebufferingEstimates[0] || null; 51409 }; 51410 /** 51411 * Chooses the appropriate media playlist, which in this case is the lowest bitrate 51412 * one with video. If no renditions with video exist, return the lowest audio rendition. 51413 * 51414 * Expects to be called within the context of an instance of HlsHandler 51415 * 51416 * @return {Object|null} 51417 * {Object} return.playlist 51418 * The lowest bitrate playlist that contains a video codec. If no such rendition 51419 * exists pick the lowest audio rendition. 51420 */ 51421 51422 51423 var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() { 51424 // filter out any playlists that have been excluded due to 51425 // incompatible configurations or playback errors 51426 var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate 51427 51428 stableSort(playlists, function (a, b) { 51429 return comparePlaylistBandwidth(a, b); 51430 }); // Parse and assume that playlists with no video codec have no video 51431 // (this is not necessarily true, although it is generally true). 51432 // 51433 // If an entire manifest has no valid videos everything will get filtered 51434 // out. 51435 51436 var playlistsWithVideo = playlists.filter(function (playlist) { 51437 return parseCodecs(playlist.attributes.CODECS).videoCodec; 51438 }); 51439 return playlistsWithVideo[0] || null; 51440 }; 51441 /** 51442 * Create captions text tracks on video.js if they do not exist 51443 * 51444 * @param {Object} inbandTextTracks a reference to current inbandTextTracks 51445 * @param {Object} tech the video.js tech 51446 * @param {Object} captionStreams the caption streams to create 51447 * @private 51448 */ 51449 51450 51451 var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) { 51452 for (var trackId in captionStreams) { 51453 if (!inbandTextTracks[trackId]) { 51454 tech.trigger({ 51455 type: 'usage', 51456 name: 'hls-608' 51457 }); 51458 var track = tech.textTracks().getTrackById(trackId); 51459 51460 if (track) { 51461 // Resuse an existing track with a CC# id because this was 51462 // very likely created by videojs-contrib-hls from information 51463 // in the m3u8 for us to use 51464 inbandTextTracks[trackId] = track; 51465 } else { 51466 // Otherwise, create a track with the default `CC#` label and 51467 // without a language 51468 inbandTextTracks[trackId] = tech.addRemoteTextTrack({ 51469 kind: 'captions', 51470 id: trackId, 51471 label: trackId 51472 }, false).track; 51473 } 51474 } 51475 } 51476 }; 51477 51478 var addCaptionData = function addCaptionData(_ref) { 51479 var inbandTextTracks = _ref.inbandTextTracks, 51480 captionArray = _ref.captionArray, 51481 timestampOffset = _ref.timestampOffset; 51482 51483 if (!captionArray) { 51484 return; 51485 } 51486 51487 var Cue = window.WebKitDataCue || window.VTTCue; 51488 captionArray.forEach(function (caption) { 51489 var track = caption.stream; 51490 var startTime = caption.startTime; 51491 var endTime = caption.endTime; 51492 51493 if (!inbandTextTracks[track]) { 51494 return; 51495 } 51496 51497 startTime += timestampOffset; 51498 endTime += timestampOffset; 51499 inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text)); 51500 }); 51501 }; 51502 /** 51503 * @file segment-loader.js 51504 */ 51505 // in ms 51506 51507 51508 var CHECK_BUFFER_DELAY = 500; 51509 /** 51510 * Determines if we should call endOfStream on the media source based 51511 * on the state of the buffer or if appened segment was the final 51512 * segment in the playlist. 51513 * 51514 * @param {Object} playlist a media playlist object 51515 * @param {Object} mediaSource the MediaSource object 51516 * @param {Number} segmentIndex the index of segment we last appended 51517 * @returns {Boolean} do we need to call endOfStream on the MediaSource 51518 */ 51519 51520 var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) { 51521 if (!playlist || !mediaSource) { 51522 return false; 51523 } 51524 51525 var segments = playlist.segments; // determine a few boolean values to help make the branch below easier 51526 // to read 51527 51528 var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream 51529 // so that MediaSources can trigger the `ended` event when it runs out of 51530 // buffered data instead of waiting for me 51531 51532 return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment; 51533 }; 51534 51535 var finite = function finite(num) { 51536 return typeof num === 'number' && isFinite(num); 51537 }; 51538 51539 var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) { 51540 // Although these checks should most likely cover non 'main' types, for now it narrows 51541 // the scope of our checks. 51542 if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) { 51543 return null; 51544 } 51545 51546 if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) { 51547 return 'Neither audio nor video found in segment.'; 51548 } 51549 51550 if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) { 51551 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.'; 51552 } 51553 51554 if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) { 51555 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.'; 51556 } 51557 51558 return null; 51559 }; 51560 /** 51561 * Calculates a time value that is safe to remove from the back buffer without interupting 51562 * playback. 51563 * 51564 * @param {TimeRange} seekable 51565 * The current seekable range 51566 * @param {Number} currentTime 51567 * The current time of the player 51568 * @param {Number} targetDuration 51569 * The target duration of the current playlist 51570 * @return {Number} 51571 * Time that is safe to remove from the back buffer without interupting playback 51572 */ 51573 51574 51575 var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) { 51576 // 30 seconds before the playhead provides a safe default for trimming. 51577 // 51578 // Choosing a reasonable default is particularly important for high bitrate content and 51579 // VOD videos/live streams with large windows, as the buffer may end up overfilled and 51580 // throw an APPEND_BUFFER_ERR. 51581 var trimTime = currentTime - 30; 51582 51583 if (seekable$$1.length) { 51584 // Some live playlists may have a shorter window of content than the full allowed back 51585 // buffer. For these playlists, don't save content that's no longer within the window. 51586 trimTime = Math.max(trimTime, seekable$$1.start(0)); 51587 } // Don't remove within target duration of the current time to avoid the possibility of 51588 // removing the GOP currently being played, as removing it can cause playback stalls. 51589 51590 51591 var maxTrimTime = currentTime - targetDuration; 51592 return Math.min(maxTrimTime, trimTime); 51593 }; 51594 51595 var segmentInfoString = function segmentInfoString(segmentInfo) { 51596 var _segmentInfo$segment = segmentInfo.segment, 51597 start = _segmentInfo$segment.start, 51598 end = _segmentInfo$segment.end, 51599 _segmentInfo$playlist = segmentInfo.playlist, 51600 seq = _segmentInfo$playlist.mediaSequence, 51601 id = _segmentInfo$playlist.id, 51602 _segmentInfo$playlist2 = _segmentInfo$playlist.segments, 51603 segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2, 51604 index = segmentInfo.mediaIndex, 51605 timeline = segmentInfo.timeline; 51606 return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' '); 51607 }; 51608 /** 51609 * An object that manages segment loading and appending. 51610 * 51611 * @class SegmentLoader 51612 * @param {Object} options required and optional options 51613 * @extends videojs.EventTarget 51614 */ 51615 51616 51617 var SegmentLoader = function (_videojs$EventTarget) { 51618 inherits$2(SegmentLoader, _videojs$EventTarget); 51619 51620 function SegmentLoader(settings) { 51621 classCallCheck$1(this, SegmentLoader); // check pre-conditions 51622 51623 var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this)); 51624 51625 if (!settings) { 51626 throw new TypeError('Initialization settings are required'); 51627 } 51628 51629 if (typeof settings.currentTime !== 'function') { 51630 throw new TypeError('No currentTime getter specified'); 51631 } 51632 51633 if (!settings.mediaSource) { 51634 throw new TypeError('No MediaSource specified'); 51635 } // public properties 51636 51637 51638 _this.bandwidth = settings.bandwidth; 51639 _this.throughput = { 51640 rate: 0, 51641 count: 0 51642 }; 51643 _this.roundTrip = NaN; 51644 51645 _this.resetStats_(); 51646 51647 _this.mediaIndex = null; // private settings 51648 51649 _this.hasPlayed_ = settings.hasPlayed; 51650 _this.currentTime_ = settings.currentTime; 51651 _this.seekable_ = settings.seekable; 51652 _this.seeking_ = settings.seeking; 51653 _this.duration_ = settings.duration; 51654 _this.mediaSource_ = settings.mediaSource; 51655 _this.hls_ = settings.hls; 51656 _this.loaderType_ = settings.loaderType; 51657 _this.startingMedia_ = void 0; 51658 _this.segmentMetadataTrack_ = settings.segmentMetadataTrack; 51659 _this.goalBufferLength_ = settings.goalBufferLength; 51660 _this.sourceType_ = settings.sourceType; 51661 _this.inbandTextTracks_ = settings.inbandTextTracks; 51662 _this.state_ = 'INIT'; // private instance variables 51663 51664 _this.checkBufferTimeout_ = null; 51665 _this.error_ = void 0; 51666 _this.currentTimeline_ = -1; 51667 _this.pendingSegment_ = null; 51668 _this.mimeType_ = null; 51669 _this.sourceUpdater_ = null; 51670 _this.xhrOptions_ = null; // Fragmented mp4 playback 51671 51672 _this.activeInitSegmentId_ = null; 51673 _this.initSegments_ = {}; // HLSe playback 51674 51675 _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys; 51676 _this.keyCache_ = {}; // Fmp4 CaptionParser 51677 51678 if (_this.loaderType_ === 'main') { 51679 _this.captionParser_ = new captionParser(); 51680 } else { 51681 _this.captionParser_ = null; 51682 } 51683 51684 _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings 51685 // between a time in the display time and a segment index within 51686 // a playlist 51687 51688 _this.syncController_ = settings.syncController; 51689 _this.syncPoint_ = { 51690 segmentIndex: 0, 51691 time: 0 51692 }; 51693 51694 _this.triggerSyncInfoUpdate_ = function () { 51695 return _this.trigger('syncinfoupdate'); 51696 }; 51697 51698 _this.syncController_.on('syncinfoupdate', _this.triggerSyncInfoUpdate_); 51699 51700 _this.mediaSource_.addEventListener('sourceopen', function () { 51701 return _this.ended_ = false; 51702 }); // ...for determining the fetch location 51703 51704 51705 _this.fetchAtBuffer_ = false; 51706 _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']'); 51707 Object.defineProperty(_this, 'state', { 51708 get: function get$$1() { 51709 return this.state_; 51710 }, 51711 set: function set$$1(newState) { 51712 if (newState !== this.state_) { 51713 this.logger_(this.state_ + ' -> ' + newState); 51714 this.state_ = newState; 51715 } 51716 } 51717 }); 51718 return _this; 51719 } 51720 /** 51721 * reset all of our media stats 51722 * 51723 * @private 51724 */ 51725 51726 51727 createClass$1(SegmentLoader, [{ 51728 key: 'resetStats_', 51729 value: function resetStats_() { 51730 this.mediaBytesTransferred = 0; 51731 this.mediaRequests = 0; 51732 this.mediaRequestsAborted = 0; 51733 this.mediaRequestsTimedout = 0; 51734 this.mediaRequestsErrored = 0; 51735 this.mediaTransferDuration = 0; 51736 this.mediaSecondsLoaded = 0; 51737 } 51738 /** 51739 * dispose of the SegmentLoader and reset to the default state 51740 */ 51741 51742 }, { 51743 key: 'dispose', 51744 value: function dispose() { 51745 this.trigger('dispose'); 51746 this.state = 'DISPOSED'; 51747 this.pause(); 51748 this.abort_(); 51749 51750 if (this.sourceUpdater_) { 51751 this.sourceUpdater_.dispose(); 51752 } 51753 51754 this.resetStats_(); 51755 51756 if (this.captionParser_) { 51757 this.captionParser_.reset(); 51758 } 51759 51760 if (this.checkBufferTimeout_) { 51761 window$3.clearTimeout(this.checkBufferTimeout_); 51762 } 51763 51764 if (this.syncController_ && this.triggerSyncInfoUpdate_) { 51765 this.syncController_.off('syncinfoupdate', this.triggerSyncInfoUpdate_); 51766 } 51767 51768 this.off(); 51769 } 51770 /** 51771 * abort anything that is currently doing on with the SegmentLoader 51772 * and reset to a default state 51773 */ 51774 51775 }, { 51776 key: 'abort', 51777 value: function abort() { 51778 if (this.state !== 'WAITING') { 51779 if (this.pendingSegment_) { 51780 this.pendingSegment_ = null; 51781 } 51782 51783 return; 51784 } 51785 51786 this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY 51787 // since we are no longer "waiting" on any requests. XHR callback is not always run 51788 // when the request is aborted. This will prevent the loader from being stuck in the 51789 // WAITING state indefinitely. 51790 51791 this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the 51792 // next segment 51793 51794 if (!this.paused()) { 51795 this.monitorBuffer_(); 51796 } 51797 } 51798 /** 51799 * abort all pending xhr requests and null any pending segements 51800 * 51801 * @private 51802 */ 51803 51804 }, { 51805 key: 'abort_', 51806 value: function abort_() { 51807 if (this.pendingSegment_) { 51808 this.pendingSegment_.abortRequests(); 51809 } // clear out the segment being processed 51810 51811 51812 this.pendingSegment_ = null; 51813 } 51814 /** 51815 * set an error on the segment loader and null out any pending segements 51816 * 51817 * @param {Error} error the error to set on the SegmentLoader 51818 * @return {Error} the error that was set or that is currently set 51819 */ 51820 51821 }, { 51822 key: 'error', 51823 value: function error(_error) { 51824 if (typeof _error !== 'undefined') { 51825 this.error_ = _error; 51826 } 51827 51828 this.pendingSegment_ = null; 51829 return this.error_; 51830 } 51831 }, { 51832 key: 'endOfStream', 51833 value: function endOfStream() { 51834 this.ended_ = true; 51835 this.pause(); 51836 this.trigger('ended'); 51837 } 51838 /** 51839 * Indicates which time ranges are buffered 51840 * 51841 * @return {TimeRange} 51842 * TimeRange object representing the current buffered ranges 51843 */ 51844 51845 }, { 51846 key: 'buffered_', 51847 value: function buffered_() { 51848 if (!this.sourceUpdater_) { 51849 return videojs$1.createTimeRanges(); 51850 } 51851 51852 return this.sourceUpdater_.buffered(); 51853 } 51854 /** 51855 * Gets and sets init segment for the provided map 51856 * 51857 * @param {Object} map 51858 * The map object representing the init segment to get or set 51859 * @param {Boolean=} set 51860 * If true, the init segment for the provided map should be saved 51861 * @return {Object} 51862 * map object for desired init segment 51863 */ 51864 51865 }, { 51866 key: 'initSegment', 51867 value: function initSegment(map) { 51868 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 51869 51870 if (!map) { 51871 return null; 51872 } 51873 51874 var id = initSegmentId(map); 51875 var storedMap = this.initSegments_[id]; 51876 51877 if (set$$1 && !storedMap && map.bytes) { 51878 this.initSegments_[id] = storedMap = { 51879 resolvedUri: map.resolvedUri, 51880 byterange: map.byterange, 51881 bytes: map.bytes, 51882 timescales: map.timescales, 51883 videoTrackIds: map.videoTrackIds 51884 }; 51885 } 51886 51887 return storedMap || map; 51888 } 51889 /** 51890 * Gets and sets key for the provided key 51891 * 51892 * @param {Object} key 51893 * The key object representing the key to get or set 51894 * @param {Boolean=} set 51895 * If true, the key for the provided key should be saved 51896 * @return {Object} 51897 * Key object for desired key 51898 */ 51899 51900 }, { 51901 key: 'segmentKey', 51902 value: function segmentKey(key) { 51903 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 51904 51905 if (!key) { 51906 return null; 51907 } 51908 51909 var id = segmentKeyId(key); 51910 var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per 51911 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3 51912 51913 if (this.cacheEncryptionKeys_ && set$$1 && !storedKey && key.bytes) { 51914 this.keyCache_[id] = storedKey = { 51915 resolvedUri: key.resolvedUri, 51916 bytes: key.bytes 51917 }; 51918 } 51919 51920 var result = { 51921 resolvedUri: (storedKey || key).resolvedUri 51922 }; 51923 51924 if (storedKey) { 51925 result.bytes = storedKey.bytes; 51926 } 51927 51928 return result; 51929 } 51930 /** 51931 * Returns true if all configuration required for loading is present, otherwise false. 51932 * 51933 * @return {Boolean} True if the all configuration is ready for loading 51934 * @private 51935 */ 51936 51937 }, { 51938 key: 'couldBeginLoading_', 51939 value: function couldBeginLoading_() { 51940 return this.playlist_ && ( // the source updater is created when init_ is called, so either having a 51941 // source updater or being in the INIT state with a mimeType is enough 51942 // to say we have all the needed configuration to start loading. 51943 this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused(); 51944 } 51945 /** 51946 * load a playlist and start to fill the buffer 51947 */ 51948 51949 }, { 51950 key: 'load', 51951 value: function load() { 51952 // un-pause 51953 this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be 51954 // specified 51955 51956 if (!this.playlist_) { 51957 return; 51958 } // not sure if this is the best place for this 51959 51960 51961 this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading 51962 51963 if (this.state === 'INIT' && this.couldBeginLoading_()) { 51964 return this.init_(); 51965 } // if we're in the middle of processing a segment already, don't 51966 // kick off an additional segment request 51967 51968 51969 if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') { 51970 return; 51971 } 51972 51973 this.state = 'READY'; 51974 } 51975 /** 51976 * Once all the starting parameters have been specified, begin 51977 * operation. This method should only be invoked from the INIT 51978 * state. 51979 * 51980 * @private 51981 */ 51982 51983 }, { 51984 key: 'init_', 51985 value: function init_() { 51986 this.state = 'READY'; 51987 this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_); 51988 this.resetEverything(); 51989 return this.monitorBuffer_(); 51990 } 51991 /** 51992 * set a playlist on the segment loader 51993 * 51994 * @param {PlaylistLoader} media the playlist to set on the segment loader 51995 */ 51996 51997 }, { 51998 key: 'playlist', 51999 value: function playlist(newPlaylist) { 52000 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 52001 52002 if (!newPlaylist) { 52003 return; 52004 } 52005 52006 var oldPlaylist = this.playlist_; 52007 var segmentInfo = this.pendingSegment_; 52008 this.playlist_ = newPlaylist; 52009 this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist 52010 // is always our zero-time so force a sync update each time the playlist 52011 // is refreshed from the server 52012 // 52013 // Use the INIT state to determine if playback has started, as the playlist sync info 52014 // should be fixed once requests begin (as sync points are generated based on sync 52015 // info), but not before then. 52016 52017 if (this.state === 'INIT') { 52018 newPlaylist.syncInfo = { 52019 mediaSequence: newPlaylist.mediaSequence, 52020 time: 0 52021 }; 52022 } 52023 52024 var oldId = null; 52025 52026 if (oldPlaylist) { 52027 if (oldPlaylist.id) { 52028 oldId = oldPlaylist.id; 52029 } else if (oldPlaylist.uri) { 52030 oldId = oldPlaylist.uri; 52031 } 52032 } 52033 52034 this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above) 52035 // in LIVE, we always want to update with new playlists (including refreshes) 52036 52037 this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start 52038 // buffering now 52039 52040 if (this.state === 'INIT' && this.couldBeginLoading_()) { 52041 return this.init_(); 52042 } 52043 52044 if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) { 52045 if (this.mediaIndex !== null) { 52046 // we must "resync" the segment loader when we switch renditions and 52047 // the segment loader is already synced to the previous rendition 52048 this.resyncLoader(); 52049 } // the rest of this function depends on `oldPlaylist` being defined 52050 52051 52052 return; 52053 } // we reloaded the same playlist so we are in a live scenario 52054 // and we will likely need to adjust the mediaIndex 52055 52056 52057 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 52058 this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader 52059 // this is important because we can abort a request and this value must be 52060 // equal to the last appended mediaIndex 52061 52062 if (this.mediaIndex !== null) { 52063 this.mediaIndex -= mediaSequenceDiff; 52064 } // update the mediaIndex on the SegmentInfo object 52065 // this is important because we will update this.mediaIndex with this value 52066 // in `handleUpdateEnd_` after the segment has been successfully appended 52067 52068 52069 if (segmentInfo) { 52070 segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is 52071 // saved for the new playlist's segment, however, if the segment fell off the 52072 // playlist, we can leave the old reference and just lose the timing info 52073 52074 if (segmentInfo.mediaIndex >= 0) { 52075 segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex]; 52076 } 52077 } 52078 52079 this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist); 52080 } 52081 /** 52082 * Prevent the loader from fetching additional segments. If there 52083 * is a segment request outstanding, it will finish processing 52084 * before the loader halts. A segment loader can be unpaused by 52085 * calling load(). 52086 */ 52087 52088 }, { 52089 key: 'pause', 52090 value: function pause() { 52091 if (this.checkBufferTimeout_) { 52092 window$3.clearTimeout(this.checkBufferTimeout_); 52093 this.checkBufferTimeout_ = null; 52094 } 52095 } 52096 /** 52097 * Returns whether the segment loader is fetching additional 52098 * segments when given the opportunity. This property can be 52099 * modified through calls to pause() and load(). 52100 */ 52101 52102 }, { 52103 key: 'paused', 52104 value: function paused() { 52105 return this.checkBufferTimeout_ === null; 52106 } 52107 /** 52108 * create/set the following mimetype on the SourceBuffer through a 52109 * SourceUpdater 52110 * 52111 * @param {String} mimeType the mime type string to use 52112 * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer 52113 * is added to the media source 52114 */ 52115 52116 }, { 52117 key: 'mimeType', 52118 value: function mimeType(_mimeType, sourceBufferEmitter) { 52119 if (this.mimeType_) { 52120 return; 52121 } 52122 52123 this.mimeType_ = _mimeType; 52124 this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start 52125 // buffering now 52126 52127 if (this.state === 'INIT' && this.couldBeginLoading_()) { 52128 this.init_(); 52129 } 52130 } 52131 /** 52132 * Delete all the buffered data and reset the SegmentLoader 52133 * @param {Function} [done] an optional callback to be executed when the remove 52134 * operation is complete 52135 */ 52136 52137 }, { 52138 key: 'resetEverything', 52139 value: function resetEverything(done) { 52140 this.ended_ = false; 52141 this.resetLoader(); // remove from 0, the earliest point, to Infinity, to signify removal of everything. 52142 // VTT Segment Loader doesn't need to do anything but in the regular SegmentLoader, 52143 // we then clamp the value to duration if necessary. 52144 52145 this.remove(0, Infinity, done); // clears fmp4 captions 52146 52147 if (this.captionParser_) { 52148 this.captionParser_.clearAllCaptions(); 52149 } 52150 52151 this.trigger('reseteverything'); 52152 } 52153 /** 52154 * Force the SegmentLoader to resync and start loading around the currentTime instead 52155 * of starting at the end of the buffer 52156 * 52157 * Useful for fast quality changes 52158 */ 52159 52160 }, { 52161 key: 'resetLoader', 52162 value: function resetLoader() { 52163 this.fetchAtBuffer_ = false; 52164 this.resyncLoader(); 52165 } 52166 /** 52167 * Force the SegmentLoader to restart synchronization and make a conservative guess 52168 * before returning to the simple walk-forward method 52169 */ 52170 52171 }, { 52172 key: 'resyncLoader', 52173 value: function resyncLoader() { 52174 this.mediaIndex = null; 52175 this.syncPoint_ = null; 52176 this.abort(); 52177 } 52178 /** 52179 * Remove any data in the source buffer between start and end times 52180 * @param {Number} start - the start time of the region to remove from the buffer 52181 * @param {Number} end - the end time of the region to remove from the buffer 52182 * @param {Function} [done] - an optional callback to be executed when the remove 52183 * operation is complete 52184 */ 52185 52186 }, { 52187 key: 'remove', 52188 value: function remove(start, end, done) { 52189 // clamp end to duration if we need to remove everything. 52190 // This is due to a browser bug that causes issues if we remove to Infinity. 52191 // videojs/videojs-contrib-hls#1225 52192 if (end === Infinity) { 52193 end = this.duration_(); 52194 } 52195 52196 if (this.sourceUpdater_) { 52197 this.sourceUpdater_.remove(start, end, done); 52198 } 52199 52200 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 52201 52202 if (this.inbandTextTracks_) { 52203 for (var id in this.inbandTextTracks_) { 52204 removeCuesFromTrack(start, end, this.inbandTextTracks_[id]); 52205 } 52206 } 52207 } 52208 /** 52209 * (re-)schedule monitorBufferTick_ to run as soon as possible 52210 * 52211 * @private 52212 */ 52213 52214 }, { 52215 key: 'monitorBuffer_', 52216 value: function monitorBuffer_() { 52217 if (this.checkBufferTimeout_) { 52218 window$3.clearTimeout(this.checkBufferTimeout_); 52219 } 52220 52221 this.checkBufferTimeout_ = window$3.setTimeout(this.monitorBufferTick_.bind(this), 1); 52222 } 52223 /** 52224 * As long as the SegmentLoader is in the READY state, periodically 52225 * invoke fillBuffer_(). 52226 * 52227 * @private 52228 */ 52229 52230 }, { 52231 key: 'monitorBufferTick_', 52232 value: function monitorBufferTick_() { 52233 if (this.state === 'READY') { 52234 this.fillBuffer_(); 52235 } 52236 52237 if (this.checkBufferTimeout_) { 52238 window$3.clearTimeout(this.checkBufferTimeout_); 52239 } 52240 52241 this.checkBufferTimeout_ = window$3.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY); 52242 } 52243 /** 52244 * fill the buffer with segements unless the sourceBuffers are 52245 * currently updating 52246 * 52247 * Note: this function should only ever be called by monitorBuffer_ 52248 * and never directly 52249 * 52250 * @private 52251 */ 52252 52253 }, { 52254 key: 'fillBuffer_', 52255 value: function fillBuffer_() { 52256 if (this.sourceUpdater_.updating()) { 52257 return; 52258 } 52259 52260 if (!this.syncPoint_) { 52261 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 52262 } // see if we need to begin loading immediately 52263 52264 52265 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 52266 52267 if (!segmentInfo) { 52268 return; 52269 } 52270 52271 if (this.isEndOfStream_(segmentInfo.mediaIndex)) { 52272 this.endOfStream(); 52273 return; 52274 } 52275 52276 if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { 52277 return; 52278 } // We will need to change timestampOffset of the sourceBuffer if: 52279 // - The segment.timeline !== this.currentTimeline 52280 // (we are crossing a discontinuity somehow) 52281 // - The "timestampOffset" for the start of this segment is less than 52282 // the currently set timestampOffset 52283 // Also, clear captions if we are crossing a discontinuity boundary 52284 // Previously, we changed the timestampOffset if the start of this segment 52285 // is less than the currently set timestampOffset but this isn't wanted 52286 // as it can produce bad behavior, especially around long running 52287 // live streams 52288 52289 52290 if (segmentInfo.timeline !== this.currentTimeline_) { 52291 this.syncController_.reset(); 52292 segmentInfo.timestampOffset = segmentInfo.startOfSegment; 52293 52294 if (this.captionParser_) { 52295 this.captionParser_.clearAllCaptions(); 52296 } 52297 } 52298 52299 this.loadSegment_(segmentInfo); 52300 } 52301 /** 52302 * Determines if this segment loader is at the end of it's stream. 52303 * 52304 * @param {Number} mediaIndex the index of segment we last appended 52305 * @param {Object} [playlist=this.playlist_] a media playlist object 52306 * @returns {Boolean} true if at end of stream, false otherwise. 52307 */ 52308 52309 }, { 52310 key: 'isEndOfStream_', 52311 value: function isEndOfStream_(mediaIndex) { 52312 var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_; 52313 return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating(); 52314 } 52315 /** 52316 * Determines what segment request should be made, given current playback 52317 * state. 52318 * 52319 * @param {TimeRanges} buffered - the state of the buffer 52320 * @param {Object} playlist - the playlist object to fetch segments from 52321 * @param {Number} mediaIndex - the previous mediaIndex fetched or null 52322 * @param {Boolean} hasPlayed - a flag indicating whether we have played or not 52323 * @param {Number} currentTime - the playback position in seconds 52324 * @param {Object} syncPoint - a segment info object that describes the 52325 * @returns {Object} a segment request object that describes the segment to load 52326 */ 52327 52328 }, { 52329 key: 'checkBuffer_', 52330 value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) { 52331 var lastBufferedEnd = 0; 52332 var startOfSegment = void 0; 52333 52334 if (buffered.length) { 52335 lastBufferedEnd = buffered.end(buffered.length - 1); 52336 } 52337 52338 var bufferedTime = Math.max(0, lastBufferedEnd - currentTime); 52339 52340 if (!playlist.segments.length) { 52341 return null; 52342 } // if there is plenty of content buffered, and the video has 52343 // been played before relax for awhile 52344 52345 52346 if (bufferedTime >= this.goalBufferLength_()) { 52347 return null; 52348 } // if the video has not yet played once, and we already have 52349 // one segment downloaded do nothing 52350 52351 52352 if (!hasPlayed && bufferedTime >= 1) { 52353 return null; 52354 } // When the syncPoint is null, there is no way of determining a good 52355 // conservative segment index to fetch from 52356 // The best thing to do here is to get the kind of sync-point data by 52357 // making a request 52358 52359 52360 if (syncPoint === null) { 52361 mediaIndex = this.getSyncSegmentCandidate_(playlist); 52362 return this.generateSegmentInfo_(playlist, mediaIndex, null, true); 52363 } // Under normal playback conditions fetching is a simple walk forward 52364 52365 52366 if (mediaIndex !== null) { 52367 var segment = playlist.segments[mediaIndex]; 52368 startOfSegment = lastBufferedEnd; 52369 return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false); 52370 } // There is a sync-point but the lack of a mediaIndex indicates that 52371 // we need to make a good conservative guess about which segment to 52372 // fetch 52373 52374 52375 if (this.fetchAtBuffer_) { 52376 // Find the segment containing the end of the buffer 52377 var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time); 52378 mediaIndex = mediaSourceInfo.mediaIndex; 52379 startOfSegment = mediaSourceInfo.startTime; 52380 } else { 52381 // Find the segment containing currentTime 52382 var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time); 52383 52384 mediaIndex = _mediaSourceInfo.mediaIndex; 52385 startOfSegment = _mediaSourceInfo.startTime; 52386 } 52387 52388 return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false); 52389 } 52390 /** 52391 * The segment loader has no recourse except to fetch a segment in the 52392 * current playlist and use the internal timestamps in that segment to 52393 * generate a syncPoint. This function returns a good candidate index 52394 * for that process. 52395 * 52396 * @param {Object} playlist - the playlist object to look for a 52397 * @returns {Number} An index of a segment from the playlist to load 52398 */ 52399 52400 }, { 52401 key: 'getSyncSegmentCandidate_', 52402 value: function getSyncSegmentCandidate_(playlist) { 52403 var _this2 = this; 52404 52405 if (this.currentTimeline_ === -1) { 52406 return 0; 52407 } 52408 52409 var segmentIndexArray = playlist.segments.map(function (s, i) { 52410 return { 52411 timeline: s.timeline, 52412 segmentIndex: i 52413 }; 52414 }).filter(function (s) { 52415 return s.timeline === _this2.currentTimeline_; 52416 }); 52417 52418 if (segmentIndexArray.length) { 52419 return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex; 52420 } 52421 52422 return Math.max(playlist.segments.length - 1, 0); 52423 } 52424 }, { 52425 key: 'generateSegmentInfo_', 52426 value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) { 52427 if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) { 52428 return null; 52429 } 52430 52431 var segment = playlist.segments[mediaIndex]; 52432 return { 52433 requestId: 'segment-loader-' + Math.random(), 52434 // resolve the segment URL relative to the playlist 52435 uri: segment.resolvedUri, 52436 // the segment's mediaIndex at the time it was requested 52437 mediaIndex: mediaIndex, 52438 // whether or not to update the SegmentLoader's state with this 52439 // segment's mediaIndex 52440 isSyncRequest: isSyncRequest, 52441 startOfSegment: startOfSegment, 52442 // the segment's playlist 52443 playlist: playlist, 52444 // unencrypted bytes of the segment 52445 bytes: null, 52446 // when a key is defined for this segment, the encrypted bytes 52447 encryptedBytes: null, 52448 // The target timestampOffset for this segment when we append it 52449 // to the source buffer 52450 timestampOffset: null, 52451 // The timeline that the segment is in 52452 timeline: segment.timeline, 52453 // The expected duration of the segment in seconds 52454 duration: segment.duration, 52455 // retain the segment in case the playlist updates while doing an async process 52456 segment: segment 52457 }; 52458 } 52459 /** 52460 * Determines if the network has enough bandwidth to complete the current segment 52461 * request in a timely manner. If not, the request will be aborted early and bandwidth 52462 * updated to trigger a playlist switch. 52463 * 52464 * @param {Object} stats 52465 * Object containing stats about the request timing and size 52466 * @return {Boolean} True if the request was aborted, false otherwise 52467 * @private 52468 */ 52469 52470 }, { 52471 key: 'abortRequestEarly_', 52472 value: function abortRequestEarly_(stats) { 52473 if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition 52474 // TODO: Replace using timeout with a boolean indicating whether this playlist is 52475 // the lowestEnabledRendition. 52476 !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes 52477 !this.playlist_.attributes.BANDWIDTH) { 52478 return false; 52479 } // Wait at least 1 second since the first byte of data has been received before 52480 // using the calculated bandwidth from the progress event to allow the bitrate 52481 // to stabilize 52482 52483 52484 if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) { 52485 return false; 52486 } 52487 52488 var currentTime = this.currentTime_(); 52489 var measuredBandwidth = stats.bandwidth; 52490 var segmentDuration = this.pendingSegment_.duration; 52491 var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort 52492 // if we are only left with less than 1 second when the request completes. 52493 // A negative timeUntilRebuffering indicates we are already rebuffering 52494 52495 var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download 52496 // is larger than the estimated time until the player runs out of forward buffer 52497 52498 if (requestTimeRemaining <= timeUntilRebuffer$$1) { 52499 return false; 52500 } 52501 52502 var switchCandidate = minRebufferMaxBandwidthSelector({ 52503 master: this.hls_.playlists.master, 52504 currentTime: currentTime, 52505 bandwidth: measuredBandwidth, 52506 duration: this.duration_(), 52507 segmentDuration: segmentDuration, 52508 timeUntilRebuffer: timeUntilRebuffer$$1, 52509 currentTimeline: this.currentTimeline_, 52510 syncController: this.syncController_ 52511 }); 52512 52513 if (!switchCandidate) { 52514 return; 52515 } 52516 52517 var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1; 52518 var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact; 52519 var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the 52520 // potential round trip time of the new request so that we are not too aggressive 52521 // with switching to a playlist that might save us a fraction of a second. 52522 52523 if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) { 52524 minimumTimeSaving = 1; 52525 } 52526 52527 if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) { 52528 return false; 52529 } // set the bandwidth to that of the desired playlist being sure to scale by 52530 // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it 52531 // don't trigger a bandwidthupdate as the bandwidth is artifial 52532 52533 52534 this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1; 52535 this.abort(); 52536 this.trigger('earlyabort'); 52537 return true; 52538 } 52539 /** 52540 * XHR `progress` event handler 52541 * 52542 * @param {Event} 52543 * The XHR `progress` event 52544 * @param {Object} simpleSegment 52545 * A simplified segment object copy 52546 * @private 52547 */ 52548 52549 }, { 52550 key: 'handleProgress_', 52551 value: function handleProgress_(event, simpleSegment) { 52552 if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) { 52553 return; 52554 } 52555 52556 this.trigger('progress'); 52557 } 52558 /** 52559 * load a specific segment from a request into the buffer 52560 * 52561 * @private 52562 */ 52563 52564 }, { 52565 key: 'loadSegment_', 52566 value: function loadSegment_(segmentInfo) { 52567 this.state = 'WAITING'; 52568 this.pendingSegment_ = segmentInfo; 52569 this.trimBackBuffer_(segmentInfo); 52570 segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback 52571 this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this)); 52572 } 52573 /** 52574 * trim the back buffer so that we don't have too much data 52575 * in the source buffer 52576 * 52577 * @private 52578 * 52579 * @param {Object} segmentInfo - the current segment 52580 */ 52581 52582 }, { 52583 key: 'trimBackBuffer_', 52584 value: function trimBackBuffer_(segmentInfo) { 52585 var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of 52586 // buffer and a very conservative "garbage collector" 52587 // We manually clear out the old buffer to ensure 52588 // we don't trigger the QuotaExceeded error 52589 // on the source buffer during subsequent appends 52590 52591 if (removeToTime > 0) { 52592 this.remove(0, removeToTime); 52593 } 52594 } 52595 /** 52596 * created a simplified copy of the segment object with just the 52597 * information necessary to perform the XHR and decryption 52598 * 52599 * @private 52600 * 52601 * @param {Object} segmentInfo - the current segment 52602 * @returns {Object} a simplified segment object copy 52603 */ 52604 52605 }, { 52606 key: 'createSimplifiedSegmentObj_', 52607 value: function createSimplifiedSegmentObj_(segmentInfo) { 52608 var segment = segmentInfo.segment; 52609 var simpleSegment = { 52610 resolvedUri: segment.resolvedUri, 52611 byterange: segment.byterange, 52612 requestId: segmentInfo.requestId 52613 }; 52614 52615 if (segment.key) { 52616 // if the media sequence is greater than 2^32, the IV will be incorrect 52617 // assuming 10s segments, that would be about 1300 years 52618 var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 52619 simpleSegment.key = this.segmentKey(segment.key); 52620 simpleSegment.key.iv = iv; 52621 } 52622 52623 if (segment.map) { 52624 simpleSegment.map = this.initSegment(segment.map); 52625 } 52626 52627 return simpleSegment; 52628 } 52629 /** 52630 * Handle the callback from the segmentRequest function and set the 52631 * associated SegmentLoader state and errors if necessary 52632 * 52633 * @private 52634 */ 52635 52636 }, { 52637 key: 'segmentRequestFinished_', 52638 value: function segmentRequestFinished_(error, simpleSegment) { 52639 // every request counts as a media request even if it has been aborted 52640 // or canceled due to a timeout 52641 this.mediaRequests += 1; 52642 52643 if (simpleSegment.stats) { 52644 this.mediaBytesTransferred += simpleSegment.stats.bytesReceived; 52645 this.mediaTransferDuration += simpleSegment.stats.roundTripTime; 52646 } // The request was aborted and the SegmentLoader has already been reset 52647 52648 52649 if (!this.pendingSegment_) { 52650 this.mediaRequestsAborted += 1; 52651 return; 52652 } // the request was aborted and the SegmentLoader has already started 52653 // another request. this can happen when the timeout for an aborted 52654 // request triggers due to a limitation in the XHR library 52655 // do not count this as any sort of request or we risk double-counting 52656 52657 52658 if (simpleSegment.requestId !== this.pendingSegment_.requestId) { 52659 return; 52660 } // an error occurred from the active pendingSegment_ so reset everything 52661 52662 52663 if (error) { 52664 this.pendingSegment_ = null; 52665 this.state = 'READY'; // the requests were aborted just record the aborted stat and exit 52666 // this is not a true error condition and nothing corrective needs 52667 // to be done 52668 52669 if (error.code === REQUEST_ERRORS.ABORTED) { 52670 this.mediaRequestsAborted += 1; 52671 return; 52672 } 52673 52674 this.pause(); // the error is really just that at least one of the requests timed-out 52675 // set the bandwidth to a very low value and trigger an ABR switch to 52676 // take emergency action 52677 52678 if (error.code === REQUEST_ERRORS.TIMEOUT) { 52679 this.mediaRequestsTimedout += 1; 52680 this.bandwidth = 1; 52681 this.roundTrip = NaN; 52682 this.trigger('bandwidthupdate'); 52683 return; 52684 } // if control-flow has arrived here, then the error is real 52685 // emit an error event to blacklist the current playlist 52686 52687 52688 this.mediaRequestsErrored += 1; 52689 this.error(error); 52690 this.trigger('error'); 52691 return; 52692 } // the response was a success so set any bandwidth stats the request 52693 // generated for ABR purposes 52694 52695 52696 this.bandwidth = simpleSegment.stats.bandwidth; 52697 this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data 52698 // to the initSegment cache 52699 52700 if (simpleSegment.map) { 52701 simpleSegment.map = this.initSegment(simpleSegment.map, true); 52702 } // if this request included a segment key, save that data in the cache 52703 52704 52705 if (simpleSegment.key) { 52706 this.segmentKey(simpleSegment.key, true); 52707 } 52708 52709 this.processSegmentResponse_(simpleSegment); 52710 } 52711 /** 52712 * Move any important data from the simplified segment object 52713 * back to the real segment object for future phases 52714 * 52715 * @private 52716 */ 52717 52718 }, { 52719 key: 'processSegmentResponse_', 52720 value: function processSegmentResponse_(simpleSegment) { 52721 var segmentInfo = this.pendingSegment_; 52722 segmentInfo.bytes = simpleSegment.bytes; 52723 52724 if (simpleSegment.map) { 52725 segmentInfo.segment.map.bytes = simpleSegment.map.bytes; 52726 } 52727 52728 segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks 52729 52730 if (simpleSegment.fmp4Captions) { 52731 createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams); 52732 addCaptionData({ 52733 inbandTextTracks: this.inbandTextTracks_, 52734 captionArray: simpleSegment.fmp4Captions, 52735 // fmp4s will not have a timestamp offset 52736 timestampOffset: 0 52737 }); // Reset stored captions since we added parsed 52738 // captions to a text track at this point 52739 52740 if (this.captionParser_) { 52741 this.captionParser_.clearParsedCaptions(); 52742 } 52743 } 52744 52745 this.handleSegment_(); 52746 } 52747 /** 52748 * append a decrypted segement to the SourceBuffer through a SourceUpdater 52749 * 52750 * @private 52751 */ 52752 52753 }, { 52754 key: 'handleSegment_', 52755 value: function handleSegment_() { 52756 var _this3 = this; 52757 52758 if (!this.pendingSegment_) { 52759 this.state = 'READY'; 52760 return; 52761 } 52762 52763 var segmentInfo = this.pendingSegment_; 52764 var segment = segmentInfo.segment; 52765 var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is 52766 // dealing with. Although we're maintaining extra state, it helps to preserve the 52767 // separation of segment loader from the actual source buffers. 52768 52769 if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are 52770 // certain that all streams will provide it. 52771 timingInfo.containsAudio || timingInfo.containsVideo)) { 52772 this.startingMedia_ = { 52773 containsAudio: timingInfo.containsAudio, 52774 containsVideo: timingInfo.containsVideo 52775 }; 52776 } 52777 52778 var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo); 52779 52780 if (illegalMediaSwitchError) { 52781 this.error({ 52782 message: illegalMediaSwitchError, 52783 blacklistDuration: Infinity 52784 }); 52785 this.trigger('error'); 52786 return; 52787 } 52788 52789 if (segmentInfo.isSyncRequest) { 52790 this.trigger('syncinfoupdate'); 52791 this.pendingSegment_ = null; 52792 this.state = 'READY'; 52793 return; 52794 } 52795 52796 if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { 52797 // Subtract any difference between the PTS and DTS times of the first frame 52798 // from the timeStampOffset (which currently equals the buffered.end) to prevent 52799 // creating any gaps in the buffer 52800 if (timingInfo && timingInfo.segmentTimestampInfo) { 52801 var ptsStartTime = timingInfo.segmentTimestampInfo[0].ptsTime; 52802 var dtsStartTime = timingInfo.segmentTimestampInfo[0].dtsTime; 52803 segmentInfo.timestampOffset -= ptsStartTime - dtsStartTime; 52804 } 52805 52806 this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities) 52807 52808 this.trigger('timestampoffset'); 52809 } 52810 52811 var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline); 52812 52813 if (timelineMapping !== null) { 52814 this.trigger({ 52815 type: 'segmenttimemapping', 52816 mapping: timelineMapping 52817 }); 52818 } 52819 52820 this.state = 'APPENDING'; // if the media initialization segment is changing, append it 52821 // before the content segment 52822 52823 if (segment.map) { 52824 var initId = initSegmentId(segment.map); 52825 52826 if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) { 52827 var initSegment = this.initSegment(segment.map); 52828 this.sourceUpdater_.appendBuffer({ 52829 bytes: initSegment.bytes 52830 }, function () { 52831 _this3.activeInitSegmentId_ = initId; 52832 }); 52833 } 52834 } 52835 52836 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 52837 52838 if (typeof segment.start === 'number' && typeof segment.end === 'number') { 52839 this.mediaSecondsLoaded += segment.end - segment.start; 52840 } else { 52841 this.mediaSecondsLoaded += segment.duration; 52842 } 52843 52844 this.logger_(segmentInfoString(segmentInfo)); 52845 this.sourceUpdater_.appendBuffer({ 52846 bytes: segmentInfo.bytes, 52847 videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId) 52848 }, this.handleUpdateEnd_.bind(this)); 52849 } 52850 }, { 52851 key: 'handleVideoSegmentTimingInfo_', 52852 value: function handleVideoSegmentTimingInfo_(requestId, event) { 52853 if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) { 52854 return; 52855 } 52856 52857 var segment = this.pendingSegment_.segment; 52858 52859 if (!segment.videoTimingInfo) { 52860 segment.videoTimingInfo = {}; 52861 } 52862 52863 segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0; 52864 segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation; 52865 segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging 52866 52867 segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime; 52868 } 52869 /** 52870 * callback to run when appendBuffer is finished. detects if we are 52871 * in a good state to do things with the data we got, or if we need 52872 * to wait for more 52873 * 52874 * @private 52875 */ 52876 52877 }, { 52878 key: 'handleUpdateEnd_', 52879 value: function handleUpdateEnd_() { 52880 if (!this.pendingSegment_) { 52881 this.state = 'READY'; 52882 52883 if (!this.paused()) { 52884 this.monitorBuffer_(); 52885 } 52886 52887 return; 52888 } 52889 52890 var segmentInfo = this.pendingSegment_; 52891 var segment = segmentInfo.segment; 52892 var isWalkingForward = this.mediaIndex !== null; 52893 this.pendingSegment_ = null; 52894 this.recordThroughput_(segmentInfo); 52895 this.addSegmentMetadataCue_(segmentInfo); 52896 this.state = 'READY'; 52897 this.mediaIndex = segmentInfo.mediaIndex; 52898 this.fetchAtBuffer_ = true; 52899 this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before 52900 // the following conditional otherwise it may consider this a bad "guess" 52901 // and attempt to resync when the post-update seekable window and live 52902 // point would mean that this was the perfect segment to fetch 52903 52904 this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before 52905 // the currentTime_ that means that our conservative guess was too conservative. 52906 // In that case, reset the loader state so that we try to use any information gained 52907 // from the previous request to create a new, more accurate, sync-point. 52908 52909 if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) { 52910 this.resetEverything(); 52911 return; 52912 } // Don't do a rendition switch unless we have enough time to get a sync segment 52913 // and conservatively guess 52914 52915 52916 if (isWalkingForward) { 52917 this.trigger('bandwidthupdate'); 52918 } 52919 52920 this.trigger('progress'); // any time an update finishes and the last segment is in the 52921 // buffer, end the stream. this ensures the "ended" event will 52922 // fire if playback reaches that point. 52923 52924 if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) { 52925 this.endOfStream(); 52926 } 52927 52928 if (!this.paused()) { 52929 this.monitorBuffer_(); 52930 } 52931 } 52932 /** 52933 * Records the current throughput of the decrypt, transmux, and append 52934 * portion of the semgment pipeline. `throughput.rate` is a the cumulative 52935 * moving average of the throughput. `throughput.count` is the number of 52936 * data points in the average. 52937 * 52938 * @private 52939 * @param {Object} segmentInfo the object returned by loadSegment 52940 */ 52941 52942 }, { 52943 key: 'recordThroughput_', 52944 value: function recordThroughput_(segmentInfo) { 52945 var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide 52946 // by zero in the case where the throughput is ridiculously high 52947 52948 var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second 52949 52950 var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation: 52951 // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1) 52952 52953 this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count; 52954 } 52955 /** 52956 * Adds a cue to the segment-metadata track with some metadata information about the 52957 * segment 52958 * 52959 * @private 52960 * @param {Object} segmentInfo 52961 * the object returned by loadSegment 52962 * @method addSegmentMetadataCue_ 52963 */ 52964 52965 }, { 52966 key: 'addSegmentMetadataCue_', 52967 value: function addSegmentMetadataCue_(segmentInfo) { 52968 if (!this.segmentMetadataTrack_) { 52969 return; 52970 } 52971 52972 var segment = segmentInfo.segment; 52973 var start = segment.start; 52974 var end = segment.end; // Do not try adding the cue if the start and end times are invalid. 52975 52976 if (!finite(start) || !finite(end)) { 52977 return; 52978 } 52979 52980 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 52981 var Cue = window$3.WebKitDataCue || window$3.VTTCue; 52982 var value = { 52983 custom: segment.custom, 52984 dateTimeObject: segment.dateTimeObject, 52985 dateTimeString: segment.dateTimeString, 52986 bandwidth: segmentInfo.playlist.attributes.BANDWIDTH, 52987 resolution: segmentInfo.playlist.attributes.RESOLUTION, 52988 codecs: segmentInfo.playlist.attributes.CODECS, 52989 byteLength: segmentInfo.byteLength, 52990 uri: segmentInfo.uri, 52991 timeline: segmentInfo.timeline, 52992 playlist: segmentInfo.playlist.id, 52993 start: start, 52994 end: end 52995 }; 52996 var data = JSON.stringify(value); 52997 var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between 52998 // the differences of WebKitDataCue in safari and VTTCue in other browsers 52999 53000 cue.value = value; 53001 this.segmentMetadataTrack_.addCue(cue); 53002 } 53003 }]); 53004 return SegmentLoader; 53005 }(videojs$1.EventTarget); 53006 53007 var uint8ToUtf8 = function uint8ToUtf8(uintArray) { 53008 return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray))); 53009 }; 53010 /** 53011 * @file vtt-segment-loader.js 53012 */ 53013 53014 53015 var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char2) { 53016 return _char2.charCodeAt(0); 53017 })); 53018 /** 53019 * An object that manages segment loading and appending. 53020 * 53021 * @class VTTSegmentLoader 53022 * @param {Object} options required and optional options 53023 * @extends videojs.EventTarget 53024 */ 53025 53026 var VTTSegmentLoader = function (_SegmentLoader) { 53027 inherits$2(VTTSegmentLoader, _SegmentLoader); 53028 53029 function VTTSegmentLoader(settings) { 53030 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 53031 classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error; 53032 // however, VTTSegmentLoader has no need of a media source, so delete the reference 53033 53034 var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options)); 53035 53036 _this.mediaSource_ = null; 53037 _this.subtitlesTrack_ = null; 53038 _this.featuresNativeTextTracks_ = settings.featuresNativeTextTracks; 53039 return _this; 53040 } 53041 /** 53042 * Indicates which time ranges are buffered 53043 * 53044 * @return {TimeRange} 53045 * TimeRange object representing the current buffered ranges 53046 */ 53047 53048 53049 createClass$1(VTTSegmentLoader, [{ 53050 key: 'buffered_', 53051 value: function buffered_() { 53052 if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) { 53053 return videojs$1.createTimeRanges(); 53054 } 53055 53056 var cues = this.subtitlesTrack_.cues; 53057 var start = cues[0].startTime; 53058 var end = cues[cues.length - 1].startTime; 53059 return videojs$1.createTimeRanges([[start, end]]); 53060 } 53061 /** 53062 * Gets and sets init segment for the provided map 53063 * 53064 * @param {Object} map 53065 * The map object representing the init segment to get or set 53066 * @param {Boolean=} set 53067 * If true, the init segment for the provided map should be saved 53068 * @return {Object} 53069 * map object for desired init segment 53070 */ 53071 53072 }, { 53073 key: 'initSegment', 53074 value: function initSegment(map) { 53075 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 53076 53077 if (!map) { 53078 return null; 53079 } 53080 53081 var id = initSegmentId(map); 53082 var storedMap = this.initSegments_[id]; 53083 53084 if (set$$1 && !storedMap && map.bytes) { 53085 // append WebVTT line terminators to the media initialization segment if it exists 53086 // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that 53087 // requires two or more WebVTT line terminators between the WebVTT header and the 53088 // rest of the file 53089 var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength; 53090 var combinedSegment = new Uint8Array(combinedByteLength); 53091 combinedSegment.set(map.bytes); 53092 combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength); 53093 this.initSegments_[id] = storedMap = { 53094 resolvedUri: map.resolvedUri, 53095 byterange: map.byterange, 53096 bytes: combinedSegment 53097 }; 53098 } 53099 53100 return storedMap || map; 53101 } 53102 /** 53103 * Returns true if all configuration required for loading is present, otherwise false. 53104 * 53105 * @return {Boolean} True if the all configuration is ready for loading 53106 * @private 53107 */ 53108 53109 }, { 53110 key: 'couldBeginLoading_', 53111 value: function couldBeginLoading_() { 53112 return this.playlist_ && this.subtitlesTrack_ && !this.paused(); 53113 } 53114 /** 53115 * Once all the starting parameters have been specified, begin 53116 * operation. This method should only be invoked from the INIT 53117 * state. 53118 * 53119 * @private 53120 */ 53121 53122 }, { 53123 key: 'init_', 53124 value: function init_() { 53125 this.state = 'READY'; 53126 this.resetEverything(); 53127 return this.monitorBuffer_(); 53128 } 53129 /** 53130 * Set a subtitle track on the segment loader to add subtitles to 53131 * 53132 * @param {TextTrack=} track 53133 * The text track to add loaded subtitles to 53134 * @return {TextTrack} 53135 * Returns the subtitles track 53136 */ 53137 53138 }, { 53139 key: 'track', 53140 value: function track(_track) { 53141 if (typeof _track === 'undefined') { 53142 return this.subtitlesTrack_; 53143 } 53144 53145 this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start 53146 // buffering now 53147 53148 if (this.state === 'INIT' && this.couldBeginLoading_()) { 53149 this.init_(); 53150 } 53151 53152 return this.subtitlesTrack_; 53153 } 53154 /** 53155 * Remove any data in the source buffer between start and end times 53156 * @param {Number} start - the start time of the region to remove from the buffer 53157 * @param {Number} end - the end time of the region to remove from the buffer 53158 */ 53159 53160 }, { 53161 key: 'remove', 53162 value: function remove(start, end) { 53163 removeCuesFromTrack(start, end, this.subtitlesTrack_); 53164 } 53165 /** 53166 * fill the buffer with segements unless the sourceBuffers are 53167 * currently updating 53168 * 53169 * Note: this function should only ever be called by monitorBuffer_ 53170 * and never directly 53171 * 53172 * @private 53173 */ 53174 53175 }, { 53176 key: 'fillBuffer_', 53177 value: function fillBuffer_() { 53178 var _this2 = this; 53179 53180 if (!this.syncPoint_) { 53181 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 53182 } // see if we need to begin loading immediately 53183 53184 53185 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 53186 segmentInfo = this.skipEmptySegments_(segmentInfo); 53187 53188 if (!segmentInfo) { 53189 return; 53190 } 53191 53192 if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) { 53193 // We don't have the timestamp offset that we need to sync subtitles. 53194 // Rerun on a timestamp offset or user interaction. 53195 var checkTimestampOffset = function checkTimestampOffset() { 53196 _this2.state = 'READY'; 53197 53198 if (!_this2.paused()) { 53199 // if not paused, queue a buffer check as soon as possible 53200 _this2.monitorBuffer_(); 53201 } 53202 }; 53203 53204 this.syncController_.one('timestampoffset', checkTimestampOffset); 53205 this.state = 'WAITING_ON_TIMELINE'; 53206 return; 53207 } 53208 53209 this.loadSegment_(segmentInfo); 53210 } 53211 /** 53212 * Prevents the segment loader from requesting segments we know contain no subtitles 53213 * by walking forward until we find the next segment that we don't know whether it is 53214 * empty or not. 53215 * 53216 * @param {Object} segmentInfo 53217 * a segment info object that describes the current segment 53218 * @return {Object} 53219 * a segment info object that describes the current segment 53220 */ 53221 53222 }, { 53223 key: 'skipEmptySegments_', 53224 value: function skipEmptySegments_(segmentInfo) { 53225 while (segmentInfo && segmentInfo.segment.empty) { 53226 segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest); 53227 } 53228 53229 return segmentInfo; 53230 } 53231 /** 53232 * append a decrypted segement to the SourceBuffer through a SourceUpdater 53233 * 53234 * @private 53235 */ 53236 53237 }, { 53238 key: 'handleSegment_', 53239 value: function handleSegment_() { 53240 var _this3 = this; 53241 53242 if (!this.pendingSegment_ || !this.subtitlesTrack_) { 53243 this.state = 'READY'; 53244 return; 53245 } 53246 53247 this.state = 'APPENDING'; 53248 var segmentInfo = this.pendingSegment_; 53249 var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 53250 53251 if (typeof window$3.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) { 53252 var loadHandler = void 0; 53253 53254 var errorHandler = function errorHandler() { 53255 _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler); 53256 53257 _this3.error({ 53258 message: 'Error loading vtt.js' 53259 }); 53260 53261 _this3.state = 'READY'; 53262 53263 _this3.pause(); 53264 53265 _this3.trigger('error'); 53266 }; 53267 53268 loadHandler = function loadHandler() { 53269 _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler); 53270 53271 _this3.handleSegment_(); 53272 }; 53273 53274 this.state = 'WAITING_ON_VTTJS'; 53275 this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler); 53276 this.subtitlesTrack_.tech_.one('vttjserror', errorHandler); 53277 return; 53278 } 53279 53280 segment.requested = true; 53281 53282 try { 53283 this.parseVTTCues_(segmentInfo); 53284 } catch (e) { 53285 this.error({ 53286 message: e.message 53287 }); 53288 this.state = 'READY'; 53289 this.pause(); 53290 return this.trigger('error'); 53291 } 53292 53293 this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_); 53294 53295 if (segmentInfo.isSyncRequest) { 53296 this.trigger('syncinfoupdate'); 53297 this.pendingSegment_ = null; 53298 this.state = 'READY'; 53299 return; 53300 } 53301 53302 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 53303 this.mediaSecondsLoaded += segment.duration; 53304 53305 if (segmentInfo.cues.length) { 53306 // remove any overlapping cues to prevent doubling 53307 this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime); 53308 } 53309 53310 segmentInfo.cues.forEach(function (cue) { 53311 _this3.subtitlesTrack_.addCue(_this3.featuresNativeTextTracks_ ? new window$3.VTTCue(cue.startTime, cue.endTime, cue.text) : cue); 53312 }); 53313 this.handleUpdateEnd_(); 53314 } 53315 /** 53316 * Uses the WebVTT parser to parse the segment response 53317 * 53318 * @param {Object} segmentInfo 53319 * a segment info object that describes the current segment 53320 * @private 53321 */ 53322 53323 }, { 53324 key: 'parseVTTCues_', 53325 value: function parseVTTCues_(segmentInfo) { 53326 var decoder = void 0; 53327 var decodeBytesToString = false; 53328 53329 if (typeof window$3.TextDecoder === 'function') { 53330 decoder = new window$3.TextDecoder('utf8'); 53331 } else { 53332 decoder = window$3.WebVTT.StringDecoder(); 53333 decodeBytesToString = true; 53334 } 53335 53336 var parser = new window$3.WebVTT.Parser(window$3, window$3.vttjs, decoder); 53337 segmentInfo.cues = []; 53338 segmentInfo.timestampmap = { 53339 MPEGTS: 0, 53340 LOCAL: 0 53341 }; 53342 parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues); 53343 53344 parser.ontimestampmap = function (map) { 53345 return segmentInfo.timestampmap = map; 53346 }; 53347 53348 parser.onparsingerror = function (error) { 53349 videojs$1.log.warn('Error encountered when parsing cues: ' + error.message); 53350 }; 53351 53352 if (segmentInfo.segment.map) { 53353 var mapData = segmentInfo.segment.map.bytes; 53354 53355 if (decodeBytesToString) { 53356 mapData = uint8ToUtf8(mapData); 53357 } 53358 53359 parser.parse(mapData); 53360 } 53361 53362 var segmentData = segmentInfo.bytes; 53363 53364 if (decodeBytesToString) { 53365 segmentData = uint8ToUtf8(segmentData); 53366 } 53367 53368 parser.parse(segmentData); 53369 parser.flush(); 53370 } 53371 /** 53372 * Updates the start and end times of any cues parsed by the WebVTT parser using 53373 * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping 53374 * from the SyncController 53375 * 53376 * @param {Object} segmentInfo 53377 * a segment info object that describes the current segment 53378 * @param {Object} mappingObj 53379 * object containing a mapping from TS to media time 53380 * @param {Object} playlist 53381 * the playlist object containing the segment 53382 * @private 53383 */ 53384 53385 }, { 53386 key: 'updateTimeMapping_', 53387 value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) { 53388 var segment = segmentInfo.segment; 53389 53390 if (!mappingObj) { 53391 // If the sync controller does not have a mapping of TS to Media Time for the 53392 // timeline, then we don't have enough information to update the cue 53393 // start/end times 53394 return; 53395 } 53396 53397 if (!segmentInfo.cues.length) { 53398 // If there are no cues, we also do not have enough information to figure out 53399 // segment timing. Mark that the segment contains no cues so we don't re-request 53400 // an empty segment. 53401 segment.empty = true; 53402 return; 53403 } 53404 53405 var timestampmap = segmentInfo.timestampmap; 53406 var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping; 53407 segmentInfo.cues.forEach(function (cue) { 53408 // First convert cue time to TS time using the timestamp-map provided within the vtt 53409 cue.startTime += diff; 53410 cue.endTime += diff; 53411 }); 53412 53413 if (!playlist.syncInfo) { 53414 var firstStart = segmentInfo.cues[0].startTime; 53415 var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; 53416 playlist.syncInfo = { 53417 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 53418 time: Math.min(firstStart, lastStart - segment.duration) 53419 }; 53420 } 53421 } 53422 }]); 53423 return VTTSegmentLoader; 53424 }(SegmentLoader); 53425 /** 53426 * @file ad-cue-tags.js 53427 */ 53428 53429 /** 53430 * Searches for an ad cue that overlaps with the given mediaTime 53431 */ 53432 53433 53434 var findAdCue = function findAdCue(track, mediaTime) { 53435 var cues = track.cues; 53436 53437 for (var i = 0; i < cues.length; i++) { 53438 var cue = cues[i]; 53439 53440 if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) { 53441 return cue; 53442 } 53443 } 53444 53445 return null; 53446 }; 53447 53448 var updateAdCues = function updateAdCues(media, track) { 53449 var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; 53450 53451 if (!media.segments) { 53452 return; 53453 } 53454 53455 var mediaTime = offset; 53456 var cue = void 0; 53457 53458 for (var i = 0; i < media.segments.length; i++) { 53459 var segment = media.segments[i]; 53460 53461 if (!cue) { 53462 // Since the cues will span for at least the segment duration, adding a fudge 53463 // factor of half segment duration will prevent duplicate cues from being 53464 // created when timing info is not exact (e.g. cue start time initialized 53465 // at 10.006677, but next call mediaTime is 10.003332 ) 53466 cue = findAdCue(track, mediaTime + segment.duration / 2); 53467 } 53468 53469 if (cue) { 53470 if ('cueIn' in segment) { 53471 // Found a CUE-IN so end the cue 53472 cue.endTime = mediaTime; 53473 cue.adEndTime = mediaTime; 53474 mediaTime += segment.duration; 53475 cue = null; 53476 continue; 53477 } 53478 53479 if (mediaTime < cue.endTime) { 53480 // Already processed this mediaTime for this cue 53481 mediaTime += segment.duration; 53482 continue; 53483 } // otherwise extend cue until a CUE-IN is found 53484 53485 53486 cue.endTime += segment.duration; 53487 } else { 53488 if ('cueOut' in segment) { 53489 cue = new window$3.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut); 53490 cue.adStartTime = mediaTime; // Assumes tag format to be 53491 // #EXT-X-CUE-OUT:30 53492 53493 cue.adEndTime = mediaTime + parseFloat(segment.cueOut); 53494 track.addCue(cue); 53495 } 53496 53497 if ('cueOutCont' in segment) { 53498 // Entered into the middle of an ad cue 53499 var adOffset = void 0; 53500 var adTotal = void 0; // Assumes tag formate to be 53501 // #EXT-X-CUE-OUT-CONT:10/30 53502 53503 var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat); 53504 53505 var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2); 53506 53507 adOffset = _segment$cueOutCont$s2[0]; 53508 adTotal = _segment$cueOutCont$s2[1]; 53509 cue = new window$3.VTTCue(mediaTime, mediaTime + segment.duration, ''); 53510 cue.adStartTime = mediaTime - adOffset; 53511 cue.adEndTime = cue.adStartTime + adTotal; 53512 track.addCue(cue); 53513 } 53514 } 53515 53516 mediaTime += segment.duration; 53517 } 53518 }; 53519 /** 53520 * @file sync-controller.js 53521 */ 53522 53523 53524 var tsprobe = tsInspector.inspect; 53525 var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always* 53526 // the equivalence display-time 0 === segment-index 0 53527 { 53528 name: 'VOD', 53529 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 53530 if (duration$$1 !== Infinity) { 53531 var syncPoint = { 53532 time: 0, 53533 segmentIndex: 0 53534 }; 53535 return syncPoint; 53536 } 53537 53538 return null; 53539 } 53540 }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist 53541 { 53542 name: 'ProgramDateTime', 53543 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 53544 if (!syncController.datetimeToDisplayTime) { 53545 return null; 53546 } 53547 53548 var segments = playlist.segments || []; 53549 var syncPoint = null; 53550 var lastDistance = null; 53551 currentTime = currentTime || 0; 53552 53553 for (var i = 0; i < segments.length; i++) { 53554 var segment = segments[i]; 53555 53556 if (segment.dateTimeObject) { 53557 var segmentTime = segment.dateTimeObject.getTime() / 1000; 53558 var segmentStart = segmentTime + syncController.datetimeToDisplayTime; 53559 var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, or if distance is 0, we have passed 53560 // currentTime and can stop looking for better candidates 53561 53562 if (lastDistance !== null && (distance === 0 || lastDistance < distance)) { 53563 break; 53564 } 53565 53566 lastDistance = distance; 53567 syncPoint = { 53568 time: segmentStart, 53569 segmentIndex: i 53570 }; 53571 } 53572 } 53573 53574 return syncPoint; 53575 } 53576 }, // Stategy "Segment": We have a known time mapping for a timeline and a 53577 // segment in the current timeline with timing data 53578 { 53579 name: 'Segment', 53580 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 53581 var segments = playlist.segments || []; 53582 var syncPoint = null; 53583 var lastDistance = null; 53584 currentTime = currentTime || 0; 53585 53586 for (var i = 0; i < segments.length; i++) { 53587 var segment = segments[i]; 53588 53589 if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') { 53590 var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed 53591 // currentTime and can stop looking for better candidates 53592 53593 if (lastDistance !== null && lastDistance < distance) { 53594 break; 53595 } 53596 53597 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 53598 lastDistance = distance; 53599 syncPoint = { 53600 time: segment.start, 53601 segmentIndex: i 53602 }; 53603 } 53604 } 53605 } 53606 53607 return syncPoint; 53608 } 53609 }, // Stategy "Discontinuity": We have a discontinuity with a known 53610 // display-time 53611 { 53612 name: 'Discontinuity', 53613 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 53614 var syncPoint = null; 53615 currentTime = currentTime || 0; 53616 53617 if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 53618 var lastDistance = null; 53619 53620 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 53621 var segmentIndex = playlist.discontinuityStarts[i]; 53622 var discontinuity = playlist.discontinuitySequence + i + 1; 53623 var discontinuitySync = syncController.discontinuities[discontinuity]; 53624 53625 if (discontinuitySync) { 53626 var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed 53627 // currentTime and can stop looking for better candidates 53628 53629 if (lastDistance !== null && lastDistance < distance) { 53630 break; 53631 } 53632 53633 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 53634 lastDistance = distance; 53635 syncPoint = { 53636 time: discontinuitySync.time, 53637 segmentIndex: segmentIndex 53638 }; 53639 } 53640 } 53641 } 53642 } 53643 53644 return syncPoint; 53645 } 53646 }, // Stategy "Playlist": We have a playlist with a known mapping of 53647 // segment index to display time 53648 { 53649 name: 'Playlist', 53650 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 53651 if (playlist.syncInfo) { 53652 var syncPoint = { 53653 time: playlist.syncInfo.time, 53654 segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence 53655 }; 53656 return syncPoint; 53657 } 53658 53659 return null; 53660 } 53661 }]; 53662 53663 var SyncController = function (_videojs$EventTarget) { 53664 inherits$2(SyncController, _videojs$EventTarget); 53665 53666 function SyncController() { 53667 classCallCheck$1(this, SyncController); // Segment Loader state variables... 53668 // ...for synching across variants 53669 53670 var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this)); 53671 53672 _this.inspectCache_ = undefined; // ...for synching across variants 53673 53674 _this.timelines = []; 53675 _this.discontinuities = []; 53676 _this.datetimeToDisplayTime = null; 53677 _this.logger_ = logger('SyncController'); 53678 return _this; 53679 } 53680 /** 53681 * Find a sync-point for the playlist specified 53682 * 53683 * A sync-point is defined as a known mapping from display-time to 53684 * a segment-index in the current playlist. 53685 * 53686 * @param {Playlist} playlist 53687 * The playlist that needs a sync-point 53688 * @param {Number} duration 53689 * Duration of the MediaSource (Infinite if playing a live source) 53690 * @param {Number} currentTimeline 53691 * The last timeline from which a segment was loaded 53692 * @returns {Object} 53693 * A sync-point object 53694 */ 53695 53696 53697 createClass$1(SyncController, [{ 53698 key: 'getSyncPoint', 53699 value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) { 53700 var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime); 53701 53702 if (!syncPoints.length) { 53703 // Signal that we need to attempt to get a sync-point manually 53704 // by fetching a segment in the playlist and constructing 53705 // a sync-point from that information 53706 return null; 53707 } // Now find the sync-point that is closest to the currentTime because 53708 // that should result in the most accurate guess about which segment 53709 // to fetch 53710 53711 53712 return this.selectSyncPoint_(syncPoints, { 53713 key: 'time', 53714 value: currentTime 53715 }); 53716 } 53717 /** 53718 * Calculate the amount of time that has expired off the playlist during playback 53719 * 53720 * @param {Playlist} playlist 53721 * Playlist object to calculate expired from 53722 * @param {Number} duration 53723 * Duration of the MediaSource (Infinity if playling a live source) 53724 * @returns {Number|null} 53725 * The amount of time that has expired off the playlist during playback. Null 53726 * if no sync-points for the playlist can be found. 53727 */ 53728 53729 }, { 53730 key: 'getExpiredTime', 53731 value: function getExpiredTime(playlist, duration$$1) { 53732 if (!playlist || !playlist.segments) { 53733 return null; 53734 } 53735 53736 var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time 53737 53738 if (!syncPoints.length) { 53739 return null; 53740 } 53741 53742 var syncPoint = this.selectSyncPoint_(syncPoints, { 53743 key: 'segmentIndex', 53744 value: 0 53745 }); // If the sync-point is beyond the start of the playlist, we want to subtract the 53746 // duration from index 0 to syncPoint.segmentIndex instead of adding. 53747 53748 if (syncPoint.segmentIndex > 0) { 53749 syncPoint.time *= -1; 53750 } 53751 53752 return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0)); 53753 } 53754 /** 53755 * Runs each sync-point strategy and returns a list of sync-points returned by the 53756 * strategies 53757 * 53758 * @private 53759 * @param {Playlist} playlist 53760 * The playlist that needs a sync-point 53761 * @param {Number} duration 53762 * Duration of the MediaSource (Infinity if playing a live source) 53763 * @param {Number} currentTimeline 53764 * The last timeline from which a segment was loaded 53765 * @returns {Array} 53766 * A list of sync-point objects 53767 */ 53768 53769 }, { 53770 key: 'runStrategies_', 53771 value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) { 53772 var syncPoints = []; // Try to find a sync-point in by utilizing various strategies... 53773 53774 for (var i = 0; i < syncPointStrategies.length; i++) { 53775 var strategy = syncPointStrategies[i]; 53776 var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime); 53777 53778 if (syncPoint) { 53779 syncPoint.strategy = strategy.name; 53780 syncPoints.push({ 53781 strategy: strategy.name, 53782 syncPoint: syncPoint 53783 }); 53784 } 53785 } 53786 53787 return syncPoints; 53788 } 53789 /** 53790 * Selects the sync-point nearest the specified target 53791 * 53792 * @private 53793 * @param {Array} syncPoints 53794 * List of sync-points to select from 53795 * @param {Object} target 53796 * Object specifying the property and value we are targeting 53797 * @param {String} target.key 53798 * Specifies the property to target. Must be either 'time' or 'segmentIndex' 53799 * @param {Number} target.value 53800 * The value to target for the specified key. 53801 * @returns {Object} 53802 * The sync-point nearest the target 53803 */ 53804 53805 }, { 53806 key: 'selectSyncPoint_', 53807 value: function selectSyncPoint_(syncPoints, target) { 53808 var bestSyncPoint = syncPoints[0].syncPoint; 53809 var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value); 53810 var bestStrategy = syncPoints[0].strategy; 53811 53812 for (var i = 1; i < syncPoints.length; i++) { 53813 var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value); 53814 53815 if (newDistance < bestDistance) { 53816 bestDistance = newDistance; 53817 bestSyncPoint = syncPoints[i].syncPoint; 53818 bestStrategy = syncPoints[i].strategy; 53819 } 53820 } 53821 53822 this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']')); 53823 return bestSyncPoint; 53824 } 53825 /** 53826 * Save any meta-data present on the segments when segments leave 53827 * the live window to the playlist to allow for synchronization at the 53828 * playlist level later. 53829 * 53830 * @param {Playlist} oldPlaylist - The previous active playlist 53831 * @param {Playlist} newPlaylist - The updated and most current playlist 53832 */ 53833 53834 }, { 53835 key: 'saveExpiredSegmentInfo', 53836 value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) { 53837 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time 53838 // save that information as a possible sync-point reference in future 53839 53840 for (var i = mediaSequenceDiff - 1; i >= 0; i--) { 53841 var lastRemovedSegment = oldPlaylist.segments[i]; 53842 53843 if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') { 53844 newPlaylist.syncInfo = { 53845 mediaSequence: oldPlaylist.mediaSequence + i, 53846 time: lastRemovedSegment.start 53847 }; 53848 this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']')); 53849 this.trigger('syncinfoupdate'); 53850 break; 53851 } 53852 } 53853 } 53854 /** 53855 * Save the mapping from playlist's ProgramDateTime to display. This should 53856 * only ever happen once at the start of playback. 53857 * 53858 * @param {Playlist} playlist - The currently active playlist 53859 */ 53860 53861 }, { 53862 key: 'setDateTimeMapping', 53863 value: function setDateTimeMapping(playlist) { 53864 if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) { 53865 var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000; 53866 this.datetimeToDisplayTime = -playlistTimestamp; 53867 } 53868 } 53869 /** 53870 * Reset the state of the inspection cache when we do a rendition 53871 * switch 53872 */ 53873 53874 }, { 53875 key: 'reset', 53876 value: function reset() { 53877 this.inspectCache_ = undefined; 53878 } 53879 /** 53880 * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start 53881 * and end of the segment in it's internal "media time". Used to generate 53882 * mappings from that internal "media time" to the display time that is 53883 * shown on the player. 53884 * 53885 * @param {SegmentInfo} segmentInfo - The current active request information 53886 */ 53887 53888 }, { 53889 key: 'probeSegmentInfo', 53890 value: function probeSegmentInfo(segmentInfo) { 53891 var segment = segmentInfo.segment; 53892 var playlist = segmentInfo.playlist; 53893 var timingInfo = void 0; 53894 53895 if (segment.map) { 53896 timingInfo = this.probeMp4Segment_(segmentInfo); 53897 } else { 53898 timingInfo = this.probeTsSegment_(segmentInfo); 53899 } 53900 53901 if (timingInfo) { 53902 if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) { 53903 this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information 53904 // now with segment timing information 53905 53906 if (!playlist.syncInfo) { 53907 playlist.syncInfo = { 53908 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 53909 time: segment.start 53910 }; 53911 } 53912 } 53913 } 53914 53915 return timingInfo; 53916 } 53917 /** 53918 * Probe an fmp4 segment to determine the start of the segment 53919 * in it's internal "composition time", which is equal to the base 53920 * media decode time plus the composition time offset value 53921 * 53922 * @private 53923 * @param {SegmentInfo} segmentInfo - The current active request information 53924 * @return {object} The start and end time of the current segment in "composition time" 53925 */ 53926 53927 }, { 53928 key: 'probeMp4Segment_', 53929 value: function probeMp4Segment_(segmentInfo) { 53930 var segment = segmentInfo.segment; // get timescales from init segment 53931 53932 var timescales = probe.timescale(segment.map.bytes); // calculate composition start time using the timescales and information 53933 // contained within the media segment 53934 53935 var compositionStartTime = probe.compositionStartTime(timescales, segmentInfo.bytes); 53936 53937 if (segmentInfo.timestampOffset !== null) { 53938 segmentInfo.timestampOffset -= compositionStartTime; 53939 } 53940 53941 return { 53942 start: compositionStartTime, 53943 end: compositionStartTime + segment.duration 53944 }; 53945 } 53946 /** 53947 * Probe an mpeg2-ts segment to determine the start and end of the segment 53948 * in it's internal "media time". 53949 * 53950 * @private 53951 * @param {SegmentInfo} segmentInfo - The current active request information 53952 * @return {object} The start and end time of the current segment in "media time" 53953 */ 53954 53955 }, { 53956 key: 'probeTsSegment_', 53957 value: function probeTsSegment_(segmentInfo) { 53958 var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_); 53959 var segmentStartTime = void 0; 53960 var segmentEndTime = void 0; 53961 var segmentTimestampInfo = void 0; 53962 53963 if (!timeInfo) { 53964 return null; 53965 } 53966 53967 if (timeInfo.video && timeInfo.video.length === 2) { 53968 this.inspectCache_ = timeInfo.video[1].dts; 53969 segmentStartTime = timeInfo.video[0].dtsTime; 53970 segmentEndTime = timeInfo.video[1].dtsTime; 53971 segmentTimestampInfo = timeInfo.video; 53972 } else if (timeInfo.audio && timeInfo.audio.length === 2) { 53973 this.inspectCache_ = timeInfo.audio[1].dts; 53974 segmentStartTime = timeInfo.audio[0].dtsTime; 53975 segmentEndTime = timeInfo.audio[1].dtsTime; 53976 segmentTimestampInfo = timeInfo.audio; 53977 } 53978 53979 var probedInfo = { 53980 segmentTimestampInfo: segmentTimestampInfo, 53981 start: segmentStartTime, 53982 end: segmentEndTime, 53983 containsVideo: timeInfo.video && timeInfo.video.length === 2, 53984 containsAudio: timeInfo.audio && timeInfo.audio.length === 2 53985 }; 53986 return probedInfo; 53987 } 53988 }, { 53989 key: 'timestampOffsetForTimeline', 53990 value: function timestampOffsetForTimeline(timeline) { 53991 if (typeof this.timelines[timeline] === 'undefined') { 53992 return null; 53993 } 53994 53995 return this.timelines[timeline].time; 53996 } 53997 }, { 53998 key: 'mappingForTimeline', 53999 value: function mappingForTimeline(timeline) { 54000 if (typeof this.timelines[timeline] === 'undefined') { 54001 return null; 54002 } 54003 54004 return this.timelines[timeline].mapping; 54005 } 54006 /** 54007 * Use the "media time" for a segment to generate a mapping to "display time" and 54008 * save that display time to the segment. 54009 * 54010 * @private 54011 * @param {SegmentInfo} segmentInfo 54012 * The current active request information 54013 * @param {object} timingInfo 54014 * The start and end time of the current segment in "media time" 54015 * @returns {Boolean} 54016 * Returns false if segment time mapping could not be calculated 54017 */ 54018 54019 }, { 54020 key: 'calculateSegmentTimeMapping_', 54021 value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) { 54022 var segment = segmentInfo.segment; 54023 var mappingObj = this.timelines[segmentInfo.timeline]; 54024 54025 if (segmentInfo.timestampOffset !== null) { 54026 mappingObj = { 54027 time: segmentInfo.startOfSegment, 54028 mapping: segmentInfo.startOfSegment - timingInfo.start 54029 }; 54030 this.timelines[segmentInfo.timeline] = mappingObj; 54031 this.trigger('timestampoffset'); 54032 this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']')); 54033 segment.start = segmentInfo.startOfSegment; 54034 segment.end = timingInfo.end + mappingObj.mapping; 54035 } else if (mappingObj) { 54036 segment.start = timingInfo.start + mappingObj.mapping; 54037 segment.end = timingInfo.end + mappingObj.mapping; 54038 } else { 54039 return false; 54040 } 54041 54042 return true; 54043 } 54044 /** 54045 * Each time we have discontinuity in the playlist, attempt to calculate the location 54046 * in display of the start of the discontinuity and save that. We also save an accuracy 54047 * value so that we save values with the most accuracy (closest to 0.) 54048 * 54049 * @private 54050 * @param {SegmentInfo} segmentInfo - The current active request information 54051 */ 54052 54053 }, { 54054 key: 'saveDiscontinuitySyncInfo_', 54055 value: function saveDiscontinuitySyncInfo_(segmentInfo) { 54056 var playlist = segmentInfo.playlist; 54057 var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where 54058 // the start of the range and it's accuracy is 0 (greater accuracy values 54059 // mean more approximation) 54060 54061 if (segment.discontinuity) { 54062 this.discontinuities[segment.timeline] = { 54063 time: segment.start, 54064 accuracy: 0 54065 }; 54066 } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 54067 // Search for future discontinuities that we can provide better timing 54068 // information for and save that information for sync purposes 54069 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 54070 var segmentIndex = playlist.discontinuityStarts[i]; 54071 var discontinuity = playlist.discontinuitySequence + i + 1; 54072 var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex; 54073 var accuracy = Math.abs(mediaIndexDiff); 54074 54075 if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) { 54076 var time = void 0; 54077 54078 if (mediaIndexDiff < 0) { 54079 time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex); 54080 } else { 54081 time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex); 54082 } 54083 54084 this.discontinuities[discontinuity] = { 54085 time: time, 54086 accuracy: accuracy 54087 }; 54088 } 54089 } 54090 } 54091 } 54092 }, { 54093 key: 'dispose', 54094 value: function dispose() { 54095 this.trigger('dispose'); 54096 this.off(); 54097 } 54098 }]); 54099 return SyncController; 54100 }(videojs$1.EventTarget); 54101 54102 var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) { 54103 var self = this; 54104 54105 var decrypterWorker = function () { 54106 /* 54107 * pkcs7.pad 54108 * https://github.com/brightcove/pkcs7 54109 * 54110 * Copyright (c) 2014 Brightcove 54111 * Licensed under the apache2 license. 54112 */ 54113 54114 /** 54115 * Returns the subarray of a Uint8Array without PKCS#7 padding. 54116 * @param padded {Uint8Array} unencrypted bytes that have been padded 54117 * @return {Uint8Array} the unpadded bytes 54118 * @see http://tools.ietf.org/html/rfc5652 54119 */ 54120 function unpad(padded) { 54121 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 54122 } 54123 54124 var classCallCheck = function classCallCheck(instance, Constructor) { 54125 if (!(instance instanceof Constructor)) { 54126 throw new TypeError("Cannot call a class as a function"); 54127 } 54128 }; 54129 54130 var createClass = function () { 54131 function defineProperties(target, props) { 54132 for (var i = 0; i < props.length; i++) { 54133 var descriptor = props[i]; 54134 descriptor.enumerable = descriptor.enumerable || false; 54135 descriptor.configurable = true; 54136 if ("value" in descriptor) descriptor.writable = true; 54137 Object.defineProperty(target, descriptor.key, descriptor); 54138 } 54139 } 54140 54141 return function (Constructor, protoProps, staticProps) { 54142 if (protoProps) defineProperties(Constructor.prototype, protoProps); 54143 if (staticProps) defineProperties(Constructor, staticProps); 54144 return Constructor; 54145 }; 54146 }(); 54147 54148 var inherits = function inherits(subClass, superClass) { 54149 if (typeof superClass !== "function" && superClass !== null) { 54150 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 54151 } 54152 54153 subClass.prototype = Object.create(superClass && superClass.prototype, { 54154 constructor: { 54155 value: subClass, 54156 enumerable: false, 54157 writable: true, 54158 configurable: true 54159 } 54160 }); 54161 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 54162 }; 54163 54164 var possibleConstructorReturn = function possibleConstructorReturn(self, call) { 54165 if (!self) { 54166 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 54167 } 54168 54169 return call && (typeof call === "object" || typeof call === "function") ? call : self; 54170 }; 54171 /** 54172 * @file aes.js 54173 * 54174 * This file contains an adaptation of the AES decryption algorithm 54175 * from the Standford Javascript Cryptography Library. That work is 54176 * covered by the following copyright and permissions notice: 54177 * 54178 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 54179 * All rights reserved. 54180 * 54181 * Redistribution and use in source and binary forms, with or without 54182 * modification, are permitted provided that the following conditions are 54183 * met: 54184 * 54185 * 1. Redistributions of source code must retain the above copyright 54186 * notice, this list of conditions and the following disclaimer. 54187 * 54188 * 2. Redistributions in binary form must reproduce the above 54189 * copyright notice, this list of conditions and the following 54190 * disclaimer in the documentation and/or other materials provided 54191 * with the distribution. 54192 * 54193 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 54194 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 54195 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 54196 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 54197 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 54198 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 54199 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 54200 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 54201 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 54202 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 54203 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54204 * 54205 * The views and conclusions contained in the software and documentation 54206 * are those of the authors and should not be interpreted as representing 54207 * official policies, either expressed or implied, of the authors. 54208 */ 54209 54210 /** 54211 * Expand the S-box tables. 54212 * 54213 * @private 54214 */ 54215 54216 54217 var precompute = function precompute() { 54218 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 54219 var encTable = tables[0]; 54220 var decTable = tables[1]; 54221 var sbox = encTable[4]; 54222 var sboxInv = decTable[4]; 54223 var i = void 0; 54224 var x = void 0; 54225 var xInv = void 0; 54226 var d = []; 54227 var th = []; 54228 var x2 = void 0; 54229 var x4 = void 0; 54230 var x8 = void 0; 54231 var s = void 0; 54232 var tEnc = void 0; 54233 var tDec = void 0; // Compute double and third tables 54234 54235 for (i = 0; i < 256; i++) { 54236 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 54237 } 54238 54239 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 54240 // Compute sbox 54241 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 54242 s = s >> 8 ^ s & 255 ^ 99; 54243 sbox[x] = s; 54244 sboxInv[s] = x; // Compute MixColumns 54245 54246 x8 = d[x4 = d[x2 = d[x]]]; 54247 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 54248 tEnc = d[s] * 0x101 ^ s * 0x1010100; 54249 54250 for (i = 0; i < 4; i++) { 54251 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 54252 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 54253 } 54254 } // Compactify. Considerable speedup on Firefox. 54255 54256 54257 for (i = 0; i < 5; i++) { 54258 encTable[i] = encTable[i].slice(0); 54259 decTable[i] = decTable[i].slice(0); 54260 } 54261 54262 return tables; 54263 }; 54264 54265 var aesTables = null; 54266 /** 54267 * Schedule out an AES key for both encryption and decryption. This 54268 * is a low-level class. Use a cipher mode to do bulk encryption. 54269 * 54270 * @class AES 54271 * @param key {Array} The key as an array of 4, 6 or 8 words. 54272 */ 54273 54274 var AES = function () { 54275 function AES(key) { 54276 classCallCheck(this, AES); 54277 /** 54278 * The expanded S-box and inverse S-box tables. These will be computed 54279 * on the client so that we don't have to send them down the wire. 54280 * 54281 * There are two tables, _tables[0] is for encryption and 54282 * _tables[1] is for decryption. 54283 * 54284 * The first 4 sub-tables are the expanded S-box with MixColumns. The 54285 * last (_tables[01][4]) is the S-box itself. 54286 * 54287 * @private 54288 */ 54289 // if we have yet to precompute the S-box tables 54290 // do so now 54291 54292 if (!aesTables) { 54293 aesTables = precompute(); 54294 } // then make a copy of that object for use 54295 54296 54297 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()]]; 54298 var i = void 0; 54299 var j = void 0; 54300 var tmp = void 0; 54301 var encKey = void 0; 54302 var decKey = void 0; 54303 var sbox = this._tables[0][4]; 54304 var decTable = this._tables[1]; 54305 var keyLen = key.length; 54306 var rcon = 1; 54307 54308 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 54309 throw new Error('Invalid aes key size'); 54310 } 54311 54312 encKey = key.slice(0); 54313 decKey = []; 54314 this._key = [encKey, decKey]; // schedule encryption keys 54315 54316 for (i = keyLen; i < 4 * keyLen + 28; i++) { 54317 tmp = encKey[i - 1]; // apply sbox 54318 54319 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 54320 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 54321 54322 if (i % keyLen === 0) { 54323 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 54324 rcon = rcon << 1 ^ (rcon >> 7) * 283; 54325 } 54326 } 54327 54328 encKey[i] = encKey[i - keyLen] ^ tmp; 54329 } // schedule decryption keys 54330 54331 54332 for (j = 0; i; j++, i--) { 54333 tmp = encKey[j & 3 ? i : i - 4]; 54334 54335 if (i <= 4 || j < 4) { 54336 decKey[j] = tmp; 54337 } else { 54338 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 54339 } 54340 } 54341 } 54342 /** 54343 * Decrypt 16 bytes, specified as four 32-bit words. 54344 * 54345 * @param {Number} encrypted0 the first word to decrypt 54346 * @param {Number} encrypted1 the second word to decrypt 54347 * @param {Number} encrypted2 the third word to decrypt 54348 * @param {Number} encrypted3 the fourth word to decrypt 54349 * @param {Int32Array} out the array to write the decrypted words 54350 * into 54351 * @param {Number} offset the offset into the output array to start 54352 * writing results 54353 * @return {Array} The plaintext. 54354 */ 54355 54356 54357 AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 54358 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 54359 54360 var a = encrypted0 ^ key[0]; 54361 var b = encrypted3 ^ key[1]; 54362 var c = encrypted2 ^ key[2]; 54363 var d = encrypted1 ^ key[3]; 54364 var a2 = void 0; 54365 var b2 = void 0; 54366 var c2 = void 0; // key.length === 2 ? 54367 54368 var nInnerRounds = key.length / 4 - 2; 54369 var i = void 0; 54370 var kIndex = 4; 54371 var table = this._tables[1]; // load up the tables 54372 54373 var table0 = table[0]; 54374 var table1 = table[1]; 54375 var table2 = table[2]; 54376 var table3 = table[3]; 54377 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 54378 54379 for (i = 0; i < nInnerRounds; i++) { 54380 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 54381 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 54382 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 54383 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 54384 kIndex += 4; 54385 a = a2; 54386 b = b2; 54387 c = c2; 54388 } // Last round. 54389 54390 54391 for (i = 0; i < 4; i++) { 54392 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 54393 a2 = a; 54394 a = b; 54395 b = c; 54396 c = d; 54397 d = a2; 54398 } 54399 }; 54400 54401 return AES; 54402 }(); 54403 /** 54404 * @file stream.js 54405 */ 54406 54407 /** 54408 * A lightweight readable stream implemention that handles event dispatching. 54409 * 54410 * @class Stream 54411 */ 54412 54413 54414 var Stream = function () { 54415 function Stream() { 54416 classCallCheck(this, Stream); 54417 this.listeners = {}; 54418 } 54419 /** 54420 * Add a listener for a specified event type. 54421 * 54422 * @param {String} type the event name 54423 * @param {Function} listener the callback to be invoked when an event of 54424 * the specified type occurs 54425 */ 54426 54427 54428 Stream.prototype.on = function on(type, listener) { 54429 if (!this.listeners[type]) { 54430 this.listeners[type] = []; 54431 } 54432 54433 this.listeners[type].push(listener); 54434 }; 54435 /** 54436 * Remove a listener for a specified event type. 54437 * 54438 * @param {String} type the event name 54439 * @param {Function} listener a function previously registered for this 54440 * type of event through `on` 54441 * @return {Boolean} if we could turn it off or not 54442 */ 54443 54444 54445 Stream.prototype.off = function off(type, listener) { 54446 if (!this.listeners[type]) { 54447 return false; 54448 } 54449 54450 var index = this.listeners[type].indexOf(listener); 54451 this.listeners[type].splice(index, 1); 54452 return index > -1; 54453 }; 54454 /** 54455 * Trigger an event of the specified type on this stream. Any additional 54456 * arguments to this function are passed as parameters to event listeners. 54457 * 54458 * @param {String} type the event name 54459 */ 54460 54461 54462 Stream.prototype.trigger = function trigger(type) { 54463 var callbacks = this.listeners[type]; 54464 54465 if (!callbacks) { 54466 return; 54467 } // Slicing the arguments on every invocation of this method 54468 // can add a significant amount of overhead. Avoid the 54469 // intermediate object creation for the common case of a 54470 // single callback argument 54471 54472 54473 if (arguments.length === 2) { 54474 var length = callbacks.length; 54475 54476 for (var i = 0; i < length; ++i) { 54477 callbacks[i].call(this, arguments[1]); 54478 } 54479 } else { 54480 var args = Array.prototype.slice.call(arguments, 1); 54481 var _length = callbacks.length; 54482 54483 for (var _i = 0; _i < _length; ++_i) { 54484 callbacks[_i].apply(this, args); 54485 } 54486 } 54487 }; 54488 /** 54489 * Destroys the stream and cleans up. 54490 */ 54491 54492 54493 Stream.prototype.dispose = function dispose() { 54494 this.listeners = {}; 54495 }; 54496 /** 54497 * Forwards all `data` events on this stream to the destination stream. The 54498 * destination stream should provide a method `push` to receive the data 54499 * events as they arrive. 54500 * 54501 * @param {Stream} destination the stream that will receive all `data` events 54502 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 54503 */ 54504 54505 54506 Stream.prototype.pipe = function pipe(destination) { 54507 this.on('data', function (data) { 54508 destination.push(data); 54509 }); 54510 }; 54511 54512 return Stream; 54513 }(); 54514 /** 54515 * @file async-stream.js 54516 */ 54517 54518 /** 54519 * A wrapper around the Stream class to use setTiemout 54520 * and run stream "jobs" Asynchronously 54521 * 54522 * @class AsyncStream 54523 * @extends Stream 54524 */ 54525 54526 54527 var AsyncStream$$1 = function (_Stream) { 54528 inherits(AsyncStream$$1, _Stream); 54529 54530 function AsyncStream$$1() { 54531 classCallCheck(this, AsyncStream$$1); 54532 54533 var _this = possibleConstructorReturn(this, _Stream.call(this, Stream)); 54534 54535 _this.jobs = []; 54536 _this.delay = 1; 54537 _this.timeout_ = null; 54538 return _this; 54539 } 54540 /** 54541 * process an async job 54542 * 54543 * @private 54544 */ 54545 54546 54547 AsyncStream$$1.prototype.processJob_ = function processJob_() { 54548 this.jobs.shift()(); 54549 54550 if (this.jobs.length) { 54551 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 54552 } else { 54553 this.timeout_ = null; 54554 } 54555 }; 54556 /** 54557 * push a job into the stream 54558 * 54559 * @param {Function} job the job to push into the stream 54560 */ 54561 54562 54563 AsyncStream$$1.prototype.push = function push(job) { 54564 this.jobs.push(job); 54565 54566 if (!this.timeout_) { 54567 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 54568 } 54569 }; 54570 54571 return AsyncStream$$1; 54572 }(Stream); 54573 /** 54574 * @file decrypter.js 54575 * 54576 * An asynchronous implementation of AES-128 CBC decryption with 54577 * PKCS#7 padding. 54578 */ 54579 54580 /** 54581 * Convert network-order (big-endian) bytes into their little-endian 54582 * representation. 54583 */ 54584 54585 54586 var ntoh = function ntoh(word) { 54587 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 54588 }; 54589 /** 54590 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 54591 * 54592 * @param {Uint8Array} encrypted the encrypted bytes 54593 * @param {Uint32Array} key the bytes of the decryption key 54594 * @param {Uint32Array} initVector the initialization vector (IV) to 54595 * use for the first round of CBC. 54596 * @return {Uint8Array} the decrypted bytes 54597 * 54598 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 54599 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 54600 * @see https://tools.ietf.org/html/rfc2315 54601 */ 54602 54603 54604 var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) { 54605 // word-level access to the encrypted bytes 54606 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 54607 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 54608 54609 var decrypted = new Uint8Array(encrypted.byteLength); 54610 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 54611 // decrypted data 54612 54613 var init0 = void 0; 54614 var init1 = void 0; 54615 var init2 = void 0; 54616 var init3 = void 0; 54617 var encrypted0 = void 0; 54618 var encrypted1 = void 0; 54619 var encrypted2 = void 0; 54620 var encrypted3 = void 0; // iteration variable 54621 54622 var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the 54623 // passed-in reference and easier access 54624 54625 init0 = initVector[0]; 54626 init1 = initVector[1]; 54627 init2 = initVector[2]; 54628 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 54629 // to each decrypted block 54630 54631 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 54632 // convert big-endian (network order) words into little-endian 54633 // (javascript order) 54634 encrypted0 = ntoh(encrypted32[wordIx]); 54635 encrypted1 = ntoh(encrypted32[wordIx + 1]); 54636 encrypted2 = ntoh(encrypted32[wordIx + 2]); 54637 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 54638 54639 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 54640 // plaintext 54641 54642 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 54643 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 54644 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 54645 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 54646 54647 init0 = encrypted0; 54648 init1 = encrypted1; 54649 init2 = encrypted2; 54650 init3 = encrypted3; 54651 } 54652 54653 return decrypted; 54654 }; 54655 /** 54656 * The `Decrypter` class that manages decryption of AES 54657 * data through `AsyncStream` objects and the `decrypt` 54658 * function 54659 * 54660 * @param {Uint8Array} encrypted the encrypted bytes 54661 * @param {Uint32Array} key the bytes of the decryption key 54662 * @param {Uint32Array} initVector the initialization vector (IV) to 54663 * @param {Function} done the function to run when done 54664 * @class Decrypter 54665 */ 54666 54667 54668 var Decrypter$$1 = function () { 54669 function Decrypter$$1(encrypted, key, initVector, done) { 54670 classCallCheck(this, Decrypter$$1); 54671 var step = Decrypter$$1.STEP; 54672 var encrypted32 = new Int32Array(encrypted.buffer); 54673 var decrypted = new Uint8Array(encrypted.byteLength); 54674 var i = 0; 54675 this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously 54676 54677 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 54678 54679 for (i = step; i < encrypted32.length; i += step) { 54680 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 54681 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 54682 } // invoke the done() callback when everything is finished 54683 54684 54685 this.asyncStream_.push(function () { 54686 // remove pkcs#7 padding from the decrypted bytes 54687 done(null, unpad(decrypted)); 54688 }); 54689 } 54690 /** 54691 * a getter for step the maximum number of bytes to process at one time 54692 * 54693 * @return {Number} the value of step 32000 54694 */ 54695 54696 /** 54697 * @private 54698 */ 54699 54700 54701 Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 54702 return function () { 54703 var bytes = decrypt$$1(encrypted, key, initVector); 54704 decrypted.set(bytes, encrypted.byteOffset); 54705 }; 54706 }; 54707 54708 createClass(Decrypter$$1, null, [{ 54709 key: 'STEP', 54710 get: function get$$1() { 54711 // 4 * 8000; 54712 return 32000; 54713 } 54714 }]); 54715 return Decrypter$$1; 54716 }(); 54717 /** 54718 * @file bin-utils.js 54719 */ 54720 54721 /** 54722 * Creates an object for sending to a web worker modifying properties that are TypedArrays 54723 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 54724 * 54725 * @param {Object} message 54726 * Object of properties and values to send to the web worker 54727 * @return {Object} 54728 * Modified message with TypedArray values expanded 54729 * @function createTransferableMessage 54730 */ 54731 54732 54733 var createTransferableMessage = function createTransferableMessage(message) { 54734 var transferable = {}; 54735 Object.keys(message).forEach(function (key) { 54736 var value = message[key]; 54737 54738 if (ArrayBuffer.isView(value)) { 54739 transferable[key] = { 54740 bytes: value.buffer, 54741 byteOffset: value.byteOffset, 54742 byteLength: value.byteLength 54743 }; 54744 } else { 54745 transferable[key] = value; 54746 } 54747 }); 54748 return transferable; 54749 }; 54750 /** 54751 * Our web worker interface so that things can talk to aes-decrypter 54752 * that will be running in a web worker. the scope is passed to this by 54753 * webworkify. 54754 * 54755 * @param {Object} self 54756 * the scope for the web worker 54757 */ 54758 54759 54760 var DecrypterWorker = function DecrypterWorker(self) { 54761 self.onmessage = function (event) { 54762 var data = event.data; 54763 var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength); 54764 var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4); 54765 var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4); 54766 /* eslint-disable no-new, handle-callback-err */ 54767 54768 new Decrypter$$1(encrypted, key, iv, function (err, bytes) { 54769 self.postMessage(createTransferableMessage({ 54770 source: data.source, 54771 decrypted: bytes 54772 }), [bytes.buffer]); 54773 }); 54774 /* eslint-enable */ 54775 }; 54776 }; 54777 54778 var decrypterWorker = new DecrypterWorker(self); 54779 return decrypterWorker; 54780 }(); 54781 }); 54782 /** 54783 * Convert the properties of an HLS track into an audioTrackKind. 54784 * 54785 * @private 54786 */ 54787 54788 var audioTrackKind_ = function audioTrackKind_(properties) { 54789 var kind = properties["default"] ? 'main' : 'alternative'; 54790 54791 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) { 54792 kind = 'main-desc'; 54793 } 54794 54795 return kind; 54796 }; 54797 /** 54798 * Pause provided segment loader and playlist loader if active 54799 * 54800 * @param {SegmentLoader} segmentLoader 54801 * SegmentLoader to pause 54802 * @param {Object} mediaType 54803 * Active media type 54804 * @function stopLoaders 54805 */ 54806 54807 54808 var stopLoaders = function stopLoaders(segmentLoader, mediaType) { 54809 segmentLoader.abort(); 54810 segmentLoader.pause(); 54811 54812 if (mediaType && mediaType.activePlaylistLoader) { 54813 mediaType.activePlaylistLoader.pause(); 54814 mediaType.activePlaylistLoader = null; 54815 } 54816 }; 54817 /** 54818 * Start loading provided segment loader and playlist loader 54819 * 54820 * @param {PlaylistLoader} playlistLoader 54821 * PlaylistLoader to start loading 54822 * @param {Object} mediaType 54823 * Active media type 54824 * @function startLoaders 54825 */ 54826 54827 54828 var startLoaders = function startLoaders(playlistLoader, mediaType) { 54829 // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the 54830 // playlist loader 54831 mediaType.activePlaylistLoader = playlistLoader; 54832 playlistLoader.load(); 54833 }; 54834 /** 54835 * Returns a function to be called when the media group changes. It performs a 54836 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a 54837 * change of group is merely a rendition switch of the same content at another encoding, 54838 * rather than a change of content, such as switching audio from English to Spanish. 54839 * 54840 * @param {String} type 54841 * MediaGroup type 54842 * @param {Object} settings 54843 * Object containing required information for media groups 54844 * @return {Function} 54845 * Handler for a non-destructive resync of SegmentLoader when the active media 54846 * group changes. 54847 * @function onGroupChanged 54848 */ 54849 54850 54851 var onGroupChanged = function onGroupChanged(type, settings) { 54852 return function () { 54853 var _settings$segmentLoad = settings.segmentLoaders, 54854 segmentLoader = _settings$segmentLoad[type], 54855 mainSegmentLoader = _settings$segmentLoad.main, 54856 mediaType = settings.mediaTypes[type]; 54857 var activeTrack = mediaType.activeTrack(); 54858 var activeGroup = mediaType.activeGroup(activeTrack); 54859 var previousActiveLoader = mediaType.activePlaylistLoader; 54860 stopLoaders(segmentLoader, mediaType); 54861 54862 if (!activeGroup) { 54863 // there is no group active 54864 return; 54865 } 54866 54867 if (!activeGroup.playlistLoader) { 54868 if (previousActiveLoader) { 54869 // The previous group had a playlist loader but the new active group does not 54870 // this means we are switching from demuxed to muxed audio. In this case we want to 54871 // do a destructive reset of the main segment loader and not restart the audio 54872 // loaders. 54873 mainSegmentLoader.resetEverything(); 54874 } 54875 54876 return; 54877 } // Non-destructive resync 54878 54879 54880 segmentLoader.resyncLoader(); 54881 startLoaders(activeGroup.playlistLoader, mediaType); 54882 }; 54883 }; 54884 /** 54885 * Returns a function to be called when the media track changes. It performs a 54886 * destructive reset of the SegmentLoader to ensure we start loading as close to 54887 * currentTime as possible. 54888 * 54889 * @param {String} type 54890 * MediaGroup type 54891 * @param {Object} settings 54892 * Object containing required information for media groups 54893 * @return {Function} 54894 * Handler for a destructive reset of SegmentLoader when the active media 54895 * track changes. 54896 * @function onTrackChanged 54897 */ 54898 54899 54900 var onTrackChanged = function onTrackChanged(type, settings) { 54901 return function () { 54902 var _settings$segmentLoad2 = settings.segmentLoaders, 54903 segmentLoader = _settings$segmentLoad2[type], 54904 mainSegmentLoader = _settings$segmentLoad2.main, 54905 mediaType = settings.mediaTypes[type]; 54906 var activeTrack = mediaType.activeTrack(); 54907 var activeGroup = mediaType.activeGroup(activeTrack); 54908 var previousActiveLoader = mediaType.activePlaylistLoader; 54909 stopLoaders(segmentLoader, mediaType); 54910 54911 if (!activeGroup) { 54912 // there is no group active so we do not want to restart loaders 54913 return; 54914 } 54915 54916 if (!activeGroup.playlistLoader) { 54917 // when switching from demuxed audio/video to muxed audio/video (noted by no playlist 54918 // loader for the audio group), we want to do a destructive reset of the main segment 54919 // loader and not restart the audio loaders 54920 mainSegmentLoader.resetEverything(); 54921 return; 54922 } 54923 54924 if (previousActiveLoader === activeGroup.playlistLoader) { 54925 // Nothing has actually changed. This can happen because track change events can fire 54926 // multiple times for a "single" change. One for enabling the new active track, and 54927 // one for disabling the track that was active 54928 startLoaders(activeGroup.playlistLoader, mediaType); 54929 return; 54930 } 54931 54932 if (segmentLoader.track) { 54933 // For WebVTT, set the new text track in the segmentloader 54934 segmentLoader.track(activeTrack); 54935 } // destructive reset 54936 54937 54938 segmentLoader.resetEverything(); 54939 startLoaders(activeGroup.playlistLoader, mediaType); 54940 }; 54941 }; 54942 54943 var onError = { 54944 /** 54945 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 54946 * an error. 54947 * 54948 * @param {String} type 54949 * MediaGroup type 54950 * @param {Object} settings 54951 * Object containing required information for media groups 54952 * @return {Function} 54953 * Error handler. Logs warning (or error if the playlist is blacklisted) to 54954 * console and switches back to default audio track. 54955 * @function onError.AUDIO 54956 */ 54957 AUDIO: function AUDIO(type, settings) { 54958 return function () { 54959 var segmentLoader = settings.segmentLoaders[type], 54960 mediaType = settings.mediaTypes[type], 54961 blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist; 54962 stopLoaders(segmentLoader, mediaType); // switch back to default audio track 54963 54964 var activeTrack = mediaType.activeTrack(); 54965 var activeGroup = mediaType.activeGroup(); 54966 var id = (activeGroup.filter(function (group) { 54967 return group["default"]; 54968 })[0] || activeGroup[0]).id; 54969 var defaultTrack = mediaType.tracks[id]; 54970 54971 if (activeTrack === defaultTrack) { 54972 // Default track encountered an error. All we can do now is blacklist the current 54973 // rendition and hope another will switch audio groups 54974 blacklistCurrentPlaylist({ 54975 message: 'Problem encountered loading the default audio track.' 54976 }); 54977 return; 54978 } 54979 54980 videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.'); 54981 54982 for (var trackId in mediaType.tracks) { 54983 mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack; 54984 } 54985 54986 mediaType.onTrackChanged(); 54987 }; 54988 }, 54989 54990 /** 54991 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 54992 * an error. 54993 * 54994 * @param {String} type 54995 * MediaGroup type 54996 * @param {Object} settings 54997 * Object containing required information for media groups 54998 * @return {Function} 54999 * Error handler. Logs warning to console and disables the active subtitle track 55000 * @function onError.SUBTITLES 55001 */ 55002 SUBTITLES: function SUBTITLES(type, settings) { 55003 return function () { 55004 var segmentLoader = settings.segmentLoaders[type], 55005 mediaType = settings.mediaTypes[type]; 55006 videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.'); 55007 stopLoaders(segmentLoader, mediaType); 55008 var track = mediaType.activeTrack(); 55009 55010 if (track) { 55011 track.mode = 'disabled'; 55012 } 55013 55014 mediaType.onTrackChanged(); 55015 }; 55016 } 55017 }; 55018 var setupListeners = { 55019 /** 55020 * Setup event listeners for audio playlist loader 55021 * 55022 * @param {String} type 55023 * MediaGroup type 55024 * @param {PlaylistLoader|null} playlistLoader 55025 * PlaylistLoader to register listeners on 55026 * @param {Object} settings 55027 * Object containing required information for media groups 55028 * @function setupListeners.AUDIO 55029 */ 55030 AUDIO: function AUDIO(type, playlistLoader, settings) { 55031 if (!playlistLoader) { 55032 // no playlist loader means audio will be muxed with the video 55033 return; 55034 } 55035 55036 var tech = settings.tech, 55037 requestOptions = settings.requestOptions, 55038 segmentLoader = settings.segmentLoaders[type]; 55039 playlistLoader.on('loadedmetadata', function () { 55040 var media = playlistLoader.media(); 55041 segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload 55042 // permits, start downloading segments 55043 55044 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 55045 segmentLoader.load(); 55046 } 55047 }); 55048 playlistLoader.on('loadedplaylist', function () { 55049 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 55050 55051 if (!tech.paused()) { 55052 segmentLoader.load(); 55053 } 55054 }); 55055 playlistLoader.on('error', onError[type](type, settings)); 55056 }, 55057 55058 /** 55059 * Setup event listeners for subtitle playlist loader 55060 * 55061 * @param {String} type 55062 * MediaGroup type 55063 * @param {PlaylistLoader|null} playlistLoader 55064 * PlaylistLoader to register listeners on 55065 * @param {Object} settings 55066 * Object containing required information for media groups 55067 * @function setupListeners.SUBTITLES 55068 */ 55069 SUBTITLES: function SUBTITLES(type, playlistLoader, settings) { 55070 var tech = settings.tech, 55071 requestOptions = settings.requestOptions, 55072 segmentLoader = settings.segmentLoaders[type], 55073 mediaType = settings.mediaTypes[type]; 55074 playlistLoader.on('loadedmetadata', function () { 55075 var media = playlistLoader.media(); 55076 segmentLoader.playlist(media, requestOptions); 55077 segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload 55078 // permits, start downloading segments 55079 55080 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 55081 segmentLoader.load(); 55082 } 55083 }); 55084 playlistLoader.on('loadedplaylist', function () { 55085 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 55086 55087 if (!tech.paused()) { 55088 segmentLoader.load(); 55089 } 55090 }); 55091 playlistLoader.on('error', onError[type](type, settings)); 55092 } 55093 }; 55094 var initialize = { 55095 /** 55096 * Setup PlaylistLoaders and AudioTracks for the audio groups 55097 * 55098 * @param {String} type 55099 * MediaGroup type 55100 * @param {Object} settings 55101 * Object containing required information for media groups 55102 * @function initialize.AUDIO 55103 */ 55104 'AUDIO': function AUDIO(type, settings) { 55105 var hls = settings.hls, 55106 sourceType = settings.sourceType, 55107 segmentLoader = settings.segmentLoaders[type], 55108 requestOptions = settings.requestOptions, 55109 mediaGroups = settings.master.mediaGroups, 55110 _settings$mediaTypes$ = settings.mediaTypes[type], 55111 groups = _settings$mediaTypes$.groups, 55112 tracks = _settings$mediaTypes$.tracks, 55113 masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none 55114 55115 if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) { 55116 mediaGroups[type] = { 55117 main: { 55118 "default": { 55119 "default": true 55120 } 55121 } 55122 }; 55123 } 55124 55125 for (var groupId in mediaGroups[type]) { 55126 if (!groups[groupId]) { 55127 groups[groupId] = []; 55128 } // List of playlists that have an AUDIO attribute value matching the current 55129 // group ID 55130 55131 55132 for (var variantLabel in mediaGroups[type][groupId]) { 55133 var properties = mediaGroups[type][groupId][variantLabel]; 55134 var playlistLoader = void 0; 55135 55136 if (properties.resolvedUri) { 55137 playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions); 55138 } else if (properties.playlists && sourceType === 'dash') { 55139 playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader); 55140 } else { 55141 // no resolvedUri means the audio is muxed with the video when using this 55142 // audio track 55143 playlistLoader = null; 55144 } 55145 55146 properties = videojs$1.mergeOptions({ 55147 id: variantLabel, 55148 playlistLoader: playlistLoader 55149 }, properties); 55150 setupListeners[type](type, properties.playlistLoader, settings); 55151 groups[groupId].push(properties); 55152 55153 if (typeof tracks[variantLabel] === 'undefined') { 55154 var track = new videojs$1.AudioTrack({ 55155 id: variantLabel, 55156 kind: audioTrackKind_(properties), 55157 enabled: false, 55158 language: properties.language, 55159 "default": properties["default"], 55160 label: variantLabel 55161 }); 55162 tracks[variantLabel] = track; 55163 } 55164 } 55165 } // setup single error event handler for the segment loader 55166 55167 55168 segmentLoader.on('error', onError[type](type, settings)); 55169 }, 55170 55171 /** 55172 * Setup PlaylistLoaders and TextTracks for the subtitle groups 55173 * 55174 * @param {String} type 55175 * MediaGroup type 55176 * @param {Object} settings 55177 * Object containing required information for media groups 55178 * @function initialize.SUBTITLES 55179 */ 55180 'SUBTITLES': function SUBTITLES(type, settings) { 55181 var tech = settings.tech, 55182 hls = settings.hls, 55183 sourceType = settings.sourceType, 55184 segmentLoader = settings.segmentLoaders[type], 55185 requestOptions = settings.requestOptions, 55186 mediaGroups = settings.master.mediaGroups, 55187 _settings$mediaTypes$2 = settings.mediaTypes[type], 55188 groups = _settings$mediaTypes$2.groups, 55189 tracks = _settings$mediaTypes$2.tracks, 55190 masterPlaylistLoader = settings.masterPlaylistLoader; 55191 55192 for (var groupId in mediaGroups[type]) { 55193 if (!groups[groupId]) { 55194 groups[groupId] = []; 55195 } 55196 55197 for (var variantLabel in mediaGroups[type][groupId]) { 55198 if (mediaGroups[type][groupId][variantLabel].forced) { 55199 // Subtitle playlists with the forced attribute are not selectable in Safari. 55200 // According to Apple's HLS Authoring Specification: 55201 // If content has forced subtitles and regular subtitles in a given language, 55202 // the regular subtitles track in that language MUST contain both the forced 55203 // subtitles and the regular subtitles for that language. 55204 // Because of this requirement and that Safari does not add forced subtitles, 55205 // forced subtitles are skipped here to maintain consistent experience across 55206 // all platforms 55207 continue; 55208 } 55209 55210 var properties = mediaGroups[type][groupId][variantLabel]; 55211 var playlistLoader = void 0; 55212 55213 if (sourceType === 'hls') { 55214 playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions); 55215 } else if (sourceType === 'dash') { 55216 playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader); 55217 } 55218 55219 properties = videojs$1.mergeOptions({ 55220 id: variantLabel, 55221 playlistLoader: playlistLoader 55222 }, properties); 55223 setupListeners[type](type, properties.playlistLoader, settings); 55224 groups[groupId].push(properties); 55225 55226 if (typeof tracks[variantLabel] === 'undefined') { 55227 var track = tech.addRemoteTextTrack({ 55228 id: variantLabel, 55229 kind: 'subtitles', 55230 "default": properties["default"] && properties.autoselect, 55231 language: properties.language, 55232 label: variantLabel 55233 }, false).track; 55234 tracks[variantLabel] = track; 55235 } 55236 } 55237 } // setup single error event handler for the segment loader 55238 55239 55240 segmentLoader.on('error', onError[type](type, settings)); 55241 }, 55242 55243 /** 55244 * Setup TextTracks for the closed-caption groups 55245 * 55246 * @param {String} type 55247 * MediaGroup type 55248 * @param {Object} settings 55249 * Object containing required information for media groups 55250 * @function initialize['CLOSED-CAPTIONS'] 55251 */ 55252 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) { 55253 var tech = settings.tech, 55254 mediaGroups = settings.master.mediaGroups, 55255 _settings$mediaTypes$3 = settings.mediaTypes[type], 55256 groups = _settings$mediaTypes$3.groups, 55257 tracks = _settings$mediaTypes$3.tracks; 55258 55259 for (var groupId in mediaGroups[type]) { 55260 if (!groups[groupId]) { 55261 groups[groupId] = []; 55262 } 55263 55264 for (var variantLabel in mediaGroups[type][groupId]) { 55265 var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that 55266 // doesn't use a CCx INSTREAM-ID 55267 55268 if (!properties.instreamId.match(/CC\d/)) { 55269 continue; 55270 } // No PlaylistLoader is required for Closed-Captions because the captions are 55271 // embedded within the video stream 55272 55273 55274 groups[groupId].push(videojs$1.mergeOptions({ 55275 id: variantLabel 55276 }, properties)); 55277 55278 if (typeof tracks[variantLabel] === 'undefined') { 55279 var track = tech.addRemoteTextTrack({ 55280 id: properties.instreamId, 55281 kind: 'captions', 55282 "default": properties["default"] && properties.autoselect, 55283 language: properties.language, 55284 label: variantLabel 55285 }, false).track; 55286 tracks[variantLabel] = track; 55287 } 55288 } 55289 } 55290 } 55291 }; 55292 /** 55293 * Returns a function used to get the active group of the provided type 55294 * 55295 * @param {String} type 55296 * MediaGroup type 55297 * @param {Object} settings 55298 * Object containing required information for media groups 55299 * @return {Function} 55300 * Function that returns the active media group for the provided type. Takes an 55301 * optional parameter {TextTrack} track. If no track is provided, a list of all 55302 * variants in the group, otherwise the variant corresponding to the provided 55303 * track is returned. 55304 * @function activeGroup 55305 */ 55306 55307 var activeGroup = function activeGroup(type, settings) { 55308 return function (track) { 55309 var masterPlaylistLoader = settings.masterPlaylistLoader, 55310 groups = settings.mediaTypes[type].groups; 55311 var media = masterPlaylistLoader.media(); 55312 55313 if (!media) { 55314 return null; 55315 } 55316 55317 var variants = null; 55318 55319 if (media.attributes[type]) { 55320 variants = groups[media.attributes[type]]; 55321 } 55322 55323 variants = variants || groups.main; 55324 55325 if (typeof track === 'undefined') { 55326 return variants; 55327 } 55328 55329 if (track === null) { 55330 // An active track was specified so a corresponding group is expected. track === null 55331 // means no track is currently active so there is no corresponding group 55332 return null; 55333 } 55334 55335 return variants.filter(function (props) { 55336 return props.id === track.id; 55337 })[0] || null; 55338 }; 55339 }; 55340 55341 var activeTrack = { 55342 /** 55343 * Returns a function used to get the active track of type provided 55344 * 55345 * @param {String} type 55346 * MediaGroup type 55347 * @param {Object} settings 55348 * Object containing required information for media groups 55349 * @return {Function} 55350 * Function that returns the active media track for the provided type. Returns 55351 * null if no track is active 55352 * @function activeTrack.AUDIO 55353 */ 55354 AUDIO: function AUDIO(type, settings) { 55355 return function () { 55356 var tracks = settings.mediaTypes[type].tracks; 55357 55358 for (var id in tracks) { 55359 if (tracks[id].enabled) { 55360 return tracks[id]; 55361 } 55362 } 55363 55364 return null; 55365 }; 55366 }, 55367 55368 /** 55369 * Returns a function used to get the active track of type provided 55370 * 55371 * @param {String} type 55372 * MediaGroup type 55373 * @param {Object} settings 55374 * Object containing required information for media groups 55375 * @return {Function} 55376 * Function that returns the active media track for the provided type. Returns 55377 * null if no track is active 55378 * @function activeTrack.SUBTITLES 55379 */ 55380 SUBTITLES: function SUBTITLES(type, settings) { 55381 return function () { 55382 var tracks = settings.mediaTypes[type].tracks; 55383 55384 for (var id in tracks) { 55385 if (tracks[id].mode === 'showing' || tracks[id].mode === 'hidden') { 55386 return tracks[id]; 55387 } 55388 } 55389 55390 return null; 55391 }; 55392 } 55393 }; 55394 /** 55395 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles, 55396 * Closed-Captions) specified in the master manifest. 55397 * 55398 * @param {Object} settings 55399 * Object containing required information for setting up the media groups 55400 * @param {SegmentLoader} settings.segmentLoaders.AUDIO 55401 * Audio segment loader 55402 * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES 55403 * Subtitle segment loader 55404 * @param {SegmentLoader} settings.segmentLoaders.main 55405 * Main segment loader 55406 * @param {Tech} settings.tech 55407 * The tech of the player 55408 * @param {Object} settings.requestOptions 55409 * XHR request options used by the segment loaders 55410 * @param {PlaylistLoader} settings.masterPlaylistLoader 55411 * PlaylistLoader for the master source 55412 * @param {HlsHandler} settings.hls 55413 * HLS SourceHandler 55414 * @param {Object} settings.master 55415 * The parsed master manifest 55416 * @param {Object} settings.mediaTypes 55417 * Object to store the loaders, tracks, and utility methods for each media type 55418 * @param {Function} settings.blacklistCurrentPlaylist 55419 * Blacklists the current rendition and forces a rendition switch. 55420 * @function setupMediaGroups 55421 */ 55422 55423 var setupMediaGroups = function setupMediaGroups(settings) { 55424 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 55425 initialize[type](type, settings); 55426 }); 55427 var mediaTypes = settings.mediaTypes, 55428 masterPlaylistLoader = settings.masterPlaylistLoader, 55429 tech = settings.tech, 55430 hls = settings.hls; // setup active group and track getters and change event handlers 55431 55432 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 55433 mediaTypes[type].activeGroup = activeGroup(type, settings); 55434 mediaTypes[type].activeTrack = activeTrack[type](type, settings); 55435 mediaTypes[type].onGroupChanged = onGroupChanged(type, settings); 55436 mediaTypes[type].onTrackChanged = onTrackChanged(type, settings); 55437 }); // DO NOT enable the default subtitle or caption track. 55438 // DO enable the default audio track 55439 55440 var audioGroup = mediaTypes.AUDIO.activeGroup(); 55441 var groupId = (audioGroup.filter(function (group) { 55442 return group["default"]; 55443 })[0] || audioGroup[0]).id; 55444 mediaTypes.AUDIO.tracks[groupId].enabled = true; 55445 mediaTypes.AUDIO.onTrackChanged(); 55446 masterPlaylistLoader.on('mediachange', function () { 55447 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 55448 return mediaTypes[type].onGroupChanged(); 55449 }); 55450 }); // custom audio track change event handler for usage event 55451 55452 var onAudioTrackChanged = function onAudioTrackChanged() { 55453 mediaTypes.AUDIO.onTrackChanged(); 55454 tech.trigger({ 55455 type: 'usage', 55456 name: 'hls-audio-change' 55457 }); 55458 }; 55459 55460 tech.audioTracks().addEventListener('change', onAudioTrackChanged); 55461 tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 55462 hls.on('dispose', function () { 55463 tech.audioTracks().removeEventListener('change', onAudioTrackChanged); 55464 tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 55465 }); // clear existing audio tracks and add the ones we just created 55466 55467 tech.clearTracks('audio'); 55468 55469 for (var id in mediaTypes.AUDIO.tracks) { 55470 tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]); 55471 } 55472 }; 55473 /** 55474 * Creates skeleton object used to store the loaders, tracks, and utility methods for each 55475 * media type 55476 * 55477 * @return {Object} 55478 * Object to store the loaders, tracks, and utility methods for each media type 55479 * @function createMediaTypes 55480 */ 55481 55482 55483 var createMediaTypes = function createMediaTypes() { 55484 var mediaTypes = {}; 55485 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 55486 mediaTypes[type] = { 55487 groups: {}, 55488 tracks: {}, 55489 activePlaylistLoader: null, 55490 activeGroup: noop$1, 55491 activeTrack: noop$1, 55492 onGroupChanged: noop$1, 55493 onTrackChanged: noop$1 55494 }; 55495 }); 55496 return mediaTypes; 55497 }; 55498 /** 55499 * @file master-playlist-controller.js 55500 */ 55501 55502 55503 var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2; 55504 var Hls = void 0; // SegmentLoader stats that need to have each loader's 55505 // values summed to calculate the final value 55506 55507 var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred']; 55508 55509 var sumLoaderStat = function sumLoaderStat(stat) { 55510 return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat]; 55511 }; 55512 55513 var shouldSwitchToMedia = function shouldSwitchToMedia(_ref) { 55514 var currentPlaylist = _ref.currentPlaylist, 55515 nextPlaylist = _ref.nextPlaylist, 55516 forwardBuffer = _ref.forwardBuffer, 55517 bufferLowWaterLine = _ref.bufferLowWaterLine, 55518 duration$$1 = _ref.duration, 55519 log = _ref.log; // we have no other playlist to switch to 55520 55521 if (!nextPlaylist) { 55522 videojs$1.log.warn('We received no playlist to switch to. Please check your stream.'); 55523 return false;
55524 } // If the playlist is live, then we want to not take low water line into account. 55525 // This is because in LIVE, the player plays 3 segments from the end of the 55526 // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble 55527 // in those segments, a viewer will never experience a rendition upswitch. 55528 55529 55530 if (!currentPlaylist.endList) { 55531 return true; 55532 } // For the same reason as LIVE, we ignore the low water line when the VOD 55533 // duration is below the max potential low water line 55534 55535 55536 if (duration$$1 < Config.MAX_BUFFER_LOW_WATER_LINE) { 55537 return true; 55538 } // we want to switch down to lower resolutions quickly to continue playback, but 55539 55540 55541 if (nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH) { 55542 return true; 55543 } // ensure we have some buffer before we switch up to prevent us running out of 55544 // buffer while loading a higher rendition. 55545 55546 55547 if (forwardBuffer >= bufferLowWaterLine) { 55548 return true; 55549 } 55550 55551 return false; 55552 }; 55553 /** 55554 * the master playlist controller controller all interactons 55555 * between playlists and segmentloaders. At this time this mainly 55556 * involves a master playlist and a series of audio playlists 55557 * if they are available 55558 * 55559 * @class MasterPlaylistController 55560 * @extends videojs.EventTarget 55561 */ 55562 55563 55564 var MasterPlaylistController = function (_videojs$EventTarget) { 55565 inherits$2(MasterPlaylistController, _videojs$EventTarget); 55566 55567 function MasterPlaylistController(options) { 55568 classCallCheck$1(this, MasterPlaylistController); 55569 55570 var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this)); 55571 55572 var url = options.url, 55573 handleManifestRedirects = options.handleManifestRedirects, 55574 withCredentials = options.withCredentials, 55575 tech = options.tech, 55576 bandwidth = options.bandwidth, 55577 externHls = options.externHls, 55578 useCueTags = options.useCueTags, 55579 blacklistDuration = options.blacklistDuration, 55580 enableLowInitialPlaylist = options.enableLowInitialPlaylist, 55581 cacheEncryptionKeys = options.cacheEncryptionKeys, 55582 sourceType = options.sourceType; 55583 55584 if (!url) { 55585 throw new Error('A non-empty playlist URL is required'); 55586 } 55587 55588 Hls = externHls; 55589 _this.withCredentials = withCredentials; 55590 _this.tech_ = tech; 55591 _this.hls_ = tech.hls; 55592 _this.sourceType_ = sourceType; 55593 _this.useCueTags_ = useCueTags;
vendor: 18,094 bytes, lines 55594-56071
55594 _this.blacklistDuration = blacklistDuration; 55595 _this.enableLowInitialPlaylist = enableLowInitialPlaylist; 55596 55597 if (_this.useCueTags_) { 55598 _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues'); 55599 _this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 55600 } 55601 55602 _this.requestOptions_ = { 55603 withCredentials: withCredentials, 55604 handleManifestRedirects: handleManifestRedirects, 55605 timeout: null 55606 }; 55607 _this.mediaTypes_ = createMediaTypes(); 55608 _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player 55609 55610 _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this)); 55611 55612 _this.seekable_ = videojs$1.createTimeRanges(); 55613 _this.hasPlayed_ = false; 55614 _this.syncController_ = new SyncController(options); 55615 _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({ 55616 kind: 'metadata', 55617 label: 'segment-metadata' 55618 }, false).track; 55619 _this.decrypter_ = new Decrypter$1(); 55620 _this.inbandTextTracks_ = {}; 55621 var segmentLoaderSettings = { 55622 hls: _this.hls_, 55623 mediaSource: _this.mediaSource, 55624 currentTime: _this.tech_.currentTime.bind(_this.tech_), 55625 seekable: function seekable$$1() { 55626 return _this.seekable(); 55627 }, 55628 seeking: function seeking() { 55629 return _this.tech_.seeking(); 55630 }, 55631 duration: function duration$$1() { 55632 return _this.mediaSource.duration; 55633 }, 55634 hasPlayed: function hasPlayed() { 55635 return _this.hasPlayed_; 55636 }, 55637 goalBufferLength: function goalBufferLength() { 55638 return _this.goalBufferLength(); 55639 }, 55640 bandwidth: bandwidth, 55641 syncController: _this.syncController_, 55642 decrypter: _this.decrypter_, 55643 sourceType: _this.sourceType_, 55644 inbandTextTracks: _this.inbandTextTracks_, 55645 cacheEncryptionKeys: cacheEncryptionKeys 55646 }; 55647 _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_); 55648 55649 _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders 55650 // combined audio/video or just video when alternate audio track is selected 55651 55652 55653 _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 55654 segmentMetadataTrack: _this.segmentMetadataTrack_, 55655 loaderType: 'main' 55656 }), options); // alternate audio track 55657 55658 _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 55659 loaderType: 'audio' 55660 }), options); 55661 _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 55662 loaderType: 'vtt', 55663 featuresNativeTextTracks: _this.tech_.featuresNativeTextTracks 55664 }), options); 55665 55666 _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters 55667 55668 55669 loaderStats.forEach(function (stat) { 55670 _this[stat + '_'] = sumLoaderStat.bind(_this, stat); 55671 }); 55672 _this.logger_ = logger('MPC'); 55673 55674 _this.masterPlaylistLoader_.load(); 55675 55676 return _this; 55677 } 55678 /** 55679 * Register event handlers on the master playlist loader. A helper 55680 * function for construction time. 55681 * 55682 * @private 55683 */ 55684 55685 55686 createClass$1(MasterPlaylistController, [{ 55687 key: 'setupMasterPlaylistLoaderListeners_', 55688 value: function setupMasterPlaylistLoaderListeners_() { 55689 var _this2 = this; 55690 55691 this.masterPlaylistLoader_.on('loadedmetadata', function () { 55692 var media = _this2.masterPlaylistLoader_.media(); 55693 55694 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 55695 // timeout the request. 55696 55697 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 55698 _this2.requestOptions_.timeout = 0; 55699 } else { 55700 _this2.requestOptions_.timeout = requestTimeout; 55701 } // if this isn't a live video and preload permits, start 55702 // downloading segments 55703 55704 55705 if (media.endList && _this2.tech_.preload() !== 'none') { 55706 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 55707 55708 _this2.mainSegmentLoader_.load(); 55709 } 55710 55711 setupMediaGroups({ 55712 sourceType: _this2.sourceType_, 55713 segmentLoaders: { 55714 AUDIO: _this2.audioSegmentLoader_, 55715 SUBTITLES: _this2.subtitleSegmentLoader_, 55716 main: _this2.mainSegmentLoader_ 55717 }, 55718 tech: _this2.tech_, 55719 requestOptions: _this2.requestOptions_, 55720 masterPlaylistLoader: _this2.masterPlaylistLoader_, 55721 hls: _this2.hls_, 55722 master: _this2.master(), 55723 mediaTypes: _this2.mediaTypes_, 55724 blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2) 55725 }); 55726 55727 _this2.triggerPresenceUsage_(_this2.master(), media); 55728 55729 try { 55730 _this2.setupSourceBuffers_(); 55731 } catch (e) { 55732 videojs$1.log.warn('Failed to create SourceBuffers', e); 55733 return _this2.mediaSource.endOfStream('decode'); 55734 } 55735 55736 _this2.setupFirstPlay(); 55737 55738 if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) { 55739 _this2.trigger('selectedinitialmedia'); 55740 } else { 55741 // We must wait for the active audio playlist loader to 55742 // finish setting up before triggering this event so the 55743 // representations API and EME setup is correct 55744 _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () { 55745 _this2.trigger('selectedinitialmedia'); 55746 }); 55747 } 55748 }); 55749 this.masterPlaylistLoader_.on('loadedplaylist', function () { 55750 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 55751 55752 if (!updatedPlaylist) { 55753 // blacklist any variants that are not supported by the browser before selecting 55754 // an initial media as the playlist selectors do not consider browser support 55755 _this2.excludeUnsupportedVariants_(); 55756 55757 var selectedMedia = void 0; 55758 55759 if (_this2.enableLowInitialPlaylist) { 55760 selectedMedia = _this2.selectInitialPlaylist(); 55761 } 55762 55763 if (!selectedMedia) { 55764 selectedMedia = _this2.selectPlaylist(); 55765 } 55766 55767 _this2.initialMedia_ = selectedMedia; 55768 55769 _this2.masterPlaylistLoader_.media(_this2.initialMedia_); 55770 55771 return; 55772 } 55773 55774 if (_this2.useCueTags_) { 55775 _this2.updateAdCues_(updatedPlaylist); 55776 } // TODO: Create a new event on the PlaylistLoader that signals 55777 // that the segments have changed in some way and use that to 55778 // update the SegmentLoader instead of doing it twice here and 55779 // on `mediachange` 55780 55781 55782 _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_); 55783 55784 _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running, 55785 // as it is possible that it was temporarily stopped while waiting for 55786 // a playlist (e.g., in case the playlist errored and we re-requested it). 55787 55788 55789 if (!_this2.tech_.paused()) { 55790 _this2.mainSegmentLoader_.load(); 55791 55792 if (_this2.audioSegmentLoader_) { 55793 _this2.audioSegmentLoader_.load(); 55794 } 55795 } 55796 55797 if (!updatedPlaylist.endList) { 55798 var addSeekableRange = function addSeekableRange() { 55799 var seekable$$1 = _this2.seekable(); 55800 55801 if (seekable$$1.length !== 0) { 55802 _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0)); 55803 } 55804 }; 55805 55806 if (_this2.duration() !== Infinity) { 55807 var onDurationchange = function onDurationchange() { 55808 if (_this2.duration() === Infinity) { 55809 addSeekableRange(); 55810 } else { 55811 _this2.tech_.one('durationchange', onDurationchange); 55812 } 55813 }; 55814 55815 _this2.tech_.one('durationchange', onDurationchange); 55816 } else { 55817 addSeekableRange(); 55818 } 55819 } 55820 }); 55821 this.masterPlaylistLoader_.on('error', function () { 55822 _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error); 55823 }); 55824 this.masterPlaylistLoader_.on('mediachanging', function () { 55825 _this2.mainSegmentLoader_.abort(); 55826 55827 _this2.mainSegmentLoader_.pause(); 55828 }); 55829 this.masterPlaylistLoader_.on('mediachange', function () { 55830 var media = _this2.masterPlaylistLoader_.media(); 55831 55832 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 55833 // timeout the request. 55834 55835 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 55836 _this2.requestOptions_.timeout = 0; 55837 } else { 55838 _this2.requestOptions_.timeout = requestTimeout; 55839 } // TODO: Create a new event on the PlaylistLoader that signals 55840 // that the segments have changed in some way and use that to 55841 // update the SegmentLoader instead of doing it twice here and 55842 // on `loadedplaylist` 55843 55844 55845 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 55846 55847 _this2.mainSegmentLoader_.load(); 55848 55849 _this2.tech_.trigger({ 55850 type: 'mediachange', 55851 bubbles: true 55852 }); 55853 }); 55854 this.masterPlaylistLoader_.on('playlistunchanged', function () { 55855 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 55856 55857 var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist); 55858 55859 if (playlistOutdated) { 55860 // Playlist has stopped updating and we're stuck at its end. Try to 55861 // blacklist it and switch to another playlist in the hope that that 55862 // one is updating (and give the player a chance to re-adjust to the 55863 // safe live point). 55864 _this2.blacklistCurrentPlaylist({ 55865 message: 'Playlist no longer updating.' 55866 }); // useful for monitoring QoS 55867 55868 55869 _this2.tech_.trigger('playliststuck'); 55870 } 55871 }); 55872 this.masterPlaylistLoader_.on('renditiondisabled', function () { 55873 _this2.tech_.trigger({ 55874 type: 'usage', 55875 name: 'hls-rendition-disabled' 55876 }); 55877 }); 55878 this.masterPlaylistLoader_.on('renditionenabled', function () { 55879 _this2.tech_.trigger({ 55880 type: 'usage', 55881 name: 'hls-rendition-enabled' 55882 }); 55883 }); 55884 } 55885 /** 55886 * A helper function for triggerring presence usage events once per source 55887 * 55888 * @private 55889 */ 55890 55891 }, { 55892 key: 'triggerPresenceUsage_', 55893 value: function triggerPresenceUsage_(master, media) { 55894 var mediaGroups = master.mediaGroups || {}; 55895 var defaultDemuxed = true; 55896 var audioGroupKeys = Object.keys(mediaGroups.AUDIO); 55897 55898 for (var mediaGroup in mediaGroups.AUDIO) { 55899 for (var label in mediaGroups.AUDIO[mediaGroup]) { 55900 var properties = mediaGroups.AUDIO[mediaGroup][label]; 55901 55902 if (!properties.uri) { 55903 defaultDemuxed = false; 55904 } 55905 } 55906 } 55907 55908 if (defaultDemuxed) { 55909 this.tech_.trigger({ 55910 type: 'usage', 55911 name: 'hls-demuxed' 55912 }); 55913 } 55914 55915 if (Object.keys(mediaGroups.SUBTITLES).length) { 55916 this.tech_.trigger({ 55917 type: 'usage', 55918 name: 'hls-webvtt' 55919 }); 55920 } 55921 55922 if (Hls.Playlist.isAes(media)) { 55923 this.tech_.trigger({ 55924 type: 'usage', 55925 name: 'hls-aes' 55926 }); 55927 } 55928 55929 if (Hls.Playlist.isFmp4(media)) { 55930 this.tech_.trigger({ 55931 type: 'usage', 55932 name: 'hls-fmp4' 55933 }); 55934 } 55935 55936 if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) { 55937 this.tech_.trigger({ 55938 type: 'usage', 55939 name: 'hls-alternate-audio' 55940 }); 55941 } 55942 55943 if (this.useCueTags_) { 55944 this.tech_.trigger({ 55945 type: 'usage', 55946 name: 'hls-playlist-cue-tags' 55947 }); 55948 } 55949 } 55950 /** 55951 * Register event handlers on the segment loaders. A helper function 55952 * for construction time. 55953 * 55954 * @private 55955 */ 55956 55957 }, { 55958 key: 'setupSegmentLoaderListeners_', 55959 value: function setupSegmentLoaderListeners_() { 55960 var _this3 = this; 55961 55962 this.mainSegmentLoader_.on('bandwidthupdate', function () { 55963 var nextPlaylist = _this3.selectPlaylist(); 55964 55965 var currentPlaylist = _this3.masterPlaylistLoader_.media(); 55966 55967 var buffered = _this3.tech_.buffered(); 55968 55969 var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0; 55970 55971 var bufferLowWaterLine = _this3.bufferLowWaterLine(); 55972 55973 if (shouldSwitchToMedia({ 55974 currentPlaylist: currentPlaylist, 55975 nextPlaylist: nextPlaylist, 55976 forwardBuffer: forwardBuffer, 55977 bufferLowWaterLine: bufferLowWaterLine, 55978 duration: _this3.duration(), 55979 log: _this3.logger_ 55980 })) { 55981 _this3.masterPlaylistLoader_.media(nextPlaylist); 55982 } 55983 55984 _this3.tech_.trigger('bandwidthupdate'); 55985 }); 55986 this.mainSegmentLoader_.on('progress', function () { 55987 _this3.trigger('progress'); 55988 }); 55989 this.mainSegmentLoader_.on('error', function () { 55990 _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error()); 55991 }); 55992 this.mainSegmentLoader_.on('syncinfoupdate', function () { 55993 _this3.onSyncInfoUpdate_(); 55994 }); 55995 this.mainSegmentLoader_.on('timestampoffset', function () { 55996 _this3.tech_.trigger({ 55997 type: 'usage', 55998 name: 'hls-timestamp-offset' 55999 }); 56000 }); 56001 this.audioSegmentLoader_.on('syncinfoupdate', function () { 56002 _this3.onSyncInfoUpdate_(); 56003 }); 56004 this.mainSegmentLoader_.on('ended', function () { 56005 _this3.onEndOfStream(); 56006 }); 56007 this.mainSegmentLoader_.on('earlyabort', function () { 56008 _this3.blacklistCurrentPlaylist({ 56009 message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.' 56010 }, ABORT_EARLY_BLACKLIST_SECONDS); 56011 }); 56012 this.mainSegmentLoader_.on('reseteverything', function () { 56013 // If playing an MTS stream, a videojs.MediaSource is listening for 56014 // hls-reset to reset caption parsing state in the transmuxer 56015 _this3.tech_.trigger('hls-reset'); 56016 }); 56017 this.mainSegmentLoader_.on('segmenttimemapping', function (event) { 56018 // If playing an MTS stream in html, a videojs.MediaSource is listening for 56019 // hls-segment-time-mapping update its internal mapping of stream to display time 56020 _this3.tech_.trigger({ 56021 type: 'hls-segment-time-mapping', 56022 mapping: event.mapping 56023 }); 56024 }); 56025 this.audioSegmentLoader_.on('ended', function () { 56026 _this3.onEndOfStream(); 56027 }); 56028 } 56029 }, { 56030 key: 'mediaSecondsLoaded_', 56031 value: function mediaSecondsLoaded_() { 56032 return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded); 56033 } 56034 /** 56035 * Call load on our SegmentLoaders 56036 */ 56037 56038 }, { 56039 key: 'load', 56040 value: function load() { 56041 this.mainSegmentLoader_.load(); 56042 56043 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56044 this.audioSegmentLoader_.load(); 56045 } 56046 56047 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 56048 this.subtitleSegmentLoader_.load(); 56049 } 56050 } 56051 /** 56052 * Re-tune playback quality level for the current player 56053 * conditions without performing destructive actions, like 56054 * removing already buffered content 56055 * 56056 * @private 56057 */ 56058 56059 }, { 56060 key: 'smoothQualityChange_', 56061 value: function smoothQualityChange_() { 56062 var media = this.selectPlaylist(); 56063 56064 if (media !== this.masterPlaylistLoader_.media()) { 56065 this.masterPlaylistLoader_.media(media); 56066 this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes 56067 } 56068 } 56069 /** 56070 * Re-tune playback quality level for the current player 56071 * conditions. This method will perform destructive actions like remov
56071ing 56072 * already buffered content in order to readjust the currently active 56073 * playlist quickly. This is good for manual quality changes 56074 * 56075 * @private 56076 */ 56077 56078 }, { 56079 key: 'fastQualityChange_', 56080 value: function fastQualityChange_() { 56081 var _this4 = this; 56082 56083 var media = this.selectPlaylist(); 56084 56085 if (media === this.masterPlaylistLoader_.media()) { 56086 return; 56087 } 56088 56089 this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give 56090 // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds 56091 // ahead is roughly the minimum that will accomplish this across a variety of content 56092 // in IE and Edge, but seeking in place is sufficient on all other browsers) 56093 // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/ 56094 // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904 56095 56096 this.mainSegmentLoader_.resetEverything(function () { 56097 // Since this is not a typical seek, we avoid the seekTo method which can cause segments 56098 // from the previously enabled rendition to load before the new playlist has finished loading 56099 if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) { 56100 _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04); 56101 } else { 56102 _this4.tech_.setCurrentTime(_this4.tech_.currentTime()); 56103 } 56104 }); // don't need to reset audio as it is reset when media changes 56105 } 56106 /** 56107 * Begin playback. 56108 */ 56109 56110 }, { 56111 key: 'play', 56112 value: function play() { 56113 if (this.setupFirstPlay()) { 56114 return; 56115 } 56116 56117 if (this.tech_.ended()) { 56118 this.tech_.setCurrentTime(0); 56119 } 56120 56121 if (this.hasPlayed_) { 56122 this.load(); 56123 } 56124 56125 var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window, 56126 // seek forward to the live point 56127 56128 if (this.tech_.duration() === Infinity) { 56129 if (this.tech_.currentTime() < seekable$$1.start(0)) { 56130 return this.tech_.setCurrentTime(seekable$$1.end(seekable$$1.length - 1)); 56131 } 56132 } 56133 } 56134 /** 56135 * Seek to the latest media position if this is a live video and the 56136 * player and video are loaded and initialized. 56137 */ 56138 56139 }, { 56140 key: 'setupFirstPlay', 56141 value: function setupFirstPlay() { 56142 var _this5 = this; 56143 56144 var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play 56145 // If 1) there is no active media 56146 // 2) the player is paused 56147 // 3) the first play has already been setup 56148 // then exit early 56149 56150 if (!media || this.tech_.paused() || this.hasPlayed_) { 56151 return false; 56152 } // when the video is a live stream 56153 56154 56155 if (!media.endList) { 56156 var seekable$$1 = this.seekable(); 56157 56158 if (!seekable$$1.length) { 56159 // without a seekable range, the player cannot seek to begin buffering at the live 56160 // point 56161 return false; 56162 } 56163 56164 if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) { 56165 // IE11 throws an InvalidStateError if you try to set currentTime while the 56166 // readyState is 0, so it must be delayed until the tech fires loadedmetadata. 56167 this.tech_.one('loadedmetadata', function () { 56168 _this5.trigger('firstplay'); 56169 56170 _this5.tech_.setCurrentTime(seekable$$1.end(0)); 56171 56172 _this5.hasPlayed_ = true; 56173 }); 56174 return false; 56175 } // trigger firstplay to inform the source handler to ignore the next seek event 56176 56177 56178 this.trigger('firstplay'); // seek to the live point 56179 56180 this.tech_.setCurrentTime(seekable$$1.end(0)); 56181 } 56182 56183 this.hasPlayed_ = true; // we can begin loading now that everything is ready 56184 56185 this.load(); 56186 return true; 56187 } 56188 /** 56189 * handle the sourceopen event on the MediaSource 56190 * 56191 * @private 56192 */ 56193 56194 }, { 56195 key: 'handleSourceOpen_', 56196 value: function handleSourceOpen_() { 56197 // Only attempt to create the source buffer if none already exist. 56198 // handleSourceOpen is also called when we are "re-opening" a source buffer 56199 // after `endOfStream` has been called (in response to a seek for instance) 56200 try { 56201 this.setupSourceBuffers_(); 56202 } catch (e) { 56203 videojs$1.log.warn('Failed to create Source Buffers', e); 56204 return this.mediaSource.endOfStream('decode'); 56205 } // if autoplay is enabled, begin playback. This is duplicative of 56206 // code in video.js but is required because play() must be invoked 56207 // *after* the media source has opened. 56208 56209 56210 if (this.tech_.autoplay()) {
vendor: 4,870 bytes, lines 56211-56322
56211 var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request 56212 // on browsers which return a promise 56213 56214 if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') { 56215 playPromise.then(null, function (e) {}); 56216 } 56217 } 56218 56219 this.trigger('sourceopen'); 56220 } 56221 /** 56222 * Calls endOfStream on the media source when all active stream types have called 56223 * endOfStream 56224 * 56225 * @param {string} streamType 56226 * Stream type of the segment loader that called endOfStream 56227 * @private 56228 */ 56229 56230 }, { 56231 key: 'onEndOfStream', 56232 value: function onEndOfStream() { 56233 var isEndOfStream = this.mainSegmentLoader_.ended_; 56234 56235 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56236 // if the audio playlist loader exists, then alternate audio is active 56237 if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) { 56238 // if we do not know if the main segment loader contains video yet or if we 56239 // definitively know the main segment loader contains video, then we need to wait 56240 // for both main and audio segment loaders to call endOfStream 56241 isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_; 56242 } else { 56243 // otherwise just rely on the audio loader 56244 isEndOfStream = this.audioSegmentLoader_.ended_; 56245 } 56246 } 56247 56248 if (!isEndOfStream) { 56249 return; 56250 } 56251 56252 this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception, 56253 // even when the media source is in a valid state. 56254 56255 try { 56256 this.mediaSource.endOfStream(); 56257 } catch (e) { 56258 videojs$1.log.warn('Failed to call media source endOfStream', e); 56259 } 56260 } 56261 /** 56262 * Check if a playlist has stopped being updated 56263 * @param {Object} playlist the media playlist object 56264 * @return {boolean} whether the playlist has stopped being updated or not 56265 */ 56266 56267 }, { 56268 key: 'stuckAtPlaylistEnd_', 56269 value: function stuckAtPlaylistEnd_(playlist) { 56270 var seekable$$1 = this.seekable(); 56271 56272 if (!seekable$$1.length) { 56273 // playlist doesn't have enough information to determine whether we are stuck 56274 return false; 56275 } 56276 56277 var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration); 56278 56279 if (expired === null) { 56280 return false; 56281 } // does not use the safe live end to calculate playlist end, since we 56282 // don't want to say we are stuck while there is still content 56283 56284 56285 var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired); 56286 var currentTime = this.tech_.currentTime(); 56287 var buffered = this.tech_.buffered(); 56288 56289 if (!buffered.length) { 56290 // return true if the playhead reached the absolute end of the playlist 56291 return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA; 56292 } 56293 56294 var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute 56295 // end of playlist 56296 56297 return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA; 56298 } 56299 /** 56300 * Blacklists a playlist when an error occurs for a set amount of time 56301 * making it unavailable for selection by the rendition selection algorithm 56302 * and then forces a new playlist (rendition) selection. 56303 * 56304 * @param {Object=} error an optional error that may include the playlist 56305 * to blacklist 56306 * @param {Number=} blacklistDuration an optional number of seconds to blacklist the 56307 * playlist 56308 */ 56309 56310 }, { 56311 key: 'blacklistCurrentPlaylist', 56312 value: function blacklistCurrentPlaylist() { 56313 var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 56314 var blacklistDuration = arguments[1]; 56315 var currentPlaylist = void 0; 56316 var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain 56317 // the playlist we were trying to load (but failed) and that should be 56318 // blacklisted instead of the currently selected playlist which is likely 56319 // out-of-date in this scenario 56320 56321 currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 56322 blacklistDuration = blacklistDuration || error.blacklistDuration || this.blackl
56322istDuration; // If there is no current playlist, then an error occurred while we were 56323 // trying to load the master OR while we were disposing of the tech 56324 56325 if (!currentPlaylist) { 56326 this.error = error; 56327 56328 try { 56329 return this.mediaSource.endOfStream('network'); 56330 } catch (e) { 56331 return this.trigger('error'); 56332 } 56333 } 56334 56335 var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1; 56336 var playlists = this.masterPlaylistLoader_.master.playlists; 56337 56338 if (playlists.length === 1) { 56339 // Never blacklisting this playlist because it's the only playlist 56340 videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the only playlist.'); 56341 this.tech_.trigger('retryplaylist'); 56342 return this.masterPlaylistLoader_.load(isFinalRendition); 56343 } 56344 56345 if (isFinalRendition) { 56346 // Since we're on the final non-blacklisted playlist, and we're about to blacklist 56347 // it, instead of erring the player or retrying this playlist, clear out the current 56348 // blacklist. This allows other playlists to be attempted in case any have been 56349 // fixed. 56350 videojs$1.log.warn('Removing all playlists from the blacklist because the last ' + 'rendition is about to be blacklisted.');
vendor: 12,527 bytes, lines 56351-56703
56351 playlists.forEach(function (playlist) { 56352 if (playlist.excludeUntil !== Infinity) { 56353 delete playlist.excludeUntil; 56354 } 56355 }); // Technically we are retrying a playlist, in that we are simply retrying a previous 56356 // playlist. This is needed for users relying on the retryplaylist event to catch a 56357 // case where the player might be stuck and looping through "dead" playlists. 56358 56359 this.tech_.trigger('retryplaylist'); 56360 } // Blacklist this playlist 56361 56362 56363 currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000; 56364 this.tech_.trigger('blacklistplaylist'); 56365 this.tech_.trigger({ 56366 type: 'usage', 56367 name: 'hls-rendition-blacklisted' 56368 }); // Select a new playlist 56369 56370 nextPlaylist = this.selectPlaylist(); 56371 videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.'); 56372 return this.masterPlaylistLoader_.media(nextPlaylist, isFinalRendition); 56373 } 56374 /** 56375 * Pause all segment loaders 56376 */ 56377 56378 }, { 56379 key: 'pauseLoading', 56380 value: function pauseLoading() { 56381 this.mainSegmentLoader_.pause(); 56382 56383 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56384 this.audioSegmentLoader_.pause(); 56385 } 56386 56387 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 56388 this.subtitleSegmentLoader_.pause(); 56389 } 56390 } 56391 /** 56392 * set the current time on all segment loaders 56393 * 56394 * @param {TimeRange} currentTime the current time to set 56395 * @return {TimeRange} the current time 56396 */ 56397 56398 }, { 56399 key: 'setCurrentTime', 56400 value: function setCurrentTime(currentTime) { 56401 var buffered = findRange(this.tech_.buffered(), currentTime); 56402 56403 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 56404 // return immediately if the metadata is not ready yet 56405 return 0; 56406 } // it's clearly an edge-case but don't thrown an error if asked to 56407 // seek within an empty playlist 56408 56409 56410 if (!this.masterPlaylistLoader_.media().segments) { 56411 return 0; 56412 } // In flash playback, the segment loaders should be reset on every seek, even 56413 // in buffer seeks. If the seek location is already buffered, continue buffering as 56414 // usual 56415 // TODO: redo this comment 56416 56417 56418 if (buffered && buffered.length) { 56419 return currentTime; 56420 } // cancel outstanding requests so we begin buffering at the new 56421 // location 56422 56423 56424 this.mainSegmentLoader_.resetEverything(); 56425 this.mainSegmentLoader_.abort(); 56426 56427 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56428 this.audioSegmentLoader_.resetEverything(); 56429 this.audioSegmentLoader_.abort(); 56430 } 56431 56432 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 56433 this.subtitleSegmentLoader_.resetEverything(); 56434 this.subtitleSegmentLoader_.abort(); 56435 } // start segment loader loading in case they are paused 56436 56437 56438 this.load(); 56439 } 56440 /** 56441 * get the current duration 56442 * 56443 * @return {TimeRange} the duration 56444 */ 56445 56446 }, { 56447 key: 'duration', 56448 value: function duration$$1() { 56449 if (!this.masterPlaylistLoader_) { 56450 return 0; 56451 } 56452 56453 if (this.mediaSource) { 56454 return this.mediaSource.duration; 56455 } 56456 56457 return Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 56458 } 56459 /** 56460 * check the seekable range 56461 * 56462 * @return {TimeRange} the seekable range 56463 */ 56464 56465 }, { 56466 key: 'seekable', 56467 value: function seekable$$1() { 56468 return this.seekable_; 56469 } 56470 }, { 56471 key: 'onSyncInfoUpdate_', 56472 value: function onSyncInfoUpdate_() { 56473 var audioSeekable = void 0; 56474 56475 if (!this.masterPlaylistLoader_) { 56476 return; 56477 } 56478 56479 var media = this.masterPlaylistLoader_.media(); 56480 56481 if (!media) { 56482 return; 56483 } 56484 56485 var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 56486 56487 if (expired === null) { 56488 // not enough information to update seekable 56489 return; 56490 } 56491 56492 var suggestedPresentationDelay = this.masterPlaylistLoader_.master.suggestedPresentationDelay; 56493 var mainSeekable = Hls.Playlist.seekable(media, expired, suggestedPresentationDelay); 56494 56495 if (mainSeekable.length === 0) { 56496 return; 56497 } 56498 56499 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 56500 media = this.mediaTypes_.AUDIO.activePlaylistLoader.media(); 56501 expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 56502 56503 if (expired === null) { 56504 return; 56505 } 56506 56507 audioSeekable = Hls.Playlist.seekable(media, expired, suggestedPresentationDelay); 56508 56509 if (audioSeekable.length === 0) { 56510 return; 56511 } 56512 } 56513 56514 var oldEnd = void 0; 56515 var oldStart = void 0; 56516 56517 if (this.seekable_ && this.seekable_.length) { 56518 oldEnd = this.seekable_.end(0); 56519 oldStart = this.seekable_.start(0); 56520 } 56521 56522 if (!audioSeekable) { 56523 // seekable has been calculated based on buffering video data so it 56524 // can be returned directly 56525 this.seekable_ = mainSeekable; 56526 } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) { 56527 // seekables are pretty far off, rely on main 56528 this.seekable_ = mainSeekable; 56529 } else { 56530 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)]]); 56531 } // seekable is the same as last time 56532 56533 56534 if (this.seekable_ && this.seekable_.length) { 56535 if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) { 56536 return; 56537 } 56538 } 56539 56540 this.logger_('seekable updated [' + printableRange(this.seekable_) + ']'); 56541 this.tech_.trigger('seekablechanged'); 56542 } 56543 /** 56544 * Update the player duration 56545 */ 56546 56547 }, { 56548 key: 'updateDuration', 56549 value: function updateDuration() { 56550 var _this6 = this; 56551 56552 var oldDuration = this.mediaSource.duration; 56553 var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 56554 var buffered = this.tech_.buffered(); 56555 56556 var setDuration = function setDuration() { 56557 // on firefox setting the duration may sometimes cause an exception 56558 // even if the media source is open and source buffers are not 56559 // updating, something about the media source being in an invalid state. 56560 _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration); 56561 56562 try { 56563 _this6.mediaSource.duration = newDuration; 56564 } catch (e) { 56565 videojs$1.log.warn('Failed to set media source duration', e); 56566 } 56567 56568 _this6.tech_.trigger('durationchange'); 56569 56570 _this6.mediaSource.removeEventListener('sourceopen', setDuration); 56571 }; 56572 56573 if (buffered.length > 0) { 56574 newDuration = Math.max(newDuration, buffered.end(buffered.length - 1)); 56575 } // if the duration has changed, invalidate the cached value 56576 56577 56578 if (oldDuration !== newDuration) { 56579 // update the duration 56580 if (this.mediaSource.readyState !== 'open') { 56581 this.mediaSource.addEventListener('sourceopen', setDuration); 56582 } else { 56583 setDuration(); 56584 } 56585 } 56586 } 56587 /** 56588 * dispose of the MasterPlaylistController and everything 56589 * that it controls 56590 */ 56591 56592 }, { 56593 key: 'dispose', 56594 value: function dispose() { 56595 var _this7 = this; 56596 56597 this.trigger('dispose'); 56598 56599 if (this.decrypter_) { 56600 this.decrypter_.terminate(); 56601 } 56602 56603 this.masterPlaylistLoader_.dispose(); 56604 this.mainSegmentLoader_.dispose(); 56605 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 56606 var groups = _this7.mediaTypes_[type].groups; 56607 56608 for (var id in groups) { 56609 groups[id].forEach(function (group) { 56610 if (group.playlistLoader) { 56611 group.playlistLoader.dispose(); 56612 } 56613 }); 56614 } 56615 }); 56616 this.audioSegmentLoader_.dispose(); 56617 this.subtitleSegmentLoader_.dispose(); 56618 this.off(); 56619 56620 if (this.mediaSource.dispose) { 56621 this.mediaSource.dispose(); 56622 } 56623 } 56624 /** 56625 * return the master playlist object if we have one 56626 * 56627 * @return {Object} the master playlist object that we parsed 56628 */ 56629 56630 }, { 56631 key: 'master', 56632 value: function master() { 56633 return this.masterPlaylistLoader_.master; 56634 } 56635 /** 56636 * return the currently selected playlist 56637 * 56638 * @return {Object} the currently selected playlist object that we parsed 56639 */ 56640 56641 }, { 56642 key: 'media', 56643 value: function media() { 56644 // playlist loader will not return media if it has not been fully loaded 56645 return this.masterPlaylistLoader_.media() || this.initialMedia_; 56646 } 56647 /** 56648 * setup our internal source buffers on our segment Loaders 56649 * 56650 * @private 56651 */ 56652 56653 }, { 56654 key: 'setupSourceBuffers_', 56655 value: function setupSourceBuffers_() { 56656 var media = this.masterPlaylistLoader_.media(); 56657 var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is 56658 // attached 56659 56660 if (!media || this.mediaSource.readyState !== 'open') { 56661 return; 56662 } 56663 56664 mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media); 56665 56666 if (mimeTypes.length < 1) { 56667 this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri; 56668 return this.mediaSource.endOfStream('decode'); 56669 } 56670 56671 this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist 56672 // selection 56673 56674 this.excludeIncompatibleVariants_(media); 56675 } 56676 }, { 56677 key: 'configureLoaderMimeTypes_', 56678 value: function configureLoaderMimeTypes_(mimeTypes) { 56679 // If the content is demuxed, we can't start appending segments to a source buffer 56680 // until both source buffers are set up, or else the browser may not let us add the 56681 // second source buffer (it will assume we are playing either audio only or video 56682 // only). 56683 var sourceBufferEmitter = // If there is more than one mime type 56684 mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio 56685 mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio 56686 // only with alternate audio) 56687 mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer 56688 new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only, 56689 // video only, or muxed content. 56690 null; 56691 this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter); 56692 56693 if (mimeTypes[1]) { 56694 this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter); 56695 } 56696 } 56697 /** 56698 * Blacklists playlists with codecs that are unsupported by the browser. 56699 */ 56700 56701 }, { 56702 key: 'excludeUnsupportedVariants_', 56703 value: function excludeUnsupportedVariants_() {
56704 this.master().playlists.forEach(function (variant) { 56705 if (variant.attributes.CODECS && window$3.MediaSource && window$3.MediaSource.isTypeSupported && !window$3.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) { 56706 variant.excludeUntil = Infinity; 56707 } 56708 }); 56709 } 56710 /** 56711 * Blacklist playlists that are known to be codec or 56712 * stream-incompatible with the SourceBuffer configuration. For 56713 * instance, Media Source Extensions would cause the video element to 56714 * stall waiting for video data if you switched from a variant with 56715 * video and audio to an audio-only one. 56716 * 56717 * @param {Object} media a media playlist compatible with the current 56718 * set of SourceBuffers. Variants in the current master playlist that 56719 * do not appear to have compatible codec or stream configurations 56720 * will be excluded from the default playlist selection algorithm 56721 * indefinitely. 56722 * @private 56723 */ 56724 56725 }, { 56726 key: 'excludeIncompatibleVariants_', 56727 value: function excludeIncompatibleVariants_(media) { 56728 var codecCount = 2; 56729 var videoCodec = null; 56730 var codecs = void 0; 56731 56732 if (media.attributes.CODECS) { 56733 codecs = parseCodecs(media.attributes.CODECS); 56734 videoCodec = codecs.videoCodec; 56735 codecCount = codecs.codecCount; 56736 } 56737
vendor: 29,861 bytes, lines 56738-57554
56738 this.master().playlists.forEach(function (variant) { 56739 var variantCodecs = { 56740 codecCount: 2, 56741 videoCodec: null 56742 }; 56743 56744 if (variant.attributes.CODECS) { 56745 variantCodecs = parseCodecs(variant.attributes.CODECS); 56746 } // if the streams differ in the presence or absence of audio or 56747 // video, they are incompatible 56748 56749 56750 if (variantCodecs.codecCount !== codecCount) { 56751 variant.excludeUntil = Infinity; 56752 } // if h.264 is specified on the current playlist, some flavor of 56753 // it must be specified on all compatible variants 56754 56755 56756 if (variantCodecs.videoCodec !== videoCodec) { 56757 variant.excludeUntil = Infinity; 56758 } 56759 }); 56760 } 56761 }, { 56762 key: 'updateAdCues_', 56763 value: function updateAdCues_(media) { 56764 var offset = 0; 56765 var seekable$$1 = this.seekable(); 56766 56767 if (seekable$$1.length) { 56768 offset = seekable$$1.start(0); 56769 } 56770 56771 updateAdCues(media, this.cueTagsTrack_, offset); 56772 } 56773 /** 56774 * Calculates the desired forward buffer length based on current time 56775 * 56776 * @return {Number} Desired forward buffer length in seconds 56777 */ 56778 56779 }, { 56780 key: 'goalBufferLength', 56781 value: function goalBufferLength() { 56782 var currentTime = this.tech_.currentTime(); 56783 var initial = Config.GOAL_BUFFER_LENGTH; 56784 var rate = Config.GOAL_BUFFER_LENGTH_RATE; 56785 var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH); 56786 return Math.min(initial + currentTime * rate, max); 56787 } 56788 /** 56789 * Calculates the desired buffer low water line based on current time 56790 * 56791 * @return {Number} Desired buffer low water line in seconds 56792 */ 56793 56794 }, { 56795 key: 'bufferLowWaterLine', 56796 value: function bufferLowWaterLine() { 56797 var currentTime = this.tech_.currentTime(); 56798 var initial = Config.BUFFER_LOW_WATER_LINE; 56799 var rate = Config.BUFFER_LOW_WATER_LINE_RATE; 56800 var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE); 56801 return Math.min(initial + currentTime * rate, max); 56802 } 56803 }]); 56804 return MasterPlaylistController; 56805 }(videojs$1.EventTarget); 56806 /** 56807 * Returns a function that acts as the Enable/disable playlist function. 56808 * 56809 * @param {PlaylistLoader} loader - The master playlist loader 56810 56811 * @param {string} playlistID - id of the playlist 56812 * @param {Function} changePlaylistFn - A function to be called after a 56813 * playlist's enabled-state has been changed. Will NOT be called if a 56814 * playlist's enabled-state is unchanged 56815 * @param {Boolean=} enable - Value to set the playlist enabled-state to 56816 * or if undefined returns the current enabled-state for the playlist 56817 * @return {Function} Function for setting/getting enabled 56818 */ 56819 56820 56821 var enableFunction = function enableFunction(loader, playlistID, changePlaylistFn) { 56822 return function (enable) { 56823 var playlist = loader.master.playlists[playlistID]; 56824 var incompatible = isIncompatible(playlist); 56825 var currentlyEnabled = isEnabled(playlist); 56826 56827 if (typeof enable === 'undefined') { 56828 return currentlyEnabled; 56829 } 56830 56831 if (enable) { 56832 delete playlist.disabled; 56833 } else { 56834 playlist.disabled = true; 56835 } 56836 56837 if (enable !== currentlyEnabled && !incompatible) { 56838 // Ensure the outside world knows about our changes 56839 changePlaylistFn(); 56840 56841 if (enable) { 56842 loader.trigger('renditionenabled'); 56843 } else { 56844 loader.trigger('renditiondisabled'); 56845 } 56846 } 56847 56848 return enable; 56849 }; 56850 }; 56851 /** 56852 * The representation object encapsulates the publicly visible information 56853 * in a media playlist along with a setter/getter-type function (enabled) 56854 * for changing the enabled-state of a particular playlist entry 56855 * 56856 * @class Representation 56857 */ 56858 56859 56860 var Representation = function Representation(hlsHandler, playlist, id) { 56861 classCallCheck$1(this, Representation); 56862 var mpc = hlsHandler.masterPlaylistController_, 56863 smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function 56864 56865 var changeType = smoothQualityChange ? 'smooth' : 'fast'; 56866 var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional 56867 56868 if (playlist.attributes.RESOLUTION) { 56869 var resolution = playlist.attributes.RESOLUTION; 56870 this.width = resolution.width; 56871 this.height = resolution.height; 56872 } 56873 56874 this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist 56875 // within the master playlist 56876 56877 this.id = id; // Partially-apply the enableFunction to create a playlist- 56878 // specific variant 56879 56880 this.enabled = enableFunction(hlsHandler.playlists, playlist.id, qualityChangeFunction); 56881 }; 56882 /** 56883 * A mixin function that adds the `representations` api to an instance 56884 * of the HlsHandler class 56885 * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the 56886 * representation API into 56887 */ 56888 56889 56890 var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) { 56891 var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance 56892 56893 hlsHandler.representations = function () { 56894 if (!playlists || !playlists.master || !playlists.master.playlists) { 56895 return []; 56896 } 56897 56898 return playlists.master.playlists.filter(function (media) { 56899 return !isIncompatible(media); 56900 }).map(function (e, i) { 56901 return new Representation(hlsHandler, e, e.id); 56902 }); 56903 }; 56904 }; 56905 /** 56906 * @file playback-watcher.js 56907 * 56908 * Playback starts, and now my watch begins. It shall not end until my death. I shall 56909 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns 56910 * and win no glory. I shall live and die at my post. I am the corrector of the underflow. 56911 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge 56912 * my life and honor to the Playback Watch, for this Player and all the Players to come. 56913 */ 56914 // Set of events that reset the playback-watcher time check logic and clear the timeout 56915 56916 56917 var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 56918 /** 56919 * @class PlaybackWatcher 56920 */ 56921 56922 var PlaybackWatcher = function () { 56923 /** 56924 * Represents an PlaybackWatcher object. 56925 * @constructor 56926 * @param {object} options an object that includes the tech and settings 56927 */ 56928 function PlaybackWatcher(options) { 56929 var _this = this; 56930 56931 classCallCheck$1(this, PlaybackWatcher); 56932 this.tech_ = options.tech; 56933 this.seekable = options.seekable; 56934 this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow; 56935 this.media = options.media; 56936 this.consecutiveUpdates = 0; 56937 this.lastRecordedTime = null; 56938 this.timer_ = null; 56939 this.checkCurrentTimeTimeout_ = null; 56940 this.logger_ = logger('PlaybackWatcher'); 56941 this.logger_('initialize'); 56942 56943 var canPlayHandler = function canPlayHandler() { 56944 return _this.monitorCurrentTime_(); 56945 }; 56946 56947 var waitingHandler = function waitingHandler() { 56948 return _this.techWaiting_(); 56949 }; 56950 56951 var cancelTimerHandler = function cancelTimerHandler() { 56952 return _this.cancelTimer_(); 56953 }; 56954 56955 var fixesBadSeeksHandler = function fixesBadSeeksHandler() { 56956 return _this.fixesBadSeeks_(); 56957 }; 56958 56959 this.tech_.on('seekablechanged', fixesBadSeeksHandler); 56960 this.tech_.on('waiting', waitingHandler); 56961 this.tech_.on(timerCancelEvents, cancelTimerHandler); 56962 this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events 56963 56964 this.dispose = function () { 56965 _this.logger_('dispose'); 56966 56967 _this.tech_.off('seekablechanged', fixesBadSeeksHandler); 56968 56969 _this.tech_.off('waiting', waitingHandler); 56970 56971 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 56972 56973 _this.tech_.off('canplay', canPlayHandler); 56974 56975 if (_this.checkCurrentTimeTimeout_) { 56976 window$3.clearTimeout(_this.checkCurrentTimeTimeout_); 56977 } 56978 56979 _this.cancelTimer_(); 56980 }; 56981 } 56982 /** 56983 * Periodically check current time to see if playback stopped 56984 * 56985 * @private 56986 */ 56987 56988 56989 createClass$1(PlaybackWatcher, [{ 56990 key: 'monitorCurrentTime_', 56991 value: function monitorCurrentTime_() { 56992 this.checkCurrentTime_(); 56993 56994 if (this.checkCurrentTimeTimeout_) { 56995 window$3.clearTimeout(this.checkCurrentTimeTimeout_); 56996 } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 56997 56998 56999 this.checkCurrentTimeTimeout_ = window$3.setTimeout(this.monitorCurrentTime_.bind(this), 250); 57000 } 57001 /** 57002 * The purpose of this function is to emulate the "waiting" event on 57003 * browsers that do not emit it when they are waiting for more 57004 * data to continue playback 57005 * 57006 * @private 57007 */ 57008 57009 }, { 57010 key: 'checkCurrentTime_', 57011 value: function checkCurrentTime_() { 57012 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 57013 this.consecutiveUpdates = 0; 57014 this.lastRecordedTime = this.tech_.currentTime(); 57015 return; 57016 } 57017 57018 if (this.tech_.paused() || this.tech_.seeking()) { 57019 return; 57020 } 57021 57022 var currentTime = this.tech_.currentTime(); 57023 var buffered = this.tech_.buffered(); 57024 57025 if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) { 57026 // If current time is at the end of the final buffered region, then any playback 57027 // stall is most likely caused by buffering in a low bandwidth environment. The tech 57028 // should fire a `waiting` event in this scenario, but due to browser and tech 57029 // inconsistencies. Calling `techWaiting_` here allows us to simulate 57030 // responding to a native `waiting` event when the tech fails to emit one. 57031 return this.techWaiting_(); 57032 } 57033 57034 if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) { 57035 this.consecutiveUpdates++; 57036 this.waiting_(); 57037 } else if (currentTime === this.lastRecordedTime) { 57038 this.consecutiveUpdates++; 57039 } else { 57040 this.consecutiveUpdates = 0; 57041 this.lastRecordedTime = currentTime; 57042 } 57043 } 57044 /** 57045 * Cancels any pending timers and resets the 'timeupdate' mechanism 57046 * designed to detect that we are stalled 57047 * 57048 * @private 57049 */ 57050 57051 }, { 57052 key: 'cancelTimer_', 57053 value: function cancelTimer_() { 57054 this.consecutiveUpdates = 0; 57055 57056 if (this.timer_) { 57057 this.logger_('cancelTimer_'); 57058 clearTimeout(this.timer_); 57059 } 57060 57061 this.timer_ = null; 57062 } 57063 /** 57064 * Fixes situations where there's a bad seek 57065 * 57066 * @return {Boolean} whether an action was taken to fix the seek 57067 * @private 57068 */ 57069 57070 }, { 57071 key: 'fixesBadSeeks_', 57072 value: function fixesBadSeeks_() { 57073 var seeking = this.tech_.seeking(); 57074 57075 if (!seeking) { 57076 return false; 57077 } 57078 57079 var seekable = this.seekable(); 57080 var currentTime = this.tech_.currentTime(); 57081 var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow); 57082 var seekTo = void 0; 57083 57084 if (isAfterSeekableRange) { 57085 var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting) 57086 57087 seekTo = seekableEnd; 57088 } 57089 57090 if (this.beforeSeekableWindow_(seekable, currentTime)) { 57091 var seekableStart = seekable.start(0); // sync to the beginning of the live window 57092 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 57093 57094 seekTo = seekableStart + SAFE_TIME_DELTA; 57095 } 57096 57097 if (typeof seekTo !== 'undefined') { 57098 this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.')); 57099 this.tech_.setCurrentTime(seekTo); 57100 return true; 57101 } 57102 57103 return false; 57104 } 57105 /** 57106 * Handler for situations when we determine the player is waiting. 57107 * 57108 * @private 57109 */ 57110 57111 }, { 57112 key: 'waiting_', 57113 value: function waiting_() { 57114 if (this.techWaiting_()) { 57115 return; 57116 } // All tech waiting checks failed. Use last resort correction 57117 57118 57119 var currentTime = this.tech_.currentTime(); 57120 var buffered = this.tech_.buffered(); 57121 var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered 57122 // region with no indication that anything is amiss (seen in Firefox). Seeking to 57123 // currentTime is usually enough to kickstart the player. This checks that the player 57124 // is currently within a buffered region before attempting a corrective seek. 57125 // Chrome does not appear to continue `timeupdate` events after a `waiting` event 57126 // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also 57127 // make sure there is ~3 seconds of forward buffer before taking any corrective action 57128 // to avoid triggering an `unknownwaiting` event when the network is slow. 57129 57130 if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { 57131 this.cancelTimer_(); 57132 this.tech_.setCurrentTime(currentTime); 57133 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 57134 57135 this.tech_.trigger({ 57136 type: 'usage', 57137 name: 'hls-unknown-waiting' 57138 }); 57139 return; 57140 } 57141 } 57142 /** 57143 * Handler for situations when the tech fires a `waiting` event 57144 * 57145 * @return {Boolean} 57146 * True if an action (or none) was needed to correct the waiting. False if no 57147 * checks passed 57148 * @private 57149 */ 57150 57151 }, { 57152 key: 'techWaiting_', 57153 value: function techWaiting_() { 57154 var seekable = this.seekable(); 57155 var currentTime = this.tech_.currentTime(); 57156 57157 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 57158 // Tech is seeking or bad seek fixed, no action needed 57159 return true; 57160 } 57161 57162 if (this.tech_.seeking() || this.timer_ !== null) { 57163 // Tech is seeking or already waiting on another action, no action needed 57164 return true; 57165 } 57166 57167 if (this.beforeSeekableWindow_(seekable, currentTime)) { 57168 var livePoint = seekable.end(seekable.length - 1); 57169 this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint)); 57170 this.cancelTimer_(); 57171 this.tech_.setCurrentTime(livePoint); // live window resyncs may be useful for monitoring QoS 57172 57173 this.tech_.trigger({ 57174 type: 'usage', 57175 name: 'hls-live-resync' 57176 }); 57177 return true; 57178 } 57179 57180 var buffered = this.tech_.buffered(); 57181 var nextRange = findNextRange(buffered, currentTime); 57182 57183 if (this.videoUnderflow_(nextRange, buffered, currentTime)) { 57184 // Even though the video underflowed and was stuck in a gap, the audio overplayed 57185 // the gap, leading currentTime into a buffered range. Seeking to currentTime 57186 // allows the video to catch up to the audio position without losing any audio 57187 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 57188 this.cancelTimer_(); 57189 this.tech_.setCurrentTime(currentTime); // video underflow may be useful for monitoring QoS 57190 57191 this.tech_.trigger({ 57192 type: 'usage', 57193 name: 'hls-video-underflow' 57194 }); 57195 return true; 57196 } // check for gap 57197 57198 57199 if (nextRange.length > 0) { 57200 var difference = nextRange.start(0) - currentTime; 57201 this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0))); 57202 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 57203 return true; 57204 } // All checks failed. Returning false to indicate failure to correct waiting 57205 57206 57207 return false; 57208 } 57209 }, { 57210 key: 'afterSeekableWindow_', 57211 value: function afterSeekableWindow_(seekable, currentTime, playlist) { 57212 var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; 57213 57214 if (!seekable.length) { 57215 // we can't make a solid case if there's no seekable, default to false 57216 return false; 57217 } 57218 57219 var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA; 57220 var isLive = !playlist.endList; 57221 57222 if (isLive && allowSeeksWithinUnsafeLiveWindow) { 57223 allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3; 57224 } 57225 57226 if (currentTime > allowedEnd) { 57227 return true; 57228 } 57229 57230 return false; 57231 } 57232 }, { 57233 key: 'beforeSeekableWindow_', 57234 value: function beforeSeekableWindow_(seekable, currentTime) { 57235 if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream 57236 seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) { 57237 return true; 57238 } 57239 57240 return false; 57241 } 57242 }, { 57243 key: 'videoUnderflow_', 57244 value: function videoUnderflow_(nextRange, buffered, currentTime) { 57245 if (nextRange.length === 0) { 57246 // Even if there is no available next range, there is still a possibility we are 57247 // stuck in a gap due to video underflow. 57248 var gap = this.gapFromVideoUnderflow_(buffered, currentTime); 57249 57250 if (gap) { 57251 this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime)); 57252 return true; 57253 } 57254 } 57255 57256 return false; 57257 } 57258 /** 57259 * Timer callback. If playback still has not proceeded, then we seek 57260 * to the start of the next buffered region. 57261 * 57262 * @private 57263 */ 57264 57265 }, { 57266 key: 'skipTheGap_', 57267 value: function skipTheGap_(scheduledCurrentTime) { 57268 var buffered = this.tech_.buffered(); 57269 var currentTime = this.tech_.currentTime(); 57270 var nextRange = findNextRange(buffered, currentTime); 57271 this.cancelTimer_(); 57272 57273 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 57274 return; 57275 } 57276 57277 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played 57278 57279 this.tech_.setCurrentTime(nextRange.start(0) + TIME_FUDGE_FACTOR); 57280 this.tech_.trigger({ 57281 type: 'usage', 57282 name: 'hls-gap-skip' 57283 }); 57284 } 57285 }, { 57286 key: 'gapFromVideoUnderflow_', 57287 value: function gapFromVideoUnderflow_(buffered, currentTime) { 57288 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 57289 // playing for ~3 seconds after the video gap starts. This is done to account for 57290 // video buffer underflow/underrun (note that this is not done when there is audio 57291 // buffer underflow/underrun -- in that case the video will stop as soon as it 57292 // encounters the gap, as audio stalls are more noticeable/jarring to a user than 57293 // video stalls). The player's time will reflect the playthrough of audio, so the 57294 // time will appear as if we are in a buffered region, even if we are stuck in a 57295 // "gap." 57296 // 57297 // Example: 57298 // video buffer: 0 => 10.1, 10.2 => 20 57299 // audio buffer: 0 => 20 57300 // overall buffer: 0 => 10.1, 10.2 => 20 57301 // current time: 13 57302 // 57303 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 57304 // however, the audio continued playing until it reached ~3 seconds past the gap 57305 // (13 seconds), at which point it stops as well. Since current time is past the 57306 // gap, findNextRange will return no ranges. 57307 // 57308 // To check for this issue, we see if there is a gap that starts somewhere within 57309 // a 3 second range (3 seconds +/- 1 second) back from our current time. 57310 var gaps = findGaps(buffered); 57311 57312 for (var i = 0; i < gaps.length; i++) { 57313 var start = gaps.start(i); 57314 var end = gaps.end(i); // gap is starts no more than 4 seconds back 57315 57316 if (currentTime - start < 4 && currentTime - start > 2) { 57317 return { 57318 start: start, 57319 end: end 57320 }; 57321 } 57322 } 57323 57324 return null; 57325 } 57326 }]); 57327 return PlaybackWatcher; 57328 }(); 57329 57330 var defaultOptions = { 57331 errorInterval: 30, 57332 getSource: function getSource(next) { 57333 var tech = this.tech({ 57334 IWillNotUseThisInPlugins: true 57335 }); 57336 var sourceObj = tech.currentSource_; 57337 return next(sourceObj); 57338 } 57339 }; 57340 /** 57341 * Main entry point for the plugin 57342 * 57343 * @param {Player} player a reference to a videojs Player instance 57344 * @param {Object} [options] an object with plugin options 57345 * @private 57346 */ 57347 57348 var initPlugin = function initPlugin(player, options) { 57349 var lastCalled = 0; 57350 var seekTo = 0; 57351 var localOptions = videojs$1.mergeOptions(defaultOptions, options); 57352 player.ready(function () { 57353 player.trigger({ 57354 type: 'usage', 57355 name: 'hls-error-reload-initialized' 57356 }); 57357 }); 57358 /** 57359 * Player modifications to perform that must wait until `loadedmetadata` 57360 * has been triggered 57361 * 57362 * @private 57363 */ 57364 57365 var loadedMetadataHandler = function loadedMetadataHandler() { 57366 if (seekTo) { 57367 player.currentTime(seekTo); 57368 } 57369 }; 57370 /** 57371 * Set the source on the player element, play, and seek if necessary 57372 * 57373 * @param {Object} sourceObj An object specifying the source url and mime-type to play 57374 * @private 57375 */ 57376 57377 57378 var setSource = function setSource(sourceObj) { 57379 if (sourceObj === null || sourceObj === undefined) { 57380 return; 57381 } 57382 57383 seekTo = player.duration() !== Infinity && player.currentTime() || 0; 57384 player.one('loadedmetadata', loadedMetadataHandler); 57385 player.src(sourceObj); 57386 player.trigger({ 57387 type: 'usage', 57388 name: 'hls-error-reload' 57389 }); 57390 player.play(); 57391 }; 57392 /** 57393 * Attempt to get a source from either the built-in getSource function 57394 * or a custom function provided via the options 57395 * 57396 * @private 57397 */ 57398 57399 57400 var errorHandler = function errorHandler() { 57401 // Do not attempt to reload the source if a source-reload occurred before 57402 // 'errorInterval' time has elapsed since the last source-reload 57403 if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { 57404 player.trigger({ 57405 type: 'usage', 57406 name: 'hls-error-reload-canceled' 57407 }); 57408 return; 57409 } 57410 57411 if (!localOptions.getSource || typeof localOptions.getSource !== 'function') { 57412 videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!'); 57413 return; 57414 } 57415 57416 lastCalled = Date.now(); 57417 return localOptions.getSource.call(player, setSource); 57418 }; 57419 /** 57420 * Unbind any event handlers that were bound by the plugin 57421 * 57422 * @private 57423 */ 57424 57425 57426 var cleanupEvents = function cleanupEvents() { 57427 player.off('loadedmetadata', loadedMetadataHandler); 57428 player.off('error', errorHandler); 57429 player.off('dispose', cleanupEvents); 57430 }; 57431 /** 57432 * Cleanup before re-initializing the plugin 57433 * 57434 * @param {Object} [newOptions] an object with plugin options 57435 * @private 57436 */ 57437 57438 57439 var reinitPlugin = function reinitPlugin(newOptions) { 57440 cleanupEvents(); 57441 initPlugin(player, newOptions); 57442 }; 57443 57444 player.on('error', errorHandler); 57445 player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before 57446 // initializing the plugin 57447 57448 player.reloadSourceOnError = reinitPlugin; 57449 }; 57450 /** 57451 * Reload the source when an error is detected as long as there 57452 * wasn't an error previously within the last 30 seconds 57453 * 57454 * @param {Object} [options] an object with plugin options 57455 */ 57456 57457 57458 var reloadSourceOnError = function reloadSourceOnError(options) { 57459 initPlugin(this, options); 57460 }; 57461 57462 var version$1 = "1.13.2"; 57463 /** 57464 * @file videojs-http-streaming.js 57465 * 57466 * The main file for the HLS project. 57467 * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE 57468 */ 57469 57470 var Hls$1 = { 57471 PlaylistLoader: PlaylistLoader, 57472 Playlist: Playlist, 57473 Decrypter: Decrypter, 57474 AsyncStream: AsyncStream, 57475 decrypt: decrypt, 57476 utils: utils$1, 57477 STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector, 57478 INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector, 57479 comparePlaylistBandwidth: comparePlaylistBandwidth, 57480 comparePlaylistResolution: comparePlaylistResolution, 57481 xhr: xhrFactory() 57482 }; // Define getter/setters for config properites 57483 57484 ['GOAL_BUFFER_LENGTH', 'MAX_GOAL_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) { 57485 Object.defineProperty(Hls$1, prop, { 57486 get: function get$$1() { 57487 videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 57488 return Config[prop]; 57489 }, 57490 set: function set$$1(value) { 57491 videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 57492 57493 if (typeof value !== 'number' || value < 0) { 57494 videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0'); 57495 return; 57496 } 57497 57498 Config[prop] = value; 57499 } 57500 }); 57501 }); 57502 var LOCAL_STORAGE_KEY$1 = 'videojs-vhs'; 57503 57504 var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) { 57505 var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 57506 57507 if (mpegurlRE.test(type)) { 57508 return 'hls'; 57509 } 57510 57511 var dashRE = /^application\/dash\+xml/i; 57512 57513 if (dashRE.test(type)) { 57514 return 'dash'; 57515 } 57516 57517 return null; 57518 }; 57519 /** 57520 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls. 57521 * 57522 * @param {QualityLevelList} qualityLevels The QualityLevelList to update. 57523 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info. 57524 * @function handleHlsMediaChange 57525 */ 57526 57527 57528 var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) { 57529 var newPlaylist = playlistLoader.media(); 57530 var selectedIndex = -1; 57531 57532 for (var i = 0; i < qualityLevels.length; i++) { 57533 if (qualityLevels[i].id === newPlaylist.id) { 57534 selectedIndex = i; 57535 break; 57536 } 57537 } 57538 57539 qualityLevels.selectedIndex_ = selectedIndex; 57540 qualityLevels.trigger({ 57541 selectedIndex: selectedIndex, 57542 type: 'change' 57543 }); 57544 }; 57545 /** 57546 * Adds quality levels to list once playlist metadata is available 57547 * 57548 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to. 57549 * @param {Object} hls Hls object to listen to for media events. 57550 * @function handleHlsLoadedMetadata 57551 */ 57552 57553 57554 var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
57555 hls.representations().forEach(function (rep) { 57556 qualityLevels.addQualityLevel(rep); 57557 }); 57558 handleHlsMediaChange(qualityLevels, hls.playlists); 57559 }; // HLS is a source handler, not a tech. Make sure attempts to use it 57560 // as one do not cause exceptions. 57561 57562 57563 Hls$1.canPlaySource = function () { 57564 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 57565 }; 57566 57567 var emeKeySystems = function emeKeySystems(keySystemOptions, mainSegmentLoader, audioSegmentLoader) { 57568 if (!keySystemOptions) { 57569 return keySystemOptions; 57570 } 57571 57572 var videoMimeType = void 0; 57573 var audioMimeType = void 0; // if there is a mimeType associated with the audioSegmentLoader, then the audio 57574 // and video mimeType and codec strings are already in the format we need to 57575 // pass with the other key systems 57576 57577 if (audioSegmentLoader.mimeType_) { 57578 videoMimeType = mainSegmentLoader.mimeType_; 57579 audioMimeType = audioSegmentLoader.mimeType_; // if there is no audioSegmentLoader mimeType, then we have to create the 57580 // the audio and video mimeType/codec strings from information extrapolated 57581 // from the mainSegmentLoader mimeType (ex. 'video/mp4; codecs="mp4, avc1"' --> 57582 // 'video/mp4; codecs="avc1"' and 'audio/mp4; codecs="mp4"') 57583 } else { 57584 var parsedMimeType = parseContentType(mainSegmentLoader.mimeType_); 57585 var codecs = parsedMimeType.parameters.codecs.split(','); 57586 var audioCodec = void 0; 57587 var videoCodec = void 0; 57588 codecs.forEach(function (codec) { 57589 codec = codec.trim(); 57590 57591 if (isAudioCodec(codec)) { 57592 audioCodec = codec; 57593 } else if (isVideoCodec(codec)) { 57594 videoCodec = codec; 57595 } 57596 }); 57597 videoMimeType = parsedMimeType.type + '; codecs="' + videoCodec + '"'; 57598 audioMimeType = parsedMimeType.type.replace('video', 'audio') + '; codecs="' + audioCodec + '"'; 57599 } // upsert the content types based on the selected playlist 57600 57601 57602 var keySystemContentTypes = {}; 57603 var videoPlaylist = mainSegmentLoader.playlist_; 57604 57605 for (var keySystem in keySystemOptions) { 57606 keySystemContentTypes[keySystem] = { 57607 audioContentType: audioMimeType, 57608 videoContentType: videoMimeType 57609 }; 57610 57611 if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) { 57612 keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh; 57613 } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url' 57614 // so we need to prevent overwriting the URL entirely 57615 57616 57617 if (typeof keySystemOptions[keySystem] === 'string') { 57618 keySystemContentTypes[keySystem].url = keySystemOptions[keySystem]; 57619 } 57620 } 57621 57622 return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes); 57623 }; 57624 57625 var setupEmeOptions = function setupEmeOptions(hlsHandler) { 57626 var mainSegmentLoader = hlsHandler.masterPlaylistController_.mainSegmentLoader_; 57627 var audioSegmentLoader = hlsHandler.masterPlaylistController_.audioSegmentLoader_; 57628 var player = videojs$1.players[hlsHandler.tech_.options_.playerId]; 57629 57630 if (player.eme) { 57631 var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, mainSegmentLoader, audioSegmentLoader); 57632 57633 if (sourceOptions) { 57634 player.currentSource().keySystems = sourceOptions; // Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 57635 // in non-IE11 browsers. In IE11 this is too early to initialize media keys 57636 57637 if (!(videojs$1.browser.IE_VERSION === 11) && player.eme.initializeMediaKeys) { 57638 player.eme.initializeMediaKeys(); 57639 } 57640 } 57641 } 57642 }; 57643 57644 var getVhsLocalStorage = function getVhsLocalStorage() { 57645 if (!window.localStorage) { 57646 return null; 57647 } 57648 57649 var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1); 57650 57651 if (!storedObject) { 57652 return null; 57653 } 57654
57655 try { 57656 return JSON.parse(storedObject); 57657 } catch (e) { 57658 // someone may have tampered with the value 57659 return null; 57660 } 57661 }; 57662 57663 var updateVhsLocalStorage = function updateVhsLocalStorage(options) { 57664 if (!window.localStorage) { 57665 return false; 57666 } 57667 57668 var objectToStore = getVhsLocalStorage(); 57669 objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options; 57670 57671 try { 57672 window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore)); 57673 } catch (e) { 57674 // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where 57675 // storage is set to 0). 57676 // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions 57677 // No need to perform any operation. 57678 return false; 57679 } 57680 57681 return objectToStore; 57682 }; 57683 /** 57684 * Whether the browser has built-in HLS support. 57685 */ 57686 57687 57688 Hls$1.supportsNativeHls = function () { 57689 var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't 57690 57691 if (!videojs$1.getTech('Html5').isSupported()) { 57692 return false; 57693 } // HLS manifests can go by many mime-types 57694 57695 57696 var canPlay = [// Apple santioned 57697 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility 57698 'audio/mpegurl', // Very common 57699 'audio/x-mpegurl', // Very common
vendor: 20,913 bytes, lines 57700-58234
57700 'application/x-mpegurl', // Included for completeness 57701 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 57702 return canPlay.some(function (canItPlay) { 57703 return /maybe|probably/i.test(video.canPlayType(canItPlay)); 57704 }); 57705 }(); 57706 57707 Hls$1.supportsNativeDash = function () { 57708 if (!videojs$1.getTech('Html5').isSupported()) { 57709 return false; 57710 } 57711 57712 return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml')); 57713 }(); 57714 57715 Hls$1.supportsTypeNatively = function (type) { 57716 if (type === 'hls') { 57717 return Hls$1.supportsNativeHls; 57718 } 57719 57720 if (type === 'dash') { 57721 return Hls$1.supportsNativeDash; 57722 } 57723 57724 return false; 57725 }; 57726 /** 57727 * HLS is a source handler, not a tech. Make sure attempts to use it 57728 * as one do not cause exceptions. 57729 */ 57730 57731 57732 Hls$1.isSupported = function () { 57733 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 57734 }; 57735 57736 var Component$1 = videojs$1.getComponent('Component'); 57737 /** 57738 * The Hls Handler object, where we orchestrate all of the parts 57739 * of HLS to interact with video.js 57740 * 57741 * @class HlsHandler 57742 * @extends videojs.Component 57743 * @param {Object} source the soruce object 57744 * @param {Tech} tech the parent tech object 57745 * @param {Object} options optional and required options 57746 */ 57747 57748 var HlsHandler = function (_Component) { 57749 inherits$2(HlsHandler, _Component); 57750 57751 function HlsHandler(source, tech, options) { 57752 classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for 57753 // backwards-compatibility 57754 57755 var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls)); 57756 57757 if (tech.options_ && tech.options_.playerId) { 57758 var _player = videojs$1(tech.options_.playerId); 57759 57760 if (!_player.hasOwnProperty('hls')) { 57761 Object.defineProperty(_player, 'hls', { 57762 get: function get$$1() { 57763 videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.'); 57764 tech.trigger({ 57765 type: 'usage', 57766 name: 'hls-player-access' 57767 }); 57768 return _this; 57769 }, 57770 configurable: true 57771 }); 57772 } // Set up a reference to the HlsHandler from player.vhs. This allows users to start 57773 // migrating from player.tech_.hls... to player.vhs... for API access. Although this 57774 // isn't the most appropriate form of reference for video.js (since all APIs should 57775 // be provided through core video.js), it is a common pattern for plugins, and vhs 57776 // will act accordingly. 57777 57778 57779 _player.vhs = _this; // deprecated, for backwards compatibility 57780 57781 _player.dash = _this; 57782 _this.player_ = _player; 57783 } 57784 57785 _this.tech_ = tech; 57786 _this.source_ = source; 57787 _this.stats = {}; 57788 _this.ignoreNextSeekingEvent_ = false; 57789 57790 _this.setOptions_(); 57791 57792 if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) { 57793 tech.overrideNativeAudioTracks(true); 57794 tech.overrideNativeVideoTracks(true); 57795 } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { 57796 // overriding native HLS only works if audio tracks have been emulated 57797 // error early if we're misconfigured 57798 throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB'); 57799 } // listen for fullscreenchange events for this player so that we 57800 // can adjust our quality selection quickly 57801 57802 57803 _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 57804 var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; 57805 57806 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 57807 _this.masterPlaylistController_.smoothQualityChange_(); 57808 } 57809 }); 57810 57811 _this.on(_this.tech_, 'seeking', function () { 57812 if (this.ignoreNextSeekingEvent_) { 57813 this.ignoreNextSeekingEvent_ = false; 57814 return; 57815 } 57816 57817 this.setCurrentTime(this.tech_.currentTime()); 57818 }); 57819 57820 _this.on(_this.tech_, 'error', function () { 57821 if (this.masterPlaylistController_) { 57822 this.masterPlaylistController_.pauseLoading(); 57823 } 57824 }); 57825 57826 _this.on(_this.tech_, 'play', _this.play); 57827 57828 return _this; 57829 } 57830 57831 createClass$1(HlsHandler, [{ 57832 key: 'setOptions_', 57833 value: function setOptions_() { 57834 var _this2 = this; // defaults 57835 57836 57837 this.options_.withCredentials = this.options_.withCredentials || false; 57838 this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false; 57839 this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true; 57840 this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false; 57841 this.options_.smoothQualityChange = this.options_.smoothQualityChange || false; 57842 this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false; 57843 this.options_.customTagParsers = this.options_.customTagParsers || []; 57844 this.options_.customTagMappers = this.options_.customTagMappers || []; 57845 this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false; 57846 57847 if (typeof this.options_.blacklistDuration !== 'number') { 57848 this.options_.blacklistDuration = 5 * 60; 57849 } 57850 57851 if (typeof this.options_.bandwidth !== 'number') { 57852 if (this.options_.useBandwidthFromLocalStorage) { 57853 var storedObject = getVhsLocalStorage(); 57854 57855 if (storedObject && storedObject.bandwidth) { 57856 this.options_.bandwidth = storedObject.bandwidth; 57857 this.tech_.trigger({ 57858 type: 'usage', 57859 name: 'hls-bandwidth-from-local-storage' 57860 }); 57861 } 57862 57863 if (storedObject && storedObject.throughput) { 57864 this.options_.throughput = storedObject.throughput; 57865 this.tech_.trigger({ 57866 type: 'usage', 57867 name: 'hls-throughput-from-local-storage' 57868 }); 57869 } 57870 } 57871 } // if bandwidth was not set by options or pulled from local storage, start playlist 57872 // selection at a reasonable bandwidth 57873 57874 57875 if (typeof this.options_.bandwidth !== 'number') { 57876 this.options_.bandwidth = Config.INITIAL_BANDWIDTH; 57877 } // If the bandwidth number is unchanged from the initial setting 57878 // then this takes precedence over the enableLowInitialPlaylist option 57879 57880 57881 this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src 57882 57883 ['withCredentials', 'useDevicePixelRatio', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys'].forEach(function (option) { 57884 if (typeof _this2.source_[option] !== 'undefined') { 57885 _this2.options_[option] = _this2.source_[option]; 57886 } 57887 }); 57888 this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions; 57889 this.useDevicePixelRatio = this.options_.useDevicePixelRatio; 57890 } 57891 /** 57892 * called when player.src gets called, handle a new source 57893 * 57894 * @param {Object} src the source object to handle 57895 */ 57896 57897 }, { 57898 key: 'src', 57899 value: function src(_src, type) { 57900 var _this3 = this; // do nothing if the src is falsey 57901 57902 57903 if (!_src) { 57904 return; 57905 } 57906 57907 this.setOptions_(); // add master playlist controller options 57908 57909 this.options_.url = this.source_.src; 57910 this.options_.tech = this.tech_; 57911 this.options_.externHls = Hls$1; 57912 this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update the tech 57913 57914 this.options_.seekTo = function (time) { 57915 _this3.tech_.setCurrentTime(time); 57916 }; 57917 57918 this.masterPlaylistController_ = new MasterPlaylistController(this.options_); 57919 this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, { 57920 seekable: function seekable$$1() { 57921 return _this3.seekable(); 57922 }, 57923 media: function media() { 57924 return _this3.masterPlaylistController_.media(); 57925 } 57926 })); 57927 this.masterPlaylistController_.on('error', function () { 57928 var player = videojs$1.players[_this3.tech_.options_.playerId]; 57929 player.error(_this3.masterPlaylistController_.error); 57930 }); // `this` in selectPlaylist should be the HlsHandler for backwards 57931 // compatibility with < v2 57932 57933 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this); 57934 this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2 57935 57936 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 57937 this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist 57938 // controller. Using a custom property for backwards compatibility 57939 // with < v2 57940 57941 Object.defineProperties(this, { 57942 selectPlaylist: { 57943 get: function get$$1() { 57944 return this.masterPlaylistController_.selectPlaylist; 57945 }, 57946 set: function set$$1(selectPlaylist) { 57947 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 57948 } 57949 }, 57950 throughput: { 57951 get: function get$$1() { 57952 return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; 57953 }, 57954 set: function set$$1(throughput) { 57955 this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value 57956 // for the cumulative average 57957 57958 this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; 57959 } 57960 }, 57961 bandwidth: { 57962 get: function get$$1() { 57963 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 57964 }, 57965 set: function set$$1(bandwidth) { 57966 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter 57967 // `count` is set to zero that current value of `rate` isn't included 57968 // in the cumulative average 57969 57970 this.masterPlaylistController_.mainSegmentLoader_.throughput = { 57971 rate: 0, 57972 count: 0 57973 }; 57974 } 57975 }, 57976 57977 /** 57978 * `systemBandwidth` is a combination of two serial processes bit-rates. The first 57979 * is the network bitrate provided by `bandwidth` and the second is the bitrate of 57980 * the entire process after that - decryption, transmuxing, and appending - provided 57981 * by `throughput`. 57982 * 57983 * Since the two process are serial, the overall system bandwidth is given by: 57984 * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput) 57985 */ 57986 systemBandwidth: { 57987 get: function get$$1() { 57988 var invBandwidth = 1 / (this.bandwidth || 1); 57989 var invThroughput = void 0; 57990 57991 if (this.throughput > 0) { 57992 invThroughput = 1 / this.throughput; 57993 } else { 57994 invThroughput = 0; 57995 } 57996 57997 var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput)); 57998 return systemBitrate; 57999 }, 58000 set: function set$$1() { 58001 videojs$1.log.error('The "systemBandwidth" property is read-only'); 58002 } 58003 } 58004 }); 58005 58006 if (this.options_.bandwidth) { 58007 this.bandwidth = this.options_.bandwidth; 58008 } 58009 58010 if (this.options_.throughput) { 58011 this.throughput = this.options_.throughput; 58012 } 58013 58014 Object.defineProperties(this.stats, { 58015 bandwidth: { 58016 get: function get$$1() { 58017 return _this3.bandwidth || 0; 58018 }, 58019 enumerable: true 58020 }, 58021 mediaRequests: { 58022 get: function get$$1() { 58023 return _this3.masterPlaylistController_.mediaRequests_() || 0; 58024 }, 58025 enumerable: true 58026 }, 58027 mediaRequestsAborted: { 58028 get: function get$$1() { 58029 return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0; 58030 }, 58031 enumerable: true 58032 }, 58033 mediaRequestsTimedout: { 58034 get: function get$$1() { 58035 return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0; 58036 }, 58037 enumerable: true 58038 }, 58039 mediaRequestsErrored: { 58040 get: function get$$1() { 58041 return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0; 58042 }, 58043 enumerable: true 58044 }, 58045 mediaTransferDuration: { 58046 get: function get$$1() { 58047 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 58048 }, 58049 enumerable: true 58050 }, 58051 mediaBytesTransferred: { 58052 get: function get$$1() { 58053 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 58054 }, 58055 enumerable: true 58056 }, 58057 mediaSecondsLoaded: { 58058 get: function get$$1() { 58059 return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0; 58060 }, 58061 enumerable: true 58062 }, 58063 buffered: { 58064 get: function get$$1() { 58065 return timeRangesToArray(_this3.tech_.buffered()); 58066 }, 58067 enumerable: true 58068 }, 58069 currentTime: { 58070 get: function get$$1() { 58071 return _this3.tech_.currentTime(); 58072 }, 58073 enumerable: true 58074 }, 58075 currentSource: { 58076 get: function get$$1() { 58077 return _this3.tech_.currentSource_; 58078 }, 58079 enumerable: true 58080 }, 58081 currentTech: { 58082 get: function get$$1() { 58083 return _this3.tech_.name_; 58084 }, 58085 enumerable: true 58086 }, 58087 duration: { 58088 get: function get$$1() { 58089 return _this3.tech_.duration(); 58090 }, 58091 enumerable: true 58092 }, 58093 master: { 58094 get: function get$$1() { 58095 return _this3.playlists.master; 58096 }, 58097 enumerable: true 58098 }, 58099 playerDimensions: { 58100 get: function get$$1() { 58101 return _this3.tech_.currentDimensions(); 58102 }, 58103 enumerable: true 58104 }, 58105 seekable: { 58106 get: function get$$1() { 58107 return timeRangesToArray(_this3.tech_.seekable()); 58108 }, 58109 enumerable: true 58110 }, 58111 timestamp: { 58112 get: function get$$1() { 58113 return Date.now(); 58114 }, 58115 enumerable: true 58116 }, 58117 videoPlaybackQuality: { 58118 get: function get$$1() { 58119 return _this3.tech_.getVideoPlaybackQuality(); 58120 }, 58121 enumerable: true 58122 } 58123 }); 58124 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 58125 this.tech_.on('bandwidthupdate', function () { 58126 if (_this3.options_.useBandwidthFromLocalStorage) { 58127 updateVhsLocalStorage({ 58128 bandwidth: _this3.bandwidth, 58129 throughput: Math.round(_this3.throughput) 58130 }); 58131 } 58132 }); 58133 this.masterPlaylistController_.on('selectedinitialmedia', function () { 58134 // Add the manual rendition mix-in to HlsHandler 58135 renditionSelectionMixin(_this3); 58136 setupEmeOptions(_this3); 58137 }); // the bandwidth of the primary segment loader is our best 58138 // estimate of overall bandwidth 58139 58140 this.on(this.masterPlaylistController_, 'progress', function () { 58141 this.tech_.trigger('progress'); 58142 }); // In the live case, we need to ignore the very first `seeking` event since 58143 // that will be the result of the seek-to-live behavior 58144 58145 this.on(this.masterPlaylistController_, 'firstplay', function () { 58146 this.ignoreNextSeekingEvent_ = true; 58147 }); 58148 this.setupQualityLevels_(); // do nothing if the tech has been disposed already 58149 // this can occur if someone sets the src in player.ready(), for instance 58150 58151 if (!this.tech_.el()) { 58152 return; 58153 } 58154 58155 this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource)); 58156 } 58157 /** 58158 * Initializes the quality levels and sets listeners to update them. 58159 * 58160 * @method setupQualityLevels_ 58161 * @private 58162 */ 58163 58164 }, { 58165 key: 'setupQualityLevels_', 58166 value: function setupQualityLevels_() { 58167 var _this4 = this; 58168 58169 var player = videojs$1.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin 58170 // or qualityLevels_ listeners have already been setup, do nothing. 58171 58172 if (!player || !player.qualityLevels || this.qualityLevels_) { 58173 return; 58174 } 58175 58176 this.qualityLevels_ = player.qualityLevels(); 58177 this.masterPlaylistController_.on('selectedinitialmedia', function () { 58178 handleHlsLoadedMetadata(_this4.qualityLevels_, _this4); 58179 }); 58180 this.playlists.on('mediachange', function () { 58181 handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists); 58182 }); 58183 } 58184 /** 58185 * Begin playing the video. 58186 */ 58187 58188 }, { 58189 key: 'play', 58190 value: function play() { 58191 this.masterPlaylistController_.play(); 58192 } 58193 /** 58194 * a wrapper around the function in MasterPlaylistController 58195 */ 58196 58197 }, { 58198 key: 'setCurrentTime', 58199 value: function setCurrentTime(currentTime) { 58200 this.masterPlaylistController_.setCurrentTime(currentTime); 58201 } 58202 /** 58203 * a wrapper around the function in MasterPlaylistController 58204 */ 58205 58206 }, { 58207 key: 'duration', 58208 value: function duration$$1() { 58209 return this.masterPlaylistController_.duration(); 58210 } 58211 /** 58212 * a wrapper around the function in MasterPlaylistController 58213 */ 58214 58215 }, { 58216 key: 'seekable', 58217 value: function seekable$$1() { 58218 return this.masterPlaylistController_.seekable(); 58219 } 58220 /** 58221 * Abort all outstanding work and cleanup. 58222 */ 58223 58224 }, { 58225 key: 'dispose', 58226 value: function dispose() { 58227 if (this.playbackWatcher_) { 58228 this.playbackWatcher_.dispose(); 58229 } 58230 58231 if (this.masterPlaylistController_) { 58232 this.masterPlaylistController_.dispose(); 58233 } 58234
58235 if (this.qualityLevels_) { 58236 this.qualityLevels_.dispose(); 58237 } 58238 58239 if (this.player_) { 58240 delete this.player_.vhs; 58241 delete this.player_.dash; 58242 delete this.player_.hls; 58243 } 58244 58245 if (this.tech_ && this.tech_.hls) { 58246 delete this.tech_.hls; 58247 } 58248 58249 get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this); 58250 } 58251 }, { 58252 key: 'convertToProgramTime', 58253 value: function convertToProgramTime(time, callback) { 58254 return getProgramTime({ 58255 playlist: this.masterPlaylistController_.media(), 58256 time: time, 58257 callback: callback 58258 }); 58259 } // the player must be playing before calling this 58260 58261 }, { 58262 key: 'seekToProgramTime', 58263 value: function seekToProgramTime$$1(programTime, callback) { 58264 var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 58265 var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2; 58266 return seekToProgramTime({ 58267 programTime: programTime, 58268 playlist: this.masterPlaylistController_.media(), 58269 retryCount: retryCount, 58270 pauseAfterSeek: pauseAfterSeek, 58271 seekTo: this.options_.seekTo, 58272 tech: this.options_.tech, 58273 callback: callback 58274 }); 58275 } 58276 }]); 58277 return HlsHandler; 58278 }(Component$1); 58279 /** 58280 * The Source Handler object, which informs video.js what additional 58281 * MIME types are supported and sets up playback. It is registered 58282 * automatically to the appropriate tech based on the capabilities of 58283 * the browser it is running in. It is not necessary to use or modify 58284 * this object in normal usage. 58285 */ 58286 58287 58288 var HlsSourceHandler = { 58289 name: 'videojs-http-streaming', 58290 VERSION: version$1, 58291 canHandleSource: function canHandleSource(srcObj) { 58292 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 58293 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 58294 return HlsSourceHandler.canPlayType(srcObj.type, localOptions); 58295 }, 58296 handleSource: function handleSource(source, tech) { 58297 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 58298 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 58299 tech.hls = new HlsHandler(source, tech, localOptions); 58300 tech.hls.xhr = xhrFactory(); 58301 tech.hls.src(source.src, source.type); 58302 return tech.hls; 58303 }, 58304 canPlayType: function canPlayType(type) { 58305 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 58306 58307 var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options), 58308 overrideNative = _videojs$mergeOptions.hls.overrideNative; 58309 58310 var supportedType = simpleTypeFromSourceType(type); 58311 var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative); 58312 return canUseMsePlayback ? 'maybe' : ''; 58313 } 58314 }; 58315 58316 if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') { 58317 videojs$1.MediaSource = MediaSource; 58318 videojs$1.URL = URL$1; 58319 } // register source handlers with the appropriate techs 58320 58321 58322 if (MediaSource.supportsNativeMediaSources()) { 58323 videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0); 58324 } 58325 58326 videojs$1.HlsHandler = HlsHandler; 58327 videojs$1.HlsSourceHandler = HlsSourceHandler; 58328 videojs$1.Hls = Hls$1; 58329 58330 if (!videojs$1.use) { 58331 videojs$1.registerComponent('Hls', Hls$1); 58332 } 58333 58334 videojs$1.options.hls = videojs$1.options.hls || {}; 58335 58336 if (videojs$1.registerPlugin) { 58337 videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError); 58338 } else { 58339 videojs$1.plugin('reloadSourceOnError', reloadSourceOnError); 58340 } 58341 58342 return videojs$1; 58343 58344 }))); 58345 58346 !function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.8.4")}(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.