1/** 2 * @license 3 * Video.js 7.7.5 <http://videojs.com/> 4 * Copyright Brightcove, Inc. <https://www.brightcove.com/> 5 * Available under Apache License Version 2.0 6 * <https://github.com/videojs/video.js/blob/master/LICENSE> 7 * 8 * Includes vtt.js <https://github.com/mozilla/vtt.js> 9 * Available under Apache License Version 2.0 10 * <https://github.com/mozilla/vtt.js/blob/master/LICENSE> 11 */ 12 13(function (global, factory) { 14 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('global/window'), require('global/document')) : 15 typeof define === 'function' && define.amd ? define(['global/window', 'global/document'], factory) : 16 (global = global || self, global.videojs = factory(global.window, global.document)); 17}(this, function (window$1, document) { 'use strict'; 18 19 window$1 = window$1 && window$1.hasOwnProperty('default') ? window$1['default'] : window$1; 20 document = document && document.hasOwnProperty('default') ? document['default'] : document; 21 22 var version = "7.7.5"; 23 24 /** 25 * @file create-logger.js 26 * @module create-logger 27 */ 28 29 var history = []; 30 /** 31 * Log messages to the console and history based on the type of message 32 * 33 * @private 34 * @param {string} type 35 * The name of the console method to use. 36 * 37 * @param {Array} args 38 * The arguments to be passed to the matching console method. 39 */ 40 41 var LogByTypeFactory = function LogByTypeFactory(name, log) { 42 return function (type, level, args) { 43 var lvl = log.levels[level]; 44 var lvlRegExp = new RegExp("^(" + lvl + ")$"); 45 46 if (type !== 'log') { 47 // Add the type to the front of the message when it's not "log". 48 args.unshift(type.toUpperCase() + ':'); 49 } // Add console prefix after adding to history. 50 51 52 args.unshift(name + ':'); // Add a clone of the args at this point to history. 53 54 if (history) { 55 history.push([].concat(args)); // only store 1000 history entries 56 57 var splice = history.length - 1000; 58 history.splice(0, splice > 0 ? splice : 0); 59 } // If there's no console then don't try to output messages, but they will 60 // still be stored in history. 61 62 63 if (!window$1.console) { 64 return; 65 } // Was setting these once outside of this function, but containing them 66 // in the function makes it easier to test cases where console doesn't exist 67 // when the module is executed. 68 69 70 var fn = window$1.console[type]; 71 72 if (!fn && type === 'debug') { 73 // Certain browsers don't have support for console.debug. For those, we 74 // should default to the closest comparable log. 75 fn = window$1.console.info || window$1.console.log; 76 } // Bail out if there's no console or if this type is not allowed by the 77 // current logging level. 78 79 80 if (!fn || !lvl || !lvlRegExp.test(type)) { 81 return; 82 } 83 84 fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args); 85 }; 86 }; 87 88 function createLogger(name) { 89 // This is the private tracking variable for logging level. 90 var level = 'info'; // the curried logByType bound to the specific log and history 91 92 var logByType; 93 /** 94 * Logs plain debug messages. Similar to `console.log`. 95 * 96 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 97 * of our JSDoc template, we cannot properly document this as both a function 98 * and a namespace, so its function signature is documented here. 99 * 100 * #### Arguments 101 * ##### *args 102 * Mixed[] 103 * 104 * Any combination of values that could be passed to `console.log()`. 105 * 106 * #### Return Value 107 * 108 * `undefined` 109 * 110 * @namespace 111 * @param {Mixed[]} args 112 * One or more messages or objects that should be logged. 113 */ 114 115 var log = function log() { 116 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 117 args[_key] = arguments[_key]; 118 } 119
vendor: 4,441 bytes, lines 120-271
120 logByType('log', level, args); 121 }; // This is the logByType helper that the logging methods below use 122 123 124 logByType = LogByTypeFactory(name, log); 125 /** 126 * Create a new sublogger which chains the old name to the new name. 127 * 128 * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following: 129 * ```js 130 * mylogger('foo'); 131 * // > VIDEOJS: player: foo 132 * ``` 133 * 134 * @param {string} name 135 * The name to add call the new logger 136 * @return {Object} 137 */ 138 139 log.createLogger = function (subname) { 140 return createLogger(name + ': ' + subname); 141 }; 142 /** 143 * Enumeration of available logging levels, where the keys are the level names 144 * and the values are `|`-separated strings containing logging methods allowed 145 * in that logging level. These strings are used to create a regular expression 146 * matching the function name being called. 147 * 148 * Levels provided by Video.js are: 149 * 150 * - `off`: Matches no calls. Any value that can be cast to `false` will have 151 * this effect. The most restrictive. 152 * - `all`: Matches only Video.js-provided functions (`debug`, `log`, 153 * `log.warn`, and `log.error`). 154 * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. 155 * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. 156 * - `warn`: Matches `log.warn` and `log.error` calls. 157 * - `error`: Matches only `log.error` calls. 158 * 159 * @type {Object} 160 */ 161 162 163 log.levels = { 164 all: 'debug|log|warn|error', 165 off: '', 166 debug: 'debug|log|warn|error', 167 info: 'log|warn|error', 168 warn: 'warn|error', 169 error: 'error', 170 DEFAULT: level 171 }; 172 /** 173 * Get or set the current logging level. 174 * 175 * If a string matching a key from {@link module:log.levels} is provided, acts 176 * as a setter. 177 * 178 * @param {string} [lvl] 179 * Pass a valid level to set a new logging level. 180 * 181 * @return {string} 182 * The current logging level. 183 */ 184 185 log.level = function (lvl) { 186 if (typeof lvl === 'string') { 187 if (!log.levels.hasOwnProperty(lvl)) { 188 throw new Error("\"" + lvl + "\" in not a valid log level"); 189 } 190 191 level = lvl; 192 } 193 194 return level; 195 }; 196 /** 197 * Returns an array containing everything that has been logged to the history. 198 * 199 * This array is a shallow clone of the internal history record. However, its 200 * contents are _not_ cloned; so, mutating objects inside this array will 201 * mutate them in history. 202 * 203 * @return {Array} 204 */ 205 206 207 log.history = function () { 208 return history ? [].concat(history) : []; 209 }; 210 /** 211 * Allows you to filter the history by the given logger name 212 * 213 * @param {string} fname 214 * The name to filter by 215 * 216 * @return {Array} 217 * The filtered list to return 218 */ 219 220 221 log.history.filter = function (fname) { 222 return (history || []).filter(function (historyItem) { 223 // if the first item in each historyItem includes `fname`, then it's a match 224 return new RegExp(".*" + fname + ".*").test(historyItem[0]); 225 }); 226 }; 227 /** 228 * Clears the internal history tracking, but does not prevent further history 229 * tracking. 230 */ 231 232 233 log.history.clear = function () { 234 if (history) { 235 history.length = 0; 236 } 237 }; 238 /** 239 * Disable history tracking if it is currently enabled. 240 */ 241 242 243 log.history.disable = function () { 244 if (history !== null) { 245 history.length = 0; 246 history = null; 247 } 248 }; 249 /** 250 * Enable history tracking if it is currently disabled. 251 */ 252 253 254 log.history.enable = function () { 255 if (history === null) { 256 history = []; 257 } 258 }; 259 /** 260 * Logs error messages. Similar to `console.error`. 261 * 262 * @param {Mixed[]} args 263 * One or more messages or objects that should be logged as an error 264 */ 265 266 267 log.error = function () { 268 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 269 args[_key2] = arguments[_key2]; 270 } 271
272 return logByType('error', level, args); 273 }; 274 /** 275 * Logs warning messages. Similar to `console.warn`. 276 * 277 * @param {Mixed[]} args 278 * One or more messages or objects that should be logged as a warning. 279 */ 280 281 282 log.warn = function () { 283 for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 284 args[_key3] = arguments[_key3]; 285 } 286 287 return logByType('warn', level, args); 288 }; 289 /** 290 * Logs debug messages. Similar to `console.debug`, but may also act as a comparable 291 * log if `console.debug` is not available 292 * 293 * @param {Mixed[]} args 294 * One or more messages or objects that should be logged as debug. 295 */ 296 297 298 log.debug = function () { 299 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 300 args[_key4] = arguments[_key4]; 301 } 302 303 return logByType('debug', level, args); 304 }; 305 306 return log; 307 } 308 309 /** 310 * @file log.js 311 * @module log 312 */ 313 var log = createLogger('VIDEOJS'); 314 var createLogger$1 = log.createLogger; 315 316 function 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$1.getComputedStyle === 'function') { 513 var computedStyleValue = window$1.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$1.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$1.parent !== window$1.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);
vendor: 9,845 bytes, lines 680-1008
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 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 supp
1008ort) 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$1.pageXOffset || body.scrollLeft; 1091 var left = box.left + scrollLeft - clientLeft; 1092 var clientTop = docEl.clientTop || body.clientTop || 0; 1093 var scrollTop = window$1.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 isReal: isReal, 1365 isEl: isEl, 1366 isInFrame: isInFrame, 1367 createEl: createEl, 1368 textContent: textContent, 1369 prependTo: prependTo, 1370 hasClass: hasClass, 1371 addClass: addClass, 1372 removeClass: removeClass, 1373 toggleClass: toggleClass, 1374 setAttributes: setAttributes, 1375 getAttributes: getAttributes, 1376 getAttribute: getAttribute, 1377 setAttribute: setAttribute, 1378 removeAttribute: removeAttribute, 1379 blockTextSelection: blockTextSelection, 1380 unblockTextSelection: unblockTextSelection, 1381 getBoundingClientRect: getBoundingClientRect, 1382 findPosition: findPosition, 1383 getPointerPosition: getPointerPosition, 1384 isTextNode: isTextNode, 1385 emptyEl: emptyEl, 1386 normalizeContent: normalizeContent, 1387 appendContent: appendContent, 1388 insertContent: insertContent, 1389 isSingleLeftClick: isSingleLeftClick, 1390 $: $, 1391 $$: $$ 1392 }); 1393 1394 /** 1395 * @file setup.js - Functions for setting up a player without 1396 * user interaction based on the data-setup `attribute` of the video tag. 1397 * 1398 * @module setup 1399 */ 1400 var _windowLoaded = false; 1401 var videojs; 1402 /** 1403 * Set up any tags that have a data-setup `attribute` when the player is started. 1404 */ 1405 1406 var autoSetup = function autoSetup() { 1407 // Protect against breakage in non-browser environments and check global autoSetup option. 1408 if (!isReal() || videojs.options.autoSetup === false) { 1409 return; 1410 } 1411 1412 var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); 1413 var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); 1414 var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js')); 1415 var mediaEls = vids.concat(audios, divs); // Check if any media elements exist 1416 1417 if (mediaEls && mediaEls.length > 0) { 1418 for (var i = 0, e = mediaEls.length; i < e; i++) { 1419 var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. 1420 1421 if (mediaEl && mediaEl.getAttribute) { 1422 // Make sure this player hasn't already been set up. 1423 if (mediaEl.player === undefined) { 1424 var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. 1425 // We only auto-setup if they've added the data-setup attr. 1426 1427 if (options !== null) { 1428 // Create new video.js instance. 1429 videojs(mediaEl); 1430 } 1431 } // If getAttribute isn't defined, we need to wait for the DOM. 1432 1433 } else { 1434 autoSetupTimeout(1); 1435 break; 1436 } 1437 } // No videos were found, so keep looping unless page is finished loading. 1438 1439 } else if (!_windowLoaded) { 1440 autoSetupTimeout(1); 1441 } 1442 }; 1443 /** 1444 * Wait until the page is loaded before running autoSetup. This will be called in 1445 * autoSetup if `hasLoaded` returns false. 1446 * 1447 * @param {number} wait 1448 * How long to wait in ms 1449 * 1450 * @param {module:videojs} [vjs] 1451 * The videojs library function 1452 */ 1453 1454 1455 function autoSetupTimeout(wait, vjs) { 1456 if (vjs) { 1457 videojs = vjs; 1458 } 1459 1460 window$1.setTimeout(autoSetup, wait); 1461 } 1462 /** 1463 * Used to set the internal tracking of window loaded state to true. 1464 * 1465 * @private 1466 */ 1467 1468 1469 function setWindowLoaded() { 1470 _windowLoaded = true; 1471 window$1.removeEventListener('load', setWindowLoaded); 1472 } 1473 1474 if (isReal()) { 1475 if (document.readyState === 'complete') { 1476 setWindowLoaded(); 1477 } else { 1478 /** 1479 * Listen for the load event on window, and set _windowLoaded to true. 1480 * 1481 * We use a standard event listener here to avoid incrementing the GUID 1482 * before any players are created. 1483 * 1484 * @listens load 1485 */ 1486 window$1.addEventListener('load', setWindowLoaded); 1487 } 1488 } 1489 1490 /** 1491 * @file stylesheet.js 1492 * @module stylesheet 1493 */ 1494 /** 1495 * Create a DOM syle element given a className for it. 1496 * 1497 * @param {string} className 1498 * The className to add to the created style element. 1499 * 1500 * @return {Element} 1501 * The element that was created. 1502 */ 1503 1504 var createStyleElement = function createStyleElement(className) { 1505 var style = document.createElement('style'); 1506 style.className = className; 1507 return style; 1508 }; 1509 /** 1510 * Add text to a DOM element. 1511 * 1512 * @param {Element} el 1513 * The Element to add text content to. 1514 * 1515 * @param {string} content 1516 * The text to add to the element. 1517 */ 1518 1519 var setTextContent = function setTextContent(el, content) { 1520 if (el.styleSheet) { 1521 el.styleSheet.cssText = content; 1522 } else { 1523 el.textContent = content; 1524 } 1525 }; 1526 1527 /** 1528 * @file guid.js 1529 * @module guid 1530 */ 1531 // Default value for GUIDs. This allows us to reset the GUID counter in tests. 1532 // 1533 // The initial GUID is 3 because some users have come to rely on the first 1534 // default player ID ending up as `vjs_video_3`. 1535 // 1536 // See: https://github.com/videojs/video.js/pull/6216 1537 var _initialGuid = 3; 1538 /** 1539 * Unique ID for an element or function 1540 * 1541 * @type {Number} 1542 */ 1543 1544 var _guid = _initialGuid; 1545 /** 1546 * Get a unique auto-incrementing ID by number that has not been returned before. 1547 * 1548 * @return {number} 1549 * A new unique ID. 1550 */ 1551 1552 function newGUID() { 1553 return _guid++; 1554 } 1555 1556 /** 1557 * @file dom-data.js 1558 * @module dom-data 1559 */ 1560 var FakeWeakMap; 1561 1562 if (!window$1.WeakMap) { 1563 FakeWeakMap = 1564 /*#__PURE__*/ 1565 function () { 1566 function FakeWeakMap() { 1567 this.vdata = 'vdata' + Math.floor(window$1.performance && window$1.performance.now() || Date.now()); 1568 this.data = {}; 1569 } 1570 1571 var _proto = FakeWeakMap.prototype; 1572 1573 _proto.set = function set(key, value) { 1574 var access = key[this.vdata] || newGUID(); 1575 1576 if (!key[this.vdata]) { 1577 key[this.vdata] = access; 1578 } 1579 1580 this.data[access] = value; 1581 return this; 1582 }; 1583 1584 _proto.get = function get(key) { 1585 var access = key[this.vdata]; // we have data, return it 1586 1587 if (access) { 1588 return this.data[access]; 1589 } // we don't have data, return nothing. 1590 // return undefined explicitly as that's the contract for this method 1591 1592 1593 log('We have no data for this element', key); 1594 return undefined; 1595 }; 1596 1597 _proto.has = function has(key) { 1598 var access = key[this.vdata]; 1599 return access in this.data; 1600 }; 1601 1602 _proto["delete"] = function _delete(key) { 1603 var access = key[this.vdata]; 1604 1605 if (access) { 1606 delete this.data[access]; 1607 delete key[this.vdata]; 1608 } 1609 }; 1610 1611 return FakeWeakMap; 1612 }(); 1613 } 1614 /** 1615 * Element Data Store. 1616 * 1617 * Allows for binding data to an element without putting it directly on the 1618 * element. Ex. Event listeners are stored here. 1619 * (also from jsninja.com, slightly modified and updated for closure compiler) 1620 * 1621 * @type {Object} 1622 * @private 1623 */ 1624 1625 1626 var DomData = window$1.WeakMap ? new WeakMap() : new FakeWeakMap(); 1627 1628 /** 1629 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) 1630 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) 1631 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as 1632 * robust as jquery's, so there's probably some differences. 1633 * 1634 * @file events.js 1635 * @module events 1636 */ 1637 /** 1638 * Clean up the listener cache and dispatchers 1639 *
vendor: 6,367 bytes, lines 1640-1831
1640 * @param {Element|Object} elem 1641 * Element to clean up 1642 * 1643 * @param {string} type 1644 * Type of event to clean up 1645 */ 1646 1647 function _cleanUpEvents(elem, type) { 1648 if (!DomData.has(elem)) { 1649 return; 1650 } 1651 1652 var data = DomData.get(elem); // Remove the events of a particular type if there are none left 1653 1654 if (data.handlers[type].length === 0) { 1655 delete data.handlers[type]; // data.handlers[type] = null; 1656 // Setting to null was causing an error with data.handlers 1657 // Remove the meta-handler from the element 1658 1659 if (elem.removeEventListener) { 1660 elem.removeEventListener(type, data.dispatcher, false); 1661 } else if (elem.detachEvent) { 1662 elem.detachEvent('on' + type, data.dispatcher); 1663 } 1664 } // Remove the events object if there are no types left 1665 1666 1667 if (Object.getOwnPropertyNames(data.handlers).length <= 0) { 1668 delete data.handlers; 1669 delete data.dispatcher; 1670 delete data.disabled; 1671 } // Finally remove the element data if there is no data left 1672 1673 1674 if (Object.getOwnPropertyNames(data).length === 0) { 1675 DomData["delete"](elem); 1676 } 1677 } 1678 /** 1679 * Loops through an array of event types and calls the requested method for each type. 1680 * 1681 * @param {Function} fn 1682 * The event method we want to use. 1683 * 1684 * @param {Element|Object} elem 1685 * Element or object to bind listeners to 1686 * 1687 * @param {string} type 1688 * Type of event to bind to. 1689 * 1690 * @param {EventTarget~EventListener} callback 1691 * Event listener. 1692 */ 1693 1694 1695 function _handleMultipleEvents(fn, elem, types, callback) { 1696 types.forEach(function (type) { 1697 // Call the event method for each one of the types 1698 fn(elem, type, callback); 1699 }); 1700 } 1701 /** 1702 * Fix a native event to have standard property values 1703 * 1704 * @param {Object} event 1705 * Event object to fix. 1706 * 1707 * @return {Object} 1708 * Fixed event object. 1709 */ 1710 1711 1712 function fixEvent(event) { 1713 if (event.fixed_) { 1714 return event; 1715 } 1716 1717 function returnTrue() { 1718 return true; 1719 } 1720 1721 function returnFalse() { 1722 return false; 1723 } // Test if fixing up is needed 1724 // Used to check if !event.stopPropagation instead of isPropagationStopped 1725 // But native events return true for stopPropagation, but don't have 1726 // other expected methods like isPropagationStopped. Seems to be a problem 1727 // with the Javascript Ninja code. So we're just overriding all events now. 1728 1729 1730 if (!event || !event.isPropagationStopped) { 1731 var old = event || window$1.event; 1732 event = {}; // Clone the old object so that we can modify the values event = {}; 1733 // IE8 Doesn't like when you mess with native event properties 1734 // Firefox returns false for event.hasOwnProperty('type') and other props 1735 // which makes copying more difficult. 1736 // TODO: Probably best to create a whitelist of event props 1737 1738 for (var key in old) { 1739 // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y 1740 // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation 1741 // and webkitMovementX/Y 1742 if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { 1743 // Chrome 32+ warns if you try to copy deprecated returnValue, but 1744 // we still want to if preventDefault isn't supported (IE8). 1745 if (!(key === 'returnValue' && old.preventDefault)) { 1746 event[key] = old[key]; 1747 } 1748 } 1749 } // The event occurred on this element 1750 1751 1752 if (!event.target) { 1753 event.target = event.srcElement || document; 1754 } // Handle which other element the event is related to 1755 1756 1757 if (!event.relatedTarget) { 1758 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 1759 } // Stop the default browser action 1760 1761 1762 event.preventDefault = function () { 1763 if (old.preventDefault) { 1764 old.preventDefault(); 1765 } 1766 1767 event.returnValue = false; 1768 old.returnValue = false; 1769 event.defaultPrevented = true; 1770 }; 1771 1772 event.defaultPrevented = false; // Stop the event from bubbling 1773 1774 event.stopPropagation = function () { 1775 if (old.stopPropagation) { 1776 old.stopPropagation(); 1777 } 1778 1779 event.cancelBubble = true; 1780 old.cancelBubble = true; 1781 event.isPropagationStopped = returnTrue; 1782 }; 1783 1784 event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers 1785 1786 event.stopImmediatePropagation = function () { 1787 if (old.stopImmediatePropagation) { 1788 old.stopImmediatePropagation(); 1789 } 1790 1791 event.isImmediatePropagationStopped = returnTrue; 1792 event.stopPropagation(); 1793 }; 1794 1795 event.isImmediatePropagationStopped = returnFalse; // Handle mouse position 1796 1797 if (event.clientX !== null && event.clientX !== undefined) { 1798 var doc = document.documentElement; 1799 var body = document.body; 1800 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 1801 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 1802 } // Handle key presses 1803 1804 1805 event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: 1806 // 0 == left; 1 == middle; 2 == right 1807 1808 if (event.button !== null && event.button !== undefined) { 1809 // The following is disabled because it does not pass videojs-standard 1810 // and... yikes. 1811 1812 /* eslint-disable */ 1813 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; 1814 /* eslint-enable */ 1815 } 1816 } 1817 1818 event.fixed_ = true; // Returns fixed-up instance 1819 1820 return event; 1821 } 1822 /** 1823 * Whether passive event listeners are supported 1824 */ 1825 1826 var _supportsPassive; 1827 1828 var supportsPassive = function supportsPassive() { 1829 if (typeof _supportsPassive !== 'boolean') { 1830 _supportsPassive = false; 1831
vendor: 4,581 bytes, lines 1832-2012
1832 try { 1833 var opts = Object.defineProperty({}, 'passive', { 1834 get: function get() { 1835 _supportsPassive = true; 1836 } 1837 }); 1838 window$1.addEventListener('test', null, opts); 1839 window$1.removeEventListener('test', null, opts); 1840 } catch (e) {// disregard 1841 } 1842 } 1843 1844 return _supportsPassive; 1845 }; 1846 /** 1847 * Touch events Chrome expects to be passive 1848 */ 1849 1850 1851 var passiveEvents = ['touchstart', 'touchmove']; 1852 /** 1853 * Add an event listener to element 1854 * It stores the handler function in a separate cache object 1855 * and adds a generic handler to the element's event, 1856 * along with a unique id (guid) to the element. 1857 * 1858 * @param {Element|Object} elem 1859 * Element or object to bind listeners to 1860 * 1861 * @param {string|string[]} type 1862 * Type of event to bind to. 1863 * 1864 * @param {EventTarget~EventListener} fn 1865 * Event listener. 1866 */ 1867 1868 function on(elem, type, fn) { 1869 if (Array.isArray(type)) { 1870 return _handleMultipleEvents(on, elem, type, fn); 1871 } 1872 1873 if (!DomData.has(elem)) { 1874 DomData.set(elem, {}); 1875 } 1876 1877 var data = DomData.get(elem); // We need a place to store all our handler data 1878 1879 if (!data.handlers) { 1880 data.handlers = {}; 1881 } 1882 1883 if (!data.handlers[type]) { 1884 data.handlers[type] = []; 1885 } 1886 1887 if (!fn.guid) { 1888 fn.guid = newGUID(); 1889 } 1890 1891 data.handlers[type].push(fn); 1892 1893 if (!data.dispatcher) { 1894 data.disabled = false; 1895 1896 data.dispatcher = function (event, hash) { 1897 if (data.disabled) { 1898 return; 1899 } 1900 1901 event = fixEvent(event); 1902 var handlers = data.handlers[event.type]; 1903 1904 if (handlers) { 1905 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 1906 var handlersCopy = handlers.slice(0); 1907 1908 for (var m = 0, n = handlersCopy.length; m < n; m++) { 1909 if (event.isImmediatePropagationStopped()) { 1910 break; 1911 } else { 1912 try { 1913 handlersCopy[m].call(elem, event, hash); 1914 } catch (e) { 1915 log.error(e); 1916 } 1917 } 1918 } 1919 } 1920 }; 1921 } 1922 1923 if (data.handlers[type].length === 1) { 1924 if (elem.addEventListener) { 1925 var options = false; 1926 1927 if (supportsPassive() && passiveEvents.indexOf(type) > -1) { 1928 options = { 1929 passive: true 1930 }; 1931 } 1932 1933 elem.addEventListener(type, data.dispatcher, options); 1934 } else if (elem.attachEvent) { 1935 elem.attachEvent('on' + type, data.dispatcher); 1936 } 1937 } 1938 } 1939 /** 1940 * Removes event listeners from an element 1941 * 1942 * @param {Element|Object} elem 1943 * Object to remove listeners from. 1944 * 1945 * @param {string|string[]} [type] 1946 * Type of listener to remove. Don't include to remove all events from element. 1947 * 1948 * @param {EventTarget~EventListener} [fn] 1949 * Specific listener to remove. Don't include to remove listeners for an event 1950 * type. 1951 */ 1952 1953 function off(elem, type, fn) { 1954 // Don't want to add a cache object through getElData if not needed 1955 if (!DomData.has(elem)) { 1956 return; 1957 } 1958 1959 var data = DomData.get(elem); // If no events exist, nothing to unbind 1960 1961 if (!data.handlers) { 1962 return; 1963 } 1964 1965 if (Array.isArray(type)) { 1966 return _handleMultipleEvents(off, elem, type, fn); 1967 } // Utility function 1968 1969 1970 var removeType = function removeType(el, t) { 1971 data.handlers[t] = []; 1972 1973 _cleanUpEvents(el, t); 1974 }; // Are we removing all bound events? 1975 1976 1977 if (type === undefined) { 1978 for (var t in data.handlers) { 1979 if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { 1980 removeType(elem, t); 1981 } 1982 } 1983 1984 return; 1985 } 1986 1987 var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind 1988 1989 if (!handlers) { 1990 return; 1991 } // If no listener was provided, remove all listeners for type 1992 1993 1994 if (!fn) { 1995 removeType(elem, type); 1996 return; 1997 } // We're only removing a single handler 1998 1999 2000 if (fn.guid) { 2001 for (var n = 0; n < handlers.length; n++) { 2002 if (handlers[n].guid === fn.guid) { 2003 handlers.splice(n--, 1); 2004 } 2005 } 2006 } 2007 2008 _cleanUpEvents(elem, type); 2009 } 2010 /** 2011 * Trigger an event for an element 2012 *
2013 * @param {Element|Object} elem 2014 * Element to trigger an event on 2015 * 2016 * @param {EventTarget~Event|string} event 2017 * A string (the type) or an event object with a type attribute 2018 * 2019 * @param {Object} [hash] 2020 * data hash to pass along with the event 2021 * 2022 * @return {boolean|undefined} 2023 * Returns the opposite of `defaultPrevented` if default was 2024 * prevented. Otherwise, returns `undefined` 2025 */ 2026 2027 function trigger(elem, event, hash) { 2028 // Fetches element data and a reference to the parent (for bubbling). 2029 // Don't want to add a data object to cache for every parent, 2030 // so checking hasElData first. 2031 var elemData = DomData.has(elem) ? DomData.get(elem) : {}; 2032 var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, 2033 // handler; 2034 // If an event name was passed as a string, creates an event out of it 2035 2036 if (typeof event === 'string') { 2037 event = { 2038 type: event, 2039 target: elem 2040 }; 2041 } else if (!event.target) { 2042 event.target = elem; 2043 } // Normalizes the event properties. 2044 2045 2046 event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. 2047 2048 if (elemData.dispatcher) { 2049 elemData.dispatcher.call(elem, event, hash); 2050 } // Unless explicitly stopped or the event does not bubble (e.g. media events) 2051 // recursively calls this function to bubble the event up the DOM. 2052 2053 2054 if (parent && !event.isPropagationStopped() && event.bubbles === true) { 2055 trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled. 2056 } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) { 2057 if (!DomData.has(event.target)) { 2058 DomData.set(event.target, {}); 2059 } 2060 2061 var targetData = DomData.get(event.target); // Checks if the target has a default action for this event. 2062 2063 if (event.target[event.type]) { 2064 // Temporarily disables event dispatching on the target as we have already executed the handler. 2065 targetData.disabled = true; // Executes the default action. 2066 2067 if (typeof event.target[event.type] === 'function') { 2068 event.target[event.type](); 2069 } // Re-enables event dispatching. 2070 2071 2072 targetData.disabled = false; 2073 } 2074 } // Inform the triggerer if the default was prevented by returning false 2075 2076 2077 return !event.defaultPrevented; 2078 } 2079 /** 2080 * Trigger a listener only once for an event. 2081 * 2082 * @param {Element|Object} elem 2083 * Element or object to bind to. 2084 * 2085 * @param {string|string[]} type 2086 * Name/type of event 2087 * 2088 * @param {Event~EventListener} fn 2089 * Event listener function 2090 */ 2091 2092 function one(elem, type, fn) { 2093 if (Array.isArray(type)) { 2094 return _handleMultipleEvents(one, elem, type, fn); 2095 } 2096 2097 var func = function func() { 2098 off(elem, type, func); 2099 fn.apply(this, arguments); 2100 }; // copy the guid to the new function so it can removed using the original function's ID 2101 2102 2103 func.guid = fn.guid = fn.guid || newGUID(); 2104 on(elem, type, func); 2105 } 2106 /** 2107 * Trigger a listener only once and then turn if off for all 2108 * configured events 2109 * 2110 * @param {Element|Object} elem 2111 * Element or object to bind to. 2112 * 2113 * @param {string|string[]} type 2114 * Name/type of event 2115 * 2116 * @param {Event~EventListener} fn 2117 * Event listener function 2118 */ 2119 2120 function any(elem, type, fn) { 2121 var func = function func() { 2122 off(elem, type, func); 2123 fn.apply(this, arguments); 2124 }; // copy the guid to the new function so it can removed using the original function's ID 2125 2126 2127 func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything 2128 2129 on(elem, type, func); 2130 } 2131 2132 var Events = /*#__PURE__*/Object.freeze({ 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$1.performance.now(); 2200 2201 var throttled = function throttled() { 2202 var now = window$1.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$1; 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 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$1.clearTimeout(oldTimeout); 2484 var timeout = window$1.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 });
vendor: 4,139 bytes, lines 2514-2661
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
vendor: 4,933 bytes, lines 2963-3125
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$1.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 = 3228 /*#__PURE__*/ 3229 function () { 3230 /** 3231 * A callback that is called when a component is ready. Does not have any 3232 * paramters and any callback value will be ignored. 3233 * 3234 * @callback Component~ReadyCallback 3235 * @this Component 3236 */ 3237 3238 /** 3239 * Creates an instance of this class. 3240 * 3241 * @param {Player} player 3242 * The `Player` that this class should be attached to. 3243 * 3244 * @param {Object} [options] 3245 * The key/value store of player options. 3246 * 3247 * @param {Object[]} [options.children] 3248 * An array of children objects to intialize this component with. Children objects have 3249 * a name property that will be used if more than one component of the same type needs to be 3250 * added. 3251 * 3252 * @param {Component~ReadyCallback} [ready] 3253 * Function that gets called when the `Component` is ready. 3254 */ 3255 function Component(player, options, ready) { 3256 // The component might be the player itself and we can't pass `this` to super 3257 if (!player && this.play) { 3258 this.player_ = player = this; // eslint-disable-line 3259 } else { 3260 this.player_ = player; 3261 } 3262 3263 this.isDisposed_ = false; // Hold the reference to the parent component via `addChild` method 3264 3265 this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults 3266 3267 this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options 3268 3269 options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied 3270 3271 this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one 3272 3273 if (!this.id_) { 3274 // Don't require the player ID function in the case of mock players 3275 var id = player && player.id && player.id() || 'no_player'; 3276 this.id_ = id + "_component_" + newGUID(); 3277 } 3278 3279 this.name_ = options.name || null; // Create element if one wasn't provided in options 3280 3281 if (options.el) { 3282 this.el_ = options.el; 3283 } else if (options.createEl !== false) { 3284 this.el_ = this.createEl(); 3285 } // if evented is anything except false, we want to mixin in evented 3286 3287 3288 if (options.evented !== false) { 3289 // Make this an evented object and use `el_`, if available, as its event bus 3290 evented(this, { 3291 eventBusKey: this.el_ ? 'el_' : null 3292 }); 3293 } 3294 3295 stateful(this, this.constructor.defaultState); 3296 this.children_ = []; 3297 this.childIndex_ = {}; 3298 this.childNameIndex_ = {}; 3299 var SetSham; 3300 3301 if (!window$1.Set) { 3302 SetSham = 3303 /*#__PURE__*/ 3304 function () { 3305 function SetSham() { 3306 this.set_ = {}; 3307 } 3308 3309 var _proto2 = SetSham.prototype; 3310 3311 _proto2.has = function has(key) { 3312 return key in this.set_; 3313 }; 3314 3315 _proto2["delete"] = function _delete(key) { 3316 var has = this.has(key); 3317 delete this.set_[key]; 3318 return has; 3319 }; 3320 3321 _proto2.add = function add(key) { 3322 this.set_[key] = 1; 3323 return this; 3324 }; 3325 3326 _proto2.forEach = function forEach(callback, thisArg) { 3327 for (var key in this.set_) { 3328 callback.call(thisArg, key, key, this); 3329 } 3330 }; 3331 3332 return SetSham; 3333 }(); 3334 } 3335 3336 this.setTimeoutIds_ = window$1.Set ? new Set() : new SetSham(); 3337 this.setIntervalIds_ = window$1.Set ? new Set() : new SetSham(); 3338 this.rafIds_ = window$1.Set ? new Set() : new SetSham(); 3339 this.clearingTimersOnDispose_ = false; // Add any child components in options 3340 3341 if (options.initChildren !== false) { 3342 this.initChildren();
3343 } 3344 3345 this.ready(ready); // Don't want to trigger ready here or it will before init is actually 3346 // finished for all children that run this constructor 3347 3348 if (options.reportTouchActivity !== false) { 3349 this.enableTouchActivity(); 3350 } 3351 } 3352 /** 3353 * Dispose of the `Component` and all child components. 3354 * 3355 * @fires Component#dispose 3356 */ 3357 3358 3359 var _proto = Component.prototype; 3360 3361 _proto.dispose = function dispose() { 3362 // Bail out if the component has already been disposed. 3363 if (this.isDisposed_) { 3364 return; 3365 } 3366 /** 3367 * Triggered when a `Component` is disposed. 3368 * 3369 * @event Component#dispose 3370 * @type {EventTarget~Event} 3371 * 3372 * @property {boolean} [bubbles=false] 3373 * set to false so that the dispose event does not 3374 * bubble up 3375 */ 3376 3377 3378 this.trigger({ 3379 type: 'dispose', 3380 bubbles: false 3381 }); 3382 this.isDisposed_ = true; // Dispose all children. 3383 3384 if (this.children_) { 3385 for (var i = this.children_.length - 1; i >= 0; i--) { 3386 if (this.children_[i].dispose) { 3387 this.children_[i].dispose(); 3388 } 3389 } 3390 } // Delete child references 3391 3392 3393 this.children_ = null; 3394 this.childIndex_ = null; 3395 this.childNameIndex_ = null; 3396 this.parentComponent_ = null; 3397 3398 if (this.el_) { 3399 // Remove element from DOM 3400 if (this.el_.parentNode) { 3401 this.el_.parentNode.removeChild(this.el_); 3402 } 3403 3404 if (DomData.has(this.el_)) { 3405 DomData["delete"](this.el_); 3406 } 3407 3408 this.el_ = null; 3409 } // remove reference to the player after disposing of the element 3410 3411 3412 this.player_ = null; 3413 } 3414 /** 3415 * Determine whether or not this component has been disposed. 3416 * 3417 * @return {boolean} 3418 * If the component has been disposed, will be `true`. Otherwise, `false`. 3419 */ 3420 ; 3421 3422 _proto.isDisposed = function isDisposed() { 3423 return Boolean(this.isDisposed_); 3424 } 3425 /** 3426 * Return the {@link Player} that the `Component` has attached to. 3427 * 3428 * @return {Player} 3429 * The player that this `Component` has attached to. 3430 */ 3431 ; 3432 3433 _proto.player = function player() { 3434 return this.player_; 3435 } 3436 /** 3437 * Deep merge of options objects with new options. 3438 * > Note: When both `obj` and `options` contain properties whose values are objects. 3439 * The two properties get merged using {@link module:mergeOptions} 3440 * 3441 * @param {Object} obj 3442 * The object that contains new options. 3443 * 3444 * @return {Object} 3445 * A new object of `this.options_` and `obj` merged together. 3446 */ 3447 ; 3448 3449 _proto.options = function options(obj) { 3450 if (!obj) { 3451 return this.options_; 3452 } 3453 3454 this.options_ = mergeOptions(this.options_, obj); 3455 return this.options_; 3456 } 3457 /** 3458 * Get the `Component`s DOM element 3459 * 3460 * @return {Element} 3461 * The DOM element for this `Component`. 3462 */ 3463 ; 3464 3465 _proto.el = function el() { 3466 return this.el_; 3467 } 3468 /** 3469 * Create the `Component`s DOM element. 3470 * 3471 * @param {string} [tagName] 3472 * Element's DOM node type. e.g. 'div' 3473 * 3474 * @param {Object} [properties] 3475 * An object of properties that should be set. 3476 * 3477 * @param {Object} [attributes] 3478 * An object of attributes that should be set. 3479 * 3480 * @return {Element} 3481 * The element that gets created. 3482 */ 3483 ; 3484 3485 _proto.createEl = function createEl$1(tagName, properties, attributes) { 3486 return createEl(tagName, properties, attributes); 3487 } 3488 /** 3489 * Localize a string given the string in english. 3490 * 3491 * If tokens are provided, it'll try and run a simple token replacement on the provided string. 3492 * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. 3493 * 3494 * If a `defaultValue` is provided, it'll use that over `string`, 3495 * if a value isn't found in provided language files. 3496 * This is useful if you want to have a descriptive key for token replacement 3497 * but have a succinct localized string and not require `en.json` to be included. 3498 * 3499 * Currently, it is used for the progress bar timing. 3500 * ```js 3501 * { 3502 * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" 3503 * } 3504 * ``` 3505 * It is then used like so: 3506 * ```js 3507 * this.localize('progress bar timing: currentTime={1} duration{2}', 3508 * [this.player_.currentTime(), this.player_.duration()], 3509 * '{1} of {2}'); 3510 * ``` 3511 * 3512 * Which outputs something like: `01:23 of 24:56`.
3513 * 3514 * 3515 * @param {string} string 3516 * The string to localize and the key to lookup in the language files. 3517 * @param {string[]} [tokens] 3518 * If the current item has token replacements, provide the tokens here. 3519 * @param {string} [defaultValue] 3520 * Defaults to `string`. Can be a default value to use for token replacement 3521 * if the lookup key is needed to be separate. 3522 * 3523 * @return {string} 3524 * The localized string or if no localization exists the english string. 3525 */ 3526 ; 3527 3528 _proto.localize = function localize(string, tokens, defaultValue) { 3529 if (defaultValue === void 0) { 3530 defaultValue = string; 3531 } 3532 3533 var code = this.player_.language && this.player_.language(); 3534 var languages = this.player_.languages && this.player_.languages(); 3535 var language = languages && languages[code]; 3536 var primaryCode = code && code.split('-')[0]; 3537 var primaryLang = languages && languages[primaryCode]; 3538 var localizedString = defaultValue; 3539 3540 if (language && language[string]) { 3541 localizedString = language[string]; 3542 } else if (primaryLang && primaryLang[string]) { 3543 localizedString = primaryLang[string]; 3544 } 3545 3546 if (tokens) { 3547 localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { 3548 var value = tokens[index - 1]; 3549 var ret = value; 3550 3551 if (typeof value === 'undefined') { 3552 ret = match; 3553 } 3554 3555 return ret; 3556 }); 3557 } 3558 3559 return localizedString; 3560 } 3561 /** 3562 * Return the `Component`s DOM element. This is where children get inserted. 3563 * This will usually be the the same as the element returned in {@link Component#el}. 3564 * 3565 * @return {Element} 3566 * The content element for this `Component`. 3567 */ 3568 ; 3569 3570 _proto.contentEl = function contentEl() { 3571 return this.contentEl_ || this.el_; 3572 } 3573 /** 3574 * Get this `Component`s ID 3575 * 3576 * @return {string} 3577 * The id of this `Component` 3578 */ 3579 ; 3580 3581 _proto.id = function id() { 3582 return this.id_; 3583 } 3584 /** 3585 * Get the `Component`s name. The name gets used to reference the `Component` 3586 * and is set during registration. 3587 * 3588 * @return {string} 3589 * The name of this `Component`. 3590 */ 3591 ; 3592 3593 _proto.name = function name() { 3594 return this.name_; 3595 } 3596 /** 3597 * Get an array of all child components 3598 * 3599 * @return {Array} 3600 * The children 3601 */ 3602 ; 3603 3604 _proto.children = function children() { 3605 return this.children_; 3606 } 3607 /** 3608 * Returns the child `Component` with the given `id`. 3609 * 3610 * @param {string} id 3611 * The id of the child `Component` to get. 3612 * 3613 * @return {Component|undefined} 3614 * The child `Component` with the given `id` or undefined. 3615 */ 3616 ; 3617 3618 _proto.getChildById = function getChildById(id) { 3619 return this.childIndex_[id]; 3620 } 3621 /** 3622 * Returns the child `Component` with the given `name`. 3623 * 3624 * @param {string} name 3625 * The name of the child `Component` to get. 3626 * 3627 * @return {Component|undefined} 3628 * The child `Component` with the given `name` or undefined. 3629 */ 3630 ; 3631 3632 _proto.getChild = function getChild(name) { 3633 if (!name) { 3634 return; 3635 } 3636 3637 return this.childNameIndex_[name]; 3638 } 3639 /** 3640 * Add a child `Component` inside the current `Component`. 3641 * 3642 * 3643 * @param {string|Component} child 3644 * The name or instance of a child to add. 3645 * 3646 * @param {Object} [options={}] 3647 * The key/value store of options that will get passed to children of 3648 * the child. 3649 * 3650 * @param {number} [index=this.children_.length] 3651 * The index to attempt to add a child into. 3652 * 3653 * @return {Component} 3654 * The `Component` that gets added as a child. When using a string the 3655 * `Component` will get created by this process. 3656 */ 3657 ; 3658 3659 _proto.addChild = function addChild(child, options, index) { 3660 if (options === void 0) { 3661 options = {}; 3662 } 3663 3664 if (index === void 0) { 3665 index = this.children_.length; 3666 } 3667 3668 var component; 3669 var componentName; // If child is a string, create component with options 3670 3671 if (typeof child === 'string') { 3672 componentName = toTitleCase(child); 3673 var componentClassName = options.componentClass || componentName; // Set name through options 3674 3675 options.name = componentName; // Create a new object & element for this controls set 3676 // If there's no .player_, this is a player 3677 3678 var ComponentClass = Component.getComponent(componentClassName); 3679 3680 if (!ComponentClass) { 3681 throw new Error("Component " + componentClassName + " does not exist"); 3682 } // data stored directly on the videojs object may be 3683 // misidentified as a component to retain 3684 // backwards-compatibility with 4.x. check to make sure the 3685 // component class can be instantiated. 3686 3687 3688 if (typeof ComponentClass !== 'function') { 3689 return null; 3690 } 3691 3692 component = new ComponentClass(this.player_ || this, options); // child is a component instance 3693 } else { 3694 component = child; 3695 } 3696 3697 if (component.parentComponent_) { 3698 component.parentComponent_.removeChild(component); 3699 } 3700 3701 this.children_.splice(index, 0, component); 3702 component.parentComponent_ = this; 3703 3704 if (typeof component.id === 'function') { 3705 this.childIndex_[component.id()] = component; 3706 } // If a name wasn't used to create the component, check if we can use the 3707 // name function of the component 3708 3709 3710 componentName = componentName || component.name && toTitleCase(component.name()); 3711 3712 if (componentName) { 3713 this.childNameIndex_[componentName] = component; 3714 this.childNameIndex_[toLowerCase(componentName)] = component; 3715 } // Add the UI object's element to the container div (box) 3716 // Having an element is not required 3717 3718 3719 if (typeof component.el === 'function' && component.el()) { 3720 // If inserting before a component, insert before that component's element
vendor: 9,093 bytes, lines 3721-4002
3721 var refNode = null; 3722 3723 if (this.children_[index + 1] && this.children_[index + 1].el_) { 3724 refNode = this.children_[index + 1].el_; 3725 } 3726 3727 this.contentEl().insertBefore(component.el(), refNode); 3728 } // Return so it can stored on parent object if desired. 3729 3730 3731 return component; 3732 } 3733 /** 3734 * Remove a child `Component` from this `Component`s list of children. Also removes 3735 * the child `Component`s element from this `Component`s element. 3736 * 3737 * @param {Component} component 3738 * The child `Component` to remove. 3739 */ 3740 ; 3741 3742 _proto.removeChild = function removeChild(component) { 3743 if (typeof component === 'string') { 3744 component = this.getChild(component); 3745 } 3746 3747 if (!component || !this.children_) { 3748 return; 3749 } 3750 3751 var childFound = false; 3752 3753 for (var i = this.children_.length - 1; i >= 0; i--) { 3754 if (this.children_[i] === component) { 3755 childFound = true; 3756 this.children_.splice(i, 1); 3757 break; 3758 } 3759 } 3760 3761 if (!childFound) { 3762 return; 3763 } 3764 3765 component.parentComponent_ = null; 3766 this.childIndex_[component.id()] = null; 3767 this.childNameIndex_[toTitleCase(component.name())] = null; 3768 this.childNameIndex_[toLowerCase(component.name())] = null; 3769 var compEl = component.el(); 3770 3771 if (compEl && compEl.parentNode === this.contentEl()) { 3772 this.contentEl().removeChild(component.el()); 3773 } 3774 } 3775 /** 3776 * Add and initialize default child `Component`s based upon options. 3777 */ 3778 ; 3779 3780 _proto.initChildren = function initChildren() { 3781 var _this = this; 3782 3783 var children = this.options_.children; 3784 3785 if (children) { 3786 // `this` is `parent` 3787 var parentOptions = this.options_; 3788 3789 var handleAdd = function handleAdd(child) { 3790 var name = child.name; 3791 var opts = child.opts; // Allow options for children to be set at the parent options 3792 // e.g. videojs(id, { controlBar: false }); 3793 // instead of videojs(id, { children: { controlBar: false }); 3794 3795 if (parentOptions[name] !== undefined) { 3796 opts = parentOptions[name]; 3797 } // Allow for disabling default components 3798 // e.g. options['children']['posterImage'] = false 3799 3800 3801 if (opts === false) { 3802 return; 3803 } // Allow options to be passed as a simple boolean if no configuration 3804 // is necessary. 3805 3806 3807 if (opts === true) { 3808 opts = {}; 3809 } // We also want to pass the original player options 3810 // to each component as well so they don't need to 3811 // reach back into the player for options later. 3812 3813 3814 opts.playerOptions = _this.options_.playerOptions; // Create and add the child component. 3815 // Add a direct reference to the child by name on the parent instance. 3816 // If two of the same component are used, different names should be supplied 3817 // for each 3818 3819 var newChild = _this.addChild(name, opts); 3820 3821 if (newChild) { 3822 _this[name] = newChild; 3823 } 3824 }; // Allow for an array of children details to passed in the options 3825 3826 3827 var workingChildren; 3828 var Tech = Component.getComponent('Tech'); 3829 3830 if (Array.isArray(children)) { 3831 workingChildren = children; 3832 } else { 3833 workingChildren = Object.keys(children); 3834 } 3835 3836 workingChildren // children that are in this.options_ but also in workingChildren would 3837 // give us extra children we do not want. So, we want to filter them out. 3838 .concat(Object.keys(this.options_).filter(function (child) { 3839 return !workingChildren.some(function (wchild) { 3840 if (typeof wchild === 'string') { 3841 return child === wchild; 3842 } 3843 3844 return child === wchild.name; 3845 }); 3846 })).map(function (child) { 3847 var name; 3848 var opts; 3849 3850 if (typeof child === 'string') { 3851 name = child; 3852 opts = children[name] || _this.options_[name] || {}; 3853 } else { 3854 name = child.name; 3855 opts = child; 3856 } 3857 3858 return { 3859 name: name, 3860 opts: opts 3861 }; 3862 }).filter(function (child) { 3863 // we have to make sure that child.name isn't in the techOrder since 3864 // techs are registerd as Components but can't aren't compatible 3865 // See https://github.com/videojs/video.js/issues/2772 3866 var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); 3867 return c && !Tech.isTech(c); 3868 }).forEach(handleAdd); 3869 } 3870 } 3871 /** 3872 * Builds the default DOM class name. Should be overriden by sub-components. 3873 * 3874 * @return {string} 3875 * The DOM class name for this object. 3876 * 3877 * @abstract 3878 */ 3879 ; 3880 3881 _proto.buildCSSClass = function buildCSSClass() { 3882 // Child classes can include a function that does: 3883 // return 'CLASS NAME' + this._super(); 3884 return ''; 3885 } 3886 /** 3887 * Bind a listener to the component's ready state. 3888 * Different from event listeners in that if the ready event has already happened 3889 * it will trigger the function immediately. 3890 * 3891 * @return {Component} 3892 * Returns itself; method can be chained. 3893 */ 3894 ; 3895 3896 _proto.ready = function ready(fn, sync) { 3897 if (sync === void 0) { 3898 sync = false; 3899 } 3900 3901 if (!fn) { 3902 return; 3903 } 3904 3905 if (!this.isReady_) { 3906 this.readyQueue_ = this.readyQueue_ || []; 3907 this.readyQueue_.push(fn); 3908 return; 3909 } 3910 3911 if (sync) { 3912 fn.call(this); 3913 } else { 3914 // Call the function asynchronously by default for consistency 3915 this.setTimeout(fn, 1); 3916 } 3917 } 3918 /** 3919 * Trigger all the ready listeners for this `Component`. 3920 * 3921 * @fires Component#ready 3922 */ 3923 ; 3924 3925 _proto.triggerReady = function triggerReady() { 3926 this.isReady_ = true; // Ensure ready is triggered asynchronously 3927 3928 this.setTimeout(function () { 3929 var readyQueue = this.readyQueue_; // Reset Ready Queue 3930 3931 this.readyQueue_ = []; 3932 3933 if (readyQueue && readyQueue.length > 0) { 3934 readyQueue.forEach(function (fn) { 3935 fn.call(this); 3936 }, this); 3937 } // Allow for using event listeners also 3938 3939 /** 3940 * Triggered when a `Component` is ready. 3941 * 3942 * @event Component#ready 3943 * @type {EventTarget~Event} 3944 */ 3945 3946 3947 this.trigger('ready'); 3948 }, 1); 3949 } 3950 /** 3951 * Find a single DOM element matching a `selector`. This can be within the `Component`s 3952 * `contentEl()` or another custom context. 3953 * 3954 * @param {string} selector 3955 * A valid CSS selector, which will be passed to `querySelector`. 3956 * 3957 * @param {Element|string} [context=this.contentEl()] 3958 * A DOM element within which to query. Can also be a selector string in 3959 * which case the first matching element will get used as context. If 3960 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3961 * nothing it falls back to `document`. 3962 * 3963 * @return {Element|null} 3964 * the dom element that was found, or null 3965 * 3966 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 3967 */ 3968 ; 3969 3970 _proto.$ = function $$1(selector, context) { 3971 return $(selector, context || this.contentEl()); 3972 } 3973 /** 3974 * Finds all DOM element matching a `selector`. This can be within the `Component`s 3975 * `contentEl()` or another custom context. 3976 * 3977 * @param {string} selector 3978 * A valid CSS selector, which will be passed to `querySelectorAll`. 3979 * 3980 * @param {Element|string} [context=this.contentEl()] 3981 * A DOM element within which to query. Can also be a selector string in 3982 * which case the first matching element will get used as context. If 3983 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3984 * nothing it falls back to `document`. 3985 * 3986 * @return {NodeList} 3987 * a list of dom elements that were found 3988 * 3989 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 3990 */ 3991 ; 3992 3993 _proto.$$ = function $$$1(selector, context) { 3994 return $$(selector, context || this.contentEl()); 3995 } 3996 /** 3997 * Check if a component's element has a CSS class name. 3998 * 3999 * @param {string} classToCheck 4000 * CSS class name to check. 4001 * 4002 * @return {boolean}
4003 * - True if the `Component` has the class. 4004 * - False if the `Component` does not have the class` 4005 */ 4006 ; 4007 4008 _proto.hasClass = function hasClass$1(classToCheck) { 4009 return hasClass(this.el_, classToCheck); 4010 } 4011 /** 4012 * Add a CSS class name to the `Component`s element. 4013 * 4014 * @param {string} classToAdd 4015 * CSS class name to add 4016 */ 4017 ; 4018 4019 _proto.addClass = function addClass$1(classToAdd) { 4020 addClass(this.el_, classToAdd); 4021 } 4022 /** 4023 * Remove a CSS class name from the `Component`s element. 4024 * 4025 * @param {string} classToRemove 4026 * CSS class name to remove 4027 */ 4028 ; 4029 4030 _proto.removeClass = function removeClass$1(classToRemove) { 4031 removeClass(this.el_, classToRemove); 4032 } 4033 /** 4034 * Add or remove a CSS class name from the component's element. 4035 * - `classToToggle` gets added when {@link Component#hasClass} would return false. 4036 * - `classToToggle` gets removed when {@link Component#hasClass} would return true. 4037 * 4038 * @param {string} classToToggle 4039 * The class to add or remove based on (@link Component#hasClass} 4040 * 4041 * @param {boolean|Dom~predicate} [predicate] 4042 * An {@link Dom~predicate} function or a boolean 4043 */ 4044 ; 4045 4046 _proto.toggleClass = function toggleClass$1(classToToggle, predicate) { 4047 toggleClass(this.el_, classToToggle, predicate); 4048 } 4049 /** 4050 * Show the `Component`s element if it is hidden by removing the 4051 * 'vjs-hidden' class name from it. 4052 */ 4053 ; 4054 4055 _proto.show = function show() { 4056 this.removeClass('vjs-hidden'); 4057 } 4058 /** 4059 * Hide the `Component`s element if it is currently showing by adding the 4060 * 'vjs-hidden` class name to it. 4061 */ 4062 ; 4063 4064 _proto.hide = function hide() { 4065 this.addClass('vjs-hidden'); 4066 } 4067 /** 4068 * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' 4069 * class name to it. Used during fadeIn/fadeOut. 4070 * 4071 * @private 4072 */ 4073 ; 4074 4075 _proto.lockShowing = function lockShowing() { 4076 this.addClass('vjs-lock-showing'); 4077 } 4078 /** 4079 * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' 4080 * class name from it. Used during fadeIn/fadeOut. 4081 * 4082 * @private 4083 */ 4084 ; 4085 4086 _proto.unlockShowing = function unlockShowing() { 4087 this.removeClass('vjs-lock-showing'); 4088 } 4089 /** 4090 * Get the value of an attribute on the `Component`s element. 4091 * 4092 * @param {string} attribute 4093 * Name of the attribute to get the value from. 4094 * 4095 * @return {string|null} 4096 * - The value of the attribute that was asked for. 4097 * - Can be an empty string on some browsers if the attribute does not exist 4098 * or has no value 4099 * - Most browsers will return null if the attibute does not exist or has 4100 * no value. 4101 * 4102 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} 4103 */ 4104 ; 4105 4106 _proto.getAttribute = function getAttribute$1(attribute) { 4107 return getAttribute(this.el_, attribute); 4108 } 4109 /** 4110 * Set the value of an attribute on the `Component`'s element 4111 * 4112 * @param {string} attribute 4113 * Name of the attribute to set. 4114 * 4115 * @param {string} value 4116 * Value to set the attribute to. 4117 * 4118 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} 4119 */ 4120 ; 4121 4122 _proto.setAttribute = function setAttribute$1(attribute, value) { 4123 setAttribute(this.el_, attribute, value); 4124 } 4125 /** 4126 * Remove an attribute from the `Component`s element. 4127 * 4128 * @param {string} attribute 4129 * Name of the attribute to remove. 4130 * 4131 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} 4132 */ 4133 ; 4134 4135 _proto.removeAttribute = function removeAttribute$1(attribute) { 4136 removeAttribute(this.el_, attribute); 4137 } 4138 /** 4139 * Get or set the width of the component based upon the CSS styles. 4140 * See {@link Component#dimension} for more detailed information. 4141 * 4142 * @param {number|string} [num] 4143 * The width that you want to set postfixed with '%', 'px' or nothing. 4144 * 4145 * @param {boolean} [skipListeners] 4146 * Skip the componentresize event trigger 4147 * 4148 * @return {number|string} 4149 * The width when getting, zero if there is no width. Can be a string 4150 * postpixed with '%' or 'px'. 4151 */ 4152 ; 4153 4154 _proto.width = function width(num, skipListeners) { 4155 return this.dimension('width', num, skipListeners); 4156 } 4157 /** 4158 * Get or set the height of the component based upon the CSS styles. 4159 * See {@link Component#dimension} for more detailed information. 4160 * 4161 * @param {number|string} [num] 4162 * The height that you want to set postfixed with '%', 'px' or nothing. 4163 * 4164 * @param {boolean} [skipListeners] 4165 * Skip the componentresize event trigger 4166 * 4167 * @return {number|string} 4168 * The width when getting, zero if there is no width. Can be a string 4169 * postpixed with '%' or 'px'. 4170 */ 4171 ; 4172 4173 _proto.height = function height(num, skipListeners) { 4174 return this.dimension('height', num, skipListeners); 4175 } 4176 /** 4177 * Set both the width and height of the `Component` element at the same time. 4178 * 4179 * @param {number|string} width 4180 * Width to set the `Component`s element to. 4181 * 4182 * @param {number|string} height 4183 * Height to set the `Component`s element to. 4184 */ 4185 ; 4186 4187 _proto.dimensions = function dimensions(width, height) { 4188 // Skip componentresize listeners on width for optimization 4189 this.width(width, true); 4190 this.height(height); 4191 } 4192 /** 4193 * Get or set width or height of the `Component` element. This is the shared code 4194 * for the {@link Component#width} and {@link Component#height}. 4195 * 4196 * Things to know: 4197 * - If the width or height in an number this will return the number postfixed with 'px'. 4198 * - If the width/height is a percent this will return the percent postfixed with '%' 4199 * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function 4200 * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. 4201 * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-u
4201sing-width/} 4202 * for more information 4203 * - If you want the computed style of the component, use {@link Component#currentWidth} 4204 * and {@link {Component#currentHeight} 4205 * 4206 * @fires Component#componentresize 4207 * 4208 * @param {string} widthOrHeight 4209 8 'width' or 'height' 4210 * 4211 * @param {number|string} [num] 4212 8 New dimension 4213 * 4214 * @param {boolean} [skipListeners] 4215 * Skip componentresize event trigger 4216 * 4217 * @return {number} 4218 * The dimension when getting or 0 if unset 4219 */ 4220 ; 4221 4222 _proto.dimension = function dimension(widthOrHeight, num, skipListeners) { 4223 if (num !== undefined) { 4224 // Set to zero if null or literally NaN (NaN !== NaN) 4225 if (num === null || num !== num) { 4226 num = 0; 4227 } // Check if using css width/height (% or px) and adjust 4228 4229 4230 if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { 4231 this.el_.style[widthOrHeight] = num; 4232 } else if (num === 'auto') { 4233 this.el_.style[widthOrHeight] = ''; 4234 } else { 4235 this.el_.style[widthOrHeight] = num + 'px'; 4236 } // skipListeners allows us to avoid triggering the resize event when setting both width and height 4237 4238 4239 if (!skipListeners) { 4240 /** 4241 * Triggered when a component is resized. 4242 * 4243 * @event Component#componentresize 4244 * @type {EventTarget~Event} 4245 */ 4246 this.trigger('componentresize'); 4247 } 4248 4249 return; 4250 } // Not setting a value, so getting it 4251 // Make sure element exists 4252 4253 4254 if (!this.el_) { 4255 return 0; 4256 } // Get dimension value from style 4257 4258 4259 var val = this.el_.style[widthOrHeight]; 4260 var pxIndex = val.indexOf('px'); 4261 4262 if (pxIndex !== -1) { 4263 // Return the pixel value with no 'px' 4264 return parseInt(val.slice(0, pxIndex), 10); 4265 } // No px so using % or no style was set, so falling back to offsetWidth/height 4266 // If component has display:none, offset will return 0 4267 // TODO: handle display:none and no dimension style using px 4268 4269 4270 return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); 4271 } 4272 /** 4273 * Get the computed width or the height of the component's element. 4274 * 4275 * Uses `window.getComputedStyle`. 4276 * 4277 * @param {string} widthOrHeight 4278 * A string containing 'width' or 'height'. Whichever one you want to get. 4279 * 4280 * @return {number} 4281 * The dimension that gets asked for or 0 if nothing was set 4282 * for that dimension. 4283 */ 4284 ; 4285 4286 _proto.currentDimension = function currentDimension(widthOrHeight) { 4287 var computedWidthOrHeight = 0; 4288 4289 if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { 4290 throw new Error('currentDimension only accepts width or height value'); 4291 } 4292 4293 computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer 4294 4295 computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying 4296 // and we want to check the offset values. 4297 // This code also runs wherever getComputedStyle doesn't exist. 4298 4299 if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) { 4300 var rule = "offset" + toTitleCase(widthOrHeight); 4301 computedWidthOrHeight = this.el_[rule]; 4302 } 4303 4304 return computedWidthOrHeight; 4305 } 4306 /** 4307 * An object that contains width and height values of the `Component`s 4308 * computed style. Uses `window.getComputedStyle`. 4309 * 4310 * @typedef {Object} Component~DimensionObject 4311 * 4312 * @property {number} width 4313 * The width of the `Component`s computed style. 4314 * 4315 * @property {number} height 4316 * The height of the `Component`s computed style. 4317 */ 4318 4319 /** 4320 * Get an object that contains computed width and height values of the 4321 * component's element. 4322 * 4323 * Uses `window.getComputedStyle`. 4324 * 4325 * @return {Component~DimensionObject} 4326 * The computed dimensions of the component's element. 4327 */ 4328 ; 4329 4330 _proto.currentDimensions = function currentDimensions() { 4331 return { 4332 width: this.currentDimension('width'), 4333 height: this.currentDimension('height') 4334 }; 4335 } 4336 /** 4337 * Get the computed width of the component's element. 4338 * 4339 * Uses `window.getComputedStyle`. 4340 * 4341 * @return {number} 4342 * The computed width of the component's element. 4343 */ 4344 ; 4345 4346 _proto.currentWidth = function currentWidth() { 4347 return this.currentDimension('width'); 4348 } 4349 /** 4350 * Get the computed height of the component's element. 4351 * 4352 * Uses `window.getComputedStyle`. 4353 * 4354 * @return {number} 4355 * The computed height of the component's element. 4356 */ 4357 ; 4358 4359 _proto.currentHeight = function currentHeight() { 4360 return this.currentDimension('height'); 4361 } 4362 /** 4363 * Set the focus to this component 4364 */ 4365 ; 4366 4367 _proto.focus = function focus() { 4368 this.el_.focus(); 4369 } 4370 /** 4371 * Remove the focus from this component 4372 */ 4373 ; 4374 4375 _proto.blur = function blur() { 4376 this.el_.blur(); 4377 } 4378 /** 4379 * When this Component receives a `keydown` event which it does not process, 4380 * it passes the event to the Player for handling. 4381 * 4382 * @param {EventTarget~Event} event
vendor: 10,781 bytes, lines 4383-4644
4383 * The `keydown` event that caused this function to be called. 4384 */ 4385 ; 4386 4387 _proto.handleKeyDown = function handleKeyDown(event) { 4388 if (this.player_) { 4389 // We only stop propagation here because we want unhandled events to fall 4390 // back to the browser. 4391 event.stopPropagation(); 4392 this.player_.handleKeyDown(event); 4393 } 4394 } 4395 /** 4396 * Many components used to have a `handleKeyPress` method, which was poorly 4397 * named because it listened to a `keydown` event. This method name now 4398 * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` 4399 * will not see their method calls stop working. 4400 * 4401 * @param {EventTarget~Event} event 4402 * The event that caused this function to be called. 4403 */ 4404 ; 4405 4406 _proto.handleKeyPress = function handleKeyPress(event) { 4407 this.handleKeyDown(event); 4408 } 4409 /** 4410 * Emit a 'tap' events when touch event support gets detected. This gets used to 4411 * support toggling the controls through a tap on the video. They get enabled 4412 * because every sub-component would have extra overhead otherwise. 4413 * 4414 * @private 4415 * @fires Component#tap 4416 * @listens Component#touchstart 4417 * @listens Component#touchmove 4418 * @listens Component#touchleave 4419 * @listens Component#touchcancel 4420 * @listens Component#touchend 4421 */ 4422 ; 4423 4424 _proto.emitTapEvents = function emitTapEvents() { 4425 // Track the start time so we can determine how long the touch lasted 4426 var touchStart = 0; 4427 var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap 4428 // Other popular libs use anywhere from 2 (hammer.js) to 15, 4429 // so 10 seems like a nice, round number. 4430 4431 var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap 4432 4433 var touchTimeThreshold = 200; 4434 var couldBeTap; 4435 this.on('touchstart', function (event) { 4436 // If more than one finger, don't consider treating this as a click 4437 if (event.touches.length === 1) { 4438 // Copy pageX/pageY from the object 4439 firstTouch = { 4440 pageX: event.touches[0].pageX, 4441 pageY: event.touches[0].pageY 4442 }; // Record start time so we can detect a tap vs. "touch and hold" 4443 4444 touchStart = window$1.performance.now(); // Reset couldBeTap tracking 4445 4446 couldBeTap = true; 4447 } 4448 }); 4449 this.on('touchmove', function (event) { 4450 // If more than one finger, don't consider treating this as a click 4451 if (event.touches.length > 1) { 4452 couldBeTap = false; 4453 } else if (firstTouch) { 4454 // Some devices will throw touchmoves for all but the slightest of taps. 4455 // So, if we moved only a small distance, this could still be a tap 4456 var xdiff = event.touches[0].pageX - firstTouch.pageX; 4457 var ydiff = event.touches[0].pageY - firstTouch.pageY; 4458 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); 4459 4460 if (touchDistance > tapMovementThreshold) { 4461 couldBeTap = false; 4462 } 4463 } 4464 }); 4465 4466 var noTap = function noTap() { 4467 couldBeTap = false; 4468 }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s 4469 4470 4471 this.on('touchleave', noTap); 4472 this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate 4473 // event 4474 4475 this.on('touchend', function (event) { 4476 firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen 4477 4478 if (couldBeTap === true) { 4479 // Measure how long the touch lasted 4480 var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap 4481 4482 if (touchTime < touchTimeThreshold) { 4483 // Don't let browser turn this into a click 4484 event.preventDefault(); 4485 /** 4486 * Triggered when a `Component` is tapped. 4487 * 4488 * @event Component#tap 4489 * @type {EventTarget~Event} 4490 */ 4491 4492 this.trigger('tap'); // It may be good to copy the touchend event object and change the 4493 // type to tap, if the other event properties aren't exact after 4494 // Events.fixEvent runs (e.g. event.target) 4495 } 4496 } 4497 }); 4498 } 4499 /** 4500 * This function reports user activity whenever touch events happen. This can get 4501 * turned off by any sub-components that wants touch events to act another way. 4502 * 4503 * Report user touch activity when touch events occur. User activity gets used to 4504 * determine when controls should show/hide. It is simple when it comes to mouse 4505 * events, because any mouse event should show the controls. So we capture mouse 4506 * events that bubble up to the player and report activity when that happens. 4507 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player 4508 * controls. So touch events can't help us at the player level either. 4509 * 4510 * User activity gets checked asynchronously. So what could happen is a tap event 4511 * on the video turns the controls off. Then the `touchend` event bubbles up to 4512 * the player. Which, if it reported user activity, would turn the controls right 4513 * back on. We also don't want to completely block touch events from bubbling up. 4514 * Furthermore a `touchmove` event and anything other than a tap, should not turn 4515 * controls back on. 4516 * 4517 * @listens Component#touchstart 4518 * @listens Component#touchmove 4519 * @listens Component#touchend 4520 * @listens Component#touchcancel 4521 */ 4522 ; 4523 4524 _proto.enableTouchActivity = function enableTouchActivity() { 4525 // Don't continue if the root player doesn't support reporting user activity 4526 if (!this.player() || !this.player().reportUserActivity) { 4527 return; 4528 } // listener for reporting that the user is active 4529 4530 4531 var report = bind(this.player(), this.player().reportUserActivity); 4532 var touchHolding; 4533 this.on('touchstart', function () { 4534 report(); // For as long as the they are touching the device or have their mouse down, 4535 // we consider them active even if they're not moving their finger or mouse. 4536 // So we want to continue to update that they are active 4537 4538 this.clearInterval(touchHolding); // report at the same interval as activityCheck 4539 4540 touchHolding = this.setInterval(report, 250); 4541 }); 4542 4543 var touchEnd = function touchEnd(event) { 4544 report(); // stop the interval that maintains activity if the touch is holding 4545 4546 this.clearInterval(touchHolding); 4547 }; 4548 4549 this.on('touchmove', report); 4550 this.on('touchend', touchEnd); 4551 this.on('touchcancel', touchEnd); 4552 } 4553 /** 4554 * A callback that has no parameters and is bound into `Component`s context. 4555 * 4556 * @callback Component~GenericCallback 4557 * @this Component 4558 */ 4559 4560 /** 4561 * Creates a function that runs after an `x` millisecond timeout. This function is a 4562 * wrapper around `window.setTimeout`. There are a few reasons to use this one 4563 * instead though: 4564 * 1. It gets cleared via {@link Component#clearTimeout} when 4565 * {@link Component#dispose} gets called. 4566 * 2. The function callback will gets turned into a {@link Component~GenericCallback} 4567 * 4568 * > Note: You can't use `window.clearTimeout` on the id returned by this function. This 4569 * will cause its dispose listener not to get cleaned up! Please use 4570 * {@link Component#clearTimeout} or {@link Component#dispose} instead. 4571 * 4572 * @param {Component~GenericCallback} fn 4573 * The function that will be run after `timeout`. 4574 * 4575 * @param {number} timeout 4576 * Timeout in milliseconds to delay before executing the specified function. 4577 * 4578 * @return {number} 4579 * Returns a timeout ID that gets used to identify the timeout. It can also 4580 * get used in {@link Component#clearTimeout} to clear the timeout that 4581 * was set. 4582 * 4583 * @listens Component#dispose 4584 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} 4585 */ 4586 ; 4587 4588 _proto.setTimeout = function setTimeout(fn, timeout) { 4589 var _this2 = this; 4590 4591 // declare as variables so they are properly available in timeout function 4592 // eslint-disable-next-line 4593 var timeoutId; 4594 fn = bind(this, fn); 4595 this.clearTimersOnDispose_(); 4596 timeoutId = window$1.setTimeout(function () { 4597 if (_this2.setTimeoutIds_.has(timeoutId)) { 4598 _this2.setTimeoutIds_["delete"](timeoutId); 4599 } 4600 4601 fn(); 4602 }, timeout); 4603 this.setTimeoutIds_.add(timeoutId); 4604 return timeoutId; 4605 } 4606 /** 4607 * Clears a timeout that gets created via `window.setTimeout` or 4608 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} 4609 * use this function instead of `window.clearTimout`. If you don't your dispose 4610 * listener will not get cleaned up until {@link Component#dispose}! 4611 * 4612 * @param {number} timeoutId 4613 * The id of the timeout to clear. The return value of 4614 * {@link Component#setTimeout} or `window.setTimeout`. 4615 * 4616 * @return {number} 4617 * Returns the timeout id that was cleared. 4618 * 4619 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} 4620 */ 4621 ; 4622 4623 _proto.clearTimeout = function clearTimeout(timeoutId) { 4624 if (this.setTimeoutIds_.has(timeoutId)) { 4625 this.setTimeoutIds_["delete"](timeoutId); 4626 window$1.clearTimeout(timeoutId); 4627 } 4628 4629 return timeoutId; 4630 } 4631 /** 4632 * Creates a function that gets run every `x` milliseconds. This function is a wrapper 4633 * around `window.setInterval`. There are a few reasons to use this one instead though. 4634 * 1. It gets cleared via {@link Component#clearInterval} when 4635 * {@link Component#dispose} gets called. 4636 * 2. The function callback will be a {@link Component~GenericCallback} 4637 * 4638 * @param {Component~GenericCallback} fn 4639 * The function to run every `x` seconds. 4640 * 4641 * @param {number} interval 4642 * Execute the specified function every `x` milliseconds. 4643 * 4644 * @return {number}
4645 * Returns an id that can be used to identify the interval. It can also be be used in 4646 * {@link Component#clearInterval} to clear the interval. 4647 * 4648 * @listens Component#dispose 4649 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} 4650 */ 4651 ; 4652 4653 _proto.setInterval = function setInterval(fn, interval) { 4654 fn = bind(this, fn); 4655 this.clearTimersOnDispose_(); 4656 var intervalId = window$1.setInterval(fn, interval); 4657 this.setIntervalIds_.add(intervalId); 4658 return intervalId; 4659 } 4660 /** 4661 * Clears an interval that gets created via `window.setInterval` or 4662 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} 4663 * use this function instead of `window.clearInterval`. If you don't your dispose 4664 * listener will not get cleaned up until {@link Component#dispose}! 4665 * 4666 * @param {number} intervalId 4667 * The id of the interval to clear. The return value of 4668 * {@link Component#setInterval} or `window.setInterval`. 4669 * 4670 * @return {number} 4671 * Returns the interval id that was cleared. 4672 * 4673 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} 4674 */ 4675 ; 4676 4677 _proto.clearInterval = function clearInterval(intervalId) { 4678 if (this.setIntervalIds_.has(intervalId)) { 4679 this.setIntervalIds_["delete"](intervalId); 4680 window$1.clearInterval(intervalId); 4681 } 4682 4683 return intervalId; 4684 } 4685 /** 4686 * Queues up a callback to be passed to requestAnimationFrame (rAF), but 4687 * with a few extra bonuses: 4688 * 4689 * - Supports browsers that do not support rAF by falling back to 4690 * {@link Component#setTimeout}. 4691 * 4692 * - The callback is turned into a {@link Component~GenericCallback} (i.e. 4693 * bound to the component). 4694 * 4695 * - Automatic cancellation of the rAF callback is handled if the component 4696 * is disposed before it is called. 4697 * 4698 * @param {Component~GenericCallback} fn 4699 * A function that will be bound to this component and executed just 4700 * before the browser's next repaint. 4701 * 4702 * @return {number} 4703 * Returns an rAF ID that gets used to identify the timeout. It can 4704 * also be used in {@link Component#cancelAnimationFrame} to cancel 4705 * the animation frame callback. 4706 * 4707 * @listens Component#dispose 4708 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} 4709 */ 4710 ; 4711 4712 _proto.requestAnimationFrame = function requestAnimationFrame(fn) { 4713 var _this3 = this; 4714 4715 // Fall back to using a timer. 4716 if (!this.supportsRaf_) { 4717 return this.setTimeout(fn, 1000 / 60); 4718 } 4719 4720 this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function 4721 // eslint-disable-next-line 4722 4723 var id; 4724 fn = bind(this, fn); 4725 id = window$1.requestAnimationFrame(function () { 4726 if (_this3.rafIds_.has(id)) { 4727 _this3.rafIds_["delete"](id); 4728 } 4729 4730 fn(); 4731 }); 4732 this.rafIds_.add(id); 4733 return id; 4734 } 4735 /** 4736 * Cancels a queued callback passed to {@link Component#requestAnimationFrame} 4737 * (rAF). 4738 * 4739 * If you queue an rAF callback via {@link Component#requestAnimationFrame}, 4740 * use this function instead of `window.cancelAnimationFrame`. If you don't, 4741 * your dispose listener will not get cleaned up until {@link Component#dispose}! 4742 * 4743 * @param {number} id 4744 * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. 4745 * 4746 * @return {number} 4747 * Returns the rAF ID that was cleared. 4748 * 4749 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} 4750 */ 4751 ; 4752 4753 _proto.cancelAnimationFrame = function cancelAnimationFrame(id) { 4754 // Fall back to using a timer. 4755 if (!this.supportsRaf_) { 4756 return this.clearTimeout(id); 4757 } 4758 4759 if (this.rafIds_.has(id)) { 4760 this.rafIds_["delete"](id); 4761 window$1.cancelAnimationFrame(id); 4762 } 4763 4764 return id; 4765 } 4766 /** 4767 * A function to setup `requestAnimationFrame`, `setTimeout`, 4768 * and `setInterval`, clearing on dispose. 4769 * 4770 * > Previously each timer added and removed dispose listeners on it's own. 4771 * For better performance it was decided to batch them all, and use `Set`s 4772 * to track outstanding timer ids. 4773 * 4774 * @private 4775 */ 4776 ; 4777 4778 _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() { 4779 var _this4 = this; 4780 4781 if (this.clearingTimersOnDispose_) { 4782 return; 4783 } 4784 4785 this.clearingTimersOnDispose_ = true; 4786 this.one('dispose', function () { 4787 [['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEac
4787h(function (_ref) { 4788 var idName = _ref[0], 4789 cancelName = _ref[1]; 4790 4791 _this4[idName].forEach(_this4[cancelName], _this4); 4792 }); 4793 _this4.clearingTimersOnDispose_ = false; 4794 }); 4795 } 4796 /** 4797 * Register a `Component` with `videojs` given the name and the component. 4798 * 4799 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s 4800 * should be registered using {@link Tech.registerTech} or 4801 * {@link videojs:videojs.registerTech}. 4802 * 4803 * > NOTE: This function can also be seen on videojs as 4804 * {@link videojs:videojs.registerComponent}. 4805 * 4806 * @param {string} name 4807 * The name of the `Component` to register. 4808 * 4809 * @param {Component} ComponentToRegister 4810 * The `Component` class to register. 4811 * 4812 * @return {Component} 4813 * The `Component` that was registered. 4814 */ 4815 ; 4816 4817 Component.registerComponent = function registerComponent(name, ComponentToRegister) { 4818 if (typeof name !== 'string' || !name) { 4819 throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string."); 4820 } 4821 4822 var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered. 4823 4824 var isTech = Tech && Tech.isTech(ComponentToRegister); 4825 var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); 4826 4827 if (isTech || !isComp) { 4828 var reason; 4829 4830 if (isTech) { 4831 reason = 'techs must be registered using Tech.registerTech()'; 4832 } else { 4833 reason = 'must be a Component subclass'; 4834 } 4835 4836 throw new Error("Illegal component, \"" + name + "\"; " + reason + "."); 4837 } 4838 4839 name = toTitleCase(name); 4840 4841 if (!Component.components_) { 4842 Component.components_ = {}; 4843 } 4844 4845 var Player = Component.getComponent('Player'); 4846 4847 if (name === 'Player' && Player && Player.players) { 4848 var players = Player.players; 4849 var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be 4850 // in Players.players. So, we must loop through and verify that the value 4851 // for each item is not null. This allows registration of the Player component 4852 // after all players have been disposed or before any were created. 4853 4854 if (players && playerNames.length > 0 && playerNames.map(function (pname) { 4855 return players[pname]; 4856 }).every(Boolean)) { 4857 throw new Error('Can not register Player component after player has been created.'); 4858 } 4859 } 4860 4861 Component.components_[name] = ComponentToRegister; 4862 Component.components_[toLowerCase(name)] = ComponentToRegister; 4863 return ComponentToRegister; 4864 } 4865 /** 4866 * Get a `Component` based on the name it was registered with. 4867 * 4868 * @param {string} name 4869 * The Name of the component to get. 4870 * 4871 * @return {Component} 4872 * The `Component` that got registered under the given name. 4873 * 4874 * @deprecated In `videojs` 6 this will not return `Component`s that were not 4875 * registered using {@link Component.registerComponent}. Currently we 4876 * check the global `videojs` object for a `Component` name and 4877 * return that if it exists. 4878 */ 4879 ; 4880 4881 Component.getComponent = function getComponent(name) { 4882 if (!name || !Component.components_) { 4883 return; 4884 } 4885 4886 return Component.components_[name]; 4887 }; 4888 4889 return Component; 4890 }(); 4891 /** 4892 * Whether or not this component supports `requestAnimationFrame`. 4893 * 4894 * This is exposed primarily for testing purposes. 4895 * 4896 * @private 4897 * @type {Boolean} 4898 */ 4899 4900 4901 Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function'; 4902 Component.registerComponent('Component', Component); 4903 4904 function _assertThisInitialized(self) { 4905 if (self === void 0) { 4906 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 4907 } 4908 4909 return self; 4910 } 4911 4912 var assertThisInitialized = _assertThisInitialized; 4913 4914 function _inheritsLoose(subClass, superClass) { 4915 subClass.prototype = Object.create(superClass.prototype); 4916 subClass.prototype.constructor = subClass; 4917 subClass.__proto__ = superClass; 4918 } 4919 4920 var inheritsLoose = _inheritsLoose; 4921 4922 /** 4923 * @file browser.js 4924 * @module browser 4925 */ 4926 var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
vendor: 18,836 bytes, lines 4927-5567
4927 var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); 4928 var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; 4929 /** 4930 * Whether or not this device is an iPod. 4931 * 4932 * @static 4933 * @const 4934 * @type {Boolean} 4935 */ 4936 4937 var IS_IPOD = /iPod/i.test(USER_AGENT); 4938 /** 4939 * The detected iOS version - or `null`. 4940 * 4941 * @static 4942 * @const 4943 * @type {string|null} 4944 */ 4945 4946 var IOS_VERSION = function () { 4947 var match = USER_AGENT.match(/OS (\d+)_/i); 4948 4949 if (match && match[1]) { 4950 return match[1]; 4951 } 4952 4953 return null; 4954 }(); 4955 /** 4956 * Whether or not this is an Android device. 4957 * 4958 * @static 4959 * @const 4960 * @type {Boolean} 4961 */ 4962 4963 var IS_ANDROID = /Android/i.test(USER_AGENT); 4964 /** 4965 * The detected Android version - or `null`. 4966 * 4967 * @static 4968 * @const 4969 * @type {number|string|null} 4970 */ 4971 4972 var ANDROID_VERSION = function () { 4973 // This matches Android Major.Minor.Patch versions 4974 // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned 4975 var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); 4976 4977 if (!match) { 4978 return null; 4979 } 4980 4981 var major = match[1] && parseFloat(match[1]); 4982 var minor = match[2] && parseFloat(match[2]); 4983 4984 if (major && minor) { 4985 return parseFloat(match[1] + '.' + match[2]); 4986 } else if (major) { 4987 return major; 4988 } 4989 4990 return null; 4991 }(); 4992 /** 4993 * Whether or not this is a native Android browser. 4994 * 4995 * @static 4996 * @const 4997 * @type {Boolean} 4998 */ 4999 5000 var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; 5001 /** 5002 * Whether or not this is Mozilla Firefox. 5003 * 5004 * @static 5005 * @const 5006 * @type {Boolean} 5007 */ 5008 5009 var IS_FIREFOX = /Firefox/i.test(USER_AGENT); 5010 /** 5011 * Whether or not this is Microsoft Edge. 5012 * 5013 * @static 5014 * @const 5015 * @type {Boolean} 5016 */ 5017 5018 var IS_EDGE = /Edge/i.test(USER_AGENT); 5019 /** 5020 * Whether or not this is Google Chrome. 5021 * 5022 * This will also be `true` for Chrome on iOS, which will have different support 5023 * as it is actually Safari under the hood. 5024 * 5025 * @static 5026 * @const 5027 * @type {Boolean} 5028 */ 5029 5030 var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); 5031 /** 5032 * The detected Google Chrome version - or `null`. 5033 * 5034 * @static 5035 * @const 5036 * @type {number|null} 5037 */ 5038 5039 var CHROME_VERSION = function () { 5040 var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); 5041 5042 if (match && match[2]) { 5043 return parseFloat(match[2]); 5044 } 5045 5046 return null; 5047 }(); 5048 /** 5049 * The detected Internet Explorer version - or `null`. 5050 * 5051 * @static 5052 * @const 5053 * @type {number|null} 5054 */ 5055 5056 var IE_VERSION = function () { 5057 var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); 5058 var version = result && parseFloat(result[1]); 5059 5060 if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { 5061 // IE 11 has a different user agent string than other IE versions 5062 version = 11.0; 5063 } 5064 5065 return version; 5066 }(); 5067 /** 5068 * Whether or not this is desktop Safari. 5069 * 5070 * @static 5071 * @const 5072 * @type {Boolean} 5073 */ 5074 5075 var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; 5076 /** 5077 * Whether or not this is a Windows machine. 5078 * 5079 * @static 5080 * @const 5081 * @type {Boolean} 5082 */ 5083 5084 var IS_WINDOWS = /Windows/i.test(USER_AGENT); 5085 /** 5086 * Whether or not this device is touch-enabled. 5087 * 5088 * @static 5089 * @const 5090 * @type {Boolean} 5091 */ 5092 5093 var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch); 5094 /** 5095 * Whether or not this device is an iPad. 5096 * 5097 * @static 5098 * @const 5099 * @type {Boolean} 5100 */ 5101 5102 var IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT); 5103 /** 5104 * Whether or not this device is an iPhone. 5105 * 5106 * @static 5107 * @const 5108 * @type {Boolean} 5109 */ 5110 // The Facebook app's UIWebView identifies as both an iPhone and iPad, so 5111 // to identify iPhones, we need to exclude iPads. 5112 // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ 5113 5114 var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; 5115 /** 5116 * Whether or not this is an iOS device. 5117 * 5118 * @static 5119 * @const 5120 * @type {Boolean} 5121 */ 5122 5123 var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; 5124 /** 5125 * Whether or not this is any flavor of Safari - including iOS. 5126 * 5127 * @static 5128 * @const 5129 * @type {Boolean} 5130 */ 5131 5132 var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; 5133 5134 var browser = /*#__PURE__*/Object.freeze({ 5135 IS_IPOD: IS_IPOD, 5136 IOS_VERSION: IOS_VERSION, 5137 IS_ANDROID: IS_ANDROID, 5138 ANDROID_VERSION: ANDROID_VERSION, 5139 IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, 5140 IS_FIREFOX: IS_FIREFOX, 5141 IS_EDGE: IS_EDGE, 5142 IS_CHROME: IS_CHROME, 5143 CHROME_VERSION: CHROME_VERSION, 5144 IE_VERSION: IE_VERSION, 5145 IS_SAFARI: IS_SAFARI, 5146 IS_WINDOWS: IS_WINDOWS, 5147 TOUCH_ENABLED: TOUCH_ENABLED, 5148 IS_IPAD: IS_IPAD, 5149 IS_IPHONE: IS_IPHONE, 5150 IS_IOS: IS_IOS, 5151 IS_ANY_SAFARI: IS_ANY_SAFARI 5152 }); 5153 5154 /** 5155 * @file time-ranges.js 5156 * @module time-ranges 5157 */ 5158 5159 /** 5160 * Returns the time for the specified index at the start or end 5161 * of a TimeRange object. 5162 * 5163 * @typedef {Function} TimeRangeIndex 5164 * 5165 * @param {number} [index=0] 5166 * The range number to return the time for. 5167 * 5168 * @return {number} 5169 * The time offset at the specified index. 5170 * 5171 * @deprecated The index argument must be provided. 5172 * In the future, leaving it out will throw an error. 5173 */ 5174 5175 /** 5176 * An object that contains ranges of time. 5177 * 5178 * @typedef {Object} TimeRange 5179 * 5180 * @property {number} length 5181 * The number of time ranges represented by this object. 5182 * 5183 * @property {module:time-ranges~TimeRangeIndex} start 5184 * Returns the time offset at which a specified time range begins. 5185 * 5186 * @property {module:time-ranges~TimeRangeIndex} end 5187 * Returns the time offset at which a specified time range ends. 5188 * 5189 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges 5190 */ 5191 5192 /** 5193 * Check if any of the time ranges are over the maximum index. 5194 * 5195 * @private 5196 * @param {string} fnName 5197 * The function name to use for logging 5198 * 5199 * @param {number} index 5200 * The index to check 5201 * 5202 * @param {number} maxIndex 5203 * The maximum possible index 5204 * 5205 * @throws {Error} if the timeRanges provided are over the maxIndex 5206 */ 5207 function rangeCheck(fnName, index, maxIndex) { 5208 if (typeof index !== 'number' || index < 0 || index > maxIndex) { 5209 throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ")."); 5210 } 5211 } 5212 /** 5213 * Get the time for the specified index at the start or end 5214 * of a TimeRange object. 5215 * 5216 * @private 5217 * @param {string} fnName 5218 * The function name to use for logging 5219 * 5220 * @param {string} valueIndex 5221 * The property that should be used to get the time. should be 5222 * 'start' or 'end' 5223 * 5224 * @param {Array} ranges 5225 * An array of time ranges 5226 * 5227 * @param {Array} [rangeIndex=0] 5228 * The index to start the search at 5229 * 5230 * @return {number} 5231 * The time that offset at the specified index. 5232 * 5233 * @deprecated rangeIndex must be set to a value, in the future this will throw an error. 5234 * @throws {Error} if rangeIndex is more than the length of ranges 5235 */ 5236 5237 5238 function getRange(fnName, valueIndex, ranges, rangeIndex) { 5239 rangeCheck(fnName, rangeIndex, ranges.length - 1); 5240 return ranges[rangeIndex][valueIndex]; 5241 } 5242 /** 5243 * Create a time range object given ranges of time. 5244 * 5245 * @private 5246 * @param {Array} [ranges] 5247 * An array of time ranges. 5248 */ 5249 5250 5251 function createTimeRangesObj(ranges) { 5252 if (ranges === undefined || ranges.length === 0) { 5253 return { 5254 length: 0, 5255 start: function start() { 5256 throw new Error('This TimeRanges object is empty'); 5257 }, 5258 end: function end() { 5259 throw new Error('This TimeRanges object is empty'); 5260 } 5261 }; 5262 } 5263 5264 return { 5265 length: ranges.length, 5266 start: getRange.bind(null, 'start', 0, ranges), 5267 end: getRange.bind(null, 'end', 1, ranges) 5268 }; 5269 } 5270 /** 5271 * Create a `TimeRange` object which mimics an 5272 * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. 5273 * 5274 * @param {number|Array[]} start 5275 * The start of a single range (a number) or an array of ranges (an 5276 * array of arrays of two numbers each). 5277 * 5278 * @param {number} end 5279 * The end of a single range. Cannot be used with the array form of 5280 * the `start` argument. 5281 */ 5282 5283 5284 function createTimeRanges(start, end) { 5285 if (Array.isArray(start)) { 5286 return createTimeRangesObj(start); 5287 } else if (start === undefined || end === undefined) { 5288 return createTimeRangesObj(); 5289 } 5290 5291 return createTimeRangesObj([[start, end]]); 5292 } 5293 5294 /** 5295 * @file buffer.js 5296 * @module buffer 5297 */ 5298 /** 5299 * Compute the percentage of the media that has been buffered. 5300 * 5301 * @param {TimeRange} buffered 5302 * The current `TimeRange` object representing buffered time ranges 5303 * 5304 * @param {number} duration 5305 * Total duration of the media 5306 * 5307 * @return {number} 5308 * Percent buffered of the total duration in decimal form. 5309 */ 5310 5311 function bufferedPercent(buffered, duration) { 5312 var bufferedDuration = 0; 5313 var start; 5314 var end; 5315 5316 if (!duration) { 5317 return 0; 5318 } 5319 5320 if (!buffered || !buffered.length) { 5321 buffered = createTimeRanges(0, 0); 5322 } 5323 5324 for (var i = 0; i < buffered.length; i++) { 5325 start = buffered.start(i); 5326 end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction 5327 5328 if (end > duration) { 5329 end = duration; 5330 } 5331 5332 bufferedDuration += end - start; 5333 } 5334 5335 return bufferedDuration / duration; 5336 } 5337 5338 /** 5339 * @file fullscreen-api.js 5340 * @module fullscreen-api 5341 * @private 5342 */ 5343 /** 5344 * Store the browser-specific methods for the fullscreen API. 5345 * 5346 * @type {Object} 5347 * @see [Specification]{@link https://fullscreen.spec.whatwg.org} 5348 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} 5349 */ 5350 5351 var FullscreenApi = { 5352 prefixed: true 5353 }; // browser API methods 5354 5355 var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit 5356 ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla 5357 ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft 5358 ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']]; 5359 var specApi = apiMap[0]; 5360 var browserApi; // determine the supported set of functions 5361 5362 for (var i = 0; i < apiMap.length; i++) { 5363 // check for exitFullscreen function 5364 if (apiMap[i][1] in document) { 5365 browserApi = apiMap[i]; 5366 break; 5367 } 5368 } // map the browser API names to the spec API names 5369 5370 5371 if (browserApi) { 5372 for (var _i = 0; _i < browserApi.length; _i++) { 5373 FullscreenApi[specApi[_i]] = browserApi[_i]; 5374 } 5375 5376 FullscreenApi.prefixed = browserApi[0] !== specApi[0]; 5377 } 5378 5379 /** 5380 * @file media-error.js 5381 */ 5382 /** 5383 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. 5384 * 5385 * @param {number|string|Object|MediaError} value 5386 * This can be of multiple types: 5387 * - number: should be a standard error code 5388 * - string: an error message (the code will be 0) 5389 * - Object: arbitrary properties 5390 * - `MediaError` (native): used to populate a video.js `MediaError` object 5391 * - `MediaError` (video.js): will return itself if it's already a 5392 * video.js `MediaError` object. 5393 * 5394 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} 5395 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} 5396 * 5397 * @class MediaError 5398 */ 5399 5400 function MediaError(value) { 5401 // Allow redundant calls to this constructor to avoid having `instanceof` 5402 // checks peppered around the code. 5403 if (value instanceof MediaError) { 5404 return value; 5405 } 5406 5407 if (typeof value === 'number') { 5408 this.code = value; 5409 } else if (typeof value === 'string') { 5410 // default code is zero, so this is a custom error 5411 this.message = value; 5412 } else if (isObject(value)) { 5413 // We assign the `code` property manually because native `MediaError` objects 5414 // do not expose it as an own/enumerable property of the object. 5415 if (typeof value.code === 'number') { 5416 this.code = value.code; 5417 } 5418 5419 assign(this, value); 5420 } 5421 5422 if (!this.message) { 5423 this.message = MediaError.defaultMessages[this.code] || ''; 5424 } 5425 } 5426 /** 5427 * The error code that refers two one of the defined `MediaError` types 5428 * 5429 * @type {Number} 5430 */ 5431 5432 5433 MediaError.prototype.code = 0; 5434 /** 5435 * An optional message that to show with the error. Message is not part of the HTML5 5436 * video spec but allows for more informative custom errors. 5437 * 5438 * @type {String} 5439 */ 5440 5441 MediaError.prototype.message = ''; 5442 /** 5443 * An optional status code that can be set by plugins to allow even more detail about 5444 * the error. For example a plugin might provide a specific HTTP status code and an 5445 * error message for that code. Then when the plugin gets that error this class will 5446 * know how to display an error message for it. This allows a custom message to show 5447 * up on the `Player` error overlay. 5448 * 5449 * @type {Array} 5450 */ 5451 5452 MediaError.prototype.status = null; 5453 /** 5454 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the 5455 * specification listed under {@link MediaError} for more information. 5456 * 5457 * @enum {array} 5458 * @readonly 5459 * @property {string} 0 - MEDIA_ERR_CUSTOM 5460 * @property {string} 1 - MEDIA_ERR_ABORTED 5461 * @property {string} 2 - MEDIA_ERR_NETWORK 5462 * @property {string} 3 - MEDIA_ERR_DECODE 5463 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED 5464 * @property {string} 5 - MEDIA_ERR_ENCRYPTED 5465 */ 5466 5467 MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; 5468 /** 5469 * The default `MediaError` messages based on the {@link MediaError.errorTypes}. 5470 * 5471 * @type {Array} 5472 * @constant 5473 */ 5474 5475 MediaError.defaultMessages = { 5476 1: 'You aborted the media playback', 5477 2: 'A network error caused the media download to fail part-way.', 5478 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 5479 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 5480 5: 'The media is encrypted and we do not have the keys to decrypt it.' 5481 }; // Add types as properties on MediaError 5482 // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 5483 5484 for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { 5485 MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance 5486 5487 MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; 5488 } // jsdocs for instance/static members added above 5489 5490 var tuple = SafeParseTuple; 5491 5492 function SafeParseTuple(obj, reviver) { 5493 var json; 5494 var error = null; 5495 5496 try { 5497 json = JSON.parse(obj, reviver); 5498 } catch (err) { 5499 error = err; 5500 } 5501 5502 return [error, json]; 5503 } 5504 5505 /** 5506 * Returns whether an object is `Promise`-like (i.e. has a `then` method). 5507 * 5508 * @param {Object} value 5509 * An object that may or may not be `Promise`-like. 5510 * 5511 * @return {boolean} 5512 * Whether or not the object is `Promise`-like. 5513 */ 5514 function isPromise(value) { 5515 return value !== undefined && value !== null && typeof value.then === 'function'; 5516 } 5517 /** 5518 * Silence a Promise-like object. 5519 * 5520 * This is useful for avoiding non-harmful, but potentially confusing "uncaught 5521 * play promise" rejection error messages. 5522 * 5523 * @param {Object} value 5524 * An object that may or may not be `Promise`-like. 5525 */ 5526 5527 function silencePromise(value) { 5528 if (isPromise(value)) { 5529 value.then(null, function (e) {}); 5530 } 5531 } 5532 5533 /** 5534 * @file text-track-list-converter.js Utilities for capturing text track state and 5535 * re-creating tracks based on a capture. 5536 * 5537 * @module text-track-list-converter 5538 */ 5539 5540 /** 5541 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that 5542 * represents the {@link TextTrack}'s state. 5543 * 5544 * @param {TextTrack} track 5545 * The text track to query. 5546 * 5547 * @return {Object} 5548 * A serializable javascript representation of the TextTrack. 5549 * @private 5550 */ 5551 var trackToJson_ = function trackToJson_(track) { 5552 var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { 5553 if (track[prop]) { 5554 acc[prop] = track[prop]; 5555 } 5556 5557 return acc; 5558 }, { 5559 cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { 5560 return { 5561 startTime: cue.startTime, 5562 endTime: cue.endTime, 5563 text: cue.text, 5564 id: cue.id 5565 }; 5566 }) 5567 });
5568 return ret; 5569 }; 5570 /** 5571 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the 5572 * state of all {@link TextTrack}s currently configured. The return array is compatible with 5573 * {@link text-track-list-converter:jsonToTextTracks}. 5574 * 5575 * @param {Tech} tech 5576 * The tech object to query 5577 * 5578 * @return {Array} 5579 * A serializable javascript representation of the {@link Tech}s 5580 * {@link TextTrackList}. 5581 */ 5582 5583 5584 var textTracksToJson = function textTracksToJson(tech) { 5585 var trackEls = tech.$$('track'); 5586 var trackObjs = Array.prototype.map.call(trackEls, function (t) { 5587 return t.track; 5588 }); 5589 var tracks = Array.prototype.map.call(trackEls, function (trackEl) { 5590 var json = trackToJson_(trackEl.track); 5591 5592 if (trackEl.src) { 5593 json.src = trackEl.src; 5594 } 5595 5596 return json; 5597 }); 5598 return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { 5599 return trackObjs.indexOf(track) === -1; 5600 }).map(trackToJson_)); 5601 }; 5602 /** 5603 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript 5604 * object {@link TextTrack} representations. 5605 * 5606 * @param {Array} json 5607 * An array of `TextTrack` representation objects, like those that would be 5608 * produced by `textTracksToJson`. 5609 * 5610 * @param {Tech} tech 5611 * The `Tech` to create the `TextTrack`s on. 5612 */ 5613 5614 5615 var jsonToTextTracks = function jsonToTextTracks(json, tech) { 5616 json.forEach(function (track) { 5617 var addedTrack = tech.addRemoteTextTrack(track).track; 5618 5619 if (!track.src && track.cues) { 5620 track.cues.forEach(function (cue) { 5621 return addedTrack.addCue(cue); 5622 }); 5623 } 5624 }); 5625 return tech.textTracks(); 5626 }; 5627 5628 var textTrackConverter = { 5629 textTracksToJson: textTracksToJson, 5630 jsonToTextTracks: jsonToTextTracks, 5631 trackToJson_: trackToJson_ 5632 }; 5633 5634 var keycode = createCommonjsModule(function (module, exports) { 5635 // Source: http://jsfiddle.net/vWx8V/ 5636 // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes 5637 5638 /** 5639 * Conenience method returns corresponding value for given keyName or keyCode. 5640 * 5641 * @param {Mixed} keyCode {Number} or keyName {String} 5642 * @return {Mixed} 5643 * @api public 5644 */ 5645 function keyCode(searchInput) { 5646 // Keyboard Events 5647 if (searchInput && 'object' === typeof searchInput) { 5648 var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; 5649 if (hasKeyCode) searchInput = hasKeyCode; 5650 } // Numbers 5651 5652 5653 if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string) 5654 5655 var search = String(searchInput); // check codes 5656 5657 var foundNamedKey = codes[search.toLowerCase()]; 5658 if (foundNamedKey) return foundNamedKey; // check aliases 5659 5660 var foundNamedKey = aliases[search.toLowerCase()]; 5661 if (foundNamedKey) return foundNamedKey; // weird character? 5662 5663 if (search.length === 1) return search.charCodeAt(0); 5664 return undefined; 5665 } 5666 /** 5667 * Compares a keyboard event with a given keyCode or keyName. 5668 * 5669 * @param {Event} event Keyboard event that should be tested 5670 * @param {Mixed} keyCode {Number} or keyName {String} 5671 * @return {Boolean} 5672 * @api public 5673 */ 5674 5675 5676 keyCode.isEventKey = function isEventKey(event, nameOrCode) { 5677 if (event && 'object' === typeof event) { 5678 var keyCode = event.which || event.keyCode || event.charCode; 5679 5680 if (keyCode === null || keyCode === undefined) { 5681 return false; 5682 } 5683 5684 if (typeof nameOrCode === 'string') { 5685 // check codes 5686 var foundNamedKey = codes[nameOrCode.toLowerCase()]; 5687 5688 if (foundNamedKey) { 5689 return foundNamedKey === keyCode; 5690 } // check aliases 5691 5692 5693 var foundNamedKey = aliases[nameOrCode.toLowerCase()]; 5694 5695 if (foundNamedKey) { 5696 return foundNamedKey === keyCode; 5697 } 5698 } else if (typeof nameOrCode === 'number') { 5699 return nameOrCode === keyCode; 5700 } 5701 5702 return false; 5703 } 5704 }; 5705 5706 exports = module.exports = keyCode; 5707 /** 5708 * Get by name 5709 * 5710 * exports.code['enter'] // => 13 5711 */ 5712 5713 var codes = exports.code = exports.codes = { 5714 'backspace': 8, 5715 'tab': 9, 5716 'enter': 13, 5717 'shift': 16, 5718 'ctrl': 17, 5719 'alt': 18, 5720 'pause/break': 19, 5721 'caps lock': 20, 5722 'esc': 27, 5723 'space': 32, 5724 'page up': 33, 5725 'page down': 34, 5726 'end': 35, 5727 'home': 36, 5728 'left': 37, 5729 'up': 38, 5730 'right': 39, 5731 'down': 40, 5732 'insert': 45, 5733 'delete': 46, 5734 'command': 91, 5735 'left command': 91, 5736 'right command': 93, 5737 'numpad *': 106, 5738 'numpad +': 107, 5739 'numpad -': 109, 5740 'numpad .': 110, 5741 'numpad /': 111, 5742 'num lock': 144, 5743 'scroll lock': 145, 5744 'my computer': 182, 5745 'my calculator': 183, 5746 ';': 186, 5747 '=': 187, 5748 ',': 188, 5749 '-': 189, 5750 '.': 190, 5751 '/': 191, 5752 '`': 192, 5753 '[': 219, 5754 '\\': 220, 5755 ']': 221, 5756 "'": 222 // Helper aliases 5757 5758 }; 5759 var aliases = exports.aliases = { 5760 'windows': 91, 5761 'â§': 16, 5762 'â¥': 18, 5763 'â': 17, 5764 'â': 91, 5765 'ctl': 17, 5766 'control': 17, 5767 'option': 18, 5768 'pause': 19, 5769 'break': 19, 5770 'caps': 20, 5771 'return': 13, 5772 'escape': 27, 5773 'spc': 32, 5774 'spacebar': 32, 5775 'pgup': 33, 5776 'pgdn': 34, 5777 'ins': 45, 5778 'del': 46, 5779 'cmd': 91 5780 /*! 5781 * Programatically add the following 5782 */ 5783 // lower case chars 5784 5785 }; 5786 5787 for (i = 97; i < 123; i++) {
5788 codes[String.fromCharCode(i)] = i - 32; 5789 } // numbers 5790 5791 5792 for (var i = 48; i < 58; i++) { 5793 codes[i - 48] = i; 5794 } // function keys 5795 5796 5797 for (i = 1; i < 13; i++) { 5798 codes['f' + i] = i + 111; 5799 } // numpad keys 5800 5801 5802 for (i = 0; i < 10; i++) { 5803 codes['numpad ' + i] = i + 96; 5804 } 5805 /** 5806 * Get by code 5807 * 5808 * exports.name[13] // => 'Enter' 5809 */ 5810 5811 5812 var names = exports.names = exports.title = {}; // title for backward compat 5813 // Create reverse mapping 5814 5815 for (i in codes) { 5816 names[codes[i]] = i; 5817 } // Add aliases 5818 5819 5820 for (var alias in aliases) { 5821 codes[alias] = aliases[alias]; 5822 } 5823 }); 5824 var keycode_1 = keycode.code; 5825 var keycode_2 = keycode.codes; 5826 var keycode_3 = keycode.aliases; 5827 var keycode_4 = keycode.names; 5828 var keycode_5 = keycode.title; 5829 5830 var MODAL_CLASS_NAME = 'vjs-modal-dialog'; 5831 /** 5832 * The `ModalDialog` displays over the video and its controls, which blocks 5833 * interaction with the player until it is closed. 5834 * 5835 * Modal dialogs include a "Close" button and will close when that button 5836 * is activated - or when ESC is pressed anywhere. 5837 * 5838 * @extends Component 5839 */ 5840 5841 var ModalDialog = 5842 /*#__PURE__*/ 5843 function (_Component) { 5844 inheritsLoose(ModalDialog, _Component); 5845 5846 /** 5847 * Create an instance of this class. 5848 * 5849 * @param {Player} player 5850 * The `Player` that this class should be attached to. 5851 * 5852 * @param {Object} [options] 5853 * The key/value store of player options. 5854 * 5855 * @param {Mixed} [options.content=undefined] 5856 * Provide customized content for this modal. 5857 * 5858 * @param {string} [options.description] 5859 * A text description for the modal, primarily for accessibility. 5860 * 5861 * @param {boolean} [options.fillAlways=false] 5862 * Normally, modals are automatically filled only the first time 5863 * they open. This tells the modal to refresh its content 5864 * every time it opens. 5865 * 5866 * @param {string} [options.label] 5867 * A text label for the modal, primarily for accessibility. 5868 * 5869 * @param {boolean} [options.pauseOnOpen=true] 5870 * If `true`, playback will will be paused if playing when 5871 * the modal opens, and resumed when it closes. 5872 * 5873 * @param {boolean} [options.temporary=true] 5874 * If `true`, the modal can only be opened once; it will be 5875 * disposed as soon as it's closed. 5876 * 5877 * @param {boolean} [options.uncloseable=false] 5878 * If `true`, the user will not be able to close the modal 5879 * through the UI in the normal ways. Programmatic closing is 5880 * still possible. 5881 */ 5882 function ModalDialog(player, options) { 5883 var _this; 5884 5885 _this = _Component.call(this, player, options) || this; 5886 _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; 5887 5888 _this.closeable(!_this.options_.uncloseable); 5889 5890 _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized 5891 // because we only want the contents of the modal in the contentEl 5892 // (not the UI elements like the close button). 5893 5894 5895 _this.contentEl_ = createEl('div', { 5896 className: MODAL_CLASS_NAME + "-content" 5897 }, { 5898 role: 'document' 5899 }); 5900 _this.descEl_ = createEl('p', { 5901 className: MODAL_CLASS_NAME + "-description vjs-control-text", 5902 id: _this.el().getAttribute('aria-describedby') 5903 }); 5904 textContent(_this.descEl_, _this.description()); 5905 5906 _this.el_.appendChild(_this.descEl_); 5907 5908 _this.el_.appendChild(_this.contentEl_); 5909 5910 return _this; 5911 } 5912 /** 5913 * Create the `ModalDialog`'s DOM element 5914 * 5915 * @return {Element} 5916 * The DOM element that gets created. 5917 */ 5918 5919 5920 var _proto = ModalDialog.prototype; 5921 5922 _proto.createEl = function createEl() { 5923 return _Component.prototype.createEl.call(this, 'div', { 5924 className: this.buildCSSClass(), 5925 tabIndex: -1 5926 }, { 5927 'aria-describedby': this.id() + "_description", 5928 'aria-hidden': 'true', 5929 'aria-label': this.label(), 5930 'role': 'dialog' 5931 }); 5932 }; 5933 5934 _proto.dispose = function dispose() { 5935 this.contentEl_ = null; 5936 this.descEl_ = null; 5937 this.previouslyActiveEl_ = null; 5938 5939 _Component.prototype.dispose.call(this); 5940 } 5941 /** 5942 * Builds the default DOM `className`. 5943 * 5944 * @return {string} 5945 * The DOM `className` for this object. 5946 */ 5947 ; 5948 5949 _proto.buildCSSClass = function buildCSSClass() { 5950 return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this); 5951 } 5952 /**
vendor: 32,228 bytes, lines 5953-7145
5953 * Returns the label string for this modal. Primarily used for accessibility. 5954 * 5955 * @return {string} 5956 * the localized or raw label of this modal. 5957 */ 5958 ; 5959 5960 _proto.label = function label() { 5961 return this.localize(this.options_.label || 'Modal Window'); 5962 } 5963 /** 5964 * Returns the description string for this modal. Primarily used for 5965 * accessibility. 5966 * 5967 * @return {string} 5968 * The localized or raw description of this modal. 5969 */ 5970 ; 5971 5972 _proto.description = function description() { 5973 var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable. 5974 5975 if (this.closeable()) { 5976 desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); 5977 } 5978 5979 return desc; 5980 } 5981 /** 5982 * Opens the modal. 5983 * 5984 * @fires ModalDialog#beforemodalopen 5985 * @fires ModalDialog#modalopen 5986 */ 5987 ; 5988 5989 _proto.open = function open() { 5990 if (!this.opened_) { 5991 var player = this.player(); 5992 /** 5993 * Fired just before a `ModalDialog` is opened. 5994 * 5995 * @event ModalDialog#beforemodalopen 5996 * @type {EventTarget~Event} 5997 */ 5998 5999 this.trigger('beforemodalopen'); 6000 this.opened_ = true; // Fill content if the modal has never opened before and 6001 // never been filled. 6002 6003 if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { 6004 this.fill(); 6005 } // If the player was playing, pause it and take note of its previously 6006 // playing state. 6007 6008 6009 this.wasPlaying_ = !player.paused(); 6010 6011 if (this.options_.pauseOnOpen && this.wasPlaying_) { 6012 player.pause(); 6013 } 6014 6015 this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled. 6016 6017 this.hadControls_ = player.controls(); 6018 player.controls(false); 6019 this.show(); 6020 this.conditionalFocus_(); 6021 this.el().setAttribute('aria-hidden', 'false'); 6022 /** 6023 * Fired just after a `ModalDialog` is opened. 6024 * 6025 * @event ModalDialog#modalopen 6026 * @type {EventTarget~Event} 6027 */ 6028 6029 this.trigger('modalopen'); 6030 this.hasBeenOpened_ = true; 6031 } 6032 } 6033 /** 6034 * If the `ModalDialog` is currently open or closed. 6035 * 6036 * @param {boolean} [value] 6037 * If given, it will open (`true`) or close (`false`) the modal. 6038 * 6039 * @return {boolean} 6040 * the current open state of the modaldialog 6041 */ 6042 ; 6043 6044 _proto.opened = function opened(value) { 6045 if (typeof value === 'boolean') { 6046 this[value ? 'open' : 'close'](); 6047 } 6048 6049 return this.opened_; 6050 } 6051 /** 6052 * Closes the modal, does nothing if the `ModalDialog` is 6053 * not open. 6054 * 6055 * @fires ModalDialog#beforemodalclose 6056 * @fires ModalDialog#modalclose 6057 */ 6058 ; 6059 6060 _proto.close = function close() { 6061 if (!this.opened_) { 6062 return; 6063 } 6064 6065 var player = this.player(); 6066 /** 6067 * Fired just before a `ModalDialog` is closed. 6068 * 6069 * @event ModalDialog#beforemodalclose 6070 * @type {EventTarget~Event} 6071 */ 6072 6073 this.trigger('beforemodalclose'); 6074 this.opened_ = false; 6075 6076 if (this.wasPlaying_ && this.options_.pauseOnOpen) { 6077 player.play(); 6078 } 6079 6080 this.off('keydown', this.handleKeyDown); 6081 6082 if (this.hadControls_) { 6083 player.controls(true); 6084 } 6085 6086 this.hide(); 6087 this.el().setAttribute('aria-hidden', 'true'); 6088 /** 6089 * Fired just after a `ModalDialog` is closed. 6090 * 6091 * @event ModalDialog#modalclose 6092 * @type {EventTarget~Event} 6093 */ 6094 6095 this.trigger('modalclose'); 6096 this.conditionalBlur_(); 6097 6098 if (this.options_.temporary) { 6099 this.dispose(); 6100 } 6101 } 6102 /** 6103 * Check to see if the `ModalDialog` is closeable via the UI. 6104 * 6105 * @param {boolean} [value] 6106 * If given as a boolean, it will set the `closeable` option. 6107 * 6108 * @return {boolean} 6109 * Returns the final value of the closable option. 6110 */ 6111 ; 6112 6113 _proto.closeable = function closeable(value) { 6114 if (typeof value === 'boolean') { 6115 var closeable = this.closeable_ = !!value; 6116 var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one. 6117 6118 if (closeable && !close) { 6119 // The close button should be a child of the modal - not its 6120 // content element, so temporarily change the content element. 6121 var temp = this.contentEl_; 6122 this.contentEl_ = this.el_; 6123 close = this.addChild('closeButton', { 6124 controlText: 'Close Modal Dialog' 6125 }); 6126 this.contentEl_ = temp; 6127 this.on(close, 'close', this.close); 6128 } // If this is being made uncloseable and has a close button, remove it. 6129 6130 6131 if (!closeable && close) { 6132 this.off(close, 'close', this.close); 6133 this.removeChild(close); 6134 close.dispose(); 6135 } 6136 } 6137 6138 return this.closeable_; 6139 } 6140 /** 6141 * Fill the modal's content element with the modal's "content" option. 6142 * The content element will be emptied before this change takes place. 6143 */ 6144 ; 6145 6146 _proto.fill = function fill() { 6147 this.fillWith(this.content()); 6148 } 6149 /** 6150 * Fill the modal's content element with arbitrary content. 6151 * The content element will be emptied before this change takes place. 6152 * 6153 * @fires ModalDialog#beforemodalfill 6154 * @fires ModalDialog#modalfill 6155 * 6156 * @param {Mixed} [content] 6157 * The same rules apply to this as apply to the `content` option. 6158 */ 6159 ; 6160 6161 _proto.fillWith = function fillWith(content) { 6162 var contentEl = this.contentEl(); 6163 var parentEl = contentEl.parentNode; 6164 var nextSiblingEl = contentEl.nextSibling; 6165 /** 6166 * Fired just before a `ModalDialog` is filled with content. 6167 * 6168 * @event ModalDialog#beforemodalfill 6169 * @type {EventTarget~Event} 6170 */ 6171 6172 this.trigger('beforemodalfill'); 6173 this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing 6174 // manipulation to avoid modifying the live DOM multiple times. 6175 6176 parentEl.removeChild(contentEl); 6177 this.empty(); 6178 insertContent(contentEl, content); 6179 /** 6180 * Fired just after a `ModalDialog` is filled with content. 6181 * 6182 * @event ModalDialog#modalfill 6183 * @type {EventTarget~Event} 6184 */ 6185 6186 this.trigger('modalfill'); // Re-inject the re-filled content element. 6187 6188 if (nextSiblingEl) { 6189 parentEl.insertBefore(contentEl, nextSiblingEl); 6190 } else { 6191 parentEl.appendChild(contentEl); 6192 } // make sure that the close button is last in the dialog DOM 6193 6194 6195 var closeButton = this.getChild('closeButton'); 6196 6197 if (closeButton) { 6198 parentEl.appendChild(closeButton.el_); 6199 } 6200 } 6201 /** 6202 * Empties the content element. This happens anytime the modal is filled. 6203 * 6204 * @fires ModalDialog#beforemodalempty 6205 * @fires ModalDialog#modalempty 6206 */ 6207 ; 6208 6209 _proto.empty = function empty() { 6210 /** 6211 * Fired just before a `ModalDialog` is emptied. 6212 * 6213 * @event ModalDialog#beforemodalempty 6214 * @type {EventTarget~Event} 6215 */ 6216 this.trigger('beforemodalempty'); 6217 emptyEl(this.contentEl()); 6218 /** 6219 * Fired just after a `ModalDialog` is emptied. 6220 * 6221 * @event ModalDialog#modalempty 6222 * @type {EventTarget~Event} 6223 */ 6224 6225 this.trigger('modalempty'); 6226 } 6227 /** 6228 * Gets or sets the modal content, which gets normalized before being 6229 * rendered into the DOM. 6230 * 6231 * This does not update the DOM or fill the modal, but it is called during 6232 * that process. 6233 * 6234 * @param {Mixed} [value] 6235 * If defined, sets the internal content value to be used on the 6236 * next call(s) to `fill`. This value is normalized before being 6237 * inserted. To "clear" the internal content value, pass `null`. 6238 * 6239 * @return {Mixed} 6240 * The current content of the modal dialog 6241 */ 6242 ; 6243 6244 _proto.content = function content(value) { 6245 if (typeof value !== 'undefined') { 6246 this.content_ = value; 6247 } 6248 6249 return this.content_; 6250 } 6251 /** 6252 * conditionally focus the modal dialog if focus was previously on the player. 6253 * 6254 * @private 6255 */ 6256 ; 6257 6258 _proto.conditionalFocus_ = function conditionalFocus_() { 6259 var activeEl = document.activeElement; 6260 var playerEl = this.player_.el_; 6261 this.previouslyActiveEl_ = null; 6262 6263 if (playerEl.contains(activeEl) || playerEl === activeEl) { 6264 this.previouslyActiveEl_ = activeEl; 6265 this.focus(); 6266 } 6267 } 6268 /** 6269 * conditionally blur the element and refocus the last focused element 6270 * 6271 * @private 6272 */ 6273 ; 6274 6275 _proto.conditionalBlur_ = function conditionalBlur_() { 6276 if (this.previouslyActiveEl_) { 6277 this.previouslyActiveEl_.focus(); 6278 this.previouslyActiveEl_ = null; 6279 } 6280 } 6281 /** 6282 * Keydown handler. Attached when modal is focused. 6283 * 6284 * @listens keydown 6285 */ 6286 ; 6287 6288 _proto.handleKeyDown = function handleKeyDown(event) { 6289 // Do not allow keydowns to reach out of the modal dialog. 6290 event.stopPropagation(); 6291 6292 if (keycode.isEventKey(event, 'Escape') && this.closeable()) { 6293 event.preventDefault(); 6294 this.close(); 6295 return; 6296 } // exit early if it isn't a tab key 6297 6298 6299 if (!keycode.isEventKey(event, 'Tab')) { 6300 return; 6301 } 6302 6303 var focusableEls = this.focusableEls_(); 6304 var activeEl = this.el_.querySelector(':focus'); 6305 var focusIndex; 6306 6307 for (var i = 0; i < focusableEls.length; i++) { 6308 if (activeEl === focusableEls[i]) { 6309 focusIndex = i; 6310 break; 6311 } 6312 } 6313 6314 if (document.activeElement === this.el_) { 6315 focusIndex = 0; 6316 } 6317 6318 if (event.shiftKey && focusIndex === 0) { 6319 focusableEls[focusableEls.length - 1].focus(); 6320 event.preventDefault(); 6321 } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { 6322 focusableEls[0].focus(); 6323 event.preventDefault(); 6324 } 6325 } 6326 /** 6327 * get all focusable elements 6328 * 6329 * @private 6330 */ 6331 ; 6332 6333 _proto.focusableEls_ = function focusableEls_() { 6334 var allChildren = this.el_.querySelectorAll('*'); 6335 return Array.prototype.filter.call(allChildren, function (child) { 6336 return (child instanceof window$1.HTMLAnchorElement || child instanceof window$1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window$1.HTMLInputElement || child instanceof window$1.HTMLSelectElement || child instanceof window$1.HTMLTextAreaElement || child instanceof window$1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window$1.HTMLIFrameElement || child instanceof window$1.HTMLObjectElement || child instanceof window$1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); 6337 }); 6338 }; 6339 6340 return ModalDialog; 6341 }(Component); 6342 /** 6343 * Default options for `ModalDialog` default options. 6344 * 6345 * @type {Object} 6346 * @private 6347 */ 6348 6349 6350 ModalDialog.prototype.options_ = { 6351 pauseOnOpen: true, 6352 temporary: true 6353 }; 6354 Component.registerComponent('ModalDialog', ModalDialog); 6355 6356 /** 6357 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and 6358 * {@link VideoTrackList} 6359 * 6360 * @extends EventTarget 6361 */ 6362 6363 var TrackList = 6364 /*#__PURE__*/ 6365 function (_EventTarget) { 6366 inheritsLoose(TrackList, _EventTarget); 6367 6368 /** 6369 * Create an instance of this class 6370 * 6371 * @param {Track[]} tracks 6372 * A list of tracks to initialize the list with. 6373 * 6374 * @abstract 6375 */ 6376 function TrackList(tracks) { 6377 var _this; 6378 6379 if (tracks === void 0) { 6380 tracks = []; 6381 } 6382 6383 _this = _EventTarget.call(this) || this; 6384 _this.tracks_ = []; 6385 /** 6386 * @memberof TrackList 6387 * @member {number} length 6388 * The current number of `Track`s in the this Trackist. 6389 * @instance 6390 */ 6391 6392 Object.defineProperty(assertThisInitialized(_this), 'length', { 6393 get: function get() { 6394 return this.tracks_.length; 6395 } 6396 }); 6397 6398 for (var i = 0; i < tracks.length; i++) { 6399 _this.addTrack(tracks[i]); 6400 } 6401 6402 return _this; 6403 } 6404 /** 6405 * Add a {@link Track} to the `TrackList` 6406 * 6407 * @param {Track} track 6408 * The audio, video, or text track to add to the list. 6409 * 6410 * @fires TrackList#addtrack 6411 */ 6412 6413 6414 var _proto = TrackList.prototype; 6415 6416 _proto.addTrack = function addTrack(track) { 6417 var index = this.tracks_.length; 6418 6419 if (!('' + index in this)) { 6420 Object.defineProperty(this, index, { 6421 get: function get() { 6422 return this.tracks_[index]; 6423 } 6424 }); 6425 } // Do not add duplicate tracks 6426 6427 6428 if (this.tracks_.indexOf(track) === -1) { 6429 this.tracks_.push(track); 6430 /** 6431 * Triggered when a track is added to a track list. 6432 * 6433 * @event TrackList#addtrack 6434 * @type {EventTarget~Event} 6435 * @property {Track} track 6436 * A reference to track that was added. 6437 */ 6438 6439 this.trigger({ 6440 track: track, 6441 type: 'addtrack', 6442 target: this 6443 }); 6444 } 6445 } 6446 /** 6447 * Remove a {@link Track} from the `TrackList` 6448 * 6449 * @param {Track} rtrack 6450 * The audio, video, or text track to remove from the list. 6451 * 6452 * @fires TrackList#removetrack 6453 */ 6454 ; 6455 6456 _proto.removeTrack = function removeTrack(rtrack) { 6457 var track; 6458 6459 for (var i = 0, l = this.length; i < l; i++) { 6460 if (this[i] === rtrack) { 6461 track = this[i]; 6462 6463 if (track.off) { 6464 track.off(); 6465 } 6466 6467 this.tracks_.splice(i, 1); 6468 break; 6469 } 6470 } 6471 6472 if (!track) { 6473 return; 6474 } 6475 /** 6476 * Triggered when a track is removed from track list. 6477 * 6478 * @event TrackList#removetrack 6479 * @type {EventTarget~Event} 6480 * @property {Track} track 6481 * A reference to track that was removed. 6482 */ 6483 6484 6485 this.trigger({ 6486 track: track, 6487 type: 'removetrack', 6488 target: this 6489 }); 6490 } 6491 /** 6492 * Get a Track from the TrackList by a tracks id 6493 * 6494 * @param {string} id - the id of the track to get 6495 * @method getTrackById 6496 * @return {Track} 6497 * @private 6498 */ 6499 ; 6500 6501 _proto.getTrackById = function getTrackById(id) { 6502 var result = null; 6503 6504 for (var i = 0, l = this.length; i < l; i++) { 6505 var track = this[i]; 6506 6507 if (track.id === id) { 6508 result = track; 6509 break; 6510 } 6511 } 6512 6513 return result; 6514 }; 6515 6516 return TrackList; 6517 }(EventTarget); 6518 /** 6519 * Triggered when a different track is selected/enabled. 6520 * 6521 * @event TrackList#change 6522 * @type {EventTarget~Event} 6523 */ 6524 6525 /** 6526 * Events that can be called with on + eventName. See {@link EventHandler}. 6527 * 6528 * @property {Object} TrackList#allowedEvents_ 6529 * @private 6530 */ 6531 6532 6533 TrackList.prototype.allowedEvents_ = { 6534 change: 'change', 6535 addtrack: 'addtrack', 6536 removetrack: 'removetrack' 6537 }; // emulate attribute EventHandler support to allow for feature detection 6538 6539 for (var event in TrackList.prototype.allowedEvents_) { 6540 TrackList.prototype['on' + event] = null; 6541 } 6542 6543 /** 6544 * Anywhere we call this function we diverge from the spec 6545 * as we only support one enabled audiotrack at a time 6546 * 6547 * @param {AudioTrackList} list 6548 * list to work on 6549 * 6550 * @param {AudioTrack} track 6551 * The track to skip 6552 * 6553 * @private 6554 */ 6555 6556 var disableOthers = function disableOthers(list, track) { 6557 for (var i = 0; i < list.length; i++) { 6558 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6559 continue; 6560 } // another audio track is enabled, disable it 6561 6562 6563 list[i].enabled = false; 6564 } 6565 }; 6566 /** 6567 * The current list of {@link AudioTrack} for a media file. 6568 * 6569 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} 6570 * @extends TrackList 6571 */ 6572 6573 6574 var AudioTrackList = 6575 /*#__PURE__*/ 6576 function (_TrackList) { 6577 inheritsLoose(AudioTrackList, _TrackList); 6578 6579 /** 6580 * Create an instance of this class. 6581 * 6582 * @param {AudioTrack[]} [tracks=[]] 6583 * A list of `AudioTrack` to instantiate the list with. 6584 */ 6585 function AudioTrackList(tracks) { 6586 var _this; 6587 6588 if (tracks === void 0) { 6589 tracks = []; 6590 } 6591 6592 // make sure only 1 track is enabled 6593 // sorted from last index to first index 6594 for (var i = tracks.length - 1; i >= 0; i--) { 6595 if (tracks[i].enabled) { 6596 disableOthers(tracks, tracks[i]); 6597 break; 6598 } 6599 } 6600 6601 _this = _TrackList.call(this, tracks) || this; 6602 _this.changing_ = false; 6603 return _this; 6604 } 6605 /** 6606 * Add an {@link AudioTrack} to the `AudioTrackList`. 6607 * 6608 * @param {AudioTrack} track 6609 * The AudioTrack to add to the list 6610 * 6611 * @fires TrackList#addtrack 6612 */ 6613 6614 6615 var _proto = AudioTrackList.prototype; 6616 6617 _proto.addTrack = function addTrack(track) { 6618 var _this2 = this; 6619 6620 if (track.enabled) { 6621 disableOthers(this, track); 6622 } 6623 6624 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6625 6626 6627 if (!track.addEventListener) { 6628 return; 6629 } 6630 6631 track.enabledChange_ = function () { 6632 // when we are disabling other tracks (since we don't support 6633 // more than one track at a time) we will set changing_ 6634 // to true so that we don't trigger additional change events 6635 if (_this2.changing_) { 6636 return; 6637 } 6638 6639 _this2.changing_ = true; 6640 disableOthers(_this2, track); 6641 _this2.changing_ = false; 6642 6643 _this2.trigger('change'); 6644 }; 6645 /** 6646 * @listens AudioTrack#enabledchange 6647 * @fires TrackList#change 6648 */ 6649 6650 6651 track.addEventListener('enabledchange', track.enabledChange_); 6652 }; 6653 6654 _proto.removeTrack = function removeTrack(rtrack) { 6655 _TrackList.prototype.removeTrack.call(this, rtrack); 6656 6657 if (rtrack.removeEventListener && rtrack.enabledChange_) { 6658 rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); 6659 rtrack.enabledChange_ = null; 6660 } 6661 }; 6662 6663 return AudioTrackList; 6664 }(TrackList); 6665 6666 /** 6667 * Un-select all other {@link VideoTrack}s that are selected. 6668 * 6669 * @param {VideoTrackList} list 6670 * list to work on 6671 * 6672 * @param {VideoTrack} track 6673 * The track to skip 6674 * 6675 * @private 6676 */ 6677 6678 var disableOthers$1 = function disableOthers(list, track) { 6679 for (var i = 0; i < list.length; i++) { 6680 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6681 continue; 6682 } // another video track is enabled, disable it 6683 6684 6685 list[i].selected = false; 6686 } 6687 }; 6688 /** 6689 * The current list of {@link VideoTrack} for a video. 6690 * 6691 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} 6692 * @extends TrackList 6693 */ 6694 6695 6696 var VideoTrackList = 6697 /*#__PURE__*/ 6698 function (_TrackList) { 6699 inheritsLoose(VideoTrackList, _TrackList); 6700 6701 /** 6702 * Create an instance of this class. 6703 * 6704 * @param {VideoTrack[]} [tracks=[]] 6705 * A list of `VideoTrack` to instantiate the list with. 6706 */ 6707 function VideoTrackList(tracks) { 6708 var _this; 6709 6710 if (tracks === void 0) { 6711 tracks = []; 6712 } 6713 6714 // make sure only 1 track is enabled 6715 // sorted from last index to first index 6716 for (var i = tracks.length - 1; i >= 0; i--) { 6717 if (tracks[i].selected) { 6718 disableOthers$1(tracks, tracks[i]); 6719 break; 6720 } 6721 } 6722 6723 _this = _TrackList.call(this, tracks) || this; 6724 _this.changing_ = false; 6725 /** 6726 * @member {number} VideoTrackList#selectedIndex 6727 * The current index of the selected {@link VideoTrack`}. 6728 */ 6729 6730 Object.defineProperty(assertThisInitialized(_this), 'selectedIndex', { 6731 get: function get() { 6732 for (var _i = 0; _i < this.length; _i++) { 6733 if (this[_i].selected) { 6734 return _i; 6735 } 6736 } 6737 6738 return -1; 6739 }, 6740 set: function set() {} 6741 }); 6742 return _this; 6743 } 6744 /** 6745 * Add a {@link VideoTrack} to the `VideoTrackList`. 6746 * 6747 * @param {VideoTrack} track 6748 * The VideoTrack to add to the list 6749 * 6750 * @fires TrackList#addtrack 6751 */ 6752 6753 6754 var _proto = VideoTrackList.prototype; 6755 6756 _proto.addTrack = function addTrack(track) { 6757 var _this2 = this; 6758 6759 if (track.selected) { 6760 disableOthers$1(this, track); 6761 } 6762 6763 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6764 6765 6766 if (!track.addEventListener) { 6767 return; 6768 } 6769 6770 track.selectedChange_ = function () { 6771 if (_this2.changing_) { 6772 return; 6773 } 6774 6775 _this2.changing_ = true; 6776 disableOthers$1(_this2, track); 6777 _this2.changing_ = false; 6778 6779 _this2.trigger('change'); 6780 }; 6781 /** 6782 * @listens VideoTrack#selectedchange 6783 * @fires TrackList#change 6784 */ 6785 6786 6787 track.addEventListener('selectedchange', track.selectedChange_); 6788 }; 6789 6790 _proto.removeTrack = function removeTrack(rtrack) { 6791 _TrackList.prototype.removeTrack.call(this, rtrack); 6792 6793 if (rtrack.removeEventListener && rtrack.selectedChange_) { 6794 rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); 6795 rtrack.selectedChange_ = null; 6796 } 6797 }; 6798 6799 return VideoTrackList; 6800 }(TrackList); 6801 6802 /** 6803 * The current list of {@link TextTrack} for a media file. 6804 * 6805 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} 6806 * @extends TrackList 6807 */ 6808 6809 var TextTrackList = 6810 /*#__PURE__*/ 6811 function (_TrackList) { 6812 inheritsLoose(TextTrackList, _TrackList); 6813 6814 function TextTrackList() { 6815 return _TrackList.apply(this, arguments) || this; 6816 } 6817 6818 var _proto = TextTrackList.prototype; 6819 6820 /** 6821 * Add a {@link TextTrack} to the `TextTrackList` 6822 * 6823 * @param {TextTrack} track 6824 * The text track to add to the list. 6825 * 6826 * @fires TrackList#addtrack 6827 */ 6828 _proto.addTrack = function addTrack(track) { 6829 var _this = this; 6830 6831 _TrackList.prototype.addTrack.call(this, track); 6832 6833 if (!this.queueChange_) { 6834 this.queueChange_ = function () { 6835 return _this.queueTrigger('change'); 6836 }; 6837 } 6838 6839 if (!this.triggerSelectedlanguagechange) { 6840 this.triggerSelectedlanguagechange_ = function () { 6841 return _this.trigger('selectedlanguagechange'); 6842 }; 6843 } 6844 /** 6845 * @listens TextTrack#modechange 6846 * @fires TrackList#change 6847 */ 6848 6849 6850 track.addEventListener('modechange', this.queueChange_); 6851 var nonLanguageTextTrackKind = ['metadata', 'chapters']; 6852 6853 if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { 6854 track.addEventListener('modechange', this.triggerSelectedlanguagechange_); 6855 } 6856 }; 6857 6858 _proto.removeTrack = function removeTrack(rtrack) { 6859 _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added 6860 6861 6862 if (rtrack.removeEventListener) { 6863 if (this.queueChange_) { 6864 rtrack.removeEventListener('modechange', this.queueChange_); 6865 } 6866 6867 if (this.selectedlanguagechange_) { 6868 rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); 6869 } 6870 } 6871 }; 6872 6873 return TextTrackList; 6874 }(TrackList); 6875 6876 /** 6877 * @file html-track-element-list.js 6878 */ 6879 6880 /** 6881 * The current list of {@link HtmlTrackElement}s. 6882 */ 6883 var HtmlTrackElementList = 6884 /*#__PURE__*/ 6885 function () { 6886 /** 6887 * Create an instance of this class. 6888 * 6889 * @param {HtmlTrackElement[]} [tracks=[]] 6890 * A list of `HtmlTrackElement` to instantiate the list with. 6891 */ 6892 function HtmlTrackElementList(trackElements) { 6893 if (trackElements === void 0) { 6894 trackElements = []; 6895 } 6896 6897 this.trackElements_ = []; 6898 /** 6899 * @memberof HtmlTrackElementList 6900 * @member {number} length 6901 * The current number of `Track`s in the this Trackist. 6902 * @instance 6903 */ 6904 6905 Object.defineProperty(this, 'length', { 6906 get: function get() { 6907 return this.trackElements_.length; 6908 } 6909 }); 6910 6911 for (var i = 0, length = trackElements.length; i < length; i++) { 6912 this.addTrackElement_(trackElements[i]); 6913 } 6914 } 6915 /** 6916 * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` 6917 * 6918 * @param {HtmlTrackElement} trackElement 6919 * The track element to add to the list. 6920 * 6921 * @private 6922 */ 6923 6924 6925 var _proto = HtmlTrackElementList.prototype; 6926 6927 _proto.addTrackElement_ = function addTrackElement_(trackElement) { 6928 var index = this.trackElements_.length; 6929 6930 if (!('' + index in this)) { 6931 Object.defineProperty(this, index, { 6932 get: function get() { 6933 return this.trackElements_[index]; 6934 } 6935 }); 6936 } // Do not add duplicate elements 6937 6938 6939 if (this.trackElements_.indexOf(trackElement) === -1) { 6940 this.trackElements_.push(trackElement); 6941 } 6942 } 6943 /** 6944 * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an 6945 * {@link TextTrack}. 6946 * 6947 * @param {TextTrack} track 6948 * The track associated with a track element. 6949 * 6950 * @return {HtmlTrackElement|undefined} 6951 * The track element that was found or undefined. 6952 * 6953 * @private 6954 */ 6955 ; 6956 6957 _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { 6958 var trackElement_; 6959 6960 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6961 if (track === this.trackElements_[i].track) { 6962 trackElement_ = this.trackElements_[i]; 6963 break; 6964 } 6965 } 6966 6967 return trackElement_; 6968 } 6969 /** 6970 * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` 6971 * 6972 * @param {HtmlTrackElement} trackElement 6973 * The track element to remove from the list. 6974 * 6975 * @private 6976 */ 6977 ; 6978 6979 _proto.removeTrackElement_ = function removeTrackElement_(trackElement) { 6980 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6981 if (trackElement === this.trackElements_[i]) { 6982 if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { 6983 this.trackElements_[i].track.off(); 6984 } 6985 6986 if (typeof this.trackElements_[i].off === 'function') { 6987 this.trackElements_[i].off(); 6988 } 6989 6990 this.trackElements_.splice(i, 1); 6991 break; 6992 } 6993 } 6994 }; 6995 6996 return HtmlTrackElementList; 6997 }(); 6998 6999 /** 7000 * @file text-track-cue-list.js 7001 */ 7002 7003 /** 7004 * @typedef {Object} TextTrackCueList~TextTrackCue 7005 * 7006 * @property {string} id 7007 * The unique id for this text track cue 7008 * 7009 * @property {number} startTime 7010 * The start time for this text track cue 7011 * 7012 * @property {number} endTime 7013 * The end time for this text track cue 7014 * 7015 * @property {boolean} pauseOnExit 7016 * Pause when the end time is reached if true. 7017 * 7018 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} 7019 */ 7020 7021 /** 7022 * A List of TextTrackCues. 7023 * 7024 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} 7025 */ 7026 var TextTrackCueList = 7027 /*#__PURE__*/ 7028 function () { 7029 /** 7030 * Create an instance of this class.. 7031 * 7032 * @param {Array} cues 7033 * A list of cues to be initialized with 7034 */ 7035 function TextTrackCueList(cues) { 7036 TextTrackCueList.prototype.setCues_.call(this, cues); 7037 /** 7038 * @memberof TextTrackCueList 7039 * @member {number} length 7040 * The current number of `TextTrackCue`s in the TextTrackCueList. 7041 * @instance 7042 */ 7043 7044 Object.defineProperty(this, 'length', { 7045 get: function get() { 7046 return this.length_; 7047 } 7048 }); 7049 } 7050 /** 7051 * A setter for cues in this list. Creates getters 7052 * an an index for the cues. 7053 * 7054 * @param {Array} cues 7055 * An array of cues to set 7056 * 7057 * @private 7058 */ 7059 7060 7061 var _proto = TextTrackCueList.prototype; 7062 7063 _proto.setCues_ = function setCues_(cues) { 7064 var oldLength = this.length || 0; 7065 var i = 0; 7066 var l = cues.length; 7067 this.cues_ = cues; 7068 this.length_ = cues.length; 7069 7070 var defineProp = function defineProp(index) { 7071 if (!('' + index in this)) { 7072 Object.defineProperty(this, '' + index, { 7073 get: function get() { 7074 return this.cues_[index]; 7075 } 7076 }); 7077 } 7078 }; 7079 7080 if (oldLength < l) { 7081 i = oldLength; 7082 7083 for (; i < l; i++) { 7084 defineProp.call(this, i); 7085 } 7086 } 7087 } 7088 /** 7089 * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. 7090 * 7091 * @param {string} id 7092 * The id of the cue that should be searched for. 7093 * 7094 * @return {TextTrackCueList~TextTrackCue|null} 7095 * A single cue or null if none was found. 7096 */ 7097 ; 7098 7099 _proto.getCueById = function getCueById(id) { 7100 var result = null; 7101 7102 for (var i = 0, l = this.length; i < l; i++) { 7103 var cue = this[i]; 7104 7105 if (cue.id === id) { 7106 result = cue; 7107 break; 7108 } 7109 } 7110 7111 return result; 7112 }; 7113 7114 return TextTrackCueList; 7115 }(); 7116 7117 /** 7118 * @file track-kinds.js 7119 */ 7120 7121 /** 7122 * All possible `VideoTrackKind`s 7123 * 7124 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind 7125 * @typedef VideoTrack~Kind 7126 * @enum 7127 */ 7128 var VideoTrackKind = { 7129 alternative: 'alternative', 7130 captions: 'captions', 7131 main: 'main', 7132 sign: 'sign', 7133 subtitles: 'subtitles', 7134 commentary: 'commentary' 7135 }; 7136 /** 7137 * All possible `AudioTrackKind`s 7138 * 7139 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind 7140 * @typedef AudioTrack~Kind 7141 * @enum 7142 */ 7143 7144 var AudioTrackKind = { 7145 'alternative': 'alternative',
7146 'descriptions': 'descriptions', 7147 'main': 'main', 7148 'main-desc': 'main-desc', 7149 'translation': 'translation', 7150 'commentary': 'commentary' 7151 }; 7152 /** 7153 * All possible `TextTrackKind`s 7154 * 7155 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind 7156 * @typedef TextTrack~Kind 7157 * @enum 7158 */ 7159 7160 var TextTrackKind = { 7161 subtitles: 'subtitles', 7162 captions: 'captions', 7163 descriptions: 'descriptions', 7164 chapters: 'chapters', 7165 metadata: 'metadata' 7166 }; 7167 /** 7168 * All possible `TextTrackMode`s 7169 * 7170 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode 7171 * @typedef TextTrack~Mode 7172 * @enum 7173 */ 7174 7175 var TextTrackMode = { 7176 disabled: 'disabled', 7177 hidden: 'hidden', 7178 showing: 'showing' 7179 }; 7180 7181 /** 7182 * A Track class that contains all of the common functionality for {@link AudioTrack}, 7183 * {@link VideoTrack}, and {@link TextTrack}. 7184 * 7185 * > Note: This class should not be used directly 7186 * 7187 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} 7188 * @extends EventTarget 7189 * @abstract 7190 */ 7191 7192 var Track = 7193 /*#__PURE__*/ 7194 function (_EventTarget) { 7195 inheritsLoose(Track, _EventTarget); 7196 7197 /** 7198 * Create an instance of this class. 7199 * 7200 * @param {Object} [options={}] 7201 * Object of option names and values 7202 * 7203 * @param {string} [options.kind=''] 7204 * A valid kind for the track type you are creating. 7205 * 7206 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7207 * A unique id for this AudioTrack. 7208 * 7209 * @param {string} [options.label=''] 7210 * The menu label for this track. 7211 * 7212 * @param {string} [options.language=''] 7213 * A valid two character language code. 7214 * 7215 * @abstract 7216 */ 7217 function Track(options) { 7218 var _this; 7219 7220 if (options === void 0) { 7221 options = {}; 7222 } 7223 7224 _this = _EventTarget.call(this) || this; 7225 var trackProps = { 7226 id: options.id || 'vjs_track_' + newGUID(), 7227 kind: options.kind || '', 7228 label: options.label || '', 7229 language: options.language || '' 7230 }; 7231 /** 7232 * @memberof Track 7233 * @member {string} id 7234 * The id of this track. Cannot be changed after creation. 7235 * @instance 7236 * 7237 * @readonly 7238 */ 7239 7240 /** 7241 * @memberof Track 7242 * @member {string} kind 7243 * The kind of track that this is. Cannot be changed after creation. 7244 * @instance 7245 * 7246 * @readonly 7247 */ 7248 7249 /** 7250 * @memberof Track 7251 * @member {string} label 7252 * The label of this track. Cannot be changed after creation. 7253 * @instance 7254 * 7255 * @readonly 7256 */ 7257 7258 /** 7259 * @memberof Track 7260 * @member {string} language 7261 * The two letter language code for this track. Cannot be changed after 7262 * creation. 7263 * @instance 7264 * 7265 * @readonly 7266 */ 7267 7268 var _loop = function _loop(key) { 7269 Object.defineProperty(assertThisInitialized(_this), key, { 7270 get: function get() { 7271 return trackProps[key]; 7272 }, 7273 set: function set() {} 7274 }); 7275 }; 7276 7277 for (var key in trackProps) { 7278 _loop(key); 7279 } 7280 7281 return _this; 7282 } 7283 7284 return Track; 7285 }(EventTarget); 7286 7287 /** 7288 * @file url.js 7289 * @module url 7290 */ 7291 /** 7292 * @typedef {Object} url:URLObject 7293 * 7294 * @property {string} protocol 7295 * The protocol of the url that was parsed. 7296 * 7297 * @property {string} hostname 7298 * The hostname of the url that was parsed. 7299 * 7300 * @property {string} port 7301 * The port of the url that was parsed. 7302 * 7303 * @property {string} pathname 7304 * The pathname of the url that was parsed. 7305 * 7306 * @property {string} search 7307 * The search query of the url that was parsed. 7308 * 7309 * @property {string} hash 7310 * The hash of the url that was parsed. 7311 * 7312 * @property {string} host 7313 * The host of the url that was parsed. 7314 */ 7315 7316 /** 7317 * Resolve and parse the elements of a URL. 7318 * 7319 * @function 7320 * @param {String} url 7321 * The url to parse 7322 * 7323 * @return {url:URLObject} 7324 * An object of url details 7325 */ 7326 7327 var parseUrl = function parseUrl(url) { 7328 var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL 7329 7330 var a = document.createElement('a'); 7331 a.href = url; // IE8 (and 9?) Fix
7332 // ie8 doesn't parse the URL correctly until the anchor is actually 7333 // added to the body, and an innerHTML is needed to trigger the parsing 7334 7335 var addToBody = a.host === '' && a.protocol !== 'file:'; 7336 var div; 7337 7338 if (addToBody) { 7339 div = document.createElement('div'); 7340 div.innerHTML = "<a href=\"" + url + "\"></a>"; 7341 a = div.firstChild; // prevent the div from affecting layout 7342 7343 div.setAttribute('style', 'display:none; position:absolute;'); 7344 document.body.appendChild(div); 7345 } // Copy the specific URL properties to a new object 7346 // This is also needed for IE8 because the anchor loses its 7347 // properties when it's removed from the dom 7348 7349 7350 var details = {}; 7351 7352 for (var i = 0; i < props.length; i++) { 7353 details[props[i]] = a[props[i]]; 7354 } // IE9 adds the port to the host property unlike everyone else. If 7355 // a port identifier is added for standard ports, strip it. 7356 7357 7358 if (details.protocol === 'http:') { 7359 details.host = details.host.replace(/:80$/, ''); 7360 } 7361 7362 if (details.protocol === 'https:') { 7363 details.host = details.host.replace(/:443$/, ''); 7364 } 7365 7366 if (!details.protocol) { 7367 details.protocol = window$1.location.protocol; 7368 } 7369 7370 if (addToBody) { 7371 document.body.removeChild(div); 7372 } 7373 7374 return details; 7375 }; 7376 /** 7377 * Get absolute version of relative URL. Used to tell Flash the correct URL. 7378 * 7379 * @function 7380 * @param {string} url 7381 * URL to make absolute 7382 * 7383 * @return {string} 7384 * Absolute URL 7385 * 7386 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 7387 */ 7388 7389 var getAbsoluteURL = function getAbsoluteURL(url) { 7390 // Check if absolute URL 7391 if (!url.match(/^https?:\/\//)) { 7392 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 7393 var div = document.createElement('div'); 7394 div.innerHTML = "<a href=\"" + url + "\">x</a>"; 7395 url = div.firstChild.href; 7396 } 7397 7398 return url; 7399 }; 7400 /** 7401 * Returns the extension of the passed file name. It will return an empty string 7402 * if passed an invalid path. 7403 * 7404 * @function 7405 * @param {string} path 7406 * The fileName path like '/path/to/file.mp4' 7407 * 7408 * @return {string} 7409 * The extension in lower case or an empty string if no 7410 * extension could be found. 7411 */ 7412 7413 var getFileExtension = function getFileExtension(path) { 7414 if (typeof path === 'string') { 7415 var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/; 7416 var pathParts = splitPathRe.exec(path); 7417 7418 if (pathParts) { 7419 return pathParts.pop().toLowerCase(); 7420 } 7421 } 7422 7423 return ''; 7424 }; 7425 /** 7426 * Returns whether the url passed is a cross domain request or not. 7427 * 7428 * @function 7429 * @param {string} url 7430 * The url to check. 7431 * 7432 * @param {Object} [winLoc] 7433 * the domain to check the url against, defaults to window.location 7434 * 7435 * @param {string} [winLoc.protocol] 7436 * The window location protocol defaults to window.location.protocol 7437 * 7438 * @param {string} [winLoc.host] 7439 * The window location host defaults to window.location.host 7440 * 7441 * @return {boolean} 7442 * Whether it is a cross domain request or not. 7443 */ 7444 7445 var isCrossOrigin = function isCrossOrigin(url, winLoc) { 7446 if (winLoc === void 0) { 7447 winLoc = window$1.location; 7448 } 7449 7450 var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol 7451 7452 var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin 7453 // IE8 doesn't know location.origin, so we won't rely on it here 7454 7455 var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; 7456 return crossOrigin; 7457 }; 7458 7459 var Url = /*#__PURE__*/Object.freeze({ 7460 parseUrl: parseUrl, 7461 getAbsoluteURL: getAbsoluteURL, 7462 getFileExtension: getFileExtension, 7463 isCrossOrigin: isCrossOrigin 7464 }); 7465 7466 var isFunction_1 = isFunction; 7467 var toString$1 = Object.prototype.toString; 7468 7469 function isFunction(fn) { 7470 var string = toString$1.call(fn); 7471 return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below 7472 fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt); 7473 } 7474 7475 /** 7476 * @license 7477 * slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/> 7478 * Copyright (c) 2014 David Björklund 7479 * Available under the MIT license 7480 * <https://github.com/kesla/parse-headers/blob/master/LICENCE> 7481 */ 7482 7483 7484 var parseHeaders = function parseHeaders(headers) { 7485 var result = {}; 7486 7487 if (!headers) { 7488 return result; 7489 } 7490 7491 headers.trim().split('\n').forEach(function (row) { 7492 var index = row.indexOf(':'); 7493 var key = row.slice(0, index).trim().toLowerCase(); 7494 var value = row.slice(index + 1).trim(); 7495 7496 if (typeof result[key] === 'undefined') { 7497 result[key] = value; 7498 } else if (Array.isArray(result[key])) { 7499 result[key].push(value); 7500 } else { 7501 result[key] = [result[key], value]; 7502 } 7503 });
vendor: 4,358 bytes, lines 7504-7670
7504 return result; 7505 }; 7506 7507 var xhr = createXHR; // Allow use of default import syntax in TypeScript 7508 7509 var default_1 = createXHR; 7510 createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop; 7511 createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest; 7512 forEachArray(["get", "put", "post", "patch", "head", "delete"], function (method) { 7513 createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) { 7514 options = initParams(uri, options, callback); 7515 options.method = method.toUpperCase(); 7516 return _createXHR(options); 7517 }; 7518 }); 7519 7520 function forEachArray(array, iterator) { 7521 for (var i = 0; i < array.length; i++) { 7522 iterator(array[i]); 7523 } 7524 } 7525 7526 function isEmpty(obj) { 7527 for (var i in obj) { 7528 if (obj.hasOwnProperty(i)) return false; 7529 } 7530 7531 return true; 7532 } 7533 7534 function initParams(uri, options, callback) { 7535 var params = uri; 7536 7537 if (isFunction_1(options)) { 7538 callback = options; 7539 7540 if (typeof uri === "string") { 7541 params = { 7542 uri: uri 7543 }; 7544 } 7545 } else { 7546 params = _extends_1({}, options, { 7547 uri: uri 7548 }); 7549 } 7550 7551 params.callback = callback; 7552 return params; 7553 } 7554 7555 function createXHR(uri, options, callback) { 7556 options = initParams(uri, options, callback); 7557 return _createXHR(options); 7558 } 7559 7560 function _createXHR(options) { 7561 if (typeof options.callback === "undefined") { 7562 throw new Error("callback argument missing"); 7563 } 7564 7565 var called = false; 7566 7567 var callback = function cbOnce(err, response, body) { 7568 if (!called) { 7569 called = true; 7570 options.callback(err, response, body); 7571 } 7572 }; 7573 7574 function readystatechange() { 7575 if (xhr.readyState === 4) { 7576 setTimeout(loadFunc, 0); 7577 } 7578 } 7579 7580 function getBody() { 7581 // Chrome with requestType=blob throws errors arround when even testing access to responseText 7582 var body = undefined; 7583 7584 if (xhr.response) { 7585 body = xhr.response; 7586 } else { 7587 body = xhr.responseText || getXml(xhr); 7588 } 7589 7590 if (isJson) { 7591 try { 7592 body = JSON.parse(body); 7593 } catch (e) {} 7594 } 7595 7596 return body; 7597 } 7598 7599 function errorFunc(evt) { 7600 clearTimeout(timeoutTimer); 7601 7602 if (!(evt instanceof Error)) { 7603 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error")); 7604 } 7605 7606 evt.statusCode = 0; 7607 return callback(evt, failureResponse); 7608 } // will load the data & process the response in a special response object 7609 7610 7611 function loadFunc() { 7612 if (aborted) return; 7613 var status; 7614 clearTimeout(timeoutTimer); 7615 7616 if (options.useXDR && xhr.status === undefined) { 7617 //IE8 CORS GET successful response doesn't have a status field, but body is fine 7618 status = 200; 7619 } else { 7620 status = xhr.status === 1223 ? 204 : xhr.status; 7621 } 7622 7623 var response = failureResponse; 7624 var err = null; 7625 7626 if (status !== 0) { 7627 response = { 7628 body: getBody(), 7629 statusCode: status, 7630 method: method, 7631 headers: {}, 7632 url: uri, 7633 rawRequest: xhr 7634 }; 7635 7636 if (xhr.getAllResponseHeaders) { 7637 //remember xhr can in fact be XDR for CORS in IE 7638 response.headers = parseHeaders(xhr.getAllResponseHeaders()); 7639 } 7640 } else { 7641 err = new Error("Internal XMLHttpRequest Error"); 7642 } 7643 7644 return callback(err, response, response.body); 7645 } 7646 7647 var xhr = options.xhr || null; 7648 7649 if (!xhr) { 7650 if (options.cors || options.useXDR) { 7651 xhr = new createXHR.XDomainRequest(); 7652 } else { 7653 xhr = new createXHR.XMLHttpRequest(); 7654 } 7655 } 7656 7657 var key; 7658 var aborted; 7659 var uri = xhr.url = options.uri || options.url; 7660 var method = xhr.method = options.method || "GET"; 7661 var body = options.body || options.data; 7662 var headers = xhr.headers = options.headers || {}; 7663 var sync = !!options.sync; 7664 var isJson = false; 7665 var timeoutTimer; 7666 var failureResponse = { 7667 body: undefined, 7668 headers: {}, 7669 statusCode: 0, 7670 method: method,
vendor: 4,117 bytes, lines 7671-7802
7671 url: uri, 7672 rawRequest: xhr 7673 }; 7674 7675 if ("json" in options && options.json !== false) { 7676 isJson = true; 7677 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 7678 7679 if (method !== "GET" && method !== "HEAD") { 7680 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 7681 7682 body = JSON.stringify(options.json === true ? body : options.json); 7683 } 7684 } 7685 7686 xhr.onreadystatechange = readystatechange; 7687 xhr.onload = loadFunc; 7688 xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function. 7689 7690 xhr.onprogress = function () {// IE must die 7691 }; 7692 7693 xhr.onabort = function () { 7694 aborted = true; 7695 }; 7696 7697 xhr.ontimeout = errorFunc; 7698 xhr.open(method, uri, !sync, options.username, options.password); //has to be after open 7699 7700 if (!sync) { 7701 xhr.withCredentials = !!options.withCredentials; 7702 } // Cannot set timeout with sync request 7703 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 7704 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 7705 7706 7707 if (!sync && options.timeout > 0) { 7708 timeoutTimer = setTimeout(function () { 7709 if (aborted) return; 7710 aborted = true; //IE9 may still call readystatechange 7711 7712 xhr.abort("timeout"); 7713 var e = new Error("XMLHttpRequest timeout"); 7714 e.code = "ETIMEDOUT"; 7715 errorFunc(e); 7716 }, options.timeout); 7717 } 7718 7719 if (xhr.setRequestHeader) { 7720 for (key in headers) { 7721 if (headers.hasOwnProperty(key)) { 7722 xhr.setRequestHeader(key, headers[key]); 7723 } 7724 } 7725 } else if (options.headers && !isEmpty(options.headers)) { 7726 throw new Error("Headers cannot be set on an XDomainRequest object"); 7727 } 7728 7729 if ("responseType" in options) { 7730 xhr.responseType = options.responseType; 7731 } 7732 7733 if ("beforeSend" in options && typeof options.beforeSend === "function") { 7734 options.beforeSend(xhr); 7735 } // Microsoft Edge browser sends "undefined" when send is called with undefined value. 7736 // XMLHttpRequest spec says to pass null as body to indicate no body 7737 // See https://github.com/naugtur/xhr/issues/100. 7738 7739 7740 xhr.send(body || null); 7741 return xhr; 7742 } 7743 7744 function getXml(xhr) { 7745 // xhr.responseXML will throw Exception "InvalidStateError" or "DOMException" 7746 // See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML. 7747 try { 7748 if (xhr.responseType === "document") { 7749 return xhr.responseXML; 7750 } 7751 7752 var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 7753 7754 if (xhr.responseType === "" && !firefoxBugTakenEffect) { 7755 return xhr.responseXML; 7756 } 7757 } catch (e) {} 7758 7759 return null; 7760 } 7761 7762 function noop() {} 7763 xhr["default"] = default_1; 7764 7765 /** 7766 * Takes a webvtt file contents and parses it into cues 7767 * 7768 * @param {string} srcContent 7769 * webVTT file contents 7770 * 7771 * @param {TextTrack} track 7772 * TextTrack to add cues to. Cues come from the srcContent. 7773 * 7774 * @private 7775 */ 7776 7777 var parseCues = function parseCues(srcContent, track) { 7778 var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder()); 7779 var errors = []; 7780 7781 parser.oncue = function (cue) { 7782 track.addCue(cue); 7783 }; 7784 7785 parser.onparsingerror = function (error) { 7786 errors.push(error); 7787 }; 7788 7789 parser.onflush = function () { 7790 track.trigger({ 7791 type: 'loadeddata', 7792 target: track 7793 }); 7794 }; 7795 7796 parser.parse(srcContent); 7797 7798 if (errors.length > 0) { 7799 if (window$1.console && window$1.console.groupCollapsed) { 7800 window$1.console.groupCollapsed("Text Track parsing errors for " + track.src); 7801 } 7802
7803 errors.forEach(function (error) { 7804 return log.error(error); 7805 }); 7806 7807 if (window$1.console && window$1.console.groupEnd) { 7808 window$1.console.groupEnd(); 7809 } 7810 } 7811 7812 parser.flush(); 7813 }; 7814 /** 7815 * Load a `TextTrack` from a specified url. 7816 * 7817 * @param {string} src 7818 * Url to load track from. 7819 * 7820 * @param {TextTrack} track 7821 * Track to add cues to. Comes from the content at the end of `url`. 7822 * 7823 * @private 7824 */ 7825 7826 7827 var loadTrack = function loadTrack(src, track) { 7828 var opts = { 7829 uri: src 7830 }; 7831 var crossOrigin = isCrossOrigin(src); 7832 7833 if (crossOrigin) { 7834 opts.cors = crossOrigin; 7835 } 7836 7837 xhr(opts, bind(this, function (err, response, responseBody) { 7838 if (err) { 7839 return log.error(err, response); 7840 } 7841 7842 track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 7843 // NOTE: this is only used for the alt/video.novtt.js build 7844 7845 if (typeof window$1.WebVTT !== 'function') { 7846 if (track.tech_) { 7847 // to prevent use before define eslint error, we define loadHandler 7848 // as a let here 7849 track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) { 7850 if (event.type === 'vttjserror') { 7851 log.error("vttjs failed to load, stopping trying to process " + track.src); 7852 return; 7853 } 7854 7855 return parseCues(responseBody, track); 7856 }); 7857 } 7858 } else { 7859 parseCues(responseBody, track); 7860 } 7861 })); 7862 }; 7863 /** 7864 * A representation of a single `TextTrack`. 7865 * 7866 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} 7867 * @extends Track 7868 */ 7869 7870 7871 var TextTrack = 7872 /*#__PURE__*/ 7873 function (_Track) { 7874 inheritsLoose(TextTrack, _Track); 7875 7876 /** 7877 * Create an instance of this class. 7878 * 7879 * @param {Object} options={} 7880 * Object of option names and values 7881 * 7882 * @param {Tech} options.tech 7883 * A reference to the tech that owns this TextTrack. 7884 * 7885 * @param {TextTrack~Kind} [options.kind='subtitles'] 7886 * A valid text track kind. 7887 * 7888 * @param {TextTrack~Mode} [options.mode='disabled'] 7889 * A valid text track mode. 7890 * 7891 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7892 * A unique id for this TextTrack. 7893 * 7894 * @param {string} [options.label=''] 7895 * The menu label for this track. 7896 * 7897 * @param {string} [options.language=''] 7898 * A valid two character language code. 7899 * 7900 * @param {string} [options.srclang=''] 7901 * A valid two character language code. An alternative, but deprioritized 7902 * version of `options.language` 7903 * 7904 * @param {string} [options.src] 7905 * A url to TextTrack cues. 7906 * 7907 * @param {boolean} [options.default] 7908 * If this track should default to on or off. 7909 */ 7910 function TextTrack(options) { 7911 var _this; 7912 7913 if (options === void 0) { 7914 options = {}; 7915 } 7916 7917 if (!options.tech) { 7918 throw new Error('A tech was not provided.'); 7919 } 7920 7921 var settings = mergeOptions(options, { 7922 kind: TextTrackKind[options.kind] || 'subtitles', 7923 language: options.language || options.srclang || '' 7924 }); 7925 var mode = TextTrackMode[settings.mode] || 'disabled'; 7926 var default_ = settings["default"]; 7927 7928 if (settings.kind === 'metadata' || settings.kind === 'chapters') { 7929 mode = 'hidden'; 7930 } 7931 7932 _this = _Track.call(this, settings) || this; 7933 _this.tech_ = settings.tech; 7934 _this.cues_ = []; 7935 _this.activeCues_ = []; 7936 _this.preload_ = _this.tech_.preloadTextTracks !== false; 7937 var cues = new TextTrackCueList(_this.cues_); 7938 var activeCues = new TextTrackCueList(_this.activeCues_); 7939 var changed = false; 7940 var timeupdateHandler = bind(assertThisInitialized(_this), function () { 7941 // Accessing this.activeCues for the side-effects of updating itself 7942 // due to its nature as a getter function. Do not remove or cues will 7943 // stop updating!
vendor: 8,677 bytes, lines 7944-8243
7944 // Use the setter to prevent deletion from uglify (pure_getters rule) 7945 this.activeCues = this.activeCues; 7946 7947 if (changed) { 7948 this.trigger('cuechange'); 7949 changed = false; 7950 } 7951 }); 7952 7953 if (mode !== 'disabled') { 7954 _this.tech_.ready(function () { 7955 _this.tech_.on('timeupdate', timeupdateHandler); 7956 }, true); 7957 } 7958 7959 Object.defineProperties(assertThisInitialized(_this), { 7960 /** 7961 * @memberof TextTrack 7962 * @member {boolean} default 7963 * If this track was set to be on or off by default. Cannot be changed after 7964 * creation. 7965 * @instance 7966 * 7967 * @readonly 7968 */ 7969 "default": { 7970 get: function get() { 7971 return default_; 7972 }, 7973 set: function set() {} 7974 }, 7975 7976 /** 7977 * @memberof TextTrack 7978 * @member {string} mode 7979 * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will 7980 * not be set if setting to an invalid mode. 7981 * @instance 7982 * 7983 * @fires TextTrack#modechange 7984 */ 7985 mode: { 7986 get: function get() { 7987 return mode; 7988 }, 7989 set: function set(newMode) { 7990 var _this2 = this; 7991 7992 if (!TextTrackMode[newMode]) { 7993 return; 7994 } 7995 7996 mode = newMode; 7997 7998 if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) { 7999 // On-demand load. 8000 loadTrack(this.src, this); 8001 } 8002 8003 if (mode !== 'disabled') { 8004 this.tech_.ready(function () { 8005 _this2.tech_.on('timeupdate', timeupdateHandler); 8006 }, true); 8007 } else { 8008 this.tech_.off('timeupdate', timeupdateHandler); 8009 } 8010 /** 8011 * An event that fires when mode changes on this track. This allows 8012 * the TextTrackList that holds this track to act accordingly. 8013 * 8014 * > Note: This is not part of the spec! 8015 * 8016 * @event TextTrack#modechange 8017 * @type {EventTarget~Event} 8018 */ 8019 8020 8021 this.trigger('modechange'); 8022 } 8023 }, 8024 8025 /** 8026 * @memberof TextTrack 8027 * @member {TextTrackCueList} cues 8028 * The text track cue list for this TextTrack. 8029 * @instance 8030 */ 8031 cues: { 8032 get: function get() { 8033 if (!this.loaded_) { 8034 return null; 8035 } 8036 8037 return cues; 8038 }, 8039 set: function set() {} 8040 }, 8041 8042 /** 8043 * @memberof TextTrack 8044 * @member {TextTrackCueList} activeCues 8045 * The list text track cues that are currently active for this TextTrack. 8046 * @instance 8047 */ 8048 activeCues: { 8049 get: function get() { 8050 if (!this.loaded_) { 8051 return null; 8052 } // nothing to do 8053 8054 8055 if (this.cues.length === 0) { 8056 return activeCues; 8057 } 8058 8059 var ct = this.tech_.currentTime(); 8060 var active = []; 8061 8062 for (var i = 0, l = this.cues.length; i < l; i++) { 8063 var cue = this.cues[i]; 8064 8065 if (cue.startTime <= ct && cue.endTime >= ct) { 8066 active.push(cue); 8067 } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { 8068 active.push(cue); 8069 } 8070 } 8071 8072 changed = false; 8073 8074 if (active.length !== this.activeCues_.length) { 8075 changed = true; 8076 } else { 8077 for (var _i = 0; _i < active.length; _i++) { 8078 if (this.activeCues_.indexOf(active[_i]) === -1) { 8079 changed = true; 8080 } 8081 } 8082 } 8083 8084 this.activeCues_ = active; 8085 activeCues.setCues_(this.activeCues_); 8086 return activeCues; 8087 }, 8088 // /!\ Keep this setter empty (see the timeupdate handler above) 8089 set: function set() {} 8090 } 8091 }); 8092 8093 if (settings.src) { 8094 _this.src = settings.src; 8095 8096 if (!_this.preload_) { 8097 // Tracks will load on-demand. 8098 // Act like we're loaded for other purposes. 8099 _this.loaded_ = true; 8100 } 8101 8102 if (_this.preload_ || default_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') { 8103 loadTrack(_this.src, assertThisInitialized(_this)); 8104 } 8105 } else { 8106 _this.loaded_ = true; 8107 } 8108 8109 return _this; 8110 } 8111 /** 8112 * Add a cue to the internal list of cues. 8113 * 8114 * @param {TextTrack~Cue} cue 8115 * The cue to add to our internal list 8116 */ 8117 8118 8119 var _proto = TextTrack.prototype; 8120 8121 _proto.addCue = function addCue(originalCue) { 8122 var cue = originalCue; 8123 8124 if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) { 8125 cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); 8126 8127 for (var prop in originalCue) { 8128 if (!(prop in cue)) { 8129 cue[prop] = originalCue[prop]; 8130 } 8131 } // make sure that `id` is copied over 8132 8133 8134 cue.id = originalCue.id; 8135 cue.originalCue_ = originalCue; 8136 } 8137 8138 var tracks = this.tech_.textTracks(); 8139 8140 for (var i = 0; i < tracks.length; i++) { 8141 if (tracks[i] !== this) { 8142 tracks[i].removeCue(cue); 8143 } 8144 } 8145 8146 this.cues_.push(cue); 8147 this.cues.setCues_(this.cues_); 8148 } 8149 /** 8150 * Remove a cue from our internal list 8151 * 8152 * @param {TextTrack~Cue} removeCue 8153 * The cue to remove from our internal list 8154 */ 8155 ; 8156 8157 _proto.removeCue = function removeCue(_removeCue) { 8158 var i = this.cues_.length; 8159 8160 while (i--) { 8161 var cue = this.cues_[i]; 8162 8163 if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { 8164 this.cues_.splice(i, 1); 8165 this.cues.setCues_(this.cues_); 8166 break; 8167 } 8168 } 8169 }; 8170 8171 return TextTrack; 8172 }(Track); 8173 /** 8174 * cuechange - One or more cues in the track have become active or stopped being active. 8175 */ 8176 8177 8178 TextTrack.prototype.allowedEvents_ = { 8179 cuechange: 'cuechange' 8180 }; 8181 8182 /** 8183 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} 8184 * only one `AudioTrack` in the list will be enabled at a time. 8185 * 8186 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} 8187 * @extends Track 8188 */ 8189 8190 var AudioTrack = 8191 /*#__PURE__*/ 8192 function (_Track) { 8193 inheritsLoose(AudioTrack, _Track); 8194 8195 /** 8196 * Create an instance of this class. 8197 * 8198 * @param {Object} [options={}] 8199 * Object of option names and values 8200 * 8201 * @param {AudioTrack~Kind} [options.kind=''] 8202 * A valid audio track kind 8203 * 8204 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8205 * A unique id for this AudioTrack. 8206 * 8207 * @param {string} [options.label=''] 8208 * The menu label for this track. 8209 * 8210 * @param {string} [options.language=''] 8211 * A valid two character language code. 8212 * 8213 * @param {boolean} [options.enabled] 8214 * If this track is the one that is currently playing. If this track is part of 8215 * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. 8216 */ 8217 function AudioTrack(options) { 8218 var _this; 8219 8220 if (options === void 0) { 8221 options = {}; 8222 } 8223 8224 var settings = mergeOptions(options, { 8225 kind: AudioTrackKind[options.kind] || '' 8226 }); 8227 _this = _Track.call(this, settings) || this; 8228 var enabled = false; 8229 /** 8230 * @memberof AudioTrack 8231 * @member {boolean} enabled 8232 * If this `AudioTrack` is enabled or not. When setting this will 8233 * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. 8234 * @instance 8235 * 8236 * @fires VideoTrack#selectedchange 8237 */ 8238 8239 Object.defineProperty(assertThisInitialized(_this), 'enabled', { 8240 get: function get() { 8241 return enabled; 8242 }, 8243 set: function set(newEnabled) {
8244 // an invalid or unchanged value 8245 if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { 8246 return; 8247 } 8248 8249 enabled = newEnabled; 8250 /** 8251 * An event that fires when enabled changes on this track. This allows 8252 * the AudioTrackList that holds this track to act accordingly. 8253 * 8254 * > Note: This is not part of the spec! Native tracks will do 8255 * this internally without an event. 8256 * 8257 * @event AudioTrack#enabledchange 8258 * @type {EventTarget~Event} 8259 */ 8260 8261 this.trigger('enabledchange'); 8262 } 8263 }); // if the user sets this track to selected then 8264 // set selected to that true value otherwise 8265 // we keep it false 8266 8267 if (settings.enabled) { 8268 _this.enabled = settings.enabled; 8269 } 8270 8271 _this.loaded_ = true; 8272 return _this; 8273 } 8274 8275 return AudioTrack; 8276 }(Track); 8277 8278 /** 8279 * A representation of a single `VideoTrack`. 8280 * 8281 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} 8282 * @extends Track 8283 */ 8284 8285 var VideoTrack = 8286 /*#__PURE__*/ 8287 function (_Track) { 8288 inheritsLoose(VideoTrack, _Track); 8289 8290 /** 8291 * Create an instance of this class. 8292 * 8293 * @param {Object} [options={}] 8294 * Object of option names and values 8295 * 8296 * @param {string} [options.kind=''] 8297 * A valid {@link VideoTrack~Kind} 8298 * 8299 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8300 * A unique id for this AudioTrack. 8301 * 8302 * @param {string} [options.label=''] 8303 * The menu label for this track. 8304 * 8305 * @param {string} [options.language=''] 8306 * A valid two character language code. 8307 * 8308 * @param {boolean} [options.selected] 8309 * If this track is the one that is currently playing. 8310 */ 8311 function VideoTrack(options) { 8312 var _this; 8313 8314 if (options === void 0) { 8315 options = {}; 8316 } 8317 8318 var settings = mergeOptions(options, { 8319 kind: VideoTrackKind[options.kind] || '' 8320 }); 8321 _this = _Track.call(this, settings) || this; 8322 var selected = false; 8323 /** 8324 * @memberof VideoTrack 8325 * @member {boolean} selected 8326 * If this `VideoTrack` is selected or not. When setting this will 8327 * fire {@link VideoTrack#selectedchange} if the state of selected changed. 8328 * @instance 8329 * 8330 * @fires VideoTrack#selectedchange 8331 */ 8332 8333 Object.defineProperty(assertThisInitialized(_this), 'selected', { 8334 get: function get() { 8335 return selected; 8336 }, 8337 set: function set(newSelected) { 8338 // an invalid or unchanged value 8339 if (typeof newSelected !== 'boolean' || newSelected === selected) { 8340 return; 8341 } 8342 8343 selected = newSelected; 8344 /** 8345 * An event that fires when selected changes on this track. This allows 8346 * the VideoTrackList that holds this track to act accordingly. 8347 * 8348 * > Note: This is not part of the spec! Native tracks will do 8349 * this internally without an event. 8350 * 8351 * @event VideoTrack#selectedchange 8352 * @type {EventTarget~Event} 8353 */ 8354 8355 this.trigger('selectedchange'); 8356 } 8357 }); // if the user sets this track to selected then 8358 // set selected to that true value otherwise 8359 // we keep it false 8360 8361 if (settings.selected) { 8362 _this.selected = settings.selected; 8363 } 8364 8365 return _this; 8366 } 8367 8368 return VideoTrack; 8369 }(Track); 8370 8371 /** 8372 * @memberof HTMLTrackElement 8373 * @typedef {HTMLTrackElement~ReadyState} 8374 * @enum {number} 8375 */ 8376 8377 var NONE = 0; 8378 var LOADING = 1; 8379 var LOADED = 2; 8380 var ERROR = 3; 8381 /** 8382 * A single track represented in the DOM. 8383 * 8384 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
8385 * @extends EventTarget 8386 */ 8387 8388 var HTMLTrackElement = 8389 /*#__PURE__*/ 8390 function (_EventTarget) { 8391 inheritsLoose(HTMLTrackElement, _EventTarget); 8392 8393 /** 8394 * Create an instance of this class. 8395 * 8396 * @param {Object} options={} 8397 * Object of option names and values 8398 * 8399 * @param {Tech} options.tech 8400 * A reference to the tech that owns this HTMLTrackElement. 8401 * 8402 * @param {TextTrack~Kind} [options.kind='subtitles'] 8403 * A valid text track kind. 8404 * 8405 * @param {TextTrack~Mode} [options.mode='disabled'] 8406 * A valid text track mode. 8407 * 8408 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8409 * A unique id for this TextTrack. 8410 * 8411 * @param {string} [options.label=''] 8412 * The menu label for this track. 8413 * 8414 * @param {string} [options.language=''] 8415 * A valid two character language code. 8416 * 8417 * @param {string} [options.srclang=''] 8418 * A valid two character language code. An alternative, but deprioritized 8419 * vesion of `options.language` 8420 * 8421 * @param {string} [options.src] 8422 * A url to TextTrack cues. 8423 * 8424 * @param {boolean} [options.default] 8425 * If this track should default to on or off. 8426 */ 8427 function HTMLTrackElement(options) { 8428 var _this; 8429 8430 if (options === void 0) { 8431 options = {}; 8432 } 8433 8434 _this = _EventTarget.call(this) || this; 8435 var readyState; 8436 var track = new TextTrack(options); 8437 _this.kind = track.kind; 8438 _this.src = track.src; 8439 _this.srclang = track.language; 8440 _this.label = track.label; 8441 _this["default"] = track["default"]; 8442 Object.defineProperties(assertThisInitialized(_this), { 8443 /** 8444 * @memberof HTMLTrackElement 8445 * @member {HTMLTrackElement~ReadyState} readyState 8446 * The current ready state of the track element. 8447 * @instance 8448 */ 8449 readyState: { 8450 get: function get() { 8451 return readyState; 8452 } 8453 }, 8454 8455 /** 8456 * @memberof HTMLTrackElement 8457 * @member {TextTrack} track 8458 * The underlying TextTrack object. 8459 * @instance 8460 * 8461 */ 8462 track: { 8463 get: function get() { 8464 return track; 8465 } 8466 } 8467 }); 8468 readyState = NONE; 8469 /** 8470 * @listens TextTrack#loadeddata 8471 * @fires HTMLTrackElement#load 8472 */ 8473 8474 track.addEventListener('loadeddata', function () { 8475 readyState = LOADED; 8476 8477 _this.trigger({ 8478 type: 'load', 8479 target: assertThisInitialized(_this) 8480 }); 8481 }); 8482 return _this; 8483 } 8484 8485 return HTMLTrackElement; 8486 }(EventTarget); 8487 8488 HTMLTrackElement.prototype.allowedEvents_ = { 8489 load: 'load' 8490 }; 8491 HTMLTrackElement.NONE = NONE; 8492 HTMLTrackElement.LOADING = LOADING; 8493 HTMLTrackElement.LOADED = LOADED; 8494 HTMLTrackElement.ERROR = ERROR; 8495 8496 /* 8497 * This file contains all track properties that are used in 8498 * player.js, tech.js, html5.js and possibly other techs in the future. 8499 */ 8500 8501 var NORMAL = { 8502 audio: { 8503 ListClass: AudioTrackList, 8504 TrackClass: AudioTrack, 8505 capitalName: 'Audio' 8506 }, 8507 video: { 8508 ListClass: VideoTrackList, 8509 TrackClass: VideoTrack, 8510 capitalName: 'Video' 8511 }, 8512 text: { 8513 ListClass: TextTrackList, 8514 TrackClass: TextTrack, 8515 capitalName: 'Text' 8516 } 8517 }; 8518 Object.keys(NORMAL).forEach(function (type) { 8519 NORMAL[type].getterName = type + "Tracks"; 8520 NORMAL[type].privateName = type + "Tracks_"; 8521 }); 8522 var REMOTE = { 8523 remoteText: { 8524 ListClass: TextTrackList, 8525 TrackClass: TextTrack, 8526 capitalName: 'RemoteText', 8527 getterName: 'remoteTextTracks', 8528 privateName: 'remoteTextTracks_' 8529 }, 8530 remoteTextEl: { 8531 ListClass: HtmlTrackElementList, 8532 TrackClass: HTMLTrackElement, 8533 capitalName: 'RemoteTextTrackEls', 8534 getterName: 'remoteTextTrackEls', 8535 privateName: 'remoteTextTrackEls_' 8536 } 8537 }; 8538 8539 var ALL = _extends_1({}, NORMAL, REMOTE); 8540 8541 REMOTE.names = Object.keys(REMOTE); 8542 NORMAL.names = Object.keys(NORMAL); 8543 ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); 8544 8545 /** 8546 * Copyright 2013 vtt.js Contributors 8547 * 8548 * Licensed under the Apache License, Version 2.0 (the "License");
vendor: 5,646 bytes, lines 8549-8727
8549 * you may not use this file except in compliance with the License. 8550 * You may obtain a copy of the License at 8551 * 8552 * http://www.apache.org/licenses/LICENSE-2.0 8553 * 8554 * Unless required by applicable law or agreed to in writing, software 8555 * distributed under the License is distributed on an "AS IS" BASIS, 8556 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8557 * See the License for the specific language governing permissions and 8558 * limitations under the License. 8559 */ 8560 8561 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 8562 8563 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 8564 8565 var _objCreate = Object.create || function () { 8566 function F() {} 8567 8568 return function (o) { 8569 if (arguments.length !== 1) { 8570 throw new Error('Object.create shim only accepts one parameter.'); 8571 } 8572 8573 F.prototype = o; 8574 return new F(); 8575 }; 8576 }(); // Creates a new ParserError object from an errorData object. The errorData 8577 // object should have default code and message properties. The default message 8578 // property can be overriden by passing in a message parameter. 8579 // See ParsingError.Errors below for acceptable errors. 8580 8581 8582 function ParsingError(errorData, message) { 8583 this.name = "ParsingError"; 8584 this.code = errorData.code; 8585 this.message = message || errorData.message; 8586 } 8587 8588 ParsingError.prototype = _objCreate(Error.prototype); 8589 ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors. 8590 8591 ParsingError.Errors = { 8592 BadSignature: { 8593 code: 0, 8594 message: "Malformed WebVTT signature." 8595 }, 8596 BadTimeStamp: { 8597 code: 1, 8598 message: "Malformed time stamp." 8599 } 8600 }; // Try to parse input as a time stamp. 8601 8602 function parseTimeStamp(input) { 8603 function computeSeconds(h, m, s, f) { 8604 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 8605 } 8606 8607 var m = input.match(/^(\d+):(\d{1,2})(:\d{1,2})?\.(\d{3})/); 8608 8609 if (!m) { 8610 return null; 8611 } 8612 8613 if (m[3]) { 8614 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 8615 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 8616 } else if (m[1] > 59) { 8617 // Timestamp takes the form of [hours]:[minutes].[milliseconds] 8618 // First position is hours as it's over 59. 8619 return computeSeconds(m[1], m[2], 0, m[4]); 8620 } else { 8621 // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 8622 return computeSeconds(0, m[1], m[2], m[4]); 8623 } 8624 } // A settings object holds key/value pairs and will ignore anything but the first 8625 // assignment to a specific key. 8626 8627 8628 function Settings() { 8629 this.values = _objCreate(null); 8630 } 8631 8632 Settings.prototype = { 8633 // Only accept the first assignment to any key. 8634 set: function set(k, v) { 8635 if (!this.get(k) && v !== "") { 8636 this.values[k] = v; 8637 } 8638 }, 8639 // Return the value for a key, or a default value. 8640 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 8641 // a number of possible default values as properties where 'defaultKey' is 8642 // the key of the property that will be chosen; otherwise it's assumed to be 8643 // a single value. 8644 get: function get(k, dflt, defaultKey) { 8645 if (defaultKey) { 8646 return this.has(k) ? this.values[k] : dflt[defaultKey]; 8647 } 8648 8649 return this.has(k) ? this.values[k] : dflt; 8650 }, 8651 // Check whether we have a value for a key. 8652 has: function has(k) { 8653 return k in this.values; 8654 }, 8655 // Accept a setting if its one of the given alternatives. 8656 alt: function alt(k, v, a) { 8657 for (var n = 0; n < a.length; ++n) { 8658 if (v === a[n]) { 8659 this.set(k, v); 8660 break; 8661 } 8662 } 8663 }, 8664 // Accept a setting if its a valid (signed) integer. 8665 integer: function integer(k, v) { 8666 if (/^-?\d+$/.test(v)) { 8667 // integer 8668 this.set(k, parseInt(v, 10)); 8669 } 8670 }, 8671 // Accept a setting if its a valid percentage. 8672 percent: function percent(k, v) { 8673 var m; 8674 8675 if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) { 8676 v = parseFloat(v); 8677 8678 if (v >= 0 && v <= 100) { 8679 this.set(k, v); 8680 return true; 8681 } 8682 } 8683 8684 return false; 8685 } 8686 }; // Helper function to parse input into groups separated by 'groupDelim', and 8687 // interprete each group as a key/value pair separated by 'keyValueDelim'. 8688 8689 function parseOptions(input, callback, keyValueDelim, groupDelim) { 8690 var groups = groupDelim ? input.split(groupDelim) : [input]; 8691 8692 for (var i in groups) { 8693 if (typeof groups[i] !== "string") { 8694 continue; 8695 } 8696 8697 var kv = groups[i].split(keyValueDelim); 8698 8699 if (kv.length !== 2) { 8700 continue; 8701 } 8702 8703 var k = kv[0]; 8704 var v = kv[1]; 8705 callback(k, v); 8706 } 8707 } 8708 8709 function parseCue(input, cue, regionList) { 8710 // Remember the original input if we need to throw an error. 8711 var oInput = input; // 4.1 WebVTT timestamp 8712 8713 function consumeTimeStamp() { 8714 var ts = parseTimeStamp(input); 8715 8716 if (ts === null) { 8717 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput); 8718 } // Remove time stamp from input. 8719 8720 8721 input = input.replace(/^[^\sa-zA-Z-]+/, ""); 8722 return ts; 8723 } // 4.4.2 WebVTT cue settings 8724 8725 8726 function consumeCueSettings(input, cue) { 8727 var settings = new Settings();
vendor: 26,221 bytes, lines 8728-9480
8728 parseOptions(input, function (k, v) { 8729 switch (k) { 8730 case "region": 8731 // Find the last region we parsed with the same region id. 8732 for (var i = regionList.length - 1; i >= 0; i--) { 8733 if (regionList[i].id === v) { 8734 settings.set(k, regionList[i].region); 8735 break; 8736 } 8737 } 8738 8739 break; 8740 8741 case "vertical": 8742 settings.alt(k, v, ["rl", "lr"]); 8743 break; 8744 8745 case "line": 8746 var vals = v.split(","), 8747 vals0 = vals[0]; 8748 settings.integer(k, vals0); 8749 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 8750 settings.alt(k, vals0, ["auto"]); 8751 8752 if (vals.length === 2) { 8753 settings.alt("lineAlign", vals[1], ["start", "center", "end"]); 8754 } 8755 8756 break; 8757 8758 case "position": 8759 vals = v.split(","); 8760 settings.percent(k, vals[0]); 8761 8762 if (vals.length === 2) { 8763 settings.alt("positionAlign", vals[1], ["start", "center", "end"]); 8764 } 8765 8766 break; 8767 8768 case "size": 8769 settings.percent(k, v); 8770 break; 8771 8772 case "align": 8773 settings.alt(k, v, ["start", "center", "end", "left", "right"]); 8774 break; 8775 } 8776 }, /:/, /\s/); // Apply default values for any missing fields. 8777 8778 cue.region = settings.get("region", null); 8779 cue.vertical = settings.get("vertical", ""); 8780 8781 try { 8782 cue.line = settings.get("line", "auto"); 8783 } catch (e) {} 8784 8785 cue.lineAlign = settings.get("lineAlign", "start"); 8786 cue.snapToLines = settings.get("snapToLines", true); 8787 cue.size = settings.get("size", 100); // Safari still uses the old middle value and won't accept center 8788 8789 try { 8790 cue.align = settings.get("align", "center"); 8791 } catch (e) { 8792 cue.align = settings.get("align", "middle"); 8793 } 8794 8795 try { 8796 cue.position = settings.get("position", "auto"); 8797 } catch (e) { 8798 cue.position = settings.get("position", { 8799 start: 0, 8800 left: 0, 8801 center: 50, 8802 middle: 50, 8803 end: 100, 8804 right: 100 8805 }, cue.align); 8806 } 8807 8808 cue.positionAlign = settings.get("positionAlign", { 8809 start: "start", 8810 left: "start", 8811 center: "center", 8812 middle: "center", 8813 end: "end", 8814 right: "end" 8815 }, cue.align); 8816 } 8817 8818 function skipWhitespace() { 8819 input = input.replace(/^\s+/, ""); 8820 } // 4.1 WebVTT cue timings. 8821 8822 8823 skipWhitespace(); 8824 cue.startTime = consumeTimeStamp(); // (1) collect cue start time 8825 8826 skipWhitespace(); 8827 8828 if (input.substr(0, 3) !== "-->") { 8829 // (3) next characters must match "-->" 8830 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput); 8831 } 8832 8833 input = input.substr(3); 8834 skipWhitespace(); 8835 cue.endTime = consumeTimeStamp(); // (5) collect cue end time 8836 // 4.1 WebVTT cue settings list. 8837 8838 skipWhitespace(); 8839 consumeCueSettings(input, cue); 8840 } 8841 8842 var TEXTAREA_ELEMENT = document.createElement("textarea"); 8843 var TAG_NAME = { 8844 c: "span", 8845 i: "i", 8846 b: "b", 8847 u: "u", 8848 ruby: "ruby", 8849 rt: "rt", 8850 v: "span", 8851 lang: "span" 8852 }; // 5.1 default text color 8853 // 5.2 default text background color is equivalent to text color with bg_ prefix 8854 8855 var DEFAULT_COLOR_CLASS = { 8856 white: 'rgba(255,255,255,1)', 8857 lime: 'rgba(0,255,0,1)', 8858 cyan: 'rgba(0,255,255,1)', 8859 red: 'rgba(255,0,0,1)', 8860 yellow: 'rgba(255,255,0,1)', 8861 magenta: 'rgba(255,0,255,1)', 8862 blue: 'rgba(0,0,255,1)', 8863 black: 'rgba(0,0,0,1)' 8864 }; 8865 var TAG_ANNOTATION = { 8866 v: "title", 8867 lang: "lang" 8868 }; 8869 var NEEDS_PARENT = { 8870 rt: "ruby" 8871 }; // Parse content into a document fragment. 8872 8873 function parseContent(window, input) { 8874 function nextToken() { 8875 // Check for end-of-string. 8876 if (!input) { 8877 return null; 8878 } // Consume 'n' characters from the input. 8879 8880 8881 function consume(result) { 8882 input = input.substr(result.length); 8883 return result; 8884 } 8885 8886 var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return 8887 // the tag. 8888 8889 return consume(m[1] ? m[1] : m[2]); 8890 } 8891 8892 function unescape(s) { 8893 TEXTAREA_ELEMENT.innerHTML = s; 8894 s = TEXTAREA_ELEMENT.textContent; 8895 TEXTAREA_ELEMENT.textContent = ""; 8896 return s; 8897 } 8898 8899 function shouldAdd(current, element) { 8900 return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName; 8901 } // Create an element for this tag. 8902 8903 8904 function createElement(type, annotation) { 8905 var tagName = TAG_NAME[type]; 8906 8907 if (!tagName) { 8908 return null; 8909 } 8910 8911 var element = window.document.createElement(tagName); 8912 var name = TAG_ANNOTATION[type]; 8913 8914 if (name && annotation) { 8915 element[name] = annotation.trim(); 8916 } 8917 8918 return element; 8919 } 8920 8921 var rootDiv = window.document.createElement("div"), 8922 current = rootDiv, 8923 t, 8924 tagStack = []; 8925 8926 while ((t = nextToken()) !== null) { 8927 if (t[0] === '<') { 8928 if (t[1] === "/") { 8929 // If the closing tag matches, move back up to the parent node. 8930 if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 8931 tagStack.pop(); 8932 current = current.parentNode; 8933 } // Otherwise just ignore the end tag. 8934 8935 8936 continue; 8937 } 8938 8939 var ts = parseTimeStamp(t.substr(1, t.length - 2)); 8940 var node; 8941 8942 if (ts) { 8943 // Timestamps are lead nodes as well. 8944 node = window.document.createProcessingInstruction("timestamp", ts); 8945 current.appendChild(node); 8946 continue; 8947 } 8948 8949 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag. 8950 8951 if (!m) { 8952 continue; 8953 } // Try to construct an element, and ignore the tag if we couldn't. 8954 8955 8956 node = createElement(m[1], m[3]); 8957 8958 if (!node) { 8959 continue; 8960 } // Determine if the tag should be added based on the context of where it 8961 // is placed in the cuetext. 8962 8963 8964 if (!shouldAdd(current, node)) { 8965 continue; 8966 } // Set the class list (as a list of classes, separated by space). 8967 8968 8969 if (m[2]) { 8970 var classes = m[2].split('.'); 8971 classes.forEach(function (cl) { 8972 var bgColor = /^bg_/.test(cl); // slice out `bg_` if it's a background color 8973 8974 var colorName = bgColor ? cl.slice(3) : cl; 8975 8976 if (DEFAULT_COLOR_CLASS.hasOwnProperty(colorName)) { 8977 var propName = bgColor ? 'background-color' : 'color'; 8978 var propValue = DEFAULT_COLOR_CLASS[colorName]; 8979 node.style[propName] = propValue; 8980 } 8981 }); 8982 node.className = classes.join(' '); 8983 } // Append the node to the current node, and enter the scope of the new 8984 // node. 8985 8986 8987 tagStack.push(m[1]); 8988 current.appendChild(node); 8989 current = node; 8990 continue; 8991 } // Text nodes are leaf nodes. 8992 8993 8994 current.appendChild(window.document.createTextNode(unescape(t))); 8995 } 8996 8997 return rootDiv; 8998 } // This is a list of all the Unicode characters that have a strong 8999 // right-to-left category. What this means is that these characters are 9000 // written right-to-left for sure. It was generated by pulling all the strong 9001 // right-to-left characters out of the Unicode data table. That table can 9002 // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 9003 9004 9005 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]]; 9006 9007 function isStrongRTLChar(charCode) { 9008 for (var i = 0; i < strongRTLRanges.length; i++) { 9009 var currentRange = strongRTLRanges[i]; 9010 9011 if (charCode >= currentRange[0] && charCode <= currentRange[1]) { 9012 return true; 9013 } 9014 } 9015 9016 return false; 9017 } 9018 9019 function determineBidi(cueDiv) { 9020 var nodeStack = [], 9021 text = "", 9022 charCode; 9023 9024 if (!cueDiv || !cueDiv.childNodes) { 9025 return "ltr"; 9026 } 9027 9028 function pushNodes(nodeStack, node) { 9029 for (var i = node.childNodes.length - 1; i >= 0; i--) { 9030 nodeStack.push(node.childNodes[i]); 9031 } 9032 } 9033 9034 function nextTextNode(nodeStack) { 9035 if (!nodeStack || !nodeStack.length) { 9036 return null; 9037 } 9038 9039 var node = nodeStack.pop(), 9040 text = node.textContent || node.innerText; 9041 9042 if (text) { 9043 // TODO: This should match all unicode type B characters (paragraph 9044 // separator characters). See issue #115. 9045 var m = text.match(/^.*(\n|\r)/); 9046 9047 if (m) { 9048 nodeStack.length = 0; 9049 return m[0]; 9050 } 9051 9052 return text; 9053 } 9054 9055 if (node.tagName === "ruby") { 9056 return nextTextNode(nodeStack); 9057 } 9058 9059 if (node.childNodes) { 9060 pushNodes(nodeStack, node); 9061 return nextTextNode(nodeStack); 9062 } 9063 } 9064 9065 pushNodes(nodeStack, cueDiv); 9066 9067 while (text = nextTextNode(nodeStack)) { 9068 for (var i = 0; i < text.length; i++) { 9069 charCode = text.charCodeAt(i); 9070 9071 if (isStrongRTLChar(charCode)) { 9072 return "rtl"; 9073 } 9074 } 9075 } 9076 9077 return "ltr"; 9078 } 9079 9080 function computeLinePos(cue) { 9081 if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) { 9082 return cue.line; 9083 } 9084 9085 if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) { 9086 return -1; 9087 } 9088 9089 var track = cue.track, 9090 trackList = track.textTrackList, 9091 count = 0; 9092 9093 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 9094 if (trackList[i].mode === "showing") { 9095 count++; 9096 } 9097 } 9098 9099 return ++count * -1; 9100 } 9101 9102 function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the 9103 // div on 'this'. 9104 9105 9106 StyleBox.prototype.applyStyles = function (styles, div) { 9107 div = div || this.div; 9108 9109 for (var prop in styles) { 9110 if (styles.hasOwnProperty(prop)) { 9111 div.style[prop] = styles[prop]; 9112 } 9113 } 9114 }; 9115 9116 StyleBox.prototype.formatStyle = function (val, unit) { 9117 return val === 0 ? 0 : val + unit; 9118 }; // Constructs the computed display state of the cue (a div). Places the div 9119 // into the overlay which should be a block level element (usually a div). 9120 9121 9122 function CueStyleBox(window, cue, styleOptions) { 9123 StyleBox.call(this); 9124 this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 9125 // have inline positioning and will function as the cue background box. 9126 9127 this.cueDiv = parseContent(window, cue.text); 9128 var styles = { 9129 color: "rgba(255, 255, 255, 1)", 9130 backgroundColor: "rgba(0, 0, 0, 0.8)", 9131 position: "relative", 9132 left: 0, 9133 right: 0, 9134 top: 0, 9135 bottom: 0, 9136 display: "inline", 9137 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9138 unicodeBidi: "plaintext" 9139 }; 9140 this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue 9141 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 9142 // mirrors of them except middle instead of center on Safari. 9143 9144 this.div = window.document.createElement("div"); 9145 styles = { 9146 direction: determineBidi(this.cueDiv), 9147 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9148 unicodeBidi: "plaintext", 9149 textAlign: cue.align === "middle" ? "center" : cue.align, 9150 font: styleOptions.font, 9151 whiteSpace: "pre-line", 9152 position: "absolute" 9153 }; 9154 this.applyStyles(styles); 9155 this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text 9156 // position of the cue box. The reference edge will be resolved later when 9157 // the box orientation styles are applied. 9158 9159 var textPos = 0; 9160 9161 switch (cue.positionAlign) { 9162 case "start": 9163 textPos = cue.position; 9164 break; 9165 9166 case "center": 9167 textPos = cue.position - cue.size / 2; 9168 break; 9169 9170 case "end": 9171 textPos = cue.position - cue.size; 9172 break; 9173 } // Horizontal box orientation; textPos is the distance from the left edge of the 9174 // area to the left edge of the box and cue.size is the distance extending to 9175 // the right from there. 9176 9177 9178 if (cue.vertical === "") { 9179 this.applyStyles({ 9180 left: this.formatStyle(textPos, "%"), 9181 width: this.formatStyle(cue.size, "%") 9182 }); // Vertical box orientation; textPos is the distance from the top edge of the 9183 // area to the top edge of the box and cue.size is the height extending 9184 // downwards from there. 9185 } else { 9186 this.applyStyles({ 9187 top: this.formatStyle(textPos, "%"), 9188 height: this.formatStyle(cue.size, "%") 9189 }); 9190 } 9191 9192 this.move = function (box) { 9193 this.applyStyles({ 9194 top: this.formatStyle(box.top, "px"), 9195 bottom: this.formatStyle(box.bottom, "px"), 9196 left: this.formatStyle(box.left, "px"), 9197 right: this.formatStyle(box.right, "px"), 9198 height: this.formatStyle(box.height, "px"), 9199 width: this.formatStyle(box.width, "px") 9200 }); 9201 }; 9202 } 9203 9204 CueStyleBox.prototype = _objCreate(StyleBox.prototype); 9205 CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily 9206 // compute things with such as if it overlaps or intersects with another Element. 9207 // Can initialize it with either a StyleBox or another BoxPosition. 9208 9209 function BoxPosition(obj) { 9210 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 9211 // was passed in and we need to copy the results of 'getBoundingClientRect' 9212 // as the object returned is readonly. All co-ordinate values are in reference 9213 // to the viewport origin (top left). 9214 var lh, height, width, top; 9215 9216 if (obj.div) { 9217 height = obj.div.offsetHeight; 9218 width = obj.div.offsetWidth; 9219 top = obj.div.offsetTop; 9220 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); 9221 obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of 9222 // the inner div's lines. This could be due to bold text, etc, on some platforms. 9223 // In this case we should get the average line height and use that. This will 9224 // result in the desired behaviour. 9225 9226 lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0; 9227 } 9228 9229 this.left = obj.left; 9230 this.right = obj.right; 9231 this.top = obj.top || top; 9232 this.height = obj.height || height; 9233 this.bottom = obj.bottom || top + (obj.height || height); 9234 this.width = obj.width || width; 9235 this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 9236 } // Move the box along a particular axis. Optionally pass in an amount to move 9237 // the box. If no amount is passed then the default is the line height of the 9238 // box. 9239 9240 9241 BoxPosition.prototype.move = function (axis, toMove) { 9242 toMove = toMove !== undefined ? toMove : this.lineHeight; 9243 9244 switch (axis) { 9245 case "+x": 9246 this.left += toMove; 9247 this.right += toMove; 9248 break; 9249 9250 case "-x": 9251 this.left -= toMove; 9252 this.right -= toMove; 9253 break; 9254 9255 case "+y": 9256 this.top += toMove; 9257 this.bottom += toMove; 9258 break; 9259 9260 case "-y": 9261 this.top -= toMove; 9262 this.bottom -= toMove; 9263 break; 9264 } 9265 }; // Check if this box overlaps another box, b2. 9266 9267 9268 BoxPosition.prototype.overlaps = function (b2) { 9269 return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top; 9270 }; // Check if this box overlaps any other boxes in boxes. 9271 9272 9273 BoxPosition.prototype.overlapsAny = function (boxes) { 9274 for (var i = 0; i < boxes.length; i++) { 9275 if (this.overlaps(boxes[i])) { 9276 return true; 9277 } 9278 } 9279 9280 return false; 9281 }; // Check if this box is within another box. 9282 9283 9284 BoxPosition.prototype.within = function (container) { 9285 return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right; 9286 }; // Check if this box is entirely within the container or it is overlapping 9287 // on the edge opposite of the axis direction passed. For example, if "+x" is 9288 // passed and the box is overlapping on the left edge of the container, then 9289 // return true. 9290 9291 9292 BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) { 9293 switch (axis) { 9294 case "+x": 9295 return this.left < container.left; 9296 9297 case "-x": 9298 return this.right > container.right; 9299 9300 case "+y": 9301 return this.top < container.top; 9302 9303 case "-y": 9304 return this.bottom > container.bottom; 9305 } 9306 }; // Find the percentage of the area that this box is overlapping with another 9307 // box. 9308 9309 9310 BoxPosition.prototype.intersectPercentage = function (b2) { 9311 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 9312 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 9313 intersectArea = x * y; 9314 return intersectArea / (this.height * this.width); 9315 }; // Convert the positions from this box to CSS compatible positions using 9316 // the reference container's positions. This has to be done because this 9317 // box's positions are in reference to the viewport origin, whereas, CSS 9318 // values are in referecne to their respective edges. 9319 9320 9321 BoxPosition.prototype.toCSSCompatValues = function (reference) { 9322 return { 9323 top: this.top - reference.top, 9324 bottom: reference.bottom - this.bottom, 9325 left: this.left - reference.left, 9326 right: reference.right - this.right, 9327 height: this.height, 9328 width: this.width 9329 }; 9330 }; // Get an object that represents the box's position without anything extra. 9331 // Can pass a StyleBox, HTMLElement, or another BoxPositon. 9332 9333 9334 BoxPosition.getSimpleBoxPosition = function (obj) { 9335 var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; 9336 var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; 9337 var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; 9338 obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; 9339 var ret = { 9340 left: obj.left, 9341 right: obj.right, 9342 top: obj.top || top, 9343 height: obj.height || height, 9344 bottom: obj.bottom || top + (obj.height || height), 9345 width: obj.width || width 9346 }; 9347 return ret; 9348 }; // Move a StyleBox to its specified, or next best, position. The containerBox 9349 // is the box that contains the StyleBox, such as a div. boxPositions are 9350 // a list of other boxes that the styleBox can't overlap with. 9351 9352 9353 function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 9354 // Find the best position for a cue box, b, on the video. The axis parameter 9355 // is a list of axis, the order of which, it will move the box along. For example: 9356 // Passing ["+x", "-x"] will move the box first along the x axis in the positive 9357 // direction. If it doesn't find a good position for it there it will then move 9358 // it along the x axis in the negative direction. 9359 function findBestPosition(b, axis) { 9360 var bestPosition, 9361 specifiedPosition = new BoxPosition(b), 9362 percentage = 1; // Highest possible so the first thing we get is better. 9363 9364 for (var i = 0; i < axis.length; i++) { 9365 while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) { 9366 b.move(axis[i]); 9367 } // We found a spot where we aren't overlapping anything. This is our 9368 // best position. 9369 9370 9371 if (b.within(containerBox)) { 9372 return b; 9373 } 9374 9375 var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try 9376 // then remember this position as the best position. 9377 9378 if (percentage > p) { 9379 bestPosition = new BoxPosition(b); 9380 percentage = p; 9381 } // Reset the box position to the specified position. 9382 9383 9384 b = new BoxPosition(specifiedPosition); 9385 } 9386 9387 return bestPosition || specifiedPosition; 9388 } 9389 9390 var boxPosition = new BoxPosition(styleBox), 9391 cue = styleBox.cue, 9392 linePos = computeLinePos(cue), 9393 axis = []; // If we have a line number to align the cue to. 9394 9395 if (cue.snapToLines) { 9396 var size; 9397 9398 switch (cue.vertical) { 9399 case "": 9400 axis = ["+y", "-y"]; 9401 size = "height"; 9402 break; 9403 9404 case "rl": 9405 axis = ["+x", "-x"]; 9406 size = "width"; 9407 break; 9408 9409 case "lr": 9410 axis = ["-x", "+x"]; 9411 size = "width"; 9412 break; 9413 } 9414 9415 var step = boxPosition.lineHeight, 9416 position = step * Math.round(linePos), 9417 maxPosition = containerBox[size] + step, 9418 initialAxis = axis[0]; // If the specified intial position is greater then the max position then 9419 // clamp the box to the amount of steps it would take for the box to 9420 // reach the max position. 9421 9422 if (Math.abs(position) > maxPosition) { 9423 position = position < 0 ? -1 : 1; 9424 position *= Math.ceil(maxPosition / step) * step; 9425 } // If computed line position returns negative then line numbers are 9426 // relative to the bottom of the video instead of the top. Therefore, we 9427 // need to increase our initial position by the length or width of the 9428 // video, depending on the writing direction, and reverse our axis directions. 9429 9430 9431 if (linePos < 0) { 9432 position += cue.vertical === "" ? containerBox.height : containerBox.width; 9433 axis = axis.reverse(); 9434 } // Move the box to the specified position. This may not be its best 9435 // position. 9436 9437 9438 boxPosition.move(initialAxis, position); 9439 } else { 9440 // If we have a percentage line value for the cue. 9441 var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100; 9442 9443 switch (cue.lineAlign) { 9444 case "center": 9445 linePos -= calculatedPercentage / 2; 9446 break; 9447 9448 case "end": 9449 linePos -= calculatedPercentage; 9450 break; 9451 } // Apply initial line position to the cue box. 9452 9453 9454 switch (cue.vertical) { 9455 case "": 9456 styleBox.applyStyles({ 9457 top: styleBox.formatStyle(linePos, "%") 9458 }); 9459 break; 9460 9461 case "rl": 9462 styleBox.applyStyles({ 9463 left: styleBox.formatStyle(linePos, "%") 9464 }); 9465 break; 9466 9467 case "lr": 9468 styleBox.applyStyles({ 9469 right: styleBox.formatStyle(linePos, "%") 9470 }); 9471 break; 9472 } 9473 9474 axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning 9475 // to it. 9476 9477 boxPosition = new BoxPosition(styleBox); 9478 } 9479 9480 var bestPosition = findBestPosition(boxPosition, axis);
vendor: 5,111 bytes, lines 9481-9647
9481 styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 9482 } 9483 9484 function WebVTT$1() {} // Nothing 9485 // Helper to allow strings to be decoded instead of the default binary utf8 data. 9486 9487 9488 WebVTT$1.StringDecoder = function () { 9489 return { 9490 decode: function decode(data) { 9491 if (!data) { 9492 return ""; 9493 } 9494 9495 if (typeof data !== "string") { 9496 throw new Error("Error - expected string data."); 9497 } 9498 9499 return decodeURIComponent(encodeURIComponent(data)); 9500 } 9501 }; 9502 }; 9503 9504 WebVTT$1.convertCueToDOMTree = function (window, cuetext) { 9505 if (!window || !cuetext) { 9506 return null; 9507 } 9508 9509 return parseContent(window, cuetext); 9510 }; 9511 9512 var FONT_SIZE_PERCENT = 0.05; 9513 var FONT_STYLE = "sans-serif"; 9514 var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it. 9515 // @param overlay A block level element (usually a div) that the computed cues 9516 // and regions will be placed into. 9517 9518 WebVTT$1.processCues = function (window, cues, overlay) { 9519 if (!window || !cues || !overlay) { 9520 return null; 9521 } // Remove all previous children. 9522 9523 9524 while (overlay.firstChild) { 9525 overlay.removeChild(overlay.firstChild); 9526 } 9527 9528 var paddedOverlay = window.document.createElement("div"); 9529 paddedOverlay.style.position = "absolute"; 9530 paddedOverlay.style.left = "0"; 9531 paddedOverlay.style.right = "0"; 9532 paddedOverlay.style.top = "0"; 9533 paddedOverlay.style.bottom = "0"; 9534 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 9535 overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could 9536 // be the case if a cue's state has been changed since the last computation or 9537 // if it has not been computed yet. 9538 9539 function shouldCompute(cues) { 9540 for (var i = 0; i < cues.length; i++) { 9541 if (cues[i].hasBeenReset || !cues[i].displayState) { 9542 return true; 9543 } 9544 } 9545 9546 return false; 9547 } // We don't need to recompute the cues' display states. Just reuse them. 9548 9549 9550 if (!shouldCompute(cues)) { 9551 for (var i = 0; i < cues.length; i++) { 9552 paddedOverlay.appendChild(cues[i].displayState); 9553 } 9554 9555 return; 9556 } 9557 9558 var boxPositions = [], 9559 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 9560 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 9561 var styleOptions = { 9562 font: fontSize + "px " + FONT_STYLE 9563 }; 9564 9565 (function () { 9566 var styleBox, cue; 9567 9568 for (var i = 0; i < cues.length; i++) { 9569 cue = cues[i]; // Compute the intial position and styles of the cue div. 9570 9571 styleBox = new CueStyleBox(window, cue, styleOptions); 9572 paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. 9573 9574 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later 9575 // if we don't have too. 9576 9577 cue.displayState = styleBox.div; 9578 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 9579 } 9580 })(); 9581 }; 9582 9583 WebVTT$1.Parser = function (window, vttjs, decoder) { 9584 if (!decoder) { 9585 decoder = vttjs; 9586 vttjs = {}; 9587 } 9588 9589 if (!vttjs) { 9590 vttjs = {}; 9591 } 9592 9593 this.window = window; 9594 this.vttjs = vttjs; 9595 this.state = "INITIAL"; 9596 this.buffer = ""; 9597 this.decoder = decoder || new TextDecoder("utf8"); 9598 this.regionList = []; 9599 }; 9600 9601 WebVTT$1.Parser.prototype = { 9602 // If the error is a ParsingError then report it to the consumer if 9603 // possible. If it's not a ParsingError then throw it like normal. 9604 reportOrThrowError: function reportOrThrowError(e) { 9605 if (e instanceof ParsingError) { 9606 this.onparsingerror && this.onparsingerror(e); 9607 } else { 9608 throw e; 9609 } 9610 }, 9611 parse: function parse(data) { 9612 var self = this; // If there is no data then we won't decode it, but will just try to parse 9613 // whatever is in buffer already. This may occur in circumstances, for 9614 // example when flush() is called. 9615 9616 if (data) { 9617 // Try to decode the data that we received. 9618 self.buffer += self.decoder.decode(data, { 9619 stream: true 9620 }); 9621 } 9622 9623 function collectNextLine() { 9624 var buffer = self.buffer; 9625 var pos = 0; 9626 9627 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 9628 ++pos; 9629 } 9630 9631 var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below. 9632 9633 if (buffer[pos] === '\r') { 9634 ++pos; 9635 } 9636 9637 if (buffer[pos] === '\n') { 9638 ++pos; 9639 } 9640 9641 self.buffer = buffer.substr(pos); 9642 return line; 9643 } // 3.4 WebVTT region and WebVTT region settings syntax 9644 9645 9646 function parseRegion(input) { 9647 var settings = new Settings();
vendor: 8,124 bytes, lines 9648-9904
9648 parseOptions(input, function (k, v) { 9649 switch (k) { 9650 case "id": 9651 settings.set(k, v); 9652 break; 9653 9654 case "width": 9655 settings.percent(k, v); 9656 break; 9657 9658 case "lines": 9659 settings.integer(k, v); 9660 break; 9661 9662 case "regionanchor": 9663 case "viewportanchor": 9664 var xy = v.split(','); 9665 9666 if (xy.length !== 2) { 9667 break; 9668 } // We have to make sure both x and y parse, so use a temporary 9669 // settings object here. 9670 9671 9672 var anchor = new Settings(); 9673 anchor.percent("x", xy[0]); 9674 anchor.percent("y", xy[1]); 9675 9676 if (!anchor.has("x") || !anchor.has("y")) { 9677 break; 9678 } 9679 9680 settings.set(k + "X", anchor.get("x")); 9681 settings.set(k + "Y", anchor.get("y")); 9682 break; 9683 9684 case "scroll": 9685 settings.alt(k, v, ["up"]); 9686 break; 9687 } 9688 }, /=/, /\s/); // Create the region, using default values for any values that were not 9689 // specified. 9690 9691 if (settings.has("id")) { 9692 var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); 9693 region.width = settings.get("width", 100); 9694 region.lines = settings.get("lines", 3); 9695 region.regionAnchorX = settings.get("regionanchorX", 0); 9696 region.regionAnchorY = settings.get("regionanchorY", 100); 9697 region.viewportAnchorX = settings.get("viewportanchorX", 0); 9698 region.viewportAnchorY = settings.get("viewportanchorY", 100); 9699 region.scroll = settings.get("scroll", ""); // Register the region. 9700 9701 self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that 9702 // reference it. 9703 9704 self.regionList.push({ 9705 id: settings.get("id"), 9706 region: region 9707 }); 9708 } 9709 } // draft-pantos-http-live-streaming-20 9710 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 9711 // 3.5 WebVTT 9712 9713 9714 function parseTimestampMap(input) { 9715 var settings = new Settings(); 9716 parseOptions(input, function (k, v) { 9717 switch (k) { 9718 case "MPEGT": 9719 settings.integer(k + 'S', v); 9720 break; 9721 9722 case "LOCA": 9723 settings.set(k + 'L', parseTimeStamp(v)); 9724 break; 9725 } 9726 }, /[^\d]:/, /,/); 9727 self.ontimestampmap && self.ontimestampmap({ 9728 "MPEGTS": settings.get("MPEGTS"), 9729 "LOCAL": settings.get("LOCAL") 9730 }); 9731 } // 3.2 WebVTT metadata header syntax 9732 9733 9734 function parseHeader(input) { 9735 if (input.match(/X-TIMESTAMP-MAP/)) { 9736 // This line contains HLS X-TIMESTAMP-MAP metadata 9737 parseOptions(input, function (k, v) { 9738 switch (k) { 9739 case "X-TIMESTAMP-MAP": 9740 parseTimestampMap(v); 9741 break; 9742 } 9743 }, /=/); 9744 } else { 9745 parseOptions(input, function (k, v) { 9746 switch (k) { 9747 case "Region": 9748 // 3.3 WebVTT region metadata header syntax 9749 parseRegion(v); 9750 break; 9751 } 9752 }, /:/); 9753 } 9754 } // 5.1 WebVTT file parsing. 9755 9756 9757 try { 9758 var line; 9759 9760 if (self.state === "INITIAL") { 9761 // We can't start parsing until we have the first line. 9762 if (!/\r\n|\n/.test(self.buffer)) { 9763 return this; 9764 } 9765 9766 line = collectNextLine(); 9767 var m = line.match(/^WEBVTT([ \t].*)?$/); 9768 9769 if (!m || !m[0]) { 9770 throw new ParsingError(ParsingError.Errors.BadSignature); 9771 } 9772 9773 self.state = "HEADER"; 9774 } 9775 9776 var alreadyCollectedLine = false; 9777 9778 while (self.buffer) { 9779 // We can't parse a line until we have the full line. 9780 if (!/\r\n|\n/.test(self.buffer)) { 9781 return this; 9782 } 9783 9784 if (!alreadyCollectedLine) { 9785 line = collectNextLine(); 9786 } else { 9787 alreadyCollectedLine = false; 9788 } 9789 9790 switch (self.state) { 9791 case "HEADER": 9792 // 13-18 - Allow a header (metadata) under the WEBVTT line. 9793 if (/:/.test(line)) { 9794 parseHeader(line); 9795 } else if (!line) { 9796 // An empty line terminates the header and starts the body (cues). 9797 self.state = "ID"; 9798 } 9799 9800 continue; 9801 9802 case "NOTE": 9803 // Ignore NOTE blocks. 9804 if (!line) { 9805 self.state = "ID"; 9806 } 9807 9808 continue; 9809 9810 case "ID": 9811 // Check for the start of NOTE blocks. 9812 if (/^NOTE($|[ \t])/.test(line)) { 9813 self.state = "NOTE"; 9814 break; 9815 } // 19-29 - Allow any number of line terminators, then initialize new cue values. 9816 9817 9818 if (!line) { 9819 continue; 9820 } 9821 9822 self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); // Safari still uses the old middle value and won't accept center 9823 9824 try { 9825 self.cue.align = "center"; 9826 } catch (e) { 9827 self.cue.align = "middle"; 9828 } 9829 9830 self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data. 9831 9832 if (line.indexOf("-->") === -1) { 9833 self.cue.id = line; 9834 continue; 9835 } 9836 9837 // Process line as start of a cue. 9838 9839 /*falls through*/ 9840 9841 case "CUE": 9842 // 40 - Collect cue timings and settings. 9843 try { 9844 parseCue(line, self.cue, self.regionList); 9845 } catch (e) { 9846 self.reportOrThrowError(e); // In case of an error ignore rest of the cue. 9847 9848 self.cue = null; 9849 self.state = "BADCUE"; 9850 continue; 9851 } 9852 9853 self.state = "CUETEXT"; 9854 continue; 9855 9856 case "CUETEXT": 9857 var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue. 9858 // 35 - If we have the special substring '-->' then report the cue, 9859 // but do not collect the line as we need to process the current 9860 // one as a new cue. 9861 9862 if (!line || hasSubstring && (alreadyCollectedLine = true)) { 9863 // We are done parsing self cue. 9864 self.oncue && self.oncue(self.cue); 9865 self.cue = null; 9866 self.state = "ID"; 9867 continue; 9868 } 9869 9870 if (self.cue.text) { 9871 self.cue.text += "\n"; 9872 } 9873 9874 self.cue.text += line.replace(/\u2028/g, '\n').replace(/u2029/g, '\n'); 9875 continue; 9876 9877 case "BADCUE": 9878 // BADCUE 9879 // 54-62 - Collect and discard the remaining cue. 9880 if (!line) { 9881 self.state = "ID"; 9882 } 9883 9884 continue; 9885 } 9886 } 9887 } catch (e) { 9888 self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have. 9889 9890 if (self.state === "CUETEXT" && self.cue && self.oncue) { 9891 self.oncue(self.cue); 9892 } 9893 9894 self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise 9895 // another exception occurred so enter BADCUE state. 9896 9897 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 9898 } 9899 9900 return this; 9901 }, 9902 flush: function flush() { 9903 var self = this; 9904
vendor: 10,224 bytes, lines 9905-10272
9905 try { 9906 // Finish decoding the stream. 9907 self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region. 9908 9909 if (self.cue || self.state === "HEADER") { 9910 self.buffer += "\n\n"; 9911 self.parse(); 9912 } // If we've flushed, parsed, and we're still on the INITIAL state then 9913 // that means we don't have enough of the stream to parse the first 9914 // line. 9915 9916 9917 if (self.state === "INITIAL") { 9918 throw new ParsingError(ParsingError.Errors.BadSignature); 9919 } 9920 } catch (e) { 9921 self.reportOrThrowError(e); 9922 } 9923 9924 self.onflush && self.onflush(); 9925 return this; 9926 } 9927 }; 9928 var vtt = WebVTT$1; 9929 9930 /** 9931 * Copyright 2013 vtt.js Contributors 9932 * 9933 * Licensed under the Apache License, Version 2.0 (the "License"); 9934 * you may not use this file except in compliance with the License. 9935 * You may obtain a copy of the License at 9936 * 9937 * http://www.apache.org/licenses/LICENSE-2.0 9938 * 9939 * Unless required by applicable law or agreed to in writing, software 9940 * distributed under the License is distributed on an "AS IS" BASIS, 9941 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9942 * See the License for the specific language governing permissions and 9943 * limitations under the License. 9944 */ 9945 var autoKeyword = "auto"; 9946 var directionSetting = { 9947 "": 1, 9948 "lr": 1, 9949 "rl": 1 9950 }; 9951 var alignSetting = { 9952 "start": 1, 9953 "center": 1, 9954 "end": 1, 9955 "left": 1, 9956 "right": 1, 9957 "auto": 1, 9958 "line-left": 1, 9959 "line-right": 1 9960 }; 9961 9962 function findDirectionSetting(value) { 9963 if (typeof value !== "string") { 9964 return false; 9965 } 9966 9967 var dir = directionSetting[value.toLowerCase()]; 9968 return dir ? value.toLowerCase() : false; 9969 } 9970 9971 function findAlignSetting(value) { 9972 if (typeof value !== "string") { 9973 return false; 9974 } 9975 9976 var align = alignSetting[value.toLowerCase()]; 9977 return align ? value.toLowerCase() : false; 9978 } 9979 9980 function VTTCue(startTime, endTime, text) { 9981 /** 9982 * Shim implementation specific properties. These properties are not in 9983 * the spec. 9984 */ 9985 // Lets us know when the VTTCue's data has changed in such a way that we need 9986 // to recompute its display state. This lets us compute its display state 9987 // lazily. 9988 this.hasBeenReset = false; 9989 /** 9990 * VTTCue and TextTrackCue properties 9991 * http://dev.w3.org/html5/webvtt/#vttcue-interface 9992 */ 9993 9994 var _id = ""; 9995 var _pauseOnExit = false; 9996 var _startTime = startTime; 9997 var _endTime = endTime; 9998 var _text = text; 9999 var _region = null; 10000 var _vertical = ""; 10001 var _snapToLines = true; 10002 var _line = "auto"; 10003 var _lineAlign = "start"; 10004 var _position = "auto"; 10005 var _positionAlign = "auto"; 10006 var _size = 100; 10007 var _align = "center"; 10008 Object.defineProperties(this, { 10009 "id": { 10010 enumerable: true, 10011 get: function get() { 10012 return _id; 10013 }, 10014 set: function set(value) { 10015 _id = "" + value; 10016 } 10017 }, 10018 "pauseOnExit": { 10019 enumerable: true, 10020 get: function get() { 10021 return _pauseOnExit; 10022 }, 10023 set: function set(value) { 10024 _pauseOnExit = !!value; 10025 } 10026 }, 10027 "startTime": { 10028 enumerable: true, 10029 get: function get() { 10030 return _startTime; 10031 }, 10032 set: function set(value) { 10033 if (typeof value !== "number") { 10034 throw new TypeError("Start time must be set to a number."); 10035 } 10036 10037 _startTime = value; 10038 this.hasBeenReset = true; 10039 } 10040 }, 10041 "endTime": { 10042 enumerable: true, 10043 get: function get() { 10044 return _endTime; 10045 }, 10046 set: function set(value) { 10047 if (typeof value !== "number") { 10048 throw new TypeError("End time must be set to a number."); 10049 } 10050 10051 _endTime = value; 10052 this.hasBeenReset = true; 10053 } 10054 }, 10055 "text": { 10056 enumerable: true, 10057 get: function get() { 10058 return _text; 10059 }, 10060 set: function set(value) { 10061 _text = "" + value; 10062 this.hasBeenReset = true; 10063 } 10064 }, 10065 "region": { 10066 enumerable: true, 10067 get: function get() { 10068 return _region; 10069 }, 10070 set: function set(value) { 10071 _region = value; 10072 this.hasBeenReset = true; 10073 } 10074 }, 10075 "vertical": { 10076 enumerable: true, 10077 get: function get() { 10078 return _vertical; 10079 }, 10080 set: function set(value) { 10081 var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. 10082 10083 if (setting === false) { 10084 throw new SyntaxError("Vertical: an invalid or illegal direction string was specified."); 10085 } 10086 10087 _vertical = setting; 10088 this.hasBeenReset = true; 10089 } 10090 }, 10091 "snapToLines": { 10092 enumerable: true, 10093 get: function get() { 10094 return _snapToLines; 10095 }, 10096 set: function set(value) { 10097 _snapToLines = !!value; 10098 this.hasBeenReset = true; 10099 } 10100 }, 10101 "line": { 10102 enumerable: true, 10103 get: function get() { 10104 return _line; 10105 }, 10106 set: function set(value) { 10107 if (typeof value !== "number" && value !== autoKeyword) { 10108 throw new SyntaxError("Line: an invalid number or illegal string was specified."); 10109 } 10110 10111 _line = value; 10112 this.hasBeenReset = true; 10113 } 10114 }, 10115 "lineAlign": { 10116 enumerable: true, 10117 get: function get() { 10118 return _lineAlign; 10119 }, 10120 set: function set(value) { 10121 var setting = findAlignSetting(value); 10122 10123 if (!setting) { 10124 console.warn("lineAlign: an invalid or illegal string was specified."); 10125 } else { 10126 _lineAlign = setting; 10127 this.hasBeenReset = true; 10128 } 10129 } 10130 }, 10131 "position": { 10132 enumerable: true, 10133 get: function get() { 10134 return _position; 10135 }, 10136 set: function set(value) { 10137 if (value < 0 || value > 100) { 10138 throw new Error("Position must be between 0 and 100."); 10139 } 10140 10141 _position = value; 10142 this.hasBeenReset = true; 10143 } 10144 }, 10145 "positionAlign": { 10146 enumerable: true, 10147 get: function get() { 10148 return _positionAlign; 10149 }, 10150 set: function set(value) { 10151 var setting = findAlignSetting(value); 10152 10153 if (!setting) { 10154 console.warn("positionAlign: an invalid or illegal string was specified."); 10155 } else { 10156 _positionAlign = setting; 10157 this.hasBeenReset = true; 10158 } 10159 } 10160 }, 10161 "size": { 10162 enumerable: true, 10163 get: function get() { 10164 return _size; 10165 }, 10166 set: function set(value) { 10167 if (value < 0 || value > 100) { 10168 throw new Error("Size must be between 0 and 100."); 10169 } 10170 10171 _size = value; 10172 this.hasBeenReset = true; 10173 } 10174 }, 10175 "align": { 10176 enumerable: true, 10177 get: function get() { 10178 return _align; 10179 }, 10180 set: function set(value) { 10181 var setting = findAlignSetting(value); 10182 10183 if (!setting) { 10184 throw new SyntaxError("align: an invalid or illegal alignment string was specified."); 10185 } 10186 10187 _align = setting; 10188 this.hasBeenReset = true; 10189 } 10190 } 10191 }); 10192 /** 10193 * Other <track> spec defined properties 10194 */ 10195 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state 10196 10197 this.displayState = undefined; 10198 } 10199 /** 10200 * VTTCue methods 10201 */ 10202 10203 10204 VTTCue.prototype.getCueAsHTML = function () { 10205 // Assume WebVTT.convertCueToDOMTree is on the global. 10206 return WebVTT.convertCueToDOMTree(window, this.text); 10207 }; 10208 10209 var vttcue = VTTCue; 10210 10211 /** 10212 * Copyright 2013 vtt.js Contributors 10213 * 10214 * Licensed under the Apache License, Version 2.0 (the "License"); 10215 * you may not use this file except in compliance with the License. 10216 * You may obtain a copy of the License at 10217 * 10218 * http://www.apache.org/licenses/LICENSE-2.0 10219 * 10220 * Unless required by applicable law or agreed to in writing, software 10221 * distributed under the License is distributed on an "AS IS" BASIS, 10222 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10223 * See the License for the specific language governing permissions and 10224 * limitations under the License. 10225 */ 10226 var scrollSetting = { 10227 "": true, 10228 "up": true 10229 }; 10230 10231 function findScrollSetting(value) { 10232 if (typeof value !== "string") { 10233 return false; 10234 } 10235 10236 var scroll = scrollSetting[value.toLowerCase()]; 10237 return scroll ? value.toLowerCase() : false; 10238 } 10239 10240 function isValidPercentValue(value) { 10241 return typeof value === "number" && value >= 0 && value <= 100; 10242 } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 10243 10244 10245 function VTTRegion() { 10246 var _width = 100; 10247 var _lines = 3; 10248 var _regionAnchorX = 0; 10249 var _regionAnchorY = 100; 10250 var _viewportAnchorX = 0; 10251 var _viewportAnchorY = 100; 10252 var _scroll = ""; 10253 Object.defineProperties(this, { 10254 "width": { 10255 enumerable: true, 10256 get: function get() { 10257 return _width; 10258 }, 10259 set: function set(value) { 10260 if (!isValidPercentValue(value)) { 10261 throw new Error("Width must be between 0 and 100."); 10262 } 10263 10264 _width = value; 10265 } 10266 }, 10267 "lines": { 10268 enumerable: true, 10269 get: function get() { 10270 return _lines; 10271 }, 10272 set: function set(value) {
10273 if (typeof value !== "number") { 10274 throw new TypeError("Lines must be set to a number."); 10275 } 10276 10277 _lines = value; 10278 } 10279 }, 10280 "regionAnchorY": { 10281 enumerable: true, 10282 get: function get() { 10283 return _regionAnchorY; 10284 }, 10285 set: function set(value) { 10286 if (!isValidPercentValue(value)) { 10287 throw new Error("RegionAnchorX must be between 0 and 100."); 10288 } 10289 10290 _regionAnchorY = value; 10291 } 10292 }, 10293 "regionAnchorX": { 10294 enumerable: true, 10295 get: function get() { 10296 return _regionAnchorX; 10297 }, 10298 set: function set(value) { 10299 if (!isValidPercentValue(value)) { 10300 throw new Error("RegionAnchorY must be between 0 and 100."); 10301 } 10302 10303 _regionAnchorX = value; 10304 } 10305 }, 10306 "viewportAnchorY": { 10307 enumerable: true, 10308 get: function get() { 10309 return _viewportAnchorY; 10310 }, 10311 set: function set(value) { 10312 if (!isValidPercentValue(value)) { 10313 throw new Error("ViewportAnchorY must be between 0 and 100."); 10314 } 10315 10316 _viewportAnchorY = value; 10317 } 10318 }, 10319 "viewportAnchorX": { 10320 enumerable: true, 10321 get: function get() { 10322 return _viewportAnchorX; 10323 }, 10324 set: function set(value) { 10325 if (!isValidPercentValue(value)) { 10326 throw new Error("ViewportAnchorX must be between 0 and 100."); 10327 } 10328 10329 _viewportAnchorX = value; 10330 } 10331 }, 10332 "scroll": { 10333 enumerable: true, 10334 get: function get() { 10335 return _scroll; 10336 }, 10337 set: function set(value) { 10338 var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value. 10339 10340 if (setting === false) { 10341 console.warn("Scroll: an invalid or illegal string was specified."); 10342 } else { 10343 _scroll = setting; 10344 } 10345 } 10346 } 10347 }); 10348 } 10349 10350 var vttregion = VTTRegion; 10351 10352 var browserIndex = createCommonjsModule(function (module) { 10353 /** 10354 * Copyright 2013 vtt.js Contributors 10355 * 10356 * Licensed under the Apache License, Version 2.0 (the "License"); 10357 * you may not use this file except in compliance with the License. 10358 * You may obtain a copy of the License at 10359 * 10360 * http://www.apache.org/licenses/LICENSE-2.0 10361 * 10362 * Unless required by applicable law or agreed to in writing, software 10363 * distributed under the License is distributed on an "AS IS" BASIS, 10364 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10365 * See the License for the specific language governing permissions and 10366 * limitations under the License. 10367 */ 10368 // Default exports for Node. Export the extended versions of VTTCue and 10369 // VTTRegion in Node since we likely want the capability to convert back and 10370 // forth between JSON. If we don't then it's not that big of a deal since we're 10371 // off browser. 10372 var vttjs = module.exports = { 10373 WebVTT: vtt, 10374 VTTCue: vttcue, 10375 VTTRegion: vttregion 10376 }; 10377 window$1.vttjs = vttjs; 10378 window$1.WebVTT = vttjs.WebVTT; 10379 var cueShim = vttjs.VTTCue; 10380 var regionShim = vttjs.VTTRegion; 10381 var nativeVTTCue = window$1.VTTCue; 10382 var nativeVTTRegion = window$1.VTTRegion; 10383 10384 vttjs.shim = function () { 10385 window$1.VTTCue = cueShim; 10386 window$1.VTTRegion = regionShim; 10387 }; 10388 10389 vttjs.restore = function () { 10390 window$1.VTTCue = nativeVTTCue; 10391 window$1.VTTRegion = nativeVTTRegion; 10392 }; 10393 10394 if (!window$1.VTTCue) { 10395 vttjs.shim(); 10396 } 10397 }); 10398 var browserIndex_1 = browserIndex.WebVTT; 10399 var browserIndex_2 = browserIndex.VTTCue; 10400 var browserIndex_3 = browserIndex.VTTRegion; 10401 10402 /** 10403 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string 10404 * that just contains the src url alone. 10405 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` 10406 * `var SourceString = 'http://example.com/some-video.mp4';` 10407 * 10408 * @typedef {Object|string} Tech~SourceObject 10409 * 10410 * @property {string} src 10411 * The url to the source 10412 * 10413 * @property {string} type 10414 * The mime type of the source 10415 */ 10416 10417 /** 10418 * A function used by {@link Tech} to create a new {@link TextTrack}. 10419 * 10420 * @private 10421 * 10422 * @param {Tech} self 10423 * An instance of the Tech class. 10424 * 10425 * @param {string} kind 10426 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 10427 * 10428 * @param {string} [label] 10429 * Label to identify the text track 10430 * 10431 * @param {string} [language] 10432 * Two letter language abbreviation 10433 * 10434 * @param {Object} [options={}] 10435 * An object with additional text track options 10436 * 10437 * @return {TextTrack} 10438 * The text track that was created. 10439 */ 10440 10441 function createTrackHelper(self, kind, label, language, options) { 10442 if (options === void 0) { 10443 options = {}; 10444 } 10445 10446 var tracks = self.textTracks(); 10447 options.kind = kind; 10448 10449 if (label) { 10450 options.label = label; 10451 } 10452 10453 if (language) { 10454 options.language = language; 10455 } 10456 10457 options.tech = self; 10458 var track = new ALL.text.TrackClass(options); 10459 tracks.addTrack(track); 10460 return track; 10461 } 10462 /** 10463 * This is the base class for media playback technology controllers, such as 10464 * {@link Flash} and {@link HTML5} 10465 * 10466 * @extends Component 10467 */ 10468 10469 10470 var Tech = 10471 /*#__PURE__*/ 10472 function (_Component) { 10473 inheritsLoose(Tech, _Component); 10474 10475 /** 10476 * Create an instance of this Tech. 10477 * 10478 * @param {Object} [options] 10479 * The key/value store of player options. 10480 * 10481 * @param {Component~ReadyCallback} ready 10482 * Callback function to call when the `HTML5` Tech is ready. 10483 */ 10484 function Tech(options, ready) { 10485 var _this; 10486 10487 if (options === void 0) { 10488 options = {}; 10489 } 10490 10491 if (ready === void 0) { 10492 ready = function ready() {}; 10493 } 10494 10495 // we don't want the tech to report user activity automatically.
vendor: 60,948 bytes, lines 10496-12588
10496 // This is done manually in addControlsListeners 10497 options.reportTouchActivity = false; 10498 _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to 10499 // implement a very limited played() 10500 10501 _this.hasStarted_ = false; 10502 10503 _this.on('playing', function () { 10504 this.hasStarted_ = true; 10505 }); 10506 10507 _this.on('loadstart', function () { 10508 this.hasStarted_ = false; 10509 }); 10510 10511 ALL.names.forEach(function (name) { 10512 var props = ALL[name]; 10513 10514 if (options && options[props.getterName]) { 10515 _this[props.privateName] = options[props.getterName]; 10516 } 10517 }); // Manually track progress in cases where the browser/flash player doesn't report it. 10518 10519 if (!_this.featuresProgressEvents) { 10520 _this.manualProgressOn(); 10521 } // Manually track timeupdates in cases where the browser/flash player doesn't report it. 10522 10523 10524 if (!_this.featuresTimeupdateEvents) { 10525 _this.manualTimeUpdatesOn(); 10526 } 10527 10528 ['Text', 'Audio', 'Video'].forEach(function (track) { 10529 if (options["native" + track + "Tracks"] === false) { 10530 _this["featuresNative" + track + "Tracks"] = false; 10531 } 10532 }); 10533 10534 if (options.nativeCaptions === false || options.nativeTextTracks === false) { 10535 _this.featuresNativeTextTracks = false; 10536 } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { 10537 _this.featuresNativeTextTracks = true; 10538 } 10539 10540 if (!_this.featuresNativeTextTracks) { 10541 _this.emulateTextTracks(); 10542 } 10543 10544 _this.preloadTextTracks = options.preloadTextTracks !== false; 10545 _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); 10546 10547 _this.initTrackListeners(); // Turn on component tap events only if not using native controls 10548 10549 10550 if (!options.nativeControlsForTouch) { 10551 _this.emitTapEvents(); 10552 } 10553 10554 if (_this.constructor) { 10555 _this.name_ = _this.constructor.name || 'Unknown Tech'; 10556 } 10557 10558 return _this; 10559 } 10560 /** 10561 * A special function to trigger source set in a way that will allow player 10562 * to re-trigger if the player or tech are not ready yet. 10563 * 10564 * @fires Tech#sourceset 10565 * @param {string} src The source string at the time of the source changing. 10566 */ 10567 10568 10569 var _proto = Tech.prototype; 10570 10571 _proto.triggerSourceset = function triggerSourceset(src) { 10572 var _this2 = this; 10573 10574 if (!this.isReady_) { 10575 // on initial ready we have to trigger source set 10576 // 1ms after ready so that player can watch for it. 10577 this.one('ready', function () { 10578 return _this2.setTimeout(function () { 10579 return _this2.triggerSourceset(src); 10580 }, 1); 10581 }); 10582 } 10583 /** 10584 * Fired when the source is set on the tech causing the media element 10585 * to reload. 10586 * 10587 * @see {@link Player#event:sourceset} 10588 * @event Tech#sourceset 10589 * @type {EventTarget~Event} 10590 */ 10591 10592 10593 this.trigger({ 10594 src: src, 10595 type: 'sourceset' 10596 }); 10597 } 10598 /* Fallbacks for unsupported event types 10599 ================================================================================ */ 10600 10601 /** 10602 * Polyfill the `progress` event for browsers that don't support it natively. 10603 * 10604 * @see {@link Tech#trackProgress} 10605 */ 10606 ; 10607 10608 _proto.manualProgressOn = function manualProgressOn() { 10609 this.on('durationchange', this.onDurationChange); 10610 this.manualProgress = true; // Trigger progress watching when a source begins loading 10611 10612 this.one('ready', this.trackProgress); 10613 } 10614 /** 10615 * Turn off the polyfill for `progress` events that was created in 10616 * {@link Tech#manualProgressOn} 10617 */ 10618 ; 10619 10620 _proto.manualProgressOff = function manualProgressOff() { 10621 this.manualProgress = false; 10622 this.stopTrackingProgress(); 10623 this.off('durationchange', this.onDurationChange); 10624 } 10625 /** 10626 * This is used to trigger a `progress` event when the buffered percent changes. It 10627 * sets an interval function that will be called every 500 milliseconds to check if the 10628 * buffer end percent has changed. 10629 * 10630 * > This function is called by {@link Tech#manualProgressOn} 10631 * 10632 * @param {EventTarget~Event} event 10633 * The `ready` event that caused this to run. 10634 * 10635 * @listens Tech#ready 10636 * @fires Tech#progress 10637 */ 10638 ; 10639 10640 _proto.trackProgress = function trackProgress(event) { 10641 this.stopTrackingProgress(); 10642 this.progressInterval = this.setInterval(bind(this, function () { 10643 // Don't trigger unless buffered amount is greater than last time 10644 var numBufferedPercent = this.bufferedPercent(); 10645 10646 if (this.bufferedPercent_ !== numBufferedPercent) { 10647 /** 10648 * See {@link Player#progress} 10649 * 10650 * @event Tech#progress 10651 * @type {EventTarget~Event} 10652 */ 10653 this.trigger('progress'); 10654 } 10655 10656 this.bufferedPercent_ = numBufferedPercent; 10657 10658 if (numBufferedPercent === 1) { 10659 this.stopTrackingProgress(); 10660 } 10661 }), 500); 10662 } 10663 /** 10664 * Update our internal duration on a `durationchange` event by calling 10665 * {@link Tech#duration}. 10666 * 10667 * @param {EventTarget~Event} event 10668 * The `durationchange` event that caused this to run. 10669 * 10670 * @listens Tech#durationchange 10671 */ 10672 ; 10673 10674 _proto.onDurationChange = function onDurationChange(event) { 10675 this.duration_ = this.duration(); 10676 } 10677 /** 10678 * Get and create a `TimeRange` object for buffering. 10679 * 10680 * @return {TimeRange} 10681 * The time range object that was created. 10682 */ 10683 ; 10684 10685 _proto.buffered = function buffered() { 10686 return createTimeRanges(0, 0); 10687 } 10688 /** 10689 * Get the percentage of the current video that is currently buffered. 10690 * 10691 * @return {number} 10692 * A number from 0 to 1 that represents the decimal percentage of the 10693 * video that is buffered. 10694 * 10695 */ 10696 ; 10697 10698 _proto.bufferedPercent = function bufferedPercent$1() { 10699 return bufferedPercent(this.buffered(), this.duration_); 10700 } 10701 /** 10702 * Turn off the polyfill for `progress` events that was created in 10703 * {@link Tech#manualProgressOn} 10704 * Stop manually tracking progress events by clearing the interval that was set in 10705 * {@link Tech#trackProgress}. 10706 */ 10707 ; 10708 10709 _proto.stopTrackingProgress = function stopTrackingProgress() { 10710 this.clearInterval(this.progressInterval); 10711 } 10712 /** 10713 * Polyfill the `timeupdate` event for browsers that don't support it. 10714 * 10715 * @see {@link Tech#trackCurrentTime} 10716 */ 10717 ; 10718 10719 _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() { 10720 this.manualTimeUpdates = true; 10721 this.on('play', this.trackCurrentTime); 10722 this.on('pause', this.stopTrackingCurrentTime); 10723 } 10724 /** 10725 * Turn off the polyfill for `timeupdate` events that was created in 10726 * {@link Tech#manualTimeUpdatesOn} 10727 */ 10728 ; 10729 10730 _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() { 10731 this.manualTimeUpdates = false; 10732 this.stopTrackingCurrentTime(); 10733 this.off('play', this.trackCurrentTime); 10734 this.off('pause', this.stopTrackingCurrentTime); 10735 } 10736 /** 10737 * Sets up an interval function to track current time and trigger `timeupdate` every 10738 * 250 milliseconds. 10739 * 10740 * @listens Tech#play 10741 * @triggers Tech#timeupdate 10742 */ 10743 ; 10744 10745 _proto.trackCurrentTime = function trackCurrentTime() { 10746 if (this.currentTimeInterval) { 10747 this.stopTrackingCurrentTime(); 10748 } 10749 10750 this.currentTimeInterval = this.setInterval(function () { 10751 /** 10752 * Triggered at an interval of 250ms to indicated that time is passing in the video. 10753 * 10754 * @event Tech#timeupdate 10755 * @type {EventTarget~Event} 10756 */ 10757 this.trigger({ 10758 type: 'timeupdate', 10759 target: this, 10760 manuallyTriggered: true 10761 }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 10762 }, 250); 10763 } 10764 /** 10765 * Stop the interval function created in {@link Tech#trackCurrentTime} so that the 10766 * `timeupdate` event is no longer triggered. 10767 * 10768 * @listens {Tech#pause} 10769 */ 10770 ; 10771 10772 _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() { 10773 this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen, 10774 // the progress bar won't make it all the way to the end 10775 10776 this.trigger({ 10777 type: 'timeupdate', 10778 target: this, 10779 manuallyTriggered: true 10780 }); 10781 } 10782 /** 10783 * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, 10784 * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. 10785 * 10786 * @fires Component#dispose 10787 */ 10788 ; 10789 10790 _proto.dispose = function dispose() { 10791 // clear out all tracks because we can't reuse them between techs 10792 this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking 10793 10794 if (this.manualProgress) { 10795 this.manualProgressOff(); 10796 } 10797 10798 if (this.manualTimeUpdates) { 10799 this.manualTimeUpdatesOff(); 10800 } 10801 10802 _Component.prototype.dispose.call(this); 10803 } 10804 /** 10805 * Clear out a single `TrackList` or an array of `TrackLists` given their names. 10806 * 10807 * > Note: Techs without source handlers should call this between sources for `video` 10808 * & `audio` tracks. You don't want to use them between tracks! 10809 * 10810 * @param {string[]|string} types 10811 * TrackList names to clear, valid names are `video`, `audio`, and 10812 * `text`. 10813 */ 10814 ; 10815 10816 _proto.clearTracks = function clearTracks(types) { 10817 var _this3 = this; 10818 10819 types = [].concat(types); // clear out all tracks because we can't reuse them between techs 10820 10821 types.forEach(function (type) { 10822 var list = _this3[type + "Tracks"]() || []; 10823 var i = list.length; 10824 10825 while (i--) { 10826 var track = list[i]; 10827 10828 if (type === 'text') { 10829 _this3.removeRemoteTextTrack(track); 10830 } 10831 10832 list.removeTrack(track); 10833 } 10834 }); 10835 } 10836 /** 10837 * Remove any TextTracks added via addRemoteTextTrack that are 10838 * flagged for automatic garbage collection 10839 */ 10840 ; 10841 10842 _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() { 10843 var list = this.autoRemoteTextTracks_ || []; 10844 var i = list.length; 10845 10846 while (i--) { 10847 var track = list[i]; 10848 this.removeRemoteTextTrack(track); 10849 } 10850 } 10851 /** 10852 * Reset the tech, which will removes all sources and reset the internal readyState. 10853 * 10854 * @abstract 10855 */ 10856 ; 10857 10858 _proto.reset = function reset() {} 10859 /** 10860 * Get or set an error on the Tech. 10861 * 10862 * @param {MediaError} [err] 10863 * Error to set on the Tech 10864 * 10865 * @return {MediaError|null} 10866 * The current error object on the tech, or null if there isn't one. 10867 */ 10868 ; 10869 10870 _proto.error = function error(err) { 10871 if (err !== undefined) { 10872 this.error_ = new MediaError(err); 10873 this.trigger('error'); 10874 } 10875 10876 return this.error_; 10877 } 10878 /** 10879 * Returns the `TimeRange`s that have been played through for the current source. 10880 * 10881 * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. 10882 * It only checks whether the source has played at all or not. 10883 * 10884 * @return {TimeRange} 10885 * - A single time range if this video has played 10886 * - An empty set of ranges if not. 10887 */ 10888 ; 10889 10890 _proto.played = function played() { 10891 if (this.hasStarted_) { 10892 return createTimeRanges(0, 0); 10893 } 10894 10895 return createTimeRanges(); 10896 } 10897 /** 10898 * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was 10899 * previously called. 10900 * 10901 * @fires Tech#timeupdate 10902 */ 10903 ; 10904 10905 _proto.setCurrentTime = function setCurrentTime() { 10906 // improve the accuracy of manual timeupdates 10907 if (this.manualTimeUpdates) { 10908 /** 10909 * A manual `timeupdate` event. 10910 * 10911 * @event Tech#timeupdate 10912 * @type {EventTarget~Event} 10913 */ 10914 this.trigger({ 10915 type: 'timeupdate', 10916 target: this, 10917 manuallyTriggered: true 10918 }); 10919 } 10920 } 10921 /** 10922 * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and 10923 * {@link TextTrackList} events. 10924 * 10925 * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. 10926 * 10927 * @fires Tech#audiotrackchange 10928 * @fires Tech#videotrackchange 10929 * @fires Tech#texttrackchange 10930 */ 10931 ; 10932 10933 _proto.initTrackListeners = function initTrackListeners() { 10934 var _this4 = this; 10935 10936 /** 10937 * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} 10938 * 10939 * @event Tech#audiotrackchange 10940 * @type {EventTarget~Event} 10941 */ 10942 10943 /** 10944 * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} 10945 * 10946 * @event Tech#videotrackchange 10947 * @type {EventTarget~Event} 10948 */ 10949 10950 /** 10951 * Triggered when tracks are added or removed on the Tech {@link TextTrackList} 10952 * 10953 * @event Tech#texttrackchange 10954 * @type {EventTarget~Event} 10955 */ 10956 NORMAL.names.forEach(function (name) { 10957 var props = NORMAL[name]; 10958 10959 var trackListChanges = function trackListChanges() { 10960 _this4.trigger(name + "trackchange"); 10961 }; 10962 10963 var tracks = _this4[props.getterName](); 10964 10965 tracks.addEventListener('removetrack', trackListChanges); 10966 tracks.addEventListener('addtrack', trackListChanges); 10967 10968 _this4.on('dispose', function () { 10969 tracks.removeEventListener('removetrack', trackListChanges); 10970 tracks.removeEventListener('addtrack', trackListChanges); 10971 }); 10972 }); 10973 } 10974 /** 10975 * Emulate TextTracks using vtt.js if necessary 10976 * 10977 * @fires Tech#vttjsloaded 10978 * @fires Tech#vttjserror 10979 */ 10980 ; 10981 10982 _proto.addWebVttScript_ = function addWebVttScript_() { 10983 var _this5 = this; 10984 10985 if (window$1.WebVTT) { 10986 return; 10987 } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system 10988 // signals that the Tech is ready at which point Tech.el_ is part of the DOM 10989 // before inserting the WebVTT script 10990 10991 10992 if (document.body.contains(this.el())) { 10993 // load via require if available and vtt.js script location was not passed in 10994 // as an option. novtt builds will turn the above require call into an empty object 10995 // which will cause this if check to always fail. 10996 if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { 10997 this.trigger('vttjsloaded'); 10998 return; 10999 } // load vtt.js via the script location option or the cdn of no location was 11000 // passed in 11001 11002 11003 var script = document.createElement('script'); 11004 script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; 11005 11006 script.onload = function () { 11007 /** 11008 * Fired when vtt.js is loaded. 11009 * 11010 * @event Tech#vttjsloaded 11011 * @type {EventTarget~Event} 11012 */ 11013 _this5.trigger('vttjsloaded'); 11014 }; 11015 11016 script.onerror = function () { 11017 /** 11018 * Fired when vtt.js was not loaded due to an error 11019 * 11020 * @event Tech#vttjsloaded 11021 * @type {EventTarget~Event} 11022 */ 11023 _this5.trigger('vttjserror'); 11024 }; 11025 11026 this.on('dispose', function () { 11027 script.onload = null; 11028 script.onerror = null; 11029 }); // but have not loaded yet and we set it to true before the inject so that 11030 // we don't overwrite the injected window.WebVTT if it loads right away 11031 11032 window$1.WebVTT = true; 11033 this.el().parentNode.appendChild(script); 11034 } else { 11035 this.ready(this.addWebVttScript_); 11036 } 11037 } 11038 /** 11039 * Emulate texttracks 11040 * 11041 */ 11042 ; 11043 11044 _proto.emulateTextTracks = function emulateTextTracks() { 11045 var _this6 = this; 11046 11047 var tracks = this.textTracks(); 11048 var remoteTracks = this.remoteTextTracks(); 11049 11050 var handleAddTrack = function handleAddTrack(e) { 11051 return tracks.addTrack(e.track); 11052 }; 11053 11054 var handleRemoveTrack = function handleRemoveTrack(e) { 11055 return tracks.removeTrack(e.track); 11056 }; 11057 11058 remoteTracks.on('addtrack', handleAddTrack); 11059 remoteTracks.on('removetrack', handleRemoveTrack); 11060 this.addWebVttScript_(); 11061 11062 var updateDisplay = function updateDisplay() { 11063 return _this6.trigger('texttrackchange'); 11064 }; 11065 11066 var textTracksChanges = function textTracksChanges() { 11067 updateDisplay(); 11068 11069 for (var i = 0; i < tracks.length; i++) { 11070 var track = tracks[i]; 11071 track.removeEventListener('cuechange', updateDisplay); 11072 11073 if (track.mode === 'showing') { 11074 track.addEventListener('cuechange', updateDisplay); 11075 } 11076 } 11077 }; 11078 11079 textTracksChanges(); 11080 tracks.addEventListener('change', textTracksChanges); 11081 tracks.addEventListener('addtrack', textTracksChanges); 11082 tracks.addEventListener('removetrack', textTracksChanges); 11083 this.on('dispose', function () { 11084 remoteTracks.off('addtrack', handleAddTrack); 11085 remoteTracks.off('removetrack', handleRemoveTrack); 11086 tracks.removeEventListener('change', textTracksChanges); 11087 tracks.removeEventListener('addtrack', textTracksChanges); 11088 tracks.removeEventListener('removetrack', textTracksChanges); 11089 11090 for (var i = 0; i < tracks.length; i++) { 11091 var track = tracks[i]; 11092 track.removeEventListener('cuechange', updateDisplay); 11093 } 11094 }); 11095 } 11096 /** 11097 * Create and returns a remote {@link TextTrack} object. 11098 * 11099 * @param {string} kind 11100 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 11101 * 11102 * @param {string} [label] 11103 * Label to identify the text track 11104 * 11105 * @param {string} [language] 11106 * Two letter language abbreviation 11107 * 11108 * @return {TextTrack} 11109 * The TextTrack that gets created. 11110 */ 11111 ; 11112 11113 _proto.addTextTrack = function addTextTrack(kind, label, language) { 11114 if (!kind) { 11115 throw new Error('TextTrack kind is required but was not provided'); 11116 } 11117 11118 return createTrackHelper(this, kind, label, language); 11119 } 11120 /** 11121 * Create an emulated TextTrack for use by addRemoteTextTrack 11122 * 11123 * This is intended to be overridden by classes that inherit from 11124 * Tech in order to create native or custom TextTracks. 11125 * 11126 * @param {Object} options 11127 * The object should contain the options to initialize the TextTrack with. 11128 * 11129 * @param {string} [options.kind] 11130 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 11131 * 11132 * @param {string} [options.label]. 11133 * Label to identify the text track 11134 * 11135 * @param {string} [options.language] 11136 * Two letter language abbreviation. 11137 * 11138 * @return {HTMLTrackElement} 11139 * The track element that gets created. 11140 */ 11141 ; 11142 11143 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 11144 var track = mergeOptions(options, { 11145 tech: this 11146 }); 11147 return new REMOTE.remoteTextEl.TrackClass(track); 11148 } 11149 /** 11150 * Creates a remote text track object and returns an html track element. 11151 * 11152 * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. 11153 * 11154 * @param {Object} options 11155 * See {@link Tech#createRemoteTextTrack} for more detailed properties. 11156 * 11157 * @param {boolean} [manualCleanup=true] 11158 * - When false: the TextTrack will be automatically removed from the video 11159 * element whenever the source changes 11160 * - When True: The TextTrack will have to be cleaned up manually 11161 * 11162 * @return {HTMLTrackElement} 11163 * An Html Track Element. 11164 * 11165 * @deprecated The default functionality for this function will be equivalent 11166 * to "manualCleanup=false" in the future. The manualCleanup parameter will 11167 * also be removed. 11168 */ 11169 ; 11170 11171 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 11172 var _this7 = this; 11173 11174 if (options === void 0) { 11175 options = {}; 11176 } 11177 11178 var htmlTrackElement = this.createRemoteTextTrack(options); 11179 11180 if (manualCleanup !== true && manualCleanup !== false) { 11181 // deprecation warning 11182 log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); 11183 manualCleanup = true; 11184 } // store HTMLTrackElement and TextTrack to remote list 11185 11186 11187 this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); 11188 this.remoteTextTracks().addTrack(htmlTrackElement.track); 11189 11190 if (manualCleanup !== true) { 11191 // create the TextTrackList if it doesn't exist 11192 this.ready(function () { 11193 return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); 11194 }); 11195 } 11196 11197 return htmlTrackElement; 11198 } 11199 /** 11200 * Remove a remote text track from the remote `TextTrackList`. 11201 * 11202 * @param {TextTrack} track 11203 * `TextTrack` to remove from the `TextTrackList` 11204 */ 11205 ; 11206 11207 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 11208 var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list 11209 11210 this.remoteTextTrackEls().removeTrackElement_(trackElement); 11211 this.remoteTextTracks().removeTrack(track); 11212 this.autoRemoteTextTracks_.removeTrack(track); 11213 } 11214 /** 11215 * Gets available media playback quality metrics as specified by the W3C's Media 11216 * Playback Quality API. 11217 * 11218 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 11219 * 11220 * @return {Object} 11221 * An object with supported media playback quality metrics 11222 * 11223 * @abstract 11224 */ 11225 ; 11226 11227 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 11228 return {}; 11229 } 11230 /** 11231 * Attempt to create a floating video window always on top of other windows 11232 * so that users may continue consuming media while they interact with other 11233 * content sites, or applications on their device. 11234 * 11235 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 11236 * 11237 * @return {Promise|undefined} 11238 * A promise with a Picture-in-Picture window if the browser supports 11239 * Promises (or one was passed in as an option). It returns undefined 11240 * otherwise. 11241 * 11242 * @abstract 11243 */ 11244 ; 11245 11246 _proto.requestPictureInPicture = function requestPictureInPicture() { 11247 var PromiseClass = this.options_.Promise || window$1.Promise; 11248 11249 if (PromiseClass) { 11250 return PromiseClass.reject(); 11251 } 11252 } 11253 /** 11254 * A method to set a poster from a `Tech`. 11255 * 11256 * @abstract 11257 */ 11258 ; 11259 11260 _proto.setPoster = function setPoster() {} 11261 /** 11262 * A method to check for the presence of the 'playsinline' <video> attribute. 11263 * 11264 * @abstract 11265 */ 11266 ; 11267 11268 _proto.playsinline = function playsinline() {} 11269 /** 11270 * A method to set or unset the 'playsinline' <video> attribute. 11271 * 11272 * @abstract 11273 */ 11274 ; 11275 11276 _proto.setPlaysinline = function setPlaysinline() {} 11277 /** 11278 * Attempt to force override of native audio tracks. 11279 * 11280 * @param {boolean} override - If set to true native audio will be overridden, 11281 * otherwise native audio will potentially be used. 11282 * 11283 * @abstract 11284 */ 11285 ; 11286 11287 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {} 11288 /** 11289 * Attempt to force override of native video tracks. 11290 * 11291 * @param {boolean} override - If set to true native video will be overridden, 11292 * otherwise native video will potentially be used. 11293 * 11294 * @abstract 11295 */ 11296 ; 11297 11298 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {} 11299 /* 11300 * Check if the tech can support the given mime-type. 11301 * 11302 * The base tech does not support any type, but source handlers might 11303 * overwrite this. 11304 * 11305 * @param {string} type 11306 * The mimetype to check for support 11307 * 11308 * @return {string} 11309 * 'probably', 'maybe', or empty string 11310 * 11311 * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType} 11312 * 11313 * @abstract 11314 */ 11315 ; 11316 11317 _proto.canPlayType = function canPlayType() { 11318 return ''; 11319 } 11320 /** 11321 * Check if the type is supported by this tech. 11322 * 11323 * The base tech does not support any type, but source handlers might 11324 * overwrite this. 11325 * 11326 * @param {string} type 11327 * The media type to check 11328 * @return {string} Returns the native video element's response 11329 */ 11330 ; 11331 11332 Tech.canPlayType = function canPlayType() { 11333 return ''; 11334 } 11335 /** 11336 * Check if the tech can support the given source 11337 * 11338 * @param {Object} srcObj 11339 * The source object 11340 * @param {Object} options 11341 * The options passed to the tech 11342 * @return {string} 'probably', 'maybe', or '' (empty string) 11343 */ 11344 ; 11345 11346 Tech.canPlaySource = function canPlaySource(srcObj, options) { 11347 return Tech.canPlayType(srcObj.type); 11348 } 11349 /* 11350 * Return whether the argument is a Tech or not. 11351 * Can be passed either a Class like `Html5` or a instance like `player.tech_` 11352 * 11353 * @param {Object} component 11354 * The item to check 11355 * 11356 * @return {boolean} 11357 * Whether it is a tech or not 11358 * - True if it is a tech 11359 * - False if it is not 11360 */ 11361 ; 11362 11363 Tech.isTech = function isTech(component) { 11364 return component.prototype instanceof Tech || component instanceof Tech || component === Tech; 11365 } 11366 /** 11367 * Registers a `Tech` into a shared list for videojs. 11368 * 11369 * @param {string} name 11370 * Name of the `Tech` to register. 11371 * 11372 * @param {Object} tech 11373 * The `Tech` class to register. 11374 */ 11375 ; 11376 11377 Tech.registerTech = function registerTech(name, tech) { 11378 if (!Tech.techs_) { 11379 Tech.techs_ = {}; 11380 } 11381 11382 if (!Tech.isTech(tech)) { 11383 throw new Error("Tech " + name + " must be a Tech"); 11384 } 11385 11386 if (!Tech.canPlayType) { 11387 throw new Error('Techs must have a static canPlayType method on them'); 11388 } 11389 11390 if (!Tech.canPlaySource) { 11391 throw new Error('Techs must have a static canPlaySource method on them'); 11392 } 11393 11394 name = toTitleCase(name); 11395 Tech.techs_[name] = tech; 11396 Tech.techs_[toLowerCase(name)] = tech; 11397 11398 if (name !== 'Tech') { 11399 // camel case the techName for use in techOrder 11400 Tech.defaultTechOrder_.push(name); 11401 } 11402 11403 return tech; 11404 } 11405 /** 11406 * Get a `Tech` from the shared list by name. 11407 * 11408 * @param {string} name 11409 * `camelCase` or `TitleCase` name of the Tech to get 11410 * 11411 * @return {Tech|undefined} 11412 * The `Tech` or undefined if there was no tech with the name requested. 11413 */ 11414 ; 11415 11416 Tech.getTech = function getTech(name) { 11417 if (!name) { 11418 return; 11419 } 11420 11421 if (Tech.techs_ && Tech.techs_[name]) { 11422 return Tech.techs_[name]; 11423 } 11424 11425 name = toTitleCase(name); 11426 11427 if (window$1 && window$1.videojs && window$1.videojs[name]) { 11428 log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)"); 11429 return window$1.videojs[name]; 11430 } 11431 }; 11432 11433 return Tech; 11434 }(Component); 11435 /** 11436 * Get the {@link VideoTrackList} 11437 * 11438 * @returns {VideoTrackList} 11439 * @method Tech.prototype.videoTracks 11440 */ 11441 11442 /** 11443 * Get the {@link AudioTrackList} 11444 * 11445 * @returns {AudioTrackList} 11446 * @method Tech.prototype.audioTracks 11447 */ 11448 11449 /** 11450 * Get the {@link TextTrackList} 11451 * 11452 * @returns {TextTrackList} 11453 * @method Tech.prototype.textTracks 11454 */ 11455 11456 /** 11457 * Get the remote element {@link TextTrackList} 11458 * 11459 * @returns {TextTrackList} 11460 * @method Tech.prototype.remoteTextTracks 11461 */ 11462 11463 /** 11464 * Get the remote element {@link HtmlTrackElementList} 11465 * 11466 * @returns {HtmlTrackElementList} 11467 * @method Tech.prototype.remoteTextTrackEls 11468 */ 11469 11470 11471 ALL.names.forEach(function (name) { 11472 var props = ALL[name]; 11473 11474 Tech.prototype[props.getterName] = function () { 11475 this[props.privateName] = this[props.privateName] || new props.ListClass(); 11476 return this[props.privateName]; 11477 }; 11478 }); 11479 /** 11480 * List of associated text tracks 11481 * 11482 * @type {TextTrackList} 11483 * @private 11484 * @property Tech#textTracks_ 11485 */ 11486 11487 /** 11488 * List of associated audio tracks. 11489 * 11490 * @type {AudioTrackList} 11491 * @private 11492 * @property Tech#audioTracks_ 11493 */ 11494 11495 /** 11496 * List of associated video tracks. 11497 * 11498 * @type {VideoTrackList} 11499 * @private 11500 * @property Tech#videoTracks_ 11501 */ 11502 11503 /** 11504 * Boolean indicating whether the `Tech` supports volume control. 11505 * 11506 * @type {boolean} 11507 * @default 11508 */ 11509 11510 Tech.prototype.featuresVolumeControl = true; 11511 /** 11512 * Boolean indicating whether the `Tech` supports muting volume. 11513 * 11514 * @type {bolean} 11515 * @default 11516 */ 11517 11518 Tech.prototype.featuresMuteControl = true; 11519 /** 11520 * Boolean indicating whether the `Tech` supports fullscreen resize control. 11521 * Resizing plugins using request fullscreen reloads the plugin 11522 * 11523 * @type {boolean} 11524 * @default 11525 */ 11526 11527 Tech.prototype.featuresFullscreenResize = false; 11528 /** 11529 * Boolean indicating whether the `Tech` supports changing the speed at which the video 11530 * plays. Examples: 11531 * - Set player to play 2x (twice) as fast 11532 * - Set player to play 0.5x (half) as fast 11533 * 11534 * @type {boolean} 11535 * @default 11536 */ 11537 11538 Tech.prototype.featuresPlaybackRate = false; 11539 /** 11540 * Boolean indicating whether the `Tech` supports the `progress` event. This is currently 11541 * not triggered by video-js-swf. This will be used to determine if 11542 * {@link Tech#manualProgressOn} should be called. 11543 * 11544 * @type {boolean} 11545 * @default 11546 */ 11547 11548 Tech.prototype.featuresProgressEvents = false; 11549 /** 11550 * Boolean indicating whether the `Tech` supports the `sourceset` event. 11551 * 11552 * A tech should set this to `true` and then use {@link Tech#triggerSourceset} 11553 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting 11554 * a new source. 11555 * 11556 * @type {boolean} 11557 * @default 11558 */ 11559 11560 Tech.prototype.featuresSourceset = false; 11561 /** 11562 * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently 11563 * not triggered by video-js-swf. This will be used to determine if 11564 * {@link Tech#manualTimeUpdates} should be called. 11565 * 11566 * @type {boolean} 11567 * @default 11568 */ 11569 11570 Tech.prototype.featuresTimeupdateEvents = false; 11571 /** 11572 * Boolean indicating whether the `Tech` supports the native `TextTrack`s. 11573 * This will help us integrate with native `TextTrack`s if the browser supports them. 11574 * 11575 * @type {boolean} 11576 * @default 11577 */ 11578 11579 Tech.prototype.featuresNativeTextTracks = false; 11580 /** 11581 * A functional mixin for techs that want to use the Source Handler pattern. 11582 * Source handlers are scripts for handling specific formats. 11583 * The source handler pattern is used for adaptive formats (HLS, DASH) that 11584 * manually load video data and feed it into a Source Buffer (Media Source Extensions) 11585 * Example: `Tech.withSourceHandlers.call(MyTech);` 11586 * 11587 * @param {Tech} _Tech 11588 * The tech to add source handler functions to. 11589 * 11590 * @mixes Tech~SourceHandlerAdditions 11591 */ 11592 11593 Tech.withSourceHandlers = function (_Tech) { 11594 /** 11595 * Register a source handler 11596 * 11597 * @param {Function} handler 11598 * The source handler class 11599 * 11600 * @param {number} [index] 11601 * Register it at the following index 11602 */ 11603 _Tech.registerSourceHandler = function (handler, index) { 11604 var handlers = _Tech.sourceHandlers; 11605 11606 if (!handlers) { 11607 handlers = _Tech.sourceHandlers = []; 11608 } 11609 11610 if (index === undefined) { 11611 // add to the end of the list 11612 index = handlers.length; 11613 } 11614 11615 handlers.splice(index, 0, handler); 11616 }; 11617 /** 11618 * Check if the tech can support the given type. Also checks the 11619 * Techs sourceHandlers. 11620 * 11621 * @param {string} type 11622 * The mimetype to check. 11623 * 11624 * @return {string} 11625 * 'probably', 'maybe', or '' (empty string) 11626 */ 11627 11628 11629 _Tech.canPlayType = function (type) { 11630 var handlers = _Tech.sourceHandlers || []; 11631 var can; 11632 11633 for (var i = 0; i < handlers.length; i++) { 11634 can = handlers[i].canPlayType(type); 11635 11636 if (can) { 11637 return can; 11638 } 11639 } 11640 11641 return ''; 11642 }; 11643 /** 11644 * Returns the first source handler that supports the source. 11645 * 11646 * TODO: Answer question: should 'probably' be prioritized over 'maybe' 11647 * 11648 * @param {Tech~SourceObject} source 11649 * The source object 11650 * 11651 * @param {Object} options 11652 * The options passed to the tech 11653 * 11654 * @return {SourceHandler|null} 11655 * The first source handler that supports the source or null if 11656 * no SourceHandler supports the source 11657 */ 11658 11659 11660 _Tech.selectSourceHandler = function (source, options) { 11661 var handlers = _Tech.sourceHandlers || []; 11662 var can; 11663 11664 for (var i = 0; i < handlers.length; i++) { 11665 can = handlers[i].canHandleSource(source, options); 11666 11667 if (can) { 11668 return handlers[i]; 11669 } 11670 } 11671 11672 return null; 11673 }; 11674 /** 11675 * Check if the tech can support the given source. 11676 * 11677 * @param {Tech~SourceObject} srcObj 11678 * The source object 11679 * 11680 * @param {Object} options 11681 * The options passed to the tech 11682 * 11683 * @return {string} 11684 * 'probably', 'maybe', or '' (empty string) 11685 */ 11686 11687 11688 _Tech.canPlaySource = function (srcObj, options) { 11689 var sh = _Tech.selectSourceHandler(srcObj, options); 11690 11691 if (sh) { 11692 return sh.canHandleSource(srcObj, options); 11693 } 11694 11695 return ''; 11696 }; 11697 /** 11698 * When using a source handler, prefer its implementation of 11699 * any function normally provided by the tech. 11700 */ 11701 11702 11703 var deferrable = ['seekable', 'seeking', 'duration']; 11704 /** 11705 * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable 11706 * function if it exists, with a fallback to the Techs seekable function. 11707 * 11708 * @method _Tech.seekable 11709 */ 11710 11711 /** 11712 * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration 11713 * function if it exists, otherwise it will fallback to the techs duration function. 11714 * 11715 * @method _Tech.duration 11716 */ 11717 11718 deferrable.forEach(function (fnName) { 11719 var originalFn = this[fnName]; 11720 11721 if (typeof originalFn !== 'function') { 11722 return; 11723 } 11724 11725 this[fnName] = function () { 11726 if (this.sourceHandler_ && this.sourceHandler_[fnName]) { 11727 return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); 11728 } 11729 11730 return originalFn.apply(this, arguments); 11731 }; 11732 }, _Tech.prototype); 11733 /** 11734 * Create a function for setting the source using a source object 11735 * and source handlers. 11736 * Should never be called unless a source handler was found. 11737 * 11738 * @param {Tech~SourceObject} source 11739 * A source object with src and type keys 11740 */ 11741 11742 _Tech.prototype.setSource = function (source) { 11743 var sh = _Tech.selectSourceHandler(source, this.options_); 11744 11745 if (!sh) { 11746 // Fall back to a native source hander when unsupported sources are 11747 // deliberately set 11748 if (_Tech.nativeSourceHandler) { 11749 sh = _Tech.nativeSourceHandler; 11750 } else { 11751 log.error('No source handler found for the current source.'); 11752 } 11753 } // Dispose any existing source handler 11754 11755 11756 this.disposeSourceHandler(); 11757 this.off('dispose', this.disposeSourceHandler); 11758 11759 if (sh !== _Tech.nativeSourceHandler) { 11760 this.currentSource_ = source; 11761 } 11762 11763 this.sourceHandler_ = sh.handleSource(source, this, this.options_); 11764 this.one('dispose', this.disposeSourceHandler); 11765 }; 11766 /** 11767 * Clean up any existing SourceHandlers and listeners when the Tech is disposed. 11768 * 11769 * @listens Tech#dispose 11770 */ 11771 11772 11773 _Tech.prototype.disposeSourceHandler = function () { 11774 // if we have a source and get another one 11775 // then we are loading something new 11776 // than clear all of our current tracks 11777 if (this.currentSource_) { 11778 this.clearTracks(['audio', 'video']); 11779 this.currentSource_ = null; 11780 } // always clean up auto-text tracks 11781 11782 11783 this.cleanupAutoTextTracks(); 11784 11785 if (this.sourceHandler_) { 11786 if (this.sourceHandler_.dispose) { 11787 this.sourceHandler_.dispose(); 11788 } 11789 11790 this.sourceHandler_ = null; 11791 } 11792 }; 11793 }; // The base Tech class needs to be registered as a Component. It is the only 11794 // Tech that can be registered as a Component. 11795 11796 11797 Component.registerComponent('Tech', Tech); 11798 Tech.registerTech('Tech', Tech); 11799 /** 11800 * A list of techs that should be added to techOrder on Players 11801 * 11802 * @private 11803 */ 11804 11805 Tech.defaultTechOrder_ = []; 11806 11807 /** 11808 * @file middleware.js 11809 * @module middleware 11810 */ 11811 var middlewares = {}; 11812 var middlewareInstances = {}; 11813 var TERMINATOR = {}; 11814 /** 11815 * A middleware object is a plain JavaScript object that has methods that 11816 * match the {@link Tech} methods found in the lists of allowed 11817 * {@link module:middleware.allowedGetters|getters}, 11818 * {@link module:middleware.allowedSetters|setters}, and 11819 * {@link module:middleware.allowedMediators|mediators}. 11820 * 11821 * @typedef {Object} MiddlewareObject 11822 */ 11823 11824 /** 11825 * A middleware factory function that should return a 11826 * {@link module:middleware~MiddlewareObject|MiddlewareObject}. 11827 * 11828 * This factory will be called for each player when needed, with the player 11829 * passed in as an argument. 11830 * 11831 * @callback MiddlewareFactory 11832 * @param {Player} player 11833 * A Video.js player. 11834 */ 11835 11836 /** 11837 * Define a middleware that the player should use by way of a factory function 11838 * that returns a middleware object. 11839 * 11840 * @param {string} type 11841 * The MIME type to match or `"*"` for all MIME types. 11842 * 11843 * @param {MiddlewareFactory} middleware 11844 * A middleware factory function that will be executed for 11845 * matching types. 11846 */ 11847 11848 function use(type, middleware) { 11849 middlewares[type] = middlewares[type] || []; 11850 middlewares[type].push(middleware); 11851 } 11852 /** 11853 * Asynchronously sets a source using middleware by recursing through any 11854 * matching middlewares and calling `setSource` on each, passing along the 11855 * previous returned value each time. 11856 * 11857 * @param {Player} player 11858 * A {@link Player} instance. 11859 * 11860 * @param {Tech~SourceObject} src 11861 * A source object. 11862 * 11863 * @param {Function} 11864 * The next middleware to run. 11865 */ 11866 11867 function setSource(player, src, next) { 11868 player.setTimeout(function () { 11869 return setSourceHelper(src, middlewares[src.type], next, player); 11870 }, 1); 11871 } 11872 /** 11873 * When the tech is set, passes the tech to each middleware's `setTech` method. 11874 * 11875 * @param {Object[]} middleware 11876 * An array of middleware instances. 11877 * 11878 * @param {Tech} tech 11879 * A Video.js tech. 11880 */ 11881 11882 function setTech(middleware, tech) { 11883 middleware.forEach(function (mw) { 11884 return mw.setTech && mw.setTech(tech); 11885 }); 11886 } 11887 /** 11888 * Calls a getter on the tech first, through each middleware 11889 * from right to left to the player. 11890 * 11891 * @param {Object[]} middleware 11892 * An array of middleware instances. 11893 * 11894 * @param {Tech} tech 11895 * The current tech. 11896 * 11897 * @param {string} method 11898 * A method name. 11899 * 11900 * @return {Mixed} 11901 * The final value from the tech after middleware has intercepted it. 11902 */ 11903 11904 function get(middleware, tech, method) { 11905 return middleware.reduceRight(middlewareIterator(method), tech[method]()); 11906 } 11907 /** 11908 * Takes the argument given to the player and calls the setter method on each 11909 * middleware from left to right to the tech. 11910 * 11911 * @param {Object[]} middleware 11912 * An array of middleware instances. 11913 * 11914 * @param {Tech} tech 11915 * The current tech. 11916 * 11917 * @param {string} method 11918 * A method name. 11919 * 11920 * @param {Mixed} arg 11921 * The value to set on the tech. 11922 * 11923 * @return {Mixed} 11924 * The return value of the `method` of the `tech`. 11925 */ 11926 11927 function set(middleware, tech, method, arg) { 11928 return tech[method](middleware.reduce(middlewareIterator(method), arg)); 11929 } 11930 /** 11931 * Takes the argument given to the player and calls the `call` version of the 11932 * method on each middleware from left to right. 11933 * 11934 * Then, call the passed in method on the tech and return the result unchanged 11935 * back to the player, through middleware, this time from right to left. 11936 * 11937 * @param {Object[]} middleware 11938 * An array of middleware instances. 11939 * 11940 * @param {Tech} tech 11941 * The current tech. 11942 * 11943 * @param {string} method 11944 * A method name. 11945 * 11946 * @param {Mixed} arg 11947 * The value to set on the tech. 11948 * 11949 * @return {Mixed} 11950 * The return value of the `method` of the `tech`, regardless of the 11951 * return values of middlewares. 11952 */ 11953 11954 function mediate(middleware, tech, method, arg) { 11955 if (arg === void 0) { 11956 arg = null; 11957 } 11958 11959 var callMethod = 'call' + toTitleCase(method); 11960 var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg); 11961 var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to 11962 // prevent confusion if a techs method actually returns null. 11963 11964 var returnValue = terminated ? null : tech[method](middlewareValue); 11965 executeRight(middleware, method, returnValue, terminated); 11966 return returnValue; 11967 } 11968 /** 11969 * Enumeration of allowed getters where the keys are method names. 11970 * 11971 * @type {Object} 11972 */ 11973 11974 var allowedGetters = { 11975 buffered: 1, 11976 currentTime: 1, 11977 duration: 1, 11978 muted: 1, 11979 played: 1, 11980 paused: 1, 11981 seekable: 1, 11982 volume: 1 11983 }; 11984 /** 11985 * Enumeration of allowed setters where the keys are method names. 11986 * 11987 * @type {Object} 11988 */ 11989 11990 var allowedSetters = { 11991 setCurrentTime: 1, 11992 setMuted: 1, 11993 setVolume: 1 11994 }; 11995 /** 11996 * Enumeration of allowed mediators where the keys are method names. 11997 * 11998 * @type {Object} 11999 */ 12000 12001 var allowedMediators = { 12002 play: 1, 12003 pause: 1 12004 }; 12005 12006 function middlewareIterator(method) { 12007 return function (value, mw) { 12008 // if the previous middleware terminated, pass along the termination 12009 if (value === TERMINATOR) { 12010 return TERMINATOR; 12011 } 12012 12013 if (mw[method]) { 12014 return mw[method](value); 12015 } 12016 12017 return value; 12018 }; 12019 } 12020 12021 function executeRight(mws, method, value, terminated) { 12022 for (var i = mws.length - 1; i >= 0; i--) { 12023 var mw = mws[i]; 12024 12025 if (mw[method]) { 12026 mw[method](terminated, value); 12027 } 12028 } 12029 } 12030 /** 12031 * Clear the middleware cache for a player. 12032 * 12033 * @param {Player} player 12034 * A {@link Player} instance. 12035 */ 12036 12037 12038 function clearCacheForPlayer(player) { 12039 middlewareInstances[player.id()] = null; 12040 } 12041 /** 12042 * { 12043 * [playerId]: [[mwFactory, mwInstance], ...] 12044 * } 12045 * 12046 * @private 12047 */ 12048 12049 function getOrCreateFactory(player, mwFactory) { 12050 var mws = middlewareInstances[player.id()]; 12051 var mw = null; 12052 12053 if (mws === undefined || mws === null) { 12054 mw = mwFactory(player); 12055 middlewareInstances[player.id()] = [[mwFactory, mw]]; 12056 return mw; 12057 } 12058 12059 for (var i = 0; i < mws.length; i++) { 12060 var _mws$i = mws[i], 12061 mwf = _mws$i[0], 12062 mwi = _mws$i[1]; 12063 12064 if (mwf !== mwFactory) { 12065 continue; 12066 } 12067 12068 mw = mwi; 12069 } 12070 12071 if (mw === null) { 12072 mw = mwFactory(player); 12073 mws.push([mwFactory, mw]); 12074 } 12075 12076 return mw; 12077 } 12078 12079 function setSourceHelper(src, middleware, next, player, acc, lastRun) { 12080 if (src === void 0) { 12081 src = {}; 12082 } 12083 12084 if (middleware === void 0) { 12085 middleware = []; 12086 } 12087 12088 if (acc === void 0) { 12089 acc = []; 12090 } 12091 12092 if (lastRun === void 0) { 12093 lastRun = false; 12094 } 12095 12096 var _middleware = middleware, 12097 mwFactory = _middleware[0], 12098 mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road 12099 12100 12101 if (typeof mwFactory === 'string') { 12102 setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw, 12103 // then call the mw's setSource method 12104 } else if (mwFactory) { 12105 var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware 12106 12107 if (!mw.setSource) { 12108 acc.push(mw); 12109 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12110 } 12111 12112 mw.setSource(assign({}, src), function (err, _src) { 12113 // something happened, try the next middleware on the current level 12114 // make sure to use the old src 12115 if (err) { 12116 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12117 } // we've succeeded, now we need to go deeper 12118 12119 12120 acc.push(mw); // if it's the same type, continue down the current chain 12121 // otherwise, we want to go down the new chain 12122 12123 setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); 12124 }); 12125 } else if (mwrest.length) { 12126 setSourceHelper(src, mwrest, next, player, acc, lastRun); 12127 } else if (lastRun) { 12128 next(src, acc); 12129 } else { 12130 setSourceHelper(src, middlewares['*'], next, player, acc, true); 12131 } 12132 } 12133 12134 /** 12135 * Mimetypes 12136 * 12137 * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm 12138 * @typedef Mimetypes~Kind 12139 * @enum 12140 */ 12141 12142 var MimetypesKind = { 12143 opus: 'video/ogg', 12144 ogv: 'video/ogg', 12145 mp4: 'video/mp4', 12146 mov: 'video/mp4', 12147 m4v: 'video/mp4', 12148 mkv: 'video/x-matroska', 12149 m4a: 'audio/mp4', 12150 mp3: 'audio/mpeg', 12151 aac: 'audio/aac', 12152 oga: 'audio/ogg', 12153 m3u8: 'application/x-mpegURL', 12154 jpg: 'image/jpeg', 12155 jpeg: 'image/jpeg', 12156 gif: 'image/gif', 12157 png: 'image/png', 12158 svg: 'image/svg+xml', 12159 webp: 'image/webp' 12160 }; 12161 /** 12162 * Get the mimetype of a given src url if possible 12163 * 12164 * @param {string} src 12165 * The url to the src 12166 * 12167 * @return {string} 12168 * return the mimetype if it was known or empty string otherwise 12169 */ 12170 12171 var getMimetype = function getMimetype(src) { 12172 if (src === void 0) { 12173 src = ''; 12174 } 12175 12176 var ext = getFileExtension(src); 12177 var mimetype = MimetypesKind[ext.toLowerCase()]; 12178 return mimetype || ''; 12179 }; 12180 /** 12181 * Find the mime type of a given source string if possible. Uses the player 12182 * source cache. 12183 * 12184 * @param {Player} player 12185 * The player object 12186 * 12187 * @param {string} src 12188 * The source string 12189 * 12190 * @return {string} 12191 * The type that was found 12192 */ 12193 12194 var findMimetype = function findMimetype(player, src) { 12195 if (!src) { 12196 return ''; 12197 } // 1. check for the type in the `source` cache 12198 12199 12200 if (player.cache_.source.src === src && player.cache_.source.type) { 12201 return player.cache_.source.type; 12202 } // 2. see if we have this source in our `currentSources` cache 12203 12204 12205 var matchingSources = player.cache_.sources.filter(function (s) { 12206 return s.src === src; 12207 }); 12208 12209 if (matchingSources.length) { 12210 return matchingSources[0].type; 12211 } // 3. look for the src url in source elements and use the type there 12212 12213 12214 var sources = player.$$('source'); 12215 12216 for (var i = 0; i < sources.length; i++) { 12217 var s = sources[i]; 12218 12219 if (s.type && s.src && s.src === src) { 12220 return s.type; 12221 } 12222 } // 4. finally fallback to our list of mime types based on src url extension 12223 12224 12225 return getMimetype(src); 12226 }; 12227 12228 /** 12229 * @module filter-source 12230 */ 12231 /** 12232 * Filter out single bad source objects or multiple source objects in an 12233 * array. Also flattens nested source object arrays into a 1 dimensional 12234 * array of source objects. 12235 * 12236 * @param {Tech~SourceObject|Tech~SourceObject[]} src 12237 * The src object to filter 12238 * 12239 * @return {Tech~SourceObject[]} 12240 * An array of sourceobjects containing only valid sources 12241 * 12242 * @private 12243 */ 12244 12245 var filterSource = function filterSource(src) { 12246 // traverse array 12247 if (Array.isArray(src)) { 12248 var newsrc = []; 12249 src.forEach(function (srcobj) { 12250 srcobj = filterSource(srcobj); 12251 12252 if (Array.isArray(srcobj)) { 12253 newsrc = newsrc.concat(srcobj); 12254 } else if (isObject(srcobj)) { 12255 newsrc.push(srcobj); 12256 } 12257 }); 12258 src = newsrc; 12259 } else if (typeof src === 'string' && src.trim()) { 12260 // convert string into object 12261 src = [fixSource({ 12262 src: src 12263 })]; 12264 } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { 12265 // src is already valid 12266 src = [fixSource(src)]; 12267 } else { 12268 // invalid source, turn it into an empty array 12269 src = []; 12270 } 12271 12272 return src; 12273 }; 12274 /** 12275 * Checks src mimetype, adding it when possible 12276 * 12277 * @param {Tech~SourceObject} src 12278 * The src object to check 12279 * @return {Tech~SourceObject} 12280 * src Object with known type 12281 */ 12282 12283 12284 function fixSource(src) { 12285 if (!src.type) { 12286 var mimetype = getMimetype(src.src); 12287 12288 if (mimetype) { 12289 src.type = mimetype; 12290 } 12291 } 12292 12293 return src; 12294 } 12295 12296 /** 12297 * The `MediaLoader` is the `Component` that decides which playback technology to load 12298 * when a player is initialized. 12299 * 12300 * @extends Component 12301 */ 12302 12303 var MediaLoader = 12304 /*#__PURE__*/ 12305 function (_Component) { 12306 inheritsLoose(MediaLoader, _Component); 12307 12308 /** 12309 * Create an instance of this class. 12310 * 12311 * @param {Player} player 12312 * The `Player` that this class should attach to. 12313 * 12314 * @param {Object} [options] 12315 * The key/value store of player options. 12316 * 12317 * @param {Component~ReadyCallback} [ready] 12318 * The function that is run when this component is ready. 12319 */ 12320 function MediaLoader(player, options, ready) { 12321 var _this; 12322 12323 // MediaLoader has no element 12324 var options_ = mergeOptions({ 12325 createEl: false 12326 }, options); 12327 _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized, 12328 // load the first supported playback technology. 12329 12330 if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { 12331 for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { 12332 var techName = toTitleCase(j[i]); 12333 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 12334 // Remove once that deprecated behavior is removed. 12335 12336 if (!techName) { 12337 tech = Component.getComponent(techName); 12338 } // Check if the browser supports this technology 12339 12340 12341 if (tech && tech.isSupported()) { 12342 player.loadTech_(techName); 12343 break; 12344 } 12345 } 12346 } else { 12347 // Loop through playback technologies (HTML5, Flash) and check for support. 12348 // Then load the best source. 12349 // A few assumptions here: 12350 // All playback technologies respect preload false. 12351 player.src(options.playerOptions.sources); 12352 } 12353 12354 return _this; 12355 } 12356 12357 return MediaLoader; 12358 }(Component); 12359 12360 Component.registerComponent('MediaLoader', MediaLoader); 12361 12362 /** 12363 * Component which is clickable or keyboard actionable, but is not a 12364 * native HTML button. 12365 * 12366 * @extends Component 12367 */ 12368 12369 var ClickableComponent = 12370 /*#__PURE__*/ 12371 function (_Component) { 12372 inheritsLoose(ClickableComponent, _Component); 12373 12374 /** 12375 * Creates an instance of this class. 12376 * 12377 * @param {Player} player 12378 * The `Player` that this class should be attached to. 12379 * 12380 * @param {Object} [options] 12381 * The key/value store of player options. 12382 * 12383 * @param {function} [options.clickHandler] 12384 * The function to call when the button is clicked / activated 12385 */ 12386 function ClickableComponent(player, options) { 12387 var _this; 12388 12389 _this = _Component.call(this, player, options) || this; 12390 12391 _this.emitTapEvents(); 12392 12393 _this.enable(); 12394 12395 return _this; 12396 } 12397 /** 12398 * Create the `ClickableComponent`s DOM element. 12399 * 12400 * @param {string} [tag=div] 12401 * The element's node type. 12402 * 12403 * @param {Object} [props={}] 12404 * An object of properties that should be set on the element. 12405 * 12406 * @param {Object} [attributes={}] 12407 * An object of attributes that should be set on the element. 12408 * 12409 * @return {Element} 12410 * The element that gets created. 12411 */ 12412 12413 12414 var _proto = ClickableComponent.prototype; 12415 12416 _proto.createEl = function createEl(tag, props, attributes) { 12417 if (tag === void 0) { 12418 tag = 'div'; 12419 } 12420 12421 if (props === void 0) { 12422 props = {}; 12423 } 12424 12425 if (attributes === void 0) { 12426 attributes = {}; 12427 } 12428 12429 props = assign({ 12430 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 12431 className: this.buildCSSClass(), 12432 tabIndex: 0 12433 }, props); 12434 12435 if (tag === 'button') { 12436 log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead."); 12437 } // Add ARIA attributes for clickable element which is not a native HTML button 12438 12439 12440 attributes = assign({ 12441 role: 'button' 12442 }, attributes); 12443 this.tabIndex_ = props.tabIndex; 12444 12445 var el = _Component.prototype.createEl.call(this, tag, props, attributes); 12446 12447 this.createControlTextEl(el); 12448 return el; 12449 }; 12450 12451 _proto.dispose = function dispose() { 12452 // remove controlTextEl_ on dispose 12453 this.controlTextEl_ = null; 12454 12455 _Component.prototype.dispose.call(this); 12456 } 12457 /** 12458 * Create a control text element on this `ClickableComponent` 12459 * 12460 * @param {Element} [el] 12461 * Parent element for the control text. 12462 * 12463 * @return {Element} 12464 * The control text element that gets created. 12465 */ 12466 ; 12467 12468 _proto.createControlTextEl = function createControlTextEl(el) { 12469 this.controlTextEl_ = createEl('span', { 12470 className: 'vjs-control-text' 12471 }, { 12472 // let the screen reader user know that the text of the element may change 12473 'aria-live': 'polite' 12474 }); 12475 12476 if (el) { 12477 el.appendChild(this.controlTextEl_); 12478 } 12479 12480 this.controlText(this.controlText_, el); 12481 return this.controlTextEl_; 12482 } 12483 /** 12484 * Get or set the localize text to use for the controls on the `ClickableComponent`. 12485 * 12486 * @param {string} [text] 12487 * Control text for element. 12488 * 12489 * @param {Element} [el=this.el()] 12490 * Element to set the title on. 12491 * 12492 * @return {string} 12493 * - The control text when getting 12494 */ 12495 ; 12496 12497 _proto.controlText = function controlText(text, el) { 12498 if (el === void 0) { 12499 el = this.el(); 12500 } 12501 12502 if (text === undefined) { 12503 return this.controlText_ || 'Need Text'; 12504 } 12505 12506 var localizedText = this.localize(text); 12507 this.controlText_ = text; 12508 textContent(this.controlTextEl_, localizedText); 12509 12510 if (!this.nonIconControl) { 12511 // Set title attribute if only an icon is shown 12512 el.setAttribute('title', localizedText); 12513 } 12514 } 12515 /** 12516 * Builds the default DOM `className`. 12517 * 12518 * @return {string} 12519 * The DOM `className` for this object. 12520 */ 12521 ; 12522 12523 _proto.buildCSSClass = function buildCSSClass() { 12524 return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this); 12525 } 12526 /** 12527 * Enable this `ClickableComponent` 12528 */ 12529 ; 12530 12531 _proto.enable = function enable() { 12532 if (!this.enabled_) { 12533 this.enabled_ = true; 12534 this.removeClass('vjs-disabled'); 12535 this.el_.setAttribute('aria-disabled', 'false'); 12536 12537 if (typeof this.tabIndex_ !== 'undefined') { 12538 this.el_.setAttribute('tabIndex', this.tabIndex_); 12539 } 12540 12541 this.on(['tap', 'click'], this.handleClick); 12542 this.on('keydown', this.handleKeyDown); 12543 } 12544 } 12545 /** 12546 * Disable this `ClickableComponent` 12547 */ 12548 ; 12549 12550 _proto.disable = function disable() { 12551 this.enabled_ = false; 12552 this.addClass('vjs-disabled'); 12553 this.el_.setAttribute('aria-disabled', 'true'); 12554 12555 if (typeof this.tabIndex_ !== 'undefined') { 12556 this.el_.removeAttribute('tabIndex'); 12557 } 12558 12559 this.off('mouseover', this.handleMouseOver); 12560 this.off('mouseout', this.handleMouseOut); 12561 this.off(['tap', 'click'], this.handleClick); 12562 this.off('keydown', this.handleKeyDown); 12563 } 12564 /** 12565 * Event handler that is called when a `ClickableComponent` receives a 12566 * `click` or `tap` event. 12567 * 12568 * @param {EventTarget~Event} event 12569 * The `tap` or `click` event that caused this function to be called. 12570 * 12571 * @listens tap 12572 * @listens click 12573 * @abstract 12574 */ 12575 ; 12576 12577 _proto.handleClick = function handleClick(event) { 12578 if (this.options_.clickHandler) { 12579 this.options_.clickHandler.call(this, arguments); 12580 } 12581 } 12582 /** 12583 * Event handler that is called when a `ClickableComponent` receives a 12584 * `keydown` event. 12585 * 12586 * By default, if the key is Space or Enter, it will trigger a `click` event. 12587 * 12588 * @param {EventTarget~Event} event
12589 * The `keydown` event that caused this function to be called. 12590 * 12591 * @listens keydown 12592 */ 12593 ; 12594 12595 _proto.handleKeyDown = function handleKeyDown(event) { 12596 // Support Space or Enter key operation to fire a click event. Also, 12597 // prevent the event from propagating through the DOM and triggering 12598 // Player hotkeys. 12599 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 12600 event.preventDefault(); 12601 event.stopPropagation(); 12602 this.trigger('click'); 12603 } else { 12604 // Pass keypress handling up for unsupported keys 12605 _Component.prototype.handleKeyDown.call(this, event); 12606 } 12607 }; 12608 12609 return ClickableComponent; 12610 }(Component); 12611 12612 Component.registerComponent('ClickableComponent', ClickableComponent); 12613 12614 /** 12615 * A `ClickableComponent` that handles showing the poster image for the player. 12616 * 12617 * @extends ClickableComponent 12618 */ 12619 12620 var PosterImage = 12621 /*#__PURE__*/ 12622 function (_ClickableComponent) { 12623 inheritsLoose(PosterImage, _ClickableComponent); 12624 12625 /** 12626 * Create an instance of this class. 12627 * 12628 * @param {Player} player 12629 * The `Player` that this class should attach to. 12630 * 12631 * @param {Object} [options] 12632 * The key/value store of player options. 12633 */ 12634 function PosterImage(player, options) { 12635 var _this; 12636 12637 _this = _ClickableComponent.call(this, player, options) || this; 12638 12639 _this.update(); 12640 12641 player.on('posterchange', bind(assertThisInitialized(_this), _this.update)); 12642 return _this; 12643 } 12644 /** 12645 * Clean up and dispose of the `PosterImage`. 12646 */ 12647 12648 12649 var _proto = PosterImage.prototype; 12650 12651 _proto.dispose = function dispose() { 12652 this.player().off('posterchange', this.update); 12653 12654 _ClickableComponent.prototype.dispose.call(this); 12655 } 12656 /** 12657 * Create the `PosterImage`s DOM element. 12658 * 12659 * @return {Element} 12660 * The element that gets created. 12661 */ 12662 ; 12663 12664 _proto.createEl = function createEl$1() { 12665 var el = createEl('div', { 12666 className: 'vjs-poster', 12667 // Don't want poster to be tabbable. 12668 tabIndex: -1 12669 }); 12670 return el; 12671 } 12672 /** 12673 * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. 12674 * 12675 * @listens Player#posterchange 12676 * 12677 * @param {EventTarget~Event} [event] 12678 * The `Player#posterchange` event that triggered this function. 12679 */ 12680 ; 12681 12682 _proto.update = function update(event) { 12683 var url = this.player().poster(); 12684 this.setSrc(url); // If there's no poster source we should display:none on this component 12685 // so it's not still clickable or right-clickable 12686 12687 if (url) { 12688 this.show(); 12689 } else { 12690 this.hide(); 12691 } 12692 } 12693 /** 12694 * Set the source of the `PosterImage` depending on the display method. 12695 * 12696 * @param {string} url 12697 * The URL to the source for the `PosterImage`. 12698 */ 12699 ; 12700 12701 _proto.setSrc = function setSrc(url) { 12702 var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise 12703 // this will throw an extra error 12704 12705 if (url) { 12706 backgroundImage = "url(\"" + url + "\")"; 12707 } 12708 12709 this.el_.style.backgroundImage = backgroundImage; 12710 } 12711 /** 12712 * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See 12713 * {@link ClickableComponent#handleClick} for instances where this will be triggered. 12714 * 12715 * @listens tap 12716 * @listens click 12717 * @listens keydown 12718 * 12719 * @param {EventTarget~Event} event 12720 + The `click`, `tap` or `keydown` event that caused this function to be called. 12721 */ 12722 ; 12723 12724 _proto.handleClick = function handleClick(event) { 12725 // We don't want a click to trigger playback when controls are disabled 12726 if (!this.player_.controls()) { 12727 return; 12728 } 12729 12730 if (this.player_.tech(true)) { 12731 this.player_.tech(true).focus(); 12732 } 12733 12734 if (this.player_.paused()) { 12735 silencePromise(this.player_.play()); 12736 } else { 12737 this.player_.pause(); 12738 } 12739 }; 12740 12741 return PosterImage; 12742 }(ClickableComponent); 12743 12744 Component.registerComponent('PosterImage', PosterImage); 12745 12746 var darkGray = '#222'; 12747 var lightGray = '#ccc'; 12748 var fontMap = { 12749 monospace: 'monospace', 12750 sansSerif: 'sans-serif', 12751 serif: 'serif', 12752 monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', 12753 monospaceSerif: '"Courier New", monospace', 12754 proportionalSansSerif: 'sans-serif', 12755 proportionalSerif: 'serif',
12756 casual: '"Comic Sans MS", Impact, fantasy', 12757 script: '"Monotype Corsiva", cursive', 12758 smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' 12759 }; 12760 /** 12761 * Construct an rgba color from a given hex color code. 12762 * 12763 * @param {number} color 12764 * Hex number for color, like #f0e or #f604e2. 12765 * 12766 * @param {number} opacity 12767 * Value for opacity, 0.0 - 1.0. 12768 * 12769 * @return {string} 12770 * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. 12771 */ 12772 12773 function constructColor(color, opacity) { 12774 var hex; 12775 12776 if (color.length === 4) { 12777 // color looks like "#f0e" 12778 hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; 12779 } else if (color.length === 7) { 12780 // color looks like "#f604e2" 12781 hex = color.slice(1); 12782 } else { 12783 throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.'); 12784 } 12785 12786 return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')'; 12787 } 12788 /** 12789 * Try to update the style of a DOM element. Some style changes will throw an error, 12790 * particularly in IE8. Those should be noops. 12791 * 12792 * @param {Element} el 12793 * The DOM element to be styled. 12794 * 12795 * @param {string} style 12796 * The CSS property on the element that should be styled. 12797 * 12798 * @param {string} rule 12799 * The style rule that should be applied to the property. 12800 * 12801 * @private 12802 */ 12803 12804 function tryUpdateStyle(el, style, rule) { 12805 try { 12806 el.style[style] = rule; 12807 } catch (e) { 12808 // Satisfies linter. 12809 return; 12810 } 12811 } 12812 /** 12813 * The component for displaying text track cues. 12814 * 12815 * @extends Component 12816 */ 12817 12818 12819 var TextTrackDisplay = 12820 /*#__PURE__*/ 12821 function (_Component) { 12822 inheritsLoose(TextTrackDisplay, _Component); 12823 12824 /** 12825 * Creates an instance of this class. 12826 * 12827 * @param {Player} player 12828 * The `Player` that this class should be attached to. 12829 * 12830 * @param {Object} [options] 12831 * The key/value store of player options. 12832 * 12833 * @param {Component~ReadyCallback} [ready] 12834 * The function to call when `TextTrackDisplay` is ready. 12835 */ 12836 function TextTrackDisplay(player, options, ready) { 12837 var _this; 12838 12839 _this = _Component.call(this, player, options, ready) || this; 12840 var updateDisplayHandler = bind(assertThisInitialized(_this), _this.updateDisplay); 12841 player.on('loadstart', bind(assertThisInitialized(_this), _this.toggleDisplay)); 12842 player.on('texttrackchange', updateDisplayHandler); 12843 player.on('loadedmetadata', bind(assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error 12844 // if a track should show by default and the display hadn't loaded yet. 12845 // Should probably be moved to an external track loader when we support 12846 // tracks that don't need a display. 12847 12848 player.ready(bind(assertThisInitialized(_this), function () { 12849 if (player.tech_ && player.tech_.featuresNativeTextTracks) { 12850 this.hide(); 12851 return; 12852 } 12853 12854 player.on('fullscreenchange', updateDisplayHandler); 12855 player.on('playerresize', updateDisplayHandler); 12856 window$1.addEventListener('orientationchange', updateDisplayHandler); 12857 player.on('dispose', function () { 12858 return window$1.removeEventListener('orientationchange', updateDisplayHandler); 12859 }); 12860 var tracks = this.options_.playerOptions.tracks || []; 12861 12862 for (var i = 0; i < tracks.length; i++) { 12863 this.player_.addRemoteTextTrack(tracks[i], true); 12864 } 12865 12866 this.preselectTrack(); 12867 })); 12868 return _this; 12869 } 12870 /** 12871 * Preselect a track following this precedence: 12872 * - matches the previously selected {@link TextTrack}'s language and kind 12873 * - matches the previously selected {@link TextTrack}'s language only 12874 * - is the first default captions track 12875 * - is the first default descriptions track 12876 * 12877 * @listens Player#loadstart 12878 */ 12879 12880 12881 var _proto = TextTrackDisplay.prototype; 12882 12883 _proto.preselectTrack = function preselectTrack() { 12884 var modes = { 12885 captions: 1, 12886 subtitles: 1 12887 }; 12888 var trackList = this.player_.textTracks(); 12889 var userPref = this.player_.cache_.selectedLanguage; 12890 var firstDesc; 12891 var firstCaptions; 12892 var preferredTrack; 12893 12894 for (var i = 0; i < trackList.length; i++) { 12895 var track = trackList[i]; 12896 12897 if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) { 12898 // Always choose the track that matches both language and kind 12899 if (track.kind === userPref.kind) { 12900 preferredTrack = track; // or choose the first track that matches language 12901 } else if (!preferredTrack) { 12902 preferredTrack = track; 12903 } // clear everything if offTextTrackMenuItem was clicked 12904 12905 } else if (userPref && !userPref.enabled) { 12906 preferredTrack = null; 12907 firstDesc = null; 12908 firstCaptions = null; 12909 } else if (track["default"]) { 12910 if (track.kind === 'descriptions' && !firstDesc) { 12911 firstDesc = track; 12912 } else if (track.kind in modes && !firstCaptions) { 12913 firstCaptions = track; 12914 } 12915 } 12916 } // The preferredTrack matches the user preference and takes 12917 // precedence over all the other tracks. 12918 // So, display the preferredTrack before the first default track 12919 // and the subtitles/captions track before the descriptions track 12920 12921 12922 if (preferredTrack) { 12923 preferredTrack.mode = 'showing'; 12924 } else if (firstCaptions) { 12925 firstCaptions.mode = 'showing'; 12926 } else if (firstDesc) { 12927 firstDesc.mode = 'showing'; 12928 } 12929 } 12930 /** 12931 * Turn display of {@link TextTrack}'s from the current state into the other state. 12932 * There are only two states: 12933 * - 'shown' 12934 * - 'hidden' 12935 * 12936 * @listens Player#loadstart 12937 */ 12938 ; 12939 12940 _proto.toggleDisplay = function toggleDisplay() { 12941 if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { 12942 this.hide(); 12943 } else { 12944 this.show(); 12945 } 12946 } 12947 /** 12948 * Create the {@link Component}'s DOM element. 12949 * 12950 * @return {Element} 12951 * The element that was created. 12952 */ 12953 ; 12954 12955 _proto.createEl = function createEl() { 12956 return _Component.prototype.createEl.call(this, 'div', { 12957 className: 'vjs-text-track-display' 12958 }, { 12959 'aria-live': 'off', 12960 'aria-atomic': 'true' 12961 }); 12962 } 12963 /** 12964 * Clear all displayed {@link TextTrack}s. 12965 */ 12966 ; 12967 12968 _proto.clearDisplay = function clearDisplay() { 12969 if (typeof window$1.WebVTT === 'function') { 12970 window$1.WebVTT.processCues(window$1, [], this.el_); 12971 } 12972 } 12973 /** 12974 * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or 12975 * a {@link Player#fullscreenchange} is fired. 12976 * 12977 * @listens Player#texttrackchange 12978 * @listens Player#fullscreenchange 12979 */ 12980 ; 12981 12982 _proto.updateDisplay = function updateDisplay() { 12983 var tracks = this.player_.textTracks(); 12984 var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks; 12985 this.clearDisplay(); 12986 12987 if (allowMultipleShowingTracks) { 12988 var showingTracks = []; 12989 12990 for (var _i = 0; _i < tracks.length; ++_i) { 12991 var track = tracks[_i]; 12992 12993 if (track.mode !== 'showing') { 12994 continue; 12995 } 12996 12997 showingTracks.push(track); 12998 } 12999 13000 this.updateForTrack(showingTracks); 13001 return; 13002 } // Track display prioritization model: if multiple tracks are 'showing', 13003 // display the first 'subtitles' or 'captions' track which is 'showing', 13004 // otherwise display the first 'descriptions' track which is 'showing' 13005 13006 13007 var descriptionsTrack = null; 13008 var captionsSubtitlesTrack = null; 13009 var i = tracks.length; 13010 13011 while (i--) { 13012 var _track = tracks[i]; 13013 13014 if (_track.mode === 'showing') { 13015 if (_track.kind === 'descriptions') { 13016 descriptionsTrack = _track; 13017 } else { 13018 captionsSubtitlesTrack = _track; 13019 } 13020 } 13021 } 13022 13023 if (captionsSubtitlesTrack) { 13024 if (this.getAttribute('aria-live') !== 'off') { 13025 this.setAttribute('aria-live', 'off'); 13026 } 13027 13028 this.updateForTrack(captionsSubtitlesTrack); 13029 } else if (descriptionsTrack) { 13030 if (this.getAttribute('aria-live') !== 'assertive') { 13031 this.setAttribute('aria-live', 'assertive'); 13032 } 13033 13034 this.updateForTrack(descriptionsTrack); 13035 } 13036 } 13037 /** 13038 * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}. 13039 * 13040 * @param {TextTrack} track
13041 * Text track object containing active cues to style. 13042 */ 13043 ; 13044 13045 _proto.updateDisplayState = function updateDisplayState(track) { 13046 var overrides = this.player_.textTrackSettings.getValues(); 13047 var cues = track.activeCues; 13048 var i = cues.length; 13049 13050 while (i--) { 13051 var cue = cues[i]; 13052 13053 if (!cue) { 13054 continue; 13055 } 13056 13057 var cueDiv = cue.displayState; 13058 13059 if (overrides.color) { 13060 cueDiv.firstChild.style.color = overrides.color; 13061 } 13062 13063 if (overrides.textOpacity) { 13064 tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); 13065 } 13066 13067 if (overrides.backgroundColor) { 13068 cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; 13069 } 13070 13071 if (overrides.backgroundOpacity) { 13072 tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); 13073 } 13074 13075 if (overrides.windowColor) { 13076 if (overrides.windowOpacity) { 13077 tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); 13078 } else { 13079 cueDiv.style.backgroundColor = overrides.windowColor; 13080 } 13081 } 13082 13083 if (overrides.edgeStyle) { 13084 if (overrides.edgeStyle === 'dropshadow') { 13085 cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray; 13086 } else if (overrides.edgeStyle === 'raised') { 13087 cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray; 13088 } else if (overrides.edgeStyle === 'depressed') { 13089 cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray; 13090 } else if (overrides.edgeStyle === 'uniform') { 13091 cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray; 13092 } 13093 } 13094 13095 if (overrides.fontPercent && overrides.fontPercent !== 1) { 13096 var fontSize = window$1.parseFloat(cueDiv.style.fontSize); 13097 cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; 13098 cueDiv.style.height = 'auto'; 13099 cueDiv.style.top = 'auto'; 13100 cueDiv.style.bottom = '2px'; 13101 } 13102 13103 if (overrides.fontFamily && overrides.fontFamily !== 'default') { 13104 if (overrides.fontFamily === 'small-caps') { 13105 cueDiv.firstChild.style.fontVariant = 'small-caps'; 13106 } else { 13107 cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; 13108 } 13109 } 13110 } 13111 } 13112 /** 13113 * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}. 13114 * 13115 * @param {TextTrack|TextTrack[]} tracks 13116 * Text track object or text track array to be added to the list. 13117 */ 13118 ; 13119 13120 _proto.updateForTrack = function updateForTrack(tracks) { 13121 if (!Array.isArray(tracks)) { 13122 tracks = [tracks]; 13123 } 13124 13125 if (typeof window$1.WebVTT !== 'function' || tracks.every(function (track) { 13126 return !track.activeCues; 13127 })) { 13128 return; 13129 } 13130 13131 var cues = []; // push all active track cues 13132 13133 for (var i = 0; i < tracks.length; ++i) { 13134 var track = tracks[i]; 13135 13136 for (var j = 0; j < track.activeCues.length; ++j) { 13137 cues.push(track.activeCues[j]); 13138 } 13139 } // removes all cues before it processes new ones 13140 13141 13142 window$1.WebVTT.processCues(window$1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary 13143 13144 for (var _i2 = 0; _i2 < tracks.length; ++_i2) { 13145 var _track2 = tracks[_i2]; 13146 13147 for (var _j = 0; _j < _track2.activeCues.length; ++_j) { 13148 var cueEl = _track2.activeCues[_j].displayState; 13149 addClass(cueEl, 'vjs-text-track-cue'); 13150 addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2)); 13151 } 13152 13153 if (this.player_.textTrackSettings) { 13154 this.updateDisplayState(_track2); 13155 } 13156 } 13157 }; 13158 13159 return TextTrackDisplay; 13160 }(Component); 13161 13162 Component.registerComponent('TextTrackDisplay', TextTrackDisplay); 13163 13164 /** 13165 * A loading spinner for use during waiting/loading events. 13166 * 13167 * @extends Component 13168 */ 13169 13170 var LoadingSpinner = 13171 /*#__PURE__*/ 13172 function (_Component) { 13173 inheritsLoose(LoadingSpinner, _Component); 13174 13175 function LoadingSpinner() { 13176 return _Component.apply(this, arguments) || this; 13177 } 13178 13179 var _proto = LoadingSpinner.prototype; 13180 13181 /** 13182 * Create the `LoadingSpinner`s DOM element. 13183 * 13184 * @return {Element} 13185 * The dom element that gets created. 13186 */ 13187 _proto.createEl = function createEl$1() { 13188 var isAudio = this.player_.isAudio(); 13189 var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player'); 13190 var controlText = createEl('span', { 13191 className: 'vjs-control-text',
13192 innerHTML: this.localize('{1} is loading.', [playerType]) 13193 }); 13194 13195 var el = _Component.prototype.createEl.call(this, 'div', { 13196 className: 'vjs-loading-spinner', 13197 dir: 'ltr' 13198 }); 13199 13200 el.appendChild(controlText); 13201 return el; 13202 }; 13203 13204 return LoadingSpinner; 13205 }(Component); 13206 13207 Component.registerComponent('LoadingSpinner', LoadingSpinner); 13208 13209 /** 13210 * Base class for all buttons. 13211 * 13212 * @extends ClickableComponent 13213 */ 13214 13215 var Button = 13216 /*#__PURE__*/ 13217 function (_ClickableComponent) { 13218 inheritsLoose(Button, _ClickableComponent); 13219 13220 function Button() { 13221 return _ClickableComponent.apply(this, arguments) || this; 13222 } 13223 13224 var _proto = Button.prototype; 13225 13226 /** 13227 * Create the `Button`s DOM element. 13228 * 13229 * @param {string} [tag="button"] 13230 * The element's node type. This argument is IGNORED: no matter what 13231 * is passed, it will always create a `button` element. 13232 * 13233 * @param {Object} [props={}] 13234 * An object of properties that should be set on the element. 13235 * 13236 * @param {Object} [attributes={}] 13237 * An object of attributes that should be set on the element. 13238 * 13239 * @return {Element} 13240 * The element that gets created. 13241 */ 13242 _proto.createEl = function createEl(tag, props, attributes) { 13243 if (props === void 0) { 13244 props = {}; 13245 } 13246 13247 if (attributes === void 0) { 13248 attributes = {}; 13249 } 13250 13251 tag = 'button'; 13252 props = assign({ 13253 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 13254 className: this.buildCSSClass() 13255 }, props); // Add attributes for button element 13256 13257 attributes = assign({ 13258 // Necessary since the default button type is "submit" 13259 type: 'button' 13260 }, attributes); 13261 var el = Component.prototype.createEl.call(this, tag, props, attributes); 13262 this.createControlTextEl(el); 13263 return el; 13264 } 13265 /** 13266 * Add a child `Component` inside of this `Button`. 13267 * 13268 * @param {string|Component} child 13269 * The name or instance of a child to add. 13270 * 13271 * @param {Object} [options={}] 13272 * The key/value store of options that will get passed to children of 13273 * the child. 13274 * 13275 * @return {Component} 13276 * The `Component` that gets added as a child. When using a string the 13277 * `Component` will get created by this process. 13278 * 13279 * @deprecated since version 5 13280 */ 13281 ; 13282 13283 _proto.addChild = function addChild(child, options) { 13284 if (options === void 0) { 13285 options = {}; 13286 } 13287 13288 var className = this.constructor.name; 13289 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 13290 13291 return Component.prototype.addChild.call(this, child, options); 13292 } 13293 /** 13294 * Enable the `Button` element so that it can be activated or clicked. Use this with 13295 * {@link Button#disable}. 13296 */ 13297 ; 13298 13299 _proto.enable = function enable() { 13300 _ClickableComponent.prototype.enable.call(this); 13301 13302 this.el_.removeAttribute('disabled'); 13303 } 13304 /** 13305 * Disable the `Button` element so that it cannot be activated or clicked. Use this with 13306 * {@link Button#enable}. 13307 */ 13308 ; 13309 13310 _proto.disable = function disable() { 13311 _ClickableComponent.prototype.disable.call(this); 13312 13313 this.el_.setAttribute('disabled', 'disabled'); 13314 } 13315 /** 13316 * This gets called when a `Button` has focus and `keydown` is triggered via a key 13317 * press. 13318 * 13319 * @param {EventTarget~Event} event 13320 * The event that caused this function to get called. 13321 * 13322 * @listens keydown 13323 */ 13324 ; 13325 13326 _proto.handleKeyDown = function handleKeyDown(event) { 13327 // Ignore Space or Enter key operation, which is handled by the browser for 13328 // a button - though not for its super class, ClickableComponent. Also, 13329 // prevent the event from propagating through the DOM and triggering Player 13330 // hotkeys. We do not preventDefault here because we _want_ the browser to 13331 // handle it. 13332 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 13333 event.stopPropagation(); 13334 return; 13335 } // Pass keypress handling up for unsupported keys 13336 13337 13338 _ClickableComponent.prototype.handleKeyDown.call(this, event); 13339 }; 13340 13341 return Button; 13342 }(ClickableComponent); 13343 13344 Component.registerComponent('Button', Button); 13345 13346 /** 13347 * The initial play button that shows before the video has played. The hiding of the 13348 * `BigPlayButton` get done via CSS and `Player` states. 13349 * 13350 * @extends Button 13351 */ 13352 13353 var BigPlayButton = 13354 /*#__PURE__*/ 13355 function (_Button) { 13356 inheritsLoose(BigPlayButton, _Button); 13357 13358 function BigPlayButton(player, options) { 13359 var _this; 13360 13361 _this = _Button.call(this, player, options) || this; 13362 _this.mouseused_ = false; 13363 13364 _this.on('mousedown', _this.handleMouseDown);
13365 13366 return _this; 13367 } 13368 /** 13369 * Builds the default DOM `className`. 13370 * 13371 * @return {string} 13372 * The DOM `className` for this object. Always returns 'vjs-big-play-button'. 13373 */ 13374 13375 13376 var _proto = BigPlayButton.prototype; 13377 13378 _proto.buildCSSClass = function buildCSSClass() { 13379 return 'vjs-big-play-button'; 13380 } 13381 /** 13382 * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} 13383 * for more detailed information on what a click can be. 13384 * 13385 * @param {EventTarget~Event} event 13386 * The `keydown`, `tap`, or `click` event that caused this function to be 13387 * called. 13388 * 13389 * @listens tap 13390 * @listens click 13391 */ 13392 ; 13393 13394 _proto.handleClick = function handleClick(event) { 13395 var playPromise = this.player_.play(); // exit early if clicked via the mouse 13396 13397 if (this.mouseused_ && event.clientX && event.clientY) { 13398 var sourceIsEncrypted = this.player_.usingPlugin('eme') && this.player_.eme.sessions && this.player_.eme.sessions.length > 0; 13399 silencePromise(playPromise); 13400 13401 if (this.player_.tech(true) && // We've observed a bug in IE and Edge when playing back DRM content where 13402 // calling .focus() on the video element causes the video to go black, 13403 // so we avoid it in that specific case 13404 !((IE_VERSION || IS_EDGE) && sourceIsEncrypted)) { 13405 this.player_.tech(true).focus(); 13406 } 13407 13408 return; 13409 } 13410 13411 var cb = this.player_.getChild('controlBar'); 13412 var playToggle = cb && cb.getChild('playToggle'); 13413 13414 if (!playToggle) { 13415 this.player_.tech(true).focus(); 13416 return; 13417 } 13418 13419 var playFocus = function playFocus() { 13420 return playToggle.focus(); 13421 }; 13422 13423 if (isPromise(playPromise)) { 13424 playPromise.then(playFocus, function () {}); 13425 } else { 13426 this.setTimeout(playFocus, 1); 13427 } 13428 }; 13429 13430 _proto.handleKeyDown = function handleKeyDown(event) { 13431 this.mouseused_ = false; 13432 13433 _Button.prototype.handleKeyDown.call(this, event); 13434 }; 13435 13436 _proto.handleMouseDown = function handleMouseDown(event) { 13437 this.mouseused_ = true; 13438 }; 13439 13440 return BigPlayButton; 13441 }(Button); 13442 /** 13443 * The text that should display over the `BigPlayButton`s controls. Added to for localization. 13444 * 13445 * @type {string} 13446 * @private 13447 */ 13448 13449 13450 BigPlayButton.prototype.controlText_ = 'Play Video'; 13451 Component.registerComponent('BigPlayButton', BigPlayButton); 13452 13453 /** 13454 * The `CloseButton` is a `{@link Button}` that fires a `close` event when 13455 * it gets clicked. 13456 * 13457 * @extends Button 13458 */ 13459 13460 var CloseButton = 13461 /*#__PURE__*/ 13462 function (_Button) { 13463 inheritsLoose(CloseButton, _Button); 13464 13465 /** 13466 * Creates an instance of the this class. 13467 * 13468 * @param {Player} player 13469 * The `Player` that this class should be attached to. 13470 * 13471 * @param {Object} [options] 13472 * The key/value store of player options. 13473 */ 13474 function CloseButton(player, options) { 13475 var _this; 13476 13477 _this = _Button.call(this, player, options) || this; 13478 13479 _this.controlText(options && options.controlText || _this.localize('Close')); 13480 13481 return _this; 13482 } 13483 /** 13484 * Builds the default DOM `className`. 13485 * 13486 * @return {string} 13487 * The DOM `className` for this object. 13488 */ 13489 13490 13491 var _proto = CloseButton.prototype; 13492 13493 _proto.buildCSSClass = function buildCSSClass() { 13494 return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this); 13495 } 13496 /** 13497 * This gets called when a `CloseButton` gets clicked. See 13498 * {@link ClickableComponent#handleClick} for more information on when 13499 * this will be triggered 13500 * 13501 * @param {EventTarget~Event} event 13502 * The `keydown`, `tap`, or `click` event that caused this function to be 13503 * called. 13504 * 13505 * @listens tap 13506 * @listens click 13507 * @fires CloseButton#close 13508 */ 13509 ; 13510 13511 _proto.handleClick = function handleClick(event) { 13512 /** 13513 * Triggered when the a `CloseButton` is clicked. 13514 * 13515 * @event CloseButton#close 13516 * @type {EventTarget~Event} 13517 * 13518 * @property {boolean} [bubbles=false] 13519 * set to false so that the close event does not 13520 * bubble up to parents if there is no listener 13521 */ 13522 this.trigger({ 13523 type: 'close', 13524 bubbles: false 13525 }); 13526 } 13527 /** 13528 * Event handler that is called when a `CloseButton` receives a 13529 * `keydown` event. 13530 * 13531 * By default, if the key is Esc, it will trigger a `click` event. 13532 * 13533 * @param {EventTarget~Event} event
13534 * The `keydown` event that caused this function to be called. 13535 * 13536 * @listens keydown 13537 */ 13538 ; 13539 13540 _proto.handleKeyDown = function handleKeyDown(event) { 13541 // Esc button will trigger `click` event 13542 if (keycode.isEventKey(event, 'Esc')) { 13543 event.preventDefault(); 13544 event.stopPropagation(); 13545 this.trigger('click'); 13546 } else { 13547 // Pass keypress handling up for unsupported keys 13548 _Button.prototype.handleKeyDown.call(this, event); 13549 } 13550 }; 13551 13552 return CloseButton; 13553 }(Button); 13554 13555 Component.registerComponent('CloseButton', CloseButton); 13556 13557 /** 13558 * Button to toggle between play and pause. 13559 * 13560 * @extends Button 13561 */ 13562 13563 var PlayToggle = 13564 /*#__PURE__*/ 13565 function (_Button) { 13566 inheritsLoose(PlayToggle, _Button); 13567 13568 /** 13569 * Creates an instance of this class. 13570 * 13571 * @param {Player} player 13572 * The `Player` that this class should be attached to. 13573 * 13574 * @param {Object} [options={}] 13575 * The key/value store of player options. 13576 */ 13577 function PlayToggle(player, options) { 13578 var _this; 13579 13580 if (options === void 0) { 13581 options = {}; 13582 } 13583 13584 _this = _Button.call(this, player, options) || this; // show or hide replay icon 13585 13586 options.replay = options.replay === undefined || options.replay; 13587 13588 _this.on(player, 'play', _this.handlePlay); 13589 13590 _this.on(player, 'pause', _this.handlePause); 13591 13592 if (options.replay) { 13593 _this.on(player, 'ended', _this.handleEnded); 13594 } 13595 13596 return _this; 13597 } 13598 /** 13599 * Builds the default DOM `className`. 13600 * 13601 * @return {string} 13602 * The DOM `className` for this object. 13603 */ 13604 13605 13606 var _proto = PlayToggle.prototype; 13607 13608 _proto.buildCSSClass = function buildCSSClass() { 13609 return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this); 13610 } 13611 /** 13612 * This gets called when an `PlayToggle` is "clicked". See 13613 * {@link ClickableComponent} for more detailed information on what a click can be. 13614 * 13615 * @param {EventTarget~Event} [event] 13616 * The `keydown`, `tap`, or `click` event that caused this function to be 13617 * called. 13618 * 13619 * @listens tap 13620 * @listens click 13621 */ 13622 ; 13623 13624 _proto.handleClick = function handleClick(event) { 13625 if (this.player_.paused()) { 13626 this.player_.play(); 13627 } else { 13628 this.player_.pause(); 13629 } 13630 } 13631 /** 13632 * This gets called once after the video has ended and the user seeks so that 13633 * we can change the replay button back to a play button. 13634 * 13635 * @param {EventTarget~Event} [event] 13636 * The event that caused this function to run. 13637 * 13638 * @listens Player#seeked 13639 */ 13640 ; 13641 13642 _proto.handleSeeked = function handleSeeked(event) { 13643 this.removeClass('vjs-ended'); 13644 13645 if (this.player_.paused()) { 13646 this.handlePause(event); 13647 } else { 13648 this.handlePlay(event); 13649 } 13650 } 13651 /** 13652 * Add the vjs-playing class to the element so it can change appearance. 13653 * 13654 * @param {EventTarget~Event} [event] 13655 * The event that caused this function to run. 13656 * 13657 * @listens Player#play 13658 */ 13659 ; 13660 13661 _proto.handlePlay = function handlePlay(event) { 13662 this.removeClass('vjs-ended'); 13663 this.removeClass('vjs-paused'); 13664 this.addClass('vjs-playing'); // change the button text to "Pause" 13665 13666 this.controlText('Pause'); 13667 } 13668 /** 13669 * Add the vjs-paused class to the element so it can change appearance. 13670 * 13671 * @param {EventTarget~Event} [event] 13672 * The event that caused this function to run. 13673 * 13674 * @listens Player#pause 13675 */ 13676 ; 13677 13678 _proto.handlePause = function handlePause(event) { 13679 this.removeClass('vjs-playing'); 13680 this.addClass('vjs-paused'); // change the button text to "Play" 13681 13682 this.controlText('Play'); 13683 } 13684 /** 13685 * Add the vjs-ended class to the element so it can change appearance 13686 * 13687 * @param {EventTarget~Event} [event] 13688 * The event that caused this function to run. 13689 * 13690 * @listens Player#ended 13691 */ 13692 ; 13693 13694 _proto.handleEnded = function handleEnded(event) { 13695 this.removeClass('vjs-playing'); 13696 this.addClass('vjs-ended'); // change the button text to "Replay" 13697 13698 this.controlText('Replay'); // on the next seek remove the replay button 13699 13700 this.one(this.player_, 'seeked', this.handleSeeked); 13701 }; 13702 13703 return PlayToggle; 13704 }(Button); 13705 /** 13706 * The text that should display over the `PlayToggle`s controls. Added for localization. 13707 * 13708 * @type {string} 13709 * @private 13710 */ 13711 13712 13713 PlayToggle.prototype.controlText_ = 'Play'; 13714 Component.registerComponent('PlayToggle', PlayToggle); 13715 13716 /** 13717 * @file format-time.js 13718 * @module format-time 13719 */ 13720 13721 /** 13722 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in 13723 * seconds) will force a number of leading zeros to cover the length of the 13724 * guide. 13725 * 13726 * @private 13727 * @param {number} seconds 13728 * Number of seconds to be turned into a string 13729 * 13730 * @param {number} guide 13731 * Number (in seconds) to model the string after 13732 * 13733 * @return {string} 13734 * Time formatted as H:MM:SS or M:SS 13735 */ 13736 var defaultImplementation = function defaultImplementation(seconds, guide) { 13737 seconds = seconds < 0 ? 0 : seconds; 13738 var s = Math.floor(seconds % 60); 13739 var m = Math.floor(seconds / 60 % 60); 13740 var h = Math.floor(seconds / 3600); 13741 var gm = Math.floor(guide / 60 % 60); 13742 var gh = Math.floor(guide / 3600); // handle invalid times 13743 13744 if (isNaN(seconds) || seconds === Infinity) { 13745 // '-' is false for all relational operators (e.g. <, >
13745=) so this setting 13746 // will add the minimum number of fields specified by the guide 13747 h = m = s = '-'; 13748 } // Check if we need to show hours 13749 13750 13751 h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero. 13752 // Always show at least one digit of minutes. 13753 13754 m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds 13755 13756 s = s < 10 ? '0' + s : s; 13757 return h + m + s; 13758 }; // Internal pointer to the current implementation. 13759 13760 13761 var implementation = defaultImplementation; 13762 /** 13763 * Replaces the default formatTime implementation with a custom implementation. 13764 * 13765 * @param {Function} customImplementation 13766 * A function which will be used in place of the default formatTime 13767 * implementation. Will receive the current time in seconds and the 13768 * guide (in seconds) as arguments. 13769 */ 13770 13771 function setFormatTime(customImplementation) { 13772 implementation = customImplementation; 13773 } 13774 /** 13775 * Resets formatTime to the default implementation. 13776 */ 13777 13778 function resetFormatTime() { 13779 implementation = defaultImplementation; 13780 } 13781 /** 13782 * Delegates to either the default time formatting function or a custom 13783 * function supplied via `setFormatTime`. 13784 * 13785 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a 13786 * guide (in seconds) will force a number of leading zeros to cover the 13787 * length of the guide. 13788 * 13789 * @static 13790 * @example formatTime(125, 600) === "02:05" 13791 * @param {number} seconds 13792 * Number of seconds to be turned into a string 13793 * 13794 * @param {number} guide 13795 * Number (in seconds) to model the string after 13796 * 13797 * @return {string} 13798 * Time formatted as H:MM:SS or M:SS 13799 */ 13800 13801 function formatTime(seconds, guide) { 13802 if (guide === void 0) { 13803 guide = seconds; 13804 } 13805 13806 return implementation(seconds, guide); 13807 } 13808 13809 /** 13810 * Displays time information about the video 13811 * 13812 * @extends Component 13813 */ 13814 13815 var TimeDisplay = 13816 /*#__PURE__*/ 13817 function (_Component) { 13818 inheritsLoose(TimeDisplay, _Component); 13819 13820 /** 13821 * Creates an instance of this class. 13822 * 13823 * @param {Player} player 13824 * The `Player` that this class should be attached to. 13825 * 13826 * @param {Object} [options] 13827 * The key/value store of player options. 13828 */ 13829 function TimeDisplay(player, options) { 13830 var _this; 13831 13832 _this = _Component.call(this, player, options) || this; 13833 13834 _this.on(player, ['timeupdate', 'ended'], _this.updateContent); 13835 13836 _this.updateTextNode_(); 13837 13838 return _this; 13839 } 13840 /** 13841 * Create the `Component`'s DOM element 13842 * 13843 * @return {Element} 13844 * The element that was created. 13845 */ 13846 13847 13848 var _proto = TimeDisplay.prototype; 13849 13850 _proto.createEl = function createEl$1() { 13851 var className = this.buildCSSClass(); 13852 13853 var el = _Component.prototype.createEl.call(this, 'div', { 13854 className: className + " vjs-time-control vjs-control", 13855 innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>" 13856 }); 13857 13858 this.contentEl_ = createEl('span', { 13859 className: className + "-display" 13860 }, { 13861 // tell screen readers not to automatically read the time as it changes 13862 'aria-live': 'off', 13863 // span elements have no implicit role, but some screen readers (notably VoiceOver) 13864 // treat them as a break between items in the DOM when using arrow keys 13865 // (or left-to-right swipes on iOS) to read contents of a page. Using 13866 // role='presentation' causes VoiceOver to NOT treat this span as a break. 13867 'role': 'presentation' 13868 }); 13869 el.appendChild(this.contentEl_); 13870 return el; 13871 }; 13872 13873 _proto.dispose = function dispose() { 13874 this.contentEl_ = null; 13875 this.textNode_ = null; 13876 13877 _Component.prototype.dispose.call(this); 13878 } 13879 /** 13880 * Updates the time display text node with a new time 13881 * 13882 * @param {number} [time=0] the time to update to 13883 * 13884 * @private 13885 */ 13886 ; 13887 13888 _proto.updateTextNode_ = function updateTextNode_(time) { 13889 var _this2 = this; 13890 13891 if (time === void 0) { 13892 time = 0; 13893 } 13894 13895 time = formatTime(time); 13896 13897 if (this.formattedTime_ === time) { 13898 return; 13899 } 13900 13901 this.formattedTime_ = time; 13902 this.requestAnimationFrame(function () { 13903 if (!_this2.contentEl_) { 13904 return; 13905 } 13906 13907 var oldNode = _this2.textNode_; 13908 _this2.textNode_ = document.createTextNode(_this2.formattedTime_); 13909 13910 if (!_this2.textNode_) { 13911 return; 13912 } 13913 13914 if (oldNode) { 13915 _this2.contentEl_.replaceChild(_this2.textNode_, oldNode); 13916 } else { 13917 _this2.contentEl_.appendChild(_this2.textNode_); 13918 } 13919 }); 13920 } 13921 /** 13922 * To be filled out in the child class, should update the displayed time 13923 * in accordance with the fact that the current time has changed. 13924 * 13925 * @param {EventTarget~Event} [event] 13926 * The `timeupdate` event that caused this to run. 13927 * 13928 * @listens Player#timeupdate 13929 */ 13930 ; 13931 13932 _proto.updateContent = function updateContent(event) {}; 13933 13934 return TimeDisplay; 13935 }(Component); 13936 /** 13937 * The text that is added to the `TimeDisplay` for screen reader users. 13938 * 13939 * @type {string} 13940 * @private 13941 */ 13942 13943 13944 TimeDisplay.prototype.labelText_ = 'Time'; 13945 /** 13946 * The text that should display over the `TimeDisplay`s controls. Added to for localization. 13947 * 13948 * @type {string} 13949 * @private 13950 *
13951 * @deprecated in v7; controlText_ is not used in non-active display Components 13952 */ 13953 13954 TimeDisplay.prototype.controlText_ = 'Time'; 13955 Component.registerComponent('TimeDisplay', TimeDisplay); 13956 13957 /** 13958 * Displays the current time 13959 * 13960 * @extends Component 13961 */ 13962 13963 var CurrentTimeDisplay = 13964 /*#__PURE__*/ 13965 function (_TimeDisplay) { 13966 inheritsLoose(CurrentTimeDisplay, _TimeDisplay); 13967 13968 function CurrentTimeDisplay() { 13969 return _TimeDisplay.apply(this, arguments) || this; 13970 } 13971 13972 var _proto = CurrentTimeDisplay.prototype; 13973 13974 /** 13975 * Builds the default DOM `className`. 13976 * 13977 * @return {string} 13978 * The DOM `className` for this object. 13979 */ 13980 _proto.buildCSSClass = function buildCSSClass() { 13981 return 'vjs-current-time'; 13982 } 13983 /** 13984 * Update current time display 13985 * 13986 * @param {EventTarget~Event} [event] 13987 * The `timeupdate` event that caused this function to run. 13988 * 13989 * @listens Player#timeupdate 13990 */ 13991 ; 13992 13993 _proto.updateContent = function updateContent(event) { 13994 // Allows for smooth scrubbing, when player can't keep up. 13995 var time; 13996 13997 if (this.player_.ended()) { 13998 time = this.player_.duration(); 13999 } else { 14000 time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 14001 } 14002 14003 this.updateTextNode_(time); 14004 }; 14005 14006 return CurrentTimeDisplay; 14007 }(TimeDisplay); 14008 /** 14009 * The text that is added to the `CurrentTimeDisplay` for screen reader users. 14010 * 14011 * @type {string} 14012 * @private 14013 */ 14014 14015 14016 CurrentTimeDisplay.prototype.labelText_ = 'Current Time'; 14017 /** 14018 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization. 14019 * 14020 * @type {string} 14021 * @private 14022 * 14023 * @deprecated in v7; controlText_ is not used in non-active display Components 14024 */ 14025 14026 CurrentTimeDisplay.prototype.controlText_ = 'Current Time'; 14027 Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); 14028 14029 /** 14030 * Displays the duration 14031 * 14032 * @extends Component 14033 */ 14034 14035 var DurationDisplay = 14036 /*#__PURE__*/ 14037 function (_TimeDisplay) { 14038 inheritsLoose(DurationDisplay, _TimeDisplay); 14039 14040 /** 14041 * Creates an instance of this class. 14042 * 14043 * @param {Player} player 14044 * The `Player` that this class should be attached to. 14045 * 14046 * @param {Object} [options] 14047 * The key/value store of player options. 14048 */ 14049 function DurationDisplay(player, options) { 14050 var _this; 14051 14052 _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes, 14053 // as they should always display the changed duration as 14054 // it has changed 14055 14056 _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded, 14057 // but the durationchange on the user agent will not fire. 14058 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 14059 14060 14061 _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those 14062 // listeners could have broken dependent applications/libraries. These 14063 // can likely be removed for 7.0. 14064 14065 14066 _this.on(player, 'loadedmetadata', _this.updateContent); 14067 14068 return _this; 14069 } 14070 /** 14071 * Builds the default DOM `className`. 14072 * 14073 * @return {string} 14074 * The DOM `className` for this object. 14075 */ 14076 14077 14078 var _proto = DurationDisplay.prototype; 14079 14080 _proto.buildCSSClass = function buildCSSClass() { 14081 return 'vjs-duration'; 14082 } 14083 /** 14084 * Update duration time display. 14085 * 14086 * @param {EventTarget~Event} [event] 14087 * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused 14088 * this function to be called. 14089 * 14090 * @listens Player#durationchange 14091 * @listens Player#timeupdate 14092 * @listens Player#loadedmetadata 14093 */ 14094 ; 14095 14096 _proto.updateContent = function updateContent(event) { 14097 var duration = this.player_.duration(); 14098 this.updateTextNode_(duration); 14099 }; 14100 14101 return DurationDisplay; 14102 }(TimeDisplay); 14103 /** 14104 * The text that is added to the `DurationDisplay` for screen reader users. 14105 * 14106 * @type {string} 14107 * @private 14108 */ 14109 14110 14111 DurationDisplay.prototype.labelText_ = 'Duration'; 14112 /** 14113 * The text that should display over the `DurationDisplay`s controls. Added to for localization. 14114 * 14115 * @type {string} 14116 * @private 14117 *
14118 * @deprecated in v7; controlText_ is not used in non-active display Components 14119 */ 14120 14121 DurationDisplay.prototype.controlText_ = 'Duration'; 14122 Component.registerComponent('DurationDisplay', DurationDisplay); 14123 14124 /** 14125 * The separator between the current time and duration. 14126 * Can be hidden if it's not needed in the design. 14127 * 14128 * @extends Component 14129 */ 14130 14131 var TimeDivider = 14132 /*#__PURE__*/ 14133 function (_Component) { 14134 inheritsLoose(TimeDivider, _Component); 14135 14136 function TimeDivider() { 14137 return _Component.apply(this, arguments) || this; 14138 } 14139 14140 var _proto = TimeDivider.prototype; 14141 14142 /** 14143 * Create the component's DOM element 14144 * 14145 * @return {Element} 14146 * The element that was created. 14147 */ 14148 _proto.createEl = function createEl() { 14149 return _Component.prototype.createEl.call(this, 'div', { 14150 className: 'vjs-time-control vjs-time-divider', 14151 innerHTML: '<div><span>/</span></div>' 14152 }, { 14153 // this element and its contents can be hidden from assistive techs since 14154 // it is made extraneous by the announcement of the control text 14155 // for the current time and duration displays 14156 'aria-hidden': true 14157 }); 14158 }; 14159 14160 return TimeDivider; 14161 }(Component); 14162 14163 Component.registerComponent('TimeDivider', TimeDivider); 14164 14165 /** 14166 * Displays the time left in the video 14167 * 14168 * @extends Component 14169 */ 14170 14171 var RemainingTimeDisplay = 14172 /*#__PURE__*/ 14173 function (_TimeDisplay) { 14174 inheritsLoose(RemainingTimeDisplay, _TimeDisplay); 14175 14176 /** 14177 * Creates an instance of this class. 14178 * 14179 * @param {Player} player 14180 * The `Player` that this class should be attached to. 14181 * 14182 * @param {Object} [options] 14183 * The key/value store of player options. 14184 */ 14185 function RemainingTimeDisplay(player, options) { 14186 var _this; 14187 14188 _this = _TimeDisplay.call(this, player, options) || this; 14189 14190 _this.on(player, 'durationchange', _this.updateContent); 14191 14192 return _this; 14193 } 14194 /** 14195 * Builds the default DOM `className`. 14196 * 14197 * @return {string} 14198 * The DOM `className` for this object. 14199 */ 14200 14201 14202 var _proto = RemainingTimeDisplay.prototype; 14203 14204 _proto.buildCSSClass = function buildCSSClass() { 14205 return 'vjs-remaining-time'; 14206 } 14207 /** 14208 * Create the `Component`'s DOM element with the "minus" characted prepend to the time 14209 * 14210 * @return {Element} 14211 * The element that was created. 14212 */ 14213 ; 14214 14215 _proto.createEl = function createEl$1() { 14216 var el = _TimeDisplay.prototype.createEl.call(this); 14217 14218 el.insertBefore(createEl('span', {}, { 14219 'aria-hidden': true 14220 }, '-'), this.contentEl_); 14221 return el; 14222 } 14223 /** 14224 * Update remaining time display. 14225 * 14226 * @param {EventTarget~Event} [event] 14227 * The `timeupdate` or `durationchange` event that caused this to run. 14228 * 14229 * @listens Player#timeupdate 14230 * @listens Player#durationchange 14231 */ 14232 ; 14233 14234 _proto.updateContent = function updateContent(event) { 14235 if (typeof this.player_.duration() !== 'number') { 14236 return; 14237 } 14238 14239 var time; // @deprecated We should only use remainingTimeDisplay 14240 // as of video.js 7 14241 14242 if (this.player_.ended()) { 14243 time = 0; 14244 } else if (this.player_.remainingTimeDisplay) { 14245 time = this.player_.remainingTimeDisplay(); 14246 } else { 14247 time = this.player_.remainingTime(); 14248 } 14249 14250 this.updateTextNode_(time); 14251 }; 14252 14253 return RemainingTimeDisplay; 14254 }(TimeDisplay); 14255 /** 14256 * The text that is added to the `RemainingTimeDisplay` for screen reader users. 14257 * 14258 * @type {string} 14259 * @private 14260 */ 14261 14262 14263 RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time'; 14264 /** 14265 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization. 14266 * 14267 * @type {string} 14268 * @private 14269 * 14270 * @deprecated in v7; controlText_ is not used in non-active display Components 14271 */ 14272 14273 RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time'; 14274 Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); 14275 14276 /** 14277 * Displays the live indicator when duration is Infinity. 14278 * 14279 * @extends Component 14280 */ 14281 14282 var LiveDisplay = 14283 /*#__PURE__*/ 14284 function (_Component) { 14285 inheritsLoose(LiveDisplay, _Component); 14286 14287 /** 14288 * Creates an instance of this class. 14289 * 14290 * @param {Player} player 14291 * The `Player` that this class should be attached to. 14292 * 14293 * @param {Object} [options] 14294 * The key/value store of player options. 14295 */ 14296 function LiveDisplay(player, options) { 14297 var _this; 14298 14299 _this = _Component.call(this, player, options) || this; 14300 14301 _this.updateShowing(); 14302 14303 _this.on(_this.player(), 'durationchange', _this.updateShowing); 14304 14305 return _this; 14306 } 14307 /** 14308 * Create the `Component`'s DOM element 14309 * 14310 * @return {Element} 14311 * The element that was created. 14312 */ 14313 14314 14315 var _proto = LiveDisplay.prototype; 14316 14317 _proto.createEl = function createEl$1() { 14318 var el = _Component.prototype.createEl.call(this, 'div', { 14319 className: 'vjs-live-control vjs-control' 14320 }); 14321 14322 this.contentEl_ = createEl('div', { 14323 className: 'vjs-live-display', 14324 innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE') 14325 }, { 14326 'aria-live': 'off' 14327 }); 14328 el.appendChild(this.contentEl_); 14329 return el; 14330 }; 14331 14332 _proto.dispose = function dispose() { 14333 this.contentEl_ = null; 14334 14335 _Component.prototype.dispose.call(this); 14336 } 14337 /** 14338 * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide 14339 * it accordingly 14340 * 14341 * @param {EventTarget~Event} [event] 14342 * The {@link Player#durationchange} event that caused this function to run. 14343 * 14344 * @listens Player#durationchange 14345 */ 14346 ; 14347 14348 _proto.updateShowing = function updateShowing(event) { 14349 if (this.player().duration() === Infinity) { 14350 this.show(); 14351 } else { 14352 this.hide(); 14353 } 14354 }; 14355 14356 return LiveDisplay; 14357 }(Component); 14358 14359 Component.registerComponent('LiveDisplay', LiveDisplay); 14360 14361 /** 14362 * Displays the live indicator when duration is Infinity. 14363 * 14364 * @extends Component 14365 */ 14366 14367 var SeekToLive = 14368 /*#__PURE__*/ 14369 function (_Button) { 14370 inheritsLoose(SeekToLive, _Button); 14371 14372 /** 14373 * Creates an instance of this class. 14374 * 14375 * @param {Player} player 14376 * The `Player` that this class should be attached to. 14377 * 14378 * @param {Object} [options] 14379 * The key/value store of player options. 14380 */ 14381 function SeekToLive(player, options) { 14382 var _this; 14383 14384 _this = _Button.call(this, player, options) || this; 14385 14386 _this.updateLiveEdgeStatus();
14387 14388 if (_this.player_.liveTracker) { 14389 _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus); 14390 } 14391 14392 return _this; 14393 } 14394 /** 14395 * Create the `Component`'s DOM element 14396 * 14397 * @return {Element} 14398 * The element that was created. 14399 */ 14400 14401 14402 var _proto = SeekToLive.prototype; 14403 14404 _proto.createEl = function createEl$1() { 14405 var el = _Button.prototype.createEl.call(this, 'button', { 14406 className: 'vjs-seek-to-live-control vjs-control' 14407 }); 14408 14409 this.textEl_ = createEl('span', { 14410 className: 'vjs-seek-to-live-text', 14411 innerHTML: this.localize('LIVE') 14412 }, { 14413 'aria-hidden': 'true' 14414 }); 14415 el.appendChild(this.textEl_); 14416 return el; 14417 } 14418 /** 14419 * Update the state of this button if we are at the live edge 14420 * or not 14421 */ 14422 ; 14423 14424 _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) { 14425 // default to live edge 14426 if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) { 14427 this.setAttribute('aria-disabled', true); 14428 this.addClass('vjs-at-live-edge'); 14429 this.controlText('Seek to live, currently playing live'); 14430 } else { 14431 this.setAttribute('aria-disabled', false); 14432 this.removeClass('vjs-at-live-edge'); 14433 this.controlText('Seek to live, currently behind live'); 14434 } 14435 } 14436 /** 14437 * On click bring us as near to the live point as possible. 14438 * This requires that we wait for the next `live-seekable-change` 14439 * event which will happen every segment length seconds. 14440 */ 14441 ; 14442 14443 _proto.handleClick = function handleClick() { 14444 this.player_.liveTracker.seekToLiveEdge(); 14445 } 14446 /** 14447 * Dispose of the element and stop tracking 14448 */ 14449 ; 14450 14451 _proto.dispose = function dispose() { 14452 if (this.player_.liveTracker) { 14453 this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus); 14454 } 14455 14456 this.textEl_ = null; 14457 14458 _Button.prototype.dispose.call(this); 14459 }; 14460 14461 return SeekToLive; 14462 }(Button); 14463 14464 SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live'; 14465 Component.registerComponent('SeekToLive', SeekToLive); 14466 14467 /** 14468 * Keep a number between a min and a max value 14469 * 14470 * @param {number} number 14471 * The number to clamp 14472 * 14473 * @param {number} min 14474 * The minimum value 14475 * @param {number} max 14476 * The maximum value 14477 * 14478 * @return {number} 14479 * the clamped number 14480 */ 14481 var clamp = function clamp(number, min, max) { 14482 number = Number(number); 14483 return Math.min(max, Math.max(min, isNaN(number) ? min : number)); 14484 }; 14485 14486 /** 14487 * The base functionality for a slider. Can be vertical or horizontal. 14488 * For instance the volume bar or the seek bar on a video is a slider. 14489 * 14490 * @extends Component 14491 */ 14492 14493 var Slider = 14494 /*#__PURE__*/ 14495 function (_Component) { 14496 inheritsLoose(Slider, _Component); 14497 14498 /** 14499 * Create an instance of this class 14500 * 14501 * @param {Player} player 14502 * The `Player` that this class should be attached to. 14503 * 14504 * @param {Object} [options] 14505 * The key/value store of player options. 14506 */ 14507 function Slider(player, options) { 14508 var _this; 14509 14510 _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for 14511 14512 _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type 14513 14514 _this.vertical(!!_this.options_.vertical); 14515 14516 _this.enable(); 14517 14518 return _this; 14519 } 14520 /** 14521 * Are controls are currently enabled for this slider or not. 14522 * 14523 * @return {boolean} 14524 * true if controls are enabled, false otherwise 14525 */ 14526 14527 14528 var _proto = Slider.prototype; 14529 14530 _proto.enabled = function enabled() { 14531 return this.enabled_; 14532 } 14533 /** 14534 * Enable controls for this slider if they are disabled 14535 */ 14536 ; 14537 14538 _proto.enable = function enable() { 14539 if (this.enabled()) { 14540 return; 14541 } 14542 14543 this.on('mousedown', this.handleMouseDown);
vendor: 5,709 bytes, lines 14544-14724
14544 this.on('touchstart', this.handleMouseDown); 14545 this.on('keydown', this.handleKeyDown); 14546 this.on('click', this.handleClick); // TODO: deprecated, controlsvisible does not seem to be fired 14547 14548 this.on(this.player_, 'controlsvisible', this.update); 14549 14550 if (this.playerEvent) { 14551 this.on(this.player_, this.playerEvent, this.update); 14552 } 14553 14554 this.removeClass('disabled'); 14555 this.setAttribute('tabindex', 0); 14556 this.enabled_ = true; 14557 } 14558 /** 14559 * Disable controls for this slider if they are enabled 14560 */ 14561 ; 14562 14563 _proto.disable = function disable() { 14564 if (!this.enabled()) { 14565 return; 14566 } 14567 14568 var doc = this.bar.el_.ownerDocument; 14569 this.off('mousedown', this.handleMouseDown); 14570 this.off('touchstart', this.handleMouseDown); 14571 this.off('keydown', this.handleKeyDown); 14572 this.off('click', this.handleClick); 14573 this.off(this.player_, 'controlsvisible', this.update); 14574 this.off(doc, 'mousemove', this.handleMouseMove); 14575 this.off(doc, 'mouseup', this.handleMouseUp); 14576 this.off(doc, 'touchmove', this.handleMouseMove); 14577 this.off(doc, 'touchend', this.handleMouseUp); 14578 this.removeAttribute('tabindex'); 14579 this.addClass('disabled'); 14580 14581 if (this.playerEvent) { 14582 this.off(this.player_, this.playerEvent, this.update); 14583 } 14584 14585 this.enabled_ = false; 14586 } 14587 /** 14588 * Create the `Slider`s DOM element. 14589 * 14590 * @param {string} type 14591 * Type of element to create. 14592 * 14593 * @param {Object} [props={}] 14594 * List of properties in Object form. 14595 * 14596 * @param {Object} [attributes={}] 14597 * list of attributes in Object form. 14598 * 14599 * @return {Element} 14600 * The element that gets created. 14601 */ 14602 ; 14603 14604 _proto.createEl = function createEl(type, props, attributes) { 14605 if (props === void 0) { 14606 props = {}; 14607 } 14608 14609 if (attributes === void 0) { 14610 attributes = {}; 14611 } 14612 14613 // Add the slider element class to all sub classes 14614 props.className = props.className + ' vjs-slider'; 14615 props = assign({ 14616 tabIndex: 0 14617 }, props); 14618 attributes = assign({ 14619 'role': 'slider', 14620 'aria-valuenow': 0, 14621 'aria-valuemin': 0, 14622 'aria-valuemax': 100, 14623 'tabIndex': 0 14624 }, attributes); 14625 return _Component.prototype.createEl.call(this, type, props, attributes); 14626 } 14627 /** 14628 * Handle `mousedown` or `touchstart` events on the `Slider`. 14629 * 14630 * @param {EventTarget~Event} event 14631 * `mousedown` or `touchstart` event that triggered this function 14632 * 14633 * @listens mousedown 14634 * @listens touchstart 14635 * @fires Slider#slideractive 14636 */ 14637 ; 14638 14639 _proto.handleMouseDown = function handleMouseDown(event) { 14640 var doc = this.bar.el_.ownerDocument; 14641 14642 if (event.type === 'mousedown') { 14643 event.preventDefault(); 14644 } // Do not call preventDefault() on touchstart in Chrome 14645 // to avoid console warnings. Use a 'touch-action: none' style 14646 // instead to prevent unintented scrolling. 14647 // https://developers.google.com/web/updates/2017/01/scrolling-intervention 14648 14649 14650 if (event.type === 'touchstart' && !IS_CHROME) { 14651 event.preventDefault(); 14652 } 14653 14654 blockTextSelection(); 14655 this.addClass('vjs-sliding'); 14656 /** 14657 * Triggered when the slider is in an active state 14658 * 14659 * @event Slider#slideractive 14660 * @type {EventTarget~Event} 14661 */ 14662 14663 this.trigger('slideractive'); 14664 this.on(doc, 'mousemove', this.handleMouseMove); 14665 this.on(doc, 'mouseup', this.handleMouseUp); 14666 this.on(doc, 'touchmove', this.handleMouseMove); 14667 this.on(doc, 'touchend', this.handleMouseUp); 14668 this.handleMouseMove(event); 14669 } 14670 /** 14671 * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. 14672 * The `mousemove` and `touchmove` events will only only trigger this function during 14673 * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and 14674 * {@link Slider#handleMouseUp}. 14675 * 14676 * @param {EventTarget~Event} event 14677 * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered 14678 * this function 14679 * 14680 * @listens mousemove 14681 * @listens touchmove 14682 */ 14683 ; 14684 14685 _proto.handleMouseMove = function handleMouseMove(event) {} 14686 /** 14687 * Handle `mouseup` or `touchend` events on the `Slider`. 14688 * 14689 * @param {EventTarget~Event} event 14690 * `mouseup` or `touchend` event that triggered this function. 14691 * 14692 * @listens touchend 14693 * @listens mouseup 14694 * @fires Slider#sliderinactive 14695 */ 14696 ; 14697 14698 _proto.handleMouseUp = function handleMouseUp() { 14699 var doc = this.bar.el_.ownerDocument; 14700 unblockTextSelection(); 14701 this.removeClass('vjs-sliding'); 14702 /** 14703 * Triggered when the slider is no longer in an active state. 14704 * 14705 * @event Slider#sliderinactive 14706 * @type {EventTarget~Event} 14707 */ 14708 14709 this.trigger('sliderinactive'); 14710 this.off(doc, 'mousemove', this.handleMouseMove); 14711 this.off(doc, 'mouseup', this.handleMouseUp); 14712 this.off(doc, 'touchmove', this.handleMouseMove); 14713 this.off(doc, 'touchend', this.handleMouseUp); 14714 this.update(); 14715 } 14716 /** 14717 * Update the progress bar of the `Slider`. 14718 * 14719 * @return {number} 14720 * The percentage of progress the progress bar represents as a 14721 * number from 0 to 1. 14722 */ 14723 ; 14724
14725 _proto.update = function update() { 14726 var _this2 = this; 14727 14728 // In VolumeBar init we have a setTimeout for update that pops and update 14729 // to the end of the execution stack. The player is destroyed before then 14730 // update will cause an error 14731 // If there's no bar... 14732 if (!this.el_ || !this.bar) { 14733 return; 14734 } // clamp progress between 0 and 1 14735 // and only round to four decimal places, as we round to two below 14736 14737 14738 var progress = this.getProgress(); 14739 14740 if (progress === this.progress_) { 14741 return progress; 14742 } 14743 14744 this.progress_ = progress; 14745 this.requestAnimationFrame(function () { 14746 // Set the new bar width or height 14747 var sizeKey = _this2.vertical() ? 'height' : 'width'; // Convert to a percentage for css value 14748 14749 _this2.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%'; 14750 }); 14751 return progress; 14752 } 14753 /** 14754 * Get the percentage of the bar that should be filled 14755 * but clamped and rounded. 14756 * 14757 * @return {number} 14758 * percentage filled that the slider is 14759 */ 14760 ; 14761 14762 _proto.getProgress = function getProgress() { 14763 return Number(clamp(this.getPercent(), 0, 1).toFixed(4)); 14764 } 14765 /** 14766 * Calculate distance for slider 14767 * 14768 * @param {EventTarget~Event} event 14769 * The event that caused this function to run. 14770 * 14771 * @return {number} 14772 * The current position of the Slider. 14773 * - position.x for vertical `Slider`s 14774 * - position.y for horizontal `Slider`s 14775 */ 14776 ; 14777 14778 _proto.calculateDistance = function calculateDistance(event) { 14779 var position = getPointerPosition(this.el_, event); 14780 14781 if (this.vertical()) { 14782 return position.y; 14783 } 14784 14785 return position.x; 14786 } 14787 /** 14788 * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down 14789 * arrow keys. This function will only be called when the slider has focus. See 14790 * {@link Slider#handleFocus} and {@link Slider#handleBlur}. 14791 * 14792 * @param {EventTarget~Event} event 14793 * the `keydown` event that caused this function to run. 14794 * 14795 * @listens keydown 14796 */ 14797 ; 14798 14799 _proto.handleKeyDown = function handleKeyDown(event) { 14800 // Left and Down Arrows 14801 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 14802 event.preventDefault(); 14803 event.stopPropagation(); 14804 this.stepBack(); // Up and Right Arrows 14805 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 14806 event.preventDefault(); 14807 event.stopPropagation(); 14808 this.stepForward(); 14809 } else { 14810 // Pass keydown handling up for unsupported keys 14811 _Component.prototype.handleKeyDown.call(this, event); 14812 } 14813 } 14814 /** 14815 * Listener for click events on slider, used to prevent clicks 14816 * from bubbling up to parent elements like button menus. 14817 * 14818 * @param {Object} event 14819 * Event that caused this object to run 14820 */ 14821 ; 14822 14823 _proto.handleClick = function handleClick(event) { 14824 event.stopPropagation(); 14825 event.preventDefault(); 14826 } 14827 /** 14828 * Get/set if slider is horizontal for vertical 14829 * 14830 * @param {boolean} [bool] 14831 * - true if slider is vertical, 14832 * - false is horizontal 14833 * 14834 * @return {boolean} 14835 * - true if slider is vertical, and getting 14836 * - false if the slider is horizontal, and getting 14837 */ 14838 ; 14839 14840 _proto.vertical = function vertical(bool) { 14841 if (bool === undefined) { 14842 return this.vertical_ || false; 14843 } 14844 14845 this.vertical_ = !!bool; 14846 14847 if (this.vertical_) { 14848 this.addClass('vjs-slider-vertical'); 14849 } else { 14850 this.addClass('vjs-slider-horizontal'); 14851 } 14852 }; 14853 14854 return Slider; 14855 }(Component); 14856 14857 Component.registerComponent('Slider', Slider); 14858 14859 var percentify = function percentify(time, end) { 14860 return clamp(time / end * 100, 0, 100).toFixed(2) + '%'; 14861 }; 14862 /** 14863 * Shows loading progress 14864 * 14865 * @extends Component 14866 */ 14867 14868 14869 var LoadProgressBar = 14870 /*#__PURE__*/ 14871 function (_Component) { 14872 inheritsLoose(LoadProgressBar, _Component); 14873 14874 /** 14875 * Creates an instance of this class. 14876 * 14877 * @param {Player} player 14878 * The `Player` that this class should be attached to. 14879 * 14880 * @param {Object} [options] 14881 * The key/value store of player options. 14882 */ 14883 function LoadProgressBar(player, options) { 14884 var _this; 14885 14886 _this = _Component.call(this, player, options) || this; 14887 _this.partEls_ = []; 14888 14889 _this.on(player, 'progress', _this.update); 14890 14891 return _this; 14892 } 14893 /** 14894 * Create the `Component`'s DOM element 14895 * 14896 * @return {Element} 14897 * The element that was created. 14898 */ 14899 14900 14901 var _proto = LoadProgressBar.prototype; 14902 14903 _proto.createEl = function createEl$1() { 14904 var el = _Component.prototype.createEl.call(this, 'div', { 14905 className: 'vjs-load-progress' 14906 }); 14907 14908 var wrapper = createEl('span', { 14909 className: 'vjs-control-text' 14910 }); 14911 var loadedText = createEl('span', { 14912 textContent: this.localize('Loaded') 14913 }); 14914 var separator = document.createTextNode(': '); 14915 this.percentageEl_ = createEl('span', { 14916 className: 'vjs-control-text-loaded-percentage', 14917 textContent: '0%' 14918 }); 14919 el.appendChild(wrapper); 14920 wrapper.appendChild(loadedText); 14921 wrapper.appendChild(separator); 14922 wrapper.appendChild(this.percentageEl_); 14923 return el; 14924 }; 14925 14926 _proto.dispose = function dispose() { 14927 this.partEls_ = null; 14928 this.percentageEl_ = null; 14929 14930 _Component.prototype.dispose.call(this); 14931 } 14932 /** 14933 * Update progress bar 14934 * 14935 * @param {EventTarget~Event} [event] 14936 * The `progress` event that caused this function to run. 14937 * 14938 * @listens Player#progress 14939 */ 14940 ; 14941 14942 _proto.update = function update(event) { 14943 var _this2 = this; 14944 14945 this.requestAnimationFrame(function () { 14946 var liveTracker = _this2.player_.liveTracker; 14947 14948 var buffered = _this2.player_.buffered(); 14949 14950 var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : _this2.player_.duration(); 14951 14952 var bufferedEnd = _this2.player_.bufferedEnd(); 14953 14954 var children = _this2.partEls_; 14955 var percent = percentify(bufferedEnd, duration); 14956 14957 if (_this2.percent_ !== percent) { 14958 // update the width of the progress bar 14959 _this2.el_.style.width = percent; // update the control-text 14960 14961 textContent(_this2.percentageEl_, percent); 14962 _this2.percent_ = percent; 14963 } // add child elements to represent the individual buffered time ranges 14964 14965 14966 for (var i = 0; i < buffered.length; i++) { 14967 var start = buffered.start(i); 14968 var end = buffered.end(i); 14969 var part = children[i]; 14970 14971 if (!part) { 14972 part = _this2.el_.appendChild(createEl()); 14973 children[i] = part; 14974 } // only update if changed 14975 14976 14977 if (part.dataset.start === start && part.dataset.end === end) { 14978 continue; 14979 } 14980 14981 part.dataset.start = start; 14982 part.dataset.end = end; // set the percent based on the width of the progress bar (bufferedEnd) 14983 14984 part.style.left = percentify(start, bufferedEnd); 14985 part.style.width = percentify(end - start, bufferedEnd); 14986 } // remove unused buffered range elements 14987 14988 14989 for (var _i = children.length; _i > buffered.length; _i--) { 14990 _this2.el_.removeChild(children[_i - 1]); 14991 } 14992 14993 children.length = buffered.length; 14994 }); 14995 }; 14996 14997 return LoadProgressBar; 14998 }(Component); 14999 15000 Component.registerComponent('LoadProgressBar', LoadProgressBar); 15001 15002 /** 15003 * Time tooltips display a time above the progress bar. 15004 * 15005 * @extends Component 15006 */ 15007 15008 var TimeTooltip = 15009 /*#__PURE__*/ 15010 function (_Component) { 15011 inheritsLoose(TimeTooltip, _Component); 15012 15013 /** 15014 * Creates an instance of this class. 15015 * 15016 * @param {Player} player 15017 * The {@link Player} that this class should be attached to. 15018 * 15019 * @param {Object} [options] 15020 * The key/value store of player options. 15021 */ 15022 function TimeTooltip(player, options) { 15023 var _this; 15024 15025 _this = _Component.call(this, player, options) || this; 15026 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15027 return _this; 15028 } 15029 /** 15030 * Create the time tooltip DOM element 15031 * 15032 * @return {Element} 15033 * The element that was created. 15034 */ 15035 15036 15037 var _proto = TimeTooltip.prototype; 15038 15039 _proto.createEl = function createEl() { 15040 return _Component.prototype.createEl.call(this, 'div', { 15041 className: 'vjs-time-tooltip' 15042 }, { 15043 'aria-hidden': 'true' 15044 }); 15045 } 15046 /** 15047 * Updates the position of the time tooltip relative to the `SeekBar`. 15048 * 15049 * @param {Object} seekBarRect 15050 * The `ClientRect` for the {@link SeekBar} element. 15051 * 15052 * @param {number} seekBarPoint 15053 * A number from 0 to 1, representing a horizontal reference point 15054 * from the left edge of the {@link SeekBar} 15055 */ 15056 ; 15057 15058 _proto.update = function update(seekBarRect, seekBarPoint, content) { 15059 var tooltipRect = getBoundingClientRect(this.el_); 15060 var playerRect = getBoundingClientRect(this.player_.el()); 15061 var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available 15062 // for example, if the player isn't in the DOM for testing 15063 15064 if (!playerRect || !tooltipRect) { 15065 return; 15066 } // This is the space left of the `seekBarPoint` available within the bounds 15067 // of the player. We calculate any gap between the left edge of the player 15068 // and the left edge of the `SeekBar` and add the number of pixels in the 15069 // `SeekBar` before hitting the `seekBarPoint` 15070 15071 15072 var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds 15073 // of the player. We calculate the number of pixels from the `seekBarPoint` 15074 // to the right edge of the `SeekBar` and add to that any gap between the 15075 // right edge of the `SeekBar` and the player. 15076 15077 var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled 15078 // further to the right to center it over the `seekBarPoint`. 15079 15080 var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on 15081 // the results of the space calculations above. 15082 15083 if (spaceLeftOfPoint < pullTooltipBy) { 15084 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 15085 }
15085 else if (spaceRightOfPoint < pullTooltipBy) { 15086 pullTooltipBy = spaceRightOfPoint; 15087 } // Due to the imprecision of decimal/ratio based calculations and varying 15088 // rounding behaviors, there are cases where the spacing adjustment is off 15089 // by a pixel or two. This adds insurance to these calculations. 15090 15091 15092 if (pullTooltipBy < 0) { 15093 pullTooltipBy = 0; 15094 } else if (pullTooltipBy > tooltipRect.width) { 15095 pullTooltipBy = tooltipRect.width; 15096 } 15097 15098 this.el_.style.right = "-" + pullTooltipBy + "px"; 15099 this.write(content); 15100 } 15101 /** 15102 * Write the time to the tooltip DOM element. 15103 * 15104 * @param {string} content 15105 * The formatted time for the tooltip. 15106 */ 15107 ; 15108 15109 _proto.write = function write(content) { 15110 textContent(this.el_, content); 15111 } 15112 /** 15113 * Updates the position of the time tooltip relative to the `SeekBar`. 15114 * 15115 * @param {Object} seekBarRect 15116 * The `ClientRect` for the {@link SeekBar} element. 15117 * 15118 * @param {number} seekBarPoint 15119 * A number from 0 to 1, representing a horizontal reference point 15120 * from the left edge of the {@link SeekBar} 15121 * 15122 * @param {number} time 15123 * The time to update the tooltip to, not used during live playback 15124 * 15125 * @param {Function} cb 15126 * A function that will be called during the request animation frame 15127 * for tooltips that need to do additional animations from the default 15128 */ 15129 ; 15130 15131 _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) { 15132 var _this2 = this; 15133 15134 // If there is an existing rAF ID, cancel it so we don't over-queue. 15135 if (this.rafId_) { 15136 this.cancelAnimationFrame(this.rafId_); 15137 } 15138 15139 this.rafId_ = this.requestAnimationFrame(function () { 15140 var content; 15141 15142 var duration = _this2.player_.duration(); 15143 15144 if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) { 15145 var liveWindow = _this2.player_.liveTracker.liveWindow(); 15146 15147 var secondsBehind = liveWindow - seekBarPoint * liveWindow; 15148 content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow); 15149 } else { 15150 content = formatTime(time, duration); 15151 } 15152 15153 _this2.update(seekBarRect, seekBarPoint, content); 15154 15155 if (cb) { 15156 cb(); 15157 } 15158 }); 15159 }; 15160 15161 return TimeTooltip; 15162 }(Component); 15163 15164 Component.registerComponent('TimeTooltip', TimeTooltip); 15165 15166 /** 15167 * Used by {@link SeekBar} to display media playback progress as part of the 15168 * {@link ProgressControl}. 15169 * 15170 * @extends Component 15171 */ 15172 15173 var PlayProgressBar = 15174 /*#__PURE__*/ 15175 function (_Component) { 15176 inheritsLoose(PlayProgressBar, _Component); 15177 15178 /** 15179 * Creates an instance of this class. 15180 * 15181 * @param {Player} player 15182 * The {@link Player} that this class should be attached to. 15183 * 15184 * @param {Object} [options] 15185 * The key/value store of player options. 15186 */ 15187 function PlayProgressBar(player, options) { 15188 var _this; 15189 15190 _this = _Component.call(this, player, options) || this; 15191 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15192 return _this; 15193 } 15194 /** 15195 * Create the the DOM element for this class. 15196 * 15197 * @return {Element} 15198 * The element that was created. 15199 */ 15200 15201 15202 var _proto = PlayProgressBar.prototype; 15203 15204 _proto.createEl = function createEl() { 15205 return _Component.prototype.createEl.call(this, 'div', { 15206 className: 'vjs-play-progress vjs-slider-bar' 15207 }, { 15208 'aria-hidden': 'true' 15209 }); 15210 } 15211 /** 15212 * Enqueues updates to its own DOM as well as the DOM of its 15213 * {@link TimeTooltip} child. 15214 * 15215 * @param {Object} seekBarRect 15216 * The `ClientRect` for the {@link SeekBar} element. 15217 * 15218 * @param {number} seekBarPoint 15219 * A number from 0 to 1, representing a horizontal reference point 15220 * from the left edge of the {@link SeekBar} 15221 */ 15222 ; 15223 15224 _proto.update = function update(seekBarRect, seekBarPoint) { 15225 var timeTooltip = this.getChild('timeTooltip'); 15226 15227 if (!timeTooltip) { 15228 return; 15229 } 15230 15231 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 15232 timeTooltip.updateTime(seekBarRect, seekBarPoint, time); 15233 }; 15234 15235 return PlayProgressBar; 15236 }(Component); 15237 /** 15238 * Default options for {@link PlayProgressBar}. 15239 * 15240 * @type {Object} 15241 * @private 15242 */ 15243 15244 15245 PlayProgressBar.prototype.options_ = { 15246 children: [] 15247 }; // Time tooltips should not be added to a player on mobile devices 15248 15249 if (!IS_IOS && !IS_ANDROID) { 15250 PlayProgressBar.prototype.options_.children.push('timeTooltip'); 15251 } 15252 15253 Component.registerComponent('PlayProgressBar', PlayProgressBar); 15254 15255 /** 15256 * The {@link MouseTimeDisplay} component tracks mouse movement over the 15257 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} 15258 * indicating the time which is represented by a given point in the 15259 * {@link ProgressControl}. 15260 * 15261 * @extends Component 15262 */ 15263 15264 var MouseTimeDisplay = 15265 /*#__PURE__*/ 15266 function (_Component) { 15267 inheritsLoose(MouseTimeDisplay, _Component); 15268 15269 /** 15270 * Creates an instance of this class. 15271 * 15272 * @param {Player} player 15273 * The {@link Player} that this class should be attached to. 15274 * 15275 * @param {Object} [options] 15276 * The key/value store of player options. 15277 */ 15278 function MouseTimeDisplay(player, options) { 15279 var _this; 15280 15281 _this = _Component.call(this, player, options) || this; 15282 _this.update = throttle(bind(assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15283 return _this; 15284 } 15285 /** 15286 * Create the DOM element for this class. 15287 * 15288 * @return {Element} 15289 * The element that was created. 15290 */ 15291 15292 15293 var _proto = MouseTimeDisplay.prototype; 15294 15295 _proto.createEl = function createEl() { 15296 return _Component.prototype.createEl.call(this, 'div', { 15297 className: 'vjs-mouse-display' 15298 }); 15299 } 15300 /** 15301 * Enqueues updates to its own DOM as well as the DOM of its 15302 * {@link TimeTooltip} child. 15303 * 15304 * @param {Object} seekBarRect 15305 * The `ClientRect` for the {@link SeekBar} element. 15306 * 15307 * @param {number} seekBarPoint 15308 * A number from 0 to 1, representing a horizontal reference point 15309 * from the left edge of the {@link SeekBar} 15310 */ 15311 ; 15312 15313 _proto.update = function update(seekBarRect, seekBarPoint) { 15314 var _this2 = this; 15315 15316 var time = seekBarPoint * this.player_.duration(); 15317 this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () { 15318 _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px"; 15319 }); 15320 }; 15321 15322 return MouseTimeDisplay; 15323 }(Component); 15324 /** 15325 * Default options for `MouseTimeDisplay` 15326 * 15327 * @type {Object} 15328 * @private 15329 */ 15330 15331 15332 MouseTimeDisplay.prototype.options_ = { 15333 children: ['timeTooltip'] 15334 }; 15335 Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); 15336 15337 var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline. 15338 15339 var PAGE_KEY_MULTIPLIER = 12; 15340 /** 15341 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} 15342 * as its `bar`. 15343 * 15344 * @extends Slider 15345 */ 15346 15347 var SeekBar = 15348 /*#__PURE__*/
15349 function (_Slider) { 15350 inheritsLoose(SeekBar, _Slider); 15351 15352 /** 15353 * Creates an instance of this class. 15354 * 15355 * @param {Player} player 15356 * The `Player` that this class should be attached to. 15357 * 15358 * @param {Object} [options] 15359 * The key/value store of player options. 15360 */ 15361 function SeekBar(player, options) { 15362 var _this; 15363 15364 _this = _Slider.call(this, player, options) || this; 15365 15366 _this.setEventHandlers_(); 15367 15368 return _this; 15369 } 15370 /** 15371 * Sets the event handlers 15372 * 15373 * @private 15374 */ 15375 15376 15377 var _proto = SeekBar.prototype; 15378 15379 _proto.setEventHandlers_ = function setEventHandlers_() { 15380 this.update_ = bind(this, this.update); 15381 this.update = throttle(this.update_, UPDATE_REFRESH_INTERVAL); 15382 this.on(this.player_, ['ended', 'durationchange', 'timeupdate'], this.update); 15383 15384 if (this.player_.liveTracker) { 15385 this.on(this.player_.liveTracker, 'liveedgechange', this.update); 15386 } // when playing, let's ensure we smoothly update the play progress bar 15387 // via an interval 15388 15389 15390 this.updateInterval = null; 15391 this.on(this.player_, ['playing'], this.enableInterval_); 15392 this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden, 15393 // also, this causes the CPU to spike and eventually crash the page on IE11. 15394 15395 if ('hidden' in document && 'visibilityState' in document) { 15396 this.on(document, 'visibilitychange', this.toggleVisibility_); 15397 } 15398 }; 15399 15400 _proto.toggleVisibility_ = function toggleVisibility_(e) { 15401 if (document.hidden) { 15402 this.disableInterval_(e); 15403 } else { 15404 this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP 15405 15406 this.update(); 15407 } 15408 }; 15409 15410 _proto.enableInterval_ = function enableInterval_() { 15411 if (this.updateInterval) { 15412 return; 15413 } 15414 15415 this.updateInterval = this.setInterval(this.update, UPDATE_REFRESH_INTERVAL); 15416 }; 15417 15418 _proto.disableInterval_ = function disableInterval_(e) { 15419 if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') { 15420 return; 15421 } 15422 15423 if (!this.updateInterval) { 15424 return; 15425 } 15426 15427 this.clearInterval(this.updateInterval); 15428 this.updateInterval = null; 15429 } 15430 /** 15431 * Create the `Component`'s DOM element 15432 * 15433 * @return {Element} 15434 * The element that was created. 15435 */ 15436 ; 15437 15438 _proto.createEl = function createEl() { 15439 return _Slider.prototype.createEl.call(this, 'div', { 15440 className: 'vjs-progress-holder' 15441 }, { 15442 'aria-label': this.localize('Progress Bar') 15443 }); 15444 } 15445 /** 15446 * This function updates the play progress bar and accessibility 15447 * attributes to whatever is passed in. 15448 * 15449 * @param {EventTarget~Event} [event] 15450 * The `timeupdate` or `ended` event that caused this to run. 15451 * 15452 * @listens Player#timeupdate 15453 * 15454 * @return {number} 15455 * The current percent at a number from 0-1 15456 */ 15457 ; 15458 15459 _proto.update = function update(event) { 15460 var _this2 = this; 15461 15462 var percent = _Slider.prototype.update.call(this); 15463 15464 this.requestAnimationFrame(function () { 15465 var currentTime = _this2.player_.ended() ? _this2.player_.duration() : _this2.getCurrentTime_(); 15466 var liveTracker = _this2.player_.liveTracker; 15467 15468 var duration = _this2.player_.duration(); 15469 15470 if (liveTracker && liveTracker.isLive()) { 15471 duration = _this2.player_.liveTracker.liveCurrentTime(); 15472 } 15473 15474 if (_this2.percent_ !== percent) { 15475 // machine readable value of progress bar (percentage complete) 15476 _this2.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); 15477 15478 _this2.percent_ = percent; 15479 } 15480 15481 if (_this2.currentTime_ !== currentTime || _this2.duration_ !== duration) { 15482 // human readable value of progress bar (time complete) 15483 _this2.el_.setAttribute('aria-valuetext', _this2.localize('progress bar timing: currentTime={1} duration={2}
15483', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); 15484 15485 _this2.currentTime_ = currentTime; 15486 _this2.duration_ = duration; 15487 } // update the progress bar time tooltip with the current time 15488 15489 15490 if (_this2.bar) { 15491 _this2.bar.update(getBoundingClientRect(_this2.el()), _this2.getProgress()); 15492 } 15493 }); 15494 return percent; 15495 } 15496 /** 15497 * Get the value of current time but allows for smooth scrubbing, 15498 * when player can't keep up. 15499 * 15500 * @return {number} 15501 * The current time value to display 15502 * 15503 * @private 15504 */ 15505 ; 15506 15507 _proto.getCurrentTime_ = function getCurrentTime_() { 15508 return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 15509 } 15510 /** 15511 * Get the percentage of media played so far. 15512 * 15513 * @return {number} 15514 * The percentage of media played so far (0 to 1). 15515 */ 15516 ; 15517 15518 _proto.getPercent = function getPercent() { 15519 var currentTime = this.getCurrentTime_(); 15520 var percent; 15521 var liveTracker = this.player_.liveTracker; 15522 15523 if (liveTracker && liveTracker.isLive()) { 15524 percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge 15525 15526 if (liveTracker.atLiveEdge()) { 15527 percent = 1; 15528 } 15529 } else { 15530 percent = currentTime / this.player_.duration(); 15531 } 15532 15533 return percent; 15534 } 15535 /** 15536 * Handle mouse down on seek bar 15537 * 15538 * @param {EventTarget~Event} event 15539 * The `mousedown` event that caused this to run. 15540 * 15541 * @listens mousedown 15542 */ 15543 ; 15544 15545 _proto.handleMouseDown = function handleMouseDown(event) { 15546 if (!isSingleLeftClick(event)) { 15547 return; 15548 } // Stop event propagation to prevent double fire in progress-control.js 15549 15550 15551 event.stopPropagation(); 15552 this.player_.scrubbing(true); 15553 this.videoWasPlaying = !this.player_.paused(); 15554 this.player_.pause(); 15555 15556 _Slider.prototype.handleMouseDown.call(this, event); 15557 } 15558 /** 15559 * Handle mouse move on seek bar 15560 * 15561 * @param {EventTarget~Event} event 15562 * The `mousemove` event that caused this to run. 15563 * 15564 * @listens mousemove 15565 */ 15566 ; 15567 15568 _proto.handleMouseMove = function handleMouseMove(event) { 15569 if (!isSingleLeftClick(event)) { 15570 return; 15571 } 15572 15573 var newTime; 15574 var distance = this.calculateDistance(event); 15575 var liveTracker = this.player_.liveTracker; 15576 15577 if (!liveTracker || !liveTracker.isLive()) { 15578 newTime = distance * this.player_.duration(); // Don't let video end while scrubbing. 15579 15580 if (newTime === this.player_.duration()) { 15581 newTime = newTime - 0.1; 15582 } 15583 } else { 15584 var seekableStart = liveTracker.seekableStart(); 15585 var seekableEnd = liveTracker.liveCurrentTime(); 15586 newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing. 15587 15588 if (newTime >= seekableEnd) { 15589 newTime = seekableEnd; 15590 } // Compensate for precision differences so that currentTime is not less 15591 // than seekable start 15592 15593 15594 if (newTime <= seekableStart) { 15595 newTime = seekableStart + 0.1; 15596 } // On android seekableEnd can be Infinity sometimes, 15597 // this will cause newTime to be Infinity, which is 15598 // not a valid currentTime. 15599 15600 15601 if (newTime === Infinity) { 15602 return; 15603 } 15604 } // Set new time (tell player to seek to new time) 15605 15606 15607 this.player_.currentTime(newTime); 15608 }; 15609 15610 _proto.enable = function enable() { 15611 _Slider.prototype.enable.call(this); 15612 15613 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 15614 15615 if (!mouseTimeDisplay) { 15616 return; 15617 } 15618 15619 mouseTimeDisplay.show(); 15620 }; 15621 15622 _proto.disable = function disable() { 15623 _Slider.prototype.disable.call(this); 15624 15625 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 15626 15627 if (!mouseTimeDisplay) { 15628 return; 15629 } 15630 15631 mouseTimeDisplay.hide(); 15632 } 15633 /** 15634 * Handle mouse up on seek bar 15635 * 15636 * @param {EventTarget~Event} event 15637 * The `mouseup` event that caused this to run. 15638 * 15639 * @listens mouseup 15640 */ 15641 ; 15642 15643 _proto.handleMouseUp = function handleMouseUp(event) { 15644 _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js 15645 15646 15647 if (event) { 15648 event.stopPropagation(); 15649 } 15650 15651 this.player_.scrubbing(false); 15652 /** 15653 * Trigger timeupdate because we're done seeking and the time has changed. 15654 * This is particularly useful for if the player is paused to time the time displays. 15655 * 15656 * @event Tech#timeupdate 15657 * @type {EventTarget~Event} 15658 */ 15659 15660 this.player_.trigger({ 15661 type: 'timeupdate', 15662 target: this, 15663 manuallyTriggered: true 15664 }); 15665 15666 if (this.videoWasPlaying) { 15667 silencePromise(this.player_.play()); 15668 } else { 15669 // We're done seeking and the time has changed. 15670 // If the player is paused, make sure we display the correct time on the seek bar. 15671 this.update_(); 15672 } 15673 } 15674 /** 15675 * Move more quickly fast forward for keyboard-only users 15676 */ 15677 ; 15678 15679 _proto.stepForward = function stepForward() { 15680 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); 15681 } 15682 /** 15683 * Move more quickly rewind for keyboard-only users 15684 */ 15685 ; 15686 15687 _proto.stepBack = function stepBack() { 15688 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); 15689 } 15690 /** 15691 * Toggles the playback state of the player 15692 * This gets called when enter or space is used on the seekbar 15693 * 15694 * @param {EventTarget~Event} event
15695 * The `keydown` event that caused this function to be called 15696 * 15697 */ 15698 ; 15699 15700 _proto.handleAction = function handleAction(event) { 15701 if (this.player_.paused()) { 15702 this.player_.play(); 15703 } else { 15704 this.player_.pause(); 15705 } 15706 } 15707 /** 15708 * Called when this SeekBar has focus and a key gets pressed down. 15709 * Supports the following keys: 15710 * 15711 * Space or Enter key fire a click event 15712 * Home key moves to start of the timeline 15713 * End key moves to end of the timeline 15714 * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline 15715 * PageDown key moves back a larger step than ArrowDown 15716 * PageUp key moves forward a large step 15717 * 15718 * @param {EventTarget~Event} event 15719 * The `keydown` event that caused this function to be called. 15720 * 15721 * @listens keydown 15722 */ 15723 ; 15724 15725 _proto.handleKeyDown = function handleKeyDown(event) { 15726 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 15727 event.preventDefault(); 15728 event.stopPropagation(); 15729 this.handleAction(event); 15730 } else if (keycode.isEventKey(event, 'Home')) { 15731 event.preventDefault(); 15732 event.stopPropagation(); 15733 this.player_.currentTime(0); 15734 } else if (keycode.isEventKey(event, 'End')) { 15735 event.preventDefault(); 15736 event.stopPropagation(); 15737 this.player_.currentTime(this.player_.duration()); 15738 } else if (/^[0-9]$/.test(keycode(event))) { 15739 event.preventDefault(); 15740 event.stopPropagation(); 15741 var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0; 15742 this.player_.currentTime(this.player_.duration() * gotoFraction); 15743 } else if (keycode.isEventKey(event, 'PgDn')) { 15744 event.preventDefault(); 15745 event.stopPropagation(); 15746 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER); 15747 } else if (keycode.isEventKey(event, 'PgUp')) { 15748 event.preventDefault(); 15749 event.stopPropagation(); 15750 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER); 15751 } else { 15752 // Pass keydown handling up for unsupported keys 15753 _Slider.prototype.handleKeyDown.call(this, event); 15754 } 15755 }; 15756 15757 return SeekBar; 15758 }(Slider); 15759 /** 15760 * Default options for the `SeekBar` 15761 * 15762 * @type {Object} 15763 * @private 15764 */ 15765 15766 15767 SeekBar.prototype.options_ = { 15768 children: ['loadProgressBar', 'playProgressBar'], 15769 barName: 'playProgressBar' 15770 }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices 15771 15772 if (!IS_IOS && !IS_ANDROID) { 15773 SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); 15774 } 15775 15776 Component.registerComponent('SeekBar', SeekBar); 15777 15778 /** 15779 * The Progress Control component contains the seek bar, load progress, 15780 * and play progress. 15781 * 15782 * @extends Component 15783 */ 15784 15785 var ProgressControl = 15786 /*#__PURE__*/ 15787 function (_Component) { 15788 inheritsLoose(ProgressControl, _Component); 15789 15790 /** 15791 * Creates an instance of this class. 15792 * 15793 * @param {Player} player 15794 * The `Player` that this class should be attached to. 15795 * 15796 * @param {Object} [options] 15797 * The key/value store of player options. 15798 */ 15799 function ProgressControl(player, options) { 15800 var _this; 15801 15802 _this = _Component.call(this, player, options) || this; 15803 _this.handleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 15804 _this.throttledHandleMouseSeek = throttle(bind(assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL); 15805 15806 _this.enable(); 15807 15808 return _this; 15809 } 15810 /** 15811 * Create the `Component`'s DOM element 15812 * 15813 * @return {Element} 15814 * The element that was created. 15815 */ 15816 15817 15818 var _proto = ProgressControl.prototype; 15819 15820 _proto.createEl = function createEl() { 15821 return _Component.prototype.createEl.call(this, 'div', { 15822 className: 'vjs-progress-control vjs-control' 15823 }); 15824 } 15825 /** 15826 * When the mouse moves over the `ProgressControl`, the pointer position 15827 * gets passed down to the `MouseTimeDisplay` component. 15828 * 15829 * @param {EventTarget~Event} event 15830 * The `mousemove` event that caused this function to run. 15831 * 15832 * @listen mousemove 15833 */ 15834 ; 15835 15836 _proto.handleMouseMove = function handleMouseMove(event) { 15837 var seekBar = this.getChild('seekBar'); 15838 15839 if (!seekBar) { 15840 return; 15841 } 15842 15843 var playProgressBar = seekBar.getChild('playProgressBar'); 15844 var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); 15845 15846 if (!playProgressBar && !mouseTimeDisplay) { 15847 return; 15848 } 15849 15850 var seekBarEl = seekBar.el(); 15851 var seekBarRect = getBoundingClientRect(seekBarEl); 15852 var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means 15853 // that it's possible to trigger this behavior outside the boundaries of 15854 // the `SeekBar`. This ensures we stay within it at all times. 15855 15856 seekBarPoint = clamp(0, 1, seekBarPoint); 15857 15858 if (mouseTimeDisplay) { 15859 mouseTimeDisplay.update(seekBarRect, seekBarPoint); 15860 } 15861 15862 if (playProgressBar) { 15863 playProgressBar.update(seekBarRect, seekBar.getProgress()); 15864 } 15865 } 15866 /** 15867 * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. 15868 * 15869 * @method ProgressControl#throttledHandleMouseSeek 15870 * @param {EventTarget~Event} event 15871 * The `mousemove` event that caused this function to run. 15872 * 15873 * @listen mousemove 15874 * @listen touchmove 15875 */ 15876 15877 /** 15878 * Handle `mousemove` or `touchmove` events on the `ProgressControl`. 15879 * 15880 * @param {EventTarget~Event} event 15881 * `mousedown` or `touchstart` event that triggered this function 15882 * 15883 * @listens mousemove 15884 * @listens touchmove 15885 */ 15886 ; 15887 15888 _proto.handleMouseSeek = function handleMouseSeek(event) { 15889 var seekBar = this.getChild('seekBar'); 15890 15891 if (seekBar) { 15892 seekBar.handleMouseMove(event); 15893 } 15894 } 15895 /**
15896 * Are controls are currently enabled for this progress control. 15897 * 15898 * @return {boolean} 15899 * true if controls are enabled, false otherwise 15900 */ 15901 ; 15902 15903 _proto.enabled = function enabled() { 15904 return this.enabled_; 15905 } 15906 /** 15907 * Disable all controls on the progress control and its children 15908 */ 15909 ; 15910 15911 _proto.disable = function disable() { 15912 this.children().forEach(function (child) { 15913 return child.disable && child.disable(); 15914 }); 15915 15916 if (!this.enabled()) { 15917 return; 15918 } 15919 15920 this.off(['mousedown', 'touchstart'], this.handleMouseDown); 15921 this.off(this.el_, 'mousemove', this.handleMouseMove); 15922 this.handleMouseUp(); 15923 this.addClass('disabled'); 15924 this.enabled_ = false; 15925 } 15926 /** 15927 * Enable all controls on the progress control and its children 15928 */ 15929 ; 15930 15931 _proto.enable = function enable() { 15932 this.children().forEach(function (child) { 15933 return child.enable && child.enable(); 15934 }); 15935 15936 if (this.enabled()) { 15937 return; 15938 } 15939 15940 this.on(['mousedown', 'touchstart'], this.handleMouseDown); 15941 this.on(this.el_, 'mousemove', this.handleMouseMove); 15942 this.removeClass('disabled'); 15943 this.enabled_ = true; 15944 } 15945 /** 15946 * Handle `mousedown` or `touchstart` events on the `ProgressControl`. 15947 * 15948 * @param {EventTarget~Event} event 15949 * `mousedown` or `touchstart` event that triggered this function 15950 * 15951 * @listens mousedown 15952 * @listens touchstart 15953 */ 15954 ; 15955 15956 _proto.handleMouseDown = function handleMouseDown(event) { 15957 var doc = this.el_.ownerDocument; 15958 var seekBar = this.getChild('seekBar'); 15959 15960 if (seekBar) { 15961 seekBar.handleMouseDown(event); 15962 } 15963 15964 this.on(doc, 'mousemove', this.throttledHandleMouseSeek); 15965 this.on(doc, 'touchmove', this.throttledHandleMouseSeek); 15966 this.on(doc, 'mouseup', this.handleMouseUp); 15967 this.on(doc, 'touchend', this.handleMouseUp); 15968 } 15969 /** 15970 * Handle `mouseup` or `touchend` events on the `ProgressControl`. 15971 * 15972 * @param {EventTarget~Event} event 15973 * `mouseup` or `touchend` event that triggered this function. 15974 * 15975 * @listens touchend 15976 * @listens mouseup 15977 */ 15978 ; 15979 15980 _proto.handleMouseUp = function handleMouseUp(event) { 15981 var doc = this.el_.ownerDocument; 15982 var seekBar = this.getChild('seekBar'); 15983 15984 if (seekBar) { 15985 seekBar.handleMouseUp(event); 15986 } 15987 15988 this.off(doc, 'mousemove', this.throttledHandleMouseSeek); 15989 this.off(doc, 'touchmove', this.throttledHandleMouseSeek); 15990 this.off(doc, 'mouseup', this.handleMouseUp); 15991 this.off(doc, 'touchend', this.handleMouseUp); 15992 }; 15993 15994 return ProgressControl; 15995 }(Component); 15996 /** 15997 * Default options for `ProgressControl` 15998 * 15999 * @type {Object} 16000 * @private 16001 */ 16002 16003 16004 ProgressControl.prototype.options_ = { 16005 children: ['seekBar'] 16006 }; 16007 Component.registerComponent('ProgressControl', ProgressControl); 16008 16009 /** 16010 * Toggle Picture-in-Picture mode 16011 * 16012 * @extends Button 16013 */ 16014 16015 var PictureInPictureToggle = 16016 /*#__PURE__*/ 16017 function (_Button) { 16018 inheritsLoose(PictureInPictureToggle, _Button); 16019 16020 /** 16021 * Creates an instance of this class. 16022 * 16023 * @param {Player} player 16024 * The `Player` that this class should be attached to. 16025 * 16026 * @param {Object} [options] 16027 * The key/value store of player options. 16028 * 16029 * @listens Player#enterpictureinpicture 16030 * @listens Player#leavepictureinpicture 16031 */ 16032 function PictureInPictureToggle(player, options) { 16033 var _this; 16034 16035 _this = _Button.call(this, player, options) || this; 16036 16037 _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], _this.handlePictureInPictureChange); // TODO: Activate button on player loadedmetadata event. 16038 // TODO: Deactivate button on player emptied event. 16039 // TODO: Deactivate button if disablepictureinpicture attribute is present. 16040 16041 16042 if (!document.pictureInPictureEnabled) { 16043 _this.disable(); 16044 } 16045 16046 return _this; 16047 } 16048 /** 16049 * Builds the default DOM `className`. 16050 * 16051 * @return {string} 16052 * The DOM `className` for this object. 16053 */ 16054 16055 16056 var _proto = PictureInPictureToggle.prototype; 16057 16058 _proto.buildCSSClass = function buildCSSClass() { 16059 return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this); 16060 } 16061 /** 16062 * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly. 16063 * 16064 * @param {EventTarget~Event} [event] 16065 * The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be 16066 * called. 16067 * 16068 * @listens Player#enterpictureinpicture 16069 * @listens Player#leavepictureinpicture 16070 */ 16071 ; 16072 16073 _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) { 16074 if (this.player_.isInPictureInPicture()) { 16075 this.controlText('Exit Picture-in-Picture'); 16076 } else { 16077 this.controlText('Picture-in-Picture'); 16078 } 16079 } 16080 /** 16081 * This gets called when an `PictureInPictureToggle` is "clicked". See 16082 * {@link ClickableComponent} for more detailed information on what a click can be. 16083 * 16084 * @param {EventTarget~Event} [event]
16085 * The `keydown`, `tap`, or `click` event that caused this function to be 16086 * called. 16087 * 16088 * @listens tap 16089 * @listens click 16090 */ 16091 ; 16092 16093 _proto.handleClick = function handleClick(event) { 16094 if (!this.player_.isInPictureInPicture()) { 16095 this.player_.requestPictureInPicture(); 16096 } else { 16097 this.player_.exitPictureInPicture(); 16098 } 16099 }; 16100 16101 return PictureInPictureToggle; 16102 }(Button); 16103 /** 16104 * The text that should display over the `PictureInPictureToggle`s controls. Added for localization. 16105 * 16106 * @type {string} 16107 * @private 16108 */ 16109 16110 16111 PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture'; 16112 Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle); 16113 16114 /** 16115 * Toggle fullscreen video 16116 * 16117 * @extends Button 16118 */ 16119 16120 var FullscreenToggle = 16121 /*#__PURE__*/ 16122 function (_Button) { 16123 inheritsLoose(FullscreenToggle, _Button); 16124 16125 /** 16126 * Creates an instance of this class. 16127 * 16128 * @param {Player} player 16129 * The `Player` that this class should be attached to. 16130 * 16131 * @param {Object} [options] 16132 * The key/value store of player options. 16133 */ 16134 function FullscreenToggle(player, options) { 16135 var _this; 16136 16137 _this = _Button.call(this, player, options) || this; 16138 16139 _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); 16140 16141 if (document[player.fsApi_.fullscreenEnabled] === false) { 16142 _this.disable(); 16143 } 16144 16145 return _this; 16146 } 16147 /** 16148 * Builds the default DOM `className`. 16149 * 16150 * @return {string} 16151 * The DOM `className` for this object. 16152 */ 16153 16154 16155 var _proto = FullscreenToggle.prototype; 16156 16157 _proto.buildCSSClass = function buildCSSClass() { 16158 return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this); 16159 } 16160 /** 16161 * Handles fullscreenchange on the player and change control text accordingly. 16162 * 16163 * @param {EventTarget~Event} [event] 16164 * The {@link Player#fullscreenchange} event that caused this function to be 16165 * called. 16166 * 16167 * @listens Player#fullscreenchange 16168 */ 16169 ; 16170 16171 _proto.handleFullscreenChange = function handleFullscreenChange(event) { 16172 if (this.player_.isFullscreen()) { 16173 this.controlText('Non-Fullscreen'); 16174 } else { 16175 this.controlText('Fullscreen'); 16176 } 16177 } 16178 /** 16179 * This gets called when an `FullscreenToggle` is "clicked". See 16180 * {@link ClickableComponent} for more detailed information on what a click can be. 16181 * 16182 * @param {EventTarget~Event} [event] 16183 * The `keydown`, `tap`, or `click` event that caused this function to be 16184 * called. 16185 * 16186 * @listens tap 16187 * @listens click 16188 */ 16189 ; 16190 16191 _proto.handleClick = function handleClick(event) { 16192 if (!this.player_.isFullscreen()) { 16193 this.player_.requestFullscreen(); 16194 } else { 16195 this.player_.exitFullscreen(); 16196 } 16197 }; 16198 16199 return FullscreenToggle; 16200 }(Button); 16201 /** 16202 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 16203 * 16204 * @type {string} 16205 * @private 16206 */ 16207 16208 16209 FullscreenToggle.prototype.controlText_ = 'Fullscreen'; 16210 Component.registerComponent('FullscreenToggle', FullscreenToggle); 16211 16212 /** 16213 * Check if volume control is supported and if it isn't hide the 16214 * `Component` that was passed using the `vjs-hidden` class. 16215 * 16216 * @param {Component} self 16217 * The component that should be hidden if volume is unsupported 16218 * 16219 * @param {Player} player 16220 * A reference to the player 16221 * 16222 * @private 16223 */ 16224 var checkVolumeSupport = function checkVolumeSupport(self, player) { 16225 // hide volume controls when they're not supported by the current tech 16226 if (player.tech_ && !player.tech_.featuresVolumeControl) { 16227 self.addClass('vjs-hidden'); 16228 } 16229 16230 self.on(player, 'loadstart', function () { 16231 if (!player.tech_.featuresVolumeControl) { 16232 self.addClass('vjs-hidden'); 16233 } else { 16234 self.removeClass('vjs-hidden'); 16235 } 16236 });
vendor: 5,375 bytes, lines 16237-16451
16237 }; 16238 16239 /** 16240 * Shows volume level 16241 * 16242 * @extends Component 16243 */ 16244 16245 var VolumeLevel = 16246 /*#__PURE__*/ 16247 function (_Component) { 16248 inheritsLoose(VolumeLevel, _Component); 16249 16250 function VolumeLevel() { 16251 return _Component.apply(this, arguments) || this; 16252 } 16253 16254 var _proto = VolumeLevel.prototype; 16255 16256 /** 16257 * Create the `Component`'s DOM element 16258 * 16259 * @return {Element} 16260 * The element that was created. 16261 */ 16262 _proto.createEl = function createEl() { 16263 return _Component.prototype.createEl.call(this, 'div', { 16264 className: 'vjs-volume-level', 16265 innerHTML: '<span class="vjs-control-text"></span>' 16266 }); 16267 }; 16268 16269 return VolumeLevel; 16270 }(Component); 16271 16272 Component.registerComponent('VolumeLevel', VolumeLevel); 16273 16274 /** 16275 * The bar that contains the volume level and can be clicked on to adjust the level 16276 * 16277 * @extends Slider 16278 */ 16279 16280 var VolumeBar = 16281 /*#__PURE__*/ 16282 function (_Slider) { 16283 inheritsLoose(VolumeBar, _Slider); 16284 16285 /** 16286 * Creates an instance of this class. 16287 * 16288 * @param {Player} player 16289 * The `Player` that this class should be attached to. 16290 * 16291 * @param {Object} [options] 16292 * The key/value store of player options. 16293 */ 16294 function VolumeBar(player, options) { 16295 var _this; 16296 16297 _this = _Slider.call(this, player, options) || this; 16298 16299 _this.on('slideractive', _this.updateLastVolume_); 16300 16301 _this.on(player, 'volumechange', _this.updateARIAAttributes); 16302 16303 player.ready(function () { 16304 return _this.updateARIAAttributes(); 16305 }); 16306 return _this; 16307 } 16308 /** 16309 * Create the `Component`'s DOM element 16310 * 16311 * @return {Element} 16312 * The element that was created. 16313 */ 16314 16315 16316 var _proto = VolumeBar.prototype; 16317 16318 _proto.createEl = function createEl() { 16319 return _Slider.prototype.createEl.call(this, 'div', { 16320 className: 'vjs-volume-bar vjs-slider-bar' 16321 }, { 16322 'aria-label': this.localize('Volume Level'), 16323 'aria-live': 'polite' 16324 }); 16325 } 16326 /** 16327 * Handle mouse down on volume bar 16328 * 16329 * @param {EventTarget~Event} event 16330 * The `mousedown` event that caused this to run. 16331 * 16332 * @listens mousedown 16333 */ 16334 ; 16335 16336 _proto.handleMouseDown = function handleMouseDown(event) { 16337 if (!isSingleLeftClick(event)) { 16338 return; 16339 } 16340 16341 _Slider.prototype.handleMouseDown.call(this, event); 16342 } 16343 /** 16344 * Handle movement events on the {@link VolumeMenuButton}. 16345 * 16346 * @param {EventTarget~Event} event 16347 * The event that caused this function to run. 16348 * 16349 * @listens mousemove 16350 */ 16351 ; 16352 16353 _proto.handleMouseMove = function handleMouseMove(event) { 16354 if (!isSingleLeftClick(event)) { 16355 return; 16356 } 16357 16358 this.checkMuted(); 16359 this.player_.volume(this.calculateDistance(event)); 16360 } 16361 /** 16362 * If the player is muted unmute it. 16363 */ 16364 ; 16365 16366 _proto.checkMuted = function checkMuted() { 16367 if (this.player_.muted()) { 16368 this.player_.muted(false); 16369 } 16370 } 16371 /** 16372 * Get percent of volume level 16373 * 16374 * @return {number} 16375 * Volume level percent as a decimal number. 16376 */ 16377 ; 16378 16379 _proto.getPercent = function getPercent() { 16380 if (this.player_.muted()) { 16381 return 0; 16382 } 16383 16384 return this.player_.volume(); 16385 } 16386 /** 16387 * Increase volume level for keyboard users 16388 */ 16389 ; 16390 16391 _proto.stepForward = function stepForward() { 16392 this.checkMuted(); 16393 this.player_.volume(this.player_.volume() + 0.1); 16394 } 16395 /** 16396 * Decrease volume level for keyboard users 16397 */ 16398 ; 16399 16400 _proto.stepBack = function stepBack() { 16401 this.checkMuted(); 16402 this.player_.volume(this.player_.volume() - 0.1); 16403 } 16404 /** 16405 * Update ARIA accessibility attributes 16406 * 16407 * @param {EventTarget~Event} [event] 16408 * The `volumechange` event that caused this function to run. 16409 * 16410 * @listens Player#volumechange 16411 */ 16412 ; 16413 16414 _proto.updateARIAAttributes = function updateARIAAttributes(event) { 16415 var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); 16416 this.el_.setAttribute('aria-valuenow', ariaValue); 16417 this.el_.setAttribute('aria-valuetext', ariaValue + '%'); 16418 } 16419 /** 16420 * Returns the current value of the player volume as a percentage 16421 * 16422 * @private 16423 */ 16424 ; 16425 16426 _proto.volumeAsPercentage_ = function volumeAsPercentage_() { 16427 return Math.round(this.player_.volume() * 100); 16428 } 16429 /** 16430 * When user starts dragging the VolumeBar, store the volume and listen for 16431 * the end of the drag. When the drag ends, if the volume was set to zero, 16432 * set lastVolume to the stored volume. 16433 * 16434 * @listens slideractive 16435 * @private 16436 */ 16437 ; 16438 16439 _proto.updateLastVolume_ = function updateLastVolume_() { 16440 var _this2 = this; 16441 16442 var volumeBeforeDrag = this.player_.volume(); 16443 this.one('sliderinactive', function () { 16444 if (_this2.player_.volume() === 0) { 16445 _this2.player_.lastVolume_(volumeBeforeDrag); 16446 } 16447 }); 16448 }; 16449 16450 return VolumeBar; 16451 }
16451(Slider); 16452 /** 16453 * Default options for the `VolumeBar` 16454 * 16455 * @type {Object} 16456 * @private 16457 */ 16458 16459 16460 VolumeBar.prototype.options_ = { 16461 children: ['volumeLevel'], 16462 barName: 'volumeLevel' 16463 }; 16464 /** 16465 * Call the update event for this Slider when this event happens on the player. 16466 * 16467 * @type {string} 16468 */ 16469 16470 VolumeBar.prototype.playerEvent = 'volumechange'; 16471 Component.registerComponent('VolumeBar', VolumeBar); 16472 16473 /** 16474 * The component for controlling the volume level 16475 * 16476 * @extends Component 16477 */ 16478 16479 var VolumeControl = 16480 /*#__PURE__*/ 16481 function (_Component) { 16482 inheritsLoose(VolumeControl, _Component); 16483 16484 /** 16485 * Creates an instance of this class. 16486 * 16487 * @param {Player} player 16488 * The `Player` that this class should be attached to. 16489 * 16490 * @param {Object} [options={}] 16491 * The key/value store of player options. 16492 */ 16493 function VolumeControl(player, options) { 16494 var _this; 16495 16496 if (options === void 0) { 16497 options = {}; 16498 } 16499 16500 options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if 16501 // the VolumeBar is turned on. 16502 16503 if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { 16504 options.volumeBar = options.volumeBar || {}; 16505 options.volumeBar.vertical = options.vertical; 16506 } 16507 16508 _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing 16509 16510 checkVolumeSupport(assertThisInitialized(_this), player); 16511 _this.throttledHandleMouseMove = throttle(bind(assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 16512 16513 _this.on('mousedown', _this.handleMouseDown); 16514 16515 _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and 16516 // is dragging) or in focus we do not want to hide the VolumeBar 16517 16518 16519 _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { 16520 _this.volumeBar.addClass('vjs-slider-active'); 16521 16522 _this.addClass('vjs-slider-active'); 16523 16524 _this.trigger('slideractive'); 16525 }); 16526 16527 _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { 16528 _this.volumeBar.removeClass('vjs-slider-active'); 16529 16530 _this.removeClass('vjs-slider-active'); 16531 16532 _this.trigger('sliderinactive'); 16533 }); 16534 16535 return _this; 16536 } 16537 /** 16538 * Create the `Component`'s DOM element 16539 * 16540 * @return {Element} 16541 * The element that was created. 16542 */ 16543 16544 16545 var _proto = VolumeControl.prototype; 16546 16547 _proto.createEl = function createEl() { 16548 var orientationClass = 'vjs-volume-horizontal'; 16549 16550 if (this.options_.vertical) { 16551 orientationClass = 'vjs-volume-vertical'; 16552 } 16553 16554 return _Component.prototype.createEl.call(this, 'div', { 16555 className: "vjs-volume-control vjs-control " + orientationClass 16556 }); 16557 } 16558 /** 16559 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 16560 * 16561 * @param {EventTarget~Event} event 16562 * `mousedown` or `touchstart` event that triggered this function 16563 * 16564 * @listens mousedown 16565 * @listens touchstart 16566 */ 16567 ; 16568 16569 _proto.handleMouseDown = function handleMouseDown(event) { 16570 var doc = this.el_.ownerDocument; 16571 this.on(doc, 'mousemove', this.throttledHandleMouseMove); 16572 this.on(doc, 'touchmove', this.throttledHandleMouseMove); 16573 this.on(doc, 'mouseup', this.handleMouseUp); 16574 this.on(doc, 'touchend', this.handleMouseUp); 16575 } 16576 /** 16577 * Handle `mouseup` or `touchend` events on the `VolumeControl`. 16578 * 16579 * @param {EventTarget~Event} event 16580 * `mouseup` or `touchend` event that triggered this function. 16581 * 16582 * @listens touchend 16583 * @listens mouseup 16584 */ 16585 ; 16586 16587 _proto.handleMouseUp = function handleMouseUp(event) { 16588 var doc = this.el_.ownerDocument; 16589 this.off(doc, 'mousemove', this.throttledHandleMouseMove); 16590 this.off(doc, 'touchmove', this.throttledHandleMouseMove); 16591 this.off(doc, 'mouseup', this.handleMouseUp); 16592 this.off(doc, 'touchend', this.handleMouseUp); 16593 } 16594 /** 16595 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 16596 * 16597 * @param {EventTarget~Event} event 16598 * `mousedown` or `touchstart` event that triggered this function 16599 * 16600 * @listens mousedown 16601 * @listens touchstart 16602 */ 16603 ; 16604 16605 _proto.handleMouseMove = function handleMouseMove(event) { 16606 this.volumeBar.handleMouseMove(event); 16607 }; 16608 16609 return VolumeControl; 16610 }(Component); 16611 /** 16612 * Default options for the `VolumeControl` 16613 * 16614 * @type {Object} 16615 * @private 16616 */ 16617 16618 16619 VolumeControl.prototype.options_ = { 16620 children: ['volumeBar'] 16621 }; 16622 Component.registerComponent('VolumeControl', VolumeControl); 16623 16624 /** 16625 * Check if muting volume is supported and if it isn't hide the mute toggle 16626 * button. 16627 * 16628 * @param {Component} self 16629 * A reference to the mute toggle button 16630 * 16631 * @param {Player} player 16632 * A reference to the player 16633 * 16634 * @private 16635 */ 16636 var checkMuteSupport = function checkMuteSupport(self, player) { 16637 // hide mute toggle button if it's not supported by the current tech 16638 if (player.tech_ && !player.tech_.featuresMuteControl) { 16639 self.addClass('vjs-hidden'); 16640 } 16641 16642 self.on(player, 'loadstart', function () { 16643 if (!player.tech_.featuresMuteControl) { 16644 self.addClass('vjs-hidden'); 16645 } else { 16646 self.removeClass('vjs-hidden'); 16647 } 16648 });
16649 }; 16650 16651 /** 16652 * A button component for muting the audio. 16653 * 16654 * @extends Button 16655 */ 16656 16657 var MuteToggle = 16658 /*#__PURE__*/ 16659 function (_Button) { 16660 inheritsLoose(MuteToggle, _Button); 16661 16662 /** 16663 * Creates an instance of this class. 16664 * 16665 * @param {Player} player 16666 * The `Player` that this class should be attached to. 16667 * 16668 * @param {Object} [options] 16669 * The key/value store of player options. 16670 */ 16671 function MuteToggle(player, options) { 16672 var _this; 16673 16674 _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing 16675 16676 checkMuteSupport(assertThisInitialized(_this), player); 16677 16678 _this.on(player, ['loadstart', 'volumechange'], _this.update); 16679 16680 return _this; 16681 } 16682 /** 16683 * Builds the default DOM `className`. 16684 * 16685 * @return {string} 16686 * The DOM `className` for this object. 16687 */ 16688 16689 16690 var _proto = MuteToggle.prototype; 16691 16692 _proto.buildCSSClass = function buildCSSClass() { 16693 return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this); 16694 } 16695 /** 16696 * This gets called when an `MuteToggle` is "clicked". See 16697 * {@link ClickableComponent} for more detailed information on what a click can be. 16698 * 16699 * @param {EventTarget~Event} [event] 16700 * The `keydown`, `tap`, or `click` event that caused this function to be 16701 * called. 16702 * 16703 * @listens tap 16704 * @listens click 16705 */ 16706 ; 16707 16708 _proto.handleClick = function handleClick(event) { 16709 var vol = this.player_.volume(); 16710 var lastVolume = this.player_.lastVolume_(); 16711 16712 if (vol === 0) { 16713 var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; 16714 this.player_.volume(volumeToSet); 16715 this.player_.muted(false); 16716 } else { 16717 this.player_.muted(this.player_.muted() ? false : true); 16718 } 16719 } 16720 /** 16721 * Update the `MuteToggle` button based on the state of `volume` and `muted` 16722 * on the player. 16723 * 16724 * @param {EventTarget~Event} [event] 16725 * The {@link Player#loadstart} event if this function was called 16726 * through an event. 16727 * 16728 * @listens Player#loadstart 16729 * @listens Player#volumechange 16730 */ 16731 ; 16732 16733 _proto.update = function update(event) { 16734 this.updateIcon_(); 16735 this.updateControlText_(); 16736 } 16737 /** 16738 * Update the appearance of the `MuteToggle` icon. 16739 * 16740 * Possible states (given `level` variable below): 16741 * - 0: crossed out 16742 * - 1: zero bars of volume 16743 * - 2: one bar of volume 16744 * - 3: two bars of volume 16745 * 16746 * @private 16747 */ 16748 ; 16749 16750 _proto.updateIcon_ = function updateIcon_() { 16751 var vol = this.player_.volume(); 16752 var level = 3; // in iOS when a player is loaded with muted attribute 16753 // and volume is changed with a native mute button 16754 // we want to make sure muted state is updated 16755 16756 if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) { 16757 this.player_.muted(this.player_.tech_.el_.muted); 16758 } 16759 16760 if (vol === 0 || this.player_.muted()) { 16761 level = 0; 16762 } else if (vol < 0.33) { 16763 level = 1; 16764 } else if (vol < 0.67) { 16765 level = 2; 16766 } // TODO improve muted icon classes 16767 16768 16769 for (var i = 0; i < 4; i++) { 16770 removeClass(this.el_, "vjs-vol-" + i); 16771 } 16772 16773 addClass(this.el_, "vjs-vol-" + level); 16774 } 16775 /** 16776 * If `muted` has changed on the player, update the control text 16777 * (`title` attribute on `vjs-mute-control` element and content of 16778 * `vjs-control-text` element). 16779 * 16780 * @private 16781 */ 16782 ; 16783 16784 _proto.updateControlText_ = function updateControlText_() { 16785 var soundOff = this.player_.muted() || this.player_.volume() === 0; 16786 var text = soundOff ? 'Unmute' : 'Mute'; 16787 16788 if (this.controlText() !== text) { 16789 this.controlText(text); 16790 } 16791 }; 16792 16793 return MuteToggle; 16794 }(Button); 16795 /** 16796 * The text that should display over the `MuteToggle`s controls. Added for localization. 16797 * 16798 * @type {string} 16799 * @private 16800 */ 16801 16802 16803 MuteToggle.prototype.controlText_ = 'Mute'; 16804 Component.registerComponent('MuteToggle', MuteToggle); 16805 16806 /** 16807 * A Component to contain the MuteToggle and VolumeControl so that 16808 * they can work together. 16809 * 16810 * @extends Component 16811 */ 16812 16813 var VolumePanel = 16814 /*#__PURE__*/ 16815 function (_Component) { 16816 inheritsLoose(VolumePanel, _Component); 16817 16818 /** 16819 * Creates an instance of this class. 16820 * 16821 * @param {Player} player 16822 * The `Player` that this class should be attached to. 16823 * 16824 * @param {Object} [options={}] 16825 * The key/value store of player options. 16826 */ 16827 function VolumePanel(player, options) { 16828 var _this; 16829 16830 if (options === void 0) { 16831 options = {}; 16832 } 16833 16834 if (typeof options.inline !== 'undefined') { 16835 options.inline = options.inline; 16836 } else { 16837 options.inline = true; 16838 } // pass the inline option down to the VolumeControl as vertical if 16839 // the VolumeControl is on. 16840 16841 16842 if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { 16843 options.volumeControl = options.volumeControl || {}; 16844 options.volumeControl.vertical = !options.inline; 16845 } 16846 16847 _this = _Component.call(this, player, options) || this; 16848 16849 _this.on(player, ['loadstart'], _this.volumePanelState_); 16850
16851 _this.on(_this.muteToggle, 'keyup', _this.handleKeyPress); 16852 16853 _this.on(_this.volumeControl, 'keyup', _this.handleVolumeControlKeyUp); 16854 16855 _this.on('keydown', _this.handleKeyPress); 16856 16857 _this.on('mouseover', _this.handleMouseOver); 16858 16859 _this.on('mouseout', _this.handleMouseOut); // while the slider is active (the mouse has been pressed down and 16860 // is dragging) we do not want to hide the VolumeBar 16861 16862 16863 _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); 16864 16865 _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); 16866 16867 return _this; 16868 } 16869 /** 16870 * Add vjs-slider-active class to the VolumePanel 16871 * 16872 * @listens VolumeControl#slideractive 16873 * @private 16874 */ 16875 16876 16877 var _proto = VolumePanel.prototype; 16878 16879 _proto.sliderActive_ = function sliderActive_() { 16880 this.addClass('vjs-slider-active'); 16881 } 16882 /** 16883 * Removes vjs-slider-active class to the VolumePanel 16884 * 16885 * @listens VolumeControl#sliderinactive 16886 * @private 16887 */ 16888 ; 16889 16890 _proto.sliderInactive_ = function sliderInactive_() { 16891 this.removeClass('vjs-slider-active'); 16892 } 16893 /** 16894 * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel 16895 * depending on MuteToggle and VolumeControl state 16896 * 16897 * @listens Player#loadstart 16898 * @private 16899 */ 16900 ; 16901 16902 _proto.volumePanelState_ = function volumePanelState_() { 16903 // hide volume panel if neither volume control or mute toggle 16904 // are displayed 16905 if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) { 16906 this.addClass('vjs-hidden'); 16907 } // if only mute toggle is visible we don't want 16908 // volume panel expanding when hovered or active 16909 16910 16911 if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) { 16912 this.addClass('vjs-mute-toggle-only'); 16913 } 16914 } 16915 /** 16916 * Create the `Component`'s DOM element 16917 * 16918 * @return {Element} 16919 * The element that was created. 16920 */ 16921 ; 16922 16923 _proto.createEl = function createEl() { 16924 var orientationClass = 'vjs-volume-panel-horizontal'; 16925 16926 if (!this.options_.inline) { 16927 orientationClass = 'vjs-volume-panel-vertical'; 16928 } 16929 16930 return _Component.prototype.createEl.call(this, 'div', { 16931 className: "vjs-volume-panel vjs-control " + orientationClass 16932 }); 16933 } 16934 /** 16935 * Dispose of the `volume-panel` and all child components. 16936 */ 16937 ; 16938 16939 _proto.dispose = function dispose() { 16940 this.handleMouseOut(); 16941 16942 _Component.prototype.dispose.call(this); 16943 } 16944 /** 16945 * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes 16946 * the volume panel and sets focus on `MuteToggle`. 16947 * 16948 * @param {EventTarget~Event} event 16949 * The `keyup` event that caused this function to be called. 16950 * 16951 * @listens keyup 16952 */ 16953 ; 16954 16955 _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) { 16956 if (keycode.isEventKey(event, 'Esc')) { 16957 this.muteToggle.focus(); 16958 } 16959 } 16960 /** 16961 * This gets called when a `VolumePanel` gains hover via a `mouseover` event. 16962 * Turns on listening for `mouseover` event. When they happen it 16963 * calls `this.handleMouseOver`. 16964 * 16965 * @param {EventTarget~Event} event 16966 * The `mouseover` event that caused this function to be called. 16967 * 16968 * @listens mouseover 16969 */ 16970 ; 16971 16972 _proto.handleMouseOver = function handleMouseOver(event) { 16973 this.addClass('vjs-hover'); 16974 on(document, 'keyup', bind(this, this.handleKeyPress)); 16975 } 16976 /** 16977 * This gets called when a `VolumePanel` gains hover via a `mouseout` event. 16978 * Turns on listening for `mouseout` event. When they happen it 16979 * calls `this.handleMouseOut`. 16980 * 16981 * @param {EventTarget~Event} event 16982 * The `mouseout` event that caused this function to be called. 16983 * 16984 * @listens mouseout 16985 */ 16986 ; 16987 16988 _proto.handleMouseOut = function handleMouseOut(event) { 16989 this.removeClass('vjs-hover'); 16990 off(document, 'keyup', bind(this, this.handleKeyPress)); 16991 } 16992 /** 16993 * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`, 16994 * looking for ESC, which hides the `VolumeControl`. 16995 * 16996 * @param {EventTarget~Event} event 16997 * The keypress that triggered this event. 16998 * 16999 * @listens keydown | keyup 17000 */ 17001 ; 17002 17003 _proto.handleKeyPress = function handleKeyPress(event) { 17004 if (keycode.isEventKey(event, 'Esc')) { 17005 this.handleMouseOut(); 17006 } 17007 }; 17008 17009 return VolumePanel; 17010 }(Component); 17011 /** 17012 * Default options for the `VolumeControl` 17013 * 17014 * @type {Object} 17015 * @private 17016 */ 17017 17018 17019 VolumePanel.prototype.options_ = { 17020 children: ['muteToggle', 'volumeControl'] 17021 }; 17022 Component.registerComponent('VolumePanel', VolumePanel); 17023 17024 /** 17025 * The Menu component is used to build popup menus, including subtitle and 17026 * captions selection menus. 17027 * 17028 * @extends Component 17029 */ 17030 17031 var Menu = 17032 /*#__PURE__*/ 17033 function (_Component) { 17034 inheritsLoose(Menu, _Component); 17035 17036 /** 17037 * Create an instance of this class. 17038 * 17039 * @param {Player} player 17040 * the player that this component should attach to 17041 * 17042 * @param {Object} [options] 17043 * Object of option names and values 17044 * 17045 */ 17046 function Menu(player, options) { 17047 var _this; 17048 17049 _this = _Component.call(this, player, options) || this; 17050 17051 if (options) { 17052 _this.menuButton_ = options.menuButton; 17053 } 17054 17055 _this.focusedChild_ = -1; 17056 17057 _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container. 17058 17059 17060 _this.boundHandleBlur_ = bind(assertThisInitialized(_this), _this.handleBlur); 17061 _this.boundHandleTapClick_ = bind(assertThisInitialized(_this), _this.handleTapClick); 17062 return _this; 17063 } 17064 /** 17065 * Add event listeners to the {@link MenuItem}. 17066 * 17067 * @param {Object} component 17068 * The instance of the `MenuItem` to add listeners to. 17069 * 17070 */ 17071 17072
17073 var _proto = Menu.prototype; 17074 17075 _proto.addEventListenerForItem = function addEventListenerForItem(component) { 17076 if (!(component instanceof Component)) { 17077 return; 17078 } 17079 17080 this.on(component, 'blur', this.boundHandleBlur_); 17081 this.on(component, ['tap', 'click'], this.boundHandleTapClick_); 17082 } 17083 /** 17084 * Remove event listeners from the {@link MenuItem}. 17085 * 17086 * @param {Object} component 17087 * The instance of the `MenuItem` to remove listeners. 17088 * 17089 */ 17090 ; 17091 17092 _proto.removeEventListenerForItem = function removeEventListenerForItem(component) { 17093 if (!(component instanceof Component)) { 17094 return; 17095 } 17096 17097 this.off(component, 'blur', this.boundHandleBlur_); 17098 this.off(component, ['tap', 'click'], this.boundHandleTapClick_); 17099 } 17100 /** 17101 * This method will be called indirectly when the component has been added 17102 * before the component adds to the new menu instance by `addItem`. 17103 * In this case, the original menu instance will remove the component 17104 * by calling `removeChild`. 17105 * 17106 * @param {Object} component 17107 * The instance of the `MenuItem` 17108 */ 17109 ; 17110 17111 _proto.removeChild = function removeChild(component) { 17112 if (typeof component === 'string') { 17113 component = this.getChild(component); 17114 } 17115 17116 this.removeEventListenerForItem(component); 17117 17118 _Component.prototype.removeChild.call(this, component); 17119 } 17120 /** 17121 * Add a {@link MenuItem} to the menu. 17122 * 17123 * @param {Object|string} component 17124 * The name or instance of the `MenuItem` to add. 17125 * 17126 */ 17127 ; 17128 17129 _proto.addItem = function addItem(component) { 17130 var childComponent = this.addChild(component); 17131 17132 if (childComponent) { 17133 this.addEventListenerForItem(childComponent); 17134 } 17135 } 17136 /** 17137 * Create the `Menu`s DOM element. 17138 * 17139 * @return {Element} 17140 * the element that was created 17141 */ 17142 ; 17143 17144 _proto.createEl = function createEl$1() { 17145 var contentElType = this.options_.contentElType || 'ul'; 17146 this.contentEl_ = createEl(contentElType, { 17147 className: 'vjs-menu-content' 17148 }); 17149 this.contentEl_.setAttribute('role', 'menu'); 17150 17151 var el = _Component.prototype.createEl.call(this, 'div', { 17152 append: this.contentEl_, 17153 className: 'vjs-menu' 17154 }); 17155 17156 el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons, 17157 // where a click on the parent is significant 17158 17159 on(el, 'click', function (event) { 17160 event.preventDefault(); 17161 event.stopImmediatePropagation(); 17162 }); 17163 return el; 17164 }; 17165 17166 _proto.dispose = function dispose() { 17167 this.contentEl_ = null; 17168 this.boundHandleBlur_ = null; 17169 this.boundHandleTapClick_ = null; 17170 17171 _Component.prototype.dispose.call(this); 17172 } 17173 /** 17174 * Called when a `MenuItem` loses focus. 17175 * 17176 * @param {EventTarget~Event} event 17177 * The `blur` event that caused this function to be called. 17178 * 17179 * @listens blur 17180 */ 17181 ; 17182 17183 _proto.handleBlur = function handleBlur(event) { 17184 var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu 17185 17186 if (!this.children().some(function (element) { 17187 return element.el() === relatedTarget; 17188 })) { 17189 var btn = this.menuButton_; 17190 17191 if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) { 17192 btn.unpressButton(); 17193 } 17194 } 17195 } 17196 /** 17197 * Called when a `MenuItem` gets clicked or tapped. 17198 * 17199 * @param {EventTarget~Event} event 17200 * The `click` or `tap` event that caused this function to be called. 17201 * 17202 * @listens click,tap 17203 */ 17204 ; 17205 17206 _proto.handleTapClick = function handleTapClick(event) { 17207 // Unpress the associated MenuButton, and move focus back to it 17208 if (this.menuButton_) { 17209 this.menuButton_.unpressButton(); 17210 var childComponents = this.children();
17211 17212 if (!Array.isArray(childComponents)) { 17213 return; 17214 } 17215 17216 var foundComponent = childComponents.filter(function (component) { 17217 return component.el() === event.target; 17218 })[0]; 17219 17220 if (!foundComponent) { 17221 return; 17222 } // don't focus menu button if item is a caption settings item 17223 // because focus will move elsewhere 17224 17225 17226 if (foundComponent.name() !== 'CaptionSettingsMenuItem') { 17227 this.menuButton_.focus(); 17228 } 17229 } 17230 } 17231 /** 17232 * Handle a `keydown` event on this menu. This listener is added in the constructor. 17233 * 17234 * @param {EventTarget~Event} event 17235 * A `keydown` event that happened on the menu. 17236 * 17237 * @listens keydown 17238 */ 17239 ; 17240 17241 _proto.handleKeyDown = function handleKeyDown(event) { 17242 // Left and Down Arrows 17243 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 17244 event.preventDefault(); 17245 event.stopPropagation(); 17246 this.stepForward(); // Up and Right Arrows 17247 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 17248 event.preventDefault(); 17249 event.stopPropagation(); 17250 this.stepBack(); 17251 } 17252 } 17253 /** 17254 * Move to next (lower) menu item for keyboard users. 17255 */ 17256 ; 17257 17258 _proto.stepForward = function stepForward() { 17259 var stepChild = 0; 17260 17261 if (this.focusedChild_ !== undefined) { 17262 stepChild = this.focusedChild_ + 1; 17263 } 17264 17265 this.focus(stepChild); 17266 } 17267 /** 17268 * Move to previous (higher) menu item for keyboard users. 17269 */ 17270 ; 17271 17272 _proto.stepBack = function stepBack() { 17273 var stepChild = 0; 17274 17275 if (this.focusedChild_ !== undefined) { 17276 stepChild = this.focusedChild_ - 1; 17277 } 17278 17279 this.focus(stepChild); 17280 } 17281 /** 17282 * Set focus on a {@link MenuItem} in the `Menu`. 17283 * 17284 * @param {Object|string} [item=0] 17285 * Index of child item set focus on. 17286 */ 17287 ; 17288 17289 _proto.focus = function focus(item) { 17290 if (item === void 0) { 17291 item = 0; 17292 } 17293 17294 var children = this.children().slice(); 17295 var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); 17296 17297 if (haveTitle) { 17298 children.shift(); 17299 } 17300 17301 if (children.length > 0) { 17302 if (item < 0) { 17303 item = 0; 17304 } else if (item >= children.length) { 17305 item = children.length - 1; 17306 } 17307 17308 this.focusedChild_ = item; 17309 children[item].el_.focus(); 17310 } 17311 }; 17312 17313 return Menu; 17314 }(Component); 17315 17316 Component.registerComponent('Menu', Menu); 17317 17318 /** 17319 * A `MenuButton` class for any popup {@link Menu}. 17320 * 17321 * @extends Component 17322 */ 17323 17324 var MenuButton = 17325 /*#__PURE__*/ 17326 function (_Component) { 17327 inheritsLoose(MenuButton, _Component); 17328 17329 /** 17330 * Creates an instance of this class. 17331 * 17332 * @param {Player} player 17333 * The `Player` that this class should be attached to. 17334 * 17335 * @param {Object} [options={}] 17336 * The key/value store of player options. 17337 */ 17338 function MenuButton(player, options) { 17339 var _this; 17340 17341 if (options === void 0) { 17342 options = {}; 17343 } 17344 17345 _this = _Component.call(this, player, options) || this; 17346 _this.menuButton_ = new Button(player, options); 17347 17348 _this.menuButton_.controlText(_this.controlText_); 17349 17350 _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper 17351 17352 17353 var buttonClass = Button.prototype.buildCSSClass(); 17354 _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; 17355 17356 _this.menuButton_.removeClass('vjs-control'); 17357 17358 _this.addChild(_this.menuButton_); 17359 17360 _this.update(); 17361 17362 _this.enabled_ = true; 17363 17364 _this.on(_this.menuButton_, 'tap', _this.handleClick); 17365 17366 _this.on(_this.menuButton_, 'click', _this.handleClick); 17367 17368 _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown); 17369 17370 _this.on(_this.menuButton_, 'mouseenter', function () { 17371 _this.addClass('vjs-hover'); 17372 17373 _this.menu.show(); 17374 17375 on(document, 'keyup', bind(assertThisInitialized(_this), _this.handleMenuKeyUp)); 17376 }); 17377 17378 _this.on('mouseleave', _this.handleMouseLeave); 17379 17380 _this.on('keydown', _this.handleSubmenuKeyDown); 17381 17382 return _this; 17383 } 17384 /** 17385 * Update the menu based on the current state of its items. 17386 */ 17387 17388
vendor: 4,553 bytes, lines 17389-17548
17389 var _proto = MenuButton.prototype; 17390 17391 _proto.update = function update() { 17392 var menu = this.createMenu(); 17393 17394 if (this.menu) { 17395 this.menu.dispose(); 17396 this.removeChild(this.menu); 17397 } 17398 17399 this.menu = menu; 17400 this.addChild(menu); 17401 /** 17402 * Track the state of the menu button 17403 * 17404 * @type {Boolean} 17405 * @private 17406 */ 17407 17408 this.buttonPressed_ = false; 17409 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 17410 17411 if (this.items && this.items.length <= this.hideThreshold_) { 17412 this.hide(); 17413 } else { 17414 this.show(); 17415 } 17416 } 17417 /** 17418 * Create the menu and add all items to it. 17419 * 17420 * @return {Menu} 17421 * The constructed menu 17422 */ 17423 ; 17424 17425 _proto.createMenu = function createMenu() { 17426 var menu = new Menu(this.player_, { 17427 menuButton: this 17428 }); 17429 /** 17430 * Hide the menu if the number of items is less than or equal to this threshold. This defaults 17431 * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list 17432 * it here because every time we run `createMenu` we need to reset the value. 17433 * 17434 * @protected 17435 * @type {Number} 17436 */ 17437 17438 this.hideThreshold_ = 0; // Add a title list item to the top 17439 17440 if (this.options_.title) { 17441 var titleEl = createEl('li', { 17442 className: 'vjs-menu-title', 17443 innerHTML: toTitleCase(this.options_.title), 17444 tabIndex: -1 17445 }); 17446 this.hideThreshold_ += 1; 17447 var titleComponent = new Component(this.player_, { 17448 el: titleEl 17449 }); 17450 menu.addItem(titleComponent); 17451 } 17452 17453 this.items = this.createItems(); 17454 17455 if (this.items) { 17456 // Add menu items to the menu 17457 for (var i = 0; i < this.items.length; i++) { 17458 menu.addItem(this.items[i]); 17459 } 17460 } 17461 17462 return menu; 17463 } 17464 /** 17465 * Create the list of menu items. Specific to each subclass. 17466 * 17467 * @abstract 17468 */ 17469 ; 17470 17471 _proto.createItems = function createItems() {} 17472 /** 17473 * Create the `MenuButtons`s DOM element. 17474 * 17475 * @return {Element} 17476 * The element that gets created. 17477 */ 17478 ; 17479 17480 _proto.createEl = function createEl() { 17481 return _Component.prototype.createEl.call(this, 'div', { 17482 className: this.buildWrapperCSSClass() 17483 }, {}); 17484 } 17485 /** 17486 * Allow sub components to stack CSS class names for the wrapper element 17487 * 17488 * @return {string} 17489 * The constructed wrapper DOM `className` 17490 */ 17491 ; 17492 17493 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 17494 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 17495 17496 if (this.options_.inline === true) { 17497 menuButtonClass += '-inline'; 17498 } else { 17499 menuButtonClass += '-popup'; 17500 } // TODO: Fix the CSS so that this isn't necessary 17501 17502 17503 var buttonClass = Button.prototype.buildCSSClass(); 17504 return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this); 17505 } 17506 /** 17507 * Builds the default DOM `className`. 17508 * 17509 * @return {string} 17510 * The DOM `className` for this object. 17511 */ 17512 ; 17513 17514 _proto.buildCSSClass = function buildCSSClass() { 17515 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 17516 17517 if (this.options_.inline === true) { 17518 menuButtonClass += '-inline'; 17519 } else { 17520 menuButtonClass += '-popup'; 17521 } 17522 17523 return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this); 17524 } 17525 /** 17526 * Get or set the localized control text that will be used for accessibility. 17527 * 17528 * > NOTE: This will come from the internal `menuButton_` element. 17529 * 17530 * @param {string} [text] 17531 * Control text for element. 17532 * 17533 * @param {Element} [el=this.menuButton_.el()] 17534 * Element to set the title on. 17535 * 17536 * @return {string} 17537 * - The control text when getting 17538 */ 17539 ; 17540 17541 _proto.controlText = function controlText(text, el) { 17542 if (el === void 0) { 17543 el = this.menuButton_.el(); 17544 } 17545 17546 return this.menuButton_.controlText(text, el); 17547 } 17548 /**
17549 * Dispose of the `menu-button` and all child components. 17550 */ 17551 ; 17552 17553 _proto.dispose = function dispose() { 17554 this.handleMouseLeave(); 17555 17556 _Component.prototype.dispose.call(this); 17557 } 17558 /** 17559 * Handle a click on a `MenuButton`. 17560 * See {@link ClickableComponent#handleClick} for instances where this is called. 17561 * 17562 * @param {EventTarget~Event} event 17563 * The `keydown`, `tap`, or `click` event that caused this function to be 17564 * called. 17565 * 17566 * @listens tap 17567 * @listens click 17568 */ 17569 ; 17570 17571 _proto.handleClick = function handleClick(event) { 17572 if (this.buttonPressed_) { 17573 this.unpressButton(); 17574 } else { 17575 this.pressButton(); 17576 } 17577 } 17578 /** 17579 * Handle `mouseleave` for `MenuButton`. 17580 * 17581 * @param {EventTarget~Event} event 17582 * The `mouseleave` event that caused this function to be called. 17583 * 17584 * @listens mouseleave 17585 */ 17586 ; 17587 17588 _proto.handleMouseLeave = function handleMouseLeave(event) { 17589 this.removeClass('vjs-hover'); 17590 off(document, 'keyup', bind(this, this.handleMenuKeyUp)); 17591 } 17592 /** 17593 * Set the focus to the actual button, not to this element 17594 */ 17595 ; 17596 17597 _proto.focus = function focus() { 17598 this.menuButton_.focus(); 17599 } 17600 /** 17601 * Remove the focus from the actual button, not this element 17602 */ 17603 ; 17604 17605 _proto.blur = function blur() { 17606 this.menuButton_.blur(); 17607 } 17608 /** 17609 * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See 17610 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 17611 * 17612 * @param {EventTarget~Event} event 17613 * The `keydown` event that caused this function to be called. 17614 * 17615 * @listens keydown 17616 */ 17617 ; 17618 17619 _proto.handleKeyDown = function handleKeyDown(event) { 17620 // Escape or Tab unpress the 'button' 17621 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17622 if (this.buttonPressed_) { 17623 this.unpressButton(); 17624 } // Don't preventDefault for Tab key - we still want to lose focus 17625 17626 17627 if (!keycode.isEventKey(event, 'Tab')) { 17628 event.preventDefault(); // Set focus back to the menu button's button 17629 17630 this.menuButton_.focus(); 17631 } // Up Arrow or Down Arrow also 'press' the button to open the menu 17632 17633 } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) { 17634 if (!this.buttonPressed_) { 17635 event.preventDefault(); 17636 this.pressButton(); 17637 } 17638 } 17639 } 17640 /** 17641 * Handle a `keyup` event on a `MenuButton`. The listener for this is added in 17642 * the constructor. 17643 * 17644 * @param {EventTarget~Event} event 17645 * Key press event 17646 * 17647 * @listens keyup 17648 */ 17649 ; 17650 17651 _proto.handleMenuKeyUp = function handleMenuKeyUp(event) { 17652 // Escape hides popup menu 17653 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17654 this.removeClass('vjs-hover'); 17655 } 17656 } 17657 /** 17658 * This method name now delegates to `handleSubmenuKeyDown`. This means 17659 * anyone calling `handleSubmenuKeyPress` will not see their method calls 17660 * stop working. 17661 * 17662 * @param {EventTarget~Event} event 17663 * The event that caused this function to be called. 17664 */ 17665 ; 17666 17667 _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { 17668 this.handleSubmenuKeyDown(event); 17669 } 17670 /** 17671 * Handle a `keydown` event on a sub-menu. The listener for this is added in 17672 * the constructor. 17673 * 17674 * @param {EventTarget~Event} event 17675 * Key press event 17676 * 17677 * @listens keydown 17678 */ 17679 ; 17680 17681 _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) { 17682 // Escape or Tab unpress the 'button' 17683 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 17684 if (this.buttonPressed_) { 17685 this.unpressButton(); 17686 } // Don't preventDefault for Tab key - we still want to lose focus 17687 17688 17689 if (!keycode.isEventKey(event, 'Tab')) { 17690 event.preventDefault(); // Set focus back to the menu button's button 17691 17692 this.menuButton_.focus(); 17693 } 17694 } 17695 } 17696 /** 17697 * Put the current `MenuButton` into a pressed state. 17698 */ 17699 ; 17700 17701 _proto.pressButton = function pressButton() { 17702 if (this.enabled_) { 17703 this.buttonPressed_ = true; 17704 this.menu.show(); 17705 this.menu.lockShowing(); 17706 this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in 17707 // undesired scrolling behavior when the player is in an iframe 17708 17709 if (IS_IOS && isInFrame()) { 17710 // Return early so that the menu isn't focused 17711 return; 17712 } 17713 17714 this.menu.focus(); 17715 } 17716 } 17717 /** 17718 * Take the current `MenuButton` out of a pressed state. 17719 */ 17720 ; 17721 17722 _proto.unpressButton = function unpressButton() { 17723 if (this.enabled_) { 17724 this.buttonPressed_ = false; 17725 this.menu.unlockShowing(); 17726 this.menu.hide(); 17727 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 17728 } 17729 } 17730 /** 17731 * Disable the `MenuButton`. Don't allow it to be clicked. 17732 */ 17733 ; 17734 17735 _proto.disable = function disable() { 17736 this.unpressButton(); 17737 this.enabled_ = false; 17738 this.addClass('vjs-disabled'); 17739 this.menuButton_.disable(); 17740 } 17741 /** 17742 * Enable the `MenuButton`. Allow it to be clicked. 17743 */ 17744 ; 17745 17746 _proto.enable = function enable() { 17747 this.enabled_ = true; 17748 this.removeClass('vjs-disabled'); 17749 this.menuButton_.enable(); 17750 }; 17751
17752 return MenuButton; 17753 }(Component); 17754 17755 Component.registerComponent('MenuButton', MenuButton); 17756 17757 /** 17758 * The base class for buttons that toggle specific track types (e.g. subtitles). 17759 * 17760 * @extends MenuButton 17761 */ 17762 17763 var TrackButton = 17764 /*#__PURE__*/ 17765 function (_MenuButton) { 17766 inheritsLoose(TrackButton, _MenuButton); 17767 17768 /** 17769 * Creates an instance of this class. 17770 * 17771 * @param {Player} player 17772 * The `Player` that this class should be attached to. 17773 * 17774 * @param {Object} [options] 17775 * The key/value store of player options. 17776 */ 17777 function TrackButton(player, options) { 17778 var _this; 17779 17780 var tracks = options.tracks; 17781 _this = _MenuButton.call(this, player, options) || this; 17782 17783 if (_this.items.length <= 1) { 17784 _this.hide(); 17785 } 17786 17787 if (!tracks) { 17788 return assertThisInitialized(_this); 17789 } 17790 17791 var updateHandler = bind(assertThisInitialized(_this), _this.update); 17792 tracks.addEventListener('removetrack', updateHandler); 17793 tracks.addEventListener('addtrack', updateHandler); 17794 17795 _this.player_.on('ready', updateHandler); 17796 17797 _this.player_.on('dispose', function () { 17798 tracks.removeEventListener('removetrack', updateHandler); 17799 tracks.removeEventListener('addtrack', updateHandler); 17800 }); 17801 17802 return _this; 17803 } 17804 17805 return TrackButton; 17806 }(MenuButton); 17807 17808 Component.registerComponent('TrackButton', TrackButton); 17809 17810 /** 17811 * @file menu-keys.js 17812 */ 17813 17814 /** 17815 * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`) 17816 * Note that 'Enter' and 'Space' are not included here (otherwise they would 17817 * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable) 17818 * @typedef MenuKeys 17819 * @array 17820 */ 17821 var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left']; 17822 17823 /** 17824 * The component for a menu item. `<li>` 17825 * 17826 * @extends ClickableComponent 17827 */ 17828 17829 var MenuItem = 17830 /*#__PURE__*/ 17831 function (_ClickableComponent) { 17832 inheritsLoose(MenuItem, _ClickableComponent); 17833 17834 /** 17835 * Creates an instance of the this class. 17836 * 17837 * @param {Player} player 17838 * The `Player` that this class should be attached to. 17839 * 17840 * @param {Object} [options={}] 17841 * The key/value store of player options. 17842 * 17843 */ 17844 function MenuItem(player, options) { 17845 var _this; 17846 17847 _this = _ClickableComponent.call(this, player, options) || this; 17848 _this.selectable = options.selectable; 17849 _this.isSelected_ = options.selected || false; 17850 _this.multiSelectable = options.multiSelectable; 17851 17852 _this.selected(_this.isSelected_); 17853 17854 if (_this.selectable) { 17855 if (_this.multiSelectable) { 17856 _this.el_.setAttribute('role', 'menuitemcheckbox'); 17857 } else { 17858 _this.el_.setAttribute('role', 'menuitemradio'); 17859 } 17860 } else { 17861 _this.el_.setAttribute('role', 'menuitem'); 17862 } 17863 17864 return _this; 17865 } 17866 /** 17867 * Create the `MenuItem's DOM element 17868 * 17869 * @param {string} [type=li] 17870 * Element's node type, not actually used, always set to `li`. 17871 * 17872 * @param {Object} [props={}] 17873 * An object of properties that should be set on the element 17874 * 17875 * @param {Object} [attrs={}] 17876 * An object of attributes that should be set on the element 17877 * 17878 * @return {Element} 17879 * The element that gets created. 17880 */ 17881 17882 17883 var _proto = MenuItem.prototype; 17884 17885 _proto.createEl = function createEl(type, props, attrs) { 17886 // The control is textual, not just an icon 17887 this.nonIconControl = true; 17888 return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ 17889 className: 'vjs-menu-item', 17890 innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>", 17891 tabIndex: -1 17892 }, props), attrs); 17893 } 17894 /** 17895 * Ignore keys which are used by the menu, but pass any other ones up. See 17896 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 17897 * 17898 * @param {EventTarget~Event} event
17899 * The `keydown` event that caused this function to be called. 17900 * 17901 * @listens keydown 17902 */ 17903 ; 17904 17905 _proto.handleKeyDown = function handleKeyDown(event) { 17906 if (!MenuKeys.some(function (key) { 17907 return keycode.isEventKey(event, key); 17908 })) { 17909 // Pass keydown handling up for unused keys 17910 _ClickableComponent.prototype.handleKeyDown.call(this, event); 17911 } 17912 } 17913 /** 17914 * Any click on a `MenuItem` puts it into the selected state. 17915 * See {@link ClickableComponent#handleClick} for instances where this is called. 17916 * 17917 * @param {EventTarget~Event} event 17918 * The `keydown`, `tap`, or `click` event that caused this function to be 17919 * called. 17920 * 17921 * @listens tap 17922 * @listens click 17923 */ 17924 ; 17925 17926 _proto.handleClick = function handleClick(event) { 17927 this.selected(true); 17928 } 17929 /** 17930 * Set the state for this menu item as selected or not. 17931 * 17932 * @param {boolean} selected 17933 * if the menu item is selected or not 17934 */ 17935 ; 17936 17937 _proto.selected = function selected(_selected) { 17938 if (this.selectable) { 17939 if (_selected) { 17940 this.addClass('vjs-selected'); 17941 this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers, 17942 // so indicate selected state to screen reader in the control text. 17943 17944 this.controlText(', selected'); 17945 this.isSelected_ = true; 17946 } else { 17947 this.removeClass('vjs-selected'); 17948 this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader 17949 17950 this.controlText(''); 17951 this.isSelected_ = false; 17952 } 17953 } 17954 }; 17955 17956 return MenuItem; 17957 }(ClickableComponent); 17958 17959 Component.registerComponent('MenuItem', MenuItem); 17960 17961 /** 17962 * The specific menu item type for selecting a language within a text track kind 17963 * 17964 * @extends MenuItem 17965 */ 17966 17967 var TextTrackMenuItem = 17968 /*#__PURE__*/ 17969 function (_MenuItem) { 17970 inheritsLoose(TextTrackMenuItem, _MenuItem); 17971 17972 /** 17973 * Creates an instance of this class. 17974 * 17975 * @param {Player} player 17976 * The `Player` that this class should be attached to. 17977 * 17978 * @param {Object} [options] 17979 * The key/value store of player options. 17980 */ 17981 function TextTrackMenuItem(player, options) { 17982 var _this; 17983 17984 var track = options.track; 17985 var tracks = player.textTracks(); // Modify options for parent MenuItem class's init. 17986 17987 options.label = track.label || track.language || 'Unknown'; 17988 options.selected = track.mode === 'showing'; 17989 _this = _MenuItem.call(this, player, options) || this; 17990 _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter 17991 // out empty kinds. 17992 17993 _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean); 17994 17995 var changeHandler = function changeHandler() { 17996 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 17997 args[_key] = arguments[_key]; 17998 } 17999 18000 _this.handleTracksChange.apply(assertThisInitialized(_this), args); 18001 }; 18002 18003 var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() { 18004 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 18005 args[_key2] = arguments[_key2]; 18006 } 18007 18008 _this.handleSelectedLanguageChange.apply(assertThisInitialized(_this), args); 18009 }; 18010 18011 player.on(['loadstart', 'texttrackchange'], changeHandler); 18012 tracks.addEventListener('change', changeHandler); 18013 tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 18014 18015 _this.on('dispose', function () { 18016 player.off(['loadstart', 'texttrackchange'], changeHandler); 18017 tracks.removeEventListener('change', changeHandler); 18018 tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 18019 }); // iOS7 doesn't dispatch change events to TextTrackLists when an 18020 // associated track's mode changes. Without something like 18021 // Object.observe() (also not present on iOS7), it's not 18022 // possible to detect changes to the mode attribute and polyfill 18023 // the change event. As a poor substitute, we manually dispatch 18024 // change events whenever the controls modify the mode. 18025 18026 18027 if (tracks.onchange === undefined) { 18028 var event; 18029 18030 _this.on(['tap', 'click'], function () { 18031 if (typeof window$1.Event !== 'object') { 18032 // Android 2.3 throws an Illegal Constructor error for window.Event 18033 try { 18034 event = new window$1.Event('change'); 18035 } catch (err) {// continue regardless of error 18036 } 18037 } 18038 18039 if (!event) { 18040 event = document.createEvent('Event'); 18041 event.initEvent('change', true, true); 18042 } 18043 18044 tracks.dispatchEvent(event); 18045 }); 18046 } // set the default state based on current tracks 18047 18048 18049 _this.handleTracksChange(); 18050 18051 return _this; 18052 } 18053 /** 18054 * This gets called when an `TextTrackMenuItem` is "clicked". See 18055 * {@link ClickableComponent} for more detailed information on what a click can be. 18056 * 18057 * @param {EventTarget~Event} event
vendor: 8,459 bytes, lines 18058-18334
18058 * The `keydown`, `tap`, or `click` event that caused this function to be 18059 * called. 18060 * 18061 * @listens tap 18062 * @listens click 18063 */ 18064 18065 18066 var _proto = TextTrackMenuItem.prototype; 18067 18068 _proto.handleClick = function handleClick(event) { 18069 var referenceTrack = this.track; 18070 var tracks = this.player_.textTracks(); 18071 18072 _MenuItem.prototype.handleClick.call(this, event); 18073 18074 if (!tracks) { 18075 return; 18076 } 18077 18078 for (var i = 0; i < tracks.length; i++) { 18079 var track = tracks[i]; // If the track from the text tracks list is not of the right kind, 18080 // skip it. We do not want to affect tracks of incompatible kind(s). 18081 18082 if (this.kinds.indexOf(track.kind) === -1) { 18083 continue; 18084 } // If this text track is the component's track and it is not showing, 18085 // set it to showing. 18086 18087 18088 if (track === referenceTrack) { 18089 if (track.mode !== 'showing') { 18090 track.mode = 'showing'; 18091 } // If this text track is not the component's track and it is not 18092 // disabled, set it to disabled. 18093 18094 } else if (track.mode !== 'disabled') { 18095 track.mode = 'disabled'; 18096 } 18097 } 18098 } 18099 /** 18100 * Handle text track list change 18101 * 18102 * @param {EventTarget~Event} event 18103 * The `change` event that caused this function to be called. 18104 * 18105 * @listens TextTrackList#change 18106 */ 18107 ; 18108 18109 _proto.handleTracksChange = function handleTracksChange(event) { 18110 var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause 18111 // screen readers to read the appended control text unnecessarily 18112 18113 if (shouldBeSelected !== this.isSelected_) { 18114 this.selected(shouldBeSelected); 18115 } 18116 }; 18117 18118 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 18119 if (this.track.mode === 'showing') { 18120 var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language 18121 18122 if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { 18123 return; 18124 } 18125 18126 this.player_.cache_.selectedLanguage = { 18127 enabled: true, 18128 language: this.track.language, 18129 kind: this.track.kind 18130 }; 18131 } 18132 }; 18133 18134 _proto.dispose = function dispose() { 18135 // remove reference to track object on dispose 18136 this.track = null; 18137 18138 _MenuItem.prototype.dispose.call(this); 18139 }; 18140 18141 return TextTrackMenuItem; 18142 }(MenuItem); 18143 18144 Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); 18145 18146 /** 18147 * A special menu item for turning of a specific type of text track 18148 * 18149 * @extends TextTrackMenuItem 18150 */ 18151 18152 var OffTextTrackMenuItem = 18153 /*#__PURE__*/ 18154 function (_TextTrackMenuItem) { 18155 inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem); 18156 18157 /** 18158 * Creates an instance of this class. 18159 * 18160 * @param {Player} player 18161 * The `Player` that this class should be attached to. 18162 * 18163 * @param {Object} [options] 18164 * The key/value store of player options. 18165 */ 18166 function OffTextTrackMenuItem(player, options) { 18167 // Create pseudo track info 18168 // Requires options['kind'] 18169 options.track = { 18170 player: player, 18171 // it is no longer necessary to store `kind` or `kinds` on the track itself 18172 // since they are now stored in the `kinds` property of all instances of 18173 // TextTrackMenuItem, but this will remain for backwards compatibility 18174 kind: options.kind, 18175 kinds: options.kinds, 18176 "default": false, 18177 mode: 'disabled' 18178 }; 18179 18180 if (!options.kinds) { 18181 options.kinds = [options.kind]; 18182 } 18183 18184 if (options.label) { 18185 options.track.label = options.label; 18186 } else { 18187 options.track.label = options.kinds.join(' and ') + ' off'; 18188 } // MenuItem is selectable 18189 18190 18191 options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 18192 18193 options.multiSelectable = false; 18194 return _TextTrackMenuItem.call(this, player, options) || this; 18195 } 18196 /** 18197 * Handle text track change 18198 * 18199 * @param {EventTarget~Event} event 18200 * The event that caused this function to run 18201 */ 18202 18203 18204 var _proto = OffTextTrackMenuItem.prototype; 18205 18206 _proto.handleTracksChange = function handleTracksChange(event) { 18207 var tracks = this.player().textTracks(); 18208 var shouldBeSelected = true; 18209 18210 for (var i = 0, l = tracks.length; i < l; i++) { 18211 var track = tracks[i]; 18212 18213 if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { 18214 shouldBeSelected = false; 18215 break; 18216 } 18217 } // Prevent redundant selected() calls because they may cause 18218 // screen readers to read the appended control text unnecessarily 18219 18220 18221 if (shouldBeSelected !== this.isSelected_) { 18222 this.selected(shouldBeSelected); 18223 } 18224 }; 18225 18226 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 18227 var tracks = this.player().textTracks(); 18228 var allHidden = true; 18229 18230 for (var i = 0, l = tracks.length; i < l; i++) { 18231 var track = tracks[i]; 18232 18233 if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { 18234 allHidden = false; 18235 break; 18236 } 18237 } 18238 18239 if (allHidden) { 18240 this.player_.cache_.selectedLanguage = { 18241 enabled: false 18242 }; 18243 } 18244 }; 18245 18246 return OffTextTrackMenuItem; 18247 }(TextTrackMenuItem); 18248 18249 Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); 18250 18251 /** 18252 * The base class for buttons that toggle specific text track types (e.g. subtitles) 18253 * 18254 * @extends MenuButton 18255 */ 18256 18257 var TextTrackButton = 18258 /*#__PURE__*/ 18259 function (_TrackButton) { 18260 inheritsLoose(TextTrackButton, _TrackButton); 18261 18262 /** 18263 * Creates an instance of this class. 18264 * 18265 * @param {Player} player 18266 * The `Player` that this class should be attached to. 18267 * 18268 * @param {Object} [options={}] 18269 * The key/value store of player options. 18270 */ 18271 function TextTrackButton(player, options) { 18272 if (options === void 0) { 18273 options = {}; 18274 } 18275 18276 options.tracks = player.textTracks(); 18277 return _TrackButton.call(this, player, options) || this; 18278 } 18279 /** 18280 * Create a menu item for each text track 18281 * 18282 * @param {TextTrackMenuItem[]} [items=[]] 18283 * Existing array of items to use during creation 18284 * 18285 * @return {TextTrackMenuItem[]} 18286 * Array of menu items that were created 18287 */ 18288 18289 18290 var _proto = TextTrackButton.prototype; 18291 18292 _proto.createItems = function createItems(items, TrackMenuItem) { 18293 if (items === void 0) { 18294 items = []; 18295 } 18296 18297 if (TrackMenuItem === void 0) { 18298 TrackMenuItem = TextTrackMenuItem; 18299 } 18300 18301 // Label is an override for the [track] off label 18302 // USed to localise captions/subtitles 18303 var label; 18304 18305 if (this.label_) { 18306 label = this.label_ + " off"; 18307 } // Add an OFF menu item to turn all tracks off 18308 18309 18310 items.push(new OffTextTrackMenuItem(this.player_, { 18311 kinds: this.kinds_, 18312 kind: this.kind_, 18313 label: label 18314 })); 18315 this.hideThreshold_ += 1; 18316 var tracks = this.player_.textTracks(); 18317 18318 if (!Array.isArray(this.kinds_)) { 18319 this.kinds_ = [this.kind_]; 18320 } 18321 18322 for (var i = 0; i < tracks.length; i++) { 18323 var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label 18324 18325 if (this.kinds_.indexOf(track.kind) > -1) { 18326 var item = new TrackMenuItem(this.player_, { 18327 track: track, 18328 kinds: this.kinds_, 18329 kind: this.kind_, 18330 // MenuItem is selectable 18331 selectable: true, 18332 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 18333 multiSelectable: false 18334 });
18335 item.addClass("vjs-" + track.kind + "-menu-item"); 18336 items.push(item); 18337 } 18338 } 18339 18340 return items; 18341 }; 18342 18343 return TextTrackButton; 18344 }(TrackButton); 18345 18346 Component.registerComponent('TextTrackButton', TextTrackButton); 18347 18348 /** 18349 * The chapter track menu item 18350 * 18351 * @extends MenuItem 18352 */ 18353 18354 var ChaptersTrackMenuItem = 18355 /*#__PURE__*/ 18356 function (_MenuItem) { 18357 inheritsLoose(ChaptersTrackMenuItem, _MenuItem); 18358 18359 /** 18360 * Creates an instance of this class. 18361 * 18362 * @param {Player} player 18363 * The `Player` that this class should be attached to. 18364 * 18365 * @param {Object} [options] 18366 * The key/value store of player options. 18367 */ 18368 function ChaptersTrackMenuItem(player, options) { 18369 var _this; 18370 18371 var track = options.track; 18372 var cue = options.cue; 18373 var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init. 18374 18375 options.selectable = true; 18376 options.multiSelectable = false; 18377 options.label = cue.text; 18378 options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; 18379 _this = _MenuItem.call(this, player, options) || this; 18380 _this.track = track; 18381 _this.cue = cue; 18382 track.addEventListener('cuechange', bind(assertThisInitialized(_this), _this.update)); 18383 return _this; 18384 } 18385 /** 18386 * This gets called when an `ChaptersTrackMenuItem` is "clicked". See 18387 * {@link ClickableComponent} for more detailed information on what a click can be. 18388 * 18389 * @param {EventTarget~Event} [event] 18390 * The `keydown`, `tap`, or `click` event that caused this function to be 18391 * called. 18392 * 18393 * @listens tap 18394 * @listens click 18395 */ 18396 18397 18398 var _proto = ChaptersTrackMenuItem.prototype; 18399 18400 _proto.handleClick = function handleClick(event) { 18401 _MenuItem.prototype.handleClick.call(this); 18402 18403 this.player_.currentTime(this.cue.startTime); 18404 this.update(this.cue.startTime); 18405 } 18406 /** 18407 * Update chapter menu item 18408 * 18409 * @param {EventTarget~Event} [event] 18410 * The `cuechange` event that caused this function to run. 18411 * 18412 * @listens TextTrack#cuechange 18413 */ 18414 ; 18415 18416 _proto.update = function update(event) { 18417 var cue = this.cue; 18418 var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime); 18419 18420 this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); 18421 }; 18422 18423 return ChaptersTrackMenuItem; 18424 }(MenuItem); 18425 18426 Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); 18427 18428 /** 18429 * The button component for toggling and selecting chapters 18430 * Chapters act much differently than other text tracks 18431 * Cues are navigation vs. other tracks of alternative languages 18432 * 18433 * @extends TextTrackButton 18434 */ 18435 18436 var ChaptersButton = 18437 /*#__PURE__*/ 18438 function (_TextTrackButton) { 18439 inheritsLoose(ChaptersButton, _TextTrackButton); 18440 18441 /** 18442 * Creates an instance of this class. 18443 * 18444 * @param {Player} player 18445 * The `Player` that this class should be attached to. 18446 * 18447 * @param {Object} [options] 18448 * The key/value store of player options. 18449 * 18450 * @param {Component~ReadyCallback} [ready] 18451 * The function to call when this function is ready. 18452 */ 18453 function ChaptersButton(player, options, ready) { 18454 return _TextTrackButton.call(this, player, options, ready) || this; 18455 } 18456 /** 18457 * Builds the default DOM `className`. 18458 * 18459 * @return {string} 18460 * The DOM `className` for this object. 18461 */ 18462 18463 18464 var _proto = ChaptersButton.prototype; 18465 18466 _proto.buildCSSClass = function buildCSSClass() { 18467 return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18468 }; 18469 18470 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18471 return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18472 } 18473 /** 18474 * Update the menu based on the current state of its items. 18475 * 18476 * @param {EventTarget~Event} [event] 18477 * An event that triggered this function to run. 18478 * 18479 * @listens TextTrackList#addtrack 18480 * @listens TextTrackList#removetrack 18481 * @listens TextTrackList#change 18482 */ 18483 ; 18484 18485 _proto.update = function update(event) { 18486 if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
vendor: 7,580 bytes, lines 18487-18763
18487 this.setTrack(this.findChaptersTrack()); 18488 } 18489 18490 _TextTrackButton.prototype.update.call(this); 18491 } 18492 /** 18493 * Set the currently selected track for the chapters button. 18494 * 18495 * @param {TextTrack} track 18496 * The new track to select. Nothing will change if this is the currently selected 18497 * track. 18498 */ 18499 ; 18500 18501 _proto.setTrack = function setTrack(track) { 18502 if (this.track_ === track) { 18503 return; 18504 } 18505 18506 if (!this.updateHandler_) { 18507 this.updateHandler_ = this.update.bind(this); 18508 } // here this.track_ refers to the old track instance 18509 18510 18511 if (this.track_) { 18512 var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 18513 18514 if (remoteTextTrackEl) { 18515 remoteTextTrackEl.removeEventListener('load', this.updateHandler_); 18516 } 18517 18518 this.track_ = null; 18519 } 18520 18521 this.track_ = track; // here this.track_ refers to the new track instance 18522 18523 if (this.track_) { 18524 this.track_.mode = 'hidden'; 18525 18526 var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 18527 18528 if (_remoteTextTrackEl) { 18529 _remoteTextTrackEl.addEventListener('load', this.updateHandler_); 18530 } 18531 } 18532 } 18533 /** 18534 * Find the track object that is currently in use by this ChaptersButton 18535 * 18536 * @return {TextTrack|undefined} 18537 * The current track or undefined if none was found. 18538 */ 18539 ; 18540 18541 _proto.findChaptersTrack = function findChaptersTrack() { 18542 var tracks = this.player_.textTracks() || []; 18543 18544 for (var i = tracks.length - 1; i >= 0; i--) { 18545 // We will always choose the last track as our chaptersTrack 18546 var track = tracks[i]; 18547 18548 if (track.kind === this.kind_) { 18549 return track; 18550 } 18551 } 18552 } 18553 /** 18554 * Get the caption for the ChaptersButton based on the track label. This will also 18555 * use the current tracks localized kind as a fallback if a label does not exist. 18556 * 18557 * @return {string} 18558 * The tracks current label or the localized track kind. 18559 */ 18560 ; 18561 18562 _proto.getMenuCaption = function getMenuCaption() { 18563 if (this.track_ && this.track_.label) { 18564 return this.track_.label; 18565 } 18566 18567 return this.localize(toTitleCase(this.kind_)); 18568 } 18569 /** 18570 * Create menu from chapter track 18571 * 18572 * @return {Menu} 18573 * New menu for the chapter buttons 18574 */ 18575 ; 18576 18577 _proto.createMenu = function createMenu() { 18578 this.options_.title = this.getMenuCaption(); 18579 return _TextTrackButton.prototype.createMenu.call(this); 18580 } 18581 /** 18582 * Create a menu item for each text track 18583 * 18584 * @return {TextTrackMenuItem[]} 18585 * Array of menu items 18586 */ 18587 ; 18588 18589 _proto.createItems = function createItems() { 18590 var items = []; 18591 18592 if (!this.track_) { 18593 return items; 18594 } 18595 18596 var cues = this.track_.cues; 18597 18598 if (!cues) { 18599 return items; 18600 } 18601 18602 for (var i = 0, l = cues.length; i < l; i++) { 18603 var cue = cues[i]; 18604 var mi = new ChaptersTrackMenuItem(this.player_, { 18605 track: this.track_, 18606 cue: cue 18607 }); 18608 items.push(mi); 18609 } 18610 18611 return items; 18612 }; 18613 18614 return ChaptersButton; 18615 }(TextTrackButton); 18616 /** 18617 * `kind` of TextTrack to look for to associate it with this menu. 18618 * 18619 * @type {string} 18620 * @private 18621 */ 18622 18623 18624 ChaptersButton.prototype.kind_ = 'chapters'; 18625 /** 18626 * The text that should display over the `ChaptersButton`s controls. Added for localization. 18627 * 18628 * @type {string} 18629 * @private 18630 */ 18631 18632 ChaptersButton.prototype.controlText_ = 'Chapters'; 18633 Component.registerComponent('ChaptersButton', ChaptersButton); 18634 18635 /** 18636 * The button component for toggling and selecting descriptions 18637 * 18638 * @extends TextTrackButton 18639 */ 18640 18641 var DescriptionsButton = 18642 /*#__PURE__*/ 18643 function (_TextTrackButton) { 18644 inheritsLoose(DescriptionsButton, _TextTrackButton); 18645 18646 /** 18647 * Creates an instance of this class. 18648 * 18649 * @param {Player} player 18650 * The `Player` that this class should be attached to. 18651 * 18652 * @param {Object} [options] 18653 * The key/value store of player options. 18654 * 18655 * @param {Component~ReadyCallback} [ready] 18656 * The function to call when this component is ready. 18657 */ 18658 function DescriptionsButton(player, options, ready) { 18659 var _this; 18660 18661 _this = _TextTrackButton.call(this, player, options, ready) || this; 18662 var tracks = player.textTracks(); 18663 var changeHandler = bind(assertThisInitialized(_this), _this.handleTracksChange); 18664 tracks.addEventListener('change', changeHandler); 18665 18666 _this.on('dispose', function () { 18667 tracks.removeEventListener('change', changeHandler); 18668 }); 18669 18670 return _this; 18671 } 18672 /** 18673 * Handle text track change 18674 * 18675 * @param {EventTarget~Event} event 18676 * The event that caused this function to run 18677 * 18678 * @listens TextTrackList#change 18679 */ 18680 18681 18682 var _proto = DescriptionsButton.prototype; 18683 18684 _proto.handleTracksChange = function handleTracksChange(event) { 18685 var tracks = this.player().textTracks(); 18686 var disabled = false; // Check whether a track of a different kind is showing 18687 18688 for (var i = 0, l = tracks.length; i < l; i++) { 18689 var track = tracks[i]; 18690 18691 if (track.kind !== this.kind_ && track.mode === 'showing') { 18692 disabled = true; 18693 break; 18694 } 18695 } // If another track is showing, disable this menu button 18696 18697 18698 if (disabled) { 18699 this.disable(); 18700 } else { 18701 this.enable(); 18702 } 18703 } 18704 /** 18705 * Builds the default DOM `className`. 18706 * 18707 * @return {string} 18708 * The DOM `className` for this object. 18709 */ 18710 ; 18711 18712 _proto.buildCSSClass = function buildCSSClass() { 18713 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18714 }; 18715 18716 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18717 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18718 }; 18719 18720 return DescriptionsButton; 18721 }(TextTrackButton); 18722 /** 18723 * `kind` of TextTrack to look for to associate it with this menu. 18724 * 18725 * @type {string} 18726 * @private 18727 */ 18728 18729 18730 DescriptionsButton.prototype.kind_ = 'descriptions'; 18731 /** 18732 * The text that should display over the `DescriptionsButton`s controls. Added for localization. 18733 * 18734 * @type {string} 18735 * @private 18736 */ 18737 18738 DescriptionsButton.prototype.controlText_ = 'Descriptions'; 18739 Component.registerComponent('DescriptionsButton', DescriptionsButton); 18740 18741 /** 18742 * The button component for toggling and selecting subtitles 18743 * 18744 * @extends TextTrackButton 18745 */ 18746 18747 var SubtitlesButton = 18748 /*#__PURE__*/ 18749 function (_TextTrackButton) { 18750 inheritsLoose(SubtitlesButton, _TextTrackButton); 18751 18752 /** 18753 * Creates an instance of this class. 18754 * 18755 * @param {Player} player 18756 * The `Player` that this class should be attached to. 18757 * 18758 * @param {Object} [options] 18759 * The key/value store of player options. 18760 * 18761 * @param {Component~ReadyCallback} [ready] 18762 * The function to call when this component is ready. 18763 */
18764 function SubtitlesButton(player, options, ready) { 18765 return _TextTrackButton.call(this, player, options, ready) || this; 18766 } 18767 /** 18768 * Builds the default DOM `className`. 18769 * 18770 * @return {string} 18771 * The DOM `className` for this object. 18772 */ 18773 18774 18775 var _proto = SubtitlesButton.prototype; 18776 18777 _proto.buildCSSClass = function buildCSSClass() { 18778 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18779 }; 18780 18781 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18782 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18783 }; 18784 18785 return SubtitlesButton; 18786 }(TextTrackButton); 18787 /** 18788 * `kind` of TextTrack to look for to associate it with this menu. 18789 * 18790 * @type {string} 18791 * @private 18792 */ 18793 18794 18795 SubtitlesButton.prototype.kind_ = 'subtitles'; 18796 /** 18797 * The text that should display over the `SubtitlesButton`s controls. Added for localization. 18798 * 18799 * @type {string} 18800 * @private 18801 */ 18802 18803 SubtitlesButton.prototype.controlText_ = 'Subtitles'; 18804 Component.registerComponent('SubtitlesButton', SubtitlesButton); 18805 18806 /** 18807 * The menu item for caption track settings menu 18808 * 18809 * @extends TextTrackMenuItem 18810 */ 18811 18812 var CaptionSettingsMenuItem = 18813 /*#__PURE__*/ 18814 function (_TextTrackMenuItem) { 18815 inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem); 18816 18817 /** 18818 * Creates an instance of this class. 18819 * 18820 * @param {Player} player 18821 * The `Player` that this class should be attached to. 18822 * 18823 * @param {Object} [options] 18824 * The key/value store of player options. 18825 */ 18826 function CaptionSettingsMenuItem(player, options) { 18827 var _this; 18828 18829 options.track = { 18830 player: player, 18831 kind: options.kind, 18832 label: options.kind + ' settings', 18833 selectable: false, 18834 "default": false, 18835 mode: 'disabled' 18836 }; // CaptionSettingsMenuItem has no concept of 'selected' 18837 18838 options.selectable = false; 18839 options.name = 'CaptionSettingsMenuItem'; 18840 _this = _TextTrackMenuItem.call(this, player, options) || this; 18841 18842 _this.addClass('vjs-texttrack-settings'); 18843 18844 _this.controlText(', opens ' + options.kind + ' settings dialog'); 18845 18846 return _this; 18847 } 18848 /** 18849 * This gets called when an `CaptionSettingsMenuItem` is "clicked". See 18850 * {@link ClickableComponent} for more detailed information on what a click can be. 18851 * 18852 * @param {EventTarget~Event} [event] 18853 * The `keydown`, `tap`, or `click` event that caused this function to be 18854 * called. 18855 * 18856 * @listens tap 18857 * @listens click 18858 */ 18859 18860 18861 var _proto = CaptionSettingsMenuItem.prototype; 18862 18863 _proto.handleClick = function handleClick(event) { 18864 this.player().getChild('textTrackSettings').open(); 18865 }; 18866 18867 return CaptionSettingsMenuItem; 18868 }(TextTrackMenuItem); 18869 18870 Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); 18871 18872 /** 18873 * The button component for toggling and selecting captions 18874 * 18875 * @extends TextTrackButton 18876 */ 18877 18878 var CaptionsButton = 18879 /*#__PURE__*/ 18880 function (_TextTrackButton) { 18881 inheritsLoose(CaptionsButton, _TextTrackButton); 18882 18883 /** 18884 * Creates an instance of this class. 18885 * 18886 * @param {Player} player 18887 * The `Player` that this class should be attached to. 18888 * 18889 * @param {Object} [options] 18890 * The key/value store of player options. 18891 * 18892 * @param {Component~ReadyCallback} [ready] 18893 * The function to call when this component is ready. 18894 */ 18895 function CaptionsButton(player, options, ready) { 18896 return _TextTrackButton.call(this, player, options, ready) || this; 18897 } 18898 /** 18899 * Builds the default DOM `className`. 18900 * 18901 * @return {string} 18902 * The DOM `className` for this object. 18903 */ 18904 18905 18906 var _proto = CaptionsButton.prototype; 18907 18908 _proto.buildCSSClass = function buildCSSClass() { 18909 return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 18910 }; 18911 18912 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18913 return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 18914 } 18915 /** 18916 * Create caption menu items 18917 * 18918 * @return {CaptionSettingsMenuItem[]} 18919 * The array of current menu items. 18920 */ 18921 ; 18922 18923 _proto.createItems = function createItems() { 18924 var items = []; 18925 18926 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 18927 items.push(new CaptionSettingsMenuItem(this.player_, { 18928 kind: this.kind_ 18929 })); 18930 this.hideThreshold_ += 1; 18931 } 18932 18933 return _TextTrackButton.prototype.createItems.call(this, items); 18934 }; 18935 18936 return CaptionsButton; 18937 }(TextTrackButton); 18938 /** 18939 * `kind` of TextTrack to look for to associate it with this menu. 18940 * 18941 * @type {string} 18942 * @private 18943 */ 18944 18945 18946 CaptionsButton.prototype.kind_ = 'captions'; 18947 /** 18948 * The text that should display over the `CaptionsButton`s controls. Added for localization. 18949 * 18950 * @type {string} 18951 * @private 18952 */ 18953 18954 CaptionsButton.prototype.controlText_ = 'Captions'; 18955 Component.registerComponent('CaptionsButton', CaptionsButton); 18956 18957 /** 18958 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles 18959 * in the SubsCapsMenu. 18960 * 18961 * @extends TextTrackMenuItem 18962 */ 18963 18964 var SubsCapsMenuItem = 18965 /*#__PURE__*/ 18966 function (_TextTrackMenuItem) {
18967 inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem); 18968 18969 function SubsCapsMenuItem() { 18970 return _TextTrackMenuItem.apply(this, arguments) || this; 18971 } 18972 18973 var _proto = SubsCapsMenuItem.prototype; 18974 18975 _proto.createEl = function createEl(type, props, attrs) { 18976 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 18977 18978 if (this.options_.track.kind === 'captions') { 18979 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n "; 18980 } 18981 18982 innerHTML += '</span>'; 18983 18984 var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({ 18985 innerHTML: innerHTML 18986 }, props), attrs); 18987 18988 return el; 18989 }; 18990 18991 return SubsCapsMenuItem; 18992 }(TextTrackMenuItem); 18993 18994 Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); 18995 18996 /** 18997 * The button component for toggling and selecting captions and/or subtitles 18998 * 18999 * @extends TextTrackButton 19000 */ 19001 19002 var SubsCapsButton = 19003 /*#__PURE__*/ 19004 function (_TextTrackButton) { 19005 inheritsLoose(SubsCapsButton, _TextTrackButton); 19006 19007 function SubsCapsButton(player, options) { 19008 var _this; 19009 19010 if (options === void 0) { 19011 options = {}; 19012 } 19013 19014 _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for 19015 // "captions and subtitles" other locales use "subtitles" 19016 19017 _this.label_ = 'subtitles'; 19018 19019 if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { 19020 _this.label_ = 'captions'; 19021 } 19022 19023 _this.menuButton_.controlText(toTitleCase(_this.label_)); 19024 19025 return _this; 19026 } 19027 /** 19028 * Builds the default DOM `className`. 19029 * 19030 * @return {string} 19031 * The DOM `className` for this object. 19032 */ 19033 19034 19035 var _proto = SubsCapsButton.prototype; 19036 19037 _proto.buildCSSClass = function buildCSSClass() { 19038 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19039 }; 19040 19041 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19042 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19043 } 19044 /** 19045 * Create caption/subtitles menu items 19046 * 19047 * @return {CaptionSettingsMenuItem[]} 19048 * The array of current menu items. 19049 */ 19050 ; 19051 19052 _proto.createItems = function createItems() { 19053 var items = []; 19054 19055 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 19056 items.push(new CaptionSettingsMenuItem(this.player_, { 19057 kind: this.label_ 19058 })); 19059 this.hideThreshold_ += 1; 19060 } 19061 19062 items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); 19063 return items; 19064 }; 19065 19066 return SubsCapsButton; 19067 }(TextTrackButton); 19068 /** 19069 * `kind`s of TextTrack to look for to associate it with this menu. 19070 * 19071 * @type {array} 19072 * @private 19073 */ 19074 19075 19076 SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; 19077 /** 19078 * The text that should display over the `SubsCapsButton`s controls. 19079 * 19080 * 19081 * @type {string} 19082 * @private 19083 */ 19084 19085 SubsCapsButton.prototype.controlText_ = 'Subtitles'; 19086 Component.registerComponent('SubsCapsButton', SubsCapsButton); 19087 19088 /** 19089 * An {@link AudioTrack} {@link MenuItem} 19090 * 19091 * @extends MenuItem 19092 */ 19093 19094 var AudioTrackMenuItem = 19095 /*#__PURE__*/ 19096 function (_MenuItem) { 19097 inheritsLoose(AudioTrackMenuItem, _MenuItem); 19098 19099 /** 19100 * Creates an instance of this class. 19101 * 19102 * @param {Player} player 19103 * The `Player` that this class should be attached to. 19104 * 19105 * @param {Object} [options] 19106 * The key/value store of player options. 19107 */ 19108 function AudioTrackMenuItem(player, options) { 19109 var _this; 19110 19111 var track = options.track; 19112 var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init. 19113 19114 options.label = track.label || track.language || 'Unknown'; 19115 options.selected = track.enabled; 19116 _this = _MenuItem.call(this, player, options) || this; 19117 _this.track = track; 19118
vendor: 6,281 bytes, lines 19119-19331
19119 _this.addClass("vjs-" + track.kind + "-menu-item"); 19120 19121 var changeHandler = function changeHandler() { 19122 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 19123 args[_key] = arguments[_key]; 19124 } 19125 19126 _this.handleTracksChange.apply(assertThisInitialized(_this), args); 19127 }; 19128 19129 tracks.addEventListener('change', changeHandler); 19130 19131 _this.on('dispose', function () { 19132 tracks.removeEventListener('change', changeHandler); 19133 }); 19134 19135 return _this; 19136 } 19137 19138 var _proto = AudioTrackMenuItem.prototype; 19139 19140 _proto.createEl = function createEl(type, props, attrs) { 19141 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 19142 19143 if (this.options_.track.kind === 'main-desc') { 19144 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n "; 19145 } 19146 19147 innerHTML += '</span>'; 19148 19149 var el = _MenuItem.prototype.createEl.call(this, type, assign({ 19150 innerHTML: innerHTML 19151 }, props), attrs); 19152 19153 return el; 19154 } 19155 /** 19156 * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} 19157 * for more detailed information on what a click can be. 19158 * 19159 * @param {EventTarget~Event} [event] 19160 * The `keydown`, `tap`, or `click` event that caused this function to be 19161 * called. 19162 * 19163 * @listens tap 19164 * @listens click 19165 */ 19166 ; 19167 19168 _proto.handleClick = function handleClick(event) { 19169 var tracks = this.player_.audioTracks(); 19170 19171 _MenuItem.prototype.handleClick.call(this, event); 19172 19173 for (var i = 0; i < tracks.length; i++) { 19174 var track = tracks[i]; 19175 track.enabled = track === this.track; 19176 } 19177 } 19178 /** 19179 * Handle any {@link AudioTrack} change. 19180 * 19181 * @param {EventTarget~Event} [event] 19182 * The {@link AudioTrackList#change} event that caused this to run. 19183 * 19184 * @listens AudioTrackList#change 19185 */ 19186 ; 19187 19188 _proto.handleTracksChange = function handleTracksChange(event) { 19189 this.selected(this.track.enabled); 19190 }; 19191 19192 return AudioTrackMenuItem; 19193 }(MenuItem); 19194 19195 Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); 19196 19197 /** 19198 * The base class for buttons that toggle specific {@link AudioTrack} types. 19199 * 19200 * @extends TrackButton 19201 */ 19202 19203 var AudioTrackButton = 19204 /*#__PURE__*/ 19205 function (_TrackButton) { 19206 inheritsLoose(AudioTrackButton, _TrackButton); 19207 19208 /** 19209 * Creates an instance of this class. 19210 * 19211 * @param {Player} player 19212 * The `Player` that this class should be attached to. 19213 * 19214 * @param {Object} [options={}] 19215 * The key/value store of player options. 19216 */ 19217 function AudioTrackButton(player, options) { 19218 if (options === void 0) { 19219 options = {}; 19220 } 19221 19222 options.tracks = player.audioTracks(); 19223 return _TrackButton.call(this, player, options) || this; 19224 } 19225 /** 19226 * Builds the default DOM `className`. 19227 * 19228 * @return {string} 19229 * The DOM `className` for this object. 19230 */ 19231 19232 19233 var _proto = AudioTrackButton.prototype; 19234 19235 _proto.buildCSSClass = function buildCSSClass() { 19236 return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this); 19237 }; 19238 19239 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19240 return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this); 19241 } 19242 /** 19243 * Create a menu item for each audio track 19244 * 19245 * @param {AudioTrackMenuItem[]} [items=[]] 19246 * An array of existing menu items to use. 19247 * 19248 * @return {AudioTrackMenuItem[]} 19249 * An array of menu items 19250 */ 19251 ; 19252 19253 _proto.createItems = function createItems(items) { 19254 if (items === void 0) { 19255 items = []; 19256 } 19257 19258 // if there's only one audio track, there no point in showing it 19259 this.hideThreshold_ = 1; 19260 var tracks = this.player_.audioTracks(); 19261 19262 for (var i = 0; i < tracks.length; i++) { 19263 var track = tracks[i]; 19264 items.push(new AudioTrackMenuItem(this.player_, { 19265 track: track, 19266 // MenuItem is selectable 19267 selectable: true, 19268 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 19269 multiSelectable: false 19270 })); 19271 } 19272 19273 return items; 19274 }; 19275 19276 return AudioTrackButton; 19277 }(TrackButton); 19278 /** 19279 * The text that should display over the `AudioTrackButton`s controls. Added for localization. 19280 * 19281 * @type {string} 19282 * @private 19283 */ 19284 19285 19286 AudioTrackButton.prototype.controlText_ = 'Audio Track'; 19287 Component.registerComponent('AudioTrackButton', AudioTrackButton); 19288 19289 /** 19290 * The specific menu item type for selecting a playback rate. 19291 * 19292 * @extends MenuItem 19293 */ 19294 19295 var PlaybackRateMenuItem = 19296 /*#__PURE__*/ 19297 function (_MenuItem) { 19298 inheritsLoose(PlaybackRateMenuItem, _MenuItem); 19299 19300 /** 19301 * Creates an instance of this class. 19302 * 19303 * @param {Player} player 19304 * The `Player` that this class should be attached to. 19305 * 19306 * @param {Object} [options] 19307 * The key/value store of player options. 19308 */ 19309 function PlaybackRateMenuItem(player, options) { 19310 var _this; 19311 19312 var label = options.rate; 19313 var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init. 19314 19315 options.label = label; 19316 options.selected = rate === 1; 19317 options.selectable = true; 19318 options.multiSelectable = false; 19319 _this = _MenuItem.call(this, player, options) || this; 19320 _this.label = label; 19321 _this.rate = rate; 19322 19323 _this.on(player, 'ratechange', _this.update); 19324 19325 return _this; 19326 } 19327 /** 19328 * This gets called when an `PlaybackRateMenuItem` is "clicked". See 19329 * {@link ClickableComponent} for more detailed information on what a click can be. 19330 * 19331 * @param {EventTarget~Event} [event]
19332 * The `keydown`, `tap`, or `click` event that caused this function to be 19333 * called. 19334 * 19335 * @listens tap 19336 * @listens click 19337 */ 19338 19339 19340 var _proto = PlaybackRateMenuItem.prototype; 19341 19342 _proto.handleClick = function handleClick(event) { 19343 _MenuItem.prototype.handleClick.call(this); 19344 19345 this.player().playbackRate(this.rate); 19346 } 19347 /** 19348 * Update the PlaybackRateMenuItem when the playbackrate changes. 19349 * 19350 * @param {EventTarget~Event} [event] 19351 * The `ratechange` event that caused this function to run. 19352 * 19353 * @listens Player#ratechange 19354 */ 19355 ; 19356 19357 _proto.update = function update(event) { 19358 this.selected(this.player().playbackRate() === this.rate); 19359 }; 19360 19361 return PlaybackRateMenuItem; 19362 }(MenuItem); 19363 /** 19364 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. 19365 * 19366 * @type {string} 19367 * @private 19368 */ 19369 19370 19371 PlaybackRateMenuItem.prototype.contentElType = 'button'; 19372 Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); 19373 19374 /** 19375 * The component for controlling the playback rate. 19376 * 19377 * @extends MenuButton 19378 */ 19379 19380 var PlaybackRateMenuButton = 19381 /*#__PURE__*/ 19382 function (_MenuButton) { 19383 inheritsLoose(PlaybackRateMenuButton, _MenuButton); 19384 19385 /** 19386 * Creates an instance of this class. 19387 * 19388 * @param {Player} player 19389 * The `Player` that this class should be attached to. 19390 * 19391 * @param {Object} [options] 19392 * The key/value store of player options. 19393 */ 19394 function PlaybackRateMenuButton(player, options) { 19395 var _this; 19396 19397 _this = _MenuButton.call(this, player, options) || this; 19398 19399 _this.updateVisibility(); 19400 19401 _this.updateLabel(); 19402 19403 _this.on(player, 'loadstart', _this.updateVisibility); 19404 19405 _this.on(player, 'ratechange', _this.updateLabel); 19406 19407 return _this; 19408 } 19409 /** 19410 * Create the `Component`'s DOM element 19411 * 19412 * @return {Element} 19413 * The element that was created. 19414 */ 19415 19416 19417 var _proto = PlaybackRateMenuButton.prototype; 19418 19419 _proto.createEl = function createEl$1() { 19420 var el = _MenuButton.prototype.createEl.call(this); 19421 19422 this.labelEl_ = createEl('div', { 19423 className: 'vjs-playback-rate-value', 19424 innerHTML: '1x' 19425 }); 19426 el.appendChild(this.labelEl_); 19427 return el; 19428 }; 19429 19430 _proto.dispose = function dispose() { 19431 this.labelEl_ = null; 19432 19433 _MenuButton.prototype.dispose.call(this); 19434 } 19435 /** 19436 * Builds the default DOM `className`. 19437 * 19438 * @return {string} 19439 * The DOM `className` for this object. 19440 */ 19441 ; 19442 19443 _proto.buildCSSClass = function buildCSSClass() { 19444 return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this); 19445 }; 19446 19447 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19448 return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this); 19449 } 19450 /** 19451 * Create the playback rate menu 19452 * 19453 * @return {Menu} 19454 * Menu object populated with {@link PlaybackRateMenuItem}s 19455 */ 19456 ; 19457 19458 _proto.createMenu = function createMenu() { 19459 var menu = new Menu(this.player()); 19460 var rates = this.playbackRates(); 19461 19462 if (rates) { 19463 for (var i = rates.length - 1; i >= 0; i--) { 19464 menu.addChild(new PlaybackRateMenuItem(this.player(), { 19465 rate: rates[i] + 'x' 19466 })); 19467 } 19468 } 19469 19470 return menu; 19471 } 19472 /** 19473 * Updates ARIA accessibility attributes 19474 */ 19475 ; 19476 19477 _proto.updateARIAAttributes = function updateARIAAttributes() { 19478 // Current playback rate 19479 this.el().setAttribute('aria-valuenow', this.player().playbackRate()); 19480 } 19481 /** 19482 * This gets called when an `PlaybackRateMenuButton` is "clicked". See 19483 * {@link ClickableComponent} for more detailed information on what a click can be. 19484 * 19485 * @param {EventTarget~Event} [event]
vendor: 23,095 bytes, lines 19486-20232
19486 * The `keydown`, `tap`, or `click` event that caused this function to be 19487 * called. 19488 * 19489 * @listens tap 19490 * @listens click 19491 */ 19492 ; 19493 19494 _proto.handleClick = function handleClick(event) { 19495 // select next rate option 19496 var currentRate = this.player().playbackRate(); 19497 var rates = this.playbackRates(); // this will select first one if the last one currently selected 19498 19499 var newRate = rates[0]; 19500 19501 for (var i = 0; i < rates.length; i++) { 19502 if (rates[i] > currentRate) { 19503 newRate = rates[i]; 19504 break; 19505 } 19506 } 19507 19508 this.player().playbackRate(newRate); 19509 } 19510 /** 19511 * Get possible playback rates 19512 * 19513 * @return {Array} 19514 * All possible playback rates 19515 */ 19516 ; 19517 19518 _proto.playbackRates = function playbackRates() { 19519 return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; 19520 } 19521 /** 19522 * Get whether playback rates is supported by the tech 19523 * and an array of playback rates exists 19524 * 19525 * @return {boolean} 19526 * Whether changing playback rate is supported 19527 */ 19528 ; 19529 19530 _proto.playbackRateSupported = function playbackRateSupported() { 19531 return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; 19532 } 19533 /** 19534 * Hide playback rate controls when they're no playback rate options to select 19535 * 19536 * @param {EventTarget~Event} [event] 19537 * The event that caused this function to run. 19538 * 19539 * @listens Player#loadstart 19540 */ 19541 ; 19542 19543 _proto.updateVisibility = function updateVisibility(event) { 19544 if (this.playbackRateSupported()) { 19545 this.removeClass('vjs-hidden'); 19546 } else { 19547 this.addClass('vjs-hidden'); 19548 } 19549 } 19550 /** 19551 * Update button label when rate changed 19552 * 19553 * @param {EventTarget~Event} [event] 19554 * The event that caused this function to run. 19555 * 19556 * @listens Player#ratechange 19557 */ 19558 ; 19559 19560 _proto.updateLabel = function updateLabel(event) { 19561 if (this.playbackRateSupported()) { 19562 this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; 19563 } 19564 }; 19565 19566 return PlaybackRateMenuButton; 19567 }(MenuButton); 19568 /** 19569 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 19570 * 19571 * @type {string} 19572 * @private 19573 */ 19574 19575 19576 PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; 19577 Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); 19578 19579 /** 19580 * Just an empty spacer element that can be used as an append point for plugins, etc. 19581 * Also can be used to create space between elements when necessary. 19582 * 19583 * @extends Component 19584 */ 19585 19586 var Spacer = 19587 /*#__PURE__*/ 19588 function (_Component) { 19589 inheritsLoose(Spacer, _Component); 19590 19591 function Spacer() { 19592 return _Component.apply(this, arguments) || this; 19593 } 19594 19595 var _proto = Spacer.prototype; 19596 19597 /** 19598 * Builds the default DOM `className`. 19599 * 19600 * @return {string} 19601 * The DOM `className` for this object. 19602 */ 19603 _proto.buildCSSClass = function buildCSSClass() { 19604 return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this); 19605 } 19606 /** 19607 * Create the `Component`'s DOM element 19608 * 19609 * @return {Element} 19610 * The element that was created. 19611 */ 19612 ; 19613 19614 _proto.createEl = function createEl() { 19615 return _Component.prototype.createEl.call(this, 'div', { 19616 className: this.buildCSSClass() 19617 }); 19618 }; 19619 19620 return Spacer; 19621 }(Component); 19622 19623 Component.registerComponent('Spacer', Spacer); 19624 19625 /** 19626 * Spacer specifically meant to be used as an insertion point for new plugins, etc. 19627 * 19628 * @extends Spacer 19629 */ 19630 19631 var CustomControlSpacer = 19632 /*#__PURE__*/ 19633 function (_Spacer) { 19634 inheritsLoose(CustomControlSpacer, _Spacer); 19635 19636 function CustomControlSpacer() { 19637 return _Spacer.apply(this, arguments) || this; 19638 } 19639 19640 var _proto = CustomControlSpacer.prototype; 19641 19642 /** 19643 * Builds the default DOM `className`. 19644 * 19645 * @return {string} 19646 * The DOM `className` for this object. 19647 */ 19648 _proto.buildCSSClass = function buildCSSClass() { 19649 return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this); 19650 } 19651 /** 19652 * Create the `Component`'s DOM element 19653 * 19654 * @return {Element} 19655 * The element that was created. 19656 */ 19657 ; 19658 19659 _proto.createEl = function createEl() { 19660 var el = _Spacer.prototype.createEl.call(this, { 19661 className: this.buildCSSClass() 19662 }); // No-flex/table-cell mode requires there be some content 19663 // in the cell to fill the remaining space of the table. 19664 19665 19666 el.innerHTML = "\xA0"; 19667 return el; 19668 }; 19669 19670 return CustomControlSpacer; 19671 }(Spacer); 19672 19673 Component.registerComponent('CustomControlSpacer', CustomControlSpacer); 19674 19675 /** 19676 * Container of main controls. 19677 * 19678 * @extends Component 19679 */ 19680 19681 var ControlBar = 19682 /*#__PURE__*/ 19683 function (_Component) { 19684 inheritsLoose(ControlBar, _Component); 19685 19686 function ControlBar() { 19687 return _Component.apply(this, arguments) || this; 19688 } 19689 19690 var _proto = ControlBar.prototype; 19691 19692 /** 19693 * Create the `Component`'s DOM element 19694 * 19695 * @return {Element} 19696 * The element that was created. 19697 */ 19698 _proto.createEl = function createEl() { 19699 return _Component.prototype.createEl.call(this, 'div', { 19700 className: 'vjs-control-bar', 19701 dir: 'ltr' 19702 }); 19703 }; 19704 19705 return ControlBar; 19706 }(Component); 19707 /** 19708 * Default options for `ControlBar` 19709 * 19710 * @type {Object} 19711 * @private 19712 */ 19713 19714 19715 ControlBar.prototype.options_ = { 19716 children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] 19717 }; 19718 19719 if ('exitPictureInPicture' in document) { 19720 ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle'); 19721 } 19722 19723 Component.registerComponent('ControlBar', ControlBar); 19724 19725 /** 19726 * A display that indicates an error has occurred. This means that the video 19727 * is unplayable. 19728 * 19729 * @extends ModalDialog 19730 */ 19731 19732 var ErrorDisplay = 19733 /*#__PURE__*/ 19734 function (_ModalDialog) { 19735 inheritsLoose(ErrorDisplay, _ModalDialog); 19736 19737 /** 19738 * Creates an instance of this class. 19739 * 19740 * @param {Player} player 19741 * The `Player` that this class should be attached to. 19742 * 19743 * @param {Object} [options] 19744 * The key/value store of player options. 19745 */ 19746 function ErrorDisplay(player, options) { 19747 var _this; 19748 19749 _this = _ModalDialog.call(this, player, options) || this; 19750 19751 _this.on(player, 'error', _this.open); 19752 19753 return _this; 19754 } 19755 /** 19756 * Builds the default DOM `className`. 19757 * 19758 * @return {string} 19759 * The DOM `className` for this object. 19760 * 19761 * @deprecated Since version 5. 19762 */ 19763 19764 19765 var _proto = ErrorDisplay.prototype; 19766 19767 _proto.buildCSSClass = function buildCSSClass() { 19768 return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this); 19769 } 19770 /** 19771 * Gets the localized error message based on the `Player`s error. 19772 * 19773 * @return {string} 19774 * The `Player`s error message localized or an empty string. 19775 */ 19776 ; 19777 19778 _proto.content = function content() { 19779 var error = this.player().error(); 19780 return error ? this.localize(error.message) : ''; 19781 }; 19782 19783 return ErrorDisplay; 19784 }(ModalDialog); 19785 /** 19786 * The default options for an `ErrorDisplay`. 19787 * 19788 * @private 19789 */ 19790 19791 19792 ErrorDisplay.prototype.options_ = _extends_1({}, ModalDialog.prototype.options_, { 19793 pauseOnOpen: false, 19794 fillAlways: true, 19795 temporary: false, 19796 uncloseable: true 19797 }); 19798 Component.registerComponent('ErrorDisplay', ErrorDisplay); 19799 19800 var LOCAL_STORAGE_KEY = 'vjs-text-track-settings'; 19801 var COLOR_BLACK = ['#000', 'Black']; 19802 var COLOR_BLUE = ['#00F', 'Blue']; 19803 var COLOR_CYAN = ['#0FF', 'Cyan']; 19804 var COLOR_GREEN = ['#0F0', 'Green']; 19805 var COLOR_MAGENTA = ['#F0F', 'Magenta']; 19806 var COLOR_RED = ['#F00', 'Red']; 19807 var COLOR_WHITE = ['#FFF', 'White']; 19808 var COLOR_YELLOW = ['#FF0', 'Yellow']; 19809 var OPACITY_OPAQUE = ['1', 'Opaque']; 19810 var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; 19811 var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component. 19812 // 19813 // Possible keys include: 19814 // 19815 // `default`: 19816 // The default option index. Only needs to be provided if not zero. 19817 // `parser`: 19818 // A function which is used to parse the value from the selected option in 19819 // a customized way. 19820 // `selector`: 19821 // The selector used to find the associated <select> element. 19822 19823 var selectConfigs = { 19824 backgroundColor: { 19825 selector: '.vjs-bg-color > select', 19826 id: 'captions-background-color-%s', 19827 label: 'Color', 19828 options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 19829 }, 19830 backgroundOpacity: { 19831 selector: '.vjs-bg-opacity > select', 19832 id: 'captions-background-opacity-%s', 19833 label: 'Transparency', 19834 options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] 19835 }, 19836 color: { 19837 selector: '.vjs-fg-color > select', 19838 id: 'captions-foreground-color-%s', 19839 label: 'Color', 19840 options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 19841 }, 19842 edgeStyle: { 19843 selector: '.vjs-edge-style > select', 19844 id: '%s', 19845 label: 'Text Edge Style', 19846 options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] 19847 }, 19848 fontFamily: { 19849 selector: '.vjs-font-family > select', 19850 id: 'captions-font-family-%s', 19851 label: 'Font Family', 19852 options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] 19853 }, 19854 fontPercent: { 19855 selector: '.vjs-font-percent > select', 19856 id: 'captions-font-size-%s', 19857 label: 'Font Size', 19858 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%']], 19859 "default": 2, 19860 parser: function parser(v) { 19861 return v === '1.00' ? null : Number(v); 19862 } 19863 }, 19864 textOpacity: { 19865 selector: '.vjs-text-opacity > select', 19866 id: 'captions-foreground-opacity-%s', 19867 label: 'Transparency', 19868 options: [OPACITY_OPAQUE, OPACITY_SEMI] 19869 }, 19870 // Options for this object are defined below. 19871 windowColor: { 19872 selector: '.vjs-window-color > select', 19873 id: 'captions-window-color-%s', 19874 label: 'Color' 19875 }, 19876 // Options for this object are defined below. 19877 windowOpacity: { 19878 selector: '.vjs-window-opacity > select', 19879 id: 'captions-window-opacity-%s', 19880 label: 'Transparency', 19881 options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] 19882 } 19883 }; 19884 selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; 19885 /** 19886 * Get the actual value of an option. 19887 * 19888 * @param {string} value 19889 * The value to get 19890 * 19891 * @param {Function} [parser] 19892 * Optional function to adjust the value. 19893 * 19894 * @return {Mixed} 19895 * - Will be `undefined` if no value exists 19896 * - Will be `undefined` if the given value is "none". 19897 * - Will be the actual value otherwise. 19898 * 19899 * @private 19900 */ 19901 19902 function parseOptionValue(value, parser) { 19903 if (parser) { 19904 value = parser(value); 19905 } 19906 19907 if (value && value !== 'none') { 19908 return value; 19909 } 19910 } 19911 /** 19912 * Gets the value of the selected <option> element within a <select> element. 19913 * 19914 * @param {Element} el 19915 * the element to look in 19916 * 19917 * @param {Function} [parser] 19918 * Optional function to adjust the value. 19919 * 19920 * @return {Mixed} 19921 * - Will be `undefined` if no value exists 19922 * - Will be `undefined` if the given value is "none". 19923 * - Will be the actual value otherwise. 19924 * 19925 * @private 19926 */ 19927 19928 19929 function getSelectedOptionValue(el, parser) { 19930 var value = el.options[el.options.selectedIndex].value; 19931 return parseOptionValue(value, parser); 19932 } 19933 /** 19934 * Sets the selected <option> element within a <select> element based on a 19935 * given value. 19936 * 19937 * @param {Element} el 19938 * The element to look in. 19939 * 19940 * @param {string} value 19941 * the property to look on. 19942 * 19943 * @param {Function} [parser] 19944 * Optional function to adjust the value before comparing. 19945 * 19946 * @private 19947 */ 19948 19949 19950 function setSelectedOption(el, value, parser) { 19951 if (!value) { 19952 return; 19953 } 19954 19955 for (var i = 0; i < el.options.length; i++) { 19956 if (parseOptionValue(el.options[i].value, parser) === value) { 19957 el.selectedIndex = i; 19958 break; 19959 } 19960 } 19961 } 19962 /** 19963 * Manipulate Text Tracks settings. 19964 * 19965 * @extends ModalDialog 19966 */ 19967 19968 19969 var TextTrackSettings = 19970 /*#__PURE__*/ 19971 function (_ModalDialog) { 19972 inheritsLoose(TextTrackSettings, _ModalDialog); 19973 19974 /** 19975 * Creates an instance of this class. 19976 * 19977 * @param {Player} player 19978 * The `Player` that this class should be attached to. 19979 * 19980 * @param {Object} [options] 19981 * The key/value store of player options. 19982 */ 19983 function TextTrackSettings(player, options) { 19984 var _this; 19985 19986 options.temporary = false; 19987 _this = _ModalDialog.call(this, player, options) || this; 19988 _this.updateDisplay = bind(assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it 19989 19990 _this.fill(); 19991 19992 _this.hasBeenOpened_ = _this.hasBeenFilled_ = true; 19993 _this.endDialog = createEl('p', { 19994 className: 'vjs-control-text', 19995 textContent: _this.localize('End of dialog window.') 19996 }); 19997 19998 _this.el().appendChild(_this.endDialog); 19999 20000 _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options 20001 20002 20003 if (options.persistTextTrackSettings === undefined) { 20004 _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings; 20005 } 20006 20007 _this.on(_this.$('.vjs-done-button'), 'click', function () { 20008 _this.saveSettings(); 20009 20010 _this.close(); 20011 }); 20012 20013 _this.on(_this.$('.vjs-default-button'), 'click', function () { 20014 _this.setDefaults(); 20015 20016 _this.updateDisplay(); 20017 }); 20018 20019 each(selectConfigs, function (config) { 20020 _this.on(_this.$(config.selector), 'change', _this.updateDisplay); 20021 }); 20022 20023 if (_this.options_.persistTextTrackSettings) { 20024 _this.restoreSettings(); 20025 } 20026 20027 return _this; 20028 } 20029 20030 var _proto = TextTrackSettings.prototype; 20031 20032 _proto.dispose = function dispose() { 20033 this.endDialog = null; 20034 20035 _ModalDialog.prototype.dispose.call(this); 20036 } 20037 /** 20038 * Create a <select> element with configured options. 20039 * 20040 * @param {string} key 20041 * Configuration key to use during creation. 20042 * 20043 * @return {string} 20044 * An HTML string. 20045 * 20046 * @private 20047 */ 20048 ; 20049 20050 _proto.createElSelect_ = function createElSelect_(key, legendId, type) { 20051 var _this2 = this; 20052 20053 if (legendId === void 0) { 20054 legendId = ''; 20055 } 20056 20057 if (type === void 0) { 20058 type = 'label'; 20059 } 20060 20061 var config = selectConfigs[key]; 20062 var id = config.id.replace('%s', this.id_); 20063 var selectLabelledbyIds = [legendId, id].join(' ').trim(); 20064 return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) { 20065 var optionId = id + '-' + o[1].replace(/\W+/g, ''); 20066 return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join(''); 20067 })).concat('</select>').join(''); 20068 } 20069 /** 20070 * Create foreground color element for the component 20071 * 20072 * @return {string} 20073 * An HTML string. 20074 * 20075 * @private 20076 */ 20077 ; 20078 20079 _proto.createElFgColor_ = function createElFgColor_() { 20080 var legendId = "captions-text-legend-" + this.id_; 20081 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(''); 20082 } 20083 /** 20084 * Create background color element for the component 20085 * 20086 * @return {string} 20087 * An HTML string. 20088 * 20089 * @private 20090 */ 20091 ; 20092 20093 _proto.createElBgColor_ = function createElBgColor_() { 20094 var legendId = "captions-background-" + this.id_; 20095 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(''); 20096 } 20097 /** 20098 * Create window color element for the component 20099 * 20100 * @return {string} 20101 * An HTML string. 20102 * 20103 * @private 20104 */ 20105 ; 20106 20107 _proto.createElWinColor_ = function createElWinColor_() { 20108 var legendId = "captions-window-" + this.id_; 20109 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(''); 20110 } 20111 /** 20112 * Create color elements for the component 20113 * 20114 * @return {Element} 20115 * The element that was created 20116 * 20117 * @private 20118 */ 20119 ; 20120 20121 _proto.createElColors_ = function createElColors_() { 20122 return createEl('div', { 20123 className: 'vjs-track-settings-colors', 20124 innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') 20125 }); 20126 } 20127 /** 20128 * Create font elements for the component 20129 * 20130 * @return {Element} 20131 * The element that was created. 20132 * 20133 * @private 20134 */ 20135 ; 20136 20137 _proto.createElFont_ = function createElFont_() { 20138 return createEl('div', { 20139 className: 'vjs-track-settings-font', 20140 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('') 20141 }); 20142 } 20143 /** 20144 * Create controls for the component 20145 * 20146 * @return {Element} 20147 * The element that was created. 20148 * 20149 * @private 20150 */ 20151 ; 20152 20153 _proto.createElControls_ = function createElControls_() { 20154 var defaultsDescription = this.localize('restore all settings to the default values'); 20155 return createEl('div', { 20156 className: 'vjs-track-settings-controls', 20157 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('') 20158 }); 20159 }; 20160 20161 _proto.content = function content() { 20162 return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; 20163 }; 20164 20165 _proto.label = function label() { 20166 return this.localize('Caption Settings Dialog'); 20167 }; 20168 20169 _proto.description = function description() { 20170 return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); 20171 }; 20172 20173 _proto.buildCSSClass = function buildCSSClass() { 20174 return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; 20175 } 20176 /** 20177 * Gets an object of text track settings (or null). 20178 * 20179 * @return {Object} 20180 * An object with config values parsed from the DOM or localStorage. 20181 */ 20182 ; 20183 20184 _proto.getValues = function getValues() { 20185 var _this3 = this; 20186 20187 return reduce(selectConfigs, function (accum, config, key) { 20188 var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); 20189 20190 if (value !== undefined) { 20191 accum[key] = value; 20192 } 20193 20194 return accum; 20195 }, {}); 20196 } 20197 /** 20198 * Sets text track settings from an object of values. 20199 * 20200 * @param {Object} values 20201 * An object with config values parsed from the DOM or localStorage. 20202 */ 20203 ; 20204 20205 _proto.setValues = function setValues(values) { 20206 var _this4 = this; 20207 20208 each(selectConfigs, function (config, key) { 20209 setSelectedOption(_this4.$(config.selector), values[key], config.parser); 20210 }); 20211 } 20212 /** 20213 * Sets all `<select>` elements to their default values. 20214 */ 20215 ; 20216 20217 _proto.setDefaults = function setDefaults() { 20218 var _this5 = this; 20219 20220 each(selectConfigs, function (config) { 20221 var index = config.hasOwnProperty('default') ? config["default"] : 0; 20222 _this5.$(config.selector).selectedIndex = index; 20223 }); 20224 } 20225 /** 20226 * Restore texttrack settings from localStorage 20227 */ 20228 ; 20229 20230 _proto.restoreSettings = function restoreSettings() { 20231 var values; 20232
20233 try { 20234 values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY)); 20235 } catch (err) { 20236 log.warn(err); 20237 } 20238 20239 if (values) { 20240 this.setValues(values); 20241 } 20242 } 20243 /** 20244 * Save text track settings to localStorage 20245 */ 20246 ; 20247 20248 _proto.saveSettings = function saveSettings() { 20249 if (!this.options_.persistTextTrackSettings) { 20250 return; 20251 } 20252 20253 var values = this.getValues(); 20254 20255 try { 20256 if (Object.keys(values).length) { 20257 window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values)); 20258 } else { 20259 window$1.localStorage.removeItem(LOCAL_STORAGE_KEY); 20260 } 20261 } catch (err) { 20262 log.warn(err); 20263 } 20264 } 20265 /** 20266 * Update display of text track settings 20267 */ 20268 ; 20269 20270 _proto.updateDisplay = function updateDisplay() { 20271 var ttDisplay = this.player_.getChild('textTrackDisplay'); 20272 20273 if (ttDisplay) { 20274 ttDisplay.updateDisplay(); 20275 } 20276 } 20277 /** 20278 * conditionally blur the element and refocus the captions button 20279 * 20280 * @private 20281 */ 20282 ; 20283 20284 _proto.conditionalBlur_ = function conditionalBlur_() { 20285 this.previouslyActiveEl_ = null; 20286 var cb = this.player_.controlBar; 20287 var subsCapsBtn = cb && cb.subsCapsButton; 20288 var ccBtn = cb && cb.captionsButton; 20289 20290 if (subsCapsBtn) { 20291 subsCapsBtn.focus(); 20292 } else if (ccBtn) { 20293 ccBtn.focus(); 20294 } 20295 }; 20296 20297 return TextTrackSettings; 20298 }(ModalDialog); 20299 20300 Component.registerComponent('TextTrackSettings', TextTrackSettings); 20301 20302 /** 20303 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions. 20304 * 20305 * 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}. 20306 * 20307 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option. 20308 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below. 20309 * @example <caption>How to disable the resize manager</caption> 20310 * const player = videojs('#vid', { 20311 * resizeManager: false 20312 * }); 20313 * 20314 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification} 20315 * 20316 * @extends Component 20317 */ 20318 20319 var ResizeManager = 20320 /*#__PURE__*/ 20321 function (_Component) { 20322 inheritsLoose(ResizeManager, _Component); 20323 20324 /** 20325 * Create the ResizeManager. 20326 * 20327 * @param {Object} player 20328 * The `Player` that this class should be attached to. 20329 * 20330 * @param {Object} [options] 20331 * The key/value store of ResizeManager options. 20332 * 20333 * @param {Object} [options.ResizeObserver] 20334 * A polyfill for ResizeObserver can be passed in here. 20335 * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback. 20336 */ 20337 function ResizeManager(player, options) { 20338 var _this; 20339 20340 var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver 20341 20342 if (options.ResizeObserver === null) { 20343 RESIZE_OBSERVER_AVAILABLE = false; 20344 } // Only create an element when ResizeObserver isn't available 20345 20346 20347 var options_ = mergeOptions({ 20348 createEl: !RESIZE_OBSERVER_AVAILABLE, 20349 reportTouchActivity: false 20350 }, options); 20351 _this = _Component.call(this, player, options_) || this; 20352 _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver; 20353 _this.loadListener_ = null; 20354 _this.resizeObserver_ = null; 20355 _this.debouncedHandler_ = debounce(function () { 20356 _this.resizeHandler(); 20357 }, 100, false, assertThisInitialized(_this)); 20358 20359 if (RESIZE_OBSERVER_AVAILABLE) { 20360 _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_); 20361
20362 _this.resizeObserver_.observe(player.el()); 20363 } else { 20364 _this.loadListener_ = function () { 20365 if (!_this.el_ || !_this.el_.contentWindow) { 20366 return; 20367 } 20368 20369 var debouncedHandler_ = _this.debouncedHandler_; 20370 20371 var unloadListener_ = _this.unloadListener_ = function () { 20372 off(this, 'resize', debouncedHandler_); 20373 off(this, 'unload', unloadListener_); 20374 unloadListener_ = null; 20375 }; // safari and edge can unload the iframe before resizemanager dispose 20376 // we have to dispose of event handlers correctly before that happens 20377 20378 20379 on(_this.el_.contentWindow, 'unload', unloadListener_); 20380 on(_this.el_.contentWindow, 'resize', debouncedHandler_); 20381 }; 20382 20383 _this.one('load', _this.loadListener_); 20384 } 20385 20386 return _this; 20387 } 20388 20389 var _proto = ResizeManager.prototype; 20390 20391 _proto.createEl = function createEl() { 20392 return _Component.prototype.createEl.call(this, 'iframe', { 20393 className: 'vjs-resize-manager', 20394 tabIndex: -1 20395 }, { 20396 'aria-hidden': 'true' 20397 }); 20398 } 20399 /** 20400 * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver 20401 * 20402 * @fires Player#playerresize 20403 */ 20404 ; 20405 20406 _proto.resizeHandler = function resizeHandler() { 20407 /** 20408 * Called when the player size has changed 20409 * 20410 * @event Player#playerresize 20411 * @type {EventTarget~Event} 20412 */ 20413 // make sure player is still around to trigger 20414 // prevents this from causing an error after dispose 20415 if (!this.player_ || !this.player_.trigger) { 20416 return; 20417 } 20418 20419 this.player_.trigger('playerresize'); 20420 }; 20421 20422 _proto.dispose = function dispose() { 20423 if (this.debouncedHandler_) { 20424 this.debouncedHandler_.cancel(); 20425 } 20426 20427 if (this.resizeObserver_) { 20428 if (this.player_.el()) { 20429 this.resizeObserver_.unobserve(this.player_.el()); 20430 } 20431 20432 this.resizeObserver_.disconnect(); 20433 } 20434 20435 if (this.loadListener_) { 20436 this.off('load', this.loadListener_); 20437 } 20438 20439 if (this.el_ && this.el_.contentWindow && this.unloadListener_) { 20440 this.unloadListener_.call(this.el_.contentWindow); 20441 } 20442 20443 this.ResizeObserver = null; 20444 this.resizeObserver = null; 20445 this.debouncedHandler_ = null; 20446 this.loadListener_ = null; 20447 20448 _Component.prototype.dispose.call(this); 20449 }; 20450 20451 return ResizeManager; 20452 }(Component); 20453 20454 Component.registerComponent('ResizeManager', ResizeManager); 20455 20456 /** 20457 * Computes the median of an array. 20458 * 20459 * @param {number[]} arr 20460 * Input array of numbers. 20461 * 20462 * @return {number} 20463 * Median value. 20464 */ 20465 var median = function median(arr) { 20466 var mid = Math.floor(arr.length / 2); 20467 var sortedList = [].concat(arr).sort(function (a, b) { 20468 return a - b; 20469 }); 20470 return arr.length % 2 !== 0 ? sortedList[mid] : (sortedList[mid - 1] + sortedList[mid]) / 2; 20471 }; 20472 20473 /* track when we are at the live edge, and other helpers for live playback */ 20474 20475 var LiveTracker = 20476 /*#__PURE__*/ 20477 function (_Component) { 20478 inheritsLoose(LiveTracker, _Component); 20479 20480 function LiveTracker(player, options) { 20481 var _this; 20482 20483 // LiveTracker does not need an element 20484 var options_ = mergeOptions({ 20485 createEl: false 20486 }, options); 20487 _this = _Component.call(this, player, options_) || this; 20488 20489 _this.reset_(); 20490 20491 _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden, 20492 // also, tracking when the document is hidden can 20493 // cause the CPU to spike and eventually crash the page on IE11. 20494 20495 20496 if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) { 20497 _this.on(document, 'visibilitychange', _this.handleVisibilityChange); 20498 } 20499 20500 return _this; 20501 } 20502 20503 var _proto = LiveTracker.prototype; 20504 20505 _proto.handleVisibilityChange = function handleVisibilityChange() { 20506 if (this.player_.duration() !== Infinity) { 20507 return; 20508 } 20509 20510 if (document.hidden) { 20511 this.stopTracking(); 20512 } else { 20513 this.startTracking(); 20514 } 20515 }; 20516 20517 _proto.isBehind_ = function isBehind_() { 20518 // don't report that we are behind until a timeupdate has been seen 20519 if (!this.timeupdateSeen_) { 20520 return false; 20521 } 20522 20523 var liveCurrentTime = this.liveCurrentTime(); 20524 var currentTime = this.player_.currentTime(); // the live edge window is the amount of seconds away from live 20525 // that a player can be, but still be considered live. 20526 // we add 0.07 because the live tracking happens every 30ms 20527 // and we want some wiggle room for short segment live playback 20528 20529 var liveEdgeWindow = this.seekableIncrement_ * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd 20530 // can be Infinity, so we handle that case. 20531 20532 return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime; 20533 } // all the functionality for tracking when seek end changes 20534 // and for tracking how far past seek end we should be 20535 ; 20536 20537 _proto.trackLive_ = function trackLive_() { 20538 this.pastSeekEnd_ = this.pastSeekEnd_; 20539 var seekable = this.player_.seekable();
20539 // skip undefined seekable 20540 20541 if (!seekable || !seekable.length) { 20542 return; 20543 } 20544 20545 var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes 20546 // once we detect that seekable has changed we check the new seek 20547 // end against current time, with a fudge value of half a second. 20548 20549 if (newSeekEnd !== this.lastSeekEnd_) { 20550 if (this.lastSeekEnd_) { 20551 // we try to get the best fit value for the seeking increment 20552 // variable from the last 12 values. 20553 this.seekableIncrementList_ = this.seekableIncrementList_.slice(-11); 20554 this.seekableIncrementList_.push(Math.abs(newSeekEnd - this.lastSeekEnd_)); 20555 20556 if (this.seekableIncrementList_.length > 3) { 20557 this.seekableIncrement_ = median(this.seekableIncrementList_); 20558 } 20559 } 20560 20561 this.pastSeekEnd_ = 0; 20562 this.lastSeekEnd_ = newSeekEnd; 20563 this.trigger('seekableendchange'); 20564 } // we should reset pastSeekEnd when the value 20565 // is much higher than seeking increment. 20566 20567 20568 if (this.pastSeekEnd() > this.seekableIncrement_ * 1.5) { 20569 this.pastSeekEnd_ = 0; 20570 } else { 20571 this.pastSeekEnd_ = this.pastSeekEnd() + 0.03; 20572 } 20573 20574 if (this.isBehind_() !== this.behindLiveEdge()) { 20575 this.behindLiveEdge_ = this.isBehind_(); 20576 this.trigger('liveedgechange'); 20577 } 20578 } 20579 /** 20580 * handle a durationchange event on the player 20581 * and start/stop tracking accordingly. 20582 */ 20583 ; 20584 20585 _proto.handleDurationchange = function handleDurationchange() { 20586 if (this.player_.duration() === Infinity) { 20587 this.startTracking(); 20588 } else { 20589 this.stopTracking(); 20590 } 20591 } 20592 /** 20593 * start tracking live playback 20594 */ 20595 ; 20596 20597 _proto.startTracking = function startTracking() { 20598 var _this2 = this; 20599 20600 if (this.isTracking()) { 20601 return; 20602 } // If we haven't seen a timeupdate, we need to check whether playback 20603 // began before this component started tracking. This can happen commonly 20604 // when using autoplay. 20605 20606 20607 if (!this.timeupdateSeen_) { 20608 this.timeupdateSeen_ = this.player_.hasStarted(); 20609 } 20610 20611 this.trackingInterval_ = this.setInterval(this.trackLive_, UPDATE_REFRESH_INTERVAL); 20612 this.trackLive_(); 20613 this.on(this.player_, 'play', this.trackLive_); 20614 this.on(this.player_, 'pause', this.trackLive_); // this is to prevent showing that we are not live 20615 // before a video starts to play 20616 20617 if (!this.timeupdateSeen_) { 20618 this.one(this.player_, 'play', this.handlePlay); 20619 20620 this.handleTimeupdate = function () { 20621 _this2.timeupdateSeen_ = true; 20622 _this2.handleTimeupdate = null; 20623 }; 20624 20625 this.one(this.player_, 'timeupdate', this.handleTimeupdate); 20626 } 20627 }; 20628 20629 _proto.handlePlay = function handlePlay() { 20630 this.one(this.player_, 'timeupdate', this.seekToLiveEdge); 20631 } 20632 /** 20633 * Stop tracking, and set all internal variables to 20634 * their initial value. 20635 */ 20636 ; 20637 20638 _proto.reset_ = function reset_() { 20639 this.pastSeekEnd_ = 0; 20640 this.lastSeekEnd_ = null; 20641 this.behindLiveEdge_ = null; 20642 this.timeupdateSeen_ = false; 20643 this.clearInterval(this.trackingInterval_); 20644 this.trackingInterval_ = null; 20645 this.seekableIncrement_ = 12; 20646 this.seekableIncrementList_ = []; 20647 this.off(this.player_, 'play', this.trackLive_); 20648 this.off(this.player_, 'pause', this.trackLive_); 20649 this.off(this.player_, 'play', this.handlePlay); 20650 this.off(this.player_, 'timeupdate', this.seekToLiveEdge); 20651 20652 if (this.handleTimeupdate) { 20653 this.off(this.player_, 'timeupdate', this.handleTimeupdate); 20654 this.handleTimeupdate = null; 20655 } 20656 } 20657 /** 20658 * stop tracking live playback 20659 */ 20660 ; 20661 20662 _proto.stopTracking = function stopTracking() { 20663 if (!this.isTracking()) { 20664 return; 20665 } 20666 20667 this.reset_(); 20668 } 20669 /** 20670 * A helper to get the player seekable end 20671 * so that we don't have to null check everywhere 20672 */ 20673 ; 20674 20675 _proto.seekableEnd = function seekableEnd() { 20676 var seekable = this.player_.seekable(); 20677 var seekableEnds = []; 20678 var i = seekable ? seekable.length : 0; 20679 20680 while (i--) { 20681 seekableEnds.push(seekable.end(i)); 20682 } // grab the furthest seekable end after sorting, or if there are none 20683 // default to Infinity 20684 20685 20686 return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity; 20687 } 20688 /** 20689 * A helper to get the player seekable start 20690 * so that we don't have to null check everywhere 20691 */ 20692 ; 20693 20694 _proto.seekableStart = function seekableStart() { 20695 var seekable = this.player_.seekable(); 20696 var seekableStarts = []; 20697 var i = seekable ? seekable.length : 0; 20698 20699 while (i--) { 20700 seekableStarts.push(seekable.start(i)); 20701 } // grab the first seekable start after sorting, or if there are none 20702 // default to 0 20703 20704 20705 return seekableStarts.length ? seekableStarts.sort()[0] : 0; 20706 } 20707 /** 20708 * Get the live time window 20709 */ 20710 ; 20711 20712 _proto.liveWindow = function liveWindow() { 20713 var liveCurrentTime = this.liveCurrentTime(); 20714 20715 if (liveCurrentTime === Infinity) { 20716 return Infinity; 20717 } 20718 20719 return liveCurrentTime - this.seekableStart(); 20720 } 20721 /** 20722 * Determines if the player is live, only checks if this component 20723 * is tracking live playback or not 20724 */ 20725 ; 20726 20727 _proto.isLive = function isLive() { 20728 return this.isTracking(); 20729 } 20730 /** 20731 * Determines if currentTime is at the live edge and won't fall behind 20732 * on each seekableendchange 20733 */ 20734 ; 20735 20736 _proto.atLiveEdge = function atLiveEdge() { 20737 return !this.behindLiveEdge(); 20738 } 20739 /** 20740 * get what we expect the live current time to be 20741 */ 20742 ; 20743 20744 _proto.liveCurrentTime = function liveCurrentTime() { 20745 return this.pastSeekEnd() + this.seekableEnd(); 20746 } 20747 /** 20748 * Returns how far past seek end we expect current time to be 20749 */ 20750 ; 20751 20752 _proto.pastSeekEnd = function pastSeekEnd() { 20753 return this.pastSeekEnd_; 20754 } 20755 /** 20756 * If we are currently behind the live edge, aka currentTime will be 20757 * behind on a seekableendchange 20758 */ 20759 ; 20760 20761 _proto.behindLiveEdge = function behindLiveEdge() { 20762 return this.behindLiveEdge_; 20763 }; 20764 20765 _proto.isTracking = function isTracking() { 20766 return typeof this.trackingInterval_ === 'number'; 20767 } 20768 /** 20769 * Seek to the live edge if we are behind the live edge 20770 */ 20771 ; 20772 20773 _proto.seekToLiveEdge = function seekToLiveEdge() { 20774 if (this.atLiveEdge()) { 20775 return; 20776 } 20777 20778 this.player_.currentTime(this.liveCurrentTime()); 20779 20780 if (this.player_.paused()) { 20781 this.player_.play(); 20782 } 20783 }; 20784 20785 _proto.dispose = function dispose() { 20786 this.stopTracking(); 20787 20788 _Component.prototype.dispose.call(this); 20789 }; 20790 20791 return LiveTracker; 20792 }(Component); 20793 20794 Component.registerComponent('LiveTracker', LiveTracker); 20795 20796 /** 20797 * This function is used to fire a sourceset when there is something 20798 * similar to `mediaEl.load()` being called. It will try to find the source via 20799 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset` 20800 * with the source that was found or empty string if we cannot know. If it cannot 20801 * find a source then `sourceset` will not be fired. 20802 * 20803 * @param {Html5} tech 20804 * The tech object that sourceset was setup on 20805 * 20806 * @return {boolean} 20807 * returns false if the sourceset was not fired and true otherwise. 20808 */ 20809 20810 var sourcesetLoad = function sourcesetLoad(tech) { 20811 var el = tech.el(); // if `el.src` is set, that source will be loaded. 20812 20813 if (el.hasAttribute('src')) { 20814 tech.triggerSourceset(el.src); 20815 return true; 20816 } 20817 /** 20818 * Since there isn't a src property on the media element, source elements will be used for 20819 * implementing the source selection algorithm. This happens asynchronously and 20820 * for most cases were there is more than one source we cannot tell what source will 20821 * be loaded, without re-implementing the source selection algorithm. At this time we are not 20822 * going to do that. There are three special cases that we do handle here though: 20823 * 20824 * 1. If there are no sources, do not fire `sourceset`. 20825 * 2. If there is only one `<source>` with a `src` property/attribute that is our `src` 20826 * 3. If there is more than one `<source>` but all of them have the same `src` url. 20827 * That will be our src. 20828 */ 20829 20830 20831 var sources = tech.$$('source'); 20832 var srcUrls = []; 20833 var src = ''; // if there are no sources, do not fire sourceset 20834 20835 if (!sources.length) { 20836 return false; 20837 }
vendor: 39,741 bytes, lines 20837-22031
20837 // only count valid/non-duplicate source elements 20838 20839 20840 for (var i = 0; i < sources.length; i++) { 20841 var url = sources[i].src; 20842 20843 if (url && srcUrls.indexOf(url) === -1) { 20844 srcUrls.push(url); 20845 } 20846 } // there were no valid sources 20847 20848 20849 if (!srcUrls.length) { 20850 return false; 20851 } // there is only one valid source element url 20852 // use that 20853 20854 20855 if (srcUrls.length === 1) { 20856 src = srcUrls[0]; 20857 } 20858 20859 tech.triggerSourceset(src); 20860 return true; 20861 }; 20862 /** 20863 * our implementation of an `innerHTML` descriptor for browsers 20864 * that do not have one. 20865 */ 20866 20867 20868 var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', { 20869 get: function get() { 20870 return this.cloneNode(true).innerHTML; 20871 }, 20872 set: function set(v) { 20873 // make a dummy node to use innerHTML on 20874 var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided 20875 20876 dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy 20877 20878 var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy 20879 // to the document fragment 20880 20881 while (dummy.childNodes.length) { 20882 docFrag.appendChild(dummy.childNodes[0]); 20883 } // remove content 20884 20885 20886 this.innerText = ''; // now we add all of that html in one by appending the 20887 // document fragment. This is how innerHTML does it. 20888 20889 window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would 20890 20891 return this.innerHTML; 20892 } 20893 }); 20894 /** 20895 * Get a property descriptor given a list of priorities and the 20896 * property to get. 20897 */ 20898 20899 var getDescriptor = function getDescriptor(priority, prop) { 20900 var descriptor = {}; 20901 20902 for (var i = 0; i < priority.length; i++) { 20903 descriptor = Object.getOwnPropertyDescriptor(priority[i], prop); 20904 20905 if (descriptor && descriptor.set && descriptor.get) { 20906 break; 20907 } 20908 } 20909 20910 descriptor.enumerable = true; 20911 descriptor.configurable = true; 20912 return descriptor; 20913 }; 20914 20915 var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) { 20916 return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML'); 20917 }; 20918 /** 20919 * Patches browser internal functions so that we can tell synchronously 20920 * if a `<source>` was appended to the media element. For some reason this 20921 * causes a `sourceset` if the the media element is ready and has no source. 20922 * This happens when: 20923 * - The page has just loaded and the media element does not have a source. 20924 * - The media element was emptied of all sources, then `load()` was called. 20925 * 20926 * It does this by patching the following functions/properties when they are supported: 20927 * 20928 * - `append()` - can be used to add a `<source>` element to the media element 20929 * - `appendChild()` - can be used to add a `<source>` element to the media element 20930 * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element 20931 * - `innerHTML` - can be used to add a `<source>` element to the media element 20932 * 20933 * @param {Html5} tech 20934 * The tech object that sourceset is being setup on. 20935 */ 20936 20937 20938 var firstSourceWatch = function firstSourceWatch(tech) { 20939 var el = tech.el(); // make sure firstSourceWatch isn't setup twice. 20940 20941 if (el.resetSourceWatch_) { 20942 return; 20943 } 20944 20945 var old = {}; 20946 var innerDescriptor = getInnerHTMLDescriptor(tech); 20947 20948 var appendWrapper = function appendWrapper(appendFn) { 20949 return function () { 20950 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 20951 args[_key] = arguments[_key]; 20952 } 20953 20954 var retval = appendFn.apply(el, args); 20955 sourcesetLoad(tech); 20956 return retval; 20957 }; 20958 }; 20959 20960 ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) { 20961 if (!el[k]) { 20962 return; 20963 } // store the old function 20964 20965 20966 old[k] = el[k]; // call the old function with a sourceset if a source 20967 // was loaded 20968 20969 el[k] = appendWrapper(old[k]); 20970 }); 20971 Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, { 20972 set: appendWrapper(innerDescriptor.set) 20973 })); 20974 20975 el.resetSourceWatch_ = function () { 20976 el.resetSourceWatch_ = null; 20977 Object.keys(old).forEach(function (k) { 20978 el[k] = old[k]; 20979 }); 20980 Object.defineProperty(el, 'innerHTML', innerDescriptor); 20981 }; // on the first sourceset, we need to revert our changes 20982 20983 20984 tech.one('sourceset', el.resetSourceWatch_); 20985 }; 20986 /** 20987 * our implementation of a `src` descriptor for browsers 20988 * that do not have one. 20989 */ 20990 20991 20992 var srcDescriptorPolyfill = Object.defineProperty({}, 'src', { 20993 get: function get() { 20994 if (this.hasAttribute('src')) { 20995 return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src')); 20996 } 20997 20998 return ''; 20999 }, 21000 set: function set(v) { 21001 window$1.Element.prototype.setAttribute.call(this, 'src', v); 21002 return v; 21003 } 21004 }); 21005 21006 var getSrcDescriptor = function getSrcDescriptor(tech) { 21007 return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src'); 21008 }; 21009 /** 21010 * setup `sourceset` handling on the `Html5` tech. This function 21011 * patches the following element properties/functions: 21012 * 21013 * - `src` - to determine when `src` is set 21014 * - `setAttribute()` - to determine when `src` is set 21015 * - `load()` - this re-triggers the source selection algorithm, and can 21016 * cause a sourceset. 21017 * 21018 * If there is no source when we are adding `sourceset` support or during a `load()` 21019 * we also patch the functions listed in `firstSourceWatch`. 21020 * 21021 * @param {Html5} tech 21022 * The tech to patch 21023 */ 21024 21025 21026 var setupSourceset = function setupSourceset(tech) { 21027 if (!tech.featuresSourceset) { 21028 return; 21029 } 21030 21031 var el = tech.el(); // make sure sourceset isn't setup twice. 21032 21033 if (el.resetSourceset_) { 21034 return; 21035 } 21036 21037 var srcDescriptor = getSrcDescriptor(tech); 21038 var oldSetAttribute = el.setAttribute; 21039 var oldLoad = el.load; 21040 Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, { 21041 set: function set(v) { 21042 var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src 21043 21044 tech.triggerSourceset(el.src); 21045 return retval; 21046 } 21047 })); 21048 21049 el.setAttribute = function (n, v) { 21050 var retval = oldSetAttribute.call(el, n, v); 21051 21052 if (/src/i.test(n)) { 21053 tech.triggerSourceset(el.src); 21054 } 21055 21056 return retval; 21057 }; 21058 21059 el.load = function () { 21060 var retval = oldLoad.call(el); // if load was called, but there was no source to fire 21061 // sourceset on. We have to watch for a source append 21062 // as that can trigger a `sourceset` when the media element 21063 // has no source 21064 21065 if (!sourcesetLoad(tech)) { 21066 tech.triggerSourceset(''); 21067 firstSourceWatch(tech); 21068 } 21069 21070 return retval; 21071 }; 21072 21073 if (el.currentSrc) { 21074 tech.triggerSourceset(el.currentSrc); 21075 } else if (!sourcesetLoad(tech)) { 21076 firstSourceWatch(tech); 21077 } 21078 21079 el.resetSourceset_ = function () { 21080 el.resetSourceset_ = null; 21081 el.load = oldLoad; 21082 el.setAttribute = oldSetAttribute; 21083 Object.defineProperty(el, 'src', srcDescriptor); 21084 21085 if (el.resetSourceWatch_) { 21086 el.resetSourceWatch_(); 21087 } 21088 }; 21089 }; 21090 21091 /** 21092 * Object.defineProperty but "lazy", which means that the value is only set after 21093 * it retrieved the first time, rather than being set right away. 21094 * 21095 * @param {Object} obj the object to set the property on 21096 * @param {string} key the key for the property to set 21097 * @param {Function} getValue the function used to get the value when it is needed. 21098 * @param {boolean} setter wether a setter shoould be allowed or not 21099 */ 21100 var defineLazyProperty = function defineLazyProperty(obj, key, getValue, setter) { 21101 if (setter === void 0) { 21102 setter = true; 21103 } 21104 21105 var set = function set(value) { 21106 return Object.defineProperty(obj, key, { 21107 value: value, 21108 enumerable: true, 21109 writable: true 21110 }); 21111 }; 21112 21113 var options = { 21114 configurable: true, 21115 enumerable: true, 21116 get: function get() { 21117 var value = getValue(); 21118 set(value); 21119 return value; 21120 } 21121 }; 21122 21123 if (setter) { 21124 options.set = set; 21125 } 21126 21127 return Object.defineProperty(obj, key, options); 21128 }; 21129 21130 /** 21131 * HTML5 Media Controller - Wrapper for HTML5 Media API 21132 * 21133 * @mixes Tech~SourceHandlerAdditions 21134 * @extends Tech 21135 */ 21136 21137 var Html5 = 21138 /*#__PURE__*/ 21139 function (_Tech) { 21140 inheritsLoose(Html5, _Tech); 21141 21142 /** 21143 * Create an instance of this Tech. 21144 * 21145 * @param {Object} [options] 21146 * The key/value store of player options. 21147 * 21148 * @param {Component~ReadyCallback} ready 21149 * Callback function to call when the `HTML5` Tech is ready. 21150 */ 21151 function Html5(options, ready) { 21152 var _this; 21153 21154 _this = _Tech.call(this, options, ready) || this; 21155 var source = options.source; 21156 var crossoriginTracks = false; // Set the source if one is provided 21157 // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) 21158 // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source 21159 // anyway so the error gets fired. 21160 21161 if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) { 21162 _this.setSource(source); 21163 } else { 21164 _this.handleLateInit_(_this.el_); 21165 } // setup sourceset after late sourceset/init 21166 21167 21168 if (options.enableSourceset) { 21169 _this.setupSourcesetHandling_(); 21170 } 21171 21172 if (_this.el_.hasChildNodes()) { 21173 var nodes = _this.el_.childNodes; 21174 var nodesLength = nodes.length; 21175 var removeNodes = []; 21176 21177 while (nodesLength--) { 21178 var node = nodes[nodesLength]; 21179 var nodeName = node.nodeName.toLowerCase(); 21180 21181 if (nodeName === 'track') { 21182 if (!_this.featuresNativeTextTracks) { 21183 // Empty video tag tracks so the built-in player doesn't use them also. 21184 // This may not be fast enough to stop HTML5 browsers from reading the tags 21185 // so we'll need to turn off any default tracks if we're manually doing 21186 // captions and subtitles. videoElement.textTracks 21187 removeNodes.push(node); 21188 } else { 21189 // store HTMLTrackElement and TextTrack to remote list 21190 _this.remoteTextTrackEls().addTrackElement_(node); 21191 21192 _this.remoteTextTracks().addTrack(node.track); 21193 21194 _this.textTracks().addTrack(node.track); 21195 21196 if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) { 21197 crossoriginTracks = true; 21198 } 21199 } 21200 } 21201 } 21202 21203 for (var i = 0; i < removeNodes.length; i++) { 21204 _this.el_.removeChild(removeNodes[i]); 21205 } 21206 } 21207 21208 _this.proxyNativeTracks_(); 21209 21210 if (_this.featuresNativeTextTracks && crossoriginTracks) { 21211 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.'); 21212 } // prevent iOS Safari from disabling metadata text tracks during native playback 21213 21214 21215 _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used 21216 // Our goal should be to get the custom controls on mobile solid everywhere 21217 // so we can remove this all together. Right now this will block custom 21218 // controls on touch enabled laptops like the Chrome Pixel 21219 21220 21221 if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { 21222 _this.setControls(true); 21223 } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` 21224 // into a `fullscreenchange` event 21225 21226 21227 _this.proxyWebkitFullscreen_(); 21228 21229 _this.triggerReady(); 21230 21231 return _this; 21232 } 21233 /** 21234 * Dispose of `HTML5` media element and remove all tracks. 21235 */ 21236 21237 21238 var _proto = Html5.prototype; 21239 21240 _proto.dispose = function dispose() { 21241 if (this.el_ && this.el_.resetSourceset_) { 21242 this.el_.resetSourceset_(); 21243 } 21244 21245 Html5.disposeMediaElement(this.el_); 21246 this.options_ = null; // tech will handle clearing of the emulated track list 21247 21248 _Tech.prototype.dispose.call(this); 21249 } 21250 /** 21251 * Modify the media element so that we can detect when 21252 * the source is changed. Fires `sourceset` just after the source has changed 21253 */ 21254 ; 21255 21256 _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() { 21257 setupSourceset(this); 21258 } 21259 /** 21260 * When a captions track is enabled in the iOS Safari native player, all other 21261 * tracks are disabled (including metadata tracks), which nulls all of their 21262 * associated cue points. This will restore metadata tracks to their pre-fullscreen 21263 * state in those cases so that cue points are not needlessly lost. 21264 * 21265 * @private 21266 */ 21267 ; 21268 21269 _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() { 21270 var textTracks = this.textTracks(); 21271 var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state 21272 21273 var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() { 21274 metadataTracksPreFullscreenState = []; 21275 21276 for (var i = 0; i < textTracks.length; i++) { 21277 var track = textTracks[i]; 21278 21279 if (track.kind === 'metadata') { 21280 metadataTracksPreFullscreenState.push({ 21281 track: track, 21282 storedMode: track.mode 21283 }); 21284 } 21285 } 21286 }; // snapshot each metadata track's initial state, and update the snapshot 21287 // each time there is a track 'change' event 21288 21289 21290 takeMetadataTrackSnapshot(); 21291 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 21292 this.on('dispose', function () { 21293 return textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 21294 }); 21295 21296 var restoreTrackMode = function restoreTrackMode() { 21297 for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) { 21298 var storedTrack = metadataTracksPreFullscreenState[i]; 21299 21300 if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) { 21301 storedTrack.track.mode = storedTrack.storedMode; 21302 } 21303 } // we only want this handler to be executed on the first 'change' event 21304 21305 21306 textTracks.removeEventListener('change', restoreTrackMode); 21307 }; // when we enter fullscreen playback, stop updating the snapshot and 21308 // restore all track modes to their pre-fullscreen state 21309 21310 21311 this.on('webkitbeginfullscreen', function () { 21312 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed 21313 21314 textTracks.removeEventListener('change', restoreTrackMode); 21315 textTracks.addEventListener('change', restoreTrackMode); 21316 }); // start updating the snapshot again after leaving fullscreen 21317 21318 this.on('webkitendfullscreen', function () { 21319 // remove the listener before adding it just in case it wasn't previously removed 21320 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 21321 textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback 21322 21323 textTracks.removeEventListener('change', restoreTrackMode); 21324 }); 21325 } 21326 /** 21327 * Attempt to force override of tracks for the given type 21328 * 21329 * @param {string} type - Track type to override, possible values include 'Audio', 21330 * 'Video', and 'Text'. 21331 * @param {boolean} override - If set to true native audio/video will be overridden, 21332 * otherwise native audio/video will potentially be used. 21333 * @private 21334 */ 21335 ; 21336 21337 _proto.overrideNative_ = function overrideNative_(type, override) { 21338 var _this2 = this; 21339 21340 // If there is no behavioral change don't add/remove listeners 21341 if (override !== this["featuresNative" + type + "Tracks"]) { 21342 return; 21343 } 21344 21345 var lowerCaseType = type.toLowerCase(); 21346 21347 if (this[lowerCaseType + "TracksListeners_"]) { 21348 Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) { 21349 var elTracks = _this2.el()[lowerCaseType + "Tracks"]; 21350 21351 elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]); 21352 }); 21353 } 21354 21355 this["featuresNative" + type + "Tracks"] = !override; 21356 this[lowerCaseType + "TracksListeners_"] = null; 21357 this.proxyNativeTracksForType_(lowerCaseType); 21358 } 21359 /** 21360 * Attempt to force override of native audio tracks. 21361 * 21362 * @param {boolean} override - If set to true native audio will be overridden, 21363 * otherwise native audio will potentially be used. 21364 */ 21365 ; 21366 21367 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) { 21368 this.overrideNative_('Audio', override); 21369 } 21370 /** 21371 * Attempt to force override of native video tracks. 21372 * 21373 * @param {boolean} override - If set to true native video will be overridden, 21374 * otherwise native video will potentially be used. 21375 */ 21376 ; 21377 21378 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) { 21379 this.overrideNative_('Video', override); 21380 } 21381 /** 21382 * Proxy native track list events for the given type to our track 21383 * lists if the browser we are playing in supports that type of track list. 21384 * 21385 * @param {string} name - Track type; values include 'audio', 'video', and 'text' 21386 * @private 21387 */ 21388 ; 21389 21390 _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) { 21391 var _this3 = this; 21392 21393 var props = NORMAL[name]; 21394 var elTracks = this.el()[props.getterName]; 21395 var techTracks = this[props.getterName](); 21396 21397 if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) { 21398 return; 21399 } 21400 21401 var listeners = { 21402 change: function change(e) { 21403 techTracks.trigger({ 21404 type: 'change', 21405 target: techTracks, 21406 currentTarget: techTracks, 21407 srcElement: techTracks 21408 }); 21409 }, 21410 addtrack: function addtrack(e) { 21411 techTracks.addTrack(e.track); 21412 }, 21413 removetrack: function removetrack(e) { 21414 techTracks.removeTrack(e.track); 21415 } 21416 }; 21417 21418 var removeOldTracks = function removeOldTracks() { 21419 var removeTracks = []; 21420 21421 for (var i = 0; i < techTracks.length; i++) { 21422 var found = false; 21423 21424 for (var j = 0; j < elTracks.length; j++) { 21425 if (elTracks[j] === techTracks[i]) { 21426 found = true; 21427 break; 21428 } 21429 } 21430 21431 if (!found) { 21432 removeTracks.push(techTracks[i]); 21433 } 21434 } 21435 21436 while (removeTracks.length) { 21437 techTracks.removeTrack(removeTracks.shift()); 21438 } 21439 }; 21440 21441 this[props.getterName + 'Listeners_'] = listeners; 21442 Object.keys(listeners).forEach(function (eventName) { 21443 var listener = listeners[eventName]; 21444 elTracks.addEventListener(eventName, listener); 21445 21446 _this3.on('dispose', function (e) { 21447 return elTracks.removeEventListener(eventName, listener); 21448 }); 21449 }); // Remove (native) tracks that are not used anymore 21450 21451 this.on('loadstart', removeOldTracks); 21452 this.on('dispose', function (e) { 21453 return _this3.off('loadstart', removeOldTracks); 21454 }); 21455 } 21456 /** 21457 * Proxy all native track list events to our track lists if the browser we are playing 21458 * in supports that type of track list. 21459 * 21460 * @private 21461 */ 21462 ; 21463 21464 _proto.proxyNativeTracks_ = function proxyNativeTracks_() { 21465 var _this4 = this; 21466 21467 NORMAL.names.forEach(function (name) { 21468 _this4.proxyNativeTracksForType_(name); 21469 }); 21470 } 21471 /** 21472 * Create the `Html5` Tech's DOM element. 21473 * 21474 * @return {Element} 21475 * The element that gets created. 21476 */ 21477 ; 21478 21479 _proto.createEl = function createEl() { 21480 var el = this.options_.tag; // Check if this browser supports moving the element into the box. 21481 // On the iPhone video will break if you move the element, 21482 // So we have to create a brand new element. 21483 // If we ingested the player div, we do not need to move the media element. 21484 21485 if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { 21486 // If the original tag is still there, clone and remove it. 21487 if (el) { 21488 var clone = el.cloneNode(true); 21489 21490 if (el.parentNode) { 21491 el.parentNode.insertBefore(clone, el); 21492 } 21493 21494 Html5.disposeMediaElement(el); 21495 el = clone; 21496 } else { 21497 el = document.createElement('video'); // determine if native controls should be used 21498 21499 var tagAttributes = this.options_.tag && getAttributes(this.options_.tag); 21500 var attributes = mergeOptions({}, tagAttributes); 21501 21502 if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { 21503 delete attributes.controls; 21504 } 21505 21506 setAttributes(el, assign(attributes, { 21507 id: this.options_.techId, 21508 "class": 'vjs-tech' 21509 })); 21510 } 21511 21512 el.playerId = this.options_.playerId; 21513 } 21514 21515 if (typeof this.options_.preload !== 'undefined') { 21516 setAttribute(el, 'preload', this.options_.preload); 21517 } // Update specific tag settings, in case they were overridden 21518 // `autoplay` has to be *last* so that `muted` and `playsinline` are present 21519 // when iOS/Safari or other browsers attempt to autoplay. 21520 21521 21522 var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay']; 21523 21524 for (var i = 0; i < settingsAttrs.length; i++) { 21525 var attr = settingsAttrs[i]; 21526 var value = this.options_[attr]; 21527 21528 if (typeof value !== 'undefined') { 21529 if (value) { 21530 setAttribute(el, attr, attr); 21531 } else { 21532 removeAttribute(el, attr); 21533 } 21534 21535 el[attr] = value; 21536 } 21537 } 21538 21539 return el; 21540 } 21541 /** 21542 * This will be triggered if the loadstart event has already fired, before videojs was 21543 * ready. Two known examples of when this can happen are: 21544 * 1. If we're loading the playback object after it has started loading 21545 * 2. The media is already playing the (often with autoplay on) then 21546 * 21547 * This function will fire another loadstart so that videojs can catchup. 21548 * 21549 * @fires Tech#loadstart 21550 * 21551 * @return {undefined} 21552 * returns nothing. 21553 */ 21554 ; 21555 21556 _proto.handleLateInit_ = function handleLateInit_(el) { 21557 if (el.networkState === 0 || el.networkState === 3) { 21558 // The video element hasn't started loading the source yet 21559 // or didn't find a source 21560 return; 21561 } 21562 21563 if (el.readyState === 0) { 21564 // NetworkState is set synchronously BUT loadstart is fired at the 21565 // end of the current stack, usually before setInterval(fn, 0). 21566 // So at this point we know loadstart may have already fired or is 21567 // about to fire, and either way the player hasn't seen it yet. 21568 // We don't want to fire loadstart prematurely here and cause a 21569 // double loadstart so we'll wait and see if it happens between now 21570 // and the next loop, and fire it if not. 21571 // HOWEVER, we also want to make sure it fires before loadedmetadata 21572 // which could also happen between now and the next loop, so we'll 21573 // watch for that also. 21574 var loadstartFired = false; 21575 21576 var setLoadstartFired = function setLoadstartFired() { 21577 loadstartFired = true; 21578 }; 21579 21580 this.on('loadstart', setLoadstartFired); 21581 21582 var triggerLoadstart = function triggerLoadstart() { 21583 // We did miss the original loadstart. Make sure the player 21584 // sees loadstart before loadedmetadata 21585 if (!loadstartFired) { 21586 this.trigger('loadstart'); 21587 } 21588 }; 21589 21590 this.on('loadedmetadata', triggerLoadstart); 21591 this.ready(function () { 21592 this.off('loadstart', setLoadstartFired); 21593 this.off('loadedmetadata', triggerLoadstart); 21594 21595 if (!loadstartFired) { 21596 // We did miss the original native loadstart. Fire it now. 21597 this.trigger('loadstart'); 21598 } 21599 }); 21600 return; 21601 } // From here on we know that loadstart already fired and we missed it. 21602 // The other readyState events aren't as much of a problem if we double 21603 // them, so not going to go to as much trouble as loadstart to prevent 21604 // that unless we find reason to. 21605 21606 21607 var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater 21608 21609 eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater 21610 21611 if (el.readyState >= 2) { 21612 eventsToTrigger.push('loadeddata'); 21613 } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater 21614 21615 21616 if (el.readyState >= 3) { 21617 eventsToTrigger.push('canplay'); 21618 } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) 21619 21620 21621 if (el.readyState >= 4) { 21622 eventsToTrigger.push('canplaythrough'); 21623 } // We still need to give the player time to add event listeners 21624 21625 21626 this.ready(function () { 21627 eventsToTrigger.forEach(function (type) { 21628 this.trigger(type); 21629 }, this); 21630 }); 21631 } 21632 /** 21633 * Set current time for the `HTML5` tech. 21634 * 21635 * @param {number} seconds 21636 * Set the current time of the media to this. 21637 */ 21638 ; 21639 21640 _proto.setCurrentTime = function setCurrentTime(seconds) { 21641 try { 21642 this.el_.currentTime = seconds; 21643 } catch (e) { 21644 log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady); 21645 } 21646 } 21647 /** 21648 * Get the current duration of the HTML5 media element. 21649 * 21650 * @return {number} 21651 * The duration of the media or 0 if there is no duration. 21652 */ 21653 ; 21654 21655 _proto.duration = function duration() { 21656 var _this5 = this; 21657 21658 // Android Chrome will report duration as Infinity for VOD HLS until after 21659 // playback has started, which triggers the live display erroneously. 21660 // Return NaN if playback has not started and trigger a durationupdate once 21661 // the duration can be reliably known. 21662 if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) { 21663 // Wait for the first `timeupdate` with currentTime > 0 - there may be 21664 // several with 0 21665 var checkProgress = function checkProgress() { 21666 if (_this5.el_.currentTime > 0) { 21667 // Trigger durationchange for genuinely live video 21668 if (_this5.el_.duration === Infinity) { 21669 _this5.trigger('durationchange'); 21670 } 21671 21672 _this5.off('timeupdate', checkProgress); 21673 } 21674 }; 21675 21676 this.on('timeupdate', checkProgress); 21677 return NaN; 21678 } 21679 21680 return this.el_.duration || NaN; 21681 } 21682 /** 21683 * Get the current width of the HTML5 media element. 21684 * 21685 * @return {number} 21686 * The width of the HTML5 media element. 21687 */ 21688 ; 21689 21690 _proto.width = function width() { 21691 return this.el_.offsetWidth; 21692 } 21693 /** 21694 * Get the current height of the HTML5 media element. 21695 * 21696 * @return {number} 21697 * The height of the HTML5 media element. 21698 */ 21699 ; 21700 21701 _proto.height = function height() { 21702 return this.el_.offsetHeight; 21703 } 21704 /** 21705 * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into 21706 * `fullscreenchange` event. 21707 * 21708 * @private 21709 * @fires fullscreenchange 21710 * @listens webkitendfullscreen 21711 * @listens webkitbeginfullscreen 21712 * @listens webkitbeginfullscreen 21713 */ 21714 ; 21715 21716 _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { 21717 var _this6 = this; 21718 21719 if (!('webkitDisplayingFullscreen' in this.el_)) { 21720 return; 21721 } 21722 21723 var endFn = function endFn() { 21724 this.trigger('fullscreenchange', { 21725 isFullscreen: false 21726 }); 21727 }; 21728 21729 var beginFn = function beginFn() { 21730 if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') { 21731 this.one('webkitendfullscreen', endFn); 21732 this.trigger('fullscreenchange', { 21733 isFullscreen: true 21734 }); 21735 } 21736 }; 21737 21738 this.on('webkitbeginfullscreen', beginFn); 21739 this.on('dispose', function () { 21740 _this6.off('webkitbeginfullscreen', beginFn); 21741 21742 _this6.off('webkitendfullscreen', endFn); 21743 }); 21744 } 21745 /** 21746 * Check if fullscreen is supported on the current playback device. 21747 * 21748 * @return {boolean} 21749 * - True if fullscreen is supported. 21750 * - False if fullscreen is not supported. 21751 */ 21752 ; 21753 21754 _proto.supportsFullScreen = function supportsFullScreen() { 21755 if (typeof this.el_.webkitEnterFullScreen === 'function') { 21756 var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard 21757 21758 if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { 21759 return true; 21760 } 21761 } 21762 21763 return false; 21764 } 21765 /** 21766 * Request that the `HTML5` Tech enter fullscreen. 21767 */ 21768 ; 21769 21770 _proto.enterFullScreen = function enterFullScreen() { 21771 var video = this.el_; 21772 21773 if (video.paused && video.networkState <= video.HAVE_METADATA) { 21774 // attempt to prime the video element for programmatic access 21775 // this isn't necessary on the desktop but shouldn't hurt 21776 this.el_.play(); // playing and pausing synchronously during the transition to fullscreen 21777 // can get iOS ~6.1 devices into a play/pause loop 21778 21779 this.setTimeout(function () { 21780 video.pause(); 21781 video.webkitEnterFullScreen(); 21782 }, 0); 21783 } else { 21784 video.webkitEnterFullScreen(); 21785 } 21786 } 21787 /** 21788 * Request that the `HTML5` Tech exit fullscreen. 21789 */ 21790 ; 21791 21792 _proto.exitFullScreen = function exitFullScreen() { 21793 this.el_.webkitExitFullScreen(); 21794 } 21795 /** 21796 * Create a floating video window always on top of other windows so that users may 21797 * continue consuming media while they interact with other content sites, or 21798 * applications on their device. 21799 * 21800 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 21801 * 21802 * @return {Promise} 21803 * A promise with a Picture-in-Picture window. 21804 */ 21805 ; 21806 21807 _proto.requestPictureInPicture = function requestPictureInPicture() { 21808 return this.el_.requestPictureInPicture(); 21809 } 21810 /** 21811 * A getter/setter for the `Html5` Tech's source object. 21812 * > Note: Please use {@link Html5#setSource} 21813 * 21814 * @param {Tech~SourceObject} [src] 21815 * The source object you want to set on the `HTML5` techs element. 21816 * 21817 * @return {Tech~SourceObject|undefined} 21818 * - The current source object when a source is not passed in. 21819 * - undefined when setting 21820 * 21821 * @deprecated Since version 5. 21822 */ 21823 ; 21824 21825 _proto.src = function src(_src) { 21826 if (_src === undefined) { 21827 return this.el_.src; 21828 } // Setting src through `src` instead of `setSrc` will be deprecated 21829 21830 21831 this.setSrc(_src); 21832 } 21833 /** 21834 * Reset the tech by removing all sources and then calling 21835 * {@link Html5.resetMediaElement}. 21836 */ 21837 ; 21838 21839 _proto.reset = function reset() { 21840 Html5.resetMediaElement(this.el_); 21841 } 21842 /** 21843 * Get the current source on the `HTML5` Tech. Falls back to returning the source from 21844 * the HTML5 media element. 21845 * 21846 * @return {Tech~SourceObject} 21847 * The current source object from the HTML5 tech. With a fallback to the 21848 * elements source. 21849 */ 21850 ; 21851 21852 _proto.currentSrc = function currentSrc() { 21853 if (this.currentSource_) { 21854 return this.currentSource_.src; 21855 } 21856 21857 return this.el_.currentSrc; 21858 } 21859 /** 21860 * Set controls attribute for the HTML5 media Element. 21861 * 21862 * @param {string} val 21863 * Value to set the controls attribute to 21864 */ 21865 ; 21866 21867 _proto.setControls = function setControls(val) { 21868 this.el_.controls = !!val; 21869 } 21870 /** 21871 * Create and returns a remote {@link TextTrack} object. 21872 * 21873 * @param {string} kind 21874 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 21875 * 21876 * @param {string} [label] 21877 * Label to identify the text track 21878 * 21879 * @param {string} [language] 21880 * Two letter language abbreviation 21881 * 21882 * @return {TextTrack} 21883 * The TextTrack that gets created. 21884 */ 21885 ; 21886 21887 _proto.addTextTrack = function addTextTrack(kind, label, language) { 21888 if (!this.featuresNativeTextTracks) { 21889 return _Tech.prototype.addTextTrack.call(this, kind, label, language); 21890 } 21891 21892 return this.el_.addTextTrack(kind, label, language); 21893 } 21894 /** 21895 * Creates either native TextTrack or an emulated TextTrack depending 21896 * on the value of `featuresNativeTextTracks` 21897 * 21898 * @param {Object} options 21899 * The object should contain the options to initialize the TextTrack with. 21900 * 21901 * @param {string} [options.kind] 21902 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 21903 * 21904 * @param {string} [options.label] 21905 * Label to identify the text track 21906 * 21907 * @param {string} [options.language] 21908 * Two letter language abbreviation. 21909 * 21910 * @param {boolean} [options.default] 21911 * Default this track to on. 21912 * 21913 * @param {string} [options.id] 21914 * The internal id to assign this track. 21915 * 21916 * @param {string} [options.src] 21917 * A source url for the track. 21918 * 21919 * @return {HTMLTrackElement} 21920 * The track element that gets created. 21921 */ 21922 ; 21923 21924 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 21925 if (!this.featuresNativeTextTracks) { 21926 return _Tech.prototype.createRemoteTextTrack.call(this, options); 21927 } 21928 21929 var htmlTrackElement = document.createElement('track'); 21930 21931 if (options.kind) { 21932 htmlTrackElement.kind = options.kind; 21933 } 21934 21935 if (options.label) { 21936 htmlTrackElement.label = options.label; 21937 } 21938 21939 if (options.language || options.srclang) { 21940 htmlTrackElement.srclang = options.language || options.srclang; 21941 } 21942 21943 if (options["default"]) { 21944 htmlTrackElement["default"] = options["default"]; 21945 } 21946 21947 if (options.id) { 21948 htmlTrackElement.id = options.id; 21949 } 21950 21951 if (options.src) { 21952 htmlTrackElement.src = options.src; 21953 } 21954 21955 return htmlTrackElement; 21956 } 21957 /** 21958 * Creates a remote text track object and returns an html track element. 21959 * 21960 * @param {Object} options The object should contain values for 21961 * kind, language, label, and src (location of the WebVTT file) 21962 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 21963 * automatically removed from the video element whenever the source changes 21964 * @return {HTMLTrackElement} An Html Track Element. 21965 * This can be an emulated {@link HTMLTrackElement} or a native one. 21966 * @deprecated The default value of the "manualCleanup" parameter will default 21967 * to "false" in upcoming versions of Video.js 21968 */ 21969 ; 21970 21971 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 21972 var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); 21973 21974 if (this.featuresNativeTextTracks) { 21975 this.el().appendChild(htmlTrackElement); 21976 } 21977 21978 return htmlTrackElement; 21979 } 21980 /** 21981 * Remove remote `TextTrack` from `TextTrackList` object 21982 * 21983 * @param {TextTrack} track 21984 * `TextTrack` object to remove 21985 */ 21986 ; 21987 21988 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 21989 _Tech.prototype.removeRemoteTextTrack.call(this, track); 21990 21991 if (this.featuresNativeTextTracks) { 21992 var tracks = this.$$('track'); 21993 var i = tracks.length; 21994 21995 while (i--) { 21996 if (track === tracks[i] || track === tracks[i].track) { 21997 this.el().removeChild(tracks[i]); 21998 } 21999 } 22000 } 22001 } 22002 /** 22003 * Gets available media playback quality metrics as specified by the W3C's Media 22004 * Playback Quality API. 22005 * 22006 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 22007 * 22008 * @return {Object} 22009 * An object with supported media playback quality metrics 22010 */ 22011 ; 22012 22013 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 22014 if (typeof this.el().getVideoPlaybackQuality === 'function') { 22015 return this.el().getVideoPlaybackQuality(); 22016 } 22017 22018 var videoPlaybackQuality = {}; 22019 22020 if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { 22021 videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; 22022 videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; 22023 } 22024 22025 if (window$1.performance && typeof window$1.performance.now === 'function') { 22026 videoPlaybackQuality.creationTime = window$1.performance.now(); 22027 } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') { 22028 videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart; 22029 } 22030 22031 return videoPlaybackQuality;
vendor: 4,248 bytes, lines 22032-22189
22032 }; 22033 22034 return Html5; 22035 }(Tech); 22036 /* HTML5 Support Testing ---------------------------------------------------- */ 22037 22038 /** 22039 * Element for testing browser HTML5 media capabilities 22040 * 22041 * @type {Element} 22042 * @constant 22043 * @private 22044 */ 22045 22046 22047 defineLazyProperty(Html5, 'TEST_VID', function () { 22048 if (!isReal()) { 22049 return; 22050 } 22051 22052 var video = document.createElement('video'); 22053 var track = document.createElement('track'); 22054 track.kind = 'captions'; 22055 track.srclang = 'en'; 22056 track.label = 'English'; 22057 video.appendChild(track); 22058 return video; 22059 }); 22060 /** 22061 * Check if HTML5 media is supported by this browser/device. 22062 * 22063 * @return {boolean} 22064 * - True if HTML5 media is supported. 22065 * - False if HTML5 media is not supported. 22066 */ 22067 22068 Html5.isSupported = function () { 22069 // IE with no Media Player is a LIAR! (#984) 22070 try { 22071 Html5.TEST_VID.volume = 0.5; 22072 } catch (e) { 22073 return false; 22074 } 22075 22076 return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); 22077 }; 22078 /** 22079 * Check if the tech can support the given type 22080 * 22081 * @param {string} type 22082 * The mimetype to check 22083 * @return {string} 'probably', 'maybe', or '' (empty string) 22084 */ 22085 22086 22087 Html5.canPlayType = function (type) { 22088 return Html5.TEST_VID.canPlayType(type); 22089 }; 22090 /** 22091 * Check if the tech can support the given source 22092 * 22093 * @param {Object} srcObj 22094 * The source object 22095 * @param {Object} options 22096 * The options passed to the tech 22097 * @return {string} 'probably', 'maybe', or '' (empty string) 22098 */ 22099 22100 22101 Html5.canPlaySource = function (srcObj, options) { 22102 return Html5.canPlayType(srcObj.type); 22103 }; 22104 /** 22105 * Check if the volume can be changed in this browser/device. 22106 * Volume cannot be changed in a lot of mobile devices. 22107 * Specifically, it can't be changed from 1 on iOS. 22108 * 22109 * @return {boolean} 22110 * - True if volume can be controlled 22111 * - False otherwise 22112 */ 22113 22114 22115 Html5.canControlVolume = function () { 22116 // IE will error if Windows Media Player not installed #3315 22117 try { 22118 var volume = Html5.TEST_VID.volume; 22119 Html5.TEST_VID.volume = volume / 2 + 0.1; 22120 return volume !== Html5.TEST_VID.volume; 22121 } catch (e) { 22122 return false; 22123 } 22124 }; 22125 /** 22126 * Check if the volume can be muted in this browser/device. 22127 * Some devices, e.g. iOS, don't allow changing volume 22128 * but permits muting/unmuting. 22129 * 22130 * @return {bolean} 22131 * - True if volume can be muted 22132 * - False otherwise 22133 */ 22134 22135 22136 Html5.canMuteVolume = function () { 22137 try { 22138 var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always 22139 // work, so we want to set both property and attribute 22140 22141 Html5.TEST_VID.muted = !muted; 22142 22143 if (Html5.TEST_VID.muted) { 22144 setAttribute(Html5.TEST_VID, 'muted', 'muted'); 22145 } else { 22146 removeAttribute(Html5.TEST_VID, 'muted', 'muted'); 22147 } 22148 22149 return muted !== Html5.TEST_VID.muted; 22150 } catch (e) { 22151 return false; 22152 } 22153 }; 22154 /** 22155 * Check if the playback rate can be changed in this browser/device. 22156 * 22157 * @return {boolean} 22158 * - True if playback rate can be controlled 22159 * - False otherwise 22160 */ 22161 22162 22163 Html5.canControlPlaybackRate = function () { 22164 // Playback rate API is implemented in Android Chrome, but doesn't do anything 22165 // https://github.com/videojs/video.js/issues/3180 22166 if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) { 22167 return false; 22168 } // IE will error if Windows Media Player not installed #3315 22169 22170 22171 try { 22172 var playbackRate = Html5.TEST_VID.playbackRate; 22173 Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; 22174 return playbackRate !== Html5.TEST_VID.playbackRate; 22175 } catch (e) { 22176 return false; 22177 } 22178 }; 22179 /** 22180 * Check if we can override a video/audio elements attributes, with 22181 * Object.defineProperty. 22182 * 22183 * @return {boolean} 22184 * - True if builtin attributes can be overridden 22185 * - False otherwise 22186 */ 22187 22188 22189 Html5.canOverrideAttributes = function () {
22190 // if we cannot overwrite the src/innerHTML property, there is no support 22191 // iOS 7 safari for instance cannot do this. 22192 try { 22193 var noop = function noop() {}; 22194 22195 Object.defineProperty(document.createElement('video'), 'src', { 22196 get: noop, 22197 set: noop 22198 }); 22199 Object.defineProperty(document.createElement('audio'), 'src', { 22200 get: noop, 22201 set: noop 22202 }); 22203 Object.defineProperty(document.createElement('video'), 'innerHTML', { 22204 get: noop, 22205 set: noop 22206 }); 22207 Object.defineProperty(document.createElement('audio'), 'innerHTML', { 22208 get: noop, 22209 set: noop 22210 }); 22211 } catch (e) { 22212 return false; 22213 } 22214 22215 return true; 22216 }; 22217 /** 22218 * Check to see if native `TextTrack`s are supported by this browser/device. 22219 * 22220 * @return {boolean} 22221 * - True if native `TextTrack`s are supported. 22222 * - False otherwise 22223 */ 22224 22225 22226 Html5.supportsNativeTextTracks = function () { 22227 return IS_ANY_SAFARI || IS_IOS && IS_CHROME; 22228 }; 22229 /** 22230 * Check to see if native `VideoTrack`s are supported by this browser/device 22231 * 22232 * @return {boolean} 22233 * - True if native `VideoTrack`s are supported. 22234 * - False otherwise 22235 */ 22236 22237 22238 Html5.supportsNativeVideoTracks = function () { 22239 return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); 22240 }; 22241 /** 22242 * Check to see if native `AudioTrack`s are supported by this browser/device 22243 * 22244 * @return {boolean} 22245 * - True if native `AudioTrack`s are supported. 22246 * - False otherwise 22247 */ 22248 22249 22250 Html5.supportsNativeAudioTracks = function () { 22251 return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); 22252 }; 22253 /** 22254 * An array of events available on the Html5 tech. 22255 * 22256 * @private 22257 * @type {Array} 22258 */ 22259 22260 22261 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']; 22262 /** 22263 * Boolean indicating whether the `Tech` supports volume control. 22264 * 22265 * @type {boolean} 22266 * @default {@link Html5.canControlVolume} 22267 */ 22268 22269 /** 22270 * Boolean indicating whether the `Tech` supports muting volume. 22271 * 22272 * @type {bolean} 22273 * @default {@link Html5.canMuteVolume} 22274 */ 22275 22276 /** 22277 * Boolean indicating whether the `Tech` supports changing the speed at which the media 22278 * plays. Examples: 22279 * - Set player to play 2x (twice) as fast 22280 * - Set player to play 0.5x (half) as fast 22281 * 22282 * @type {boolean} 22283 * @default {@link Html5.canControlPlaybackRate} 22284 */ 22285 22286 /** 22287 * Boolean indicating whether the `Tech` supports the `sourceset` event. 22288 * 22289 * @type {boolean} 22290 * @default 22291 */ 22292 22293 /** 22294 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. 22295 * 22296 * @type {boolean} 22297 * @default {@link Html5.supportsNativeTextTracks} 22298 */ 22299 22300 /** 22301 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. 22302 * 22303 * @type {boolean} 22304 * @default {@link Html5.supportsNativeVideoTracks} 22305 */ 22306 22307 /** 22308 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. 22309 * 22310 * @type {boolean} 22311 * @default {@link Html5.supportsNativeAudioTracks} 22312 */ 22313 22314 [['featuresVolumeControl', 'canControlVolume'], ['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) { 22315 var key = _ref[0], 22316 fn = _ref[1]; 22317 defineLazyProperty(Html5.prototype, key, function () { 22318 return Html5[fn](); 22319 }, true); 22320 }); 22321 /** 22322 * Boolean indicating whether the `HTML5` tech currently supports the media element 22323 * moving in the DOM. iOS breaks if you move the media element, so this is set this to 22324 * false there. Everywhere else this should be true. 22325 * 22326 * @type {boolean} 22327 * @default 22328 */ 22329 22330 Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed. 22331 // Is this true? 22332 22333 /** 22334 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize 22335 * when going into fullscreen. 22336 * 22337 * @type {boolean} 22338 * @default 22339 */ 22340 22341 Html5.prototype.featuresFullscreenResize = true; 22342 /** 22343 * Boolean indicating whether the `HTML5` tech currently supports the progress event. 22344 * If this is false, manual `progress` events will be triggered instead. 22345 * 22346 * @type {boolean} 22347 * @default 22348 */ 22349 22350 Html5.prototype.featuresProgressEvents = true; 22351 /** 22352 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. 22353 * If this is false, manual `timeupdate` events will be triggered instead. 22354 * 22355 * @default 22356 */ 22357 22358 Html5.prototype.featuresTimeupdateEvents = true; // HTML5 Feature detection and Device Fixes --------------------------------- // 22359 22360 var canPlayType; 22361 22362 Html5.patchCanPlayType = function () { 22363 // Android 4.0 and above can play HLS to some extent but it reports being unable to do so 22364 // Firefox and Chrome report correctly 22365 if (ANDROID_VERSION >
vendor: 14,811 bytes, lines 22365-22757
22365= 4.0 && !IS_FIREFOX && !IS_CHROME) { 22366 canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; 22367 22368 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 22369 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 22370 22371 if (type && mpegurlRE.test(type)) { 22372 return 'maybe'; 22373 } 22374 22375 return canPlayType.call(this, type); 22376 }; 22377 } 22378 }; 22379 22380 Html5.unpatchCanPlayType = function () { 22381 var r = Html5.TEST_VID.constructor.prototype.canPlayType; 22382 22383 if (canPlayType) { 22384 Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; 22385 } 22386 22387 return r; 22388 }; // by default, patch the media element 22389 22390 22391 Html5.patchCanPlayType(); 22392 22393 Html5.disposeMediaElement = function (el) { 22394 if (!el) { 22395 return; 22396 } 22397 22398 if (el.parentNode) { 22399 el.parentNode.removeChild(el); 22400 } // remove any child track or source nodes to prevent their loading 22401 22402 22403 while (el.hasChildNodes()) { 22404 el.removeChild(el.firstChild); 22405 } // remove any src reference. not setting `src=''` because that causes a warning 22406 // in firefox 22407 22408 22409 el.removeAttribute('src'); // force the media element to update its loading state by calling load() 22410 // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) 22411 22412 if (typeof el.load === 'function') { 22413 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 22414 (function () { 22415 try { 22416 el.load(); 22417 } catch (e) {// not supported 22418 } 22419 })(); 22420 } 22421 }; 22422 22423 Html5.resetMediaElement = function (el) { 22424 if (!el) { 22425 return; 22426 } 22427 22428 var sources = el.querySelectorAll('source'); 22429 var i = sources.length; 22430 22431 while (i--) { 22432 el.removeChild(sources[i]); 22433 } // remove any src reference. 22434 // not setting `src=''` because that throws an error 22435 22436 22437 el.removeAttribute('src'); 22438 22439 if (typeof el.load === 'function') { 22440 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 22441 (function () { 22442 try { 22443 el.load(); 22444 } catch (e) {// satisfy linter 22445 } 22446 })(); 22447 } 22448 }; 22449 /* Native HTML5 element property wrapping ----------------------------------- */ 22450 // Wrap native boolean attributes with getters that check both property and attribute 22451 // The list is as followed: 22452 // muted, defaultMuted, autoplay, controls, loop, playsinline 22453 22454 22455 [ 22456 /** 22457 * Get the value of `muted` from the media element. `muted` indicates 22458 * that the volume for the media should be set to silent. This does not actually change 22459 * the `volume` attribute. 22460 * 22461 * @method Html5#muted 22462 * @return {boolean} 22463 * - True if the value of `volume` should be ignored and the audio set to silent. 22464 * - False if the value of `volume` should be used. 22465 * 22466 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 22467 */ 22468 'muted', 22469 /** 22470 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates 22471 * whether the media should start muted or not. Only changes the default state of the 22472 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the 22473 * current state. 22474 * 22475 * @method Html5#defaultMuted 22476 * @return {boolean} 22477 * - The value of `defaultMuted` from the media element. 22478 * - True indicates that the media should start muted. 22479 * - False indicates that the media should not start muted 22480 * 22481 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 22482 */ 22483 'defaultMuted', 22484 /** 22485 * Get the value of `autoplay` from the media element. `autoplay` indicates 22486 * that the media should start to play as soon as the page is ready. 22487 * 22488 * @method Html5#autoplay 22489 * @return {boolean} 22490 * - The value of `autoplay` from the media element. 22491 * - True indicates that the media should start as soon as the page loads. 22492 * - False indicates that the media should not start as soon as the page loads. 22493 * 22494 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 22495 */ 22496 'autoplay', 22497 /** 22498 * Get the value of `controls` from the media element. `controls` indicates 22499 * whether the native media controls should be shown or hidden. 22500 * 22501 * @method Html5#controls 22502 * @return {boolean} 22503 * - The value of `controls` from the media element. 22504 * - True indicates that native controls should be showing. 22505 * - False indicates that native controls should be hidden. 22506 * 22507 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} 22508 */ 22509 'controls', 22510 /** 22511 * Get the value of `loop` from the media element. `loop` indicates 22512 * that the media should return to the start of the media and continue playing once 22513 * it reaches the end. 22514 * 22515 * @method Html5#loop 22516 * @return {boolean} 22517 * - The value of `loop` from the media element. 22518 * - True indicates that playback should seek back to start once 22519 * the end of a media is reached. 22520 * - False indicates that playback should not loop back to the start when the 22521 * end of the media is reached. 22522 * 22523 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 22524 */ 22525 'loop', 22526 /** 22527 * Get the value of `playsinline` from the media element. `playsinline` indicates 22528 * to the browser that non-fullscreen playback is preferred when fullscreen 22529 * playback is the native default, such as in iOS Safari. 22530 * 22531 * @method Html5#playsinline 22532 * @return {boolean} 22533 * - The value of `playsinline` from the media element. 22534 * - True indicates that the media should play inline. 22535 * - False indicates that the media should not play inline. 22536 * 22537 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 22538 */ 22539 'playsinline'].forEach(function (prop) { 22540 Html5.prototype[prop] = function () { 22541 return this.el_[prop] || this.el_.hasAttribute(prop); 22542 }; 22543 }); // Wrap native boolean attributes with setters that set both property and attribute 22544 // The list is as followed: 22545 // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline 22546 // setControls is special-cased above 22547 22548 [ 22549 /** 22550 * Set the value of `muted` on the media element. `muted` indicates that the current 22551 * audio level should be silent. 22552 * 22553 * @method Html5#setMuted 22554 * @param {boolean} muted 22555 * - True if the audio should be set to silent 22556 * - False otherwise 22557 * 22558 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 22559 */ 22560 'muted', 22561 /** 22562 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current 22563 * audio level should be silent, but will only effect the muted level on intial playback.. 22564 * 22565 * @method Html5.prototype.setDefaultMuted 22566 * @param {boolean} defaultMuted 22567 * - True if the audio should be set to silent 22568 * - False otherwise 22569 * 22570 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 22571 */ 22572 'defaultMuted', 22573 /** 22574 * Set the value of `autoplay` on the media element. `autoplay` indicates 22575 * that the media should start to play as soon as the page is ready. 22576 * 22577 * @method Html5#setAutoplay 22578 * @param {boolean} autoplay 22579 * - True indicates that the media should start as soon as the page loads. 22580 * - False indicates that the media should not start as soon as the page loads. 22581 * 22582 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 22583 */ 22584 'autoplay', 22585 /** 22586 * Set the value of `loop` on the media element. `loop` indicates 22587 * that the media should return to the start of the media and continue playing once 22588 * it reaches the end. 22589 * 22590 * @method Html5#setLoop 22591 * @param {boolean} loop 22592 * - True indicates that playback should seek back to start once 22593 * the end of a media is reached. 22594 * - False indicates that playback should not loop back to the start when the 22595 * end of the media is reached. 22596 * 22597 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 22598 */ 22599 'loop', 22600 /** 22601 * Set the value of `playsinline` from the media element. `playsinline` indicates 22602 * to the browser that non-fullscreen playback is preferred when fullscreen 22603 * playback is the native default, such as in iOS Safari. 22604 * 22605 * @method Html5#setPlaysinline 22606 * @param {boolean} playsinline 22607 * - True indicates that the media should play inline. 22608 * - False indicates that the media should not play inline. 22609 * 22610 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 22611 */ 22612 'playsinline'].forEach(function (prop) { 22613 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 22614 this.el_[prop] = v; 22615 22616 if (v) { 22617 this.el_.setAttribute(prop, prop); 22618 } else { 22619 this.el_.removeAttribute(prop); 22620 } 22621 }; 22622 }); // Wrap native properties with a getter 22623 // The list is as followed 22624 // paused, currentTime, buffered, volume, poster, preload, error, seeking 22625 // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState 22626 // readyState, videoWidth, videoHeight 22627 22628 [ 22629 /** 22630 * Get the value of `paused` from the media element. `paused` indicates whether the media element 22631 * is currently paused or not. 22632 * 22633 * @method Html5#paused 22634 * @return {boolean} 22635 * The value of `paused` from the media element. 22636 * 22637 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} 22638 */ 22639 'paused', 22640 /** 22641 * Get the value of `currentTime` from the media element. `currentTime` indicates 22642 * the current second that the media is at in playback. 22643 * 22644 * @method Html5#currentTime 22645 * @return {number} 22646 * The value of `currentTime` from the media element. 22647 * 22648 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} 22649 */ 22650 'currentTime', 22651 /** 22652 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` 22653 * object that represents the parts of the media that are already downloaded and 22654 * available for playback. 22655 * 22656 * @method Html5#buffered 22657 * @return {TimeRange} 22658 * The value of `buffered` from the media element. 22659 * 22660 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} 22661 */ 22662 'buffered', 22663 /** 22664 * Get the value of `volume` from the media element. `volume` indicates 22665 * the current playback volume of audio for a media. `volume` will be a value from 0 22666 * (silent) to 1 (loudest and default). 22667 * 22668 * @method Html5#volume 22669 * @return {number} 22670 * The value of `volume` from the media element. Value will be between 0-1. 22671 * 22672 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 22673 */ 22674 'volume', 22675 /** 22676 * Get the value of `poster` from the media element. `poster` indicates 22677 * that the url of an image file that can/will be shown when no media data is available. 22678 * 22679 * @method Html5#poster 22680 * @return {string} 22681 * The value of `poster` from the media element. Value will be a url to an 22682 * image. 22683 * 22684 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} 22685 */ 22686 'poster', 22687 /** 22688 * Get the value of `preload` from the media element. `preload` indicates 22689 * what should download before the media is interacted with. It can have the following 22690 * values: 22691 * - none: nothing should be downloaded 22692 * - metadata: poster and the first few frames of the media may be downloaded to get 22693 * media dimensions and other metadata 22694 * - auto: allow the media and metadata for the media to be downloaded before 22695 * interaction 22696 * 22697 * @method Html5#preload 22698 * @return {string} 22699 * The value of `preload` from the media element. Will be 'none', 'metadata', 22700 * or 'auto'. 22701 * 22702 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 22703 */ 22704 'preload', 22705 /** 22706 * Get the value of the `error` from the media element. `error` indicates any 22707 * MediaError that may have occurred during playback. If error returns null there is no 22708 * current error. 22709 * 22710 * @method Html5#error 22711 * @return {MediaError|null} 22712 * The value of `error` from the media element. Will be `MediaError` if there 22713 * is a current error and null otherwise. 22714 * 22715 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} 22716 */ 22717 'error', 22718 /** 22719 * Get the value of `seeking` from the media element. `seeking` indicates whether the 22720 * media is currently seeking to a new position or not. 22721 * 22722 * @method Html5#seeking 22723 * @return {boolean} 22724 * - The value of `seeking` from the media element. 22725 * - True indicates that the media is currently seeking to a new position. 22726 * - False indicates that the media is not seeking to a new position at this time. 22727 * 22728 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} 22729 */ 22730 'seeking', 22731 /** 22732 * Get the value of `seekable` from the media element. `seekable` returns a 22733 * `TimeRange` object indicating ranges of time that can currently be `seeked` to. 22734 * 22735 * @method Html5#seekable 22736 * @return {TimeRange} 22737 * The value of `seekable` from the media element. A `TimeRange` object 22738 * indicating the current ranges of time that can be seeked to. 22739 * 22740 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} 22741 */ 22742 'seekable', 22743 /** 22744 * Get the value of `ended` from the media element. `ended` indicates whether 22745 * the media has reached the end or not. 22746 * 22747 * @method Html5#ended 22748 * @return {boolean} 22749 * - The value of `ended` from the media element. 22750 * - True indicates that the media has ended. 22751 * - False indicates that the media has not ended. 22752 * 22753 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} 22754 */ 22755 'ended', 22756 /** 22757 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
22758 * the rate at which the media is currently playing back. Examples: 22759 * - if playbackRate is set to 2, media will play twice as fast. 22760 * - if playbackRate is set to 0.5, media will play half as fast. 22761 * 22762 * @method Html5#playbackRate 22763 * @return {number} 22764 * The value of `playbackRate` from the media element. A number indicating 22765 * the current playback speed of the media, where 1 is normal speed. 22766 * 22767 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22768 */ 22769 'playbackRate', 22770 /** 22771 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates 22772 * the rate at which the media is currently playing back. This value will not indicate the current 22773 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. 22774 * 22775 * Examples: 22776 * - if defaultPlaybackRate is set to 2, media will play twice as fast. 22777 * - if defaultPlaybackRate is set to 0.5, media will play half as fast. 22778 * 22779 * @method Html5.prototype.defaultPlaybackRate 22780 * @return {number} 22781 * The value of `defaultPlaybackRate` from the media element. A number indicating 22782 * the current playback speed of the media, where 1 is normal speed. 22783 * 22784 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22785 */ 22786 'defaultPlaybackRate', 22787 /** 22788 * Get the value of `played` from the media element. `played` returns a `TimeRange` 22789 * object representing points in the media timeline that have been played. 22790 * 22791 * @method Html5#played 22792 * @return {TimeRange} 22793 * The value of `played` from the media element. A `TimeRange` object indicating 22794 * the ranges of time that have been played. 22795 * 22796 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} 22797 */ 22798 'played', 22799 /** 22800 * Get the value of `networkState` from the media element. `networkState` indicates 22801 * the current network state. It returns an enumeration from the following list: 22802 * - 0: NETWORK_EMPTY 22803 * - 1: NETWORK_IDLE 22804 * - 2: NETWORK_LOADING 22805 * - 3: NETWORK_NO_SOURCE 22806 * 22807 * @method Html5#networkState 22808 * @return {number} 22809 * The value of `networkState` from the media element. This will be a number 22810 * from the list in the description. 22811 * 22812 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} 22813 */ 22814 'networkState', 22815 /** 22816 * Get the value of `readyState` from the media element. `readyState` indicates 22817 * the current state of the media element. It returns an enumeration from the 22818 * following list: 22819 * - 0: HAVE_NOTHING 22820 * - 1: HAVE_METADATA 22821 * - 2: HAVE_CURRENT_DATA 22822 * - 3: HAVE_FUTURE_DATA 22823 * - 4: HAVE_ENOUGH_DATA 22824 * 22825 * @method Html5#readyState 22826 * @return {number} 22827 * The value of `readyState` from the media element. This will be a number 22828 * from the list in the description. 22829 * 22830 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} 22831 */ 22832 'readyState', 22833 /** 22834 * Get the value of `videoWidth` from the video element. `videoWidth` indicates 22835 * the current width of the video in css pixels. 22836 * 22837 * @method Html5#videoWidth 22838 * @return {number} 22839 * The value of `videoWidth` from the video element. This will be a number 22840 * in css pixels. 22841 * 22842 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 22843 */ 22844 'videoWidth', 22845 /** 22846 * Get the value of `videoHeight` from the video element. `videoHeight` indicates 22847 * the current height of the video in css pixels. 22848 * 22849 * @method Html5#videoHeight 22850 * @return {number} 22851 * The value of `videoHeight` from the video element. This will be a number 22852 * in css pixels. 22853 * 22854 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 22855 */ 22856 'videoHeight'].forEach(function (prop) { 22857 Html5.prototype[prop] = function () { 22858 return this.el_[prop]; 22859 }; 22860 }); // Wrap native properties with a setter in this format: 22861 // set + toTitleCase(name) 22862 // The list is as follows: 22863 // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate 22864 22865 [ 22866 /** 22867 * Set the value of `volume` on the media element. `volume` indicates the current 22868 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and 22869 * so on. 22870 * 22871 * @method Html5#setVolume 22872 * @param {number} percentAsDecimal 22873 * The volume percent as a decimal. Valid range is from 0-1. 22874 * 22875 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 22876 */ 22877 'volume', 22878 /** 22879 * Set the value of `src` on the media element. `src` indicates the current 22880 * {@link Tech~SourceObject} for the media. 22881 * 22882 * @method Html5#setSrc 22883 * @param {Tech~SourceObject} src 22884 * The source object to set as the current source. 22885 * 22886 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} 22887 */ 22888 'src', 22889 /** 22890 * Set the value of `poster` on the media element. `poster` is the url to 22891 * an image file that can/will be shown when no media data is available. 22892 * 22893 * @method Html5#setPoster 22894 * @param {string} poster 22895 * The url to an image that should be used as the `poster` for the media 22896 * element. 22897 * 22898 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} 22899 */ 22900 'poster', 22901 /** 22902 * Set the value of `preload` on the media element. `preload` indicates 22903 * what should download before the media is interacted with. It can have the following 22904 * values: 22905 * - none: nothing should be downloaded 22906 * - metadata: poster and the first few frames of the media may be downloaded to get 22907 * media dimensions and other metadata 22908 * - auto: allow the media and metadata for the media to be downloaded before 22909 * interaction 22910 * 22911 * @method Html5#setPreload 22912 * @param {string} preload 22913 * The value of `preload` to set on the media element. Must be 'none', 'metadata', 22914 * or 'auto'. 22915 * 22916 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 22917 */ 22918 'preload', 22919 /** 22920 * Set the value of `playbackRate` on the media element. `playbackRate` indicates 22921 * the rate at which the media should play back. Examples: 22922 * - if playbackRate is set to 2, media will play twice as fast. 22923 * - if playbackRate is set to 0.5, media will play half as fast. 22924 * 22925 * @method Html5#setPlaybackRate 22926 * @return {number} 22927 * The value of `playbackRate` from the media element. A number indicating 22928 * the current playback speed of the media, where 1 is normal speed. 22929 * 22930 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 22931 */ 22932 'playbackRate', 22933 /** 22934 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates 22935 * the rate at which the media should play back upon initial startup. Changing this value 22936 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. 22937 * 22938 * Example Values: 22939 * - if playbackRate is set to 2, media will play twice as fast. 22940 * - if playbackRate is set to 0.5, media will play half as fast. 22941 * 22942 * @method Html5.prototype.setDefaultPlaybackRate 22943 * @return {number} 22944 * The value of `defaultPlaybackRate` from the media element. A number indicating 22945 * the current playback speed of the media, where 1 is normal speed. 22946 * 22947 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} 22948 */ 22949 'defaultPlaybackRate'].forEach(function (prop) { 22950 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 22951 this.el_[prop] = v; 22952 }; 22953 }); // wrap native functions with a function 22954 // The list is as follows: 22955 // pause, load, play 22956 22957 [ 22958 /** 22959 * A wrapper around the media elements `pause` function. This will call the `HTML5` 22960 * media elements `pause` function. 22961 * 22962 * @method Html5#pause 22963 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} 22964 */ 22965 'pause', 22966 /** 22967 * A wrapper around the media elements `load` function. This will call the `HTML5`s 22968 * media element `load` function. 22969 * 22970 * @method Html5#load 22971 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} 22972 */ 22973 'load', 22974 /** 22975 * A wrapper around the media elements `play` function. This will call the `HTML5`s 22976 * media element `play` function. 22977 * 22978 * @method Html5#play 22979 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} 22980 */ 22981 'play'].forEach(function (prop) { 22982 Html5.prototype[prop] = function () { 22983 return this.el_[prop](); 22984 }; 22985 }); 22986 Tech.withSourceHandlers(Html5); 22987 /**
22988 * Native source handler for Html5, simply passes the source to the media element. 22989 * 22990 * @property {Tech~SourceObject} source 22991 * The source object 22992 * 22993 * @property {Html5} tech 22994 * The instance of the HTML5 tech. 22995 */ 22996 22997 Html5.nativeSourceHandler = {}; 22998 /** 22999 * Check if the media element can play the given mime type. 23000 * 23001 * @param {string} type 23002 * The mimetype to check 23003 * 23004 * @return {string} 23005 * 'probably', 'maybe', or '' (empty string) 23006 */ 23007 23008 Html5.nativeSourceHandler.canPlayType = function (type) { 23009 // IE without MediaPlayer throws an error (#519) 23010 try { 23011 return Html5.TEST_VID.canPlayType(type); 23012 } catch (e) { 23013 return ''; 23014 } 23015 }; 23016 /** 23017 * Check if the media element can handle a source natively. 23018 * 23019 * @param {Tech~SourceObject} source 23020 * The source object 23021 * 23022 * @param {Object} [options] 23023 * Options to be passed to the tech. 23024 * 23025 * @return {string} 23026 * 'probably', 'maybe', or '' (empty string). 23027 */ 23028 23029 23030 Html5.nativeSourceHandler.canHandleSource = function (source, options) { 23031 // If a type was provided we should rely on that 23032 if (source.type) { 23033 return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]' 23034 } else if (source.src) { 23035 var ext = getFileExtension(source.src); 23036 return Html5.nativeSourceHandler.canPlayType("video/" + ext); 23037 } 23038 23039 return ''; 23040 }; 23041 /** 23042 * Pass the source to the native media element. 23043 * 23044 * @param {Tech~SourceObject} source 23045 * The source object 23046 * 23047 * @param {Html5} tech 23048 * The instance of the Html5 tech 23049 * 23050 * @param {Object} [options] 23051 * The options to pass to the source 23052 */ 23053 23054 23055 Html5.nativeSourceHandler.handleSource = function (source, tech, options) { 23056 tech.setSrc(source.src); 23057 }; 23058 /** 23059 * A noop for the native dispose function, as cleanup is not needed. 23060 */ 23061 23062 23063 Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler 23064 23065 23066 Html5.registerSourceHandler(Html5.nativeSourceHandler); 23067 Tech.registerTech('Html5', Html5); 23068 23069 // on the player when they happen 23070 23071 var TECH_EVENTS_RETRIGGER = [ 23072 /** 23073 * Fired while the user agent is downloading media data. 23074 * 23075 * @event Player#progress 23076 * @type {EventTarget~Event} 23077 */ 23078 23079 /** 23080 * Retrigger the `progress` event that was triggered by the {@link Tech}. 23081 * 23082 * @private 23083 * @method Player#handleTechProgress_ 23084 * @fires Player#progress 23085 * @listens Tech#progress 23086 */ 23087 'progress', 23088 /** 23089 * Fires when the loading of an audio/video is aborted. 23090 * 23091 * @event Player#abort 23092 * @type {EventTarget~Event} 23093 */ 23094 23095 /** 23096 * Retrigger the `abort` event that was triggered by the {@link Tech}. 23097 * 23098 * @private 23099 * @method Player#handleTechAbort_ 23100 * @fires Player#abort 23101 * @listens Tech#abort 23102 */ 23103 'abort', 23104 /** 23105 * Fires when the browser is intentionally not getting media data. 23106 * 23107 * @event Player#suspend 23108 * @type {EventTarget~Event} 23109 */ 23110 23111 /** 23112 * Retrigger the `suspend` event that was triggered by the {@link Tech}. 23113 * 23114 * @private 23115 * @method Player#handleTechSuspend_ 23116 * @fires Player#suspend 23117 * @listens Tech#suspend 23118 */ 23119 'suspend', 23120 /** 23121 * Fires when the current playlist is empty. 23122 * 23123 * @event Player#emptied 23124 * @type {EventTarget~Event} 23125 */ 23126 23127 /** 23128 * Retrigger the `emptied` event that was triggered by the {@link Tech}. 23129 * 23130 * @private 23131 * @method Player#handleTechEmptied_ 23132 * @fires Player#emptied 23133 * @listens Tech#emptied 23134 */ 23135 'emptied', 23136 /** 23137 * Fires when the browser is trying to get media data, but data is not available. 23138 * 23139 * @event Player#stalled 23140 * @type {EventTarget~Event} 23141 */ 23142 23143 /** 23144 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 23145 * 23146 * @private 23147 * @method Player#handleTechStalled_ 23148 * @fires Player#stalled 23149 * @listens Tech#stalled 23150 */ 23151 'stalled', 23152 /** 23153 * Fires when the browser has loaded meta data for the audio/video. 23154 * 23155 * @event Player#loadedmetadata 23156 * @type {EventTarget~Event} 23157 */ 23158 23159 /** 23160 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 23161 * 23162 * @private 23163 * @method Player#handleTechLoadedmetadata_ 23164 * @fires Player#loadedmetadata 23165 * @listens Tech#loadedmetadata 23166 */ 23167 'loadedmetadata', 23168 /** 23169 * Fires when the browser has loaded the current frame of the audio/video. 23170 * 23171 * @event Player#loadeddata 23172 * @type {event} 23173 */ 23174 23175 /** 23176 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. 23177 * 23178 * @private 23179 * @method Player#handleTechLoaddeddata_ 23180 * @fires Player#loadeddata 23181 * @listens Tech#loadeddata 23182 */ 23183 'loadeddata', 23184 /** 23185 * Fires when the current playback position has changed. 23186 * 23187 * @event Player#timeupdate 23188 * @type {event} 23189 */ 23190 23191 /** 23192 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. 23193 * 23194 * @private 23195 * @method Player#handleTechTimeUpdate_ 23196 * @fires Player#timeupdate 23197 * @listens Tech#timeupdate 23198 */ 23199 'timeupdate', 23200 /** 23201 * Fires when the video's intrinsic dimensions change 23202 * 23203 * @event Player#resize 23204 * @type {event} 23205 */ 23206 23207 /** 23208 * Retrigger the `resize` event that was triggered by the {@link Tech}. 23209 * 23210 * @private 23211 * @method Player#handleTechResize_ 23212 * @fires Player#resize 23213 * @listens Tech#resize 23214 */ 23215 'resize',
23216 /** 23217 * Fires when the volume has been changed 23218 * 23219 * @event Player#volumechange 23220 * @type {event} 23221 */ 23222 23223 /** 23224 * Retrigger the `volumechange` event that was triggered by the {@link Tech}. 23225 * 23226 * @private 23227 * @method Player#handleTechVolumechange_ 23228 * @fires Player#volumechange 23229 * @listens Tech#volumechange 23230 */ 23231 'volumechange', 23232 /** 23233 * Fires when the text track has been changed 23234 * 23235 * @event Player#texttrackchange 23236 * @type {event} 23237 */ 23238 23239 /** 23240 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. 23241 * 23242 * @private 23243 * @method Player#handleTechTexttrackchange_ 23244 * @fires Player#texttrackchange 23245 * @listens Tech#texttrackchange 23246 */ 23247 'texttrackchange']; // events to queue when playback rate is zero 23248 // this is a hash for the sole purpose of mapping non-camel-cased event names 23249 // to camel-cased function names 23250 23251 var TECH_EVENTS_QUEUE = { 23252 canplay: 'CanPlay', 23253 canplaythrough: 'CanPlayThrough', 23254 playing: 'Playing', 23255 seeked: 'Seeked' 23256 }; 23257 var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge']; 23258 var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny 23259 // grep: vjs-layout-x-small 23260 // grep: vjs-layout-small 23261 // grep: vjs-layout-medium 23262 // grep: vjs-layout-large 23263 // grep: vjs-layout-x-large 23264 // grep: vjs-layout-huge 23265 23266 BREAKPOINT_ORDER.forEach(function (k) { 23267 var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k; 23268 BREAKPOINT_CLASSES[k] = "vjs-layout-" + v; 23269 }); 23270 var DEFAULT_BREAKPOINTS = { 23271 tiny: 210, 23272 xsmall: 320, 23273 small: 425, 23274 medium: 768, 23275 large: 1440, 23276 xlarge: 2560, 23277 huge: Infinity 23278 }; 23279 /** 23280 * An instance of the `Player` class is created when any of the Video.js setup methods 23281 * are used to initialize a video. 23282 * 23283 * After an instance has been created it can be accessed globally in two ways: 23284 * 1. By calling `videojs('example_video_1');` 23285 * 2. By using it directly via `videojs.players.example_video_1;` 23286 * 23287 * @extends Component 23288 */ 23289 23290 var Player = 23291 /*#__PURE__*/ 23292 function (_Component) { 23293 inheritsLoose(Player, _Component); 23294 23295 /** 23296 * Create an instance of this class. 23297 * 23298 * @param {Element} tag 23299 * The original video DOM element used for configuring options. 23300 * 23301 * @param {Object} [options] 23302 * Object of option names and values. 23303 * 23304 * @param {Component~ReadyCallback} [ready] 23305 * Ready callback function. 23306 */ 23307 function Player(tag, options, ready) { 23308 var _this; 23309 23310 // Make sure tag ID exists 23311 tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options 23312 // The options argument overrides options set in the video tag 23313 // which overrides globally set options. 23314 // This latter part coincides with the load order 23315 // (tag must exist before Player) 23316 23317 options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up 23318 // player properties first, and can't use `this` before `super()` 23319 23320 options.initChildren = false; // Same with creating the element 23321 23322 options.createEl = false; // don't auto mixin the evented mixin 23323 23324 options.evented = false; // we don't want the player to report touch activity on itself 23325 // see enableTouchActivity in Component 23326 23327 options.reportTouchActivity = false; // If language is not set, get the closest lang attribute 23328 23329 if (!options.language) { 23330 if (typeof tag.closest === 'function') { 23331 var closest = tag.closest('[lang]'); 23332 23333 if (closest && closest.getAttribute) { 23334 options.language = closest.getAttribute('lang'); 23335 } 23336 } else { 23337 var element = tag; 23338 23339 while (element && element.nodeType === 1) { 23340 if (getAttributes(element).hasOwnProperty('lang')) { 23341 options.language = element.getAttribute('lang'); 23342 break; 23343 } 23344 23345 element = element.parentNode; 23346 } 23347 } 23348 } // Run base component initializing with new options 23349 23350 23351 _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners. 23352 23353 _this.boundDocumentFullscreenChange_ = bind(assertThisInitialized(_this), _this.documentFullscreenChange_);
23354 _this.boundFullWindowOnEscKey_ = bind(assertThisInitialized(_this), _this.fullWindowOnEscKey); // default isFullscreen_ to false 23355 23356 _this.isFullscreen_ = false; // create logger 23357 23358 _this.log = createLogger$1(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests 23359 23360 _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster 23361 23362 _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero 23363 // and a seek is happening 23364 23365 _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously 23366 23367 _this.isReady_ = false; // Init state hasStarted_ 23368 23369 _this.hasStarted_ = false; // Init state userActive_ 23370 23371 _this.userActive_ = false; // if the global option object was accidentally blown away by 23372 // someone, bail early with an informative error 23373 23374 if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { 23375 throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); 23376 } // Store the original tag used to set options 23377 23378 23379 _this.tag = tag; // Store the tag attributes used to restore html5 element 23380 23381 _this.tagAttributes = tag && getAttributes(tag); // Update current language 23382 23383 _this.language(_this.options_.language); // Update Supported Languages 23384 23385 23386 if (options.languages) { 23387 // Normalise player option languages to lowercase 23388 var languagesToLower = {}; 23389 Object.getOwnPropertyNames(options.languages).forEach(function (name) { 23390 languagesToLower[name.toLowerCase()] = options.languages[name]; 23391 }); 23392 _this.languages_ = languagesToLower; 23393 } else { 23394 _this.languages_ = Player.prototype.options_.languages; 23395 } 23396 23397 _this.resetCache_(); // Set poster 23398 23399 23400 _this.poster_ = options.poster || ''; // Set controls 23401 23402 _this.controls_ = !!options.controls; // Original tag settings stored in options 23403 // now remove immediately so native controls don't flash. 23404 // May be turned back on by HTML5 tech if nativeControlsForTouch is true 23405 23406 tag.controls = false; 23407 tag.removeAttribute('controls'); 23408 _this.changingSrc_ = false; 23409 _this.playCallbacks_ = []; 23410 _this.playTerminatedQueue_ = []; // the attribute overrides the option 23411 23412 if (tag.hasAttribute('autoplay')) { 23413 _this.autoplay(true); 23414 } else { 23415 // otherwise use the setter to validate and 23416 // set the correct value. 23417 _this.autoplay(_this.options_.autoplay); 23418 } // check plugins 23419 23420 23421 if (options.plugins) { 23422 Object.keys(options.plugins).forEach(function (name) { 23423 if (typeof _this[name] !== 'function') { 23424 throw new Error("plugin \"" + name + "\" does not exist"); 23425 } 23426 }); 23427 } 23428 /* 23429 * Store the internal state of scrubbing 23430 * 23431 * @private 23432 * @return {Boolean} True if the user is scrubbing 23433 */ 23434 23435 23436 _this.scrubbing_ = false; 23437 _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus. 23438 23439 evented(assertThisInitialized(_this), { 23440 eventBusKey: 'el_' 23441 }); // listen to document and player fullscreenchange handlers so we receive those events 23442 // before a user can receive them so we can update isFullscreen appropriately. 23443 // make sure that we listen to fullscreenchange events before everything else to make sure that 23444 // our isFullscreen method is updated properly for internal components as well as external. 23445 23446 if (_this.fsApi_.requestFullscreen) { 23447 on(document, _this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_); 23448 23449 _this.on(_this.fsApi_.fullscreenchange, _this.boundDocumentFullscreenChange_); 23450 } 23451 23452 if (_this.fluid_) { 23453 _this.on('playerreset', _this.updateStyleEl_); 23454 } // We also want to pass the original player options to each component and plugin 23455 // as well so they don't need to reach back into the player for options later. 23456 // We also need to do another copy of this.options_ so we don't end up with 23457 // an infinite loop. 23458 23459 23460 var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins 23461 23462 if (options.plugins) {
vendor: 4,134 bytes, lines 23463-23600
23463 Object.keys(options.plugins).forEach(function (name) { 23464 _this[name](options.plugins[name]); 23465 }); 23466 } 23467 23468 _this.options_.playerOptions = playerOptionsCopy; 23469 _this.middleware_ = []; 23470 23471 _this.initChildren(); // Set isAudio based on whether or not an audio tag was used 23472 23473 23474 _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially 23475 // set because the element doesn't exist yet. 23476 23477 23478 if (_this.controls()) { 23479 _this.addClass('vjs-controls-enabled'); 23480 } else { 23481 _this.addClass('vjs-controls-disabled'); 23482 } // Set ARIA label and region role depending on player type 23483 23484 23485 _this.el_.setAttribute('role', 'region'); 23486 23487 if (_this.isAudio()) { 23488 _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); 23489 } else { 23490 _this.el_.setAttribute('aria-label', _this.localize('Video Player')); 23491 } 23492 23493 if (_this.isAudio()) { 23494 _this.addClass('vjs-audio'); 23495 } 23496 23497 if (_this.flexNotSupported_()) { 23498 _this.addClass('vjs-no-flex'); 23499 } // TODO: Make this smarter. Toggle user state between touching/mousing 23500 // using events, since devices can have both touch and mouse events. 23501 // TODO: Make this check be performed again when the window switches between monitors 23502 // (See https://github.com/videojs/video.js/issues/5683) 23503 23504 23505 if (TOUCH_ENABLED) { 23506 _this.addClass('vjs-touch-enabled'); 23507 } // iOS Safari has broken hover handling 23508 23509 23510 if (!IS_IOS) { 23511 _this.addClass('vjs-workinghover'); 23512 } // Make player easily findable by ID 23513 23514 23515 Player.players[_this.id_] = assertThisInitialized(_this); // Add a major version class to aid css in plugins 23516 23517 var majorVersion = version.split('.')[0]; 23518 23519 _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components 23520 // like the control bar show themselves if needed 23521 23522 23523 _this.userActive(true); 23524 23525 _this.reportUserActivity(); 23526 23527 _this.one('play', _this.listenForUserActivity_); 23528 23529 _this.on('stageclick', _this.handleStageClick_); 23530 23531 _this.on('keydown', _this.handleKeyDown); 23532 23533 _this.breakpoints(_this.options_.breakpoints); 23534 23535 _this.responsive(_this.options_.responsive); 23536 23537 return _this; 23538 } 23539 /** 23540 * Destroys the video player and does any necessary cleanup. 23541 * 23542 * This is especially helpful if you are dynamically adding and removing videos 23543 * to/from the DOM. 23544 * 23545 * @fires Player#dispose 23546 */ 23547 23548 23549 var _proto = Player.prototype; 23550 23551 _proto.dispose = function dispose() { 23552 var _this2 = this; 23553 23554 /** 23555 * Called when the player is being disposed of. 23556 * 23557 * @event Player#dispose 23558 * @type {EventTarget~Event} 23559 */ 23560 this.trigger('dispose'); // prevent dispose from being called twice 23561 23562 this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is 23563 23564 off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_); 23565 off(document, 'keydown', this.boundFullWindowOnEscKey_); 23566 23567 if (this.styleEl_ && this.styleEl_.parentNode) { 23568 this.styleEl_.parentNode.removeChild(this.styleEl_); 23569 this.styleEl_ = null; 23570 } // Kill reference to this player 23571 23572 23573 Player.players[this.id_] = null; 23574 23575 if (this.tag && this.tag.player) { 23576 this.tag.player = null; 23577 } 23578 23579 if (this.el_ && this.el_.player) { 23580 this.el_.player = null; 23581 } 23582 23583 if (this.tech_) { 23584 this.tech_.dispose(); 23585 this.isPosterFromTech_ = false; 23586 this.poster_ = ''; 23587 } 23588 23589 if (this.playerElIngest_) { 23590 this.playerElIngest_ = null; 23591 } 23592 23593 if (this.tag) { 23594 this.tag = null; 23595 } 23596 23597 clearCacheForPlayer(this); // remove all event handlers for track lists 23598 // all tracks and track listeners are removed on 23599 // tech dispose 23600
vendor: 6,243 bytes, lines 23601-23762
23601 ALL.names.forEach(function (name) { 23602 var props = ALL[name]; 23603 23604 var list = _this2[props.getterName](); // if it is not a native list 23605 // we have to manually remove event listeners 23606 23607 23608 if (list && list.off) { 23609 list.off(); 23610 } 23611 }); // the actual .el_ is removed here 23612 23613 _Component.prototype.dispose.call(this); 23614 } 23615 /** 23616 * Create the `Player`'s DOM element. 23617 * 23618 * @return {Element} 23619 * The DOM element that gets created. 23620 */ 23621 ; 23622 23623 _proto.createEl = function createEl() { 23624 var tag = this.tag; 23625 var el; 23626 var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); 23627 var divEmbed = this.tag.tagName.toLowerCase() === 'video-js'; 23628 23629 if (playerElIngest) { 23630 el = this.el_ = tag.parentNode; 23631 } else if (!divEmbed) { 23632 el = this.el_ = _Component.prototype.createEl.call(this, 'div'); 23633 } // Copy over all the attributes from the tag, including ID and class 23634 // ID will now reference player box, not the video tag 23635 23636 23637 var attrs = getAttributes(tag); 23638 23639 if (divEmbed) { 23640 el = this.el_ = tag; 23641 tag = this.tag = document.createElement('video'); 23642 23643 while (el.children.length) { 23644 tag.appendChild(el.firstChild); 23645 } 23646 23647 if (!hasClass(el, 'video-js')) { 23648 addClass(el, 'video-js'); 23649 } 23650 23651 el.appendChild(tag); 23652 playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element 23653 // to our new `video` element. This will move things like 23654 // `src` or `controls` that were set via js before the player 23655 // was initialized. 23656 23657 Object.keys(el).forEach(function (k) { 23658 try { 23659 tag[k] = el[k]; 23660 } catch (e) {// we got a a property like outerHTML which we can't actually copy, ignore it 23661 } 23662 }); 23663 } // set tabindex to -1 to remove the video element from the focus order 23664 23665 23666 tag.setAttribute('tabindex', '-1'); 23667 attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and 23668 // for the same issue with Chrome (on Windows) with JAWS. 23669 // See https://github.com/FreedomScientific/VFO-standards-support/issues/78 23670 // Note that we can't detect if JAWS is being used, but this ARIA attribute 23671 // doesn't change behavior of IE11 or Chrome if JAWS is not being used 23672 23673 if (IE_VERSION || IS_CHROME && IS_WINDOWS) { 23674 tag.setAttribute('role', 'application'); 23675 attrs.role = 'application'; 23676 } // Remove width/height attrs from tag so CSS can make it 100% width/height 23677 23678 23679 tag.removeAttribute('width'); 23680 tag.removeAttribute('height'); 23681 23682 if ('width' in attrs) { 23683 delete attrs.width; 23684 } 23685 23686 if ('height' in attrs) { 23687 delete attrs.height; 23688 } 23689 23690 Object.getOwnPropertyNames(attrs).forEach(function (attr) { 23691 // don't copy over the class attribute to the player element when we're in a div embed 23692 // the class is already set up properly in the divEmbed case 23693 // and we want to make sure that the `video-js` class doesn't get lost 23694 if (!(divEmbed && attr === 'class')) { 23695 el.setAttribute(attr, attrs[attr]); 23696 } 23697 23698 if (divEmbed) { 23699 tag.setAttribute(attr, attrs[attr]); 23700 } 23701 }); // Update tag id/class for use as HTML5 playback tech 23702 // Might think we should do this after embedding in container so .vjs-tech class 23703 // doesn't flash 100% width/height, but class only applies with .video-js parent 23704 23705 tag.playerId = tag.id; 23706 tag.id += '_html5_api'; 23707 tag.className = 'vjs-tech'; // Make player findable on elements 23708 23709 tag.player = el.player = this; // Default state of video is paused 23710 23711 this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height 23712 // of the player in a way that's still overrideable by CSS, just like the 23713 // video element 23714 23715 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) { 23716 this.styleEl_ = createStyleElement('vjs-styles-dimensions'); 23717 var defaultsStyleEl = $('.vjs-styles-defaults'); 23718 var head = $('head'); 23719 head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); 23720 } 23721 23722 this.fill_ = false; 23723 this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el 23724 23725 this.width(this.options_.width); 23726 this.height(this.options_.height); 23727 this.fill(this.options_.fill); 23728 this.fluid(this.options_.fluid); 23729 this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag, 23730 // because IE doesn't hide them completely from screen readers. 23731 23732 var links = tag.getElementsByTagName('a'); 23733 23734 for (var i = 0; i < links.length; i++) { 23735 var linkEl = links.item(i); 23736 addClass(linkEl, 'vjs-hidden'); 23737 linkEl.setAttribute('hidden', 'hidden'); 23738 } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so 23739 // keep track of the original for later so we can know if the source originally failed 23740 23741 23742 tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container 23743 23744 if (tag.parentNode && !playerElIngest) { 23745 tag.parentNode.insertBefore(el, tag); 23746 } // insert the tag as the first child of the player element 23747 // then manually add it to the children array so that this.addChild 23748 // will work properly for other components 23749 // 23750 // Breaks iPhone, fixed in HTML5 setup. 23751 23752 23753 prependTo(tag, el); 23754 this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player 23755 // if it's been set to something different to the doc 23756 23757 this.el_.setAttribute('lang', this.language_); 23758 this.el_ = el; 23759 return el; 23760 } 23761 /** 23762 * A getter/setter for the `Player`'s width. Returns the player'
23762s configured value. 23763 * To get the current width use `currentWidth()`. 23764 * 23765 * @param {number} [value] 23766 * The value to set the `Player`'s width to. 23767 * 23768 * @return {number} 23769 * The current width of the `Player` when getting. 23770 */ 23771 ; 23772 23773 _proto.width = function width(value) { 23774 return this.dimension('width', value); 23775 } 23776 /** 23777 * A getter/setter for the `Player`'s height. Returns the player's configured value. 23778 * To get the current height use `currentheight()`. 23779 * 23780 * @param {number} [value] 23781 * The value to set the `Player`'s heigth to. 23782 * 23783 * @return {number} 23784 * The current height of the `Player` when getting. 23785 */ 23786 ; 23787 23788 _proto.height = function height(value) { 23789 return this.dimension('height', value); 23790 } 23791 /** 23792 * A getter/setter for the `Player`'s width & height. 23793 * 23794 * @param {string} dimension 23795 * This string can be: 23796 * - 'width' 23797 * - 'height' 23798 * 23799 * @param {number} [value] 23800 * Value for dimension specified in the first argument. 23801 * 23802 * @return {number} 23803 * The dimension arguments value when getting (width/height). 23804 */ 23805 ; 23806 23807 _proto.dimension = function dimension(_dimension, value) { 23808 var privDimension = _dimension + '_'; 23809 23810 if (value === undefined) { 23811 return this[privDimension] || 0; 23812 } 23813 23814 if (value === '' || value === 'auto') { 23815 // If an empty string is given, reset the dimension to be automatic 23816 this[privDimension] = undefined; 23817 this.updateStyleEl_(); 23818 return; 23819 } 23820 23821 var parsedVal = parseFloat(value); 23822 23823 if (isNaN(parsedVal)) { 23824 log.error("Improper value \"" + value + "\" supplied for for " + _dimension); 23825 return; 23826 } 23827 23828 this[privDimension] = parsedVal; 23829 this.updateStyleEl_(); 23830 } 23831 /** 23832 * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. 23833 * 23834 * Turning this on will turn off fill mode. 23835 * 23836 * @param {boolean} [bool] 23837 * - A value of true adds the class. 23838 * - A value of false removes the class. 23839 * - No value will be a getter. 23840 * 23841 * @return {boolean|undefined} 23842 * - The value of fluid when getting. 23843 * - `undefined` when setting. 23844 */ 23845 ; 23846 23847 _proto.fluid = function fluid(bool) { 23848 if (bool === undefined) { 23849 return !!this.fluid_; 23850 } 23851 23852 this.fluid_ = !!bool; 23853 23854 if (isEvented(this)) { 23855 this.off('playerreset', this.updateStyleEl_); 23856 } 23857 23858 if (bool) { 23859 this.addClass('vjs-fluid'); 23860 this.fill(false); 23861 addEventedCallback(function () { 23862 this.on('playerreset', this.updateStyleEl_); 23863 }); 23864 } else { 23865 this.removeClass('vjs-fluid'); 23866 } 23867 23868 this.updateStyleEl_(); 23869 } 23870 /** 23871 * A getter/setter/toggler for the vjs-fill `className` on the `Player`. 23872 * 23873 * Turning this on will turn off fluid mode. 23874 * 23875 * @param {boolean} [bool] 23876 * - A value of true adds the class. 23877 * - A value of false removes the class. 23878 * - No value will be a getter. 23879 * 23880 * @return {boolean|undefined} 23881 * - The value of fluid when getting. 23882 * - `undefined` when setting. 23883 */ 23884 ; 23885 23886 _proto.fill = function fill(bool) { 23887 if (bool === undefined) { 23888 return !!this.fill_; 23889 } 23890 23891 this.fill_ = !!bool; 23892 23893 if (bool) { 23894 this.addClass('vjs-fill'); 23895 this.fluid(false); 23896 } else { 23897 this.removeClass('vjs-fill'); 23898 } 23899 } 23900 /** 23901 * Get/Set the aspect ratio 23902 * 23903 * @param {string} [ratio] 23904 * Aspect ratio for player 23905 * 23906 * @return {string|undefined} 23907 * returns the current aspect ratio when getting 23908 */ 23909 23910 /** 23911 * A getter/setter for the `Player`'s aspect ratio. 23912 * 23913 * @param {string} [ratio] 23914 * The value to set the `Player's aspect ratio to. 23915 * 23916 * @return {string|undefined} 23917 * - The current aspect ratio of the `Player` when getting. 23918 * - undefined when setting 23919 */ 23920 ; 23921 23922 _proto.aspectRatio = function aspectRatio(ratio) { 23923 if (ratio === undefined) { 23924 return this.aspectRatio_; 23925 } // Check for width:height format 23926 23927 23928 if (!/^\d+\:\d+$/.test(ratio)) { 23929 throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); 23930 } 23931 23932 this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode, 23933 // because in fixed mode you could calculate width and height yourself. 23934 23935 this.fluid(true); 23936 this.updateStyleEl_(); 23937 } 23938 /** 23939 * Update styles of the `Player` element (height, width and aspect ratio). 23940 * 23941 * @private 23942 * @listens Tech#loadedmetadata 23943 */ 23944 ; 23945 23946 _proto.updateStyleEl_ = function updateStyleEl_() { 23947 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) { 23948 var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; 23949 23950 var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; 23951 23952 var techEl = this.tech_ && this.tech_.el(); 23953 23954 if (techEl) { 23955 if (_width >= 0) { 23956 techEl.width = _width; 23957 } 23958 23959 if (_height >= 0) {
vendor: 14,623 bytes, lines 23960-24315
23960 techEl.height = _height; 23961 } 23962 } 23963 23964 return; 23965 } 23966 23967 var width; 23968 var height; 23969 var aspectRatio; 23970 var idClass; // The aspect ratio is either used directly or to calculate width and height. 23971 23972 if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { 23973 // Use any aspectRatio that's been specifically set 23974 aspectRatio = this.aspectRatio_; 23975 } else if (this.videoWidth() > 0) { 23976 // Otherwise try to get the aspect ratio from the video metadata 23977 aspectRatio = this.videoWidth() + ':' + this.videoHeight(); 23978 } else { 23979 // Or use a default. The video element's is 2:1, but 16:9 is more common. 23980 aspectRatio = '16:9'; 23981 } // Get the ratio as a decimal we can use to calculate dimensions 23982 23983 23984 var ratioParts = aspectRatio.split(':'); 23985 var ratioMultiplier = ratioParts[1] / ratioParts[0]; 23986 23987 if (this.width_ !== undefined) { 23988 // Use any width that's been specifically set 23989 width = this.width_; 23990 } else if (this.height_ !== undefined) { 23991 // Or calulate the width from the aspect ratio if a height has been set 23992 width = this.height_ / ratioMultiplier; 23993 } else { 23994 // Or use the video's metadata, or use the video el's default of 300 23995 width = this.videoWidth() || 300; 23996 } 23997 23998 if (this.height_ !== undefined) { 23999 // Use any height that's been specifically set 24000 height = this.height_; 24001 } else { 24002 // Otherwise calculate the height from the ratio and the width 24003 height = width * ratioMultiplier; 24004 } // Ensure the CSS class is valid by starting with an alpha character 24005 24006 24007 if (/^[^a-zA-Z]/.test(this.id())) { 24008 idClass = 'dimensions-' + this.id(); 24009 } else { 24010 idClass = this.id() + '-dimensions'; 24011 } // Ensure the right class is still on the player for the style element 24012 24013 24014 this.addClass(idClass); 24015 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 "); 24016 } 24017 /** 24018 * Load/Create an instance of playback {@link Tech} including element 24019 * and API methods. Then append the `Tech` element in `Player` as a child. 24020 * 24021 * @param {string} techName 24022 * name of the playback technology 24023 * 24024 * @param {string} source 24025 * video source 24026 * 24027 * @private 24028 */ 24029 ; 24030 24031 _proto.loadTech_ = function loadTech_(techName, source) { 24032 var _this3 = this; 24033 24034 // Pause and remove current playback technology 24035 if (this.tech_) { 24036 this.unloadTech_(); 24037 } 24038 24039 var titleTechName = toTitleCase(techName); 24040 var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech 24041 24042 if (titleTechName !== 'Html5' && this.tag) { 24043 Tech.getTech('Html5').disposeMediaElement(this.tag); 24044 this.tag.player = null; 24045 this.tag = null; 24046 } 24047 24048 this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously 24049 24050 this.isReady_ = false; // if autoplay is a string we pass false to the tech 24051 // because the player is going to handle autoplay on `loadstart` 24052 24053 var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use. 24054 24055 var techOptions = { 24056 source: source, 24057 autoplay: autoplay, 24058 'nativeControlsForTouch': this.options_.nativeControlsForTouch, 24059 'playerId': this.id(), 24060 'techId': this.id() + "_" + camelTechName + "_api", 24061 'playsinline': this.options_.playsinline, 24062 'preload': this.options_.preload, 24063 'loop': this.options_.loop, 24064 'muted': this.options_.muted, 24065 'poster': this.poster(), 24066 'language': this.language(), 24067 'playerElIngest': this.playerElIngest_ || false, 24068 'vtt.js': this.options_['vtt.js'], 24069 'canOverridePoster': !!this.options_.techCanOverridePoster, 24070 'enableSourceset': this.options_.enableSourceset, 24071 'Promise': this.options_.Promise 24072 }; 24073 ALL.names.forEach(function (name) { 24074 var props = ALL[name]; 24075 techOptions[props.getterName] = _this3[props.privateName]; 24076 }); 24077 assign(techOptions, this.options_[titleTechName]); 24078 assign(techOptions, this.options_[camelTechName]); 24079 assign(techOptions, this.options_[techName.toLowerCase()]); 24080 24081 if (this.tag) { 24082 techOptions.tag = this.tag; 24083 } 24084 24085 if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { 24086 techOptions.startTime = this.cache_.currentTime; 24087 } // Initialize tech instance 24088 24089 24090 var TechClass = Tech.getTech(techName); 24091 24092 if (!TechClass) { 24093 throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'"); 24094 } 24095 24096 this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async 24097 24098 this.tech_.ready(bind(this, this.handleTechReady_), true); 24099 textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player 24100 24101 TECH_EVENTS_RETRIGGER.forEach(function (event) { 24102 _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]); 24103 }); 24104 Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) { 24105 _this3.on(_this3.tech_, event, function (eventObj) { 24106 if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) { 24107 _this3.queuedCallbacks_.push({ 24108 callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3), 24109 event: eventObj 24110 }); 24111 24112 return; 24113 } 24114 24115 _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj); 24116 }); 24117 }); 24118 this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); 24119 this.on(this.tech_, 'sourceset', this.handleTechSourceset_); 24120 this.on(this.tech_, 'waiting', this.handleTechWaiting_); 24121 this.on(this.tech_, 'ended', this.handleTechEnded_); 24122 this.on(this.tech_, 'seeking', this.handleTechSeeking_); 24123 this.on(this.tech_, 'play', this.handleTechPlay_); 24124 this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); 24125 this.on(this.tech_, 'pause', this.handleTechPause_); 24126 this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); 24127 this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); 24128 this.on(this.tech_, 'enterpictureinpicture', this.handleTechEnterPictureInPicture_); 24129 this.on(this.tech_, 'leavepictureinpicture', this.handleTechLeavePictureInPicture_); 24130 this.on(this.tech_, 'error', this.handleTechError_); 24131 this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); 24132 this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); 24133 this.on(this.tech_, 'textdata', this.handleTechTextData_); 24134 this.on(this.tech_, 'ratechange', this.handleTechRateChange_); 24135 this.usingNativeControls(this.techGet_('controls')); 24136 24137 if (this.controls() && !this.usingNativeControls()) { 24138 this.addTechControlsListeners_(); 24139 } // Add the tech element in the DOM if it was not already there 24140 // Make sure to not insert the original video element if using Html5 24141 24142 24143 if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { 24144 prependTo(this.tech_.el(), this.el()); 24145 } // Get rid of the original video tag reference after the first tech is loaded 24146 24147 24148 if (this.tag) { 24149 this.tag.player = null; 24150 this.tag = null; 24151 } 24152 } 24153 /** 24154 * Unload and dispose of the current playback {@link Tech}. 24155 * 24156 * @private 24157 */ 24158 ; 24159 24160 _proto.unloadTech_ = function unloadTech_() { 24161 var _this4 = this; 24162 24163 // Save the current text tracks so that we can reuse the same text tracks with the next tech 24164 ALL.names.forEach(function (name) { 24165 var props = ALL[name]; 24166 _this4[props.privateName] = _this4[props.getterName](); 24167 }); 24168 this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_); 24169 this.isReady_ = false; 24170 this.tech_.dispose(); 24171 this.tech_ = false; 24172 24173 if (this.isPosterFromTech_) { 24174 this.poster_ = ''; 24175 this.trigger('posterchange'); 24176 } 24177 24178 this.isPosterFromTech_ = false; 24179 } 24180 /** 24181 * Return a reference to the current {@link Tech}. 24182 * It will print a warning by default about the danger of using the tech directly 24183 * but any argument that is passed in will silence the warning. 24184 * 24185 * @param {*} [safety] 24186 * Anything passed in to silence the warning 24187 * 24188 * @return {Tech} 24189 * The Tech 24190 */ 24191 ; 24192 24193 _proto.tech = function tech(safety) { 24194 if (safety === undefined) { 24195 log.warn('Using the tech directly can be dangerous. I hope you know what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n'); 24196 } 24197 24198 return this.tech_; 24199 } 24200 /** 24201 * Set up click and touch listeners for the playback element 24202 * 24203 * - On desktops: a click on the video itself will toggle playback 24204 * - On mobile devices: a click on the video toggles controls 24205 * which is done by toggling the user state between active and 24206 * inactive 24207 * - A tap can signal that a user has become active or has become inactive 24208 * e.g. a quick tap on an iPhone movie should reveal the controls. Another 24209 * quick tap should hide them again (signaling the user is in an inactive 24210 * viewing state) 24211 * - In addition to this, we still want the user to be considered inactive after 24212 * a few seconds of inactivity. 24213 * 24214 * > Note: the only part of iOS interaction we can't mimic with this setup 24215 * is a touch and hold on the video element counting as activity in order to 24216 * keep the controls showing, but that shouldn't be an issue. A touch and hold 24217 * on any controls will still keep the user active 24218 * 24219 * @private 24220 */ 24221 ; 24222 24223 _proto.addTechControlsListeners_ = function addTechControlsListeners_() { 24224 // Make sure to remove all the previous listeners in case we are called multiple times. 24225 this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do 24226 // trigger mousedown/up. 24227 // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object 24228 // Any touch events are set to block the mousedown event from happening 24229 24230 this.on(this.tech_, 'mouseup', this.handleTechClick_); 24231 this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event 24232 // so we'll check if the controls were already showing before reporting user 24233 // activity 24234 24235 this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); 24236 this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); 24237 this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap 24238 // listener cancels out any reportedUserActivity when setting userActive(false) 24239 24240 this.on(this.tech_, 'tap', this.handleTechTap_); 24241 } 24242 /** 24243 * Remove the listeners used for click and tap controls. This is needed for 24244 * toggling to controls disabled, where a tap/touch should do nothing. 24245 * 24246 * @private 24247 */ 24248 ; 24249 24250 _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() { 24251 // We don't want to just use `this.off()` because there might be other needed 24252 // listeners added by techs that extend this. 24253 this.off(this.tech_, 'tap', this.handleTechTap_); 24254 this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); 24255 this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); 24256 this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); 24257 this.off(this.tech_, 'mouseup', this.handleTechClick_); 24258 this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_); 24259 } 24260 /** 24261 * Player waits for the tech to be ready 24262 * 24263 * @private 24264 */ 24265 ; 24266 24267 _proto.handleTechReady_ = function handleTechReady_() { 24268 this.triggerReady(); // Keep the same volume as before 24269 24270 if (this.cache_.volume) { 24271 this.techCall_('setVolume', this.cache_.volume); 24272 } // Look if the tech found a higher resolution poster while loading 24273 24274 24275 this.handleTechPosterChange_(); // Update the duration if available 24276 24277 this.handleTechDurationChange_(); 24278 } 24279 /** 24280 * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This 24281 * function will also trigger {@link Player#firstplay} if it is the first loadstart 24282 * for a video. 24283 * 24284 * @fires Player#loadstart 24285 * @fires Player#firstplay 24286 * @listens Tech#loadstart 24287 * @private 24288 */ 24289 ; 24290 24291 _proto.handleTechLoadStart_ = function handleTechLoadStart_() { 24292 // TODO: Update to use `emptied` event instead. See #1277. 24293 this.removeClass('vjs-ended'); 24294 this.removeClass('vjs-seeking'); // reset the error state 24295 24296 this.error(null); // Update the duration 24297 24298 this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now. 24299 // The firstplay event relies on both the play and loadstart events 24300 // which can happen in any order for a new source 24301 24302 if (!this.paused()) { 24303 /** 24304 * Fired when the user agent begins looking for media data 24305 * 24306 * @event Player#loadstart 24307 * @type {EventTarget~Event} 24308 */ 24309 this.trigger('loadstart'); 24310 this.trigger('firstplay'); 24311 } else { 24312 // reset the hasStarted state 24313 this.hasStarted(false); 24314 this.trigger('loadstart'); 24315 } // autoplay happens after loadstart for the browser,
24316 // so we mimic that behavior 24317 24318 24319 this.manualAutoplay_(this.autoplay()); 24320 } 24321 /** 24322 * Handle autoplay string values, rather than the typical boolean 24323 * values that should be handled by the tech. Note that this is not 24324 * part of any specification. Valid values and what they do can be 24325 * found on the autoplay getter at Player#autoplay() 24326 */ 24327 ; 24328 24329 _proto.manualAutoplay_ = function manualAutoplay_(type) { 24330 var _this5 = this; 24331 24332 if (!this.tech_ || typeof type !== 'string') { 24333 return; 24334 } 24335 24336 var muted = function muted() { 24337 var previouslyMuted = _this5.muted(); 24338 24339 _this5.muted(true); 24340 24341 var restoreMuted = function restoreMuted() { 24342 _this5.muted(previouslyMuted); 24343 }; // restore muted on play terminatation 24344 24345 24346 _this5.playTerminatedQueue_.push(restoreMuted); 24347 24348 var mutedPromise = _this5.play(); 24349 24350 if (!isPromise(mutedPromise)) { 24351 return; 24352 } 24353 24354 return mutedPromise["catch"](restoreMuted); 24355 }; 24356 24357 var promise; // if muted defaults to true 24358 // the only thing we can do is call play 24359 24360 if (type === 'any' && this.muted() !== true) { 24361 promise = this.play(); 24362 24363 if (isPromise(promise)) { 24364 promise = promise["catch"](muted); 24365 } 24366 } else if (type === 'muted' && this.muted() !== true) { 24367 promise = muted(); 24368 } else { 24369 promise = this.play(); 24370 } 24371 24372 if (!isPromise(promise)) { 24373 return; 24374 } 24375 24376 return promise.then(function () { 24377 _this5.trigger({ 24378 type: 'autoplay-success', 24379 autoplay: type 24380 }); 24381 })["catch"](function (e) { 24382 _this5.trigger({ 24383 type: 'autoplay-failure', 24384 autoplay: type 24385 }); 24386 }); 24387 } 24388 /** 24389 * Update the internal source caches so that we return the correct source from 24390 * `src()`, `currentSource()`, and `currentSources()`. 24391 * 24392 * > Note: `currentSources` will not be updated if the source that is passed in exists 24393 * in the current `currentSources` cache. 24394 * 24395 * 24396 * @param {Tech~SourceObject} srcObj 24397 * A string or object source to update our caches to. 24398 */ 24399 ; 24400 24401 _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) { 24402 if (srcObj === void 0) { 24403 srcObj = ''; 24404 } 24405 24406 var src = srcObj; 24407 var type = ''; 24408 24409 if (typeof src !== 'string') { 24410 src = srcObj.src; 24411 type = srcObj.type; 24412 } // make sure all the caches are set to default values 24413 // to prevent null checking 24414 24415 24416 this.cache_.source = this.cache_.source || {}; 24417 this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in 24418 24419 if (src && !type) { 24420 type = findMimetype(this, src); 24421 } // update `currentSource` cache always 24422 24423 24424 this.cache_.source = mergeOptions({}, srcObj, { 24425 src: src, 24426 type: type 24427 }); 24428 var matchingSources = this.cache_.sources.filter(function (s) { 24429 return s.src && s.src === src; 24430 }); 24431 var sourceElSources = []; 24432 var sourceEls = this.$$('source'); 24433 var matchingSourceEls = []; 24434 24435 for (var i = 0; i < sourceEls.length; i++) { 24436 var sourceObj = getAttributes(sourceEls[i]); 24437 sourceElSources.push(sourceObj); 24438 24439 if (sourceObj.src && sourceObj.src === src) { 24440 matchingSourceEls.push(sourceObj.src); 24441 } 24442 } // if we have matching source els but not matching sources 24443 // the current source cache is not up to date 24444 24445 24446 if (matchingSourceEls.length && !matchingSources.length) { 24447 this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the 24448 // sources cache to the `currentSource` cache 24449 } else if (!matchingSources.length) { 24450 this.cache_.sources = [this.cache_.source]; 24451 } // update the tech `src` cache 24452 24453 24454 this.cache_.src = src; 24455 } 24456 /**
24457 * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech} 24458 * causing the media element to reload. 24459 * 24460 * It will fire for the initial source and each subsequent source. 24461 * This event is a custom event from Video.js and is triggered by the {@link Tech}. 24462 * 24463 * The event object for this event contains a `src` property that will contain the source 24464 * that was available when the event was triggered. This is generally only necessary if Video.js 24465 * is switching techs while the source was being changed. 24466 * 24467 * It is also fired when `load` is called on the player (or media element) 24468 * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`} 24469 * says that the resource selection algorithm needs to be aborted and restarted. 24470 * In this case, it is very likely that the `src` property will be set to the 24471 * empty string `""` to indicate we do not know what the source will be but 24472 * that it is changing. 24473 * 24474 * *This event is currently still experimental and may change in minor releases.* 24475 * __To use this, pass `enableSourceset` option to the player.__ 24476 * 24477 * @event Player#sourceset 24478 * @type {EventTarget~Event} 24479 * @prop {string} src 24480 * The source url available when the `sourceset` was triggered. 24481 * It will be an empty string if we cannot know what the source is 24482 * but know that the source will change. 24483 */ 24484 24485 /** 24486 * Retrigger the `sourceset` event that was triggered by the {@link Tech}. 24487 * 24488 * @fires Player#sourceset 24489 * @listens Tech#sourceset 24490 * @private 24491 */ 24492 ; 24493 24494 _proto.handleTechSourceset_ = function handleTechSourceset_(event) { 24495 var _this6 = this; 24496 24497 // only update the source cache when the source 24498 // was not updated using the player api 24499 if (!this.changingSrc_) { 24500 var updateSourceCaches = function updateSourceCaches(src) { 24501 return _this6.updateSourceCaches_(src); 24502 }; 24503 24504 var playerSrc = this.currentSource().src; 24505 var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob 24506 24507 if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) { 24508 // if both the tech source and the player source were updated we assume 24509 // something like @videojs/http-streaming did the sourceset and skip updating the source cache. 24510 if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) { 24511 updateSourceCaches = function updateSourceCaches() {}; 24512 } 24513 } // update the source to the intial source right away 24514 // in some cases this will be empty string 24515 24516 24517 updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string 24518 // wait for a `loadstart` to update the cache to `currentSrc`. 24519 // If a sourceset happens before a `loadstart`, we reset the state 24520 24521 if (!event.src) { 24522 this.tech_.any(['sourceset', 'loadstart'], function (e) { 24523 // if a sourceset happens before a `loadstart` there 24524 // is nothing to do as this `handleTechSourceset_` 24525 // will be called again and this will be handled there. 24526 if (e.type === 'sourceset') { 24527 return; 24528 } 24529 24530 var techSrc = _this6.techGet('currentSrc'); 24531 24532 _this6.lastSource_.tech = techSrc; 24533 24534 _this6.updateSourceCaches_(techSrc); 24535 }); 24536 } 24537 } 24538 24539 this.lastSource_ = { 24540 player: this.currentSource().src, 24541 tech: event.src 24542 }; 24543 this.trigger({ 24544 src: event.src, 24545 type: 'sourceset' 24546 }); 24547 } 24548 /** 24549 * Add/remove the vjs-has-started class 24550 * 24551 * @fires Player#firstplay 24552 * 24553 * @param {boolean} request 24554 * - true: adds the class 24555 * - false: remove the class 24556 * 24557 * @return {boolean} 24558 * the boolean value of hasStarted_ 24559 */ 24560 ; 24561 24562 _proto.hasStarted = function hasStarted(request) { 24563 if (request === undefined) { 24564 // act as getter, if we have no request to change 24565 return this.hasStarted_; 24566 } 24567 24568 if (request === this.hasStarted_) { 24569 return; 24570 } 24571 24572 this.hasStarted_ = request; 24573 24574 if (this.hasStarted_) { 24575 this.addClass('vjs-has-started'); 24576 this.trigger('firstplay'); 24577 } else { 24578 this.removeClass('vjs-has-started'); 24579 } 24580 } 24581 /** 24582 * Fired whenever the media begins or resumes playback 24583 * 24584 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} 24585 * @fires Player#play 24586 * @listens Tech#play 24587 * @private 24588 */ 24589 ; 24590 24591 _proto.handleTechPlay_ = function handleTechPlay_() { 24592 this.removeClass('vjs-ended'); 24593 this.removeClass('vjs-paused'); 24594 this.addClass('vjs-playing'); // hide the poster when the user hits play 24595 24596 this.hasStarted(true); 24597 /** 24598 * Triggered whenever an {@link Tech#play} event happens. Indicates that 24599 * playback has started or resumed. 24600 * 24601 * @event Player#play 24602 * @type {EventTarget~Event} 24603 */ 24604 24605 this.trigger('play'); 24606 } 24607 /** 24608 * Retrigger the `ratechange` event that was triggered by the {@link Tech}. 24609 * 24610 * If there were any events queued while the playback rate was zero, fire 24611 * those events now. 24612 * 24613 * @private 24614 * @method Player#handleTechRateChange_ 24615 * @fires Player#ratechange 24616 * @listens Tech#ratechange 24617 */ 24618 ; 24619 24620 _proto.handleTechRateChange_ = function handleTechRateChange_() { 24621 if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) { 24622 this.queuedCallbacks_.forEach(function (queued) { 24623 return queued.callback(queued.event); 24624 }); 24625 this.queuedCallbacks_ = []; 24626 } 24627 24628 this.cache_.lastPlaybackRate = this.tech_.playbackRate(); 24629 /** 24630 * Fires when the playing speed of the audio/video is changed 24631 * 24632 * @event Player#ratechange 24633 * @type {event} 24634 */ 24635 24636 this.trigger('ratechange'); 24637 } 24638 /** 24639 * Retrigger the `waiting` event that was triggered by the {@link Tech}. 24640 * 24641 * @fires Player#waiting 24642 * @listens Tech#waiting 24643 * @private 24644 */ 24645 ; 24646 24647 _proto.handleTechWaiting_ = function handleTechWaiting_() { 24648 var _this7 = this; 24649 24650 this.addClass('vjs-waiting'); 24651 /** 24652 * A readyState change on the DOM element has caused playback to stop. 24653 * 24654 * @event Player#waiting 24655 * @type {EventTarget~Event} 24656 */ 24657 24658 this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent 24659 // premature removal of the waiting class, wait for the time to change. 24660 24661 var timeWhenWaiting = this.currentTime(); 24662 24663 var timeUpdateListener = function timeUpdateListener() { 24664 if (timeWhenWaiting !== _this7.currentTime()) { 24665 _this7.removeClass('vjs-waiting'); 24666 24667 _this7.off('timeupdate', timeUpdateListener); 24668 } 24669 }; 24670 24671 this.on('timeupdate', timeUpdateListener); 24672 } 24673 /** 24674 * Retrigger the `canplay` event that was triggered by the {@link Tech}. 24675 * > Note: This is not consistent between browsers. See #1351 24676 * 24677 * @fires Player#canplay 24678 * @listens Tech#canplay 24679 * @private 24680 */ 24681 ; 24682 24683 _proto.handleTechCanPlay_ = function handleTechCanPlay_() { 24684 this.removeClass('vjs-waiting'); 24685 /** 24686 * The media has a readyState of HAVE_FUTURE_DATA or greater. 24687 * 24688 * @event Player#canplay 24689 * @type {EventTarget~Event} 24690 */ 24691 24692 this.trigger('canplay'); 24693 } 24694 /** 24695 * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. 24696 * 24697 * @fires Player#canplaythrough 24698 * @listens Tech#canplaythrough 24699 * @private 24700 */ 24701 ; 24702 24703 _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { 24704 this.removeClass('vjs-waiting'); 24705 /** 24706 * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the 24707 * entire media file can be played without buffering. 24708 * 24709 * @event Player#canplaythrough 24710 * @type {EventTarget~Event} 24711 */ 24712 24713 this.trigger('canplaythrough'); 24714 } 24715 /** 24716 * Retrigger the `playing` event that was triggered by the {@link Tech}. 24717 * 24718 * @fires Player#playing 24719 * @listens Tech#playing 24720 * @private 24721 */ 24722 ; 24723 24724 _proto.handleTechPlaying_ = function handleTechPlaying_() { 24725 this.removeClass('vjs-waiting'); 24726 /** 24727 * The media is no longer blocked from playback, and has started playing. 24728 * 24729 * @event Player#playing 24730 * @type {EventTarget~Event} 24731 */ 24732 24733 this.trigger('playing'); 24734 } 24735 /** 24736 * Retrigger the `seeking` event that was triggered by the {@link Tech}. 24737 * 24738 * @fires Player#seeking 24739 * @listens Tech#seeking 24740 * @private 24741 */ 24742 ; 24743 24744 _proto.handleTechSeeking_ = function handleTechSeeking_() { 24745 this.addClass('vjs-seeking'); 24746 /** 24747 * Fired whenever the player is jumping to a new time 24748 * 24749 * @event Player#seeking 24750 * @type {EventTarget~Event} 24751 */ 24752 24753 this.trigger('seeking'); 24754 } 24755 /** 24756 * Retrigger the `seeked` event that was triggered by the {@link Tech}. 24757 * 24758 * @fires Player#seeked 24759 * @listens Tech#seeked 24760 * @private 24761 */ 24762 ; 24763 24764 _proto.handleTechSeeked_ = function handleTechSeeked_() { 24765 this.removeClass('vjs-seeking'); 24766 this.removeClass('vjs-ended'); 24767 /** 24768 * Fired when the player has finished jumping to a new time 24769 * 24770 * @event Player#seeked 24771 * @type {EventTarget~Event} 24772 */ 24773 24774 this.trigger('seeked'); 24775 } 24776 /** 24777 * Retrigger the `firstplay` event that was triggered by the {@link Tech}. 24778 * 24779 * @fires Player#firstplay 24780 * @listens Tech#firstplay 24781 * @deprecated As of 6.0 firstplay event is deprecated. 24782 * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. 24783 * @private 24784 */ 24785 ; 24786 24787 _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() { 24788 // If the first starttime attribute is specified 24789 // then we will start at the given offset in seconds 24790 if (this.options_.starttime) { 24791 log.warn('Passing the `starttime` option to the player will be deprecated
24791in 6.0'); 24792 this.currentTime(this.options_.starttime); 24793 } 24794 24795 this.addClass('vjs-has-started'); 24796 /** 24797 * Fired the first time a video is played. Not part of the HLS spec, and this is 24798 * probably not the best implementation yet, so use sparingly. If you don't have a 24799 * reason to prevent playback, use `myPlayer.one('play');` instead. 24800 * 24801 * @event Player#firstplay 24802 * @deprecated As of 6.0 firstplay event is deprecated. 24803 * @type {EventTarget~Event} 24804 */ 24805 24806 this.trigger('firstplay'); 24807 } 24808 /** 24809 * Retrigger the `pause` event that was triggered by the {@link Tech}. 24810 * 24811 * @fires Player#pause 24812 * @listens Tech#pause 24813 * @private 24814 */ 24815 ; 24816 24817 _proto.handleTechPause_ = function handleTechPause_() { 24818 this.removeClass('vjs-playing'); 24819 this.addClass('vjs-paused'); 24820 /** 24821 * Fired whenever the media has been paused 24822 * 24823 * @event Player#pause 24824 * @type {EventTarget~Event} 24825 */ 24826 24827 this.trigger('pause'); 24828 } 24829 /** 24830 * Retrigger the `ended` event that was triggered by the {@link Tech}. 24831 * 24832 * @fires Player#ended 24833 * @listens Tech#ended 24834 * @private 24835 */ 24836 ; 24837 24838 _proto.handleTechEnded_ = function handleTechEnded_() { 24839 this.addClass('vjs-ended'); 24840 24841 if (this.options_.loop) { 24842 this.currentTime(0); 24843 this.play(); 24844 } else if (!this.paused()) { 24845 this.pause(); 24846 } 24847 /** 24848 * Fired when the end of the media resource is reached (currentTime == duration) 24849 * 24850 * @event Player#ended 24851 * @type {EventTarget~Event} 24852 */ 24853 24854 24855 this.trigger('ended'); 24856 } 24857 /** 24858 * Fired when the duration of the media resource is first known or changed 24859 * 24860 * @listens Tech#durationchange 24861 * @private 24862 */ 24863 ; 24864 24865 _proto.handleTechDurationChange_ = function handleTechDurationChange_() { 24866 this.duration(this.techGet_('duration')); 24867 } 24868 /** 24869 * Handle a click on the media element to play/pause 24870 * 24871 * @param {EventTarget~Event} event 24872 * the event that caused this function to trigger 24873 * 24874 * @listens Tech#mouseup 24875 * @private 24876 */ 24877 ; 24878 24879 _proto.handleTechClick_ = function handleTechClick_(event) { 24880 if (!isSingleLeftClick(event)) { 24881 return; 24882 } // When controls are disabled a click should not toggle playback because 24883 // the click is considered a control 24884 24885 24886 if (!this.controls_) { 24887 return; 24888 } 24889 24890 if (this.paused()) { 24891 silencePromise(this.play()); 24892 } else { 24893 this.pause(); 24894 } 24895 } 24896 /** 24897 * Handle a double-click on the media element to enter/exit fullscreen 24898 * 24899 * @param {EventTarget~Event} event 24900 * the event that caused this function to trigger 24901 * 24902 * @listens Tech#dblclick 24903 * @private 24904 */ 24905 ; 24906 24907 _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) { 24908 if (!this.controls_) { 24909 return; 24910 } // we do not want to toggle fullscreen state 24911 // when double-clicking inside a control bar or a modal 24912 24913 24914 var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) { 24915 return el.contains(event.target); 24916 }); 24917 24918 if (!inAllowedEls) { 24919 /* 24920 * options.userActions.doubleClick 24921 * 24922 * If `undefined` or `true`, double-click toggles fullscreen if controls are present 24923 * Set to `false` to disable double-click handling 24924 * Set to a function to substitute an external double-click handler 24925 */ 24926 if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) { 24927 if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') { 24928 this.options_.userActions.doubleClick.call(this, event); 24929 } else if (this.isFullscreen()) { 24930 this.exitFullscreen(); 24931 } else { 24932 this.requestFullscreen(); 24933 } 24934 } 24935 } 24936 } 24937 /** 24938 * Handle a tap on the media element. It will toggle the user 24939 * activity state, which hides and shows the controls. 24940 * 24941 * @listens Tech#tap 24942 * @private 24943 */ 24944 ; 24945 24946 _proto.handleTechTap_ = function handleTechTap_() { 24947 this.userActive(!this.userActive()); 24948 } 24949 /** 24950 * Handle touch to start 24951 * 24952 * @listens Tech#touchstart 24953 * @private 24954 */ 24955 ; 24956 24957 _proto.handleTechTouchStart_ = function handleTechTouchStart_() { 24958 this.userWasActive = this.userActive(); 24959 } 24960 /** 24961 * Handle touch to move 24962 * 24963 * @listens Tech#touchmove 24964 * @private 24965 */ 24966 ; 24967 24968 _proto.handleTechTouchMove_ = function handleTechTouchMove_() { 24969 if (this.userWasActive) { 24970 this.reportUserActivity(); 24971 } 24972 } 24973 /** 24974 * Handle touch to end 24975 * 24976 * @param {EventTarget~Event} event 24977 * the touchend event that triggered 24978 * this function 24979 * 24980 * @listens Tech#touchend 24981 * @private 24982 */ 24983 ; 24984 24985 _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { 24986 // Stop the mouse events from also happening 24987 event.preventDefault(); 24988 } 24989 /** 24990 * native click events on the SWF aren't triggered on IE11, Win8.1RT 24991 * use stageclick events triggered from inside the SWF instead 24992 * 24993 * @private 24994 * @listens stageclick 24995 */ 24996 ; 24997 24998 _proto.handleStageClick_ = function handleStageClick_() { 24999 this.reportUserActivity(); 25000 } 25001 /** 25002 * @private 25003 */ 25004 ; 25005 25006 _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() { 25007 if (this.isFullscreen()) { 25008 this.addClass('vjs-fullscreen'); 25009 } else { 25010 this.removeClass('vjs-fullscreen'); 25011 } 25012 } 25013 /** 25014 * when the document fschange event triggers it calls this 25015 */ 25016 ; 25017 25018 _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) { 25019 var targetPlayer = e.target.player; // if another player was fullscreen 25020 // do a null check for targetPlayer because older firefox's would put document as e.target 25021 25022 if (targetPlayer && targetPlayer !== this) { 25023 return; 25024 } 25025 25026 var el = this.el(); 25027 var isFs = document[this.fsApi_.fullscreenElement] === el; 25028 25029 if (!isFs && el.matches) { 25030 isFs = el.matches(':' + this.fsApi_.fullscreen); 25031 } else if (!isFs && el.msMatchesSelector) { 25032 isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen); 25033 } 25034 25035 this.isFullscreen(isFs); 25036 } 25037 /** 25038 * Handle Tech Fullscreen Change 25039 * 25040 * @param {EventTarget~Event} event 25041 * the fullscreenchange event that triggered this function 25042 * 25043 * @param {Object} data 25044 * the data that was sent with the event 25045 * 25046 * @private 25047 * @listens Tech#fullscreenchange 25048 * @fires Player#fullscreenchange 25049 */ 25050 ; 25051 25052 _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { 25053 if (data) { 25054 this.isFullscreen(data.isFullscreen); 25055 } 25056 } 25057 /** 25058 * @private 25059 */ 25060 ; 25061 25062 _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() { 25063 if (this.isInPictureInPicture()) { 25064 this.addClass('vjs-picture-in-picture'); 25065 } else { 25066 this.removeClass('vjs-picture-in-picture'); 25067 } 25068 } 25069 /** 25070 * Handle Tech Enter Picture-in-Picture. 25071 * 25072 * @param {EventTarget~Event} event 25073 * the enterpictureinpicture event that triggered this function 25074 * 25075 * @private 25076 * @listens Tech#enterpictureinpicture 25077 */ 25078 ; 25079 25080 _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) { 25081 this.isInPictureInPicture(true); 25082 } 25083 /** 25084 * Handle Tech Leave Picture-in-Picture. 25085 * 25086 * @param {EventTarget~Event} event 25087 * the leavepictureinpicture event that triggered this function 25088 * 25089 * @private 25090 * @listens Tech#leavepictureinpicture 25091 */ 25092 ; 25093 25094 _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) { 25095 this.isInPictureInPicture(false); 25096 } 25097 /** 25098 * Fires when an error occurred during the loading of an audio/video. 25099 * 25100 * @private 25101 * @listens Tech#error 25102 */ 25103 ; 25104 25105 _proto.handleTechError_ = function handleTechError_() { 25106 var error = this.tech_.error(); 25107 this.error(error); 25108 } 25109 /** 25110 * Retrigger the `textdata` event that was triggered by the {@link Tech}. 25111 * 25112 * @fires Player#textdata 25113 * @listens Tech#textdata 25114 * @private 25115 */ 25116 ; 25117 25118 _proto.handleTechTextData_ = function handleTechTextData_() { 25119 var data = null; 25120 25121 if (arguments.length > 1) { 25122 data = arguments[1]; 25123 } 25124 /** 25125 * Fires when we get a textdata event from tech 25126 * 25127 * @event Player#textdata 25128 * @type {EventTarget~Event} 25129 */ 25130 25131 25132 this.trigger('textdata', data); 25133 } 25134 /** 25135 * Get object for cached values. 25136 * 25137 * @return {Object} 25138 * get the current object cache 25139 */ 25140 ; 25141 25142 _proto.getCache = function getCache() { 25143 return this.cache_; 25144 } 25145 /**
25146 * Resets the internal cache object. 25147 * 25148 * Using this function outside the player constructor or reset method may 25149 * have unintended side-effects. 25150 * 25151 * @private 25152 */ 25153 ; 25154 25155 _proto.resetCache_ = function resetCache_() { 25156 this.cache_ = { 25157 // Right now, the currentTime is not _really_ cached because it is always 25158 // retrieved from the tech (see: currentTime). However, for completeness, 25159 // we set it to zero here to ensure that if we do start actually caching 25160 // it, we reset it along with everything else. 25161 currentTime: 0, 25162 inactivityTimeout: this.options_.inactivityTimeout, 25163 duration: NaN, 25164 lastVolume: 1, 25165 lastPlaybackRate: this.defaultPlaybackRate(), 25166 media: null, 25167 src: '', 25168 source: {}, 25169 sources: [], 25170 volume: 1 25171 }; 25172 } 25173 /** 25174 * Pass values to the playback tech 25175 * 25176 * @param {string} [method] 25177 * the method to call 25178 * 25179 * @param {Object} arg 25180 * the argument to pass 25181 * 25182 * @private 25183 */ 25184 ; 25185 25186 _proto.techCall_ = function techCall_(method, arg) { 25187 // If it's not ready yet, call method when it is 25188 this.ready(function () { 25189 if (method in allowedSetters) { 25190 return set(this.middleware_, this.tech_, method, arg); 25191 } else if (method in allowedMediators) { 25192 return mediate(this.middleware_, this.tech_, method, arg); 25193 } 25194 25195 try { 25196 if (this.tech_) { 25197 this.tech_[method](arg); 25198 } 25199 } catch (e) { 25200 log(e); 25201 throw e; 25202 } 25203 }, true); 25204 } 25205 /** 25206 * Get calls can't wait for the tech, and sometimes don't need to. 25207 * 25208 * @param {string} method 25209 * Tech method 25210 * 25211 * @return {Function|undefined} 25212 * the method or undefined 25213 * 25214 * @private 25215 */ 25216 ; 25217 25218 _proto.techGet_ = function techGet_(method) { 25219 if (!this.tech_ || !this.tech_.isReady_) { 25220 return; 25221 } 25222 25223 if (method in allowedGetters) { 25224 return get(this.middleware_, this.tech_, method); 25225 } else if (method in allowedMediators) { 25226 return mediate(this.middleware_, this.tech_, method); 25227 } // Flash likes to die and reload when you hide or reposition it. 25228 // In these cases the object methods go away and we get errors. 25229 // When that happens we'll catch the errors and inform tech that it's not ready any more. 25230 25231 25232 try { 25233 return this.tech_[method](); 25234 } catch (e) { 25235 // When building additional tech libs, an expected method may not be defined yet 25236 if (this.tech_[method] === undefined) { 25237 log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e); 25238 throw e; 25239 } // When a method isn't available on the object it throws a TypeError 25240 25241 25242 if (e.name === 'TypeError') { 25243 log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e); 25244 this.tech_.isReady_ = false; 25245 throw e; 25246 } // If error unknown, just log and throw 25247 25248 25249 log(e); 25250 throw e; 25251 } 25252 } 25253 /** 25254 * Attempt to begin playback at the first opportunity. 25255 * 25256 * @return {Promise|undefined} 25257 * Returns a promise if the browser supports Promises (or one 25258 * was passed in as an option). This promise will be resolved on 25259 * the return value of play. If this is undefined it will fulfill the 25260 * promise chain otherwise the promise chain will be fulfilled when 25261 * the promise from play is fulfilled. 25262 */ 25263 ; 25264 25265 _proto.play = function play() { 25266 var _this8 = this; 25267 25268 var PromiseClass = this.options_.Promise || window$1.Promise; 25269 25270 if (PromiseClass) { 25271 return new PromiseClass(function (resolve) { 25272 _this8.play_(resolve); 25273 }); 25274 } 25275 25276 return this.play_(); 25277 } 25278 /** 25279 * The actual logic for play, takes a callback that will be resolved on the 25280 * return value of play. This allows us to resolve to the play promise if there 25281 * is one on modern browsers. 25282 * 25283 * @private 25284 * @param {Function} [callback] 25285 * The callback that should be called when the techs play is actually called 25286 */ 25287 ; 25288 25289 _proto.play_ = function play_(callback) { 25290 var _this9 = this; 25291 25292 if (callback === void 0) { 25293 callback = silencePromise; 25294 } 25295 25296 this.playCallbacks_.push(callback);
25297 var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function 25298 25299 if (this.waitToPlay_) { 25300 this.off(['ready', 'loadstart'], this.waitToPlay_); 25301 this.waitToPlay_ = null; 25302 } // if the player/tech is not ready or the src itself is not ready 25303 // queue up a call to play on `ready` or `loadstart` 25304 25305 25306 if (!this.isReady_ || !isSrcReady) { 25307 this.waitToPlay_ = function (e) { 25308 _this9.play_(); 25309 }; 25310 25311 this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod 25312 // in that case, we need to prime the video element by calling load so it'll be ready in time 25313 25314 if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) { 25315 this.load(); 25316 } 25317 25318 return; 25319 } // If the player/tech is ready and we have a source, we can attempt playback. 25320 25321 25322 var val = this.techGet_('play'); // play was terminated if the returned value is null 25323 25324 if (val === null) { 25325 this.runPlayTerminatedQueue_(); 25326 } else { 25327 this.runPlayCallbacks_(val); 25328 } 25329 } 25330 /** 25331 * These functions will be run when if play is terminated. If play 25332 * runPlayCallbacks_ is run these function will not be run. This allows us 25333 * to differenciate between a terminated play and an actual call to play. 25334 */ 25335 ; 25336 25337 _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() { 25338 var queue = this.playTerminatedQueue_.slice(0); 25339 this.playTerminatedQueue_ = []; 25340 queue.forEach(function (q) { 25341 q(); 25342 }); 25343 } 25344 /** 25345 * When a callback to play is delayed we have to run these 25346 * callbacks when play is actually called on the tech. This function 25347 * runs the callbacks that were delayed and accepts the return value 25348 * from the tech. 25349 * 25350 * @param {undefined|Promise} val 25351 * The return value from the tech. 25352 */ 25353 ; 25354 25355 _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) { 25356 var callbacks = this.playCallbacks_.slice(0); 25357 this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play 25358 25359 this.playTerminatedQueue_ = []; 25360 callbacks.forEach(function (cb) { 25361 cb(val); 25362 }); 25363 } 25364 /** 25365 * Pause the video playback 25366 * 25367 * @return {Player} 25368 * A reference to the player object this function was called on 25369 */ 25370 ; 25371 25372 _proto.pause = function pause() { 25373 this.techCall_('pause'); 25374 } 25375 /** 25376 * Check if the player is paused or has yet to play 25377 * 25378 * @return {boolean} 25379 * - false: if the media is currently playing 25380 * - true: if media is not currently playing 25381 */ 25382 ; 25383 25384 _proto.paused = function paused() { 25385 // The initial state of paused should be true (in Safari it's actually false) 25386 return this.techGet_('paused') === false ? false : true; 25387 } 25388 /** 25389 * Get a TimeRange object representing the current ranges of time that the user 25390 * has played. 25391 * 25392 * @return {TimeRange} 25393 * A time range object that represents all the increments of time that have 25394 * been played. 25395 */ 25396 ; 25397 25398 _proto.played = function played() { 25399 return this.techGet_('played') || createTimeRanges(0, 0); 25400 } 25401 /** 25402 * Returns whether or not the user is "scrubbing". Scrubbing is 25403 * when the user has clicked the progress bar handle and is 25404 * dragging it along the progress bar. 25405 * 25406 * @param {boolean} [isScrubbing] 25407 * whether the user is or is not scrubbing 25408 * 25409 * @return {boolean} 25410 * The value of scrubbing when getting 25411 */ 25412 ; 25413 25414 _proto.scrubbing = function scrubbing(isScrubbing) { 25415 if (typeof isScrubbing === 'undefined') { 25416 return this.scrubbing_; 25417 } 25418 25419 this.scrubbing_ = !!isScrubbing; 25420 25421 if (isScrubbing) { 25422 this.addClass('vjs-scrubbing'); 25423 } else { 25424 this.removeClass('vjs-scrubbing'); 25425 } 25426 } 25427 /** 25428 * Get or set the current time (in seconds) 25429 * 25430 * @param {number|string} [seconds] 25431 * The time to seek to in seconds 25432 * 25433 * @return {number} 25434 * - the current time in seconds when getting 25435 */ 25436 ; 25437 25438 _proto.currentTime = function currentTime(seconds) { 25439 if (typeof seconds !== 'undefined') { 25440 if (seconds < 0) { 25441 seconds = 0; 25442 } 25443 25444 this.techCall_('setCurrentTime', seconds); 25445 return; 25446 } // cache last currentTime and return. default to 0 seconds 25447 // 25448 // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
vendor: 5,572 bytes, lines 25449-25625
25449 // currentTime when scrubbing, but may not provide much performance benefit afterall. 25450 // Should be tested. Also something has to read the actual current time or the cache will 25451 // never get updated. 25452 25453 25454 this.cache_.currentTime = this.techGet_('currentTime') || 0; 25455 return this.cache_.currentTime; 25456 } 25457 /** 25458 * Normally gets the length in time of the video in seconds; 25459 * in all but the rarest use cases an argument will NOT be passed to the method 25460 * 25461 * > **NOTE**: The video must have started loading before the duration can be 25462 * known, and in the case of Flash, may not be known until the video starts 25463 * playing. 25464 * 25465 * @fires Player#durationchange 25466 * 25467 * @param {number} [seconds] 25468 * The duration of the video to set in seconds 25469 * 25470 * @return {number} 25471 * - The duration of the video in seconds when getting 25472 */ 25473 ; 25474 25475 _proto.duration = function duration(seconds) { 25476 if (seconds === undefined) { 25477 // return NaN if the duration is not known 25478 return this.cache_.duration !== undefined ? this.cache_.duration : NaN; 25479 } 25480 25481 seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live 25482 25483 if (seconds < 0) { 25484 seconds = Infinity; 25485 } 25486 25487 if (seconds !== this.cache_.duration) { 25488 // Cache the last set value for optimized scrubbing (esp. Flash) 25489 this.cache_.duration = seconds; 25490 25491 if (seconds === Infinity) { 25492 this.addClass('vjs-live'); 25493 25494 if (this.options_.liveui && this.player_.liveTracker) { 25495 this.addClass('vjs-liveui'); 25496 } 25497 } else { 25498 this.removeClass('vjs-live'); 25499 this.removeClass('vjs-liveui'); 25500 } 25501 25502 if (!isNaN(seconds)) { 25503 // Do not fire durationchange unless the duration value is known. 25504 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 25505 25506 /** 25507 * @event Player#durationchange 25508 * @type {EventTarget~Event} 25509 */ 25510 this.trigger('durationchange'); 25511 } 25512 } 25513 } 25514 /** 25515 * Calculates how much time is left in the video. Not part 25516 * of the native video API. 25517 * 25518 * @return {number} 25519 * The time remaining in seconds 25520 */ 25521 ; 25522 25523 _proto.remainingTime = function remainingTime() { 25524 return this.duration() - this.currentTime(); 25525 } 25526 /** 25527 * A remaining time function that is intented to be used when 25528 * the time is to be displayed directly to the user. 25529 * 25530 * @return {number} 25531 * The rounded time remaining in seconds 25532 */ 25533 ; 25534 25535 _proto.remainingTimeDisplay = function remainingTimeDisplay() { 25536 return Math.floor(this.duration()) - Math.floor(this.currentTime()); 25537 } // 25538 // Kind of like an array of portions of the video that have been downloaded. 25539 25540 /** 25541 * Get a TimeRange object with an array of the times of the video 25542 * that have been downloaded. If you just want the percent of the 25543 * video that's been downloaded, use bufferedPercent. 25544 * 25545 * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} 25546 * 25547 * @return {TimeRange} 25548 * A mock TimeRange object (following HTML spec) 25549 */ 25550 ; 25551 25552 _proto.buffered = function buffered() { 25553 var buffered = this.techGet_('buffered'); 25554 25555 if (!buffered || !buffered.length) { 25556 buffered = createTimeRanges(0, 0); 25557 } 25558 25559 return buffered; 25560 } 25561 /** 25562 * Get the percent (as a decimal) of the video that's been downloaded. 25563 * This method is not a part of the native HTML video API. 25564 * 25565 * @return {number} 25566 * A decimal between 0 and 1 representing the percent 25567 * that is buffered 0 being 0% and 1 being 100% 25568 */ 25569 ; 25570 25571 _proto.bufferedPercent = function bufferedPercent$1() { 25572 return bufferedPercent(this.buffered(), this.duration()); 25573 } 25574 /** 25575 * Get the ending time of the last buffered time range 25576 * This is used in the progress bar to encapsulate all time ranges. 25577 * 25578 * @return {number} 25579 * The end of the last buffered time range 25580 */ 25581 ; 25582 25583 _proto.bufferedEnd = function bufferedEnd() { 25584 var buffered = this.buffered(); 25585 var duration = this.duration(); 25586 var end = buffered.end(buffered.length - 1); 25587 25588 if (end > duration) { 25589 end = duration; 25590 } 25591 25592 return end; 25593 } 25594 /** 25595 * Get or set the current volume of the media 25596 * 25597 * @param {number} [percentAsDecimal] 25598 * The new volume as a decimal percent: 25599 * - 0 is muted/0%/off 25600 * - 1.0 is 100%/full 25601 * - 0.5 is half volume or 50% 25602 * 25603 * @return {number} 25604 * The current volume as a percent when getting 25605 */ 25606 ; 25607 25608 _proto.volume = function volume(percentAsDecimal) { 25609 var vol; 25610 25611 if (percentAsDecimal !== undefined) { 25612 // Force value to between 0 and 1 25613 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); 25614 this.cache_.volume = vol; 25615 this.techCall_('setVolume', vol); 25616 25617 if (vol > 0) { 25618 this.lastVolume_(vol); 25619 } 25620 25621 return; 25622 } // Default to 1 when returning current volume. 25623 25624 25625 vol = parseFloat(this.techGet_('volume'));
25626 return isNaN(vol) ? 1 : vol; 25627 } 25628 /** 25629 * Get the current muted state, or turn mute on or off 25630 * 25631 * @param {boolean} [muted] 25632 * - true to mute 25633 * - false to unmute 25634 * 25635 * @return {boolean} 25636 * - true if mute is on and getting 25637 * - false if mute is off and getting 25638 */ 25639 ; 25640 25641 _proto.muted = function muted(_muted) { 25642 if (_muted !== undefined) { 25643 this.techCall_('setMuted', _muted); 25644 return; 25645 } 25646 25647 return this.techGet_('muted') || false; 25648 } 25649 /** 25650 * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted 25651 * indicates the state of muted on initial playback. 25652 * 25653 * ```js 25654 * var myPlayer = videojs('some-player-id'); 25655 * 25656 * myPlayer.src("http://www.example.com/path/to/video.mp4"); 25657 * 25658 * // get, should be false 25659 * console.log(myPlayer.defaultMuted()); 25660 * // set to true 25661 * myPlayer.defaultMuted(true); 25662 * // get should be true 25663 * console.log(myPlayer.defaultMuted()); 25664 * ``` 25665 * 25666 * @param {boolean} [defaultMuted] 25667 * - true to mute 25668 * - false to unmute 25669 * 25670 * @return {boolean|Player} 25671 * - true if defaultMuted is on and getting 25672 * - false if defaultMuted is off and getting 25673 * - A reference to the current player when setting 25674 */ 25675 ; 25676 25677 _proto.defaultMuted = function defaultMuted(_defaultMuted) { 25678 if (_defaultMuted !== undefined) { 25679 return this.techCall_('setDefaultMuted', _defaultMuted); 25680 } 25681 25682 return this.techGet_('defaultMuted') || false; 25683 } 25684 /** 25685 * Get the last volume, or set it 25686 * 25687 * @param {number} [percentAsDecimal] 25688 * The new last volume as a decimal percent: 25689 * - 0 is muted/0%/off 25690 * - 1.0 is 100%/full 25691 * - 0.5 is half volume or 50% 25692 * 25693 * @return {number} 25694 * the current value of lastVolume as a percent when getting 25695 * 25696 * @private 25697 */ 25698 ; 25699 25700 _proto.lastVolume_ = function lastVolume_(percentAsDecimal) { 25701 if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { 25702 this.cache_.lastVolume = percentAsDecimal; 25703 return; 25704 } 25705 25706 return this.cache_.lastVolume; 25707 } 25708 /** 25709 * Check if current tech can support native fullscreen 25710 * (e.g. with built in controls like iOS, so not our flash swf) 25711 * 25712 * @return {boolean} 25713 * if native fullscreen is supported 25714 */ 25715 ; 25716 25717 _proto.supportsFullScreen = function supportsFullScreen() { 25718 return this.techGet_('supportsFullScreen') || false; 25719 } 25720 /** 25721 * Check if the player is in fullscreen mode or tell the player that it 25722 * is or is not in fullscreen mode. 25723 * 25724 * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official 25725 * property and instead document.fullscreenElement is used. But isFullscreen is 25726 * still a valuable property for internal player workings. 25727 * 25728 * @param {boolean} [isFS] 25729 * Set the players current fullscreen state 25730 * 25731 * @return {boolean} 25732 * - true if fullscreen is on and getting 25733 * - false if fullscreen is off and getting 25734 */ 25735 ; 25736 25737 _proto.isFullscreen = function isFullscreen(isFS) { 25738 if (isFS !== undefined) { 25739 var oldValue = this.isFullscreen_; 25740 this.isFullscreen_ = Boolean(isFS); // if we changed fullscreen state and we're in prefixed mode, trigger fullscreenchange 25741 // this is the only place where we trigger fullscreenchange events for older browsers 25742 // fullWindow mode is treated as a prefixed event and will get a fullscreenchange event as well 25743 25744 if (this.isFullscreen_ !== oldValue && this.fsApi_.prefixed) { 25745 /** 25746 * @event Player#fullscreenchange 25747 * @type {EventTarget~Event} 25748 */ 25749 this.trigger('fullscreenchange'); 25750 } 25751 25752 this.toggleFullscreenClass_(); 25753 return; 25754 } 25755 25756 return this.isFullscreen_; 25757 } 25758 /** 25759 * Increase the size of the video to full screen
25760 * In some browsers, full screen is not supported natively, so it enters 25761 * "full window mode", where the video fills the browser window. 25762 * In browsers and devices that support native full screen, sometimes the 25763 * browser's default controls will be shown, and not the Video.js custom skin. 25764 * This includes most mobile devices (iOS, Android) and older versions of 25765 * Safari. 25766 * 25767 * @param {Object} [fullscreenOptions] 25768 * Override the player fullscreen options 25769 * 25770 * @fires Player#fullscreenchange 25771 */ 25772 ; 25773 25774 _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) { 25775 var _this10 = this; 25776 25777 var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers. 25778 // Use defaults or player configured option unless passed directly to this method. 25779 25780 if (!this.fsApi_.prefixed) { 25781 fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {}; 25782 25783 if (fullscreenOptions !== undefined) { 25784 fsOptions = fullscreenOptions; 25785 } 25786 } // This method works as follows: 25787 // 1. if a fullscreen api is available, use it 25788 // 1. call requestFullscreen with potential options 25789 // 2. if we got a promise from above, use it to update isFullscreen() 25790 // 2. otherwise, if the tech supports fullscreen, call `enterFullScreen` on it. 25791 // This is particularly used for iPhone, older iPads, and non-safari browser on iOS. 25792 // 3. otherwise, use "fullWindow" mode 25793 25794 25795 if (this.fsApi_.requestFullscreen) { 25796 var promise = this.el_[this.fsApi_.requestFullscreen](fsOptions); 25797 25798 if (promise) { 25799 promise.then(function () { 25800 return _this10.isFullscreen(true); 25801 }, function () { 25802 return _this10.isFullscreen(false); 25803 }); 25804 } 25805 25806 return promise; 25807 } else if (this.tech_.supportsFullScreen()) { 25808 // we can't take the video.js controls fullscreen but we can go fullscreen 25809 // with native controls 25810 this.techCall_('enterFullScreen'); 25811 } else { 25812 // fullscreen isn't supported so we'll just stretch the video element to 25813 // fill the viewport 25814 this.enterFullWindow(); 25815 } 25816 } 25817 /** 25818 * Return the video to its normal size after having been in full screen mode 25819 * 25820 * @fires Player#fullscreenchange 25821 */ 25822 ; 25823 25824 _proto.exitFullscreen = function exitFullscreen() { 25825 var _this11 = this; 25826 25827 if (this.fsApi_.requestFullscreen) { 25828 var promise = document[this.fsApi_.exitFullscreen](); 25829 25830 if (promise) { 25831 promise.then(function () { 25832 return _this11.isFullscreen(false); 25833 }); 25834 } 25835 25836 return promise; 25837 } else if (this.tech_.supportsFullScreen()) { 25838 this.techCall_('exitFullScreen'); 25839 } else { 25840 this.exitFullWindow(); 25841 } 25842 } 25843 /** 25844 * When fullscreen isn't supported we can stretch the 25845 * video container to as wide as the browser will let us. 25846 * 25847 * @fires Player#enterFullWindow 25848 */ 25849 ; 25850 25851 _proto.enterFullWindow = function enterFullWindow() { 25852 this.isFullscreen(true); 25853 this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off 25854 25855 this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen 25856 25857 on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars 25858 25859 document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles 25860 25861 addClass(document.body, 'vjs-full-window'); 25862 /** 25863 * @event Player#enterFullWindow 25864 * @type {EventTarget~Event} 25865 */ 25866 25867 this.trigger('enterFullWindow'); 25868 } 25869 /** 25870 * Check for call to either exit full window or 25871 * full screen on ESC key 25872 * 25873 * @param {string} event 25874 * Event to check for key press 25875 */ 25876 ; 25877 25878 _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) { 25879 if (keycode.isEventKey(event, 'Esc')) { 25880 if (this.isFullscreen() === true) { 25881 this.exitFullscreen(); 25882 } else { 25883 this.exitFullWindow(); 25884 } 25885 } 25886 } 25887 /** 25888 * Exit full window 25889 * 25890 * @fires Player#exitFullWindow 25891 */ 25892 ; 25893 25894 _proto.exitFullWindow = function exitFullWindow() { 25895 this.isFullscreen(false); 25896 this.isFullWindow = false; 25897 off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars. 25898 25899 document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles 25900 25901 removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes 25902 // this.positionAll(); 25903 25904 /** 25905 * @event Player#exitFullWindow 25906 * @type {EventTarget~Event} 25907 */ 25908 25909 this.trigger('exitFullWindow'); 25910 } 25911 /** 25912 * Check if the player is in Picture-in-Picture mode or tell the player that it 25913 * is or is not in Picture-in-Picture mode. 25914 * 25915 * @param {boolean} [isPiP] 25916 * Set the players current Picture-in-Picture state 25917 * 25918 * @return {boolean} 25919 * - true if Picture-in-Picture is on and getting 25920 * - false if Picture-in-Picture is off and getting 25921 */ 25922 ; 25923 25924 _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) { 25925 if (isPiP !== undefined) { 25926 this.isInPictureInPicture_ = !!isPiP; 25927 this.togglePictureInPictureClass_(); 25928 return; 25929 } 25930 25931 return !!this.isInPictureInPicture_; 25932 } 25933 /** 25934 * Create a floating video window always on top of other windows so that users may 25935 * continue consuming media while they interact with other content sites, or 25936 * applications on their device. 25937 * 25938 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 25939 * 25940 * @fires Player#enterpictureinpicture 25941 * 25942 * @return {Promise} 25943 * A promise with a Picture-in-Picture window. 25944 */ 25945 ; 25946 25947 _proto.requestPictureInPicture = function requestPictureInPicture() { 25948 if ('pictureInPictureEnabled' in document) { 25949 /** 25950 * This event fires when the player enters picture in picture mode 25951 * 25952 * @event Player#enterpictureinpicture 25953 * @type {EventTarget~Event} 25954 */ 25955 return this.techGet_('requestPictureInPicture'); 25956 } 25957 } 25958 /** 25959 * Exit Picture-in-Picture mode. 25960 * 25961 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 25962 * 25963 * @fires Player#leavepictureinpicture 25964 * 25965 * @return {Promise} 25966 * A promise. 25967 */ 25968 ; 25969 25970 _proto.exitPictureInPicture = function exitPictureInPicture() { 25971 if ('pictureInPictureEnabled' in document) { 25972 /** 25973 * This event fires when the player leaves picture in picture mode 25974 * 25975 * @event Player#leavepictureinpicture 25976 * @type {EventTarget~Event} 25977 */ 25978 return document.exitPictureInPicture(); 25979 } 25980 } 25981 /** 25982 * Called when this Player has focus and a key gets pressed down, or when 25983 * any Component of this player receives a key press that it doesn't handle. 25984 * This allows player-wide hotkeys (either as defined below, or optionally 25985 * by an external function). 25986 * 25987 * @param {EventTarget~Event} event
25988 * The `keydown` event that caused this function to be called. 25989 * 25990 * @listens keydown 25991 */ 25992 ; 25993 25994 _proto.handleKeyDown = function handleKeyDown(event) { 25995 var userActions = this.options_.userActions; // Bail out if hotkeys are not configured. 25996 25997 if (!userActions || !userActions.hotkeys) { 25998 return; 25999 } // Function that determines whether or not to exclude an element from 26000 // hotkeys handling. 26001 26002 26003 var excludeElement = function excludeElement(el) { 26004 var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements. 26005 26006 if (el.isContentEditable) { 26007 return true; 26008 } // Inputs matching these types will still trigger hotkey handling as 26009 // they are not text inputs. 26010 26011 26012 var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit']; 26013 26014 if (tagName === 'input') { 26015 return allowedInputTypes.indexOf(el.type) === -1; 26016 } // The final test is by tag name. These tags will be excluded entirely. 26017 26018 26019 var excludedTags = ['textarea']; 26020 return excludedTags.indexOf(tagName) !== -1; 26021 }; // Bail out if the user is focused on an interactive form element. 26022 26023 26024 if (excludeElement(this.el_.ownerDocument.activeElement)) { 26025 return; 26026 } 26027 26028 if (typeof userActions.hotkeys === 'function') { 26029 userActions.hotkeys.call(this, event); 26030 } else { 26031 this.handleHotkeys(event); 26032 } 26033 } 26034 /** 26035 * Called when this Player receives a hotkey keydown event. 26036 * Supported player-wide hotkeys are: 26037 * 26038 * f - toggle fullscreen 26039 * m - toggle mute 26040 * k or Space - toggle play/pause 26041 * 26042 * @param {EventTarget~Event} event 26043 * The `keydown` event that caused this function to be called. 26044 */ 26045 ; 26046 26047 _proto.handleHotkeys = function handleHotkeys(event) { 26048 var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set 26049 26050 var _hotkeys$fullscreenKe = hotkeys.fullscreenKey, 26051 fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) { 26052 return keycode.isEventKey(keydownEvent, 'f'); 26053 } : _hotkeys$fullscreenKe, 26054 _hotkeys$muteKey = hotkeys.muteKey, 26055 muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) { 26056 return keycode.isEventKey(keydownEvent, 'm'); 26057 } : _hotkeys$muteKey, 26058 _hotkeys$playPauseKey = hotkeys.playPauseKey, 26059 playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) { 26060 return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'); 26061 } : _hotkeys$playPauseKey; 26062 26063 if (fullscreenKey.call(this, event)) { 26064 event.preventDefault(); 26065 event.stopPropagation(); 26066 var FSToggle = Component.getComponent('FullscreenToggle'); 26067 26068 if (document[this.fsApi_.fullscreenEnabled] !== false) { 26069 FSToggle.prototype.handleClick.call(this, event); 26070 } 26071 } else if (muteKey.call(this, event)) { 26072 event.preventDefault(); 26073 event.stopPropagation(); 26074 var MuteToggle = Component.getComponent('MuteToggle'); 26075 MuteToggle.prototype.handleClick.call(this, event); 26076 } else if (playPauseKey.call(this, event)) { 26077 event.preventDefault(); 26078 event.stopPropagation(); 26079 var PlayToggle = Component.getComponent('PlayToggle'); 26080 PlayToggle.prototype.handleClick.call(this, event); 26081 } 26082 } 26083 /** 26084 * Check whether the player can play a given mimetype 26085 * 26086 * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
vendor: 8,594 bytes, lines 26087-26348
26087 * 26088 * @param {string} type 26089 * The mimetype to check 26090 * 26091 * @return {string} 26092 * 'probably', 'maybe', or '' (empty string) 26093 */ 26094 ; 26095 26096 _proto.canPlayType = function canPlayType(type) { 26097 var can; // Loop through each playback technology in the options order 26098 26099 for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { 26100 var techName = j[i]; 26101 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 26102 // Remove once that deprecated behavior is removed. 26103 26104 if (!tech) { 26105 tech = Component.getComponent(techName); 26106 } // Check if the current tech is defined before continuing 26107 26108 26109 if (!tech) { 26110 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 26111 continue; 26112 } // Check if the browser supports this technology 26113 26114 26115 if (tech.isSupported()) { 26116 can = tech.canPlayType(type); 26117 26118 if (can) { 26119 return can; 26120 } 26121 } 26122 } 26123 26124 return ''; 26125 } 26126 /** 26127 * Select source based on tech-order or source-order 26128 * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, 26129 * defaults to tech-order selection 26130 * 26131 * @param {Array} sources 26132 * The sources for a media asset 26133 * 26134 * @return {Object|boolean} 26135 * Object of source and tech order or false 26136 */ 26137 ; 26138 26139 _proto.selectSource = function selectSource(sources) { 26140 var _this12 = this; 26141 26142 // Get only the techs specified in `techOrder` that exist and are supported by the 26143 // current platform 26144 var techs = this.options_.techOrder.map(function (techName) { 26145 return [techName, Tech.getTech(techName)]; 26146 }).filter(function (_ref) { 26147 var techName = _ref[0], 26148 tech = _ref[1]; 26149 26150 // Check if the current tech is defined before continuing 26151 if (tech) { 26152 // Check if the browser supports this technology 26153 return tech.isSupported(); 26154 } 26155 26156 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 26157 return false; 26158 }); // Iterate over each `innerArray` element once per `outerArray` element and execute 26159 // `tester` with both. If `tester` returns a non-falsy value, exit early and return 26160 // that value. 26161 26162 var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { 26163 var found; 26164 outerArray.some(function (outerChoice) { 26165 return innerArray.some(function (innerChoice) { 26166 found = tester(outerChoice, innerChoice); 26167 26168 if (found) { 26169 return true; 26170 } 26171 }); 26172 }); 26173 return found; 26174 }; 26175 26176 var foundSourceAndTech; 26177 26178 var flip = function flip(fn) { 26179 return function (a, b) { 26180 return fn(b, a); 26181 }; 26182 }; 26183 26184 var finder = function finder(_ref2, source) { 26185 var techName = _ref2[0], 26186 tech = _ref2[1]; 26187 26188 if (tech.canPlaySource(source, _this12.options_[techName.toLowerCase()])) { 26189 return { 26190 source: source, 26191 tech: techName 26192 }; 26193 } 26194 }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources 26195 // to select from them based on their priority. 26196 26197 26198 if (this.options_.sourceOrder) { 26199 // Source-first ordering 26200 foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); 26201 } else { 26202 // Tech-first ordering 26203 foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); 26204 } 26205 26206 return foundSourceAndTech || false; 26207 } 26208 /** 26209 * Get or set the video source. 26210 * 26211 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 26212 * A SourceObject, an array of SourceObjects, or a string referencing 26213 * a URL to a media source. It is _highly recommended_ that an object 26214 * or array of objects is used here, so that source selection 26215 * algorithms can take the `type` into account. 26216 * 26217 * If not provided, this method acts as a getter. 26218 * 26219 * @return {string|undefined} 26220 * If the `source` argument is missing, returns the current source 26221 * URL. Otherwise, returns nothing/undefined. 26222 */ 26223 ; 26224 26225 _proto.src = function src(source) { 26226 var _this13 = this; 26227 26228 // getter usage 26229 if (typeof source === 'undefined') { 26230 return this.cache_.src || ''; 26231 } // filter out invalid sources and turn our source into 26232 // an array of source objects 26233 26234 26235 var sources = filterSource(source); // if a source was passed in then it is invalid because 26236 // it was filtered to a zero length Array. So we have to 26237 // show an error 26238 26239 if (!sources.length) { 26240 this.setTimeout(function () { 26241 this.error({ 26242 code: 4, 26243 message: this.localize(this.options_.notSupportedMessage) 26244 }); 26245 }, 0); 26246 return; 26247 } // intial sources 26248 26249 26250 this.changingSrc_ = true; 26251 this.cache_.sources = sources; 26252 this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware 26253 26254 setSource(this, sources[0], function (middlewareSource, mws) { 26255 _this13.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since 26256 // the source that is selected could be out of order from the cache update above this callback. 26257 26258 _this13.cache_.sources = sources; 26259 26260 _this13.updateSourceCaches_(middlewareSource); 26261 26262 var err = _this13.src_(middlewareSource); 26263 26264 if (err) { 26265 if (sources.length > 1) { 26266 return _this13.src(sources.slice(1)); 26267 } 26268 26269 _this13.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers 26270 26271 _this13.setTimeout(function () { 26272 this.error({ 26273 code: 4, 26274 message: this.localize(this.options_.notSupportedMessage) 26275 }); 26276 }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it 26277 // this needs a better comment about why this is needed 26278 26279 26280 _this13.triggerReady(); 26281 26282 return; 26283 } 26284 26285 setTech(mws, _this13.tech_); 26286 }); 26287 } 26288 /** 26289 * Set the source object on the tech, returns a boolean that indicates whether 26290 * there is a tech that can play the source or not 26291 * 26292 * @param {Tech~SourceObject} source 26293 * The source object to set on the Tech 26294 * 26295 * @return {boolean} 26296 * - True if there is no Tech to playback this source 26297 * - False otherwise 26298 * 26299 * @private 26300 */ 26301 ; 26302 26303 _proto.src_ = function src_(source) { 26304 var _this14 = this; 26305 26306 var sourceTech = this.selectSource([source]); 26307 26308 if (!sourceTech) { 26309 return true; 26310 } 26311 26312 if (!titleCaseEquals(sourceTech.tech, this.techName_)) { 26313 this.changingSrc_ = true; // load this technology with the chosen source 26314 26315 this.loadTech_(sourceTech.tech, sourceTech.source); 26316 this.tech_.ready(function () { 26317 _this14.changingSrc_ = false; 26318 }); 26319 return false; 26320 } // wait until the tech is ready to set the source 26321 // and set it synchronously if possible (#2326) 26322 26323 26324 this.ready(function () { 26325 // The setSource tech method was added with source handlers 26326 // so older techs won't support it 26327 // We need to check the direct prototype for the case where subclasses 26328 // of the tech do not support source handlers 26329 if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { 26330 this.techCall_('setSource', source); 26331 } else { 26332 this.techCall_('src', source.src); 26333 } 26334 26335 this.changingSrc_ = false; 26336 }, true); 26337 return false; 26338 } 26339 /** 26340 * Begin loading the src data. 26341 */ 26342 ; 26343 26344 _proto.load = function load() { 26345 this.techCall_('load'); 26346 } 26347 /** 26348 * Reset the player. Loads the first tech in the techOrder,
26349 * removes all the text tracks in the existing `tech`, 26350 * and calls `reset` on the `tech`. 26351 */ 26352 ; 26353 26354 _proto.reset = function reset() { 26355 var _this15 = this; 26356 26357 var PromiseClass = this.options_.Promise || window$1.Promise; 26358 26359 if (this.paused() || !PromiseClass) { 26360 this.doReset_(); 26361 } else { 26362 var playPromise = this.play(); 26363 silencePromise(playPromise.then(function () { 26364 return _this15.doReset_(); 26365 })); 26366 } 26367 }; 26368 26369 _proto.doReset_ = function doReset_() { 26370 if (this.tech_) { 26371 this.tech_.clearTracks('text'); 26372 } 26373 26374 this.resetCache_(); 26375 this.poster(''); 26376 this.loadTech_(this.options_.techOrder[0], null); 26377 this.techCall_('reset'); 26378 this.resetControlBarUI_(); 26379 26380 if (isEvented(this)) { 26381 this.trigger('playerreset'); 26382 } 26383 } 26384 /** 26385 * Reset Control Bar's UI by calling sub-methods that reset 26386 * all of Control Bar's components 26387 */ 26388 ; 26389 26390 _proto.resetControlBarUI_ = function resetControlBarUI_() { 26391 this.resetProgressBar_(); 26392 this.resetPlaybackRate_(); 26393 this.resetVolumeBar_(); 26394 } 26395 /** 26396 * Reset tech's progress so progress bar is reset in the UI 26397 */ 26398 ; 26399 26400 _proto.resetProgressBar_ = function resetProgressBar_() { 26401 this.currentTime(0); 26402 var _this$controlBar = this.controlBar, 26403 durationDisplay = _this$controlBar.durationDisplay, 26404 remainingTimeDisplay = _this$controlBar.remainingTimeDisplay; 26405 26406 if (durationDisplay) { 26407 durationDisplay.updateContent(); 26408 } 26409 26410 if (remainingTimeDisplay) { 26411 remainingTimeDisplay.updateContent(); 26412 } 26413 } 26414 /** 26415 * Reset Playback ratio 26416 */ 26417 ; 26418 26419 _proto.resetPlaybackRate_ = function resetPlaybackRate_() { 26420 this.playbackRate(this.defaultPlaybackRate()); 26421 this.handleTechRateChange_(); 26422 } 26423 /** 26424 * Reset Volume bar 26425 */ 26426 ; 26427 26428 _proto.resetVolumeBar_ = function resetVolumeBar_() { 26429 this.volume(1.0); 26430 this.trigger('volumechange'); 26431 } 26432 /** 26433 * Returns all of the current source objects. 26434 * 26435 * @return {Tech~SourceObject[]} 26436 * The current source objects 26437 */ 26438 ; 26439 26440 _proto.currentSources = function currentSources() { 26441 var source = this.currentSource(); 26442 var sources = []; // assume `{}` or `{ src }` 26443 26444 if (Object.keys(source).length !== 0) { 26445 sources.push(source); 26446 } 26447 26448 return this.cache_.sources || sources; 26449 } 26450 /** 26451 * Returns the current source object. 26452 * 26453 * @return {Tech~SourceObject} 26454 * The current source object 26455 */ 26456 ; 26457 26458 _proto.currentSource = function currentSource() { 26459 return this.cache_.source || {}; 26460 } 26461 /** 26462 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 26463 * Can be used in conjunction with `currentType` to assist in rebuilding the current source object. 26464 * 26465 * @return {string} 26466 * The current source 26467 */ 26468 ; 26469 26470 _proto.currentSrc = function currentSrc() { 26471 return this.currentSource() && this.currentSource().src || ''; 26472 } 26473 /** 26474 * Get the current source type e.g. video/mp4 26475 * This can allow you rebuild the current source object so that you could load the same 26476 * source and tech later 26477 * 26478 * @return {string} 26479 * The source MIME type 26480 */ 26481 ; 26482 26483 _proto.currentType = function currentType() { 26484 return this.currentSource() && this.currentSource().type || ''; 26485 } 26486 /** 26487 * Get or set the preload attribute 26488 * 26489 * @param {boolean} [value] 26490 * - true means that we should preload 26491 * - false means that we should not preload 26492 * 26493 * @return {string} 26494 * The preload attribute value when getting 26495 */ 26496 ; 26497 26498 _proto.preload = function preload(value) { 26499 if (value !== undefined) { 26500 this.techCall_('setPreload', value); 26501 this.options_.preload = value; 26502 return; 26503 } 26504 26505 return this.techGet_('preload'); 26506 } 26507 /** 26508 * Get or set the autoplay option. When this is a boolean it will 26509 * modify the attribute on the tech. When this is a string the attribute on 26510 * the tech will be removed and `Player` will handle autoplay on loadstarts. 26511 * 26512 * @param {boolean|string} [value] 26513 * - true: autoplay using the browser behavior 26514 * - false: do not autoplay 26515 * - 'play': call play() on every loadstart 26516 * - 'muted': call muted() then play() on every loadstart 26517 * - 'any': call play() on every loadstart. if that fails call muted() then play(). 26518 * - *: values other than those listed here will be set `autoplay` to true 26519 * 26520 * @return {boolean|string} 26521 * The current value of autoplay when getting 26522 */ 26523 ; 26524 26525 _proto.autoplay = function autoplay(value) { 26526 // getter usage 26527 if (value === undefined) { 26528 return this.options_.autoplay || false; 26529 } 26530 26531 var techAutoplay; // if the value is a valid string set it to that 26532 26533 if (typeof value === 'string' && /(any|play|muted)/.test(value)) { 26534 this.options_.autoplay = value; 26535 this.manualAutoplay_(value); 26536 techAutoplay = false;
vendor: 25,380 bytes, lines 26536-27325
26536 // any falsy value sets autoplay to false in the browser, 26537 // lets do the same 26538 } else if (!value) { 26539 this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true 26540 } else { 26541 this.options_.autoplay = true; 26542 } 26543 26544 techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up 26545 // a setAutoplay call on tech ready. We do this because the 26546 // autoplay option will be passed in the constructor and we 26547 // do not need to set it twice 26548 26549 if (this.tech_) { 26550 this.techCall_('setAutoplay', techAutoplay); 26551 } 26552 } 26553 /** 26554 * Set or unset the playsinline attribute. 26555 * Playsinline tells the browser that non-fullscreen playback is preferred. 26556 * 26557 * @param {boolean} [value] 26558 * - true means that we should try to play inline by default 26559 * - false means that we should use the browser's default playback mode, 26560 * which in most cases is inline. iOS Safari is a notable exception 26561 * and plays fullscreen by default. 26562 * 26563 * @return {string|Player} 26564 * - the current value of playsinline 26565 * - the player when setting 26566 * 26567 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 26568 */ 26569 ; 26570 26571 _proto.playsinline = function playsinline(value) { 26572 if (value !== undefined) { 26573 this.techCall_('setPlaysinline', value); 26574 this.options_.playsinline = value; 26575 return this; 26576 } 26577 26578 return this.techGet_('playsinline'); 26579 } 26580 /** 26581 * Get or set the loop attribute on the video element. 26582 * 26583 * @param {boolean} [value] 26584 * - true means that we should loop the video 26585 * - false means that we should not loop the video 26586 * 26587 * @return {boolean} 26588 * The current value of loop when getting 26589 */ 26590 ; 26591 26592 _proto.loop = function loop(value) { 26593 if (value !== undefined) { 26594 this.techCall_('setLoop', value); 26595 this.options_.loop = value; 26596 return; 26597 } 26598 26599 return this.techGet_('loop'); 26600 } 26601 /** 26602 * Get or set the poster image source url 26603 * 26604 * @fires Player#posterchange 26605 * 26606 * @param {string} [src] 26607 * Poster image source URL 26608 * 26609 * @return {string} 26610 * The current value of poster when getting 26611 */ 26612 ; 26613 26614 _proto.poster = function poster(src) { 26615 if (src === undefined) { 26616 return this.poster_; 26617 } // The correct way to remove a poster is to set as an empty string 26618 // other falsey values will throw errors 26619 26620 26621 if (!src) { 26622 src = ''; 26623 } 26624 26625 if (src === this.poster_) { 26626 return; 26627 } // update the internal poster variable 26628 26629 26630 this.poster_ = src; // update the tech's poster 26631 26632 this.techCall_('setPoster', src); 26633 this.isPosterFromTech_ = false; // alert components that the poster has been set 26634 26635 /** 26636 * This event fires when the poster image is changed on the player. 26637 * 26638 * @event Player#posterchange 26639 * @type {EventTarget~Event} 26640 */ 26641 26642 this.trigger('posterchange'); 26643 } 26644 /** 26645 * Some techs (e.g. YouTube) can provide a poster source in an 26646 * asynchronous way. We want the poster component to use this 26647 * poster source so that it covers up the tech's controls. 26648 * (YouTube's play button). However we only want to use this 26649 * source if the player user hasn't set a poster through 26650 * the normal APIs. 26651 * 26652 * @fires Player#posterchange 26653 * @listens Tech#posterchange 26654 * @private 26655 */ 26656 ; 26657 26658 _proto.handleTechPosterChange_ = function handleTechPosterChange_() { 26659 if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) { 26660 var newPoster = this.tech_.poster() || ''; 26661 26662 if (newPoster !== this.poster_) { 26663 this.poster_ = newPoster; 26664 this.isPosterFromTech_ = true; // Let components know the poster has changed 26665 26666 this.trigger('posterchange'); 26667 } 26668 } 26669 } 26670 /** 26671 * Get or set whether or not the controls are showing. 26672 * 26673 * @fires Player#controlsenabled 26674 * 26675 * @param {boolean} [bool] 26676 * - true to turn controls on 26677 * - false to turn controls off 26678 * 26679 * @return {boolean} 26680 * The current value of controls when getting 26681 */ 26682 ; 26683 26684 _proto.controls = function controls(bool) { 26685 if (bool === undefined) { 26686 return !!this.controls_; 26687 } 26688 26689 bool = !!bool; // Don't trigger a change event unless it actually changed 26690 26691 if (this.controls_ === bool) { 26692 return; 26693 } 26694 26695 this.controls_ = bool; 26696 26697 if (this.usingNativeControls()) { 26698 this.techCall_('setControls', bool); 26699 } 26700 26701 if (this.controls_) { 26702 this.removeClass('vjs-controls-disabled'); 26703 this.addClass('vjs-controls-enabled'); 26704 /** 26705 * @event Player#controlsenabled 26706 * @type {EventTarget~Event} 26707 */ 26708 26709 this.trigger('controlsenabled'); 26710 26711 if (!this.usingNativeControls()) { 26712 this.addTechControlsListeners_(); 26713 } 26714 } else { 26715 this.removeClass('vjs-controls-enabled'); 26716 this.addClass('vjs-controls-disabled'); 26717 /** 26718 * @event Player#controlsdisabled 26719 * @type {EventTarget~Event} 26720 */ 26721 26722 this.trigger('controlsdisabled'); 26723 26724 if (!this.usingNativeControls()) { 26725 this.removeTechControlsListeners_(); 26726 } 26727 } 26728 } 26729 /** 26730 * Toggle native controls on/off. Native controls are the controls built into 26731 * devices (e.g. default iPhone controls), Flash, or other techs 26732 * (e.g. Vimeo Controls) 26733 * **This should only be set by the current tech, because only the tech knows 26734 * if it can support native controls** 26735 * 26736 * @fires Player#usingnativecontrols 26737 * @fires Player#usingcustomcontrols 26738 * 26739 * @param {boolean} [bool] 26740 * - true to turn native controls on 26741 * - false to turn native controls off 26742 * 26743 * @return {boolean} 26744 * The current value of native controls when getting 26745 */ 26746 ; 26747 26748 _proto.usingNativeControls = function usingNativeControls(bool) { 26749 if (bool === undefined) { 26750 return !!this.usingNativeControls_; 26751 } 26752 26753 bool = !!bool; // Don't trigger a change event unless it actually changed 26754 26755 if (this.usingNativeControls_ === bool) { 26756 return; 26757 } 26758 26759 this.usingNativeControls_ = bool; 26760 26761 if (this.usingNativeControls_) { 26762 this.addClass('vjs-using-native-controls'); 26763 /** 26764 * player is using the native device controls 26765 * 26766 * @event Player#usingnativecontrols 26767 * @type {EventTarget~Event} 26768 */ 26769 26770 this.trigger('usingnativecontrols'); 26771 } else { 26772 this.removeClass('vjs-using-native-controls'); 26773 /** 26774 * player is using the custom HTML controls 26775 * 26776 * @event Player#usingcustomcontrols 26777 * @type {EventTarget~Event} 26778 */ 26779 26780 this.trigger('usingcustomcontrols'); 26781 } 26782 } 26783 /** 26784 * Set or get the current MediaError 26785 * 26786 * @fires Player#error 26787 * 26788 * @param {MediaError|string|number} [err] 26789 * A MediaError or a string/number to be turned 26790 * into a MediaError 26791 * 26792 * @return {MediaError|null} 26793 * The current MediaError when getting (or null) 26794 */ 26795 ; 26796 26797 _proto.error = function error(err) { 26798 if (err === undefined) { 26799 return this.error_ || null; 26800 } // Suppress the first error message for no compatible source until 26801 // user interaction 26802 26803 26804 if (this.options_.suppressNotSupportedError && err && err.code === 4) { 26805 var triggerSuppressedError = function triggerSuppressedError() { 26806 this.error(err); 26807 }; 26808 26809 this.options_.suppressNotSupportedError = false; 26810 this.any(['click', 'touchstart'], triggerSuppressedError); 26811 this.one('loadstart', function () { 26812 this.off(['click', 'touchstart'], triggerSuppressedError); 26813 }); 26814 return; 26815 } // restoring to default 26816 26817 26818 if (err === null) { 26819 this.error_ = err; 26820 this.removeClass('vjs-error'); 26821 26822 if (this.errorDisplay) { 26823 this.errorDisplay.close(); 26824 } 26825 26826 return; 26827 } 26828 26829 this.error_ = new MediaError(err); // add the vjs-error classname to the player 26830 26831 this.addClass('vjs-error'); // log the name of the error type and any message 26832 // IE11 logs "[object object]" and required you to expand message to see error object 26833 26834 log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_); 26835 /** 26836 * @event Player#error 26837 * @type {EventTarget~Event} 26838 */ 26839 26840 this.trigger('error'); 26841 return; 26842 } 26843 /** 26844 * Report user activity 26845 * 26846 * @param {Object} event 26847 * Event object 26848 */ 26849 ; 26850 26851 _proto.reportUserActivity = function reportUserActivity(event) { 26852 this.userActivity_ = true; 26853 } 26854 /** 26855 * Get/set if user is active 26856 * 26857 * @fires Player#useractive 26858 * @fires Player#userinactive 26859 * 26860 * @param {boolean} [bool] 26861 * - true if the user is active 26862 * - false if the user is inactive 26863 * 26864 * @return {boolean} 26865 * The current value of userActive when getting 26866 */ 26867 ; 26868 26869 _proto.userActive = function userActive(bool) { 26870 if (bool === undefined) { 26871 return this.userActive_; 26872 } 26873 26874 bool = !!bool; 26875 26876 if (bool === this.userActive_) { 26877 return; 26878 } 26879 26880 this.userActive_ = bool; 26881 26882 if (this.userActive_) { 26883 this.userActivity_ = true; 26884 this.removeClass('vjs-user-inactive'); 26885 this.addClass('vjs-user-active'); 26886 /** 26887 * @event Player#useractive 26888 * @type {EventTarget~Event} 26889 */ 26890 26891 this.trigger('useractive'); 26892 return; 26893 } // Chrome/Safari/IE have bugs where when you change the cursor it can 26894 // trigger a mousemove event. This causes an issue when you're hiding 26895 // the cursor when the user is inactive, and a mousemove signals user 26896 // activity. Making it impossible to go into inactive mode. Specifically 26897 // this happens in fullscreen when we really need to hide the cursor. 26898 // 26899 // When this gets resolved in ALL browsers it can be removed 26900 // https://code.google.com/p/chromium/issues/detail?id=103041 26901 26902 26903 if (this.tech_) { 26904 this.tech_.one('mousemove', function (e) { 26905 e.stopPropagation(); 26906 e.preventDefault(); 26907 }); 26908 } 26909 26910 this.userActivity_ = false; 26911 this.removeClass('vjs-user-active'); 26912 this.addClass('vjs-user-inactive'); 26913 /** 26914 * @event Player#userinactive 26915 * @type {EventTarget~Event} 26916 */ 26917 26918 this.trigger('userinactive'); 26919 } 26920 /** 26921 * Listen for user activity based on timeout value 26922 * 26923 * @private 26924 */ 26925 ; 26926 26927 _proto.listenForUserActivity_ = function listenForUserActivity_() { 26928 var mouseInProgress; 26929 var lastMoveX; 26930 var lastMoveY; 26931 var handleActivity = bind(this, this.reportUserActivity); 26932 26933 var handleMouseMove = function handleMouseMove(e) { 26934 // #1068 - Prevent mousemove spamming 26935 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 26936 if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { 26937 lastMoveX = e.screenX; 26938 lastMoveY = e.screenY; 26939 handleActivity(); 26940 } 26941 }; 26942 26943 var handleMouseDown = function handleMouseDown() { 26944 handleActivity(); // For as long as the they are touching the device or have their mouse down, 26945 // we consider them active even if they're not moving their finger or mouse. 26946 // So we want to continue to update that they are active 26947 26948 this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time 26949 // as the activityCheck interval (250) should ensure we never miss the 26950 // next activityCheck 26951 26952 mouseInProgress = this.setInterval(handleActivity, 250); 26953 }; 26954 26955 var handleMouseUpAndMouseLeave = function handleMouseUpAndMouseLeave(event) { 26956 handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down 26957 26958 this.clearInterval(mouseInProgress); 26959 }; // Any mouse movement will be considered user activity 26960 26961 26962 this.on('mousedown', handleMouseDown); 26963 this.on('mousemove', handleMouseMove); 26964 this.on('mouseup', handleMouseUpAndMouseLeave); 26965 this.on('mouseleave', handleMouseUpAndMouseLeave); 26966 var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed) 26967 // controlBar would no longer be hidden by default timeout. 26968 26969 if (controlBar && !IS_IOS && !IS_ANDROID) { 26970 controlBar.on('mouseenter', function (event) { 26971 this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout; 26972 this.player().options_.inactivityTimeout = 0; 26973 }); 26974 controlBar.on('mouseleave', function (event) { 26975 this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout; 26976 }); 26977 } // Listen for keyboard navigation 26978 // Shouldn't need to use inProgress interval because of key repeat 26979 26980 26981 this.on('keydown', handleActivity); 26982 this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into 26983 // the mousemove/touchmove function itself, to prevent performance degradation. 26984 // `this.reportUserActivity` simply sets this.userActivity_ to true, which 26985 // then gets picked up by this loop 26986 // http://ejohn.org/blog/learning-from-twitter/ 26987 26988 var inactivityTimeout; 26989 this.setInterval(function () { 26990 // Check to see if mouse/touch activity has happened 26991 if (!this.userActivity_) { 26992 return; 26993 } // Reset the activity tracker 26994 26995 26996 this.userActivity_ = false; // If the user state was inactive, set the state to active 26997 26998 this.userActive(true); // Clear any existing inactivity timeout to start the timer over 26999 27000 this.clearTimeout(inactivityTimeout); 27001 var timeout = this.options_.inactivityTimeout; 27002 27003 if (timeout <= 0) { 27004 return; 27005 } // In <timeout> milliseconds, if no more activity has occurred the 27006 // user will be considered inactive 27007 27008 27009 inactivityTimeout = this.setTimeout(function () { 27010 // Protect against the case where the inactivityTimeout can trigger just 27011 // before the next user activity is picked up by the activity check loop 27012 // causing a flicker 27013 if (!this.userActivity_) { 27014 this.userActive(false); 27015 } 27016 }, timeout); 27017 }, 250); 27018 } 27019 /** 27020 * Gets or sets the current playback rate. A playback rate of 27021 * 1.0 represents normal speed and 0.5 would indicate half-speed 27022 * playback, for instance. 27023 * 27024 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate 27025 * 27026 * @param {number} [rate] 27027 * New playback rate to set. 27028 * 27029 * @return {number} 27030 * The current playback rate when getting or 1.0 27031 */ 27032 ; 27033 27034 _proto.playbackRate = function playbackRate(rate) { 27035 if (rate !== undefined) { 27036 // NOTE: this.cache_.lastPlaybackRate is set from the tech handler 27037 // that is registered above 27038 this.techCall_('setPlaybackRate', rate); 27039 return; 27040 } 27041 27042 if (this.tech_ && this.tech_.featuresPlaybackRate) { 27043 return this.cache_.lastPlaybackRate || this.techGet_('playbackRate'); 27044 } 27045 27046 return 1.0; 27047 } 27048 /** 27049 * Gets or sets the current default playback rate. A default playback rate of 27050 * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. 27051 * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not 27052 * not the current playbackRate. 27053 * 27054 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate 27055 * 27056 * @param {number} [rate] 27057 * New default playback rate to set. 27058 * 27059 * @return {number|Player} 27060 * - The default playback rate when getting or 1.0 27061 * - the player when setting 27062 */ 27063 ; 27064 27065 _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) { 27066 if (rate !== undefined) { 27067 return this.techCall_('setDefaultPlaybackRate', rate); 27068 } 27069 27070 if (this.tech_ && this.tech_.featuresPlaybackRate) { 27071 return this.techGet_('defaultPlaybackRate'); 27072 } 27073 27074 return 1.0; 27075 } 27076 /** 27077 * Gets or sets the audio flag 27078 * 27079 * @param {boolean} bool 27080 * - true signals that this is an audio player 27081 * - false signals that this is not an audio player 27082 * 27083 * @return {boolean} 27084 * The current value of isAudio when getting 27085 */ 27086 ; 27087 27088 _proto.isAudio = function isAudio(bool) { 27089 if (bool !== undefined) { 27090 this.isAudio_ = !!bool; 27091 return; 27092 } 27093 27094 return !!this.isAudio_; 27095 } 27096 /** 27097 * A helper method for adding a {@link TextTrack} to our 27098 * {@link TextTrackList}. 27099 * 27100 * In addition to the W3C settings we allow adding additional info through options. 27101 * 27102 * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack 27103 * 27104 * @param {string} [kind] 27105 * the kind of TextTrack you are adding 27106 * 27107 * @param {string} [label] 27108 * the label to give the TextTrack label 27109 * 27110 * @param {string} [language] 27111 * the language to set on the TextTrack 27112 * 27113 * @return {TextTrack|undefined} 27114 * the TextTrack that was added or undefined 27115 * if there is no tech 27116 */ 27117 ; 27118 27119 _proto.addTextTrack = function addTextTrack(kind, label, language) { 27120 if (this.tech_) { 27121 return this.tech_.addTextTrack(kind, label, language); 27122 } 27123 } 27124 /** 27125 * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will 27126 * automatically removed from the video element whenever the source changes, unless 27127 * manualCleanup is set to false. 27128 * 27129 * @param {Object} options 27130 * Options to pass to {@link HTMLTrackElement} during creation. See 27131 * {@link HTMLTrackElement} for object properties that you should use. 27132 * 27133 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 27134 * 27135 * @return {HtmlTrackElement} 27136 * the HTMLTrackElement that was created and added 27137 * to the HtmlTrackElementList and the remote 27138 * TextTrackList 27139 * 27140 * @deprecated The default value of the "manualCleanup" parameter will default 27141 * to "false" in upcoming versions of Video.js 27142 */ 27143 ; 27144 27145 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 27146 if (this.tech_) { 27147 return this.tech_.addRemoteTextTrack(options, manualCleanup); 27148 } 27149 } 27150 /** 27151 * Remove a remote {@link TextTrack} from the respective 27152 * {@link TextTrackList} and {@link HtmlTrackElementList}. 27153 * 27154 * @param {Object} track 27155 * Remote {@link TextTrack} to remove 27156 * 27157 * @return {undefined} 27158 * does not return anything 27159 */ 27160 ; 27161 27162 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) { 27163 if (obj === void 0) { 27164 obj = {}; 27165 } 27166 27167 var _obj = obj, 27168 track = _obj.track; 27169 27170 if (!track) { 27171 track = obj; 27172 } // destructure the input into an object with a track argument, defaulting to arguments[0] 27173 // default the whole argument to an empty object if nothing was passed in 27174 27175 27176 if (this.tech_) { 27177 return this.tech_.removeRemoteTextTrack(track); 27178 } 27179 } 27180 /** 27181 * Gets available media playback quality metrics as specified by the W3C's Media 27182 * Playback Quality API. 27183 * 27184 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 27185 * 27186 * @return {Object|undefined} 27187 * An object with supported media playback quality metrics or undefined if there 27188 * is no tech or the tech does not support it. 27189 */ 27190 ; 27191 27192 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 27193 return this.techGet_('getVideoPlaybackQuality'); 27194 } 27195 /** 27196 * Get video width 27197 * 27198 * @return {number} 27199 * current video width 27200 */ 27201 ; 27202 27203 _proto.videoWidth = function videoWidth() { 27204 return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; 27205 } 27206 /** 27207 * Get video height 27208 * 27209 * @return {number} 27210 * current video height 27211 */ 27212 ; 27213 27214 _proto.videoHeight = function videoHeight() { 27215 return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; 27216 } 27217 /** 27218 * The player's language code 27219 * NOTE: The language should be set in the player options if you want the 27220 * the controls to be built with a specific language. Changing the language 27221 * later will not update controls text. 27222 * 27223 * @param {string} [code] 27224 * the language code to set the player to 27225 * 27226 * @return {string} 27227 * The current language code when getting 27228 */ 27229 ; 27230 27231 _proto.language = function language(code) { 27232 if (code === undefined) { 27233 return this.language_; 27234 } 27235 27236 this.language_ = String(code).toLowerCase(); 27237 } 27238 /** 27239 * Get the player's language dictionary 27240 * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time 27241 * Languages specified directly in the player options have precedence 27242 * 27243 * @return {Array} 27244 * An array of of supported languages 27245 */ 27246 ; 27247 27248 _proto.languages = function languages() { 27249 return mergeOptions(Player.prototype.options_.languages, this.languages_); 27250 } 27251 /** 27252 * returns a JavaScript object reperesenting the current track 27253 * information. **DOES not return it as JSON** 27254 * 27255 * @return {Object} 27256 * Object representing the current of track info 27257 */ 27258 ; 27259 27260 _proto.toJSON = function toJSON() { 27261 var options = mergeOptions(this.options_); 27262 var tracks = options.tracks; 27263 options.tracks = []; 27264 27265 for (var i = 0; i < tracks.length; i++) { 27266 var track = tracks[i]; // deep merge tracks and null out player so no circular references 27267 27268 track = mergeOptions(track); 27269 track.player = undefined; 27270 options.tracks[i] = track; 27271 } 27272 27273 return options; 27274 } 27275 /** 27276 * Creates a simple modal dialog (an instance of the {@link ModalDialog} 27277 * component) that immediately overlays the player with arbitrary 27278 * content and removes itself when closed. 27279 * 27280 * @param {string|Function|Element|Array|null} content 27281 * Same as {@link ModalDialog#content}'s param of the same name. 27282 * The most straight-forward usage is to provide a string or DOM 27283 * element. 27284 * 27285 * @param {Object} [options] 27286 * Extra options which will be passed on to the {@link ModalDialog}. 27287 * 27288 * @return {ModalDialog} 27289 * the {@link ModalDialog} that was created 27290 */ 27291 ; 27292 27293 _proto.createModal = function createModal(content, options) { 27294 var _this16 = this; 27295 27296 options = options || {}; 27297 options.content = content || ''; 27298 var modal = new ModalDialog(this, options); 27299 this.addChild(modal); 27300 modal.on('dispose', function () { 27301 _this16.removeChild(modal); 27302 }); 27303 modal.open(); 27304 return modal; 27305 } 27306 /** 27307 * Change breakpoint classes when the player resizes. 27308 * 27309 * @private 27310 */ 27311 ; 27312 27313 _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() { 27314 if (!this.responsive()) { 27315 return; 27316 } 27317 27318 var currentBreakpoint = this.currentBreakpoint(); 27319 var currentWidth = this.currentWidth(); 27320 27321 for (var i = 0; i < BREAKPOINT_ORDER.length; i++) { 27322 var candidateBreakpoint = BREAKPOINT_ORDER[i]; 27323 var maxWidth = this.breakpoints_[candidateBreakpoint]; 27324 27325 if (currentWidth <= maxWidth) {
27326 // The current breakpoint did not change, nothing to do. 27327 if (currentBreakpoint === candidateBreakpoint) { 27328 return; 27329 } // Only remove a class if there is a current breakpoint. 27330 27331 27332 if (currentBreakpoint) { 27333 this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]); 27334 } 27335 27336 this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]); 27337 this.breakpoint_ = candidateBreakpoint; 27338 break; 27339 } 27340 } 27341 } 27342 /** 27343 * Removes the current breakpoint. 27344 * 27345 * @private 27346 */ 27347 ; 27348 27349 _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() { 27350 var className = this.currentBreakpointClass(); 27351 this.breakpoint_ = ''; 27352 27353 if (className) { 27354 this.removeClass(className); 27355 } 27356 } 27357 /** 27358 * Get or set breakpoints on the player. 27359 * 27360 * Calling this method with an object or `true` will remove any previous 27361 * custom breakpoints and start from the defaults again. 27362 * 27363 * @param {Object|boolean} [breakpoints] 27364 * If an object is given, it can be used to provide custom 27365 * breakpoints. If `true` is given, will set default breakpoints. 27366 * If this argument is not given, will simply return the current 27367 * breakpoints. 27368 * 27369 * @param {number} [breakpoints.tiny] 27370 * The maximum width for the "vjs-layout-tiny" class. 27371 * 27372 * @param {number} [breakpoints.xsmall] 27373 * The maximum width for the "vjs-layout-x-small" class. 27374 * 27375 * @param {number} [breakpoints.small] 27376 * The maximum width for the "vjs-layout-small" class. 27377 * 27378 * @param {number} [breakpoints.medium] 27379 * The maximum width for the "vjs-layout-medium" class. 27380 * 27381 * @param {number} [breakpoints.large] 27382 * The maximum width for the "vjs-layout-large" class. 27383 * 27384 * @param {number} [breakpoints.xlarge] 27385 * The maximum width for the "vjs-layout-x-large" class. 27386 * 27387 * @param {number} [breakpoints.huge] 27388 * The maximum width for the "vjs-layout-huge" class. 27389 * 27390 * @return {Object} 27391 * An object mapping breakpoint names to maximum width values. 27392 */ 27393 ; 27394 27395 _proto.breakpoints = function breakpoints(_breakpoints) { 27396 // Used as a getter. 27397 if (_breakpoints === undefined) { 27398 return assign(this.breakpoints_); 27399 } 27400 27401 this.breakpoint_ = ''; 27402 this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently 27403 // selected breakpoint. 27404 27405 this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning. 27406 27407 return assign(this.breakpoints_); 27408 } 27409 /** 27410 * Get or set a flag indicating whether or not this player should adjust 27411 * its UI based on its dimensions. 27412 * 27413 * @param {boolean} value 27414 * Should be `true` if the player should adjust its UI based on its 27415 * dimensions; otherwise, should be `false`. 27416 * 27417 * @return {boolean} 27418 * Will be `true` if this player should adjust its UI based on its 27419 * dimensions; otherwise, will be `false`. 27420 */ 27421 ; 27422 27423 _proto.responsive = function responsive(value) { 27424 // Used as a getter. 27425 if (value === undefined) { 27426 return this.responsive_; 27427 } 27428 27429 value = Boolean(value); 27430 var current = this.responsive_; // Nothing changed. 27431 27432 if (value === current) { 27433 return; 27434 } // The value actually changed, set it. 27435 27436 27437 this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the 27438 // player is now responsive. 27439 27440 if (value) { 27441 this.on('playerresize', this.updateCurrentBreakpoint_); 27442 this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive. 27443 } else { 27444 this.off('playerresize', this.updateCurrentBreakpoint_); 27445 this.removeCurrentBreakpoint_(); 27446 } 27447 27448 return value; 27449 } 27450 /** 27451 * Get current breakpoint name, if any. 27452 * 27453 * @return {string} 27454 * If there is currently a breakpoint set, returns a the key from the 27455 * breakpoints object matching it. Otherwise, returns an empty string. 27456 */ 27457 ; 27458 27459 _proto.currentBreakpoint = function currentBreakpoint() { 27460 return this.breakpoint_; 27461 } 27462 /** 27463 * Get the current breakpoint class name. 27464 * 27465 * @return {string} 27466 * The matching class name (e.g. `"vjs-layout-tiny"` or 27467 * `"vjs-layout-large"`) for the current breakpoint. Empty string if 27468 * there is no current breakpoint. 27469 */ 27470 ; 27471 27472 _proto.currentBreakpointClass = function currentBreakpointClass() { 27473 return BREAKPOINT_CLASSES[this.breakpoint_] || ''; 27474 } 27475 /** 27476 * An object that describes a single piece of media. 27477 * 27478 * Properties that are not part of this type description will be retained; so, 27479 * this can be viewed as a generic metadata storage mechanism as well. 27480 * 27481 * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface} 27482 * @typedef {Object} Player~MediaObject 27483 * 27484 * @property {string} [album] 27485 * Unused, except if this object is passed to the `MediaSession` 27486 * API. 27487 * 27488 * @property {string} [artist] 27489 * Unused, except if this object is passed to the `MediaSession` 27490 * API. 27491 * 27492 * @property {Object[]} [artwork] 27493 * Unused, except if this object is passed to the `MediaSession` 27494 * API. If not specified, will be populated via the `poster`, if 27495 * available. 27496 * 27497 * @property {string} [poster] 27498 * URL to an image that will display before playback. 27499 * 27500 * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src] 27501 * A single source object, an array of source objects, or a string
27502 * referencing a URL to a media source. It is _highly recommended_ 27503 * that an object or array of objects is used here, so that source 27504 * selection algorithms can take the `type` into account. 27505 * 27506 * @property {string} [title] 27507 * Unused, except if this object is passed to the `MediaSession` 27508 * API. 27509 * 27510 * @property {Object[]} [textTracks] 27511 * An array of objects to be used to create text tracks, following 27512 * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}. 27513 * For ease of removal, these will be created as "remote" text 27514 * tracks and set to automatically clean up on source changes. 27515 * 27516 * These objects may have properties like `src`, `kind`, `label`, 27517 * and `language`, see {@link Tech#createRemoteTextTrack}. 27518 */ 27519 27520 /** 27521 * Populate the player using a {@link Player~MediaObject|MediaObject}. 27522 * 27523 * @param {Player~MediaObject} media 27524 * A media object. 27525 * 27526 * @param {Function} ready 27527 * A callback to be called when the player is ready. 27528 */ 27529 ; 27530 27531 _proto.loadMedia = function loadMedia(media, ready) { 27532 var _this17 = this; 27533 27534 if (!media || typeof media !== 'object') { 27535 return; 27536 } 27537 27538 this.reset(); // Clone the media object so it cannot be mutated from outside. 27539 27540 this.cache_.media = mergeOptions(media); 27541 var _this$cache_$media = this.cache_.media, 27542 artwork = _this$cache_$media.artwork, 27543 poster = _this$cache_$media.poster, 27544 src = _this$cache_$media.src, 27545 textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`. 27546 27547 if (!artwork && poster) { 27548 this.cache_.media.artwork = [{ 27549 src: poster, 27550 type: getMimetype(poster) 27551 }]; 27552 } 27553 27554 if (src) { 27555 this.src(src); 27556 } 27557 27558 if (poster) { 27559 this.poster(poster); 27560 } 27561 27562 if (Array.isArray(textTracks)) { 27563 textTracks.forEach(function (tt) { 27564 return _this17.addRemoteTextTrack(tt, false); 27565 }); 27566 } 27567 27568 this.ready(ready); 27569 } 27570 /** 27571 * Get a clone of the current {@link Player~MediaObject} for this player. 27572 * 27573 * If the `loadMedia` method has not been used, will attempt to return a 27574 * {@link Player~MediaObject} based on the current state of the player. 27575 * 27576 * @return {Player~MediaObject} 27577 */ 27578 ; 27579 27580 _proto.getMedia = function getMedia() { 27581 if (!this.cache_.media) { 27582 var poster = this.poster(); 27583 var src = this.currentSources(); 27584 var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) { 27585 return { 27586 kind: tt.kind, 27587 label: tt.label, 27588 language: tt.language, 27589 src: tt.src 27590 }; 27591 }); 27592 var media = { 27593 src: src, 27594 textTracks: textTracks 27595 }; 27596 27597 if (poster) { 27598 media.poster = poster; 27599 media.artwork = [{ 27600 src: media.poster, 27601 type: getMimetype(media.poster) 27602 }]; 27603 } 27604 27605 return media; 27606 } 27607 27608 return mergeOptions(this.cache_.media); 27609 } 27610 /** 27611 * Gets tag settings 27612 * 27613 * @param {Element} tag 27614 * The player tag 27615 * 27616 * @return {Object} 27617 * An object containing all of the settings 27618 * for a player tag 27619 */ 27620 ; 27621 27622 Player.getTagSettings = function getTagSettings(tag) { 27623 var baseOptions = { 27624 sources: [], 27625 tracks: [] 27626 }; 27627 var tagOptions = getAttributes(tag); 27628 var dataSetup = tagOptions['data-setup']; 27629 27630 if (hasClass(tag, 'vjs-fill')) { 27631 tagOptions.fill = true; 27632 } 27633 27634 if (hasClass(tag, 'vjs-fluid')) { 27635 tagOptions.fluid = true; 27636 } // Check if data-setup attr exists. 27637 27638 27639 if (dataSetup !== null) { 27640 // Parse options JSON 27641 // If empty string, make it a parsable json object. 27642 var _safeParseTuple = tuple(dataSetup || '{}'), 27643 err = _safeParseTuple[0], 27644 data = _safeParseTuple[1];
vendor: 7,667 bytes, lines 27645-27874
27645 27646 if (err) { 27647 log.error(err); 27648 } 27649 27650 assign(tagOptions, data); 27651 } 27652 27653 assign(baseOptions, tagOptions); // Get tag children settings 27654 27655 if (tag.hasChildNodes()) { 27656 var children = tag.childNodes; 27657 27658 for (var i = 0, j = children.length; i < j; i++) { 27659 var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ 27660 27661 var childName = child.nodeName.toLowerCase(); 27662 27663 if (childName === 'source') { 27664 baseOptions.sources.push(getAttributes(child)); 27665 } else if (childName === 'track') { 27666 baseOptions.tracks.push(getAttributes(child)); 27667 } 27668 } 27669 } 27670 27671 return baseOptions; 27672 } 27673 /** 27674 * Determine whether or not flexbox is supported 27675 * 27676 * @return {boolean} 27677 * - true if flexbox is supported 27678 * - false if flexbox is not supported 27679 */ 27680 ; 27681 27682 _proto.flexNotSupported_ = function flexNotSupported_() { 27683 var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more 27684 // common flex features that we can rely on when checking for flex support. 27685 27686 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 27687 'msFlexOrder' in elem.style); 27688 }; 27689 27690 return Player; 27691 }(Component); 27692 /** 27693 * Get the {@link VideoTrackList} 27694 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist 27695 * 27696 * @return {VideoTrackList} 27697 * the current video track list 27698 * 27699 * @method Player.prototype.videoTracks 27700 */ 27701 27702 /** 27703 * Get the {@link AudioTrackList} 27704 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist 27705 * 27706 * @return {AudioTrackList} 27707 * the current audio track list 27708 * 27709 * @method Player.prototype.audioTracks 27710 */ 27711 27712 /** 27713 * Get the {@link TextTrackList} 27714 * 27715 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks 27716 * 27717 * @return {TextTrackList} 27718 * the current text track list 27719 * 27720 * @method Player.prototype.textTracks 27721 */ 27722 27723 /** 27724 * Get the remote {@link TextTrackList} 27725 * 27726 * @return {TextTrackList} 27727 * The current remote text track list 27728 * 27729 * @method Player.prototype.remoteTextTracks 27730 */ 27731 27732 /** 27733 * Get the remote {@link HtmlTrackElementList} tracks. 27734 * 27735 * @return {HtmlTrackElementList} 27736 * The current remote text track element list 27737 * 27738 * @method Player.prototype.remoteTextTrackEls 27739 */ 27740 27741 27742 ALL.names.forEach(function (name) { 27743 var props = ALL[name]; 27744 27745 Player.prototype[props.getterName] = function () { 27746 if (this.tech_) { 27747 return this.tech_[props.getterName](); 27748 } // if we have not yet loadTech_, we create {video,audio,text}Tracks_ 27749 // these will be passed to the tech during loading 27750 27751 27752 this[props.privateName] = this[props.privateName] || new props.ListClass(); 27753 return this[props.privateName]; 27754 }; 27755 }); 27756 /** 27757 * Global enumeration of players. 27758 * 27759 * The keys are the player IDs and the values are either the {@link Player} 27760 * instance or `null` for disposed players. 27761 * 27762 * @type {Object} 27763 */ 27764 27765 Player.players = {}; 27766 var navigator = window$1.navigator; 27767 /* 27768 * Player instance options, surfaced using options 27769 * options = Player.prototype.options_ 27770 * Make changes in options, not here. 27771 * 27772 * @type {Object} 27773 * @private 27774 */ 27775 27776 Player.prototype.options_ = { 27777 // Default order of fallback technology 27778 techOrder: Tech.defaultTechOrder_, 27779 html5: {}, 27780 flash: {}, 27781 // default inactivity timeout 27782 inactivityTimeout: 2000, 27783 // default playback rates 27784 playbackRates: [], 27785 // Add playback rate selection by adding rates 27786 // 'playbackRates': [0.5, 1, 1.5, 2], 27787 liveui: false, 27788 // Included control sets 27789 children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'], 27790 language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', 27791 // locales and their language translations 27792 languages: {}, 27793 // Default message to show when a video cannot be played. 27794 notSupportedMessage: 'No compatible source was found for this media.', 27795 fullscreen: { 27796 options: { 27797 navigationUI: 'hide' 27798 } 27799 }, 27800 breakpoints: {}, 27801 responsive: false 27802 }; 27803 [ 27804 /** 27805 * Returns whether or not the player is in the "ended" state. 27806 * 27807 * @return {Boolean} True if the player is in the ended state, false if not. 27808 * @method Player#ended 27809 */ 27810 'ended', 27811 /** 27812 * Returns whether or not the player is in the "seeking" state. 27813 * 27814 * @return {Boolean} True if the player is in the seeking state, false if not. 27815 * @method Player#seeking 27816 */ 27817 'seeking', 27818 /** 27819 * Returns the TimeRanges of the media that are currently available 27820 * for seeking to. 27821 * 27822 * @return {TimeRanges} the seekable intervals of the media timeline 27823 * @method Player#seekable 27824 */ 27825 'seekable', 27826 /** 27827 * Returns the current state of network activity for the element, from 27828 * the codes in the list below. 27829 * - NETWORK_EMPTY (numeric value 0) 27830 * The element has not yet been initialised. All attributes are in 27831 * their initial states. 27832 * - NETWORK_IDLE (numeric value 1) 27833 * The element's resource selection algorithm is active and has 27834 * selected a resource, but it is not actually using the network at 27835 * this time. 27836 * - NETWORK_LOADING (numeric value 2) 27837 * The user agent is actively trying to download data. 27838 * - NETWORK_NO_SOURCE (numeric value 3) 27839 * The element's resource selection algorithm is active, but it has 27840 * not yet found a resource to use. 27841 * 27842 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states 27843 * @return {number} the current network activity state 27844 * @method Player#networkState 27845 */ 27846 'networkState', 27847 /** 27848 * Returns a value that expresses the current state of the element 27849 * with respect to rendering the current playback position, from the 27850 * codes in the list below. 27851 * - HAVE_NOTHING (numeric value 0) 27852 * No information regarding the media resource is available. 27853 * - HAVE_METADATA (numeric value 1) 27854 * Enough of the resource has been obtained that the duration of the 27855 * resource is available. 27856 * - HAVE_CURRENT_DATA (numeric value 2) 27857 * Data for the immediate current playback position is available. 27858 * - HAVE_FUTURE_DATA (numeric value 3) 27859 * Data for the immediate current playback position is available, as 27860 * well as enough data for the user agent to advance the current 27861 * playback position in the direction of playback. 27862 * - HAVE_ENOUGH_DATA (numeric value 4) 27863 * The user agent estimates that enough data is available for 27864 * playback to proceed uninterrupted. 27865 * 27866 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate 27867 * @return {number} the current playback rendering state 27868 * @method Player#readyState 27869 */ 27870 'readyState'].forEach(function (fn) { 27871 Player.prototype[fn] = function () { 27872 return this.techGet_(fn); 27873 }; 27874 });
27875 TECH_EVENTS_RETRIGGER.forEach(function (event) { 27876 Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () { 27877 return this.trigger(event); 27878 }; 27879 }); 27880 /** 27881 * Fired when the player has initial duration and dimension information 27882 * 27883 * @event Player#loadedmetadata 27884 * @type {EventTarget~Event} 27885 */ 27886 27887 /** 27888 * Fired when the player has downloaded data at the current playback position 27889 * 27890 * @event Player#loadeddata 27891 * @type {EventTarget~Event} 27892 */ 27893 27894 /** 27895 * Fired when the current playback position has changed * 27896 * During playback this is fired every 15-250 milliseconds, depending on the 27897 * playback technology in use. 27898 * 27899 * @event Player#timeupdate 27900 * @type {EventTarget~Event} 27901 */ 27902 27903 /** 27904 * Fired when the volume changes 27905 * 27906 * @event Player#volumechange 27907 * @type {EventTarget~Event} 27908 */ 27909 27910 /** 27911 * Reports whether or not a player has a plugin available. 27912 * 27913 * This does not report whether or not the plugin has ever been initialized 27914 * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. 27915 * 27916 * @method Player#hasPlugin 27917 * @param {string} name 27918 * The name of a plugin. 27919 * 27920 * @return {boolean} 27921 * Whether or not this player has the requested plugin available. 27922 */ 27923 27924 /** 27925 * Reports whether or not a player is using a plugin by name. 27926 * 27927 * For basic plugins, this only reports whether the plugin has _ever_ been 27928 * initialized on this player. 27929 * 27930 * @method Player#usingPlugin 27931 * @param {string} name 27932 * The name of a plugin. 27933 * 27934 * @return {boolean} 27935 * Whether or not this player is using the requested plugin. 27936 */ 27937 27938 Component.registerComponent('Player', Player); 27939 27940 var setPrototypeOf = createCommonjsModule(function (module) { 27941 function _setPrototypeOf(o, p) { 27942 module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 27943 o.__proto__ = p; 27944 return o; 27945 }; 27946 27947 return _setPrototypeOf(o, p); 27948 } 27949 27950 module.exports = _setPrototypeOf; 27951 }); 27952 27953 var construct = createCommonjsModule(function (module) { 27954 function isNativeReflectConstruct() { 27955 if (typeof Reflect === "undefined" || !Reflect.construct) return false; 27956 if (Reflect.construct.sham) return false; 27957 if (typeof Proxy === "function") return true; 27958 27959 try { 27960 Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 27961 return true; 27962 } catch (e) { 27963 return false; 27964 } 27965 } 27966 27967 function _construct(Parent, args, Class) { 27968 if (isNativeReflectConstruct()) { 27969 module.exports = _construct = Reflect.construct; 27970 } else { 27971 module.exports = _construct = function _construct(Parent, args, Class) { 27972 var a = [null]; 27973 a.push.apply(a, args); 27974 var Constructor = Function.bind.apply(Parent, a); 27975 var instance = new Constructor(); 27976 if (Class) setPrototypeOf(instance, Class.prototype); 27977 return instance; 27978 }; 27979 } 27980 27981 return _construct.apply(null, arguments); 27982 } 27983 27984 module.exports = _construct; 27985 }); 27986 27987 /** 27988 * The base plugin name. 27989 * 27990 * @private 27991 * @constant 27992 * @type {string} 27993 */ 27994 27995 var BASE_PLUGIN_NAME = 'plugin'; 27996 /** 27997 * The key on which a player's active plugins cache is stored. 27998 * 27999 * @private 28000 * @constant 28001 * @type {string} 28002 */ 28003 28004 var PLUGIN_CACHE_KEY = 'activePlugins_'; 28005 /** 28006 * Stores registered plugins in a private space. 28007 * 28008 * @private 28009 * @type {Object} 28010 */ 28011 28012 var pluginStorage = {}; 28013 /** 28014 * Reports whether or not a plugin has been registered. 28015 * 28016 * @private 28017 * @param {string} name 28018 * The name of a plugin. 28019 * 28020 * @return {boolean} 28021 * Whether or not the plugin has been registered. 28022 */ 28023 28024 var pluginExists = function pluginExists(name) { 28025 return pluginStorage.hasOwnProperty(name); 28026 }; 28027 /** 28028 * Get a single registered plugin by name. 28029 * 28030 * @private 28031 * @param {string} name 28032 * The name of a plugin. 28033 * 28034 * @return {Function|undefined} 28035 * The plugin (or undefined). 28036 */ 28037 28038 28039 var getPlugin = function getPlugin(name) { 28040 return pluginExists(name) ? pluginStorage[name] : undefined; 28041 }; 28042 /** 28043 * Marks a plugin as "active" on a player. 28044 * 28045 * Also, ensures that the player has an object for tracking active plugins. 28046 * 28047 * @private 28048 * @param {Player} player 28049 * A Video.js player instance. 28050 * 28051 * @param {string} name 28052 * The name of a plugin. 28053 */ 28054 28055 28056 var markPluginAsActive = function markPluginAsActive(player, name) { 28057 player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; 28058 player[PLUGIN_CACHE_KEY][name] = true; 28059 }; 28060 /** 28061 * Triggers a pair of plugin setup events. 28062 * 28063 * @private 28064 * @param {Player} player 28065 * A Video.js player instance. 28066 * 28067 * @param {Plugin~PluginEventHash} hash 28068 * A plugin event hash. 28069 * 28070 * @param {boolean} [before] 28071 * If true, prefixes the event name with "before". In other words, 28072 * use this to trigger "beforepluginsetup" instead of "pluginsetup". 28073 */ 28074 28075 28076 var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { 28077 var eventName = (before ? 'before' : '') + 'pluginsetup'; 28078 player.trigger(eventName, hash); 28079 player.trigger(eventName + ':' + hash.name, hash); 28080 }; 28081 /** 28082 * Takes a basic plugin function and returns a wrapper function which marks 28083 * on the player that the plugin has been activated. 28084 * 28085 * @private 28086 * @param {string} name 28087 * The name of the plugin. 28088 * 28089 * @param {Function} plugin 28090 * The basic plugin. 28091 * 28092 * @return {Function} 28093 * A wrapper function for the given plugin. 28094 */ 28095 28096 28097 var createBasicPlugin = function createBasicPlugin(name, plugin) { 28098 var basicPluginWrapper = function basicPluginWrapper() { 28099 // We trigger the "beforepluginsetup" and "pluginsetup" events on the player 28100 // regardless, but we want the hash to be consistent with the hash provided 28101 // for advanced plugins. 28102 // 28103 // The only potentially counter-intuitive thing here is the `instance` in 28104 // the "pluginsetup" event is the value returned by the `plugin` function. 28105 triggerSetupEvent(this, { 28106 name: name, 28107 plugin: plugin, 28108 instance: null 28109 }, true); 28110 var instance = plugin.apply(this, arguments); 28111 markPluginAsActive(this, name); 28112 triggerSetupEvent(this, { 28113 name: name, 28114 plugin: plugin, 28115 instance: instance 28116 }); 28117 return instance; 28118 }; 28119
28120 Object.keys(plugin).forEach(function (prop) { 28121 basicPluginWrapper[prop] = plugin[prop]; 28122 }); 28123 return basicPluginWrapper; 28124 }; 28125 /** 28126 * Takes a plugin sub-class and returns a factory function for generating 28127 * instances of it. 28128 * 28129 * This factory function will replace itself with an instance of the requested 28130 * sub-class of Plugin. 28131 * 28132 * @private 28133 * @param {string} name 28134 * The name of the plugin. 28135 * 28136 * @param {Plugin} PluginSubClass 28137 * The advanced plugin. 28138 * 28139 * @return {Function} 28140 */ 28141 28142 28143 var createPluginFactory = function createPluginFactory(name, PluginSubClass) { 28144 // Add a `name` property to the plugin prototype so that each plugin can 28145 // refer to itself by name. 28146 PluginSubClass.prototype.name = name; 28147 return function () { 28148 triggerSetupEvent(this, { 28149 name: name, 28150 plugin: PluginSubClass, 28151 instance: null 28152 }, true); 28153 28154 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 28155 args[_key] = arguments[_key]; 28156 } 28157 28158 var instance = construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance. 28159 28160 28161 this[name] = function () { 28162 return instance; 28163 }; 28164 28165 triggerSetupEvent(this, instance.getEventHash()); 28166 return instance; 28167 }; 28168 }; 28169 /** 28170 * Parent class for all advanced plugins. 28171 * 28172 * @mixes module:evented~EventedMixin 28173 * @mixes module:stateful~StatefulMixin 28174 * @fires Player#beforepluginsetup 28175 * @fires Player#beforepluginsetup:$name 28176 * @fires Player#pluginsetup 28177 * @fires Player#pluginsetup:$name 28178 * @listens Player#dispose 28179 * @throws {Error} 28180 * If attempting to instantiate the base {@link Plugin} class 28181 * directly instead of via a sub-class. 28182 */ 28183 28184 28185 var Plugin = 28186 /*#__PURE__*/ 28187 function () { 28188 /** 28189 * Creates an instance of this class. 28190 * 28191 * Sub-classes should call `super` to ensure plugins are properly initialized. 28192 * 28193 * @param {Player} player 28194 * A Video.js player instance. 28195 */ 28196 function Plugin(player) { 28197 if (this.constructor === Plugin) { 28198 throw new Error('Plugin must be sub-classed; not directly instantiated.'); 28199 } 28200 28201 this.player = player; // Make this object evented, but remove the added `trigger` method so we 28202 // use the prototype version instead. 28203 28204 evented(this); 28205 delete this.trigger; 28206 stateful(this, this.constructor.defaultState); 28207 markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind 28208 // it later easily. 28209 28210 this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin. 28211 28212 player.on('dispose', this.dispose); 28213 } 28214 /** 28215 * Get the version of the plugin that was set on <pluginName>.VERSION 28216 */ 28217 28218 28219 var _proto = Plugin.prototype; 28220 28221 _proto.version = function version() { 28222 return this.constructor.VERSION; 28223 } 28224 /** 28225 * Each event triggered by plugins includes a hash of additional data with 28226 * conventional properties. 28227 * 28228 * This returns that object or mutates an existing hash. 28229 * 28230 * @param {Object} [hash={}] 28231 * An object to be used as event an event hash. 28232 * 28233 * @return {Plugin~PluginEventHash} 28234 * An event hash object with provided properties mixed-in. 28235 */ 28236 ; 28237 28238 _proto.getEventHash = function getEventHash(hash) { 28239 if (hash === void 0) { 28240 hash = {}; 28241 } 28242 28243 hash.name = this.name; 28244 hash.plugin = this.constructor; 28245 hash.instance = this; 28246 return hash; 28247 } 28248 /** 28249 * Triggers an event on the plugin object and overrides 28250 * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. 28251 * 28252 * @param {string|Object} event 28253 * An event type or an object with a type property. 28254 * 28255 * @param {Object} [hash={}] 28256 * Additional data hash to merge with a 28257 * {@link Plugin~PluginEventHash|PluginEventHash}. 28258 * 28259 * @return {boolean} 28260 * Whether or not default was prevented. 28261 */ 28262 ; 28263 28264 _proto.trigger = function trigger$1(event, hash) { 28265 if (hash === void 0) { 28266 hash = {}; 28267 } 28268 28269 return trigger(this.eventBusEl_, event, this.getEventHash(hash)); 28270 } 28271 /** 28272 * Handles "statechanged" events on the plugin. No-op by default, override by 28273 * subclassing. 28274 * 28275 * @abstract 28276 * @param {Event} e 28277 * An event object provided by a "statechanged" event. 28278 * 28279 * @param {Object} e.changes 28280 * An object describing changes that occurred with the "statechanged" 28281 * event. 28282 */ 28283 ; 28284 28285 _proto.handleStateChanged = function handleStateChanged(e) {} 28286 /**
vendor: 4,134 bytes, lines 28287-28401
28287 * Disposes a plugin. 28288 * 28289 * Subclasses can override this if they want, but for the sake of safety, 28290 * it's probably best to subscribe the "dispose" event. 28291 * 28292 * @fires Plugin#dispose 28293 */ 28294 ; 28295 28296 _proto.dispose = function dispose() { 28297 var name = this.name, 28298 player = this.player; 28299 /** 28300 * Signals that a advanced plugin is about to be disposed. 28301 * 28302 * @event Plugin#dispose 28303 * @type {EventTarget~Event} 28304 */ 28305 28306 this.trigger('dispose'); 28307 this.off(); 28308 player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up 28309 // references between the player and the plugin instance and nulling out 28310 // the plugin's state and replacing methods with a function that throws. 28311 28312 player[PLUGIN_CACHE_KEY][name] = false; 28313 this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory 28314 // function, so that the plugin is ready to be set up again. 28315 28316 player[name] = createPluginFactory(name, pluginStorage[name]); 28317 } 28318 /** 28319 * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). 28320 * 28321 * @param {string|Function} plugin 28322 * If a string, matches the name of a plugin. If a function, will be 28323 * tested directly. 28324 * 28325 * @return {boolean} 28326 * Whether or not a plugin is a basic plugin. 28327 */ 28328 ; 28329 28330 Plugin.isBasic = function isBasic(plugin) { 28331 var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; 28332 return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); 28333 } 28334 /** 28335 * Register a Video.js plugin. 28336 * 28337 * @param {string} name 28338 * The name of the plugin to be registered. Must be a string and 28339 * must not match an existing plugin or a method on the `Player` 28340 * prototype. 28341 * 28342 * @param {Function} plugin 28343 * A sub-class of `Plugin` or a function for basic plugins. 28344 * 28345 * @return {Function} 28346 * For advanced plugins, a factory function for that plugin. For 28347 * basic plugins, a wrapper function that initializes the plugin. 28348 */ 28349 ; 28350 28351 Plugin.registerPlugin = function registerPlugin(name, plugin) { 28352 if (typeof name !== 'string') { 28353 throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + "."); 28354 } 28355 28356 if (pluginExists(name)) { 28357 log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!"); 28358 } else if (Player.prototype.hasOwnProperty(name)) { 28359 throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!"); 28360 } 28361 28362 if (typeof plugin !== 'function') { 28363 throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + "."); 28364 } 28365 28366 pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for 28367 // the base Plugin class). 28368 28369 if (name !== BASE_PLUGIN_NAME) { 28370 if (Plugin.isBasic(plugin)) { 28371 Player.prototype[name] = createBasicPlugin(name, plugin); 28372 } else { 28373 Player.prototype[name] = createPluginFactory(name, plugin); 28374 } 28375 } 28376 28377 return plugin; 28378 } 28379 /** 28380 * De-register a Video.js plugin. 28381 * 28382 * @param {string} name 28383 * The name of the plugin to be de-registered. Must be a string that 28384 * matches an existing plugin. 28385 * 28386 * @throws {Error} 28387 * If an attempt is made to de-register the base plugin. 28388 */ 28389 ; 28390 28391 Plugin.deregisterPlugin = function deregisterPlugin(name) { 28392 if (name === BASE_PLUGIN_NAME) { 28393 throw new Error('Cannot de-register base plugin.'); 28394 } 28395 28396 if (pluginExists(name)) { 28397 delete pluginStorage[name]; 28398 delete Player.prototype[name]; 28399 } 28400 } 28401 /**
28402 * Gets an object containing multiple Video.js plugins. 28403 * 28404 * @param {Array} [names] 28405 * If provided, should be an array of plugin names. Defaults to _all_ 28406 * plugin names. 28407 * 28408 * @return {Object|undefined} 28409 * An object containing plugin(s) associated with their name(s) or 28410 * `undefined` if no matching plugins exist). 28411 */ 28412 ; 28413 28414 Plugin.getPlugins = function getPlugins(names) { 28415 if (names === void 0) { 28416 names = Object.keys(pluginStorage); 28417 } 28418 28419 var result; 28420 names.forEach(function (name) { 28421 var plugin = getPlugin(name); 28422 28423 if (plugin) { 28424 result = result || {}; 28425 result[name] = plugin; 28426 } 28427 }); 28428 return result; 28429 } 28430 /** 28431 * Gets a plugin's version, if available 28432 * 28433 * @param {string} name 28434 * The name of a plugin. 28435 * 28436 * @return {string} 28437 * The plugin's version or an empty string. 28438 */ 28439 ; 28440 28441 Plugin.getPluginVersion = function getPluginVersion(name) { 28442 var plugin = getPlugin(name); 28443 return plugin && plugin.VERSION || ''; 28444 }; 28445 28446 return Plugin; 28447 }(); 28448 /** 28449 * Gets a plugin by name if it exists. 28450 * 28451 * @static 28452 * @method getPlugin 28453 * @memberOf Plugin 28454 * @param {string} name 28455 * The name of a plugin. 28456 * 28457 * @returns {Function|undefined} 28458 * The plugin (or `undefined`). 28459 */ 28460 28461 28462 Plugin.getPlugin = getPlugin; 28463 /** 28464 * The name of the base plugin class as it is registered. 28465 * 28466 * @type {string} 28467 */ 28468 28469 Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; 28470 Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); 28471 /** 28472 * Documented in player.js 28473 * 28474 * @ignore 28475 */ 28476 28477 Player.prototype.usingPlugin = function (name) { 28478 return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; 28479 }; 28480 /** 28481 * Documented in player.js 28482 * 28483 * @ignore 28484 */ 28485 28486 28487 Player.prototype.hasPlugin = function (name) { 28488 return !!pluginExists(name); 28489 }; 28490 /** 28491 * Signals that a plugin is about to be set up on a player. 28492 * 28493 * @event Player#beforepluginsetup 28494 * @type {Plugin~PluginEventHash} 28495 */ 28496 28497 /** 28498 * Signals that a plugin is about to be set up on a player - by name. The name 28499 * is the name of the plugin. 28500 * 28501 * @event Player#beforepluginsetup:$name 28502 * @type {Plugin~PluginEventHash} 28503 */ 28504 28505 /** 28506 * Signals that a plugin has just been set up on a player. 28507 * 28508 * @event Player#pluginsetup 28509 * @type {Plugin~PluginEventHash} 28510 */ 28511 28512 /** 28513 * Signals that a plugin has just been set up on a player - by name. The name 28514 * is the name of the plugin. 28515 * 28516 * @event Player#pluginsetup:$name 28517 * @type {Plugin~PluginEventHash} 28518 */ 28519 28520 /** 28521 * @typedef {Object} Plugin~PluginEventHash 28522 * 28523 * @property {string} instance 28524 * For basic plugins, the return value of the plugin function. For 28525 * advanced plugins, the plugin instance on which the event is fired. 28526 * 28527 * @property {string} name 28528 * The name of the plugin. 28529 * 28530 * @property {string} plugin 28531 * For basic plugins, the plugin function. For advanced plugins, the 28532 * plugin class/constructor. 28533 */ 28534 28535 function _inherits(subClass, superClass) { 28536 if (typeof superClass !== "function" && superClass !== null) { 28537 throw new TypeError("Super expression must either be null or a function"); 28538 } 28539 28540 subClass.prototype = Object.create(superClass && superClass.prototype, { 28541 constructor: { 28542 value: subClass, 28543 writable: true, 28544 configurable: true 28545 } 28546 }); 28547 if (superClass) setPrototypeOf(subClass, superClass); 28548 } 28549 28550 var inherits = _inherits; 28551 28552 /** 28553 * @file extend.js 28554 * @module extend 28555 */ 28556 /** 28557 * Used to subclass an existing class by emulating ES subclassing using the 28558 * `extends` keyword. 28559 * 28560 * @function 28561 * @example 28562 * var MyComponent = videojs.extend(videojs.getComponent('Component'), { 28563 * myCustomMethod: function() { 28564 * // Do things in my method. 28565 * } 28566 * }); 28567 * 28568 * @param {Function} superClass 28569 * The class to inherit from 28570 * 28571 * @param {Object} [subClassMethods={}] 28572 * Methods of the new class 28573 * 28574 * @return {Function} 28575 * The new class with subClassMethods that inherited superClass. 28576 */ 28577 28578 var extend = function extend(superClass, subClassMethods) { 28579 if (subClassMethods === void 0) { 28580 subClassMethods = {}; 28581 } 28582 28583 var subClass = function subClass() { 28584 superClass.apply(this, arguments); 28585 }; 28586 28587 var methods = {}; 28588 28589 if (typeof subClassMethods === 'object') { 28590 if (subClassMethods.constructor !== Object.prototype.constructor) { 28591 subClass = subClassMethods.constructor; 28592 } 28593 28594 methods = subClassMethods; 28595 } else if (typeof subClassMethods === 'function') { 28596 subClass = subClassMethods; 28597 } 28598 28599 inherits(subClass, superClass); // this is needed for backward-compatibility and node compatibility. 28600 28601 28602 if (superClass) { 28603 subClass.super_ = superClass; 28604 } // Extend subObj's prototype with functions and other properties from props 28605 28606 28607 for (var name in methods) { 28608 if (methods.hasOwnProperty(name)) { 28609 subClass.prototype[name] = methods[name]; 28610 } 28611 } 28612 28613 return subClass; 28614 }; 28615 28616 /** 28617 * @file video.js 28618 * @module videojs 28619 */ 28620 /** 28621 * Normalize an `id` value by trimming off a leading `#` 28622 * 28623 * @private 28624 * @param {string} id 28625 * A string, maybe with a leading `#`. 28626 * 28627 * @return {string} 28628 * The string, without any leading `#`. 28629 */ 28630 28631 var normalizeId = function normalizeId(id) { 28632 return id.indexOf('#') === 0 ? id.slice(1) : id; 28633 }; 28634 /** 28635 * The `videojs()` function doubles as the main function for users to create a 28636 * {@link Player} instance as well as the main library namespace. 28637 * 28638 * It can also be used as a getter for a pre-existing {@link Player} instance. 28639 * However, we _strongly_ recommend using `videojs.getPlayer()` for this 28640 * purpose because it avoids any potential for unintended initialization. 28641 * 28642 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 28643 * of our JSDoc template, we cannot properly document this as both a function 28644 * and a namespace, so its function signature is documented here. 28645 * 28646 * #### Arguments 28647 * ##### id 28648 * string|Element, **required** 28649 * 28650 * Video element or video element ID. 28651 * 28652 * ##### options 28653 * Object, optional 28654 * 28655 * Options object for providing settings. 28656 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 28657 * 28658 * ##### ready 28659 * {@link Component~ReadyCallback}, optional 28660 * 28661 * A function to be called when the {@link Player} and {@link Tech} are ready. 28662 * 28663 * #### Return Value 28664 * 28665 * The `videojs()` function returns a {@link Player} instance. 28666 * 28667 * @namespace 28668 * 28669 * @borrows AudioTrack as AudioTrack 28670 * @borrows Component.getComponent as getComponent 28671 * @borrows module:computed-style~computedStyle as computedStyle 28672 * @borrows module:events.on as on 28673 * @borrows module:events.one as one 28674 * @borrows module:events.off as off 28675 * @borrows module:events.trigger as trigger 28676 * @borrows EventTarget as EventTarget 28677 * @borrows module:extend~extend as extend 28678 * @borrows module:fn.bind as bind 28679 * @borrows module:format-time.formatTime as formatTime 28680 * @borrows module:format-time.resetFormatTime as resetFormatTime 28681 * @borrows module:format-time.setFormatTime as setFormatTime 28682 * @borrows module:merge-options.mergeOptions as mergeOptions 28683 * @borrows module:middleware.use as use 28684 * @borrows Player.players as players 28685 * @borrows Plugin.registerPlugin as registerPlugin 28686 * @borrows Plugin.deregisterPlugin as deregisterPlugin 28687 * @borrows Plugin.getPlugins as getPlugins 28688 * @borrows Plugin.getPlugin as getPlugin 28689 * @borrows Plugin.getPluginVersion as getPluginVersion 28690 * @borrows Tech.getTech as getTech 28691 * @borrows Tech.registerTech as registerTech 28692 * @borrows TextTrack as TextTrack 28693 * @borrows module:time-ranges.createTimeRanges as createTimeRange 28694 * @borrows module:time-ranges.createTimeRanges as createTimeRanges 28695 * @borrows module:url.isCrossOrigin as isCrossOrigin 28696 * @borrows module:url.parseUrl as parseUrl 28697 * @borrows VideoTrack as VideoTrack 28698 * 28699 * @param {string|Element}
28699 id 28700 * Video element or video element ID. 28701 * 28702 * @param {Object} [options] 28703 * Options object for providing settings. 28704 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 28705 * 28706 * @param {Component~ReadyCallback} [ready] 28707 * A function to be called when the {@link Player} and {@link Tech} are 28708 * ready. 28709 * 28710 * @return {Player} 28711 * The `videojs()` function returns a {@link Player|Player} instance. 28712 */ 28713 28714 28715 function videojs$1(id, options, ready) { 28716 var player = videojs$1.getPlayer(id); 28717 28718 if (player) { 28719 if (options) { 28720 log.warn("Player \"" + id + "\" is already initialised. Options will not be applied."); 28721 } 28722 28723 if (ready) { 28724 player.ready(ready); 28725 } 28726 28727 return player; 28728 } 28729 28730 var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id; 28731 28732 if (!isEl(el)) { 28733 throw new TypeError('The element or ID supplied is not valid. (videojs)'); 28734 } // document.body.contains(el) will only check if el is contained within that one document. 28735 // This causes problems for elements in iframes. 28736 // Instead, use the element's ownerDocument instead of the global document. 28737 // This will make sure that the element is indeed in the dom of that document. 28738 // Additionally, check that the document in question has a default view. 28739 // If the document is no longer attached to the dom, the defaultView of the document will be null. 28740 28741 28742 if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) { 28743 log.warn('The element supplied is not included in the DOM'); 28744 } 28745 28746 options = options || {}; 28747 videojs$1.hooks('beforesetup').forEach(function (hookFunction) { 28748 var opts = hookFunction(el, mergeOptions(options)); 28749 28750 if (!isObject(opts) || Array.isArray(opts)) { 28751 log.error('please return an object in beforesetup hooks'); 28752 return; 28753 } 28754 28755 options = mergeOptions(options, opts); 28756 }); // We get the current "Player" component here in case an integration has 28757 // replaced it with a custom player. 28758 28759 var PlayerComponent = Component.getComponent('Player'); 28760 player = new PlayerComponent(el, options, ready); 28761 videojs$1.hooks('setup').forEach(function (hookFunction) { 28762 return hookFunction(player); 28763 }); 28764 return player; 28765 } 28766 /** 28767 * An Object that contains lifecycle hooks as keys which point to an array 28768 * of functions that are run when a lifecycle is triggered 28769 * 28770 * @private 28771 */ 28772 28773 28774 videojs$1.hooks_ = {}; 28775 /** 28776 * Get a list of hooks for a specific lifecycle 28777 * 28778 * @param {string} type 28779 * the lifecyle to get hooks from 28780 * 28781 * @param {Function|Function[]} [fn] 28782 * Optionally add a hook (or hooks) to the lifecycle that your are getting. 28783 * 28784 * @return {Array} 28785 * an array of hooks, or an empty array if there are none. 28786 */ 28787 28788 videojs$1.hooks = function (type, fn) { 28789 videojs$1.hooks_[type] = videojs$1.hooks_[type] || []; 28790 28791 if (fn) { 28792 videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn); 28793 } 28794 28795 return videojs$1.hooks_[type]; 28796 }; 28797 /** 28798 * Add a function hook to a specific videojs lifecycle. 28799 * 28800 * @param {string} type 28801 * the lifecycle to hook the function to. 28802 * 28803 * @param {Function|Function[]} 28804 * The function or array of functions to attach. 28805 */ 28806 28807 28808 videojs$1.hook = function (type, fn) { 28809 videojs$1.hooks(type, fn); 28810 }; 28811 /** 28812 * Add a function hook that will only run once to a specific videojs lifecycle. 28813 * 28814 * @param {string} type 28815 * the lifecycle to hook the function to. 28816 * 28817 * @param {Function|Function[]} 28818 * The function or array of functions to attach. 28819 */ 28820 28821 28822 videojs$1.hookOnce = function (type, fn) { 28823 videojs$1.hooks(type, [].concat(fn).map(function (original) { 28824 var wrapper = function wrapper() { 28825 videojs$1.removeHook(type, wrapper); 28826 return original.apply(void 0, arguments); 28827 }; 28828 28829 return wrapper; 28830 })); 28831 }; 28832 /** 28833 * Remove a hook from a specific videojs lifecycle. 28834 * 28835 * @param {string} type 28836 * the lifecycle that the function hooked to 28837 * 28838 * @param {Function} fn 28839 * The hooked function to remove 28840 * 28841 * @return {boolean} 28842 * The function that was removed or undef 28843 */ 28844 28845
vendor: 13,853 bytes, lines 28846-29249
28846 videojs$1.removeHook = function (type, fn) { 28847 var index = videojs$1.hooks(type).indexOf(fn); 28848 28849 if (index <= -1) { 28850 return false; 28851 } 28852 28853 videojs$1.hooks_[type] = videojs$1.hooks_[type].slice(); 28854 videojs$1.hooks_[type].splice(index, 1); 28855 return true; 28856 }; // Add default styles 28857 28858 28859 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) { 28860 var style = $('.vjs-styles-defaults'); 28861 28862 if (!style) { 28863 style = createStyleElement('vjs-styles-defaults'); 28864 var head = $('head'); 28865 28866 if (head) { 28867 head.insertBefore(style, head.firstChild); 28868 } 28869 28870 setTextContent(style, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n "); 28871 } 28872 } // Run Auto-load players 28873 // You have to wait at least once in case this script is loaded after your 28874 // video in the DOM (weird behavior only with minified version) 28875 28876 28877 autoSetupTimeout(1, videojs$1); 28878 /** 28879 * Current Video.js version. Follows [semantic versioning](https://semver.org/). 28880 * 28881 * @type {string} 28882 */ 28883 28884 videojs$1.VERSION = version; 28885 /** 28886 * The global options object. These are the settings that take effect 28887 * if no overrides are specified when the player is created. 28888 * 28889 * @type {Object} 28890 */ 28891 28892 videojs$1.options = Player.prototype.options_; 28893 /** 28894 * Get an object with the currently created players, keyed by player ID 28895 * 28896 * @return {Object} 28897 * The created players 28898 */ 28899 28900 videojs$1.getPlayers = function () { 28901 return Player.players; 28902 }; 28903 /** 28904 * Get a single player based on an ID or DOM element. 28905 * 28906 * This is useful if you want to check if an element or ID has an associated 28907 * Video.js player, but not create one if it doesn't. 28908 * 28909 * @param {string|Element} id 28910 * An HTML element - `<video>`, `<audio>`, or `<video-js>` - 28911 * or a string matching the `id` of such an element. 28912 * 28913 * @return {Player|undefined} 28914 * A player instance or `undefined` if there is no player instance 28915 * matching the argument. 28916 */ 28917 28918 28919 videojs$1.getPlayer = function (id) { 28920 var players = Player.players; 28921 var tag; 28922 28923 if (typeof id === 'string') { 28924 var nId = normalizeId(id); 28925 var player = players[nId]; 28926 28927 if (player) { 28928 return player; 28929 } 28930 28931 tag = $('#' + nId); 28932 } else { 28933 tag = id; 28934 } 28935 28936 if (isEl(tag)) { 28937 var _tag = tag, 28938 _player = _tag.player, 28939 playerId = _tag.playerId; // Element may have a `player` property referring to an already created 28940 // player instance. If so, return that. 28941 28942 if (_player || players[playerId]) { 28943 return _player || players[playerId]; 28944 } 28945 } 28946 }; 28947 /** 28948 * Returns an array of all current players. 28949 * 28950 * @return {Array} 28951 * An array of all players. The array will be in the order that 28952 * `Object.keys` provides, which could potentially vary between 28953 * JavaScript engines. 28954 * 28955 */ 28956 28957 28958 videojs$1.getAllPlayers = function () { 28959 return (// Disposed players leave a key with a `null` value, so we need to make sure 28960 // we filter those out. 28961 Object.keys(Player.players).map(function (k) { 28962 return Player.players[k]; 28963 }).filter(Boolean) 28964 ); 28965 }; 28966 28967 videojs$1.players = Player.players; 28968 videojs$1.getComponent = Component.getComponent; 28969 /** 28970 * Register a component so it can referred to by name. Used when adding to other 28971 * components, either through addChild `component.addChild('myComponent')` or through 28972 * default children options `{ children: ['myComponent'] }`. 28973 * 28974 * > NOTE: You could also just initialize the component before adding. 28975 * `component.addChild(new MyComponent());` 28976 * 28977 * @param {string} name 28978 * The class name of the component 28979 * 28980 * @param {Component} comp 28981 * The component class 28982 * 28983 * @return {Component} 28984 * The newly registered component 28985 */ 28986 28987 videojs$1.registerComponent = function (name, comp) { 28988 if (Tech.isTech(comp)) { 28989 log.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)"); 28990 } 28991 28992 Component.registerComponent.call(Component, name, comp); 28993 }; 28994 28995 videojs$1.getTech = Tech.getTech; 28996 videojs$1.registerTech = Tech.registerTech; 28997 videojs$1.use = use; 28998 /** 28999 * An object that can be returned by a middleware to signify 29000 * that the middleware is being terminated. 29001 * 29002 * @type {object} 29003 * @property {object} middleware.TERMINATOR 29004 */ 29005 29006 Object.defineProperty(videojs$1, 'middleware', { 29007 value: {}, 29008 writeable: false, 29009 enumerable: true 29010 }); 29011 Object.defineProperty(videojs$1.middleware, 'TERMINATOR', { 29012 value: TERMINATOR, 29013 writeable: false, 29014 enumerable: true 29015 }); 29016 /** 29017 * A reference to the {@link module:browser|browser utility module} as an object. 29018 * 29019 * @type {Object} 29020 * @see {@link module:browser|browser} 29021 */ 29022 29023 videojs$1.browser = browser; 29024 /** 29025 * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only 29026 * included for backward-compatibility with 4.x. 29027 * 29028 * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead. 29029 * @type {boolean} 29030 */ 29031 29032 videojs$1.TOUCH_ENABLED = TOUCH_ENABLED; 29033 videojs$1.extend = extend; 29034 videojs$1.mergeOptions = mergeOptions; 29035 videojs$1.bind = bind; 29036 videojs$1.registerPlugin = Plugin.registerPlugin; 29037 videojs$1.deregisterPlugin = Plugin.deregisterPlugin; 29038 /** 29039 * Deprecated method to register a plugin with Video.js 29040 * 29041 * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead 29042 * 29043 * @param {string} name 29044 * The plugin name 29045 * 29046 * @param {Plugin|Function} plugin 29047 * The plugin sub-class or function 29048 */ 29049 29050 videojs$1.plugin = function (name, plugin) { 29051 log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead'); 29052 return Plugin.registerPlugin(name, plugin); 29053 }; 29054 29055 videojs$1.getPlugins = Plugin.getPlugins; 29056 videojs$1.getPlugin = Plugin.getPlugin; 29057 videojs$1.getPluginVersion = Plugin.getPluginVersion; 29058 /** 29059 * Adding languages so that they're available to all players. 29060 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });` 29061 * 29062 * @param {string} code 29063 * The language code or dictionary property 29064 * 29065 * @param {Object} data 29066 * The data values to be translated 29067 * 29068 * @return {Object} 29069 * The resulting language dictionary object 29070 */ 29071 29072 videojs$1.addLanguage = function (code, data) { 29073 var _mergeOptions; 29074 29075 code = ('' + code).toLowerCase(); 29076 videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions)); 29077 return videojs$1.options.languages[code]; 29078 }; 29079 /** 29080 * A reference to the {@link module:log|log utility module} as an object. 29081 * 29082 * @type {Function} 29083 * @see {@link module:log|log} 29084 */ 29085 29086 29087 videojs$1.log = log; 29088 videojs$1.createLogger = createLogger$1; 29089 videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges; 29090 videojs$1.formatTime = formatTime; 29091 videojs$1.setFormatTime = setFormatTime; 29092 videojs$1.resetFormatTime = resetFormatTime; 29093 videojs$1.parseUrl = parseUrl; 29094 videojs$1.isCrossOrigin = isCrossOrigin; 29095 videojs$1.EventTarget = EventTarget; 29096 videojs$1.on = on; 29097 videojs$1.one = one; 29098 videojs$1.off = off; 29099 videojs$1.trigger = trigger; 29100 /** 29101 * A cross-browser XMLHttpRequest wrapper. 29102 * 29103 * @function 29104 * @param {Object} options 29105 * Settings for the request. 29106 * 29107 * @return {XMLHttpRequest|XDomainRequest} 29108 * The request object. 29109 * 29110 * @see https://github.com/Raynos/xhr 29111 */ 29112 29113 videojs$1.xhr = xhr; 29114 videojs$1.TextTrack = TextTrack; 29115 videojs$1.AudioTrack = AudioTrack; 29116 videojs$1.VideoTrack = VideoTrack; 29117 ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) { 29118 videojs$1[k] = function () { 29119 log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead"); 29120 return Dom[k].apply(null, arguments); 29121 }; 29122 }); 29123 videojs$1.computedStyle = computedStyle; 29124 /** 29125 * A reference to the {@link module:dom|DOM utility module} as an object. 29126 * 29127 * @type {Object} 29128 * @see {@link module:dom|dom} 29129 */ 29130 29131 videojs$1.dom = Dom; 29132 /** 29133 * A reference to the {@link module:url|URL utility module} as an object. 29134 * 29135 * @type {Object} 29136 * @see {@link module:url|url} 29137 */ 29138 29139 videojs$1.url = Url; 29140 videojs$1.defineLazyProperty = defineLazyProperty; 29141 29142 var urlToolkit = createCommonjsModule(function (module, exports) { 29143 // see https://tools.ietf.org/html/rfc1808 29144 29145 /* jshint ignore:start */ 29146 (function (root) { 29147 /* jshint ignore:end */ 29148 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; 29149 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; 29150 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 29151 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; 29152 var URLToolkit = { 29153 // jshint ignore:line 29154 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 29155 // E.g 29156 // With opts.alwaysNormalize = false (default, spec compliant) 29157 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 29158 // With opts.alwaysNormalize = true (not spec compliant) 29159 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 29160 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 29161 opts = opts || {}; // remove any remaining space and CRLF 29162 29163 baseURL = baseURL.trim(); 29164 relativeURL = relativeURL.trim(); 29165 29166 if (!relativeURL) { 29167 // 2a) If the embedded URL is entirely empty, it inherits the 29168 // entire base URL (i.e., is set equal to the base URL) 29169 // and we are done. 29170 if (!opts.alwaysNormalize) { 29171 return baseURL; 29172 } 29173 29174 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 29175 29176 if (!basePartsForNormalise) { 29177 throw new Error('Error trying to parse base URL.'); 29178 } 29179 29180 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 29181 return URLToolkit.buildURLFromParts(basePartsForNormalise); 29182 } 29183 29184 var relativeParts = URLToolkit.parseURL(relativeURL); 29185 29186 if (!relativeParts) { 29187 throw new Error('Error trying to parse relative URL.'); 29188 } 29189 29190 if (relativeParts.scheme) { 29191 // 2b) If the embedded URL starts with a scheme name, it is 29192 // interpreted as an absolute URL and we are done. 29193 if (!opts.alwaysNormalize) { 29194 return relativeURL; 29195 } 29196 29197 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 29198 return URLToolkit.buildURLFromParts(relativeParts); 29199 } 29200 29201 var baseParts = URLToolkit.parseURL(baseURL); 29202 29203 if (!baseParts) { 29204 throw new Error('Error trying to parse base URL.'); 29205 } 29206 29207 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 29208 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 29209 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 29210 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 29211 baseParts.netLoc = pathParts[1]; 29212 baseParts.path = pathParts[2]; 29213 } 29214 29215 if (baseParts.netLoc && !baseParts.path) { 29216 baseParts.path = '/'; 29217 } 29218 29219 var builtParts = { 29220 // 2c) Otherwise, the embedded URL inherits the scheme of 29221 // the base URL. 29222 scheme: baseParts.scheme, 29223 netLoc: relativeParts.netLoc, 29224 path: null, 29225 params: relativeParts.params, 29226 query: relativeParts.query, 29227 fragment: relativeParts.fragment 29228 }; 29229 29230 if (!relativeParts.netLoc) { 29231 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 29232 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 29233 // (if any) of the base URL. 29234 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 29235 // path is not relative and we skip to Step 7. 29236 29237 if (relativeParts.path[0] !== '/') { 29238 if (!relativeParts.path) { 29239 // 5) If the embedded URL path is empty (and not preceded by a 29240 // slash), then the embedded URL inherits the base URL path 29241 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 29242 // step 7; otherwise, it inherits the <params> of the base 29243 // URL (if any) and 29244 29245 if (!relativeParts.params) { 29246 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 29247 // step 7; otherwise, it inherits the <query> of the base 29248 // URL (if any) and we skip to step 7. 29249
29250 if (!relativeParts.query) { 29251 builtParts.query = baseParts.query; 29252 } 29253 } 29254 } else { 29255 // 6) The last segment of the base URL's path (anything 29256 // following the rightmost slash "/", or the entire path if no 29257 // slash is present) is removed and the embedded URL's path is 29258 // appended in its place. 29259 var baseURLPath = baseParts.path; 29260 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 29261 builtParts.path = URLToolkit.normalizePath(newPath); 29262 } 29263 } 29264 } 29265 29266 if (builtParts.path === null) { 29267 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 29268 } 29269 29270 return URLToolkit.buildURLFromParts(builtParts); 29271 }, 29272 parseURL: function parseURL(url) { 29273 var parts = URL_REGEX.exec(url); 29274 29275 if (!parts) { 29276 return null; 29277 } 29278 29279 return { 29280 scheme: parts[1] || '', 29281 netLoc: parts[2] || '', 29282 path: parts[3] || '', 29283 params: parts[4] || '', 29284 query: parts[5] || '', 29285 fragment: parts[6] || '' 29286 }; 29287 }, 29288 normalizePath: function normalizePath(path) { 29289 // The following operations are 29290 // then applied, in order, to the new path: 29291 // 6a) All occurrences of "./", where "." is a complete path 29292 // segment, are removed. 29293 // 6b) If the path ends with "." as a complete path segment, 29294 // that "." is removed. 29295 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 29296 // complete path segment not equal to "..", are removed. 29297 // Removal of these path segments is performed iteratively, 29298 // removing the leftmost matching pattern on each iteration, 29299 // until no matching pattern remains. 29300 // 6d) If the path ends with "<segment>/..", where <segment> is a 29301 // complete path segment not equal to "..", that 29302 // "<segment>/.." is removed. 29303 29304 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line 29305 29306 29307 return path.split('').reverse().join(''); 29308 }, 29309 buildURLFromParts: function buildURLFromParts(parts) { 29310 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 29311 } 29312 }; 29313 /* jshint ignore:start */ 29314 29315 module.exports = URLToolkit; 29316 })(); 29317 /* jshint ignore:end */ 29318 29319 }); 29320 29321 /*! @name m3u8-parser @version 4.4.0 @license Apache-2.0 */ 29322 29323 function _extends() { 29324 _extends = Object.assign || function (target) { 29325 for (var i = 1; i < arguments.length; i++) { 29326 var source = arguments[i]; 29327 29328 for (var key in source) { 29329 if (Object.prototype.hasOwnProperty.call(source, key)) { 29330 target[key] = source[key]; 29331 } 29332 } 29333 } 29334 29335 return target; 29336 }; 29337 29338 return _extends.apply(this, arguments); 29339 } 29340 29341 function _inheritsLoose$1(subClass, superClass) { 29342 subClass.prototype = Object.create(superClass.prototype); 29343 subClass.prototype.constructor = subClass; 29344 subClass.__proto__ = superClass; 29345 } 29346 29347 function _assertThisInitialized$1(self) { 29348 if (self === void 0) { 29349 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 29350 } 29351 29352 return self; 29353 } 29354 /** 29355 * @file stream.js 29356 */ 29357 29358 /** 29359 * A lightweight readable stream implementation that handles event dispatching. 29360 * 29361 * @class Stream 29362 */ 29363 29364 29365 var Stream = 29366 /*#__PURE__*/ 29367 function () { 29368 function Stream() { 29369 this.listeners = {}; 29370 } 29371 /** 29372 * Add a listener for a specified event type. 29373 * 29374 * @param {string} type the event name 29375 * @param {Function} listener the callback to be invoked when an event of 29376 * the specified type occurs 29377 */ 29378 29379 29380 var _proto = Stream.prototype; 29381 29382 _proto.on = function on(type, listener) { 29383 if (!this.listeners[type]) { 29384 this.listeners[type] = []; 29385 } 29386 29387 this.listeners[type].push(listener); 29388 } 29389 /** 29390 * Remove a listener for a specified event type. 29391 * 29392 * @param {string} type the event name 29393 * @param {Function} listener a function previously registered for this 29394 * type of event through `on` 29395 * @return {boolean} if we could turn it off or not 29396 */ 29397 ; 29398 29399 _proto.off = function off(type, listener) { 29400 if (!this.listeners[type]) { 29401 return false; 29402 } 29403 29404 var index = this.listeners[type].indexOf(listener); 29405 this.listeners[type].splice(index, 1); 29406 return index > -1; 29407 } 29408 /** 29409 * Trigger an event of the specified type on this stream. Any additional 29410 * arguments to this function are passed as parameters to event listeners. 29411 * 29412 * @param {string} type the event name 29413 */ 29414 ; 29415 29416 _proto.trigger = function trigger(type) { 29417 var callbacks = this.listeners[type]; 29418 var i; 29419 var length; 29420 var args; 29421 29422 if (!callbacks) { 29423 return; 29424 } // Slicing the arguments on every invocation of this method 29425 // can add a significant amount of overhead. Avoid the 29426 // intermediate object creation for the common case of a 29427 // single callback argument 29428 29429 29430 if (arguments.length === 2) { 29431 length = callbacks.length; 29432 29433 for (i = 0; i < length; ++i) { 29434 callbacks[i].call(this, arguments[1]); 29435 } 29436 } else { 29437 args = Array.prototype.slice.call(arguments, 1); 29438 length = callbacks.length; 29439 29440 for (i = 0; i < length; ++i) { 29441 callbacks[i].apply(this, args); 29442 } 29443 } 29444 } 29445 /** 29446 * Destroys the stream and cleans up. 29447 */ 29448 ; 29449 29450 _proto.dispose = function dispose() { 29451 this.listeners = {}; 29452 } 29453 /** 29454 * Forwards all `data` events on this stream to the destination stream. The 29455 * destination stream should provide a method `push` to receive the data 29456 * events as they arrive. 29457 * 29458 * @param {Stream} destination the stream that will receive all `data` events 29459 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 29460 */ 29461 ; 29462 29463 _proto.pipe = function pipe(destination) { 29464 this.on('data', function (data) { 29465 destination.push(data); 29466 }); 29467 }; 29468 29469 return Stream; 29470 }(); 29471 /** 29472 * A stream that buffers string input and generates a `data` event for each 29473 * line. 29474 * 29475 * @class LineStream 29476 * @extends Stream 29477 */ 29478 29479 29480 var LineStream = 29481 /*#__PURE__*/
29482 function (_Stream) { 29483 _inheritsLoose$1(LineStream, _Stream); 29484 29485 function LineStream() { 29486 var _this; 29487 29488 _this = _Stream.call(this) || this; 29489 _this.buffer = ''; 29490 return _this; 29491 } 29492 /** 29493 * Add new data to be parsed. 29494 * 29495 * @param {string} data the text to process 29496 */ 29497 29498 29499 var _proto = LineStream.prototype; 29500 29501 _proto.push = function push(data) { 29502 var nextNewline; 29503 this.buffer += data; 29504 nextNewline = this.buffer.indexOf('\n'); 29505 29506 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 29507 this.trigger('data', this.buffer.substring(0, nextNewline)); 29508 this.buffer = this.buffer.substring(nextNewline + 1); 29509 } 29510 }; 29511 29512 return LineStream; 29513 }(Stream); 29514 /** 29515 * "forgiving" attribute list psuedo-grammar: 29516 * attributes -> keyvalue (',' keyvalue)* 29517 * keyvalue -> key '=' value 29518 * key -> [^=]* 29519 * value -> '"' [^"]* '"' | [^,]* 29520 */ 29521 29522 29523 var attributeSeparator = function attributeSeparator() { 29524 var key = '[^=]*'; 29525 var value = '"[^"]*"|[^,]*'; 29526 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 29527 return new RegExp('(?:^|,)(' + keyvalue + ')'); 29528 }; 29529 /** 29530 * Parse attributes from a line given the separator 29531 * 29532 * @param {string} attributes the attribute line to parse 29533 */ 29534 29535 29536 var parseAttributes = function parseAttributes(attributes) { 29537 // split the string using attributes as the separator 29538 var attrs = attributes.split(attributeSeparator()); 29539 var result = {}; 29540 var i = attrs.length; 29541 var attr; 29542 29543 while (i--) { 29544 // filter out unmatched portions of the string 29545 if (attrs[i] === '') { 29546 continue; 29547 } // split the key and value 29548 29549 29550 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value 29551 29552 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 29553 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 29554 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 29555 result[attr[0]] = attr[1]; 29556 } 29557 29558 return result; 29559 }; 29560 /** 29561 * A line-level M3U8 parser event stream. It expects to receive input one 29562 * line at a time and performs a context-free parse of its contents. A stream 29563 * interpretation of a manifest can be useful if the manifest is expected to 29564 * be too large to fit comfortably into memory or the entirety of the input 29565 * is not immediately available. Otherwise, it's probably much easier to work 29566 * with a regular `Parser` object. 29567 * 29568 * Produces `data` events with an object that captures the parser's 29569 * interpretation of the input. That object has a property `tag` that is one 29570 * of `uri`, `comment`, or `tag`. URIs only have a single additional 29571 * property, `line`, which captures the entirety of the input without 29572 * interpretation. Comments similarly have a single additional property 29573 * `text` which is the input without the leading `#`. 29574 * 29575 * Tags always have a property `tagType` which is the lower-cased version of 29576 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 29577 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 29578 * tags are given the tag type `unknown` and a single additional property 29579 * `data` with the remainder of the input. 29580 * 29581 * @class ParseStream 29582 * @extends Stream 29583 */ 29584 29585 29586 var ParseStream = 29587 /*#__PURE__*/ 29588 function (_Stream) { 29589 _inheritsLoose$1(ParseStream, _Stream); 29590 29591 function ParseStream() { 29592 var _this; 29593 29594 _this = _Stream.call(this) || this; 29595 _this.customParsers = []; 29596 _this.tagMappers = []; 29597 return _this; 29598 } 29599 /** 29600 * Parses an additional line of input. 29601 * 29602 * @param {string} line a single line of an M3U8 file to parse 29603 */ 29604 29605 29606 var _proto = ParseStream.prototype; 29607 29608 _proto.push = function push(line) { 29609 var _this2 = this; 29610 29611 var match; 29612 var event; // strip whitespace 29613 29614 line = line.trim(); 29615 29616 if (line.length === 0) { 29617 // ignore empty lines 29618 return; 29619 } // URIs 29620 29621 29622 if (line[0] !== '#') { 29623 this.trigger('data', { 29624 type: 'uri', 29625 uri: line 29626 }); 29627 return; 29628 } // map tags 29629 29630 29631 var newLines = this.tagMappers.reduce(function (acc, mapper) { 29632 var mappedLine = mapper(line);
vendor: 33,998 bytes, lines 29632-30744
29632 // skip if unchanged 29633 29634 if (mappedLine === line) { 29635 return acc; 29636 } 29637 29638 return acc.concat([mappedLine]); 29639 }, [line]); 29640 newLines.forEach(function (newLine) { 29641 for (var i = 0; i < _this2.customParsers.length; i++) { 29642 if (_this2.customParsers[i].call(_this2, newLine)) { 29643 return; 29644 } 29645 } // Comments 29646 29647 29648 if (newLine.indexOf('#EXT') !== 0) { 29649 _this2.trigger('data', { 29650 type: 'comment', 29651 text: newLine.slice(1) 29652 }); 29653 29654 return; 29655 } // strip off any carriage returns here so the regex matching 29656 // doesn't have to account for them. 29657 29658 29659 newLine = newLine.replace('\r', ''); // Tags 29660 29661 match = /^#EXTM3U/.exec(newLine); 29662 29663 if (match) { 29664 _this2.trigger('data', { 29665 type: 'tag', 29666 tagType: 'm3u' 29667 }); 29668 29669 return; 29670 } 29671 29672 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine); 29673 29674 if (match) { 29675 event = { 29676 type: 'tag', 29677 tagType: 'inf' 29678 }; 29679 29680 if (match[1]) { 29681 event.duration = parseFloat(match[1]); 29682 } 29683 29684 if (match[2]) { 29685 event.title = match[2]; 29686 } 29687 29688 _this2.trigger('data', event); 29689 29690 return; 29691 } 29692 29693 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine); 29694 29695 if (match) { 29696 event = { 29697 type: 'tag', 29698 tagType: 'targetduration' 29699 }; 29700 29701 if (match[1]) { 29702 event.duration = parseInt(match[1], 10); 29703 } 29704 29705 _this2.trigger('data', event); 29706 29707 return; 29708 } 29709 29710 match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine); 29711 29712 if (match) { 29713 event = { 29714 type: 'tag', 29715 tagType: 'totalduration' 29716 }; 29717 29718 if (match[1]) { 29719 event.duration = parseInt(match[1], 10); 29720 } 29721 29722 _this2.trigger('data', event); 29723 29724 return; 29725 } 29726 29727 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine); 29728 29729 if (match) { 29730 event = { 29731 type: 'tag', 29732 tagType: 'version' 29733 }; 29734 29735 if (match[1]) { 29736 event.version = parseInt(match[1], 10); 29737 } 29738 29739 _this2.trigger('data', event); 29740 29741 return; 29742 } 29743 29744 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 29745 29746 if (match) { 29747 event = { 29748 type: 'tag', 29749 tagType: 'media-sequence' 29750 }; 29751 29752 if (match[1]) { 29753 event.number = parseInt(match[1], 10); 29754 } 29755 29756 _this2.trigger('data', event); 29757 29758 return; 29759 } 29760 29761 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 29762 29763 if (match) { 29764 event = { 29765 type: 'tag', 29766 tagType: 'discontinuity-sequence' 29767 }; 29768 29769 if (match[1]) { 29770 event.number = parseInt(match[1], 10); 29771 } 29772 29773 _this2.trigger('data', event); 29774 29775 return; 29776 } 29777 29778 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine); 29779 29780 if (match) { 29781 event = { 29782 type: 'tag', 29783 tagType: 'playlist-type' 29784 }; 29785 29786 if (match[1]) { 29787 event.playlistType = match[1]; 29788 } 29789 29790 _this2.trigger('data', event); 29791 29792 return; 29793 } 29794 29795 match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine); 29796 29797 if (match) { 29798 event = { 29799 type: 'tag', 29800 tagType: 'byterange' 29801 }; 29802 29803 if (match[1]) { 29804 event.length = parseInt(match[1], 10); 29805 } 29806 29807 if (match[2]) { 29808 event.offset = parseInt(match[2], 10); 29809 } 29810 29811 _this2.trigger('data', event); 29812 29813 return; 29814 } 29815 29816 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine); 29817 29818 if (match) { 29819 event = { 29820 type: 'tag', 29821 tagType: 'allow-cache' 29822 }; 29823 29824 if (match[1]) { 29825 event.allowed = !/NO/.test(match[1]); 29826 } 29827 29828 _this2.trigger('data', event); 29829 29830 return; 29831 } 29832 29833 match = /^#EXT-X-MAP:?(.*)$/.exec(newLine); 29834 29835 if (match) { 29836 event = { 29837 type: 'tag', 29838 tagType: 'map' 29839 }; 29840 29841 if (match[1]) { 29842 var attributes = parseAttributes(match[1]); 29843 29844 if (attributes.URI) { 29845 event.uri = attributes.URI; 29846 } 29847 29848 if (attributes.BYTERANGE) { 29849 var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'), 29850 length = _attributes$BYTERANGE[0], 29851 offset = _attributes$BYTERANGE[1]; 29852 29853 event.byterange = {}; 29854 29855 if (length) { 29856 event.byterange.length = parseInt(length, 10); 29857 } 29858 29859 if (offset) { 29860 event.byterange.offset = parseInt(offset, 10); 29861 } 29862 } 29863 } 29864 29865 _this2.trigger('data', event); 29866 29867 return; 29868 } 29869 29870 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine); 29871 29872 if (match) { 29873 event = { 29874 type: 'tag', 29875 tagType: 'stream-inf' 29876 }; 29877 29878 if (match[1]) { 29879 event.attributes = parseAttributes(match[1]); 29880 29881 if (event.attributes.RESOLUTION) { 29882 var split = event.attributes.RESOLUTION.split('x'); 29883 var resolution = {}; 29884 29885 if (split[0]) { 29886 resolution.width = parseInt(split[0], 10); 29887 } 29888 29889 if (split[1]) { 29890 resolution.height = parseInt(split[1], 10); 29891 } 29892 29893 event.attributes.RESOLUTION = resolution; 29894 } 29895 29896 if (event.attributes.BANDWIDTH) { 29897 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 29898 } 29899 29900 if (event.attributes['PROGRAM-ID']) { 29901 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 29902 } 29903 } 29904 29905 _this2.trigger('data', event); 29906 29907 return; 29908 } 29909 29910 match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine); 29911 29912 if (match) { 29913 event = { 29914 type: 'tag', 29915 tagType: 'media' 29916 }; 29917 29918 if (match[1]) { 29919 event.attributes = parseAttributes(match[1]); 29920 } 29921 29922 _this2.trigger('data', event); 29923 29924 return; 29925 } 29926 29927 match = /^#EXT-X-ENDLIST/.exec(newLine); 29928 29929 if (match) { 29930 _this2.trigger('data', { 29931 type: 'tag', 29932 tagType: 'endlist' 29933 }); 29934 29935 return; 29936 } 29937 29938 match = /^#EXT-X-DISCONTINUITY/.exec(newLine); 29939 29940 if (match) { 29941 _this2.trigger('data', { 29942 type: 'tag', 29943 tagType: 'discontinuity' 29944 }); 29945 29946 return; 29947 } 29948 29949 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine); 29950 29951 if (match) { 29952 event = { 29953 type: 'tag', 29954 tagType: 'program-date-time' 29955 }; 29956 29957 if (match[1]) { 29958 event.dateTimeString = match[1]; 29959 event.dateTimeObject = new Date(match[1]); 29960 } 29961 29962 _this2.trigger('data', event); 29963 29964 return; 29965 } 29966 29967 match = /^#EXT-X-KEY:?(.*)$/.exec(newLine); 29968 29969 if (match) { 29970 event = { 29971 type: 'tag', 29972 tagType: 'key' 29973 }; 29974 29975 if (match[1]) { 29976 event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array 29977 29978 if (event.attributes.IV) { 29979 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 29980 event.attributes.IV = event.attributes.IV.substring(2); 29981 } 29982 29983 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 29984 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 29985 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 29986 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 29987 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 29988 event.attributes.IV = new Uint32Array(event.attributes.IV); 29989 } 29990 } 29991 29992 _this2.trigger('data', event); 29993 29994 return; 29995 } 29996 29997 match = /^#EXT-X-START:?(.*)$/.exec(newLine); 29998 29999 if (match) { 30000 event = { 30001 type: 'tag', 30002 tagType: 'start' 30003 }; 30004 30005 if (match[1]) { 30006 event.attributes = parseAttributes(match[1]); 30007 event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']); 30008 event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE); 30009 } 30010 30011 _this2.trigger('data', event); 30012 30013 return; 30014 } 30015 30016 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine); 30017 30018 if (match) { 30019 event = { 30020 type: 'tag', 30021 tagType: 'cue-out-cont' 30022 }; 30023 30024 if (match[1]) { 30025 event.data = match[1]; 30026 } else { 30027 event.data = ''; 30028 } 30029 30030 _this2.trigger('data', event); 30031 30032 return; 30033 } 30034 30035 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine); 30036 30037 if (match) { 30038 event = { 30039 type: 'tag', 30040 tagType: 'cue-out' 30041 }; 30042 30043 if (match[1]) { 30044 event.data = match[1]; 30045 } else { 30046 event.data = ''; 30047 } 30048 30049 _this2.trigger('data', event); 30050 30051 return; 30052 } 30053 30054 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine); 30055 30056 if (match) { 30057 event = { 30058 type: 'tag', 30059 tagType: 'cue-in' 30060 }; 30061 30062 if (match[1]) { 30063 event.data = match[1]; 30064 } else { 30065 event.data = ''; 30066 } 30067 30068 _this2.trigger('data', event); 30069 30070 return; 30071 } // unknown tag type 30072 30073 30074 _this2.trigger('data', { 30075 type: 'tag', 30076 data: newLine.slice(4) 30077 }); 30078 }); 30079 } 30080 /** 30081 * Add a parser for custom headers 30082 * 30083 * @param {Object} options a map of options for the added parser 30084 * @param {RegExp} options.expression a regular expression to match the custom header 30085 * @param {string} options.customType the custom type to register to the output 30086 * @param {Function} [options.dataParser] function to parse the line into an object 30087 * @param {boolean} [options.segment] should tag data be attached to the segment object 30088 */ 30089 ; 30090 30091 _proto.addParser = function addParser(_ref) { 30092 var _this3 = this; 30093 30094 var expression = _ref.expression, 30095 customType = _ref.customType, 30096 dataParser = _ref.dataParser, 30097 segment = _ref.segment; 30098 30099 if (typeof dataParser !== 'function') { 30100 dataParser = function dataParser(line) { 30101 return line; 30102 }; 30103 } 30104 30105 this.customParsers.push(function (line) { 30106 var match = expression.exec(line); 30107 30108 if (match) { 30109 _this3.trigger('data', { 30110 type: 'custom', 30111 data: dataParser(line), 30112 customType: customType, 30113 segment: segment 30114 }); 30115 30116 return true; 30117 } 30118 }); 30119 } 30120 /** 30121 * Add a custom header mapper 30122 * 30123 * @param {Object} options 30124 * @param {RegExp} options.expression a regular expression to match the custom header 30125 * @param {Function} options.map function to translate tag into a different tag 30126 */ 30127 ; 30128 30129 _proto.addTagMapper = function addTagMapper(_ref2) { 30130 var expression = _ref2.expression, 30131 map = _ref2.map; 30132 30133 var mapFn = function mapFn(line) { 30134 if (expression.test(line)) { 30135 return map(line); 30136 } 30137 30138 return line; 30139 }; 30140 30141 this.tagMappers.push(mapFn); 30142 }; 30143 30144 return ParseStream; 30145 }(Stream); 30146 30147 function decodeB64ToUint8Array(b64Text) { 30148 var decodedString = window$1.atob(b64Text || ''); 30149 var array = new Uint8Array(decodedString.length); 30150 30151 for (var i = 0; i < decodedString.length; i++) { 30152 array[i] = decodedString.charCodeAt(i); 30153 } 30154 30155 return array; 30156 } 30157 /** 30158 * A parser for M3U8 files. The current interpretation of the input is 30159 * exposed as a property `manifest` on parser objects. It's just two lines to 30160 * create and parse a manifest once you have the contents available as a string: 30161 * 30162 * ```js 30163 * var parser = new m3u8.Parser(); 30164 * parser.push(xhr.responseText); 30165 * ``` 30166 * 30167 * New input can later be applied to update the manifest object by calling 30168 * `push` again. 30169 * 30170 * The parser attempts to create a usable manifest object even if the 30171 * underlying input is somewhat nonsensical. It emits `info` and `warning` 30172 * events during the parse if it encounters input that seems invalid or 30173 * requires some property of the manifest object to be defaulted. 30174 * 30175 * @class Parser 30176 * @extends Stream 30177 */ 30178 30179 30180 var Parser = 30181 /*#__PURE__*/ 30182 function (_Stream) { 30183 _inheritsLoose$1(Parser, _Stream); 30184 30185 function Parser() { 30186 var _this; 30187 30188 _this = _Stream.call(this) || this; 30189 _this.lineStream = new LineStream(); 30190 _this.parseStream = new ParseStream(); 30191 30192 _this.lineStream.pipe(_this.parseStream); 30193 /* eslint-disable consistent-this */ 30194 30195 30196 var self = _assertThisInitialized$1(_this); 30197 /* eslint-enable consistent-this */ 30198 30199 30200 var uris = []; 30201 var currentUri = {}; // if specified, the active EXT-X-MAP definition 30202 30203 var currentMap; // if specified, the active decryption key 30204 30205 var _key; 30206 30207 var noop = function noop() {}; 30208 30209 var defaultMediaGroups = { 30210 'AUDIO': {}, 30211 'VIDEO': {}, 30212 'CLOSED-CAPTIONS': {}, 30213 'SUBTITLES': {} 30214 }; // This is the Widevine UUID from DASH IF IOP. The same exact string is 30215 // used in MPDs with Widevine encrypted streams. 30216 30217 var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities 30218 30219 var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data 30220 30221 _this.manifest = { 30222 allowCache: true, 30223 discontinuityStarts: [], 30224 segments: [] 30225 }; // update the manifest with the m3u8 entry from the parse stream 30226 30227 _this.parseStream.on('data', function (entry) { 30228 var mediaGroup; 30229 var rendition; 30230 ({ 30231 tag: function tag() { 30232 // switch based on the tag type 30233 (({ 30234 'allow-cache': function allowCache() { 30235 this.manifest.allowCache = entry.allowed; 30236 30237 if (!('allowed' in entry)) { 30238 this.trigger('info', { 30239 message: 'defaulting allowCache to YES' 30240 }); 30241 this.manifest.allowCache = true; 30242 } 30243 }, 30244 byterange: function byterange() { 30245 var byterange = {}; 30246 30247 if ('length' in entry) { 30248 currentUri.byterange = byterange; 30249 byterange.length = entry.length; 30250 30251 if (!('offset' in entry)) { 30252 this.trigger('info', { 30253 message: 'defaulting offset to zero' 30254 }); 30255 entry.offset = 0; 30256 } 30257 } 30258 30259 if ('offset' in entry) { 30260 currentUri.byterange = byterange; 30261 byterange.offset = entry.offset; 30262 } 30263 }, 30264 endlist: function endlist() { 30265 this.manifest.endList = true; 30266 }, 30267 inf: function inf() { 30268 if (!('mediaSequence' in this.manifest)) { 30269 this.manifest.mediaSequence = 0; 30270 this.trigger('info', { 30271 message: 'defaulting media sequence to zero' 30272 }); 30273 } 30274 30275 if (!('discontinuitySequence' in this.manifest)) { 30276 this.manifest.discontinuitySequence = 0; 30277 this.trigger('info', { 30278 message: 'defaulting discontinuity sequence to zero' 30279 }); 30280 } 30281 30282 if (entry.duration > 0) { 30283 currentUri.duration = entry.duration; 30284 } 30285 30286 if (entry.duration === 0) { 30287 currentUri.duration = 0.01; 30288 this.trigger('info', { 30289 message: 'updating zero segment duration to a small value' 30290 }); 30291 } 30292 30293 this.manifest.segments = uris; 30294 }, 30295 key: function key() { 30296 if (!entry.attributes) { 30297 this.trigger('warn', { 30298 message: 'ignoring key declaration without attribute list' 30299 }); 30300 return; 30301 } // clear the active encryption key 30302 30303 30304 if (entry.attributes.METHOD === 'NONE') { 30305 _key = null; 30306 return; 30307 } 30308 30309 if (!entry.attributes.URI) { 30310 this.trigger('warn', { 30311 message: 'ignoring key declaration without URI' 30312 }); 30313 return; 30314 } // check if the content is encrypted for Widevine 30315 // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf 30316 30317 30318 if (entry.attributes.KEYFORMAT === widevineUuid) { 30319 var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC']; 30320 30321 if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) { 30322 this.trigger('warn', { 30323 message: 'invalid key method provided for Widevine' 30324 }); 30325 return; 30326 } 30327 30328 if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') { 30329 this.trigger('warn', { 30330 message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead' 30331 }); 30332 } 30333 30334 if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') { 30335 this.trigger('warn', { 30336 message: 'invalid key URI provided for Widevine' 30337 }); 30338 return; 30339 } 30340 30341 if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) { 30342 this.trigger('warn', { 30343 message: 'invalid key ID provided for Widevine' 30344 }); 30345 return; 30346 } // if Widevine key attributes are valid, store them as `contentProtection` 30347 // on the manifest to emulate Widevine tag structure in a DASH mpd 30348 30349 30350 this.manifest.contentProtection = { 30351 'com.widevine.alpha': { 30352 attributes: { 30353 schemeIdUri: entry.attributes.KEYFORMAT, 30354 // remove '0x' from the key id string 30355 keyId: entry.attributes.KEYID.substring(2) 30356 }, 30357 // decode the base64-encoded PSSH box 30358 pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1]) 30359 } 30360 }; 30361 return; 30362 } 30363 30364 if (!entry.attributes.METHOD) { 30365 this.trigger('warn', { 30366 message: 'defaulting key method to AES-128' 30367 }); 30368 } // setup an encryption key for upcoming segments 30369 30370 30371 _key = { 30372 method: entry.attributes.METHOD || 'AES-128', 30373 uri: entry.attributes.URI 30374 }; 30375 30376 if (typeof entry.attributes.IV !== 'undefined') { 30377 _key.iv = entry.attributes.IV; 30378 } 30379 }, 30380 'media-sequence': function mediaSequence() { 30381 if (!isFinite(entry.number)) { 30382 this.trigger('warn', { 30383 message: 'ignoring invalid media sequence: ' + entry.number 30384 }); 30385 return; 30386 } 30387 30388 this.manifest.mediaSequence = entry.number; 30389 }, 30390 'discontinuity-sequence': function discontinuitySequence() { 30391 if (!isFinite(entry.number)) { 30392 this.trigger('warn', { 30393 message: 'ignoring invalid discontinuity sequence: ' + entry.number 30394 }); 30395 return; 30396 } 30397 30398 this.manifest.discontinuitySequence = entry.number; 30399 currentTimeline = entry.number; 30400 }, 30401 'playlist-type': function playlistType() { 30402 if (!/VOD|EVENT/.test(entry.playlistType)) { 30403 this.trigger('warn', { 30404 message: 'ignoring unknown playlist type: ' + entry.playlist 30405 }); 30406 return; 30407 } 30408 30409 this.manifest.playlistType = entry.playlistType; 30410 }, 30411 map: function map() { 30412 currentMap = {}; 30413 30414 if (entry.uri) { 30415 currentMap.uri = entry.uri; 30416 } 30417 30418 if (entry.byterange) { 30419 currentMap.byterange = entry.byterange; 30420 } 30421 }, 30422 'stream-inf': function streamInf() { 30423 this.manifest.playlists = uris; 30424 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 30425 30426 if (!entry.attributes) { 30427 this.trigger('warn', { 30428 message: 'ignoring empty stream-inf attributes' 30429 }); 30430 return; 30431 } 30432 30433 if (!currentUri.attributes) { 30434 currentUri.attributes = {}; 30435 } 30436 30437 _extends(currentUri.attributes, entry.attributes); 30438 }, 30439 media: function media() { 30440 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 30441 30442 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 30443 this.trigger('warn', { 30444 message: 'ignoring incomplete or missing media group' 30445 }); 30446 return; 30447 } // find the media group, creating defaults as necessary 30448 30449 30450 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 30451 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 30452 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata 30453 30454 rendition = { 30455 "default": /yes/i.test(entry.attributes.DEFAULT) 30456 }; 30457 30458 if (rendition["default"]) { 30459 rendition.autoselect = true; 30460 } else { 30461 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 30462 } 30463 30464 if (entry.attributes.LANGUAGE) { 30465 rendition.language = entry.attributes.LANGUAGE; 30466 } 30467 30468 if (entry.attributes.URI) { 30469 rendition.uri = entry.attributes.URI; 30470 } 30471 30472 if (entry.attributes['INSTREAM-ID']) { 30473 rendition.instreamId = entry.attributes['INSTREAM-ID']; 30474 } 30475 30476 if (entry.attributes.CHARACTERISTICS) { 30477 rendition.characteristics = entry.attributes.CHARACTERISTICS; 30478 } 30479 30480 if (entry.attributes.FORCED) { 30481 rendition.forced = /yes/i.test(entry.attributes.FORCED); 30482 } // insert the new rendition 30483 30484 30485 mediaGroup[entry.attributes.NAME] = rendition; 30486 }, 30487 discontinuity: function discontinuity() { 30488 currentTimeline += 1; 30489 currentUri.discontinuity = true; 30490 this.manifest.discontinuityStarts.push(uris.length); 30491 }, 30492 'program-date-time': function programDateTime() { 30493 if (typeof this.manifest.dateTimeString === 'undefined') { 30494 // PROGRAM-DATE-TIME is a media-segment tag, but for backwards 30495 // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag 30496 // to the manifest object 30497 // TODO: Consider removing this in future major version 30498 this.manifest.dateTimeString = entry.dateTimeString; 30499 this.manifest.dateTimeObject = entry.dateTimeObject; 30500 } 30501 30502 currentUri.dateTimeString = entry.dateTimeString; 30503 currentUri.dateTimeObject = entry.dateTimeObject; 30504 }, 30505 targetduration: function targetduration() { 30506 if (!isFinite(entry.duration) || entry.duration < 0) { 30507 this.trigger('warn', { 30508 message: 'ignoring invalid target duration: ' + entry.duration 30509 }); 30510 return; 30511 } 30512 30513 this.manifest.targetDuration = entry.duration; 30514 }, 30515 totalduration: function totalduration() { 30516 if (!isFinite(entry.duration) || entry.duration < 0) { 30517 this.trigger('warn', { 30518 message: 'ignoring invalid total duration: ' + entry.duration 30519 }); 30520 return; 30521 } 30522 30523 this.manifest.totalDuration = entry.duration; 30524 }, 30525 start: function start() { 30526 if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) { 30527 this.trigger('warn', { 30528 message: 'ignoring start declaration without appropriate attribute list' 30529 }); 30530 return; 30531 } 30532 30533 this.manifest.start = { 30534 timeOffset: entry.attributes['TIME-OFFSET'], 30535 precise: entry.attributes.PRECISE 30536 }; 30537 }, 30538 'cue-out': function cueOut() { 30539 currentUri.cueOut = entry.data; 30540 }, 30541 'cue-out-cont': function cueOutCont() { 30542 currentUri.cueOutCont = entry.data; 30543 }, 30544 'cue-in': function cueIn() { 30545 currentUri.cueIn = entry.data; 30546 } 30547 })[entry.tagType] || noop).call(self); 30548 }, 30549 uri: function uri() { 30550 currentUri.uri = entry.uri; 30551 uris.push(currentUri); // if no explicit duration was declared, use the target duration 30552 30553 if (this.manifest.targetDuration && !('duration' in currentUri)) { 30554 this.trigger('warn', { 30555 message: 'defaulting segment duration to the target duration' 30556 }); 30557 currentUri.duration = this.manifest.targetDuration; 30558 } // annotate with encryption information, if necessary 30559 30560 30561 if (_key) { 30562 currentUri.key = _key; 30563 } 30564 30565 currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary 30566 30567 if (currentMap) { 30568 currentUri.map = currentMap; 30569 } // prepare for the next URI 30570 30571 30572 currentUri = {}; 30573 }, 30574 comment: function comment() {// comments are not important for playback 30575 }, 30576 custom: function custom() { 30577 // if this is segment-level data attach the output to the segment 30578 if (entry.segment) { 30579 currentUri.custom = currentUri.custom || {}; 30580 currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object 30581 } else { 30582 this.manifest.custom = this.manifest.custom || {}; 30583 this.manifest.custom[entry.customType] = entry.data; 30584 } 30585 } 30586 })[entry.type].call(self); 30587 }); 30588 30589 return _this; 30590 } 30591 /** 30592 * Parse the input string and update the manifest object. 30593 * 30594 * @param {string} chunk a potentially incomplete portion of the manifest 30595 */ 30596 30597 30598 var _proto = Parser.prototype; 30599 30600 _proto.push = function push(chunk) { 30601 this.lineStream.push(chunk); 30602 } 30603 /** 30604 * Flush any remaining input. This can be handy if the last line of an M3U8 30605 * manifest did not contain a trailing newline but the file has been 30606 * completely received. 30607 */ 30608 ; 30609 30610 _proto.end = function end() { 30611 // flush any buffered input 30612 this.lineStream.push('\n'); 30613 } 30614 /** 30615 * Add an additional parser for non-standard tags 30616 * 30617 * @param {Object} options a map of options for the added parser 30618 * @param {RegExp} options.expression a regular expression to match the custom header 30619 * @param {string} options.type the type to register to the output 30620 * @param {Function} [options.dataParser] function to parse the line into an object 30621 * @param {boolean} [options.segment] should tag data be attached to the segment object 30622 */ 30623 ; 30624 30625 _proto.addParser = function addParser(options) { 30626 this.parseStream.addParser(options); 30627 } 30628 /** 30629 * Add a custom header mapper 30630 * 30631 * @param {Object} options 30632 * @param {RegExp} options.expression a regular expression to match the custom header 30633 * @param {Function} options.map function to translate tag into a different tag 30634 */ 30635 ; 30636 30637 _proto.addTagMapper = function addTagMapper(options) { 30638 this.parseStream.addTagMapper(options); 30639 }; 30640 30641 return Parser; 30642 }(Stream); 30643 30644 /*! @name mpd-parser @version 0.8.1 @license Apache-2.0 */ 30645 30646 var isObject$1 = function isObject(obj) { 30647 return !!obj && typeof obj === 'object'; 30648 }; 30649 30650 var merge = function merge() { 30651 for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) { 30652 objects[_key] = arguments[_key]; 30653 } 30654 30655 return objects.reduce(function (result, source) { 30656 Object.keys(source).forEach(function (key) { 30657 if (Array.isArray(result[key]) && Array.isArray(source[key])) { 30658 result[key] = result[key].concat(source[key]); 30659 } else if (isObject$1(result[key]) && isObject$1(source[key])) { 30660 result[key] = merge(result[key], source[key]); 30661 } else { 30662 result[key] = source[key]; 30663 } 30664 }); 30665 return result; 30666 }, {}); 30667 }; 30668 30669 var values = function values(o) { 30670 return Object.keys(o).map(function (k) { 30671 return o[k]; 30672 }); 30673 }; 30674 30675 var range = function range(start, end) { 30676 var result = []; 30677 30678 for (var i = start; i < end; i++) { 30679 result.push(i); 30680 } 30681 30682 return result; 30683 }; 30684 30685 var flatten = function flatten(lists) { 30686 return lists.reduce(function (x, y) { 30687 return x.concat(y); 30688 }, []); 30689 }; 30690 30691 var from = function from(list) { 30692 if (!list.length) { 30693 return []; 30694 } 30695 30696 var result = []; 30697 30698 for (var i = 0; i < list.length; i++) { 30699 result.push(list[i]); 30700 } 30701 30702 return result; 30703 }; 30704 30705 var findIndexes = function findIndexes(l, key) { 30706 return l.reduce(function (a, e, i) { 30707 if (e[key]) { 30708 a.push(i); 30709 } 30710 30711 return a; 30712 }, []); 30713 }; 30714 30715 var errors = { 30716 INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD', 30717 DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST', 30718 DASH_INVALID_XML: 'DASH_INVALID_XML', 30719 NO_BASE_URL: 'NO_BASE_URL', 30720 MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', 30721 SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED', 30722 UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME' 30723 }; 30724 30725 function createCommonjsModule$1(fn, module) { 30726 return module = { 30727 exports: {} 30728 }, fn(module, module.exports), module.exports; 30729 } 30730 30731 var urlToolkit$1 = createCommonjsModule$1(function (module, exports) { 30732 // see https://tools.ietf.org/html/rfc1808 30733 30734 /* jshint ignore:start */ 30735 (function (root) { 30736 /* jshint ignore:end */ 30737 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; 30738 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; 30739 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 30740 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; 30741 var URLToolkit = { 30742 // jshint ignore:line 30743 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 30744 // E.g
vendor: 4,132 bytes, lines 30745-30838
30745 // With opts.alwaysNormalize = false (default, spec compliant) 30746 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 30747 // With opts.alwaysNormalize = true (not spec compliant) 30748 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 30749 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 30750 opts = opts || {}; // remove any remaining space and CRLF 30751 30752 baseURL = baseURL.trim(); 30753 relativeURL = relativeURL.trim(); 30754 30755 if (!relativeURL) { 30756 // 2a) If the embedded URL is entirely empty, it inherits the 30757 // entire base URL (i.e., is set equal to the base URL) 30758 // and we are done. 30759 if (!opts.alwaysNormalize) { 30760 return baseURL; 30761 } 30762 30763 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 30764 30765 if (!basePartsForNormalise) { 30766 throw new Error('Error trying to parse base URL.'); 30767 } 30768 30769 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 30770 return URLToolkit.buildURLFromParts(basePartsForNormalise); 30771 } 30772 30773 var relativeParts = URLToolkit.parseURL(relativeURL); 30774 30775 if (!relativeParts) { 30776 throw new Error('Error trying to parse relative URL.'); 30777 } 30778 30779 if (relativeParts.scheme) { 30780 // 2b) If the embedded URL starts with a scheme name, it is 30781 // interpreted as an absolute URL and we are done. 30782 if (!opts.alwaysNormalize) { 30783 return relativeURL; 30784 } 30785 30786 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 30787 return URLToolkit.buildURLFromParts(relativeParts); 30788 } 30789 30790 var baseParts = URLToolkit.parseURL(baseURL); 30791 30792 if (!baseParts) { 30793 throw new Error('Error trying to parse base URL.'); 30794 } 30795 30796 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 30797 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 30798 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 30799 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 30800 baseParts.netLoc = pathParts[1]; 30801 baseParts.path = pathParts[2]; 30802 } 30803 30804 if (baseParts.netLoc && !baseParts.path) { 30805 baseParts.path = '/'; 30806 } 30807 30808 var builtParts = { 30809 // 2c) Otherwise, the embedded URL inherits the scheme of 30810 // the base URL. 30811 scheme: baseParts.scheme, 30812 netLoc: relativeParts.netLoc, 30813 path: null, 30814 params: relativeParts.params, 30815 query: relativeParts.query, 30816 fragment: relativeParts.fragment 30817 }; 30818 30819 if (!relativeParts.netLoc) { 30820 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 30821 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 30822 // (if any) of the base URL. 30823 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 30824 // path is not relative and we skip to Step 7. 30825 30826 if (relativeParts.path[0] !== '/') { 30827 if (!relativeParts.path) { 30828 // 5) If the embedded URL path is empty (and not preceded by a 30829 // slash), then the embedded URL inherits the base URL path 30830 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 30831 // step 7; otherwise, it inherits the <params> of the base 30832 // URL (if any) and 30833 30834 if (!relativeParts.params) { 30835 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 30836 // step 7; otherwise, it inherits the <query> of the base 30837 // URL (if any) and we skip to step 7. 30838
vendor: 4,111 bytes, lines 30839-30941
30839 if (!relativeParts.query) { 30840 builtParts.query = baseParts.query; 30841 } 30842 } 30843 } else { 30844 // 6) The last segment of the base URL's path (anything 30845 // following the rightmost slash "/", or the entire path if no 30846 // slash is present) is removed and the embedded URL's path is 30847 // appended in its place. 30848 var baseURLPath = baseParts.path; 30849 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 30850 builtParts.path = URLToolkit.normalizePath(newPath); 30851 } 30852 } 30853 } 30854 30855 if (builtParts.path === null) { 30856 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 30857 } 30858 30859 return URLToolkit.buildURLFromParts(builtParts); 30860 }, 30861 parseURL: function parseURL(url) { 30862 var parts = URL_REGEX.exec(url); 30863 30864 if (!parts) { 30865 return null; 30866 } 30867 30868 return { 30869 scheme: parts[1] || '', 30870 netLoc: parts[2] || '', 30871 path: parts[3] || '', 30872 params: parts[4] || '', 30873 query: parts[5] || '', 30874 fragment: parts[6] || '' 30875 }; 30876 }, 30877 normalizePath: function normalizePath(path) { 30878 // The following operations are 30879 // then applied, in order, to the new path: 30880 // 6a) All occurrences of "./", where "." is a complete path 30881 // segment, are removed. 30882 // 6b) If the path ends with "." as a complete path segment, 30883 // that "." is removed. 30884 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 30885 // complete path segment not equal to "..", are removed. 30886 // Removal of these path segments is performed iteratively, 30887 // removing the leftmost matching pattern on each iteration, 30888 // until no matching pattern remains. 30889 // 6d) If the path ends with "<segment>/..", where <segment> is a 30890 // complete path segment not equal to "..", that 30891 // "<segment>/.." is removed. 30892 30893 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line 30894 30895 30896 return path.split('').reverse().join(''); 30897 }, 30898 buildURLFromParts: function buildURLFromParts(parts) { 30899 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 30900 } 30901 }; 30902 /* jshint ignore:start */ 30903 30904 module.exports = URLToolkit; 30905 })(); 30906 /* jshint ignore:end */ 30907 30908 }); 30909 30910 var resolveUrl = function resolveUrl(baseUrl, relativeUrl) { 30911 // return early if we don't need to resolve 30912 if (/^[a-z]+:/i.test(relativeUrl)) { 30913 return relativeUrl; 30914 } // if the base URL is relative then combine with the current location 30915 30916 30917 if (!/\/\//i.test(baseUrl)) { 30918 baseUrl = urlToolkit$1.buildAbsoluteURL(window$1.location.href, baseUrl); 30919 } 30920 30921 return urlToolkit$1.buildAbsoluteURL(baseUrl, relativeUrl); 30922 }; 30923 /** 30924 * @typedef {Object} SingleUri 30925 * @property {string} uri - relative location of segment 30926 * @property {string} resolvedUri - resolved location of segment 30927 * @property {Object} byterange - Object containing information on how to make byte range 30928 * requests following byte-range-spec per RFC2616. 30929 * @property {String} byterange.length - length of range request 30930 * @property {String} byterange.offset - byte offset of range request 30931 * 30932 * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 30933 */ 30934 30935 /** 30936 * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object 30937 * that conforms to how m3u8-parser is structured 30938 * 30939 * @see https://github.com/videojs/m3u8-parser 30940 * 30941 * @param {string} baseUrl - baseUrl provided by <BaseUrl>
30941 nodes 30942 * @param {string} source - source url for segment 30943 * @param {string} range - optional range used for range calls, 30944 * follows RFC 2616, Clause 14.35.1 30945 * @return {SingleUri} full segment information transformed into a format similar 30946 * to m3u8-parser 30947 */ 30948 30949 30950 var urlTypeToSegment = function urlTypeToSegment(_ref) { 30951 var _ref$baseUrl = _ref.baseUrl, 30952 baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl, 30953 _ref$source = _ref.source, 30954 source = _ref$source === void 0 ? '' : _ref$source, 30955 _ref$range = _ref.range, 30956 range = _ref$range === void 0 ? '' : _ref$range, 30957 _ref$indexRange = _ref.indexRange, 30958 indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange; 30959 var segment = { 30960 uri: source, 30961 resolvedUri: resolveUrl(baseUrl || '', source) 30962 }; 30963 30964 if (range || indexRange) { 30965 var rangeStr = range ? range : indexRange; 30966 var ranges = rangeStr.split('-'); 30967 var startRange = parseInt(ranges[0], 10); 30968 var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to 30969 // RFC 2616, Clause 14.35.1 30970 30971 segment.byterange = { 30972 length: endRange - startRange + 1, 30973 offset: startRange 30974 }; 30975 } 30976 30977 return segment; 30978 }; 30979 30980 var byteRangeToString = function byteRangeToString(byterange) { 30981 // `endRange` is one less than `offset + length` because the HTTP range 30982 // header uses inclusive ranges 30983 var endRange = byterange.offset + byterange.length - 1; 30984 return byterange.offset + "-" + endRange; 30985 }; 30986 /** 30987 * Functions for calculating the range of available segments in static and dynamic 30988 * manifests. 30989 */ 30990 30991 30992 var segmentRange = { 30993 /** 30994 * Returns the entire range of available segments for a static MPD 30995 * 30996 * @param {Object} attributes 30997 * Inheritied MPD attributes 30998 * @return {{ start: number, end: number }} 30999 * The start and end numbers for available segments 31000 */ 31001 "static": function _static(attributes) { 31002 var duration = attributes.duration, 31003 _attributes$timescale = attributes.timescale, 31004 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 31005 sourceDuration = attributes.sourceDuration; 31006 return { 31007 start: 0, 31008 end: Math.ceil(sourceDuration / (duration / timescale)) 31009 }; 31010 }, 31011 31012 /** 31013 * Returns the current live window range of available segments for a dynamic MPD 31014 * 31015 * @param {Object} attributes 31016 * Inheritied MPD attributes 31017 * @return {{ start: number, end: number }} 31018 * The start and end numbers for available segments 31019 */ 31020 dynamic: function dynamic(attributes) { 31021 var NOW = attributes.NOW, 31022 clientOffset = attributes.clientOffset, 31023 availabilityStartTime = attributes.availabilityStartTime, 31024 _attributes$timescale2 = attributes.timescale, 31025 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 31026 duration = attributes.duration, 31027 _attributes$start = attributes.start, 31028 start = _attributes$start === void 0 ? 0 : _attributes$start, 31029 _attributes$minimumUp = attributes.minimumUpdatePeriod, 31030 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp, 31031 _attributes$timeShift = attributes.timeShiftBufferDepth, 31032 timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift; 31033 var now = (NOW + clientOffset) / 1000; 31034 var periodStartWC = availabilityStartTime + start; 31035 var periodEndWC = now + minimumUpdatePeriod; 31036 var periodDuration = periodEndWC - periodStartWC; 31037 var segmentCount = Math.ceil(periodDuration * timescale / duration); 31038 var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration); 31039 var availableEnd = Math.floor((now - periodStartWC) * timescale / duration); 31040 return { 31041 start: Math.max(0, availableStart), 31042 end: Math.min(segmentCount, availableEnd) 31043 }; 31044 } 31045 }; 31046 /** 31047 * Maps a range of numbers to objects with information needed to build the corresponding 31048 * segment list 31049 * 31050 * @name toSegmentsCallback 31051 * @function 31052 * @param {number} number 31053 * Number of the segment 31054 * @param {number} index 31055 * Index of the number in the range list 31056 * @return {{ number: Number, duration: Number, timeline: Number, time: Number }} 31057 * Object with segment timing and duration info 31058 */ 31059 31060 /** 31061 * Returns a callback for Array.prototype.map for mapping a range of numbers to 31062 * information needed to build the segment list. 31063 * 31064 * @param {Object} attributes 31065 * Inherited MPD attributes 31066 * @return {toSegmentsCallback} 31067 * Callback map function 31068 */ 31069 31070 var toSegments = function toSegments(attributes) { 31071 return function (number, index) { 31072 var duration = attributes.duration, 31073 _attributes$timescale3 = attributes.timescale, 31074 timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3, 31075 periodIndex = attributes.periodIndex, 31076 _attributes$startNumb = attributes.startNumber, 31077 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb; 31078 return { 31079 number: startNumber + number, 31080 duration: duration / timescale, 31081 timeline: periodIndex, 31082 time: index * duration 31083 }; 31084 }; 31085 }; 31086 /** 31087 * Returns a list of objects containing segment timing and duration info used for 31088 * building the list of segments. This uses the @duration attribute specified 31089 * in the MPD manifest to derive the range of segments. 31090 * 31091 * @param {Object} attributes 31092 * Inherited MPD attributes 31093 * @return {{number: number, duration: number, time: number, timeline: number}[]} 31094 * List of Objects with segment timing and duration info 31095 */ 31096 31097 31098 var parseByDuration = function parseByDuration(attributes) { 31099 var _attributes$type = attributes.type, 31100 type = _attributes$type === void 0 ? 'static' : _attributes$type, 31101 duration = attributes.duration, 31102 _attributes$timescale4 = attributes.timescale, 31103 timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4, 31104 sourceDuration = attributes.sourceDuration; 31105 31106 var _segmentRange$type = segmentRange[type](attributes), 31107 start = _segmentRange$type.start, 31108 end = _segmentRange$type.end; 31109 31110 var segments = range(start, end).map(toSegments(attributes)); 31111 31112 if (type === 'static') { 31113 var index = segments.length - 1; // final segment may be less than full segment duration 31114 31115 segments[index].duration = sourceDuration - duration / timescale * index; 31116 } 31117 31118 return segments; 31119 }; 31120 /** 31121 * Translates SegmentBase into a set of segments. 31122 * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 31123 * node should be translated into segment. 31124 * 31125 * @param {Object} attributes 31126 * Object containing all inherited attributes from parent elements with attribute 31127 * names as keys 31128 * @return {Object.<Array>} list of segments 31129 */ 31130 31131 31132 var segmentsFromBase = function segmentsFromBase(attributes) { 31133 var baseUrl = attributes.baseUrl, 31134 _attributes$initializ = attributes.initialization, 31135 initialization = _attributes$initializ === void 0 ? {}
31135 : _attributes$initializ, 31136 sourceDuration = attributes.sourceDuration, 31137 _attributes$timescale = attributes.timescale, 31138 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 31139 _attributes$indexRang = attributes.indexRange, 31140 indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang, 31141 duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) 31142 31143 if (!baseUrl) { 31144 throw new Error(errors.NO_BASE_URL); 31145 } 31146 31147 var initSegment = urlTypeToSegment({ 31148 baseUrl: baseUrl, 31149 source: initialization.sourceURL, 31150 range: initialization.range 31151 }); 31152 var segment = urlTypeToSegment({ 31153 baseUrl: baseUrl, 31154 source: baseUrl, 31155 indexRange: indexRange 31156 }); 31157 segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source 31158 // (since SegmentBase is only for one total segment) 31159 31160 if (duration) { 31161 var segmentTimeInfo = parseByDuration(attributes); 31162 31163 if (segmentTimeInfo.length) { 31164 segment.duration = segmentTimeInfo[0].duration; 31165 segment.timeline = segmentTimeInfo[0].timeline; 31166 } 31167 } else if (sourceDuration) { 31168 segment.duration = sourceDuration / timescale; 31169 segment.timeline = 0; 31170 } // This is used for mediaSequence 31171 31172 31173 segment.number = 0; 31174 return [segment]; 31175 }; 31176 /** 31177 * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist 31178 * according to the sidx information given. 31179 * 31180 * playlist.sidx has metadadata about the sidx where-as the sidx param 31181 * is the parsed sidx box itself. 31182 * 31183 * @param {Object} playlist the playlist to update the sidx information for 31184 * @param {Object} sidx the parsed sidx box 31185 * @return {Object} the playlist object with the updated sidx information 31186 */ 31187 31188 31189 var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) { 31190 // Retain init segment information 31191 var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing 31192 31193 var sourceDuration = playlist.sidx.duration; // Retain source timeline 31194 31195 var timeline = playlist.timeline || 0; 31196 var sidxByteRange = playlist.sidx.byterange; 31197 var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx 31198 31199 var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes 31200 31201 var mediaReferences = sidx.references.filter(function (r) { 31202 return r.referenceType !== 1; 31203 }); 31204 var segments = []; // firstOffset is the offset from the end of the sidx box 31205 31206 var startIndex = sidxEnd + sidx.firstOffset; 31207 31208 for (var i = 0; i < mediaReferences.length; i++) { 31209 var reference = sidx.references[i]; // size of the referenced (sub)segment 31210 31211 var size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale 31212 // this will be converted to seconds when generating segments 31213 31214 var duration = reference.subsegmentDuration; // should be an inclusive range 31215 31216 var endIndex = startIndex + size - 1; 31217 var indexRange = startIndex + "-" + endIndex; 31218 var attributes = { 31219 baseUrl: baseUrl, 31220 timescale: timescale, 31221 timeline: timeline, 31222 // this is used in parseByDuration 31223 periodIndex: timeline, 31224 duration: duration, 31225 sourceDuration: sourceDuration, 31226 indexRange: indexRange 31227 }; 31228 var segment = segmentsFromBase(attributes)[0]; 31229 31230 if (initSegment) { 31231 segment.map = initSegment; 31232 } 31233 31234 segments.push(segment); 31235 startIndex += size; 31236 } 31237 31238 playlist.segments = segments; 31239 return playlist; 31240 }; 31241 31242 var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) { 31243 var mergedPlaylists = values(playlists.reduce(function (acc, playlist) { 31244 // assuming playlist IDs are the same across periods 31245 // TODO: handle multiperiod where representation sets are not the same 31246 // across periods 31247 var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first 31248 31249 if (acc[name]) { 31250 var _acc$name$segments; // first segment of subsequent periods signal a discontinuity 31251 31252 31253 if (playlist.segments[0]) { 31254 playlist.segments[0].discontinuity = true; 31255 } 31256 31257 (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content 31258 // has the same contentProtection 31259 31260 31261 if (playlist.attributes.contentProtection) { 31262 acc[name].attributes.contentProtection = playlist.attributes.contentProtection; 31263 } 31264 } else { 31265 // first Period 31266 acc[name] = playlist; 31267 } 31268 31269 return acc; 31270 }, {})); 31271 return mergedPlaylists.map(function (playlist) { 31272 playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity'); 31273 return playlist; 31274 });
vendor: 20,528 bytes, lines 31275-31868
31275 }; 31276 31277 var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) { 31278 if (sidxMapping === void 0) { 31279 sidxMapping = {}; 31280 } 31281 31282 if (!Object.keys(sidxMapping).length) { 31283 return playlists; 31284 } 31285 31286 for (var i in playlists) { 31287 var playlist = playlists[i]; 31288 31289 if (!playlist.sidx) { 31290 continue; 31291 } 31292 31293 var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange); 31294 var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx; 31295 31296 if (playlist.sidx && sidxMatch) { 31297 addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri); 31298 } 31299 } 31300 31301 return playlists; 31302 }; 31303 31304 var formatAudioPlaylist = function formatAudioPlaylist(_ref) { 31305 var _attributes; 31306 31307 var attributes = _ref.attributes, 31308 segments = _ref.segments, 31309 sidx = _ref.sidx; 31310 var playlist = { 31311 attributes: (_attributes = { 31312 NAME: attributes.id, 31313 BANDWIDTH: attributes.bandwidth, 31314 CODECS: attributes.codecs 31315 }, _attributes['PROGRAM-ID'] = 1, _attributes), 31316 uri: '', 31317 endList: (attributes.type || 'static') === 'static', 31318 timeline: attributes.periodIndex, 31319 resolvedUri: '', 31320 targetDuration: attributes.duration, 31321 segments: segments, 31322 mediaSequence: segments.length ? segments[0].number : 1 31323 }; 31324 31325 if (attributes.contentProtection) { 31326 playlist.contentProtection = attributes.contentProtection; 31327 } 31328 31329 if (sidx) { 31330 playlist.sidx = sidx; 31331 } 31332 31333 return playlist; 31334 }; 31335 31336 var formatVttPlaylist = function formatVttPlaylist(_ref2) { 31337 var _attributes2; 31338 31339 var attributes = _ref2.attributes, 31340 segments = _ref2.segments; 31341 31342 if (typeof segments === 'undefined') { 31343 // vtt tracks may use single file in BaseURL 31344 segments = [{ 31345 uri: attributes.baseUrl, 31346 timeline: attributes.periodIndex, 31347 resolvedUri: attributes.baseUrl || '', 31348 duration: attributes.sourceDuration, 31349 number: 0 31350 }]; // targetDuration should be the same duration as the only segment 31351 31352 attributes.duration = attributes.sourceDuration; 31353 } 31354 31355 return { 31356 attributes: (_attributes2 = { 31357 NAME: attributes.id, 31358 BANDWIDTH: attributes.bandwidth 31359 }, _attributes2['PROGRAM-ID'] = 1, _attributes2), 31360 uri: '', 31361 endList: (attributes.type || 'static') === 'static', 31362 timeline: attributes.periodIndex, 31363 resolvedUri: attributes.baseUrl || '', 31364 targetDuration: attributes.duration, 31365 segments: segments, 31366 mediaSequence: segments.length ? segments[0].number : 1 31367 }; 31368 }; 31369 31370 var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) { 31371 if (sidxMapping === void 0) { 31372 sidxMapping = {}; 31373 } 31374 31375 var mainPlaylist; 31376 var formattedPlaylists = playlists.reduce(function (a, playlist) { 31377 var role = playlist.attributes.role && playlist.attributes.role.value || ''; 31378 var language = playlist.attributes.lang || ''; 31379 var label = 'main'; 31380 31381 if (language) { 31382 var roleLabel = role ? " (" + role + ")" : ''; 31383 label = "" + playlist.attributes.lang + roleLabel; 31384 } // skip if we already have the highest quality audio for a language 31385 31386 31387 if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) { 31388 return a; 31389 } 31390 31391 a[label] = { 31392 language: language, 31393 autoselect: true, 31394 "default": role === 'main', 31395 playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping), 31396 uri: '' 31397 }; 31398 31399 if (typeof mainPlaylist === 'undefined' && role === 'main') { 31400 mainPlaylist = playlist; 31401 mainPlaylist["default"] = true; 31402 } 31403 31404 return a; 31405 }, {}); // if no playlists have role "main", mark the first as main 31406 31407 if (!mainPlaylist) { 31408 var firstLabel = Object.keys(formattedPlaylists)[0]; 31409 formattedPlaylists[firstLabel]["default"] = true; 31410 } 31411 31412 return formattedPlaylists; 31413 }; 31414 31415 var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) { 31416 if (sidxMapping === void 0) { 31417 sidxMapping = {}; 31418 } 31419 31420 return playlists.reduce(function (a, playlist) { 31421 var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles 31422 31423 if (a[label]) { 31424 return a; 31425 } 31426 31427 a[label] = { 31428 language: label, 31429 "default": false, 31430 autoselect: false, 31431 playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping), 31432 uri: '' 31433 }; 31434 return a; 31435 }, {}); 31436 }; 31437 31438 var formatVideoPlaylist = function formatVideoPlaylist(_ref3) { 31439 var _attributes3; 31440 31441 var attributes = _ref3.attributes, 31442 segments = _ref3.segments, 31443 sidx = _ref3.sidx; 31444 var playlist = { 31445 attributes: (_attributes3 = { 31446 NAME: attributes.id, 31447 AUDIO: 'audio', 31448 SUBTITLES: 'subs', 31449 RESOLUTION: { 31450 width: attributes.width, 31451 height: attributes.height 31452 }, 31453 CODECS: attributes.codecs, 31454 BANDWIDTH: attributes.bandwidth 31455 }, _attributes3['PROGRAM-ID'] = 1, _attributes3), 31456 uri: '', 31457 endList: (attributes.type || 'static') === 'static', 31458 timeline: attributes.periodIndex, 31459 resolvedUri: '', 31460 targetDuration: attributes.duration, 31461 segments: segments, 31462 mediaSequence: segments.length ? segments[0].number : 1 31463 }; 31464 31465 if (attributes.contentProtection) { 31466 playlist.contentProtection = attributes.contentProtection; 31467 } 31468 31469 if (sidx) { 31470 playlist.sidx = sidx; 31471 } 31472 31473 return playlist; 31474 }; 31475 31476 var toM3u8 = function toM3u8(dashPlaylists, sidxMapping) { 31477 var _mediaGroups; 31478 31479 if (sidxMapping === void 0) { 31480 sidxMapping = {}; 31481 } 31482 31483 if (!dashPlaylists.length) { 31484 return {}; 31485 } // grab all master attributes 31486 31487 31488 var _dashPlaylists$0$attr = dashPlaylists[0].attributes, 31489 duration = _dashPlaylists$0$attr.sourceDuration, 31490 _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod, 31491 minimumUpdatePeriod = _dashPlaylists$0$attr2 === void 0 ? 0 : _dashPlaylists$0$attr2; 31492 31493 var videoOnly = function videoOnly(_ref4) { 31494 var attributes = _ref4.attributes; 31495 return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video'; 31496 }; 31497 31498 var audioOnly = function audioOnly(_ref5) { 31499 var attributes = _ref5.attributes; 31500 return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio'; 31501 }; 31502 31503 var vttOnly = function vttOnly(_ref6) { 31504 var attributes = _ref6.attributes; 31505 return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text'; 31506 }; 31507 31508 var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist); 31509 var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly)); 31510 var vttPlaylists = dashPlaylists.filter(vttOnly); 31511 var master = { 31512 allowCache: true, 31513 discontinuityStarts: [], 31514 segments: [], 31515 endList: true, 31516 mediaGroups: (_mediaGroups = { 31517 AUDIO: {}, 31518 VIDEO: {} 31519 }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups), 31520 uri: '', 31521 duration: duration, 31522 playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping), 31523 minimumUpdatePeriod: minimumUpdatePeriod * 1000 31524 }; 31525 31526 if (audioPlaylists.length) { 31527 master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping); 31528 } 31529 31530 if (vttPlaylists.length) { 31531 master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping); 31532 } 31533 31534 return master; 31535 }; 31536 /** 31537 * Calculates the R (repetition) value for a live stream (for the final segment 31538 * in a manifest where the r value is negative 1) 31539 * 31540 * @param {Object} attributes 31541 * Object containing all inherited attributes from parent elements with attribute 31542 * names as keys 31543 * @param {number} time 31544 * current time (typically the total time up until the final segment) 31545 * @param {number} duration 31546 * duration property for the given <S /> 31547 * 31548 * @return {number} 31549 * R value to reach the end of the given period 31550 */ 31551 31552 31553 var getLiveRValue = function getLiveRValue(attributes, time, duration) { 31554 var NOW = attributes.NOW, 31555 clientOffset = attributes.clientOffset, 31556 availabilityStartTime = attributes.availabilityStartTime, 31557 _attributes$timescale = attributes.timescale, 31558 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 31559 _attributes$start = attributes.start, 31560 start = _attributes$start === void 0 ? 0 : _attributes$start, 31561 _attributes$minimumUp = attributes.minimumUpdatePeriod, 31562 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp; 31563 var now = (NOW + clientOffset) / 1000; 31564 var periodStartWC = availabilityStartTime + start; 31565 var periodEndWC = now + minimumUpdatePeriod; 31566 var periodDuration = periodEndWC - periodStartWC; 31567 return Math.ceil((periodDuration * timescale - time) / duration); 31568 }; 31569 /** 31570 * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment 31571 * timing and duration 31572 * 31573 * @param {Object} attributes 31574 * Object containing all inherited attributes from parent elements with attribute 31575 * names as keys 31576 * @param {Object[]} segmentTimeline 31577 * List of objects representing the attributes of each S element contained within 31578 * 31579 * @return {{number: number, duration: number, time: number, timeline: number}[]} 31580 * List of Objects with segment timing and duration info 31581 */ 31582 31583 31584 var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) { 31585 var _attributes$type = attributes.type, 31586 type = _attributes$type === void 0 ? 'static' : _attributes$type, 31587 _attributes$minimumUp2 = attributes.minimumUpdatePeriod, 31588 minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2, 31589 _attributes$media = attributes.media, 31590 media = _attributes$media === void 0 ? '' : _attributes$media, 31591 sourceDuration = attributes.sourceDuration, 31592 _attributes$timescale2 = attributes.timescale, 31593 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 31594 _attributes$startNumb = attributes.startNumber, 31595 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb, 31596 timeline = attributes.periodIndex; 31597 var segments = []; 31598 var time = -1; 31599 31600 for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { 31601 var S = segmentTimeline[sIndex]; 31602 var duration = S.d; 31603 var repeat = S.r || 0; 31604 var segmentTime = S.t || 0; 31605 31606 if (time < 0) { 31607 // first segment 31608 time = segmentTime; 31609 } 31610 31611 if (segmentTime && segmentTime > time) { 31612 // discontinuity 31613 // TODO: How to handle this type of discontinuity 31614 // timeline++ here would treat it like HLS discontuity and content would 31615 // get appended without gap 31616 // E.G. 31617 // <S t="0" d="1" /> 31618 // <S d="1" /> 31619 // <S d="1" /> 31620 // <S t="5" d="1" /> 31621 // would have $Time$ values of [0, 1, 2, 5] 31622 // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) 31623 // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) 31624 // does the value of sourceDuration consider this when calculating arbitrary 31625 // negative @r repeat value? 31626 // E.G. Same elements as above with this added at the end 31627 // <S d="1" r="-1" /> 31628 // with a sourceDuration of 10 31629 // Would the 2 gaps be included in the time duration calculations resulting in 31630 // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments 31631 // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? 31632 time = segmentTime; 31633 } 31634 31635 var count = void 0; 31636 31637 if (repeat < 0) { 31638 var nextS = sIndex + 1; 31639 31640 if (nextS === segmentTimeline.length) { 31641 // last segment 31642 if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) { 31643 count = getLiveRValue(attributes, time, duration); 31644 } else { 31645 // TODO: This may be incorrect depending on conclusion of TODO above 31646 count = (sourceDuration * timescale - time) / duration; 31647 } 31648 } else { 31649 count = (segmentTimeline[nextS].t - time) / duration; 31650 } 31651 } else { 31652 count = repeat + 1; 31653 } 31654 31655 var end = startNumber + segments.length + count; 31656 var number = startNumber + segments.length; 31657 31658 while (number < end) { 31659 segments.push({ 31660 number: number, 31661 duration: duration / timescale, 31662 time: time, 31663 timeline: timeline 31664 }); 31665 time += duration; 31666 number++; 31667 } 31668 } 31669 31670 return segments; 31671 }; 31672 31673 var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; 31674 /** 31675 * Replaces template identifiers with corresponding values. To be used as the callback 31676 * for String.prototype.replace 31677 * 31678 * @name replaceCallback 31679 * @function 31680 * @param {string} match 31681 * Entire match of identifier 31682 * @param {string} identifier 31683 * Name of matched identifier 31684 * @param {string} format 31685 * Format tag string. Its presence indicates that padding is expected 31686 * @param {string} width 31687 * Desired length of the replaced value. Values less than this width shall be left 31688 * zero padded 31689 * @return {string} 31690 * Replacement for the matched identifier 31691 */ 31692 31693 /** 31694 * Returns a function to be used as a callback for String.prototype.replace to replace 31695 * template identifiers 31696 * 31697 * @param {Obect} values 31698 * Object containing values that shall be used to replace known identifiers 31699 * @param {number} values.RepresentationID 31700 * Value of the Representation@id attribute 31701 * @param {number} values.Number 31702 * Number of the corresponding segment 31703 * @param {number} values.Bandwidth 31704 * Value of the Representation@bandwidth attribute. 31705 * @param {number} values.Time 31706 * Timestamp value of the corresponding segment 31707 * @return {replaceCallback} 31708 * Callback to be used with String.prototype.replace to replace identifiers 31709 */ 31710 31711 var identifierReplacement = function identifierReplacement(values) { 31712 return function (match, identifier, format, width) { 31713 if (match === '$$') { 31714 // escape sequence 31715 return '$'; 31716 } 31717 31718 if (typeof values[identifier] === 'undefined') { 31719 return match; 31720 } 31721 31722 var value = '' + values[identifier]; 31723 31724 if (identifier === 'RepresentationID') { 31725 // Format tag shall not be present with RepresentationID 31726 return value; 31727 } 31728 31729 if (!format) { 31730 width = 1; 31731 } else { 31732 width = parseInt(width, 10); 31733 } 31734 31735 if (value.length >= width) { 31736 return value; 31737 } 31738 31739 return "" + new Array(width - value.length + 1).join('0') + value; 31740 }; 31741 }; 31742 /** 31743 * Constructs a segment url from a template string 31744 * 31745 * @param {string} url 31746 * Template string to construct url from 31747 * @param {Obect} values 31748 * Object containing values that shall be used to replace known identifiers 31749 * @param {number} values.RepresentationID 31750 * Value of the Representation@id attribute 31751 * @param {number} values.Number 31752 * Number of the corresponding segment 31753 * @param {number} values.Bandwidth 31754 * Value of the Representation@bandwidth attribute. 31755 * @param {number} values.Time 31756 * Timestamp value of the corresponding segment 31757 * @return {string} 31758 * Segment url with identifiers replaced 31759 */ 31760 31761 31762 var constructTemplateUrl = function constructTemplateUrl(url, values) { 31763 return url.replace(identifierPattern, identifierReplacement(values)); 31764 }; 31765 /** 31766 * Generates a list of objects containing timing and duration information about each 31767 * segment needed to generate segment uris and the complete segment object 31768 * 31769 * @param {Object} attributes 31770 * Object containing all inherited attributes from parent elements with attribute 31771 * names as keys 31772 * @param {Object[]|undefined} segmentTimeline 31773 * List of objects representing the attributes of each S element contained within 31774 * the SegmentTimeline element 31775 * @return {{number: number, duration: number, time: number, timeline: number}[]} 31776 * List of Objects with segment timing and duration info 31777 */ 31778 31779 31780 var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) { 31781 if (!attributes.duration && !segmentTimeline) { 31782 // if neither @duration or SegmentTimeline are present, then there shall be exactly 31783 // one media segment 31784 return [{ 31785 number: attributes.startNumber || 1, 31786 duration: attributes.sourceDuration, 31787 time: 0, 31788 timeline: attributes.periodIndex 31789 }]; 31790 } 31791 31792 if (attributes.duration) { 31793 return parseByDuration(attributes); 31794 } 31795 31796 return parseByTimeline(attributes, segmentTimeline); 31797 }; 31798 /** 31799 * Generates a list of segments using information provided by the SegmentTemplate element 31800 * 31801 * @param {Object} attributes 31802 * Object containing all inherited attributes from parent elements with attribute 31803 * names as keys 31804 * @param {Object[]|undefined} segmentTimeline 31805 * List of objects representing the attributes of each S element contained within 31806 * the SegmentTimeline element 31807 * @return {Object[]} 31808 * List of segment objects 31809 */ 31810 31811 31812 var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) { 31813 var templateValues = { 31814 RepresentationID: attributes.id, 31815 Bandwidth: attributes.bandwidth || 0 31816 }; 31817 var _attributes$initializ = attributes.initialization, 31818 initialization = _attributes$initializ === void 0 ? { 31819 sourceURL: '', 31820 range: '' 31821 } : _attributes$initializ; 31822 var mapSegment = urlTypeToSegment({ 31823 baseUrl: attributes.baseUrl, 31824 source: constructTemplateUrl(initialization.sourceURL, templateValues), 31825 range: initialization.range 31826 }); 31827 var segments = parseTemplateInfo(attributes, segmentTimeline); 31828 return segments.map(function (segment) { 31829 templateValues.Number = segment.number; 31830 templateValues.Time = segment.time; 31831 var uri = constructTemplateUrl(attributes.media || '', templateValues); 31832 return { 31833 uri: uri, 31834 timeline: segment.timeline, 31835 duration: segment.duration, 31836 resolvedUri: resolveUrl(attributes.baseUrl || '', uri), 31837 map: mapSegment, 31838 number: segment.number 31839 }; 31840 }); 31841 }; 31842 /** 31843 * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14) 31844 * to an object that matches the output of a segment in videojs/mpd-parser 31845 * 31846 * @param {Object} attributes 31847 * Object containing all inherited attributes from parent elements with attribute 31848 * names as keys 31849 * @param {Object} segmentUrl 31850 * <SegmentURL> node to translate into a segment object 31851 * @return {Object} translated segment object 31852 */ 31853 31854 31855 var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) { 31856 var baseUrl = attributes.baseUrl, 31857 _attributes$initializ = attributes.initialization, 31858 initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ; 31859 var initSegment = urlTypeToSegment({ 31860 baseUrl: baseUrl, 31861 source: initialization.sourceURL, 31862 range: initialization.range 31863 }); 31864 var segment = urlTypeToSegment({ 31865 baseUrl: baseUrl, 31866 source: segmentUrl.media, 31867 range: segmentUrl.mediaRange 31868 });
31869 segment.map = initSegment; 31870 return segment; 31871 }; 31872 /** 31873 * Generates a list of segments using information provided by the SegmentList element 31874 * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 31875 * node should be translated into segment. 31876 * 31877 * @param {Object} attributes 31878 * Object containing all inherited attributes from parent elements with attribute 31879 * names as keys 31880 * @param {Object[]|undefined} segmentTimeline 31881 * List of objects representing the attributes of each S element contained within 31882 * the SegmentTimeline element 31883 * @return {Object.<Array>} list of segments 31884 */ 31885 31886 31887 var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) { 31888 var duration = attributes.duration, 31889 _attributes$segmentUr = attributes.segmentUrls, 31890 segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR 31891 // if both SegmentTimeline and @duration are defined, it is outside of spec. 31892 31893 if (!duration && !segmentTimeline || duration && segmentTimeline) { 31894 throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); 31895 } 31896 31897 var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) { 31898 return SegmentURLToSegmentObject(attributes, segmentUrlObject); 31899 }); 31900 var segmentTimeInfo; 31901 31902 if (duration) { 31903 segmentTimeInfo = parseByDuration(attributes); 31904 } 31905 31906 if (segmentTimeline) { 31907 segmentTimeInfo = parseByTimeline(attributes, segmentTimeline); 31908 } 31909 31910 var segments = segmentTimeInfo.map(function (segmentTime, index) { 31911 if (segmentUrlMap[index]) { 31912 var segment = segmentUrlMap[index]; 31913 segment.timeline = segmentTime.timeline; 31914 segment.duration = segmentTime.duration; 31915 segment.number = segmentTime.number; 31916 return segment; 31917 } // Since we're mapping we should get rid of any blank segments (in case 31918 // the given SegmentTimeline is handling for more elements than we have 31919 // SegmentURLs for). 31920 31921 }).filter(function (segment) { 31922 return segment; 31923 }); 31924 return segments; 31925 }; 31926 31927 var generateSegments = function generateSegments(_ref) { 31928 var attributes = _ref.attributes, 31929 segmentInfo = _ref.segmentInfo; 31930 var segmentAttributes; 31931 var segmentsFn; 31932 31933 if (segmentInfo.template) { 31934 segmentsFn = segmentsFromTemplate; 31935 segmentAttributes = merge(attributes, segmentInfo.template); 31936 } else if (segmentInfo.base) { 31937 segmentsFn = segmentsFromBase; 31938 segmentAttributes = merge(attributes, segmentInfo.base); 31939 } else if (segmentInfo.list) { 31940 segmentsFn = segmentsFromList; 31941 segmentAttributes = merge(attributes, segmentInfo.list); 31942 } 31943 31944 var segmentsInfo = { 31945 attributes: attributes 31946 }; 31947 31948 if (!segmentsFn) { 31949 return segmentsInfo; 31950 } 31951 31952 var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which 31953 // must be in seconds. Since we've generated the segment list, we no longer need 31954 // @duration to be in @timescale units, so we can convert it here. 31955 31956 if (segmentAttributes.duration) { 31957 var _segmentAttributes = segmentAttributes, 31958 duration = _segmentAttributes.duration, 31959 _segmentAttributes$ti = _segmentAttributes.timescale, 31960 timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti; 31961 segmentAttributes.duration = duration / timescale; 31962 } else if (segments.length) { 31963 // if there is no @duration attribute, use the largest segment duration as 31964 // as target duration 31965 segmentAttributes.duration = segments.reduce(function (max, segment) { 31966 return Math.max(max, Math.ceil(segment.duration)); 31967 }, 0); 31968 } else { 31969 segmentAttributes.duration = 0; 31970 } 31971 31972 segmentsInfo.attributes = segmentAttributes; 31973 segmentsInfo.segments = segments; // This is a sidx box without actual segment information 31974 31975 if (segmentInfo.base && segmentAttributes.indexRange) { 31976 segmentsInfo.sidx = segments[0]; 31977 segmentsInfo.segments = []; 31978 } 31979 31980 return segmentsInfo; 31981 }; 31982 31983 var toPlaylists = function toPlaylists(representations) { 31984 return representations.map(generateSegments); 31985 }; 31986
vendor: 13,526 bytes, lines 31987-32383
31987 var findChildren = function findChildren(element, name) { 31988 return from(element.childNodes).filter(function (_ref) { 31989 var tagName = _ref.tagName; 31990 return tagName === name; 31991 }); 31992 }; 31993 31994 var getContent = function getContent(element) { 31995 return element.textContent.trim(); 31996 }; 31997 31998 var parseDuration = function parseDuration(str) { 31999 var SECONDS_IN_YEAR = 365 * 24 * 60 * 60; 32000 var SECONDS_IN_MONTH = 30 * 24 * 60 * 60; 32001 var SECONDS_IN_DAY = 24 * 60 * 60; 32002 var SECONDS_IN_HOUR = 60 * 60; 32003 var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S 32004 32005 var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/; 32006 var match = durationRegex.exec(str); 32007 32008 if (!match) { 32009 return 0; 32010 } 32011 32012 var _match$slice = match.slice(1), 32013 year = _match$slice[0], 32014 month = _match$slice[1], 32015 day = _match$slice[2], 32016 hour = _match$slice[3], 32017 minute = _match$slice[4], 32018 second = _match$slice[5]; 32019 32020 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); 32021 }; 32022 32023 var parseDate = function parseDate(str) { 32024 // Date format without timezone according to ISO 8601 32025 // YYY-MM-DDThh:mm:ss.ssssss 32026 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 32027 // expressed by ending with 'Z' 32028 32029 if (dateRegex.test(str)) { 32030 str += 'Z'; 32031 } 32032 32033 return Date.parse(str); 32034 }; 32035 32036 var parsers = { 32037 /** 32038 * Specifies the duration of the entire Media Presentation. Format is a duration string 32039 * as specified in ISO 8601 32040 * 32041 * @param {string} value 32042 * value of attribute as a string 32043 * @return {number} 32044 * The duration in seconds 32045 */ 32046 mediaPresentationDuration: function mediaPresentationDuration(value) { 32047 return parseDuration(value); 32048 }, 32049 32050 /** 32051 * Specifies the Segment availability start time for all Segments referred to in this 32052 * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability 32053 * time. Format is a date string as specified in ISO 8601 32054 * 32055 * @param {string} value 32056 * value of attribute as a string 32057 * @return {number} 32058 * The date as seconds from unix epoch 32059 */ 32060 availabilityStartTime: function availabilityStartTime(value) { 32061 return parseDate(value) / 1000; 32062 }, 32063 32064 /** 32065 * Specifies the smallest period between potential changes to the MPD. Format is a 32066 * duration string as specified in ISO 8601 32067 * 32068 * @param {string} value 32069 * value of attribute as a string 32070 * @return {number} 32071 * The duration in seconds 32072 */ 32073 minimumUpdatePeriod: function minimumUpdatePeriod(value) { 32074 return parseDuration(value); 32075 }, 32076 32077 /** 32078 * Specifies the duration of the smallest time shifting buffer for any Representation 32079 * in the MPD. Format is a duration string as specified in ISO 8601 32080 * 32081 * @param {string} value 32082 * value of attribute as a string 32083 * @return {number} 32084 * The duration in seconds 32085 */ 32086 timeShiftBufferDepth: function timeShiftBufferDepth(value) { 32087 return parseDuration(value); 32088 }, 32089 32090 /** 32091 * Specifies the PeriodStart time of the Period relative to the availabilityStarttime. 32092 * Format is a duration string as specified in ISO 8601 32093 * 32094 * @param {string} value 32095 * value of attribute as a string 32096 * @return {number} 32097 * The duration in seconds 32098 */ 32099 start: function start(value) { 32100 return parseDuration(value); 32101 }, 32102 32103 /** 32104 * Specifies the width of the visual presentation 32105 * 32106 * @param {string} value 32107 * value of attribute as a string 32108 * @return {number} 32109 * The parsed width 32110 */ 32111 width: function width(value) { 32112 return parseInt(value, 10); 32113 }, 32114 32115 /** 32116 * Specifies the height of the visual presentation 32117 * 32118 * @param {string} value 32119 * value of attribute as a string 32120 * @return {number} 32121 * The parsed height 32122 */ 32123 height: function height(value) { 32124 return parseInt(value, 10); 32125 }, 32126 32127 /** 32128 * Specifies the bitrate of the representation 32129 * 32130 * @param {string} value 32131 * value of attribute as a string 32132 * @return {number} 32133 * The parsed bandwidth 32134 */ 32135 bandwidth: function bandwidth(value) { 32136 return parseInt(value, 10); 32137 }, 32138 32139 /** 32140 * Specifies the number of the first Media Segment in this Representation in the Period 32141 * 32142 * @param {string} value 32143 * value of attribute as a string 32144 * @return {number} 32145 * The parsed number 32146 */ 32147 startNumber: function startNumber(value) { 32148 return parseInt(value, 10); 32149 }, 32150 32151 /** 32152 * Specifies the timescale in units per seconds 32153 * 32154 * @param {string} value 32155 * value of attribute as a string 32156 * @return {number} 32157 * The aprsed timescale 32158 */ 32159 timescale: function timescale(value) { 32160 return parseInt(value, 10); 32161 }, 32162 32163 /** 32164 * Specifies the constant approximate Segment duration 32165 * NOTE: The <Period> element also contains an @duration attribute. This duration 32166 * specifies the duration of the Period. This attribute is currently not 32167 * supported by the rest of the parser, however we still check for it to prevent 32168 * errors. 32169 * 32170 * @param {string} value 32171 * value of attribute as a string 32172 * @return {number} 32173 * The parsed duration 32174 */ 32175 duration: function duration(value) { 32176 var parsedValue = parseInt(value, 10); 32177 32178 if (isNaN(parsedValue)) { 32179 return parseDuration(value); 32180 } 32181 32182 return parsedValue; 32183 }, 32184 32185 /** 32186 * Specifies the Segment duration, in units of the value of the @timescale. 32187 * 32188 * @param {string} value 32189 * value of attribute as a string 32190 * @return {number} 32191 * The parsed duration 32192 */ 32193 d: function d(value) { 32194 return parseInt(value, 10); 32195 }, 32196 32197 /** 32198 * Specifies the MPD start time, in @timescale units, the first Segment in the series 32199 * starts relative to the beginning of the Period 32200 * 32201 * @param {string} value 32202 * value of attribute as a string 32203 * @return {number} 32204 * The parsed time 32205 */ 32206 t: function t(value) { 32207 return parseInt(value, 10); 32208 }, 32209 32210 /** 32211 * Specifies the repeat count of the number of following contiguous Segments with the 32212 * same duration expressed by the value of @d 32213 * 32214 * @param {string} value 32215 * value of attribute as a string 32216 * @return {number} 32217 * The parsed number 32218 */ 32219 r: function r(value) { 32220 return parseInt(value, 10); 32221 }, 32222 32223 /** 32224 * Default parser for all other attributes. Acts as a no-op and just returns the value 32225 * as a string 32226 * 32227 * @param {string} value 32228 * value of attribute as a string 32229 * @return {string} 32230 * Unparsed value 32231 */ 32232 DEFAULT: function DEFAULT(value) { 32233 return value; 32234 } 32235 }; 32236 /** 32237 * Gets all the attributes and values of the provided node, parses attributes with known 32238 * types, and returns an object with attribute names mapped to values. 32239 * 32240 * @param {Node} el 32241 * The node to parse attributes from 32242 * @return {Object} 32243 * Object with all attributes of el parsed 32244 */ 32245 32246 var parseAttributes$1 = function parseAttributes(el) { 32247 if (!(el && el.attributes)) { 32248 return {}; 32249 } 32250 32251 return from(el.attributes).reduce(function (a, e) { 32252 var parseFn = parsers[e.name] || parsers.DEFAULT; 32253 a[e.name] = parseFn(e.value); 32254 return a; 32255 }, {}); 32256 }; 32257 32258 function decodeB64ToUint8Array$1(b64Text) { 32259 var decodedString = window$1.atob(b64Text); 32260 var array = new Uint8Array(decodedString.length); 32261 32262 for (var i = 0; i < decodedString.length; i++) { 32263 array[i] = decodedString.charCodeAt(i); 32264 } 32265 32266 return array; 32267 } 32268 32269 var keySystemsMap = { 32270 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', 32271 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', 32272 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready', 32273 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime' 32274 }; 32275 /** 32276 * Builds a list of urls that is the product of the reference urls and BaseURL values 32277 * 32278 * @param {string[]} referenceUrls 32279 * List of reference urls to resolve to 32280 * @param {Node[]} baseUrlElements 32281 * List of BaseURL nodes from the mpd 32282 * @return {string[]} 32283 * List of resolved urls 32284 */ 32285 32286 var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) { 32287 if (!baseUrlElements.length) { 32288 return referenceUrls; 32289 } 32290 32291 return flatten(referenceUrls.map(function (reference) { 32292 return baseUrlElements.map(function (baseUrlElement) { 32293 return resolveUrl(reference, getContent(baseUrlElement)); 32294 }); 32295 })); 32296 }; 32297 /** 32298 * Contains all Segment information for its containing AdaptationSet 32299 * 32300 * @typedef {Object} SegmentInformation 32301 * @property {Object|undefined} template 32302 * Contains the attributes for the SegmentTemplate node 32303 * @property {Object[]|undefined} timeline 32304 * Contains a list of atrributes for each S node within the SegmentTimeline node 32305 * @property {Object|undefined} list 32306 * Contains the attributes for the SegmentList node 32307 * @property {Object|undefined} base 32308 * Contains the attributes for the SegmentBase node 32309 */ 32310 32311 /** 32312 * Returns all available Segment information contained within the AdaptationSet node 32313 * 32314 * @param {Node} adaptationSet 32315 * The AdaptationSet node to get Segment information from 32316 * @return {SegmentInformation} 32317 * The Segment information contained within the provided AdaptationSet 32318 */ 32319 32320 32321 var getSegmentInformation = function getSegmentInformation(adaptationSet) { 32322 var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; 32323 var segmentList = findChildren(adaptationSet, 'SegmentList')[0]; 32324 var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) { 32325 return merge({ 32326 tag: 'SegmentURL' 32327 }, parseAttributes$1(s)); 32328 }); 32329 var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; 32330 var segmentTimelineParentNode = segmentList || segmentTemplate; 32331 var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0]; 32332 var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate; 32333 var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both 32334 // @initialization and an <Initialization> node. @initialization can be templated, 32335 // while the node can have a url and range specified. If the <SegmentTemplate> has 32336 // both @initialization and an <Initialization> subelement we opt to override with 32337 // the node, as this interaction is not defined in the spec. 32338 32339 var template = segmentTemplate && parseAttributes$1(segmentTemplate); 32340 32341 if (template && segmentInitialization) { 32342 template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization); 32343 } else if (template && template.initialization) { 32344 // If it is @initialization we convert it to an object since this is the format that 32345 // later functions will rely on for the initialization segment. This is only valid 32346 // for <SegmentTemplate> 32347 template.initialization = { 32348 sourceURL: template.initialization 32349 }; 32350 } 32351 32352 var segmentInfo = { 32353 template: template, 32354 timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) { 32355 return parseAttributes$1(s); 32356 }), 32357 list: segmentList && merge(parseAttributes$1(segmentList), { 32358 segmentUrls: segmentUrls, 32359 initialization: parseAttributes$1(segmentInitialization) 32360 }), 32361 base: segmentBase && merge(parseAttributes$1(segmentBase), { 32362 initialization: parseAttributes$1(segmentInitialization) 32363 }) 32364 }; 32365 Object.keys(segmentInfo).forEach(function (key) { 32366 if (!segmentInfo[key]) { 32367 delete segmentInfo[key]; 32368 } 32369 }); 32370 return segmentInfo; 32371 }; 32372 /** 32373 * Contains Segment information and attributes needed to construct a Playlist object 32374 * from a Representation 32375 * 32376 * @typedef {Object} RepresentationInformation 32377 * @property {SegmentInformation} segmentInfo 32378 * Segment information for this Representation 32379 * @property {Object} attributes 32380 * Inherited attributes for this Representation 32381 */ 32382 32383 /**
32384 * Maps a Representation node to an object containing Segment information and attributes 32385 * 32386 * @name inheritBaseUrlsCallback 32387 * @function 32388 * @param {Node} representation 32389 * Representation node from the mpd 32390 * @return {RepresentationInformation} 32391 * Representation information needed to construct a Playlist object 32392 */ 32393 32394 /** 32395 * Returns a callback for Array.prototype.map for mapping Representation nodes to 32396 * Segment information and attributes using inherited BaseURL nodes. 32397 * 32398 * @param {Object} adaptationSetAttributes 32399 * Contains attributes inherited by the AdaptationSet 32400 * @param {string[]} adaptationSetBaseUrls 32401 * Contains list of resolved base urls inherited by the AdaptationSet 32402 * @param {SegmentInformation} adaptationSetSegmentInfo 32403 * Contains Segment information for the AdaptationSet 32404 * @return {inheritBaseUrlsCallback} 32405 * Callback map function 32406 */ 32407 32408 32409 var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) { 32410 return function (representation) { 32411 var repBaseUrlElements = findChildren(representation, 'BaseURL'); 32412 var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements); 32413 var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation)); 32414 var representationSegmentInfo = getSegmentInformation(representation); 32415 return repBaseUrls.map(function (baseUrl) { 32416 return { 32417 segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo), 32418 attributes: merge(attributes, { 32419 baseUrl: baseUrl 32420 }) 32421 }; 32422 }); 32423 }; 32424 }; 32425 /** 32426 * Tranforms a series of content protection nodes to
vendor: 13,158 bytes, lines 32427-32817
32427 * an object containing pssh data by key system 32428 * 32429 * @param {Node[]} contentProtectionNodes 32430 * Content protection nodes 32431 * @return {Object} 32432 * Object containing pssh data by key system 32433 */ 32434 32435 32436 var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) { 32437 return contentProtectionNodes.reduce(function (acc, node) { 32438 var attributes = parseAttributes$1(node); 32439 var keySystem = keySystemsMap[attributes.schemeIdUri]; 32440 32441 if (keySystem) { 32442 acc[keySystem] = { 32443 attributes: attributes 32444 }; 32445 var psshNode = findChildren(node, 'cenc:pssh')[0]; 32446 32447 if (psshNode) { 32448 var pssh = getContent(psshNode); 32449 var psshBuffer = pssh && decodeB64ToUint8Array$1(pssh); 32450 acc[keySystem].pssh = psshBuffer; 32451 } 32452 } 32453 32454 return acc; 32455 }, {}); 32456 }; 32457 /** 32458 * Maps an AdaptationSet node to a list of Representation information objects 32459 * 32460 * @name toRepresentationsCallback 32461 * @function 32462 * @param {Node} adaptationSet 32463 * AdaptationSet node from the mpd 32464 * @return {RepresentationInformation[]} 32465 * List of objects containing Representaion information 32466 */ 32467 32468 /** 32469 * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of 32470 * Representation information objects 32471 * 32472 * @param {Object} periodAttributes 32473 * Contains attributes inherited by the Period 32474 * @param {string[]} periodBaseUrls 32475 * Contains list of resolved base urls inherited by the Period 32476 * @param {string[]} periodSegmentInfo 32477 * Contains Segment Information at the period level 32478 * @return {toRepresentationsCallback} 32479 * Callback map function 32480 */ 32481 32482 32483 var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) { 32484 return function (adaptationSet) { 32485 var adaptationSetAttributes = parseAttributes$1(adaptationSet); 32486 var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL')); 32487 var role = findChildren(adaptationSet, 'Role')[0]; 32488 var roleAttributes = { 32489 role: parseAttributes$1(role) 32490 }; 32491 var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes); 32492 var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection')); 32493 32494 if (Object.keys(contentProtection).length) { 32495 attrs = merge(attrs, { 32496 contentProtection: contentProtection 32497 }); 32498 } 32499 32500 var segmentInfo = getSegmentInformation(adaptationSet); 32501 var representations = findChildren(adaptationSet, 'Representation'); 32502 var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo); 32503 return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo))); 32504 }; 32505 }; 32506 /** 32507 * Maps an Period node to a list of Representation inforamtion objects for all 32508 * AdaptationSet nodes contained within the Period 32509 * 32510 * @name toAdaptationSetsCallback 32511 * @function 32512 * @param {Node} period 32513 * Period node from the mpd 32514 * @param {number} periodIndex 32515 * Index of the Period within the mpd 32516 * @return {RepresentationInformation[]} 32517 * List of objects containing Representaion information 32518 */ 32519 32520 /** 32521 * Returns a callback for Array.prototype.map for mapping Period nodes to a list of 32522 * Representation information objects 32523 * 32524 * @param {Object} mpdAttributes 32525 * Contains attributes inherited by the mpd 32526 * @param {string[]} mpdBaseUrls 32527 * Contains list of resolved base urls inherited by the mpd 32528 * @return {toAdaptationSetsCallback} 32529 * Callback map function 32530 */ 32531 32532 32533 var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) { 32534 return function (period, index) { 32535 var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL')); 32536 var periodAtt = parseAttributes$1(period); 32537 var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number 32538 32539 var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId; 32540 var periodAttributes = merge(mpdAttributes, { 32541 periodIndex: periodIndex 32542 }); 32543 var adaptationSets = findChildren(period, 'AdaptationSet'); 32544 var periodSegmentInfo = getSegmentInformation(period); 32545 return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo))); 32546 }; 32547 }; 32548 /** 32549 * Traverses the mpd xml tree to generate a list of Representation information objects 32550 * that have inherited attributes from parent nodes 32551 * 32552 * @param {Node} mpd 32553 * The root node of the mpd 32554 * @param {Object} options 32555 * Available options for inheritAttributes 32556 * @param {string} options.manifestUri 32557 * The uri source of the mpd 32558 * @param {number} options.NOW 32559 * Current time per DASH IOP. Default is current time in ms since epoch 32560 * @param {number} options.clientOffset 32561 * Client time difference from NOW (in milliseconds) 32562 * @return {RepresentationInformation[]} 32563 * List of objects containing Representation information 32564 */ 32565 32566 32567 var inheritAttributes = function inheritAttributes(mpd, options) { 32568 if (options === void 0) { 32569 options = {}; 32570 } 32571 32572 var _options = options, 32573 _options$manifestUri = _options.manifestUri, 32574 manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri, 32575 _options$NOW = _options.NOW, 32576 NOW = _options$NOW === void 0 ? Date.now() : _options$NOW, 32577 _options$clientOffset = _options.clientOffset, 32578 clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset; 32579 var periods = findChildren(mpd, 'Period'); 32580 32581 if (!periods.length) { 32582 throw new Error(errors.INVALID_NUMBER_OF_PERIOD); 32583 } 32584 32585 var mpdAttributes = parseAttributes$1(mpd); 32586 var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); 32587 mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0; 32588 mpdAttributes.NOW = NOW; 32589 mpdAttributes.clientOffset = clientOffset; 32590 return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))); 32591 }; 32592 32593 var stringToMpdXml = function stringToMpdXml(manifestString) { 32594 if (manifestString === '') { 32595 throw new Error(errors.DASH_EMPTY_MANIFEST); 32596 } 32597 32598 var parser = new window$1.DOMParser(); 32599 var xml = parser.parseFromString(manifestString, 'application/xml'); 32600 var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null; 32601 32602 if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) { 32603 throw new Error(errors.DASH_INVALID_XML); 32604 } 32605 32606 return mpd; 32607 }; 32608 /** 32609 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 32610 * 32611 * @param {string} mpd 32612 * XML string of the MPD manifest 32613 * @return {Object|null} 32614 * Attributes of UTCTiming node specified in the manifest. Null if none found 32615 */ 32616 32617 32618 var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) { 32619 var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0]; 32620 32621 if (!UTCTimingNode) { 32622 return null; 32623 } 32624 32625 var attributes = parseAttributes$1(UTCTimingNode); 32626 32627 switch (attributes.schemeIdUri) { 32628 case 'urn:mpeg:dash:utc:http-head:2014': 32629 case 'urn:mpeg:dash:utc:http-head:2012': 32630 attributes.method = 'HEAD'; 32631 break; 32632 32633 case 'urn:mpeg:dash:utc:http-xsdate:2014': 32634 case 'urn:mpeg:dash:utc:http-iso:2014': 32635 case 'urn:mpeg:dash:utc:http-xsdate:2012': 32636 case 'urn:mpeg:dash:utc:http-iso:2012': 32637 attributes.method = 'GET'; 32638 break; 32639 32640 case 'urn:mpeg:dash:utc:direct:2014': 32641 case 'urn:mpeg:dash:utc:direct:2012': 32642 attributes.method = 'DIRECT'; 32643 attributes.value = Date.parse(attributes.value); 32644 break; 32645 32646 case 'urn:mpeg:dash:utc:http-ntp:2014': 32647 case 'urn:mpeg:dash:utc:ntp:2014': 32648 case 'urn:mpeg:dash:utc:sntp:2014': 32649 default: 32650 throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME); 32651 } 32652 32653 return attributes; 32654 }; 32655 32656 var parse = function parse(manifestString, options) { 32657 if (options === void 0) { 32658 options = {}; 32659 } 32660 32661 return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)), options.sidxMapping); 32662 }; 32663 /** 32664 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 32665 * 32666 * @param {string} manifestString 32667 * XML string of the MPD manifest 32668 * @return {Object|null} 32669 * Attributes of UTCTiming node specified in the manifest. Null if none found 32670 */ 32671 32672 32673 var parseUTCTiming = function parseUTCTiming(manifestString) { 32674 return parseUTCTimingScheme(stringToMpdXml(manifestString)); 32675 }; 32676 32677 /** 32678 * mux.js 32679 * 32680 * Copyright (c) Brightcove 32681 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 32682 */ 32683 var toUnsigned = function toUnsigned(value) { 32684 return value >>> 0; 32685 }; 32686 32687 var toHexString = function toHexString(value) { 32688 return ('00' + value.toString(16)).slice(-2); 32689 }; 32690 32691 var bin = { 32692 toUnsigned: toUnsigned, 32693 toHexString: toHexString 32694 }; 32695 32696 var inspectMp4, 32697 _textifyMp, 32698 toUnsigned$1 = bin.toUnsigned, 32699 parseMp4Date = function parseMp4Date(seconds) { 32700 return new Date(seconds * 1000 - 2082844800000); 32701 }, 32702 parseSampleFlags = function parseSampleFlags(flags) { 32703 return { 32704 isLeading: (flags[0] & 0x0c) >>> 2, 32705 dependsOn: flags[0] & 0x03, 32706 isDependedOn: (flags[1] & 0xc0) >>> 6, 32707 hasRedundancy: (flags[1] & 0x30) >>> 4, 32708 paddingValue: (flags[1] & 0x0e) >>> 1, 32709 isNonSyncSample: flags[1] & 0x01, 32710 degradationPriority: flags[2] << 8 | flags[3] 32711 }; 32712 }, 32713 32714 /** 32715 * Returns the string representation of an ASCII encoded four byte buffer. 32716 * @param buffer {Uint8Array} a four-byte buffer to translate 32717 * @return {string} the corresponding string 32718 */ 32719 parseType = function parseType(buffer) { 32720 var result = ''; 32721 result += String.fromCharCode(buffer[0]); 32722 result += String.fromCharCode(buffer[1]); 32723 result += String.fromCharCode(buffer[2]); 32724 result += String.fromCharCode(buffer[3]); 32725 return result; 32726 }, 32727 // Find the data for a box specified by its path 32728 findBox = function findBox(data, path) { 32729 var results = [], 32730 i, 32731 size, 32732 type, 32733 end, 32734 subresults; 32735 32736 if (!path.length) { 32737 // short-circuit the search for empty paths 32738 return null; 32739 } 32740 32741 for (i = 0; i < data.byteLength;) { 32742 size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]); 32743 type = parseType(data.subarray(i + 4, i + 8)); 32744 end = size > 1 ? i + size : data.byteLength; 32745 32746 if (type === path[0]) { 32747 if (path.length === 1) { 32748 // this is the end of the path and we've found the box we were 32749 // looking for 32750 results.push(data.subarray(i + 8, end)); 32751 } else { 32752 // recursively search for the next box along the path 32753 subresults = findBox(data.subarray(i + 8, end), path.slice(1)); 32754 32755 if (subresults.length) { 32756 results = results.concat(subresults); 32757 } 32758 } 32759 } 32760 32761 i = end; 32762 } // we've finished searching all of data 32763 32764 32765 return results; 32766 }, 32767 nalParse = function nalParse(avcStream) { 32768 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 32769 result = [], 32770 i, 32771 length; 32772 32773 for (i = 0; i + 4 < avcStream.length; i += length) { 32774 length = avcView.getUint32(i); 32775 i += 4; // bail if this doesn't appear to be an H264 stream 32776 32777 if (length <= 0) { 32778 result.push('<span style=\'color:red;\'>MALFORMED DATA</span>'); 32779 continue; 32780 } 32781 32782 switch (avcStream[i] & 0x1F) { 32783 case 0x01: 32784 result.push('slice_layer_without_partitioning_rbsp'); 32785 break; 32786 32787 case 0x05: 32788 result.push('slice_layer_without_partitioning_rbsp_idr'); 32789 break; 32790 32791 case 0x06: 32792 result.push('sei_rbsp'); 32793 break; 32794 32795 case 0x07: 32796 result.push('seq_parameter_set_rbsp'); 32797 break; 32798 32799 case 0x08: 32800 result.push('pic_parameter_set_rbsp'); 32801 break; 32802 32803 case 0x09: 32804 result.push('access_unit_delimiter_rbsp'); 32805 break; 32806 32807 default: 32808 result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F); 32809 break; 32810 } 32811 } 32812 32813 return result; 32814 }, 32815 // registry of handlers for individual mp4 box types 32816 parse$1 = { 32817 // codingname, not a first-class box type. stsd entries share the
vendor: 6,697 bytes, lines 32818-33018
32818 // same format as real boxes so the parsing infrastructure can be 32819 // shared 32820 avc1: function avc1(data) { 32821 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 32822 return { 32823 dataReferenceIndex: view.getUint16(6), 32824 width: view.getUint16(24), 32825 height: view.getUint16(26), 32826 horizresolution: view.getUint16(28) + view.getUint16(30) / 16, 32827 vertresolution: view.getUint16(32) + view.getUint16(34) / 16, 32828 frameCount: view.getUint16(40), 32829 depth: view.getUint16(74), 32830 config: inspectMp4(data.subarray(78, data.byteLength)) 32831 }; 32832 }, 32833 avcC: function avcC(data) { 32834 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 32835 result = { 32836 configurationVersion: data[0], 32837 avcProfileIndication: data[1], 32838 profileCompatibility: data[2], 32839 avcLevelIndication: data[3], 32840 lengthSizeMinusOne: data[4] & 0x03, 32841 sps: [], 32842 pps: [] 32843 }, 32844 numOfSequenceParameterSets = data[5] & 0x1f, 32845 numOfPictureParameterSets, 32846 nalSize, 32847 offset, 32848 i; // iterate past any SPSs 32849 32850 offset = 6; 32851 32852 for (i = 0; i < numOfSequenceParameterSets; i++) { 32853 nalSize = view.getUint16(offset); 32854 offset += 2; 32855 result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 32856 offset += nalSize; 32857 } // iterate past any PPSs 32858 32859 32860 numOfPictureParameterSets = data[offset]; 32861 offset++; 32862 32863 for (i = 0; i < numOfPictureParameterSets; i++) { 32864 nalSize = view.getUint16(offset); 32865 offset += 2; 32866 result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 32867 offset += nalSize; 32868 } 32869 32870 return result; 32871 }, 32872 btrt: function btrt(data) { 32873 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 32874 return { 32875 bufferSizeDB: view.getUint32(0), 32876 maxBitrate: view.getUint32(4), 32877 avgBitrate: view.getUint32(8) 32878 }; 32879 }, 32880 esds: function esds(data) { 32881 return { 32882 version: data[0], 32883 flags: new Uint8Array(data.subarray(1, 4)), 32884 esId: data[6] << 8 | data[7], 32885 streamPriority: data[8] & 0x1f, 32886 decoderConfig: { 32887 objectProfileIndication: data[11], 32888 streamType: data[12] >>> 2 & 0x3f, 32889 bufferSize: data[13] << 16 | data[14] << 8 | data[15], 32890 maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19], 32891 avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23], 32892 decoderConfigDescriptor: { 32893 tag: data[24], 32894 length: data[25], 32895 audioObjectType: data[26] >>> 3 & 0x1f, 32896 samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01, 32897 channelConfiguration: data[27] >>> 3 & 0x0f 32898 } 32899 } 32900 }; 32901 }, 32902 ftyp: function ftyp(data) { 32903 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 32904 result = { 32905 majorBrand: parseType(data.subarray(0, 4)), 32906 minorVersion: view.getUint32(4), 32907 compatibleBrands: [] 32908 }, 32909 i = 8; 32910 32911 while (i < data.byteLength) { 32912 result.compatibleBrands.push(parseType(data.subarray(i, i + 4))); 32913 i += 4; 32914 } 32915 32916 return result; 32917 }, 32918 dinf: function dinf(data) { 32919 return { 32920 boxes: inspectMp4(data) 32921 }; 32922 }, 32923 dref: function dref(data) { 32924 return { 32925 version: data[0], 32926 flags: new Uint8Array(data.subarray(1, 4)), 32927 dataReferences: inspectMp4(data.subarray(8)) 32928 }; 32929 }, 32930 hdlr: function hdlr(data) { 32931 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 32932 result = { 32933 version: view.getUint8(0), 32934 flags: new Uint8Array(data.subarray(1, 4)), 32935 handlerType: parseType(data.subarray(8, 12)), 32936 name: '' 32937 }, 32938 i = 8; // parse out the name field 32939 32940 for (i = 24; i < data.byteLength; i++) { 32941 if (data[i] === 0x00) { 32942 // the name field is null-terminated 32943 i++; 32944 break; 32945 } 32946 32947 result.name += String.fromCharCode(data[i]); 32948 } // decode UTF-8 to javascript's internal representation 32949 // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html 32950 32951 32952 result.name = decodeURIComponent(escape(result.name)); 32953 return result; 32954 }, 32955 mdat: function mdat(data) { 32956 return { 32957 byteLength: data.byteLength, 32958 nals: nalParse(data) 32959 }; 32960 }, 32961 mdhd: function mdhd(data) { 32962 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 32963 i = 4, 32964 language, 32965 result = { 32966 version: view.getUint8(0), 32967 flags: new Uint8Array(data.subarray(1, 4)), 32968 language: '' 32969 }; 32970 32971 if (result.version === 1) { 32972 i += 4; 32973 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 32974 32975 i += 8; 32976 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 32977 32978 i += 4; 32979 result.timescale = view.getUint32(i); 32980 i += 8; 32981 result.duration = view.getUint32(i); // truncating top 4 bytes 32982 } else { 32983 result.creationTime = parseMp4Date(view.getUint32(i)); 32984 i += 4; 32985 result.modificationTime = parseMp4Date(view.getUint32(i)); 32986 i += 4; 32987 result.timescale = view.getUint32(i); 32988 i += 4; 32989 result.duration = view.getUint32(i); 32990 } 32991 32992 i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields 32993 // each field is the packed difference between its ASCII value and 0x60 32994 32995 language = view.getUint16(i); 32996 result.language += String.fromCharCode((language >> 10) + 0x60); 32997 result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60); 32998 result.language += String.fromCharCode((language & 0x1f) + 0x60); 32999 return result; 33000 }, 33001 mdia: function mdia(data) { 33002 return { 33003 boxes: inspectMp4(data) 33004 }; 33005 }, 33006 mfhd: function mfhd(data) { 33007 return { 33008 version: data[0], 33009 flags: new Uint8Array(data.subarray(1, 4)), 33010 sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] 33011 }; 33012 }, 33013 minf: function minf(data) { 33014 return { 33015 boxes: inspectMp4(data) 33016 }; 33017 }, 33018 // codingname, not a first-class box type. stsd entries share the
vendor: 25,421 bytes, lines 33019-33778
33019 // same format as real boxes so the parsing infrastructure can be 33020 // shared 33021 mp4a: function mp4a(data) { 33022 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33023 result = { 33024 // 6 bytes reserved 33025 dataReferenceIndex: view.getUint16(6), 33026 // 4 + 4 bytes reserved 33027 channelcount: view.getUint16(16), 33028 samplesize: view.getUint16(18), 33029 // 2 bytes pre_defined 33030 // 2 bytes reserved 33031 samplerate: view.getUint16(24) + view.getUint16(26) / 65536 33032 }; // if there are more bytes to process, assume this is an ISO/IEC 33033 // 14496-14 MP4AudioSampleEntry and parse the ESDBox 33034 33035 if (data.byteLength > 28) { 33036 result.streamDescriptor = inspectMp4(data.subarray(28))[0]; 33037 } 33038 33039 return result; 33040 }, 33041 moof: function moof(data) { 33042 return { 33043 boxes: inspectMp4(data) 33044 }; 33045 }, 33046 moov: function moov(data) { 33047 return { 33048 boxes: inspectMp4(data) 33049 }; 33050 }, 33051 mvex: function mvex(data) { 33052 return { 33053 boxes: inspectMp4(data) 33054 }; 33055 }, 33056 mvhd: function mvhd(data) { 33057 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33058 i = 4, 33059 result = { 33060 version: view.getUint8(0), 33061 flags: new Uint8Array(data.subarray(1, 4)) 33062 }; 33063 33064 if (result.version === 1) { 33065 i += 4; 33066 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 33067 33068 i += 8; 33069 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 33070 33071 i += 4; 33072 result.timescale = view.getUint32(i); 33073 i += 8; 33074 result.duration = view.getUint32(i); // truncating top 4 bytes 33075 } else { 33076 result.creationTime = parseMp4Date(view.getUint32(i)); 33077 i += 4; 33078 result.modificationTime = parseMp4Date(view.getUint32(i)); 33079 i += 4; 33080 result.timescale = view.getUint32(i); 33081 i += 4; 33082 result.duration = view.getUint32(i); 33083 } 33084 33085 i += 4; // convert fixed-point, base 16 back to a number 33086 33087 result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16; 33088 i += 4; 33089 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 33090 i += 2; 33091 i += 2; 33092 i += 2 * 4; 33093 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 33094 i += 9 * 4; 33095 i += 6 * 4; 33096 result.nextTrackId = view.getUint32(i); 33097 return result; 33098 }, 33099 pdin: function pdin(data) { 33100 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 33101 return { 33102 version: view.getUint8(0), 33103 flags: new Uint8Array(data.subarray(1, 4)), 33104 rate: view.getUint32(4), 33105 initialDelay: view.getUint32(8) 33106 }; 33107 }, 33108 sdtp: function sdtp(data) { 33109 var result = { 33110 version: data[0], 33111 flags: new Uint8Array(data.subarray(1, 4)), 33112 samples: [] 33113 }, 33114 i; 33115 33116 for (i = 4; i < data.byteLength; i++) { 33117 result.samples.push({ 33118 dependsOn: (data[i] & 0x30) >> 4, 33119 isDependedOn: (data[i] & 0x0c) >> 2, 33120 hasRedundancy: data[i] & 0x03 33121 }); 33122 } 33123 33124 return result; 33125 }, 33126 sidx: function sidx(data) { 33127 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33128 result = { 33129 version: data[0], 33130 flags: new Uint8Array(data.subarray(1, 4)), 33131 references: [], 33132 referenceId: view.getUint32(4), 33133 timescale: view.getUint32(8), 33134 earliestPresentationTime: view.getUint32(12), 33135 firstOffset: view.getUint32(16) 33136 }, 33137 referenceCount = view.getUint16(22), 33138 i; 33139 33140 for (i = 24; referenceCount; i += 12, referenceCount--) { 33141 result.references.push({ 33142 referenceType: (data[i] & 0x80) >>> 7, 33143 referencedSize: view.getUint32(i) & 0x7FFFFFFF, 33144 subsegmentDuration: view.getUint32(i + 4), 33145 startsWithSap: !!(data[i + 8] & 0x80), 33146 sapType: (data[i + 8] & 0x70) >>> 4, 33147 sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF 33148 }); 33149 } 33150 33151 return result; 33152 }, 33153 smhd: function smhd(data) { 33154 return { 33155 version: data[0], 33156 flags: new Uint8Array(data.subarray(1, 4)), 33157 balance: data[4] + data[5] / 256 33158 }; 33159 }, 33160 stbl: function stbl(data) { 33161 return { 33162 boxes: inspectMp4(data) 33163 }; 33164 }, 33165 stco: function stco(data) { 33166 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33167 result = { 33168 version: data[0], 33169 flags: new Uint8Array(data.subarray(1, 4)), 33170 chunkOffsets: [] 33171 }, 33172 entryCount = view.getUint32(4), 33173 i; 33174 33175 for (i = 8; entryCount; i += 4, entryCount--) { 33176 result.chunkOffsets.push(view.getUint32(i)); 33177 } 33178 33179 return result; 33180 }, 33181 stsc: function stsc(data) { 33182 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33183 entryCount = view.getUint32(4), 33184 result = { 33185 version: data[0], 33186 flags: new Uint8Array(data.subarray(1, 4)), 33187 sampleToChunks: [] 33188 }, 33189 i; 33190 33191 for (i = 8; entryCount; i += 12, entryCount--) { 33192 result.sampleToChunks.push({ 33193 firstChunk: view.getUint32(i), 33194 samplesPerChunk: view.getUint32(i + 4), 33195 sampleDescriptionIndex: view.getUint32(i + 8) 33196 }); 33197 } 33198 33199 return result; 33200 }, 33201 stsd: function stsd(data) { 33202 return { 33203 version: data[0], 33204 flags: new Uint8Array(data.subarray(1, 4)), 33205 sampleDescriptions: inspectMp4(data.subarray(8)) 33206 }; 33207 }, 33208 stsz: function stsz(data) { 33209 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33210 result = { 33211 version: data[0], 33212 flags: new Uint8Array(data.subarray(1, 4)), 33213 sampleSize: view.getUint32(4), 33214 entries: [] 33215 }, 33216 i; 33217 33218 for (i = 12; i < data.byteLength; i += 4) { 33219 result.entries.push(view.getUint32(i)); 33220 } 33221 33222 return result; 33223 }, 33224 stts: function stts(data) { 33225 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33226 result = { 33227 version: data[0], 33228 flags: new Uint8Array(data.subarray(1, 4)), 33229 timeToSamples: [] 33230 }, 33231 entryCount = view.getUint32(4), 33232 i; 33233 33234 for (i = 8; entryCount; i += 8, entryCount--) { 33235 result.timeToSamples.push({ 33236 sampleCount: view.getUint32(i), 33237 sampleDelta: view.getUint32(i + 4) 33238 }); 33239 } 33240 33241 return result; 33242 }, 33243 styp: function styp(data) { 33244 return parse$1.ftyp(data); 33245 }, 33246 tfdt: function tfdt(data) { 33247 var result = { 33248 version: data[0], 33249 flags: new Uint8Array(data.subarray(1, 4)), 33250 baseMediaDecodeTime: toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]) 33251 }; 33252 33253 if (result.version === 1) { 33254 result.baseMediaDecodeTime *= Math.pow(2, 32); 33255 result.baseMediaDecodeTime += toUnsigned$1(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]); 33256 } 33257 33258 return result; 33259 }, 33260 tfhd: function tfhd(data) { 33261 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33262 result = { 33263 version: data[0], 33264 flags: new Uint8Array(data.subarray(1, 4)), 33265 trackId: view.getUint32(4) 33266 }, 33267 baseDataOffsetPresent = result.flags[2] & 0x01, 33268 sampleDescriptionIndexPresent = result.flags[2] & 0x02, 33269 defaultSampleDurationPresent = result.flags[2] & 0x08, 33270 defaultSampleSizePresent = result.flags[2] & 0x10, 33271 defaultSampleFlagsPresent = result.flags[2] & 0x20, 33272 durationIsEmpty = result.flags[0] & 0x010000, 33273 defaultBaseIsMoof = result.flags[0] & 0x020000, 33274 i; 33275 i = 8; 33276 33277 if (baseDataOffsetPresent) { 33278 i += 4; // truncate top 4 bytes 33279 // FIXME: should we read the full 64 bits? 33280 33281 result.baseDataOffset = view.getUint32(12); 33282 i += 4; 33283 } 33284 33285 if (sampleDescriptionIndexPresent) { 33286 result.sampleDescriptionIndex = view.getUint32(i); 33287 i += 4; 33288 } 33289 33290 if (defaultSampleDurationPresent) { 33291 result.defaultSampleDuration = view.getUint32(i); 33292 i += 4; 33293 } 33294 33295 if (defaultSampleSizePresent) { 33296 result.defaultSampleSize = view.getUint32(i); 33297 i += 4; 33298 } 33299 33300 if (defaultSampleFlagsPresent) { 33301 result.defaultSampleFlags = view.getUint32(i); 33302 } 33303 33304 if (durationIsEmpty) { 33305 result.durationIsEmpty = true; 33306 } 33307 33308 if (!baseDataOffsetPresent && defaultBaseIsMoof) { 33309 result.baseDataOffsetIsMoof = true; 33310 } 33311 33312 return result; 33313 }, 33314 tkhd: function tkhd(data) { 33315 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33316 i = 4, 33317 result = { 33318 version: view.getUint8(0), 33319 flags: new Uint8Array(data.subarray(1, 4)) 33320 }; 33321 33322 if (result.version === 1) { 33323 i += 4; 33324 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 33325 33326 i += 8; 33327 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 33328 33329 i += 4; 33330 result.trackId = view.getUint32(i); 33331 i += 4; 33332 i += 8; 33333 result.duration = view.getUint32(i); // truncating top 4 bytes 33334 } else { 33335 result.creationTime = parseMp4Date(view.getUint32(i)); 33336 i += 4; 33337 result.modificationTime = parseMp4Date(view.getUint32(i)); 33338 i += 4; 33339 result.trackId = view.getUint32(i); 33340 i += 4; 33341 i += 4; 33342 result.duration = view.getUint32(i); 33343 } 33344 33345 i += 4; 33346 i += 2 * 4; 33347 result.layer = view.getUint16(i); 33348 i += 2; 33349 result.alternateGroup = view.getUint16(i); 33350 i += 2; // convert fixed-point, base 16 back to a number 33351 33352 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 33353 i += 2; 33354 i += 2; 33355 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 33356 i += 9 * 4; 33357 result.width = view.getUint16(i) + view.getUint16(i + 2) / 16; 33358 i += 4; 33359 result.height = view.getUint16(i) + view.getUint16(i + 2) / 16; 33360 return result; 33361 }, 33362 traf: function traf(data) { 33363 return { 33364 boxes: inspectMp4(data) 33365 }; 33366 }, 33367 trak: function trak(data) { 33368 return { 33369 boxes: inspectMp4(data) 33370 }; 33371 }, 33372 trex: function trex(data) { 33373 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 33374 return { 33375 version: data[0], 33376 flags: new Uint8Array(data.subarray(1, 4)), 33377 trackId: view.getUint32(4), 33378 defaultSampleDescriptionIndex: view.getUint32(8), 33379 defaultSampleDuration: view.getUint32(12), 33380 defaultSampleSize: view.getUint32(16), 33381 sampleDependsOn: data[20] & 0x03, 33382 sampleIsDependedOn: (data[21] & 0xc0) >> 6, 33383 sampleHasRedundancy: (data[21] & 0x30) >> 4, 33384 samplePaddingValue: (data[21] & 0x0e) >> 1, 33385 sampleIsDifferenceSample: !!(data[21] & 0x01), 33386 sampleDegradationPriority: view.getUint16(22) 33387 }; 33388 }, 33389 trun: function trun(data) { 33390 var result = { 33391 version: data[0], 33392 flags: new Uint8Array(data.subarray(1, 4)), 33393 samples: [] 33394 }, 33395 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33396 // Flag interpretation 33397 dataOffsetPresent = result.flags[2] & 0x01, 33398 // compare with 2nd byte of 0x1 33399 firstSampleFlagsPresent = result.flags[2] & 0x04, 33400 // compare with 2nd byte of 0x4 33401 sampleDurationPresent = result.flags[1] & 0x01, 33402 // compare with 2nd byte of 0x100 33403 sampleSizePresent = result.flags[1] & 0x02, 33404 // compare with 2nd byte of 0x200 33405 sampleFlagsPresent = result.flags[1] & 0x04, 33406 // compare with 2nd byte of 0x400 33407 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, 33408 // compare with 2nd byte of 0x800 33409 sampleCount = view.getUint32(4), 33410 offset = 8, 33411 sample; 33412 33413 if (dataOffsetPresent) { 33414 // 32 bit signed integer 33415 result.dataOffset = view.getInt32(offset); 33416 offset += 4; 33417 } // Overrides the flags for the first sample only. The order of 33418 // optional values will be: duration, size, compositionTimeOffset 33419 33420 33421 if (firstSampleFlagsPresent && sampleCount) { 33422 sample = { 33423 flags: parseSampleFlags(data.subarray(offset, offset + 4)) 33424 }; 33425 offset += 4; 33426 33427 if (sampleDurationPresent) { 33428 sample.duration = view.getUint32(offset); 33429 offset += 4; 33430 } 33431 33432 if (sampleSizePresent) { 33433 sample.size = view.getUint32(offset); 33434 offset += 4; 33435 } 33436 33437 if (sampleCompositionTimeOffsetPresent) { 33438 // Note: this should be a signed int if version is 1 33439 sample.compositionTimeOffset = view.getUint32(offset); 33440 offset += 4; 33441 } 33442 33443 result.samples.push(sample); 33444 sampleCount--; 33445 } 33446 33447 while (sampleCount--) { 33448 sample = {}; 33449 33450 if (sampleDurationPresent) { 33451 sample.duration = view.getUint32(offset); 33452 offset += 4; 33453 } 33454 33455 if (sampleSizePresent) { 33456 sample.size = view.getUint32(offset); 33457 offset += 4; 33458 } 33459 33460 if (sampleFlagsPresent) { 33461 sample.flags = parseSampleFlags(data.subarray(offset, offset + 4)); 33462 offset += 4; 33463 } 33464 33465 if (sampleCompositionTimeOffsetPresent) { 33466 // Note: this should be a signed int if version is 1 33467 sample.compositionTimeOffset = view.getUint32(offset); 33468 offset += 4; 33469 } 33470 33471 result.samples.push(sample); 33472 } 33473 33474 return result; 33475 }, 33476 'url ': function url(data) { 33477 return { 33478 version: data[0], 33479 flags: new Uint8Array(data.subarray(1, 4)) 33480 }; 33481 }, 33482 vmhd: function vmhd(data) { 33483 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 33484 return { 33485 version: data[0], 33486 flags: new Uint8Array(data.subarray(1, 4)), 33487 graphicsmode: view.getUint16(4), 33488 opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)]) 33489 }; 33490 } 33491 }; 33492 /** 33493 * Return a javascript array of box objects parsed from an ISO base 33494 * media file. 33495 * @param data {Uint8Array} the binary data of the media to be inspected 33496 * @return {array} a javascript array of potentially nested box objects 33497 */ 33498 33499 33500 inspectMp4 = function inspectMp4(data) { 33501 var i = 0, 33502 result = [], 33503 view, 33504 size, 33505 type, 33506 end, 33507 box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API 33508 33509 var ab = new ArrayBuffer(data.length); 33510 var v = new Uint8Array(ab); 33511 33512 for (var z = 0; z < data.length; ++z) { 33513 v[z] = data[z]; 33514 } 33515 33516 view = new DataView(ab); 33517 33518 while (i < data.byteLength) { 33519 // parse box data 33520 size = view.getUint32(i); 33521 type = parseType(data.subarray(i + 4, i + 8)); 33522 end = size > 1 ? i + size : data.byteLength; // parse type-specific data 33523 33524 box = (parse$1[type] || function (data) { 33525 return { 33526 data: data 33527 }; 33528 })(data.subarray(i + 8, end)); 33529 33530 box.size = size; 33531 box.type = type; // store this box and move to the next 33532 33533 result.push(box); 33534 i = end; 33535 } 33536 33537 return result; 33538 }; 33539 /** 33540 * Returns a textual representation of the javascript represtentation 33541 * of an MP4 file. You can use it as an alternative to 33542 * JSON.stringify() to compare inspected MP4s. 33543 * @param inspectedMp4 {array} the parsed array of boxes in an MP4 33544 * file 33545 * @param depth {number} (optional) the number of ancestor boxes of 33546 * the elements of inspectedMp4. Assumed to be zero if unspecified. 33547 * @return {string} a text representation of the parsed MP4 33548 */ 33549 33550 33551 _textifyMp = function textifyMp4(inspectedMp4, depth) { 33552 var indent; 33553 depth = depth || 0; 33554 indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes 33555 33556 return inspectedMp4.map(function (box, index) { 33557 // list the box type first at the current indentation level 33558 return indent + box.type + '\n' + // the type is already included and handle child boxes separately 33559 Object.keys(box).filter(function (key) { 33560 return key !== 'type' && key !== 'boxes'; // output all the box properties 33561 }).map(function (key) { 33562 var prefix = indent + ' ' + key + ': ', 33563 value = box[key]; // print out raw bytes as hexademical 33564 33565 if (value instanceof Uint8Array || value instanceof Uint32Array) { 33566 var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (_byte) { 33567 return ' ' + ('00' + _byte.toString(16)).slice(-2); 33568 }).join('').match(/.{1,24}/g); 33569 33570 if (!bytes) { 33571 return prefix + '<>'; 33572 } 33573 33574 if (bytes.length === 1) { 33575 return prefix + '<' + bytes.join('').slice(1) + '>'; 33576 } 33577 33578 return prefix + '<\n' + bytes.map(function (line) { 33579 return indent + ' ' + line; 33580 }).join('\n') + '\n' + indent + ' >'; 33581 } // stringify generic objects 33582 33583 33584 return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) { 33585 if (index === 0) { 33586 return line; 33587 } 33588 33589 return indent + ' ' + line; 33590 }).join('\n'); 33591 }).join('\n') + ( // recursively textify the child boxes 33592 box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : ''); 33593 }).join('\n'); 33594 }; 33595 33596 var mp4Inspector = { 33597 inspect: inspectMp4, 33598 textify: _textifyMp, 33599 parseType: parseType, 33600 findBox: findBox, 33601 parseTraf: parse$1.traf, 33602 parseTfdt: parse$1.tfdt, 33603 parseHdlr: parse$1.hdlr, 33604 parseTfhd: parse$1.tfhd, 33605 parseTrun: parse$1.trun, 33606 parseSidx: parse$1.sidx 33607 }; 33608 33609 var toUnsigned$2 = bin.toUnsigned; 33610 var toHexString$1 = bin.toHexString; 33611 var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks; 33612 /** 33613 * Parses an MP4 initialization segment and extracts the timescale 33614 * values for any declared tracks. Timescale values indicate the 33615 * number of clock ticks per second to assume for time-based values 33616 * elsewhere in the MP4. 33617 * 33618 * To determine the start time of an MP4, you need two pieces of 33619 * information: the timescale unit and the earliest base media decode 33620 * time. Multiple timescales can be specified within an MP4 but the 33621 * base media decode time is always expressed in the timescale from 33622 * the media header box for the track: 33623 * ``` 33624 * moov > trak > mdia > mdhd.timescale 33625 * ``` 33626 * @param init {Uint8Array} the bytes of the init segment 33627 * @return {object} a hash of track ids to timescale values or null if 33628 * the init segment is malformed. 33629 */ 33630 33631 timescale = function timescale(init) { 33632 var result = {}, 33633 traks = mp4Inspector.findBox(init, ['moov', 'trak']); // mdhd timescale 33634 33635 return traks.reduce(function (result, trak) { 33636 var tkhd, version, index, id, mdhd; 33637 tkhd = mp4Inspector.findBox(trak, ['tkhd'])[0]; 33638 33639 if (!tkhd) { 33640 return null; 33641 } 33642 33643 version = tkhd[0]; 33644 index = version === 0 ? 12 : 20; 33645 id = toUnsigned$2(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]); 33646 mdhd = mp4Inspector.findBox(trak, ['mdia', 'mdhd'])[0]; 33647 33648 if (!mdhd) { 33649 return null; 33650 } 33651 33652 version = mdhd[0]; 33653 index = version === 0 ? 12 : 20; 33654 result[id] = toUnsigned$2(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 33655 return result; 33656 }, result); 33657 }; 33658 /** 33659 * Determine the base media decode start time, in seconds, for an MP4 33660 * fragment. If multiple fragments are specified, the earliest time is 33661 * returned. 33662 * 33663 * The base media decode time can be parsed from track fragment 33664 * metadata: 33665 * ``` 33666 * moof > traf > tfdt.baseMediaDecodeTime 33667 * ``` 33668 * It requires the timescale value from the mdhd to interpret. 33669 * 33670 * @param timescale {object} a hash of track ids to timescale values. 33671 * @return {number} the earliest base media decode start time for the 33672 * fragment, in seconds 33673 */ 33674 33675 33676 startTime = function startTime(timescale, fragment) { 33677 var trafs, baseTimes, result; // we need info from two childrend of each track fragment box 33678 33679 trafs = mp4Inspector.findBox(fragment, ['moof', 'traf']); // determine the start times for each track 33680 33681 baseTimes = [].concat.apply([], trafs.map(function (traf) { 33682 return mp4Inspector.findBox(traf, ['tfhd']).map(function (tfhd) { 33683 var id, scale, baseTime; // get the track id from the tfhd 33684 33685 id = toUnsigned$2(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified 33686 33687 scale = timescale[id] || 90e3; // get the base media decode time from the tfdt 33688 33689 baseTime = mp4Inspector.findBox(traf, ['tfdt']).map(function (tfdt) { 33690 var version, result; 33691 version = tfdt[0]; 33692 result = toUnsigned$2(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]); 33693 33694 if (version === 1) { 33695 result *= Math.pow(2, 32); 33696 result += toUnsigned$2(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]); 33697 } 33698 33699 return result; 33700 })[0]; 33701 baseTime = baseTime || Infinity; // convert base time to seconds 33702 33703 return baseTime / scale; 33704 }); 33705 })); // return the minimum 33706 33707 result = Math.min.apply(null, baseTimes); 33708 return isFinite(result) ? result : 0; 33709 }; 33710 /** 33711 * Determine the composition start, in seconds, for an MP4 33712 * fragment. 33713 * 33714 * The composition start time of a fragment can be calculated using the base 33715 * media decode time, composition time offset, and timescale, as follows: 33716 * 33717 * compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale 33718 * 33719 * All of the aforementioned information is contained within a media fragment's 33720 * `traf` box, except for timescale info, which comes from the initialization 33721 * segment, so a track id (also contained within a `traf`) is also necessary to 33722 * associate it with a timescale 33723 * 33724 * 33725 * @param timescales {object} - a hash of track ids to timescale values. 33726 * @param fragment {Unit8Array} - the bytes of a media segment 33727 * @return {number} the composition start time for the fragment, in seconds 33728 **/ 33729 33730 33731 compositionStartTime = function compositionStartTime(timescales, fragment) { 33732 var trafBoxes = mp4Inspector.findBox(fragment, ['moof', 'traf']); 33733 var baseMediaDecodeTime = 0; 33734 var compositionTimeOffset = 0; 33735 var trackId; 33736 33737 if (trafBoxes && trafBoxes.length) { 33738 // The spec states that track run samples contained within a `traf` box are contiguous, but 33739 // it does not explicitly state whether the `traf` boxes themselves are contiguous. 33740 // We will assume that they are, so we only need the first to calculate start time. 33741 var parsedTraf = mp4Inspector.parseTraf(trafBoxes[0]); 33742 33743 for (var i = 0; i < parsedTraf.boxes.length; i++) { 33744 if (parsedTraf.boxes[i].type === 'tfhd') { 33745 trackId = parsedTraf.boxes[i].trackId; 33746 } else if (parsedTraf.boxes[i].type === 'tfdt') { 33747 baseMediaDecodeTime = parsedTraf.boxes[i].baseMediaDecodeTime; 33748 } else if (parsedTraf.boxes[i].type === 'trun' && parsedTraf.boxes[i].samples.length) { 33749 compositionTimeOffset = parsedTraf.boxes[i].samples[0].compositionTimeOffset || 0; 33750 } 33751 } 33752 } // Get timescale for this specific track. Assume a 90kHz clock if no timescale was 33753 // specified. 33754 33755 33756 var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds 33757 33758 return (baseMediaDecodeTime + compositionTimeOffset) / timescale; 33759 }; 33760 /** 33761 * Find the trackIds of the video tracks in this source. 33762 * Found by parsing the Handler Reference and Track Header Boxes: 33763 * moov > trak > mdia > hdlr 33764 * moov > trak > tkhd 33765 * 33766 * @param {Uint8Array} init - The bytes of the init segment for this source 33767 * @return {Number[]} A list of trackIds 33768 * 33769 * @see ISO-BMFF-12/2015, Section 8.4.3 33770 **/ 33771 33772 33773 getVideoTrackIds = function getVideoTrackIds(init) { 33774 var traks = mp4Inspector.findBox(init, ['moov', 'trak']); 33775 var videoTrackIds = []; 33776 traks.forEach(function (trak) { 33777 var hdlrs = mp4Inspector.findBox(trak, ['mdia', 'hdlr']); 33778 var tkhds = mp4Inspector.findBox(trak, ['tkhd']);
vendor: 13,289 bytes, lines 33779-34197
33779 hdlrs.forEach(function (hdlr, index) { 33780 var handlerType = mp4Inspector.parseType(hdlr.subarray(8, 12)); 33781 var tkhd = tkhds[index]; 33782 var view; 33783 var version; 33784 var trackId; 33785 33786 if (handlerType === 'vide') { 33787 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 33788 version = view.getUint8(0); 33789 trackId = version === 0 ? view.getUint32(12) : view.getUint32(20); 33790 videoTrackIds.push(trackId); 33791 } 33792 }); 33793 }); 33794 return videoTrackIds; 33795 }; 33796 /** 33797 * Get all the video, audio, and hint tracks from a non fragmented 33798 * mp4 segment 33799 */ 33800 33801 33802 getTracks = function getTracks(init) { 33803 var traks = mp4Inspector.findBox(init, ['moov', 'trak']); 33804 var tracks = []; 33805 traks.forEach(function (trak) { 33806 var track = {}; 33807 var tkhd = mp4Inspector.findBox(trak, ['tkhd'])[0]; 33808 var view, version; // id 33809 33810 if (tkhd) { 33811 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 33812 version = view.getUint8(0); 33813 track.id = version === 0 ? view.getUint32(12) : view.getUint32(20); 33814 } 33815 33816 var hdlr = mp4Inspector.findBox(trak, ['mdia', 'hdlr'])[0]; // type 33817 33818 if (hdlr) { 33819 var type = mp4Inspector.parseType(hdlr.subarray(8, 12)); 33820 33821 if (type === 'vide') { 33822 track.type = 'video'; 33823 } else if (type === 'soun') { 33824 track.type = 'audio'; 33825 } else { 33826 track.type = type; 33827 } 33828 } // codec 33829 33830 33831 var stsd = mp4Inspector.findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; 33832 33833 if (stsd) { 33834 var sampleDescriptions = stsd.subarray(8); // gives the codec type string 33835 33836 track.codec = mp4Inspector.parseType(sampleDescriptions.subarray(4, 8)); 33837 var codecBox = mp4Inspector.findBox(sampleDescriptions, [track.codec])[0]; 33838 var codecConfig, codecConfigType; 33839 33840 if (codecBox) { 33841 // https://tools.ietf.org/html/rfc6381#section-3.3 33842 if (/^[a-z]vc[1-9]$/i.test(track.codec)) { 33843 // we don't need anything but the "config" parameter of the 33844 // avc1 codecBox 33845 codecConfig = codecBox.subarray(78); 33846 codecConfigType = mp4Inspector.parseType(codecConfig.subarray(4, 8)); 33847 33848 if (codecConfigType === 'avcC' && codecConfig.length > 11) { 33849 track.codec += '.'; // left padded with zeroes for single digit hex 33850 // profile idc 33851 33852 track.codec += toHexString$1(codecConfig[9]); // the byte containing the constraint_set flags 33853 33854 track.codec += toHexString$1(codecConfig[10]); // level idc 33855 33856 track.codec += toHexString$1(codecConfig[11]); 33857 } else { 33858 // TODO: show a warning that we couldn't parse the codec 33859 // and are using the default 33860 track.codec = 'avc1.4d400d'; 33861 } 33862 } else if (/^mp4[a,v]$/i.test(track.codec)) { 33863 // we do not need anything but the streamDescriptor of the mp4a codecBox 33864 codecConfig = codecBox.subarray(28); 33865 codecConfigType = mp4Inspector.parseType(codecConfig.subarray(4, 8)); 33866 33867 if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) { 33868 track.codec += '.' + toHexString$1(codecConfig[19]); // this value is only a single digit 33869 33870 track.codec += '.' + toHexString$1(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, ''); 33871 } else { 33872 // TODO: show a warning that we couldn't parse the codec 33873 // and are using the default 33874 track.codec = 'mp4a.40.2'; 33875 } 33876 } 33877 } 33878 } 33879 33880 var mdhd = mp4Inspector.findBox(trak, ['mdia', 'mdhd'])[0]; 33881 33882 if (mdhd && tkhd) { 33883 var index = version === 0 ? 12 : 20; 33884 track.timescale = toUnsigned$2(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 33885 } 33886 33887 tracks.push(track); 33888 }); 33889 return tracks; 33890 }; 33891 33892 var probe = { 33893 // export mp4 inspector's findBox and parseType for backwards compatibility 33894 findBox: mp4Inspector.findBox, 33895 parseType: mp4Inspector.parseType, 33896 timescale: timescale, 33897 startTime: startTime, 33898 compositionStartTime: compositionStartTime, 33899 videoTrackIds: getVideoTrackIds, 33900 tracks: getTracks 33901 }; 33902 33903 /** 33904 * mux.js 33905 * 33906 * Copyright (c) Brightcove 33907 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 33908 * 33909 * Reads in-band caption information from a video elementary 33910 * stream. Captions must follow the CEA-708 standard for injection 33911 * into an MPEG-2 transport streams. 33912 * @see https://en.wikipedia.org/wiki/CEA-708 33913 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 33914 */ 33915 // payload type field to indicate how they are to be 33916 // interpreted. CEAS-708 caption content is always transmitted with 33917 // payload type 0x04. 33918 33919 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 33920 RBSP_TRAILING_BITS = 128; 33921 /** 33922 * Parse a supplemental enhancement information (SEI) NAL unit. 33923 * Stops parsing once a message of type ITU T T35 has been found. 33924 * 33925 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 33926 * @return {object} the parsed SEI payload 33927 * @see Rec. ITU-T H.264, 7.3.2.3.1 33928 */ 33929 33930 var parseSei = function parseSei(bytes) { 33931 var i = 0, 33932 result = { 33933 payloadType: -1, 33934 payloadSize: 0 33935 }, 33936 payloadType = 0, 33937 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 33938 33939 while (i < bytes.byteLength) { 33940 // stop once we have hit the end of the sei_rbsp 33941 if (bytes[i] === RBSP_TRAILING_BITS) { 33942 break; 33943 } // Parse payload type 33944 33945 33946 while (bytes[i] === 0xFF) { 33947 payloadType += 255; 33948 i++; 33949 } 33950 33951 payloadType += bytes[i++]; // Parse payload size 33952 33953 while (bytes[i] === 0xFF) { 33954 payloadSize += 255; 33955 i++; 33956 } 33957 33958 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 33959 // there can only ever be one caption message in a frame's sei 33960 33961 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 33962 result.payloadType = payloadType; 33963 result.payloadSize = payloadSize; 33964 result.payload = bytes.subarray(i, i + payloadSize); 33965 break; 33966 } // skip the payload and parse the next message 33967 33968 33969 i += payloadSize; 33970 payloadType = 0; 33971 payloadSize = 0; 33972 } 33973 33974 return result; 33975 }; // see ANSI/SCTE 128-1 (2013), section 8.1 33976 33977 33978 var parseUserData = function parseUserData(sei) { 33979 // itu_t_t35_contry_code must be 181 (United States) for 33980 // captions 33981 if (sei.payload[0] !== 181) { 33982 return null; 33983 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 33984 33985 33986 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 33987 return null; 33988 } // the user_identifier should be "GA94" to indicate ATSC1 data 33989 33990 33991 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 33992 return null; 33993 } // finally, user_data_type_code should be 0x03 for caption data 33994 33995 33996 if (sei.payload[7] !== 0x03) { 33997 return null; 33998 } // return the user_data_type_structure and strip the trailing 33999 // marker bits 34000 34001 34002 return sei.payload.subarray(8, sei.payload.length - 1); 34003 }; // see CEA-708-D, section 4.4 34004 34005 34006 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 34007 var results = [], 34008 i, 34009 count, 34010 offset, 34011 data; // if this is just filler, return immediately 34012 34013 if (!(userData[0] & 0x40)) { 34014 return results; 34015 } // parse out the cc_data_1 and cc_data_2 fields 34016 34017 34018 count = userData[0] & 0x1f; 34019 34020 for (i = 0; i < count; i++) { 34021 offset = i * 3; 34022 data = { 34023 type: userData[offset + 2] & 0x03, 34024 pts: pts 34025 }; // capture cc data when cc_valid is 1 34026 34027 if (userData[offset + 2] & 0x04) { 34028 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 34029 results.push(data); 34030 } 34031 } 34032 34033 return results; 34034 }; 34035 34036 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 34037 var length = data.byteLength, 34038 emulationPreventionBytesPositions = [], 34039 i = 1, 34040 newLength, 34041 newData; // Find all `Emulation Prevention Bytes` 34042 34043 while (i < length - 2) { 34044 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 34045 emulationPreventionBytesPositions.push(i + 2); 34046 i += 2; 34047 } else { 34048 i++; 34049 } 34050 } // If no Emulation Prevention Bytes were found just return the original 34051 // array 34052 34053 34054 if (emulationPreventionBytesPositions.length === 0) { 34055 return data; 34056 } // Create a new array to hold the NAL unit data 34057 34058 34059 newLength = length - emulationPreventionBytesPositions.length; 34060 newData = new Uint8Array(newLength); 34061 var sourceIndex = 0; 34062 34063 for (i = 0; i < newLength; sourceIndex++, i++) { 34064 if (sourceIndex === emulationPreventionBytesPositions[0]) { 34065 // Skip this byte 34066 sourceIndex++; // Remove this position index 34067 34068 emulationPreventionBytesPositions.shift(); 34069 } 34070 34071 newData[i] = data[sourceIndex]; 34072 } 34073 34074 return newData; 34075 }; // exports 34076 34077 34078 var captionPacketParser = { 34079 parseSei: parseSei, 34080 parseUserData: parseUserData, 34081 parseCaptionPackets: parseCaptionPackets, 34082 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 34083 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 34084 }; 34085 34086 /** 34087 * mux.js 34088 * 34089 * Copyright (c) Brightcove 34090 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 34091 * 34092 * A lightweight readable stream implemention that handles event dispatching. 34093 * Objects that inherit from streams should call init in their constructors. 34094 */ 34095 34096 var Stream$1 = function Stream() { 34097 this.init = function () { 34098 var listeners = {}; 34099 /** 34100 * Add a listener for a specified event type. 34101 * @param type {string} the event name 34102 * @param listener {function} the callback to be invoked when an event of 34103 * the specified type occurs 34104 */ 34105 34106 this.on = function (type, listener) { 34107 if (!listeners[type]) { 34108 listeners[type] = []; 34109 } 34110 34111 listeners[type] = listeners[type].concat(listener); 34112 }; 34113 /** 34114 * Remove a listener for a specified event type. 34115 * @param type {string} the event name 34116 * @param listener {function} a function previously registered for this 34117 * type of event through `on` 34118 */ 34119 34120 34121 this.off = function (type, listener) { 34122 var index; 34123 34124 if (!listeners[type]) { 34125 return false; 34126 } 34127 34128 index = listeners[type].indexOf(listener); 34129 listeners[type] = listeners[type].slice(); 34130 listeners[type].splice(index, 1); 34131 return index > -1; 34132 }; 34133 /** 34134 * Trigger an event of the specified type on this stream. Any additional 34135 * arguments to this function are passed as parameters to event listeners. 34136 * @param type {string} the event name 34137 */ 34138 34139 34140 this.trigger = function (type) { 34141 var callbacks, i, length, args; 34142 callbacks = listeners[type]; 34143 34144 if (!callbacks) { 34145 return; 34146 } // Slicing the arguments on every invocation of this method 34147 // can add a significant amount of overhead. Avoid the 34148 // intermediate object creation for the common case of a 34149 // single callback argument 34150 34151 34152 if (arguments.length === 2) { 34153 length = callbacks.length; 34154 34155 for (i = 0; i < length; ++i) { 34156 callbacks[i].call(this, arguments[1]); 34157 } 34158 } else { 34159 args = []; 34160 i = arguments.length; 34161 34162 for (i = 1; i < arguments.length; ++i) { 34163 args.push(arguments[i]); 34164 } 34165 34166 length = callbacks.length; 34167 34168 for (i = 0; i < length; ++i) { 34169 callbacks[i].apply(this, args); 34170 } 34171 } 34172 }; 34173 /** 34174 * Destroys the stream and cleans up. 34175 */ 34176 34177 34178 this.dispose = function () { 34179 listeners = {}; 34180 }; 34181 }; 34182 }; 34183 /** 34184 * Forwards all `data` events on this stream to the destination stream. The 34185 * destination stream should provide a method `push` to receive the data 34186 * events as they arrive. 34187 * @param destination {stream} the stream that will receive all `data` events 34188 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 34189 * when the current stream emits a 'done' event 34190 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 34191 */ 34192 34193 34194 Stream$1.prototype.pipe = function (destination) { 34195 this.on('data', function (data) { 34196 destination.push(data); 34197 });
vendor: 4,187 bytes, lines 34198-34317
34198 this.on('done', function (flushSource) { 34199 destination.flush(flushSource); 34200 }); 34201 this.on('partialdone', function (flushSource) { 34202 destination.partialFlush(flushSource); 34203 }); 34204 this.on('endedtimeline', function (flushSource) { 34205 destination.endTimeline(flushSource); 34206 }); 34207 this.on('reset', function (flushSource) { 34208 destination.reset(flushSource); 34209 }); 34210 return destination; 34211 }; // Default stream functions that are expected to be overridden to perform 34212 // actual work. These are provided by the prototype as a sort of no-op 34213 // implementation so that we don't have to check for their existence in the 34214 // `pipe` function above. 34215 34216 34217 Stream$1.prototype.push = function (data) { 34218 this.trigger('data', data); 34219 }; 34220 34221 Stream$1.prototype.flush = function (flushSource) { 34222 this.trigger('done', flushSource); 34223 }; 34224 34225 Stream$1.prototype.partialFlush = function (flushSource) { 34226 this.trigger('partialdone', flushSource); 34227 }; 34228 34229 Stream$1.prototype.endTimeline = function (flushSource) { 34230 this.trigger('endedtimeline', flushSource); 34231 }; 34232 34233 Stream$1.prototype.reset = function (flushSource) { 34234 this.trigger('reset', flushSource); 34235 }; 34236 34237 var stream = Stream$1; 34238 34239 // Link To Transport 34240 // ----------------- 34241 34242 34243 var CaptionStream = function CaptionStream() { 34244 CaptionStream.prototype.init.call(this); 34245 this.captionPackets_ = []; 34246 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 34247 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 34248 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 34249 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 34250 ]; 34251 this.reset(); // forward data and done events from CCs to this CaptionStream 34252 34253 this.ccStreams_.forEach(function (cc) { 34254 cc.on('data', this.trigger.bind(this, 'data')); 34255 cc.on('partialdone', this.trigger.bind(this, 'partialdone')); 34256 cc.on('done', this.trigger.bind(this, 'done')); 34257 }, this); 34258 }; 34259 34260 CaptionStream.prototype = new stream(); 34261 34262 CaptionStream.prototype.push = function (event) { 34263 var sei, userData, newCaptionPackets; // only examine SEI NALs 34264 34265 if (event.nalUnitType !== 'sei_rbsp') { 34266 return; 34267 } // parse the sei 34268 34269 34270 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 34271 34272 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 34273 return; 34274 } // parse out the user data payload 34275 34276 34277 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 34278 34279 if (!userData) { 34280 return; 34281 } // Sometimes, the same segment # will be downloaded twice. To stop the 34282 // caption data from being processed twice, we track the latest dts we've 34283 // received and ignore everything with a dts before that. However, since 34284 // data for a specific dts can be split across packets on either side of 34285 // a segment boundary, we need to make sure we *don't* ignore the packets 34286 // from the *next* segment that have dts === this.latestDts_. By constantly 34287 // tracking the number of packets received with dts === this.latestDts_, we 34288 // know how many should be ignored once we start receiving duplicates. 34289 34290 34291 if (event.dts < this.latestDts_) { 34292 // We've started getting older data, so set the flag. 34293 this.ignoreNextEqualDts_ = true; 34294 return; 34295 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 34296 this.numSameDts_--; 34297 34298 if (!this.numSameDts_) { 34299 // We've received the last duplicate packet, time to start processing again 34300 this.ignoreNextEqualDts_ = false; 34301 } 34302 34303 return; 34304 } // parse out CC data packets and save them for later 34305 34306 34307 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 34308 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 34309 34310 if (this.latestDts_ !== event.dts) { 34311 this.numSameDts_ = 0; 34312 } 34313 34314 this.numSameDts_++; 34315 this.latestDts_ = event.dts; 34316 }; 34317
vendor: 4,788 bytes, lines 34318-34451
34318 CaptionStream.prototype.flushCCStreams = function (flushType) { 34319 this.ccStreams_.forEach(function (cc) { 34320 return flushType === 'flush' ? cc.flush() : cc.partialFlush(); 34321 }, this); 34322 }; 34323 34324 CaptionStream.prototype.flushStream = function (flushType) { 34325 // make sure we actually parsed captions before proceeding 34326 if (!this.captionPackets_.length) { 34327 this.flushCCStreams(flushType); 34328 return; 34329 } // In Chrome, the Array#sort function is not stable so add a 34330 // presortIndex that we can use to ensure we get a stable-sort 34331 34332 34333 this.captionPackets_.forEach(function (elem, idx) { 34334 elem.presortIndex = idx; 34335 }); // sort caption byte-pairs based on their PTS values 34336 34337 this.captionPackets_.sort(function (a, b) { 34338 if (a.pts === b.pts) { 34339 return a.presortIndex - b.presortIndex; 34340 } 34341 34342 return a.pts - b.pts; 34343 }); 34344 this.captionPackets_.forEach(function (packet) { 34345 if (packet.type < 2) { 34346 // Dispatch packet to the right Cea608Stream 34347 this.dispatchCea608Packet(packet); 34348 } // this is where an 'else' would go for a dispatching packets 34349 // to a theoretical Cea708Stream that handles SERVICEn data 34350 34351 }, this); 34352 this.captionPackets_.length = 0; 34353 this.flushCCStreams(flushType); 34354 }; 34355 34356 CaptionStream.prototype.flush = function () { 34357 return this.flushStream('flush'); 34358 }; // Only called if handling partial data 34359 34360 34361 CaptionStream.prototype.partialFlush = function () { 34362 return this.flushStream('partialFlush'); 34363 }; 34364 34365 CaptionStream.prototype.reset = function () { 34366 this.latestDts_ = null; 34367 this.ignoreNextEqualDts_ = false; 34368 this.numSameDts_ = 0; 34369 this.activeCea608Channel_ = [null, null]; 34370 this.ccStreams_.forEach(function (ccStream) { 34371 ccStream.reset(); 34372 }); 34373 }; // From the CEA-608 spec: 34374 34375 /* 34376 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed 34377 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is 34378 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair 34379 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the 34380 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) 34381 * to switch to captioning or Text. 34382 */ 34383 // With that in mind, we ignore any data between an XDS control code and a 34384 // subsequent closed-captioning control code. 34385 34386 34387 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 34388 // NOTE: packet.type is the CEA608 field 34389 if (this.setsTextOrXDSActive(packet)) { 34390 this.activeCea608Channel_[packet.type] = null; 34391 } else if (this.setsChannel1Active(packet)) { 34392 this.activeCea608Channel_[packet.type] = 0; 34393 } else if (this.setsChannel2Active(packet)) { 34394 this.activeCea608Channel_[packet.type] = 1; 34395 } 34396 34397 if (this.activeCea608Channel_[packet.type] === null) { 34398 // If we haven't received anything to set the active channel, or the 34399 // packets are Text/XDS data, discard the data; we don't want jumbled 34400 // captions 34401 return; 34402 } 34403 34404 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 34405 }; 34406 34407 CaptionStream.prototype.setsChannel1Active = function (packet) { 34408 return (packet.ccData & 0x7800) === 0x1000; 34409 }; 34410 34411 CaptionStream.prototype.setsChannel2Active = function (packet) { 34412 return (packet.ccData & 0x7800) === 0x1800; 34413 }; 34414 34415 CaptionStream.prototype.setsTextOrXDSActive = function (packet) { 34416 return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a; 34417 }; // ---------------------- 34418 // Session to Application 34419 // ---------------------- 34420 // This hash maps non-ASCII, special, and extended character codes to their 34421 // proper Unicode equivalent. The first keys that are only a single byte 34422 // are the non-standard ASCII characters, which simply map the CEA608 byte 34423 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 34424 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 34425 // can be performed regardless of the field and data channel on which the 34426 // character code was received. 34427 34428 34429 var CHARACTER_TRANSLATION = { 34430 0x2a: 0xe1, 34431 // á 34432 0x5c: 0xe9, 34433 // é 34434 0x5e: 0xed, 34435 // à 34436 0x5f: 0xf3, 34437 // ó 34438 0x60: 0xfa, 34439 // ú 34440 0x7b: 0xe7, 34441 // ç 34442 0x7c: 0xf7, 34443 // ÷ 34444 0x7d: 0xd1, 34445 // à 34446 0x7e: 0xf1, 34447 // ñ 34448 0x7f: 0x2588, 34449 // â 34450 0x0130: 0xae, 34451 // ®
34452 0x0131: 0xb0, 34453 // ° 34454 0x0132: 0xbd, 34455 // ½ 34456 0x0133: 0xbf, 34457 // ¿ 34458 0x0134: 0x2122, 34459 // ⢠34460 0x0135: 0xa2, 34461 // ¢ 34462 0x0136: 0xa3, 34463 // £ 34464 0x0137: 0x266a, 34465 // ⪠34466 0x0138: 0xe0, 34467 // à 34468 0x0139: 0xa0, 34469 // 34470 0x013a: 0xe8, 34471 // è 34472 0x013b: 0xe2, 34473 // â 34474 0x013c: 0xea, 34475 // ê 34476 0x013d: 0xee, 34477 // î 34478 0x013e: 0xf4, 34479 // ô 34480 0x013f: 0xfb, 34481 // û 34482 0x0220: 0xc1, 34483 // à 34484 0x0221: 0xc9, 34485 // à 34486 0x0222: 0xd3, 34487 // à 34488 0x0223: 0xda, 34489 // à 34490 0x0224: 0xdc, 34491 // à 34492 0x0225: 0xfc, 34493 // ü 34494 0x0226: 0x2018, 34495 // â 34496 0x0227: 0xa1, 34497 // ¡ 34498 0x0228: 0x2a, 34499 // * 34500 0x0229: 0x27, 34501 // ' 34502 0x022a: 0x2014, 34503 // â 34504 0x022b: 0xa9, 34505 // © 34506 0x022c: 0x2120, 34507 // â 34508 0x022d: 0x2022, 34509 // ⢠34510 0x022e: 0x201c, 34511 // â 34512 0x022f: 0x201d, 34513 // â 34514 0x0230: 0xc0, 34515 // à 34516 0x0231: 0xc2, 34517 // à 34518 0x0232: 0xc7, 34519 // à 34520 0x0233: 0xc8, 34521 // à 34522 0x0234: 0xca, 34523 // à 34524 0x0235: 0xcb, 34525 // à 34526 0x0236: 0xeb, 34527 // ë 34528 0x0237: 0xce, 34529 // à 34530 0x0238: 0xcf, 34531 // à 34532 0x0239: 0xef, 34533 // ï 34534 0x023a: 0xd4, 34535 // à 34536 0x023b: 0xd9, 34537 // à 34538 0x023c: 0xf9, 34539 // ù 34540 0x023d: 0xdb, 34541 // à 34542 0x023e: 0xab, 34543 // « 34544 0x023f: 0xbb, 34545 // » 34546 0x0320: 0xc3, 34547 // à 34548 0x0321: 0xe3, 34549 // ã 34550 0x0322: 0xcd, 34551 // à 34552 0x0323: 0xcc, 34553 // à 34554 0x0324: 0xec, 34555 // ì 34556 0x0325: 0xd2, 34557 // à 34558 0x0326: 0xf2, 34559 // ò 34560 0x0327: 0xd5, 34561 // à 34562 0x0328: 0xf5, 34563 // õ 34564 0x0329: 0x7b, 34565 // { 34566 0x032a: 0x7d, 34567 // } 34568 0x032b: 0x5c, 34569 // \ 34570 0x032c: 0x5e, 34571 // ^ 34572 0x032d: 0x5f, 34573 // _ 34574 0x032e: 0x7c, 34575 // | 34576 0x032f: 0x7e, 34577 // ~ 34578 0x0330: 0xc4, 34579 // à 34580 0x0331: 0xe4,
vendor: 18,474 bytes, lines 34581-35080
34581 // ä 34582 0x0332: 0xd6, 34583 // à 34584 0x0333: 0xf6, 34585 // ö 34586 0x0334: 0xdf, 34587 // à 34588 0x0335: 0xa5, 34589 // Â¥ 34590 0x0336: 0xa4, 34591 // ¤ 34592 0x0337: 0x2502, 34593 // â 34594 0x0338: 0xc5, 34595 // à 34596 0x0339: 0xe5, 34597 // Ã¥ 34598 0x033a: 0xd8, 34599 // à 34600 0x033b: 0xf8, 34601 // ø 34602 0x033c: 0x250c, 34603 // â 34604 0x033d: 0x2510, 34605 // â 34606 0x033e: 0x2514, 34607 // â 34608 0x033f: 0x2518 // â 34609 34610 }; 34611 34612 var getCharFromCode = function getCharFromCode(code) { 34613 if (code === null) { 34614 return ''; 34615 } 34616 34617 code = CHARACTER_TRANSLATION[code] || code; 34618 return String.fromCharCode(code); 34619 }; // the index of the last row in a CEA-608 display buffer 34620 34621 34622 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 34623 // getting it through bit logic. 34624 34625 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 34626 // cells. The "bottom" row is the last element in the outer array. 34627 34628 var createDisplayBuffer = function createDisplayBuffer() { 34629 var result = [], 34630 i = BOTTOM_ROW + 1; 34631 34632 while (i--) { 34633 result.push(''); 34634 } 34635 34636 return result; 34637 }; 34638 34639 var Cea608Stream = function Cea608Stream(field, dataChannel) { 34640 Cea608Stream.prototype.init.call(this); 34641 this.field_ = field || 0; 34642 this.dataChannel_ = dataChannel || 0; 34643 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 34644 this.setConstants(); 34645 this.reset(); 34646 34647 this.push = function (packet) { 34648 var data, swap, char0, char1, text; // remove the parity bits 34649 34650 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 34651 34652 if (data === this.lastControlCode_) { 34653 this.lastControlCode_ = null; 34654 return; 34655 } // Store control codes 34656 34657 34658 if ((data & 0xf000) === 0x1000) { 34659 this.lastControlCode_ = data; 34660 } else if (data !== this.PADDING_) { 34661 this.lastControlCode_ = null; 34662 } 34663 34664 char0 = data >>> 8; 34665 char1 = data & 0xff; 34666 34667 if (data === this.PADDING_) { 34668 return; 34669 } else if (data === this.RESUME_CAPTION_LOADING_) { 34670 this.mode_ = 'popOn'; 34671 } else if (data === this.END_OF_CAPTION_) { 34672 // If an EOC is received while in paint-on mode, the displayed caption 34673 // text should be swapped to non-displayed memory as if it was a pop-on 34674 // caption. Because of that, we should explicitly switch back to pop-on 34675 // mode 34676 this.mode_ = 'popOn'; 34677 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 34678 34679 this.flushDisplayed(packet.pts); // flip memory 34680 34681 swap = this.displayed_; 34682 this.displayed_ = this.nonDisplayed_; 34683 this.nonDisplayed_ = swap; // start measuring the time to display the caption 34684 34685 this.startPts_ = packet.pts; 34686 } else if (data === this.ROLL_UP_2_ROWS_) { 34687 this.rollUpRows_ = 2; 34688 this.setRollUp(packet.pts); 34689 } else if (data === this.ROLL_UP_3_ROWS_) { 34690 this.rollUpRows_ = 3; 34691 this.setRollUp(packet.pts); 34692 } else if (data === this.ROLL_UP_4_ROWS_) { 34693 this.rollUpRows_ = 4; 34694 this.setRollUp(packet.pts); 34695 } else if (data === this.CARRIAGE_RETURN_) { 34696 this.clearFormatting(packet.pts); 34697 this.flushDisplayed(packet.pts); 34698 this.shiftRowsUp_(); 34699 this.startPts_ = packet.pts; 34700 } else if (data === this.BACKSPACE_) { 34701 if (this.mode_ === 'popOn') { 34702 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 34703 } else { 34704 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 34705 } 34706 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 34707 this.flushDisplayed(packet.pts); 34708 this.displayed_ = createDisplayBuffer(); 34709 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 34710 this.nonDisplayed_ = createDisplayBuffer(); 34711 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 34712 if (this.mode_ !== 'paintOn') { 34713 // NOTE: This should be removed when proper caption positioning is 34714 // implemented 34715 this.flushDisplayed(packet.pts); 34716 this.displayed_ = createDisplayBuffer(); 34717 } 34718 34719 this.mode_ = 'paintOn'; 34720 this.startPts_ = packet.pts; // Append special characters to caption text 34721 } else if (this.isSpecialCharacter(char0, char1)) { 34722 // Bitmask char0 so that we can apply character transformations 34723 // regardless of field and data channel. 34724 // Then byte-shift to the left and OR with char1 so we can pass the 34725 // entire character code to `getCharFromCode`. 34726 char0 = (char0 & 0x03) << 8; 34727 text = getCharFromCode(char0 | char1); 34728 this[this.mode_](packet.pts, text); 34729 this.column_++; // Append extended characters to caption text 34730 } else if (this.isExtCharacter(char0, char1)) { 34731 // Extended characters always follow their "non-extended" equivalents. 34732 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 34733 // decoders are supposed to drop the "è", while compliant decoders 34734 // backspace the "e" and insert "è". 34735 // Delete the previous character 34736 if (this.mode_ === 'popOn') { 34737 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 34738 } else { 34739 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 34740 } // Bitmask char0 so that we can apply character transformations 34741 // regardless of field and data channel. 34742 // Then byte-shift to the left and OR with char1 so we can pass the 34743 // entire character code to `getCharFromCode`. 34744 34745 34746 char0 = (char0 & 0x03) << 8; 34747 text = getCharFromCode(char0 | char1); 34748 this[this.mode_](packet.pts, text); 34749 this.column_++; // Process mid-row codes 34750 } else if (this.isMidRowCode(char0, char1)) { 34751 // Attributes are not additive, so clear all formatting 34752 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 34753 // should be replaced with spaces, so add one now 34754 34755 this[this.mode_](packet.pts, ' '); 34756 this.column_++; 34757 34758 if ((char1 & 0xe) === 0xe) { 34759 this.addFormatting(packet.pts, ['i']); 34760 } 34761 34762 if ((char1 & 0x1) === 0x1) { 34763 this.addFormatting(packet.pts, ['u']); 34764 } // Detect offset control codes and adjust cursor 34765 34766 } else if (this.isOffsetControlCode(char0, char1)) { 34767 // Cursor position is set by indent PAC (see below) in 4-column 34768 // increments, with an additional offset code of 1-3 to reach any 34769 // of the 32 columns specified by CEA-608. So all we need to do 34770 // here is increment the column cursor by the given offset. 34771 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 34772 } else if (this.isPAC(char0, char1)) { 34773 // There's no logic for PAC -> row mapping, so we have to just 34774 // find the row code in an array and use its index :( 34775 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 34776 34777 if (this.mode_ === 'rollUp') { 34778 // This implies that the base row is incorrectly set. 34779 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 34780 // of roll-up rows set. 34781 if (row - this.rollUpRows_ + 1 < 0) { 34782 row = this.rollUpRows_ - 1; 34783 } 34784 34785 this.setRollUp(packet.pts, row); 34786 } 34787 34788 if (row !== this.row_) { 34789 // formatting is only persistent for current row 34790 this.clearFormatting(packet.pts); 34791 this.row_ = row; 34792 } // All PACs can apply underline, so detect and apply 34793 // (All odd-numbered second bytes set underline) 34794 34795 34796 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 34797 this.addFormatting(packet.pts, ['u']); 34798 } 34799 34800 if ((data & 0x10) === 0x10) { 34801 // We've got an indent level code. Each successive even number 34802 // increments the column cursor by 4, so we can get the desired 34803 // column position by bit-shifting to the right (to get n/2) 34804 // and multiplying by 4. 34805 this.column_ = ((data & 0xe) >> 1) * 4; 34806 } 34807 34808 if (this.isColorPAC(char1)) { 34809 // it's a color code, though we only support white, which 34810 // can be either normal or italicized. white italics can be 34811 // either 0x4e or 0x6e depending on the row, so we just 34812 // bitwise-and with 0xe to see if italics should be turned on 34813 if ((char1 & 0xe) === 0xe) { 34814 this.addFormatting(packet.pts, ['i']); 34815 } 34816 } // We have a normal character in char0, and possibly one in char1 34817 34818 } else if (this.isNormalChar(char0)) { 34819 if (char1 === 0x00) { 34820 char1 = null; 34821 } 34822 34823 text = getCharFromCode(char0); 34824 text += getCharFromCode(char1); 34825 this[this.mode_](packet.pts, text); 34826 this.column_ += text.length; 34827 } // finish data processing 34828 34829 }; 34830 }; 34831 34832 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 34833 // display buffer 34834 34835 Cea608Stream.prototype.flushDisplayed = function (pts) { 34836 var content = this.displayed_ // remove spaces from the start and end of the string 34837 .map(function (row) { 34838 try { 34839 return row.trim(); 34840 } catch (e) { 34841 // Ordinarily, this shouldn't happen. However, caption 34842 // parsing errors should not throw exceptions and 34843 // break playback. 34844 // eslint-disable-next-line no-console 34845 console.error('Skipping malformed caption.'); 34846 return ''; 34847 } 34848 }) // combine all text rows to display in one cue 34849 .join('\n') // and remove blank rows from the start and end, but not the middle 34850 .replace(/^\n+|\n+$/g, ''); 34851 34852 if (content.length) { 34853 this.trigger('data', { 34854 startPts: this.startPts_, 34855 endPts: pts, 34856 text: content, 34857 stream: this.name_ 34858 }); 34859 } 34860 }; 34861 /** 34862 * Zero out the data, used for startup and on seek 34863 */ 34864 34865 34866 Cea608Stream.prototype.reset = function () { 34867 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 34868 // actually display captions. If a caption is shifted to a row 34869 // with a lower index than this, it is cleared from the display 34870 // buffer 34871 34872 this.topRow_ = 0; 34873 this.startPts_ = 0; 34874 this.displayed_ = createDisplayBuffer(); 34875 this.nonDisplayed_ = createDisplayBuffer(); 34876 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 34877 34878 this.column_ = 0; 34879 this.row_ = BOTTOM_ROW; 34880 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 34881 34882 this.formatting_ = []; 34883 }; 34884 /** 34885 * Sets up control code and related constants for this instance 34886 */ 34887 34888 34889 Cea608Stream.prototype.setConstants = function () { 34890 // The following attributes have these uses: 34891 // ext_ : char0 for mid-row codes, and the base for extended 34892 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 34893 // extended codes) 34894 // control_: char0 for control codes, except byte-shifted to the 34895 // left so that we can do this.control_ | CONTROL_CODE 34896 // offset_: char0 for tab offset codes 34897 // 34898 // It's also worth noting that control codes, and _only_ control codes, 34899 // differ between field 1 and field2. Field 2 control codes are always 34900 // their field 1 value plus 1. That's why there's the "| field" on the 34901 // control value. 34902 if (this.dataChannel_ === 0) { 34903 this.BASE_ = 0x10; 34904 this.EXT_ = 0x11; 34905 this.CONTROL_ = (0x14 | this.field_) << 8; 34906 this.OFFSET_ = 0x17; 34907 } else if (this.dataChannel_ === 1) { 34908 this.BASE_ = 0x18; 34909 this.EXT_ = 0x19; 34910 this.CONTROL_ = (0x1c | this.field_) << 8; 34911 this.OFFSET_ = 0x1f; 34912 } // Constants for the LSByte command codes recognized by Cea608Stream. This 34913 // list is not exhaustive. For a more comprehensive listing and semantics see 34914 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 34915 // Padding 34916 34917 34918 this.PADDING_ = 0x0000; // Pop-on Mode 34919 34920 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 34921 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 34922 34923 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 34924 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 34925 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 34926 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 34927 34928 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 34929 34930 this.BACKSPACE_ = this.CONTROL_ | 0x21; 34931 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 34932 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 34933 }; 34934 /** 34935 * Detects if the 2-byte packet data is a special character 34936 * 34937 * Special characters have a second byte in the range 0x30 to 0x3f, 34938 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 34939 * data channel 2). 34940 * 34941 * @param {Integer} char0 The first byte 34942 * @param {Integer} char1 The second byte 34943 * @return {Boolean} Whether the 2 bytes are an special character 34944 */ 34945 34946 34947 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 34948 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 34949 }; 34950 /** 34951 * Detects if the 2-byte packet data is an extended character 34952 * 34953 * Extended characters have a second byte in the range 0x20 to 0x3f, 34954 * with the first byte being 0x12 or 0x13 (for data channel 1) or 34955 * 0x1a or 0x1b (for data channel 2). 34956 * 34957 * @param {Integer} char0 The first byte 34958 * @param {Integer} char1 The second byte 34959 * @return {Boolean} Whether the 2 bytes are an extended character 34960 */ 34961 34962 34963 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 34964 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 34965 }; 34966 /** 34967 * Detects if the 2-byte packet is a mid-row code 34968 * 34969 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 34970 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 34971 * channel 2). 34972 * 34973 * @param {Integer} char0 The first byte 34974 * @param {Integer} char1 The second byte 34975 * @return {Boolean} Whether the 2 bytes are a mid-row code 34976 */ 34977 34978 34979 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 34980 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 34981 }; 34982 /** 34983 * Detects if the 2-byte packet is an offset control code 34984 * 34985 * Offset control codes have a second byte in the range 0x21 to 0x23, 34986 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 34987 * data channel 2). 34988 * 34989 * @param {Integer} char0 The first byte 34990 * @param {Integer} char1 The second byte 34991 * @return {Boolean} Whether the 2 bytes are an offset control code 34992 */ 34993 34994 34995 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 34996 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 34997 }; 34998 /** 34999 * Detects if the 2-byte packet is a Preamble Address Code 35000 * 35001 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 35002 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 35003 * range 0x40 to 0x7f. 35004 * 35005 * @param {Integer} char0 The first byte 35006 * @param {Integer} char1 The second byte 35007 * @return {Boolean} Whether the 2 bytes are a PAC 35008 */ 35009 35010 35011 Cea608Stream.prototype.isPAC = function (char0, char1) { 35012 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 35013 }; 35014 /** 35015 * Detects if a packet's second byte is in the range of a PAC color code 35016 * 35017 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 35018 * 0x60 to 0x6f. 35019 * 35020 * @param {Integer} char1 The second byte 35021 * @return {Boolean} Whether the byte is a color PAC 35022 */ 35023 35024 35025 Cea608Stream.prototype.isColorPAC = function (char1) { 35026 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 35027 }; 35028 /** 35029 * Detects if a single byte is in the range of a normal character 35030 * 35031 * Normal text bytes are in the range 0x20 to 0x7f. 35032 * 35033 * @param {Integer} char The byte 35034 * @return {Boolean} Whether the byte is a normal character 35035 */ 35036 35037 35038 Cea608Stream.prototype.isNormalChar = function (_char) { 35039 return _char >= 0x20 && _char <= 0x7f; 35040 }; 35041 /** 35042 * Configures roll-up 35043 * 35044 * @param {Integer} pts Current PTS 35045 * @param {Integer} newBaseRow Used by PACs to slide the current window to 35046 * a new position 35047 */ 35048 35049 35050 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 35051 // Reset the base row to the bottom row when switching modes 35052 if (this.mode_ !== 'rollUp') { 35053 this.row_ = BOTTOM_ROW; 35054 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 35055 35056 this.flushDisplayed(pts); 35057 this.nonDisplayed_ = createDisplayBuffer(); 35058 this.displayed_ = createDisplayBuffer(); 35059 } 35060 35061 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 35062 // move currently displayed captions (up or down) to the new base row 35063 for (var i = 0; i < this.rollUpRows_; i++) { 35064 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 35065 this.displayed_[this.row_ - i] = ''; 35066 } 35067 } 35068 35069 if (newBaseRow === undefined) { 35070 newBaseRow = this.row_; 35071 } 35072 35073 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 35074 }; // Adds the opening HTML tag for the passed character to the caption text, 35075 // and keeps track of it for later closing 35076 35077 35078 Cea608Stream.prototype.addFormatting = function (pts, format) { 35079 this.formatting_ = this.formatting_.concat(format); 35080 var text = format.reduce(function (text, format) {
vendor: 2,997 bytes, lines 35081-35181
35081 return text + '<' + format + '>'; 35082 }, ''); 35083 this[this.mode_](pts, text); 35084 }; // Adds HTML closing tags for current formatting to caption text and 35085 // clears remembered formatting 35086 35087 35088 Cea608Stream.prototype.clearFormatting = function (pts) { 35089 if (!this.formatting_.length) { 35090 return; 35091 } 35092 35093 var text = this.formatting_.reverse().reduce(function (text, format) { 35094 return text + '</' + format + '>'; 35095 }, ''); 35096 this.formatting_ = []; 35097 this[this.mode_](pts, text); 35098 }; // Mode Implementations 35099 35100 35101 Cea608Stream.prototype.popOn = function (pts, text) { 35102 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 35103 35104 baseRow += text; 35105 this.nonDisplayed_[this.row_] = baseRow; 35106 }; 35107 35108 Cea608Stream.prototype.rollUp = function (pts, text) { 35109 var baseRow = this.displayed_[this.row_]; 35110 baseRow += text; 35111 this.displayed_[this.row_] = baseRow; 35112 }; 35113 35114 Cea608Stream.prototype.shiftRowsUp_ = function () { 35115 var i; // clear out inactive rows 35116 35117 for (i = 0; i < this.topRow_; i++) { 35118 this.displayed_[i] = ''; 35119 } 35120 35121 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 35122 this.displayed_[i] = ''; 35123 } // shift displayed rows up 35124 35125 35126 for (i = this.topRow_; i < this.row_; i++) { 35127 this.displayed_[i] = this.displayed_[i + 1]; 35128 } // clear out the bottom row 35129 35130 35131 this.displayed_[this.row_] = ''; 35132 }; 35133 35134 Cea608Stream.prototype.paintOn = function (pts, text) { 35135 var baseRow = this.displayed_[this.row_]; 35136 baseRow += text; 35137 this.displayed_[this.row_] = baseRow; 35138 }; // exports 35139 35140 35141 var captionStream = { 35142 CaptionStream: CaptionStream, 35143 Cea608Stream: Cea608Stream 35144 }; 35145 35146 var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes; 35147 var CaptionStream$1 = captionStream.CaptionStream; 35148 /** 35149 * Maps an offset in the mdat to a sample based on the the size of the samples. 35150 * Assumes that `parseSamples` has been called first. 35151 * 35152 * @param {Number} offset - The offset into the mdat 35153 * @param {Object[]} samples - An array of samples, parsed using `parseSamples` 35154 * @return {?Object} The matching sample, or null if no match was found. 35155 * 35156 * @see ISO-BMFF-12/2015, Section 8.8.8 35157 **/ 35158 35159 var mapToSample = function mapToSample(offset, samples) { 35160 var approximateOffset = offset; 35161 35162 for (var i = 0; i < samples.length; i++) { 35163 var sample = samples[i]; 35164 35165 if (approximateOffset < sample.size) { 35166 return sample; 35167 } 35168 35169 approximateOffset -= sample.size; 35170 } 35171 35172 return null; 35173 }; 35174 /** 35175 * Finds SEI nal units contained in a Media Data Box. 35176 * Assumes that `parseSamples` has been called first. 35177 * 35178 * @param {Uint8Array} avcStream - The bytes of the mdat 35179 * @param {Object[]} samples - The samples parsed out by `parseSamples` 35180 * @param {Number} trackId - The trackId of this video track 35181 * @return {Object[]}
vendor: 8,070 bytes, lines 35181-35409
35181 seiNals - the parsed SEI NALUs found. 35182 * The contents of the seiNal should match what is expected by 35183 * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts) 35184 * 35185 * @see ISO-BMFF-12/2015, Section 8.1.1 35186 * @see Rec. ITU-T H.264, 7.3.2.3.1 35187 **/ 35188 35189 35190 var findSeiNals = function findSeiNals(avcStream, samples, trackId) { 35191 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 35192 result = [], 35193 seiNal, 35194 i, 35195 length, 35196 lastMatchedSample; 35197 35198 for (i = 0; i + 4 < avcStream.length; i += length) { 35199 length = avcView.getUint32(i); 35200 i += 4; // Bail if this doesn't appear to be an H264 stream 35201 35202 if (length <= 0) { 35203 continue; 35204 } 35205 35206 switch (avcStream[i] & 0x1F) { 35207 case 0x06: 35208 var data = avcStream.subarray(i + 1, i + 1 + length); 35209 var matchingSample = mapToSample(i, samples); 35210 seiNal = { 35211 nalUnitType: 'sei_rbsp', 35212 size: length, 35213 data: data, 35214 escapedRBSP: discardEmulationPreventionBytes$1(data), 35215 trackId: trackId 35216 }; 35217 35218 if (matchingSample) { 35219 seiNal.pts = matchingSample.pts; 35220 seiNal.dts = matchingSample.dts; 35221 lastMatchedSample = matchingSample; 35222 } else { 35223 // If a matching sample cannot be found, use the last 35224 // sample's values as they should be as close as possible 35225 seiNal.pts = lastMatchedSample.pts; 35226 seiNal.dts = lastMatchedSample.dts; 35227 } 35228 35229 result.push(seiNal); 35230 break; 35231 35232 default: 35233 break; 35234 } 35235 } 35236 35237 return result; 35238 }; 35239 /** 35240 * Parses sample information out of Track Run Boxes and calculates 35241 * the absolute presentation and decode timestamps of each sample. 35242 * 35243 * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed 35244 * @param {Number} baseMediaDecodeTime - base media decode time from tfdt 35245 @see ISO-BMFF-12/2015, Section 8.8.12 35246 * @param {Object} tfhd - The parsed Track Fragment Header 35247 * @see inspect.parseTfhd 35248 * @return {Object[]} the parsed samples 35249 * 35250 * @see ISO-BMFF-12/2015, Section 8.8.8 35251 **/ 35252 35253 35254 var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) { 35255 var currentDts = baseMediaDecodeTime; 35256 var defaultSampleDuration = tfhd.defaultSampleDuration || 0; 35257 var defaultSampleSize = tfhd.defaultSampleSize || 0; 35258 var trackId = tfhd.trackId; 35259 var allSamples = []; 35260 truns.forEach(function (trun) { 35261 // Note: We currently do not parse the sample table as well 35262 // as the trun. It's possible some sources will require this. 35263 // moov > trak > mdia > minf > stbl 35264 var trackRun = mp4Inspector.parseTrun(trun); 35265 var samples = trackRun.samples; 35266 samples.forEach(function (sample) { 35267 if (sample.duration === undefined) { 35268 sample.duration = defaultSampleDuration; 35269 } 35270 35271 if (sample.size === undefined) { 35272 sample.size = defaultSampleSize; 35273 } 35274 35275 sample.trackId = trackId; 35276 sample.dts = currentDts; 35277 35278 if (sample.compositionTimeOffset === undefined) { 35279 sample.compositionTimeOffset = 0; 35280 } 35281 35282 sample.pts = currentDts + sample.compositionTimeOffset; 35283 currentDts += sample.duration; 35284 }); 35285 allSamples = allSamples.concat(samples); 35286 }); 35287 return allSamples; 35288 }; 35289 /** 35290 * Parses out caption nals from an FMP4 segment's video tracks. 35291 * 35292 * @param {Uint8Array} segment - The bytes of a single segment 35293 * @param {Number} videoTrackId - The trackId of a video track in the segment 35294 * @return {Object.<Number, Object[]>} A mapping of video trackId to 35295 * a list of seiNals found in that track 35296 **/ 35297 35298 35299 var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) { 35300 // To get the samples 35301 var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units 35302 35303 var mdats = probe.findBox(segment, ['mdat']); 35304 var captionNals = {}; 35305 var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs 35306 35307 mdats.forEach(function (mdat, index) { 35308 var matchingTraf = trafs[index]; 35309 mdatTrafPairs.push({ 35310 mdat: mdat, 35311 traf: matchingTraf 35312 }); 35313 }); 35314 mdatTrafPairs.forEach(function (pair) { 35315 var mdat = pair.mdat; 35316 var traf = pair.traf; 35317 var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf 35318 35319 var headerInfo = mp4Inspector.parseTfhd(tfhd[0]); 35320 var trackId = headerInfo.trackId; 35321 var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf 35322 35323 var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0; 35324 var truns = probe.findBox(traf, ['trun']); 35325 var samples; 35326 var seiNals; // Only parse video data for the chosen video track 35327 35328 if (videoTrackId === trackId && truns.length > 0) { 35329 samples = parseSamples(truns, baseMediaDecodeTime, headerInfo); 35330 seiNals = findSeiNals(mdat, samples, trackId); 35331 35332 if (!captionNals[trackId]) { 35333 captionNals[trackId] = []; 35334 } 35335 35336 captionNals[trackId] = captionNals[trackId].concat(seiNals); 35337 } 35338 }); 35339 return captionNals; 35340 }; 35341 /** 35342 * Parses out inband captions from an MP4 container and returns 35343 * caption objects that can be used by WebVTT and the TextTrack API. 35344 * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue 35345 * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack 35346 * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first 35347 * 35348 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 35349 * @param {Number} trackId - The id of the video track to parse 35350 * @param {Number} timescale - The timescale for the video track from the init segment 35351 * 35352 * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks 35353 * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds 35354 * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds 35355 * @return {String} parsedCaptions[].text - The visible content of the caption 35356 **/ 35357 35358 35359 var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) { 35360 var seiNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 35361 35362 if (trackId === null) { 35363 return null; 35364 } 35365 35366 seiNals = parseCaptionNals(segment, trackId); 35367 return { 35368 seiNals: seiNals[trackId], 35369 timescale: timescale 35370 }; 35371 }; 35372 /** 35373 * Converts SEI NALUs into captions that can be used by video.js 35374 **/ 35375 35376 35377 var CaptionParser = function CaptionParser() { 35378 var isInitialized = false; 35379 var captionStream; // Stores segments seen before trackId and timescale are set 35380 35381 var segmentCache; // Stores video track ID of the track being parsed 35382 35383 var trackId; // Stores the timescale of the track being parsed 35384 35385 var timescale; // Stores captions parsed so far 35386 35387 var parsedCaptions; // Stores whether we are receiving partial data or not 35388 35389 var parsingPartial; 35390 /** 35391 * A method to indicate whether a CaptionParser has been initalized 35392 * @returns {Boolean} 35393 **/ 35394 35395 this.isInitialized = function () { 35396 return isInitialized; 35397 }; 35398 /** 35399 * Initializes the underlying CaptionStream, SEI NAL parsing 35400 * and management, and caption collection 35401 **/ 35402 35403 35404 this.init = function (options) { 35405 captionStream = new CaptionStream$1(); 35406 isInitialized = true; 35407 parsingPartial = options ? options.isPartial : false; // Collect dispatched captions 35408 35409 captionStream.on('data', function (event) {
vendor: 4,550 bytes, lines 35410-35559
35410 // Convert to seconds in the source's timescale 35411 event.startTime = event.startPts / timescale; 35412 event.endTime = event.endPts / timescale; 35413 parsedCaptions.captions.push(event); 35414 parsedCaptions.captionStreams[event.stream] = true; 35415 }); 35416 }; 35417 /** 35418 * Determines if a new video track will be selected 35419 * or if the timescale changed 35420 * @return {Boolean} 35421 **/ 35422 35423 35424 this.isNewInit = function (videoTrackIds, timescales) { 35425 if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) { 35426 return false; 35427 } 35428 35429 return trackId !== videoTrackIds[0] || timescale !== timescales[trackId]; 35430 }; 35431 /** 35432 * Parses out SEI captions and interacts with underlying 35433 * CaptionStream to return dispatched captions 35434 * 35435 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 35436 * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment 35437 * @param {Object.<Number, Number>} timescales - The timescales found in the init segment 35438 * @see parseEmbeddedCaptions 35439 * @see m2ts/caption-stream.js 35440 **/ 35441 35442 35443 this.parse = function (segment, videoTrackIds, timescales) { 35444 var parsedData; 35445 35446 if (!this.isInitialized()) { 35447 return null; // This is not likely to be a video segment 35448 } else if (!videoTrackIds || !timescales) { 35449 return null; 35450 } else if (this.isNewInit(videoTrackIds, timescales)) { 35451 // Use the first video track only as there is no 35452 // mechanism to switch to other video tracks 35453 trackId = videoTrackIds[0]; 35454 timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment 35455 // data until we have one. 35456 // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 35457 } else if (trackId === null || !timescale) { 35458 segmentCache.push(segment); 35459 return null; 35460 } // Now that a timescale and trackId is set, parse cached segments 35461 35462 35463 while (segmentCache.length > 0) { 35464 var cachedSegment = segmentCache.shift(); 35465 this.parse(cachedSegment, videoTrackIds, timescales); 35466 } 35467 35468 parsedData = parseEmbeddedCaptions(segment, trackId, timescale); 35469 35470 if (parsedData === null || !parsedData.seiNals) { 35471 return null; 35472 } 35473 35474 this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched 35475 35476 this.flushStream(); 35477 return parsedCaptions; 35478 }; 35479 /** 35480 * Pushes SEI NALUs onto CaptionStream 35481 * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals` 35482 * Assumes that `parseCaptionNals` has been called first 35483 * @see m2ts/caption-stream.js 35484 **/ 35485 35486 35487 this.pushNals = function (nals) { 35488 if (!this.isInitialized() || !nals || nals.length === 0) { 35489 return null; 35490 } 35491 35492 nals.forEach(function (nal) { 35493 captionStream.push(nal); 35494 }); 35495 }; 35496 /** 35497 * Flushes underlying CaptionStream to dispatch processed, displayable captions 35498 * @see m2ts/caption-stream.js 35499 **/ 35500 35501 35502 this.flushStream = function () { 35503 if (!this.isInitialized()) { 35504 return null; 35505 } 35506 35507 if (!parsingPartial) { 35508 captionStream.flush(); 35509 } else { 35510 captionStream.partialFlush(); 35511 } 35512 }; 35513 /** 35514 * Reset caption buckets for new data 35515 **/ 35516 35517 35518 this.clearParsedCaptions = function () { 35519 parsedCaptions.captions = []; 35520 parsedCaptions.captionStreams = {}; 35521 }; 35522 /** 35523 * Resets underlying CaptionStream 35524 * @see m2ts/caption-stream.js 35525 **/ 35526 35527 35528 this.resetCaptionStream = function () { 35529 if (!this.isInitialized()) { 35530 return null; 35531 } 35532 35533 captionStream.reset(); 35534 }; 35535 /** 35536 * Convenience method to clear all captions flushed from the 35537 * CaptionStream and still being parsed 35538 * @see m2ts/caption-stream.js 35539 **/ 35540 35541 35542 this.clearAllCaptions = function () { 35543 this.clearParsedCaptions(); 35544 this.resetCaptionStream(); 35545 }; 35546 /** 35547 * Reset caption parser 35548 **/ 35549 35550 35551 this.reset = function () { 35552 segmentCache = []; 35553 trackId = null; 35554 timescale = null; 35555 35556 if (!parsedCaptions) { 35557 parsedCaptions = { 35558 captions: [], 35559 // CC1, CC2, CC3, CC4
vendor: 28,883 bytes, lines 35560-36460
35560 captionStreams: {} 35561 }; 35562 } else { 35563 this.clearParsedCaptions(); 35564 } 35565 35566 this.resetCaptionStream(); 35567 }; 35568 35569 this.reset(); 35570 }; 35571 35572 var captionParser = CaptionParser; 35573 35574 /** 35575 * mux.js 35576 * 35577 * Copyright (c) Brightcove 35578 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 35579 */ 35580 35581 var streamTypes = { 35582 H264_STREAM_TYPE: 0x1B, 35583 ADTS_STREAM_TYPE: 0x0F, 35584 METADATA_STREAM_TYPE: 0x15 35585 }; 35586 35587 var MAX_TS = 8589934592; 35588 var RO_THRESH = 4294967296; 35589 var TYPE_SHARED = 'shared'; 35590 35591 var handleRollover = function handleRollover(value, reference) { 35592 var direction = 1; 35593 35594 if (value > reference) { 35595 // If the current timestamp value is greater than our reference timestamp and we detect a 35596 // timestamp rollover, this means the roll over is happening in the opposite direction. 35597 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 35598 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 35599 // rollover point. In loading this segment, the timestamp values will be very large, 35600 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 35601 // the time stamp to be `value - 2^33`. 35602 direction = -1; 35603 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 35604 // cause an incorrect adjustment. 35605 35606 35607 while (Math.abs(reference - value) > RO_THRESH) { 35608 value += direction * MAX_TS; 35609 } 35610 35611 return value; 35612 }; 35613 35614 var TimestampRolloverStream = function TimestampRolloverStream(type) { 35615 var lastDTS, referenceDTS; 35616 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 35617 // video and audio. We could use `undefined` here, but having a string 35618 // makes debugging a little clearer. 35619 35620 this.type_ = type || TYPE_SHARED; 35621 35622 this.push = function (data) { 35623 // Any "shared" rollover streams will accept _all_ data. Otherwise, 35624 // streams will only accept data that matches their type. 35625 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 35626 return; 35627 } 35628 35629 if (referenceDTS === undefined) { 35630 referenceDTS = data.dts; 35631 } 35632 35633 data.dts = handleRollover(data.dts, referenceDTS); 35634 data.pts = handleRollover(data.pts, referenceDTS); 35635 lastDTS = data.dts; 35636 this.trigger('data', data); 35637 }; 35638 35639 this.flush = function () { 35640 referenceDTS = lastDTS; 35641 this.trigger('done'); 35642 }; 35643 35644 this.endTimeline = function () { 35645 this.flush(); 35646 this.trigger('endedtimeline'); 35647 }; 35648 35649 this.discontinuity = function () { 35650 referenceDTS = void 0; 35651 lastDTS = void 0; 35652 }; 35653 35654 this.reset = function () { 35655 this.discontinuity(); 35656 this.trigger('reset'); 35657 }; 35658 }; 35659 35660 TimestampRolloverStream.prototype = new stream(); 35661 var timestampRolloverStream = { 35662 TimestampRolloverStream: TimestampRolloverStream, 35663 handleRollover: handleRollover 35664 }; 35665 35666 var parsePid = function parsePid(packet) { 35667 var pid = packet[1] & 0x1f; 35668 pid <<= 8; 35669 pid |= packet[2]; 35670 return pid; 35671 }; 35672 35673 var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) { 35674 return !!(packet[1] & 0x40); 35675 }; 35676 35677 var parseAdaptionField = function parseAdaptionField(packet) { 35678 var offset = 0; // if an adaption field is present, its length is specified by the 35679 // fifth byte of the TS packet header. The adaptation field is 35680 // used to add stuffing to PES packets that don't fill a complete 35681 // TS packet, and to specify some forms of timing and control data 35682 // that we do not currently use. 35683 35684 if ((packet[3] & 0x30) >>> 4 > 0x01) { 35685 offset += packet[4] + 1; 35686 } 35687 35688 return offset; 35689 }; 35690 35691 var parseType$1 = function parseType(packet, pmtPid) { 35692 var pid = parsePid(packet); 35693 35694 if (pid === 0) { 35695 return 'pat'; 35696 } else if (pid === pmtPid) { 35697 return 'pmt'; 35698 } else if (pmtPid) { 35699 return 'pes'; 35700 } 35701 35702 return null; 35703 }; 35704 35705 var parsePat = function parsePat(packet) { 35706 var pusi = parsePayloadUnitStartIndicator(packet); 35707 var offset = 4 + parseAdaptionField(packet); 35708 35709 if (pusi) { 35710 offset += packet[offset] + 1; 35711 } 35712 35713 return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; 35714 }; 35715 35716 var parsePmt = function parsePmt(packet) { 35717 var programMapTable = {}; 35718 var pusi = parsePayloadUnitStartIndicator(packet); 35719 var payloadOffset = 4 + parseAdaptionField(packet); 35720 35721 if (pusi) { 35722 payloadOffset += packet[payloadOffset] + 1; 35723 } // PMTs can be sent ahead of the time when they should actually 35724 // take effect. We don't believe this should ever be the case 35725 // for HLS but we'll ignore "forward" PMT declarations if we see 35726 // them. Future PMT declarations have the current_next_indicator 35727 // set to zero. 35728 35729 35730 if (!(packet[payloadOffset + 5] & 0x01)) { 35731 return; 35732 } 35733 35734 var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section 35735 35736 sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; 35737 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 35738 // long the program info descriptors are 35739 35740 programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table 35741 35742 var offset = 12 + programInfoLength; 35743 35744 while (offset < tableEnd) { 35745 var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type 35746 35747 programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry 35748 // skip past the elementary stream descriptors, if present 35749 35750 offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; 35751 } 35752 35753 return programMapTable; 35754 }; 35755 35756 var parsePesType = function parsePesType(packet, programMapTable) { 35757 var pid = parsePid(packet); 35758 var type = programMapTable[pid]; 35759 35760 switch (type) { 35761 case streamTypes.H264_STREAM_TYPE: 35762 return 'video'; 35763 35764 case streamTypes.ADTS_STREAM_TYPE: 35765 return 'audio'; 35766 35767 case streamTypes.METADATA_STREAM_TYPE: 35768 return 'timed-metadata'; 35769 35770 default: 35771 return null; 35772 } 35773 }; 35774 35775 var parsePesTime = function parsePesTime(packet) { 35776 var pusi = parsePayloadUnitStartIndicator(packet); 35777 35778 if (!pusi) { 35779 return null; 35780 } 35781 35782 var offset = 4 + parseAdaptionField(packet); 35783 35784 if (offset >= packet.byteLength) { 35785 // From the H 222.0 MPEG-TS spec 35786 // "For transport stream packets carrying PES packets, stuffing is needed when there 35787 // is insufficient PES packet data to completely fill the transport stream packet 35788 // payload bytes. Stuffing is accomplished by defining an adaptation field longer than 35789 // the sum of the lengths of the data elements in it, so that the payload bytes 35790 // remaining after the adaptation field exactly accommodates the available PES packet 35791 // data." 35792 // 35793 // If the offset is >= the length of the packet, then the packet contains no data 35794 // and instead is just adaption field stuffing bytes 35795 return null; 35796 } 35797 35798 var pes = null; 35799 var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value 35800 // and a DTS value. Determine what combination of values is 35801 // available to work with. 35802 35803 ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 35804 // performs all bitwise operations on 32-bit integers but javascript 35805 // supports a much greater range (52-bits) of integer using standard 35806 // mathematical operations. 35807 // We construct a 31-bit value using bitwise operators over the 31 35808 // most significant bits and then multiply by 4 (equal to a left-shift 35809 // of 2) before we add the final 2 least significant bits of the 35810 // timestamp (equal to an OR.) 35811 35812 if (ptsDtsFlags & 0xC0) { 35813 pes = {}; // the PTS and DTS are not written out directly. For information 35814 // on how they are encoded, see 35815 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 35816 35817 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; 35818 pes.pts *= 4; // Left shift by 2 35819 35820 pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs 35821 35822 pes.dts = pes.pts; 35823 35824 if (ptsDtsFlags & 0x40) { 35825 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; 35826 pes.dts *= 4; // Left shift by 2 35827 35828 pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs 35829 } 35830 } 35831 35832 return pes; 35833 }; 35834 35835 var parseNalUnitType = function parseNalUnitType(type) { 35836 switch (type) { 35837 case 0x05: 35838 return 'slice_layer_without_partitioning_rbsp_idr'; 35839 35840 case 0x06: 35841 return 'sei_rbsp'; 35842 35843 case 0x07: 35844 return 'seq_parameter_set_rbsp'; 35845 35846 case 0x08: 35847 return 'pic_parameter_set_rbsp'; 35848 35849 case 0x09: 35850 return 'access_unit_delimiter_rbsp'; 35851 35852 default: 35853 return null; 35854 } 35855 }; 35856 35857 var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) { 35858 var offset = 4 + parseAdaptionField(packet); 35859 var frameBuffer = packet.subarray(offset); 35860 var frameI = 0; 35861 var frameSyncPoint = 0; 35862 var foundKeyFrame = false; 35863 var nalType; // advance the sync point to a NAL start, if necessary 35864 35865 for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { 35866 if (frameBuffer[frameSyncPoint + 2] === 1) { 35867 // the sync point is properly aligned 35868 frameI = frameSyncPoint + 5; 35869 break; 35870 } 35871 } 35872 35873 while (frameI < frameBuffer.byteLength) { 35874 // look at the current byte to determine if we've hit the end of 35875 // a NAL unit boundary 35876 switch (frameBuffer[frameI]) { 35877 case 0: 35878 // skip past non-sync sequences 35879 if (frameBuffer[frameI - 1] !== 0) { 35880 frameI += 2; 35881 break; 35882 } else if (frameBuffer[frameI - 2] !== 0) { 35883 frameI++; 35884 break; 35885 } 35886 35887 if (frameSyncPoint + 3 !== frameI - 2) { 35888 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 35889 35890 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 35891 foundKeyFrame = true; 35892 } 35893 } // drop trailing zeroes 35894 35895 35896 do { 35897 frameI++; 35898 } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); 35899 35900 frameSyncPoint = frameI - 2; 35901 frameI += 3; 35902 break; 35903 35904 case 1: 35905 // skip past non-sync sequences 35906 if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) { 35907 frameI += 3; 35908 break; 35909 } 35910 35911 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 35912 35913 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 35914 foundKeyFrame = true; 35915 } 35916 35917 frameSyncPoint = frameI - 2; 35918 frameI += 3; 35919 break; 35920 35921 default: 35922 // the current byte isn't a one or zero, so it cannot be part 35923 // of a sync sequence 35924 frameI += 3; 35925 break; 35926 } 35927 } 35928 35929 frameBuffer = frameBuffer.subarray(frameSyncPoint); 35930 frameI -= frameSyncPoint; 35931 frameSyncPoint = 0; // parse the final nal 35932 35933 if (frameBuffer && frameBuffer.byteLength > 3) { 35934 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 35935 35936 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 35937 foundKeyFrame = true; 35938 } 35939 } 35940 35941 return foundKeyFrame; 35942 }; 35943 35944 var probe$1 = { 35945 parseType: parseType$1, 35946 parsePat: parsePat, 35947 parsePmt: parsePmt, 35948 parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, 35949 parsePesType: parsePesType, 35950 parsePesTime: parsePesTime, 35951 videoPacketContainsKeyFrame: videoPacketContainsKeyFrame 35952 }; 35953 35954 /** 35955 * mux.js 35956 * 35957 * Copyright (c) Brightcove 35958 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 35959 * 35960 * Utilities to detect basic properties and metadata about Aac data. 35961 */ 35962 35963 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 35964 35965 var isLikelyAacData = function isLikelyAacData(data) { 35966 if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) { 35967 return true; 35968 } 35969 35970 return false; 35971 }; 35972 35973 var parseSyncSafeInteger = function parseSyncSafeInteger(data) { 35974 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 35975 }; // return a percent-encoded representation of the specified byte range 35976 // @see http://en.wikipedia.org/wiki/Percent-encoding 35977 35978 35979 var percentEncode = function percentEncode(bytes, start, end) { 35980 var i, 35981 result = ''; 35982 35983 for (i = start; i < end; i++) { 35984 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 35985 } 35986 35987 return result; 35988 }; // return the string representation of the specified byte range, 35989 // interpreted as ISO-8859-1. 35990 35991 35992 var parseIso88591 = function parseIso88591(bytes, start, end) { 35993 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 35994 }; 35995 35996 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 35997 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 35998 flags = header[byteIndex + 5], 35999 footerPresent = (flags & 16) >> 4; 36000 36001 if (footerPresent) { 36002 return returnSize + 20; 36003 } 36004 36005 return returnSize + 10; 36006 }; 36007 36008 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 36009 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 36010 middle = header[byteIndex + 4] << 3, 36011 highTwo = header[byteIndex + 3] & 0x3 << 11; 36012 return highTwo | middle | lowThree; 36013 }; 36014 36015 var parseType$2 = function parseType(header, byteIndex) { 36016 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 36017 return 'timed-metadata'; 36018 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 36019 return 'audio'; 36020 } 36021 36022 return null; 36023 }; 36024 36025 var parseSampleRate = function parseSampleRate(packet) { 36026 var i = 0; 36027 36028 while (i + 5 < packet.length) { 36029 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 36030 // If a valid header was not found, jump one forward and attempt to 36031 // find a valid ADTS header starting at the next byte 36032 i++; 36033 continue; 36034 } 36035 36036 return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2]; 36037 } 36038 36039 return null; 36040 }; 36041 36042 var parseAacTimestamp = function parseAacTimestamp(packet) { 36043 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 36044 36045 frameStart = 10; 36046 36047 if (packet[5] & 0x40) { 36048 // advance the frame start past the extended header 36049 frameStart += 4; // header size field 36050 36051 frameStart += parseSyncSafeInteger(packet.subarray(10, 14)); 36052 } // parse one or more ID3 frames 36053 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 36054 36055 36056 do { 36057 // determine the number of bytes in this frame 36058 frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8)); 36059 36060 if (frameSize < 1) { 36061 return null; 36062 } 36063 36064 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 36065 36066 if (frameHeader === 'PRIV') { 36067 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 36068 36069 for (var i = 0; i < frame.byteLength; i++) { 36070 if (frame[i] === 0) { 36071 var owner = parseIso88591(frame, 0, i); 36072 36073 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 36074 var d = frame.subarray(i + 1); 36075 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 36076 size *= 4; 36077 size += d[7] & 0x03; 36078 return size; 36079 } 36080 36081 break; 36082 } 36083 } 36084 } 36085 36086 frameStart += 10; // advance past the frame header 36087 36088 frameStart += frameSize; // advance past the frame body 36089 } while (frameStart < packet.byteLength); 36090 36091 return null; 36092 }; 36093 36094 var utils = { 36095 isLikelyAacData: isLikelyAacData, 36096 parseId3TagSize: parseId3TagSize, 36097 parseAdtsSize: parseAdtsSize, 36098 parseType: parseType$2, 36099 parseSampleRate: parseSampleRate, 36100 parseAacTimestamp: parseAacTimestamp 36101 }; 36102 36103 /** 36104 * mux.js 36105 * 36106 * Copyright (c) Brightcove 36107 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 36108 */ 36109 var ONE_SECOND_IN_TS = 90000, 36110 // 90kHz clock 36111 secondsToVideoTs, 36112 secondsToAudioTs, 36113 videoTsToSeconds, 36114 audioTsToSeconds, 36115 audioTsToVideoTs, 36116 videoTsToAudioTs, 36117 metadataTsToSeconds; 36118 36119 secondsToVideoTs = function secondsToVideoTs(seconds) { 36120 return seconds * ONE_SECOND_IN_TS; 36121 }; 36122 36123 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 36124 return seconds * sampleRate; 36125 }; 36126 36127 videoTsToSeconds = function videoTsToSeconds(timestamp) { 36128 return timestamp / ONE_SECOND_IN_TS; 36129 }; 36130 36131 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 36132 return timestamp / sampleRate; 36133 }; 36134 36135 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 36136 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 36137 }; 36138 36139 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 36140 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 36141 }; 36142 /** 36143 * Adjust ID3 tag or caption timing information by the timeline pts values 36144 * (if keepOriginalTimestamps is false) and convert to seconds 36145 */ 36146 36147 36148 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) { 36149 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 36150 }; 36151 36152 var clock = { 36153 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 36154 secondsToVideoTs: secondsToVideoTs, 36155 secondsToAudioTs: secondsToAudioTs, 36156 videoTsToSeconds: videoTsToSeconds, 36157 audioTsToSeconds: audioTsToSeconds, 36158 audioTsToVideoTs: audioTsToVideoTs, 36159 videoTsToAudioTs: videoTsToAudioTs, 36160 metadataTsToSeconds: metadataTsToSeconds 36161 }; 36162 36163 var handleRollover$1 = timestampRolloverStream.handleRollover; 36164 var probe$2 = {}; 36165 probe$2.ts = probe$1; 36166 probe$2.aac = utils; 36167 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; 36168 var MP2T_PACKET_LENGTH = 188, 36169 // bytes 36170 SYNC_BYTE = 0x47; 36171 /** 36172 * walks through segment data looking for pat and pmt packets to parse out 36173 * program map table information 36174 */ 36175 36176 var parsePsi_ = function parsePsi_(bytes, pmt) { 36177 var startIndex = 0, 36178 endIndex = MP2T_PACKET_LENGTH, 36179 packet, 36180 type; 36181 36182 while (endIndex < bytes.byteLength) { 36183 // Look for a pair of start and end sync bytes in the data.. 36184 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 36185 // We found a packet 36186 packet = bytes.subarray(startIndex, endIndex); 36187 type = probe$2.ts.parseType(packet, pmt.pid); 36188 36189 switch (type) { 36190 case 'pat': 36191 if (!pmt.pid) { 36192 pmt.pid = probe$2.ts.parsePat(packet); 36193 } 36194 36195 break; 36196 36197 case 'pmt': 36198 if (!pmt.table) { 36199 pmt.table = probe$2.ts.parsePmt(packet); 36200 } 36201 36202 break; 36203 36204 default: 36205 break; 36206 } // Found the pat and pmt, we can stop walking the segment 36207 36208 36209 if (pmt.pid && pmt.table) { 36210 return; 36211 } 36212 36213 startIndex += MP2T_PACKET_LENGTH; 36214 endIndex += MP2T_PACKET_LENGTH; 36215 continue; 36216 } // If we get here, we have somehow become de-synchronized and we need to step 36217 // forward one byte at a time until we find a pair of sync bytes that denote 36218 // a packet 36219 36220 36221 startIndex++; 36222 endIndex++; 36223 } 36224 }; 36225 /** 36226 * walks through the segment data from the start and end to get timing information 36227 * for the first and last audio pes packets 36228 */ 36229 36230 36231 var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) { 36232 var startIndex = 0, 36233 endIndex = MP2T_PACKET_LENGTH, 36234 packet, 36235 type, 36236 pesType, 36237 pusi, 36238 parsed; 36239 var endLoop = false; // Start walking from start of segment to get first audio packet 36240 36241 while (endIndex <= bytes.byteLength) { 36242 // Look for a pair of start and end sync bytes in the data.. 36243 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 36244 // We found a packet 36245 packet = bytes.subarray(startIndex, endIndex); 36246 type = probe$2.ts.parseType(packet, pmt.pid); 36247 36248 switch (type) { 36249 case 'pes': 36250 pesType = probe$2.ts.parsePesType(packet, pmt.table); 36251 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 36252 36253 if (pesType === 'audio' && pusi) { 36254 parsed = probe$2.ts.parsePesTime(packet); 36255 36256 if (parsed) { 36257 parsed.type = 'audio'; 36258 result.audio.push(parsed); 36259 endLoop = true; 36260 } 36261 } 36262 36263 break; 36264 36265 default: 36266 break; 36267 } 36268 36269 if (endLoop) { 36270 break; 36271 } 36272 36273 startIndex += MP2T_PACKET_LENGTH; 36274 endIndex += MP2T_PACKET_LENGTH; 36275 continue; 36276 } // If we get here, we have somehow become de-synchronized and we need to step 36277 // forward one byte at a time until we find a pair of sync bytes that denote 36278 // a packet 36279 36280 36281 startIndex++; 36282 endIndex++; 36283 } // Start walking from end of segment to get last audio packet 36284 36285 36286 endIndex = bytes.byteLength; 36287 startIndex = endIndex - MP2T_PACKET_LENGTH; 36288 endLoop = false; 36289 36290 while (startIndex >= 0) { 36291 // Look for a pair of start and end sync bytes in the data.. 36292 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 36293 // We found a packet 36294 packet = bytes.subarray(startIndex, endIndex); 36295 type = probe$2.ts.parseType(packet, pmt.pid); 36296 36297 switch (type) { 36298 case 'pes': 36299 pesType = probe$2.ts.parsePesType(packet, pmt.table); 36300 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 36301 36302 if (pesType === 'audio' && pusi) { 36303 parsed = probe$2.ts.parsePesTime(packet); 36304 36305 if (parsed) { 36306 parsed.type = 'audio'; 36307 result.audio.push(parsed); 36308 endLoop = true; 36309 } 36310 } 36311 36312 break; 36313 36314 default: 36315 break; 36316 } 36317 36318 if (endLoop) { 36319 break; 36320 } 36321 36322 startIndex -= MP2T_PACKET_LENGTH; 36323 endIndex -= MP2T_PACKET_LENGTH; 36324 continue; 36325 } // If we get here, we have somehow become de-synchronized and we need to step 36326 // forward one byte at a time until we find a pair of sync bytes that denote 36327 // a packet 36328 36329 36330 startIndex--; 36331 endIndex--; 36332 } 36333 }; 36334 /** 36335 * walks through the segment data from the start and end to get timing information 36336 * for the first and last video pes packets as well as timing information for the first 36337 * key frame. 36338 */ 36339 36340 36341 var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) { 36342 var startIndex = 0, 36343 endIndex = MP2T_PACKET_LENGTH, 36344 packet, 36345 type, 36346 pesType, 36347 pusi, 36348 parsed, 36349 frame, 36350 i, 36351 pes; 36352 var endLoop = false; 36353 var currentFrame = { 36354 data: [], 36355 size: 0 36356 }; // Start walking from start of segment to get first video packet 36357 36358 while (endIndex < bytes.byteLength) { 36359 // Look for a pair of start and end sync bytes in the data.. 36360 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 36361 // We found a packet 36362 packet = bytes.subarray(startIndex, endIndex); 36363 type = probe$2.ts.parseType(packet, pmt.pid); 36364 36365 switch (type) { 36366 case 'pes': 36367 pesType = probe$2.ts.parsePesType(packet, pmt.table); 36368 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 36369 36370 if (pesType === 'video') { 36371 if (pusi && !endLoop) { 36372 parsed = probe$2.ts.parsePesTime(packet); 36373 36374 if (parsed) { 36375 parsed.type = 'video'; 36376 result.video.push(parsed); 36377 endLoop = true; 36378 } 36379 } 36380 36381 if (!result.firstKeyFrame) { 36382 if (pusi) { 36383 if (currentFrame.size !== 0) { 36384 frame = new Uint8Array(currentFrame.size); 36385 i = 0; 36386 36387 while (currentFrame.data.length) { 36388 pes = currentFrame.data.shift(); 36389 frame.set(pes, i); 36390 i += pes.byteLength; 36391 } 36392 36393 if (probe$2.ts.videoPacketContainsKeyFrame(frame)) { 36394 var firstKeyFrame = probe$2.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting 36395 // the keyframe seems to work fine with HLS playback 36396 // and definitely preferable to a crash with TypeError... 36397 36398 if (firstKeyFrame) { 36399 result.firstKeyFrame = firstKeyFrame; 36400 result.firstKeyFrame.type = 'video'; 36401 } else { 36402 // eslint-disable-next-line 36403 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.'); 36404 } 36405 } 36406 36407 currentFrame.size = 0; 36408 } 36409 } 36410 36411 currentFrame.data.push(packet); 36412 currentFrame.size += packet.byteLength; 36413 } 36414 } 36415 36416 break; 36417 36418 default: 36419 break; 36420 } 36421 36422 if (endLoop && result.firstKeyFrame) { 36423 break; 36424 } 36425 36426 startIndex += MP2T_PACKET_LENGTH; 36427 endIndex += MP2T_PACKET_LENGTH; 36428 continue; 36429 } // If we get here, we have somehow become de-synchronized and we need to step 36430 // forward one byte at a time until we find a pair of sync bytes that denote 36431 // a packet 36432 36433 36434 startIndex++; 36435 endIndex++; 36436 } // Start walking from end of segment to get last video packet 36437 36438 36439 endIndex = bytes.byteLength; 36440 startIndex = endIndex - MP2T_PACKET_LENGTH; 36441 endLoop = false; 36442 36443 while (startIndex >= 0) { 36444 // Look for a pair of start and end sync bytes in the data.. 36445 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 36446 // We found a packet 36447 packet = bytes.subarray(startIndex, endIndex); 36448 type = probe$2.ts.parseType(packet, pmt.pid); 36449 36450 switch (type) { 36451 case 'pes': 36452 pesType = probe$2.ts.parsePesType(packet, pmt.table); 36453 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 36454 36455 if (pesType === 'video' && pusi) { 36456 parsed = probe$2.ts.parsePesTime(packet); 36457 36458 if (parsed) { 36459 parsed.type = 'video'; 36460 result.video.push(parsed);
vendor: 76,827 bytes, lines 36461-38860
36461 endLoop = true; 36462 } 36463 } 36464 36465 break; 36466 36467 default: 36468 break; 36469 } 36470 36471 if (endLoop) { 36472 break; 36473 } 36474 36475 startIndex -= MP2T_PACKET_LENGTH; 36476 endIndex -= MP2T_PACKET_LENGTH; 36477 continue; 36478 } // If we get here, we have somehow become de-synchronized and we need to step 36479 // forward one byte at a time until we find a pair of sync bytes that denote 36480 // a packet 36481 36482 36483 startIndex--; 36484 endIndex--; 36485 } 36486 }; 36487 /** 36488 * Adjusts the timestamp information for the segment to account for 36489 * rollover and convert to seconds based on pes packet timescale (90khz clock) 36490 */ 36491 36492 36493 var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) { 36494 if (segmentInfo.audio && segmentInfo.audio.length) { 36495 var audioBaseTimestamp = baseTimestamp; 36496 36497 if (typeof audioBaseTimestamp === 'undefined') { 36498 audioBaseTimestamp = segmentInfo.audio[0].dts; 36499 } 36500 36501 segmentInfo.audio.forEach(function (info) { 36502 info.dts = handleRollover$1(info.dts, audioBaseTimestamp); 36503 info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds 36504 36505 info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; 36506 info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; 36507 }); 36508 } 36509 36510 if (segmentInfo.video && segmentInfo.video.length) { 36511 var videoBaseTimestamp = baseTimestamp; 36512 36513 if (typeof videoBaseTimestamp === 'undefined') { 36514 videoBaseTimestamp = segmentInfo.video[0].dts; 36515 } 36516 36517 segmentInfo.video.forEach(function (info) { 36518 info.dts = handleRollover$1(info.dts, videoBaseTimestamp); 36519 info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds 36520 36521 info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; 36522 info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; 36523 }); 36524 36525 if (segmentInfo.firstKeyFrame) { 36526 var frame = segmentInfo.firstKeyFrame; 36527 frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp); 36528 frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds 36529 36530 frame.dtsTime = frame.dts / ONE_SECOND_IN_TS$1; 36531 frame.ptsTime = frame.dts / ONE_SECOND_IN_TS$1; 36532 } 36533 } 36534 }; 36535 /** 36536 * inspects the aac data stream for start and end time information 36537 */ 36538 36539 36540 var inspectAac_ = function inspectAac_(bytes) { 36541 var endLoop = false, 36542 audioCount = 0, 36543 sampleRate = null, 36544 timestamp = null, 36545 frameSize = 0, 36546 byteIndex = 0, 36547 packet; 36548 36549 while (bytes.length - byteIndex >= 3) { 36550 var type = probe$2.aac.parseType(bytes, byteIndex); 36551 36552 switch (type) { 36553 case 'timed-metadata': 36554 // Exit early because we don't have enough to parse 36555 // the ID3 tag header 36556 if (bytes.length - byteIndex < 10) { 36557 endLoop = true; 36558 break; 36559 } 36560 36561 frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 36562 // to emit a full packet 36563 36564 if (frameSize > bytes.length) { 36565 endLoop = true; 36566 break; 36567 } 36568 36569 if (timestamp === null) { 36570 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 36571 timestamp = probe$2.aac.parseAacTimestamp(packet); 36572 } 36573 36574 byteIndex += frameSize; 36575 break; 36576 36577 case 'audio': 36578 // Exit early because we don't have enough to parse 36579 // the ADTS frame header 36580 if (bytes.length - byteIndex < 7) { 36581 endLoop = true; 36582 break; 36583 } 36584 36585 frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 36586 // to emit a full packet 36587 36588 if (frameSize > bytes.length) { 36589 endLoop = true; 36590 break; 36591 } 36592 36593 if (sampleRate === null) { 36594 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 36595 sampleRate = probe$2.aac.parseSampleRate(packet); 36596 } 36597 36598 audioCount++; 36599 byteIndex += frameSize; 36600 break; 36601 36602 default: 36603 byteIndex++; 36604 break; 36605 } 36606 36607 if (endLoop) { 36608 return null; 36609 } 36610 } 36611 36612 if (sampleRate === null || timestamp === null) { 36613 return null; 36614 } 36615 36616 var audioTimescale = ONE_SECOND_IN_TS$1 / sampleRate; 36617 var result = { 36618 audio: [{ 36619 type: 'audio', 36620 dts: timestamp, 36621 pts: timestamp 36622 }, { 36623 type: 'audio', 36624 dts: timestamp + audioCount * 1024 * audioTimescale, 36625 pts: timestamp + audioCount * 1024 * audioTimescale 36626 }] 36627 }; 36628 return result; 36629 }; 36630 /** 36631 * inspects the transport stream segment data for start and end time information 36632 * of the audio and video tracks (when present) as well as the first key frame's 36633 * start time. 36634 */ 36635 36636 36637 var inspectTs_ = function inspectTs_(bytes) { 36638 var pmt = { 36639 pid: null, 36640 table: null 36641 }; 36642 var result = {}; 36643 parsePsi_(bytes, pmt); 36644 36645 for (var pid in pmt.table) { 36646 if (pmt.table.hasOwnProperty(pid)) { 36647 var type = pmt.table[pid]; 36648 36649 switch (type) { 36650 case streamTypes.H264_STREAM_TYPE: 36651 result.video = []; 36652 parseVideoPes_(bytes, pmt, result); 36653 36654 if (result.video.length === 0) { 36655 delete result.video; 36656 } 36657 36658 break; 36659 36660 case streamTypes.ADTS_STREAM_TYPE: 36661 result.audio = []; 36662 parseAudioPes_(bytes, pmt, result); 36663 36664 if (result.audio.length === 0) { 36665 delete result.audio; 36666 } 36667 36668 break; 36669 36670 default: 36671 break; 36672 } 36673 } 36674 } 36675 36676 return result; 36677 }; 36678 /** 36679 * Inspects segment byte data and returns an object with start and end timing information 36680 * 36681 * @param {Uint8Array} bytes The segment byte data 36682 * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame 36683 * timestamps for rollover. This value must be in 90khz clock. 36684 * @return {Object} Object containing start and end frame timing info of segment. 36685 */ 36686 36687 36688 var inspect = function inspect(bytes, baseTimestamp) { 36689 var isAacData = probe$2.aac.isLikelyAacData(bytes); 36690 var result; 36691 36692 if (isAacData) { 36693 result = inspectAac_(bytes); 36694 } else { 36695 result = inspectTs_(bytes); 36696 } 36697 36698 if (!result || !result.audio && !result.video) { 36699 return null; 36700 } 36701 36702 adjustTimestamp_(result, baseTimestamp); 36703 return result; 36704 }; 36705 36706 var tsInspector = { 36707 inspect: inspect, 36708 parseAudioPes_: parseAudioPes_ 36709 }; 36710 36711 /* 36712 * pkcs7.pad 36713 * https://github.com/brightcove/pkcs7 36714 * 36715 * Copyright (c) 2014 Brightcove 36716 * Licensed under the apache2 license. 36717 */ 36718 /** 36719 * Returns the subarray of a Uint8Array without PKCS#7 padding. 36720 * @param padded {Uint8Array} unencrypted bytes that have been padded 36721 * @return {Uint8Array} the unpadded bytes 36722 * @see http://tools.ietf.org/html/rfc5652 36723 */ 36724 36725 function unpad(padded) { 36726 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 36727 } 36728 36729 var classCallCheck = function classCallCheck(instance, Constructor) { 36730 if (!(instance instanceof Constructor)) { 36731 throw new TypeError("Cannot call a class as a function"); 36732 } 36733 }; 36734 36735 var createClass = function () { 36736 function defineProperties(target, props) { 36737 for (var i = 0; i < props.length; i++) { 36738 var descriptor = props[i]; 36739 descriptor.enumerable = descriptor.enumerable || false; 36740 descriptor.configurable = true; 36741 if ("value" in descriptor) descriptor.writable = true; 36742 Object.defineProperty(target, descriptor.key, descriptor); 36743 } 36744 } 36745 36746 return function (Constructor, protoProps, staticProps) { 36747 if (protoProps) defineProperties(Constructor.prototype, protoProps); 36748 if (staticProps) defineProperties(Constructor, staticProps); 36749 return Constructor; 36750 }; 36751 }(); 36752 36753 var inherits$1 = function inherits(subClass, superClass) { 36754 if (typeof superClass !== "function" && superClass !== null) { 36755 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 36756 } 36757 36758 subClass.prototype = Object.create(superClass && superClass.prototype, { 36759 constructor: { 36760 value: subClass, 36761 enumerable: false, 36762 writable: true, 36763 configurable: true 36764 } 36765 }); 36766 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 36767 }; 36768 36769 var possibleConstructorReturn = function possibleConstructorReturn(self, call) { 36770 if (!self) { 36771 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 36772 } 36773 36774 return call && (typeof call === "object" || typeof call === "function") ? call : self; 36775 }; 36776 /** 36777 * @file aes.js 36778 * 36779 * This file contains an adaptation of the AES decryption algorithm 36780 * from the Standford Javascript Cryptography Library. That work is 36781 * covered by the following copyright and permissions notice: 36782 * 36783 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 36784 * All rights reserved. 36785 * 36786 * Redistribution and use in source and binary forms, with or without 36787 * modification, are permitted provided that the following conditions are 36788 * met: 36789 * 36790 * 1. Redistributions of source code must retain the above copyright 36791 * notice, this list of conditions and the following disclaimer. 36792 * 36793 * 2. Redistributions in binary form must reproduce the above 36794 * copyright notice, this list of conditions and the following 36795 * disclaimer in the documentation and/or other materials provided 36796 * with the distribution. 36797 * 36798 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 36799 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 36800 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 36801 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 36802 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 36803 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 36804 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 36805 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 36806 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 36807 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 36808 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36809 * 36810 * The views and conclusions contained in the software and documentation 36811 * are those of the authors and should not be interpreted as representing 36812 * official policies, either expressed or implied, of the authors. 36813 */ 36814 36815 /** 36816 * Expand the S-box tables. 36817 * 36818 * @private 36819 */ 36820 36821 36822 var precompute = function precompute() { 36823 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 36824 var encTable = tables[0]; 36825 var decTable = tables[1]; 36826 var sbox = encTable[4]; 36827 var sboxInv = decTable[4]; 36828 var i = void 0; 36829 var x = void 0; 36830 var xInv = void 0; 36831 var d = []; 36832 var th = []; 36833 var x2 = void 0; 36834 var x4 = void 0; 36835 var x8 = void 0; 36836 var s = void 0; 36837 var tEnc = void 0; 36838 var tDec = void 0; // Compute double and third tables 36839 36840 for (i = 0; i < 256; i++) { 36841 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 36842 } 36843 36844 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 36845 // Compute sbox 36846 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 36847 s = s >> 8 ^ s & 255 ^ 99; 36848 sbox[x] = s; 36849 sboxInv[s] = x; // Compute MixColumns 36850 36851 x8 = d[x4 = d[x2 = d[x]]]; 36852 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 36853 tEnc = d[s] * 0x101 ^ s * 0x1010100; 36854 36855 for (i = 0; i < 4; i++) { 36856 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 36857 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 36858 } 36859 } // Compactify. Considerable speedup on Firefox. 36860 36861 36862 for (i = 0; i < 5; i++) { 36863 encTable[i] = encTable[i].slice(0); 36864 decTable[i] = decTable[i].slice(0); 36865 } 36866 36867 return tables; 36868 }; 36869 36870 var aesTables = null; 36871 /** 36872 * Schedule out an AES key for both encryption and decryption. This 36873 * is a low-level class. Use a cipher mode to do bulk encryption. 36874 * 36875 * @class AES 36876 * @param key {Array} The key as an array of 4, 6 or 8 words. 36877 */ 36878 36879 var AES = function () { 36880 function AES(key) { 36881 classCallCheck(this, AES); 36882 /** 36883 * The expanded S-box and inverse S-box tables. These will be computed 36884 * on the client so that we don't have to send them down the wire. 36885 * 36886 * There are two tables, _tables[0] is for encryption and 36887 * _tables[1] is for decryption. 36888 * 36889 * The first 4 sub-tables are the expanded S-box with MixColumns. The 36890 * last (_tables[01][4]) is the S-box itself. 36891 * 36892 * @private 36893 */ 36894 // if we have yet to precompute the S-box tables 36895 // do so now 36896 36897 if (!aesTables) { 36898 aesTables = precompute(); 36899 } // then make a copy of that object for use 36900 36901 36902 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()]]; 36903 var i = void 0; 36904 var j = void 0; 36905 var tmp = void 0; 36906 var encKey = void 0; 36907 var decKey = void 0; 36908 var sbox = this._tables[0][4]; 36909 var decTable = this._tables[1]; 36910 var keyLen = key.length; 36911 var rcon = 1; 36912 36913 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 36914 throw new Error('Invalid aes key size'); 36915 } 36916 36917 encKey = key.slice(0); 36918 decKey = []; 36919 this._key = [encKey, decKey]; // schedule encryption keys 36920 36921 for (i = keyLen; i < 4 * keyLen + 28; i++) { 36922 tmp = encKey[i - 1]; // apply sbox 36923 36924 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 36925 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 36926 36927 if (i % keyLen === 0) { 36928 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 36929 rcon = rcon << 1 ^ (rcon >> 7) * 283; 36930 } 36931 } 36932 36933 encKey[i] = encKey[i - keyLen] ^ tmp; 36934 } // schedule decryption keys 36935 36936 36937 for (j = 0; i; j++, i--) { 36938 tmp = encKey[j & 3 ? i : i - 4]; 36939 36940 if (i <= 4 || j < 4) { 36941 decKey[j] = tmp; 36942 } else { 36943 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 36944 } 36945 } 36946 } 36947 /** 36948 * Decrypt 16 bytes, specified as four 32-bit words. 36949 * 36950 * @param {Number} encrypted0 the first word to decrypt 36951 * @param {Number} encrypted1 the second word to decrypt 36952 * @param {Number} encrypted2 the third word to decrypt 36953 * @param {Number} encrypted3 the fourth word to decrypt 36954 * @param {Int32Array} out the array to write the decrypted words 36955 * into 36956 * @param {Number} offset the offset into the output array to start 36957 * writing results 36958 * @return {Array} The plaintext. 36959 */ 36960 36961 36962 AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 36963 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 36964 36965 var a = encrypted0 ^ key[0]; 36966 var b = encrypted3 ^ key[1]; 36967 var c = encrypted2 ^ key[2]; 36968 var d = encrypted1 ^ key[3]; 36969 var a2 = void 0; 36970 var b2 = void 0; 36971 var c2 = void 0; // key.length === 2 ? 36972 36973 var nInnerRounds = key.length / 4 - 2; 36974 var i = void 0; 36975 var kIndex = 4; 36976 var table = this._tables[1]; // load up the tables 36977 36978 var table0 = table[0]; 36979 var table1 = table[1]; 36980 var table2 = table[2]; 36981 var table3 = table[3]; 36982 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 36983 36984 for (i = 0; i < nInnerRounds; i++) { 36985 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 36986 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 36987 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 36988 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 36989 kIndex += 4; 36990 a = a2; 36991 b = b2; 36992 c = c2; 36993 } // Last round. 36994 36995 36996 for (i = 0; i < 4; i++) { 36997 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 36998 a2 = a; 36999 a = b; 37000 b = c; 37001 c = d; 37002 d = a2; 37003 } 37004 }; 37005 37006 return AES; 37007 }(); 37008 /** 37009 * @file stream.js 37010 */ 37011 37012 /** 37013 * A lightweight readable stream implemention that handles event dispatching. 37014 * 37015 * @class Stream 37016 */ 37017 37018 37019 var Stream$2 = function () { 37020 function Stream() { 37021 classCallCheck(this, Stream); 37022 this.listeners = {}; 37023 } 37024 /** 37025 * Add a listener for a specified event type. 37026 * 37027 * @param {String} type the event name 37028 * @param {Function} listener the callback to be invoked when an event of 37029 * the specified type occurs 37030 */ 37031 37032 37033 Stream.prototype.on = function on(type, listener) { 37034 if (!this.listeners[type]) { 37035 this.listeners[type] = []; 37036 } 37037 37038 this.listeners[type].push(listener); 37039 }; 37040 /** 37041 * Remove a listener for a specified event type. 37042 * 37043 * @param {String} type the event name 37044 * @param {Function} listener a function previously registered for this 37045 * type of event through `on` 37046 * @return {Boolean} if we could turn it off or not 37047 */ 37048 37049 37050 Stream.prototype.off = function off(type, listener) { 37051 if (!this.listeners[type]) { 37052 return false; 37053 } 37054 37055 var index = this.listeners[type].indexOf(listener); 37056 this.listeners[type].splice(index, 1); 37057 return index > -1; 37058 }; 37059 /** 37060 * Trigger an event of the specified type on this stream. Any additional 37061 * arguments to this function are passed as parameters to event listeners. 37062 * 37063 * @param {String} type the event name 37064 */ 37065 37066 37067 Stream.prototype.trigger = function trigger(type) { 37068 var callbacks = this.listeners[type]; 37069 37070 if (!callbacks) { 37071 return; 37072 } // Slicing the arguments on every invocation of this method 37073 // can add a significant amount of overhead. Avoid the 37074 // intermediate object creation for the common case of a 37075 // single callback argument 37076 37077 37078 if (arguments.length === 2) { 37079 var length = callbacks.length; 37080 37081 for (var i = 0; i < length; ++i) { 37082 callbacks[i].call(this, arguments[1]); 37083 } 37084 } else { 37085 var args = Array.prototype.slice.call(arguments, 1); 37086 var _length = callbacks.length; 37087 37088 for (var _i = 0; _i < _length; ++_i) { 37089 callbacks[_i].apply(this, args); 37090 } 37091 } 37092 }; 37093 /** 37094 * Destroys the stream and cleans up. 37095 */ 37096 37097 37098 Stream.prototype.dispose = function dispose() { 37099 this.listeners = {}; 37100 }; 37101 /** 37102 * Forwards all `data` events on this stream to the destination stream. The 37103 * destination stream should provide a method `push` to receive the data 37104 * events as they arrive. 37105 * 37106 * @param {Stream} destination the stream that will receive all `data` events 37107 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 37108 */ 37109 37110 37111 Stream.prototype.pipe = function pipe(destination) { 37112 this.on('data', function (data) { 37113 destination.push(data); 37114 }); 37115 }; 37116 37117 return Stream; 37118 }(); 37119 /** 37120 * @file async-stream.js 37121 */ 37122 37123 /** 37124 * A wrapper around the Stream class to use setTiemout 37125 * and run stream "jobs" Asynchronously 37126 * 37127 * @class AsyncStream 37128 * @extends Stream 37129 */ 37130 37131 37132 var AsyncStream = function (_Stream) { 37133 inherits$1(AsyncStream, _Stream); 37134 37135 function AsyncStream() { 37136 classCallCheck(this, AsyncStream); 37137 37138 var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2)); 37139 37140 _this.jobs = []; 37141 _this.delay = 1; 37142 _this.timeout_ = null; 37143 return _this; 37144 } 37145 /** 37146 * process an async job 37147 * 37148 * @private 37149 */ 37150 37151 37152 AsyncStream.prototype.processJob_ = function processJob_() { 37153 this.jobs.shift()(); 37154 37155 if (this.jobs.length) { 37156 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 37157 } else { 37158 this.timeout_ = null; 37159 } 37160 }; 37161 /** 37162 * push a job into the stream 37163 * 37164 * @param {Function} job the job to push into the stream 37165 */ 37166 37167 37168 AsyncStream.prototype.push = function push(job) { 37169 this.jobs.push(job); 37170 37171 if (!this.timeout_) { 37172 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 37173 } 37174 }; 37175 37176 return AsyncStream; 37177 }(Stream$2); 37178 /** 37179 * @file decrypter.js 37180 * 37181 * An asynchronous implementation of AES-128 CBC decryption with 37182 * PKCS#7 padding. 37183 */ 37184 37185 /** 37186 * Convert network-order (big-endian) bytes into their little-endian 37187 * representation. 37188 */ 37189 37190 37191 var ntoh = function ntoh(word) { 37192 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 37193 }; 37194 /** 37195 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 37196 * 37197 * @param {Uint8Array} encrypted the encrypted bytes 37198 * @param {Uint32Array} key the bytes of the decryption key 37199 * @param {Uint32Array} initVector the initialization vector (IV) to 37200 * use for the first round of CBC. 37201 * @return {Uint8Array} the decrypted bytes 37202 * 37203 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 37204 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 37205 * @see https://tools.ietf.org/html/rfc2315 37206 */ 37207 37208 37209 var decrypt = function decrypt(encrypted, key, initVector) { 37210 // word-level access to the encrypted bytes 37211 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 37212 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 37213 37214 var decrypted = new Uint8Array(encrypted.byteLength); 37215 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 37216 // decrypted data 37217 37218 var init0 = void 0; 37219 var init1 = void 0; 37220 var init2 = void 0; 37221 var init3 = void 0; 37222 var encrypted0 = void 0; 37223 var encrypted1 = void 0; 37224 var encrypted2 = void 0; 37225 var encrypted3 = void 0; // iteration variable 37226 37227 var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the 37228 // passed-in reference and easier access 37229 37230 init0 = initVector[0]; 37231 init1 = initVector[1]; 37232 init2 = initVector[2]; 37233 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 37234 // to each decrypted block 37235 37236 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 37237 // convert big-endian (network order) words into little-endian 37238 // (javascript order) 37239 encrypted0 = ntoh(encrypted32[wordIx]); 37240 encrypted1 = ntoh(encrypted32[wordIx + 1]); 37241 encrypted2 = ntoh(encrypted32[wordIx + 2]); 37242 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 37243 37244 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 37245 // plaintext 37246 37247 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 37248 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 37249 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 37250 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 37251 37252 init0 = encrypted0; 37253 init1 = encrypted1; 37254 init2 = encrypted2; 37255 init3 = encrypted3; 37256 } 37257 37258 return decrypted; 37259 }; 37260 /** 37261 * The `Decrypter` class that manages decryption of AES 37262 * data through `AsyncStream` objects and the `decrypt` 37263 * function 37264 * 37265 * @param {Uint8Array} encrypted the encrypted bytes 37266 * @param {Uint32Array} key the bytes of the decryption key 37267 * @param {Uint32Array} initVector the initialization vector (IV) to 37268 * @param {Function} done the function to run when done 37269 * @class Decrypter 37270 */ 37271 37272 37273 var Decrypter = function () { 37274 function Decrypter(encrypted, key, initVector, done) { 37275 classCallCheck(this, Decrypter); 37276 var step = Decrypter.STEP; 37277 var encrypted32 = new Int32Array(encrypted.buffer); 37278 var decrypted = new Uint8Array(encrypted.byteLength); 37279 var i = 0; 37280 this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously 37281 37282 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 37283 37284 for (i = step; i < encrypted32.length; i += step) { 37285 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 37286 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 37287 } // invoke the done() callback when everything is finished 37288 37289 37290 this.asyncStream_.push(function () { 37291 // remove pkcs#7 padding from the decrypted bytes 37292 done(null, unpad(decrypted)); 37293 }); 37294 } 37295 /** 37296 * a getter for step the maximum number of bytes to process at one time 37297 * 37298 * @return {Number} the value of step 32000 37299 */ 37300 37301 /** 37302 * @private 37303 */ 37304 37305 37306 Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 37307 return function () { 37308 var bytes = decrypt(encrypted, key, initVector); 37309 decrypted.set(bytes, encrypted.byteOffset); 37310 }; 37311 }; 37312 37313 createClass(Decrypter, null, [{ 37314 key: 'STEP', 37315 get: function get$$1() { 37316 // 4 * 8000; 37317 return 32000; 37318 } 37319 }]); 37320 return Decrypter; 37321 }(); 37322 37323 /** 37324 * @videojs/http-streaming 37325 * @version 1.11.2 37326 * @copyright 2019 Brightcove, Inc 37327 * @license Apache-2.0 37328 */ 37329 /** 37330 * @file resolve-url.js - Handling how URLs are resolved and manipulated 37331 */ 37332 37333 var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) { 37334 // return early if we don't need to resolve 37335 if (/^[a-z]+:/i.test(relativeURL)) { 37336 return relativeURL; 37337 } // if the base URL is relative then combine with the current location 37338 37339 37340 if (!/\/\//i.test(baseURL)) { 37341 baseURL = urlToolkit.buildAbsoluteURL(window$1.location.href, baseURL); 37342 } 37343 37344 return urlToolkit.buildAbsoluteURL(baseURL, relativeURL); 37345 }; 37346 /** 37347 * Checks whether xhr request was redirected and returns correct url depending 37348 * on `handleManifestRedirects` option 37349 * 37350 * @api private 37351 * 37352 * @param {String} url - an url being requested 37353 * @param {XMLHttpRequest} req - xhr request result 37354 * 37355 * @return {String} 37356 */ 37357 37358 37359 var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) { 37360 // To understand how the responseURL below is set and generated: 37361 // - https://fetch.spec.whatwg.org/#concept-response-url 37362 // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling 37363 if (handleManifestRedirect && req.responseURL && url !== req.responseURL) { 37364 return req.responseURL; 37365 } 37366 37367 return url; 37368 }; 37369 37370 var classCallCheck$1 = function classCallCheck(instance, Constructor) { 37371 if (!(instance instanceof Constructor)) { 37372 throw new TypeError("Cannot call a class as a function"); 37373 } 37374 }; 37375 37376 var createClass$1 = function () { 37377 function defineProperties(target, props) { 37378 for (var i = 0; i < props.length; i++) { 37379 var descriptor = props[i]; 37380 descriptor.enumerable = descriptor.enumerable || false; 37381 descriptor.configurable = true; 37382 if ("value" in descriptor) descriptor.writable = true; 37383 Object.defineProperty(target, descriptor.key, descriptor); 37384 } 37385 } 37386 37387 return function (Constructor, protoProps, staticProps) { 37388 if (protoProps) defineProperties(Constructor.prototype, protoProps); 37389 if (staticProps) defineProperties(Constructor, staticProps); 37390 return Constructor; 37391 }; 37392 }(); 37393 37394 var get$1 = function get(object, property, receiver) { 37395 if (object === null) object = Function.prototype; 37396 var desc = Object.getOwnPropertyDescriptor(object, property); 37397 37398 if (desc === undefined) { 37399 var parent = Object.getPrototypeOf(object); 37400 37401 if (parent === null) { 37402 return undefined; 37403 } else { 37404 return get(parent, property, receiver); 37405 } 37406 } else if ("value" in desc) { 37407 return desc.value; 37408 } else { 37409 var getter = desc.get; 37410 37411 if (getter === undefined) { 37412 return undefined; 37413 } 37414 37415 return getter.call(receiver); 37416 } 37417 }; 37418 37419 var inherits$2 = function inherits(subClass, superClass) { 37420 if (typeof superClass !== "function" && superClass !== null) { 37421 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 37422 } 37423 37424 subClass.prototype = Object.create(superClass && superClass.prototype, { 37425 constructor: { 37426 value: subClass, 37427 enumerable: false, 37428 writable: true, 37429 configurable: true 37430 } 37431 }); 37432 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 37433 }; 37434 37435 var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) { 37436 if (!self) { 37437 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 37438 } 37439 37440 return call && (typeof call === "object" || typeof call === "function") ? call : self; 37441 }; 37442 37443 var slicedToArray = function () { 37444 function sliceIterator(arr, i) { 37445 var _arr = []; 37446 var _n = true; 37447 var _d = false; 37448 var _e = undefined; 37449 37450 try { 37451 for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 37452 _arr.push(_s.value); 37453 37454 if (i && _arr.length === i) break; 37455 } 37456 } catch (err) { 37457 _d = true; 37458 _e = err; 37459 } finally { 37460 try { 37461 if (!_n && _i["return"]) _i["return"](); 37462 } finally { 37463 if (_d) throw _e; 37464 } 37465 } 37466 37467 return _arr; 37468 } 37469 37470 return function (arr, i) { 37471 if (Array.isArray(arr)) { 37472 return arr; 37473 } else if (Symbol.iterator in Object(arr)) { 37474 return sliceIterator(arr, i); 37475 } else { 37476 throw new TypeError("Invalid attempt to destructure non-iterable instance"); 37477 } 37478 }; 37479 }(); 37480 /** 37481 * @file playlist-loader.js 37482 * 37483 * A state machine that manages the loading, caching, and updating of 37484 * M3U8 playlists. 37485 * 37486 */ 37487 37488 37489 var mergeOptions$1 = videojs$1.mergeOptions, 37490 EventTarget$1 = videojs$1.EventTarget, 37491 log$1 = videojs$1.log; 37492 /** 37493 * Loops through all supported media groups in master and calls the provided 37494 * callback for each group 37495 * 37496 * @param {Object} master 37497 * The parsed master manifest object 37498 * @param {Function} callback 37499 * Callback to call for each media group 37500 */ 37501 37502 var forEachMediaGroup = function forEachMediaGroup(master, callback) { 37503 ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) { 37504 for (var groupKey in master.mediaGroups[mediaType]) { 37505 for (var labelKey in master.mediaGroups[mediaType][groupKey]) { 37506 var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey]; 37507 callback(mediaProperties, mediaType, groupKey, labelKey); 37508 } 37509 } 37510 }); 37511 }; 37512 /** 37513 * Returns a new array of segments that is the result of merging 37514 * properties from an older list of segments onto an updated 37515 * list. No properties on the updated playlist will be overridden. 37516 * 37517 * @param {Array} original the outdated list of segments 37518 * @param {Array} update the updated list of segments 37519 * @param {Number=} offset the index of the first update 37520 * segment in the original segment list. For non-live playlists, 37521 * this should always be zero and does not need to be 37522 * specified. For live playlists, it should be the difference 37523 * between the media sequence numbers in the original and updated 37524 * playlists. 37525 * @return a list of merged segment objects 37526 */ 37527 37528 37529 var updateSegments = function updateSegments(original, update, offset) { 37530 var result = update.slice(); 37531 offset = offset || 0; 37532 var length = Math.min(original.length, update.length + offset); 37533 37534 for (var i = offset; i < length; i++) { 37535 result[i - offset] = mergeOptions$1(original[i], result[i - offset]); 37536 } 37537 37538 return result; 37539 }; 37540 37541 var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) { 37542 if (!segment.resolvedUri) { 37543 segment.resolvedUri = resolveUrl$1(baseUri, segment.uri); 37544 } 37545 37546 if (segment.key && !segment.key.resolvedUri) { 37547 segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri); 37548 } 37549 37550 if (segment.map && !segment.map.resolvedUri) { 37551 segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri); 37552 } 37553 }; 37554 /** 37555 * Returns a new master playlist that is the result of merging an 37556 * updated media playlist into the original version. If the 37557 * updated media playlist does not match any of the playlist 37558 * entries in the original master playlist, null is returned. 37559 * 37560 * @param {Object} master a parsed master M3U8 object 37561 * @param {Object} media a parsed media M3U8 object 37562 * @return {Object} a new object that represents the original 37563 * master playlist with the updated media playlist merged in, or 37564 * null if the merge produced no change. 37565 */ 37566 37567 37568 var updateMaster = function updateMaster(master, media) { 37569 var result = mergeOptions$1(master, {}); 37570 var playlist = result.playlists[media.uri]; 37571 37572 if (!playlist) { 37573 return null; 37574 } // consider the playlist unchanged if the number of segments is equal, the media 37575 // sequence number is unchanged, and this playlist hasn't become the end of the playlist 37576 37577 37578 if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) { 37579 return null; 37580 } 37581 37582 var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists 37583 37584 if (playlist.segments) { 37585 mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); 37586 } // resolve any segment URIs to prevent us from having to do it later 37587 37588 37589 mergedPlaylist.segments.forEach(function (segment) { 37590 resolveSegmentUris(segment, mergedPlaylist.resolvedUri); 37591 }); // TODO Right now in the playlists array there are two references to each playlist, one 37592 // that is referenced by index, and one by URI. The index reference may no longer be 37593 // necessary. 37594 37595 for (var i = 0; i < result.playlists.length; i++) { 37596 if (result.playlists[i].uri === media.uri) { 37597 result.playlists[i] = mergedPlaylist; 37598 } 37599 } 37600 37601 result.playlists[media.uri] = mergedPlaylist; 37602 return result; 37603 }; 37604 37605 var setupMediaPlaylists = function setupMediaPlaylists(master) { 37606 // setup by-URI lookups and resolve media playlist URIs 37607 var i = master.playlists.length; 37608 37609 while (i--) { 37610 var playlist = master.playlists[i]; 37611 master.playlists[playlist.uri] = playlist; 37612 playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri); 37613 playlist.id = i; 37614 37615 if (!playlist.attributes) { 37616 // Although the spec states an #EXT-X-STREAM-INF tag MUST have a 37617 // BANDWIDTH attribute, we can play the stream without it. This means a poorly 37618 // formatted master playlist may not have an attribute list. An attributes 37619 // property is added here to prevent undefined references when we encounter 37620 // this scenario. 37621 playlist.attributes = {}; 37622 log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.'); 37623 } 37624 } 37625 }; 37626 37627 var resolveMediaGroupUris = function resolveMediaGroupUris(master) { 37628 forEachMediaGroup(master, function (properties) { 37629 if (properties.uri) { 37630 properties.resolvedUri = resolveUrl$1(master.uri, properties.uri); 37631 } 37632 }); 37633 }; 37634 /** 37635 * Calculates the time to wait before refreshing a live playlist 37636 * 37637 * @param {Object} media 37638 * The current media 37639 * @param {Boolean} update 37640 * True if there were any updates from the last refresh, false otherwise 37641 * @return {Number} 37642 * The time in ms to wait before refreshing the live playlist 37643 */ 37644 37645 37646 var refreshDelay = function refreshDelay(media, update) { 37647 var lastSegment = media.segments[media.segments.length - 1]; 37648 var delay = void 0; 37649 37650 if (update && lastSegment && lastSegment.duration) { 37651 delay = lastSegment.duration * 1000; 37652 } else { 37653 // if the playlist is unchanged since the last reload or last segment duration 37654 // cannot be determined, try again after half the target duration 37655 delay = (media.targetDuration || 10) * 500; 37656 } 37657 37658 return delay; 37659 }; 37660 /** 37661 * Load a playlist from a remote location 37662 * 37663 * @class PlaylistLoader 37664 * @extends Stream 37665 * @param {String} srcUrl the url to start with 37666 * @param {Boolean} withCredentials the withCredentials xhr option 37667 * @constructor 37668 */ 37669 37670 37671 var PlaylistLoader = function (_EventTarget) { 37672 inherits$2(PlaylistLoader, _EventTarget); 37673 37674 function PlaylistLoader(srcUrl, hls) { 37675 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 37676 classCallCheck$1(this, PlaylistLoader); 37677 37678 var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this)); 37679 37680 var _options$withCredenti = options.withCredentials, 37681 withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti, 37682 _options$handleManife = options.handleManifestRedirects, 37683 handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife; 37684 _this.srcUrl = srcUrl; 37685 _this.hls_ = hls; 37686 _this.withCredentials = withCredentials; 37687 _this.handleManifestRedirects = handleManifestRedirects; 37688 var hlsOptions = hls.options_; 37689 _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || []; 37690 _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || []; 37691 37692 if (!_this.srcUrl) { 37693 throw new Error('A non-empty playlist URL is required'); 37694 } // initialize the loader state 37695 37696 37697 _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout 37698 37699 _this.on('mediaupdatetimeout', function () { 37700 if (_this.state !== 'HAVE_METADATA') { 37701 // only refresh the media playlist if no other activity is going on 37702 return; 37703 } 37704 37705 _this.state = 'HAVE_CURRENT_METADATA'; 37706 _this.request = _this.hls_.xhr({ 37707 uri: resolveUrl$1(_this.master.uri, _this.media().uri), 37708 withCredentials: _this.withCredentials 37709 }, function (error, req) { 37710 // disposed 37711 if (!_this.request) { 37712 return; 37713 } 37714 37715 if (error) { 37716 return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA'); 37717 } 37718 37719 _this.haveMetadata(_this.request, _this.media().uri); 37720 }); 37721 }); 37722 37723 return _this; 37724 } 37725 37726 createClass$1(PlaylistLoader, [{ 37727 key: 'playlistRequestError', 37728 value: function playlistRequestError(xhr, url, startingState) { 37729 // any in-flight request is now finished 37730 this.request = null; 37731 37732 if (startingState) { 37733 this.state = startingState; 37734 } 37735 37736 this.error = { 37737 playlist: this.master.playlists[url], 37738 status: xhr.status, 37739 message: 'HLS playlist request error at URL: ' + url + '.', 37740 responseText: xhr.responseText, 37741 code: xhr.status >= 500 ? 4 : 2 37742 }; 37743 this.trigger('error'); 37744 } // update the playlist loader's state in response to a new or 37745 // updated playlist. 37746 37747 }, { 37748 key: 'haveMetadata', 37749 value: function haveMetadata(xhr, url) { 37750 var _this2 = this; // any in-flight request is now finished 37751 37752 37753 this.request = null; 37754 this.state = 'HAVE_METADATA'; 37755 var parser = new Parser(); // adding custom tag parsers 37756 37757 this.customTagParsers.forEach(function (customParser) { 37758 return parser.addParser(customParser); 37759 }); // adding custom tag mappers 37760 37761 this.customTagMappers.forEach(function (mapper) { 37762 return parser.addTagMapper(mapper); 37763 }); 37764 parser.push(xhr.responseText); 37765 parser.end(); 37766 parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make 37767 // sure that the property is attached to avoid undefined reference errors 37768 37769 parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master 37770 37771 var update = updateMaster(this.master, parser.manifest); 37772 this.targetDuration = parser.manifest.targetDuration; 37773 37774 if (update) { 37775 this.master = update; 37776 this.media_ = this.master.playlists[parser.manifest.uri]; 37777 } else { 37778 this.trigger('playlistunchanged'); 37779 } // refresh live playlists after a target duration passes 37780 37781 37782 if (!this.media().endList) { 37783 window$1.clearTimeout(this.mediaUpdateTimeout); 37784 this.mediaUpdateTimeout = window$1.setTimeout(function () { 37785 _this2.trigger('mediaupdatetimeout'); 37786 }, refreshDelay(this.media(), !!update)); 37787 } 37788 37789 this.trigger('loadedplaylist'); 37790 } 37791 /** 37792 * Abort any outstanding work and clean up. 37793 */ 37794 37795 }, { 37796 key: 'dispose', 37797 value: function dispose() { 37798 this.stopRequest(); 37799 window$1.clearTimeout(this.mediaUpdateTimeout); 37800 window$1.clearTimeout(this.finalRenditionTimeout); 37801 } 37802 }, { 37803 key: 'stopRequest', 37804 value: function stopRequest() { 37805 if (this.request) { 37806 var oldRequest = this.request; 37807 this.request = null; 37808 oldRequest.onreadystatechange = null; 37809 oldRequest.abort(); 37810 } 37811 } 37812 /** 37813 * When called without any arguments, returns the currently 37814 * active media playlist. When called with a single argument, 37815 * triggers the playlist loader to asynchronously switch to the 37816 * specified media playlist. Calling this method while the 37817 * loader is in the HAVE_NOTHING causes an error to be emitted 37818 * but otherwise has no effect. 37819 * 37820 * @param {Object=} playlist the parsed media playlist 37821 * object to switch to 37822 * @param {Boolean=} is this the last available playlist 37823 * 37824 * @return {Playlist} the current loaded media 37825 */ 37826 37827 }, { 37828 key: 'media', 37829 value: function media(playlist, isFinalRendition) { 37830 var _this3 = this; // getter 37831 37832 37833 if (!playlist) { 37834 return this.media_; 37835 } // setter 37836 37837 37838 if (this.state === 'HAVE_NOTHING') { 37839 throw new Error('Cannot switch media playlist from ' + this.state); 37840 } // find the playlist object if the target playlist has been 37841 // specified by URI 37842 37843 37844 if (typeof playlist === 'string') { 37845 if (!this.master.playlists[playlist]) { 37846 throw new Error('Unknown playlist URI: ' + playlist); 37847 } 37848 37849 playlist = this.master.playlists[playlist]; 37850 } 37851 37852 window$1.clearTimeout(this.finalRenditionTimeout); 37853 37854 if (isFinalRendition) { 37855 var delay = playlist.targetDuration / 2 * 1000 || 5 * 1000; 37856 this.finalRenditionTimeout = window$1.setTimeout(this.media.bind(this, playlist, false), delay); 37857 return; 37858 } 37859 37860 var startingState = this.state; 37861 var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately 37862 37863 if (this.master.playlists[playlist.uri].endList) { 37864 // abort outstanding playlist requests 37865 if (this.request) { 37866 this.request.onreadystatechange = null; 37867 this.request.abort(); 37868 this.request = null; 37869 } 37870 37871 this.state = 'HAVE_METADATA'; 37872 this.media_ = playlist; // trigger media change if the active media has been updated 37873 37874 if (mediaChange) { 37875 this.trigger('mediachanging'); 37876 this.trigger('mediachange'); 37877 } 37878 37879 return; 37880 } // switching to the active playlist is a no-op 37881 37882 37883 if (!mediaChange) { 37884 return; 37885 } 37886 37887 this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request 37888 37889 if (this.request) { 37890 if (playlist.resolvedUri === this.request.url) { 37891 // requesting to switch to the same playlist multiple times 37892 // has no effect after the first 37893 return; 37894 } 37895 37896 this.request.onreadystatechange = null; 37897 this.request.abort(); 37898 this.request = null; 37899 } // request the new playlist 37900 37901 37902 if (this.media_) { 37903 this.trigger('mediachanging'); 37904 } 37905 37906 this.request = this.hls_.xhr({ 37907 uri: playlist.resolvedUri, 37908 withCredentials: this.withCredentials 37909 }, function (error, req) { 37910 // disposed 37911 if (!_this3.request) { 37912 return; 37913 } 37914 37915 playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req); 37916 37917 if (error) { 37918 return _this3.playlistRequestError(_this3.request, playlist.uri, startingState); 37919 } 37920 37921 _this3.haveMetadata(req, playlist.uri); // fire loadedmetadata the first time a media playlist is loaded 37922 37923 37924 if (startingState === 'HAVE_MASTER') { 37925 _this3.trigger('loadedmetadata'); 37926 } else { 37927 _this3.trigger('mediachange'); 37928 } 37929 }); 37930 } 37931 /** 37932 * pause loading of the playlist 37933 */ 37934 37935 }, { 37936 key: 'pause', 37937 value: function pause() { 37938 this.stopRequest(); 37939 window$1.clearTimeout(this.mediaUpdateTimeout); 37940 37941 if (this.state === 'HAVE_NOTHING') { 37942 // If we pause the loader before any data has been retrieved, its as if we never 37943 // started, so reset to an unstarted state. 37944 this.started = false; 37945 } // Need to restore state now that no activity is happening 37946 37947 37948 if (this.state === 'SWITCHING_MEDIA') { 37949 // if the loader was in the process of switching media, it should either return to 37950 // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media 37951 // playlist yet. This is determined by the existence of loader.media_ 37952 if (this.media_) { 37953 this.state = 'HAVE_METADATA'; 37954 } else { 37955 this.state = 'HAVE_MASTER'; 37956 } 37957 } else if (this.state === 'HAVE_CURRENT_METADATA') { 37958 this.state = 'HAVE_METADATA'; 37959 } 37960 } 37961 /** 37962 * start loading of the playlist 37963 */ 37964 37965 }, { 37966 key: 'load', 37967 value: function load(isFinalRendition) { 37968 var _this4 = this; 37969 37970 window$1.clearTimeout(this.mediaUpdateTimeout); 37971 var media = this.media(); 37972 37973 if (isFinalRendition) { 37974 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 37975 this.mediaUpdateTimeout = window$1.setTimeout(function () { 37976 return _this4.load(); 37977 }, delay); 37978 return; 37979 } 37980 37981 if (!this.started) { 37982 this.start(); 37983 return; 37984 } 37985 37986 if (media && !media.endList) { 37987 this.trigger('mediaupdatetimeout'); 37988 } else { 37989 this.trigger('loadedplaylist'); 37990 } 37991 } 37992 /** 37993 * start loading of the playlist 37994 */ 37995 37996 }, { 37997 key: 'start', 37998 value: function start() { 37999 var _this5 = this; 38000 38001 this.started = true; // request the specified URL 38002 38003 this.request = this.hls_.xhr({ 38004 uri: this.srcUrl, 38005 withCredentials: this.withCredentials 38006 }, function (error, req) { 38007 // disposed 38008 if (!_this5.request) { 38009 return; 38010 } // clear the loader's request reference 38011 38012 38013 _this5.request = null; 38014 38015 if (error) { 38016 _this5.error = { 38017 status: req.status, 38018 message: 'HLS playlist request error at URL: ' + _this5.srcUrl + '.', 38019 responseText: req.responseText, 38020 // MEDIA_ERR_NETWORK 38021 code: 2 38022 }; 38023 38024 if (_this5.state === 'HAVE_NOTHING') { 38025 _this5.started = false; 38026 } 38027 38028 return _this5.trigger('error'); 38029 } 38030 38031 var parser = new Parser(); // adding custom tag parsers 38032 38033 _this5.customTagParsers.forEach(function (customParser) { 38034 return parser.addParser(customParser); 38035 }); // adding custom tag mappers 38036 38037 38038 _this5.customTagMappers.forEach(function (mapper) { 38039 return parser.addTagMapper(mapper); 38040 }); 38041 38042 parser.push(req.responseText); 38043 parser.end(); 38044 _this5.state = 'HAVE_MASTER'; 38045 _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req); 38046 parser.manifest.uri = _this5.srcUrl; // loaded a master playlist 38047 38048 if (parser.manifest.playlists) { 38049 _this5.master = parser.manifest; 38050 setupMediaPlaylists(_this5.master); 38051 resolveMediaGroupUris(_this5.master); 38052 38053 _this5.trigger('loadedplaylist'); 38054 38055 if (!_this5.request) { 38056 // no media playlist was specifically selected so start 38057 // from the first listed one 38058 _this5.media(parser.manifest.playlists[0]); 38059 } 38060 38061 return; 38062 } // loaded a media playlist 38063 // infer a master playlist if none was previously requested 38064 38065 38066 _this5.master = { 38067 mediaGroups: { 38068 'AUDIO': {}, 38069 'VIDEO': {}, 38070 'CLOSED-CAPTIONS': {}, 38071 'SUBTITLES': {} 38072 }, 38073 uri: window$1.location.href, 38074 playlists: [{ 38075 uri: _this5.srcUrl, 38076 id: 0, 38077 resolvedUri: _this5.srcUrl, 38078 // m3u8-parser does not attach an attributes property to media playlists so make 38079 // sure that the property is attached to avoid undefined reference errors 38080 attributes: {} 38081 }] 38082 }; 38083 _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0]; 38084 38085 _this5.haveMetadata(req, _this5.srcUrl); 38086 38087 return _this5.trigger('loadedmetadata'); 38088 }); 38089 } 38090 }]); 38091 return PlaylistLoader; 38092 }(EventTarget$1); 38093 /** 38094 * @file playlist.js 38095 * 38096 * Playlist related utilities. 38097 */ 38098 38099 38100 var createTimeRange = videojs$1.createTimeRange; 38101 /** 38102 * walk backward until we find a duration we can use 38103 * or return a failure 38104 * 38105 * @param {Playlist} playlist the playlist to walk through 38106 * @param {Number} endSequence the mediaSequence to stop walking on 38107 */ 38108 38109 var backwardDuration = function backwardDuration(playlist, endSequence) { 38110 var result = 0; 38111 var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following 38112 // the interval, use it 38113 38114 var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline 38115 // information that is earlier than endSequence 38116 38117 if (segment) { 38118 if (typeof segment.start !== 'undefined') { 38119 return { 38120 result: segment.start, 38121 precise: true 38122 }; 38123 } 38124 38125 if (typeof segment.end !== 'undefined') { 38126 return { 38127 result: segment.end - segment.duration, 38128 precise: true 38129 }; 38130 } 38131 } 38132 38133 while (i--) { 38134 segment = playlist.segments[i]; 38135 38136 if (typeof segment.end !== 'undefined') { 38137 return { 38138 result: result + segment.end, 38139 precise: true 38140 }; 38141 } 38142 38143 result += segment.duration; 38144 38145 if (typeof segment.start !== 'undefined') { 38146 return { 38147 result: result + segment.start, 38148 precise: true 38149 }; 38150 } 38151 } 38152 38153 return { 38154 result: result, 38155 precise: false 38156 }; 38157 }; 38158 /** 38159 * walk forward until we find a duration we can use 38160 * or return a failure 38161 * 38162 * @param {Playlist} playlist the playlist to walk through 38163 * @param {Number} endSequence the mediaSequence to stop walking on 38164 */ 38165 38166 38167 var forwardDuration = function forwardDuration(playlist, endSequence) { 38168 var result = 0; 38169 var segment = void 0; 38170 var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline 38171 // information 38172 38173 for (; i < playlist.segments.length; i++) { 38174 segment = playlist.segments[i]; 38175 38176 if (typeof segment.start !== 'undefined') { 38177 return { 38178 result: segment.start - result, 38179 precise: true 38180 }; 38181 } 38182 38183 result += segment.duration; 38184 38185 if (typeof segment.end !== 'undefined') { 38186 return { 38187 result: segment.end - result, 38188 precise: true 38189 }; 38190 } 38191 } // indicate we didn't find a useful duration estimate 38192 38193 38194 return { 38195 result: -1, 38196 precise: false 38197 }; 38198 }; 38199 /** 38200 * Calculate the media duration from the segments associated with a 38201 * playlist. The duration of a subinterval of the available segments 38202 * may be calculated by specifying an end index. 38203 * 38204 * @param {Object} playlist a media playlist object 38205 * @param {Number=} endSequence an exclusive upper boundary 38206 * for the playlist. Defaults to playlist length. 38207 * @param {Number} expired the amount of time that has dropped 38208 * off the front of the playlist in a live scenario 38209 * @return {Number} the duration between the first available segment 38210 * and end index. 38211 */ 38212 38213 38214 var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 38215 var backward = void 0; 38216 var forward = void 0; 38217 38218 if (typeof endSequence === 'undefined') { 38219 endSequence = playlist.mediaSequence + playlist.segments.length; 38220 } 38221 38222 if (endSequence < playlist.mediaSequence) { 38223 return 0; 38224 } // do a backward walk to estimate the duration 38225 38226 38227 backward = backwardDuration(playlist, endSequence); 38228 38229 if (backward.precise) { 38230 // if we were able to base our duration estimate on timing 38231 // information provided directly from the Media Source, return 38232 // it 38233 return backward.result; 38234 } // walk forward to see if a precise duration estimate can be made 38235 // that way 38236 38237 38238 forward = forwardDuration(playlist, endSequence); 38239 38240 if (forward.precise) { 38241 // we found a segment that has been buffered and so it's 38242 // position is known precisely 38243 return forward.result; 38244 } // return the less-precise, playlist-based duration estimate 38245 38246 38247 return backward.result + expired; 38248 }; 38249 /** 38250 * Calculates the duration of a playlist. If a start and end index 38251 * are specified, the duration will be for the subset of the media 38252 * timeline between those two indices. The total duration for live 38253 * playlists is always Infinity. 38254 * 38255 * @param {Object} playlist a media playlist object 38256 * @param {Number=} endSequence an exclusive upper 38257 * boundary for the playlist. Defaults to the playlist media 38258 * sequence number plus its length. 38259 * @param {Number=} expired the amount of time that has 38260 * dropped off the front of the playlist in a live scenario 38261 * @return {Number} the duration between the start index and end 38262 * index. 38263 */ 38264 38265 38266 var duration = function duration(playlist, endSequence, expired) { 38267 if (!playlist) { 38268 return 0; 38269 } 38270 38271 if (typeof expired !== 'number') { 38272 expired = 0; 38273 } // if a slice of the total duration is not requested, use 38274 // playlist-level duration indicators when they're present 38275 38276 38277 if (typeof endSequence === 'undefined') { 38278 // if present, use the duration specified in the playlist 38279 if (playlist.totalDuration) { 38280 return playlist.totalDuration; 38281 } // duration should be Infinity for live playlists 38282 38283 38284 if (!playlist.endList) { 38285 return window$1.Infinity; 38286 } 38287 } // calculate the total duration based on the segment durations 38288 38289 38290 return intervalDuration(playlist, endSequence, expired); 38291 }; 38292 /** 38293 * Calculate the time between two indexes in the current playlist 38294 * neight the start- nor the end-index need to be within the current 38295 * playlist in which case, the targetDuration of the playlist is used 38296 * to approximate the durations of the segments 38297 * 38298 * @param {Object} playlist a media playlist object 38299 * @param {Number} startIndex 38300 * @param {Number} endIndex 38301 * @return {Number} the number of seconds between startIndex and endIndex 38302 */ 38303 38304 38305 var sumDurations = function sumDurations(playlist, startIndex, endIndex) { 38306 var durations = 0; 38307 38308 if (startIndex > endIndex) { 38309 var _ref = [endIndex, startIndex]; 38310 startIndex = _ref[0]; 38311 endIndex = _ref[1]; 38312 } 38313 38314 if (startIndex < 0) { 38315 for (var i = startIndex; i < Math.min(0, endIndex); i++) { 38316 durations += playlist.targetDuration; 38317 } 38318 38319 startIndex = 0; 38320 } 38321 38322 for (var _i = startIndex; _i < endIndex; _i++) { 38323 durations += playlist.segments[_i].duration; 38324 } 38325 38326 return durations; 38327 }; 38328 /** 38329 * Determines the media index of the segment corresponding to the safe edge of the live 38330 * window which is the duration of the last segment plus 2 target durations from the end 38331 * of the playlist. 38332 * 38333 * @param {Object} playlist 38334 * a media playlist object 38335 * @return {Number} 38336 * The media index of the segment at the safe live point. 0 if there is no "safe" 38337 * point. 38338 * @function safeLiveIndex 38339 */ 38340 38341 38342 var safeLiveIndex = function safeLiveIndex(playlist) { 38343 if (!playlist.segments.length) { 38344 return 0; 38345 } 38346 38347 var i = playlist.segments.length - 1; 38348 var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration; 38349 var safeDistance = distanceFromEnd + playlist.targetDuration * 2; 38350 38351 while (i--) { 38352 distanceFromEnd += playlist.segments[i].duration; 38353 38354 if (distanceFromEnd >= safeDistance) { 38355 break; 38356 } 38357 } 38358 38359 return Math.max(0, i); 38360 }; 38361 /** 38362 * Calculates the playlist end time 38363 * 38364 * @param {Object} playlist a media playlist object 38365 * @param {Number=} expired the amount of time that has 38366 * dropped off the front of the playlist in a live scenario 38367 * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the 38368 * playlist end calculation should consider the safe live end 38369 * (truncate the playlist end by three segments). This is normally 38370 * used for calculating the end of the playlist's seekable range. 38371 * @returns {Number} the end time of playlist 38372 * @function playlistEnd 38373 */ 38374 38375 38376 var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) { 38377 if (!playlist || !playlist.segments) { 38378 return null; 38379 } 38380 38381 if (playlist.endList) { 38382 return duration(playlist); 38383 } 38384 38385 if (expired === null) { 38386 return null; 38387 } 38388 38389 expired = expired || 0; 38390 var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length; 38391 return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); 38392 }; 38393 /** 38394 * Calculates the interval of time that is currently seekable in a 38395 * playlist. The returned time ranges are relative to the earliest 38396 * moment in the specified playlist that is still available. A full 38397 * seekable implementation for live streams would need to offset 38398 * these values by the duration of content that has expired from the 38399 * stream. 38400 * 38401 * @param {Object} playlist a media playlist object 38402 * dropped off the front of the playlist in a live scenario 38403 * @param {Number=} expired the amount of time that has 38404 * dropped off the front of the playlist in a live scenario 38405 * @return {TimeRanges} the periods of time that are valid targets 38406 * for seeking 38407 */ 38408 38409 38410 var seekable = function seekable(playlist, expired) { 38411 var useSafeLiveEnd = true; 38412 var seekableStart = expired || 0; 38413 var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd); 38414 38415 if (seekableEnd === null) { 38416 return createTimeRange(); 38417 } 38418 38419 return createTimeRange(seekableStart, seekableEnd); 38420 }; 38421 38422 var isWholeNumber = function isWholeNumber(num) { 38423 return num - Math.floor(num) === 0; 38424 }; 38425 38426 var roundSignificantDigit = function roundSignificantDigit(increment, num) { 38427 // If we have a whole number, just add 1 to it 38428 if (isWholeNumber(num)) { 38429 return num + increment * 0.1; 38430 } 38431 38432 var numDecimalDigits = num.toString().split('.')[1].length; 38433 38434 for (var i = 1; i <= numDecimalDigits; i++) { 38435 var scale = Math.pow(10, i); 38436 var temp = num * scale; 38437 38438 if (isWholeNumber(temp) || i === numDecimalDigits) { 38439 return (temp + increment) / scale; 38440 } 38441 } 38442 }; 38443 38444 var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1); 38445 var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1); 38446 /** 38447 * Determine the index and estimated starting time of the segment that 38448 * contains a specified playback position in a media playlist. 38449 * 38450 * @param {Object} playlist the media playlist to query 38451 * @param {Number} currentTime The number of seconds since the earliest 38452 * possible position to determine the containing segment for 38453 * @param {Number} startIndex 38454 * @param {Number} startTime 38455 * @return {Object} 38456 */ 38457 38458 var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) { 38459 var i = void 0; 38460 var segment = void 0; 38461 var numSegments = playlist.segments.length; 38462 var time = currentTime - startTime; 38463 38464 if (time < 0) { 38465 // Walk backward from startIndex in the playlist, adding durations 38466 // until we find a segment that contains `time` and return it 38467 if (startIndex > 0) { 38468 for (i = startIndex - 1; i >= 0; i--) { 38469 segment = playlist.segments[i]; 38470 time += floorLeastSignificantDigit(segment.duration); 38471 38472 if (time > 0) { 38473 return { 38474 mediaIndex: i, 38475 startTime: startTime - sumDurations(playlist, startIndex, i) 38476 }; 38477 } 38478 } 38479 } // We were unable to find a good segment within the playlist 38480 // so select the first segment 38481 38482 38483 return { 38484 mediaIndex: 0, 38485 startTime: currentTime 38486 }; 38487 } // When startIndex is negative, we first walk forward to first segment 38488 // adding target durations. If we "run out of time" before getting to 38489 // the first segment, return the first segment 38490 38491 38492 if (startIndex < 0) { 38493 for (i = startIndex; i < 0; i++) { 38494 time -= playlist.targetDuration; 38495 38496 if (time < 0) { 38497 return { 38498 mediaIndex: 0, 38499 startTime: currentTime 38500 }; 38501 } 38502 } 38503 38504 startIndex = 0; 38505 } // Walk forward from startIndex in the playlist, subtracting durations 38506 // until we find a segment that contains `time` and return it 38507 38508 38509 for (i = startIndex; i < numSegments; i++) { 38510 segment = playlist.segments[i]; 38511 time -= ceilLeastSignificantDigit(segment.duration); 38512 38513 if (time < 0) { 38514 return { 38515 mediaIndex: i, 38516 startTime: startTime + sumDurations(playlist, startIndex, i) 38517 }; 38518 } 38519 } // We are out of possible candidates so load the last one... 38520 38521 38522 return { 38523 mediaIndex: numSegments - 1, 38524 startTime: currentTime 38525 }; 38526 }; 38527 /** 38528 * Check whether the playlist is blacklisted or not. 38529 * 38530 * @param {Object} playlist the media playlist object 38531 * @return {boolean} whether the playlist is blacklisted or not 38532 * @function isBlacklisted 38533 */ 38534 38535 38536 var isBlacklisted = function isBlacklisted(playlist) { 38537 return playlist.excludeUntil && playlist.excludeUntil > Date.now(); 38538 }; 38539 /** 38540 * Check whether the playlist is compatible with current playback configuration or has 38541 * been blacklisted permanently for being incompatible. 38542 * 38543 * @param {Object} playlist the media playlist object 38544 * @return {boolean} whether the playlist is incompatible or not 38545 * @function isIncompatible 38546 */ 38547 38548 38549 var isIncompatible = function isIncompatible(playlist) { 38550 return playlist.excludeUntil && playlist.excludeUntil === Infinity; 38551 }; 38552 /** 38553 * Check whether the playlist is enabled or not. 38554 * 38555 * @param {Object} playlist the media playlist object 38556 * @return {boolean} whether the playlist is enabled or not 38557 * @function isEnabled 38558 */ 38559 38560 38561 var isEnabled = function isEnabled(playlist) { 38562 var blacklisted = isBlacklisted(playlist); 38563 return !playlist.disabled && !blacklisted; 38564 }; 38565 /** 38566 * Check whether the playlist has been manually disabled through the representations api. 38567 * 38568 * @param {Object} playlist the media playlist object 38569 * @return {boolean} whether the playlist is disabled manually or not 38570 * @function isDisabled 38571 */ 38572 38573 38574 var isDisabled = function isDisabled(playlist) { 38575 return playlist.disabled; 38576 }; 38577 /** 38578 * Returns whether the current playlist is an AES encrypted HLS stream 38579 * 38580 * @return {Boolean} true if it's an AES encrypted HLS stream 38581 */ 38582 38583 38584 var isAes = function isAes(media) { 38585 for (var i = 0; i < media.segments.length; i++) { 38586 if (media.segments[i].key) { 38587 return true; 38588 } 38589 } 38590 38591 return false; 38592 }; 38593 /** 38594 * Returns whether the current playlist contains fMP4 38595 * 38596 * @return {Boolean} true if the playlist contains fMP4 38597 */ 38598 38599 38600 var isFmp4 = function isFmp4(media) { 38601 for (var i = 0; i < media.segments.length; i++) { 38602 if (media.segments[i].map) { 38603 return true; 38604 } 38605 } 38606 38607 return false; 38608 }; 38609 /** 38610 * Checks if the playlist has a value for the specified attribute 38611 * 38612 * @param {String} attr 38613 * Attribute to check for 38614 * @param {Object} playlist 38615 * The media playlist object 38616 * @return {Boolean} 38617 * Whether the playlist contains a value for the attribute or not 38618 * @function hasAttribute 38619 */ 38620 38621 38622 var hasAttribute = function hasAttribute(attr, playlist) { 38623 return playlist.attributes && playlist.attributes[attr]; 38624 }; 38625 /** 38626 * Estimates the time required to complete a segment download from the specified playlist 38627 * 38628 * @param {Number} segmentDuration 38629 * Duration of requested segment 38630 * @param {Number} bandwidth 38631 * Current measured bandwidth of the player 38632 * @param {Object} playlist 38633 * The media playlist object 38634 * @param {Number=} bytesReceived 38635 * Number of bytes already received for the request. Defaults to 0 38636 * @return {Number|NaN} 38637 * The estimated time to request the segment. NaN if bandwidth information for 38638 * the given playlist is unavailable 38639 * @function estimateSegmentRequestTime 38640 */ 38641 38642 38643 var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) { 38644 var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; 38645 38646 if (!hasAttribute('BANDWIDTH', playlist)) { 38647 return NaN; 38648 } 38649 38650 var size = segmentDuration * playlist.attributes.BANDWIDTH; 38651 return (size - bytesReceived * 8) / bandwidth; 38652 }; 38653 /* 38654 * Returns whether the current playlist is the lowest rendition 38655 * 38656 * @return {Boolean} true if on lowest rendition 38657 */ 38658 38659 38660 var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) { 38661 if (master.playlists.length === 1) { 38662 return true; 38663 } 38664 38665 var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; 38666 return master.playlists.filter(function (playlist) { 38667 if (!isEnabled(playlist)) { 38668 return false; 38669 } 38670 38671 return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth; 38672 }).length === 0; 38673 }; // exports 38674 38675 38676 var Playlist = { 38677 duration: duration, 38678 seekable: seekable, 38679 safeLiveIndex: safeLiveIndex, 38680 getMediaInfoForTime: getMediaInfoForTime, 38681 isEnabled: isEnabled, 38682 isDisabled: isDisabled, 38683 isBlacklisted: isBlacklisted, 38684 isIncompatible: isIncompatible, 38685 playlistEnd: playlistEnd, 38686 isAes: isAes, 38687 isFmp4: isFmp4, 38688 hasAttribute: hasAttribute, 38689 estimateSegmentRequestTime: estimateSegmentRequestTime, 38690 isLowestEnabledRendition: isLowestEnabledRendition 38691 }; 38692 /** 38693 * @file xhr.js 38694 */ 38695 38696 var videojsXHR = videojs$1.xhr, 38697 mergeOptions$1$1 = videojs$1.mergeOptions; 38698 38699 var xhrFactory = function xhrFactory() { 38700 var xhr = function XhrFunction(options, callback) { 38701 // Add a default timeout for all hls requests 38702 options = mergeOptions$1$1({ 38703 timeout: 45e3 38704 }, options); // Allow an optional user-specified function to modify the option 38705 // object before we construct the xhr request 38706 38707 var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest; 38708 38709 if (beforeRequest && typeof beforeRequest === 'function') { 38710 var newOptions = beforeRequest(options); 38711 38712 if (newOptions) { 38713 options = newOptions; 38714 } 38715 } 38716 38717 var request = videojsXHR(options, function (error, response) { 38718 var reqResponse = request.response; 38719 38720 if (!error && reqResponse) { 38721 request.responseTime = Date.now(); 38722 request.roundTripTime = request.responseTime - request.requestTime; 38723 request.bytesReceived = reqResponse.byteLength || reqResponse.length; 38724 38725 if (!request.bandwidth) { 38726 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 38727 } 38728 } 38729 38730 if (response.headers) { 38731 request.responseHeaders = response.headers; 38732 } // videojs.xhr now uses a specific code on the error 38733 // object to signal that a request has timed out instead 38734 // of setting a boolean on the request object 38735 38736 38737 if (error && error.code === 'ETIMEDOUT') { 38738 request.timedout = true; 38739 } // videojs.xhr no longer considers status codes outside of 200 and 0 38740 // (for file uris) to be errors, but the old XHR did, so emulate that 38741 // behavior. Status 206 may be used in response to byterange requests. 38742 38743 38744 if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 38745 error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))); 38746 } 38747 38748 callback(error, request); 38749 }); 38750 var originalAbort = request.abort; 38751 38752 request.abort = function () { 38753 request.aborted = true; 38754 return originalAbort.apply(request, arguments); 38755 }; 38756 38757 request.uri = options.uri; 38758 request.requestTime = Date.now(); 38759 return request; 38760 }; 38761 38762 return xhr; 38763 }; 38764 /** 38765 * Turns segment byterange into a string suitable for use in 38766 * HTTP Range requests 38767 * 38768 * @param {Object} byterange - an object with two values defining the start and end 38769 * of a byte-range 38770 */ 38771 38772 38773 var byterangeStr = function byterangeStr(byterange) { 38774 var byterangeStart = void 0; 38775 var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range 38776 // header uses inclusive ranges 38777 38778 byterangeEnd = byterange.offset + byterange.length - 1; 38779 byterangeStart = byterange.offset; 38780 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 38781 }; 38782 /** 38783 * Defines headers for use in the xhr request for a particular segment. 38784 * 38785 * @param {Object} segment - a simplified copy of the segmentInfo object 38786 * from SegmentLoader 38787 */ 38788 38789 38790 var segmentXhrHeaders = function segmentXhrHeaders(segment) { 38791 var headers = {}; 38792 38793 if (segment.byterange) { 38794 headers.Range = byterangeStr(segment.byterange); 38795 } 38796 38797 return headers; 38798 }; 38799 /** 38800 * @file bin-utils.js 38801 */ 38802 38803 /** 38804 * convert a TimeRange to text 38805 * 38806 * @param {TimeRange} range the timerange to use for conversion 38807 * @param {Number} i the iterator on the range to convert 38808 */ 38809 38810 38811 var textRange = function textRange(range, i) { 38812 return range.start(i) + '-' + range.end(i); 38813 }; 38814 /** 38815 * format a number as hex string 38816 * 38817 * @param {Number} e The number 38818 * @param {Number} i the iterator 38819 */ 38820 38821 38822 var formatHexString = function formatHexString(e, i) { 38823 var value = e.toString(16); 38824 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 38825 }; 38826 38827 var formatAsciiString = function formatAsciiString(e) { 38828 if (e >= 0x20 && e < 0x7e) { 38829 return String.fromCharCode(e); 38830 } 38831 38832 return '.'; 38833 }; 38834 /** 38835 * Creates an object for sending to a web worker modifying properties that are TypedArrays 38836 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 38837 * 38838 * @param {Object} message 38839 * Object of properties and values to send to the web worker 38840 * @return {Object} 38841 * Modified message with TypedArray values expanded 38842 * @function createTransferableMessage 38843 */ 38844 38845 38846 var createTransferableMessage = function createTransferableMessage(message) { 38847 var transferable = {}; 38848 Object.keys(message).forEach(function (key) { 38849 var value = message[key]; 38850 38851 if (ArrayBuffer.isView(value)) { 38852 transferable[key] = { 38853 bytes: value.buffer, 38854 byteOffset: value.byteOffset, 38855 byteLength: value.byteLength 38856 }; 38857 } else { 38858 transferable[key] = value; 38859 } 38860 });
38861 return transferable; 38862 }; 38863 /** 38864 * Returns a unique string identifier for a media initialization 38865 * segment. 38866 */ 38867 38868 38869 var initSegmentId = function initSegmentId(initSegment) { 38870 var byterange = initSegment.byterange || { 38871 length: Infinity, 38872 offset: 0 38873 }; 38874 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 38875 }; 38876 /** 38877 * Returns a unique string identifier for a media segment key. 38878 */ 38879 38880 38881 var segmentKeyId = function segmentKeyId(key) { 38882 return key.resolvedUri; 38883 }; 38884 /** 38885 * utils to help dump binary data to the console 38886 */ 38887 38888 38889 var hexDump = function hexDump(data) { 38890 var bytes = Array.prototype.slice.call(data); 38891 var step = 16; 38892 var result = ''; 38893 var hex = void 0; 38894 var ascii = void 0; 38895 38896 for (var j = 0; j < bytes.length / step; j++) { 38897 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 38898 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 38899 result += hex + ' ' + ascii + '\n'; 38900 } 38901 38902 return result; 38903 }; 38904 38905 var tagDump = function tagDump(_ref) { 38906 var bytes = _ref.bytes; 38907 return hexDump(bytes); 38908 }; 38909 38910 var textRanges = function textRanges(ranges) { 38911 var result = ''; 38912 var i = void 0; 38913 38914 for (i = 0; i < ranges.length; i++) { 38915 result += textRange(ranges, i) + ' '; 38916 } 38917 38918 return result; 38919 }; 38920 38921 var utils$1 = 38922 /*#__PURE__*/ 38923 Object.freeze({ 38924 createTransferableMessage: createTransferableMessage, 38925 initSegmentId: initSegmentId, 38926 segmentKeyId: segmentKeyId, 38927 hexDump: hexDump, 38928 tagDump: tagDump, 38929 textRanges: textRanges 38930 }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux 38931 // Add 25% to the segment duration to account for small discrepencies in segment timing. 38932 // 25% was arbitrarily chosen, and may need to be refined over time. 38933 38934 var SEGMENT_END_FUDGE_PERCENT = 0.25; 38935 /** 38936 * Converts a player time (any time that can be gotten/set from player.currentTime(), 38937 * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a 38938 * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)). 38939 * 38940 * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor 38941 * point" (a point where we have a mapping from program time to player time, with player 38942 * time being the post transmux start of the segment). 38943 * 38944 * For more details, see [this doc](../../docs/program-time-from-player-time.md). 38945 * 38946 * @param {Number} playerTime the player time 38947 * @param {Object} segment the segment which contains the player time 38948 * @return {Date} program time 38949 */ 38950 38951 var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) { 38952 if (!segment.dateTimeObject) { 38953 // Can't convert without an "anchor point" for the program time (i.e., a time that can 38954 // be used to map the start of a segment with a real world time). 38955 return null; 38956 } 38957 38958 var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds; 38959 var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended 38960 38961 var startOfSegment = transmuxedStart + transmuxerPrependedSeconds; 38962 var offsetFromSegmentStart = playerTime - startOfSegment; 38963 return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000); 38964 }; 38965 38966 var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) { 38967 return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds; 38968 }; 38969 /** 38970 * Finds a segment that contains the time requested given as an ISO-8601 string. The 38971 * returned segment might be an estimate or an accurate match. 38972 * 38973 * @param {String} programTime The ISO-8601 programTime to find a match for 38974 * @param {Object} playlist A playlist object to search within 38975 */ 38976 38977 38978 var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) { 38979 // Assumptions: 38980 // - verifyProgramDateTimeTags has already been run 38981 // - live streams have been started 38982 var dateTimeObject = void 0; 38983 38984 try { 38985 dateTimeObject = new Date(programTime); 38986 } catch (e) { 38987 return null; 38988 } 38989 38990 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 38991 return null; 38992 } 38993 38994 var segment = playlist.segments[0]; 38995 38996 if (dateTimeObject < segment.dateTimeObject) { 38997 // Requested time is before stream start. 38998 return null; 38999 } 39000 39001 for (var i = 0; i < playlist.segments.length - 1; i++) { 39002 segment = playlist.segments[i]; 39003 var nextSegmentStart = playlist.segments[i + 1].dateTimeObject; 39004 39005 if (dateTimeObject < nextSegmentStart) { 39006 break; 39007 } 39008 } 39009 39010 var lastSegment = playlist.segments[playlist.segments.length - 1]; 39011 var lastSegmentStart = lastSegment.dateTimeObject; 39012 var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT; 39013 var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000); 39014 39015 if (dateTimeObject > lastSegmentEnd) { 39016 // Beyond the end of the stream, or our best guess of the end of the stream. 39017 return null; 39018 } 39019 39020 if (dateTimeObject > lastSegmentStart) { 39021 segment = lastSegment; 39022 } 39023 39024 return { 39025 segment: segment, 39026 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)), 39027 // Although, given that all segments have accurate date time objects, the segment 39028 // selected should be accurate, unless the video has been transmuxed at some point 39029 // (determined by the presence of the videoTimingInfo object), the segment's "player 39030 // time" (the start time in the player) can't be considered accurate. 39031 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 39032 }; 39033 }; 39034 /** 39035 * Finds a segment that contains the given player time(in seconds). 39036 * 39037 * @param {Number} time The player time to find a match for 39038 * @param {Object} playlist A playlist object to search within 39039 */ 39040 39041 39042 var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) { 39043 // Assumptions: 39044 // - there will always be a segment.duration 39045 // - we can start from zero
39046 // - segments are in time order 39047 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 39048 return null; 39049 } 39050 39051 var segmentEnd = 0; 39052 var segment = void 0; 39053 39054 for (var i = 0; i < playlist.segments.length; i++) { 39055 segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and 39056 // should contain the most accurate values we have for the segment's player times. 39057 // 39058 // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall 39059 // back to an estimate based on the manifest derived (inaccurate) segment.duration, to 39060 // calculate an end value. 39061 39062 segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration; 39063 39064 if (time <= segmentEnd) { 39065 break; 39066 } 39067 } 39068 39069 var lastSegment = playlist.segments[playlist.segments.length - 1]; 39070 39071 if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) { 39072 // The time requested is beyond the stream end. 39073 return null; 39074 } 39075 39076 if (time > segmentEnd) { 39077 // The time is within or beyond the last segment. 39078 // 39079 // Check to see if the time is beyond a reasonable guess of the end of the stream. 39080 if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) { 39081 // Technically, because the duration value is only an estimate, the time may still 39082 // exist in the last segment, however, there isn't enough information to make even 39083 // a reasonable estimate. 39084 return null; 39085 } 39086 39087 segment = lastSegment; 39088 } 39089 39090 return { 39091 segment: segment, 39092 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration, 39093 // Because videoTimingInfo is only set after transmux, it is the only way to get 39094 // accurate timing values. 39095 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 39096 }; 39097 }; 39098 /** 39099 * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds. 39100 * If the offset returned is positive, the programTime occurs after the 39101 * comparisonTimestamp. 39102 * If the offset is negative, the programTime occurs before the comparisonTimestamp. 39103 * 39104 * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against 39105 * @param {String} programTime The programTime as an ISO-8601 string 39106 * @return {Number} offset 39107 */ 39108 39109 39110 var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) { 39111 var segmentDateTime = void 0; 39112 var programDateTime = void 0; 39113 39114 try { 39115 segmentDateTime = new Date(comparisonTimeStamp); 39116 programDateTime = new Date(programTime); 39117 } catch (e) {// TODO handle error 39118 } 39119 39120 var segmentTimeEpoch = segmentDateTime.getTime(); 39121 var programTimeEpoch = programDateTime.getTime(); 39122 return (programTimeEpoch - segmentTimeEpoch) / 1000; 39123 }; 39124 /** 39125 * Checks that all segments in this playlist have programDateTime tags. 39126 * 39127 * @param {Object} playlist A playlist object 39128 */ 39129 39130 39131 var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) { 39132 if (!playlist.segments || playlist.segments.length === 0) { 39133 return false; 39134 } 39135 39136 for (var i = 0; i < playlist.segments.length; i++) { 39137 var segment = playlist.segments[i]; 39138 39139 if (!segment.dateTimeObject) { 39140 return false; 39141 } 39142 } 39143 39144 return true; 39145 }; 39146 /** 39147 * Returns the programTime of the media given a playlist and a playerTime. 39148 * The playlist must have programDateTime tags for a programDateTime tag to be returned. 39149 * If the segments containing the time requested have not been buffered yet, an estimate 39150 * may be returned to the callback. 39151 * 39152 * @param {Object} args 39153 * @param {Object} args.playlist A playlist object to search within 39154 * @param {Number} time A playerTime in seconds 39155 * @param {Function} callback(err, programTime) 39156 * @returns {String} err.message A detailed error message 39157 * @returns {Object} programTime 39158 * @returns {Number} programTime.mediaSeconds The streamTime in seconds 39159 * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String 39160 */ 39161 39162 39163 var getProgramTime = function getProgramTime(_ref) { 39164 var playlist = _ref.playlist, 39165 _ref$time = _ref.time, 39166 time = _ref$time === undefined ? undefined : _ref$time, 39167 callback = _ref.callback; 39168 39169 if (!callback) { 39170 throw new Error('getProgramTime: callback must be provided'); 39171 } 39172 39173 if (!playlist || time === undefined) { 39174 return callback({ 39175 message: 'getProgramTime: playlist and time must be provided' 39176 }); 39177 } 39178 39179 var matchedSegment = findSegmentForPlayerTime(time, playlist); 39180 39181 if (!matchedSegment) { 39182 return callback({ 39183 message: 'valid programTime was not found' 39184 }); 39185 } 39186 39187 if (matchedSegment.type === 'estimate') { 39188 return callback({ 39189 message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again', 39190 seekTime: matchedSegment.estimatedStart 39191 }); 39192 } 39193 39194 var programTimeObject = { 39195 mediaSeconds: time 39196 }; 39197 var programTime = playerTimeToProgramTime(time, matchedSegment.segment); 39198 39199 if (programTime) { 39200 programTimeObject.programDateTime = programTime.toISOString(); 39201 } 39202 39203 return callback(null, programTimeObject); 39204 }; 39205 /** 39206 * Seeks in the player to a time that matches the given programTime ISO-8601 string. 39207 * 39208 * @param {Object} args 39209 * @param {String} args.programTime A programTime to seek to as an ISO-8601 String 39210 * @param {Object} args.playlist A playlist to look within 39211 * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2. 39212 * @param {Function} args.seekTo A method to perform a seek 39213 * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true. 39214 * @param {Object} args.tech The tech to seek on 39215 * @param {Function} args.callback(err, newTime) A callback to return the new time to 39216 * @returns {String} err.message A detailed error message 39217 * @returns {Number} newTime The exact time that was seeked to in seconds 39218 */ 39219 39220 39221 var seekToProgramTime = function seekToProgramTime(_ref2) { 39222 var programTime = _ref2.programTime, 39223 playlist = _ref2.playlist, 39224 _ref2$retryCount = _ref2.retryCount, 39225 retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount, 39226 seekTo = _ref2.seekTo, 39227 _ref2$pauseAfterSeek = _ref2.pauseAfterSeek, 39228 pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek, 39229 tech = _ref2.tech, 39230 callback = _ref2.callback; 39231 39232 if (!callback) { 39233 throw new Error('seekToProgramTime: callback must be provided'); 39234 } 39235 39236 if (typeof programTime === 'undefined' || !playlist || !seekTo) { 39237 return callback({ 39238 message: 'seekToProgramTime: programTime, seekTo and playlist must be provided' 39239 }); 39240 } 39241 39242 if (!playlist.endList && !tech.hasStarted_) { 39243 return callback({ 39244 message: 'player must be playing a live stream to start buffering' 39245 }); 39246 } 39247 39248 if (!verifyProgramDateTimeTags(playlist)) { 39249 return callback({ 39250 message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri 39251 }); 39252 } 39253 39254 var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match 39255 39256 if (!matchedSegment) { 39257 return callback({ 39258 message: programTime + ' was not found in the stream' 39259 }); 39260 } 39261 39262 var segment = matchedSegment.segment; 39263 var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime); 39264 39265 if (matchedSegment.type === 'estimate') { 39266 // we've run out of retries 39267 if (retryCount === 0) { 39268 return callback({ 39269 message: programTime + ' is not buffered yet. Try again' 39270 }); 39271 } 39272 39273 seekTo(matchedSegment.estimatedStart + mediaOffset); 39274 tech.one('seeked', function () { 39275 seekToProgramTime({ 39276 programTime: programTime, 39277 playlist: playlist, 39278 retryCount: retryCount - 1, 39279 seekTo: seekTo, 39280 pauseAfterSeek: pauseAfterSeek, 39281 tech: tech, 39282 callback: callback 39283 }); 39284 }); 39285 return; 39286 } // Since the segment.start value is determined from the buffered end or ending time 39287 // of the prior segment, the seekToTime doesn't need to account for any transmuxer 39288 // modifications. 39289 39290 39291 var seekToTime = segment.start + mediaOffset; 39292 39293 var seekedCallback = function seekedCallback() { 39294 return callback(null, tech.currentTime()); 39295 }; // listen for seeked event 39296 39297 39298 tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state 39299 39300 if (pauseAfterSeek) { 39301 tech.pause(); 39302 } 39303 39304 seekTo(seekToTime); 39305 }; 39306 /** 39307 * ranges 39308 * 39309 * Utilities for working with TimeRanges. 39310 * 39311 */ 39312 // Fudge factor to account for TimeRanges rounding 39313 39314 39315 var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range 39316 // can be misleading because of precision differences or when the current media has poorly 39317 // aligned audio and video, which can cause values to be slightly off from what you would 39318 // expect. This value is what we consider to be safe to use in such comparisons to account 39319 // for these scenarios. 39320 39321 var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3; 39322 39323 var filterRanges = function filterRanges(timeRanges, predicate) { 39324 var results = []; 39325 var i = void 0; 39326 39327 if (timeRanges && timeRanges.length) { 39328 // Search for ranges that match the predicate 39329 for (i = 0; i < timeRanges.length; i++) { 39330 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 39331 results.push([timeRanges.start(i), timeRanges.end(i)]); 39332 } 39333 } 39334 } 39335 39336 return videojs$1.createTimeRanges(results); 39337 }; 39338 /** 39339 * Attempts to find the buffered TimeRange that contains the specified 39340 * time. 39341 * @param {TimeRanges} buffered - the TimeRanges object to query 39342 * @param {number} time - the time to filter on. 39343 * @returns {TimeRanges} a new TimeRanges object 39344 */ 39345 39346 39347 var findRange = function findRange(buffered, time) { 39348 return filterRanges(buffered, function (start, end) { 39349 return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time; 39350 });
vendor: 20,100 bytes, lines 39351-39985
39351 }; 39352 /** 39353 * Returns the TimeRanges that begin later than the specified time. 39354 * @param {TimeRanges} timeRanges - the TimeRanges object to query 39355 * @param {number} time - the time to filter on. 39356 * @returns {TimeRanges} a new TimeRanges object. 39357 */ 39358 39359 39360 var findNextRange = function findNextRange(timeRanges, time) { 39361 return filterRanges(timeRanges, function (start) { 39362 return start - TIME_FUDGE_FACTOR >= time; 39363 }); 39364 }; 39365 /** 39366 * Returns gaps within a list of TimeRanges 39367 * @param {TimeRanges} buffered - the TimeRanges object 39368 * @return {TimeRanges} a TimeRanges object of gaps 39369 */ 39370 39371 39372 var findGaps = function findGaps(buffered) { 39373 if (buffered.length < 2) { 39374 return videojs$1.createTimeRanges(); 39375 } 39376 39377 var ranges = []; 39378 39379 for (var i = 1; i < buffered.length; i++) { 39380 var start = buffered.end(i - 1); 39381 var end = buffered.start(i); 39382 ranges.push([start, end]); 39383 } 39384 39385 return videojs$1.createTimeRanges(ranges); 39386 }; 39387 /** 39388 * Gets a human readable string for a TimeRange 39389 * 39390 * @param {TimeRange} range 39391 * @returns {String} a human readable string 39392 */ 39393 39394 39395 var printableRange = function printableRange(range) { 39396 var strArr = []; 39397 39398 if (!range || !range.length) { 39399 return ''; 39400 } 39401 39402 for (var i = 0; i < range.length; i++) { 39403 strArr.push(range.start(i) + ' => ' + range.end(i)); 39404 } 39405 39406 return strArr.join(', '); 39407 }; 39408 /** 39409 * Calculates the amount of time left in seconds until the player hits the end of the 39410 * buffer and causes a rebuffer 39411 * 39412 * @param {TimeRange} buffered 39413 * The state of the buffer 39414 * @param {Numnber} currentTime 39415 * The current time of the player 39416 * @param {Number} playbackRate 39417 * The current playback rate of the player. Defaults to 1. 39418 * @return {Number} 39419 * Time until the player has to start rebuffering in seconds. 39420 * @function timeUntilRebuffer 39421 */ 39422 39423 39424 var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) { 39425 var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; 39426 var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0; 39427 return (bufferedEnd - currentTime) / playbackRate; 39428 }; 39429 /** 39430 * Converts a TimeRanges object into an array representation 39431 * @param {TimeRanges} timeRanges 39432 * @returns {Array} 39433 */ 39434 39435 39436 var timeRangesToArray = function timeRangesToArray(timeRanges) { 39437 var timeRangesList = []; 39438 39439 for (var i = 0; i < timeRanges.length; i++) { 39440 timeRangesList.push({ 39441 start: timeRanges.start(i), 39442 end: timeRanges.end(i) 39443 }); 39444 } 39445 39446 return timeRangesList; 39447 }; 39448 /** 39449 * @file create-text-tracks-if-necessary.js 39450 */ 39451 39452 /** 39453 * Create text tracks on video.js if they exist on a segment. 39454 * 39455 * @param {Object} sourceBuffer the VSB or FSB 39456 * @param {Object} mediaSource the HTML media source 39457 * @param {Object} segment the segment that may contain the text track 39458 * @private 39459 */ 39460 39461 39462 var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) { 39463 var player = mediaSource.player_; // create an in-band caption track if one is present in the segment 39464 39465 if (segment.captions && segment.captions.length) { 39466 if (!sourceBuffer.inbandTextTracks_) { 39467 sourceBuffer.inbandTextTracks_ = {}; 39468 } 39469 39470 for (var trackId in segment.captionStreams) { 39471 if (!sourceBuffer.inbandTextTracks_[trackId]) { 39472 player.tech_.trigger({ 39473 type: 'usage', 39474 name: 'hls-608' 39475 }); 39476 var track = player.textTracks().getTrackById(trackId); 39477 39478 if (track) { 39479 // Resuse an existing track with a CC# id because this was 39480 // very likely created by videojs-contrib-hls from information 39481 // in the m3u8 for us to use 39482 sourceBuffer.inbandTextTracks_[trackId] = track; 39483 } else { 39484 // Otherwise, create a track with the default `CC#` label and 39485 // without a language 39486 sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({ 39487 kind: 'captions', 39488 id: trackId, 39489 label: trackId 39490 }, false).track; 39491 } 39492 } 39493 } 39494 } 39495 39496 if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) { 39497 sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({ 39498 kind: 'metadata', 39499 label: 'Timed Metadata' 39500 }, false).track; 39501 sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType; 39502 } 39503 }; 39504 /** 39505 * @file remove-cues-from-track.js 39506 */ 39507 39508 /** 39509 * Remove cues from a track on video.js. 39510 * 39511 * @param {Double} start start of where we should remove the cue 39512 * @param {Double} end end of where the we should remove the cue 39513 * @param {Object} track the text track to remove the cues from 39514 * @private 39515 */ 39516 39517 39518 var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 39519 var i = void 0; 39520 var cue = void 0; 39521 39522 if (!track) { 39523 return; 39524 } 39525 39526 if (!track.cues) { 39527 return; 39528 } 39529 39530 i = track.cues.length; 39531 39532 while (i--) { 39533 cue = track.cues[i]; // Remove any overlapping cue 39534 39535 if (cue.startTime <= end && cue.endTime >= start) { 39536 track.removeCue(cue); 39537 } 39538 } 39539 }; 39540 /** 39541 * @file add-text-track-data.js 39542 */ 39543 39544 /** 39545 * Define properties on a cue for backwards compatability, 39546 * but warn the user that the way that they are using it 39547 * is depricated and will be removed at a later date. 39548 * 39549 * @param {Cue} cue the cue to add the properties on 39550 * @private 39551 */ 39552 39553 39554 var deprecateOldCue = function deprecateOldCue(cue) { 39555 Object.defineProperties(cue.frame, { 39556 id: { 39557 get: function get() { 39558 videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 39559 return cue.value.key; 39560 } 39561 }, 39562 value: { 39563 get: function get() { 39564 videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 39565 return cue.value.data; 39566 } 39567 }, 39568 privateData: { 39569 get: function get() { 39570 videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 39571 return cue.value.data; 39572 } 39573 } 39574 }); 39575 }; 39576 39577 var durationOfVideo = function durationOfVideo(duration) { 39578 var dur = void 0; 39579 39580 if (isNaN(duration) || Math.abs(duration) === Infinity) { 39581 dur = Number.MAX_VALUE; 39582 } else { 39583 dur = duration; 39584 } 39585 39586 return dur; 39587 }; 39588 /** 39589 * Add text track data to a source handler given the captions and 39590 * metadata from the buffer. 39591 * 39592 * @param {Object} sourceHandler the virtual source buffer 39593 * @param {Array} captionArray an array of caption data 39594 * @param {Array} metadataArray an array of meta data 39595 * @private 39596 */ 39597 39598 39599 var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) { 39600 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 39601 39602 if (captionArray) { 39603 captionArray.forEach(function (caption) { 39604 var track = caption.stream; 39605 this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text)); 39606 }, sourceHandler); 39607 } 39608 39609 if (metadataArray) { 39610 var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration); 39611 metadataArray.forEach(function (metadata) { 39612 var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN, 39613 // ignore this bit of metadata. 39614 // This likely occurs when you have an non-timed ID3 tag like TIT2, 39615 // which is the "Title/Songname/Content description" frame 39616 39617 if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) { 39618 return; 39619 } 39620 39621 metadata.frames.forEach(function (frame) { 39622 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 39623 cue.frame = frame; 39624 cue.value = frame; 39625 deprecateOldCue(cue); 39626 this.metadataTrack_.addCue(cue); 39627 }, this); 39628 }, sourceHandler); // Updating the metadeta cues so that 39629 // the endTime of each cue is the startTime of the next cue 39630 // the endTime of last cue is the duration of the video 39631 39632 if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) { 39633 var cues = sourceHandler.metadataTrack_.cues; 39634 var cuesArray = []; // Create a copy of the TextTrackCueList... 39635 // ...disregarding cues with a falsey value 39636 39637 for (var i = 0; i < cues.length; i++) { 39638 if (cues[i]) { 39639 cuesArray.push(cues[i]); 39640 } 39641 } // Group cues by their startTime value 39642 39643 39644 var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) { 39645 var timeSlot = obj[cue.startTime] || []; 39646 timeSlot.push(cue); 39647 obj[cue.startTime] = timeSlot; 39648 return obj; 39649 }, {}); // Sort startTimes by ascending order 39650 39651 var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) { 39652 return Number(a) - Number(b); 39653 }); // Map each cue group's endTime to the next group's startTime 39654 39655 sortedStartTimes.forEach(function (startTime, idx) { 39656 var cueGroup = cuesGroupedByStartTime[startTime]; 39657 var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime 39658 39659 cueGroup.forEach(function (cue) { 39660 cue.endTime = nextTime; 39661 }); 39662 }); 39663 } 39664 } 39665 }; 39666 39667 var win = typeof window !== 'undefined' ? window : {}, 39668 TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(), 39669 SCRIPT_TYPE = 'application/javascript', 39670 BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder, 39671 URL = win.URL || win.webkitURL || URL && URL.msURL, 39672 Worker = win.Worker; 39673 /** 39674 * Returns a wrapper around Web Worker code that is constructible. 39675 * 39676 * @function shimWorker 39677 * 39678 * @param { String } filename The name of the file 39679 * @param { Function } fn Function wrapping the code of the worker 39680 */ 39681 39682 function shimWorker(filename, fn) { 39683 return function ShimWorker(forceFallback) { 39684 var o = this; 39685 39686 if (!fn) { 39687 return new Worker(filename); 39688 } else if (Worker && !forceFallback) { 39689 // Convert the function's inner code to a string to construct the worker 39690 var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1), 39691 objURL = createSourceObject(source); 39692 this[TARGET] = new Worker(objURL); 39693 wrapTerminate(this[TARGET], objURL); 39694 return this[TARGET]; 39695 } else { 39696 var selfShim = { 39697 postMessage: function postMessage(m) { 39698 if (o.onmessage) { 39699 setTimeout(function () { 39700 o.onmessage({ 39701 data: m, 39702 target: selfShim 39703 }); 39704 }); 39705 } 39706 } 39707 }; 39708 fn.call(selfShim); 39709 39710 this.postMessage = function (m) { 39711 setTimeout(function () { 39712 selfShim.onmessage({ 39713 data: m, 39714 target: o 39715 }); 39716 }); 39717 }; 39718 39719 this.isThisThread = true; 39720 } 39721 }; 39722 } // Test Worker capabilities 39723 39724 39725 if (Worker) { 39726 var testWorker, 39727 objURL = createSourceObject('self.onmessage = function () {}'), 39728 testArray = new Uint8Array(1); 39729 39730 try { 39731 testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it 39732 39733 testWorker.postMessage(testArray, [testArray.buffer]); 39734 } catch (e) { 39735 Worker = null; 39736 } finally { 39737 URL.revokeObjectURL(objURL); 39738 39739 if (testWorker) { 39740 testWorker.terminate(); 39741 } 39742 } 39743 } 39744 39745 function createSourceObject(str) { 39746 try { 39747 return URL.createObjectURL(new Blob([str], { 39748 type: SCRIPT_TYPE 39749 })); 39750 } catch (e) { 39751 var blob = new BlobBuilder(); 39752 blob.append(str); 39753 return URL.createObjectURL(blob.getBlob(type)); 39754 } 39755 } 39756 39757 function wrapTerminate(worker, objURL) { 39758 if (!worker || !objURL) return; 39759 var term = worker.terminate; 39760 worker.objURL = objURL; 39761 39762 worker.terminate = function () { 39763 if (worker.objURL) URL.revokeObjectURL(worker.objURL); 39764 term.call(worker); 39765 }; 39766 } 39767 39768 var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) { 39769 var self = this; 39770 39771 var transmuxerWorker = function () { 39772 /** 39773 * mux.js 39774 * 39775 * Copyright (c) Brightcove 39776 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 39777 * 39778 * A lightweight readable stream implemention that handles event dispatching. 39779 * Objects that inherit from streams should call init in their constructors. 39780 */ 39781 var Stream = function Stream() { 39782 this.init = function () { 39783 var listeners = {}; 39784 /** 39785 * Add a listener for a specified event type. 39786 * @param type {string} the event name 39787 * @param listener {function} the callback to be invoked when an event of 39788 * the specified type occurs 39789 */ 39790 39791 this.on = function (type, listener) { 39792 if (!listeners[type]) { 39793 listeners[type] = []; 39794 } 39795 39796 listeners[type] = listeners[type].concat(listener); 39797 }; 39798 /** 39799 * Remove a listener for a specified event type. 39800 * @param type {string} the event name 39801 * @param listener {function} a function previously registered for this 39802 * type of event through `on` 39803 */ 39804 39805 39806 this.off = function (type, listener) { 39807 var index; 39808 39809 if (!listeners[type]) { 39810 return false; 39811 } 39812 39813 index = listeners[type].indexOf(listener); 39814 listeners[type] = listeners[type].slice(); 39815 listeners[type].splice(index, 1); 39816 return index > -1; 39817 }; 39818 /** 39819 * Trigger an event of the specified type on this stream. Any additional 39820 * arguments to this function are passed as parameters to event listeners. 39821 * @param type {string} the event name 39822 */ 39823 39824 39825 this.trigger = function (type) { 39826 var callbacks, i, length, args; 39827 callbacks = listeners[type]; 39828 39829 if (!callbacks) { 39830 return; 39831 } // Slicing the arguments on every invocation of this method 39832 // can add a significant amount of overhead. Avoid the 39833 // intermediate object creation for the common case of a 39834 // single callback argument 39835 39836 39837 if (arguments.length === 2) { 39838 length = callbacks.length; 39839 39840 for (i = 0; i < length; ++i) { 39841 callbacks[i].call(this, arguments[1]); 39842 } 39843 } else { 39844 args = []; 39845 i = arguments.length; 39846 39847 for (i = 1; i < arguments.length; ++i) { 39848 args.push(arguments[i]); 39849 } 39850 39851 length = callbacks.length; 39852 39853 for (i = 0; i < length; ++i) { 39854 callbacks[i].apply(this, args); 39855 } 39856 } 39857 }; 39858 /** 39859 * Destroys the stream and cleans up. 39860 */ 39861 39862 39863 this.dispose = function () { 39864 listeners = {}; 39865 }; 39866 }; 39867 }; 39868 /** 39869 * Forwards all `data` events on this stream to the destination stream. The 39870 * destination stream should provide a method `push` to receive the data 39871 * events as they arrive. 39872 * @param destination {stream} the stream that will receive all `data` events 39873 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 39874 * when the current stream emits a 'done' event 39875 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 39876 */ 39877 39878 39879 Stream.prototype.pipe = function (destination) { 39880 this.on('data', function (data) { 39881 destination.push(data); 39882 }); 39883 this.on('done', function (flushSource) { 39884 destination.flush(flushSource); 39885 }); 39886 this.on('partialdone', function (flushSource) { 39887 destination.partialFlush(flushSource); 39888 }); 39889 this.on('endedtimeline', function (flushSource) { 39890 destination.endTimeline(flushSource); 39891 }); 39892 this.on('reset', function (flushSource) { 39893 destination.reset(flushSource); 39894 }); 39895 return destination; 39896 }; // Default stream functions that are expected to be overridden to perform 39897 // actual work. These are provided by the prototype as a sort of no-op 39898 // implementation so that we don't have to check for their existence in the 39899 // `pipe` function above. 39900 39901 39902 Stream.prototype.push = function (data) { 39903 this.trigger('data', data); 39904 }; 39905 39906 Stream.prototype.flush = function (flushSource) { 39907 this.trigger('done', flushSource); 39908 }; 39909 39910 Stream.prototype.partialFlush = function (flushSource) { 39911 this.trigger('partialdone', flushSource); 39912 }; 39913 39914 Stream.prototype.endTimeline = function (flushSource) { 39915 this.trigger('endedtimeline', flushSource); 39916 }; 39917 39918 Stream.prototype.reset = function (flushSource) { 39919 this.trigger('reset', flushSource); 39920 }; 39921 39922 var stream = Stream; 39923 /** 39924 * mux.js 39925 * 39926 * Copyright (c) Brightcove 39927 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 39928 * 39929 * Functions that generate fragmented MP4s suitable for use with Media 39930 * Source Extensions. 39931 */ 39932 39933 var UINT32_MAX = Math.pow(2, 32) - 1; 39934 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 39935 39936 (function () { 39937 var i; 39938 types = { 39939 avc1: [], 39940 // codingname 39941 avcC: [], 39942 btrt: [], 39943 dinf: [], 39944 dref: [], 39945 esds: [], 39946 ftyp: [], 39947 hdlr: [], 39948 mdat: [], 39949 mdhd: [], 39950 mdia: [], 39951 mfhd: [], 39952 minf: [], 39953 moof: [], 39954 moov: [], 39955 mp4a: [], 39956 // codingname 39957 mvex: [], 39958 mvhd: [], 39959 pasp: [], 39960 sdtp: [], 39961 smhd: [], 39962 stbl: [], 39963 stco: [], 39964 stsc: [], 39965 stsd: [], 39966 stsz: [], 39967 stts: [], 39968 styp: [], 39969 tfdt: [], 39970 tfhd: [], 39971 traf: [], 39972 trak: [], 39973 trun: [], 39974 trex: [], 39975 tkhd: [], 39976 vmhd: [] 39977 }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 39978 // don't throw an error 39979 39980 if (typeof Uint8Array === 'undefined') { 39981 return; 39982 } 39983 39984 for (i in types) { 39985 if (types.hasOwnProperty(i)) {
vendor: 16,384 bytes, lines 39986-40355
39986 types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; 39987 } 39988 } 39989 39990 MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]); 39991 AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]); 39992 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 39993 VIDEO_HDLR = new Uint8Array([0x00, // version 0 39994 0x00, 0x00, 0x00, // flags 39995 0x00, 0x00, 0x00, 0x00, // pre_defined 39996 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 39997 0x00, 0x00, 0x00, 0x00, // reserved 39998 0x00, 0x00, 0x00, 0x00, // reserved 39999 0x00, 0x00, 0x00, 0x00, // reserved 40000 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 40001 ]); 40002 AUDIO_HDLR = new Uint8Array([0x00, // version 0 40003 0x00, 0x00, 0x00, // flags 40004 0x00, 0x00, 0x00, 0x00, // pre_defined 40005 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 40006 0x00, 0x00, 0x00, 0x00, // reserved 40007 0x00, 0x00, 0x00, 0x00, // reserved 40008 0x00, 0x00, 0x00, 0x00, // reserved 40009 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 40010 ]); 40011 HDLR_TYPES = { 40012 video: VIDEO_HDLR, 40013 audio: AUDIO_HDLR 40014 }; 40015 DREF = new Uint8Array([0x00, // version 0 40016 0x00, 0x00, 0x00, // flags 40017 0x00, 0x00, 0x00, 0x01, // entry_count 40018 0x00, 0x00, 0x00, 0x0c, // entry_size 40019 0x75, 0x72, 0x6c, 0x20, // 'url' type 40020 0x00, // version 0 40021 0x00, 0x00, 0x01 // entry_flags 40022 ]); 40023 SMHD = new Uint8Array([0x00, // version 40024 0x00, 0x00, 0x00, // flags 40025 0x00, 0x00, // balance, 0 means centered 40026 0x00, 0x00 // reserved 40027 ]); 40028 STCO = new Uint8Array([0x00, // version 40029 0x00, 0x00, 0x00, // flags 40030 0x00, 0x00, 0x00, 0x00 // entry_count 40031 ]); 40032 STSC = STCO; 40033 STSZ = new Uint8Array([0x00, // version 40034 0x00, 0x00, 0x00, // flags 40035 0x00, 0x00, 0x00, 0x00, // sample_size 40036 0x00, 0x00, 0x00, 0x00 // sample_count 40037 ]); 40038 STTS = STCO; 40039 VMHD = new Uint8Array([0x00, // version 40040 0x00, 0x00, 0x01, // flags 40041 0x00, 0x00, // graphicsmode 40042 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor 40043 ]); 40044 })(); 40045 40046 box = function box(type) { 40047 var payload = [], 40048 size = 0, 40049 i, 40050 result, 40051 view; 40052 40053 for (i = 1; i < arguments.length; i++) { 40054 payload.push(arguments[i]); 40055 } 40056 40057 i = payload.length; // calculate the total size we need to allocate 40058 40059 while (i--) { 40060 size += payload[i].byteLength; 40061 } 40062 40063 result = new Uint8Array(size + 8); 40064 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 40065 view.setUint32(0, result.byteLength); 40066 result.set(type, 4); // copy the payload into the result 40067 40068 for (i = 0, size = 8; i < payload.length; i++) { 40069 result.set(payload[i], size); 40070 size += payload[i].byteLength; 40071 } 40072 40073 return result; 40074 }; 40075 40076 dinf = function dinf() { 40077 return box(types.dinf, box(types.dref, DREF)); 40078 }; 40079 40080 esds = function esds(track) { 40081 return box(types.esds, new Uint8Array([0x00, // version 40082 0x00, 0x00, 0x00, // flags 40083 // ES_Descriptor 40084 0x03, // tag, ES_DescrTag 40085 0x19, // length 40086 0x00, 0x00, // ES_ID 40087 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 40088 // DecoderConfigDescriptor 40089 0x04, // tag, DecoderConfigDescrTag 40090 0x11, // length 40091 0x40, // object type 40092 0x15, // streamType 40093 0x00, 0x06, 0x00, // bufferSizeDB 40094 0x00, 0x00, 0xda, 0xc0, // maxBitrate 40095 0x00, 0x00, 0xda, 0xc0, // avgBitrate 40096 // DecoderSpecificInfo 40097 0x05, // tag, DecoderSpecificInfoTag 40098 0x02, // length 40099 // ISO/IEC 14496-3, AudioSpecificConfig 40100 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 40101 track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig 40102 ])); 40103 }; 40104 40105 ftyp = function ftyp() { 40106 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 40107 }; 40108 40109 hdlr = function hdlr(type) { 40110 return box(types.hdlr, HDLR_TYPES[type]); 40111 }; 40112 40113 mdat = function mdat(data) { 40114 return box(types.mdat, data); 40115 }; 40116 40117 mdhd = function mdhd(track) { 40118 var result = new Uint8Array([0x00, // version 0 40119 0x00, 0x00, 0x00, // flags 40120 0x00, 0x00, 0x00, 0x02, // creation_time 40121 0x00, 0x00, 0x00, 0x03, // modification_time 40122 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 40123 track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration 40124 0x55, 0xc4, // 'und' language (undetermined) 40125 0x00, 0x00]); // Use the sample rate from the track metadata, when it is 40126 // defined. The sample rate can be parsed out of an ADTS header, for 40127 // instance. 40128 40129 if (track.samplerate) { 40130 result[12] = track.samplerate >>> 24 & 0xFF; 40131 result[13] = track.samplerate >>> 16 & 0xFF; 40132 result[14] = track.samplerate >>> 8 & 0xFF; 40133 result[15] = track.samplerate & 0xFF; 40134 } 40135 40136 return box(types.mdhd, result); 40137 }; 40138 40139 mdia = function mdia(track) { 40140 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 40141 }; 40142 40143 mfhd = function mfhd(sequenceNumber) { 40144 return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags 40145 (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number 40146 ])); 40147 }; 40148 40149 minf = function minf(track) { 40150 return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track)); 40151 }; 40152 40153 moof = function moof(sequenceNumber, tracks) { 40154 var trackFragments = [], 40155 i = tracks.length; // build traf boxes for each track fragment 40156 40157 while (i--) { 40158 trackFragments[i] = traf(tracks[i]); 40159 } 40160 40161 return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments)); 40162 }; 40163 /** 40164 * Returns a movie box. 40165 * @param tracks {array} the tracks associated with this movie 40166 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 40167 */ 40168 40169 40170 moov = function moov(tracks) { 40171 var i = tracks.length, 40172 boxes = []; 40173 40174 while (i--) { 40175 boxes[i] = trak(tracks[i]); 40176 } 40177 40178 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 40179 }; 40180 40181 mvex = function mvex(tracks) { 40182 var i = tracks.length, 40183 boxes = []; 40184 40185 while (i--) { 40186 boxes[i] = trex(tracks[i]); 40187 } 40188 40189 return box.apply(null, [types.mvex].concat(boxes)); 40190 }; 40191 40192 mvhd = function mvhd(duration) { 40193 var bytes = new Uint8Array([0x00, // version 0 40194 0x00, 0x00, 0x00, // flags 40195 0x00, 0x00, 0x00, 0x01, // creation_time 40196 0x00, 0x00, 0x00, 0x02, // modification_time 40197 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 40198 (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration 40199 0x00, 0x01, 0x00, 0x00, // 1.0 rate 40200 0x01, 0x00, // 1.0 volume 40201 0x00, 0x00, // reserved 40202 0x00, 0x00, 0x00, 0x00, // reserved 40203 0x00, 0x00, 0x00, 0x00, // reserved 40204 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 40205 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 40206 0xff, 0xff, 0xff, 0xff // next_track_ID 40207 ]); 40208 return box(types.mvhd, bytes); 40209 }; 40210 40211 sdtp = function sdtp(track) { 40212 var samples = track.samples || [], 40213 bytes = new Uint8Array(4 + samples.length), 40214 flags, 40215 i; // leave the full box header (4 bytes) all zero 40216 // write the sample table 40217 40218 for (i = 0; i < samples.length; i++) { 40219 flags = samples[i].flags; 40220 bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; 40221 } 40222 40223 return box(types.sdtp, bytes); 40224 }; 40225 40226 stbl = function stbl(track) { 40227 return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO)); 40228 }; 40229 40230 (function () { 40231 var videoSample, audioSample; 40232 40233 stsd = function stsd(track) { 40234 return box(types.stsd, new Uint8Array([0x00, // version 0 40235 0x00, 0x00, 0x00, // flags 40236 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track)); 40237 }; 40238 40239 videoSample = function videoSample(track) { 40240 var sps = track.sps || [], 40241 pps = track.pps || [], 40242 sequenceParameterSets = [], 40243 pictureParameterSets = [], 40244 i, 40245 avc1Box; // assemble the SPSs 40246 40247 for (i = 0; i < sps.length; i++) { 40248 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 40249 sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength 40250 40251 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 40252 } // assemble the PPSs 40253 40254 40255 for (i = 0; i < pps.length; i++) { 40256 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 40257 pictureParameterSets.push(pps[i].byteLength & 0xFF); 40258 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 40259 } 40260 40261 avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 40262 0x00, 0x01, // data_reference_index 40263 0x00, 0x00, // pre_defined 40264 0x00, 0x00, // reserved 40265 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 40266 (track.width & 0xff00) >> 8, track.width & 0xff, // width 40267 (track.height & 0xff00) >> 8, track.height & 0xff, // height 40268 0x00, 0x48, 0x00, 0x00, // horizresolution 40269 0x00, 0x48, 0x00, 0x00, // vertresolution 40270 0x00, 0x00, 0x00, 0x00, // reserved 40271 0x00, 0x01, // frame_count 40272 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 40273 0x00, 0x18, // depth = 24 40274 0x11, 0x11 // pre_defined = -1 40275 ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion 40276 track.profileIdc, // AVCProfileIndication 40277 track.profileCompatibility, // profile_compatibility 40278 track.levelIdc, // AVCLevelIndication 40279 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 40280 ].concat([sps.length], // numOfSequenceParameterSets 40281 sequenceParameterSets, // "SPS" 40282 [pps.length], // numOfPictureParameterSets 40283 pictureParameterSets // "PPS" 40284 ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 40285 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 40286 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate 40287 ]))]; 40288 40289 if (track.sarRatio) { 40290 var hSpacing = track.sarRatio[0], 40291 vSpacing = track.sarRatio[1]; 40292 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]))); 40293 } 40294 40295 return box.apply(null, avc1Box); 40296 }; 40297 40298 audioSample = function audioSample(track) { 40299 return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12 40300 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 40301 0x00, 0x01, // data_reference_index 40302 // AudioSampleEntry, ISO/IEC 14496-12 40303 0x00, 0x00, 0x00, 0x00, // reserved 40304 0x00, 0x00, 0x00, 0x00, // reserved 40305 (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount 40306 (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize 40307 0x00, 0x00, // pre_defined 40308 0x00, 0x00, // reserved 40309 (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16 40310 // MP4AudioSampleEntry, ISO/IEC 14496-14 40311 ]), esds(track)); 40312 }; 40313 })(); 40314 40315 tkhd = function tkhd(track) { 40316 var result = new Uint8Array([0x00, // version 0 40317 0x00, 0x00, 0x07, // flags 40318 0x00, 0x00, 0x00, 0x00, // creation_time 40319 0x00, 0x00, 0x00, 0x00, // modification_time 40320 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 40321 0x00, 0x00, 0x00, 0x00, // reserved 40322 (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration 40323 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 40324 0x00, 0x00, // layer 40325 0x00, 0x00, // alternate_group 40326 0x01, 0x00, // non-audio track volume 40327 0x00, 0x00, // reserved 40328 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 40329 (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width 40330 (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height 40331 ]); 40332 return box(types.tkhd, result); 40333 }; 40334 /** 40335 * Generate a track fragment (traf) box. A traf box collects metadata 40336 * about tracks in a movie fragment (moof) box. 40337 */ 40338 40339 40340 traf = function traf(track) { 40341 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 40342 trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0 40343 0x00, 0x00, 0x3a, // flags 40344 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 40345 0x00, 0x00, 0x00, 0x01, // sample_description_index 40346 0x00, 0x00, 0x00, 0x00, // default_sample_duration 40347 0x00, 0x00, 0x00, 0x00, // default_sample_size 40348 0x00, 0x00, 0x00, 0x00 // default_sample_flags 40349 ])); 40350 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 40351 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 40352 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1 40353 0x00, 0x00, 0x00, // flags 40354 // baseMediaDecodeTime 40355 upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime &
vendor: 10,330 bytes, lines 40355-40600
403550xFF])); // the data offset specifies the number of bytes from the start of 40356 // the containing moof to the first payload byte of the associated 40357 // mdat 40358 40359 dataOffset = 32 + // tfhd 40360 20 + // tfdt 40361 8 + // traf header 40362 16 + // mfhd 40363 8 + // moof header 40364 8; // mdat header 40365 // audio tracks require less metadata 40366 40367 if (track.type === 'audio') { 40368 trackFragmentRun = trun(track, dataOffset); 40369 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun); 40370 } // video tracks should contain an independent and disposable samples 40371 // box (sdtp) 40372 // generate one and adjust offsets to match 40373 40374 40375 sampleDependencyTable = sdtp(track); 40376 trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset); 40377 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable); 40378 }; 40379 /** 40380 * Generate a track box. 40381 * @param track {object} a track definition 40382 * @return {Uint8Array} the track box 40383 */ 40384 40385 40386 trak = function trak(track) { 40387 track.duration = track.duration || 0xffffffff; 40388 return box(types.trak, tkhd(track), mdia(track)); 40389 }; 40390 40391 trex = function trex(track) { 40392 var result = new Uint8Array([0x00, // version 0 40393 0x00, 0x00, 0x00, // flags 40394 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 40395 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 40396 0x00, 0x00, 0x00, 0x00, // default_sample_duration 40397 0x00, 0x00, 0x00, 0x00, // default_sample_size 40398 0x00, 0x01, 0x00, 0x01 // default_sample_flags 40399 ]); // the last two bytes of default_sample_flags is the sample 40400 // degradation priority, a hint about the importance of this sample 40401 // relative to others. Lower the degradation priority for all sample 40402 // types other than video. 40403 40404 if (track.type !== 'video') { 40405 result[result.length - 1] = 0x00; 40406 } 40407 40408 return box(types.trex, result); 40409 }; 40410 40411 (function () { 40412 var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a 40413 // duration is present for the first sample, it will be present for 40414 // all subsequent samples. 40415 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 40416 40417 trunHeader = function trunHeader(samples, offset) { 40418 var durationPresent = 0, 40419 sizePresent = 0, 40420 flagsPresent = 0, 40421 compositionTimeOffset = 0; // trun flag constants 40422 40423 if (samples.length) { 40424 if (samples[0].duration !== undefined) { 40425 durationPresent = 0x1; 40426 } 40427 40428 if (samples[0].size !== undefined) { 40429 sizePresent = 0x2; 40430 } 40431 40432 if (samples[0].flags !== undefined) { 40433 flagsPresent = 0x4; 40434 } 40435 40436 if (samples[0].compositionTimeOffset !== undefined) { 40437 compositionTimeOffset = 0x8; 40438 } 40439 } 40440 40441 return [0x00, // version 0 40442 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags 40443 (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count 40444 (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset 40445 ]; 40446 }; 40447 40448 videoTrun = function videoTrun(track, offset) { 40449 var bytes, samples, sample, i; 40450 samples = track.samples || []; 40451 offset += 8 + 12 + 16 * samples.length; 40452 bytes = trunHeader(samples, offset); 40453 40454 for (i = 0; i < samples.length; i++) { 40455 sample = samples[i]; 40456 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 40457 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size 40458 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 40459 (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset 40460 ]); 40461 } 40462 40463 return box(types.trun, new Uint8Array(bytes)); 40464 }; 40465 40466 audioTrun = function audioTrun(track, offset) { 40467 var bytes, samples, sample, i; 40468 samples = track.samples || []; 40469 offset += 8 + 12 + 8 * samples.length; 40470 bytes = trunHeader(samples, offset); 40471 40472 for (i = 0; i < samples.length; i++) { 40473 sample = samples[i]; 40474 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 40475 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size 40476 } 40477 40478 return box(types.trun, new Uint8Array(bytes)); 40479 }; 40480 40481 trun = function trun(track, offset) { 40482 if (track.type === 'audio') { 40483 return audioTrun(track, offset); 40484 } 40485 40486 return videoTrun(track, offset); 40487 }; 40488 })(); 40489 40490 var mp4Generator = { 40491 ftyp: ftyp, 40492 mdat: mdat, 40493 moof: moof, 40494 moov: moov, 40495 initSegment: function initSegment(tracks) { 40496 var fileType = ftyp(), 40497 movie = moov(tracks), 40498 result; 40499 result = new Uint8Array(fileType.byteLength + movie.byteLength); 40500 result.set(fileType); 40501 result.set(movie, fileType.byteLength); 40502 return result; 40503 } 40504 }; 40505 /** 40506 * mux.js 40507 * 40508 * Copyright (c) Brightcove 40509 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 40510 */ 40511 // Convert an array of nal units into an array of frames with each frame being 40512 // composed of the nal units that make up that frame 40513 // Also keep track of cummulative data about the frame from the nal units such 40514 // as the frame duration, starting pts, etc. 40515 40516 var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) { 40517 var i, 40518 currentNal, 40519 currentFrame = [], 40520 frames = []; // TODO added for LHLS, make sure this is OK 40521 40522 frames.byteLength = 0; 40523 frames.nalCount = 0; 40524 frames.duration = 0; 40525 currentFrame.byteLength = 0; 40526 40527 for (i = 0; i < nalUnits.length; i++) { 40528 currentNal = nalUnits[i]; // Split on 'aud'-type nal units 40529 40530 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 40531 // Since the very first nal unit is expected to be an AUD 40532 // only push to the frames array when currentFrame is not empty 40533 if (currentFrame.length) { 40534 currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK 40535 40536 frames.byteLength += currentFrame.byteLength; 40537 frames.nalCount += currentFrame.length; 40538 frames.duration += currentFrame.duration; 40539 frames.push(currentFrame); 40540 } 40541 40542 currentFrame = [currentNal]; 40543 currentFrame.byteLength = currentNal.data.byteLength; 40544 currentFrame.pts = currentNal.pts; 40545 currentFrame.dts = currentNal.dts; 40546 } else { 40547 // Specifically flag key frames for ease of use later 40548 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 40549 currentFrame.keyFrame = true; 40550 } 40551 40552 currentFrame.duration = currentNal.dts - currentFrame.dts; 40553 currentFrame.byteLength += currentNal.data.byteLength; 40554 currentFrame.push(currentNal); 40555 } 40556 } // For the last frame, use the duration of the previous frame if we 40557 // have nothing better to go on 40558 40559 40560 if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) { 40561 currentFrame.duration = frames[frames.length - 1].duration; 40562 } // Push the final frame 40563 // TODO added for LHLS, make sure this is OK 40564 40565 40566 frames.byteLength += currentFrame.byteLength; 40567 frames.nalCount += currentFrame.length; 40568 frames.duration += currentFrame.duration; 40569 frames.push(currentFrame); 40570 return frames; 40571 }; // Convert an array of frames into an array of Gop with each Gop being composed 40572 // of the frames that make up that Gop 40573 // Also keep track of cummulative data about the Gop from the frames such as the 40574 // Gop duration, starting pts, etc. 40575 40576 40577 var groupFramesIntoGops = function groupFramesIntoGops(frames) { 40578 var i, 40579 currentFrame, 40580 currentGop = [], 40581 gops = []; // We must pre-set some of the values on the Gop since we 40582 // keep running totals of these values 40583 40584 currentGop.byteLength = 0; 40585 currentGop.nalCount = 0; 40586 currentGop.duration = 0; 40587 currentGop.pts = frames[0].pts; 40588 currentGop.dts = frames[0].dts; // store some metadata about all the Gops 40589 40590 gops.byteLength = 0; 40591 gops.nalCount = 0; 40592 gops.duration = 0; 40593 gops.pts = frames[0].pts; 40594 gops.dts = frames[0].dts; 40595 40596 for (i = 0; i < frames.length; i++) { 40597 currentFrame = frames[i]; 40598 40599 if (currentFrame.keyFrame) { 40600 // Since the very first frame is expected to be an keyframe
vendor: 7,922 bytes, lines 40601-40833
40601 // only push to the gops array when currentGop is not empty 40602 if (currentGop.length) { 40603 gops.push(currentGop); 40604 gops.byteLength += currentGop.byteLength; 40605 gops.nalCount += currentGop.nalCount; 40606 gops.duration += currentGop.duration; 40607 } 40608 40609 currentGop = [currentFrame]; 40610 currentGop.nalCount = currentFrame.length; 40611 currentGop.byteLength = currentFrame.byteLength; 40612 currentGop.pts = currentFrame.pts; 40613 currentGop.dts = currentFrame.dts; 40614 currentGop.duration = currentFrame.duration; 40615 } else { 40616 currentGop.duration += currentFrame.duration; 40617 currentGop.nalCount += currentFrame.length; 40618 currentGop.byteLength += currentFrame.byteLength; 40619 currentGop.push(currentFrame); 40620 } 40621 } 40622 40623 if (gops.length && currentGop.duration <= 0) { 40624 currentGop.duration = gops[gops.length - 1].duration; 40625 } 40626 40627 gops.byteLength += currentGop.byteLength; 40628 gops.nalCount += currentGop.nalCount; 40629 gops.duration += currentGop.duration; // push the final Gop 40630 40631 gops.push(currentGop); 40632 return gops; 40633 }; 40634 /* 40635 * Search for the first keyframe in the GOPs and throw away all frames 40636 * until that keyframe. Then extend the duration of the pulled keyframe 40637 * and pull the PTS and DTS of the keyframe so that it covers the time 40638 * range of the frames that were disposed. 40639 * 40640 * @param {Array} gops video GOPs 40641 * @returns {Array} modified video GOPs 40642 */ 40643 40644 40645 var extendFirstKeyFrame = function extendFirstKeyFrame(gops) { 40646 var currentGop; 40647 40648 if (!gops[0][0].keyFrame && gops.length > 1) { 40649 // Remove the first GOP 40650 currentGop = gops.shift(); 40651 gops.byteLength -= currentGop.byteLength; 40652 gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the 40653 // first gop to cover the time period of the 40654 // frames we just removed 40655 40656 gops[0][0].dts = currentGop.dts; 40657 gops[0][0].pts = currentGop.pts; 40658 gops[0][0].duration += currentGop.duration; 40659 } 40660 40661 return gops; 40662 }; 40663 /** 40664 * Default sample object 40665 * see ISO/IEC 14496-12:2012, section 8.6.4.3 40666 */ 40667 40668 40669 var createDefaultSample = function createDefaultSample() { 40670 return { 40671 size: 0, 40672 flags: { 40673 isLeading: 0, 40674 dependsOn: 1, 40675 isDependedOn: 0, 40676 hasRedundancy: 0, 40677 degradationPriority: 0, 40678 isNonSyncSample: 1 40679 } 40680 }; 40681 }; 40682 /* 40683 * Collates information from a video frame into an object for eventual 40684 * entry into an MP4 sample table. 40685 * 40686 * @param {Object} frame the video frame 40687 * @param {Number} dataOffset the byte offset to position the sample 40688 * @return {Object} object containing sample table info for a frame 40689 */ 40690 40691 40692 var sampleForFrame = function sampleForFrame(frame, dataOffset) { 40693 var sample = createDefaultSample(); 40694 sample.dataOffset = dataOffset; 40695 sample.compositionTimeOffset = frame.pts - frame.dts; 40696 sample.duration = frame.duration; 40697 sample.size = 4 * frame.length; // Space for nal unit size 40698 40699 sample.size += frame.byteLength; 40700 40701 if (frame.keyFrame) { 40702 sample.flags.dependsOn = 2; 40703 sample.flags.isNonSyncSample = 0; 40704 } 40705 40706 return sample; 40707 }; // generate the track's sample table from an array of gops 40708 40709 40710 var generateSampleTable = function generateSampleTable(gops, baseDataOffset) { 40711 var h, 40712 i, 40713 sample, 40714 currentGop, 40715 currentFrame, 40716 dataOffset = baseDataOffset || 0, 40717 samples = []; 40718 40719 for (h = 0; h < gops.length; h++) { 40720 currentGop = gops[h]; 40721 40722 for (i = 0; i < currentGop.length; i++) { 40723 currentFrame = currentGop[i]; 40724 sample = sampleForFrame(currentFrame, dataOffset); 40725 dataOffset += sample.size; 40726 samples.push(sample); 40727 } 40728 } 40729 40730 return samples; 40731 }; // generate the track's raw mdat data from an array of gops 40732 40733 40734 var concatenateNalData = function concatenateNalData(gops) { 40735 var h, 40736 i, 40737 j, 40738 currentGop, 40739 currentFrame, 40740 currentNal, 40741 dataOffset = 0, 40742 nalsByteLength = gops.byteLength, 40743 numberOfNals = gops.nalCount, 40744 totalByteLength = nalsByteLength + 4 * numberOfNals, 40745 data = new Uint8Array(totalByteLength), 40746 view = new DataView(data.buffer); // For each Gop.. 40747 40748 for (h = 0; h < gops.length; h++) { 40749 currentGop = gops[h]; // For each Frame.. 40750 40751 for (i = 0; i < currentGop.length; i++) { 40752 currentFrame = currentGop[i]; // For each NAL.. 40753 40754 for (j = 0; j < currentFrame.length; j++) { 40755 currentNal = currentFrame[j]; 40756 view.setUint32(dataOffset, currentNal.data.byteLength); 40757 dataOffset += 4; 40758 data.set(currentNal.data, dataOffset); 40759 dataOffset += currentNal.data.byteLength; 40760 } 40761 } 40762 } 40763 40764 return data; 40765 }; // generate the track's sample table from a frame 40766 40767 40768 var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) { 40769 var sample, 40770 dataOffset = baseDataOffset || 0, 40771 samples = []; 40772 sample = sampleForFrame(frame, dataOffset); 40773 samples.push(sample); 40774 return samples; 40775 }; // generate the track's raw mdat data from a frame 40776 40777 40778 var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) { 40779 var i, 40780 currentNal, 40781 dataOffset = 0, 40782 nalsByteLength = frame.byteLength, 40783 numberOfNals = frame.length, 40784 totalByteLength = nalsByteLength + 4 * numberOfNals, 40785 data = new Uint8Array(totalByteLength), 40786 view = new DataView(data.buffer); // For each NAL.. 40787 40788 for (i = 0; i < frame.length; i++) { 40789 currentNal = frame[i]; 40790 view.setUint32(dataOffset, currentNal.data.byteLength); 40791 dataOffset += 4; 40792 data.set(currentNal.data, dataOffset); 40793 dataOffset += currentNal.data.byteLength; 40794 } 40795 40796 return data; 40797 }; 40798 40799 var frameUtils = { 40800 groupNalsIntoFrames: groupNalsIntoFrames, 40801 groupFramesIntoGops: groupFramesIntoGops, 40802 extendFirstKeyFrame: extendFirstKeyFrame, 40803 generateSampleTable: generateSampleTable, 40804 concatenateNalData: concatenateNalData, 40805 generateSampleTableForFrame: generateSampleTableForFrame, 40806 concatenateNalDataForFrame: concatenateNalDataForFrame 40807 }; 40808 /** 40809 * mux.js 40810 * 40811 * Copyright (c) Brightcove 40812 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 40813 */ 40814 40815 var highPrefix = [33, 16, 5, 32, 164, 27]; 40816 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 40817 40818 var zeroFill = function zeroFill(count) { 40819 var a = []; 40820 40821 while (count--) { 40822 a.push(0); 40823 } 40824 40825 return a; 40826 }; 40827 40828 var makeTable = function makeTable(metaTable) { 40829 return Object.keys(metaTable).reduce(function (obj, key) { 40830 obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) { 40831 return arr.concat(part); 40832 }, [])); 40833 return obj;
vendor: 8,797 bytes, lines 40834-41061
40834 }, {}); 40835 }; // Frames-of-silence to use for filling in missing AAC frames 40836 40837 40838 var coneOfSilence = { 40839 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 40840 88200: [highPrefix, [231], zeroFill(170), [56]], 40841 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 40842 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 40843 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 40844 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 40845 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 40846 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 40847 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]], 40848 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]], 40849 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 40850 }; 40851 var silence = makeTable(coneOfSilence); 40852 /** 40853 * mux.js 40854 * 40855 * Copyright (c) Brightcove 40856 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 40857 */ 40858 40859 var ONE_SECOND_IN_TS = 90000, 40860 // 90kHz clock 40861 secondsToVideoTs, 40862 secondsToAudioTs, 40863 videoTsToSeconds, 40864 audioTsToSeconds, 40865 audioTsToVideoTs, 40866 videoTsToAudioTs, 40867 metadataTsToSeconds; 40868 40869 secondsToVideoTs = function secondsToVideoTs(seconds) { 40870 return seconds * ONE_SECOND_IN_TS; 40871 }; 40872 40873 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 40874 return seconds * sampleRate; 40875 }; 40876 40877 videoTsToSeconds = function videoTsToSeconds(timestamp) { 40878 return timestamp / ONE_SECOND_IN_TS; 40879 }; 40880 40881 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 40882 return timestamp / sampleRate; 40883 }; 40884 40885 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 40886 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 40887 }; 40888 40889 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 40890 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 40891 }; 40892 /** 40893 * Adjust ID3 tag or caption timing information by the timeline pts values 40894 * (if keepOriginalTimestamps is false) and convert to seconds 40895 */ 40896 40897 40898 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) { 40899 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 40900 }; 40901 40902 var clock = { 40903 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 40904 secondsToVideoTs: secondsToVideoTs, 40905 secondsToAudioTs: secondsToAudioTs, 40906 videoTsToSeconds: videoTsToSeconds, 40907 audioTsToSeconds: audioTsToSeconds, 40908 audioTsToVideoTs: audioTsToVideoTs, 40909 videoTsToAudioTs: videoTsToAudioTs, 40910 metadataTsToSeconds: metadataTsToSeconds 40911 }; 40912 /** 40913 * mux.js 40914 * 40915 * Copyright (c) Brightcove 40916 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 40917 */ 40918 40919 /** 40920 * Sum the `byteLength` properties of the data in each AAC frame 40921 */ 40922 40923 var sumFrameByteLengths = function sumFrameByteLengths(array) { 40924 var i, 40925 currentObj, 40926 sum = 0; // sum the byteLength's all each nal unit in the frame 40927 40928 for (i = 0; i < array.length; i++) { 40929 currentObj = array[i]; 40930 sum += currentObj.data.byteLength; 40931 } 40932 40933 return sum; 40934 }; // Possibly pad (prefix) the audio track with silence if appending this track 40935 // would lead to the introduction of a gap in the audio buffer 40936 40937 40938 var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) { 40939 var baseMediaDecodeTimeTs, 40940 frameDuration = 0, 40941 audioGapDuration = 0, 40942 audioFillFrameCount = 0, 40943 audioFillDuration = 0, 40944 silentFrame, 40945 i, 40946 firstFrame; 40947 40948 if (!frames.length) { 40949 return; 40950 } 40951 40952 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills 40953 40954 frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024)); 40955 40956 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 40957 // insert the shortest possible amount (audio gap or audio to video gap) 40958 audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap 40959 40960 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 40961 audioFillDuration = audioFillFrameCount * frameDuration; 40962 } // don't attempt to fill gaps smaller than a single frame or larger 40963 // than a half second 40964 40965 40966 if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) { 40967 return; 40968 } 40969 40970 silentFrame = silence[track.samplerate]; 40971 40972 if (!silentFrame) { 40973 // we don't have a silent frame pregenerated for the sample rate, so use a frame 40974 // from the content instead 40975 silentFrame = frames[0].data; 40976 } 40977 40978 for (i = 0; i < audioFillFrameCount; i++) { 40979 firstFrame = frames[0]; 40980 frames.splice(0, 0, { 40981 data: silentFrame, 40982 dts: firstFrame.dts - frameDuration, 40983 pts: firstFrame.pts - frameDuration 40984 }); 40985 } 40986 40987 track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 40988 }; // If the audio segment extends before the earliest allowed dts 40989 // value, remove AAC frames until starts at or after the earliest 40990 // allowed DTS so that we don't end up with a negative baseMedia- 40991 // DecodeTime for the audio track 40992 40993 40994 var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) { 40995 if (track.minSegmentDts >= earliestAllowedDts) { 40996 return adtsFrames; 40997 } // We will need to recalculate the earliest segment Dts 40998 40999 41000 track.minSegmentDts = Infinity; 41001 return adtsFrames.filter(function (currentFrame) { 41002 // If this is an allowed frame, keep it and record it's Dts 41003 if (currentFrame.dts >= earliestAllowedDts) { 41004 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 41005 track.minSegmentPts = track.minSegmentDts; 41006 return true; 41007 } // Otherwise, discard it 41008 41009 41010 return false; 41011 }); 41012 }; // generate the track's raw mdat data from an array of frames 41013 41014 41015 var generateSampleTable$1 = function generateSampleTable(frames) { 41016 var i, 41017 currentFrame, 41018 samples = []; 41019 41020 for (i = 0; i < frames.length; i++) { 41021 currentFrame = frames[i]; 41022 samples.push({ 41023 size: currentFrame.data.byteLength, 41024 duration: 1024 // For AAC audio, all samples contain 1024 samples 41025 41026 }); 41027 } 41028 41029 return samples; 41030 }; // generate the track's sample table from an array of frames 41031 41032 41033 var concatenateFrameData = function concatenateFrameData(frames) { 41034 var i, 41035 currentFrame, 41036 dataOffset = 0, 41037 data = new Uint8Array(sumFrameByteLengths(frames)); 41038 41039 for (i = 0; i < frames.length; i++) { 41040 currentFrame = frames[i]; 41041 data.set(currentFrame.data, dataOffset); 41042 dataOffset += currentFrame.data.byteLength; 41043 } 41044 41045 return data; 41046 }; 41047 41048 var audioFrameUtils = { 41049 prefixWithSilence: prefixWithSilence, 41050 trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts, 41051 generateSampleTable: generateSampleTable$1, 41052 concatenateFrameData: concatenateFrameData 41053 }; 41054 /** 41055 * mux.js 41056 * 41057 * Copyright (c) Brightcove 41058 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 41059 */ 41060 41061 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS;
vendor: 12,931 bytes, lines 41062-41420
41062 /** 41063 * Store information about the start and end of the track and the 41064 * duration for each frame/sample we process in order to calculate 41065 * the baseMediaDecodeTime 41066 */ 41067 41068 var collectDtsInfo = function collectDtsInfo(track, data) { 41069 if (typeof data.pts === 'number') { 41070 if (track.timelineStartInfo.pts === undefined) { 41071 track.timelineStartInfo.pts = data.pts; 41072 } 41073 41074 if (track.minSegmentPts === undefined) { 41075 track.minSegmentPts = data.pts; 41076 } else { 41077 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 41078 } 41079 41080 if (track.maxSegmentPts === undefined) { 41081 track.maxSegmentPts = data.pts; 41082 } else { 41083 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 41084 } 41085 } 41086 41087 if (typeof data.dts === 'number') { 41088 if (track.timelineStartInfo.dts === undefined) { 41089 track.timelineStartInfo.dts = data.dts; 41090 } 41091 41092 if (track.minSegmentDts === undefined) { 41093 track.minSegmentDts = data.dts; 41094 } else { 41095 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 41096 } 41097 41098 if (track.maxSegmentDts === undefined) { 41099 track.maxSegmentDts = data.dts; 41100 } else { 41101 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 41102 } 41103 } 41104 }; 41105 /** 41106 * Clear values used to calculate the baseMediaDecodeTime between 41107 * tracks 41108 */ 41109 41110 41111 var clearDtsInfo = function clearDtsInfo(track) { 41112 delete track.minSegmentDts; 41113 delete track.maxSegmentDts; 41114 delete track.minSegmentPts; 41115 delete track.maxSegmentPts; 41116 }; 41117 /** 41118 * Calculate the track's baseMediaDecodeTime based on the earliest 41119 * DTS the transmuxer has ever seen and the minimum DTS for the 41120 * current track 41121 * @param track {object} track metadata configuration 41122 * @param keepOriginalTimestamps {boolean} If true, keep the timestamps 41123 * in the source; false to adjust the first segment to start at 0. 41124 */ 41125 41126 41127 var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) { 41128 var baseMediaDecodeTime, 41129 scale, 41130 minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero. 41131 41132 if (!keepOriginalTimestamps) { 41133 minSegmentDts -= track.timelineStartInfo.dts; 41134 } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 41135 // we want the start of the first segment to be placed 41136 41137 41138 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first 41139 41140 baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative 41141 41142 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 41143 41144 if (track.type === 'audio') { 41145 // Audio has a different clock equal to the sampling_rate so we need to 41146 // scale the PTS values into the clock rate of the track 41147 scale = track.samplerate / ONE_SECOND_IN_TS$1; 41148 baseMediaDecodeTime *= scale; 41149 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 41150 } 41151 41152 return baseMediaDecodeTime; 41153 }; 41154 41155 var trackDecodeInfo = { 41156 clearDtsInfo: clearDtsInfo, 41157 calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime, 41158 collectDtsInfo: collectDtsInfo 41159 }; 41160 /** 41161 * mux.js 41162 * 41163 * Copyright (c) Brightcove 41164 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 41165 * 41166 * Reads in-band caption information from a video elementary 41167 * stream. Captions must follow the CEA-708 standard for injection 41168 * into an MPEG-2 transport streams. 41169 * @see https://en.wikipedia.org/wiki/CEA-708 41170 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 41171 */ 41172 // Supplemental enhancement information (SEI) NAL units have a 41173 // payload type field to indicate how they are to be 41174 // interpreted. CEAS-708 caption content is always transmitted with 41175 // payload type 0x04. 41176 41177 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 41178 RBSP_TRAILING_BITS = 128; 41179 /** 41180 * Parse a supplemental enhancement information (SEI) NAL unit. 41181 * Stops parsing once a message of type ITU T T35 has been found. 41182 * 41183 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 41184 * @return {object} the parsed SEI payload 41185 * @see Rec. ITU-T H.264, 7.3.2.3.1 41186 */ 41187 41188 var parseSei = function parseSei(bytes) { 41189 var i = 0, 41190 result = { 41191 payloadType: -1, 41192 payloadSize: 0 41193 }, 41194 payloadType = 0, 41195 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 41196 41197 while (i < bytes.byteLength) { 41198 // stop once we have hit the end of the sei_rbsp 41199 if (bytes[i] === RBSP_TRAILING_BITS) { 41200 break; 41201 } // Parse payload type 41202 41203 41204 while (bytes[i] === 0xFF) { 41205 payloadType += 255; 41206 i++; 41207 } 41208 41209 payloadType += bytes[i++]; // Parse payload size 41210 41211 while (bytes[i] === 0xFF) { 41212 payloadSize += 255; 41213 i++; 41214 } 41215 41216 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 41217 // there can only ever be one caption message in a frame's sei 41218 41219 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 41220 result.payloadType = payloadType; 41221 result.payloadSize = payloadSize; 41222 result.payload = bytes.subarray(i, i + payloadSize); 41223 break; 41224 } // skip the payload and parse the next message 41225 41226 41227 i += payloadSize; 41228 payloadType = 0; 41229 payloadSize = 0; 41230 } 41231 41232 return result; 41233 }; // see ANSI/SCTE 128-1 (2013), section 8.1 41234 41235 41236 var parseUserData = function parseUserData(sei) { 41237 // itu_t_t35_contry_code must be 181 (United States) for 41238 // captions 41239 if (sei.payload[0] !== 181) { 41240 return null; 41241 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 41242 41243 41244 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 41245 return null; 41246 } // the user_identifier should be "GA94" to indicate ATSC1 data 41247 41248 41249 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 41250 return null; 41251 } // finally, user_data_type_code should be 0x03 for caption data 41252 41253 41254 if (sei.payload[7] !== 0x03) { 41255 return null; 41256 } // return the user_data_type_structure and strip the trailing 41257 // marker bits 41258 41259 41260 return sei.payload.subarray(8, sei.payload.length - 1); 41261 }; // see CEA-708-D, section 4.4 41262 41263 41264 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 41265 var results = [], 41266 i, 41267 count, 41268 offset, 41269 data; // if this is just filler, return immediately 41270 41271 if (!(userData[0] & 0x40)) { 41272 return results; 41273 } // parse out the cc_data_1 and cc_data_2 fields 41274 41275 41276 count = userData[0] & 0x1f; 41277 41278 for (i = 0; i < count; i++) { 41279 offset = i * 3; 41280 data = { 41281 type: userData[offset + 2] & 0x03, 41282 pts: pts 41283 }; // capture cc data when cc_valid is 1 41284 41285 if (userData[offset + 2] & 0x04) { 41286 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 41287 results.push(data); 41288 } 41289 } 41290 41291 return results; 41292 }; 41293 41294 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 41295 var length = data.byteLength, 41296 emulationPreventionBytesPositions = [], 41297 i = 1, 41298 newLength, 41299 newData; // Find all `Emulation Prevention Bytes` 41300 41301 while (i < length - 2) { 41302 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 41303 emulationPreventionBytesPositions.push(i + 2); 41304 i += 2; 41305 } else { 41306 i++; 41307 } 41308 } // If no Emulation Prevention Bytes were found just return the original 41309 // array 41310 41311 41312 if (emulationPreventionBytesPositions.length === 0) { 41313 return data; 41314 } // Create a new array to hold the NAL unit data 41315 41316 41317 newLength = length - emulationPreventionBytesPositions.length; 41318 newData = new Uint8Array(newLength); 41319 var sourceIndex = 0; 41320 41321 for (i = 0; i < newLength; sourceIndex++, i++) { 41322 if (sourceIndex === emulationPreventionBytesPositions[0]) { 41323 // Skip this byte 41324 sourceIndex++; // Remove this position index 41325 41326 emulationPreventionBytesPositions.shift(); 41327 } 41328 41329 newData[i] = data[sourceIndex]; 41330 } 41331 41332 return newData; 41333 }; // exports 41334 41335 41336 var captionPacketParser = { 41337 parseSei: parseSei, 41338 parseUserData: parseUserData, 41339 parseCaptionPackets: parseCaptionPackets, 41340 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 41341 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 41342 }; // ----------------- 41343 // Link To Transport 41344 // ----------------- 41345 41346 var CaptionStream = function CaptionStream() { 41347 CaptionStream.prototype.init.call(this); 41348 this.captionPackets_ = []; 41349 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 41350 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 41351 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 41352 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 41353 ]; 41354 this.reset(); // forward data and done events from CCs to this CaptionStream 41355 41356 this.ccStreams_.forEach(function (cc) { 41357 cc.on('data', this.trigger.bind(this, 'data')); 41358 cc.on('partialdone', this.trigger.bind(this, 'partialdone')); 41359 cc.on('done', this.trigger.bind(this, 'done')); 41360 }, this); 41361 }; 41362 41363 CaptionStream.prototype = new stream(); 41364 41365 CaptionStream.prototype.push = function (event) { 41366 var sei, userData, newCaptionPackets; // only examine SEI NALs 41367 41368 if (event.nalUnitType !== 'sei_rbsp') { 41369 return; 41370 } // parse the sei 41371 41372 41373 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 41374 41375 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 41376 return; 41377 } // parse out the user data payload 41378 41379 41380 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 41381 41382 if (!userData) { 41383 return; 41384 } // Sometimes, the same segment # will be downloaded twice. To stop the 41385 // caption data from being processed twice, we track the latest dts we've 41386 // received and ignore everything with a dts before that. However, since 41387 // data for a specific dts can be split across packets on either side of 41388 // a segment boundary, we need to make sure we *don't* ignore the packets 41389 // from the *next* segment that have dts === this.latestDts_. By constantly 41390 // tracking the number of packets received with dts === this.latestDts_, we 41391 // know how many should be ignored once we start receiving duplicates. 41392 41393 41394 if (event.dts < this.latestDts_) { 41395 // We've started getting older data, so set the flag. 41396 this.ignoreNextEqualDts_ = true; 41397 return; 41398 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 41399 this.numSameDts_--; 41400 41401 if (!this.numSameDts_) { 41402 // We've received the last duplicate packet, time to start processing again 41403 this.ignoreNextEqualDts_ = false; 41404 } 41405 41406 return; 41407 } // parse out CC data packets and save them for later 41408 41409 41410 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 41411 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 41412 41413 if (this.latestDts_ !== event.dts) { 41414 this.numSameDts_ = 0; 41415 } 41416 41417 this.numSameDts_++; 41418 this.latestDts_ = event.dts; 41419 }; 41420
vendor: 5,244 bytes, lines 41421-41554
41421 CaptionStream.prototype.flushCCStreams = function (flushType) { 41422 this.ccStreams_.forEach(function (cc) { 41423 return flushType === 'flush' ? cc.flush() : cc.partialFlush(); 41424 }, this); 41425 }; 41426 41427 CaptionStream.prototype.flushStream = function (flushType) { 41428 // make sure we actually parsed captions before proceeding 41429 if (!this.captionPackets_.length) { 41430 this.flushCCStreams(flushType); 41431 return; 41432 } // In Chrome, the Array#sort function is not stable so add a 41433 // presortIndex that we can use to ensure we get a stable-sort 41434 41435 41436 this.captionPackets_.forEach(function (elem, idx) { 41437 elem.presortIndex = idx; 41438 }); // sort caption byte-pairs based on their PTS values 41439 41440 this.captionPackets_.sort(function (a, b) { 41441 if (a.pts === b.pts) { 41442 return a.presortIndex - b.presortIndex; 41443 } 41444 41445 return a.pts - b.pts; 41446 }); 41447 this.captionPackets_.forEach(function (packet) { 41448 if (packet.type < 2) { 41449 // Dispatch packet to the right Cea608Stream 41450 this.dispatchCea608Packet(packet); 41451 } // this is where an 'else' would go for a dispatching packets 41452 // to a theoretical Cea708Stream that handles SERVICEn data 41453 41454 }, this); 41455 this.captionPackets_.length = 0; 41456 this.flushCCStreams(flushType); 41457 }; 41458 41459 CaptionStream.prototype.flush = function () { 41460 return this.flushStream('flush'); 41461 }; // Only called if handling partial data 41462 41463 41464 CaptionStream.prototype.partialFlush = function () { 41465 return this.flushStream('partialFlush'); 41466 }; 41467 41468 CaptionStream.prototype.reset = function () { 41469 this.latestDts_ = null; 41470 this.ignoreNextEqualDts_ = false; 41471 this.numSameDts_ = 0; 41472 this.activeCea608Channel_ = [null, null]; 41473 this.ccStreams_.forEach(function (ccStream) { 41474 ccStream.reset(); 41475 }); 41476 }; // From the CEA-608 spec: 41477 41478 /* 41479 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed 41480 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is 41481 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair 41482 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the 41483 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) 41484 * to switch to captioning or Text. 41485 */ 41486 // With that in mind, we ignore any data between an XDS control code and a 41487 // subsequent closed-captioning control code. 41488 41489 41490 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 41491 // NOTE: packet.type is the CEA608 field 41492 if (this.setsTextOrXDSActive(packet)) { 41493 this.activeCea608Channel_[packet.type] = null; 41494 } else if (this.setsChannel1Active(packet)) { 41495 this.activeCea608Channel_[packet.type] = 0; 41496 } else if (this.setsChannel2Active(packet)) { 41497 this.activeCea608Channel_[packet.type] = 1; 41498 } 41499 41500 if (this.activeCea608Channel_[packet.type] === null) { 41501 // If we haven't received anything to set the active channel, or the 41502 // packets are Text/XDS data, discard the data; we don't want jumbled 41503 // captions 41504 return; 41505 } 41506 41507 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 41508 }; 41509 41510 CaptionStream.prototype.setsChannel1Active = function (packet) { 41511 return (packet.ccData & 0x7800) === 0x1000; 41512 }; 41513 41514 CaptionStream.prototype.setsChannel2Active = function (packet) { 41515 return (packet.ccData & 0x7800) === 0x1800; 41516 }; 41517 41518 CaptionStream.prototype.setsTextOrXDSActive = function (packet) { 41519 return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a; 41520 }; // ---------------------- 41521 // Session to Application 41522 // ---------------------- 41523 // This hash maps non-ASCII, special, and extended character codes to their 41524 // proper Unicode equivalent. The first keys that are only a single byte 41525 // are the non-standard ASCII characters, which simply map the CEA608 byte 41526 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 41527 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 41528 // can be performed regardless of the field and data channel on which the 41529 // character code was received. 41530 41531 41532 var CHARACTER_TRANSLATION = { 41533 0x2a: 0xe1, 41534 // á 41535 0x5c: 0xe9, 41536 // é 41537 0x5e: 0xed, 41538 // à 41539 0x5f: 0xf3, 41540 // ó 41541 0x60: 0xfa, 41542 // ú 41543 0x7b: 0xe7, 41544 // ç 41545 0x7c: 0xf7, 41546 // ÷ 41547 0x7d: 0xd1, 41548 // à 41549 0x7e: 0xf1, 41550 // ñ 41551 0x7f: 0x2588, 41552 // â 41553 0x0130: 0xae, 41554 // ®
41555 0x0131: 0xb0, 41556 // ° 41557 0x0132: 0xbd, 41558 // ½ 41559 0x0133: 0xbf, 41560 // ¿ 41561 0x0134: 0x2122, 41562 // ⢠41563 0x0135: 0xa2, 41564 // ¢ 41565 0x0136: 0xa3, 41566 // £ 41567 0x0137: 0x266a, 41568 // ⪠41569 0x0138: 0xe0, 41570 // à 41571 0x0139: 0xa0, 41572 // 41573 0x013a: 0xe8, 41574 // è 41575 0x013b: 0xe2, 41576 // â 41577 0x013c: 0xea, 41578 // ê 41579 0x013d: 0xee, 41580 // î 41581 0x013e: 0xf4, 41582 // ô 41583 0x013f: 0xfb, 41584 // û 41585 0x0220: 0xc1, 41586 // à 41587 0x0221: 0xc9, 41588 // à 41589 0x0222: 0xd3, 41590 // à 41591 0x0223: 0xda, 41592 // à 41593 0x0224: 0xdc, 41594 // à 41595 0x0225: 0xfc, 41596 // ü 41597 0x0226: 0x2018, 41598 // â 41599 0x0227: 0xa1, 41600 // ¡ 41601 0x0228: 0x2a, 41602 // * 41603 0x0229: 0x27, 41604 // ' 41605 0x022a: 0x2014, 41606 // â 41607 0x022b: 0xa9, 41608 // © 41609 0x022c: 0x2120, 41610 // â 41611 0x022d: 0x2022, 41612 // ⢠41613 0x022e: 0x201c, 41614 // â 41615 0x022f: 0x201d, 41616 // â 41617 0x0230: 0xc0, 41618 // à 41619 0x0231: 0xc2, 41620 // à 41621 0x0232: 0xc7, 41622 // à 41623 0x0233: 0xc8, 41624 // à 41625 0x0234: 0xca, 41626 // à 41627 0x0235: 0xcb, 41628 // à 41629 0x0236: 0xeb, 41630 // ë 41631 0x0237: 0xce, 41632 // à 41633 0x0238: 0xcf, 41634 // à 41635 0x0239: 0xef, 41636 // ï 41637 0x023a: 0xd4, 41638 // à 41639 0x023b: 0xd9, 41640 // à 41641 0x023c: 0xf9, 41642 // ù 41643 0x023d: 0xdb, 41644 // à 41645 0x023e: 0xab, 41646 // « 41647 0x023f: 0xbb, 41648 // » 41649 0x0320: 0xc3, 41650 // à 41651 0x0321: 0xe3, 41652 // ã 41653 0x0322: 0xcd, 41654 // à 41655 0x0323: 0xcc, 41656 // à 41657 0x0324: 0xec, 41658 // ì 41659 0x0325: 0xd2, 41660 // à 41661 0x0326: 0xf2, 41662 // ò 41663 0x0327: 0xd5, 41664 // à 41665 0x0328: 0xf5, 41666 // õ 41667 0x0329: 0x7b, 41668 // { 41669 0x032a: 0x7d, 41670 // } 41671 0x032b: 0x5c, 41672 // \ 41673 0x032c: 0x5e, 41674 // ^ 41675 0x032d: 0x5f, 41676 // _ 41677 0x032e: 0x7c, 41678 // | 41679 0x032f: 0x7e, 41680 // ~ 41681 0x0330: 0xc4, 41682 // à 41683 0x0331: 0xe4,
vendor: 20,174 bytes, lines 41684-42183
41684 // ä 41685 0x0332: 0xd6, 41686 // à 41687 0x0333: 0xf6, 41688 // ö 41689 0x0334: 0xdf, 41690 // à 41691 0x0335: 0xa5, 41692 // Â¥ 41693 0x0336: 0xa4, 41694 // ¤ 41695 0x0337: 0x2502, 41696 // â 41697 0x0338: 0xc5, 41698 // à 41699 0x0339: 0xe5, 41700 // Ã¥ 41701 0x033a: 0xd8, 41702 // à 41703 0x033b: 0xf8, 41704 // ø 41705 0x033c: 0x250c, 41706 // â 41707 0x033d: 0x2510, 41708 // â 41709 0x033e: 0x2514, 41710 // â 41711 0x033f: 0x2518 // â 41712 41713 }; 41714 41715 var getCharFromCode = function getCharFromCode(code) { 41716 if (code === null) { 41717 return ''; 41718 } 41719 41720 code = CHARACTER_TRANSLATION[code] || code; 41721 return String.fromCharCode(code); 41722 }; // the index of the last row in a CEA-608 display buffer 41723 41724 41725 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 41726 // getting it through bit logic. 41727 41728 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 41729 // cells. The "bottom" row is the last element in the outer array. 41730 41731 var createDisplayBuffer = function createDisplayBuffer() { 41732 var result = [], 41733 i = BOTTOM_ROW + 1; 41734 41735 while (i--) { 41736 result.push(''); 41737 } 41738 41739 return result; 41740 }; 41741 41742 var Cea608Stream = function Cea608Stream(field, dataChannel) { 41743 Cea608Stream.prototype.init.call(this); 41744 this.field_ = field || 0; 41745 this.dataChannel_ = dataChannel || 0; 41746 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 41747 this.setConstants(); 41748 this.reset(); 41749 41750 this.push = function (packet) { 41751 var data, swap, char0, char1, text; // remove the parity bits 41752 41753 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 41754 41755 if (data === this.lastControlCode_) { 41756 this.lastControlCode_ = null; 41757 return; 41758 } // Store control codes 41759 41760 41761 if ((data & 0xf000) === 0x1000) { 41762 this.lastControlCode_ = data; 41763 } else if (data !== this.PADDING_) { 41764 this.lastControlCode_ = null; 41765 } 41766 41767 char0 = data >>> 8; 41768 char1 = data & 0xff; 41769 41770 if (data === this.PADDING_) { 41771 return; 41772 } else if (data === this.RESUME_CAPTION_LOADING_) { 41773 this.mode_ = 'popOn'; 41774 } else if (data === this.END_OF_CAPTION_) { 41775 // If an EOC is received while in paint-on mode, the displayed caption 41776 // text should be swapped to non-displayed memory as if it was a pop-on 41777 // caption. Because of that, we should explicitly switch back to pop-on 41778 // mode 41779 this.mode_ = 'popOn'; 41780 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 41781 41782 this.flushDisplayed(packet.pts); // flip memory 41783 41784 swap = this.displayed_; 41785 this.displayed_ = this.nonDisplayed_; 41786 this.nonDisplayed_ = swap; // start measuring the time to display the caption 41787 41788 this.startPts_ = packet.pts; 41789 } else if (data === this.ROLL_UP_2_ROWS_) { 41790 this.rollUpRows_ = 2; 41791 this.setRollUp(packet.pts); 41792 } else if (data === this.ROLL_UP_3_ROWS_) { 41793 this.rollUpRows_ = 3; 41794 this.setRollUp(packet.pts); 41795 } else if (data === this.ROLL_UP_4_ROWS_) { 41796 this.rollUpRows_ = 4; 41797 this.setRollUp(packet.pts); 41798 } else if (data === this.CARRIAGE_RETURN_) { 41799 this.clearFormatting(packet.pts); 41800 this.flushDisplayed(packet.pts); 41801 this.shiftRowsUp_(); 41802 this.startPts_ = packet.pts; 41803 } else if (data === this.BACKSPACE_) { 41804 if (this.mode_ === 'popOn') { 41805 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 41806 } else { 41807 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 41808 } 41809 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 41810 this.flushDisplayed(packet.pts); 41811 this.displayed_ = createDisplayBuffer(); 41812 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 41813 this.nonDisplayed_ = createDisplayBuffer(); 41814 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 41815 if (this.mode_ !== 'paintOn') { 41816 // NOTE: This should be removed when proper caption positioning is 41817 // implemented 41818 this.flushDisplayed(packet.pts); 41819 this.displayed_ = createDisplayBuffer(); 41820 } 41821 41822 this.mode_ = 'paintOn'; 41823 this.startPts_ = packet.pts; // Append special characters to caption text 41824 } else if (this.isSpecialCharacter(char0, char1)) { 41825 // Bitmask char0 so that we can apply character transformations 41826 // regardless of field and data channel. 41827 // Then byte-shift to the left and OR with char1 so we can pass the 41828 // entire character code to `getCharFromCode`. 41829 char0 = (char0 & 0x03) << 8; 41830 text = getCharFromCode(char0 | char1); 41831 this[this.mode_](packet.pts, text); 41832 this.column_++; // Append extended characters to caption text 41833 } else if (this.isExtCharacter(char0, char1)) { 41834 // Extended characters always follow their "non-extended" equivalents. 41835 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 41836 // decoders are supposed to drop the "è", while compliant decoders 41837 // backspace the "e" and insert "è". 41838 // Delete the previous character 41839 if (this.mode_ === 'popOn') { 41840 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 41841 } else { 41842 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 41843 } // Bitmask char0 so that we can apply character transformations 41844 // regardless of field and data channel. 41845 // Then byte-shift to the left and OR with char1 so we can pass the 41846 // entire character code to `getCharFromCode`. 41847 41848 41849 char0 = (char0 & 0x03) << 8; 41850 text = getCharFromCode(char0 | char1); 41851 this[this.mode_](packet.pts, text); 41852 this.column_++; // Process mid-row codes 41853 } else if (this.isMidRowCode(char0, char1)) { 41854 // Attributes are not additive, so clear all formatting 41855 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 41856 // should be replaced with spaces, so add one now 41857 41858 this[this.mode_](packet.pts, ' '); 41859 this.column_++; 41860 41861 if ((char1 & 0xe) === 0xe) { 41862 this.addFormatting(packet.pts, ['i']); 41863 } 41864 41865 if ((char1 & 0x1) === 0x1) { 41866 this.addFormatting(packet.pts, ['u']); 41867 } // Detect offset control codes and adjust cursor 41868 41869 } else if (this.isOffsetControlCode(char0, char1)) { 41870 // Cursor position is set by indent PAC (see below) in 4-column 41871 // increments, with an additional offset code of 1-3 to reach any 41872 // of the 32 columns specified by CEA-608. So all we need to do 41873 // here is increment the column cursor by the given offset. 41874 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 41875 } else if (this.isPAC(char0, char1)) { 41876 // There's no logic for PAC -> row mapping, so we have to just 41877 // find the row code in an array and use its index :( 41878 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 41879 41880 if (this.mode_ === 'rollUp') { 41881 // This implies that the base row is incorrectly set. 41882 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 41883 // of roll-up rows set. 41884 if (row - this.rollUpRows_ + 1 < 0) { 41885 row = this.rollUpRows_ - 1; 41886 } 41887 41888 this.setRollUp(packet.pts, row); 41889 } 41890 41891 if (row !== this.row_) { 41892 // formatting is only persistent for current row 41893 this.clearFormatting(packet.pts); 41894 this.row_ = row; 41895 } // All PACs can apply underline, so detect and apply 41896 // (All odd-numbered second bytes set underline) 41897 41898 41899 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 41900 this.addFormatting(packet.pts, ['u']); 41901 } 41902 41903 if ((data & 0x10) === 0x10) { 41904 // We've got an indent level code. Each successive even number 41905 // increments the column cursor by 4, so we can get the desired 41906 // column position by bit-shifting to the right (to get n/2) 41907 // and multiplying by 4. 41908 this.column_ = ((data & 0xe) >> 1) * 4; 41909 } 41910 41911 if (this.isColorPAC(char1)) { 41912 // it's a color code, though we only support white, which 41913 // can be either normal or italicized. white italics can be 41914 // either 0x4e or 0x6e depending on the row, so we just 41915 // bitwise-and with 0xe to see if italics should be turned on 41916 if ((char1 & 0xe) === 0xe) { 41917 this.addFormatting(packet.pts, ['i']); 41918 } 41919 } // We have a normal character in char0, and possibly one in char1 41920 41921 } else if (this.isNormalChar(char0)) { 41922 if (char1 === 0x00) { 41923 char1 = null; 41924 } 41925 41926 text = getCharFromCode(char0); 41927 text += getCharFromCode(char1); 41928 this[this.mode_](packet.pts, text); 41929 this.column_ += text.length; 41930 } // finish data processing 41931 41932 }; 41933 }; 41934 41935 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 41936 // display buffer 41937 41938 Cea608Stream.prototype.flushDisplayed = function (pts) { 41939 var content = this.displayed_ // remove spaces from the start and end of the string 41940 .map(function (row) { 41941 try { 41942 return row.trim(); 41943 } catch (e) { 41944 // Ordinarily, this shouldn't happen. However, caption 41945 // parsing errors should not throw exceptions and 41946 // break playback. 41947 // eslint-disable-next-line no-console 41948 console.error('Skipping malformed caption.'); 41949 return ''; 41950 } 41951 }) // combine all text rows to display in one cue 41952 .join('\n') // and remove blank rows from the start and end, but not the middle 41953 .replace(/^\n+|\n+$/g, ''); 41954 41955 if (content.length) { 41956 this.trigger('data', { 41957 startPts: this.startPts_, 41958 endPts: pts, 41959 text: content, 41960 stream: this.name_ 41961 }); 41962 } 41963 }; 41964 /** 41965 * Zero out the data, used for startup and on seek 41966 */ 41967 41968 41969 Cea608Stream.prototype.reset = function () { 41970 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 41971 // actually display captions. If a caption is shifted to a row 41972 // with a lower index than this, it is cleared from the display 41973 // buffer 41974 41975 this.topRow_ = 0; 41976 this.startPts_ = 0; 41977 this.displayed_ = createDisplayBuffer(); 41978 this.nonDisplayed_ = createDisplayBuffer(); 41979 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 41980 41981 this.column_ = 0; 41982 this.row_ = BOTTOM_ROW; 41983 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 41984 41985 this.formatting_ = []; 41986 }; 41987 /** 41988 * Sets up control code and related constants for this instance 41989 */ 41990 41991 41992 Cea608Stream.prototype.setConstants = function () { 41993 // The following attributes have these uses: 41994 // ext_ : char0 for mid-row codes, and the base for extended 41995 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 41996 // extended codes) 41997 // control_: char0 for control codes, except byte-shifted to the 41998 // left so that we can do this.control_ | CONTROL_CODE 41999 // offset_: char0 for tab offset codes 42000 // 42001 // It's also worth noting that control codes, and _only_ control codes, 42002 // differ between field 1 and field2. Field 2 control codes are always 42003 // their field 1 value plus 1. That's why there's the "| field" on the 42004 // control value. 42005 if (this.dataChannel_ === 0) { 42006 this.BASE_ = 0x10; 42007 this.EXT_ = 0x11; 42008 this.CONTROL_ = (0x14 | this.field_) << 8; 42009 this.OFFSET_ = 0x17; 42010 } else if (this.dataChannel_ === 1) { 42011 this.BASE_ = 0x18; 42012 this.EXT_ = 0x19; 42013 this.CONTROL_ = (0x1c | this.field_) << 8; 42014 this.OFFSET_ = 0x1f; 42015 } // Constants for the LSByte command codes recognized by Cea608Stream. This 42016 // list is not exhaustive. For a more comprehensive listing and semantics see 42017 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 42018 // Padding 42019 42020 42021 this.PADDING_ = 0x0000; // Pop-on Mode 42022 42023 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 42024 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 42025 42026 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 42027 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 42028 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 42029 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 42030 42031 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 42032 42033 this.BACKSPACE_ = this.CONTROL_ | 0x21; 42034 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 42035 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 42036 }; 42037 /** 42038 * Detects if the 2-byte packet data is a special character 42039 * 42040 * Special characters have a second byte in the range 0x30 to 0x3f, 42041 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 42042 * data channel 2). 42043 * 42044 * @param {Integer} char0 The first byte 42045 * @param {Integer} char1 The second byte 42046 * @return {Boolean} Whether the 2 bytes are an special character 42047 */ 42048 42049 42050 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 42051 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 42052 }; 42053 /** 42054 * Detects if the 2-byte packet data is an extended character 42055 * 42056 * Extended characters have a second byte in the range 0x20 to 0x3f, 42057 * with the first byte being 0x12 or 0x13 (for data channel 1) or 42058 * 0x1a or 0x1b (for data channel 2). 42059 * 42060 * @param {Integer} char0 The first byte 42061 * @param {Integer} char1 The second byte 42062 * @return {Boolean} Whether the 2 bytes are an extended character 42063 */ 42064 42065 42066 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 42067 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 42068 }; 42069 /** 42070 * Detects if the 2-byte packet is a mid-row code 42071 * 42072 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 42073 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 42074 * channel 2). 42075 * 42076 * @param {Integer} char0 The first byte 42077 * @param {Integer} char1 The second byte 42078 * @return {Boolean} Whether the 2 bytes are a mid-row code 42079 */ 42080 42081 42082 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 42083 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 42084 }; 42085 /** 42086 * Detects if the 2-byte packet is an offset control code 42087 * 42088 * Offset control codes have a second byte in the range 0x21 to 0x23, 42089 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 42090 * data channel 2). 42091 * 42092 * @param {Integer} char0 The first byte 42093 * @param {Integer} char1 The second byte 42094 * @return {Boolean} Whether the 2 bytes are an offset control code 42095 */ 42096 42097 42098 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 42099 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 42100 }; 42101 /** 42102 * Detects if the 2-byte packet is a Preamble Address Code 42103 * 42104 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 42105 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 42106 * range 0x40 to 0x7f. 42107 * 42108 * @param {Integer} char0 The first byte 42109 * @param {Integer} char1 The second byte 42110 * @return {Boolean} Whether the 2 bytes are a PAC 42111 */ 42112 42113 42114 Cea608Stream.prototype.isPAC = function (char0, char1) { 42115 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 42116 }; 42117 /** 42118 * Detects if a packet's second byte is in the range of a PAC color code 42119 * 42120 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 42121 * 0x60 to 0x6f. 42122 * 42123 * @param {Integer} char1 The second byte 42124 * @return {Boolean} Whether the byte is a color PAC 42125 */ 42126 42127 42128 Cea608Stream.prototype.isColorPAC = function (char1) { 42129 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 42130 }; 42131 /** 42132 * Detects if a single byte is in the range of a normal character 42133 * 42134 * Normal text bytes are in the range 0x20 to 0x7f. 42135 * 42136 * @param {Integer} char The byte 42137 * @return {Boolean} Whether the byte is a normal character 42138 */ 42139 42140 42141 Cea608Stream.prototype.isNormalChar = function (_char) { 42142 return _char >= 0x20 && _char <= 0x7f; 42143 }; 42144 /** 42145 * Configures roll-up 42146 * 42147 * @param {Integer} pts Current PTS 42148 * @param {Integer} newBaseRow Used by PACs to slide the current window to 42149 * a new position 42150 */ 42151 42152 42153 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 42154 // Reset the base row to the bottom row when switching modes 42155 if (this.mode_ !== 'rollUp') { 42156 this.row_ = BOTTOM_ROW; 42157 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 42158 42159 this.flushDisplayed(pts); 42160 this.nonDisplayed_ = createDisplayBuffer(); 42161 this.displayed_ = createDisplayBuffer(); 42162 } 42163 42164 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 42165 // move currently displayed captions (up or down) to the new base row 42166 for (var i = 0; i < this.rollUpRows_; i++) { 42167 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 42168 this.displayed_[this.row_ - i] = ''; 42169 } 42170 } 42171 42172 if (newBaseRow === undefined) { 42173 newBaseRow = this.row_; 42174 } 42175 42176 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 42177 }; // Adds the opening HTML tag for the passed character to the caption text, 42178 // and keeps track of it for later closing 42179 42180 42181 Cea608Stream.prototype.addFormatting = function (pts, format) { 42182 this.formatting_ = this.formatting_.concat(format); 42183 var text = format.reduce(function (text, format) {
vendor: 16,176 bytes, lines 42184-42626
42184 return text + '<' + format + '>'; 42185 }, ''); 42186 this[this.mode_](pts, text); 42187 }; // Adds HTML closing tags for current formatting to caption text and 42188 // clears remembered formatting 42189 42190 42191 Cea608Stream.prototype.clearFormatting = function (pts) { 42192 if (!this.formatting_.length) { 42193 return; 42194 } 42195 42196 var text = this.formatting_.reverse().reduce(function (text, format) { 42197 return text + '</' + format + '>'; 42198 }, ''); 42199 this.formatting_ = []; 42200 this[this.mode_](pts, text); 42201 }; // Mode Implementations 42202 42203 42204 Cea608Stream.prototype.popOn = function (pts, text) { 42205 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 42206 42207 baseRow += text; 42208 this.nonDisplayed_[this.row_] = baseRow; 42209 }; 42210 42211 Cea608Stream.prototype.rollUp = function (pts, text) { 42212 var baseRow = this.displayed_[this.row_]; 42213 baseRow += text; 42214 this.displayed_[this.row_] = baseRow; 42215 }; 42216 42217 Cea608Stream.prototype.shiftRowsUp_ = function () { 42218 var i; // clear out inactive rows 42219 42220 for (i = 0; i < this.topRow_; i++) { 42221 this.displayed_[i] = ''; 42222 } 42223 42224 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 42225 this.displayed_[i] = ''; 42226 } // shift displayed rows up 42227 42228 42229 for (i = this.topRow_; i < this.row_; i++) { 42230 this.displayed_[i] = this.displayed_[i + 1]; 42231 } // clear out the bottom row 42232 42233 42234 this.displayed_[this.row_] = ''; 42235 }; 42236 42237 Cea608Stream.prototype.paintOn = function (pts, text) { 42238 var baseRow = this.displayed_[this.row_]; 42239 baseRow += text; 42240 this.displayed_[this.row_] = baseRow; 42241 }; // exports 42242 42243 42244 var captionStream = { 42245 CaptionStream: CaptionStream, 42246 Cea608Stream: Cea608Stream 42247 }; 42248 /** 42249 * mux.js 42250 * 42251 * Copyright (c) Brightcove 42252 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 42253 */ 42254 42255 var streamTypes = { 42256 H264_STREAM_TYPE: 0x1B, 42257 ADTS_STREAM_TYPE: 0x0F, 42258 METADATA_STREAM_TYPE: 0x15 42259 }; 42260 var MAX_TS = 8589934592; 42261 var RO_THRESH = 4294967296; 42262 var TYPE_SHARED = 'shared'; 42263 42264 var handleRollover = function handleRollover(value, reference) { 42265 var direction = 1; 42266 42267 if (value > reference) { 42268 // If the current timestamp value is greater than our reference timestamp and we detect a 42269 // timestamp rollover, this means the roll over is happening in the opposite direction. 42270 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 42271 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 42272 // rollover point. In loading this segment, the timestamp values will be very large, 42273 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 42274 // the time stamp to be `value - 2^33`. 42275 direction = -1; 42276 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 42277 // cause an incorrect adjustment. 42278 42279 42280 while (Math.abs(reference - value) > RO_THRESH) { 42281 value += direction * MAX_TS; 42282 } 42283 42284 return value; 42285 }; 42286 42287 var TimestampRolloverStream = function TimestampRolloverStream(type) { 42288 var lastDTS, referenceDTS; 42289 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 42290 // video and audio. We could use `undefined` here, but having a string 42291 // makes debugging a little clearer. 42292 42293 this.type_ = type || TYPE_SHARED; 42294 42295 this.push = function (data) { 42296 // Any "shared" rollover streams will accept _all_ data. Otherwise, 42297 // streams will only accept data that matches their type. 42298 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 42299 return; 42300 } 42301 42302 if (referenceDTS === undefined) { 42303 referenceDTS = data.dts; 42304 } 42305 42306 data.dts = handleRollover(data.dts, referenceDTS); 42307 data.pts = handleRollover(data.pts, referenceDTS); 42308 lastDTS = data.dts; 42309 this.trigger('data', data); 42310 }; 42311 42312 this.flush = function () { 42313 referenceDTS = lastDTS; 42314 this.trigger('done'); 42315 }; 42316 42317 this.endTimeline = function () { 42318 this.flush(); 42319 this.trigger('endedtimeline'); 42320 }; 42321 42322 this.discontinuity = function () { 42323 referenceDTS = void 0; 42324 lastDTS = void 0; 42325 }; 42326 42327 this.reset = function () { 42328 this.discontinuity(); 42329 this.trigger('reset'); 42330 }; 42331 }; 42332 42333 TimestampRolloverStream.prototype = new stream(); 42334 var timestampRolloverStream = { 42335 TimestampRolloverStream: TimestampRolloverStream, 42336 handleRollover: handleRollover 42337 }; 42338 42339 var percentEncode = function percentEncode(bytes, start, end) { 42340 var i, 42341 result = ''; 42342 42343 for (i = start; i < end; i++) { 42344 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 42345 } 42346 42347 return result; 42348 }, 42349 // return the string representation of the specified byte range, 42350 // interpreted as UTf-8. 42351 parseUtf8 = function parseUtf8(bytes, start, end) { 42352 return decodeURIComponent(percentEncode(bytes, start, end)); 42353 }, 42354 // return the string representation of the specified byte range, 42355 // interpreted as ISO-8859-1. 42356 parseIso88591 = function parseIso88591(bytes, start, end) { 42357 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 42358 }, 42359 parseSyncSafeInteger = function parseSyncSafeInteger(data) { 42360 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 42361 }, 42362 tagParsers = { 42363 TXXX: function TXXX(tag) { 42364 var i; 42365 42366 if (tag.data[0] !== 3) { 42367 // ignore frames with unrecognized character encodings 42368 return; 42369 } 42370 42371 for (i = 1; i < tag.data.length; i++) { 42372 if (tag.data[i] === 0) { 42373 // parse the text fields 42374 tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value 42375 42376 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 42377 break; 42378 } 42379 } 42380 42381 tag.data = tag.value; 42382 }, 42383 WXXX: function WXXX(tag) { 42384 var i; 42385 42386 if (tag.data[0] !== 3) { 42387 // ignore frames with unrecognized character encodings 42388 return; 42389 } 42390 42391 for (i = 1; i < tag.data.length; i++) { 42392 if (tag.data[i] === 0) { 42393 // parse the description and URL fields 42394 tag.description = parseUtf8(tag.data, 1, i); 42395 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 42396 break; 42397 } 42398 } 42399 }, 42400 PRIV: function PRIV(tag) { 42401 var i; 42402 42403 for (i = 0; i < tag.data.length; i++) { 42404 if (tag.data[i] === 0) { 42405 // parse the description and URL fields 42406 tag.owner = parseIso88591(tag.data, 0, i); 42407 break; 42408 } 42409 } 42410 42411 tag.privateData = tag.data.subarray(i + 1); 42412 tag.data = tag.privateData; 42413 } 42414 }, 42415 _MetadataStream; 42416 42417 _MetadataStream = function MetadataStream(options) { 42418 var settings = { 42419 debug: !!(options && options.debug), 42420 // the bytes of the program-level descriptor field in MP2T 42421 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 42422 // program element descriptors" 42423 descriptor: options && options.descriptor 42424 }, 42425 // the total size in bytes of the ID3 tag being parsed 42426 tagSize = 0, 42427 // tag data that is not complete enough to be parsed 42428 buffer = [], 42429 // the total number of bytes currently in the buffer 42430 bufferSize = 0, 42431 i; 42432 42433 _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type 42434 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 42435 42436 42437 this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16); 42438 42439 if (settings.descriptor) { 42440 for (i = 0; i < settings.descriptor.length; i++) { 42441 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 42442 } 42443 } 42444 42445 this.push = function (chunk) { 42446 var tag, frameStart, frameSize, frame, i, frameHeader; 42447 42448 if (chunk.type !== 'timed-metadata') { 42449 return; 42450 } // if data_alignment_indicator is set in the PES header, 42451 // we must have the start of a new ID3 tag. Assume anything 42452 // remaining in the buffer was malformed and throw it out 42453 42454 42455 if (chunk.dataAlignmentIndicator) { 42456 bufferSize = 0; 42457 buffer.length = 0; 42458 } // ignore events that don't look like ID3 data 42459 42460 42461 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))) { 42462 if (settings.debug) { 42463 // eslint-disable-next-line no-console 42464 console.log('Skipping unrecognized metadata packet'); 42465 } 42466 42467 return; 42468 } // add this chunk to the data we've collected so far 42469 42470 42471 buffer.push(chunk); 42472 bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header 42473 42474 if (buffer.length === 1) { 42475 // the frame size is transmitted as a 28-bit integer in the 42476 // last four bytes of the ID3 header. 42477 // The most significant bit of each byte is dropped and the 42478 // results concatenated to recover the actual value. 42479 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more 42480 // convenient for our comparisons to include it 42481 42482 tagSize += 10; 42483 } // if the entire frame has not arrived, wait for more data 42484 42485 42486 if (bufferSize < tagSize) { 42487 return; 42488 } // collect the entire frame so it can be parsed 42489 42490 42491 tag = { 42492 data: new Uint8Array(tagSize), 42493 frames: [], 42494 pts: buffer[0].pts, 42495 dts: buffer[0].dts 42496 }; 42497 42498 for (i = 0; i < tagSize;) { 42499 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 42500 i += buffer[0].data.byteLength; 42501 bufferSize -= buffer[0].data.byteLength; 42502 buffer.shift(); 42503 } // find the start of the first frame and the end of the tag 42504 42505 42506 frameStart = 10; 42507 42508 if (tag.data[5] & 0x40) { 42509 // advance the frame start past the extended header 42510 frameStart += 4; // header size field 42511 42512 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end 42513 42514 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 42515 } // parse one or more ID3 frames 42516 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 42517 42518 42519 do { 42520 // determine the number of bytes in this frame 42521 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 42522 42523 if (frameSize < 1) { 42524 // eslint-disable-next-line no-console 42525 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 42526 } 42527 42528 frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]); 42529 frame = { 42530 id: frameHeader, 42531 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 42532 }; 42533 frame.key = frame.id; 42534 42535 if (tagParsers[frame.id]) { 42536 tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start 42537 // time for raw AAC data 42538 42539 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 42540 var d = frame.data, 42541 size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 42542 size *= 4; 42543 size += d[7] & 0x03; 42544 frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based 42545 // on the value of this frame 42546 // we couldn't have known the appropriate pts and dts before 42547 // parsing this ID3 tag so set those values now 42548 42549 if (tag.pts === undefined && tag.dts === undefined) { 42550 tag.pts = frame.timeStamp; 42551 tag.dts = frame.timeStamp; 42552 } 42553 42554 this.trigger('timestamp', frame); 42555 } 42556 } 42557 42558 tag.frames.push(frame); 42559 frameStart += 10; // advance past the frame header 42560 42561 frameStart += frameSize; // advance past the frame body 42562 } while (frameStart < tagSize); 42563 42564 this.trigger('data', tag); 42565 }; 42566 }; 42567 42568 _MetadataStream.prototype = new stream(); 42569 var metadataStream = _MetadataStream; 42570 var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types 42571 42572 var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants 42573 42574 42575 var MP2T_PACKET_LENGTH = 188, 42576 // bytes 42577 SYNC_BYTE = 0x47; 42578 /** 42579 * Splits an incoming stream of binary data into MPEG-2 Transport 42580 * Stream packets. 42581 */ 42582 42583 _TransportPacketStream = function TransportPacketStream() { 42584 var buffer = new Uint8Array(MP2T_PACKET_LENGTH), 42585 bytesInBuffer = 0; 42586 42587 _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream. 42588 42589 /** 42590 * Split a stream of data into M2TS packets 42591 **/ 42592 42593 42594 this.push = function (bytes) { 42595 var startIndex = 0, 42596 endIndex = MP2T_PACKET_LENGTH, 42597 everything; // If there are bytes remaining from the last segment, prepend them to the 42598 // bytes that were pushed in 42599 42600 if (bytesInBuffer) { 42601 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 42602 everything.set(buffer.subarray(0, bytesInBuffer)); 42603 everything.set(bytes, bytesInBuffer); 42604 bytesInBuffer = 0; 42605 } else { 42606 everything = bytes; 42607 } // While we have enough data for a packet 42608 42609 42610 while (endIndex < everything.byteLength) { 42611 // Look for a pair of start and end sync bytes in the data.. 42612 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 42613 // We found a packet so emit it and jump one whole packet forward in 42614 // the stream 42615 this.trigger('data', everything.subarray(startIndex, endIndex)); 42616 startIndex += MP2T_PACKET_LENGTH; 42617 endIndex += MP2T_PACKET_LENGTH; 42618 continue; 42619 } // If we get here, we have somehow become de-synchronized and we need to step 42620 // forward one byte at a time until we find a pair of sync bytes that denote 42621 // a packet 42622 42623 42624 startIndex++; 42625 endIndex++; 42626 } // If there was some data left over at the end of the segment that couldn't
vendor: 17,064 bytes, lines 42627-43062
42627 // possibly be a whole packet, keep it because it might be the start of a packet 42628 // that continues in the next segment 42629 42630 42631 if (startIndex < everything.byteLength) { 42632 buffer.set(everything.subarray(startIndex), 0); 42633 bytesInBuffer = everything.byteLength - startIndex; 42634 } 42635 }; 42636 /** 42637 * Passes identified M2TS packets to the TransportParseStream to be parsed 42638 **/ 42639 42640 42641 this.flush = function () { 42642 // If the buffer contains a whole packet when we are being flushed, emit it 42643 // and empty the buffer. Otherwise hold onto the data because it may be 42644 // important for decoding the next segment 42645 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 42646 this.trigger('data', buffer); 42647 bytesInBuffer = 0; 42648 } 42649 42650 this.trigger('done'); 42651 }; 42652 42653 this.endTimeline = function () { 42654 this.flush(); 42655 this.trigger('endedtimeline'); 42656 }; 42657 42658 this.reset = function () { 42659 bytesInBuffer = 0; 42660 this.trigger('reset'); 42661 }; 42662 }; 42663 42664 _TransportPacketStream.prototype = new stream(); 42665 /** 42666 * Accepts an MP2T TransportPacketStream and emits data events with parsed 42667 * forms of the individual transport stream packets. 42668 */ 42669 42670 _TransportParseStream = function TransportParseStream() { 42671 var parsePsi, parsePat, parsePmt, self; 42672 42673 _TransportParseStream.prototype.init.call(this); 42674 42675 self = this; 42676 this.packetsWaitingForPmt = []; 42677 this.programMapTable = undefined; 42678 42679 parsePsi = function parsePsi(payload, psi) { 42680 var offset = 0; // PSI packets may be split into multiple sections and those 42681 // sections may be split into multiple packets. If a PSI 42682 // section starts in this packet, the payload_unit_start_indicator 42683 // will be true and the first byte of the payload will indicate 42684 // the offset from the current position to the start of the 42685 // section. 42686 42687 if (psi.payloadUnitStartIndicator) { 42688 offset += payload[offset] + 1; 42689 } 42690 42691 if (psi.type === 'pat') { 42692 parsePat(payload.subarray(offset), psi); 42693 } else { 42694 parsePmt(payload.subarray(offset), psi); 42695 } 42696 }; 42697 42698 parsePat = function parsePat(payload, pat) { 42699 pat.section_number = payload[7]; // eslint-disable-line camelcase 42700 42701 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 42702 // skip the PSI header and parse the first PMT entry 42703 42704 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 42705 pat.pmtPid = self.pmtPid; 42706 }; 42707 /** 42708 * Parse out the relevant fields of a Program Map Table (PMT). 42709 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 42710 * packet. The first byte in this array should be the table_id 42711 * field. 42712 * @param pmt {object} the object that should be decorated with 42713 * fields parsed from the PMT. 42714 */ 42715 42716 42717 parsePmt = function parsePmt(payload, pmt) { 42718 var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually 42719 // take effect. We don't believe this should ever be the case 42720 // for HLS but we'll ignore "forward" PMT declarations if we see 42721 // them. Future PMT declarations have the current_next_indicator 42722 // set to zero. 42723 42724 if (!(payload[5] & 0x01)) { 42725 return; 42726 } // overwrite any existing program map table 42727 42728 42729 self.programMapTable = { 42730 video: null, 42731 audio: null, 42732 'timed-metadata': {} 42733 }; // the mapping table ends at the end of the current section 42734 42735 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 42736 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 42737 // long the program info descriptors are 42738 42739 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table 42740 42741 offset = 12 + programInfoLength; 42742 42743 while (offset < tableEnd) { 42744 var streamType = payload[offset]; 42745 var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types 42746 // TODO: should this be done for metadata too? for now maintain behavior of 42747 // multiple metadata streams 42748 42749 if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) { 42750 self.programMapTable.video = pid; 42751 } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) { 42752 self.programMapTable.audio = pid; 42753 } else if (streamType === streamTypes.METADATA_STREAM_TYPE) { 42754 // map pid to stream type for metadata streams 42755 self.programMapTable['timed-metadata'][pid] = streamType; 42756 } // move to the next table entry 42757 // skip past the elementary stream descriptors, if present 42758 42759 42760 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 42761 } // record the map on the packet as well 42762 42763 42764 pmt.programMapTable = self.programMapTable; 42765 }; 42766 /** 42767 * Deliver a new MP2T packet to the next stream in the pipeline. 42768 */ 42769 42770 42771 this.push = function (packet) { 42772 var result = {}, 42773 offset = 4; 42774 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1] 42775 42776 result.pid = packet[1] & 0x1f; 42777 result.pid <<= 8; 42778 result.pid |= packet[2]; // if an adaption field is present, its length is specified by the 42779 // fifth byte of the TS packet header. The adaptation field is 42780 // used to add stuffing to PES packets that don't fill a complete 42781 // TS packet, and to specify some forms of timing and control data 42782 // that we do not currently use. 42783 42784 if ((packet[3] & 0x30) >>> 4 > 0x01) { 42785 offset += packet[offset] + 1; 42786 } // parse the rest of the packet based on the type 42787 42788 42789 if (result.pid === 0) { 42790 result.type = 'pat'; 42791 parsePsi(packet.subarray(offset), result); 42792 this.trigger('data', result); 42793 } else if (result.pid === this.pmtPid) { 42794 result.type = 'pmt'; 42795 parsePsi(packet.subarray(offset), result); 42796 this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now 42797 42798 while (this.packetsWaitingForPmt.length) { 42799 this.processPes_.apply(this, this.packetsWaitingForPmt.shift()); 42800 } 42801 } else if (this.programMapTable === undefined) { 42802 // When we have not seen a PMT yet, defer further processing of 42803 // PES packets until one has been parsed 42804 this.packetsWaitingForPmt.push([packet, offset, result]); 42805 } else { 42806 this.processPes_(packet, offset, result); 42807 } 42808 }; 42809 42810 this.processPes_ = function (packet, offset, result) { 42811 // set the appropriate stream type 42812 if (result.pid === this.programMapTable.video) { 42813 result.streamType = streamTypes.H264_STREAM_TYPE; 42814 } else if (result.pid === this.programMapTable.audio) { 42815 result.streamType = streamTypes.ADTS_STREAM_TYPE; 42816 } else { 42817 // if not video or audio, it is timed-metadata or unknown 42818 // if unknown, streamType will be undefined 42819 result.streamType = this.programMapTable['timed-metadata'][result.pid]; 42820 } 42821 42822 result.type = 'pes'; 42823 result.data = packet.subarray(offset); 42824 this.trigger('data', result); 42825 }; 42826 }; 42827 42828 _TransportParseStream.prototype = new stream(); 42829 _TransportParseStream.STREAM_TYPES = { 42830 h264: 0x1b, 42831 adts: 0x0f 42832 }; 42833 /** 42834 * Reconsistutes program elementary stream (PES) packets from parsed 42835 * transport stream packets. That is, if you pipe an 42836 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 42837 * events will be events which capture the bytes for individual PES 42838 * packets plus relevant metadata that has been extracted from the 42839 * container. 42840 */ 42841 42842 _ElementaryStream = function ElementaryStream() { 42843 var self = this, 42844 // PES packet fragments 42845 video = { 42846 data: [], 42847 size: 0 42848 }, 42849 audio = { 42850 data: [], 42851 size: 0 42852 }, 42853 timedMetadata = { 42854 data: [], 42855 size: 0 42856 }, 42857 programMapTable, 42858 parsePes = function parsePes(payload, pes) { 42859 var ptsDtsFlags; // get the packet length, this will be 0 for video 42860 42861 pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe 42862 42863 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value 42864 // and a DTS value. Determine what combination of values is 42865 // available to work with. 42866 42867 ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 42868 // performs all bitwise operations on 32-bit integers but javascript 42869 // supports a much greater range (52-bits) of integer using standard 42870 // mathematical operations. 42871 // We construct a 31-bit value using bitwise operators over the 31 42872 // most significant bits and then multiply by 4 (equal to a left-shift 42873 // of 2) before we add the final 2 least significant bits of the 42874 // timestamp (equal to an OR.) 42875 42876 if (ptsDtsFlags & 0xC0) { 42877 // the PTS and DTS are not written out directly. For information 42878 // on how they are encoded, see 42879 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 42880 pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3; 42881 pes.pts *= 4; // Left shift by 2 42882 42883 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 42884 42885 pes.dts = pes.pts; 42886 42887 if (ptsDtsFlags & 0x40) { 42888 pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3; 42889 pes.dts *= 4; // Left shift by 2 42890 42891 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 42892 } 42893 } // the data section starts immediately after the PES header. 42894 // pes_header_data_length specifies the number of header bytes 42895 // that follow the last byte of the field. 42896 42897 42898 pes.data = payload.subarray(9 + payload[8]); 42899 }, 42900 42901 /** 42902 * Pass completely parsed PES packets to the next stream in the pipeline 42903 **/ 42904 flushStream = function flushStream(stream$$1, type, forceFlush) { 42905 var packetData = new Uint8Array(stream$$1.size), 42906 event = { 42907 type: type 42908 }, 42909 i = 0, 42910 offset = 0, 42911 packetFlushable = false, 42912 fragment; // do nothing if there is not enough buffered data for a complete 42913 // PES header 42914 42915 if (!stream$$1.data.length || stream$$1.size < 9) { 42916 return; 42917 } 42918 42919 event.trackId = stream$$1.data[0].pid; // reassemble the packet 42920 42921 for (i = 0; i < stream$$1.data.length; i++) { 42922 fragment = stream$$1.data[i]; 42923 packetData.set(fragment.data, offset); 42924 offset += fragment.data.byteLength; 42925 } // parse assembled packet's PES header 42926 42927 42928 parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length 42929 // check that there is enough stream data to fill the packet 42930 42931 packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right 42932 42933 if (forceFlush || packetFlushable) { 42934 stream$$1.size = 0; 42935 stream$$1.data.length = 0; 42936 } // only emit packets that are complete. this is to avoid assembling 42937 // incomplete PES packets due to poor segmentation 42938 42939 42940 if (packetFlushable) { 42941 self.trigger('data', event); 42942 } 42943 }; 42944 42945 _ElementaryStream.prototype.init.call(this); 42946 /** 42947 * Identifies M2TS packet types and parses PES packets using metadata 42948 * parsed from the PMT 42949 **/ 42950 42951 42952 this.push = function (data) { 42953 ({ 42954 pat: function pat() {// we have to wait for the PMT to arrive as well before we 42955 // have any meaningful metadata 42956 }, 42957 pes: function pes() { 42958 var stream$$1, streamType; 42959 42960 switch (data.streamType) { 42961 case streamTypes.H264_STREAM_TYPE: 42962 case streamTypes.H264_STREAM_TYPE: 42963 stream$$1 = video; 42964 streamType = 'video'; 42965 break; 42966 42967 case streamTypes.ADTS_STREAM_TYPE: 42968 stream$$1 = audio; 42969 streamType = 'audio'; 42970 break; 42971 42972 case streamTypes.METADATA_STREAM_TYPE: 42973 stream$$1 = timedMetadata; 42974 streamType = 'timed-metadata'; 42975 break; 42976 42977 default: 42978 // ignore unknown stream types 42979 return; 42980 } // if a new packet is starting, we can flush the completed 42981 // packet 42982 42983 42984 if (data.payloadUnitStartIndicator) { 42985 flushStream(stream$$1, streamType, true); 42986 } // buffer this fragment until we are sure we've received the 42987 // complete payload 42988 42989 42990 stream$$1.data.push(data); 42991 stream$$1.size += data.data.byteLength; 42992 }, 42993 pmt: function pmt() { 42994 var event = { 42995 type: 'metadata', 42996 tracks: [] 42997 }; 42998 programMapTable = data.programMapTable; // translate audio and video streams to tracks 42999 43000 if (programMapTable.video !== null) { 43001 event.tracks.push({ 43002 timelineStartInfo: { 43003 baseMediaDecodeTime: 0 43004 }, 43005 id: +programMapTable.video, 43006 codec: 'avc', 43007 type: 'video' 43008 }); 43009 } 43010 43011 if (programMapTable.audio !== null) { 43012 event.tracks.push({ 43013 timelineStartInfo: { 43014 baseMediaDecodeTime: 0 43015 }, 43016 id: +programMapTable.audio, 43017 codec: 'adts', 43018 type: 'audio' 43019 }); 43020 } 43021 43022 self.trigger('data', event); 43023 } 43024 })[data.type](); 43025 }; 43026 43027 this.reset = function () { 43028 video.size = 0; 43029 video.data.length = 0; 43030 audio.size = 0; 43031 audio.data.length = 0; 43032 this.trigger('reset'); 43033 }; 43034 /** 43035 * Flush any remaining input. Video PES packets may be of variable 43036 * length. Normally, the start of a new video packet can trigger the 43037 * finalization of the previous packet. That is not possible if no 43038 * more video is forthcoming, however. In that case, some other 43039 * mechanism (like the end of the file) has to be employed. When it is 43040 * clear that no additional data is forthcoming, calling this method 43041 * will flush the buffered packets. 43042 */ 43043 43044 43045 this.flushStreams_ = function () { 43046 // !!THIS ORDER IS IMPORTANT!! 43047 // video first then audio 43048 flushStream(video, 'video'); 43049 flushStream(audio, 'audio'); 43050 flushStream(timedMetadata, 'timed-metadata'); 43051 }; 43052 43053 this.flush = function () { 43054 this.flushStreams_(); 43055 this.trigger('done'); 43056 }; 43057 }; 43058 43059 _ElementaryStream.prototype = new stream(); 43060 var m2ts = { 43061 PAT_PID: 0x0000, 43062 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
vendor: 6,108 bytes, lines 43063-43230
43063 TransportPacketStream: _TransportPacketStream, 43064 TransportParseStream: _TransportParseStream, 43065 ElementaryStream: _ElementaryStream, 43066 TimestampRolloverStream: TimestampRolloverStream$1, 43067 CaptionStream: captionStream.CaptionStream, 43068 Cea608Stream: captionStream.Cea608Stream, 43069 MetadataStream: metadataStream 43070 }; 43071 43072 for (var type in streamTypes) { 43073 if (streamTypes.hasOwnProperty(type)) { 43074 m2ts[type] = streamTypes[type]; 43075 } 43076 } 43077 43078 var m2ts_1 = m2ts; 43079 var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS; 43080 43081 var _AdtsStream; 43082 43083 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 43084 /* 43085 * Accepts a ElementaryStream and emits data events with parsed 43086 * AAC Audio Frames of the individual packets. Input audio in ADTS 43087 * format is unpacked and re-emitted as AAC frames. 43088 * 43089 * @see http://wiki.multimedia.cx/index.php?title=ADTS 43090 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 43091 */ 43092 43093 _AdtsStream = function AdtsStream(handlePartialSegments) { 43094 var buffer, 43095 frameNum = 0; 43096 43097 _AdtsStream.prototype.init.call(this); 43098 43099 this.push = function (packet) { 43100 var i = 0, 43101 frameLength, 43102 protectionSkipBytes, 43103 frameEnd, 43104 oldBuffer, 43105 sampleCount, 43106 adtsFrameDuration; 43107 43108 if (!handlePartialSegments) { 43109 frameNum = 0; 43110 } 43111 43112 if (packet.type !== 'audio') { 43113 // ignore non-audio data 43114 return; 43115 } // Prepend any data in the buffer to the input data so that we can parse 43116 // aac frames the cross a PES packet boundary 43117 43118 43119 if (buffer) { 43120 oldBuffer = buffer; 43121 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 43122 buffer.set(oldBuffer); 43123 buffer.set(packet.data, oldBuffer.byteLength); 43124 } else { 43125 buffer = packet.data; 43126 } // unpack any ADTS frames which have been fully received 43127 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 43128 43129 43130 while (i + 5 < buffer.length) { 43131 // Look for the start of an ADTS header.. 43132 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 43133 // If a valid header was not found, jump one forward and attempt to 43134 // find a valid ADTS header starting at the next byte 43135 i++; 43136 continue; 43137 } // The protection skip bit tells us if we have 2 bytes of CRC data at the 43138 // end of the ADTS header 43139 43140 43141 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the 43142 // end of the sync sequence 43143 43144 frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5; 43145 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 43146 adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 43147 frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return 43148 // and wait for more data 43149 43150 if (buffer.byteLength < frameEnd) { 43151 return; 43152 } // Otherwise, deliver the complete AAC frame 43153 43154 43155 this.trigger('data', { 43156 pts: packet.pts + frameNum * adtsFrameDuration, 43157 dts: packet.dts + frameNum * adtsFrameDuration, 43158 sampleCount: sampleCount, 43159 audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1, 43160 channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6, 43161 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 43162 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 43163 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 43164 samplesize: 16, 43165 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 43166 }); 43167 frameNum++; // If the buffer is empty, clear it and return 43168 43169 if (buffer.byteLength === frameEnd) { 43170 buffer = undefined; 43171 return; 43172 } // Remove the finished frame from the buffer and start the process again 43173 43174 43175 buffer = buffer.subarray(frameEnd); 43176 } 43177 }; 43178 43179 this.flush = function () { 43180 frameNum = 0; 43181 this.trigger('done'); 43182 }; 43183 43184 this.reset = function () { 43185 buffer = void 0; 43186 this.trigger('reset'); 43187 }; 43188 43189 this.endTimeline = function () { 43190 buffer = void 0; 43191 this.trigger('endedtimeline'); 43192 }; 43193 }; 43194 43195 _AdtsStream.prototype = new stream(); 43196 var adts = _AdtsStream; 43197 /** 43198 * mux.js 43199 * 43200 * Copyright (c) Brightcove 43201 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43202 */ 43203 43204 var ExpGolomb; 43205 /** 43206 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 43207 * scheme used by h264. 43208 */ 43209 43210 ExpGolomb = function ExpGolomb(workingData) { 43211 var // the number of bytes left to examine in workingData 43212 workingBytesAvailable = workingData.byteLength, 43213 // the current word being examined 43214 workingWord = 0, 43215 // :uint 43216 // the number of bits left to examine in the current word 43217 workingBitsAvailable = 0; // :uint; 43218 // ():uint 43219 43220 this.length = function () { 43221 return 8 * workingBytesAvailable; 43222 }; // ():uint 43223 43224 43225 this.bitsAvailable = function () { 43226 return 8 * workingBytesAvailable + workingBitsAvailable; 43227 }; // ():void 43228 43229 43230 this.loadWord = function () {
vendor: 8,404 bytes, lines 43231-43508
43231 var position = workingData.byteLength - workingBytesAvailable, 43232 workingBytes = new Uint8Array(4), 43233 availableBytes = Math.min(4, workingBytesAvailable); 43234 43235 if (availableBytes === 0) { 43236 throw new Error('no bytes available'); 43237 } 43238 43239 workingBytes.set(workingData.subarray(position, position + availableBytes)); 43240 workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed 43241 43242 workingBitsAvailable = availableBytes * 8; 43243 workingBytesAvailable -= availableBytes; 43244 }; // (count:int):void 43245 43246 43247 this.skipBits = function (count) { 43248 var skipBytes; // :int 43249 43250 if (workingBitsAvailable > count) { 43251 workingWord <<= count; 43252 workingBitsAvailable -= count; 43253 } else { 43254 count -= workingBitsAvailable; 43255 skipBytes = Math.floor(count / 8); 43256 count -= skipBytes * 8; 43257 workingBytesAvailable -= skipBytes; 43258 this.loadWord(); 43259 workingWord <<= count; 43260 workingBitsAvailable -= count; 43261 } 43262 }; // (size:int):uint 43263 43264 43265 this.readBits = function (size) { 43266 var bits = Math.min(workingBitsAvailable, size), 43267 // :uint 43268 valu = workingWord >>> 32 - bits; // :uint 43269 // if size > 31, handle error 43270 43271 workingBitsAvailable -= bits; 43272 43273 if (workingBitsAvailable > 0) { 43274 workingWord <<= bits; 43275 } else if (workingBytesAvailable > 0) { 43276 this.loadWord(); 43277 } 43278 43279 bits = size - bits; 43280 43281 if (bits > 0) { 43282 return valu << bits | this.readBits(bits); 43283 } 43284 43285 return valu; 43286 }; // ():uint 43287 43288 43289 this.skipLeadingZeros = function () { 43290 var leadingZeroCount; // :uint 43291 43292 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 43293 if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) { 43294 // the first bit of working word is 1 43295 workingWord <<= leadingZeroCount; 43296 workingBitsAvailable -= leadingZeroCount; 43297 return leadingZeroCount; 43298 } 43299 } // we exhausted workingWord and still have not found a 1 43300 43301 43302 this.loadWord(); 43303 return leadingZeroCount + this.skipLeadingZeros(); 43304 }; // ():void 43305 43306 43307 this.skipUnsignedExpGolomb = function () { 43308 this.skipBits(1 + this.skipLeadingZeros()); 43309 }; // ():void 43310 43311 43312 this.skipExpGolomb = function () { 43313 this.skipBits(1 + this.skipLeadingZeros()); 43314 }; // ():uint 43315 43316 43317 this.readUnsignedExpGolomb = function () { 43318 var clz = this.skipLeadingZeros(); // :uint 43319 43320 return this.readBits(clz + 1) - 1; 43321 }; // ():int 43322 43323 43324 this.readExpGolomb = function () { 43325 var valu = this.readUnsignedExpGolomb(); // :int 43326 43327 if (0x01 & valu) { 43328 // the number is odd if the low order bit is set 43329 return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 43330 } 43331 43332 return -1 * (valu >>> 1); // divide by two then make it negative 43333 }; // Some convenience functions 43334 // :Boolean 43335 43336 43337 this.readBoolean = function () { 43338 return this.readBits(1) === 1; 43339 }; // ():int 43340 43341 43342 this.readUnsignedByte = function () { 43343 return this.readBits(8); 43344 }; 43345 43346 this.loadWord(); 43347 }; 43348 43349 var expGolomb = ExpGolomb; 43350 43351 var _H264Stream, _NalByteStream; 43352 43353 var PROFILES_WITH_OPTIONAL_SPS_DATA; 43354 /** 43355 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 43356 */ 43357 43358 _NalByteStream = function NalByteStream() { 43359 var syncPoint = 0, 43360 i, 43361 buffer; 43362 43363 _NalByteStream.prototype.init.call(this); 43364 /* 43365 * Scans a byte stream and triggers a data event with the NAL units found. 43366 * @param {Object} data Event received from H264Stream 43367 * @param {Uint8Array} data.data The h264 byte stream to be scanned 43368 * 43369 * @see H264Stream.push 43370 */ 43371 43372 43373 this.push = function (data) { 43374 var swapBuffer; 43375 43376 if (!buffer) { 43377 buffer = data.data; 43378 } else { 43379 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 43380 swapBuffer.set(buffer); 43381 swapBuffer.set(data.data, buffer.byteLength); 43382 buffer = swapBuffer; 43383 } 43384 43385 var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B 43386 // scan for NAL unit boundaries 43387 // a match looks like this: 43388 // 0 0 1 .. NAL .. 0 0 1 43389 // ^ sync point ^ i 43390 // or this: 43391 // 0 0 1 .. NAL .. 0 0 0 43392 // ^ sync point ^ i 43393 // advance the sync point to a NAL start, if necessary 43394 43395 for (; syncPoint < len - 3; syncPoint++) { 43396 if (buffer[syncPoint + 2] === 1) { 43397 // the sync point is properly aligned 43398 i = syncPoint + 5; 43399 break; 43400 } 43401 } 43402 43403 while (i < len) { 43404 // look at the current byte to determine if we've hit the end of 43405 // a NAL unit boundary 43406 switch (buffer[i]) { 43407 case 0: 43408 // skip past non-sync sequences 43409 if (buffer[i - 1] !== 0) { 43410 i += 2; 43411 break; 43412 } else if (buffer[i - 2] !== 0) { 43413 i++; 43414 break; 43415 } // deliver the NAL unit if it isn't empty 43416 43417 43418 if (syncPoint + 3 !== i - 2) { 43419 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 43420 } // drop trailing zeroes 43421 43422 43423 do { 43424 i++; 43425 } while (buffer[i] !== 1 && i < len); 43426 43427 syncPoint = i - 2; 43428 i += 3; 43429 break; 43430 43431 case 1: 43432 // skip past non-sync sequences 43433 if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) { 43434 i += 3; 43435 break; 43436 } // deliver the NAL unit 43437 43438 43439 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 43440 syncPoint = i - 2; 43441 i += 3; 43442 break; 43443 43444 default: 43445 // the current byte isn't a one or zero, so it cannot be part 43446 // of a sync sequence 43447 i += 3; 43448 break; 43449 } 43450 } // filter out the NAL units that were delivered 43451 43452 43453 buffer = buffer.subarray(syncPoint); 43454 i -= syncPoint; 43455 syncPoint = 0; 43456 }; 43457 43458 this.reset = function () { 43459 buffer = null; 43460 syncPoint = 0; 43461 this.trigger('reset'); 43462 }; 43463 43464 this.flush = function () { 43465 // deliver the last buffered NAL unit 43466 if (buffer && buffer.byteLength > 3) { 43467 this.trigger('data', buffer.subarray(syncPoint + 3)); 43468 } // reset the stream state 43469 43470 43471 buffer = null; 43472 syncPoint = 0; 43473 this.trigger('done'); 43474 }; 43475 43476 this.endTimeline = function () { 43477 this.flush(); 43478 this.trigger('endedtimeline'); 43479 }; 43480 }; 43481 43482 _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS 43483 // see Recommendation ITU-T H.264 (4/2013), 43484 // 7.3.2.1.1 Sequence parameter set data syntax 43485 43486 PROFILES_WITH_OPTIONAL_SPS_DATA = { 43487 100: true, 43488 110: true, 43489 122: true, 43490 244: true, 43491 44: true, 43492 83: true, 43493 86: true, 43494 118: true, 43495 128: true, 43496 138: true, 43497 139: true, 43498 134: true 43499 }; 43500 /** 43501 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 43502 * events. 43503 */ 43504 43505 _H264Stream = function H264Stream() { 43506 var nalByteStream = new _NalByteStream(), 43507 self, 43508 trackId,
43509 currentPts, 43510 currentDts, 43511 discardEmulationPreventionBytes, 43512 readSequenceParameterSet, 43513 skipScalingList; 43514 43515 _H264Stream.prototype.init.call(this); 43516 43517 self = this; 43518 /* 43519 * Pushes a packet from a stream onto the NalByteStream 43520 * 43521 * @param {Object} packet - A packet received from a stream 43522 * @param {Uint8Array} packet.data - The raw bytes of the packet 43523 * @param {Number} packet.dts - Decode timestamp of the packet 43524 * @param {Number} packet.pts - Presentation timestamp of the packet 43525 * @param {Number} packet.trackId - The id of the h264 track this packet came from 43526 * @param {('video'|'audio')} packet.type - The type of packet 43527 * 43528 */ 43529 43530 this.push = function (packet) { 43531 if (packet.type !== 'video') { 43532 return; 43533 } 43534 43535 trackId = packet.trackId; 43536 currentPts = packet.pts; 43537 currentDts = packet.dts; 43538 nalByteStream.push(packet); 43539 }; 43540 /* 43541 * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps 43542 * for the NALUs to the next stream component. 43543 * Also, preprocess caption and sequence parameter NALUs. 43544 * 43545 * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push` 43546 * @see NalByteStream.push 43547 */ 43548 43549 43550 nalByteStream.on('data', function (data) { 43551 var event = { 43552 trackId: trackId,
vendor: 16,384 bytes, lines 43553-44023
43553 pts: currentPts, 43554 dts: currentDts, 43555 data: data 43556 }; 43557 43558 switch (data[0] & 0x1f) { 43559 case 0x05: 43560 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 43561 break; 43562 43563 case 0x06: 43564 event.nalUnitType = 'sei_rbsp'; 43565 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 43566 break; 43567 43568 case 0x07: 43569 event.nalUnitType = 'seq_parameter_set_rbsp'; 43570 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 43571 event.config = readSequenceParameterSet(event.escapedRBSP); 43572 break; 43573 43574 case 0x08: 43575 event.nalUnitType = 'pic_parameter_set_rbsp'; 43576 break; 43577 43578 case 0x09: 43579 event.nalUnitType = 'access_unit_delimiter_rbsp'; 43580 break; 43581 43582 default: 43583 break; 43584 } // This triggers data on the H264Stream 43585 43586 43587 self.trigger('data', event); 43588 }); 43589 nalByteStream.on('done', function () { 43590 self.trigger('done'); 43591 }); 43592 nalByteStream.on('partialdone', function () { 43593 self.trigger('partialdone'); 43594 }); 43595 nalByteStream.on('reset', function () { 43596 self.trigger('reset'); 43597 }); 43598 nalByteStream.on('endedtimeline', function () { 43599 self.trigger('endedtimeline'); 43600 }); 43601 43602 this.flush = function () { 43603 nalByteStream.flush(); 43604 }; 43605 43606 this.partialFlush = function () { 43607 nalByteStream.partialFlush(); 43608 }; 43609 43610 this.reset = function () { 43611 nalByteStream.reset(); 43612 }; 43613 43614 this.endTimeline = function () { 43615 nalByteStream.endTimeline(); 43616 }; 43617 /** 43618 * Advance the ExpGolomb decoder past a scaling list. The scaling 43619 * list is optionally transmitted as part of a sequence parameter 43620 * set and is not relevant to transmuxing. 43621 * @param count {number} the number of entries in this scaling list 43622 * @param expGolombDecoder {object} an ExpGolomb pointed to the 43623 * start of a scaling list 43624 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 43625 */ 43626 43627 43628 skipScalingList = function skipScalingList(count, expGolombDecoder) { 43629 var lastScale = 8, 43630 nextScale = 8, 43631 j, 43632 deltaScale; 43633 43634 for (j = 0; j < count; j++) { 43635 if (nextScale !== 0) { 43636 deltaScale = expGolombDecoder.readExpGolomb(); 43637 nextScale = (lastScale + deltaScale + 256) % 256; 43638 } 43639 43640 lastScale = nextScale === 0 ? lastScale : nextScale; 43641 } 43642 }; 43643 /** 43644 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 43645 * Sequence Payload" 43646 * @param data {Uint8Array} the bytes of a RBSP from a NAL 43647 * unit 43648 * @return {Uint8Array} the RBSP without any Emulation 43649 * Prevention Bytes 43650 */ 43651 43652 43653 discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 43654 var length = data.byteLength, 43655 emulationPreventionBytesPositions = [], 43656 i = 1, 43657 newLength, 43658 newData; // Find all `Emulation Prevention Bytes` 43659 43660 while (i < length - 2) { 43661 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 43662 emulationPreventionBytesPositions.push(i + 2); 43663 i += 2; 43664 } else { 43665 i++; 43666 } 43667 } // If no Emulation Prevention Bytes were found just return the original 43668 // array 43669 43670 43671 if (emulationPreventionBytesPositions.length === 0) { 43672 return data; 43673 } // Create a new array to hold the NAL unit data 43674 43675 43676 newLength = length - emulationPreventionBytesPositions.length; 43677 newData = new Uint8Array(newLength); 43678 var sourceIndex = 0; 43679 43680 for (i = 0; i < newLength; sourceIndex++, i++) { 43681 if (sourceIndex === emulationPreventionBytesPositions[0]) { 43682 // Skip this byte 43683 sourceIndex++; // Remove this position index 43684 43685 emulationPreventionBytesPositions.shift(); 43686 } 43687 43688 newData[i] = data[sourceIndex]; 43689 } 43690 43691 return newData; 43692 }; 43693 /** 43694 * Read a sequence parameter set and return some interesting video 43695 * properties. A sequence parameter set is the H264 metadata that 43696 * describes the properties of upcoming video frames. 43697 * @param data {Uint8Array} the bytes of a sequence parameter set 43698 * @return {object} an object with configuration parsed from the 43699 * sequence parameter set, including the dimensions of the 43700 * associated video frames. 43701 */ 43702 43703 43704 readSequenceParameterSet = function readSequenceParameterSet(data) { 43705 var frameCropLeftOffset = 0, 43706 frameCropRightOffset = 0, 43707 frameCropTopOffset = 0, 43708 frameCropBottomOffset = 0, 43709 sarScale = 1, 43710 expGolombDecoder, 43711 profileIdc, 43712 levelIdc, 43713 profileCompatibility, 43714 chromaFormatIdc, 43715 picOrderCntType, 43716 numRefFramesInPicOrderCntCycle, 43717 picWidthInMbsMinus1, 43718 picHeightInMapUnitsMinus1, 43719 frameMbsOnlyFlag, 43720 scalingListCount, 43721 sarRatio, 43722 aspectRatioIdc, 43723 i; 43724 expGolombDecoder = new expGolomb(data); 43725 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 43726 43727 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 43728 43729 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 43730 43731 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 43732 // some profiles have more optional data we don't need 43733 43734 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 43735 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 43736 43737 if (chromaFormatIdc === 3) { 43738 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 43739 } 43740 43741 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 43742 43743 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 43744 43745 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 43746 43747 if (expGolombDecoder.readBoolean()) { 43748 // seq_scaling_matrix_present_flag 43749 scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; 43750 43751 for (i = 0; i < scalingListCount; i++) { 43752 if (expGolombDecoder.readBoolean()) { 43753 // seq_scaling_list_present_flag[ i ] 43754 if (i < 6) { 43755 skipScalingList(16, expGolombDecoder); 43756 } else { 43757 skipScalingList(64, expGolombDecoder); 43758 } 43759 } 43760 } 43761 } 43762 } 43763 43764 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 43765 43766 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 43767 43768 if (picOrderCntType === 0) { 43769 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 43770 } else if (picOrderCntType === 1) { 43771 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 43772 43773 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 43774 43775 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 43776 43777 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 43778 43779 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 43780 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 43781 } 43782 } 43783 43784 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 43785 43786 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 43787 43788 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 43789 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 43790 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 43791 43792 if (frameMbsOnlyFlag === 0) { 43793 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 43794 } 43795 43796 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 43797 43798 if (expGolombDecoder.readBoolean()) { 43799 // frame_cropping_flag 43800 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 43801 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 43802 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 43803 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 43804 } 43805 43806 if (expGolombDecoder.readBoolean()) { 43807 // vui_parameters_present_flag 43808 if (expGolombDecoder.readBoolean()) { 43809 // aspect_ratio_info_present_flag 43810 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 43811 43812 switch (aspectRatioIdc) { 43813 case 1: 43814 sarRatio = [1, 1]; 43815 break; 43816 43817 case 2: 43818 sarRatio = [12, 11]; 43819 break; 43820 43821 case 3: 43822 sarRatio = [10, 11]; 43823 break; 43824 43825 case 4: 43826 sarRatio = [16, 11]; 43827 break; 43828 43829 case 5: 43830 sarRatio = [40, 33]; 43831 break; 43832 43833 case 6: 43834 sarRatio = [24, 11]; 43835 break; 43836 43837 case 7: 43838 sarRatio = [20, 11]; 43839 break; 43840 43841 case 8: 43842 sarRatio = [32, 11]; 43843 break; 43844 43845 case 9: 43846 sarRatio = [80, 33]; 43847 break; 43848 43849 case 10: 43850 sarRatio = [18, 11]; 43851 break; 43852 43853 case 11: 43854 sarRatio = [15, 11]; 43855 break; 43856 43857 case 12: 43858 sarRatio = [64, 33]; 43859 break; 43860 43861 case 13: 43862 sarRatio = [160, 99]; 43863 break; 43864 43865 case 14: 43866 sarRatio = [4, 3]; 43867 break; 43868 43869 case 15: 43870 sarRatio = [3, 2]; 43871 break; 43872 43873 case 16: 43874 sarRatio = [2, 1]; 43875 break; 43876 43877 case 255: 43878 { 43879 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()]; 43880 break; 43881 } 43882 } 43883 43884 if (sarRatio) { 43885 sarScale = sarRatio[0] / sarRatio[1]; 43886 } 43887 } 43888 } 43889 43890 return { 43891 profileIdc: profileIdc, 43892 levelIdc: levelIdc, 43893 profileCompatibility: profileCompatibility, 43894 width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 43895 height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2, 43896 sarRatio: sarRatio 43897 }; 43898 }; 43899 }; 43900 43901 _H264Stream.prototype = new stream(); 43902 var h264 = { 43903 H264Stream: _H264Stream, 43904 NalByteStream: _NalByteStream 43905 }; 43906 /** 43907 * mux.js 43908 * 43909 * Copyright (c) Brightcove 43910 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43911 * 43912 * Utilities to detect basic properties and metadata about Aac data. 43913 */ 43914 43915 var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 43916 43917 var isLikelyAacData = function isLikelyAacData(data) { 43918 if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) { 43919 return true; 43920 } 43921 43922 return false; 43923 }; 43924 43925 var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) { 43926 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 43927 }; // return a percent-encoded representation of the specified byte range 43928 // @see http://en.wikipedia.org/wiki/Percent-encoding 43929 43930 43931 var percentEncode$1 = function percentEncode(bytes, start, end) { 43932 var i, 43933 result = ''; 43934 43935 for (i = start; i < end; i++) { 43936 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 43937 } 43938 43939 return result; 43940 }; // return the string representation of the specified byte range, 43941 // interpreted as ISO-8859-1. 43942 43943 43944 var parseIso88591$1 = function parseIso88591(bytes, start, end) { 43945 return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line 43946 }; 43947 43948 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 43949 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 43950 flags = header[byteIndex + 5], 43951 footerPresent = (flags & 16) >> 4; 43952 43953 if (footerPresent) { 43954 return returnSize + 20; 43955 } 43956 43957 return returnSize + 10; 43958 }; 43959 43960 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 43961 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 43962 middle = header[byteIndex + 4] << 3, 43963 highTwo = header[byteIndex + 3] & 0x3 << 11; 43964 return highTwo | middle | lowThree; 43965 }; 43966 43967 var parseType = function parseType(header, byteIndex) { 43968 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 43969 return 'timed-metadata'; 43970 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 43971 return 'audio'; 43972 } 43973 43974 return null; 43975 }; 43976 43977 var parseSampleRate = function parseSampleRate(packet) { 43978 var i = 0; 43979 43980 while (i + 5 < packet.length) { 43981 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 43982 // If a valid header was not found, jump one forward and attempt to 43983 // find a valid ADTS header starting at the next byte 43984 i++; 43985 continue; 43986 } 43987 43988 return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2]; 43989 } 43990 43991 return null; 43992 }; 43993 43994 var parseAacTimestamp = function parseAacTimestamp(packet) { 43995 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 43996 43997 frameStart = 10; 43998 43999 if (packet[5] & 0x40) { 44000 // advance the frame start past the extended header 44001 frameStart += 4; // header size field 44002 44003 frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14)); 44004 } // parse one or more ID3 frames 44005 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 44006 44007 44008 do { 44009 // determine the number of bytes in this frame 44010 frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8)); 44011 44012 if (frameSize < 1) { 44013 return null; 44014 } 44015 44016 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 44017 44018 if (frameHeader === 'PRIV') { 44019 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 44020 44021 for (var i = 0; i < frame.byteLength; i++) { 44022 if (frame[i] === 0) { 44023 var owner = parseIso88591$1(frame,
vendor: 5,833 bytes, lines 44023-44195
440230, i); 44024 44025 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 44026 var d = frame.subarray(i + 1); 44027 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 44028 size *= 4; 44029 size += d[7] & 0x03; 44030 return size; 44031 } 44032 44033 break; 44034 } 44035 } 44036 } 44037 44038 frameStart += 10; // advance past the frame header 44039 44040 frameStart += frameSize; // advance past the frame body 44041 } while (frameStart < packet.byteLength); 44042 44043 return null; 44044 }; 44045 44046 var utils = { 44047 isLikelyAacData: isLikelyAacData, 44048 parseId3TagSize: parseId3TagSize, 44049 parseAdtsSize: parseAdtsSize, 44050 parseType: parseType, 44051 parseSampleRate: parseSampleRate, 44052 parseAacTimestamp: parseAacTimestamp 44053 }; // Constants 44054 44055 var _AacStream; 44056 /** 44057 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 44058 */ 44059 44060 44061 _AacStream = function AacStream() { 44062 var everything = new Uint8Array(), 44063 timeStamp = 0; 44064 44065 _AacStream.prototype.init.call(this); 44066 44067 this.setTimestamp = function (timestamp) { 44068 timeStamp = timestamp; 44069 }; 44070 44071 this.push = function (bytes) { 44072 var frameSize = 0, 44073 byteIndex = 0, 44074 bytesLeft, 44075 chunk, 44076 packet, 44077 tempLength; // If there are bytes remaining from the last segment, prepend them to the 44078 // bytes that were pushed in 44079 44080 if (everything.length) { 44081 tempLength = everything.length; 44082 everything = new Uint8Array(bytes.byteLength + tempLength); 44083 everything.set(everything.subarray(0, tempLength)); 44084 everything.set(bytes, tempLength); 44085 } else { 44086 everything = bytes; 44087 } 44088 44089 while (everything.length - byteIndex >= 3) { 44090 if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) { 44091 // Exit early because we don't have enough to parse 44092 // the ID3 tag header 44093 if (everything.length - byteIndex < 10) { 44094 break; 44095 } // check framesize 44096 44097 44098 frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 44099 // to emit a full packet 44100 // Add to byteIndex to support multiple ID3 tags in sequence 44101 44102 if (byteIndex + frameSize > everything.length) { 44103 break; 44104 } 44105 44106 chunk = { 44107 type: 'timed-metadata', 44108 data: everything.subarray(byteIndex, byteIndex + frameSize) 44109 }; 44110 this.trigger('data', chunk); 44111 byteIndex += frameSize; 44112 continue; 44113 } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) { 44114 // Exit early because we don't have enough to parse 44115 // the ADTS frame header 44116 if (everything.length - byteIndex < 7) { 44117 break; 44118 } 44119 44120 frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 44121 // to emit a full packet 44122 44123 if (byteIndex + frameSize > everything.length) { 44124 break; 44125 } 44126 44127 packet = { 44128 type: 'audio', 44129 data: everything.subarray(byteIndex, byteIndex + frameSize), 44130 pts: timeStamp, 44131 dts: timeStamp 44132 }; 44133 this.trigger('data', packet); 44134 byteIndex += frameSize; 44135 continue; 44136 } 44137 44138 byteIndex++; 44139 } 44140 44141 bytesLeft = everything.length - byteIndex; 44142 44143 if (bytesLeft > 0) { 44144 everything = everything.subarray(byteIndex); 44145 } else { 44146 everything = new Uint8Array(); 44147 } 44148 }; 44149 44150 this.reset = function () { 44151 everything = new Uint8Array(); 44152 this.trigger('reset'); 44153 }; 44154 44155 this.endTimeline = function () { 44156 everything = new Uint8Array(); 44157 this.trigger('endedtimeline'); 44158 }; 44159 }; 44160 44161 _AacStream.prototype = new stream(); 44162 var aac = _AacStream; 44163 var H264Stream = h264.H264Stream; 44164 var isLikelyAacData$1 = utils.isLikelyAacData; 44165 var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS; // constants 44166 44167 var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize']; 44168 var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio']; // object types 44169 44170 var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream; 44171 /** 44172 * Compare two arrays (even typed) for same-ness 44173 */ 44174 44175 44176 var arrayEquals = function arrayEquals(a, b) { 44177 var i; 44178 44179 if (a.length !== b.length) { 44180 return false; 44181 } // compare the value of each element in the array 44182 44183 44184 for (i = 0; i < a.length; i++) { 44185 if (a[i] !== b[i]) { 44186 return false; 44187 } 44188 } 44189 44190 return true; 44191 }; 44192 44193 var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) { 44194 var ptsOffsetFromDts = startPts - startDts, 44195 decodeDuration = endDts - startDts,
vendor: 6,058 bytes, lines 44196-44338
44196 presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment, 44197 // however, the player time values will reflect a start from the baseMediaDecodeTime. 44198 // In order to provide relevant values for the player times, base timing info on the 44199 // baseMediaDecodeTime and the DTS and PTS durations of the segment. 44200 44201 return { 44202 start: { 44203 dts: baseMediaDecodeTime, 44204 pts: baseMediaDecodeTime + ptsOffsetFromDts 44205 }, 44206 end: { 44207 dts: baseMediaDecodeTime + decodeDuration, 44208 pts: baseMediaDecodeTime + presentationDuration 44209 }, 44210 prependedContentDuration: prependedContentDuration, 44211 baseMediaDecodeTime: baseMediaDecodeTime 44212 }; 44213 }; 44214 /** 44215 * Constructs a single-track, ISO BMFF media segment from AAC data 44216 * events. The output of this stream can be fed to a SourceBuffer 44217 * configured with a suitable initialization segment. 44218 * @param track {object} track metadata configuration 44219 * @param options {object} transmuxer options object 44220 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 44221 * in the source; false to adjust the first segment to start at 0. 44222 */ 44223 44224 44225 _AudioSegmentStream = function AudioSegmentStream(track, options) { 44226 var adtsFrames = [], 44227 sequenceNumber = 0, 44228 earliestAllowedDts = 0, 44229 audioAppendStartTs = 0, 44230 videoBaseMediaDecodeTime = Infinity; 44231 options = options || {}; 44232 44233 _AudioSegmentStream.prototype.init.call(this); 44234 44235 this.push = function (data) { 44236 trackDecodeInfo.collectDtsInfo(track, data); 44237 44238 if (track) { 44239 AUDIO_PROPERTIES.forEach(function (prop) { 44240 track[prop] = data[prop]; 44241 }); 44242 } // buffer audio data until end() is called 44243 44244 44245 adtsFrames.push(data); 44246 }; 44247 44248 this.setEarliestDts = function (earliestDts) { 44249 earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; 44250 }; 44251 44252 this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) { 44253 videoBaseMediaDecodeTime = baseMediaDecodeTime; 44254 }; 44255 44256 this.setAudioAppendStart = function (timestamp) { 44257 audioAppendStartTs = timestamp; 44258 }; 44259 44260 this.flush = function () { 44261 var frames, moof, mdat, boxes, frameDuration; // return early if no audio data has been observed 44262 44263 if (adtsFrames.length === 0) { 44264 this.trigger('done', 'AudioSegmentStream'); 44265 return; 44266 } 44267 44268 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts); 44269 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 44270 audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to 44271 // samples (that is, adts frames) in the audio data 44272 44273 track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat 44274 44275 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames)); 44276 adtsFrames = []; 44277 moof = mp4Generator.moof(sequenceNumber, [track]); 44278 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time 44279 44280 sequenceNumber++; 44281 boxes.set(moof); 44282 boxes.set(mdat, moof.byteLength); 44283 trackDecodeInfo.clearDtsInfo(track); 44284 frameDuration = Math.ceil(ONE_SECOND_IN_TS$3 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with 44285 // tests) on adding the timingInfo event. However, it seems unlikely that there's a 44286 // valid use-case where an init segment/data should be triggered without associated 44287 // frames. Leaving for now, but should be looked into. 44288 44289 if (frames.length) { 44290 this.trigger('timingInfo', { 44291 start: frames[0].dts, 44292 end: frames[0].dts + frames.length * frameDuration 44293 }); 44294 } 44295 44296 this.trigger('data', { 44297 track: track, 44298 boxes: boxes 44299 }); 44300 this.trigger('done', 'AudioSegmentStream'); 44301 }; 44302 44303 this.reset = function () { 44304 trackDecodeInfo.clearDtsInfo(track); 44305 adtsFrames = []; 44306 this.trigger('reset'); 44307 }; 44308 }; 44309 44310 _AudioSegmentStream.prototype = new stream(); 44311 /** 44312 * Constructs a single-track, ISO BMFF media segment from H264 data 44313 * events. The output of this stream can be fed to a SourceBuffer 44314 * configured with a suitable initialization segment. 44315 * @param track {object} track metadata configuration 44316 * @param options {object} transmuxer options object 44317 * @param options.alignGopsAtEnd {boolean} If true, start from the end of the 44318 * gopsToAlignWith list when attempting to align gop pts 44319 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 44320 * in the source; false to adjust the first segment to start at 0. 44321 */ 44322 44323 _VideoSegmentStream = function VideoSegmentStream(track, options) { 44324 var sequenceNumber = 0, 44325 nalUnits = [], 44326 gopsToAlignWith = [], 44327 config, 44328 pps; 44329 options = options || {}; 44330 44331 _VideoSegmentStream.prototype.init.call(this); 44332 44333 delete track.minPTS; 44334 this.gopCache_ = []; 44335 /** 44336 * Constructs a ISO BMFF segment given H264 nalUnits 44337 * @param {Object} nalUnit A data event representing a nalUnit 44338 * @param {String} nalUnit.nalUnitType
vendor: 12,823 bytes, lines 44339-44662
44339 * @param {Object} nalUnit.config Properties for a mp4 track 44340 * @param {Uint8Array} nalUnit.data The nalUnit bytes 44341 * @see lib/codecs/h264.js 44342 **/ 44343 44344 this.push = function (nalUnit) { 44345 trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config 44346 44347 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 44348 config = nalUnit.config; 44349 track.sps = [nalUnit.data]; 44350 VIDEO_PROPERTIES.forEach(function (prop) { 44351 track[prop] = config[prop]; 44352 }, this); 44353 } 44354 44355 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) { 44356 pps = nalUnit.data; 44357 track.pps = [nalUnit.data]; 44358 } // buffer video until flush() is called 44359 44360 44361 nalUnits.push(nalUnit); 44362 }; 44363 /** 44364 * Pass constructed ISO BMFF track and boxes on to the 44365 * next stream in the pipeline 44366 **/ 44367 44368 44369 this.flush = function () { 44370 var frames, 44371 gopForFusion, 44372 gops, 44373 moof, 44374 mdat, 44375 boxes, 44376 prependedContentDuration = 0, 44377 firstGop, 44378 lastGop; // Throw away nalUnits at the start of the byte stream until 44379 // we find the first AUD 44380 44381 while (nalUnits.length) { 44382 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 44383 break; 44384 } 44385 44386 nalUnits.shift(); 44387 } // Return early if no video data has been observed 44388 44389 44390 if (nalUnits.length === 0) { 44391 this.resetStream_(); 44392 this.trigger('done', 'VideoSegmentStream'); 44393 return; 44394 } // Organize the raw nal-units into arrays that represent 44395 // higher-level constructs such as frames and gops 44396 // (group-of-pictures) 44397 44398 44399 frames = frameUtils.groupNalsIntoFrames(nalUnits); 44400 gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have 44401 // a problem since MSE (on Chrome) requires a leading keyframe. 44402 // 44403 // We have two approaches to repairing this situation: 44404 // 1) GOP-FUSION: 44405 // This is where we keep track of the GOPS (group-of-pictures) 44406 // from previous fragments and attempt to find one that we can 44407 // prepend to the current fragment in order to create a valid 44408 // fragment. 44409 // 2) KEYFRAME-PULLING: 44410 // Here we search for the first keyframe in the fragment and 44411 // throw away all the frames between the start of the fragment 44412 // and that keyframe. We then extend the duration and pull the 44413 // PTS of the keyframe forward so that it covers the time range 44414 // of the frames that were disposed of. 44415 // 44416 // #1 is far prefereable over #2 which can cause "stuttering" but 44417 // requires more things to be just right. 44418 44419 if (!gops[0][0].keyFrame) { 44420 // Search for a gop for fusion from our gopCache 44421 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 44422 44423 if (gopForFusion) { 44424 // in order to provide more accurate timing information about the segment, save 44425 // the number of seconds prepended to the original segment due to GOP fusion 44426 prependedContentDuration = gopForFusion.duration; 44427 gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the 44428 // new gop at the beginning 44429 44430 gops.byteLength += gopForFusion.byteLength; 44431 gops.nalCount += gopForFusion.nalCount; 44432 gops.pts = gopForFusion.pts; 44433 gops.dts = gopForFusion.dts; 44434 gops.duration += gopForFusion.duration; 44435 } else { 44436 // If we didn't find a candidate gop fall back to keyframe-pulling 44437 gops = frameUtils.extendFirstKeyFrame(gops); 44438 } 44439 } // Trim gops to align with gopsToAlignWith 44440 44441 44442 if (gopsToAlignWith.length) { 44443 var alignedGops; 44444 44445 if (options.alignGopsAtEnd) { 44446 alignedGops = this.alignGopsAtEnd_(gops); 44447 } else { 44448 alignedGops = this.alignGopsAtStart_(gops); 44449 } 44450 44451 if (!alignedGops) { 44452 // save all the nals in the last GOP into the gop cache 44453 this.gopCache_.unshift({ 44454 gop: gops.pop(), 44455 pps: track.pps, 44456 sps: track.sps 44457 }); // Keep a maximum of 6 GOPs in the cache 44458 44459 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 44460 44461 nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith 44462 44463 this.resetStream_(); 44464 this.trigger('done', 'VideoSegmentStream'); 44465 return; 44466 } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct 44467 // when recalculated before sending off to CoalesceStream 44468 44469 44470 trackDecodeInfo.clearDtsInfo(track); 44471 gops = alignedGops; 44472 } 44473 44474 trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to 44475 // samples (that is, frames) in the video data 44476 44477 track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat 44478 44479 mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops)); 44480 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 44481 this.trigger('processedGopsInfo', gops.map(function (gop) { 44482 return { 44483 pts: gop.pts, 44484 dts: gop.dts, 44485 byteLength: gop.byteLength 44486 }; 44487 })); 44488 firstGop = gops[0]; 44489 lastGop = gops[gops.length - 1]; 44490 this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); 44491 this.trigger('timingInfo', { 44492 start: gops[0].dts, 44493 end: gops[gops.length - 1].dts + gops[gops.length - 1].duration 44494 }); // save all the nals in the last GOP into the gop cache 44495 44496 this.gopCache_.unshift({ 44497 gop: gops.pop(), 44498 pps: track.pps, 44499 sps: track.sps 44500 }); // Keep a maximum of 6 GOPs in the cache 44501 44502 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 44503 44504 nalUnits = []; 44505 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 44506 this.trigger('timelineStartInfo', track.timelineStartInfo); 44507 moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of 44508 // throwing away hundreds of media segment fragments 44509 44510 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time 44511 44512 sequenceNumber++; 44513 boxes.set(moof); 44514 boxes.set(mdat, moof.byteLength); 44515 this.trigger('data', { 44516 track: track, 44517 boxes: boxes 44518 }); 44519 this.resetStream_(); // Continue with the flush process now 44520 44521 this.trigger('done', 'VideoSegmentStream'); 44522 }; 44523 44524 this.reset = function () { 44525 this.resetStream_(); 44526 nalUnits = []; 44527 this.gopCache_.length = 0; 44528 gopsToAlignWith.length = 0; 44529 this.trigger('reset'); 44530 }; 44531 44532 this.resetStream_ = function () { 44533 trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments 44534 // for instance, when we are rendition switching 44535 44536 config = undefined; 44537 pps = undefined; 44538 }; // Search for a candidate Gop for gop-fusion from the gop cache and 44539 // return it or return null if no good candidate was found 44540 44541 44542 this.getGopForFusion_ = function (nalUnit) { 44543 var halfSecond = 45000, 44544 // Half-a-second in a 90khz clock 44545 allowableOverlap = 10000, 44546 // About 3 frames @ 30fps 44547 nearestDistance = Infinity, 44548 dtsDistance, 44549 nearestGopObj, 44550 currentGop, 44551 currentGopObj, 44552 i; // Search for the GOP nearest to the beginning of this nal unit 44553 44554 for (i = 0; i < this.gopCache_.length; i++) { 44555 currentGopObj = this.gopCache_[i]; 44556 currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS 44557 44558 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 44559 continue; 44560 } // Reject Gops that would require a negative baseMediaDecodeTime 44561 44562 44563 if (currentGop.dts < track.timelineStartInfo.dts) { 44564 continue; 44565 } // The distance between the end of the gop and the start of the nalUnit 44566 44567 44568 dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within 44569 // a half-second of the nal unit 44570 44571 if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) { 44572 // Always use the closest GOP we found if there is more than 44573 // one candidate 44574 if (!nearestGopObj || nearestDistance > dtsDistance) { 44575 nearestGopObj = currentGopObj; 44576 nearestDistance = dtsDistance; 44577 } 44578 } 44579 } 44580 44581 if (nearestGopObj) { 44582 return nearestGopObj.gop; 44583 } 44584 44585 return null; 44586 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 44587 // of gopsToAlignWith starting from the START of the list 44588 44589 44590 this.alignGopsAtStart_ = function (gops) { 44591 var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops; 44592 byteLength = gops.byteLength; 44593 nalCount = gops.nalCount; 44594 duration = gops.duration; 44595 alignIndex = gopIndex = 0; 44596 44597 while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) { 44598 align = gopsToAlignWith[alignIndex]; 44599 gop = gops[gopIndex]; 44600 44601 if (align.pts === gop.pts) { 44602 break; 44603 } 44604 44605 if (gop.pts > align.pts) { 44606 // this current gop starts after the current gop we want to align on, so increment 44607 // align index 44608 alignIndex++; 44609 continue; 44610 } // current gop starts before the current gop we want to align on. so increment gop 44611 // index 44612 44613 44614 gopIndex++; 44615 byteLength -= gop.byteLength; 44616 nalCount -= gop.nalCount; 44617 duration -= gop.duration; 44618 } 44619 44620 if (gopIndex === 0) { 44621 // no gops to trim 44622 return gops; 44623 } 44624 44625 if (gopIndex === gops.length) { 44626 // all gops trimmed, skip appending all gops 44627 return null; 44628 } 44629 44630 alignedGops = gops.slice(gopIndex); 44631 alignedGops.byteLength = byteLength; 44632 alignedGops.duration = duration; 44633 alignedGops.nalCount = nalCount; 44634 alignedGops.pts = alignedGops[0].pts; 44635 alignedGops.dts = alignedGops[0].dts; 44636 return alignedGops; 44637 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 44638 // of gopsToAlignWith starting from the END of the list 44639 44640 44641 this.alignGopsAtEnd_ = function (gops) { 44642 var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound; 44643 alignIndex = gopsToAlignWith.length - 1; 44644 gopIndex = gops.length - 1; 44645 alignEndIndex = null; 44646 matchFound = false; 44647 44648 while (alignIndex >= 0 && gopIndex >= 0) { 44649 align = gopsToAlignWith[alignIndex]; 44650 gop = gops[gopIndex]; 44651 44652 if (align.pts === gop.pts) { 44653 matchFound = true; 44654 break; 44655 } 44656 44657 if (align.pts > gop.pts) { 44658 alignIndex--; 44659 continue; 44660 } 44661 44662 if (alignIndex === gopsToAlignWith.length - 1) {
vendor: 4,564 bytes, lines 44663-44793
44663 // gop.pts is greater than the last alignment candidate. If no match is found 44664 // by the end of this loop, we still want to append gops that come after this 44665 // point 44666 alignEndIndex = gopIndex; 44667 } 44668 44669 gopIndex--; 44670 } 44671 44672 if (!matchFound && alignEndIndex === null) { 44673 return null; 44674 } 44675 44676 var trimIndex; 44677 44678 if (matchFound) { 44679 trimIndex = gopIndex; 44680 } else { 44681 trimIndex = alignEndIndex; 44682 } 44683 44684 if (trimIndex === 0) { 44685 return gops; 44686 } 44687 44688 var alignedGops = gops.slice(trimIndex); 44689 var metadata = alignedGops.reduce(function (total, gop) { 44690 total.byteLength += gop.byteLength; 44691 total.duration += gop.duration; 44692 total.nalCount += gop.nalCount; 44693 return total; 44694 }, { 44695 byteLength: 0, 44696 duration: 0, 44697 nalCount: 0 44698 }); 44699 alignedGops.byteLength = metadata.byteLength; 44700 alignedGops.duration = metadata.duration; 44701 alignedGops.nalCount = metadata.nalCount; 44702 alignedGops.pts = alignedGops[0].pts; 44703 alignedGops.dts = alignedGops[0].dts; 44704 return alignedGops; 44705 }; 44706 44707 this.alignGopsWith = function (newGopsToAlignWith) { 44708 gopsToAlignWith = newGopsToAlignWith; 44709 }; 44710 }; 44711 44712 _VideoSegmentStream.prototype = new stream(); 44713 /** 44714 * A Stream that can combine multiple streams (ie. audio & video) 44715 * into a single output segment for MSE. Also supports audio-only 44716 * and video-only streams. 44717 * @param options {object} transmuxer options object 44718 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 44719 * in the source; false to adjust the first segment to start at media timeline start. 44720 */ 44721 44722 _CoalesceStream = function CoalesceStream(options, metadataStream) { 44723 // Number of Tracks per output segment 44724 // If greater than 1, we combine multiple 44725 // tracks into a single segment 44726 this.numberOfTracks = 0; 44727 this.metadataStream = metadataStream; 44728 options = options || {}; 44729 44730 if (typeof options.remux !== 'undefined') { 44731 this.remuxTracks = !!options.remux; 44732 } else { 44733 this.remuxTracks = true; 44734 } 44735 44736 if (typeof options.keepOriginalTimestamps === 'boolean') { 44737 this.keepOriginalTimestamps = options.keepOriginalTimestamps; 44738 } else { 44739 this.keepOriginalTimestamps = false; 44740 } 44741 44742 this.pendingTracks = []; 44743 this.videoTrack = null; 44744 this.pendingBoxes = []; 44745 this.pendingCaptions = []; 44746 this.pendingMetadata = []; 44747 this.pendingBytes = 0; 44748 this.emittedTracks = 0; 44749 44750 _CoalesceStream.prototype.init.call(this); // Take output from multiple 44751 44752 44753 this.push = function (output) { 44754 // buffer incoming captions until the associated video segment 44755 // finishes 44756 if (output.text) { 44757 return this.pendingCaptions.push(output); 44758 } // buffer incoming id3 tags until the final flush 44759 44760 44761 if (output.frames) { 44762 return this.pendingMetadata.push(output); 44763 } // Add this track to the list of pending tracks and store 44764 // important information required for the construction of 44765 // the final segment 44766 44767 44768 this.pendingTracks.push(output.track); 44769 this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome? 44770 // We unshift audio and push video because 44771 // as of Chrome 75 when switching from 44772 // one init segment to another if the video 44773 // mdat does not appear after the audio mdat 44774 // only audio will play for the duration of our transmux. 44775 44776 if (output.track.type === 'video') { 44777 this.videoTrack = output.track; 44778 this.pendingBoxes.push(output.boxes); 44779 } 44780 44781 if (output.track.type === 'audio') { 44782 this.audioTrack = output.track; 44783 this.pendingBoxes.unshift(output.boxes); 44784 } 44785 }; 44786 }; 44787 44788 _CoalesceStream.prototype = new stream(); 44789 44790 _CoalesceStream.prototype.flush = function (flushSource) { 44791 var offset = 0, 44792 event = { 44793 captions: [],
vendor: 4,830 bytes, lines 44794-44899
44794 captionStreams: {}, 44795 metadata: [], 44796 info: {} 44797 }, 44798 caption, 44799 id3, 44800 initSegment, 44801 timelineStartPts = 0, 44802 i; 44803 44804 if (this.pendingTracks.length < this.numberOfTracks) { 44805 if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') { 44806 // Return because we haven't received a flush from a data-generating 44807 // portion of the segment (meaning that we have only recieved meta-data 44808 // or captions.) 44809 return; 44810 } else if (this.remuxTracks) { 44811 // Return until we have enough tracks from the pipeline to remux (if we 44812 // are remuxing audio and video into a single MP4) 44813 return; 44814 } else if (this.pendingTracks.length === 0) { 44815 // In the case where we receive a flush without any data having been 44816 // received we consider it an emitted track for the purposes of coalescing 44817 // `done` events. 44818 // We do this for the case where there is an audio and video track in the 44819 // segment but no audio data. (seen in several playlists with alternate 44820 // audio tracks and no audio present in the main TS segments.) 44821 this.emittedTracks++; 44822 44823 if (this.emittedTracks >= this.numberOfTracks) { 44824 this.trigger('done'); 44825 this.emittedTracks = 0; 44826 } 44827 44828 return; 44829 } 44830 } 44831 44832 if (this.videoTrack) { 44833 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 44834 VIDEO_PROPERTIES.forEach(function (prop) { 44835 event.info[prop] = this.videoTrack[prop]; 44836 }, this); 44837 } else if (this.audioTrack) { 44838 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 44839 AUDIO_PROPERTIES.forEach(function (prop) { 44840 event.info[prop] = this.audioTrack[prop]; 44841 }, this); 44842 } 44843 44844 if (this.videoTrack || this.audioTrack) { 44845 if (this.pendingTracks.length === 1) { 44846 event.type = this.pendingTracks[0].type; 44847 } else { 44848 event.type = 'combined'; 44849 } 44850 44851 this.emittedTracks += this.pendingTracks.length; 44852 initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment 44853 44854 event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov 44855 // and track definitions 44856 44857 event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats 44858 44859 event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together 44860 44861 for (i = 0; i < this.pendingBoxes.length; i++) { 44862 event.data.set(this.pendingBoxes[i], offset); 44863 offset += this.pendingBoxes[i].byteLength; 44864 } // Translate caption PTS times into second offsets to match the 44865 // video timeline for the segment, and add track info 44866 44867 44868 for (i = 0; i < this.pendingCaptions.length; i++) { 44869 caption = this.pendingCaptions[i]; 44870 caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps); 44871 caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps); 44872 event.captionStreams[caption.stream] = true; 44873 event.captions.push(caption); 44874 } // Translate ID3 frame PTS times into second offsets to match the 44875 // video timeline for the segment 44876 44877 44878 for (i = 0; i < this.pendingMetadata.length; i++) { 44879 id3 = this.pendingMetadata[i]; 44880 id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps); 44881 event.metadata.push(id3); 44882 } // We add this to every single emitted segment even though we only need 44883 // it for the first 44884 44885 44886 event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state 44887 44888 this.pendingTracks.length = 0; 44889 this.videoTrack = null; 44890 this.pendingBoxes.length = 0; 44891 this.pendingCaptions.length = 0; 44892 this.pendingBytes = 0; 44893 this.pendingMetadata.length = 0; // Emit the built segment 44894 // We include captions and ID3 tags for backwards compatibility, 44895 // ideally we should send only video and audio in the data event 44896 44897 this.trigger('data', event); // Emit each caption to the outside world 44898 // Ideally, this would happen immediately on parsing captions, 44899 // but we need to ensure that video data is sent back first
vendor: 46,856 bytes, lines 44900-46193
44900 // so that caption timing can be adjusted to match video timing 44901 44902 for (i = 0; i < event.captions.length; i++) { 44903 caption = event.captions[i]; 44904 this.trigger('caption', caption); 44905 } // Emit each id3 tag to the outside world 44906 // Ideally, this would happen immediately on parsing the tag, 44907 // but we need to ensure that video data is sent back first 44908 // so that ID3 frame timing can be adjusted to match video timing 44909 44910 44911 for (i = 0; i < event.metadata.length; i++) { 44912 id3 = event.metadata[i]; 44913 this.trigger('id3Frame', id3); 44914 } 44915 } // Only emit `done` if all tracks have been flushed and emitted 44916 44917 44918 if (this.emittedTracks >= this.numberOfTracks) { 44919 this.trigger('done'); 44920 this.emittedTracks = 0; 44921 } 44922 }; 44923 44924 _CoalesceStream.prototype.setRemux = function (val) { 44925 this.remuxTracks = val; 44926 }; 44927 /** 44928 * A Stream that expects MP2T binary data as input and produces 44929 * corresponding media segments, suitable for use with Media Source 44930 * Extension (MSE) implementations that support the ISO BMFF byte 44931 * stream format, like Chrome. 44932 */ 44933 44934 44935 _Transmuxer = function Transmuxer(options) { 44936 var self = this, 44937 hasFlushed = true, 44938 videoTrack, 44939 audioTrack; 44940 44941 _Transmuxer.prototype.init.call(this); 44942 44943 options = options || {}; 44944 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 44945 this.transmuxPipeline_ = {}; 44946 44947 this.setupAacPipeline = function () { 44948 var pipeline = {}; 44949 this.transmuxPipeline_ = pipeline; 44950 pipeline.type = 'aac'; 44951 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 44952 44953 pipeline.aacStream = new aac(); 44954 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 44955 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 44956 pipeline.adtsStream = new adts(); 44957 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 44958 pipeline.headOfPipeline = pipeline.aacStream; 44959 pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 44960 pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); 44961 pipeline.metadataStream.on('timestamp', function (frame) { 44962 pipeline.aacStream.setTimestamp(frame.timeStamp); 44963 }); 44964 pipeline.aacStream.on('data', function (data) { 44965 if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { 44966 audioTrack = audioTrack || { 44967 timelineStartInfo: { 44968 baseMediaDecodeTime: self.baseMediaDecodeTime 44969 }, 44970 codec: 'adts', 44971 type: 'audio' 44972 }; // hook up the audio segment stream to the first track with aac data 44973 44974 pipeline.coalesceStream.numberOfTracks++; 44975 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 44976 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 44977 44978 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 44979 } // emit pmt info 44980 44981 44982 self.trigger('trackinfo', { 44983 hasAudio: !!audioTrack, 44984 hasVideo: !!videoTrack 44985 }); 44986 }); // Re-emit any data coming from the coalesce stream to the outside world 44987 44988 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 44989 44990 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 44991 }; 44992 44993 this.setupTsPipeline = function () { 44994 var pipeline = {}; 44995 this.transmuxPipeline_ = pipeline; 44996 pipeline.type = 'ts'; 44997 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 44998 44999 pipeline.packetStream = new m2ts_1.TransportPacketStream(); 45000 pipeline.parseStream = new m2ts_1.TransportParseStream(); 45001 pipeline.elementaryStream = new m2ts_1.ElementaryStream(); 45002 pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream(); 45003 pipeline.adtsStream = new adts(); 45004 pipeline.h264Stream = new H264Stream(); 45005 pipeline.captionStream = new m2ts_1.CaptionStream(); 45006 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 45007 pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams 45008 45009 pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!! 45010 // demux the streams 45011 45012 pipeline.timestampRolloverStream.pipe(pipeline.h264Stream); 45013 pipeline.timestampRolloverStream.pipe(pipeline.adtsStream); 45014 pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream 45015 45016 pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream); 45017 pipeline.elementaryStream.on('data', function (data) { 45018 var i; 45019 45020 if (data.type === 'metadata') { 45021 i = data.tracks.length; // scan the tracks listed in the metadata 45022 45023 while (i--) { 45024 if (!videoTrack && data.tracks[i].type === 'video') { 45025 videoTrack = data.tracks[i]; 45026 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 45027 } else if (!audioTrack && data.tracks[i].type === 'audio') { 45028 audioTrack = data.tracks[i]; 45029 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 45030 } 45031 } // hook up the video segment stream to the first track with h264 data 45032 45033 45034 if (videoTrack && !pipeline.videoSegmentStream) { 45035 pipeline.coalesceStream.numberOfTracks++; 45036 pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options); 45037 pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) { 45038 // When video emits timelineStartInfo data after a flush, we forward that 45039 // info to the AudioSegmentStream, if it exists, because video timeline 45040 // data takes precedence. 45041 if (audioTrack) { 45042 audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the 45043 // very earliest DTS we have seen in video because Chrome will 45044 // interpret any video track with a baseMediaDecodeTime that is 45045 // non-zero as a gap. 45046 45047 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); 45048 } 45049 }); 45050 pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo')); 45051 pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo')); 45052 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) { 45053 if (audioTrack) { 45054 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 45055 } 45056 }); 45057 pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline 45058 45059 pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream); 45060 } 45061 45062 if (audioTrack && !pipeline.audioSegmentStream) { 45063 // hook up the audio segment stream to the first track with aac data 45064 pipeline.coalesceStream.numberOfTracks++; 45065 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 45066 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 45067 45068 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 45069 } // emit pmt info 45070 45071 45072 self.trigger('trackinfo', { 45073 hasAudio: !!audioTrack, 45074 hasVideo: !!videoTrack 45075 }); 45076 } 45077 }); // Re-emit any data coming from the coalesce stream to the outside world 45078 45079 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 45080 pipeline.coalesceStream.on('id3Frame', function (id3Frame) { 45081 id3Frame.dispatchType = pipeline.metadataStream.dispatchType; 45082 self.trigger('id3Frame', id3Frame); 45083 }); 45084 pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline 45085 45086 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 45087 }; // hook up the segment streams once track metadata is delivered 45088 45089 45090 this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { 45091 var pipeline = this.transmuxPipeline_; 45092 45093 if (!options.keepOriginalTimestamps) { 45094 this.baseMediaDecodeTime = baseMediaDecodeTime; 45095 } 45096 45097 if (audioTrack) { 45098 audioTrack.timelineStartInfo.dts = undefined; 45099 audioTrack.timelineStartInfo.pts = undefined; 45100 trackDecodeInfo.clearDtsInfo(audioTrack); 45101 45102 if (!options.keepOriginalTimestamps) { 45103 audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 45104 } 45105 45106 if (pipeline.audioTimestampRolloverStream) { 45107 pipeline.audioTimestampRolloverStream.discontinuity(); 45108 } 45109 } 45110 45111 if (videoTrack) { 45112 if (pipeline.videoSegmentStream) { 45113 pipeline.videoSegmentStream.gopCache_ = []; 45114 } 45115 45116 videoTrack.timelineStartInfo.dts = undefined; 45117 videoTrack.timelineStartInfo.pts = undefined; 45118 trackDecodeInfo.clearDtsInfo(videoTrack); 45119 pipeline.captionStream.reset(); 45120 45121 if (!options.keepOriginalTimestamps) { 45122 videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 45123 } 45124 } 45125 45126 if (pipeline.timestampRolloverStream) { 45127 pipeline.timestampRolloverStream.discontinuity(); 45128 } 45129 }; 45130 45131 this.setAudioAppendStart = function (timestamp) { 45132 if (audioTrack) { 45133 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 45134 } 45135 }; 45136 45137 this.setRemux = function (val) { 45138 var pipeline = this.transmuxPipeline_; 45139 options.remux = val; 45140 45141 if (pipeline && pipeline.coalesceStream) { 45142 pipeline.coalesceStream.setRemux(val); 45143 } 45144 }; 45145 45146 this.alignGopsWith = function (gopsToAlignWith) { 45147 if (videoTrack && this.transmuxPipeline_.videoSegmentStream) { 45148 this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith); 45149 } 45150 }; // feed incoming data to the front of the parsing pipeline 45151 45152 45153 this.push = function (data) { 45154 if (hasFlushed) { 45155 var isAac = isLikelyAacData$1(data); 45156 45157 if (isAac && this.transmuxPipeline_.type !== 'aac') { 45158 this.setupAacPipeline(); 45159 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 45160 this.setupTsPipeline(); 45161 } 45162 45163 hasFlushed = false; 45164 } 45165 45166 this.transmuxPipeline_.headOfPipeline.push(data); 45167 }; // flush any buffered data 45168 45169 45170 this.flush = function () { 45171 hasFlushed = true; // Start at the top of the pipeline and flush all pending work 45172 45173 this.transmuxPipeline_.headOfPipeline.flush(); 45174 }; 45175 45176 this.endTimeline = function () { 45177 this.transmuxPipeline_.headOfPipeline.endTimeline(); 45178 }; 45179 45180 this.reset = function () { 45181 if (this.transmuxPipeline_.headOfPipeline) { 45182 this.transmuxPipeline_.headOfPipeline.reset(); 45183 } 45184 }; // Caption data has to be reset when seeking outside buffered range 45185 45186 45187 this.resetCaptions = function () { 45188 if (this.transmuxPipeline_.captionStream) { 45189 this.transmuxPipeline_.captionStream.reset(); 45190 } 45191 }; 45192 }; 45193 45194 _Transmuxer.prototype = new stream(); 45195 var transmuxer = { 45196 Transmuxer: _Transmuxer, 45197 VideoSegmentStream: _VideoSegmentStream, 45198 AudioSegmentStream: _AudioSegmentStream, 45199 AUDIO_PROPERTIES: AUDIO_PROPERTIES, 45200 VIDEO_PROPERTIES: VIDEO_PROPERTIES, 45201 // exported for testing 45202 generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo 45203 }; 45204 45205 var classCallCheck = function classCallCheck(instance, Constructor) { 45206 if (!(instance instanceof Constructor)) { 45207 throw new TypeError("Cannot call a class as a function"); 45208 } 45209 }; 45210 45211 var createClass = function () { 45212 function defineProperties(target, props) { 45213 for (var i = 0; i < props.length; i++) { 45214 var descriptor = props[i]; 45215 descriptor.enumerable = descriptor.enumerable || false; 45216 descriptor.configurable = true; 45217 if ("value" in descriptor) descriptor.writable = true; 45218 Object.defineProperty(target, descriptor.key, descriptor); 45219 } 45220 } 45221 45222 return function (Constructor, protoProps, staticProps) { 45223 if (protoProps) defineProperties(Constructor.prototype, protoProps); 45224 if (staticProps) defineProperties(Constructor, staticProps); 45225 return Constructor; 45226 }; 45227 }(); 45228 /** 45229 * @file transmuxer-worker.js 45230 */ 45231 45232 /** 45233 * Re-emits transmuxer events by converting them into messages to the 45234 * world outside the worker. 45235 * 45236 * @param {Object} transmuxer the transmuxer to wire events on 45237 * @private 45238 */ 45239 45240 45241 var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer$$1) { 45242 transmuxer$$1.on('data', function (segment) { 45243 // transfer ownership of the underlying ArrayBuffer 45244 // instead of doing a copy to save memory 45245 // ArrayBuffers are transferable but generic TypedArrays are not 45246 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 45247 var initArray = segment.initSegment; 45248 segment.initSegment = { 45249 data: initArray.buffer, 45250 byteOffset: initArray.byteOffset, 45251 byteLength: initArray.byteLength 45252 }; 45253 var typedArray = segment.data; 45254 segment.data = typedArray.buffer; 45255 self.postMessage({ 45256 action: 'data', 45257 segment: segment, 45258 byteOffset: typedArray.byteOffset, 45259 byteLength: typedArray.byteLength 45260 }, [segment.data]); 45261 }); 45262 45263 if (transmuxer$$1.captionStream) { 45264 transmuxer$$1.captionStream.on('data', function (caption) { 45265 self.postMessage({ 45266 action: 'caption', 45267 data: caption 45268 }); 45269 }); 45270 } 45271 45272 transmuxer$$1.on('done', function (data) { 45273 self.postMessage({ 45274 action: 'done' 45275 }); 45276 }); 45277 transmuxer$$1.on('gopInfo', function (gopInfo) { 45278 self.postMessage({ 45279 action: 'gopInfo', 45280 gopInfo: gopInfo 45281 }); 45282 }); 45283 transmuxer$$1.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) { 45284 self.postMessage({ 45285 action: 'videoSegmentTimingInfo', 45286 videoSegmentTimingInfo: videoSegmentTimingInfo 45287 }); 45288 }); 45289 }; 45290 /** 45291 * All incoming messages route through this hash. If no function exists 45292 * to handle an incoming message, then we ignore the message. 45293 * 45294 * @class MessageHandlers 45295 * @param {Object} options the options to initialize with 45296 */ 45297 45298 45299 var MessageHandlers = function () { 45300 function MessageHandlers(self, options) { 45301 classCallCheck(this, MessageHandlers); 45302 this.options = options || {}; 45303 this.self = self; 45304 this.init(); 45305 } 45306 /** 45307 * initialize our web worker and wire all the events. 45308 */ 45309 45310 45311 createClass(MessageHandlers, [{ 45312 key: 'init', 45313 value: function init() { 45314 if (this.transmuxer) { 45315 this.transmuxer.dispose(); 45316 } 45317 45318 this.transmuxer = new transmuxer.Transmuxer(this.options); 45319 wireTransmuxerEvents(this.self, this.transmuxer); 45320 } 45321 /** 45322 * Adds data (a ts segment) to the start of the transmuxer pipeline for 45323 * processing. 45324 * 45325 * @param {ArrayBuffer} data data to push into the muxer 45326 */ 45327 45328 }, { 45329 key: 'push', 45330 value: function push(data) { 45331 // Cast array buffer to correct type for transmuxer 45332 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 45333 this.transmuxer.push(segment); 45334 } 45335 /** 45336 * Recreate the transmuxer so that the next segment added via `push` 45337 * start with a fresh transmuxer. 45338 */ 45339 45340 }, { 45341 key: 'reset', 45342 value: function reset() { 45343 this.init(); 45344 } 45345 /** 45346 * Set the value that will be used as the `baseMediaDecodeTime` time for the 45347 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 45348 * set relative to the first based on the PTS values. 45349 * 45350 * @param {Object} data used to set the timestamp offset in the muxer 45351 */ 45352 45353 }, { 45354 key: 'setTimestampOffset', 45355 value: function setTimestampOffset(data) { 45356 var timestampOffset = data.timestampOffset || 0; 45357 this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000)); 45358 } 45359 }, { 45360 key: 'setAudioAppendStart', 45361 value: function setAudioAppendStart(data) { 45362 this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000)); 45363 } 45364 /** 45365 * Forces the pipeline to finish processing the last segment and emit it's 45366 * results. 45367 * 45368 * @param {Object} data event data, not really used 45369 */ 45370 45371 }, { 45372 key: 'flush', 45373 value: function flush(data) { 45374 this.transmuxer.flush(); 45375 } 45376 }, { 45377 key: 'resetCaptions', 45378 value: function resetCaptions() { 45379 this.transmuxer.resetCaptions(); 45380 } 45381 }, { 45382 key: 'alignGopsWith', 45383 value: function alignGopsWith(data) { 45384 this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice()); 45385 } 45386 }]); 45387 return MessageHandlers; 45388 }(); 45389 /** 45390 * Our web wroker interface so that things can talk to mux.js 45391 * that will be running in a web worker. the scope is passed to this by 45392 * webworkify. 45393 * 45394 * @param {Object} self the scope for the web worker 45395 */ 45396 45397 45398 var TransmuxerWorker = function TransmuxerWorker(self) { 45399 self.onmessage = function (event) { 45400 if (event.data.action === 'init' && event.data.options) { 45401 this.messageHandlers = new MessageHandlers(self, event.data.options); 45402 return; 45403 } 45404 45405 if (!this.messageHandlers) { 45406 this.messageHandlers = new MessageHandlers(self); 45407 } 45408 45409 if (event.data && event.data.action && event.data.action !== 'init') { 45410 if (this.messageHandlers[event.data.action]) { 45411 this.messageHandlers[event.data.action](event.data); 45412 } 45413 } 45414 }; 45415 }; 45416 45417 var transmuxerWorker = new TransmuxerWorker(self); 45418 return transmuxerWorker; 45419 }(); 45420 }); 45421 /** 45422 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to 45423 * codec strings, or translating codec strings into objects that can be examined. 45424 */ 45425 // Default codec parameters if none were provided for video and/or audio 45426 45427 var defaultCodecs = { 45428 videoCodec: 'avc1', 45429 videoObjectTypeIndicator: '.4d400d', 45430 // AAC-LC 45431 audioProfile: '2' 45432 }; 45433 /** 45434 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 45435 * `avc1.<hhhhhh>` 45436 * 45437 * @param {Array} codecs an array of codec strings to fix 45438 * @return {Array} the translated codec array 45439 * @private 45440 */ 45441 45442 var translateLegacyCodecs = function translateLegacyCodecs(codecs) { 45443 return codecs.map(function (codec) { 45444 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 45445 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 45446 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 45447 return 'avc1.' + profileHex + '00' + avcLevelHex; 45448 }); 45449 }); 45450 }; 45451 /** 45452 * Parses a codec string to retrieve the number of codecs specified, 45453 * the video codec and object type indicator, and the audio profile. 45454 */ 45455 45456 45457 var parseCodecs = function parseCodecs() { 45458 var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 45459 var result = { 45460 codecCount: 0 45461 }; 45462 var parsed = void 0; 45463 result.codecCount = codecs.split(',').length; 45464 result.codecCount = result.codecCount || 2; // parse the video codec 45465 45466 parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs); 45467 45468 if (parsed) { 45469 result.videoCodec = parsed[2]; 45470 result.videoObjectTypeIndicator = parsed[3]; 45471 } // parse the last field of the audio codec 45472 45473 45474 result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs); 45475 result.audioProfile = result.audioProfile && result.audioProfile[2]; 45476 return result; 45477 }; 45478 /** 45479 * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the 45480 * standard `avc1.<hhhhhh>`. 45481 * 45482 * @param codecString {String} the codec string 45483 * @return {String} the codec string with old apple-style codecs replaced 45484 * 45485 * @private 45486 */ 45487 45488 45489 var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) { 45490 return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) { 45491 return translateLegacyCodecs([match])[0]; 45492 }); 45493 }; 45494 /** 45495 * Build a media mime-type string from a set of parameters 45496 * @param {String} type either 'audio' or 'video' 45497 * @param {String} container either 'mp2t' or 'mp4' 45498 * @param {Array} codecs an array of codec strings to add 45499 * @return {String} a valid media mime-type 45500 */ 45501 45502 45503 var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) { 45504 // The codecs array is filtered so that falsey values are 45505 // dropped and don't cause Array#join to create spurious 45506 // commas 45507 return type + '/' + container + '; codecs="' + codecs.filter(function (c) { 45508 return !!c; 45509 }).join(', ') + '"'; 45510 }; 45511 /** 45512 * Returns the type container based on information in the playlist 45513 * @param {Playlist} media the current media playlist 45514 * @return {String} a valid media container type 45515 */ 45516 45517 45518 var getContainerType = function getContainerType(media) { 45519 // An initialization segment means the media playlist is an iframe 45520 // playlist or is using the mp4 container. We don't currently 45521 // support iframe playlists, so assume this is signalling mp4 45522 // fragments. 45523 if (media.segments && media.segments.length && media.segments[0].map) { 45524 return 'mp4'; 45525 } 45526 45527 return 'mp2t'; 45528 }; 45529 /** 45530 * Returns a set of codec strings parsed from the playlist or the default 45531 * codec strings if no codecs were specified in the playlist 45532 * @param {Playlist} media the current media playlist 45533 * @return {Object} an object with the video and audio codecs 45534 */ 45535 45536 45537 var getCodecs = function getCodecs(media) { 45538 // if the codecs were explicitly specified, use them instead of the 45539 // defaults 45540 var mediaAttributes = media.attributes || {}; 45541 45542 if (mediaAttributes.CODECS) { 45543 return parseCodecs(mediaAttributes.CODECS); 45544 } 45545 45546 return defaultCodecs; 45547 }; 45548 45549 var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) { 45550 if (!master.mediaGroups.AUDIO || !audioGroupId) { 45551 return null; 45552 } 45553 45554 var audioGroup = master.mediaGroups.AUDIO[audioGroupId]; 45555 45556 if (!audioGroup) { 45557 return null; 45558 } 45559 45560 for (var name in audioGroup) { 45561 var audioType = audioGroup[name]; 45562 45563 if (audioType["default"] && audioType.playlists) { 45564 // codec should be the same for all playlists within the audio type 45565 return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile; 45566 } 45567 } 45568 45569 return null; 45570 }; 45571 /** 45572 * Calculates the MIME type strings for a working configuration of 45573 * SourceBuffers to play variant streams in a master playlist. If 45574 * there is no possible working configuration, an empty array will be 45575 * returned. 45576 * 45577 * @param master {Object} the m3u8 object for the master playlist 45578 * @param media {Object} the m3u8 object for the variant playlist 45579 * @return {Array} the MIME type strings. If the array has more than 45580 * one entry, the first element should be applied to the video 45581 * SourceBuffer and the second to the audio SourceBuffer. 45582 * 45583 * @private 45584 */ 45585 45586 45587 var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) { 45588 var containerType = getContainerType(media); 45589 var codecInfo = getCodecs(media); 45590 var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video) 45591 45592 var isMuxed = true; 45593 var isMaat = false; 45594 45595 if (!media) { 45596 // Not enough information 45597 return []; 45598 } 45599 45600 if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) { 45601 var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario 45602 45603 if (audioGroup) { 45604 isMaat = true; // Start with the everything demuxed then... 45605 45606 isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri) 45607 45608 for (var groupId in audioGroup) { 45609 // either a uri is present (if the case of HLS and an external playlist), or 45610 // playlists is present (in the case of DASH where we don't have external audio 45611 // playlists) 45612 if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) { 45613 isMuxed = true; 45614 break; 45615 } 45616 } 45617 } 45618 } // HLS with multiple-audio tracks must always get an audio codec. 45619 // Put another way, there is no way to have a video-only multiple-audio HLS! 45620 45621 45622 if (isMaat && !codecInfo.audioProfile) { 45623 if (!isMuxed) { 45624 // It is possible for codecs to be specified on the audio media group playlist but 45625 // not on the rendition playlist. This is mostly the case for DASH, where audio and 45626 // video are always separate (and separately specified). 45627 codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO); 45628 } 45629 45630 if (!codecInfo.audioProfile) { 45631 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)'); 45632 codecInfo.audioProfile = defaultCodecs.audioProfile; 45633 } 45634 } // Generate the final codec strings from the codec object generated above 45635 45636 45637 var codecStrings = {}; 45638 45639 if (codecInfo.videoCodec) { 45640 codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator; 45641 } 45642 45643 if (codecInfo.audioProfile) { 45644 codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile; 45645 } // Finally, make and return an array with proper mime-types depending on 45646 // the configuration 45647 45648 45649 var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]); 45650 var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]); 45651 var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]); 45652 45653 if (isMaat) { 45654 if (!isMuxed && codecStrings.video) { 45655 return [justVideo, justAudio]; 45656 } 45657 45658 if (!isMuxed && !codecStrings.video) { 45659 // There is no muxed content and no video codec string, so this is an audio only 45660 // stream with alternate audio. 45661 return [justAudio, justAudio]; 45662 } // There exists the possiblity that this will return a `video/container` 45663 // mime-type for the first entry in the array even when there is only audio. 45664 // This doesn't appear to be a problem and simplifies the code. 45665 45666 45667 return [bothVideoAudio, justAudio]; 45668 } // If there is no video codec at all, always just return a single 45669 // audio/<container> mime-type 45670 45671 45672 if (!codecStrings.video) { 45673 return [justAudio]; 45674 } // When not using separate audio media groups, audio and video is 45675 // *always* muxed 45676 45677 45678 return [bothVideoAudio]; 45679 }; 45680 /** 45681 * Parse a content type header into a type and parameters 45682 * object 45683 * 45684 * @param {String} type the content type header 45685 * @return {Object} the parsed content-type 45686 * @private 45687 */ 45688 45689 45690 var parseContentType = function parseContentType(type) { 45691 var object = { 45692 type: '', 45693 parameters: {} 45694 }; 45695 var parameters = type.trim().split(';'); // first parameter should always be content-type 45696 45697 object.type = parameters.shift().trim(); 45698 parameters.forEach(function (parameter) { 45699 var pair = parameter.trim().split('='); 45700 45701 if (pair.length > 1) { 45702 var name = pair[0].replace(/"/g, '').trim(); 45703 var value = pair[1].replace(/"/g, '').trim(); 45704 object.parameters[name] = value; 45705 } 45706 }); 45707 return object; 45708 }; 45709 /** 45710 * Check if a codec string refers to an audio codec. 45711 * 45712 * @param {String} codec codec string to check 45713 * @return {Boolean} if this is an audio codec 45714 * @private 45715 */ 45716 45717 45718 var isAudioCodec = function isAudioCodec(codec) { 45719 return /mp4a\.\d+.\d+/i.test(codec); 45720 }; 45721 /** 45722 * Check if a codec string refers to a video codec. 45723 * 45724 * @param {String} codec codec string to check 45725 * @return {Boolean} if this is a video codec 45726 * @private 45727 */ 45728 45729 45730 var isVideoCodec = function isVideoCodec(codec) { 45731 return /avc1\.[\da-f]+/i.test(codec); 45732 }; 45733 /** 45734 * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in 45735 * front of current time. 45736 * 45737 * @param {Array} buffer 45738 * The current buffer of gop information 45739 * @param {Number} currentTime 45740 * The current time 45741 * @param {Double} mapping 45742 * Offset to map display time to stream presentation time 45743 * @return {Array} 45744 * List of gops considered safe to append over 45745 */ 45746 45747 45748 var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) { 45749 if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) { 45750 return []; 45751 } // pts value for current time + 3 seconds to give a bit more wiggle room 45752 45753 45754 var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000); 45755 var i = void 0; 45756 45757 for (i = 0; i < buffer.length; i++) { 45758 if (buffer[i].pts > currentTimePts) { 45759 break; 45760 } 45761 } 45762 45763 return buffer.slice(i); 45764 }; 45765 /** 45766 * Appends gop information (timing and byteLength) received by the transmuxer for the 45767 * gops appended in the last call to appendBuffer 45768 * 45769 * @param {Array} buffer 45770 * The current buffer of gop information 45771 * @param {Array} gops 45772 * List of new gop information 45773 * @param {boolean} replace 45774 * If true, replace the buffer with the new gop information. If false, append the 45775 * new gop information to the buffer in the right location of time. 45776 * @return {Array} 45777 * Updated list of gop information 45778 */ 45779 45780 45781 var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) { 45782 if (!gops.length) { 45783 return buffer; 45784 } 45785 45786 if (replace) { 45787 // If we are in safe append mode, then completely overwrite the gop buffer 45788 // with the most recent appeneded data. This will make sure that when appending 45789 // future segments, we only try to align with gops that are both ahead of current 45790 // time and in the last segment appended. 45791 return gops.slice(); 45792 } 45793 45794 var start = gops[0].pts; 45795 var i = 0; 45796 45797 for (i; i < buffer.length; i++) { 45798 if (buffer[i].pts >= start) { 45799 break; 45800 } 45801 } 45802 45803 return buffer.slice(0, i).concat(gops); 45804 }; 45805 /** 45806 * Removes gop information in buffer that overlaps with provided start and end 45807 * 45808 * @param {Array} buffer 45809 * The current buffer of gop information 45810 * @param {Double} start 45811 * position to start the remove at 45812 * @param {Double} end 45813 * position to end the remove at 45814 * @param {Double} mapping 45815 * Offset to map display time to stream presentation time 45816 */ 45817 45818 45819 var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) { 45820 var startPts = Math.ceil((start - mapping) * 90000); 45821 var endPts = Math.ceil((end - mapping) * 90000); 45822 var updatedBuffer = buffer.slice(); 45823 var i = buffer.length; 45824 45825 while (i--) { 45826 if (buffer[i].pts <= endPts) { 45827 break; 45828 } 45829 } 45830 45831 if (i === -1) { 45832 // no removal because end of remove range is before start of buffer 45833 return updatedBuffer; 45834 } 45835 45836 var j = i + 1; 45837 45838 while (j--) { 45839 if (buffer[j].pts <= startPts) { 45840 break; 45841 } 45842 } // clamp remove range start to 0 index 45843 45844 45845 j = Math.max(j, 0); 45846 updatedBuffer.splice(j, i - j + 1); 45847 return updatedBuffer; 45848 }; 45849 45850 var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) { 45851 var start = null; 45852 var end = null; 45853 var arity = 0; 45854 var extents = []; 45855 var ranges = []; // neither buffer has been created yet 45856 45857 if (!videoBuffer && !audioBuffer) { 45858 return videojs$1.createTimeRange(); 45859 } // only one buffer is configured 45860 45861 45862 if (!videoBuffer) { 45863 return audioBuffer.buffered; 45864 } 45865 45866 if (!audioBuffer) { 45867 return videoBuffer.buffered; 45868 } // both buffers are configured 45869 45870 45871 if (audioDisabled) { 45872 return videoBuffer.buffered; 45873 } // both buffers are empty 45874 45875 45876 if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) { 45877 return videojs$1.createTimeRange(); 45878 } // Handle the case where we have both buffers and create an 45879 // intersection of the two 45880 45881 45882 var videoBuffered = videoBuffer.buffered; 45883 var audioBuffered = audioBuffer.buffered; 45884 var count = videoBuffered.length; // A) Gather up all start and end times 45885 45886 while (count--) { 45887 extents.push({ 45888 time: videoBuffered.start(count), 45889 type: 'start' 45890 }); 45891 extents.push({ 45892 time: videoBuffered.end(count), 45893 type: 'end' 45894 }); 45895 } 45896 45897 count = audioBuffered.length; 45898 45899 while (count--) { 45900 extents.push({ 45901 time: audioBuffered.start(count), 45902 type: 'start' 45903 }); 45904 extents.push({ 45905 time: audioBuffered.end(count), 45906 type: 'end' 45907 }); 45908 } // B) Sort them by time 45909 45910 45911 extents.sort(function (a, b) { 45912 return a.time - b.time; 45913 }); // C) Go along one by one incrementing arity for start and decrementing 45914 // arity for ends 45915 45916 for (count = 0; count < extents.length; count++) { 45917 if (extents[count].type === 'start') { 45918 arity++; // D) If arity is ever incremented to 2 we are entering an 45919 // overlapping range 45920 45921 if (arity === 2) { 45922 start = extents[count].time; 45923 } 45924 } else if (extents[count].type === 'end') { 45925 arity--; // E) If arity is ever decremented to 1 we leaving an 45926 // overlapping range 45927 45928 if (arity === 1) { 45929 end = extents[count].time; 45930 } 45931 } // F) Record overlapping ranges 45932 45933 45934 if (start !== null && end !== null) { 45935 ranges.push([start, end]); 45936 start = null; 45937 end = null; 45938 } 45939 } 45940 45941 return videojs$1.createTimeRanges(ranges); 45942 }; 45943 /** 45944 * @file virtual-source-buffer.js 45945 */ 45946 45947 45948 var ONE_SECOND_IN_TS$2 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the 45949 // state of the `updating` property manually. We have to do this because 45950 // Firefox changes `updating` to false long before triggering `updateend` 45951 // events and that was causing strange problems in videojs-contrib-hls 45952 45953 var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) { 45954 var sourceBuffer = mediaSource.addSourceBuffer(mimeType); 45955 var wrapper = Object.create(null); 45956 wrapper.updating = false; 45957 wrapper.realBuffer_ = sourceBuffer; 45958 45959 var _loop = function _loop(key) { 45960 if (typeof sourceBuffer[key] === 'function') { 45961 wrapper[key] = function () { 45962 return sourceBuffer[key].apply(sourceBuffer, arguments); 45963 }; 45964 } else if (typeof wrapper[key] === 'undefined') { 45965 Object.defineProperty(wrapper, key, { 45966 get: function get$$1() { 45967 return sourceBuffer[key]; 45968 }, 45969 set: function set$$1(v) { 45970 return sourceBuffer[key] = v; 45971 } 45972 }); 45973 } 45974 }; 45975 45976 for (var key in sourceBuffer) { 45977 _loop(key); 45978 } 45979 45980 return wrapper; 45981 }; 45982 /** 45983 * VirtualSourceBuffers exist so that we can transmux non native formats 45984 * into a native format, but keep the same api as a native source buffer. 45985 * It creates a transmuxer, that works in its own thread (a web worker) and 45986 * that transmuxer muxes the data into a native format. VirtualSourceBuffer will 45987 * then send all of that data to the naive sourcebuffer so that it is 45988 * indestinguishable from a natively supported format. 45989 * 45990 * @param {HtmlMediaSource} mediaSource the parent mediaSource 45991 * @param {Array} codecs array of codecs that we will be dealing with 45992 * @class VirtualSourceBuffer 45993 * @extends video.js.EventTarget 45994 */ 45995 45996 45997 var VirtualSourceBuffer = function (_videojs$EventTarget) { 45998 inherits$2(VirtualSourceBuffer, _videojs$EventTarget); 45999 46000 function VirtualSourceBuffer(mediaSource, codecs) { 46001 classCallCheck$1(this, VirtualSourceBuffer); 46002 46003 var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget)); 46004 46005 _this.timestampOffset_ = 0; 46006 _this.pendingBuffers_ = []; 46007 _this.bufferUpdating_ = false; 46008 _this.mediaSource_ = mediaSource; 46009 _this.codecs_ = codecs; 46010 _this.audioCodec_ = null; 46011 _this.videoCodec_ = null; 46012 _this.audioDisabled_ = false; 46013 _this.appendAudioInitSegment_ = true; 46014 _this.gopBuffer_ = []; 46015 _this.timeMapping_ = 0; 46016 _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11; 46017 var options = { 46018 remux: false, 46019 alignGopsAtEnd: _this.safeAppend_ 46020 }; 46021 46022 _this.codecs_.forEach(function (codec) { 46023 if (isAudioCodec(codec)) { 46024 _this.audioCodec_ = codec; 46025 } else if (isVideoCodec(codec)) { 46026 _this.videoCodec_ = codec; 46027 } 46028 }); // append muxed segments to their respective native buffers as 46029 // soon as they are available 46030 46031 46032 _this.transmuxer_ = new TransmuxWorker(); 46033 46034 _this.transmuxer_.postMessage({ 46035 action: 'init', 46036 options: options 46037 }); 46038 46039 _this.transmuxer_.onmessage = function (event) { 46040 if (event.data.action === 'data') { 46041 return _this.data_(event); 46042 } 46043 46044 if (event.data.action === 'done') { 46045 return _this.done_(event); 46046 } 46047 46048 if (event.data.action === 'gopInfo') { 46049 return _this.appendGopInfo_(event); 46050 } 46051 46052 if (event.data.action === 'videoSegmentTimingInfo') { 46053 return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo); 46054 } 46055 }; // this timestampOffset is a property with the side-effect of resetting 46056 // baseMediaDecodeTime in the transmuxer on the setter 46057 46058 46059 Object.defineProperty(_this, 'timestampOffset', { 46060 get: function get$$1() { 46061 return this.timestampOffset_; 46062 }, 46063 set: function set$$1(val) { 46064 if (typeof val === 'number' && val >= 0) { 46065 this.timestampOffset_ = val; 46066 this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline 46067 46068 this.gopBuffer_.length = 0; 46069 this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to 46070 // the desired timestampOffset for the next segment 46071 46072 this.transmuxer_.postMessage({ 46073 action: 'setTimestampOffset', 46074 timestampOffset: val 46075 }); 46076 } 46077 } 46078 }); // setting the append window affects both source buffers 46079 46080 Object.defineProperty(_this, 'appendWindowStart', { 46081 get: function get$$1() { 46082 return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart; 46083 }, 46084 set: function set$$1(start) { 46085 if (this.videoBuffer_) { 46086 this.videoBuffer_.appendWindowStart = start; 46087 } 46088 46089 if (this.audioBuffer_) { 46090 this.audioBuffer_.appendWindowStart = start; 46091 } 46092 } 46093 }); // this buffer is "updating" if either of its native buffers are 46094 46095 Object.defineProperty(_this, 'updating', { 46096 get: function get$$1() { 46097 return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating); 46098 } 46099 }); // the buffered property is the intersection of the buffered 46100 // ranges of the native source buffers 46101 46102 Object.defineProperty(_this, 'buffered', { 46103 get: function get$$1() { 46104 return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_); 46105 } 46106 }); 46107 return _this; 46108 } 46109 /** 46110 * When we get a data event from the transmuxer 46111 * we call this function and handle the data that 46112 * was sent to us 46113 * 46114 * @private 46115 * @param {Event} event the data event from the transmuxer 46116 */ 46117 46118 46119 createClass$1(VirtualSourceBuffer, [{ 46120 key: 'data_', 46121 value: function data_(event) { 46122 var segment = event.data.segment; // Cast ArrayBuffer to TypedArray 46123 46124 segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength); 46125 segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength); 46126 createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array 46127 46128 this.pendingBuffers_.push(segment); 46129 return; 46130 } 46131 /** 46132 * When we get a done event from the transmuxer 46133 * we call this function and we process all 46134 * of the pending data that we have been saving in the 46135 * data_ function 46136 * 46137 * @private 46138 * @param {Event} event the done event from the transmuxer 46139 */ 46140 46141 }, { 46142 key: 'done_', 46143 value: function done_(event) { 46144 // Don't process and append data if the mediaSource is closed 46145 if (this.mediaSource_.readyState === 'closed') { 46146 this.pendingBuffers_.length = 0; 46147 return; 46148 } // All buffers should have been flushed from the muxer 46149 // start processing anything we have received 46150 46151 46152 this.processPendingSegments_(); 46153 return; 46154 } 46155 }, { 46156 key: 'videoSegmentTimingInfo_', 46157 value: function videoSegmentTimingInfo_(timingInfo) { 46158 var timingInfoInSeconds = { 46159 start: { 46160 decode: timingInfo.start.dts / ONE_SECOND_IN_TS$2, 46161 presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$2 46162 }, 46163 end: { 46164 decode: timingInfo.end.dts / ONE_SECOND_IN_TS$2, 46165 presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$2 46166 }, 46167 baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$2 46168 }; 46169 46170 if (timingInfo.prependedContentDuration) { 46171 timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$2; 46172 } 46173 46174 this.trigger({ 46175 type: 'videoSegmentTimingInfo', 46176 videoSegmentTimingInfo: timingInfoInSeconds 46177 }); 46178 } 46179 /** 46180 * Create our internal native audio/video source buffers and add 46181 * event handlers to them with the following conditions: 46182 * 1. they do not already exist on the mediaSource 46183 * 2. this VSB has a codec for them 46184 * 46185 * @private 46186 */ 46187 46188 }, { 46189 key: 'createRealSourceBuffers_', 46190 value: function createRealSourceBuffers_() { 46191 var _this2 = this; 46192 46193 var types = ['audio', 'video'];
46194 types.forEach(function (type) { 46195 // Don't create a SourceBuffer of this type if we don't have a 46196 // codec for it 46197 if (!_this2[type + 'Codec_']) { 46198 return; 46199 } // Do nothing if a SourceBuffer of this type already exists 46200 46201 46202 if (_this2[type + 'Buffer_']) { 46203 return; 46204 } 46205 46206 var buffer = null; // If the mediasource already has a SourceBuffer for the codec 46207 // use that 46208 46209 if (_this2.mediaSource_[type + 'Buffer_']) { 46210 buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled 46211 // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier 46212 // than createRealSourceBuffers_ is called to create the second 46213 // VirtualSourceBuffer because that happens as a side-effect of 46214 // videojs-contrib-hls starting the audioSegmentLoader. As a result, 46215 // the audioBuffer is essentially "ownerless" and no one will toggle
vendor: 30,121 bytes, lines 46216-47041
46216 // the `updating` state back to false once the `updateend` event is received 46217 // 46218 // Setting `updating` to false manually will work around this 46219 // situation and allow work to continue 46220 46221 buffer.updating = false; 46222 } else { 46223 var codecProperty = type + 'Codec_'; 46224 var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"'; 46225 buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType); 46226 _this2.mediaSource_[type + 'Buffer_'] = buffer; 46227 } 46228 46229 _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer 46230 46231 ['update', 'updatestart', 'updateend'].forEach(function (event) { 46232 buffer.addEventListener(event, function () { 46233 // if audio is disabled 46234 if (type === 'audio' && _this2.audioDisabled_) { 46235 return; 46236 } 46237 46238 if (event === 'updateend') { 46239 _this2[type + 'Buffer_'].updating = false; 46240 } 46241 46242 var shouldTrigger = types.every(function (t) { 46243 // skip checking audio's updating status if audio 46244 // is not enabled 46245 if (t === 'audio' && _this2.audioDisabled_) { 46246 return true; 46247 } // if the other type is updating we don't trigger 46248 46249 46250 if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) { 46251 return false; 46252 } 46253 46254 return true; 46255 }); 46256 46257 if (shouldTrigger) { 46258 return _this2.trigger(event); 46259 } 46260 }); 46261 }); 46262 }); 46263 } 46264 /** 46265 * Emulate the native mediasource function, but our function will 46266 * send all of the proposed segments to the transmuxer so that we 46267 * can transmux them before we append them to our internal 46268 * native source buffers in the correct format. 46269 * 46270 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 46271 * @param {Uint8Array} segment the segment to append to the buffer 46272 */ 46273 46274 }, { 46275 key: 'appendBuffer', 46276 value: function appendBuffer(segment) { 46277 // Start the internal "updating" state 46278 this.bufferUpdating_ = true; 46279 46280 if (this.audioBuffer_ && this.audioBuffer_.buffered.length) { 46281 var audioBuffered = this.audioBuffer_.buffered; 46282 this.transmuxer_.postMessage({ 46283 action: 'setAudioAppendStart', 46284 appendStart: audioBuffered.end(audioBuffered.length - 1) 46285 }); 46286 } 46287 46288 if (this.videoBuffer_) { 46289 this.transmuxer_.postMessage({ 46290 action: 'alignGopsWith', 46291 gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_) 46292 }); 46293 } 46294 46295 this.transmuxer_.postMessage({ 46296 action: 'push', 46297 // Send the typed-array of data as an ArrayBuffer so that 46298 // it can be sent as a "Transferable" and avoid the costly 46299 // memory copy 46300 data: segment.buffer, 46301 // To recreate the original typed-array, we need information 46302 // about what portion of the ArrayBuffer it was a view into 46303 byteOffset: segment.byteOffset, 46304 byteLength: segment.byteLength 46305 }, [segment.buffer]); 46306 this.transmuxer_.postMessage({ 46307 action: 'flush' 46308 }); 46309 } 46310 /** 46311 * Appends gop information (timing and byteLength) received by the transmuxer for the 46312 * gops appended in the last call to appendBuffer 46313 * 46314 * @param {Event} event 46315 * The gopInfo event from the transmuxer 46316 * @param {Array} event.data.gopInfo 46317 * List of gop info to append 46318 */ 46319 46320 }, { 46321 key: 'appendGopInfo_', 46322 value: function appendGopInfo_(event) { 46323 this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_); 46324 } 46325 /** 46326 * Emulate the native mediasource function and remove parts 46327 * of the buffer from any of our internal buffers that exist 46328 * 46329 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 46330 * @param {Double} start position to start the remove at 46331 * @param {Double} end position to end the remove at 46332 */ 46333 46334 }, { 46335 key: 'remove', 46336 value: function remove(start, end) { 46337 if (this.videoBuffer_) { 46338 this.videoBuffer_.updating = true; 46339 this.videoBuffer_.remove(start, end); 46340 this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_); 46341 } 46342 46343 if (!this.audioDisabled_ && this.audioBuffer_) { 46344 this.audioBuffer_.updating = true; 46345 this.audioBuffer_.remove(start, end); 46346 } // Remove Metadata Cues (id3) 46347 46348 46349 removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions 46350 46351 if (this.inbandTextTracks_) { 46352 for (var track in this.inbandTextTracks_) { 46353 removeCuesFromTrack(start, end, this.inbandTextTracks_[track]); 46354 } 46355 } 46356 } 46357 /** 46358 * Process any segments that the muxer has output 46359 * Concatenate segments together based on type and append them into 46360 * their respective sourceBuffers 46361 * 46362 * @private 46363 */ 46364 46365 }, { 46366 key: 'processPendingSegments_', 46367 value: function processPendingSegments_() { 46368 var sortedSegments = { 46369 video: { 46370 segments: [], 46371 bytes: 0 46372 }, 46373 audio: { 46374 segments: [], 46375 bytes: 0 46376 }, 46377 captions: [], 46378 metadata: [] 46379 }; 46380 46381 if (!this.pendingBuffers_.length) { 46382 // We are no longer in the internal "updating" state 46383 this.trigger('updateend'); 46384 this.bufferUpdating_ = false; 46385 return; 46386 } // Sort segments into separate video/audio arrays and 46387 // keep track of their total byte lengths 46388 46389 46390 sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) { 46391 var type = segment.type; 46392 var data = segment.data; 46393 var initSegment = segment.initSegment; 46394 segmentObj[type].segments.push(data); 46395 segmentObj[type].bytes += data.byteLength; 46396 segmentObj[type].initSegment = initSegment; // Gather any captions into a single array 46397 46398 if (segment.captions) { 46399 segmentObj.captions = segmentObj.captions.concat(segment.captions); 46400 } 46401 46402 if (segment.info) { 46403 segmentObj[type].info = segment.info; 46404 } // Gather any metadata into a single array 46405 46406 46407 if (segment.metadata) { 46408 segmentObj.metadata = segmentObj.metadata.concat(segment.metadata); 46409 } 46410 46411 return segmentObj; 46412 }, sortedSegments); // Create the real source buffers if they don't exist by now since we 46413 // finally are sure what tracks are contained in the source 46414 46415 if (!this.videoBuffer_ && !this.audioBuffer_) { 46416 // Remove any codecs that may have been specified by default but 46417 // are no longer applicable now 46418 if (sortedSegments.video.bytes === 0) { 46419 this.videoCodec_ = null; 46420 } 46421 46422 if (sortedSegments.audio.bytes === 0) { 46423 this.audioCodec_ = null; 46424 } 46425 46426 this.createRealSourceBuffers_(); 46427 } 46428 46429 if (sortedSegments.audio.info) { 46430 this.mediaSource_.trigger({ 46431 type: 'audioinfo', 46432 info: sortedSegments.audio.info 46433 }); 46434 } 46435 46436 if (sortedSegments.video.info) { 46437 this.mediaSource_.trigger({ 46438 type: 'videoinfo', 46439 info: sortedSegments.video.info 46440 }); 46441 } 46442 46443 if (this.appendAudioInitSegment_) { 46444 if (!this.audioDisabled_ && this.audioBuffer_) { 46445 sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment); 46446 sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength; 46447 } 46448 46449 this.appendAudioInitSegment_ = false; 46450 } 46451 46452 var triggerUpdateend = false; // Merge multiple video and audio segments into one and append 46453 46454 if (this.videoBuffer_ && sortedSegments.video.bytes) { 46455 sortedSegments.video.segments.unshift(sortedSegments.video.initSegment); 46456 sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength; 46457 this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_); 46458 } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) { 46459 // The transmuxer did not return any bytes of video, meaning it was all trimmed 46460 // for gop alignment. Since we have a video buffer and audio is disabled, updateend 46461 // will never be triggered by this source buffer, which will cause contrib-hls 46462 // to be stuck forever waiting for updateend. If audio is not disabled, updateend 46463 // will be triggered by the audio buffer, which will be sent upwards since the video 46464 // buffer will not be in an updating state. 46465 triggerUpdateend = true; 46466 } // Add text-track data for all 46467 46468 46469 addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata); 46470 46471 if (!this.audioDisabled_ && this.audioBuffer_) { 46472 this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_); 46473 } 46474 46475 this.pendingBuffers_.length = 0; 46476 46477 if (triggerUpdateend) { 46478 this.trigger('updateend'); 46479 } // We are no longer in the internal "updating" state 46480 46481 46482 this.bufferUpdating_ = false; 46483 } 46484 /** 46485 * Combine all segments into a single Uint8Array and then append them 46486 * to the destination buffer 46487 * 46488 * @param {Object} segmentObj 46489 * @param {SourceBuffer} destinationBuffer native source buffer to append data to 46490 * @private 46491 */ 46492 46493 }, { 46494 key: 'concatAndAppendSegments_', 46495 value: function concatAndAppendSegments_(segmentObj, destinationBuffer) { 46496 var offset = 0; 46497 var tempBuffer = void 0; 46498 46499 if (segmentObj.bytes) { 46500 tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array 46501 46502 segmentObj.segments.forEach(function (segment) { 46503 tempBuffer.set(segment, offset); 46504 offset += segment.byteLength; 46505 }); 46506 46507 try { 46508 destinationBuffer.updating = true; 46509 destinationBuffer.appendBuffer(tempBuffer); 46510 } catch (error) { 46511 if (this.mediaSource_.player_) { 46512 this.mediaSource_.player_.error({ 46513 code: -3, 46514 type: 'APPEND_BUFFER_ERR', 46515 message: error.message, 46516 originalError: error 46517 }); 46518 } 46519 } 46520 } 46521 } 46522 /** 46523 * Emulate the native mediasource function. abort any soureBuffer 46524 * actions and throw out any un-appended data. 46525 * 46526 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 46527 */ 46528 46529 }, { 46530 key: 'abort', 46531 value: function abort() { 46532 if (this.videoBuffer_) { 46533 this.videoBuffer_.abort(); 46534 } 46535 46536 if (!this.audioDisabled_ && this.audioBuffer_) { 46537 this.audioBuffer_.abort(); 46538 } 46539 46540 if (this.transmuxer_) { 46541 this.transmuxer_.postMessage({ 46542 action: 'reset' 46543 }); 46544 } 46545 46546 this.pendingBuffers_.length = 0; 46547 this.bufferUpdating_ = false; 46548 } 46549 }]); 46550 return VirtualSourceBuffer; 46551 }(videojs$1.EventTarget); 46552 /** 46553 * @file html-media-source.js 46554 */ 46555 46556 /** 46557 * Our MediaSource implementation in HTML, mimics native 46558 * MediaSource where/if possible. 46559 * 46560 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 46561 * @class HtmlMediaSource 46562 * @extends videojs.EventTarget 46563 */ 46564 46565 46566 var HtmlMediaSource = function (_videojs$EventTarget) { 46567 inherits$2(HtmlMediaSource, _videojs$EventTarget); 46568 46569 function HtmlMediaSource() { 46570 classCallCheck$1(this, HtmlMediaSource); 46571 46572 var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this)); 46573 46574 var property = void 0; 46575 _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default 46576 46577 for (property in _this.nativeMediaSource_) { 46578 if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') { 46579 _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_); 46580 } 46581 } // emulate `duration` and `seekable` until seeking can be 46582 // handled uniformly for live streams 46583 // see https://github.com/w3c/media-source/issues/5 46584 46585 46586 _this.duration_ = NaN; 46587 Object.defineProperty(_this, 'duration', { 46588 get: function get$$1() { 46589 if (this.duration_ === Infinity) { 46590 return this.duration_; 46591 } 46592 46593 return this.nativeMediaSource_.duration; 46594 }, 46595 set: function set$$1(duration) { 46596 this.duration_ = duration; 46597 46598 if (duration !== Infinity) { 46599 this.nativeMediaSource_.duration = duration; 46600 return; 46601 } 46602 } 46603 }); 46604 Object.defineProperty(_this, 'seekable', { 46605 get: function get$$1() { 46606 if (this.duration_ === Infinity) { 46607 return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]); 46608 } 46609 46610 return this.nativeMediaSource_.seekable; 46611 } 46612 }); 46613 Object.defineProperty(_this, 'readyState', { 46614 get: function get$$1() { 46615 return this.nativeMediaSource_.readyState; 46616 } 46617 }); 46618 Object.defineProperty(_this, 'activeSourceBuffers', { 46619 get: function get$$1() { 46620 return this.activeSourceBuffers_; 46621 } 46622 }); // the list of virtual and native SourceBuffers created by this 46623 // MediaSource 46624 46625 _this.sourceBuffers = []; 46626 _this.activeSourceBuffers_ = []; 46627 /** 46628 * update the list of active source buffers based upon various 46629 * imformation from HLS and video.js 46630 * 46631 * @private 46632 */ 46633 46634 _this.updateActiveSourceBuffers_ = function () { 46635 // Retain the reference but empty the array 46636 _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will 46637 // be disabled based on the codec of the source buffer 46638 46639 if (_this.sourceBuffers.length === 1) { 46640 var sourceBuffer = _this.sourceBuffers[0]; 46641 sourceBuffer.appendAudioInitSegment_ = true; 46642 sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_; 46643 46644 _this.activeSourceBuffers_.push(sourceBuffer); 46645 46646 return; 46647 } // There are 2 source buffers, a combined (possibly video only) source buffer and 46648 // and an audio only source buffer. 46649 // By default, the audio in the combined virtual source buffer is enabled 46650 // and the audio-only source buffer (if it exists) is disabled. 46651 46652 46653 var disableCombined = false; 46654 var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects? 46655 // safari may do something like this 46656 46657 for (var i = 0; i < _this.player_.audioTracks().length; i++) { 46658 var track = _this.player_.audioTracks()[i]; 46659 46660 if (track.enabled && track.kind !== 'main') { 46661 // The enabled track is an alternate audio track so disable the audio in 46662 // the combined source buffer and enable the audio-only source buffer. 46663 disableCombined = true; 46664 disableAudioOnly = false; 46665 break; 46666 } 46667 } 46668 46669 _this.sourceBuffers.forEach(function (sourceBuffer, index) { 46670 /* eslinst-disable */ 46671 // TODO once codecs are required, we can switch to using the codecs to determine 46672 // what stream is the video stream, rather than relying on videoTracks 46673 46674 /* eslinst-enable */ 46675 sourceBuffer.appendAudioInitSegment_ = true; 46676 46677 if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 46678 // combined 46679 sourceBuffer.audioDisabled_ = disableCombined; 46680 } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) { 46681 // If the "combined" source buffer is video only, then we do not want 46682 // disable the audio-only source buffer (this is mostly for demuxed 46683 // audio and video hls) 46684 sourceBuffer.audioDisabled_ = true; 46685 disableAudioOnly = false; 46686 } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 46687 // audio only 46688 // In the case of audio only with alternate audio and disableAudioOnly is true 46689 // this means we want to disable the audio on the alternate audio sourcebuffer 46690 // but not the main "combined" source buffer. The "combined" source buffer is 46691 // always at index 0, so this ensures audio won't be disabled in both source 46692 // buffers. 46693 sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly; 46694 46695 if (sourceBuffer.audioDisabled_) { 46696 return; 46697 } 46698 } 46699 46700 _this.activeSourceBuffers_.push(sourceBuffer); 46701 }); 46702 }; 46703 46704 _this.onPlayerMediachange_ = function () { 46705 _this.sourceBuffers.forEach(function (sourceBuffer) { 46706 sourceBuffer.appendAudioInitSegment_ = true; 46707 }); 46708 }; 46709 46710 _this.onHlsReset_ = function () { 46711 _this.sourceBuffers.forEach(function (sourceBuffer) { 46712 if (sourceBuffer.transmuxer_) { 46713 sourceBuffer.transmuxer_.postMessage({ 46714 action: 'resetCaptions' 46715 }); 46716 } 46717 }); 46718 }; 46719 46720 _this.onHlsSegmentTimeMapping_ = function (event) { 46721 _this.sourceBuffers.forEach(function (buffer) { 46722 return buffer.timeMapping_ = event.mapping; 46723 }); 46724 }; // Re-emit MediaSource events on the polyfill 46725 46726 46727 ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) { 46728 this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this)); 46729 }, _this); // capture the associated player when the MediaSource is 46730 // successfully attached 46731 46732 _this.on('sourceopen', function (event) { 46733 // Get the player this MediaSource is attached to 46734 var video = document.querySelector('[src="' + _this.url_ + '"]'); 46735 46736 if (!video) { 46737 return; 46738 } 46739 46740 _this.player_ = videojs$1(video.parentNode); 46741 46742 if (!_this.player_) { 46743 return; 46744 } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader 46745 // resets its state and flushes the buffer 46746 46747 46748 _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main 46749 // SegmentLoader inspects an MTS segment and has an accurate stream to display 46750 // time mapping 46751 46752 46753 _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_); 46754 46755 if (_this.player_.audioTracks && _this.player_.audioTracks()) { 46756 _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_); 46757 46758 _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_); 46759 46760 _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_); 46761 } 46762 46763 _this.player_.on('mediachange', _this.onPlayerMediachange_); 46764 }); 46765 46766 _this.on('sourceended', function (event) { 46767 var duration = durationOfVideo(_this.duration); 46768 46769 for (var i = 0; i < _this.sourceBuffers.length; i++) { 46770 var sourcebuffer = _this.sourceBuffers[i]; 46771 var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues; 46772 46773 if (cues && cues.length) { 46774 cues[cues.length - 1].endTime = duration; 46775 } 46776 } 46777 }); // explicitly terminate any WebWorkers that were created 46778 // by SourceHandlers 46779 46780 46781 _this.on('sourceclose', function (event) { 46782 this.sourceBuffers.forEach(function (sourceBuffer) { 46783 if (sourceBuffer.transmuxer_) { 46784 sourceBuffer.transmuxer_.terminate(); 46785 } 46786 }); 46787 this.sourceBuffers.length = 0; 46788 46789 if (!this.player_) { 46790 return; 46791 } 46792 46793 if (this.player_.audioTracks && this.player_.audioTracks()) { 46794 this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_); 46795 this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_); 46796 this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_); 46797 } // We can only change this if the player hasn't been disposed of yet 46798 // because `off` eventually tries to use the el_ property. If it has 46799 // been disposed of, then don't worry about it because there are no 46800 // event handlers left to unbind anyway 46801 46802 46803 if (this.player_.el_) { 46804 this.player_.off('mediachange', this.onPlayerMediachange_); 46805 } 46806 46807 if (this.player_.tech_ && this.player_.tech_.el_) { 46808 this.player_.tech_.off('hls-reset', this.onHlsReset_); 46809 this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_); 46810 } 46811 }); 46812 46813 return _this; 46814 } 46815 /** 46816 * Add a range that that can now be seeked to. 46817 * 46818 * @param {Double} start where to start the addition 46819 * @param {Double} end where to end the addition 46820 * @private 46821 */ 46822 46823 46824 createClass$1(HtmlMediaSource, [{ 46825 key: 'addSeekableRange_', 46826 value: function addSeekableRange_(start, end) { 46827 var error = void 0; 46828 46829 if (this.duration !== Infinity) { 46830 error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity'); 46831 error.name = 'InvalidStateError'; 46832 error.code = 11; 46833 throw error; 46834 } 46835 46836 if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) { 46837 this.nativeMediaSource_.duration = end; 46838 } 46839 } 46840 /** 46841 * Add a source buffer to the media source. 46842 * 46843 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 46844 * @param {String} type the content-type of the content 46845 * @return {Object} the created source buffer 46846 */ 46847 46848 }, { 46849 key: 'addSourceBuffer', 46850 value: function addSourceBuffer(type) { 46851 var buffer = void 0; 46852 var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport 46853 // stream segments into fragmented MP4s 46854 46855 if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) { 46856 var codecs = []; 46857 46858 if (parsedType.parameters && parsedType.parameters.codecs) { 46859 codecs = parsedType.parameters.codecs.split(','); 46860 codecs = translateLegacyCodecs(codecs); 46861 codecs = codecs.filter(function (codec) { 46862 return isAudioCodec(codec) || isVideoCodec(codec); 46863 }); 46864 } 46865 46866 if (codecs.length === 0) { 46867 codecs = ['avc1.4d400d', 'mp4a.40.2']; 46868 } 46869 46870 buffer = new VirtualSourceBuffer(this, codecs); 46871 46872 if (this.sourceBuffers.length !== 0) { 46873 // If another VirtualSourceBuffer already exists, then we are creating a 46874 // SourceBuffer for an alternate audio track and therefore we know that 46875 // the source has both an audio and video track. 46876 // That means we should trigger the manual creation of the real 46877 // SourceBuffers instead of waiting for the transmuxer to return data 46878 this.sourceBuffers[0].createRealSourceBuffers_(); 46879 buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if 46880 // a second source buffer is ever created 46881 46882 this.sourceBuffers[0].audioDisabled_ = true; 46883 } 46884 } else { 46885 // delegate to the native implementation 46886 buffer = this.nativeMediaSource_.addSourceBuffer(type); 46887 } 46888 46889 this.sourceBuffers.push(buffer); 46890 return buffer; 46891 } 46892 }]); 46893 return HtmlMediaSource; 46894 }(videojs$1.EventTarget); 46895 /** 46896 * @file videojs-contrib-media-sources.js 46897 */ 46898 46899 46900 var urlCount = 0; // ------------ 46901 // Media Source 46902 // ------------ 46903 // store references to the media sources so they can be connected 46904 // to a video element (a swf object) 46905 // TODO: can we store this somewhere local to this module? 46906 46907 videojs$1.mediaSources = {}; 46908 /** 46909 * Provide a method for a swf object to notify JS that a 46910 * media source is now open. 46911 * 46912 * @param {String} msObjectURL string referencing the MSE Object URL 46913 * @param {String} swfId the swf id 46914 */ 46915 46916 var open = function open(msObjectURL, swfId) { 46917 var mediaSource = videojs$1.mediaSources[msObjectURL]; 46918 46919 if (mediaSource) { 46920 mediaSource.trigger({ 46921 type: 'sourceopen', 46922 swfId: swfId 46923 }); 46924 } else { 46925 throw new Error('Media Source not found (Video.js)'); 46926 } 46927 }; 46928 /** 46929 * Check to see if the native MediaSource object exists and supports 46930 * an MP4 container with both H.264 video and AAC-LC audio. 46931 * 46932 * @return {Boolean} if native media sources are supported 46933 */ 46934 46935 46936 var supportsNativeMediaSources = function supportsNativeMediaSources() { 46937 return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"'); 46938 }; 46939 /** 46940 * An emulation of the MediaSource API so that we can support 46941 * native and non-native functionality. returns an instance of 46942 * HtmlMediaSource. 46943 * 46944 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource 46945 */ 46946 46947 46948 var MediaSource = function MediaSource() { 46949 this.MediaSource = { 46950 open: open, 46951 supportsNativeMediaSources: supportsNativeMediaSources 46952 }; 46953 46954 if (supportsNativeMediaSources()) { 46955 return new HtmlMediaSource(); 46956 } 46957 46958 throw new Error('Cannot use create a virtual MediaSource for this video'); 46959 }; 46960 46961 MediaSource.open = open; 46962 MediaSource.supportsNativeMediaSources = supportsNativeMediaSources; 46963 /** 46964 * A wrapper around the native URL for our MSE object 46965 * implementation, this object is exposed under videojs.URL 46966 * 46967 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL 46968 */ 46969 46970 var URL$1 = { 46971 /** 46972 * A wrapper around the native createObjectURL for our objects. 46973 * This function maps a native or emulated mediaSource to a blob 46974 * url so that it can be loaded into video.js 46975 * 46976 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL 46977 * @param {MediaSource} object the object to create a blob url to 46978 */ 46979 createObjectURL: function createObjectURL(object) { 46980 var objectUrlPrefix = 'blob:vjs-media-source/'; 46981 var url = void 0; // use the native MediaSource to generate an object URL 46982 46983 if (object instanceof HtmlMediaSource) { 46984 url = window$1.URL.createObjectURL(object.nativeMediaSource_); 46985 object.url_ = url; 46986 return url; 46987 } // if the object isn't an emulated MediaSource, delegate to the 46988 // native implementation 46989 46990 46991 if (!(object instanceof HtmlMediaSource)) { 46992 url = window$1.URL.createObjectURL(object); 46993 object.url_ = url; 46994 return url; 46995 } // build a URL that can be used to map back to the emulated 46996 // MediaSource 46997 46998 46999 url = objectUrlPrefix + urlCount; 47000 urlCount++; // setup the mapping back to object 47001 47002 videojs$1.mediaSources[url] = object; 47003 return url; 47004 } 47005 }; 47006 videojs$1.MediaSource = MediaSource; 47007 videojs$1.URL = URL$1; 47008 var EventTarget$1$1 = videojs$1.EventTarget, 47009 mergeOptions$2 = videojs$1.mergeOptions; 47010 /** 47011 * Returns a new master manifest that is the result of merging an updated master manifest 47012 * into the original version. 47013 * 47014 * @param {Object} oldMaster 47015 * The old parsed mpd object 47016 * @param {Object} newMaster 47017 * The updated parsed mpd object 47018 * @return {Object} 47019 * A new object representing the original master manifest with the updated media 47020 * playlists merged in 47021 */ 47022 47023 var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) { 47024 var noChanges = void 0; 47025 var update = mergeOptions$2(oldMaster, { 47026 // These are top level properties that can be updated 47027 duration: newMaster.duration, 47028 minimumUpdatePeriod: newMaster.minimumUpdatePeriod 47029 }); // First update the playlists in playlist list 47030 47031 for (var i = 0; i < newMaster.playlists.length; i++) { 47032 var playlistUpdate = updateMaster(update, newMaster.playlists[i]); 47033 47034 if (playlistUpdate) { 47035 update = playlistUpdate; 47036 } else { 47037 noChanges = true; 47038 } 47039 } // Then update media group playlists 47040 47041
47042 forEachMediaGroup(newMaster, function (properties, type, group, label) { 47043 if (properties.playlists && properties.playlists.length) { 47044 var uri = properties.playlists[0].uri; 47045 47046 var _playlistUpdate = updateMaster(update, properties.playlists[0]); 47047 47048 if (_playlistUpdate) { 47049 update = _playlistUpdate; // update the playlist reference within media groups 47050 47051 update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri]; 47052 noChanges = false; 47053 } 47054 } 47055 }); 47056 47057 if (noChanges) { 47058 return null; 47059 } 47060 47061 return update; 47062 }; 47063 47064 var generateSidxKey = function generateSidxKey(sidxInfo) { 47065 // should be non-inclusive 47066 var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1; 47067 return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd; 47068 }; // SIDX should be equivalent if the URI and byteranges of the SIDX match. 47069 // If the SIDXs have maps, the two maps should match, 47070 // both `a` and `b` missing SIDXs is considered matching. 47071 // If `a` or `b` but not both have a map, they aren't matching. 47072 47073 47074 var equivalentSidx = function equivalentSidx(a, b) { 47075 var neitherMap = Boolean(!a.map && !b.map); 47076 var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length); 47077 return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length; 47078 }; // exported for testing 47079 47080 47081 var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) { 47082 var newSidxMapping = {}; 47083 47084 for (var uri in playlists) { 47085 var playlist = playlists[uri]; 47086 var currentSidxInfo = playlist.sidx; 47087 47088 if (currentSidxInfo) { 47089 var key = generateSidxKey(currentSidxInfo); 47090 47091 if (!oldSidxMapping[key]) { 47092 break; 47093 } 47094 47095 var savedSidxInfo = oldSidxMapping[key].sidxInfo; 47096 47097 if (equivalentSidx(savedSidxInfo, currentSidxInfo)) { 47098 newSidxMapping[key] = oldSidxMapping[key]; 47099 } 47100 } 47101 } 47102 47103 return newSidxMapping; 47104 }; 47105 /** 47106 * A function that filters out changed items as they need to be requested separately. 47107 * 47108 * The method is exported for testing 47109 * 47110 * @param {Object} masterXml the mpd XML 47111 * @param {string} srcUrl the mpd url 47112 * @param {Date} clientOffset a time difference between server and client (passed through and not used) 47113 * @param {Object} oldSidxMapping the SIDX to compare against 47114 */ 47115 47116 47117 var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) { 47118 // Don't pass current sidx mapping 47119 var master = parse(masterXml, { 47120 manifestUri: srcUrl, 47121 clientOffset: clientOffset 47122 }); 47123 var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping); 47124 var mediaGroupSidx = videoSidx; 47125 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 47126 if (properties.playlists && properties.playlists.length) { 47127 var playlists = properties.playlists; 47128 mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping)); 47129 } 47130 }); 47131 return mediaGroupSidx; 47132 }; // exported for testing 47133 47134 47135 var requestSidx_ = function requestSidx_(sidxRange, playlist, xhr, options, finishProcessingFn) { 47136 var sidxInfo = { 47137 // resolve the segment URL relative to the playlist 47138 uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri), 47139 // resolvedUri: sidxRange.resolvedUri, 47140 byterange: sidxRange.byterange, 47141 // the segment's playlist 47142 playlist: playlist 47143 }; 47144 var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, { 47145 responseType: 'arraybuffer', 47146 headers: segmentXhrHeaders(sidxInfo) 47147 }); 47148 return xhr(sidxRequestOptions, finishProcessingFn); 47149 }; 47150 47151 var DashPlaylistLoader = function (_EventTarget) { 47152 inherits$2(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent 47153 // playlist loader setups from media groups will expect to be able to pass a playlist 47154 // (since there aren't external URLs to media playlists with DASH) 47155 47156 function DashPlaylistLoader(srcUrlOrPlaylist, hls) { 47157 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 47158 var masterPlaylistLoader = arguments[3]; 47159 classCallCheck$1(this, DashPlaylistLoader); 47160 47161 var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this)); 47162 47163 var _options$withCredenti = options.withCredentials, 47164 withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti, 47165 _options$handleManife = options.handleManifestRedirects, 47166 handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife; 47167 _this.hls_ = hls; 47168 _this.withCredentials = withCredentials; 47169 _this.handleManifestRedirects = handleManifestRedirects; 47170 47171 if (!srcUrlOrPlaylist) { 47172 throw new Error('A non-empty playlist URL or playlist is required'); 47173 } // event naming? 47174 47175 47176 _this.on('minimumUpdatePeriod', function () { 47177 _this.refreshXml_(); 47178 }); // live playlist staleness timeout 47179 47180 47181 _this.on('mediaupdatetimeout', function () { 47182 _this.refreshMedia_(_this.media().uri); 47183 }); 47184 47185 _this.state = 'HAVE_NOTHING'; 47186 _this.loadedPlaylists_ = {}; // initialize the loader state 47187 // The masterPlaylistLoader will be created with a string 47188 47189 if (typeof srcUrlOrPlaylist === 'string') { 47190 _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes 47191 // once multi-period is refactored 47192 47193 _this.sidxMapping_ = {}; 47194 return possibleConstructorReturn$1(_this); 47195 } 47196 47197 _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist); 47198 47199 return _this; 47200 } 47201 47202 createClass$1(DashPlaylistLoader, [{ 47203 key: 'setupChildLoader', 47204 value: function setupChildLoader(masterPlaylistLoader, playlist) { 47205 this.masterPlaylistLoader_ = masterPlaylistLoader; 47206 this.childPlaylist_ = playlist; 47207 } 47208 }, { 47209 key: 'dispose', 47210 value: function dispose() { 47211 this.stopRequest(); 47212 this.loadedPlaylists_ = {}; 47213 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 47214 window$1.clearTimeout(this.mediaRequest_); 47215 window$1.clearTimeout(this.mediaUpdateTimeout); 47216 } 47217 }, { 47218 key: 'hasPendingRequest', 47219 value: function hasPendingRequest() { 47220 return this.request || this.mediaRequest_; 47221 } 47222 }, { 47223 key: 'stopRequest', 47224 value: function stopRequest() { 47225 if (this.request) { 47226 var oldRequest = this.request; 47227 this.request = null; 47228 oldRequest.onreadystatechange = null; 47229 oldRequest.abort(); 47230 } 47231 } 47232 }, { 47233 key: 'sidxRequestFinished_', 47234 value: function sidxRequestFinished_(playlist, master, startingState, doneFn) { 47235 var _this2 = this; 47236 47237 return function (err, request) { 47238 // disposed 47239 if (!_this2.request) { 47240 return; 47241 } // pending request is cleared 47242 47243 47244 _this2.request = null; 47245 47246 if (err) { 47247 _this2.error = { 47248 status: request.status, 47249 message: 'DASH playlist request error at URL: ' + playlist.uri, 47250 response: request.response, 47251 // MEDIA_ERR_NETWORK 47252 code: 2 47253 }; 47254 47255 if (startingState) { 47256 _this2.state = startingState; 47257 } 47258 47259 _this2.trigger('error'); 47260 47261 return doneFn(master, null); 47262 } 47263 47264 var bytes = new Uint8Array(request.response); 47265 var sidx = mp4Inspector.parseSidx(bytes.subarray(8)); 47266 return doneFn(master, sidx); 47267 }; 47268 } 47269 }, { 47270 key: 'media', 47271 value: function media(playlist) { 47272 var _this3 = this; // getter 47273 47274 47275 if (!playlist) { 47276 return this.media_; 47277 } // setter 47278 47279 47280 if (this.state === 'HAVE_NOTHING') { 47281 throw new Error('Cannot switch media playlist from ' + this.state); 47282 } 47283 47284 var startingState = this.state; // find the playlist object if the target playlist has been specified by URI 47285 47286 if (typeof playlist === 'string') { 47287 if (!this.master.playlists[playlist]) { 47288 throw new Error('Unknown playlist URI: ' + playlist); 47289 } 47290 47291 playlist = this.master.playlists[playlist]; 47292 } 47293 47294 var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately 47295 47296 if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) { 47297 this.state = 'HAVE_METADATA'; 47298 this.media_ = playlist; // trigger media change if the active media has been updated 47299 47300 if (mediaChange) { 47301 this.trigger('mediachanging'); 47302 this.trigger('mediachange'); 47303 } 47304 47305 return; 47306 } // switching to the active playlist is a no-op 47307 47308 47309 if (!mediaChange) { 47310 return; 47311 } // switching from an already loaded playlist 47312 47313 47314 if (this.media_) { 47315 this.trigger('mediachanging'); 47316 } 47317 47318 if (!playlist.sidx) { 47319 // Continue asynchronously if there is no sidx 47320 // wait one tick to allow haveMaster to run first on a child loader
47321 this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, { 47322 startingState: startingState, 47323 playlist: playlist 47324 }), 0); // exit early and don't do sidx work 47325 47326 return; 47327 } // we have sidx mappings 47328 47329 47330 var oldMaster = void 0; 47331 var sidxMapping = void 0; // sidxMapping is used when parsing the masterXml, so store 47332 // it on the masterPlaylistLoader 47333 47334 if (this.masterPlaylistLoader_) { 47335 oldMaster = this.masterPlaylistLoader_.master; 47336 sidxMapping = this.masterPlaylistLoader_.sidxMapping_; 47337 } else { 47338 oldMaster = this.master; 47339 sidxMapping = this.sidxMapping_; 47340 } 47341 47342 var sidxKey = generateSidxKey(playlist.sidx); 47343 sidxMapping[sidxKey] = { 47344 sidxInfo: playlist.sidx 47345 }; 47346 this.request = requestSidx_(playlist.sidx, playlist, this.hls_.xhr, { 47347 handleManifestRedirects: this.handleManifestRedirects 47348 }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) { 47349 if (!newMaster || !sidx) { 47350 throw new Error('failed to request sidx'); 47351 } // update loader's sidxMapping with parsed sidx box 47352 47353 47354 sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata 47355 47356 _this3.haveMetadata({ 47357 startingState: startingState, 47358 playlist: newMaster.playlists[playlist.uri] 47359 }); 47360 })); 47361 } 47362 }, { 47363 key: 'haveMetadata', 47364 value: function haveMetadata(_ref) { 47365 var startingState = _ref.startingState, 47366 playlist = _ref.playlist; 47367 this.state = 'HAVE_METADATA'; 47368 this.loadedPlaylists_[playlist.uri] = playlist; 47369 this.mediaRequest_ = null; // This will trigger loadedplaylist 47370 47371 this.refreshMedia_(playlist.uri); // fire loadedmetadata the first time a media playlist is loaded 47372 // to resolve setup of media groups 47373 47374 if (startingState === 'HAVE_MASTER') { 47375 this.trigger('loadedmetadata'); 47376 } else { 47377 // trigger media change if the active media has been updated 47378 this.trigger('mediachange'); 47379 } 47380 } 47381 }, { 47382 key: 'pause', 47383 value: function pause() { 47384 this.stopRequest(); 47385 window$1.clearTimeout(this.mediaUpdateTimeout); 47386 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 47387 47388 if (this.state === 'HAVE_NOTHING') { 47389 // If we pause the loader before any data has been retrieved, its as if we never 47390 // started, so reset to an unstarted state. 47391 this.started = false; 47392 } 47393 } 47394 }, { 47395 key: 'load', 47396 value: function load(isFinalRendition) { 47397 var _this4 = this; 47398 47399 window$1.clearTimeout(this.mediaUpdateTimeout); 47400 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 47401 var media = this.media(); 47402 47403 if (isFinalRendition) { 47404 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 47405 this.mediaUpdateTimeout = window$1.setTimeout(function () { 47406 return _this4.load(); 47407 }, delay); 47408 return; 47409 } // because the playlists are internal to the manifest, load should either load the 47410 // main manifest, or do nothing but trigger an event 47411 47412 47413 if (!this.started) { 47414 this.start(); 47415 return; 47416 } 47417 47418 this.trigger('loadedplaylist'); 47419 } 47420 /** 47421 * Parses the master xml string and updates playlist uri references 47422 * 47423 * @return {Object} 47424 * The parsed mpd manifest object 47425 */ 47426 47427 }, { 47428 key: 'parseMasterXml', 47429 value: function parseMasterXml() { 47430 var master = parse(this.masterXml_, { 47431 manifestUri: this.srcUrl, 47432 clientOffset: this.clientOffset_, 47433 sidxMapping: this.sidxMapping_ 47434 }); 47435 master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH 47436 // but reference playlists by their URI throughout the project 47437 // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes). 47438 47439 for (var i = 0; i < master.playlists.length; i++) { 47440 var phonyUri = 'placeholder-uri-' + i; 47441 master.playlists[i].uri = phonyUri; // set up by URI references 47442 47443 master.playlists[phonyUri] = master.playlists[i]; 47444 } // set up phony URIs for the media group playlists since we won't have external 47445 // URIs for DASH but reference playlists by their URI throughout the project 47446 47447
vendor: 25,700 bytes, lines 47448-48218
47448 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 47449 if (properties.playlists && properties.playlists.length) { 47450 var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey; 47451 47452 properties.playlists[0].uri = _phonyUri; // setup URI references 47453 47454 master.playlists[_phonyUri] = properties.playlists[0]; 47455 } 47456 }); 47457 setupMediaPlaylists(master); 47458 resolveMediaGroupUris(master); 47459 return master; 47460 } 47461 }, { 47462 key: 'start', 47463 value: function start() { 47464 var _this5 = this; 47465 47466 this.started = true; // We don't need to request the master manifest again 47467 // Call this asynchronously to match the xhr request behavior below 47468 47469 if (this.masterPlaylistLoader_) { 47470 this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0); 47471 return; 47472 } // request the specified URL 47473 47474 47475 this.request = this.hls_.xhr({ 47476 uri: this.srcUrl, 47477 withCredentials: this.withCredentials 47478 }, function (error, req) { 47479 // disposed 47480 if (!_this5.request) { 47481 return; 47482 } // clear the loader's request reference 47483 47484 47485 _this5.request = null; 47486 47487 if (error) { 47488 _this5.error = { 47489 status: req.status, 47490 message: 'DASH playlist request error at URL: ' + _this5.srcUrl, 47491 responseText: req.responseText, 47492 // MEDIA_ERR_NETWORK 47493 code: 2 47494 }; 47495 47496 if (_this5.state === 'HAVE_NOTHING') { 47497 _this5.started = false; 47498 } 47499 47500 return _this5.trigger('error'); 47501 } 47502 47503 _this5.masterXml_ = req.responseText; 47504 47505 if (req.responseHeaders && req.responseHeaders.date) { 47506 _this5.masterLoaded_ = Date.parse(req.responseHeaders.date); 47507 } else { 47508 _this5.masterLoaded_ = Date.now(); 47509 } 47510 47511 _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req); 47512 47513 _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5)); 47514 }); 47515 } 47516 /** 47517 * Parses the master xml for UTCTiming node to sync the client clock to the server 47518 * clock. If the UTCTiming node requires a HEAD or GET request, that request is made. 47519 * 47520 * @param {Function} done 47521 * Function to call when clock sync has completed 47522 */ 47523 47524 }, { 47525 key: 'syncClientServerClock_', 47526 value: function syncClientServerClock_(done) { 47527 var _this6 = this; 47528 47529 var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the 47530 // server clock 47531 47532 if (utcTiming === null) { 47533 this.clientOffset_ = this.masterLoaded_ - Date.now(); 47534 return done(); 47535 } 47536 47537 if (utcTiming.method === 'DIRECT') { 47538 this.clientOffset_ = utcTiming.value - Date.now(); 47539 return done(); 47540 } 47541 47542 this.request = this.hls_.xhr({ 47543 uri: resolveUrl$1(this.srcUrl, utcTiming.value), 47544 method: utcTiming.method, 47545 withCredentials: this.withCredentials 47546 }, function (error, req) { 47547 // disposed 47548 if (!_this6.request) { 47549 return; 47550 } 47551 47552 if (error) { 47553 // sync request failed, fall back to using date header from mpd 47554 // TODO: log warning 47555 _this6.clientOffset_ = _this6.masterLoaded_ - Date.now(); 47556 return done(); 47557 } 47558 47559 var serverTime = void 0; 47560 47561 if (utcTiming.method === 'HEAD') { 47562 if (!req.responseHeaders || !req.responseHeaders.date) { 47563 // expected date header not preset, fall back to using date header from mpd 47564 // TODO: log warning 47565 serverTime = _this6.masterLoaded_; 47566 } else { 47567 serverTime = Date.parse(req.responseHeaders.date); 47568 } 47569 } else { 47570 serverTime = Date.parse(req.responseText); 47571 } 47572 47573 _this6.clientOffset_ = serverTime - Date.now(); 47574 done(); 47575 }); 47576 } 47577 }, { 47578 key: 'haveMaster_', 47579 value: function haveMaster_() { 47580 this.state = 'HAVE_MASTER'; // clear media request 47581 47582 this.mediaRequest_ = null; 47583 47584 if (!this.masterPlaylistLoader_) { 47585 this.master = this.parseMasterXml(); // We have the master playlist at this point, so 47586 // trigger this to allow MasterPlaylistController 47587 // to make an initial playlist selection 47588 47589 this.trigger('loadedplaylist'); 47590 } else if (!this.media_) { 47591 // no media playlist was specifically selected so select 47592 // the one the child playlist loader was created with 47593 this.media(this.childPlaylist_); 47594 } 47595 } 47596 /** 47597 * Handler for after client/server clock synchronization has happened. Sets up 47598 * xml refresh timer if specificed by the manifest. 47599 */ 47600 47601 }, { 47602 key: 'onClientServerClockSync_', 47603 value: function onClientServerClockSync_() { 47604 var _this7 = this; 47605 47606 this.haveMaster_(); 47607 47608 if (!this.hasPendingRequest() && !this.media_) { 47609 this.media(this.master.playlists[0]); 47610 } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not 47611 // be refreshed when this is the case. The inter-op guide says that when the 47612 // minimumUpdatePeriod is 0, the manifest should outline all currently available 47613 // segments, but future segments may require an update. I think a good solution 47614 // would be to update the manifest at the same rate that the media playlists 47615 // are "refreshed", i.e. every targetDuration. 47616 47617 47618 if (this.master && this.master.minimumUpdatePeriod) { 47619 this.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () { 47620 _this7.trigger('minimumUpdatePeriod'); 47621 }, this.master.minimumUpdatePeriod); 47622 } 47623 } 47624 /** 47625 * Sends request to refresh the master xml and updates the parsed master manifest 47626 * TODO: Does the client offset need to be recalculated when the xml is refreshed? 47627 */ 47628 47629 }, { 47630 key: 'refreshXml_', 47631 value: function refreshXml_() { 47632 var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when 47633 // sidx is implemented 47634 47635 47636 this.request = this.hls_.xhr({ 47637 uri: this.srcUrl, 47638 withCredentials: this.withCredentials 47639 }, function (error, req) { 47640 // disposed 47641 if (!_this8.request) { 47642 return; 47643 } // clear the loader's request reference 47644 47645 47646 _this8.request = null; 47647 47648 if (error) { 47649 _this8.error = { 47650 status: req.status, 47651 message: 'DASH playlist request error at URL: ' + _this8.srcUrl, 47652 responseText: req.responseText, 47653 // MEDIA_ERR_NETWORK 47654 code: 2 47655 }; 47656 47657 if (_this8.state === 'HAVE_NOTHING') { 47658 _this8.started = false; 47659 } 47660 47661 return _this8.trigger('error'); 47662 } 47663 47664 _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping 47665 47666 _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_); 47667 47668 var master = _this8.parseMasterXml(); 47669 47670 var updatedMaster = updateMaster$1(_this8.master, master); 47671 47672 var currentSidxInfo = _this8.media().sidx; 47673 47674 if (updatedMaster) { 47675 if (currentSidxInfo) { 47676 var sidxKey = generateSidxKey(currentSidxInfo); // the sidx was updated, so the previous mapping was removed 47677 47678 if (!_this8.sidxMapping_[sidxKey]) { 47679 var playlist = _this8.media(); 47680 47681 _this8.request = requestSidx_(playlist.sidx, playlist, _this8.hls_.xhr, { 47682 handleManifestRedirects: _this8.handleManifestRedirects 47683 }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) { 47684 if (!newMaster || !sidx) { 47685 throw new Error('failed to request sidx on minimumUpdatePeriod'); 47686 } // update loader's sidxMapping with parsed sidx box 47687 47688 47689 _this8.sidxMapping_[sidxKey].sidx = sidx; 47690 _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () { 47691 _this8.trigger('minimumUpdatePeriod'); 47692 }, _this8.master.minimumUpdatePeriod); // TODO: do we need to reload the current playlist? 47693 47694 _this8.refreshMedia_(_this8.media().uri); 47695 47696 return; 47697 })); 47698 } 47699 } else { 47700 _this8.master = updatedMaster; 47701 } 47702 } 47703 47704 _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () { 47705 _this8.trigger('minimumUpdatePeriod'); 47706 }, _this8.master.minimumUpdatePeriod); 47707 }); 47708 } 47709 /** 47710 * Refreshes the media playlist by re-parsing the master xml and updating playlist 47711 * references. If this is an alternate loader, the updated parsed manifest is retrieved 47712 * from the master loader. 47713 */ 47714 47715 }, { 47716 key: 'refreshMedia_', 47717 value: function refreshMedia_(mediaUri) { 47718 var _this9 = this; 47719 47720 if (!mediaUri) { 47721 throw new Error('refreshMedia_ must take a media uri'); 47722 } 47723 47724 var oldMaster = void 0; 47725 var newMaster = void 0; 47726 47727 if (this.masterPlaylistLoader_) { 47728 oldMaster = this.masterPlaylistLoader_.master; 47729 newMaster = this.masterPlaylistLoader_.parseMasterXml(); 47730 } else { 47731 oldMaster = this.master; 47732 newMaster = this.parseMasterXml(); 47733 } 47734 47735 var updatedMaster = updateMaster$1(oldMaster, newMaster); 47736 47737 if (updatedMaster) { 47738 if (this.masterPlaylistLoader_) { 47739 this.masterPlaylistLoader_.master = updatedMaster; 47740 } else { 47741 this.master = updatedMaster; 47742 } 47743 47744 this.media_ = updatedMaster.playlists[mediaUri]; 47745 } else { 47746 this.media_ = newMaster.playlists[mediaUri]; 47747 this.trigger('playlistunchanged'); 47748 } 47749 47750 if (!this.media().endList) { 47751 this.mediaUpdateTimeout = window$1.setTimeout(function () { 47752 _this9.trigger('mediaupdatetimeout'); 47753 }, refreshDelay(this.media(), !!updatedMaster)); 47754 } 47755 47756 this.trigger('loadedplaylist'); 47757 } 47758 }]); 47759 return DashPlaylistLoader; 47760 }(EventTarget$1$1); 47761 47762 var logger = function logger(source) { 47763 if (videojs$1.log.debug) { 47764 return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >'); 47765 } 47766 47767 return function () {}; 47768 }; 47769 47770 function noop$1() {} 47771 /** 47772 * @file source-updater.js 47773 */ 47774 47775 /** 47776 * A queue of callbacks to be serialized and applied when a 47777 * MediaSource and its associated SourceBuffers are not in the 47778 * updating state. It is used by the segment loader to update the 47779 * underlying SourceBuffers when new data is loaded, for instance. 47780 * 47781 * @class SourceUpdater 47782 * @param {MediaSource} mediaSource the MediaSource to create the 47783 * SourceBuffer from 47784 * @param {String} mimeType the desired MIME type of the underlying 47785 * SourceBuffer 47786 * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is 47787 * added to the media source 47788 */ 47789 47790 47791 var SourceUpdater = function () { 47792 function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) { 47793 classCallCheck$1(this, SourceUpdater); 47794 this.callbacks_ = []; 47795 this.pendingCallback_ = null; 47796 this.timestampOffset_ = 0; 47797 this.mediaSource = mediaSource; 47798 this.processedAppend_ = false; 47799 this.type_ = type; 47800 this.mimeType_ = mimeType; 47801 this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']'); 47802 47803 if (mediaSource.readyState === 'closed') { 47804 mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter)); 47805 } else { 47806 this.createSourceBuffer_(mimeType, sourceBufferEmitter); 47807 } 47808 } 47809 47810 createClass$1(SourceUpdater, [{ 47811 key: 'createSourceBuffer_', 47812 value: function createSourceBuffer_(mimeType, sourceBufferEmitter) { 47813 var _this = this; 47814 47815 this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType); 47816 this.logger_('created SourceBuffer'); 47817 47818 if (sourceBufferEmitter) { 47819 sourceBufferEmitter.trigger('sourcebufferadded'); 47820 47821 if (this.mediaSource.sourceBuffers.length < 2) { 47822 // There's another source buffer we must wait for before we can start updating 47823 // our own (or else we can get into a bad state, i.e., appending video/audio data 47824 // before the other video/audio source buffer is available and leading to a video 47825 // or audio only buffer). 47826 sourceBufferEmitter.on('sourcebufferadded', function () { 47827 _this.start_(); 47828 }); 47829 return; 47830 } 47831 } 47832 47833 this.start_(); 47834 } 47835 }, { 47836 key: 'start_', 47837 value: function start_() { 47838 var _this2 = this; 47839 47840 this.started_ = true; // run completion handlers and process callbacks as updateend 47841 // events fire 47842 47843 this.onUpdateendCallback_ = function () { 47844 var pendingCallback = _this2.pendingCallback_; 47845 _this2.pendingCallback_ = null; 47846 _this2.sourceBuffer_.removing = false; 47847 47848 _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']'); 47849 47850 if (pendingCallback) { 47851 pendingCallback(); 47852 } 47853 47854 _this2.runCallback_(); 47855 }; 47856 47857 this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_); 47858 this.runCallback_(); 47859 } 47860 /** 47861 * Aborts the current segment and resets the segment parser. 47862 * 47863 * @param {Function} done function to call when done 47864 * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void 47865 */ 47866 47867 }, { 47868 key: 'abort', 47869 value: function abort(done) { 47870 var _this3 = this; 47871 47872 if (this.processedAppend_) { 47873 this.queueCallback_(function () { 47874 _this3.sourceBuffer_.abort(); 47875 }, done); 47876 } 47877 } 47878 /** 47879 * Queue an update to append an ArrayBuffer. 47880 * 47881 * @param {ArrayBuffer} bytes 47882 * @param {Function} done the function to call when done 47883 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 47884 */ 47885 47886 }, { 47887 key: 'appendBuffer', 47888 value: function appendBuffer(config, done) { 47889 var _this4 = this; 47890 47891 this.processedAppend_ = true; 47892 this.queueCallback_(function () { 47893 if (config.videoSegmentTimingInfoCallback) { 47894 _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); 47895 } 47896 47897 _this4.sourceBuffer_.appendBuffer(config.bytes); 47898 }, function () { 47899 if (config.videoSegmentTimingInfoCallback) { 47900 _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); 47901 } 47902 47903 done(); 47904 }); 47905 } 47906 /** 47907 * Indicates what TimeRanges are buffered in the managed SourceBuffer. 47908 * 47909 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered 47910 */ 47911 47912 }, { 47913 key: 'buffered', 47914 value: function buffered() { 47915 if (!this.sourceBuffer_) { 47916 return videojs$1.createTimeRanges(); 47917 } 47918 47919 return this.sourceBuffer_.buffered; 47920 } 47921 /** 47922 * Queue an update to remove a time range from the buffer. 47923 * 47924 * @param {Number} start where to start the removal 47925 * @param {Number} end where to end the removal 47926 * @param {Function} [done=noop] optional callback to be executed when the remove 47927 * operation is complete 47928 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 47929 */ 47930 47931 }, { 47932 key: 'remove', 47933 value: function remove(start, end) { 47934 var _this5 = this; 47935 47936 var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1; 47937 47938 if (this.processedAppend_) { 47939 this.queueCallback_(function () { 47940 _this5.logger_('remove [' + start + ' => ' + end + ']'); 47941 47942 _this5.sourceBuffer_.removing = true; 47943 47944 _this5.sourceBuffer_.remove(start, end); 47945 }, done); 47946 } 47947 } 47948 /** 47949 * Whether the underlying sourceBuffer is updating or not 47950 * 47951 * @return {Boolean} the updating status of the SourceBuffer 47952 */ 47953 47954 }, { 47955 key: 'updating', 47956 value: function updating() { 47957 // we are updating if the sourcebuffer is updating or 47958 return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop 47959 !!this.pendingCallback_ && this.pendingCallback_ !== noop$1; 47960 } 47961 /** 47962 * Set/get the timestampoffset on the SourceBuffer 47963 * 47964 * @return {Number} the timestamp offset 47965 */ 47966 47967 }, { 47968 key: 'timestampOffset', 47969 value: function timestampOffset(offset) { 47970 var _this6 = this; 47971 47972 if (typeof offset !== 'undefined') { 47973 this.queueCallback_(function () { 47974 _this6.sourceBuffer_.timestampOffset = offset; 47975 47976 _this6.runCallback_(); 47977 }); 47978 this.timestampOffset_ = offset; 47979 } 47980 47981 return this.timestampOffset_; 47982 } 47983 /** 47984 * Queue a callback to run 47985 */ 47986 47987 }, { 47988 key: 'queueCallback_', 47989 value: function queueCallback_(callback, done) { 47990 this.callbacks_.push([callback.bind(this), done]); 47991 this.runCallback_(); 47992 } 47993 /** 47994 * Run a queued callback 47995 */ 47996 47997 }, { 47998 key: 'runCallback_', 47999 value: function runCallback_() { 48000 var callbacks = void 0; 48001 48002 if (!this.updating() && this.callbacks_.length && this.started_) { 48003 callbacks = this.callbacks_.shift(); 48004 this.pendingCallback_ = callbacks[1]; 48005 callbacks[0](); 48006 } 48007 } 48008 /** 48009 * dispose of the source updater and the underlying sourceBuffer 48010 */ 48011 48012 }, { 48013 key: 'dispose', 48014 value: function dispose() { 48015 var _this7 = this; 48016 48017 var disposeFn = function disposeFn() { 48018 if (_this7.sourceBuffer_ && _this7.mediaSource.readyState === 'open') { 48019 _this7.sourceBuffer_.abort(); 48020 } 48021 48022 _this7.sourceBuffer_.removeEventListener('updateend', disposeFn); 48023 }; 48024 48025 this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_); 48026 48027 if (this.sourceBuffer_.removing) { 48028 this.sourceBuffer_.addEventListener('updateend', disposeFn); 48029 } else { 48030 disposeFn(); 48031 } 48032 } 48033 }]); 48034 return SourceUpdater; 48035 }(); 48036 48037 var Config = { 48038 GOAL_BUFFER_LENGTH: 30, 48039 MAX_GOAL_BUFFER_LENGTH: 60, 48040 GOAL_BUFFER_LENGTH_RATE: 1, 48041 // 0.5 MB/s 48042 INITIAL_BANDWIDTH: 4194304, 48043 // A fudge factor to apply to advertised playlist bitrates to account for 48044 // temporary flucations in client bandwidth 48045 BANDWIDTH_VARIANCE: 1.2, 48046 // How much of the buffer must be filled before we consider upswitching 48047 BUFFER_LOW_WATER_LINE: 0, 48048 MAX_BUFFER_LOW_WATER_LINE: 30, 48049 BUFFER_LOW_WATER_LINE_RATE: 1 48050 }; 48051 var REQUEST_ERRORS = { 48052 FAILURE: 2, 48053 TIMEOUT: -101, 48054 ABORTED: -102 48055 }; 48056 /** 48057 * Abort all requests 48058 * 48059 * @param {Object} activeXhrs - an object that tracks all XHR requests 48060 */ 48061 48062 var abortAll = function abortAll(activeXhrs) { 48063 activeXhrs.forEach(function (xhr) { 48064 xhr.abort(); 48065 }); 48066 }; 48067 /** 48068 * Gather important bandwidth stats once a request has completed 48069 * 48070 * @param {Object} request - the XHR request from which to gather stats 48071 */ 48072 48073 48074 var getRequestStats = function getRequestStats(request) { 48075 return { 48076 bandwidth: request.bandwidth, 48077 bytesReceived: request.bytesReceived || 0, 48078 roundTripTime: request.roundTripTime || 0 48079 }; 48080 }; 48081 /** 48082 * If possible gather bandwidth stats as a request is in 48083 * progress 48084 * 48085 * @param {Event} progressEvent - an event object from an XHR's progress event 48086 */ 48087 48088 48089 var getProgressStats = function getProgressStats(progressEvent) { 48090 var request = progressEvent.target; 48091 var roundTripTime = Date.now() - request.requestTime; 48092 var stats = { 48093 bandwidth: Infinity, 48094 bytesReceived: 0, 48095 roundTripTime: roundTripTime || 0 48096 }; 48097 stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok 48098 // because we should only use bandwidth stats on progress to determine when 48099 // abort a request early due to insufficient bandwidth 48100 48101 stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000); 48102 return stats; 48103 }; 48104 /** 48105 * Handle all error conditions in one place and return an object 48106 * with all the information 48107 * 48108 * @param {Error|null} error - if non-null signals an error occured with the XHR 48109 * @param {Object} request - the XHR request that possibly generated the error 48110 */ 48111 48112 48113 var handleErrors = function handleErrors(error, request) { 48114 if (request.timedout) { 48115 return { 48116 status: request.status, 48117 message: 'HLS request timed-out at URL: ' + request.uri, 48118 code: REQUEST_ERRORS.TIMEOUT, 48119 xhr: request 48120 }; 48121 } 48122 48123 if (request.aborted) { 48124 return { 48125 status: request.status, 48126 message: 'HLS request aborted at URL: ' + request.uri, 48127 code: REQUEST_ERRORS.ABORTED, 48128 xhr: request 48129 }; 48130 } 48131 48132 if (error) { 48133 return { 48134 status: request.status, 48135 message: 'HLS request errored at URL: ' + request.uri, 48136 code: REQUEST_ERRORS.FAILURE, 48137 xhr: request 48138 }; 48139 } 48140 48141 return null; 48142 }; 48143 /** 48144 * Handle responses for key data and convert the key data to the correct format 48145 * for the decryption step later 48146 * 48147 * @param {Object} segment - a simplified copy of the segmentInfo object 48148 * from SegmentLoader 48149 * @param {Function} finishProcessingFn - a callback to execute to continue processing 48150 * this request 48151 */ 48152 48153 48154 var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) { 48155 return function (error, request) { 48156 var response = request.response; 48157 var errorObj = handleErrors(error, request); 48158 48159 if (errorObj) { 48160 return finishProcessingFn(errorObj, segment); 48161 } 48162 48163 if (response.byteLength !== 16) { 48164 return finishProcessingFn({ 48165 status: request.status, 48166 message: 'Invalid HLS key at URL: ' + request.uri, 48167 code: REQUEST_ERRORS.FAILURE, 48168 xhr: request 48169 }, segment); 48170 } 48171 48172 var view = new DataView(response); 48173 segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 48174 return finishProcessingFn(null, segment); 48175 }; 48176 }; 48177 /** 48178 * Handle init-segment responses 48179 * 48180 * @param {Object} segment - a simplified copy of the segmentInfo object 48181 * from SegmentLoader 48182 * @param {Function} finishProcessingFn - a callback to execute to continue processing 48183 * this request 48184 */ 48185 48186 48187 var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) { 48188 return function (error, request) { 48189 var response = request.response; 48190 var errorObj = handleErrors(error, request); 48191 48192 if (errorObj) { 48193 return finishProcessingFn(errorObj, segment); 48194 } // stop processing if received empty content 48195 48196 48197 if (response.byteLength === 0) { 48198 return finishProcessingFn({ 48199 status: request.status, 48200 message: 'Empty HLS segment content at URL: ' + request.uri, 48201 code: REQUEST_ERRORS.FAILURE, 48202 xhr: request 48203 }, segment); 48204 } 48205 48206 segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet 48207 48208 if (captionParser && !captionParser.isInitialized()) { 48209 captionParser.init(); 48210 } 48211 48212 segment.map.timescales = probe.timescale(segment.map.bytes); 48213 segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes); 48214 return finishProcessingFn(null, segment); 48215 }; 48216 }; 48217 /** 48218 * Response handler for segment-requests being sure to set the correct
48219 * property depending on whether the segment is encryped or not 48220 * Also records and keeps track of stats that are used for ABR purposes 48221 * 48222 * @param {Object} segment - a simplified copy of the segmentInfo object 48223 * from SegmentLoader 48224 * @param {Function} finishProcessingFn - a callback to execute to continue processing 48225 * this request 48226 */ 48227 48228 48229 var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) { 48230 return function (error, request) { 48231 var response = request.response; 48232 var errorObj = handleErrors(error, request); 48233 var parsed = void 0; 48234 48235 if (errorObj) { 48236 return finishProcessingFn(errorObj, segment); 48237 } // stop processing if received empty content 48238 48239 48240 if (response.byteLength === 0) { 48241 return finishProcessingFn({ 48242 status: request.status, 48243 message: 'Empty HLS segment content at URL: ' + request.uri, 48244 code: REQUEST_ERRORS.FAILURE, 48245 xhr: request 48246 }, segment); 48247 } 48248 48249 segment.stats = getRequestStats(request); 48250 48251 if (segment.key) { 48252 segment.encryptedBytes = new Uint8Array(request.response); 48253 } else { 48254 segment.bytes = new Uint8Array(request.response); 48255 } // This is likely an FMP4 and has the init segment. 48256 // Run through the CaptionParser in case there are captions. 48257 48258 48259 if (captionParser && segment.map && segment.map.bytes) { 48260 // Initialize CaptionParser if it hasn't been yet 48261 if (!captionParser.isInitialized()) { 48262 captionParser.init(); 48263 } 48264 48265 parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales); 48266 48267 if (parsed && parsed.captions) { 48268 segment.captionStreams = parsed.captionStreams; 48269 segment.fmp4Captions = parsed.captions; 48270 } 48271 } 48272 48273 return finishProcessingFn(null, segment); 48274 }; 48275 }; 48276 /** 48277 * Decrypt the segment via the decryption web worker 48278 * 48279 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 48280 * @param {Object} segment - a simplified copy of the segmentInfo object 48281 * from SegmentLoader 48282 * @param {Function} doneFn - a callback that is executed after decryption has completed 48283 */ 48284 48285 48286 var decryptSegment = function decryptSegment(decrypter, segment, doneFn) { 48287 var decryptionHandler = function decryptionHandler(event) { 48288 if (event.data.source === segment.requestId) { 48289 decrypter.removeEventListener('message', decryptionHandler); 48290 var decrypted = event.data.decrypted; 48291 segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength); 48292 return doneFn(null, segment); 48293 } 48294 }; 48295 48296 decrypter.addEventListener('message', decryptionHandler); 48297 var keyBytes = void 0; 48298 48299 if (segment.key.bytes.slice) { 48300 keyBytes = segment.key.bytes.slice(); 48301 } else { 48302 keyBytes = new Uint32Array(Array.prototype.slice.call(segment.key.bytes)); 48303 } // this is an encrypted segment 48304 // incrementally decrypt the segment 48305 48306 48307 decrypter.postMessage(createTransferableMessage({ 48308 source: segment.requestId, 48309 encrypted: segment.encryptedBytes, 48310 key: keyBytes, 48311 iv: segment.key.iv 48312 }), [segment.encryptedBytes.buffer, keyBytes.buffer]); 48313 }; 48314 /** 48315 * This function waits for all XHRs to finish (with either success or failure) 48316 * before continueing processing via it's callback. The function gathers errors 48317 * from each request into a single errors array so that the error status for 48318 * each request can be examined later. 48319 * 48320 * @param {Object} activeXhrs - an object that tracks all XHR requests 48321 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 48322 * @param {Function} doneFn - a callback that is executed after all resources have been 48323 * downloaded and any decryption completed 48324 */ 48325 48326 48327 var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) { 48328 var count = 0; 48329 var didError = false; 48330 return function (error, segment) { 48331 if (didError) { 48332 return; 48333 } 48334 48335 if (error) { 48336 didError = true; // If there are errors, we have to abort any outstanding requests 48337 48338 abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we 48339 // handle the aborted events from those requests, there are some cases where we may 48340 // never get an aborted event. For instance, if the network connection is lost and
vendor: 199,163 bytes, lines 48341-53660
48341 // there were two requests, the first may have triggered an error immediately, while 48342 // the second request remains unsent. In that case, the aborted algorithm will not 48343 // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method 48344 // 48345 // We also can't rely on the ready state of the XHR, since the request that 48346 // triggered the connection error may also show as a ready state of 0 (unsent). 48347 // Therefore, we have to finish this group of requests immediately after the first 48348 // seen error. 48349 48350 return doneFn(error, segment); 48351 } 48352 48353 count += 1; 48354 48355 if (count === activeXhrs.length) { 48356 // Keep track of when *all* of the requests have completed 48357 segment.endOfAllRequests = Date.now(); 48358 48359 if (segment.encryptedBytes) { 48360 return decryptSegment(decrypter, segment, doneFn); 48361 } // Otherwise, everything is ready just continue 48362 48363 48364 return doneFn(null, segment); 48365 } 48366 }; 48367 }; 48368 /** 48369 * Simple progress event callback handler that gathers some stats before 48370 * executing a provided callback with the `segment` object 48371 * 48372 * @param {Object} segment - a simplified copy of the segmentInfo object 48373 * from SegmentLoader 48374 * @param {Function} progressFn - a callback that is executed each time a progress event 48375 * is received 48376 * @param {Event} event - the progress event object from XMLHttpRequest 48377 */ 48378 48379 48380 var handleProgress = function handleProgress(segment, progressFn) { 48381 return function (event) { 48382 segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data 48383 48384 if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) { 48385 segment.stats.firstBytesReceivedAt = Date.now(); 48386 } 48387 48388 return progressFn(event, segment); 48389 }; 48390 }; 48391 /** 48392 * Load all resources and does any processing necessary for a media-segment 48393 * 48394 * Features: 48395 * decrypts the media-segment if it has a key uri and an iv 48396 * aborts *all* requests if *any* one request fails 48397 * 48398 * The segment object, at minimum, has the following format: 48399 * { 48400 * resolvedUri: String, 48401 * [byterange]: { 48402 * offset: Number, 48403 * length: Number 48404 * }, 48405 * [key]: { 48406 * resolvedUri: String 48407 * [byterange]: { 48408 * offset: Number, 48409 * length: Number 48410 * }, 48411 * iv: { 48412 * bytes: Uint32Array 48413 * } 48414 * }, 48415 * [map]: { 48416 * resolvedUri: String, 48417 * [byterange]: { 48418 * offset: Number, 48419 * length: Number 48420 * }, 48421 * [bytes]: Uint8Array 48422 * } 48423 * } 48424 * ...where [name] denotes optional properties 48425 * 48426 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js 48427 * @param {Object} xhrOptions - the base options to provide to all xhr requests 48428 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 48429 * decryption routines 48430 * @param {Object} segment - a simplified copy of the segmentInfo object 48431 * from SegmentLoader 48432 * @param {Function} progressFn - a callback that receives progress events from the main 48433 * segment's xhr request 48434 * @param {Function} doneFn - a callback that is executed only once all requests have 48435 * succeeded or failed 48436 * @returns {Function} a function that, when invoked, immediately aborts all 48437 * outstanding requests 48438 */ 48439 48440 48441 var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) { 48442 var activeXhrs = []; 48443 var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key 48444 48445 if (segment.key && !segment.key.bytes) { 48446 var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, { 48447 uri: segment.key.resolvedUri, 48448 responseType: 'arraybuffer' 48449 }); 48450 var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn); 48451 var keyXhr = xhr(keyRequestOptions, keyRequestCallback); 48452 activeXhrs.push(keyXhr); 48453 } // optionally, request the associated media init segment 48454 48455 48456 if (segment.map && !segment.map.bytes) { 48457 var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, { 48458 uri: segment.map.resolvedUri, 48459 responseType: 'arraybuffer', 48460 headers: segmentXhrHeaders(segment.map) 48461 }); 48462 var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn); 48463 var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); 48464 activeXhrs.push(initSegmentXhr); 48465 } 48466 48467 var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, { 48468 uri: segment.resolvedUri, 48469 responseType: 'arraybuffer', 48470 headers: segmentXhrHeaders(segment) 48471 }); 48472 var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn); 48473 var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); 48474 segmentXhr.addEventListener('progress', handleProgress(segment, progressFn)); 48475 activeXhrs.push(segmentXhr); 48476 return function () { 48477 return abortAll(activeXhrs); 48478 }; 48479 }; // Utilities 48480 48481 /** 48482 * Returns the CSS value for the specified property on an element 48483 * using `getComputedStyle`. Firefox has a long-standing issue where 48484 * getComputedStyle() may return null when running in an iframe with 48485 * `display: none`. 48486 * 48487 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 48488 * @param {HTMLElement} el the htmlelement to work on 48489 * @param {string} the proprety to get the style for 48490 */ 48491 48492 48493 var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 48494 var result = void 0; 48495 48496 if (!el) { 48497 return ''; 48498 } 48499 48500 result = window$1.getComputedStyle(el); 48501 48502 if (!result) { 48503 return ''; 48504 } 48505 48506 return result[property]; 48507 }; 48508 /** 48509 * Resuable stable sort function 48510 * 48511 * @param {Playlists} array 48512 * @param {Function} sortFn Different comparators 48513 * @function stableSort 48514 */ 48515 48516 48517 var stableSort = function stableSort(array, sortFn) { 48518 var newArray = array.slice(); 48519 array.sort(function (left, right) { 48520 var cmp = sortFn(left, right); 48521 48522 if (cmp === 0) { 48523 return newArray.indexOf(left) - newArray.indexOf(right); 48524 } 48525 48526 return cmp; 48527 }); 48528 }; 48529 /** 48530 * A comparator function to sort two playlist object by bandwidth. 48531 * 48532 * @param {Object} left a media playlist object 48533 * @param {Object} right a media playlist object 48534 * @return {Number} Greater than zero if the bandwidth attribute of 48535 * left is greater than the corresponding attribute of right. Less 48536 * than zero if the bandwidth of right is greater than left and 48537 * exactly zero if the two are equal. 48538 */ 48539 48540 48541 var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) { 48542 var leftBandwidth = void 0; 48543 var rightBandwidth = void 0; 48544 48545 if (left.attributes.BANDWIDTH) { 48546 leftBandwidth = left.attributes.BANDWIDTH; 48547 } 48548 48549 leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE; 48550 48551 if (right.attributes.BANDWIDTH) { 48552 rightBandwidth = right.attributes.BANDWIDTH; 48553 } 48554 48555 rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE; 48556 return leftBandwidth - rightBandwidth; 48557 }; 48558 /** 48559 * A comparator function to sort two playlist object by resolution (width). 48560 * @param {Object} left a media playlist object 48561 * @param {Object} right a media playlist object 48562 * @return {Number} Greater than zero if the resolution.width attribute of 48563 * left is greater than the corresponding attribute of right. Less 48564 * than zero if the resolution.width of right is greater than left and 48565 * exactly zero if the two are equal. 48566 */ 48567 48568 48569 var comparePlaylistResolution = function comparePlaylistResolution(left, right) { 48570 var leftWidth = void 0; 48571 var rightWidth = void 0; 48572 48573 if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 48574 leftWidth = left.attributes.RESOLUTION.width; 48575 } 48576 48577 leftWidth = leftWidth || window$1.Number.MAX_VALUE; 48578 48579 if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 48580 rightWidth = right.attributes.RESOLUTION.width; 48581 } 48582 48583 rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 48584 // have the same media dimensions/ resolution 48585 48586 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 48587 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 48588 } 48589 48590 return leftWidth - rightWidth; 48591 }; 48592 /** 48593 * Chooses the appropriate media playlist based on bandwidth and player size 48594 * 48595 * @param {Object} master 48596 * Object representation of the master manifest 48597 * @param {Number} playerBandwidth 48598 * Current calculated bandwidth of the player 48599 * @param {Number} playerWidth 48600 * Current width of the player element 48601 * @param {Number} playerHeight 48602 * Current height of the player element 48603 * @param {Boolean} limitRenditionByPlayerDimensions 48604 * True if the player width and height should be used during the selection, false otherwise 48605 * @return {Playlist} the highest bitrate playlist less than the 48606 * currently detected bandwidth, accounting for some amount of 48607 * bandwidth variance 48608 */ 48609 48610 48611 var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) { 48612 // convert the playlists to an intermediary representation to make comparisons easier 48613 var sortedPlaylistReps = master.playlists.map(function (playlist) { 48614 var width = void 0; 48615 var height = void 0; 48616 var bandwidth = void 0; 48617 width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width; 48618 height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height; 48619 bandwidth = playlist.attributes.BANDWIDTH; 48620 bandwidth = bandwidth || window$1.Number.MAX_VALUE; 48621 return { 48622 bandwidth: bandwidth, 48623 width: width, 48624 height: height, 48625 playlist: playlist 48626 }; 48627 }); 48628 stableSort(sortedPlaylistReps, function (left, right) { 48629 return left.bandwidth - right.bandwidth; 48630 }); // filter out any playlists that have been excluded due to 48631 // incompatible configurations 48632 48633 sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) { 48634 return !Playlist.isIncompatible(rep.playlist); 48635 }); // filter out any playlists that have been disabled manually through the representations 48636 // api or blacklisted temporarily due to playback errors. 48637 48638 var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 48639 return Playlist.isEnabled(rep.playlist); 48640 }); 48641 48642 if (!enabledPlaylistReps.length) { 48643 // if there are no enabled playlists, then they have all been blacklisted or disabled 48644 // by the user through the representations api. In this case, ignore blacklisting and 48645 // fallback to what the user wants by using playlists the user has not disabled. 48646 enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 48647 return !Playlist.isDisabled(rep.playlist); 48648 }); 48649 } // filter out any variant that has greater effective bitrate 48650 // than the current estimated bandwidth 48651 48652 48653 var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) { 48654 return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth; 48655 }); 48656 var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth 48657 // and then taking the very first element 48658 48659 var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) { 48660 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 48661 })[0]; // if we're not going to limit renditions by player size, make an early decision. 48662 48663 if (limitRenditionByPlayerDimensions === false) { 48664 var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 48665 48666 return _chosenRep ? _chosenRep.playlist : null; 48667 } // filter out playlists without resolution information 48668 48669 48670 var haveResolution = bandwidthPlaylistReps.filter(function (rep) { 48671 return rep.width && rep.height; 48672 }); // sort variants by resolution 48673 48674 stableSort(haveResolution, function (left, right) { 48675 return left.width - right.width; 48676 }); // if we have the exact resolution as the player use it 48677 48678 var resolutionBestRepList = haveResolution.filter(function (rep) { 48679 return rep.width === playerWidth && rep.height === playerHeight; 48680 }); 48681 highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution 48682 48683 var resolutionBestRep = resolutionBestRepList.filter(function (rep) { 48684 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 48685 })[0]; 48686 var resolutionPlusOneList = void 0; 48687 var resolutionPlusOneSmallest = void 0; 48688 var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player 48689 // if there is no match of exact resolution 48690 48691 if (!resolutionBestRep) { 48692 resolutionPlusOneList = haveResolution.filter(function (rep) { 48693 return rep.width > playerWidth || rep.height > playerHeight; 48694 }); // find all the variants have the same smallest resolution 48695 48696 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) { 48697 return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height; 48698 }); // ensure that we also pick the highest bandwidth variant that 48699 // is just-larger-than the video player 48700 48701 highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1]; 48702 resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) { 48703 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 48704 })[0]; 48705 } // fallback chain of variants 48706 48707 48708 var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 48709 return chosenRep ? chosenRep.playlist : null; 48710 }; // Playlist Selectors 48711 48712 /** 48713 * Chooses the appropriate media playlist based on the most recent 48714 * bandwidth estimate and the player size. 48715 * 48716 * Expects to be called within the context of an instance of HlsHandler 48717 * 48718 * @return {Playlist} the highest bitrate playlist less than the 48719 * currently detected bandwidth, accounting for some amount of 48720 * bandwidth variance 48721 */ 48722 48723 48724 var lastBandwidthSelector = function lastBandwidthSelector() { 48725 return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions); 48726 }; 48727 /** 48728 * Chooses the appropriate media playlist based on the potential to rebuffer 48729 * 48730 * @param {Object} settings 48731 * Object of information required to use this selector 48732 * @param {Object} settings.master 48733 * Object representation of the master manifest 48734 * @param {Number} settings.currentTime 48735 * The current time of the player 48736 * @param {Number} settings.bandwidth 48737 * Current measured bandwidth 48738 * @param {Number} settings.duration 48739 * Duration of the media 48740 * @param {Number} settings.segmentDuration 48741 * Segment duration to be used in round trip time calculations 48742 * @param {Number} settings.timeUntilRebuffer 48743 * Time left in seconds until the player has to rebuffer 48744 * @param {Number} settings.currentTimeline 48745 * The current timeline segments are being loaded from 48746 * @param {SyncController} settings.syncController 48747 * SyncController for determining if we have a sync point for a given playlist 48748 * @return {Object|null} 48749 * {Object} return.playlist 48750 * The highest bandwidth playlist with the least amount of rebuffering 48751 * {Number} return.rebufferingImpact 48752 * The amount of time in seconds switching to this playlist will rebuffer. A 48753 * negative value means that switching will cause zero rebuffering. 48754 */ 48755 48756 48757 var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) { 48758 var master = settings.master, 48759 currentTime = settings.currentTime, 48760 bandwidth = settings.bandwidth, 48761 duration$$1 = settings.duration, 48762 segmentDuration = settings.segmentDuration, 48763 timeUntilRebuffer = settings.timeUntilRebuffer, 48764 currentTimeline = settings.currentTimeline, 48765 syncController = settings.syncController; // filter out any playlists that have been excluded due to 48766 // incompatible configurations 48767 48768 var compatiblePlaylists = master.playlists.filter(function (playlist) { 48769 return !Playlist.isIncompatible(playlist); 48770 }); // filter out any playlists that have been disabled manually through the representations 48771 // api or blacklisted temporarily due to playback errors. 48772 48773 var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled); 48774 48775 if (!enabledPlaylists.length) { 48776 // if there are no enabled playlists, then they have all been blacklisted or disabled 48777 // by the user through the representations api. In this case, ignore blacklisting and 48778 // fallback to what the user wants by using playlists the user has not disabled. 48779 enabledPlaylists = compatiblePlaylists.filter(function (playlist) { 48780 return !Playlist.isDisabled(playlist); 48781 }); 48782 } 48783 48784 var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH')); 48785 var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) { 48786 var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a 48787 // sync request first. This will double the request time 48788 48789 var numRequests = syncPoint ? 1 : 2; 48790 var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist); 48791 var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer; 48792 return { 48793 playlist: playlist, 48794 rebufferingImpact: rebufferingImpact 48795 }; 48796 }); 48797 var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) { 48798 return estimate.rebufferingImpact <= 0; 48799 }); // Sort by bandwidth DESC 48800 48801 stableSort(noRebufferingPlaylists, function (a, b) { 48802 return comparePlaylistBandwidth(b.playlist, a.playlist); 48803 }); 48804 48805 if (noRebufferingPlaylists.length) { 48806 return noRebufferingPlaylists[0]; 48807 } 48808 48809 stableSort(rebufferingEstimates, function (a, b) { 48810 return a.rebufferingImpact - b.rebufferingImpact; 48811 }); 48812 return rebufferingEstimates[0] || null; 48813 }; 48814 /** 48815 * Chooses the appropriate media playlist, which in this case is the lowest bitrate 48816 * one with video. If no renditions with video exist, return the lowest audio rendition. 48817 * 48818 * Expects to be called within the context of an instance of HlsHandler 48819 * 48820 * @return {Object|null} 48821 * {Object} return.playlist 48822 * The lowest bitrate playlist that contains a video codec. If no such rendition 48823 * exists pick the lowest audio rendition. 48824 */ 48825 48826 48827 var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() { 48828 // filter out any playlists that have been excluded due to 48829 // incompatible configurations or playback errors 48830 var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate 48831 48832 stableSort(playlists, function (a, b) { 48833 return comparePlaylistBandwidth(a, b); 48834 }); // Parse and assume that playlists with no video codec have no video 48835 // (this is not necessarily true, although it is generally true). 48836 // 48837 // If an entire manifest has no valid videos everything will get filtered 48838 // out. 48839 48840 var playlistsWithVideo = playlists.filter(function (playlist) { 48841 return parseCodecs(playlist.attributes.CODECS).videoCodec; 48842 }); 48843 return playlistsWithVideo[0] || null; 48844 }; 48845 /** 48846 * Create captions text tracks on video.js if they do not exist 48847 * 48848 * @param {Object} inbandTextTracks a reference to current inbandTextTracks 48849 * @param {Object} tech the video.js tech 48850 * @param {Object} captionStreams the caption streams to create 48851 * @private 48852 */ 48853 48854 48855 var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) { 48856 for (var trackId in captionStreams) { 48857 if (!inbandTextTracks[trackId]) { 48858 tech.trigger({ 48859 type: 'usage', 48860 name: 'hls-608' 48861 }); 48862 var track = tech.textTracks().getTrackById(trackId); 48863 48864 if (track) { 48865 // Resuse an existing track with a CC# id because this was 48866 // very likely created by videojs-contrib-hls from information 48867 // in the m3u8 for us to use 48868 inbandTextTracks[trackId] = track; 48869 } else { 48870 // Otherwise, create a track with the default `CC#` label and 48871 // without a language 48872 inbandTextTracks[trackId] = tech.addRemoteTextTrack({ 48873 kind: 'captions', 48874 id: trackId, 48875 label: trackId 48876 }, false).track; 48877 } 48878 } 48879 } 48880 }; 48881 48882 var addCaptionData = function addCaptionData(_ref) { 48883 var inbandTextTracks = _ref.inbandTextTracks, 48884 captionArray = _ref.captionArray, 48885 timestampOffset = _ref.timestampOffset; 48886 48887 if (!captionArray) { 48888 return; 48889 } 48890 48891 var Cue = window.WebKitDataCue || window.VTTCue; 48892 captionArray.forEach(function (caption) { 48893 var track = caption.stream; 48894 var startTime = caption.startTime; 48895 var endTime = caption.endTime; 48896 48897 if (!inbandTextTracks[track]) { 48898 return; 48899 } 48900 48901 startTime += timestampOffset; 48902 endTime += timestampOffset; 48903 inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text)); 48904 }); 48905 }; 48906 /** 48907 * @file segment-loader.js 48908 */ 48909 // in ms 48910 48911 48912 var CHECK_BUFFER_DELAY = 500; 48913 /** 48914 * Determines if we should call endOfStream on the media source based 48915 * on the state of the buffer or if appened segment was the final 48916 * segment in the playlist. 48917 * 48918 * @param {Object} playlist a media playlist object 48919 * @param {Object} mediaSource the MediaSource object 48920 * @param {Number} segmentIndex the index of segment we last appended 48921 * @returns {Boolean} do we need to call endOfStream on the MediaSource 48922 */ 48923 48924 var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) { 48925 if (!playlist || !mediaSource) { 48926 return false; 48927 } 48928 48929 var segments = playlist.segments; // determine a few boolean values to help make the branch below easier 48930 // to read 48931 48932 var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream 48933 // so that MediaSources can trigger the `ended` event when it runs out of 48934 // buffered data instead of waiting for me 48935 48936 return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment; 48937 }; 48938 48939 var finite = function finite(num) { 48940 return typeof num === 'number' && isFinite(num); 48941 }; 48942 48943 var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) { 48944 // Although these checks should most likely cover non 'main' types, for now it narrows 48945 // the scope of our checks. 48946 if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) { 48947 return null; 48948 } 48949 48950 if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) { 48951 return 'Neither audio nor video found in segment.'; 48952 } 48953 48954 if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) { 48955 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.'; 48956 } 48957 48958 if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) { 48959 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.'; 48960 } 48961 48962 return null; 48963 }; 48964 /** 48965 * Calculates a time value that is safe to remove from the back buffer without interupting 48966 * playback. 48967 * 48968 * @param {TimeRange} seekable 48969 * The current seekable range 48970 * @param {Number} currentTime 48971 * The current time of the player 48972 * @param {Number} targetDuration 48973 * The target duration of the current playlist 48974 * @return {Number} 48975 * Time that is safe to remove from the back buffer without interupting playback 48976 */ 48977 48978 48979 var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) { 48980 var removeToTime = void 0; 48981 48982 if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) { 48983 // If we have a seekable range use that as the limit for what can be removed safely 48984 removeToTime = seekable$$1.start(0); 48985 } else { 48986 // otherwise remove anything older than 30 seconds before the current play head 48987 removeToTime = currentTime - 30; 48988 } // Don't allow removing from the buffer within target duration of current time 48989 // to avoid the possibility of removing the GOP currently being played which could 48990 // cause playback stalls. 48991 48992 48993 return Math.min(removeToTime, currentTime - targetDuration); 48994 }; 48995 48996 var segmentInfoString = function segmentInfoString(segmentInfo) { 48997 var _segmentInfo$segment = segmentInfo.segment, 48998 start = _segmentInfo$segment.start, 48999 end = _segmentInfo$segment.end, 49000 _segmentInfo$playlist = segmentInfo.playlist, 49001 seq = _segmentInfo$playlist.mediaSequence, 49002 id = _segmentInfo$playlist.id, 49003 _segmentInfo$playlist2 = _segmentInfo$playlist.segments, 49004 segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2, 49005 index = segmentInfo.mediaIndex, 49006 timeline = segmentInfo.timeline; 49007 return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' '); 49008 }; 49009 /** 49010 * An object that manages segment loading and appending. 49011 * 49012 * @class SegmentLoader 49013 * @param {Object} options required and optional options 49014 * @extends videojs.EventTarget 49015 */ 49016 49017 49018 var SegmentLoader = function (_videojs$EventTarget) { 49019 inherits$2(SegmentLoader, _videojs$EventTarget); 49020 49021 function SegmentLoader(settings) { 49022 classCallCheck$1(this, SegmentLoader); // check pre-conditions 49023 49024 var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this)); 49025 49026 if (!settings) { 49027 throw new TypeError('Initialization settings are required'); 49028 } 49029 49030 if (typeof settings.currentTime !== 'function') { 49031 throw new TypeError('No currentTime getter specified'); 49032 } 49033 49034 if (!settings.mediaSource) { 49035 throw new TypeError('No MediaSource specified'); 49036 } // public properties 49037 49038 49039 _this.bandwidth = settings.bandwidth; 49040 _this.throughput = { 49041 rate: 0, 49042 count: 0 49043 }; 49044 _this.roundTrip = NaN; 49045 49046 _this.resetStats_(); 49047 49048 _this.mediaIndex = null; // private settings 49049 49050 _this.hasPlayed_ = settings.hasPlayed; 49051 _this.currentTime_ = settings.currentTime; 49052 _this.seekable_ = settings.seekable; 49053 _this.seeking_ = settings.seeking; 49054 _this.duration_ = settings.duration; 49055 _this.mediaSource_ = settings.mediaSource; 49056 _this.hls_ = settings.hls; 49057 _this.loaderType_ = settings.loaderType; 49058 _this.startingMedia_ = void 0; 49059 _this.segmentMetadataTrack_ = settings.segmentMetadataTrack; 49060 _this.goalBufferLength_ = settings.goalBufferLength; 49061 _this.sourceType_ = settings.sourceType; 49062 _this.inbandTextTracks_ = settings.inbandTextTracks; 49063 _this.state_ = 'INIT'; // private instance variables 49064 49065 _this.checkBufferTimeout_ = null; 49066 _this.error_ = void 0; 49067 _this.currentTimeline_ = -1; 49068 _this.pendingSegment_ = null; 49069 _this.mimeType_ = null; 49070 _this.sourceUpdater_ = null; 49071 _this.xhrOptions_ = null; // Fragmented mp4 playback 49072 49073 _this.activeInitSegmentId_ = null; 49074 _this.initSegments_ = {}; // HLSe playback 49075 49076 _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys; 49077 _this.keyCache_ = {}; // Fmp4 CaptionParser 49078 49079 if (_this.loaderType_ === 'main') { 49080 _this.captionParser_ = new captionParser(); 49081 } else { 49082 _this.captionParser_ = null; 49083 } 49084 49085 _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings 49086 // between a time in the display time and a segment index within 49087 // a playlist 49088 49089 _this.syncController_ = settings.syncController; 49090 _this.syncPoint_ = { 49091 segmentIndex: 0, 49092 time: 0 49093 }; 49094 49095 _this.syncController_.on('syncinfoupdate', function () { 49096 return _this.trigger('syncinfoupdate'); 49097 }); 49098 49099 _this.mediaSource_.addEventListener('sourceopen', function () { 49100 return _this.ended_ = false; 49101 }); // ...for determining the fetch location 49102 49103 49104 _this.fetchAtBuffer_ = false; 49105 _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']'); 49106 Object.defineProperty(_this, 'state', { 49107 get: function get$$1() { 49108 return this.state_; 49109 }, 49110 set: function set$$1(newState) { 49111 if (newState !== this.state_) { 49112 this.logger_(this.state_ + ' -> ' + newState); 49113 this.state_ = newState; 49114 } 49115 } 49116 }); 49117 return _this; 49118 } 49119 /** 49120 * reset all of our media stats 49121 * 49122 * @private 49123 */ 49124 49125 49126 createClass$1(SegmentLoader, [{ 49127 key: 'resetStats_', 49128 value: function resetStats_() { 49129 this.mediaBytesTransferred = 0; 49130 this.mediaRequests = 0; 49131 this.mediaRequestsAborted = 0; 49132 this.mediaRequestsTimedout = 0; 49133 this.mediaRequestsErrored = 0; 49134 this.mediaTransferDuration = 0; 49135 this.mediaSecondsLoaded = 0; 49136 } 49137 /** 49138 * dispose of the SegmentLoader and reset to the default state 49139 */ 49140 49141 }, { 49142 key: 'dispose', 49143 value: function dispose() { 49144 this.state = 'DISPOSED'; 49145 this.pause(); 49146 this.abort_(); 49147 49148 if (this.sourceUpdater_) { 49149 this.sourceUpdater_.dispose(); 49150 } 49151 49152 this.resetStats_(); 49153 49154 if (this.captionParser_) { 49155 this.captionParser_.reset(); 49156 } 49157 } 49158 /** 49159 * abort anything that is currently doing on with the SegmentLoader 49160 * and reset to a default state 49161 */ 49162 49163 }, { 49164 key: 'abort', 49165 value: function abort() { 49166 if (this.state !== 'WAITING') { 49167 if (this.pendingSegment_) { 49168 this.pendingSegment_ = null; 49169 } 49170 49171 return; 49172 } 49173 49174 this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY 49175 // since we are no longer "waiting" on any requests. XHR callback is not always run 49176 // when the request is aborted. This will prevent the loader from being stuck in the 49177 // WAITING state indefinitely. 49178 49179 this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the 49180 // next segment 49181 49182 if (!this.paused()) { 49183 this.monitorBuffer_(); 49184 } 49185 } 49186 /** 49187 * abort all pending xhr requests and null any pending segements 49188 * 49189 * @private 49190 */ 49191 49192 }, { 49193 key: 'abort_', 49194 value: function abort_() { 49195 if (this.pendingSegment_) { 49196 this.pendingSegment_.abortRequests(); 49197 } // clear out the segment being processed 49198 49199 49200 this.pendingSegment_ = null; 49201 } 49202 /** 49203 * set an error on the segment loader and null out any pending segements 49204 * 49205 * @param {Error} error the error to set on the SegmentLoader 49206 * @return {Error} the error that was set or that is currently set 49207 */ 49208 49209 }, { 49210 key: 'error', 49211 value: function error(_error) { 49212 if (typeof _error !== 'undefined') { 49213 this.error_ = _error; 49214 } 49215 49216 this.pendingSegment_ = null; 49217 return this.error_; 49218 } 49219 }, { 49220 key: 'endOfStream', 49221 value: function endOfStream() { 49222 this.ended_ = true; 49223 this.pause(); 49224 this.trigger('ended'); 49225 } 49226 /** 49227 * Indicates which time ranges are buffered 49228 * 49229 * @return {TimeRange} 49230 * TimeRange object representing the current buffered ranges 49231 */ 49232 49233 }, { 49234 key: 'buffered_', 49235 value: function buffered_() { 49236 if (!this.sourceUpdater_) { 49237 return videojs$1.createTimeRanges(); 49238 } 49239 49240 return this.sourceUpdater_.buffered(); 49241 } 49242 /** 49243 * Gets and sets init segment for the provided map 49244 * 49245 * @param {Object} map 49246 * The map object representing the init segment to get or set 49247 * @param {Boolean=} set 49248 * If true, the init segment for the provided map should be saved 49249 * @return {Object} 49250 * map object for desired init segment 49251 */ 49252 49253 }, { 49254 key: 'initSegment', 49255 value: function initSegment(map) { 49256 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 49257 49258 if (!map) { 49259 return null; 49260 } 49261 49262 var id = initSegmentId(map); 49263 var storedMap = this.initSegments_[id]; 49264 49265 if (set$$1 && !storedMap && map.bytes) { 49266 this.initSegments_[id] = storedMap = { 49267 resolvedUri: map.resolvedUri, 49268 byterange: map.byterange, 49269 bytes: map.bytes, 49270 timescales: map.timescales, 49271 videoTrackIds: map.videoTrackIds 49272 }; 49273 } 49274 49275 return storedMap || map; 49276 } 49277 /** 49278 * Gets and sets key for the provided key 49279 * 49280 * @param {Object} key 49281 * The key object representing the key to get or set 49282 * @param {Boolean=} set 49283 * If true, the key for the provided key should be saved 49284 * @return {Object} 49285 * Key object for desired key 49286 */ 49287 49288 }, { 49289 key: 'segmentKey', 49290 value: function segmentKey(key) { 49291 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 49292 49293 if (!key) { 49294 return null; 49295 } 49296 49297 var id = segmentKeyId(key); 49298 var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per 49299 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3 49300 49301 if (this.cacheEncryptionKeys_ && set$$1 && !storedKey && key.bytes) { 49302 this.keyCache_[id] = storedKey = { 49303 resolvedUri: key.resolvedUri, 49304 bytes: key.bytes 49305 }; 49306 } 49307 49308 var result = { 49309 resolvedUri: (storedKey || key).resolvedUri 49310 }; 49311 49312 if (storedKey) { 49313 result.bytes = storedKey.bytes; 49314 } 49315 49316 return result; 49317 } 49318 /** 49319 * Returns true if all configuration required for loading is present, otherwise false. 49320 * 49321 * @return {Boolean} True if the all configuration is ready for loading 49322 * @private 49323 */ 49324 49325 }, { 49326 key: 'couldBeginLoading_', 49327 value: function couldBeginLoading_() { 49328 return this.playlist_ && ( // the source updater is created when init_ is called, so either having a 49329 // source updater or being in the INIT state with a mimeType is enough 49330 // to say we have all the needed configuration to start loading. 49331 this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused(); 49332 } 49333 /** 49334 * load a playlist and start to fill the buffer 49335 */ 49336 49337 }, { 49338 key: 'load', 49339 value: function load() { 49340 // un-pause 49341 this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be 49342 // specified 49343 49344 if (!this.playlist_) { 49345 return; 49346 } // not sure if this is the best place for this 49347 49348 49349 this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading 49350 49351 if (this.state === 'INIT' && this.couldBeginLoading_()) { 49352 return this.init_(); 49353 } // if we're in the middle of processing a segment already, don't 49354 // kick off an additional segment request 49355 49356 49357 if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') { 49358 return; 49359 } 49360 49361 this.state = 'READY'; 49362 } 49363 /** 49364 * Once all the starting parameters have been specified, begin 49365 * operation. This method should only be invoked from the INIT 49366 * state. 49367 * 49368 * @private 49369 */ 49370 49371 }, { 49372 key: 'init_', 49373 value: function init_() { 49374 this.state = 'READY'; 49375 this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_); 49376 this.resetEverything(); 49377 return this.monitorBuffer_(); 49378 } 49379 /** 49380 * set a playlist on the segment loader 49381 * 49382 * @param {PlaylistLoader} media the playlist to set on the segment loader 49383 */ 49384 49385 }, { 49386 key: 'playlist', 49387 value: function playlist(newPlaylist) { 49388 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 49389 49390 if (!newPlaylist) { 49391 return; 49392 } 49393 49394 var oldPlaylist = this.playlist_; 49395 var segmentInfo = this.pendingSegment_; 49396 this.playlist_ = newPlaylist; 49397 this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist 49398 // is always our zero-time so force a sync update each time the playlist 49399 // is refreshed from the server 49400 49401 if (!this.hasPlayed_()) { 49402 newPlaylist.syncInfo = { 49403 mediaSequence: newPlaylist.mediaSequence, 49404 time: 0 49405 }; 49406 } 49407 49408 var oldId = null; 49409 49410 if (oldPlaylist) { 49411 if (oldPlaylist.id) { 49412 oldId = oldPlaylist.id; 49413 } else if (oldPlaylist.uri) { 49414 oldId = oldPlaylist.uri; 49415 } 49416 } 49417 49418 this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above) 49419 // in LIVE, we always want to update with new playlists (including refreshes) 49420 49421 this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start 49422 // buffering now 49423 49424 if (this.state === 'INIT' && this.couldBeginLoading_()) { 49425 return this.init_(); 49426 } 49427 49428 if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) { 49429 if (this.mediaIndex !== null) { 49430 // we must "resync" the segment loader when we switch renditions and 49431 // the segment loader is already synced to the previous rendition 49432 this.resyncLoader(); 49433 } // the rest of this function depends on `oldPlaylist` being defined 49434 49435 49436 return; 49437 } // we reloaded the same playlist so we are in a live scenario 49438 // and we will likely need to adjust the mediaIndex 49439 49440 49441 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 49442 this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader 49443 // this is important because we can abort a request and this value must be 49444 // equal to the last appended mediaIndex 49445 49446 if (this.mediaIndex !== null) { 49447 this.mediaIndex -= mediaSequenceDiff; 49448 } // update the mediaIndex on the SegmentInfo object 49449 // this is important because we will update this.mediaIndex with this value 49450 // in `handleUpdateEnd_` after the segment has been successfully appended 49451 49452 49453 if (segmentInfo) { 49454 segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is 49455 // saved for the new playlist's segment, however, if the segment fell off the 49456 // playlist, we can leave the old reference and just lose the timing info 49457 49458 if (segmentInfo.mediaIndex >= 0) { 49459 segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex]; 49460 } 49461 } 49462 49463 this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist); 49464 } 49465 /** 49466 * Prevent the loader from fetching additional segments. If there 49467 * is a segment request outstanding, it will finish processing 49468 * before the loader halts. A segment loader can be unpaused by 49469 * calling load(). 49470 */ 49471 49472 }, { 49473 key: 'pause', 49474 value: function pause() { 49475 if (this.checkBufferTimeout_) { 49476 window$1.clearTimeout(this.checkBufferTimeout_); 49477 this.checkBufferTimeout_ = null; 49478 } 49479 } 49480 /** 49481 * Returns whether the segment loader is fetching additional 49482 * segments when given the opportunity. This property can be 49483 * modified through calls to pause() and load(). 49484 */ 49485 49486 }, { 49487 key: 'paused', 49488 value: function paused() { 49489 return this.checkBufferTimeout_ === null; 49490 } 49491 /** 49492 * create/set the following mimetype on the SourceBuffer through a 49493 * SourceUpdater 49494 * 49495 * @param {String} mimeType the mime type string to use 49496 * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer 49497 * is added to the media source 49498 */ 49499 49500 }, { 49501 key: 'mimeType', 49502 value: function mimeType(_mimeType, sourceBufferEmitter) { 49503 if (this.mimeType_) { 49504 return; 49505 } 49506 49507 this.mimeType_ = _mimeType; 49508 this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start 49509 // buffering now 49510 49511 if (this.state === 'INIT' && this.couldBeginLoading_()) { 49512 this.init_(); 49513 } 49514 } 49515 /** 49516 * Delete all the buffered data and reset the SegmentLoader 49517 * @param {Function} [done] an optional callback to be executed when the remove 49518 * operation is complete 49519 */ 49520 49521 }, { 49522 key: 'resetEverything', 49523 value: function resetEverything(done) { 49524 this.ended_ = false; 49525 this.resetLoader(); 49526 this.remove(0, this.duration_(), done); // clears fmp4 captions 49527 49528 if (this.captionParser_) { 49529 this.captionParser_.clearAllCaptions(); 49530 } 49531 49532 this.trigger('reseteverything'); 49533 } 49534 /** 49535 * Force the SegmentLoader to resync and start loading around the currentTime instead 49536 * of starting at the end of the buffer 49537 * 49538 * Useful for fast quality changes 49539 */ 49540 49541 }, { 49542 key: 'resetLoader', 49543 value: function resetLoader() { 49544 this.fetchAtBuffer_ = false; 49545 this.resyncLoader(); 49546 } 49547 /** 49548 * Force the SegmentLoader to restart synchronization and make a conservative guess 49549 * before returning to the simple walk-forward method 49550 */ 49551 49552 }, { 49553 key: 'resyncLoader', 49554 value: function resyncLoader() { 49555 this.mediaIndex = null; 49556 this.syncPoint_ = null; 49557 this.abort(); 49558 } 49559 /** 49560 * Remove any data in the source buffer between start and end times 49561 * @param {Number} start - the start time of the region to remove from the buffer 49562 * @param {Number} end - the end time of the region to remove from the buffer 49563 * @param {Function} [done] - an optional callback to be executed when the remove 49564 * operation is complete 49565 */ 49566 49567 }, { 49568 key: 'remove', 49569 value: function remove(start, end, done) { 49570 if (this.sourceUpdater_) { 49571 this.sourceUpdater_.remove(start, end, done); 49572 } 49573 49574 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 49575 49576 if (this.inbandTextTracks_) { 49577 for (var id in this.inbandTextTracks_) { 49578 removeCuesFromTrack(start, end, this.inbandTextTracks_[id]); 49579 } 49580 } 49581 } 49582 /** 49583 * (re-)schedule monitorBufferTick_ to run as soon as possible 49584 * 49585 * @private 49586 */ 49587 49588 }, { 49589 key: 'monitorBuffer_', 49590 value: function monitorBuffer_() { 49591 if (this.checkBufferTimeout_) { 49592 window$1.clearTimeout(this.checkBufferTimeout_); 49593 } 49594 49595 this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1); 49596 } 49597 /** 49598 * As long as the SegmentLoader is in the READY state, periodically 49599 * invoke fillBuffer_(). 49600 * 49601 * @private 49602 */ 49603 49604 }, { 49605 key: 'monitorBufferTick_', 49606 value: function monitorBufferTick_() { 49607 if (this.state === 'READY') { 49608 this.fillBuffer_(); 49609 } 49610 49611 if (this.checkBufferTimeout_) { 49612 window$1.clearTimeout(this.checkBufferTimeout_); 49613 } 49614 49615 this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY); 49616 } 49617 /** 49618 * fill the buffer with segements unless the sourceBuffers are 49619 * currently updating 49620 * 49621 * Note: this function should only ever be called by monitorBuffer_ 49622 * and never directly 49623 * 49624 * @private 49625 */ 49626 49627 }, { 49628 key: 'fillBuffer_', 49629 value: function fillBuffer_() { 49630 if (this.sourceUpdater_.updating()) { 49631 return; 49632 } 49633 49634 if (!this.syncPoint_) { 49635 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 49636 } // see if we need to begin loading immediately 49637 49638 49639 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 49640 49641 if (!segmentInfo) { 49642 return; 49643 } 49644 49645 if (this.isEndOfStream_(segmentInfo.mediaIndex)) { 49646 this.endOfStream(); 49647 return; 49648 } 49649 49650 if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { 49651 return; 49652 } // We will need to change timestampOffset of the sourceBuffer if: 49653 // - The segment.timeline !== this.currentTimeline 49654 // (we are crossing a discontinuity somehow) 49655 // - The "timestampOffset" for the start of this segment is less than 49656 // the currently set timestampOffset 49657 // Also, clear captions if we are crossing a discontinuity boundary 49658 // Previously, we changed the timestampOffset if the start of this segment 49659 // is less than the currently set timestampOffset but this isn't wanted 49660 // as it can produce bad behavior, especially around long running 49661 // live streams 49662 49663 49664 if (segmentInfo.timeline !== this.currentTimeline_) { 49665 this.syncController_.reset(); 49666 segmentInfo.timestampOffset = segmentInfo.startOfSegment; 49667 49668 if (this.captionParser_) { 49669 this.captionParser_.clearAllCaptions(); 49670 } 49671 } 49672 49673 this.loadSegment_(segmentInfo); 49674 } 49675 /** 49676 * Determines if this segment loader is at the end of it's stream. 49677 * 49678 * @param {Number} mediaIndex the index of segment we last appended 49679 * @param {Object} [playlist=this.playlist_] a media playlist object 49680 * @returns {Boolean} true if at end of stream, false otherwise. 49681 */ 49682 49683 }, { 49684 key: 'isEndOfStream_', 49685 value: function isEndOfStream_(mediaIndex) { 49686 var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_; 49687 return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating(); 49688 } 49689 /** 49690 * Determines what segment request should be made, given current playback 49691 * state. 49692 * 49693 * @param {TimeRanges} buffered - the state of the buffer 49694 * @param {Object} playlist - the playlist object to fetch segments from 49695 * @param {Number} mediaIndex - the previous mediaIndex fetched or null 49696 * @param {Boolean} hasPlayed - a flag indicating whether we have played or not 49697 * @param {Number} currentTime - the playback position in seconds 49698 * @param {Object} syncPoint - a segment info object that describes the 49699 * @returns {Object} a segment request object that describes the segment to load 49700 */ 49701 49702 }, { 49703 key: 'checkBuffer_', 49704 value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) { 49705 var lastBufferedEnd = 0; 49706 var startOfSegment = void 0; 49707 49708 if (buffered.length) { 49709 lastBufferedEnd = buffered.end(buffered.length - 1); 49710 } 49711 49712 var bufferedTime = Math.max(0, lastBufferedEnd - currentTime); 49713 49714 if (!playlist.segments.length) { 49715 return null; 49716 } // if there is plenty of content buffered, and the video has 49717 // been played before relax for awhile 49718 49719 49720 if (bufferedTime >= this.goalBufferLength_()) { 49721 return null; 49722 } // if the video has not yet played once, and we already have 49723 // one segment downloaded do nothing 49724 49725 49726 if (!hasPlayed && bufferedTime >= 1) { 49727 return null; 49728 } // When the syncPoint is null, there is no way of determining a good 49729 // conservative segment index to fetch from 49730 // The best thing to do here is to get the kind of sync-point data by 49731 // making a request 49732 49733 49734 if (syncPoint === null) { 49735 mediaIndex = this.getSyncSegmentCandidate_(playlist); 49736 return this.generateSegmentInfo_(playlist, mediaIndex, null, true); 49737 } // Under normal playback conditions fetching is a simple walk forward 49738 49739 49740 if (mediaIndex !== null) { 49741 var segment = playlist.segments[mediaIndex]; 49742 startOfSegment = lastBufferedEnd; 49743 return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false); 49744 } // There is a sync-point but the lack of a mediaIndex indicates that 49745 // we need to make a good conservative guess about which segment to 49746 // fetch 49747 49748 49749 if (this.fetchAtBuffer_) { 49750 // Find the segment containing the end of the buffer 49751 var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time); 49752 mediaIndex = mediaSourceInfo.mediaIndex; 49753 startOfSegment = mediaSourceInfo.startTime; 49754 } else { 49755 // Find the segment containing currentTime 49756 var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time); 49757 49758 mediaIndex = _mediaSourceInfo.mediaIndex; 49759 startOfSegment = _mediaSourceInfo.startTime; 49760 } 49761 49762 return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false); 49763 } 49764 /** 49765 * The segment loader has no recourse except to fetch a segment in the 49766 * current playlist and use the internal timestamps in that segment to 49767 * generate a syncPoint. This function returns a good candidate index 49768 * for that process. 49769 * 49770 * @param {Object} playlist - the playlist object to look for a 49771 * @returns {Number} An index of a segment from the playlist to load 49772 */ 49773 49774 }, { 49775 key: 'getSyncSegmentCandidate_', 49776 value: function getSyncSegmentCandidate_(playlist) { 49777 var _this2 = this; 49778 49779 if (this.currentTimeline_ === -1) { 49780 return 0; 49781 } 49782 49783 var segmentIndexArray = playlist.segments.map(function (s, i) { 49784 return { 49785 timeline: s.timeline, 49786 segmentIndex: i 49787 }; 49788 }).filter(function (s) { 49789 return s.timeline === _this2.currentTimeline_; 49790 }); 49791 49792 if (segmentIndexArray.length) { 49793 return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex; 49794 } 49795 49796 return Math.max(playlist.segments.length - 1, 0); 49797 } 49798 }, { 49799 key: 'generateSegmentInfo_', 49800 value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) { 49801 if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) { 49802 return null; 49803 } 49804 49805 var segment = playlist.segments[mediaIndex]; 49806 return { 49807 requestId: 'segment-loader-' + Math.random(), 49808 // resolve the segment URL relative to the playlist 49809 uri: segment.resolvedUri, 49810 // the segment's mediaIndex at the time it was requested 49811 mediaIndex: mediaIndex, 49812 // whether or not to update the SegmentLoader's state with this 49813 // segment's mediaIndex 49814 isSyncRequest: isSyncRequest, 49815 startOfSegment: startOfSegment, 49816 // the segment's playlist 49817 playlist: playlist, 49818 // unencrypted bytes of the segment 49819 bytes: null, 49820 // when a key is defined for this segment, the encrypted bytes 49821 encryptedBytes: null, 49822 // The target timestampOffset for this segment when we append it 49823 // to the source buffer 49824 timestampOffset: null, 49825 // The timeline that the segment is in 49826 timeline: segment.timeline, 49827 // The expected duration of the segment in seconds 49828 duration: segment.duration, 49829 // retain the segment in case the playlist updates while doing an async process 49830 segment: segment 49831 }; 49832 } 49833 /** 49834 * Determines if the network has enough bandwidth to complete the current segment 49835 * request in a timely manner. If not, the request will be aborted early and bandwidth 49836 * updated to trigger a playlist switch. 49837 * 49838 * @param {Object} stats 49839 * Object containing stats about the request timing and size 49840 * @return {Boolean} True if the request was aborted, false otherwise 49841 * @private 49842 */ 49843 49844 }, { 49845 key: 'abortRequestEarly_', 49846 value: function abortRequestEarly_(stats) { 49847 if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition 49848 // TODO: Replace using timeout with a boolean indicating whether this playlist is 49849 // the lowestEnabledRendition. 49850 !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes 49851 !this.playlist_.attributes.BANDWIDTH) { 49852 return false; 49853 } // Wait at least 1 second since the first byte of data has been received before 49854 // using the calculated bandwidth from the progress event to allow the bitrate 49855 // to stabilize 49856 49857 49858 if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) { 49859 return false; 49860 } 49861 49862 var currentTime = this.currentTime_(); 49863 var measuredBandwidth = stats.bandwidth; 49864 var segmentDuration = this.pendingSegment_.duration; 49865 var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort 49866 // if we are only left with less than 1 second when the request completes. 49867 // A negative timeUntilRebuffering indicates we are already rebuffering 49868 49869 var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download 49870 // is larger than the estimated time until the player runs out of forward buffer 49871 49872 if (requestTimeRemaining <= timeUntilRebuffer$$1) { 49873 return false; 49874 } 49875 49876 var switchCandidate = minRebufferMaxBandwidthSelector({ 49877 master: this.hls_.playlists.master, 49878 currentTime: currentTime, 49879 bandwidth: measuredBandwidth, 49880 duration: this.duration_(), 49881 segmentDuration: segmentDuration, 49882 timeUntilRebuffer: timeUntilRebuffer$$1, 49883 currentTimeline: this.currentTimeline_, 49884 syncController: this.syncController_ 49885 }); 49886 49887 if (!switchCandidate) { 49888 return; 49889 } 49890 49891 var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1; 49892 var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact; 49893 var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the 49894 // potential round trip time of the new request so that we are not too aggressive 49895 // with switching to a playlist that might save us a fraction of a second. 49896 49897 if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) { 49898 minimumTimeSaving = 1; 49899 } 49900 49901 if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) { 49902 return false; 49903 } // set the bandwidth to that of the desired playlist being sure to scale by 49904 // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it 49905 // don't trigger a bandwidthupdate as the bandwidth is artifial 49906 49907 49908 this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1; 49909 this.abort(); 49910 this.trigger('earlyabort'); 49911 return true; 49912 } 49913 /** 49914 * XHR `progress` event handler 49915 * 49916 * @param {Event} 49917 * The XHR `progress` event 49918 * @param {Object} simpleSegment 49919 * A simplified segment object copy 49920 * @private 49921 */ 49922 49923 }, { 49924 key: 'handleProgress_', 49925 value: function handleProgress_(event, simpleSegment) { 49926 if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) { 49927 return; 49928 } 49929 49930 this.trigger('progress'); 49931 } 49932 /** 49933 * load a specific segment from a request into the buffer 49934 * 49935 * @private 49936 */ 49937 49938 }, { 49939 key: 'loadSegment_', 49940 value: function loadSegment_(segmentInfo) { 49941 this.state = 'WAITING'; 49942 this.pendingSegment_ = segmentInfo; 49943 this.trimBackBuffer_(segmentInfo); 49944 segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback 49945 this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this)); 49946 } 49947 /** 49948 * trim the back buffer so that we don't have too much data 49949 * in the source buffer 49950 * 49951 * @private 49952 * 49953 * @param {Object} segmentInfo - the current segment 49954 */ 49955 49956 }, { 49957 key: 'trimBackBuffer_', 49958 value: function trimBackBuffer_(segmentInfo) { 49959 var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of 49960 // buffer and a very conservative "garbage collector" 49961 // We manually clear out the old buffer to ensure 49962 // we don't trigger the QuotaExceeded error 49963 // on the source buffer during subsequent appends 49964 49965 if (removeToTime > 0) { 49966 this.remove(0, removeToTime); 49967 } 49968 } 49969 /** 49970 * created a simplified copy of the segment object with just the 49971 * information necessary to perform the XHR and decryption 49972 * 49973 * @private 49974 * 49975 * @param {Object} segmentInfo - the current segment 49976 * @returns {Object} a simplified segment object copy 49977 */ 49978 49979 }, { 49980 key: 'createSimplifiedSegmentObj_', 49981 value: function createSimplifiedSegmentObj_(segmentInfo) { 49982 var segment = segmentInfo.segment; 49983 var simpleSegment = { 49984 resolvedUri: segment.resolvedUri, 49985 byterange: segment.byterange, 49986 requestId: segmentInfo.requestId 49987 }; 49988 49989 if (segment.key) { 49990 // if the media sequence is greater than 2^32, the IV will be incorrect 49991 // assuming 10s segments, that would be about 1300 years 49992 var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 49993 simpleSegment.key = this.segmentKey(segment.key); 49994 simpleSegment.key.iv = iv; 49995 } 49996 49997 if (segment.map) { 49998 simpleSegment.map = this.initSegment(segment.map); 49999 } 50000 50001 return simpleSegment; 50002 } 50003 /** 50004 * Handle the callback from the segmentRequest function and set the 50005 * associated SegmentLoader state and errors if necessary 50006 * 50007 * @private 50008 */ 50009 50010 }, { 50011 key: 'segmentRequestFinished_', 50012 value: function segmentRequestFinished_(error, simpleSegment) { 50013 // every request counts as a media request even if it has been aborted 50014 // or canceled due to a timeout 50015 this.mediaRequests += 1; 50016 50017 if (simpleSegment.stats) { 50018 this.mediaBytesTransferred += simpleSegment.stats.bytesReceived; 50019 this.mediaTransferDuration += simpleSegment.stats.roundTripTime; 50020 } // The request was aborted and the SegmentLoader has already been reset 50021 50022 50023 if (!this.pendingSegment_) { 50024 this.mediaRequestsAborted += 1; 50025 return; 50026 } // the request was aborted and the SegmentLoader has already started 50027 // another request. this can happen when the timeout for an aborted 50028 // request triggers due to a limitation in the XHR library 50029 // do not count this as any sort of request or we risk double-counting 50030 50031 50032 if (simpleSegment.requestId !== this.pendingSegment_.requestId) { 50033 return; 50034 } // an error occurred from the active pendingSegment_ so reset everything 50035 50036 50037 if (error) { 50038 this.pendingSegment_ = null; 50039 this.state = 'READY'; // the requests were aborted just record the aborted stat and exit 50040 // this is not a true error condition and nothing corrective needs 50041 // to be done 50042 50043 if (error.code === REQUEST_ERRORS.ABORTED) { 50044 this.mediaRequestsAborted += 1; 50045 return; 50046 } 50047 50048 this.pause(); // the error is really just that at least one of the requests timed-out 50049 // set the bandwidth to a very low value and trigger an ABR switch to 50050 // take emergency action 50051 50052 if (error.code === REQUEST_ERRORS.TIMEOUT) { 50053 this.mediaRequestsTimedout += 1; 50054 this.bandwidth = 1; 50055 this.roundTrip = NaN; 50056 this.trigger('bandwidthupdate'); 50057 return; 50058 } // if control-flow has arrived here, then the error is real 50059 // emit an error event to blacklist the current playlist 50060 50061 50062 this.mediaRequestsErrored += 1; 50063 this.error(error); 50064 this.trigger('error'); 50065 return; 50066 } // the response was a success so set any bandwidth stats the request 50067 // generated for ABR purposes 50068 50069 50070 this.bandwidth = simpleSegment.stats.bandwidth; 50071 this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data 50072 // to the initSegment cache 50073 50074 if (simpleSegment.map) { 50075 simpleSegment.map = this.initSegment(simpleSegment.map, true); 50076 } // if this request included a segment key, save that data in the cache 50077 50078 50079 if (simpleSegment.key) { 50080 this.segmentKey(simpleSegment.key, true); 50081 } 50082 50083 this.processSegmentResponse_(simpleSegment); 50084 } 50085 /** 50086 * Move any important data from the simplified segment object 50087 * back to the real segment object for future phases 50088 * 50089 * @private 50090 */ 50091 50092 }, { 50093 key: 'processSegmentResponse_', 50094 value: function processSegmentResponse_(simpleSegment) { 50095 var segmentInfo = this.pendingSegment_; 50096 segmentInfo.bytes = simpleSegment.bytes; 50097 50098 if (simpleSegment.map) { 50099 segmentInfo.segment.map.bytes = simpleSegment.map.bytes; 50100 } 50101 50102 segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks 50103 50104 if (simpleSegment.fmp4Captions) { 50105 createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams); 50106 addCaptionData({ 50107 inbandTextTracks: this.inbandTextTracks_, 50108 captionArray: simpleSegment.fmp4Captions, 50109 // fmp4s will not have a timestamp offset 50110 timestampOffset: 0 50111 }); // Reset stored captions since we added parsed 50112 // captions to a text track at this point 50113 50114 if (this.captionParser_) { 50115 this.captionParser_.clearParsedCaptions(); 50116 } 50117 } 50118 50119 this.handleSegment_(); 50120 } 50121 /** 50122 * append a decrypted segement to the SourceBuffer through a SourceUpdater 50123 * 50124 * @private 50125 */ 50126 50127 }, { 50128 key: 'handleSegment_', 50129 value: function handleSegment_() { 50130 var _this3 = this; 50131 50132 if (!this.pendingSegment_) { 50133 this.state = 'READY'; 50134 return; 50135 } 50136 50137 var segmentInfo = this.pendingSegment_; 50138 var segment = segmentInfo.segment; 50139 var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is 50140 // dealing with. Although we're maintaining extra state, it helps to preserve the 50141 // separation of segment loader from the actual source buffers. 50142 50143 if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are 50144 // certain that all streams will provide it. 50145 timingInfo.containsAudio || timingInfo.containsVideo)) { 50146 this.startingMedia_ = { 50147 containsAudio: timingInfo.containsAudio, 50148 containsVideo: timingInfo.containsVideo 50149 }; 50150 } 50151 50152 var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo); 50153 50154 if (illegalMediaSwitchError) { 50155 this.error({ 50156 message: illegalMediaSwitchError, 50157 blacklistDuration: Infinity 50158 }); 50159 this.trigger('error'); 50160 return; 50161 } 50162 50163 if (segmentInfo.isSyncRequest) { 50164 this.trigger('syncinfoupdate'); 50165 this.pendingSegment_ = null; 50166 this.state = 'READY'; 50167 return; 50168 } 50169 50170 if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { 50171 // Subtract any difference between the PTS and DTS times of the first frame 50172 // from the timeStampOffset (which currently equals the buffered.end) to prevent 50173 // creating any gaps in the buffer 50174 if (timingInfo && timingInfo.segmentTimestampInfo) { 50175 var ptsStartTime = timingInfo.segmentTimestampInfo[0].ptsTime; 50176 var dtsStartTime = timingInfo.segmentTimestampInfo[0].dtsTime; 50177 segmentInfo.timestampOffset -= ptsStartTime - dtsStartTime; 50178 } 50179 50180 this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities) 50181 50182 this.trigger('timestampoffset'); 50183 } 50184 50185 var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline); 50186 50187 if (timelineMapping !== null) { 50188 this.trigger({ 50189 type: 'segmenttimemapping', 50190 mapping: timelineMapping 50191 }); 50192 } 50193 50194 this.state = 'APPENDING'; // if the media initialization segment is changing, append it 50195 // before the content segment 50196 50197 if (segment.map) { 50198 var initId = initSegmentId(segment.map); 50199 50200 if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) { 50201 var initSegment = this.initSegment(segment.map); 50202 this.sourceUpdater_.appendBuffer({ 50203 bytes: initSegment.bytes 50204 }, function () { 50205 _this3.activeInitSegmentId_ = initId; 50206 }); 50207 } 50208 } 50209 50210 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 50211 50212 if (typeof segment.start === 'number' && typeof segment.end === 'number') { 50213 this.mediaSecondsLoaded += segment.end - segment.start; 50214 } else { 50215 this.mediaSecondsLoaded += segment.duration; 50216 } 50217 50218 this.logger_(segmentInfoString(segmentInfo)); 50219 this.sourceUpdater_.appendBuffer({ 50220 bytes: segmentInfo.bytes, 50221 videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId) 50222 }, this.handleUpdateEnd_.bind(this)); 50223 } 50224 }, { 50225 key: 'handleVideoSegmentTimingInfo_', 50226 value: function handleVideoSegmentTimingInfo_(requestId, event) { 50227 if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) { 50228 return; 50229 } 50230 50231 var segment = this.pendingSegment_.segment; 50232 50233 if (!segment.videoTimingInfo) { 50234 segment.videoTimingInfo = {}; 50235 } 50236 50237 segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0; 50238 segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation; 50239 segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging 50240 50241 segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime; 50242 } 50243 /** 50244 * callback to run when appendBuffer is finished. detects if we are 50245 * in a good state to do things with the data we got, or if we need 50246 * to wait for more 50247 * 50248 * @private 50249 */ 50250 50251 }, { 50252 key: 'handleUpdateEnd_', 50253 value: function handleUpdateEnd_() { 50254 if (!this.pendingSegment_) { 50255 this.state = 'READY'; 50256 50257 if (!this.paused()) { 50258 this.monitorBuffer_(); 50259 } 50260 50261 return; 50262 } 50263 50264 var segmentInfo = this.pendingSegment_; 50265 var segment = segmentInfo.segment; 50266 var isWalkingForward = this.mediaIndex !== null; 50267 this.pendingSegment_ = null; 50268 this.recordThroughput_(segmentInfo); 50269 this.addSegmentMetadataCue_(segmentInfo); 50270 this.state = 'READY'; 50271 this.mediaIndex = segmentInfo.mediaIndex; 50272 this.fetchAtBuffer_ = true; 50273 this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before 50274 // the following conditional otherwise it may consider this a bad "guess" 50275 // and attempt to resync when the post-update seekable window and live 50276 // point would mean that this was the perfect segment to fetch 50277 50278 this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before 50279 // the currentTime_ that means that our conservative guess was too conservative. 50280 // In that case, reset the loader state so that we try to use any information gained 50281 // from the previous request to create a new, more accurate, sync-point. 50282 50283 if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) { 50284 this.resetEverything(); 50285 return; 50286 } // Don't do a rendition switch unless we have enough time to get a sync segment 50287 // and conservatively guess 50288 50289 50290 if (isWalkingForward) { 50291 this.trigger('bandwidthupdate'); 50292 } 50293 50294 this.trigger('progress'); // any time an update finishes and the last segment is in the 50295 // buffer, end the stream. this ensures the "ended" event will 50296 // fire if playback reaches that point. 50297 50298 if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) { 50299 this.endOfStream(); 50300 } 50301 50302 if (!this.paused()) { 50303 this.monitorBuffer_(); 50304 } 50305 } 50306 /** 50307 * Records the current throughput of the decrypt, transmux, and append 50308 * portion of the semgment pipeline. `throughput.rate` is a the cumulative 50309 * moving average of the throughput. `throughput.count` is the number of 50310 * data points in the average. 50311 * 50312 * @private 50313 * @param {Object} segmentInfo the object returned by loadSegment 50314 */ 50315 50316 }, { 50317 key: 'recordThroughput_', 50318 value: function recordThroughput_(segmentInfo) { 50319 var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide 50320 // by zero in the case where the throughput is ridiculously high 50321 50322 var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second 50323 50324 var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation: 50325 // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1) 50326 50327 this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count; 50328 } 50329 /** 50330 * Adds a cue to the segment-metadata track with some metadata information about the 50331 * segment 50332 * 50333 * @private 50334 * @param {Object} segmentInfo 50335 * the object returned by loadSegment 50336 * @method addSegmentMetadataCue_ 50337 */ 50338 50339 }, { 50340 key: 'addSegmentMetadataCue_', 50341 value: function addSegmentMetadataCue_(segmentInfo) { 50342 if (!this.segmentMetadataTrack_) { 50343 return; 50344 } 50345 50346 var segment = segmentInfo.segment; 50347 var start = segment.start; 50348 var end = segment.end; // Do not try adding the cue if the start and end times are invalid. 50349 50350 if (!finite(start) || !finite(end)) { 50351 return; 50352 } 50353 50354 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 50355 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 50356 var value = { 50357 custom: segment.custom, 50358 dateTimeObject: segment.dateTimeObject, 50359 dateTimeString: segment.dateTimeString, 50360 bandwidth: segmentInfo.playlist.attributes.BANDWIDTH, 50361 resolution: segmentInfo.playlist.attributes.RESOLUTION, 50362 codecs: segmentInfo.playlist.attributes.CODECS, 50363 byteLength: segmentInfo.byteLength, 50364 uri: segmentInfo.uri, 50365 timeline: segmentInfo.timeline, 50366 playlist: segmentInfo.playlist.uri, 50367 start: start, 50368 end: end 50369 }; 50370 var data = JSON.stringify(value); 50371 var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between 50372 // the differences of WebKitDataCue in safari and VTTCue in other browsers 50373 50374 cue.value = value; 50375 this.segmentMetadataTrack_.addCue(cue); 50376 } 50377 }]); 50378 return SegmentLoader; 50379 }(videojs$1.EventTarget); 50380 50381 var uint8ToUtf8 = function uint8ToUtf8(uintArray) { 50382 return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray))); 50383 }; 50384 /** 50385 * @file vtt-segment-loader.js 50386 */ 50387 50388 50389 var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char2) { 50390 return _char2.charCodeAt(0); 50391 })); 50392 /** 50393 * An object that manages segment loading and appending. 50394 * 50395 * @class VTTSegmentLoader 50396 * @param {Object} options required and optional options 50397 * @extends videojs.EventTarget 50398 */ 50399 50400 var VTTSegmentLoader = function (_SegmentLoader) { 50401 inherits$2(VTTSegmentLoader, _SegmentLoader); 50402 50403 function VTTSegmentLoader(settings) { 50404 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 50405 classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error; 50406 // however, VTTSegmentLoader has no need of a media source, so delete the reference 50407 50408 var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options)); 50409 50410 _this.mediaSource_ = null; 50411 _this.subtitlesTrack_ = null; 50412 return _this; 50413 } 50414 /** 50415 * Indicates which time ranges are buffered 50416 * 50417 * @return {TimeRange} 50418 * TimeRange object representing the current buffered ranges 50419 */ 50420 50421 50422 createClass$1(VTTSegmentLoader, [{ 50423 key: 'buffered_', 50424 value: function buffered_() { 50425 if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) { 50426 return videojs$1.createTimeRanges(); 50427 } 50428 50429 var cues = this.subtitlesTrack_.cues; 50430 var start = cues[0].startTime; 50431 var end = cues[cues.length - 1].startTime; 50432 return videojs$1.createTimeRanges([[start, end]]); 50433 } 50434 /** 50435 * Gets and sets init segment for the provided map 50436 * 50437 * @param {Object} map 50438 * The map object representing the init segment to get or set 50439 * @param {Boolean=} set 50440 * If true, the init segment for the provided map should be saved 50441 * @return {Object} 50442 * map object for desired init segment 50443 */ 50444 50445 }, { 50446 key: 'initSegment', 50447 value: function initSegment(map) { 50448 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 50449 50450 if (!map) { 50451 return null; 50452 } 50453 50454 var id = initSegmentId(map); 50455 var storedMap = this.initSegments_[id]; 50456 50457 if (set$$1 && !storedMap && map.bytes) { 50458 // append WebVTT line terminators to the media initialization segment if it exists 50459 // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that 50460 // requires two or more WebVTT line terminators between the WebVTT header and the 50461 // rest of the file 50462 var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength; 50463 var combinedSegment = new Uint8Array(combinedByteLength); 50464 combinedSegment.set(map.bytes); 50465 combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength); 50466 this.initSegments_[id] = storedMap = { 50467 resolvedUri: map.resolvedUri, 50468 byterange: map.byterange, 50469 bytes: combinedSegment 50470 }; 50471 } 50472 50473 return storedMap || map; 50474 } 50475 /** 50476 * Returns true if all configuration required for loading is present, otherwise false. 50477 * 50478 * @return {Boolean} True if the all configuration is ready for loading 50479 * @private 50480 */ 50481 50482 }, { 50483 key: 'couldBeginLoading_', 50484 value: function couldBeginLoading_() { 50485 return this.playlist_ && this.subtitlesTrack_ && !this.paused(); 50486 } 50487 /** 50488 * Once all the starting parameters have been specified, begin 50489 * operation. This method should only be invoked from the INIT 50490 * state. 50491 * 50492 * @private 50493 */ 50494 50495 }, { 50496 key: 'init_', 50497 value: function init_() { 50498 this.state = 'READY'; 50499 this.resetEverything(); 50500 return this.monitorBuffer_(); 50501 } 50502 /** 50503 * Set a subtitle track on the segment loader to add subtitles to 50504 * 50505 * @param {TextTrack=} track 50506 * The text track to add loaded subtitles to 50507 * @return {TextTrack} 50508 * Returns the subtitles track 50509 */ 50510 50511 }, { 50512 key: 'track', 50513 value: function track(_track) { 50514 if (typeof _track === 'undefined') { 50515 return this.subtitlesTrack_; 50516 } 50517 50518 this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start 50519 // buffering now 50520 50521 if (this.state === 'INIT' && this.couldBeginLoading_()) { 50522 this.init_(); 50523 } 50524 50525 return this.subtitlesTrack_; 50526 } 50527 /** 50528 * Remove any data in the source buffer between start and end times 50529 * @param {Number} start - the start time of the region to remove from the buffer 50530 * @param {Number} end - the end time of the region to remove from the buffer 50531 */ 50532 50533 }, { 50534 key: 'remove', 50535 value: function remove(start, end) { 50536 removeCuesFromTrack(start, end, this.subtitlesTrack_); 50537 } 50538 /** 50539 * fill the buffer with segements unless the sourceBuffers are 50540 * currently updating 50541 * 50542 * Note: this function should only ever be called by monitorBuffer_ 50543 * and never directly 50544 * 50545 * @private 50546 */ 50547 50548 }, { 50549 key: 'fillBuffer_', 50550 value: function fillBuffer_() { 50551 var _this2 = this; 50552 50553 if (!this.syncPoint_) { 50554 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 50555 } // see if we need to begin loading immediately 50556 50557 50558 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 50559 segmentInfo = this.skipEmptySegments_(segmentInfo); 50560 50561 if (!segmentInfo) { 50562 return; 50563 } 50564 50565 if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) { 50566 // We don't have the timestamp offset that we need to sync subtitles. 50567 // Rerun on a timestamp offset or user interaction. 50568 var checkTimestampOffset = function checkTimestampOffset() { 50569 _this2.state = 'READY'; 50570 50571 if (!_this2.paused()) { 50572 // if not paused, queue a buffer check as soon as possible 50573 _this2.monitorBuffer_(); 50574 } 50575 }; 50576 50577 this.syncController_.one('timestampoffset', checkTimestampOffset); 50578 this.state = 'WAITING_ON_TIMELINE'; 50579 return; 50580 } 50581 50582 this.loadSegment_(segmentInfo); 50583 } 50584 /** 50585 * Prevents the segment loader from requesting segments we know contain no subtitles 50586 * by walking forward until we find the next segment that we don't know whether it is 50587 * empty or not. 50588 * 50589 * @param {Object} segmentInfo 50590 * a segment info object that describes the current segment 50591 * @return {Object} 50592 * a segment info object that describes the current segment 50593 */ 50594 50595 }, { 50596 key: 'skipEmptySegments_', 50597 value: function skipEmptySegments_(segmentInfo) { 50598 while (segmentInfo && segmentInfo.segment.empty) { 50599 segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest); 50600 } 50601 50602 return segmentInfo; 50603 } 50604 /** 50605 * append a decrypted segement to the SourceBuffer through a SourceUpdater 50606 * 50607 * @private 50608 */ 50609 50610 }, { 50611 key: 'handleSegment_', 50612 value: function handleSegment_() { 50613 var _this3 = this; 50614 50615 if (!this.pendingSegment_ || !this.subtitlesTrack_) { 50616 this.state = 'READY'; 50617 return; 50618 } 50619 50620 this.state = 'APPENDING'; 50621 var segmentInfo = this.pendingSegment_; 50622 var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 50623 50624 if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) { 50625 var loadHandler = void 0; 50626 50627 var errorHandler = function errorHandler() { 50628 _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler); 50629 50630 _this3.error({ 50631 message: 'Error loading vtt.js' 50632 }); 50633 50634 _this3.state = 'READY'; 50635 50636 _this3.pause(); 50637 50638 _this3.trigger('error'); 50639 }; 50640 50641 loadHandler = function loadHandler() { 50642 _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler); 50643 50644 _this3.handleSegment_(); 50645 }; 50646 50647 this.state = 'WAITING_ON_VTTJS'; 50648 this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler); 50649 this.subtitlesTrack_.tech_.one('vttjserror', errorHandler); 50650 return; 50651 } 50652 50653 segment.requested = true; 50654 50655 try { 50656 this.parseVTTCues_(segmentInfo); 50657 } catch (e) { 50658 this.error({ 50659 message: e.message 50660 }); 50661 this.state = 'READY'; 50662 this.pause(); 50663 return this.trigger('error'); 50664 } 50665 50666 this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_); 50667 50668 if (segmentInfo.isSyncRequest) { 50669 this.trigger('syncinfoupdate'); 50670 this.pendingSegment_ = null; 50671 this.state = 'READY'; 50672 return; 50673 } 50674 50675 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 50676 this.mediaSecondsLoaded += segment.duration; 50677 50678 if (segmentInfo.cues.length) { 50679 // remove any overlapping cues to prevent doubling 50680 this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime); 50681 } 50682 50683 segmentInfo.cues.forEach(function (cue) { 50684 _this3.subtitlesTrack_.addCue(cue); 50685 }); 50686 this.handleUpdateEnd_(); 50687 } 50688 /** 50689 * Uses the WebVTT parser to parse the segment response 50690 * 50691 * @param {Object} segmentInfo 50692 * a segment info object that describes the current segment 50693 * @private 50694 */ 50695 50696 }, { 50697 key: 'parseVTTCues_', 50698 value: function parseVTTCues_(segmentInfo) { 50699 var decoder = void 0; 50700 var decodeBytesToString = false; 50701 50702 if (typeof window$1.TextDecoder === 'function') { 50703 decoder = new window$1.TextDecoder('utf8'); 50704 } else { 50705 decoder = window$1.WebVTT.StringDecoder(); 50706 decodeBytesToString = true; 50707 } 50708 50709 var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder); 50710 segmentInfo.cues = []; 50711 segmentInfo.timestampmap = { 50712 MPEGTS: 0, 50713 LOCAL: 0 50714 }; 50715 parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues); 50716 50717 parser.ontimestampmap = function (map) { 50718 return segmentInfo.timestampmap = map; 50719 }; 50720 50721 parser.onparsingerror = function (error) { 50722 videojs$1.log.warn('Error encountered when parsing cues: ' + error.message); 50723 }; 50724 50725 if (segmentInfo.segment.map) { 50726 var mapData = segmentInfo.segment.map.bytes; 50727 50728 if (decodeBytesToString) { 50729 mapData = uint8ToUtf8(mapData); 50730 } 50731 50732 parser.parse(mapData); 50733 } 50734 50735 var segmentData = segmentInfo.bytes; 50736 50737 if (decodeBytesToString) { 50738 segmentData = uint8ToUtf8(segmentData); 50739 } 50740 50741 parser.parse(segmentData); 50742 parser.flush(); 50743 } 50744 /** 50745 * Updates the start and end times of any cues parsed by the WebVTT parser using 50746 * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping 50747 * from the SyncController 50748 * 50749 * @param {Object} segmentInfo 50750 * a segment info object that describes the current segment 50751 * @param {Object} mappingObj 50752 * object containing a mapping from TS to media time 50753 * @param {Object} playlist 50754 * the playlist object containing the segment 50755 * @private 50756 */ 50757 50758 }, { 50759 key: 'updateTimeMapping_', 50760 value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) { 50761 var segment = segmentInfo.segment; 50762 50763 if (!mappingObj) { 50764 // If the sync controller does not have a mapping of TS to Media Time for the 50765 // timeline, then we don't have enough information to update the cue 50766 // start/end times 50767 return; 50768 } 50769 50770 if (!segmentInfo.cues.length) { 50771 // If there are no cues, we also do not have enough information to figure out 50772 // segment timing. Mark that the segment contains no cues so we don't re-request 50773 // an empty segment. 50774 segment.empty = true; 50775 return; 50776 } 50777 50778 var timestampmap = segmentInfo.timestampmap; 50779 var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping; 50780 segmentInfo.cues.forEach(function (cue) { 50781 // First convert cue time to TS time using the timestamp-map provided within the vtt 50782 cue.startTime += diff; 50783 cue.endTime += diff; 50784 }); 50785 50786 if (!playlist.syncInfo) { 50787 var firstStart = segmentInfo.cues[0].startTime; 50788 var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; 50789 playlist.syncInfo = { 50790 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 50791 time: Math.min(firstStart, lastStart - segment.duration) 50792 }; 50793 } 50794 } 50795 }]); 50796 return VTTSegmentLoader; 50797 }(SegmentLoader); 50798 /** 50799 * @file ad-cue-tags.js 50800 */ 50801 50802 /** 50803 * Searches for an ad cue that overlaps with the given mediaTime 50804 */ 50805 50806 50807 var findAdCue = function findAdCue(track, mediaTime) { 50808 var cues = track.cues; 50809 50810 for (var i = 0; i < cues.length; i++) { 50811 var cue = cues[i]; 50812 50813 if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) { 50814 return cue; 50815 } 50816 } 50817 50818 return null; 50819 }; 50820 50821 var updateAdCues = function updateAdCues(media, track) { 50822 var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; 50823 50824 if (!media.segments) { 50825 return; 50826 } 50827 50828 var mediaTime = offset; 50829 var cue = void 0; 50830 50831 for (var i = 0; i < media.segments.length; i++) { 50832 var segment = media.segments[i]; 50833 50834 if (!cue) { 50835 // Since the cues will span for at least the segment duration, adding a fudge 50836 // factor of half segment duration will prevent duplicate cues from being 50837 // created when timing info is not exact (e.g. cue start time initialized 50838 // at 10.006677, but next call mediaTime is 10.003332 ) 50839 cue = findAdCue(track, mediaTime + segment.duration / 2); 50840 } 50841 50842 if (cue) { 50843 if ('cueIn' in segment) { 50844 // Found a CUE-IN so end the cue 50845 cue.endTime = mediaTime; 50846 cue.adEndTime = mediaTime; 50847 mediaTime += segment.duration; 50848 cue = null; 50849 continue; 50850 } 50851 50852 if (mediaTime < cue.endTime) { 50853 // Already processed this mediaTime for this cue 50854 mediaTime += segment.duration; 50855 continue; 50856 } // otherwise extend cue until a CUE-IN is found 50857 50858 50859 cue.endTime += segment.duration; 50860 } else { 50861 if ('cueOut' in segment) { 50862 cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut); 50863 cue.adStartTime = mediaTime; // Assumes tag format to be 50864 // #EXT-X-CUE-OUT:30 50865 50866 cue.adEndTime = mediaTime + parseFloat(segment.cueOut); 50867 track.addCue(cue); 50868 } 50869 50870 if ('cueOutCont' in segment) { 50871 // Entered into the middle of an ad cue 50872 var adOffset = void 0; 50873 var adTotal = void 0; // Assumes tag formate to be 50874 // #EXT-X-CUE-OUT-CONT:10/30 50875 50876 var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat); 50877 50878 var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2); 50879 50880 adOffset = _segment$cueOutCont$s2[0]; 50881 adTotal = _segment$cueOutCont$s2[1]; 50882 cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, ''); 50883 cue.adStartTime = mediaTime - adOffset; 50884 cue.adEndTime = cue.adStartTime + adTotal; 50885 track.addCue(cue); 50886 } 50887 } 50888 50889 mediaTime += segment.duration; 50890 } 50891 }; 50892 /** 50893 * @file sync-controller.js 50894 */ 50895 50896 50897 var tsprobe = tsInspector.inspect; 50898 var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always* 50899 // the equivalence display-time 0 === segment-index 0 50900 { 50901 name: 'VOD', 50902 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 50903 if (duration$$1 !== Infinity) { 50904 var syncPoint = { 50905 time: 0, 50906 segmentIndex: 0 50907 }; 50908 return syncPoint; 50909 } 50910 50911 return null; 50912 } 50913 }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist 50914 { 50915 name: 'ProgramDateTime', 50916 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 50917 if (!syncController.datetimeToDisplayTime) { 50918 return null; 50919 } 50920 50921 var segments = playlist.segments || []; 50922 var syncPoint = null; 50923 var lastDistance = null; 50924 currentTime = currentTime || 0; 50925 50926 for (var i = 0; i < segments.length; i++) { 50927 var segment = segments[i]; 50928 50929 if (segment.dateTimeObject) { 50930 var segmentTime = segment.dateTimeObject.getTime() / 1000; 50931 var segmentStart = segmentTime + syncController.datetimeToDisplayTime; 50932 var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, or if distance is 0, we have passed 50933 // currentTime and can stop looking for better candidates 50934 50935 if (lastDistance !== null && (distance === 0 || lastDistance < distance)) { 50936 break; 50937 } 50938 50939 lastDistance = distance; 50940 syncPoint = { 50941 time: segmentStart, 50942 segmentIndex: i 50943 }; 50944 } 50945 } 50946 50947 return syncPoint; 50948 } 50949 }, // Stategy "Segment": We have a known time mapping for a timeline and a 50950 // segment in the current timeline with timing data 50951 { 50952 name: 'Segment', 50953 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 50954 var segments = playlist.segments || []; 50955 var syncPoint = null; 50956 var lastDistance = null; 50957 currentTime = currentTime || 0; 50958 50959 for (var i = 0; i < segments.length; i++) { 50960 var segment = segments[i]; 50961 50962 if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') { 50963 var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed 50964 // currentTime and can stop looking for better candidates 50965 50966 if (lastDistance !== null && lastDistance < distance) { 50967 break; 50968 } 50969 50970 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 50971 lastDistance = distance; 50972 syncPoint = { 50973 time: segment.start, 50974 segmentIndex: i 50975 }; 50976 } 50977 } 50978 } 50979 50980 return syncPoint; 50981 } 50982 }, // Stategy "Discontinuity": We have a discontinuity with a known 50983 // display-time 50984 { 50985 name: 'Discontinuity', 50986 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 50987 var syncPoint = null; 50988 currentTime = currentTime || 0; 50989 50990 if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 50991 var lastDistance = null; 50992 50993 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 50994 var segmentIndex = playlist.discontinuityStarts[i]; 50995 var discontinuity = playlist.discontinuitySequence + i + 1; 50996 var discontinuitySync = syncController.discontinuities[discontinuity]; 50997 50998 if (discontinuitySync) { 50999 var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed 51000 // currentTime and can stop looking for better candidates 51001 51002 if (lastDistance !== null && lastDistance < distance) { 51003 break; 51004 } 51005 51006 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 51007 lastDistance = distance; 51008 syncPoint = { 51009 time: discontinuitySync.time, 51010 segmentIndex: segmentIndex 51011 }; 51012 } 51013 } 51014 } 51015 } 51016 51017 return syncPoint; 51018 } 51019 }, // Stategy "Playlist": We have a playlist with a known mapping of 51020 // segment index to display time 51021 { 51022 name: 'Playlist', 51023 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 51024 if (playlist.syncInfo) { 51025 var syncPoint = { 51026 time: playlist.syncInfo.time, 51027 segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence 51028 }; 51029 return syncPoint; 51030 } 51031 51032 return null; 51033 } 51034 }]; 51035 51036 var SyncController = function (_videojs$EventTarget) { 51037 inherits$2(SyncController, _videojs$EventTarget); 51038 51039 function SyncController() { 51040 classCallCheck$1(this, SyncController); // Segment Loader state variables... 51041 // ...for synching across variants 51042 51043 var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this)); 51044 51045 _this.inspectCache_ = undefined; // ...for synching across variants 51046 51047 _this.timelines = []; 51048 _this.discontinuities = []; 51049 _this.datetimeToDisplayTime = null; 51050 _this.logger_ = logger('SyncController'); 51051 return _this; 51052 } 51053 /** 51054 * Find a sync-point for the playlist specified 51055 * 51056 * A sync-point is defined as a known mapping from display-time to 51057 * a segment-index in the current playlist. 51058 * 51059 * @param {Playlist} playlist 51060 * The playlist that needs a sync-point 51061 * @param {Number} duration 51062 * Duration of the MediaSource (Infinite if playing a live source) 51063 * @param {Number} currentTimeline 51064 * The last timeline from which a segment was loaded 51065 * @returns {Object} 51066 * A sync-point object 51067 */ 51068 51069 51070 createClass$1(SyncController, [{ 51071 key: 'getSyncPoint', 51072 value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) { 51073 var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime); 51074 51075 if (!syncPoints.length) { 51076 // Signal that we need to attempt to get a sync-point manually 51077 // by fetching a segment in the playlist and constructing 51078 // a sync-point from that information 51079 return null; 51080 } // Now find the sync-point that is closest to the currentTime because 51081 // that should result in the most accurate guess about which segment 51082 // to fetch 51083 51084 51085 return this.selectSyncPoint_(syncPoints, { 51086 key: 'time', 51087 value: currentTime 51088 }); 51089 } 51090 /** 51091 * Calculate the amount of time that has expired off the playlist during playback 51092 * 51093 * @param {Playlist} playlist 51094 * Playlist object to calculate expired from 51095 * @param {Number} duration 51096 * Duration of the MediaSource (Infinity if playling a live source) 51097 * @returns {Number|null} 51098 * The amount of time that has expired off the playlist during playback. Null 51099 * if no sync-points for the playlist can be found. 51100 */ 51101 51102 }, { 51103 key: 'getExpiredTime', 51104 value: function getExpiredTime(playlist, duration$$1) { 51105 if (!playlist || !playlist.segments) { 51106 return null; 51107 } 51108 51109 var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time 51110 51111 if (!syncPoints.length) { 51112 return null; 51113 } 51114 51115 var syncPoint = this.selectSyncPoint_(syncPoints, { 51116 key: 'segmentIndex', 51117 value: 0 51118 }); // If the sync-point is beyond the start of the playlist, we want to subtract the 51119 // duration from index 0 to syncPoint.segmentIndex instead of adding. 51120 51121 if (syncPoint.segmentIndex > 0) { 51122 syncPoint.time *= -1; 51123 } 51124 51125 return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0)); 51126 } 51127 /** 51128 * Runs each sync-point strategy and returns a list of sync-points returned by the 51129 * strategies 51130 * 51131 * @private 51132 * @param {Playlist} playlist 51133 * The playlist that needs a sync-point 51134 * @param {Number} duration 51135 * Duration of the MediaSource (Infinity if playing a live source) 51136 * @param {Number} currentTimeline 51137 * The last timeline from which a segment was loaded 51138 * @returns {Array} 51139 * A list of sync-point objects 51140 */ 51141 51142 }, { 51143 key: 'runStrategies_', 51144 value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) { 51145 var syncPoints = []; // Try to find a sync-point in by utilizing various strategies... 51146 51147 for (var i = 0; i < syncPointStrategies.length; i++) { 51148 var strategy = syncPointStrategies[i]; 51149 var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime); 51150 51151 if (syncPoint) { 51152 syncPoint.strategy = strategy.name; 51153 syncPoints.push({ 51154 strategy: strategy.name, 51155 syncPoint: syncPoint 51156 }); 51157 } 51158 } 51159 51160 return syncPoints; 51161 } 51162 /** 51163 * Selects the sync-point nearest the specified target 51164 * 51165 * @private 51166 * @param {Array} syncPoints 51167 * List of sync-points to select from 51168 * @param {Object} target 51169 * Object specifying the property and value we are targeting 51170 * @param {String} target.key 51171 * Specifies the property to target. Must be either 'time' or 'segmentIndex' 51172 * @param {Number} target.value 51173 * The value to target for the specified key. 51174 * @returns {Object} 51175 * The sync-point nearest the target 51176 */ 51177 51178 }, { 51179 key: 'selectSyncPoint_', 51180 value: function selectSyncPoint_(syncPoints, target) { 51181 var bestSyncPoint = syncPoints[0].syncPoint; 51182 var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value); 51183 var bestStrategy = syncPoints[0].strategy; 51184 51185 for (var i = 1; i < syncPoints.length; i++) { 51186 var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value); 51187 51188 if (newDistance < bestDistance) { 51189 bestDistance = newDistance; 51190 bestSyncPoint = syncPoints[i].syncPoint; 51191 bestStrategy = syncPoints[i].strategy; 51192 } 51193 } 51194 51195 this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']')); 51196 return bestSyncPoint; 51197 } 51198 /** 51199 * Save any meta-data present on the segments when segments leave 51200 * the live window to the playlist to allow for synchronization at the 51201 * playlist level later. 51202 * 51203 * @param {Playlist} oldPlaylist - The previous active playlist 51204 * @param {Playlist} newPlaylist - The updated and most current playlist 51205 */ 51206 51207 }, { 51208 key: 'saveExpiredSegmentInfo', 51209 value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) { 51210 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time 51211 // save that information as a possible sync-point reference in future 51212 51213 for (var i = mediaSequenceDiff - 1; i >= 0; i--) { 51214 var lastRemovedSegment = oldPlaylist.segments[i]; 51215 51216 if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') { 51217 newPlaylist.syncInfo = { 51218 mediaSequence: oldPlaylist.mediaSequence + i, 51219 time: lastRemovedSegment.start 51220 }; 51221 this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']')); 51222 this.trigger('syncinfoupdate'); 51223 break; 51224 } 51225 } 51226 } 51227 /** 51228 * Save the mapping from playlist's ProgramDateTime to display. This should 51229 * only ever happen once at the start of playback. 51230 * 51231 * @param {Playlist} playlist - The currently active playlist 51232 */ 51233 51234 }, { 51235 key: 'setDateTimeMapping', 51236 value: function setDateTimeMapping(playlist) { 51237 if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) { 51238 var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000; 51239 this.datetimeToDisplayTime = -playlistTimestamp; 51240 } 51241 } 51242 /** 51243 * Reset the state of the inspection cache when we do a rendition 51244 * switch 51245 */ 51246 51247 }, { 51248 key: 'reset', 51249 value: function reset() { 51250 this.inspectCache_ = undefined; 51251 } 51252 /** 51253 * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start 51254 * and end of the segment in it's internal "media time". Used to generate 51255 * mappings from that internal "media time" to the display time that is 51256 * shown on the player. 51257 * 51258 * @param {SegmentInfo} segmentInfo - The current active request information 51259 */ 51260 51261 }, { 51262 key: 'probeSegmentInfo', 51263 value: function probeSegmentInfo(segmentInfo) { 51264 var segment = segmentInfo.segment; 51265 var playlist = segmentInfo.playlist; 51266 var timingInfo = void 0; 51267 51268 if (segment.map) { 51269 timingInfo = this.probeMp4Segment_(segmentInfo); 51270 } else { 51271 timingInfo = this.probeTsSegment_(segmentInfo); 51272 } 51273 51274 if (timingInfo) { 51275 if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) { 51276 this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information 51277 // now with segment timing information 51278 51279 if (!playlist.syncInfo) { 51280 playlist.syncInfo = { 51281 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 51282 time: segment.start 51283 }; 51284 } 51285 } 51286 } 51287 51288 return timingInfo; 51289 } 51290 /** 51291 * Probe an fmp4 segment to determine the start of the segment 51292 * in it's internal "composition time", which is equal to the base 51293 * media decode time plus the composition time offset value 51294 * 51295 * @private 51296 * @param {SegmentInfo} segmentInfo - The current active request information 51297 * @return {object} The start and end time of the current segment in "composition time" 51298 */ 51299 51300 }, { 51301 key: 'probeMp4Segment_', 51302 value: function probeMp4Segment_(segmentInfo) { 51303 var segment = segmentInfo.segment; // get timescales from init segment 51304 51305 var timescales = probe.timescale(segment.map.bytes); // calculate composition start time using the timescales and information 51306 // contained within the media segment 51307 51308 var compositionStartTime = probe.compositionStartTime(timescales, segmentInfo.bytes); 51309 51310 if (segmentInfo.timestampOffset !== null) { 51311 segmentInfo.timestampOffset -= compositionStartTime; 51312 } 51313 51314 return { 51315 start: compositionStartTime, 51316 end: compositionStartTime + segment.duration 51317 }; 51318 } 51319 /** 51320 * Probe an mpeg2-ts segment to determine the start and end of the segment 51321 * in it's internal "media time". 51322 * 51323 * @private 51324 * @param {SegmentInfo} segmentInfo - The current active request information 51325 * @return {object} The start and end time of the current segment in "media time" 51326 */ 51327 51328 }, { 51329 key: 'probeTsSegment_', 51330 value: function probeTsSegment_(segmentInfo) { 51331 var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_); 51332 var segmentStartTime = void 0; 51333 var segmentEndTime = void 0; 51334 var segmentTimestampInfo = void 0; 51335 51336 if (!timeInfo) { 51337 return null; 51338 } 51339 51340 if (timeInfo.video && timeInfo.video.length === 2) { 51341 this.inspectCache_ = timeInfo.video[1].dts; 51342 segmentStartTime = timeInfo.video[0].dtsTime; 51343 segmentEndTime = timeInfo.video[1].dtsTime; 51344 segmentTimestampInfo = timeInfo.video; 51345 } else if (timeInfo.audio && timeInfo.audio.length === 2) { 51346 this.inspectCache_ = timeInfo.audio[1].dts; 51347 segmentStartTime = timeInfo.audio[0].dtsTime; 51348 segmentEndTime = timeInfo.audio[1].dtsTime; 51349 segmentTimestampInfo = timeInfo.audio; 51350 } 51351 51352 var probedInfo = { 51353 segmentTimestampInfo: segmentTimestampInfo, 51354 start: segmentStartTime, 51355 end: segmentEndTime, 51356 containsVideo: timeInfo.video && timeInfo.video.length === 2, 51357 containsAudio: timeInfo.audio && timeInfo.audio.length === 2 51358 }; 51359 return probedInfo; 51360 } 51361 }, { 51362 key: 'timestampOffsetForTimeline', 51363 value: function timestampOffsetForTimeline(timeline) { 51364 if (typeof this.timelines[timeline] === 'undefined') { 51365 return null; 51366 } 51367 51368 return this.timelines[timeline].time; 51369 } 51370 }, { 51371 key: 'mappingForTimeline', 51372 value: function mappingForTimeline(timeline) { 51373 if (typeof this.timelines[timeline] === 'undefined') { 51374 return null; 51375 } 51376 51377 return this.timelines[timeline].mapping; 51378 } 51379 /** 51380 * Use the "media time" for a segment to generate a mapping to "display time" and 51381 * save that display time to the segment. 51382 * 51383 * @private 51384 * @param {SegmentInfo} segmentInfo 51385 * The current active request information 51386 * @param {object} timingInfo 51387 * The start and end time of the current segment in "media time" 51388 * @returns {Boolean} 51389 * Returns false if segment time mapping could not be calculated 51390 */ 51391 51392 }, { 51393 key: 'calculateSegmentTimeMapping_', 51394 value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) { 51395 var segment = segmentInfo.segment; 51396 var mappingObj = this.timelines[segmentInfo.timeline]; 51397 51398 if (segmentInfo.timestampOffset !== null) { 51399 mappingObj = { 51400 time: segmentInfo.startOfSegment, 51401 mapping: segmentInfo.startOfSegment - timingInfo.start 51402 }; 51403 this.timelines[segmentInfo.timeline] = mappingObj; 51404 this.trigger('timestampoffset'); 51405 this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']')); 51406 segment.start = segmentInfo.startOfSegment; 51407 segment.end = timingInfo.end + mappingObj.mapping; 51408 } else if (mappingObj) { 51409 segment.start = timingInfo.start + mappingObj.mapping; 51410 segment.end = timingInfo.end + mappingObj.mapping; 51411 } else { 51412 return false; 51413 } 51414 51415 return true; 51416 } 51417 /** 51418 * Each time we have discontinuity in the playlist, attempt to calculate the location 51419 * in display of the start of the discontinuity and save that. We also save an accuracy 51420 * value so that we save values with the most accuracy (closest to 0.) 51421 * 51422 * @private 51423 * @param {SegmentInfo} segmentInfo - The current active request information 51424 */ 51425 51426 }, { 51427 key: 'saveDiscontinuitySyncInfo_', 51428 value: function saveDiscontinuitySyncInfo_(segmentInfo) { 51429 var playlist = segmentInfo.playlist; 51430 var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where 51431 // the start of the range and it's accuracy is 0 (greater accuracy values 51432 // mean more approximation) 51433 51434 if (segment.discontinuity) { 51435 this.discontinuities[segment.timeline] = { 51436 time: segment.start, 51437 accuracy: 0 51438 }; 51439 } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 51440 // Search for future discontinuities that we can provide better timing 51441 // information for and save that information for sync purposes 51442 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 51443 var segmentIndex = playlist.discontinuityStarts[i]; 51444 var discontinuity = playlist.discontinuitySequence + i + 1; 51445 var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex; 51446 var accuracy = Math.abs(mediaIndexDiff); 51447 51448 if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) { 51449 var time = void 0; 51450 51451 if (mediaIndexDiff < 0) { 51452 time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex); 51453 } else { 51454 time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex); 51455 } 51456 51457 this.discontinuities[discontinuity] = { 51458 time: time, 51459 accuracy: accuracy 51460 }; 51461 } 51462 } 51463 } 51464 } 51465 }]); 51466 return SyncController; 51467 }(videojs$1.EventTarget); 51468 51469 var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) { 51470 var self = this; 51471 51472 var decrypterWorker = function () { 51473 /* 51474 * pkcs7.pad 51475 * https://github.com/brightcove/pkcs7 51476 * 51477 * Copyright (c) 2014 Brightcove 51478 * Licensed under the apache2 license. 51479 */ 51480 51481 /** 51482 * Returns the subarray of a Uint8Array without PKCS#7 padding. 51483 * @param padded {Uint8Array} unencrypted bytes that have been padded 51484 * @return {Uint8Array} the unpadded bytes 51485 * @see http://tools.ietf.org/html/rfc5652 51486 */ 51487 function unpad(padded) { 51488 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 51489 } 51490 51491 var classCallCheck = function classCallCheck(instance, Constructor) { 51492 if (!(instance instanceof Constructor)) { 51493 throw new TypeError("Cannot call a class as a function"); 51494 } 51495 }; 51496 51497 var createClass = function () { 51498 function defineProperties(target, props) { 51499 for (var i = 0; i < props.length; i++) { 51500 var descriptor = props[i]; 51501 descriptor.enumerable = descriptor.enumerable || false; 51502 descriptor.configurable = true; 51503 if ("value" in descriptor) descriptor.writable = true; 51504 Object.defineProperty(target, descriptor.key, descriptor); 51505 } 51506 } 51507 51508 return function (Constructor, protoProps, staticProps) { 51509 if (protoProps) defineProperties(Constructor.prototype, protoProps); 51510 if (staticProps) defineProperties(Constructor, staticProps); 51511 return Constructor; 51512 }; 51513 }(); 51514 51515 var inherits = function inherits(subClass, superClass) { 51516 if (typeof superClass !== "function" && superClass !== null) { 51517 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 51518 } 51519 51520 subClass.prototype = Object.create(superClass && superClass.prototype, { 51521 constructor: { 51522 value: subClass, 51523 enumerable: false, 51524 writable: true, 51525 configurable: true 51526 } 51527 }); 51528 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 51529 }; 51530 51531 var possibleConstructorReturn = function possibleConstructorReturn(self, call) { 51532 if (!self) { 51533 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 51534 } 51535 51536 return call && (typeof call === "object" || typeof call === "function") ? call : self; 51537 }; 51538 /** 51539 * @file aes.js 51540 * 51541 * This file contains an adaptation of the AES decryption algorithm 51542 * from the Standford Javascript Cryptography Library. That work is 51543 * covered by the following copyright and permissions notice: 51544 * 51545 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 51546 * All rights reserved. 51547 * 51548 * Redistribution and use in source and binary forms, with or without 51549 * modification, are permitted provided that the following conditions are 51550 * met: 51551 * 51552 * 1. Redistributions of source code must retain the above copyright 51553 * notice, this list of conditions and the following disclaimer. 51554 * 51555 * 2. Redistributions in binary form must reproduce the above 51556 * copyright notice, this list of conditions and the following 51557 * disclaimer in the documentation and/or other materials provided 51558 * with the distribution. 51559 * 51560 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 51561 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 51562 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 51563 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 51564 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 51565 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 51566 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 51567 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 51568 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 51569 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 51570 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 51571 * 51572 * The views and conclusions contained in the software and documentation 51573 * are those of the authors and should not be interpreted as representing 51574 * official policies, either expressed or implied, of the authors. 51575 */ 51576 51577 /** 51578 * Expand the S-box tables. 51579 * 51580 * @private 51581 */ 51582 51583 51584 var precompute = function precompute() { 51585 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 51586 var encTable = tables[0]; 51587 var decTable = tables[1]; 51588 var sbox = encTable[4]; 51589 var sboxInv = decTable[4]; 51590 var i = void 0; 51591 var x = void 0; 51592 var xInv = void 0; 51593 var d = []; 51594 var th = []; 51595 var x2 = void 0; 51596 var x4 = void 0; 51597 var x8 = void 0; 51598 var s = void 0; 51599 var tEnc = void 0; 51600 var tDec = void 0; // Compute double and third tables 51601 51602 for (i = 0; i < 256; i++) { 51603 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 51604 } 51605 51606 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 51607 // Compute sbox 51608 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 51609 s = s >> 8 ^ s & 255 ^ 99; 51610 sbox[x] = s; 51611 sboxInv[s] = x; // Compute MixColumns 51612 51613 x8 = d[x4 = d[x2 = d[x]]]; 51614 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 51615 tEnc = d[s] * 0x101 ^ s * 0x1010100; 51616 51617 for (i = 0; i < 4; i++) { 51618 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 51619 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 51620 } 51621 } // Compactify. Considerable speedup on Firefox. 51622 51623 51624 for (i = 0; i < 5; i++) { 51625 encTable[i] = encTable[i].slice(0); 51626 decTable[i] = decTable[i].slice(0); 51627 } 51628 51629 return tables; 51630 }; 51631 51632 var aesTables = null; 51633 /** 51634 * Schedule out an AES key for both encryption and decryption. This 51635 * is a low-level class. Use a cipher mode to do bulk encryption. 51636 * 51637 * @class AES 51638 * @param key {Array} The key as an array of 4, 6 or 8 words. 51639 */ 51640 51641 var AES = function () { 51642 function AES(key) { 51643 classCallCheck(this, AES); 51644 /** 51645 * The expanded S-box and inverse S-box tables. These will be computed 51646 * on the client so that we don't have to send them down the wire. 51647 * 51648 * There are two tables, _tables[0] is for encryption and 51649 * _tables[1] is for decryption. 51650 * 51651 * The first 4 sub-tables are the expanded S-box with MixColumns. The 51652 * last (_tables[01][4]) is the S-box itself. 51653 * 51654 * @private 51655 */ 51656 // if we have yet to precompute the S-box tables 51657 // do so now 51658 51659 if (!aesTables) { 51660 aesTables = precompute(); 51661 } // then make a copy of that object for use 51662 51663 51664 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()]]; 51665 var i = void 0; 51666 var j = void 0; 51667 var tmp = void 0; 51668 var encKey = void 0; 51669 var decKey = void 0; 51670 var sbox = this._tables[0][4]; 51671 var decTable = this._tables[1]; 51672 var keyLen = key.length; 51673 var rcon = 1; 51674 51675 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 51676 throw new Error('Invalid aes key size'); 51677 } 51678 51679 encKey = key.slice(0); 51680 decKey = []; 51681 this._key = [encKey, decKey]; // schedule encryption keys 51682 51683 for (i = keyLen; i < 4 * keyLen + 28; i++) { 51684 tmp = encKey[i - 1]; // apply sbox 51685 51686 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 51687 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 51688 51689 if (i % keyLen === 0) { 51690 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 51691 rcon = rcon << 1 ^ (rcon >> 7) * 283; 51692 } 51693 } 51694 51695 encKey[i] = encKey[i - keyLen] ^ tmp; 51696 } // schedule decryption keys 51697 51698 51699 for (j = 0; i; j++, i--) { 51700 tmp = encKey[j & 3 ? i : i - 4]; 51701 51702 if (i <= 4 || j < 4) { 51703 decKey[j] = tmp; 51704 } else { 51705 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 51706 } 51707 } 51708 } 51709 /** 51710 * Decrypt 16 bytes, specified as four 32-bit words. 51711 * 51712 * @param {Number} encrypted0 the first word to decrypt 51713 * @param {Number} encrypted1 the second word to decrypt 51714 * @param {Number} encrypted2 the third word to decrypt 51715 * @param {Number} encrypted3 the fourth word to decrypt 51716 * @param {Int32Array} out the array to write the decrypted words 51717 * into 51718 * @param {Number} offset the offset into the output array to start 51719 * writing results 51720 * @return {Array} The plaintext. 51721 */ 51722 51723 51724 AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 51725 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 51726 51727 var a = encrypted0 ^ key[0]; 51728 var b = encrypted3 ^ key[1]; 51729 var c = encrypted2 ^ key[2]; 51730 var d = encrypted1 ^ key[3]; 51731 var a2 = void 0; 51732 var b2 = void 0; 51733 var c2 = void 0; // key.length === 2 ? 51734 51735 var nInnerRounds = key.length / 4 - 2; 51736 var i = void 0; 51737 var kIndex = 4; 51738 var table = this._tables[1]; // load up the tables 51739 51740 var table0 = table[0]; 51741 var table1 = table[1]; 51742 var table2 = table[2]; 51743 var table3 = table[3]; 51744 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 51745 51746 for (i = 0; i < nInnerRounds; i++) { 51747 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 51748 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 51749 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 51750 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 51751 kIndex += 4; 51752 a = a2; 51753 b = b2; 51754 c = c2; 51755 } // Last round. 51756 51757 51758 for (i = 0; i < 4; i++) { 51759 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 51760 a2 = a; 51761 a = b; 51762 b = c; 51763 c = d; 51764 d = a2; 51765 } 51766 }; 51767 51768 return AES; 51769 }(); 51770 /** 51771 * @file stream.js 51772 */ 51773 51774 /** 51775 * A lightweight readable stream implemention that handles event dispatching. 51776 * 51777 * @class Stream 51778 */ 51779 51780 51781 var Stream = function () { 51782 function Stream() { 51783 classCallCheck(this, Stream); 51784 this.listeners = {}; 51785 } 51786 /** 51787 * Add a listener for a specified event type. 51788 * 51789 * @param {String} type the event name 51790 * @param {Function} listener the callback to be invoked when an event of 51791 * the specified type occurs 51792 */ 51793 51794 51795 Stream.prototype.on = function on(type, listener) { 51796 if (!this.listeners[type]) { 51797 this.listeners[type] = []; 51798 } 51799 51800 this.listeners[type].push(listener); 51801 }; 51802 /** 51803 * Remove a listener for a specified event type. 51804 * 51805 * @param {String} type the event name 51806 * @param {Function} listener a function previously registered for this 51807 * type of event through `on` 51808 * @return {Boolean} if we could turn it off or not 51809 */ 51810 51811 51812 Stream.prototype.off = function off(type, listener) { 51813 if (!this.listeners[type]) { 51814 return false; 51815 } 51816 51817 var index = this.listeners[type].indexOf(listener); 51818 this.listeners[type].splice(index, 1); 51819 return index > -1; 51820 }; 51821 /** 51822 * Trigger an event of the specified type on this stream. Any additional 51823 * arguments to this function are passed as parameters to event listeners. 51824 * 51825 * @param {String} type the event name 51826 */ 51827 51828 51829 Stream.prototype.trigger = function trigger(type) { 51830 var callbacks = this.listeners[type]; 51831 51832 if (!callbacks) { 51833 return; 51834 } // Slicing the arguments on every invocation of this method 51835 // can add a significant amount of overhead. Avoid the 51836 // intermediate object creation for the common case of a 51837 // single callback argument 51838 51839 51840 if (arguments.length === 2) { 51841 var length = callbacks.length; 51842 51843 for (var i = 0; i < length; ++i) { 51844 callbacks[i].call(this, arguments[1]); 51845 } 51846 } else { 51847 var args = Array.prototype.slice.call(arguments, 1); 51848 var _length = callbacks.length; 51849 51850 for (var _i = 0; _i < _length; ++_i) { 51851 callbacks[_i].apply(this, args); 51852 } 51853 } 51854 }; 51855 /** 51856 * Destroys the stream and cleans up. 51857 */ 51858 51859 51860 Stream.prototype.dispose = function dispose() { 51861 this.listeners = {}; 51862 }; 51863 /** 51864 * Forwards all `data` events on this stream to the destination stream. The 51865 * destination stream should provide a method `push` to receive the data 51866 * events as they arrive. 51867 * 51868 * @param {Stream} destination the stream that will receive all `data` events 51869 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 51870 */ 51871 51872 51873 Stream.prototype.pipe = function pipe(destination) { 51874 this.on('data', function (data) { 51875 destination.push(data); 51876 }); 51877 }; 51878 51879 return Stream; 51880 }(); 51881 /** 51882 * @file async-stream.js 51883 */ 51884 51885 /** 51886 * A wrapper around the Stream class to use setTiemout 51887 * and run stream "jobs" Asynchronously 51888 * 51889 * @class AsyncStream 51890 * @extends Stream 51891 */ 51892 51893 51894 var AsyncStream$$1 = function (_Stream) { 51895 inherits(AsyncStream$$1, _Stream); 51896 51897 function AsyncStream$$1() { 51898 classCallCheck(this, AsyncStream$$1); 51899 51900 var _this = possibleConstructorReturn(this, _Stream.call(this, Stream)); 51901 51902 _this.jobs = []; 51903 _this.delay = 1; 51904 _this.timeout_ = null; 51905 return _this; 51906 } 51907 /** 51908 * process an async job 51909 * 51910 * @private 51911 */ 51912 51913 51914 AsyncStream$$1.prototype.processJob_ = function processJob_() { 51915 this.jobs.shift()(); 51916 51917 if (this.jobs.length) { 51918 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 51919 } else { 51920 this.timeout_ = null; 51921 } 51922 }; 51923 /** 51924 * push a job into the stream 51925 * 51926 * @param {Function} job the job to push into the stream 51927 */ 51928 51929 51930 AsyncStream$$1.prototype.push = function push(job) { 51931 this.jobs.push(job); 51932 51933 if (!this.timeout_) { 51934 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 51935 } 51936 }; 51937 51938 return AsyncStream$$1; 51939 }(Stream); 51940 /** 51941 * @file decrypter.js 51942 * 51943 * An asynchronous implementation of AES-128 CBC decryption with 51944 * PKCS#7 padding. 51945 */ 51946 51947 /** 51948 * Convert network-order (big-endian) bytes into their little-endian 51949 * representation. 51950 */ 51951 51952 51953 var ntoh = function ntoh(word) { 51954 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 51955 }; 51956 /** 51957 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 51958 * 51959 * @param {Uint8Array} encrypted the encrypted bytes 51960 * @param {Uint32Array} key the bytes of the decryption key 51961 * @param {Uint32Array} initVector the initialization vector (IV) to 51962 * use for the first round of CBC. 51963 * @return {Uint8Array} the decrypted bytes 51964 * 51965 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 51966 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 51967 * @see https://tools.ietf.org/html/rfc2315 51968 */ 51969 51970 51971 var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) { 51972 // word-level access to the encrypted bytes 51973 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 51974 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 51975 51976 var decrypted = new Uint8Array(encrypted.byteLength); 51977 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 51978 // decrypted data 51979 51980 var init0 = void 0; 51981 var init1 = void 0; 51982 var init2 = void 0; 51983 var init3 = void 0; 51984 var encrypted0 = void 0; 51985 var encrypted1 = void 0; 51986 var encrypted2 = void 0; 51987 var encrypted3 = void 0; // iteration variable 51988 51989 var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the 51990 // passed-in reference and easier access 51991 51992 init0 = initVector[0]; 51993 init1 = initVector[1]; 51994 init2 = initVector[2]; 51995 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 51996 // to each decrypted block 51997 51998 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 51999 // convert big-endian (network order) words into little-endian 52000 // (javascript order) 52001 encrypted0 = ntoh(encrypted32[wordIx]); 52002 encrypted1 = ntoh(encrypted32[wordIx + 1]); 52003 encrypted2 = ntoh(encrypted32[wordIx + 2]); 52004 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 52005 52006 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 52007 // plaintext 52008 52009 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 52010 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 52011 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 52012 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 52013 52014 init0 = encrypted0; 52015 init1 = encrypted1; 52016 init2 = encrypted2; 52017 init3 = encrypted3; 52018 } 52019 52020 return decrypted; 52021 }; 52022 /** 52023 * The `Decrypter` class that manages decryption of AES 52024 * data through `AsyncStream` objects and the `decrypt` 52025 * function 52026 * 52027 * @param {Uint8Array} encrypted the encrypted bytes 52028 * @param {Uint32Array} key the bytes of the decryption key 52029 * @param {Uint32Array} initVector the initialization vector (IV) to 52030 * @param {Function} done the function to run when done 52031 * @class Decrypter 52032 */ 52033 52034 52035 var Decrypter$$1 = function () { 52036 function Decrypter$$1(encrypted, key, initVector, done) { 52037 classCallCheck(this, Decrypter$$1); 52038 var step = Decrypter$$1.STEP; 52039 var encrypted32 = new Int32Array(encrypted.buffer); 52040 var decrypted = new Uint8Array(encrypted.byteLength); 52041 var i = 0; 52042 this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously 52043 52044 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 52045 52046 for (i = step; i < encrypted32.length; i += step) { 52047 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 52048 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 52049 } // invoke the done() callback when everything is finished 52050 52051 52052 this.asyncStream_.push(function () { 52053 // remove pkcs#7 padding from the decrypted bytes 52054 done(null, unpad(decrypted)); 52055 }); 52056 } 52057 /** 52058 * a getter for step the maximum number of bytes to process at one time 52059 * 52060 * @return {Number} the value of step 32000 52061 */ 52062 52063 /** 52064 * @private 52065 */ 52066 52067 52068 Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 52069 return function () { 52070 var bytes = decrypt$$1(encrypted, key, initVector); 52071 decrypted.set(bytes, encrypted.byteOffset); 52072 }; 52073 }; 52074 52075 createClass(Decrypter$$1, null, [{ 52076 key: 'STEP', 52077 get: function get$$1() { 52078 // 4 * 8000; 52079 return 32000; 52080 } 52081 }]); 52082 return Decrypter$$1; 52083 }(); 52084 /** 52085 * @file bin-utils.js 52086 */ 52087 52088 /** 52089 * Creates an object for sending to a web worker modifying properties that are TypedArrays 52090 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 52091 * 52092 * @param {Object} message 52093 * Object of properties and values to send to the web worker 52094 * @return {Object} 52095 * Modified message with TypedArray values expanded 52096 * @function createTransferableMessage 52097 */ 52098 52099 52100 var createTransferableMessage = function createTransferableMessage(message) { 52101 var transferable = {}; 52102 Object.keys(message).forEach(function (key) { 52103 var value = message[key]; 52104 52105 if (ArrayBuffer.isView(value)) { 52106 transferable[key] = { 52107 bytes: value.buffer, 52108 byteOffset: value.byteOffset, 52109 byteLength: value.byteLength 52110 }; 52111 } else { 52112 transferable[key] = value; 52113 } 52114 }); 52115 return transferable; 52116 }; 52117 /** 52118 * Our web worker interface so that things can talk to aes-decrypter 52119 * that will be running in a web worker. the scope is passed to this by 52120 * webworkify. 52121 * 52122 * @param {Object} self 52123 * the scope for the web worker 52124 */ 52125 52126 52127 var DecrypterWorker = function DecrypterWorker(self) { 52128 self.onmessage = function (event) { 52129 var data = event.data; 52130 var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength); 52131 var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4); 52132 var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4); 52133 /* eslint-disable no-new, handle-callback-err */ 52134 52135 new Decrypter$$1(encrypted, key, iv, function (err, bytes) { 52136 self.postMessage(createTransferableMessage({ 52137 source: data.source, 52138 decrypted: bytes 52139 }), [bytes.buffer]); 52140 }); 52141 /* eslint-enable */ 52142 }; 52143 }; 52144 52145 var decrypterWorker = new DecrypterWorker(self); 52146 return decrypterWorker; 52147 }(); 52148 }); 52149 /** 52150 * Convert the properties of an HLS track into an audioTrackKind. 52151 * 52152 * @private 52153 */ 52154 52155 var audioTrackKind_ = function audioTrackKind_(properties) { 52156 var kind = properties["default"] ? 'main' : 'alternative'; 52157 52158 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) { 52159 kind = 'main-desc'; 52160 } 52161 52162 return kind; 52163 }; 52164 /** 52165 * Pause provided segment loader and playlist loader if active 52166 * 52167 * @param {SegmentLoader} segmentLoader 52168 * SegmentLoader to pause 52169 * @param {Object} mediaType 52170 * Active media type 52171 * @function stopLoaders 52172 */ 52173 52174 52175 var stopLoaders = function stopLoaders(segmentLoader, mediaType) { 52176 segmentLoader.abort(); 52177 segmentLoader.pause(); 52178 52179 if (mediaType && mediaType.activePlaylistLoader) { 52180 mediaType.activePlaylistLoader.pause(); 52181 mediaType.activePlaylistLoader = null; 52182 } 52183 }; 52184 /** 52185 * Start loading provided segment loader and playlist loader 52186 * 52187 * @param {PlaylistLoader} playlistLoader 52188 * PlaylistLoader to start loading 52189 * @param {Object} mediaType 52190 * Active media type 52191 * @function startLoaders 52192 */ 52193 52194 52195 var startLoaders = function startLoaders(playlistLoader, mediaType) { 52196 // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the 52197 // playlist loader 52198 mediaType.activePlaylistLoader = playlistLoader; 52199 playlistLoader.load(); 52200 }; 52201 /** 52202 * Returns a function to be called when the media group changes. It performs a 52203 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a 52204 * change of group is merely a rendition switch of the same content at another encoding, 52205 * rather than a change of content, such as switching audio from English to Spanish. 52206 * 52207 * @param {String} type 52208 * MediaGroup type 52209 * @param {Object} settings 52210 * Object containing required information for media groups 52211 * @return {Function} 52212 * Handler for a non-destructive resync of SegmentLoader when the active media 52213 * group changes. 52214 * @function onGroupChanged 52215 */ 52216 52217 52218 var onGroupChanged = function onGroupChanged(type, settings) { 52219 return function () { 52220 var _settings$segmentLoad = settings.segmentLoaders, 52221 segmentLoader = _settings$segmentLoad[type], 52222 mainSegmentLoader = _settings$segmentLoad.main, 52223 mediaType = settings.mediaTypes[type]; 52224 var activeTrack = mediaType.activeTrack(); 52225 var activeGroup = mediaType.activeGroup(activeTrack); 52226 var previousActiveLoader = mediaType.activePlaylistLoader; 52227 stopLoaders(segmentLoader, mediaType); 52228 52229 if (!activeGroup) { 52230 // there is no group active 52231 return; 52232 } 52233 52234 if (!activeGroup.playlistLoader) { 52235 if (previousActiveLoader) { 52236 // The previous group had a playlist loader but the new active group does not 52237 // this means we are switching from demuxed to muxed audio. In this case we want to 52238 // do a destructive reset of the main segment loader and not restart the audio 52239 // loaders. 52240 mainSegmentLoader.resetEverything(); 52241 } 52242 52243 return; 52244 } // Non-destructive resync 52245 52246 52247 segmentLoader.resyncLoader(); 52248 startLoaders(activeGroup.playlistLoader, mediaType); 52249 }; 52250 }; 52251 /** 52252 * Returns a function to be called when the media track changes. It performs a 52253 * destructive reset of the SegmentLoader to ensure we start loading as close to 52254 * currentTime as possible. 52255 * 52256 * @param {String} type 52257 * MediaGroup type 52258 * @param {Object} settings 52259 * Object containing required information for media groups 52260 * @return {Function} 52261 * Handler for a destructive reset of SegmentLoader when the active media 52262 * track changes. 52263 * @function onTrackChanged 52264 */ 52265 52266 52267 var onTrackChanged = function onTrackChanged(type, settings) { 52268 return function () { 52269 var _settings$segmentLoad2 = settings.segmentLoaders, 52270 segmentLoader = _settings$segmentLoad2[type], 52271 mainSegmentLoader = _settings$segmentLoad2.main, 52272 mediaType = settings.mediaTypes[type]; 52273 var activeTrack = mediaType.activeTrack(); 52274 var activeGroup = mediaType.activeGroup(activeTrack); 52275 var previousActiveLoader = mediaType.activePlaylistLoader; 52276 stopLoaders(segmentLoader, mediaType); 52277 52278 if (!activeGroup) { 52279 // there is no group active so we do not want to restart loaders 52280 return; 52281 } 52282 52283 if (!activeGroup.playlistLoader) { 52284 // when switching from demuxed audio/video to muxed audio/video (noted by no playlist 52285 // loader for the audio group), we want to do a destructive reset of the main segment 52286 // loader and not restart the audio loaders 52287 mainSegmentLoader.resetEverything(); 52288 return; 52289 } 52290 52291 if (previousActiveLoader === activeGroup.playlistLoader) { 52292 // Nothing has actually changed. This can happen because track change events can fire 52293 // multiple times for a "single" change. One for enabling the new active track, and 52294 // one for disabling the track that was active 52295 startLoaders(activeGroup.playlistLoader, mediaType); 52296 return; 52297 } 52298 52299 if (segmentLoader.track) { 52300 // For WebVTT, set the new text track in the segmentloader 52301 segmentLoader.track(activeTrack); 52302 } // destructive reset 52303 52304 52305 segmentLoader.resetEverything(); 52306 startLoaders(activeGroup.playlistLoader, mediaType); 52307 }; 52308 }; 52309 52310 var onError = { 52311 /** 52312 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 52313 * an error. 52314 * 52315 * @param {String} type 52316 * MediaGroup type 52317 * @param {Object} settings 52318 * Object containing required information for media groups 52319 * @return {Function} 52320 * Error handler. Logs warning (or error if the playlist is blacklisted) to 52321 * console and switches back to default audio track. 52322 * @function onError.AUDIO 52323 */ 52324 AUDIO: function AUDIO(type, settings) { 52325 return function () { 52326 var segmentLoader = settings.segmentLoaders[type], 52327 mediaType = settings.mediaTypes[type], 52328 blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist; 52329 stopLoaders(segmentLoader, mediaType); // switch back to default audio track 52330 52331 var activeTrack = mediaType.activeTrack(); 52332 var activeGroup = mediaType.activeGroup(); 52333 var id = (activeGroup.filter(function (group) { 52334 return group["default"]; 52335 })[0] || activeGroup[0]).id; 52336 var defaultTrack = mediaType.tracks[id]; 52337 52338 if (activeTrack === defaultTrack) { 52339 // Default track encountered an error. All we can do now is blacklist the current 52340 // rendition and hope another will switch audio groups 52341 blacklistCurrentPlaylist({ 52342 message: 'Problem encountered loading the default audio track.' 52343 }); 52344 return; 52345 } 52346 52347 videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.'); 52348 52349 for (var trackId in mediaType.tracks) { 52350 mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack; 52351 } 52352 52353 mediaType.onTrackChanged(); 52354 }; 52355 }, 52356 52357 /** 52358 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 52359 * an error. 52360 * 52361 * @param {String} type 52362 * MediaGroup type 52363 * @param {Object} settings 52364 * Object containing required information for media groups 52365 * @return {Function} 52366 * Error handler. Logs warning to console and disables the active subtitle track 52367 * @function onError.SUBTITLES 52368 */ 52369 SUBTITLES: function SUBTITLES(type, settings) { 52370 return function () { 52371 var segmentLoader = settings.segmentLoaders[type], 52372 mediaType = settings.mediaTypes[type]; 52373 videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.'); 52374 stopLoaders(segmentLoader, mediaType); 52375 var track = mediaType.activeTrack(); 52376 52377 if (track) { 52378 track.mode = 'disabled'; 52379 } 52380 52381 mediaType.onTrackChanged(); 52382 }; 52383 } 52384 }; 52385 var setupListeners = { 52386 /** 52387 * Setup event listeners for audio playlist loader 52388 * 52389 * @param {String} type 52390 * MediaGroup type 52391 * @param {PlaylistLoader|null} playlistLoader 52392 * PlaylistLoader to register listeners on 52393 * @param {Object} settings 52394 * Object containing required information for media groups 52395 * @function setupListeners.AUDIO 52396 */ 52397 AUDIO: function AUDIO(type, playlistLoader, settings) { 52398 if (!playlistLoader) { 52399 // no playlist loader means audio will be muxed with the video 52400 return; 52401 } 52402 52403 var tech = settings.tech, 52404 requestOptions = settings.requestOptions, 52405 segmentLoader = settings.segmentLoaders[type]; 52406 playlistLoader.on('loadedmetadata', function () { 52407 var media = playlistLoader.media(); 52408 segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload 52409 // permits, start downloading segments 52410 52411 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 52412 segmentLoader.load(); 52413 } 52414 }); 52415 playlistLoader.on('loadedplaylist', function () { 52416 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 52417 52418 if (!tech.paused()) { 52419 segmentLoader.load(); 52420 } 52421 }); 52422 playlistLoader.on('error', onError[type](type, settings)); 52423 }, 52424 52425 /** 52426 * Setup event listeners for subtitle playlist loader 52427 * 52428 * @param {String} type 52429 * MediaGroup type 52430 * @param {PlaylistLoader|null} playlistLoader 52431 * PlaylistLoader to register listeners on 52432 * @param {Object} settings 52433 * Object containing required information for media groups 52434 * @function setupListeners.SUBTITLES 52435 */ 52436 SUBTITLES: function SUBTITLES(type, playlistLoader, settings) { 52437 var tech = settings.tech, 52438 requestOptions = settings.requestOptions, 52439 segmentLoader = settings.segmentLoaders[type], 52440 mediaType = settings.mediaTypes[type]; 52441 playlistLoader.on('loadedmetadata', function () { 52442 var media = playlistLoader.media(); 52443 segmentLoader.playlist(media, requestOptions); 52444 segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload 52445 // permits, start downloading segments 52446 52447 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 52448 segmentLoader.load(); 52449 } 52450 }); 52451 playlistLoader.on('loadedplaylist', function () { 52452 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 52453 52454 if (!tech.paused()) { 52455 segmentLoader.load(); 52456 } 52457 }); 52458 playlistLoader.on('error', onError[type](type, settings)); 52459 } 52460 }; 52461 var initialize = { 52462 /** 52463 * Setup PlaylistLoaders and AudioTracks for the audio groups 52464 * 52465 * @param {String} type 52466 * MediaGroup type 52467 * @param {Object} settings 52468 * Object containing required information for media groups 52469 * @function initialize.AUDIO 52470 */ 52471 'AUDIO': function AUDIO(type, settings) { 52472 var hls = settings.hls, 52473 sourceType = settings.sourceType, 52474 segmentLoader = settings.segmentLoaders[type], 52475 requestOptions = settings.requestOptions, 52476 mediaGroups = settings.master.mediaGroups, 52477 _settings$mediaTypes$ = settings.mediaTypes[type], 52478 groups = _settings$mediaTypes$.groups, 52479 tracks = _settings$mediaTypes$.tracks, 52480 masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none 52481 52482 if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) { 52483 mediaGroups[type] = { 52484 main: { 52485 "default": { 52486 "default": true 52487 } 52488 } 52489 }; 52490 } 52491 52492 for (var groupId in mediaGroups[type]) { 52493 if (!groups[groupId]) { 52494 groups[groupId] = []; 52495 } // List of playlists that have an AUDIO attribute value matching the current 52496 // group ID 52497 52498 52499 for (var variantLabel in mediaGroups[type][groupId]) { 52500 var properties = mediaGroups[type][groupId][variantLabel]; 52501 var playlistLoader = void 0; 52502 52503 if (properties.resolvedUri) { 52504 playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions); 52505 } else if (properties.playlists && sourceType === 'dash') { 52506 playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader); 52507 } else { 52508 // no resolvedUri means the audio is muxed with the video when using this 52509 // audio track 52510 playlistLoader = null; 52511 } 52512 52513 properties = videojs$1.mergeOptions({ 52514 id: variantLabel, 52515 playlistLoader: playlistLoader 52516 }, properties); 52517 setupListeners[type](type, properties.playlistLoader, settings); 52518 groups[groupId].push(properties); 52519 52520 if (typeof tracks[variantLabel] === 'undefined') { 52521 var track = new videojs$1.AudioTrack({ 52522 id: variantLabel, 52523 kind: audioTrackKind_(properties), 52524 enabled: false, 52525 language: properties.language, 52526 "default": properties["default"], 52527 label: variantLabel 52528 }); 52529 tracks[variantLabel] = track; 52530 } 52531 } 52532 } // setup single error event handler for the segment loader 52533 52534 52535 segmentLoader.on('error', onError[type](type, settings)); 52536 }, 52537 52538 /** 52539 * Setup PlaylistLoaders and TextTracks for the subtitle groups 52540 * 52541 * @param {String} type 52542 * MediaGroup type 52543 * @param {Object} settings 52544 * Object containing required information for media groups 52545 * @function initialize.SUBTITLES 52546 */ 52547 'SUBTITLES': function SUBTITLES(type, settings) { 52548 var tech = settings.tech, 52549 hls = settings.hls, 52550 sourceType = settings.sourceType, 52551 segmentLoader = settings.segmentLoaders[type], 52552 requestOptions = settings.requestOptions, 52553 mediaGroups = settings.master.mediaGroups, 52554 _settings$mediaTypes$2 = settings.mediaTypes[type], 52555 groups = _settings$mediaTypes$2.groups, 52556 tracks = _settings$mediaTypes$2.tracks, 52557 masterPlaylistLoader = settings.masterPlaylistLoader; 52558 52559 for (var groupId in mediaGroups[type]) { 52560 if (!groups[groupId]) { 52561 groups[groupId] = []; 52562 } 52563 52564 for (var variantLabel in mediaGroups[type][groupId]) { 52565 if (mediaGroups[type][groupId][variantLabel].forced) { 52566 // Subtitle playlists with the forced attribute are not selectable in Safari. 52567 // According to Apple's HLS Authoring Specification: 52568 // If content has forced subtitles and regular subtitles in a given language, 52569 // the regular subtitles track in that language MUST contain both the forced 52570 // subtitles and the regular subtitles for that language. 52571 // Because of this requirement and that Safari does not add forced subtitles, 52572 // forced subtitles are skipped here to maintain consistent experience across 52573 // all platforms 52574 continue; 52575 } 52576 52577 var properties = mediaGroups[type][groupId][variantLabel]; 52578 var playlistLoader = void 0; 52579 52580 if (sourceType === 'hls') { 52581 playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions); 52582 } else if (sourceType === 'dash') { 52583 playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader); 52584 } 52585 52586 properties = videojs$1.mergeOptions({ 52587 id: variantLabel, 52588 playlistLoader: playlistLoader 52589 }, properties); 52590 setupListeners[type](type, properties.playlistLoader, settings); 52591 groups[groupId].push(properties); 52592 52593 if (typeof tracks[variantLabel] === 'undefined') { 52594 var track = tech.addRemoteTextTrack({ 52595 id: variantLabel, 52596 kind: 'subtitles', 52597 "default": properties["default"] && properties.autoselect, 52598 language: properties.language, 52599 label: variantLabel 52600 }, false).track; 52601 tracks[variantLabel] = track; 52602 } 52603 } 52604 } // setup single error event handler for the segment loader 52605 52606 52607 segmentLoader.on('error', onError[type](type, settings)); 52608 }, 52609 52610 /** 52611 * Setup TextTracks for the closed-caption groups 52612 * 52613 * @param {String} type 52614 * MediaGroup type 52615 * @param {Object} settings 52616 * Object containing required information for media groups 52617 * @function initialize['CLOSED-CAPTIONS'] 52618 */ 52619 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) { 52620 var tech = settings.tech, 52621 mediaGroups = settings.master.mediaGroups, 52622 _settings$mediaTypes$3 = settings.mediaTypes[type], 52623 groups = _settings$mediaTypes$3.groups, 52624 tracks = _settings$mediaTypes$3.tracks; 52625 52626 for (var groupId in mediaGroups[type]) { 52627 if (!groups[groupId]) { 52628 groups[groupId] = []; 52629 } 52630 52631 for (var variantLabel in mediaGroups[type][groupId]) { 52632 var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that 52633 // doesn't use a CCx INSTREAM-ID 52634 52635 if (!properties.instreamId.match(/CC\d/)) { 52636 continue; 52637 } // No PlaylistLoader is required for Closed-Captions because the captions are 52638 // embedded within the video stream 52639 52640 52641 groups[groupId].push(videojs$1.mergeOptions({ 52642 id: variantLabel 52643 }, properties)); 52644 52645 if (typeof tracks[variantLabel] === 'undefined') { 52646 var track = tech.addRemoteTextTrack({ 52647 id: properties.instreamId, 52648 kind: 'captions', 52649 "default": properties["default"] && properties.autoselect, 52650 language: properties.language, 52651 label: variantLabel 52652 }, false).track; 52653 tracks[variantLabel] = track; 52654 } 52655 } 52656 } 52657 } 52658 }; 52659 /** 52660 * Returns a function used to get the active group of the provided type 52661 * 52662 * @param {String} type 52663 * MediaGroup type 52664 * @param {Object} settings 52665 * Object containing required information for media groups 52666 * @return {Function} 52667 * Function that returns the active media group for the provided type. Takes an 52668 * optional parameter {TextTrack} track. If no track is provided, a list of all 52669 * variants in the group, otherwise the variant corresponding to the provided 52670 * track is returned. 52671 * @function activeGroup 52672 */ 52673 52674 var activeGroup = function activeGroup(type, settings) { 52675 return function (track) { 52676 var masterPlaylistLoader = settings.masterPlaylistLoader, 52677 groups = settings.mediaTypes[type].groups; 52678 var media = masterPlaylistLoader.media(); 52679 52680 if (!media) { 52681 return null; 52682 } 52683 52684 var variants = null; 52685 52686 if (media.attributes[type]) { 52687 variants = groups[media.attributes[type]]; 52688 } 52689 52690 variants = variants || groups.main; 52691 52692 if (typeof track === 'undefined') { 52693 return variants; 52694 } 52695 52696 if (track === null) { 52697 // An active track was specified so a corresponding group is expected. track === null 52698 // means no track is currently active so there is no corresponding group 52699 return null; 52700 } 52701 52702 return variants.filter(function (props) { 52703 return props.id === track.id; 52704 })[0] || null; 52705 }; 52706 }; 52707 52708 var activeTrack = { 52709 /** 52710 * Returns a function used to get the active track of type provided 52711 * 52712 * @param {String} type 52713 * MediaGroup type 52714 * @param {Object} settings 52715 * Object containing required information for media groups 52716 * @return {Function} 52717 * Function that returns the active media track for the provided type. Returns 52718 * null if no track is active 52719 * @function activeTrack.AUDIO 52720 */ 52721 AUDIO: function AUDIO(type, settings) { 52722 return function () { 52723 var tracks = settings.mediaTypes[type].tracks; 52724 52725 for (var id in tracks) { 52726 if (tracks[id].enabled) { 52727 return tracks[id]; 52728 } 52729 } 52730 52731 return null; 52732 }; 52733 }, 52734 52735 /** 52736 * Returns a function used to get the active track of type provided 52737 * 52738 * @param {String} type 52739 * MediaGroup type 52740 * @param {Object} settings 52741 * Object containing required information for media groups 52742 * @return {Function} 52743 * Function that returns the active media track for the provided type. Returns 52744 * null if no track is active 52745 * @function activeTrack.SUBTITLES 52746 */ 52747 SUBTITLES: function SUBTITLES(type, settings) { 52748 return function () { 52749 var tracks = settings.mediaTypes[type].tracks; 52750 52751 for (var id in tracks) { 52752 if (tracks[id].mode === 'showing') { 52753 return tracks[id]; 52754 } 52755 } 52756 52757 return null; 52758 }; 52759 } 52760 }; 52761 /** 52762 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles, 52763 * Closed-Captions) specified in the master manifest. 52764 * 52765 * @param {Object} settings 52766 * Object containing required information for setting up the media groups 52767 * @param {SegmentLoader} settings.segmentLoaders.AUDIO 52768 * Audio segment loader 52769 * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES 52770 * Subtitle segment loader 52771 * @param {SegmentLoader} settings.segmentLoaders.main 52772 * Main segment loader 52773 * @param {Tech} settings.tech 52774 * The tech of the player 52775 * @param {Object} settings.requestOptions 52776 * XHR request options used by the segment loaders 52777 * @param {PlaylistLoader} settings.masterPlaylistLoader 52778 * PlaylistLoader for the master source 52779 * @param {HlsHandler} settings.hls 52780 * HLS SourceHandler 52781 * @param {Object} settings.master 52782 * The parsed master manifest 52783 * @param {Object} settings.mediaTypes 52784 * Object to store the loaders, tracks, and utility methods for each media type 52785 * @param {Function} settings.blacklistCurrentPlaylist 52786 * Blacklists the current rendition and forces a rendition switch. 52787 * @function setupMediaGroups 52788 */ 52789 52790 var setupMediaGroups = function setupMediaGroups(settings) { 52791 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 52792 initialize[type](type, settings); 52793 }); 52794 var mediaTypes = settings.mediaTypes, 52795 masterPlaylistLoader = settings.masterPlaylistLoader, 52796 tech = settings.tech, 52797 hls = settings.hls; // setup active group and track getters and change event handlers 52798 52799 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 52800 mediaTypes[type].activeGroup = activeGroup(type, settings); 52801 mediaTypes[type].activeTrack = activeTrack[type](type, settings); 52802 mediaTypes[type].onGroupChanged = onGroupChanged(type, settings); 52803 mediaTypes[type].onTrackChanged = onTrackChanged(type, settings); 52804 }); // DO NOT enable the default subtitle or caption track. 52805 // DO enable the default audio track 52806 52807 var audioGroup = mediaTypes.AUDIO.activeGroup(); 52808 var groupId = (audioGroup.filter(function (group) { 52809 return group["default"]; 52810 })[0] || audioGroup[0]).id; 52811 mediaTypes.AUDIO.tracks[groupId].enabled = true; 52812 mediaTypes.AUDIO.onTrackChanged(); 52813 masterPlaylistLoader.on('mediachange', function () { 52814 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 52815 return mediaTypes[type].onGroupChanged(); 52816 }); 52817 }); // custom audio track change event handler for usage event 52818 52819 var onAudioTrackChanged = function onAudioTrackChanged() { 52820 mediaTypes.AUDIO.onTrackChanged(); 52821 tech.trigger({ 52822 type: 'usage', 52823 name: 'hls-audio-change' 52824 }); 52825 }; 52826 52827 tech.audioTracks().addEventListener('change', onAudioTrackChanged); 52828 tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 52829 hls.on('dispose', function () { 52830 tech.audioTracks().removeEventListener('change', onAudioTrackChanged); 52831 tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 52832 }); // clear existing audio tracks and add the ones we just created 52833 52834 tech.clearTracks('audio'); 52835 52836 for (var id in mediaTypes.AUDIO.tracks) { 52837 tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]); 52838 } 52839 }; 52840 /** 52841 * Creates skeleton object used to store the loaders, tracks, and utility methods for each 52842 * media type 52843 * 52844 * @return {Object} 52845 * Object to store the loaders, tracks, and utility methods for each media type 52846 * @function createMediaTypes 52847 */ 52848 52849 52850 var createMediaTypes = function createMediaTypes() { 52851 var mediaTypes = {}; 52852 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 52853 mediaTypes[type] = { 52854 groups: {}, 52855 tracks: {}, 52856 activePlaylistLoader: null, 52857 activeGroup: noop$1, 52858 activeTrack: noop$1, 52859 onGroupChanged: noop$1, 52860 onTrackChanged: noop$1 52861 }; 52862 }); 52863 return mediaTypes; 52864 }; 52865 /** 52866 * @file master-playlist-controller.js 52867 */ 52868 52869 52870 var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2; 52871 var Hls = void 0; // SegmentLoader stats that need to have each loader's 52872 // values summed to calculate the final value 52873 52874 var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred']; 52875 52876 var sumLoaderStat = function sumLoaderStat(stat) { 52877 return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat]; 52878 }; 52879 /** 52880 * the master playlist controller controller all interactons 52881 * between playlists and segmentloaders. At this time this mainly 52882 * involves a master playlist and a series of audio playlists 52883 * if they are available 52884 * 52885 * @class MasterPlaylistController 52886 * @extends videojs.EventTarget 52887 */ 52888 52889 52890 var MasterPlaylistController = function (_videojs$EventTarget) { 52891 inherits$2(MasterPlaylistController, _videojs$EventTarget); 52892 52893 function MasterPlaylistController(options) { 52894 classCallCheck$1(this, MasterPlaylistController); 52895 52896 var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this)); 52897 52898 var url = options.url, 52899 handleManifestRedirects = options.handleManifestRedirects, 52900 withCredentials = options.withCredentials, 52901 tech = options.tech, 52902 bandwidth = options.bandwidth, 52903 externHls = options.externHls, 52904 useCueTags = options.useCueTags, 52905 blacklistDuration = options.blacklistDuration, 52906 enableLowInitialPlaylist = options.enableLowInitialPlaylist, 52907 sourceType = options.sourceType, 52908 seekTo = options.seekTo, 52909 cacheEncryptionKeys = options.cacheEncryptionKeys; 52910 52911 if (!url) { 52912 throw new Error('A non-empty playlist URL is required'); 52913 } 52914 52915 Hls = externHls; 52916 _this.withCredentials = withCredentials; 52917 _this.tech_ = tech; 52918 _this.hls_ = tech.hls; 52919 _this.seekTo_ = seekTo; 52920 _this.sourceType_ = sourceType; 52921 _this.useCueTags_ = useCueTags; 52922 _this.blacklistDuration = blacklistDuration; 52923 _this.enableLowInitialPlaylist = enableLowInitialPlaylist; 52924 52925 if (_this.useCueTags_) { 52926 _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues'); 52927 _this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 52928 } 52929 52930 _this.requestOptions_ = { 52931 withCredentials: withCredentials, 52932 handleManifestRedirects: handleManifestRedirects, 52933 timeout: null 52934 }; 52935 _this.mediaTypes_ = createMediaTypes(); 52936 _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player 52937 52938 _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this)); 52939 52940 _this.seekable_ = videojs$1.createTimeRanges(); 52941 52942 _this.hasPlayed_ = function () { 52943 return false; 52944 }; 52945 52946 _this.syncController_ = new SyncController(options); 52947 _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({ 52948 kind: 'metadata', 52949 label: 'segment-metadata' 52950 }, false).track; 52951 _this.decrypter_ = new Decrypter$1(); 52952 _this.inbandTextTracks_ = {}; 52953 var segmentLoaderSettings = { 52954 hls: _this.hls_, 52955 mediaSource: _this.mediaSource, 52956 currentTime: _this.tech_.currentTime.bind(_this.tech_), 52957 seekable: function seekable$$1() { 52958 return _this.seekable(); 52959 }, 52960 seeking: function seeking() { 52961 return _this.tech_.seeking(); 52962 }, 52963 duration: function duration$$1() { 52964 return _this.mediaSource.duration; 52965 }, 52966 hasPlayed: function hasPlayed() { 52967 return _this.hasPlayed_(); 52968 }, 52969 goalBufferLength: function goalBufferLength() { 52970 return _this.goalBufferLength(); 52971 }, 52972 bandwidth: bandwidth, 52973 syncController: _this.syncController_, 52974 decrypter: _this.decrypter_, 52975 sourceType: _this.sourceType_, 52976 inbandTextTracks: _this.inbandTextTracks_, 52977 cacheEncryptionKeys: cacheEncryptionKeys 52978 }; 52979 _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_); 52980 52981 _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders 52982 // combined audio/video or just video when alternate audio track is selected 52983 52984 52985 _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 52986 segmentMetadataTrack: _this.segmentMetadataTrack_, 52987 loaderType: 'main' 52988 }), options); // alternate audio track 52989 52990 _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 52991 loaderType: 'audio' 52992 }), options); 52993 _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 52994 loaderType: 'vtt' 52995 }), options); 52996 52997 _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters 52998 52999 53000 loaderStats.forEach(function (stat) { 53001 _this[stat + '_'] = sumLoaderStat.bind(_this, stat); 53002 }); 53003 _this.logger_ = logger('MPC'); 53004 53005 _this.masterPlaylistLoader_.load(); 53006 53007 return _this; 53008 } 53009 /** 53010 * Register event handlers on the master playlist loader. A helper 53011 * function for construction time. 53012 * 53013 * @private 53014 */ 53015 53016 53017 createClass$1(MasterPlaylistController, [{ 53018 key: 'setupMasterPlaylistLoaderListeners_', 53019 value: function setupMasterPlaylistLoaderListeners_() { 53020 var _this2 = this; 53021 53022 this.masterPlaylistLoader_.on('loadedmetadata', function () { 53023 var media = _this2.masterPlaylistLoader_.media(); 53024 53025 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 53026 // timeout the request. 53027 53028 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 53029 _this2.requestOptions_.timeout = 0; 53030 } else { 53031 _this2.requestOptions_.timeout = requestTimeout; 53032 } // if this isn't a live video and preload permits, start 53033 // downloading segments 53034 53035 53036 if (media.endList && _this2.tech_.preload() !== 'none') { 53037 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 53038 53039 _this2.mainSegmentLoader_.load(); 53040 } 53041 53042 setupMediaGroups({ 53043 sourceType: _this2.sourceType_, 53044 segmentLoaders: { 53045 AUDIO: _this2.audioSegmentLoader_, 53046 SUBTITLES: _this2.subtitleSegmentLoader_, 53047 main: _this2.mainSegmentLoader_ 53048 }, 53049 tech: _this2.tech_, 53050 requestOptions: _this2.requestOptions_, 53051 masterPlaylistLoader: _this2.masterPlaylistLoader_, 53052 hls: _this2.hls_, 53053 master: _this2.master(), 53054 mediaTypes: _this2.mediaTypes_, 53055 blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2) 53056 }); 53057 53058 _this2.triggerPresenceUsage_(_this2.master(), media); 53059 53060 try { 53061 _this2.setupSourceBuffers_(); 53062 } catch (e) { 53063 videojs$1.log.warn('Failed to create SourceBuffers', e); 53064 return _this2.mediaSource.endOfStream('decode'); 53065 } 53066 53067 _this2.setupFirstPlay(); 53068 53069 if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) { 53070 _this2.trigger('selectedinitialmedia'); 53071 } else { 53072 // We must wait for the active audio playlist loader to 53073 // finish setting up before triggering this event so the 53074 // representations API and EME setup is correct 53075 _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () { 53076 _this2.trigger('selectedinitialmedia'); 53077 }); 53078 } 53079 }); 53080 this.masterPlaylistLoader_.on('loadedplaylist', function () { 53081 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 53082 53083 if (!updatedPlaylist) { 53084 // blacklist any variants that are not supported by the browser before selecting 53085 // an initial media as the playlist selectors do not consider browser support 53086 _this2.excludeUnsupportedVariants_(); 53087 53088 var selectedMedia = void 0; 53089 53090 if (_this2.enableLowInitialPlaylist) { 53091 selectedMedia = _this2.selectInitialPlaylist(); 53092 } 53093 53094 if (!selectedMedia) { 53095 selectedMedia = _this2.selectPlaylist(); 53096 } 53097 53098 _this2.initialMedia_ = selectedMedia; 53099 53100 _this2.masterPlaylistLoader_.media(_this2.initialMedia_); 53101 53102 return; 53103 } 53104 53105 if (_this2.useCueTags_) { 53106 _this2.updateAdCues_(updatedPlaylist); 53107 } // TODO: Create a new event on the PlaylistLoader that signals 53108 // that the segments have changed in some way and use that to 53109 // update the SegmentLoader instead of doing it twice here and 53110 // on `mediachange` 53111 53112 53113 _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_); 53114 53115 _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running, 53116 // as it is possible that it was temporarily stopped while waiting for 53117 // a playlist (e.g., in case the playlist errored and we re-requested it). 53118 53119 53120 if (!_this2.tech_.paused()) { 53121 _this2.mainSegmentLoader_.load(); 53122 53123 if (_this2.audioSegmentLoader_) { 53124 _this2.audioSegmentLoader_.load(); 53125 } 53126 } 53127 53128 if (!updatedPlaylist.endList) { 53129 var addSeekableRange = function addSeekableRange() { 53130 var seekable$$1 = _this2.seekable(); 53131 53132 if (seekable$$1.length !== 0) { 53133 _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0)); 53134 } 53135 }; 53136 53137 if (_this2.duration() !== Infinity) { 53138 var onDurationchange = function onDurationchange() { 53139 if (_this2.duration() === Infinity) { 53140 addSeekableRange(); 53141 } else { 53142 _this2.tech_.one('durationchange', onDurationchange); 53143 } 53144 }; 53145 53146 _this2.tech_.one('durationchange', onDurationchange); 53147 } else { 53148 addSeekableRange(); 53149 } 53150 } 53151 }); 53152 this.masterPlaylistLoader_.on('error', function () { 53153 _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error); 53154 }); 53155 this.masterPlaylistLoader_.on('mediachanging', function () { 53156 _this2.mainSegmentLoader_.abort(); 53157 53158 _this2.mainSegmentLoader_.pause(); 53159 }); 53160 this.masterPlaylistLoader_.on('mediachange', function () { 53161 var media = _this2.masterPlaylistLoader_.media(); 53162 53163 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 53164 // timeout the request. 53165 53166 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 53167 _this2.requestOptions_.timeout = 0; 53168 } else { 53169 _this2.requestOptions_.timeout = requestTimeout; 53170 } // TODO: Create a new event on the PlaylistLoader that signals 53171 // that the segments have changed in some way and use that to 53172 // update the SegmentLoader instead of doing it twice here and 53173 // on `loadedplaylist` 53174 53175 53176 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 53177 53178 _this2.mainSegmentLoader_.load(); 53179 53180 _this2.tech_.trigger({ 53181 type: 'mediachange', 53182 bubbles: true 53183 }); 53184 }); 53185 this.masterPlaylistLoader_.on('playlistunchanged', function () { 53186 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 53187 53188 var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist); 53189 53190 if (playlistOutdated) { 53191 // Playlist has stopped updating and we're stuck at its end. Try to 53192 // blacklist it and switch to another playlist in the hope that that 53193 // one is updating (and give the player a chance to re-adjust to the 53194 // safe live point). 53195 _this2.blacklistCurrentPlaylist({ 53196 message: 'Playlist no longer updating.' 53197 }); // useful for monitoring QoS 53198 53199 53200 _this2.tech_.trigger('playliststuck'); 53201 } 53202 }); 53203 this.masterPlaylistLoader_.on('renditiondisabled', function () { 53204 _this2.tech_.trigger({ 53205 type: 'usage', 53206 name: 'hls-rendition-disabled' 53207 }); 53208 }); 53209 this.masterPlaylistLoader_.on('renditionenabled', function () { 53210 _this2.tech_.trigger({ 53211 type: 'usage', 53212 name: 'hls-rendition-enabled' 53213 }); 53214 }); 53215 } 53216 /** 53217 * A helper function for triggerring presence usage events once per source 53218 * 53219 * @private 53220 */ 53221 53222 }, { 53223 key: 'triggerPresenceUsage_', 53224 value: function triggerPresenceUsage_(master, media) { 53225 var mediaGroups = master.mediaGroups || {}; 53226 var defaultDemuxed = true; 53227 var audioGroupKeys = Object.keys(mediaGroups.AUDIO); 53228 53229 for (var mediaGroup in mediaGroups.AUDIO) { 53230 for (var label in mediaGroups.AUDIO[mediaGroup]) { 53231 var properties = mediaGroups.AUDIO[mediaGroup][label]; 53232 53233 if (!properties.uri) { 53234 defaultDemuxed = false; 53235 } 53236 } 53237 } 53238 53239 if (defaultDemuxed) { 53240 this.tech_.trigger({ 53241 type: 'usage', 53242 name: 'hls-demuxed' 53243 }); 53244 } 53245 53246 if (Object.keys(mediaGroups.SUBTITLES).length) { 53247 this.tech_.trigger({ 53248 type: 'usage', 53249 name: 'hls-webvtt' 53250 }); 53251 } 53252 53253 if (Hls.Playlist.isAes(media)) { 53254 this.tech_.trigger({ 53255 type: 'usage', 53256 name: 'hls-aes' 53257 }); 53258 } 53259 53260 if (Hls.Playlist.isFmp4(media)) { 53261 this.tech_.trigger({ 53262 type: 'usage', 53263 name: 'hls-fmp4' 53264 }); 53265 } 53266 53267 if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) { 53268 this.tech_.trigger({ 53269 type: 'usage', 53270 name: 'hls-alternate-audio' 53271 }); 53272 } 53273 53274 if (this.useCueTags_) { 53275 this.tech_.trigger({ 53276 type: 'usage', 53277 name: 'hls-playlist-cue-tags' 53278 }); 53279 } 53280 } 53281 /** 53282 * Register event handlers on the segment loaders. A helper function 53283 * for construction time. 53284 * 53285 * @private 53286 */ 53287 53288 }, { 53289 key: 'setupSegmentLoaderListeners_', 53290 value: function setupSegmentLoaderListeners_() { 53291 var _this3 = this; 53292 53293 this.mainSegmentLoader_.on('bandwidthupdate', function () { 53294 var nextPlaylist = _this3.selectPlaylist(); 53295 53296 var currentPlaylist = _this3.masterPlaylistLoader_.media(); 53297 53298 var buffered = _this3.tech_.buffered(); 53299 53300 var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0; 53301 53302 var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account. 53303 // This is because in LIVE, the player plays 3 segments from the end of the 53304 // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble 53305 // in those segments, a viewer will never experience a rendition upswitch. 53306 53307 53308 if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD 53309 // duration is below the max potential low water line 53310 _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but 53311 nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of 53312 // buffer while loading a higher rendition. 53313 forwardBuffer >= bufferLowWaterLine) { 53314 _this3.masterPlaylistLoader_.media(nextPlaylist); 53315 } 53316 53317 _this3.tech_.trigger('bandwidthupdate'); 53318 }); 53319 this.mainSegmentLoader_.on('progress', function () { 53320 _this3.trigger('progress'); 53321 }); 53322 this.mainSegmentLoader_.on('error', function () { 53323 _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error()); 53324 }); 53325 this.mainSegmentLoader_.on('syncinfoupdate', function () { 53326 _this3.onSyncInfoUpdate_(); 53327 }); 53328 this.mainSegmentLoader_.on('timestampoffset', function () { 53329 _this3.tech_.trigger({ 53330 type: 'usage', 53331 name: 'hls-timestamp-offset' 53332 }); 53333 }); 53334 this.audioSegmentLoader_.on('syncinfoupdate', function () { 53335 _this3.onSyncInfoUpdate_(); 53336 }); 53337 this.mainSegmentLoader_.on('ended', function () { 53338 _this3.onEndOfStream(); 53339 }); 53340 this.mainSegmentLoader_.on('earlyabort', function () { 53341 _this3.blacklistCurrentPlaylist({ 53342 message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.' 53343 }, ABORT_EARLY_BLACKLIST_SECONDS); 53344 }); 53345 this.mainSegmentLoader_.on('reseteverything', function () { 53346 // If playing an MTS stream, a videojs.MediaSource is listening for 53347 // hls-reset to reset caption parsing state in the transmuxer 53348 _this3.tech_.trigger('hls-reset'); 53349 }); 53350 this.mainSegmentLoader_.on('segmenttimemapping', function (event) { 53351 // If playing an MTS stream in html, a videojs.MediaSource is listening for 53352 // hls-segment-time-mapping update its internal mapping of stream to display time 53353 _this3.tech_.trigger({ 53354 type: 'hls-segment-time-mapping', 53355 mapping: event.mapping 53356 }); 53357 }); 53358 this.audioSegmentLoader_.on('ended', function () { 53359 _this3.onEndOfStream(); 53360 }); 53361 } 53362 }, { 53363 key: 'mediaSecondsLoaded_', 53364 value: function mediaSecondsLoaded_() { 53365 return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded); 53366 } 53367 /** 53368 * Call load on our SegmentLoaders 53369 */ 53370 53371 }, { 53372 key: 'load', 53373 value: function load() { 53374 this.mainSegmentLoader_.load(); 53375 53376 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 53377 this.audioSegmentLoader_.load(); 53378 } 53379 53380 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 53381 this.subtitleSegmentLoader_.load(); 53382 } 53383 } 53384 /** 53385 * Re-tune playback quality level for the current player 53386 * conditions without performing destructive actions, like 53387 * removing already buffered content 53388 * 53389 * @private 53390 */ 53391 53392 }, { 53393 key: 'smoothQualityChange_', 53394 value: function smoothQualityChange_() { 53395 var media = this.selectPlaylist(); 53396 53397 if (media !== this.masterPlaylistLoader_.media()) { 53398 this.masterPlaylistLoader_.media(media); 53399 this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes 53400 } 53401 } 53402 /** 53403 * Re-tune playback quality level for the current player 53404 * conditions. This method will perform destructive actions like removing 53405 * already buffered content in order to readjust the currently active 53406 * playlist quickly. This is good for manual quality changes 53407 * 53408 * @private 53409 */ 53410 53411 }, { 53412 key: 'fastQualityChange_', 53413 value: function fastQualityChange_() { 53414 var _this4 = this; 53415 53416 var media = this.selectPlaylist(); 53417 53418 if (media === this.masterPlaylistLoader_.media()) { 53419 return; 53420 } 53421 53422 this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give 53423 // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds 53424 // ahead is roughly the minimum that will accomplish this across a variety of content 53425 // in IE and Edge, but seeking in place is sufficient on all other browsers) 53426 // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/ 53427 // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904 53428 53429 this.mainSegmentLoader_.resetEverything(function () { 53430 // Since this is not a typical seek, we avoid the seekTo method which can cause segments 53431 // from the previously enabled rendition to load before the new playlist has finished loading 53432 if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) { 53433 _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04); 53434 } else { 53435 _this4.tech_.setCurrentTime(_this4.tech_.currentTime()); 53436 } 53437 }); // don't need to reset audio as it is reset when media changes 53438 } 53439 /** 53440 * Begin playback. 53441 */ 53442 53443 }, { 53444 key: 'play', 53445 value: function play() { 53446 if (this.setupFirstPlay()) { 53447 return; 53448 } 53449 53450 if (this.tech_.ended()) { 53451 this.seekTo_(0); 53452 } 53453 53454 if (this.hasPlayed_()) { 53455 this.load(); 53456 } 53457 53458 var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window, 53459 // seek forward to the live point 53460 53461 if (this.tech_.duration() === Infinity) { 53462 if (this.tech_.currentTime() < seekable$$1.start(0)) { 53463 return this.seekTo_(seekable$$1.end(seekable$$1.length - 1)); 53464 } 53465 } 53466 } 53467 /** 53468 * Seek to the latest media position if this is a live video and the 53469 * player and video are loaded and initialized. 53470 */ 53471 53472 }, { 53473 key: 'setupFirstPlay', 53474 value: function setupFirstPlay() { 53475 var _this5 = this; 53476 53477 var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play 53478 // If 1) there is no active media 53479 // 2) the player is paused 53480 // 3) the first play has already been setup 53481 // then exit early 53482 53483 if (!media || this.tech_.paused() || this.hasPlayed_()) { 53484 return false; 53485 } // when the video is a live stream 53486 53487 53488 if (!media.endList) { 53489 var seekable$$1 = this.seekable(); 53490 53491 if (!seekable$$1.length) { 53492 // without a seekable range, the player cannot seek to begin buffering at the live 53493 // point 53494 return false; 53495 } 53496 53497 if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) { 53498 // IE11 throws an InvalidStateError if you try to set currentTime while the 53499 // readyState is 0, so it must be delayed until the tech fires loadedmetadata. 53500 this.tech_.one('loadedmetadata', function () { 53501 _this5.trigger('firstplay'); 53502 53503 _this5.seekTo_(seekable$$1.end(0)); 53504 53505 _this5.hasPlayed_ = function () { 53506 return true; 53507 }; 53508 }); 53509 return false; 53510 } // trigger firstplay to inform the source handler to ignore the next seek event 53511 53512 53513 this.trigger('firstplay'); // seek to the live point 53514 53515 this.seekTo_(seekable$$1.end(0)); 53516 } 53517 53518 this.hasPlayed_ = function () { 53519 return true; 53520 }; // we can begin loading now that everything is ready 53521 53522 53523 this.load(); 53524 return true; 53525 } 53526 /** 53527 * handle the sourceopen event on the MediaSource 53528 * 53529 * @private 53530 */ 53531 53532 }, { 53533 key: 'handleSourceOpen_', 53534 value: function handleSourceOpen_() { 53535 // Only attempt to create the source buffer if none already exist. 53536 // handleSourceOpen is also called when we are "re-opening" a source buffer 53537 // after `endOfStream` has been called (in response to a seek for instance) 53538 try { 53539 this.setupSourceBuffers_(); 53540 } catch (e) { 53541 videojs$1.log.warn('Failed to create Source Buffers', e); 53542 return this.mediaSource.endOfStream('decode'); 53543 } // if autoplay is enabled, begin playback. This is duplicative of 53544 // code in video.js but is required because play() must be invoked 53545 // *after* the media source has opened. 53546 53547 53548 if (this.tech_.autoplay()) { 53549 var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request 53550 // on browsers which return a promise 53551 53552 if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') { 53553 playPromise.then(null, function (e) {}); 53554 } 53555 } 53556 53557 this.trigger('sourceopen'); 53558 } 53559 /** 53560 * Calls endOfStream on the media source when all active stream types have called 53561 * endOfStream 53562 * 53563 * @param {string} streamType 53564 * Stream type of the segment loader that called endOfStream 53565 * @private 53566 */ 53567 53568 }, { 53569 key: 'onEndOfStream', 53570 value: function onEndOfStream() { 53571 var isEndOfStream = this.mainSegmentLoader_.ended_; 53572 53573 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 53574 // if the audio playlist loader exists, then alternate audio is active 53575 if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) { 53576 // if we do not know if the main segment loader contains video yet or if we 53577 // definitively know the main segment loader contains video, then we need to wait 53578 // for both main and audio segment loaders to call endOfStream 53579 isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_; 53580 } else { 53581 // otherwise just rely on the audio loader 53582 isEndOfStream = this.audioSegmentLoader_.ended_; 53583 } 53584 } 53585 53586 if (!isEndOfStream) { 53587 return; 53588 } 53589 53590 this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception, 53591 // even when the media source is in a valid state. 53592 53593 try { 53594 this.mediaSource.endOfStream(); 53595 } catch (e) { 53596 videojs$1.log.warn('Failed to call media source endOfStream', e); 53597 } 53598 } 53599 /** 53600 * Check if a playlist has stopped being updated 53601 * @param {Object} playlist the media playlist object 53602 * @return {boolean} whether the playlist has stopped being updated or not 53603 */ 53604 53605 }, { 53606 key: 'stuckAtPlaylistEnd_', 53607 value: function stuckAtPlaylistEnd_(playlist) { 53608 var seekable$$1 = this.seekable(); 53609 53610 if (!seekable$$1.length) { 53611 // playlist doesn't have enough information to determine whether we are stuck 53612 return false; 53613 } 53614 53615 var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration); 53616 53617 if (expired === null) { 53618 return false; 53619 } // does not use the safe live end to calculate playlist end, since we 53620 // don't want to say we are stuck while there is still content 53621 53622 53623 var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired); 53624 var currentTime = this.tech_.currentTime(); 53625 var buffered = this.tech_.buffered(); 53626 53627 if (!buffered.length) { 53628 // return true if the playhead reached the absolute end of the playlist 53629 return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA; 53630 } 53631 53632 var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute 53633 // end of playlist 53634 53635 return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA; 53636 } 53637 /** 53638 * Blacklists a playlist when an error occurs for a set amount of time 53639 * making it unavailable for selection by the rendition selection algorithm 53640 * and then forces a new playlist (rendition) selection. 53641 * 53642 * @param {Object=} error an optional error that may include the playlist 53643 * to blacklist 53644 * @param {Number=} blacklistDuration an optional number of seconds to blacklist the 53645 * playlist 53646 */ 53647 53648 }, { 53649 key: 'blacklistCurrentPlaylist', 53650 value: function blacklistCurrentPlaylist() { 53651 var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 53652 var blacklistDuration = arguments[1]; 53653 var currentPlaylist = void 0; 53654 var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain 53655 // the playlist we were trying to load (but failed) and that should be 53656 // blacklisted instead of the currently selected playlist which is likely 53657 // out-of-date in this scenario 53658 53659 currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 53660 blacklistDuration = blacklistDuration || error.blacklistDuration || this.blackl
53660istDuration; // If there is no current playlist, then an error occurred while we were 53661 // trying to load the master OR while we were disposing of the tech 53662 53663 if (!currentPlaylist) { 53664 this.error = error; 53665 53666 try { 53667 return this.mediaSource.endOfStream('network'); 53668 } catch (e) { 53669 return this.trigger('error'); 53670 } 53671 } 53672 53673 var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1; 53674 var playlists = this.masterPlaylistLoader_.master.playlists; 53675 53676 if (playlists.length === 1) { 53677 // Never blacklisting this playlist because it's the only playlist 53678 videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the only playlist.'); 53679 this.tech_.trigger('retryplaylist'); 53680 return this.masterPlaylistLoader_.load(isFinalRendition); 53681 } 53682 53683 if (isFinalRendition) { 53684 // Since we're on the final non-blacklisted playlist, and we're about to blacklist 53685 // it, instead of erring the player or retrying this playlist, clear out the current 53686 // blacklist. This allows other playlists to be attempted in case any have been 53687 // fixed. 53688 videojs$1.log.warn('Removing all playlists from the blacklist because the last ' + 'rendition is about to be blacklisted.');
vendor: 11,849 bytes, lines 53689-54031
53689 playlists.forEach(function (playlist) { 53690 if (playlist.excludeUntil !== Infinity) { 53691 delete playlist.excludeUntil; 53692 } 53693 }); // Technically we are retrying a playlist, in that we are simply retrying a previous 53694 // playlist. This is needed for users relying on the retryplaylist event to catch a 53695 // case where the player might be stuck and looping through "dead" playlists. 53696 53697 this.tech_.trigger('retryplaylist'); 53698 } // Blacklist this playlist 53699 53700 53701 currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000; 53702 this.tech_.trigger('blacklistplaylist'); 53703 this.tech_.trigger({ 53704 type: 'usage', 53705 name: 'hls-rendition-blacklisted' 53706 }); // Select a new playlist 53707 53708 nextPlaylist = this.selectPlaylist(); 53709 videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.'); 53710 return this.masterPlaylistLoader_.media(nextPlaylist, isFinalRendition); 53711 } 53712 /** 53713 * Pause all segment loaders 53714 */ 53715 53716 }, { 53717 key: 'pauseLoading', 53718 value: function pauseLoading() { 53719 this.mainSegmentLoader_.pause(); 53720 53721 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 53722 this.audioSegmentLoader_.pause(); 53723 } 53724 53725 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 53726 this.subtitleSegmentLoader_.pause(); 53727 } 53728 } 53729 /** 53730 * set the current time on all segment loaders 53731 * 53732 * @param {TimeRange} currentTime the current time to set 53733 * @return {TimeRange} the current time 53734 */ 53735 53736 }, { 53737 key: 'setCurrentTime', 53738 value: function setCurrentTime(currentTime) { 53739 var buffered = findRange(this.tech_.buffered(), currentTime); 53740 53741 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 53742 // return immediately if the metadata is not ready yet 53743 return 0; 53744 } // it's clearly an edge-case but don't thrown an error if asked to 53745 // seek within an empty playlist 53746 53747 53748 if (!this.masterPlaylistLoader_.media().segments) { 53749 return 0; 53750 } // In flash playback, the segment loaders should be reset on every seek, even 53751 // in buffer seeks. If the seek location is already buffered, continue buffering as 53752 // usual 53753 // TODO: redo this comment 53754 53755 53756 if (buffered && buffered.length) { 53757 return currentTime; 53758 } // cancel outstanding requests so we begin buffering at the new 53759 // location 53760 53761 53762 this.mainSegmentLoader_.resetEverything(); 53763 this.mainSegmentLoader_.abort(); 53764 53765 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 53766 this.audioSegmentLoader_.resetEverything(); 53767 this.audioSegmentLoader_.abort(); 53768 } 53769 53770 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 53771 this.subtitleSegmentLoader_.resetEverything(); 53772 this.subtitleSegmentLoader_.abort(); 53773 } // start segment loader loading in case they are paused 53774 53775 53776 this.load(); 53777 } 53778 /** 53779 * get the current duration 53780 * 53781 * @return {TimeRange} the duration 53782 */ 53783 53784 }, { 53785 key: 'duration', 53786 value: function duration$$1() { 53787 if (!this.masterPlaylistLoader_) { 53788 return 0; 53789 } 53790 53791 if (this.mediaSource) { 53792 return this.mediaSource.duration; 53793 } 53794 53795 return Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 53796 } 53797 /** 53798 * check the seekable range 53799 * 53800 * @return {TimeRange} the seekable range 53801 */ 53802 53803 }, { 53804 key: 'seekable', 53805 value: function seekable$$1() { 53806 return this.seekable_; 53807 } 53808 }, { 53809 key: 'onSyncInfoUpdate_', 53810 value: function onSyncInfoUpdate_() { 53811 var mainSeekable = void 0; 53812 var audioSeekable = void 0; 53813 53814 if (!this.masterPlaylistLoader_) { 53815 return; 53816 } 53817 53818 var media = this.masterPlaylistLoader_.media(); 53819 53820 if (!media) { 53821 return; 53822 } 53823 53824 var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 53825 53826 if (expired === null) { 53827 // not enough information to update seekable 53828 return; 53829 } 53830 53831 mainSeekable = Hls.Playlist.seekable(media, expired); 53832 53833 if (mainSeekable.length === 0) { 53834 return; 53835 } 53836 53837 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 53838 media = this.mediaTypes_.AUDIO.activePlaylistLoader.media(); 53839 expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 53840 53841 if (expired === null) { 53842 return; 53843 } 53844 53845 audioSeekable = Hls.Playlist.seekable(media, expired); 53846 53847 if (audioSeekable.length === 0) { 53848 return; 53849 } 53850 } 53851 53852 var oldEnd = void 0; 53853 var oldStart = void 0; 53854 53855 if (this.seekable_ && this.seekable_.length) { 53856 oldEnd = this.seekable_.end(0); 53857 oldStart = this.seekable_.start(0); 53858 } 53859 53860 if (!audioSeekable) { 53861 // seekable has been calculated based on buffering video data so it 53862 // can be returned directly 53863 this.seekable_ = mainSeekable; 53864 } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) { 53865 // seekables are pretty far off, rely on main 53866 this.seekable_ = mainSeekable; 53867 } else { 53868 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)]]); 53869 } // seekable is the same as last time 53870 53871 53872 if (this.seekable_ && this.seekable_.length) { 53873 if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) { 53874 return; 53875 } 53876 } 53877 53878 this.logger_('seekable updated [' + printableRange(this.seekable_) + ']'); 53879 this.tech_.trigger('seekablechanged'); 53880 } 53881 /** 53882 * Update the player duration 53883 */ 53884 53885 }, { 53886 key: 'updateDuration', 53887 value: function updateDuration() { 53888 var _this6 = this; 53889 53890 var oldDuration = this.mediaSource.duration; 53891 var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 53892 var buffered = this.tech_.buffered(); 53893 53894 var setDuration = function setDuration() { 53895 // on firefox setting the duration may sometimes cause an exception 53896 // even if the media source is open and source buffers are not 53897 // updating, something about the media source being in an invalid state. 53898 _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration); 53899 53900 try { 53901 _this6.mediaSource.duration = newDuration; 53902 } catch (e) { 53903 videojs$1.log.warn('Failed to set media source duration', e); 53904 } 53905 53906 _this6.tech_.trigger('durationchange'); 53907 53908 _this6.mediaSource.removeEventListener('sourceopen', setDuration); 53909 }; 53910 53911 if (buffered.length > 0) { 53912 newDuration = Math.max(newDuration, buffered.end(buffered.length - 1)); 53913 } // if the duration has changed, invalidate the cached value 53914 53915 53916 if (oldDuration !== newDuration) { 53917 // update the duration 53918 if (this.mediaSource.readyState !== 'open') { 53919 this.mediaSource.addEventListener('sourceopen', setDuration); 53920 } else { 53921 setDuration(); 53922 } 53923 } 53924 } 53925 /** 53926 * dispose of the MasterPlaylistController and everything 53927 * that it controls 53928 */ 53929 53930 }, { 53931 key: 'dispose', 53932 value: function dispose() { 53933 var _this7 = this; 53934 53935 this.decrypter_.terminate(); 53936 this.masterPlaylistLoader_.dispose(); 53937 this.mainSegmentLoader_.dispose(); 53938 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 53939 var groups = _this7.mediaTypes_[type].groups; 53940 53941 for (var id in groups) { 53942 groups[id].forEach(function (group) { 53943 if (group.playlistLoader) { 53944 group.playlistLoader.dispose(); 53945 } 53946 }); 53947 } 53948 }); 53949 this.audioSegmentLoader_.dispose(); 53950 this.subtitleSegmentLoader_.dispose(); 53951 } 53952 /** 53953 * return the master playlist object if we have one 53954 * 53955 * @return {Object} the master playlist object that we parsed 53956 */ 53957 53958 }, { 53959 key: 'master', 53960 value: function master() { 53961 return this.masterPlaylistLoader_.master; 53962 } 53963 /** 53964 * return the currently selected playlist 53965 * 53966 * @return {Object} the currently selected playlist object that we parsed 53967 */ 53968 53969 }, { 53970 key: 'media', 53971 value: function media() { 53972 // playlist loader will not return media if it has not been fully loaded 53973 return this.masterPlaylistLoader_.media() || this.initialMedia_; 53974 } 53975 /** 53976 * setup our internal source buffers on our segment Loaders 53977 * 53978 * @private 53979 */ 53980 53981 }, { 53982 key: 'setupSourceBuffers_', 53983 value: function setupSourceBuffers_() { 53984 var media = this.masterPlaylistLoader_.media(); 53985 var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is 53986 // attached 53987 53988 if (!media || this.mediaSource.readyState !== 'open') { 53989 return; 53990 } 53991 53992 mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media); 53993 53994 if (mimeTypes.length < 1) { 53995 this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri; 53996 return this.mediaSource.endOfStream('decode'); 53997 } 53998 53999 this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist 54000 // selection 54001 54002 this.excludeIncompatibleVariants_(media); 54003 } 54004 }, { 54005 key: 'configureLoaderMimeTypes_', 54006 value: function configureLoaderMimeTypes_(mimeTypes) { 54007 // If the content is demuxed, we can't start appending segments to a source buffer 54008 // until both source buffers are set up, or else the browser may not let us add the 54009 // second source buffer (it will assume we are playing either audio only or video 54010 // only). 54011 var sourceBufferEmitter = // If there is more than one mime type 54012 mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio 54013 mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio 54014 // only with alternate audio) 54015 mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer 54016 new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only, 54017 // video only, or muxed content. 54018 null; 54019 this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter); 54020 54021 if (mimeTypes[1]) { 54022 this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter); 54023 } 54024 } 54025 /** 54026 * Blacklists playlists with codecs that are unsupported by the browser. 54027 */ 54028 54029 }, { 54030 key: 'excludeUnsupportedVariants_', 54031 value: function excludeUnsupportedVariants_() {
54032 this.master().playlists.forEach(function (variant) { 54033 if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) { 54034 variant.excludeUntil = Infinity; 54035 } 54036 }); 54037 } 54038 /** 54039 * Blacklist playlists that are known to be codec or 54040 * stream-incompatible with the SourceBuffer configuration. For 54041 * instance, Media Source Extensions would cause the video element to 54042 * stall waiting for video data if you switched from a variant with 54043 * video and audio to an audio-only one. 54044 * 54045 * @param {Object} media a media playlist compatible with the current 54046 * set of SourceBuffers. Variants in the current master playlist that 54047 * do not appear to have compatible codec or stream configurations 54048 * will be excluded from the default playlist selection algorithm 54049 * indefinitely. 54050 * @private 54051 */ 54052 54053 }, { 54054 key: 'excludeIncompatibleVariants_', 54055 value: function excludeIncompatibleVariants_(media) { 54056 var codecCount = 2; 54057 var videoCodec = null; 54058 var codecs = void 0; 54059 54060 if (media.attributes.CODECS) { 54061 codecs = parseCodecs(media.attributes.CODECS); 54062 videoCodec = codecs.videoCodec; 54063 codecCount = codecs.codecCount; 54064 } 54065
vendor: 28,186 bytes, lines 54066-54840
54066 this.master().playlists.forEach(function (variant) { 54067 var variantCodecs = { 54068 codecCount: 2, 54069 videoCodec: null 54070 }; 54071 54072 if (variant.attributes.CODECS) { 54073 variantCodecs = parseCodecs(variant.attributes.CODECS); 54074 } // if the streams differ in the presence or absence of audio or 54075 // video, they are incompatible 54076 54077 54078 if (variantCodecs.codecCount !== codecCount) { 54079 variant.excludeUntil = Infinity; 54080 } // if h.264 is specified on the current playlist, some flavor of 54081 // it must be specified on all compatible variants 54082 54083 54084 if (variantCodecs.videoCodec !== videoCodec) { 54085 variant.excludeUntil = Infinity; 54086 } 54087 }); 54088 } 54089 }, { 54090 key: 'updateAdCues_', 54091 value: function updateAdCues_(media) { 54092 var offset = 0; 54093 var seekable$$1 = this.seekable(); 54094 54095 if (seekable$$1.length) { 54096 offset = seekable$$1.start(0); 54097 } 54098 54099 updateAdCues(media, this.cueTagsTrack_, offset); 54100 } 54101 /** 54102 * Calculates the desired forward buffer length based on current time 54103 * 54104 * @return {Number} Desired forward buffer length in seconds 54105 */ 54106 54107 }, { 54108 key: 'goalBufferLength', 54109 value: function goalBufferLength() { 54110 var currentTime = this.tech_.currentTime(); 54111 var initial = Config.GOAL_BUFFER_LENGTH; 54112 var rate = Config.GOAL_BUFFER_LENGTH_RATE; 54113 var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH); 54114 return Math.min(initial + currentTime * rate, max); 54115 } 54116 /** 54117 * Calculates the desired buffer low water line based on current time 54118 * 54119 * @return {Number} Desired buffer low water line in seconds 54120 */ 54121 54122 }, { 54123 key: 'bufferLowWaterLine', 54124 value: function bufferLowWaterLine() { 54125 var currentTime = this.tech_.currentTime(); 54126 var initial = Config.BUFFER_LOW_WATER_LINE; 54127 var rate = Config.BUFFER_LOW_WATER_LINE_RATE; 54128 var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE); 54129 return Math.min(initial + currentTime * rate, max); 54130 } 54131 }]); 54132 return MasterPlaylistController; 54133 }(videojs$1.EventTarget); 54134 /** 54135 * Returns a function that acts as the Enable/disable playlist function. 54136 * 54137 * @param {PlaylistLoader} loader - The master playlist loader 54138 * @param {String} playlistUri - uri of the playlist 54139 * @param {Function} changePlaylistFn - A function to be called after a 54140 * playlist's enabled-state has been changed. Will NOT be called if a 54141 * playlist's enabled-state is unchanged 54142 * @param {Boolean=} enable - Value to set the playlist enabled-state to 54143 * or if undefined returns the current enabled-state for the playlist 54144 * @return {Function} Function for setting/getting enabled 54145 */ 54146 54147 54148 var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) { 54149 return function (enable) { 54150 var playlist = loader.master.playlists[playlistUri]; 54151 var incompatible = isIncompatible(playlist); 54152 var currentlyEnabled = isEnabled(playlist); 54153 54154 if (typeof enable === 'undefined') { 54155 return currentlyEnabled; 54156 } 54157 54158 if (enable) { 54159 delete playlist.disabled; 54160 } else { 54161 playlist.disabled = true; 54162 } 54163 54164 if (enable !== currentlyEnabled && !incompatible) { 54165 // Ensure the outside world knows about our changes 54166 changePlaylistFn(); 54167 54168 if (enable) { 54169 loader.trigger('renditionenabled'); 54170 } else { 54171 loader.trigger('renditiondisabled'); 54172 } 54173 } 54174 54175 return enable; 54176 }; 54177 }; 54178 /** 54179 * The representation object encapsulates the publicly visible information 54180 * in a media playlist along with a setter/getter-type function (enabled) 54181 * for changing the enabled-state of a particular playlist entry 54182 * 54183 * @class Representation 54184 */ 54185 54186 54187 var Representation = function Representation(hlsHandler, playlist, id) { 54188 classCallCheck$1(this, Representation); 54189 var mpc = hlsHandler.masterPlaylistController_, 54190 smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function 54191 54192 var changeType = smoothQualityChange ? 'smooth' : 'fast'; 54193 var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional 54194 54195 if (playlist.attributes.RESOLUTION) { 54196 var resolution = playlist.attributes.RESOLUTION; 54197 this.width = resolution.width; 54198 this.height = resolution.height; 54199 } 54200 54201 this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist 54202 // within the master playlist 54203 54204 this.id = id; // Partially-apply the enableFunction to create a playlist- 54205 // specific variant 54206 54207 this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction); 54208 }; 54209 /** 54210 * A mixin function that adds the `representations` api to an instance 54211 * of the HlsHandler class 54212 * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the 54213 * representation API into 54214 */ 54215 54216 54217 var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) { 54218 var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance 54219 54220 hlsHandler.representations = function () { 54221 return playlists.master.playlists.filter(function (media) { 54222 return !isIncompatible(media); 54223 }).map(function (e, i) { 54224 return new Representation(hlsHandler, e, e.uri); 54225 }); 54226 }; 54227 }; 54228 /** 54229 * @file playback-watcher.js 54230 * 54231 * Playback starts, and now my watch begins. It shall not end until my death. I shall 54232 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns 54233 * and win no glory. I shall live and die at my post. I am the corrector of the underflow. 54234 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge 54235 * my life and honor to the Playback Watch, for this Player and all the Players to come. 54236 */ 54237 // Set of events that reset the playback-watcher time check logic and clear the timeout 54238 54239 54240 var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 54241 /** 54242 * @class PlaybackWatcher 54243 */ 54244 54245 var PlaybackWatcher = function () { 54246 /** 54247 * Represents an PlaybackWatcher object. 54248 * @constructor 54249 * @param {object} options an object that includes the tech and settings 54250 */ 54251 function PlaybackWatcher(options) { 54252 var _this = this; 54253 54254 classCallCheck$1(this, PlaybackWatcher); 54255 this.tech_ = options.tech; 54256 this.seekable = options.seekable; 54257 this.seekTo = options.seekTo; 54258 this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow; 54259 this.media = options.media; 54260 this.consecutiveUpdates = 0; 54261 this.lastRecordedTime = null; 54262 this.timer_ = null; 54263 this.checkCurrentTimeTimeout_ = null; 54264 this.logger_ = logger('PlaybackWatcher'); 54265 this.logger_('initialize'); 54266 54267 var canPlayHandler = function canPlayHandler() { 54268 return _this.monitorCurrentTime_(); 54269 }; 54270 54271 var waitingHandler = function waitingHandler() { 54272 return _this.techWaiting_(); 54273 }; 54274 54275 var cancelTimerHandler = function cancelTimerHandler() { 54276 return _this.cancelTimer_(); 54277 }; 54278 54279 var fixesBadSeeksHandler = function fixesBadSeeksHandler() { 54280 return _this.fixesBadSeeks_(); 54281 }; 54282 54283 this.tech_.on('seekablechanged', fixesBadSeeksHandler); 54284 this.tech_.on('waiting', waitingHandler); 54285 this.tech_.on(timerCancelEvents, cancelTimerHandler); 54286 this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events 54287 54288 this.dispose = function () { 54289 _this.logger_('dispose'); 54290 54291 _this.tech_.off('seekablechanged', fixesBadSeeksHandler); 54292 54293 _this.tech_.off('waiting', waitingHandler); 54294 54295 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 54296 54297 _this.tech_.off('canplay', canPlayHandler); 54298 54299 if (_this.checkCurrentTimeTimeout_) { 54300 window$1.clearTimeout(_this.checkCurrentTimeTimeout_); 54301 } 54302 54303 _this.cancelTimer_(); 54304 }; 54305 } 54306 /** 54307 * Periodically check current time to see if playback stopped 54308 * 54309 * @private 54310 */ 54311 54312 54313 createClass$1(PlaybackWatcher, [{ 54314 key: 'monitorCurrentTime_', 54315 value: function monitorCurrentTime_() { 54316 this.checkCurrentTime_(); 54317 54318 if (this.checkCurrentTimeTimeout_) { 54319 window$1.clearTimeout(this.checkCurrentTimeTimeout_); 54320 } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 54321 54322 54323 this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250); 54324 } 54325 /** 54326 * The purpose of this function is to emulate the "waiting" event on 54327 * browsers that do not emit it when they are waiting for more 54328 * data to continue playback 54329 * 54330 * @private 54331 */ 54332 54333 }, { 54334 key: 'checkCurrentTime_', 54335 value: function checkCurrentTime_() { 54336 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 54337 this.consecutiveUpdates = 0; 54338 this.lastRecordedTime = this.tech_.currentTime(); 54339 return; 54340 } 54341 54342 if (this.tech_.paused() || this.tech_.seeking()) { 54343 return; 54344 } 54345 54346 var currentTime = this.tech_.currentTime(); 54347 var buffered = this.tech_.buffered(); 54348 54349 if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) { 54350 // If current time is at the end of the final buffered region, then any playback 54351 // stall is most likely caused by buffering in a low bandwidth environment. The tech 54352 // should fire a `waiting` event in this scenario, but due to browser and tech 54353 // inconsistencies. Calling `techWaiting_` here allows us to simulate 54354 // responding to a native `waiting` event when the tech fails to emit one. 54355 return this.techWaiting_(); 54356 } 54357 54358 if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) { 54359 this.consecutiveUpdates++; 54360 this.waiting_(); 54361 } else if (currentTime === this.lastRecordedTime) { 54362 this.consecutiveUpdates++; 54363 } else { 54364 this.consecutiveUpdates = 0; 54365 this.lastRecordedTime = currentTime; 54366 } 54367 } 54368 /** 54369 * Cancels any pending timers and resets the 'timeupdate' mechanism 54370 * designed to detect that we are stalled 54371 * 54372 * @private 54373 */ 54374 54375 }, { 54376 key: 'cancelTimer_', 54377 value: function cancelTimer_() { 54378 this.consecutiveUpdates = 0; 54379 54380 if (this.timer_) { 54381 this.logger_('cancelTimer_'); 54382 clearTimeout(this.timer_); 54383 } 54384 54385 this.timer_ = null; 54386 } 54387 /** 54388 * Fixes situations where there's a bad seek 54389 * 54390 * @return {Boolean} whether an action was taken to fix the seek 54391 * @private 54392 */ 54393 54394 }, { 54395 key: 'fixesBadSeeks_', 54396 value: function fixesBadSeeks_() { 54397 var seeking = this.tech_.seeking(); 54398 54399 if (!seeking) { 54400 return false; 54401 } 54402 54403 var seekable = this.seekable(); 54404 var currentTime = this.tech_.currentTime(); 54405 var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow); 54406 var seekTo = void 0; 54407 54408 if (isAfterSeekableRange) { 54409 var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting) 54410 54411 seekTo = seekableEnd; 54412 } 54413 54414 if (this.beforeSeekableWindow_(seekable, currentTime)) { 54415 var seekableStart = seekable.start(0); // sync to the beginning of the live window 54416 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 54417 54418 seekTo = seekableStart + SAFE_TIME_DELTA; 54419 } 54420 54421 if (typeof seekTo !== 'undefined') { 54422 this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.')); 54423 this.seekTo(seekTo); 54424 return true; 54425 } 54426 54427 return false; 54428 } 54429 /** 54430 * Handler for situations when we determine the player is waiting. 54431 * 54432 * @private 54433 */ 54434 54435 }, { 54436 key: 'waiting_', 54437 value: function waiting_() { 54438 if (this.techWaiting_()) { 54439 return; 54440 } // All tech waiting checks failed. Use last resort correction 54441 54442 54443 var currentTime = this.tech_.currentTime(); 54444 var buffered = this.tech_.buffered(); 54445 var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered 54446 // region with no indication that anything is amiss (seen in Firefox). Seeking to 54447 // currentTime is usually enough to kickstart the player. This checks that the player 54448 // is currently within a buffered region before attempting a corrective seek. 54449 // Chrome does not appear to continue `timeupdate` events after a `waiting` event 54450 // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also 54451 // make sure there is ~3 seconds of forward buffer before taking any corrective action 54452 // to avoid triggering an `unknownwaiting` event when the network is slow. 54453 54454 if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { 54455 this.cancelTimer_(); 54456 this.seekTo(currentTime); 54457 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 54458 54459 this.tech_.trigger({ 54460 type: 'usage', 54461 name: 'hls-unknown-waiting' 54462 }); 54463 return; 54464 } 54465 } 54466 /** 54467 * Handler for situations when the tech fires a `waiting` event 54468 * 54469 * @return {Boolean} 54470 * True if an action (or none) was needed to correct the waiting. False if no 54471 * checks passed 54472 * @private 54473 */ 54474 54475 }, { 54476 key: 'techWaiting_', 54477 value: function techWaiting_() { 54478 var seekable = this.seekable(); 54479 var currentTime = this.tech_.currentTime(); 54480 54481 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 54482 // Tech is seeking or bad seek fixed, no action needed 54483 return true; 54484 } 54485 54486 if (this.tech_.seeking() || this.timer_ !== null) { 54487 // Tech is seeking or already waiting on another action, no action needed 54488 return true; 54489 } 54490 54491 if (this.beforeSeekableWindow_(seekable, currentTime)) { 54492 var livePoint = seekable.end(seekable.length - 1); 54493 this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint)); 54494 this.cancelTimer_(); 54495 this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS 54496 54497 this.tech_.trigger({ 54498 type: 'usage', 54499 name: 'hls-live-resync' 54500 }); 54501 return true; 54502 } 54503 54504 var buffered = this.tech_.buffered(); 54505 var nextRange = findNextRange(buffered, currentTime); 54506 54507 if (this.videoUnderflow_(nextRange, buffered, currentTime)) { 54508 // Even though the video underflowed and was stuck in a gap, the audio overplayed 54509 // the gap, leading currentTime into a buffered range. Seeking to currentTime 54510 // allows the video to catch up to the audio position without losing any audio 54511 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 54512 this.cancelTimer_(); 54513 this.seekTo(currentTime); // video underflow may be useful for monitoring QoS 54514 54515 this.tech_.trigger({ 54516 type: 'usage', 54517 name: 'hls-video-underflow' 54518 }); 54519 return true; 54520 } // check for gap 54521 54522 54523 if (nextRange.length > 0) { 54524 var difference = nextRange.start(0) - currentTime; 54525 this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0))); 54526 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 54527 return true; 54528 } // All checks failed. Returning false to indicate failure to correct waiting 54529 54530 54531 return false; 54532 } 54533 }, { 54534 key: 'afterSeekableWindow_', 54535 value: function afterSeekableWindow_(seekable, currentTime, playlist) { 54536 var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; 54537 54538 if (!seekable.length) { 54539 // we can't make a solid case if there's no seekable, default to false 54540 return false; 54541 } 54542 54543 var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA; 54544 var isLive = !playlist.endList; 54545 54546 if (isLive && allowSeeksWithinUnsafeLiveWindow) { 54547 allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3; 54548 } 54549 54550 if (currentTime > allowedEnd) { 54551 return true; 54552 } 54553 54554 return false; 54555 } 54556 }, { 54557 key: 'beforeSeekableWindow_', 54558 value: function beforeSeekableWindow_(seekable, currentTime) { 54559 if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream 54560 seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) { 54561 return true; 54562 } 54563 54564 return false; 54565 } 54566 }, { 54567 key: 'videoUnderflow_', 54568 value: function videoUnderflow_(nextRange, buffered, currentTime) { 54569 if (nextRange.length === 0) { 54570 // Even if there is no available next range, there is still a possibility we are 54571 // stuck in a gap due to video underflow. 54572 var gap = this.gapFromVideoUnderflow_(buffered, currentTime); 54573 54574 if (gap) { 54575 this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime)); 54576 return true; 54577 } 54578 } 54579 54580 return false; 54581 } 54582 /** 54583 * Timer callback. If playback still has not proceeded, then we seek 54584 * to the start of the next buffered region. 54585 * 54586 * @private 54587 */ 54588 54589 }, { 54590 key: 'skipTheGap_', 54591 value: function skipTheGap_(scheduledCurrentTime) { 54592 var buffered = this.tech_.buffered(); 54593 var currentTime = this.tech_.currentTime(); 54594 var nextRange = findNextRange(buffered, currentTime); 54595 this.cancelTimer_(); 54596 54597 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 54598 return; 54599 } 54600 54601 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played 54602 54603 this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR); 54604 this.tech_.trigger({ 54605 type: 'usage', 54606 name: 'hls-gap-skip' 54607 }); 54608 } 54609 }, { 54610 key: 'gapFromVideoUnderflow_', 54611 value: function gapFromVideoUnderflow_(buffered, currentTime) { 54612 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 54613 // playing for ~3 seconds after the video gap starts. This is done to account for 54614 // video buffer underflow/underrun (note that this is not done when there is audio 54615 // buffer underflow/underrun -- in that case the video will stop as soon as it 54616 // encounters the gap, as audio stalls are more noticeable/jarring to a user than 54617 // video stalls). The player's time will reflect the playthrough of audio, so the 54618 // time will appear as if we are in a buffered region, even if we are stuck in a 54619 // "gap." 54620 // 54621 // Example: 54622 // video buffer: 0 => 10.1, 10.2 => 20 54623 // audio buffer: 0 => 20 54624 // overall buffer: 0 => 10.1, 10.2 => 20 54625 // current time: 13 54626 // 54627 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 54628 // however, the audio continued playing until it reached ~3 seconds past the gap 54629 // (13 seconds), at which point it stops as well. Since current time is past the 54630 // gap, findNextRange will return no ranges. 54631 // 54632 // To check for this issue, we see if there is a gap that starts somewhere within 54633 // a 3 second range (3 seconds +/- 1 second) back from our current time. 54634 var gaps = findGaps(buffered); 54635 54636 for (var i = 0; i < gaps.length; i++) { 54637 var start = gaps.start(i); 54638 var end = gaps.end(i); // gap is starts no more than 4 seconds back 54639 54640 if (currentTime - start < 4 && currentTime - start > 2) { 54641 return { 54642 start: start, 54643 end: end 54644 }; 54645 } 54646 } 54647 54648 return null; 54649 } 54650 }]); 54651 return PlaybackWatcher; 54652 }(); 54653 54654 var defaultOptions = { 54655 errorInterval: 30, 54656 getSource: function getSource(next) { 54657 var tech = this.tech({ 54658 IWillNotUseThisInPlugins: true 54659 }); 54660 var sourceObj = tech.currentSource_; 54661 return next(sourceObj); 54662 } 54663 }; 54664 /** 54665 * Main entry point for the plugin 54666 * 54667 * @param {Player} player a reference to a videojs Player instance 54668 * @param {Object} [options] an object with plugin options 54669 * @private 54670 */ 54671 54672 var initPlugin = function initPlugin(player, options) { 54673 var lastCalled = 0; 54674 var seekTo = 0; 54675 var localOptions = videojs$1.mergeOptions(defaultOptions, options); 54676 player.ready(function () { 54677 player.trigger({ 54678 type: 'usage', 54679 name: 'hls-error-reload-initialized' 54680 }); 54681 }); 54682 /** 54683 * Player modifications to perform that must wait until `loadedmetadata` 54684 * has been triggered 54685 * 54686 * @private 54687 */ 54688 54689 var loadedMetadataHandler = function loadedMetadataHandler() { 54690 if (seekTo) { 54691 player.currentTime(seekTo); 54692 } 54693 }; 54694 /** 54695 * Set the source on the player element, play, and seek if necessary 54696 * 54697 * @param {Object} sourceObj An object specifying the source url and mime-type to play 54698 * @private 54699 */ 54700 54701 54702 var setSource = function setSource(sourceObj) { 54703 if (sourceObj === null || sourceObj === undefined) { 54704 return; 54705 } 54706 54707 seekTo = player.duration() !== Infinity && player.currentTime() || 0; 54708 player.one('loadedmetadata', loadedMetadataHandler); 54709 player.src(sourceObj); 54710 player.trigger({ 54711 type: 'usage', 54712 name: 'hls-error-reload' 54713 }); 54714 player.play(); 54715 }; 54716 /** 54717 * Attempt to get a source from either the built-in getSource function 54718 * or a custom function provided via the options 54719 * 54720 * @private 54721 */ 54722 54723 54724 var errorHandler = function errorHandler() { 54725 // Do not attempt to reload the source if a source-reload occurred before 54726 // 'errorInterval' time has elapsed since the last source-reload 54727 if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { 54728 player.trigger({ 54729 type: 'usage', 54730 name: 'hls-error-reload-canceled' 54731 }); 54732 return; 54733 } 54734 54735 if (!localOptions.getSource || typeof localOptions.getSource !== 'function') { 54736 videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!'); 54737 return; 54738 } 54739 54740 lastCalled = Date.now(); 54741 return localOptions.getSource.call(player, setSource); 54742 }; 54743 /** 54744 * Unbind any event handlers that were bound by the plugin 54745 * 54746 * @private 54747 */ 54748 54749 54750 var cleanupEvents = function cleanupEvents() { 54751 player.off('loadedmetadata', loadedMetadataHandler); 54752 player.off('error', errorHandler); 54753 player.off('dispose', cleanupEvents); 54754 }; 54755 /** 54756 * Cleanup before re-initializing the plugin 54757 * 54758 * @param {Object} [newOptions] an object with plugin options 54759 * @private 54760 */ 54761 54762 54763 var reinitPlugin = function reinitPlugin(newOptions) { 54764 cleanupEvents(); 54765 initPlugin(player, newOptions); 54766 }; 54767 54768 player.on('error', errorHandler); 54769 player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before 54770 // initializing the plugin 54771 54772 player.reloadSourceOnError = reinitPlugin; 54773 }; 54774 /** 54775 * Reload the source when an error is detected as long as there 54776 * wasn't an error previously within the last 30 seconds 54777 * 54778 * @param {Object} [options] an object with plugin options 54779 */ 54780 54781 54782 var reloadSourceOnError = function reloadSourceOnError(options) { 54783 initPlugin(this, options); 54784 }; 54785 54786 var version$1 = "1.11.2"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all 54787 54788 videojs$1.use('*', function (player) { 54789 return { 54790 setSource: function setSource(srcObj, next) { 54791 // pass null as the first argument to indicate that the source is not rejected 54792 next(null, srcObj); 54793 }, 54794 // VHS needs to know when seeks happen. For external seeks (generated at the player 54795 // level), this middleware will capture the action. For internal seeks (generated at 54796 // the tech level), we use a wrapped function so that we can handle it on our own 54797 // (specified elsewhere). 54798 setCurrentTime: function setCurrentTime(time) { 54799 if (player.vhs && player.currentSource().src === player.vhs.source_.src) { 54800 player.vhs.setCurrentTime(time); 54801 } 54802 54803 return time; 54804 }, 54805 // Sync VHS after play requests. 54806 // This specifically handles replay where the order of actions is 54807 // play, video element will seek to 0 (skipping the setCurrentTime middleware) 54808 // then triggers a play event. 54809 play: function play() { 54810 if (player.vhs && player.currentSource().src === player.vhs.source_.src) { 54811 player.vhs.setCurrentTime(player.tech_.currentTime()); 54812 } 54813 } 54814 }; 54815 }); 54816 /** 54817 * @file videojs-http-streaming.js 54818 * 54819 * The main file for the HLS project. 54820 * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE 54821 */ 54822 54823 var Hls$1 = { 54824 PlaylistLoader: PlaylistLoader, 54825 Playlist: Playlist, 54826 Decrypter: Decrypter, 54827 AsyncStream: AsyncStream, 54828 decrypt: decrypt, 54829 utils: utils$1, 54830 STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector, 54831 INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector, 54832 comparePlaylistBandwidth: comparePlaylistBandwidth, 54833 comparePlaylistResolution: comparePlaylistResolution, 54834 xhr: xhrFactory() 54835 }; // Define getter/setters for config properites 54836 54837 ['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) { 54838 Object.defineProperty(Hls$1, prop, { 54839 get: function get$$1() { 54840 videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know
54840what you are doing'); 54841 return Config[prop]; 54842 }, 54843 set: function set$$1(value) { 54844 videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 54845 54846 if (typeof value !== 'number' || value < 0) { 54847 videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0'); 54848 return; 54849 } 54850 54851 Config[prop] = value; 54852 } 54853 }); 54854 }); 54855 var LOCAL_STORAGE_KEY$1 = 'videojs-vhs'; 54856 54857 var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) { 54858 var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 54859 54860 if (mpegurlRE.test(type)) { 54861 return 'hls'; 54862 } 54863 54864 var dashRE = /^application\/dash\+xml/i; 54865 54866 if (dashRE.test(type)) { 54867 return 'dash'; 54868 } 54869 54870 return null; 54871 }; 54872 /** 54873 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls. 54874 * 54875 * @param {QualityLevelList} qualityLevels The QualityLevelList to update. 54876 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info. 54877 * @function handleHlsMediaChange 54878 */ 54879 54880 54881 var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) { 54882 var newPlaylist = playlistLoader.media(); 54883 var selectedIndex = -1; 54884 54885 for (var i = 0; i < qualityLevels.length; i++) { 54886 if (qualityLevels[i].id === newPlaylist.uri) { 54887 selectedIndex = i; 54888 break; 54889 } 54890 } 54891 54892 qualityLevels.selectedIndex_ = selectedIndex; 54893 qualityLevels.trigger({ 54894 selectedIndex: selectedIndex, 54895 type: 'change' 54896 }); 54897 }; 54898 /** 54899 * Adds quality levels to list once playlist metadata is available 54900 * 54901 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to. 54902 * @param {Object} hls Hls object to listen to for media events. 54903 * @function handleHlsLoadedMetadata 54904 */ 54905 54906 54907 var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) { 54908 hls.representations().forEach(function (rep) { 54909 qualityLevels.addQualityLevel(rep); 54910 }); 54911 handleHlsMediaChange(qualityLevels, hls.playlists); 54912 }; // HLS is a source handler, not a tech. Make sure attempts to use it 54913 // as one do not cause exceptions. 54914 54915 54916 Hls$1.canPlaySource = function () { 54917 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 54918 }; 54919 54920 var emeKeySystems = function emeKeySystems(keySystemOptions, mainSegmentLoader, audioSegmentLoader) { 54921 if (!keySystemOptions) { 54922 return keySystemOptions; 54923 } 54924 54925 var videoMimeType = void 0; 54926 var audioMimeType = void 0; // if there is a mimeType associated with the audioSegmentLoader, then the audio 54927 // and video mimeType and codec strings are already in the format we need to 54928 // pass with the other key systems 54929 54930 if (audioSegmentLoader.mimeType_) { 54931 videoMimeType = mainSegmentLoader.mimeType_; 54932 audioMimeType = audioSegmentLoader.mimeType_; // if there is no audioSegmentLoader mimeType, then we have to create the 54933 // the audio and video mimeType/codec strings from information extrapolated 54934 // from the mainSegmentLoader mimeType (ex. 'video/mp4; codecs="mp4, avc1"' --> 54935 // 'video/mp4; codecs="avc1"' and 'audio/mp4; codecs="mp4"') 54936 } else { 54937 var parsedMimeType = parseContentType(mainSegmentLoader.mimeType_); 54938 var codecs = parsedMimeType.parameters.codecs.split(','); 54939 var audioCodec = void 0; 54940 var videoCodec = void 0; 54941 codecs.forEach(function (codec) { 54942 codec = codec.trim(); 54943 54944 if (isAudioCodec(codec)) { 54945 audioCodec = codec; 54946 } else if (isVideoCodec(codec)) { 54947 videoCodec = codec; 54948 } 54949 }); 54950 videoMimeType = parsedMimeType.type + '; codecs="' + videoCodec + '"'; 54951 audioMimeType = parsedMimeType.type.replace('video', 'audio') + '; codecs="' + audioCodec + '"'; 54952 } // upsert the content types based on the selected playlist 54953 54954 54955 var keySystemContentTypes = {}; 54956 var videoPlaylist = mainSegmentLoader.playlist_; 54957 54958 for (var keySystem in keySystemOptions) { 54959 keySystemContentTypes[keySystem] = { 54960 audioContentType: audioMimeType, 54961 videoContentType: videoMimeType 54962 }; 54963 54964 if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) { 54965 keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh; 54966 } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url'
54967 // so we need to prevent overwriting the URL entirely 54968 54969 54970 if (typeof keySystemOptions[keySystem] === 'string') { 54971 keySystemContentTypes[keySystem].url = keySystemOptions[keySystem]; 54972 } 54973 } 54974 54975 return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes); 54976 }; 54977 54978 var setupEmeOptions = function setupEmeOptions(hlsHandler) { 54979 var mainSegmentLoader = hlsHandler.masterPlaylistController_.mainSegmentLoader_; 54980 var audioSegmentLoader = hlsHandler.masterPlaylistController_.audioSegmentLoader_; 54981 var player = videojs$1.players[hlsHandler.tech_.options_.playerId]; 54982 54983 if (player.eme) { 54984 var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, mainSegmentLoader, audioSegmentLoader); 54985 54986 if (sourceOptions) { 54987 player.currentSource().keySystems = sourceOptions; // Works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 54988 // in non-IE11 browsers. In IE11 this is too early to initialize media keys 54989 54990 if (!(videojs$1.browser.IE_VERSION === 11) && player.eme.initializeMediaKeys) { 54991 player.eme.initializeMediaKeys(); 54992 } 54993 } 54994 } 54995 }; 54996 54997 var getVhsLocalStorage = function getVhsLocalStorage() { 54998 if (!window.localStorage) { 54999 return null; 55000 } 55001 55002 var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1); 55003 55004 if (!storedObject) { 55005 return null; 55006 } 55007 55008 try { 55009 return JSON.parse(storedObject); 55010 } catch (e) { 55011 // someone may have tampered with the value 55012 return null; 55013 } 55014 }; 55015 55016 var updateVhsLocalStorage = function updateVhsLocalStorage(options) { 55017 if (!window.localStorage) { 55018 return false; 55019 } 55020 55021 var objectToStore = getVhsLocalStorage(); 55022 objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options; 55023 55024 try { 55025 window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore)); 55026 } catch (e) { 55027 // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where 55028 // storage is set to 0). 55029 // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions 55030 // No need to perform any operation. 55031 return false; 55032 } 55033 55034 return objectToStore; 55035 }; 55036 /** 55037 * Whether the browser has built-in HLS support. 55038 */ 55039 55040 55041 Hls$1.supportsNativeHls = function () { 55042 var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't 55043 55044 if (!videojs$1.getTech('Html5').isSupported()) { 55045 return false; 55046 } // HLS manifests can go by many mime-types 55047 55048 55049 var canPlay = [// Apple santioned 55050 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility 55051 'audio/mpegurl', // Very common 55052 'audio/x-mpegurl', // Very common
vendor: 20,034 bytes, lines 55053-55580
55053 'application/x-mpegurl', // Included for completeness 55054 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 55055 return canPlay.some(function (canItPlay) { 55056 return /maybe|probably/i.test(video.canPlayType(canItPlay)); 55057 }); 55058 }(); 55059 55060 Hls$1.supportsNativeDash = function () { 55061 if (!videojs$1.getTech('Html5').isSupported()) { 55062 return false; 55063 } 55064 55065 return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml')); 55066 }(); 55067 55068 Hls$1.supportsTypeNatively = function (type) { 55069 if (type === 'hls') { 55070 return Hls$1.supportsNativeHls; 55071 } 55072 55073 if (type === 'dash') { 55074 return Hls$1.supportsNativeDash; 55075 } 55076 55077 return false; 55078 }; 55079 /** 55080 * HLS is a source handler, not a tech. Make sure attempts to use it 55081 * as one do not cause exceptions. 55082 */ 55083 55084 55085 Hls$1.isSupported = function () { 55086 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 55087 }; 55088 55089 var Component$1 = videojs$1.getComponent('Component'); 55090 /** 55091 * The Hls Handler object, where we orchestrate all of the parts 55092 * of HLS to interact with video.js 55093 * 55094 * @class HlsHandler 55095 * @extends videojs.Component 55096 * @param {Object} source the soruce object 55097 * @param {Tech} tech the parent tech object 55098 * @param {Object} options optional and required options 55099 */ 55100 55101 var HlsHandler = function (_Component) { 55102 inherits$2(HlsHandler, _Component); 55103 55104 function HlsHandler(source, tech, options) { 55105 classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for 55106 // backwards-compatibility 55107 55108 var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls)); 55109 55110 if (tech.options_ && tech.options_.playerId) { 55111 var _player = videojs$1(tech.options_.playerId); 55112 55113 if (!_player.hasOwnProperty('hls')) { 55114 Object.defineProperty(_player, 'hls', { 55115 get: function get$$1() { 55116 videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.'); 55117 tech.trigger({ 55118 type: 'usage', 55119 name: 'hls-player-access' 55120 }); 55121 return _this; 55122 }, 55123 configurable: true 55124 }); 55125 } // Set up a reference to the HlsHandler from player.vhs. This allows users to start 55126 // migrating from player.tech_.hls... to player.vhs... for API access. Although this 55127 // isn't the most appropriate form of reference for video.js (since all APIs should 55128 // be provided through core video.js), it is a common pattern for plugins, and vhs 55129 // will act accordingly. 55130 55131 55132 _player.vhs = _this; // deprecated, for backwards compatibility 55133 55134 _player.dash = _this; 55135 _this.player_ = _player; 55136 } 55137 55138 _this.tech_ = tech; 55139 _this.source_ = source; 55140 _this.stats = {}; 55141 55142 _this.setOptions_(); 55143 55144 if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) { 55145 tech.overrideNativeAudioTracks(true); 55146 tech.overrideNativeVideoTracks(true); 55147 } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { 55148 // overriding native HLS only works if audio tracks have been emulated 55149 // error early if we're misconfigured 55150 throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB'); 55151 } // listen for fullscreenchange events for this player so that we 55152 // can adjust our quality selection quickly 55153 55154 55155 _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 55156 var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; 55157 55158 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 55159 _this.masterPlaylistController_.smoothQualityChange_(); 55160 } 55161 }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech 55162 55163 55164 _this.on(_this.tech_, 'seeking', function () { 55165 if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) { 55166 this.setCurrentTime(0); 55167 } 55168 }); 55169 55170 _this.on(_this.tech_, 'error', function () { 55171 if (this.masterPlaylistController_) { 55172 this.masterPlaylistController_.pauseLoading(); 55173 } 55174 }); 55175 55176 _this.on(_this.tech_, 'play', _this.play); 55177 55178 return _this; 55179 } 55180 55181 createClass$1(HlsHandler, [{ 55182 key: 'setOptions_', 55183 value: function setOptions_() { 55184 var _this2 = this; // defaults 55185 55186 55187 this.options_.withCredentials = this.options_.withCredentials || false; 55188 this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false; 55189 this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true; 55190 this.options_.smoothQualityChange = this.options_.smoothQualityChange || false; 55191 this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false; 55192 this.options_.customTagParsers = this.options_.customTagParsers || []; 55193 this.options_.customTagMappers = this.options_.customTagMappers || []; 55194 this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false; 55195 55196 if (typeof this.options_.blacklistDuration !== 'number') { 55197 this.options_.blacklistDuration = 5 * 60; 55198 } 55199 55200 if (typeof this.options_.bandwidth !== 'number') { 55201 if (this.options_.useBandwidthFromLocalStorage) { 55202 var storedObject = getVhsLocalStorage(); 55203 55204 if (storedObject && storedObject.bandwidth) { 55205 this.options_.bandwidth = storedObject.bandwidth; 55206 this.tech_.trigger({ 55207 type: 'usage', 55208 name: 'hls-bandwidth-from-local-storage' 55209 }); 55210 } 55211 55212 if (storedObject && storedObject.throughput) { 55213 this.options_.throughput = storedObject.throughput; 55214 this.tech_.trigger({ 55215 type: 'usage', 55216 name: 'hls-throughput-from-local-storage' 55217 }); 55218 } 55219 } 55220 } // if bandwidth was not set by options or pulled from local storage, start playlist 55221 // selection at a reasonable bandwidth 55222 55223 55224 if (typeof this.options_.bandwidth !== 'number') { 55225 this.options_.bandwidth = Config.INITIAL_BANDWIDTH; 55226 } // If the bandwidth number is unchanged from the initial setting 55227 // then this takes precedence over the enableLowInitialPlaylist option 55228 55229 55230 this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src 55231 55232 ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys'].forEach(function (option) { 55233 if (typeof _this2.source_[option] !== 'undefined') { 55234 _this2.options_[option] = _this2.source_[option]; 55235 } 55236 }); 55237 this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions; 55238 } 55239 /** 55240 * called when player.src gets called, handle a new source 55241 * 55242 * @param {Object} src the source object to handle 55243 */ 55244 55245 }, { 55246 key: 'src', 55247 value: function src(_src, type) { 55248 var _this3 = this; // do nothing if the src is falsey 55249 55250 55251 if (!_src) { 55252 return; 55253 } 55254 55255 this.setOptions_(); // add master playlist controller options 55256 55257 this.options_.url = this.source_.src; 55258 this.options_.tech = this.tech_; 55259 this.options_.externHls = Hls$1; 55260 this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own 55261 // setCurrentTime function. This is needed because "seeking" events aren't always 55262 // reliable. External seeks (via the player object) are handled via middleware. 55263 55264 this.options_.seekTo = function (time) { 55265 _this3.tech_.setCurrentTime(time); 55266 55267 _this3.setCurrentTime(time); 55268 }; 55269 55270 this.masterPlaylistController_ = new MasterPlaylistController(this.options_); 55271 this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, { 55272 seekable: function seekable$$1() { 55273 return _this3.seekable(); 55274 }, 55275 media: function media() { 55276 return _this3.masterPlaylistController_.media(); 55277 } 55278 })); 55279 this.masterPlaylistController_.on('error', function () { 55280 var player = videojs$1.players[_this3.tech_.options_.playerId]; 55281 player.error(_this3.masterPlaylistController_.error); 55282 }); // `this` in selectPlaylist should be the HlsHandler for backwards 55283 // compatibility with < v2 55284 55285 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this); 55286 this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2 55287 55288 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 55289 this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist 55290 // controller. Using a custom property for backwards compatibility 55291 // with < v2 55292 55293 Object.defineProperties(this, { 55294 selectPlaylist: { 55295 get: function get$$1() { 55296 return this.masterPlaylistController_.selectPlaylist; 55297 }, 55298 set: function set$$1(selectPlaylist) { 55299 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 55300 } 55301 }, 55302 throughput: { 55303 get: function get$$1() { 55304 return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; 55305 }, 55306 set: function set$$1(throughput) { 55307 this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value 55308 // for the cumulative average 55309 55310 this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; 55311 } 55312 }, 55313 bandwidth: { 55314 get: function get$$1() { 55315 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 55316 }, 55317 set: function set$$1(bandwidth) { 55318 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter 55319 // `count` is set to zero that current value of `rate` isn't included 55320 // in the cumulative average 55321 55322 this.masterPlaylistController_.mainSegmentLoader_.throughput = { 55323 rate: 0, 55324 count: 0 55325 }; 55326 } 55327 }, 55328 55329 /** 55330 * `systemBandwidth` is a combination of two serial processes bit-rates. The first 55331 * is the network bitrate provided by `bandwidth` and the second is the bitrate of 55332 * the entire process after that - decryption, transmuxing, and appending - provided 55333 * by `throughput`. 55334 * 55335 * Since the two process are serial, the overall system bandwidth is given by: 55336 * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput) 55337 */ 55338 systemBandwidth: { 55339 get: function get$$1() { 55340 var invBandwidth = 1 / (this.bandwidth || 1); 55341 var invThroughput = void 0; 55342 55343 if (this.throughput > 0) { 55344 invThroughput = 1 / this.throughput; 55345 } else { 55346 invThroughput = 0; 55347 } 55348 55349 var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput)); 55350 return systemBitrate; 55351 }, 55352 set: function set$$1() { 55353 videojs$1.log.error('The "systemBandwidth" property is read-only'); 55354 } 55355 } 55356 }); 55357 55358 if (this.options_.bandwidth) { 55359 this.bandwidth = this.options_.bandwidth; 55360 } 55361 55362 if (this.options_.throughput) { 55363 this.throughput = this.options_.throughput; 55364 } 55365 55366 Object.defineProperties(this.stats, { 55367 bandwidth: { 55368 get: function get$$1() { 55369 return _this3.bandwidth || 0; 55370 }, 55371 enumerable: true 55372 }, 55373 mediaRequests: { 55374 get: function get$$1() { 55375 return _this3.masterPlaylistController_.mediaRequests_() || 0; 55376 }, 55377 enumerable: true 55378 }, 55379 mediaRequestsAborted: { 55380 get: function get$$1() { 55381 return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0; 55382 }, 55383 enumerable: true 55384 }, 55385 mediaRequestsTimedout: { 55386 get: function get$$1() { 55387 return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0; 55388 }, 55389 enumerable: true 55390 }, 55391 mediaRequestsErrored: { 55392 get: function get$$1() { 55393 return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0; 55394 }, 55395 enumerable: true 55396 }, 55397 mediaTransferDuration: { 55398 get: function get$$1() { 55399 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 55400 }, 55401 enumerable: true 55402 }, 55403 mediaBytesTransferred: { 55404 get: function get$$1() { 55405 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 55406 }, 55407 enumerable: true 55408 }, 55409 mediaSecondsLoaded: { 55410 get: function get$$1() { 55411 return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0; 55412 }, 55413 enumerable: true 55414 }, 55415 buffered: { 55416 get: function get$$1() { 55417 return timeRangesToArray(_this3.tech_.buffered()); 55418 }, 55419 enumerable: true 55420 }, 55421 currentTime: { 55422 get: function get$$1() { 55423 return _this3.tech_.currentTime(); 55424 }, 55425 enumerable: true 55426 }, 55427 currentSource: { 55428 get: function get$$1() { 55429 return _this3.tech_.currentSource_; 55430 }, 55431 enumerable: true 55432 }, 55433 currentTech: { 55434 get: function get$$1() { 55435 return _this3.tech_.name_; 55436 }, 55437 enumerable: true 55438 }, 55439 duration: { 55440 get: function get$$1() { 55441 return _this3.tech_.duration(); 55442 }, 55443 enumerable: true 55444 }, 55445 master: { 55446 get: function get$$1() { 55447 return _this3.playlists.master; 55448 }, 55449 enumerable: true 55450 }, 55451 playerDimensions: { 55452 get: function get$$1() { 55453 return _this3.tech_.currentDimensions(); 55454 }, 55455 enumerable: true 55456 }, 55457 seekable: { 55458 get: function get$$1() { 55459 return timeRangesToArray(_this3.tech_.seekable()); 55460 }, 55461 enumerable: true 55462 }, 55463 timestamp: { 55464 get: function get$$1() { 55465 return Date.now(); 55466 }, 55467 enumerable: true 55468 }, 55469 videoPlaybackQuality: { 55470 get: function get$$1() { 55471 return _this3.tech_.getVideoPlaybackQuality(); 55472 }, 55473 enumerable: true 55474 } 55475 }); 55476 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 55477 this.tech_.on('bandwidthupdate', function () { 55478 if (_this3.options_.useBandwidthFromLocalStorage) { 55479 updateVhsLocalStorage({ 55480 bandwidth: _this3.bandwidth, 55481 throughput: Math.round(_this3.throughput) 55482 }); 55483 } 55484 }); 55485 this.masterPlaylistController_.on('selectedinitialmedia', function () { 55486 // Add the manual rendition mix-in to HlsHandler 55487 renditionSelectionMixin(_this3); 55488 setupEmeOptions(_this3); 55489 }); // the bandwidth of the primary segment loader is our best 55490 // estimate of overall bandwidth 55491 55492 this.on(this.masterPlaylistController_, 'progress', function () { 55493 this.tech_.trigger('progress'); 55494 }); 55495 this.tech_.ready(function () { 55496 return _this3.setupQualityLevels_(); 55497 }); // do nothing if the tech has been disposed already 55498 // this can occur if someone sets the src in player.ready(), for instance 55499 55500 if (!this.tech_.el()) { 55501 return; 55502 } 55503 55504 this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource)); 55505 } 55506 /** 55507 * Initializes the quality levels and sets listeners to update them. 55508 * 55509 * @method setupQualityLevels_ 55510 * @private 55511 */ 55512 55513 }, { 55514 key: 'setupQualityLevels_', 55515 value: function setupQualityLevels_() { 55516 var _this4 = this; 55517 55518 var player = videojs$1.players[this.tech_.options_.playerId]; 55519 55520 if (player && player.qualityLevels) { 55521 this.qualityLevels_ = player.qualityLevels(); 55522 this.masterPlaylistController_.on('selectedinitialmedia', function () { 55523 handleHlsLoadedMetadata(_this4.qualityLevels_, _this4); 55524 }); 55525 this.playlists.on('mediachange', function () { 55526 handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists); 55527 }); 55528 } 55529 } 55530 /** 55531 * Begin playing the video. 55532 */ 55533 55534 }, { 55535 key: 'play', 55536 value: function play() { 55537 this.masterPlaylistController_.play(); 55538 } 55539 /** 55540 * a wrapper around the function in MasterPlaylistController 55541 */ 55542 55543 }, { 55544 key: 'setCurrentTime', 55545 value: function setCurrentTime(currentTime) { 55546 this.masterPlaylistController_.setCurrentTime(currentTime); 55547 } 55548 /** 55549 * a wrapper around the function in MasterPlaylistController 55550 */ 55551 55552 }, { 55553 key: 'duration', 55554 value: function duration$$1() { 55555 return this.masterPlaylistController_.duration(); 55556 } 55557 /** 55558 * a wrapper around the function in MasterPlaylistController 55559 */ 55560 55561 }, { 55562 key: 'seekable', 55563 value: function seekable$$1() { 55564 return this.masterPlaylistController_.seekable(); 55565 } 55566 /** 55567 * Abort all outstanding work and cleanup. 55568 */ 55569 55570 }, { 55571 key: 'dispose', 55572 value: function dispose() { 55573 if (this.playbackWatcher_) { 55574 this.playbackWatcher_.dispose(); 55575 } 55576 55577 if (this.masterPlaylistController_) { 55578 this.masterPlaylistController_.dispose(); 55579 } 55580
55581 if (this.qualityLevels_) { 55582 this.qualityLevels_.dispose(); 55583 } 55584 55585 if (this.player_) { 55586 delete this.player_.vhs; 55587 delete this.player_.dash; 55588 delete this.player_.hls; 55589 } 55590 55591 if (this.tech_ && this.tech_.hls) { 55592 delete this.tech_.hls; 55593 } 55594 55595 get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this); 55596 } 55597 }, { 55598 key: 'convertToProgramTime', 55599 value: function convertToProgramTime(time, callback) { 55600 return getProgramTime({ 55601 playlist: this.masterPlaylistController_.media(), 55602 time: time, 55603 callback: callback 55604 }); 55605 } // the player must be playing before calling this 55606 55607 }, { 55608 key: 'seekToProgramTime', 55609 value: function seekToProgramTime$$1(programTime, callback) { 55610 var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 55611 var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2; 55612 return seekToProgramTime({ 55613 programTime: programTime, 55614 playlist: this.masterPlaylistController_.media(), 55615 retryCount: retryCount, 55616 pauseAfterSeek: pauseAfterSeek, 55617 seekTo: this.options_.seekTo, 55618 tech: this.options_.tech, 55619 callback: callback 55620 }); 55621 } 55622 }]); 55623 return HlsHandler; 55624 }(Component$1); 55625 /** 55626 * The Source Handler object, which informs video.js what additional 55627 * MIME types are supported and sets up playback. It is registered 55628 * automatically to the appropriate tech based on the capabilities of 55629 * the browser it is running in. It is not necessary to use or modify 55630 * this object in normal usage. 55631 */ 55632 55633 55634 var HlsSourceHandler = { 55635 name: 'videojs-http-streaming', 55636 VERSION: version$1, 55637 canHandleSource: function canHandleSource(srcObj) { 55638 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 55639 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 55640 return HlsSourceHandler.canPlayType(srcObj.type, localOptions); 55641 }, 55642 handleSource: function handleSource(source, tech) { 55643 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 55644 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 55645 tech.hls = new HlsHandler(source, tech, localOptions); 55646 tech.hls.xhr = xhrFactory(); 55647 tech.hls.src(source.src, source.type); 55648 return tech.hls; 55649 }, 55650 canPlayType: function canPlayType(type) { 55651 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 55652 55653 var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options), 55654 overrideNative = _videojs$mergeOptions.hls.overrideNative; 55655 55656 var supportedType = simpleTypeFromSourceType(type); 55657 var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative); 55658 return canUseMsePlayback ? 'maybe' : ''; 55659 } 55660 }; 55661 55662 if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') { 55663 videojs$1.MediaSource = MediaSource; 55664 videojs$1.URL = URL$1; 55665 } // register source handlers with the appropriate techs 55666 55667 55668 if (MediaSource.supportsNativeMediaSources()) { 55669 videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0); 55670 } 55671 55672 videojs$1.HlsHandler = HlsHandler; 55673 videojs$1.HlsSourceHandler = HlsSourceHandler; 55674 videojs$1.Hls = Hls$1; 55675 55676 if (!videojs$1.use) { 55677 videojs$1.registerComponent('Hls', Hls$1); 55678 } 55679 55680 videojs$1.options.hls = videojs$1.options.hls || {}; 55681 55682 if (videojs$1.registerPlugin) { 55683 videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError); 55684 } else { 55685 videojs$1.plugin('reloadSourceOnError', reloadSourceOnError); 55686 } 55687 55688 return videojs$1; 55689 55690})); 55691 55692!function(){!function(a){var b=a&&a.videojs;b&&(b.CDN_VERSION="7.7.5")}(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.