1/** 2 * @license 3 * Video.js 7.6.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) { 18 window$1 = window$1 && window$1.hasOwnProperty('default') ? window$1['default'] : window$1; 19 document = document && document.hasOwnProperty('default') ? document['default'] : document; 20 21 var version = "7.6.5"; 22 23 /** 24 * @file create-logger.js 25 * @module create-logger 26 */ 27 28 var history = []; 29 /** 30 * Log messages to the console and history based on the type of message 31 * 32 * @private 33 * @param {string} type 34 * The name of the console method to use. 35 * 36 * @param {Array} args 37 * The arguments to be passed to the matching console method. 38 */ 39 40 var LogByTypeFactory = function LogByTypeFactory(name, log) { 41 return function (type, level, args) { 42 var lvl = log.levels[level]; 43 var lvlRegExp = new RegExp("^(" + lvl + ")$"); 44 45 if (type !== 'log') { 46 // Add the type to the front of the message when it's not "log". 47 args.unshift(type.toUpperCase() + ':'); 48 } // Add console prefix after adding to history. 49 50 51 args.unshift(name + ':'); // Add a clone of the args at this point to history. 52 53 if (history) { 54 history.push([].concat(args)); 55 } // If there's no console then don't try to output messages, but they will 56 // still be stored in history. 57 58 59 if (!window$1.console) { 60 return; 61 } // Was setting these once outside of this function, but containing them 62 // in the function makes it easier to test cases where console doesn't exist 63 // when the module is executed. 64 65 66 var fn = window$1.console[type]; 67 68 if (!fn && type === 'debug') { 69 // Certain browsers don't have support for console.debug. For those, we 70 // should default to the closest comparable log. 71 fn = window$1.console.info || window$1.console.log; 72 } // Bail out if there's no console or if this type is not allowed by the 73 // current logging level. 74 75 76 if (!fn || !lvl || !lvlRegExp.test(type)) { 77 return; 78 } 79 80 fn[Array.isArray(args) ? 'apply' : 'call'](window$1.console, args); 81 }; 82 }; 83 84 function createLogger(name) { 85 // This is the private tracking variable for logging level. 86 var level = 'info'; // the curried logByType bound to the specific log and history 87 88 var logByType; 89 /** 90 * Logs plain debug messages. Similar to `console.log`. 91 * 92 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 93 * of our JSDoc template, we cannot properly document this as both a function 94 * and a namespace, so its function signature is documented here. 95 * 96 * #### Arguments 97 * ##### *args 98 * Mixed[] 99 * 100 * Any combination of values that could be passed to `console.log()`. 101 * 102 * #### Return Value 103 * 104 * `undefined` 105 * 106 * @namespace 107 * @param {Mixed[]} args 108 * One or more messages or objects that should be logged. 109 */ 110 111 var log = function log() { 112 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 113 args[_key] = arguments[_key]; 114 } 115 116 logByType('log', level, args); 117 }; // This is the logByType helper that the logging methods below use 118 119 120 logByType = LogByTypeFactory(name, log); 121 /** 122 * Create a new sublogger which chains the old name to the new name. 123 * 124 * For example, doing `videojs.log.createLogger('player')` and then using that logger will log the following: 125 * ```js 126 * mylogger('foo'); 127 * // > VIDEOJS: player: foo 128 * ``` 129 * 130 * @param {string} name 131 * The name to add call the new logger 132 * @return {Object} 133 */ 134
vendor: 23,607 bytes, lines 135-972
135 log.createLogger = function (subname) { 136 return createLogger(name + ': ' + subname); 137 }; 138 /** 139 * Enumeration of available logging levels, where the keys are the level names 140 * and the values are `|`-separated strings containing logging methods allowed 141 * in that logging level. These strings are used to create a regular expression 142 * matching the function name being called. 143 * 144 * Levels provided by Video.js are: 145 * 146 * - `off`: Matches no calls. Any value that can be cast to `false` will have 147 * this effect. The most restrictive. 148 * - `all`: Matches only Video.js-provided functions (`debug`, `log`, 149 * `log.warn`, and `log.error`). 150 * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls. 151 * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls. 152 * - `warn`: Matches `log.warn` and `log.error` calls. 153 * - `error`: Matches only `log.error` calls. 154 * 155 * @type {Object} 156 */ 157 158 159 log.levels = { 160 all: 'debug|log|warn|error', 161 off: '', 162 debug: 'debug|log|warn|error', 163 info: 'log|warn|error', 164 warn: 'warn|error', 165 error: 'error', 166 DEFAULT: level 167 }; 168 /** 169 * Get or set the current logging level. 170 * 171 * If a string matching a key from {@link module:log.levels} is provided, acts 172 * as a setter. 173 * 174 * @param {string} [lvl] 175 * Pass a valid level to set a new logging level. 176 * 177 * @return {string} 178 * The current logging level. 179 */ 180 181 log.level = function (lvl) { 182 if (typeof lvl === 'string') { 183 if (!log.levels.hasOwnProperty(lvl)) { 184 throw new Error("\"" + lvl + "\" in not a valid log level"); 185 } 186 187 level = lvl; 188 } 189 190 return level; 191 }; 192 /** 193 * Returns an array containing everything that has been logged to the history. 194 * 195 * This array is a shallow clone of the internal history record. However, its 196 * contents are _not_ cloned; so, mutating objects inside this array will 197 * mutate them in history. 198 * 199 * @return {Array} 200 */ 201 202 203 log.history = function () { 204 return history ? [].concat(history) : []; 205 }; 206 /** 207 * Allows you to filter the history by the given logger name 208 * 209 * @param {string} fname 210 * The name to filter by 211 * 212 * @return {Array} 213 * The filtered list to return 214 */ 215 216 217 log.history.filter = function (fname) { 218 return (history || []).filter(function (historyItem) { 219 // if the first item in each historyItem includes `fname`, then it's a match 220 return new RegExp(".*" + fname + ".*").test(historyItem[0]); 221 }); 222 }; 223 /** 224 * Clears the internal history tracking, but does not prevent further history 225 * tracking. 226 */ 227 228 229 log.history.clear = function () { 230 if (history) { 231 history.length = 0; 232 } 233 }; 234 /** 235 * Disable history tracking if it is currently enabled. 236 */ 237 238 239 log.history.disable = function () { 240 if (history !== null) { 241 history.length = 0; 242 history = null; 243 } 244 }; 245 /** 246 * Enable history tracking if it is currently disabled. 247 */ 248 249 250 log.history.enable = function () { 251 if (history === null) { 252 history = []; 253 } 254 }; 255 /** 256 * Logs error messages. Similar to `console.error`. 257 * 258 * @param {Mixed[]} args 259 * One or more messages or objects that should be logged as an error 260 */ 261 262 263 log.error = function () { 264 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 265 args[_key2] = arguments[_key2]; 266 } 267 268 return logByType('error', level, args); 269 }; 270 /** 271 * Logs warning messages. Similar to `console.warn`. 272 * 273 * @param {Mixed[]} args 274 * One or more messages or objects that should be logged as a warning. 275 */ 276 277 278 log.warn = function () { 279 for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 280 args[_key3] = arguments[_key3]; 281 } 282 283 return logByType('warn', level, args); 284 }; 285 /** 286 * Logs debug messages. Similar to `console.debug`, but may also act as a comparable 287 * log if `console.debug` is not available 288 * 289 * @param {Mixed[]} args 290 * One or more messages or objects that should be logged as debug. 291 */ 292 293 294 log.debug = function () { 295 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 296 args[_key4] = arguments[_key4]; 297 } 298 299 return logByType('debug', level, args); 300 }; 301 302 return log; 303 } 304 305 /** 306 * @file log.js 307 * @module log 308 */ 309 var log = createLogger('VIDEOJS'); 310 var createLogger$1 = log.createLogger; 311 312 /** 313 * @file obj.js 314 * @module obj 315 */ 316 317 /** 318 * @callback obj:EachCallback 319 * 320 * @param {Mixed} value 321 * The current key for the object that is being iterated over. 322 * 323 * @param {string} key 324 * The current key-value for object that is being iterated over 325 */ 326 327 /** 328 * @callback obj:ReduceCallback 329 * 330 * @param {Mixed} accum 331 * The value that is accumulating over the reduce loop. 332 * 333 * @param {Mixed} value 334 * The current key for the object that is being iterated over. 335 * 336 * @param {string} key 337 * The current key-value for object that is being iterated over 338 * 339 * @return {Mixed} 340 * The new accumulated value. 341 */ 342 var toString = Object.prototype.toString; 343 /** 344 * Get the keys of an Object 345 * 346 * @param {Object} 347 * The Object to get the keys from 348 * 349 * @return {string[]} 350 * An array of the keys from the object. Returns an empty array if the 351 * object passed in was invalid or had no keys. 352 * 353 * @private 354 */ 355 356 var keys = function keys(object) { 357 return isObject(object) ? Object.keys(object) : []; 358 }; 359 /** 360 * Array-like iteration for objects. 361 * 362 * @param {Object} object 363 * The object to iterate over 364 * 365 * @param {obj:EachCallback} fn 366 * The callback function which is called for each key in the object. 367 */ 368 369 370 function each(object, fn) { 371 keys(object).forEach(function (key) { 372 return fn(object[key], key); 373 }); 374 } 375 /** 376 * Array-like reduce for objects. 377 * 378 * @param {Object} object 379 * The Object that you want to reduce. 380 * 381 * @param {Function} fn 382 * A callback function which is called for each key in the object. It 383 * receives the accumulated value and the per-iteration value and key 384 * as arguments. 385 * 386 * @param {Mixed} [initial = 0] 387 * Starting value 388 * 389 * @return {Mixed} 390 * The final accumulated value. 391 */ 392 393 function reduce(object, fn, initial) { 394 if (initial === void 0) { 395 initial = 0; 396 } 397 398 return keys(object).reduce(function (accum, key) { 399 return fn(accum, object[key], key); 400 }, initial); 401 } 402 /** 403 * Object.assign-style object shallow merge/extend. 404 * 405 * @param {Object} target 406 * @param {Object} ...sources 407 * @return {Object} 408 */ 409 410 function assign(target) { 411 for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 412 sources[_key - 1] = arguments[_key]; 413 } 414 415 if (Object.assign) { 416 return Object.assign.apply(Object, [target].concat(sources)); 417 } 418 419 sources.forEach(function (source) { 420 if (!source) { 421 return; 422 } 423 424 each(source, function (value, key) { 425 target[key] = value; 426 }); 427 }); 428 return target; 429 } 430 /** 431 * Returns whether a value is an object of any kind - including DOM nodes, 432 * arrays, regular expressions, etc. Not functions, though. 433 * 434 * This avoids the gotcha where using `typeof` on a `null` value 435 * results in `'object'`. 436 * 437 * @param {Object} value 438 * @return {boolean} 439 */ 440 441 function isObject(value) { 442 return !!value && typeof value === 'object'; 443 } 444 /** 445 * Returns whether an object appears to be a "plain" object - that is, a 446 * direct instance of `Object`. 447 * 448 * @param {Object} value 449 * @return {boolean} 450 */ 451 452 function isPlain(value) { 453 return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object; 454 } 455 456 /** 457 * @file computed-style.js 458 * @module computed-style 459 */ 460 /** 461 * A safe getComputedStyle. 462 * 463 * This is needed because in Firefox, if the player is loaded in an iframe with 464 * `display:none`, then `getComputedStyle` returns `null`, so, we do a 465 * null-check to make sure that the player doesn't break in these cases. 466 * 467 * @function 468 * @param {Element} el 469 * The element you want the computed style of 470 * 471 * @param {string} prop 472 * The property name you want 473 * 474 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 475 */ 476 477 function computedStyle(el, prop) { 478 if (!el || !prop) { 479 return ''; 480 } 481 482 if (typeof window$1.getComputedStyle === 'function') { 483 var computedStyleValue = window$1.getComputedStyle(el); 484 return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : ''; 485 } 486 487 return ''; 488 } 489 490 /** 491 * @file dom.js 492 * @module dom 493 */ 494 /** 495 * Detect if a value is a string with any non-whitespace characters. 496 * 497 * @private 498 * @param {string} str 499 * The string to check 500 * 501 * @return {boolean} 502 * Will be `true` if the string is non-blank, `false` otherwise. 503 * 504 */ 505 506 function isNonBlankString(str) { 507 return typeof str === 'string' && /\S/.test(str); 508 } 509 /** 510 * Throws an error if the passed string has whitespace. This is used by 511 * class methods to be relatively consistent with the classList API. 512 * 513 * @private 514 * @param {string} str 515 * The string to check for whitespace. 516 * 517 * @throws {Error} 518 * Throws an error if there is whitespace in the string. 519 */ 520 521 522 function throwIfWhitespace(str) { 523 if (/\s/.test(str)) { 524 throw new Error('class has illegal whitespace characters'); 525 } 526 } 527 /** 528 * Produce a regular expression for matching a className within an elements className. 529 * 530 * @private 531 * @param {string} className 532 * The className to generate the RegExp for. 533 * 534 * @return {RegExp} 535 * The RegExp that will check for a specific `className` in an elements 536 * className. 537 */ 538 539 540 function classRegExp(className) { 541 return new RegExp('(^|\\s)' + className + '($|\\s)'); 542 } 543 /** 544 * Whether the current DOM interface appears to be real (i.e. not simulated). 545 * 546 * @return {boolean} 547 * Will be `true` if the DOM appears to be real, `false` otherwise. 548 */ 549 550 551 function isReal() { 552 // Both document and window will never be undefined thanks to `global`. 553 return document === window$1.document; 554 } 555 /** 556 * Determines, via duck typing, whether or not a value is a DOM element. 557 * 558 * @param {Mixed} value 559 * The value to check. 560 * 561 * @return {boolean} 562 * Will be `true` if the value is a DOM element, `false` otherwise. 563 */ 564 565 function isEl(value) { 566 return isObject(value) && value.nodeType === 1; 567 } 568 /** 569 * Determines if the current DOM is embedded in an iframe. 570 * 571 * @return {boolean} 572 * Will be `true` if the DOM is embedded in an iframe, `false` 573 * otherwise. 574 */ 575 576 function isInFrame() { 577 // We need a try/catch here because Safari will throw errors when attempting 578 // to get either `parent` or `self` 579 try { 580 return window$1.parent !== window$1.self; 581 } catch (x) { 582 return true; 583 } 584 } 585 /** 586 * Creates functions to query the DOM using a given method. 587 * 588 * @private 589 * @param {string} method 590 * The method to create the query with. 591 * 592 * @return {Function} 593 * The query method 594 */ 595 596 function createQuerier(method) { 597 return function (selector, context) { 598 if (!isNonBlankString(selector)) { 599 return document[method](null); 600 } 601 602 if (isNonBlankString(context)) { 603 context = document.querySelector(context); 604 } 605 606 var ctx = isEl(context) ? context : document; 607 return ctx[method] && ctx[method](selector); 608 }; 609 } 610 /** 611 * Creates an element and applies properties, attributes, and inserts content. 612 * 613 * @param {string} [tagName='div'] 614 * Name of tag to be created. 615 * 616 * @param {Object} [properties={}] 617 * Element properties to be applied. 618 * 619 * @param {Object} [attributes={}] 620 * Element attributes to be applied. 621 * 622 * @param {module:dom~ContentDescriptor} content 623 * A content descriptor object. 624 * 625 * @return {Element} 626 * The element that was created. 627 */ 628 629 630 function createEl(tagName, properties, attributes, content) { 631 if (tagName === void 0) { 632 tagName = 'div'; 633 } 634 635 if (properties === void 0) { 636 properties = {}; 637 } 638 639 if (attributes === void 0) { 640 attributes = {}; 641 } 642 643 var el = document.createElement(tagName); 644 Object.getOwnPropertyNames(properties).forEach(function (propName) { 645 var val = properties[propName]; // See #2176 646 // We originally were accepting both properties and attributes in the 647 // same object, but that doesn't work so well. 648 649 if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { 650 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 + ".")); 651 el.setAttribute(propName, val); // Handle textContent since it's not supported everywhere and we have a 652 // method for it. 653 } else if (propName === 'textContent') { 654 textContent(el, val); 655 } else { 656 el[propName] = val; 657 } 658 }); 659 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 660 el.setAttribute(attrName, attributes[attrName]); 661 }); 662 663 if (content) { 664 appendContent(el, content); 665 } 666 667 return el; 668 } 669 /** 670 * Injects text into an element, replacing any existing contents entirely. 671 * 672 * @param {Element} el 673 * The element to add text content into 674 * 675 * @param {string} text 676 * The text content to add. 677 * 678 * @return {Element} 679 * The element with added text content. 680 */ 681 682 function textContent(el, text) { 683 if (typeof el.textContent === 'undefined') { 684 el.innerText = text; 685 } else { 686 el.textContent = text; 687 } 688 689 return el; 690 } 691 /** 692 * Insert an element as the first child node of another 693 * 694 * @param {Element} child 695 * Element to insert 696 * 697 * @param {Element} parent 698 * Element to insert child into 699 */ 700 701 function prependTo(child, parent) { 702 if (parent.firstChild) { 703 parent.insertBefore(child, parent.firstChild); 704 } else { 705 parent.appendChild(child); 706 } 707 } 708 /** 709 * Check if an element has a class name. 710 * 711 * @param {Element} element 712 * Element to check 713 * 714 * @param {string} classToCheck 715 * Class name to check for 716 * 717 * @return {boolean} 718 * Will be `true` if the element has a class, `false` otherwise. 719 * 720 * @throws {Error} 721 * Throws an error if `classToCheck` has white space. 722 */ 723 724 function hasClass(element, classToCheck) { 725 throwIfWhitespace(classToCheck); 726 727 if (element.classList) { 728 return element.classList.contains(classToCheck); 729 } 730 731 return classRegExp(classToCheck).test(element.className); 732 } 733 /** 734 * Add a class name to an element. 735 * 736 * @param {Element} element 737 * Element to add class name to. 738 * 739 * @param {string} classToAdd 740 * Class name to add. 741 * 742 * @return {Element} 743 * The DOM element with the added class name. 744 */ 745 746 function addClass(element, classToAdd) { 747 if (element.classList) { 748 element.classList.add(classToAdd); // Don't need to `throwIfWhitespace` here because `hasElClass` will do it 749 // in the case of classList not being supported. 750 } else if (!hasClass(element, classToAdd)) { 751 element.className = (element.className + ' ' + classToAdd).trim(); 752 } 753 754 return element; 755 } 756 /** 757 * Remove a class name from an element. 758 * 759 * @param {Element} element 760 * Element to remove a class name from. 761 * 762 * @param {string} classToRemove 763 * Class name to remove 764 * 765 * @return {Element} 766 * The DOM element with class name removed. 767 */ 768 769 function removeClass(element, classToRemove) { 770 if (element.classList) { 771 element.classList.remove(classToRemove); 772 } else { 773 throwIfWhitespace(classToRemove); 774 element.className = element.className.split(/\s+/).filter(function (c) { 775 return c !== classToRemove; 776 }).join(' '); 777 } 778 779 return element; 780 } 781 /** 782 * The callback definition for toggleClass. 783 * 784 * @callback module:dom~PredicateCallback 785 * @param {Element} element 786 * The DOM element of the Component. 787 * 788 * @param {string} classToToggle 789 * The `className` that wants to be toggled 790 * 791 * @return {boolean|undefined} 792 * If `true` is returned, the `classToToggle` will be added to the 793 * `element`. If `false`, the `classToToggle` will be removed from 794 * the `element`. If `undefined`, the callback will be ignored. 795 */ 796 797 /** 798 * Adds or removes a class name to/from an element depending on an optional 799 * condition or the presence/absence of the class name. 800 * 801 * @param {Element} element 802 * The element to toggle a class name on. 803 * 804 * @param {string} classToToggle 805 * The class that should be toggled. 806 * 807 * @param {boolean|module:dom~PredicateCallback} [predicate] 808 * See the return value for {@link module:dom~PredicateCallback} 809 * 810 * @return {Element} 811 * The element with a class that has been toggled. 812 */ 813 814 function toggleClass(element, classToToggle, predicate) { 815 // This CANNOT use `classList` internally because IE11 does not support the 816 // second parameter to the `classList.toggle()` method! Which is fine because 817 // `classList` will be used by the add/remove functions. 818 var has = hasClass(element, classToToggle); 819 820 if (typeof predicate === 'function') { 821 predicate = predicate(element, classToToggle); 822 } 823 824 if (typeof predicate !== 'boolean') { 825 predicate = !has; 826 } // If the necessary class operation matches the current state of the 827 // element, no action is required. 828 829 830 if (predicate === has) { 831 return; 832 } 833 834 if (predicate) { 835 addClass(element, classToToggle); 836 } else { 837 removeClass(element, classToToggle); 838 } 839 840 return element; 841 } 842 /** 843 * Apply attributes to an HTML element. 844 * 845 * @param {Element} el 846 * Element to add attributes to. 847 * 848 * @param {Object} [attributes] 849 * Attributes to be applied. 850 */ 851 852 function setAttributes(el, attributes) { 853 Object.getOwnPropertyNames(attributes).forEach(function (attrName) { 854 var attrValue = attributes[attrName]; 855 856 if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { 857 el.removeAttribute(attrName); 858 } else { 859 el.setAttribute(attrName, attrValue === true ? '' : attrValue); 860 } 861 }); 862 } 863 /** 864 * Get an element's attribute values, as defined on the HTML tag. 865 * 866 * Attributes are not the same as properties. They're defined on the tag 867 * or with setAttribute. 868 * 869 * @param {Element} tag 870 * Element from which to get tag attributes. 871 * 872 * @return {Object} 873 * All attributes of the element. Boolean attributes will be `true` or 874 * `false`, others will be strings. 875 */ 876 877 function getAttributes(tag) { 878 var obj = {}; // known boolean attributes 879 // we can check for matching boolean properties, but not all browsers 880 // and not all tags know about these attributes, so, we still want to check them manually 881 882 var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ','; 883 884 if (tag && tag.attributes && tag.attributes.length > 0) { 885 var attrs = tag.attributes; 886 887 for (var i = attrs.length - 1; i >= 0; i--) { 888 var attrName = attrs[i].name; 889 var attrVal = attrs[i].value; // check for known booleans 890 // the matching element property will return a value for typeof 891 892 if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { 893 // the value of an included boolean attribute is typically an empty 894 // string ('') which would equal false if we just check for a false value. 895 // we also don't want support bad code like autoplay='false' 896 attrVal = attrVal !== null ? true : false; 897 } 898 899 obj[attrName] = attrVal; 900 } 901 } 902 903 return obj; 904 } 905 /** 906 * Get the value of an element's attribute. 907 * 908 * @param {Element} el 909 * A DOM element. 910 * 911 * @param {string} attribute 912 * Attribute to get the value of. 913 * 914 * @return {string} 915 * The value of the attribute. 916 */ 917 918 function getAttribute(el, attribute) { 919 return el.getAttribute(attribute); 920 } 921 /** 922 * Set the value of an element's attribute. 923 * 924 * @param {Element} el 925 * A DOM element. 926 * 927 * @param {string} attribute 928 * Attribute to set. 929 * 930 * @param {string} value 931 * Value to set the attribute to. 932 */ 933 934 function setAttribute(el, attribute, value) { 935 el.setAttribute(attribute, value); 936 } 937 /** 938 * Remove an element's attribute. 939 * 940 * @param {Element} el 941 * A DOM element. 942 * 943 * @param {string} attribute 944 * Attribute to remove. 945 */ 946 947 function removeAttribute(el, attribute) { 948 el.removeAttribute(attribute); 949 } 950 /** 951 * Attempt to block the ability to select text. 952 */ 953 954 function blockTextSelection() { 955 document.body.focus(); 956 957 document.onselectstart = function () { 958 return false; 959 }; 960 } 961 /** 962 * Turn off text selection blocking. 963 */ 964 965 function unblockTextSelection() { 966 document.onselectstart = function () { 967 return true; 968 }; 969 } 970 /** 971 * Identical to the native `getBoundingClientRect` function, but ensures that 972 * the method is supported at all (it is in all browsers we claim to supp
972ort) 973 * and that the element is in the DOM before continuing. 974 * 975 * This wrapper function also shims properties which are not provided by some 976 * older browsers (namely, IE8). 977 * 978 * Additionally, some browsers do not support adding properties to a 979 * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard 980 * properties (except `x` and `y` which are not widely supported). This helps 981 * avoid implementations where keys are non-enumerable. 982 * 983 * @param {Element} el 984 * Element whose `ClientRect` we want to calculate. 985 * 986 * @return {Object|undefined} 987 * Always returns a plain object - or `undefined` if it cannot. 988 */ 989 990 function getBoundingClientRect(el) { 991 if (el && el.getBoundingClientRect && el.parentNode) { 992 var rect = el.getBoundingClientRect(); 993 var result = {}; 994 ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { 995 if (rect[k] !== undefined) { 996 result[k] = rect[k]; 997 } 998 }); 999 1000 if (!result.height) { 1001 result.height = parseFloat(computedStyle(el, 'height')); 1002 } 1003 1004 if (!result.width) { 1005 result.width = parseFloat(computedStyle(el, 'width')); 1006 } 1007 1008 return result; 1009 } 1010 } 1011 /** 1012 * Represents the position of a DOM element on the page. 1013 * 1014 * @typedef {Object} module:dom~Position 1015 * 1016 * @property {number} left 1017 * Pixels to the left. 1018 * 1019 * @property {number} top 1020 * Pixels from the top. 1021 */ 1022 1023 /** 1024 * Get the position of an element in the DOM. 1025 * 1026 * Uses `getBoundingClientRect` technique from John Resig. 1027 * 1028 * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ 1029 * 1030 * @param {Element} el 1031 * Element from which to get offset. 1032 * 1033 * @return {module:dom~Position} 1034 * The position of the element that was passed in. 1035 */ 1036 1037 function findPosition(el) { 1038 var box; 1039 1040 if (el.getBoundingClientRect && el.parentNode) { 1041 box = el.getBoundingClientRect(); 1042 } 1043 1044 if (!box) { 1045 return { 1046 left: 0, 1047 top: 0 1048 }; 1049 } 1050 1051 var docEl = document.documentElement; 1052 var body = document.body; 1053 var clientLeft = docEl.clientLeft || body.clientLeft || 0; 1054 var scrollLeft = window$1.pageXOffset || body.scrollLeft; 1055 var left = box.left + scrollLeft - clientLeft; 1056 var clientTop = docEl.clientTop || body.clientTop || 0; 1057 var scrollTop = window$1.pageYOffset || body.scrollTop; 1058 var top = box.top + scrollTop - clientTop; // Android sometimes returns slightly off decimal values, so need to round 1059 1060 return { 1061 left: Math.round(left), 1062 top: Math.round(top) 1063 }; 1064 } 1065 /** 1066 * Represents x and y coordinates for a DOM element or mouse pointer. 1067 * 1068 * @typedef {Object} module:dom~Coordinates 1069 * 1070 * @property {number} x 1071 * x coordinate in pixels 1072 * 1073 * @property {number} y 1074 * y coordinate in pixels 1075 */ 1076 1077 /** 1078 * Get the pointer position within an element. 1079 * 1080 * The base on the coordinates are the bottom left of the element. 1081 * 1082 * @param {Element} el 1083 * Element on which to get the pointer position on. 1084 * 1085 * @param {EventTarget~Event} event 1086 * Event object. 1087 * 1088 * @return {module:dom~Coordinates} 1089 * A coordinates object corresponding to the mouse position. 1090 * 1091 */ 1092 1093 function getPointerPosition(el, event) { 1094 var position = {}; 1095 var box = findPosition(el); 1096 var boxW = el.offsetWidth; 1097 var boxH = el.offsetHeight; 1098 var boxY = box.top; 1099 var boxX = box.left; 1100 var pageY = event.pageY; 1101 var pageX = event.pageX; 1102 1103 if (event.changedTouches) { 1104 pageX = event.changedTouches[0].pageX; 1105 pageY = event.changedTouches[0].pageY; 1106 } 1107 1108 position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH)); 1109 position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); 1110 return position; 1111 } 1112 /** 1113 * Determines, via duck typing, whether or not a value is a text node. 1114 * 1115 * @param {Mixed} value 1116 * Check if this value is a text node. 1117 * 1118 * @return {boolean} 1119 * Will be `true` if the value is a text node, `false` otherwise. 1120 */ 1121 1122 function isTextNode(value) { 1123 return isObject(value) && value.nodeType === 3; 1124 } 1125 /** 1126 * Empties the contents of an element. 1127 * 1128 * @param {Element} el 1129 * The element to empty children from 1130 * 1131 * @return {Element} 1132 * The element with no children 1133 */ 1134 1135 function emptyEl(el) { 1136 while (el.firstChild) { 1137 el.removeChild(el.firstChild); 1138 } 1139 1140 return el; 1141 } 1142 /** 1143 * This is a mixed value that describes content to be injected into the DOM 1144 * via some method. It can be of the following types: 1145 * 1146 * Type | Description 1147 * -----------|------------- 1148 * `string` | The value will be normalized into a text node. 1149 * `Element` | The value will be accepted as-is. 1150 * `TextNode` | The value will be accepted as-is.
1151 * `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). 1152 * `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. 1153 * 1154 * @typedef {string|Element|TextNode|Array|Function} module:dom~ContentDescriptor 1155 */ 1156 1157 /** 1158 * Normalizes content for eventual insertion into the DOM. 1159 * 1160 * This allows a wide range of content definition methods, but helps protect 1161 * from falling into the trap of simply writing to `innerHTML`, which could 1162 * be an XSS concern. 1163 * 1164 * The content for an element can be passed in multiple types and 1165 * combinations, whose behavior is as follows: 1166 * 1167 * @param {module:dom~ContentDescriptor} content 1168 * A content descriptor value. 1169 * 1170 * @return {Array} 1171 * All of the content that was passed in, normalized to an array of 1172 * elements or text nodes. 1173 */ 1174 1175 function normalizeContent(content) { 1176 // First, invoke content if it is a function. If it produces an array, 1177 // that needs to happen before normalization. 1178 if (typeof content === 'function') { 1179 content = content(); 1180 } // Next up, normalize to an array, so one or many items can be normalized, 1181 // filtered, and returned. 1182 1183 1184 return (Array.isArray(content) ? content : [content]).map(function (value) { 1185 // First, invoke value if it is a function to produce a new value, 1186 // which will be subsequently normalized to a Node of some kind. 1187 if (typeof value === 'function') { 1188 value = value(); 1189 } 1190 1191 if (isEl(value) || isTextNode(value)) { 1192 return value; 1193 } 1194 1195 if (typeof value === 'string' && /\S/.test(value)) { 1196 return document.createTextNode(value); 1197 } 1198 }).filter(function (value) { 1199 return value; 1200 }); 1201 } 1202 /** 1203 * Normalizes and appends content to an element. 1204 * 1205 * @param {Element} el 1206 * Element to append normalized content to. 1207 * 1208 * @param {module:dom~ContentDescriptor} content 1209 * A content descriptor value. 1210 * 1211 * @return {Element} 1212 * The element with appended normalized content. 1213 */ 1214 1215 function appendContent(el, content) { 1216 normalizeContent(content).forEach(function (node) { 1217 return el.appendChild(node); 1218 }); 1219 return el; 1220 } 1221 /** 1222 * Normalizes and inserts content into an element; this is identical to 1223 * `appendContent()`, except it empties the element first. 1224 * 1225 * @param {Element} el 1226 * Element to insert normalized content into. 1227 * 1228 * @param {module:dom~ContentDescriptor} content 1229 * A content descriptor value. 1230 * 1231 * @return {Element} 1232 * The element with inserted normalized content. 1233 */ 1234 1235 function insertContent(el, content) { 1236 return appendContent(emptyEl(el), content); 1237 } 1238 /** 1239 * Check if an event was a single left click. 1240 * 1241 * @param {EventTarget~Event} event 1242 * Event object. 1243 * 1244 * @return {boolean} 1245 * Will be `true` if a single left click, `false` otherwise. 1246 */ 1247 1248 function isSingleLeftClick(event) { 1249 // Note: if you create something draggable, be sure to 1250 // call it on both `mousedown` and `mousemove` event, 1251 // otherwise `mousedown` should be enough for a button 1252 if (event.button === undefined && event.buttons === undefined) { 1253 // Why do we need `buttons` ? 1254 // Because, middle mouse sometimes have this: 1255 // e.button === 0 and e.buttons === 4 1256 // Furthermore, we want to prevent combination click, something like 1257 // HOLD middlemouse then left click, that would be 1258 // e.button === 0, e.buttons === 5 1259 // just `button` is not gonna work 1260 // Alright, then what this block does ? 1261 // this is for chrome `simulate mobile devices`
vendor: 23,646 bytes, lines 1262-2044
1262 // I want to support this as well 1263 return true; 1264 } 1265 1266 if (event.button === 0 && event.buttons === undefined) { 1267 // Touch screen, sometimes on some specific device, `buttons` 1268 // doesn't have anything (safari on ios, blackberry...) 1269 return true; 1270 } // `mouseup` event on a single left click has 1271 // `button` and `buttons` equal to 0 1272 1273 1274 if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) { 1275 return true; 1276 } 1277 1278 if (event.button !== 0 || event.buttons !== 1) { 1279 // This is the reason we have those if else block above 1280 // if any special case we can catch and let it slide 1281 // we do it above, when get to here, this definitely 1282 // is-not-left-click 1283 return false; 1284 } 1285 1286 return true; 1287 } 1288 /** 1289 * Finds a single DOM element matching `selector` within the optional 1290 * `context` of another DOM element (defaulting to `document`). 1291 * 1292 * @param {string} selector 1293 * A valid CSS selector, which will be passed to `querySelector`. 1294 * 1295 * @param {Element|String} [context=document] 1296 * A DOM element within which to query. Can also be a selector 1297 * string in which case the first matching element will be used 1298 * as context. If missing (or no element matches selector), falls 1299 * back to `document`. 1300 * 1301 * @return {Element|null} 1302 * The element that was found or null. 1303 */ 1304 1305 var $ = createQuerier('querySelector'); 1306 /** 1307 * Finds a all DOM elements matching `selector` within the optional 1308 * `context` of another DOM element (defaulting to `document`). 1309 * 1310 * @param {string} selector 1311 * A valid CSS selector, which will be passed to `querySelectorAll`. 1312 * 1313 * @param {Element|String} [context=document] 1314 * A DOM element within which to query. Can also be a selector 1315 * string in which case the first matching element will be used 1316 * as context. If missing (or no element matches selector), falls 1317 * back to `document`. 1318 * 1319 * @return {NodeList} 1320 * A element list of elements that were found. Will be empty if none 1321 * were found. 1322 * 1323 */ 1324 1325 var $$ = createQuerier('querySelectorAll'); 1326 1327 var Dom = /*#__PURE__*/Object.freeze({ 1328 isReal: isReal, 1329 isEl: isEl, 1330 isInFrame: isInFrame, 1331 createEl: createEl, 1332 textContent: textContent, 1333 prependTo: prependTo, 1334 hasClass: hasClass, 1335 addClass: addClass, 1336 removeClass: removeClass, 1337 toggleClass: toggleClass, 1338 setAttributes: setAttributes, 1339 getAttributes: getAttributes, 1340 getAttribute: getAttribute, 1341 setAttribute: setAttribute, 1342 removeAttribute: removeAttribute, 1343 blockTextSelection: blockTextSelection, 1344 unblockTextSelection: unblockTextSelection, 1345 getBoundingClientRect: getBoundingClientRect, 1346 findPosition: findPosition, 1347 getPointerPosition: getPointerPosition, 1348 isTextNode: isTextNode, 1349 emptyEl: emptyEl, 1350 normalizeContent: normalizeContent, 1351 appendContent: appendContent, 1352 insertContent: insertContent, 1353 isSingleLeftClick: isSingleLeftClick, 1354 $: $, 1355 $$: $$ 1356 }); 1357 1358 /** 1359 * @file setup.js - Functions for setting up a player without 1360 * user interaction based on the data-setup `attribute` of the video tag. 1361 * 1362 * @module setup 1363 */ 1364 var _windowLoaded = false; 1365 var videojs; 1366 /** 1367 * Set up any tags that have a data-setup `attribute` when the player is started. 1368 */ 1369 1370 var autoSetup = function autoSetup() { 1371 // Protect against breakage in non-browser environments and check global autoSetup option. 1372 if (!isReal() || videojs.options.autoSetup === false) { 1373 return; 1374 } 1375 1376 var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); 1377 var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); 1378 var divs = Array.prototype.slice.call(document.getElementsByTagName('video-js')); 1379 var mediaEls = vids.concat(audios, divs); // Check if any media elements exist 1380 1381 if (mediaEls && mediaEls.length > 0) { 1382 for (var i = 0, e = mediaEls.length; i < e; i++) { 1383 var mediaEl = mediaEls[i]; // Check if element exists, has getAttribute func. 1384 1385 if (mediaEl && mediaEl.getAttribute) { 1386 // Make sure this player hasn't already been set up. 1387 if (mediaEl.player === undefined) { 1388 var options = mediaEl.getAttribute('data-setup'); // Check if data-setup attr exists. 1389 // We only auto-setup if they've added the data-setup attr. 1390 1391 if (options !== null) { 1392 // Create new video.js instance. 1393 videojs(mediaEl); 1394 } 1395 } // If getAttribute isn't defined, we need to wait for the DOM. 1396 1397 } else { 1398 autoSetupTimeout(1); 1399 break; 1400 } 1401 } // No videos were found, so keep looping unless page is finished loading. 1402 1403 } else if (!_windowLoaded) { 1404 autoSetupTimeout(1); 1405 } 1406 }; 1407 /** 1408 * Wait until the page is loaded before running autoSetup. This will be called in 1409 * autoSetup if `hasLoaded` returns false. 1410 * 1411 * @param {number} wait 1412 * How long to wait in ms 1413 * 1414 * @param {module:videojs} [vjs] 1415 * The videojs library function 1416 */ 1417 1418 1419 function autoSetupTimeout(wait, vjs) { 1420 if (vjs) { 1421 videojs = vjs; 1422 } 1423 1424 window$1.setTimeout(autoSetup, wait); 1425 } 1426 /** 1427 * Used to set the internal tracking of window loaded state to true. 1428 * 1429 * @private 1430 */ 1431 1432 1433 function setWindowLoaded() { 1434 _windowLoaded = true; 1435 window$1.removeEventListener('load', setWindowLoaded); 1436 } 1437 1438 if (isReal()) { 1439 if (document.readyState === 'complete') { 1440 setWindowLoaded(); 1441 } else { 1442 /** 1443 * Listen for the load event on window, and set _windowLoaded to true. 1444 * 1445 * We use a standard event listener here to avoid incrementing the GUID 1446 * before any players are created. 1447 * 1448 * @listens load 1449 */ 1450 window$1.addEventListener('load', setWindowLoaded); 1451 } 1452 } 1453 1454 /** 1455 * @file stylesheet.js 1456 * @module stylesheet 1457 */ 1458 /** 1459 * Create a DOM syle element given a className for it. 1460 * 1461 * @param {string} className 1462 * The className to add to the created style element. 1463 * 1464 * @return {Element} 1465 * The element that was created. 1466 */ 1467 1468 var createStyleElement = function createStyleElement(className) { 1469 var style = document.createElement('style'); 1470 style.className = className; 1471 return style; 1472 }; 1473 /** 1474 * Add text to a DOM element. 1475 * 1476 * @param {Element} el 1477 * The Element to add text content to. 1478 * 1479 * @param {string} content 1480 * The text to add to the element. 1481 */ 1482 1483 var setTextContent = function setTextContent(el, content) { 1484 if (el.styleSheet) { 1485 el.styleSheet.cssText = content; 1486 } else { 1487 el.textContent = content; 1488 } 1489 }; 1490 1491 /** 1492 * @file dom-data.js 1493 * @module dom-data 1494 */ 1495 1496 /** 1497 * Element Data Store. 1498 * 1499 * Allows for binding data to an element without putting it directly on the 1500 * element. Ex. Event listeners are stored here. 1501 * (also from jsninja.com, slightly modified and updated for closure compiler) 1502 * 1503 * @type {Object} 1504 * @private 1505 */ 1506 var DomData = new WeakMap(); 1507 1508 /** 1509 * @file guid.js 1510 * @module guid 1511 */ 1512 // Default value for GUIDs. This allows us to reset the GUID counter in tests. 1513 // 1514 // The initial GUID is 3 because some users have come to rely on the first 1515 // default player ID ending up as `vjs_video_3`. 1516 // 1517 // See: https://github.com/videojs/video.js/pull/6216 1518 var _initialGuid = 3; 1519 /** 1520 * Unique ID for an element or function 1521 * 1522 * @type {Number} 1523 */ 1524 1525 var _guid = _initialGuid; 1526 /** 1527 * Get a unique auto-incrementing ID by number that has not been returned before. 1528 * 1529 * @return {number} 1530 * A new unique ID. 1531 */ 1532 1533 function newGUID() { 1534 return _guid++; 1535 } 1536 1537 /** 1538 * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) 1539 * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) 1540 * This should work very similarly to jQuery's events, however it's based off the book version which isn't as 1541 * robust as jquery's, so there's probably some differences. 1542 * 1543 * @file events.js 1544 * @module events 1545 */ 1546 /** 1547 * Clean up the listener cache and dispatchers 1548 * 1549 * @param {Element|Object} elem 1550 * Element to clean up 1551 * 1552 * @param {string} type 1553 * Type of event to clean up 1554 */ 1555 1556 function _cleanUpEvents(elem, type) { 1557 if (!DomData.has(elem)) { 1558 return; 1559 } 1560 1561 var data = DomData.get(elem); // Remove the events of a particular type if there are none left 1562 1563 if (data.handlers[type].length === 0) { 1564 delete data.handlers[type]; // data.handlers[type] = null; 1565 // Setting to null was causing an error with data.handlers 1566 // Remove the meta-handler from the element 1567 1568 if (elem.removeEventListener) { 1569 elem.removeEventListener(type, data.dispatcher, false); 1570 } else if (elem.detachEvent) { 1571 elem.detachEvent('on' + type, data.dispatcher); 1572 } 1573 } // Remove the events object if there are no types left 1574 1575 1576 if (Object.getOwnPropertyNames(data.handlers).length <= 0) { 1577 delete data.handlers; 1578 delete data.dispatcher; 1579 delete data.disabled; 1580 } // Finally remove the element data if there is no data left 1581 1582 1583 if (Object.getOwnPropertyNames(data).length === 0) { 1584 DomData["delete"](elem); 1585 } 1586 } 1587 /** 1588 * Loops through an array of event types and calls the requested method for each type. 1589 * 1590 * @param {Function} fn 1591 * The event method we want to use. 1592 * 1593 * @param {Element|Object} elem 1594 * Element or object to bind listeners to 1595 * 1596 * @param {string} type 1597 * Type of event to bind to. 1598 * 1599 * @param {EventTarget~EventListener} callback 1600 * Event listener. 1601 */ 1602 1603 1604 function _handleMultipleEvents(fn, elem, types, callback) { 1605 types.forEach(function (type) { 1606 // Call the event method for each one of the types 1607 fn(elem, type, callback); 1608 }); 1609 } 1610 /** 1611 * Fix a native event to have standard property values 1612 * 1613 * @param {Object} event 1614 * Event object to fix. 1615 * 1616 * @return {Object} 1617 * Fixed event object. 1618 */ 1619 1620 1621 function fixEvent(event) { 1622 function returnTrue() { 1623 return true; 1624 } 1625 1626 function returnFalse() { 1627 return false; 1628 } // Test if fixing up is needed 1629 // Used to check if !event.stopPropagation instead of isPropagationStopped 1630 // But native events return true for stopPropagation, but don't have 1631 // other expected methods like isPropagationStopped. Seems to be a problem 1632 // with the Javascript Ninja code. So we're just overriding all events now. 1633 1634 1635 if (!event || !event.isPropagationStopped) { 1636 var old = event || window$1.event; 1637 event = {}; // Clone the old object so that we can modify the values event = {}; 1638 // IE8 Doesn't like when you mess with native event properties 1639 // Firefox returns false for event.hasOwnProperty('type') and other props 1640 // which makes copying more difficult. 1641 // TODO: Probably best to create a whitelist of event props 1642 1643 for (var key in old) { 1644 // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y 1645 // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation 1646 // and webkitMovementX/Y 1647 if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { 1648 // Chrome 32+ warns if you try to copy deprecated returnValue, but 1649 // we still want to if preventDefault isn't supported (IE8). 1650 if (!(key === 'returnValue' && old.preventDefault)) { 1651 event[key] = old[key]; 1652 } 1653 } 1654 } // The event occurred on this element 1655 1656 1657 if (!event.target) { 1658 event.target = event.srcElement || document; 1659 } // Handle which other element the event is related to 1660 1661 1662 if (!event.relatedTarget) { 1663 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 1664 } // Stop the default browser action 1665 1666 1667 event.preventDefault = function () { 1668 if (old.preventDefault) { 1669 old.preventDefault(); 1670 } 1671 1672 event.returnValue = false; 1673 old.returnValue = false; 1674 event.defaultPrevented = true; 1675 }; 1676 1677 event.defaultPrevented = false; // Stop the event from bubbling 1678 1679 event.stopPropagation = function () { 1680 if (old.stopPropagation) { 1681 old.stopPropagation(); 1682 } 1683 1684 event.cancelBubble = true; 1685 old.cancelBubble = true; 1686 event.isPropagationStopped = returnTrue; 1687 }; 1688 1689 event.isPropagationStopped = returnFalse; // Stop the event from bubbling and executing other handlers 1690 1691 event.stopImmediatePropagation = function () { 1692 if (old.stopImmediatePropagation) { 1693 old.stopImmediatePropagation(); 1694 } 1695 1696 event.isImmediatePropagationStopped = returnTrue; 1697 event.stopPropagation(); 1698 }; 1699 1700 event.isImmediatePropagationStopped = returnFalse; // Handle mouse position 1701 1702 if (event.clientX !== null && event.clientX !== undefined) { 1703 var doc = document.documentElement; 1704 var body = document.body; 1705 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 1706 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 1707 } // Handle key presses 1708 1709 1710 event.which = event.charCode || event.keyCode; // Fix button for mouse clicks: 1711 // 0 == left; 1 == middle; 2 == right 1712 1713 if (event.button !== null && event.button !== undefined) { 1714 // The following is disabled because it does not pass videojs-standard 1715 // and... yikes. 1716 1717 /* eslint-disable */ 1718 event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; 1719 /* eslint-enable */ 1720 } 1721 } // Returns fixed-up instance 1722 1723 1724 return event; 1725 } 1726 /** 1727 * Whether passive event listeners are supported 1728 */ 1729 1730 var _supportsPassive = false; 1731 1732 (function () { 1733 try { 1734 var opts = Object.defineProperty({}, 'passive', { 1735 get: function get() { 1736 _supportsPassive = true; 1737 } 1738 }); 1739 window$1.addEventListener('test', null, opts); 1740 window$1.removeEventListener('test', null, opts); 1741 } catch (e) {// disregard 1742 } 1743 })(); 1744 /** 1745 * Touch events Chrome expects to be passive 1746 */ 1747 1748 1749 var passiveEvents = ['touchstart', 'touchmove']; 1750 /** 1751 * Add an event listener to element 1752 * It stores the handler function in a separate cache object 1753 * and adds a generic handler to the element's event, 1754 * along with a unique id (guid) to the element. 1755 * 1756 * @param {Element|Object} elem 1757 * Element or object to bind listeners to 1758 * 1759 * @param {string|string[]} type 1760 * Type of event to bind to. 1761 * 1762 * @param {EventTarget~EventListener} fn 1763 * Event listener. 1764 */ 1765 1766 function on(elem, type, fn) { 1767 if (Array.isArray(type)) { 1768 return _handleMultipleEvents(on, elem, type, fn); 1769 } 1770 1771 if (!DomData.has(elem)) { 1772 DomData.set(elem, {}); 1773 } 1774 1775 var data = DomData.get(elem); // We need a place to store all our handler data 1776 1777 if (!data.handlers) { 1778 data.handlers = {}; 1779 } 1780 1781 if (!data.handlers[type]) { 1782 data.handlers[type] = []; 1783 } 1784 1785 if (!fn.guid) { 1786 fn.guid = newGUID(); 1787 } 1788 1789 data.handlers[type].push(fn); 1790 1791 if (!data.dispatcher) { 1792 data.disabled = false; 1793 1794 data.dispatcher = function (event, hash) { 1795 if (data.disabled) { 1796 return; 1797 } 1798 1799 event = fixEvent(event); 1800 var handlers = data.handlers[event.type]; 1801 1802 if (handlers) { 1803 // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. 1804 var handlersCopy = handlers.slice(0); 1805 1806 for (var m = 0, n = handlersCopy.length; m < n; m++) { 1807 if (event.isImmediatePropagationStopped()) { 1808 break; 1809 } else { 1810 try { 1811 handlersCopy[m].call(elem, event, hash); 1812 } catch (e) { 1813 log.error(e); 1814 } 1815 } 1816 } 1817 } 1818 }; 1819 } 1820 1821 if (data.handlers[type].length === 1) { 1822 if (elem.addEventListener) { 1823 var options = false; 1824 1825 if (_supportsPassive && passiveEvents.indexOf(type) > -1) { 1826 options = { 1827 passive: true 1828 }; 1829 } 1830 1831 elem.addEventListener(type, data.dispatcher, options); 1832 } else if (elem.attachEvent) { 1833 elem.attachEvent('on' + type, data.dispatcher); 1834 } 1835 } 1836 } 1837 /** 1838 * Removes event listeners from an element 1839 * 1840 * @param {Element|Object} elem 1841 * Object to remove listeners from. 1842 * 1843 * @param {string|string[]} [type] 1844 * Type of listener to remove. Don't include to remove all events from element. 1845 * 1846 * @param {EventTarget~EventListener} [fn] 1847 * Specific listener to remove. Don't include to remove listeners for an event 1848 * type. 1849 */ 1850 1851 function off(elem, type, fn) { 1852 // Don't want to add a cache object through getElData if not needed 1853 if (!DomData.has(elem)) { 1854 return; 1855 } 1856 1857 var data = DomData.get(elem); // If no events exist, nothing to unbind 1858 1859 if (!data.handlers) { 1860 return; 1861 } 1862 1863 if (Array.isArray(type)) { 1864 return _handleMultipleEvents(off, elem, type, fn); 1865 } // Utility function 1866 1867 1868 var removeType = function removeType(el, t) { 1869 data.handlers[t] = []; 1870 1871 _cleanUpEvents(el, t); 1872 }; // Are we removing all bound events? 1873 1874 1875 if (type === undefined) { 1876 for (var t in data.handlers) { 1877 if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) { 1878 removeType(elem, t); 1879 } 1880 } 1881 1882 return; 1883 } 1884 1885 var handlers = data.handlers[type]; // If no handlers exist, nothing to unbind 1886 1887 if (!handlers) { 1888 return; 1889 } // If no listener was provided, remove all listeners for type 1890 1891 1892 if (!fn) { 1893 removeType(elem, type); 1894 return; 1895 } // We're only removing a single handler 1896 1897 1898 if (fn.guid) { 1899 for (var n = 0; n < handlers.length; n++) { 1900 if (handlers[n].guid === fn.guid) { 1901 handlers.splice(n--, 1); 1902 } 1903 } 1904 } 1905 1906 _cleanUpEvents(elem, type); 1907 } 1908 /** 1909 * Trigger an event for an element 1910 * 1911 * @param {Element|Object} elem 1912 * Element to trigger an event on 1913 * 1914 * @param {EventTarget~Event|string} event 1915 * A string (the type) or an event object with a type attribute 1916 * 1917 * @param {Object} [hash] 1918 * data hash to pass along with the event 1919 * 1920 * @return {boolean|undefined} 1921 * Returns the opposite of `defaultPrevented` if default was 1922 * prevented. Otherwise, returns `undefined` 1923 */ 1924 1925 function trigger(elem, event, hash) { 1926 // Fetches element data and a reference to the parent (for bubbling). 1927 // Don't want to add a data object to cache for every parent, 1928 // so checking hasElData first. 1929 var elemData = DomData.has(elem) ? DomData.get(elem) : {}; 1930 var parent = elem.parentNode || elem.ownerDocument; // type = event.type || event, 1931 // handler; 1932 // If an event name was passed as a string, creates an event out of it 1933 1934 if (typeof event === 'string') { 1935 event = { 1936 type: event, 1937 target: elem 1938 }; 1939 } else if (!event.target) { 1940 event.target = elem; 1941 } // Normalizes the event properties. 1942 1943 1944 event = fixEvent(event); // If the passed element has a dispatcher, executes the established handlers. 1945 1946 if (elemData.dispatcher) { 1947 elemData.dispatcher.call(elem, event, hash); 1948 } // Unless explicitly stopped or the event does not bubble (e.g. media events) 1949 // recursively calls this function to bubble the event up the DOM. 1950 1951 1952 if (parent && !event.isPropagationStopped() && event.bubbles === true) { 1953 trigger.call(null, parent, event, hash); // If at the top of the DOM, triggers the default action unless disabled. 1954 } else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) { 1955 if (!DomData.has(event.target)) { 1956 DomData.set(event.target, {}); 1957 } 1958 1959 var targetData = DomData.get(event.target); // Checks if the target has a default action for this event. 1960 1961 if (event.target[event.type]) { 1962 // Temporarily disables event dispatching on the target as we have already executed the handler. 1963 targetData.disabled = true; // Executes the default action. 1964 1965 if (typeof event.target[event.type] === 'function') { 1966 event.target[event.type](); 1967 } // Re-enables event dispatching. 1968 1969 1970 targetData.disabled = false; 1971 } 1972 } // Inform the triggerer if the default was prevented by returning false 1973 1974 1975 return !event.defaultPrevented; 1976 } 1977 /** 1978 * Trigger a listener only once for an event. 1979 * 1980 * @param {Element|Object} elem 1981 * Element or object to bind to. 1982 * 1983 * @param {string|string[]} type 1984 * Name/type of event 1985 * 1986 * @param {Event~EventListener} fn 1987 * Event listener function 1988 */ 1989 1990 function one(elem, type, fn) { 1991 if (Array.isArray(type)) { 1992 return _handleMultipleEvents(one, elem, type, fn); 1993 } 1994 1995 var func = function func() { 1996 off(elem, type, func); 1997 fn.apply(this, arguments); 1998 }; // copy the guid to the new function so it can removed using the original function's ID 1999 2000 2001 func.guid = fn.guid = fn.guid || newGUID(); 2002 on(elem, type, func); 2003 } 2004 /** 2005 * Trigger a listener only once and then turn if off for all 2006 * configured events 2007 * 2008 * @param {Element|Object} elem 2009 * Element or object to bind to. 2010 * 2011 * @param {string|string[]} type 2012 * Name/type of event 2013 * 2014 * @param {Event~EventListener} fn 2015 * Event listener function 2016 */ 2017 2018 function any(elem, type, fn) { 2019 var func = function func() { 2020 off(elem, type, func); 2021 fn.apply(this, arguments); 2022 }; // copy the guid to the new function so it can removed using the original function's ID 2023 2024 2025 func.guid = fn.guid = fn.guid || newGUID(); // multiple ons, but one off for everything 2026 2027 on(elem, type, func); 2028 } 2029 2030 var Events = /*#__PURE__*/Object.freeze({ 2031 fixEvent: fixEvent, 2032 on: on, 2033 off: off, 2034 trigger: trigger, 2035 one: one, 2036 any: any 2037 }); 2038 2039 /** 2040 * @file fn.js 2041 * @module fn 2042 */ 2043 var UPDATE_REFRESH_INTERVAL = 30; 2044 /**
2045 * Bind (a.k.a proxy or context). A simple method for changing the context of 2046 * a function. 2047 * 2048 * It also stores a unique id on the function so it can be easily removed from 2049 * events. 2050 * 2051 * @function 2052 * @param {Mixed} context 2053 * The object to bind as scope. 2054 * 2055 * @param {Function} fn 2056 * The function to be bound to a scope. 2057 * 2058 * @param {number} [uid] 2059 * An optional unique ID for the function to be set 2060 * 2061 * @return {Function} 2062 * The new function that will be bound into the context given 2063 */ 2064 2065 var bind = function bind(context, fn, uid) { 2066 // Make sure the function has a unique ID 2067 if (!fn.guid) { 2068 fn.guid = newGUID(); 2069 } // Create the new function that changes the context 2070 2071 2072 var bound = fn.bind(context); // Allow for the ability to individualize this function 2073 // Needed in the case where multiple objects might share the same prototype 2074 // IF both items add an event listener with the same function, then you try to remove just one 2075 // it will remove both because they both have the same guid. 2076 // when using this, you need to use the bind method when you remove the listener as well. 2077 // currently used in text tracks 2078 2079 bound.guid = uid ? uid + '_' + fn.guid : fn.guid; 2080 return bound; 2081 }; 2082 /** 2083 * Wraps the given function, `fn`, with a new function that only invokes `fn` 2084 * at most once per every `wait` milliseconds. 2085 * 2086 * @function 2087 * @param {Function} fn 2088 * The function to be throttled. 2089 * 2090 * @param {number} wait 2091 * The number of milliseconds by which to throttle. 2092 * 2093 * @return {Function} 2094 */ 2095 2096 var throttle = function throttle(fn, wait) { 2097 var last = window$1.performance.now(); 2098 2099 var throttled = function throttled() { 2100 var now = window$1.performance.now(); 2101 2102 if (now - last >= wait) { 2103 fn.apply(void 0, arguments); 2104 last = now; 2105 } 2106 }; 2107 2108 return throttled; 2109 }; 2110 /** 2111 * Creates a debounced function that delays invoking `func` until after `wait` 2112 * milliseconds have elapsed since the last time the debounced function was 2113 * invoked. 2114 * 2115 * Inspired by lodash and underscore implementations. 2116 * 2117 * @function 2118 * @param {Function} func 2119 * The function to wrap with debounce behavior. 2120 * 2121 * @param {number} wait 2122 * The number of milliseconds to wait after the last invocation. 2123 * 2124 * @param {boolean} [immediate] 2125 * Whether or not to invoke the function immediately upon creation. 2126 * 2127 * @param {Object} [context=window] 2128 * The "context" in which the debounced function should debounce. For 2129 * example, if this function should be tied to a Video.js player, 2130 * the player can be passed here. Alternatively, defaults to the 2131 * global `window` object. 2132 * 2133 * @return {Function} 2134 * A debounced function. 2135 */ 2136 2137 var debounce = function debounce(func, wait, immediate, context) { 2138 if (context === void 0) { 2139 context = window$1; 2140 } 2141 2142 var timeout; 2143 2144 var cancel = function cancel() { 2145 context.clearTimeout(timeout); 2146 timeout = null; 2147 }; 2148 /* eslint-disable consistent-this */ 2149 2150 2151 var debounced = function debounced() { 2152 var self = this; 2153 var args = arguments; 2154 2155 var _later = function later() { 2156 timeout = null; 2157 _later = null; 2158 2159 if (!immediate) { 2160 func.apply(self, args); 2161 } 2162 }; 2163 2164 if (!timeout && immediate) { 2165 func.apply(self, args); 2166 } 2167 2168 context.clearTimeout(timeout); 2169 timeout = context.setTimeout(_later, wait); 2170 }; 2171 /* eslint-enable consistent-this */ 2172 2173 2174 debounced.cancel = cancel; 2175 return debounced; 2176 }; 2177 2178 /** 2179 * @file src/js/event-target.js 2180 */ 2181 /** 2182 * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It 2183 * adds shorthand functions that wrap around lengthy functions. For example: 2184 * the `on` function is a wrapper around `addEventListener`. 2185 * 2186 * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} 2187 * @class EventTarget 2188 */ 2189 2190 var EventTarget = function EventTarget() {}; 2191 /** 2192 * A Custom DOM event. 2193 * 2194 * @typedef {Object} EventTarget~Event 2195 * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} 2196 */ 2197 2198 /** 2199 * All event listeners should follow the following format. 2200 * 2201 * @callback EventTarget~EventListener 2202 * @this {EventTarget} 2203 * 2204 * @param {EventTarget~Event} event 2205 * the event that triggered this function 2206 * 2207 * @param {Object} [hash] 2208 * hash of data sent during the event 2209 */ 2210 2211 /**
vendor: 6,091 bytes, lines 2212-2411
2212 * An object containing event names as keys and booleans as values. 2213 * 2214 * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} 2215 * will have extra functionality. See that function for more information. 2216 * 2217 * @property EventTarget.prototype.allowedEvents_ 2218 * @private 2219 */ 2220 2221 2222 EventTarget.prototype.allowedEvents_ = {}; 2223 /** 2224 * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a 2225 * function that will get called when an event with a certain name gets triggered. 2226 * 2227 * @param {string|string[]} type 2228 * An event name or an array of event names. 2229 * 2230 * @param {EventTarget~EventListener} fn 2231 * The function to call with `EventTarget`s 2232 */ 2233 2234 EventTarget.prototype.on = function (type, fn) { 2235 // Remove the addEventListener alias before calling Events.on 2236 // so we don't get into an infinite type loop 2237 var ael = this.addEventListener; 2238 2239 this.addEventListener = function () {}; 2240 2241 on(this, type, fn); 2242 this.addEventListener = ael; 2243 }; 2244 /** 2245 * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic 2246 * the standard DOM API. 2247 * 2248 * @function 2249 * @see {@link EventTarget#on} 2250 */ 2251 2252 2253 EventTarget.prototype.addEventListener = EventTarget.prototype.on; 2254 /** 2255 * Removes an `event listener` for a specific event from an instance of `EventTarget`. 2256 * This makes it so that the `event listener` will no longer get called when the 2257 * named event happens. 2258 * 2259 * @param {string|string[]} type 2260 * An event name or an array of event names. 2261 * 2262 * @param {EventTarget~EventListener} fn 2263 * The function to remove. 2264 */ 2265 2266 EventTarget.prototype.off = function (type, fn) { 2267 off(this, type, fn); 2268 }; 2269 /** 2270 * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic 2271 * the standard DOM API. 2272 * 2273 * @function 2274 * @see {@link EventTarget#off} 2275 */ 2276 2277 2278 EventTarget.prototype.removeEventListener = EventTarget.prototype.off; 2279 /** 2280 * This function will add an `event listener` that gets triggered only once. After the 2281 * first trigger it will get removed. This is like adding an `event listener` 2282 * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. 2283 * 2284 * @param {string|string[]} type 2285 * An event name or an array of event names. 2286 * 2287 * @param {EventTarget~EventListener} fn 2288 * The function to be called once for each event name. 2289 */ 2290 2291 EventTarget.prototype.one = function (type, fn) { 2292 // Remove the addEventListener aliasing Events.on 2293 // so we don't get into an infinite type loop 2294 var ael = this.addEventListener; 2295 2296 this.addEventListener = function () {}; 2297 2298 one(this, type, fn); 2299 this.addEventListener = ael; 2300 }; 2301 2302 EventTarget.prototype.any = function (type, fn) { 2303 // Remove the addEventListener aliasing Events.on 2304 // so we don't get into an infinite type loop 2305 var ael = this.addEventListener; 2306 2307 this.addEventListener = function () {}; 2308 2309 any(this, type, fn); 2310 this.addEventListener = ael; 2311 }; 2312 /** 2313 * This function causes an event to happen. This will then cause any `event listeners` 2314 * that are waiting for that event, to get called. If there are no `event listeners` 2315 * for an event then nothing will happen. 2316 * 2317 * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. 2318 * Trigger will also call the `on` + `uppercaseEventName` function. 2319 * 2320 * Example: 2321 * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call 2322 * `onClick` if it exists. 2323 * 2324 * @param {string|EventTarget~Event|Object} event 2325 * The name of the event, an `Event`, or an object with a key of type set to 2326 * an event name. 2327 */ 2328 2329 2330 EventTarget.prototype.trigger = function (event) { 2331 var type = event.type || event; // deprecation 2332 // In a future version we should default target to `this` 2333 // similar to how we default the target to `elem` in 2334 // `Events.trigger`. Right now the default `target` will be 2335 // `document` due to the `Event.fixEvent` call. 2336 2337 if (typeof event === 'string') { 2338 event = { 2339 type: type 2340 }; 2341 } 2342 2343 event = fixEvent(event); 2344 2345 if (this.allowedEvents_[type] && this['on' + type]) { 2346 this['on' + type](event); 2347 } 2348 2349 trigger(this, event); 2350 }; 2351 /** 2352 * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic 2353 * the standard DOM API. 2354 * 2355 * @function 2356 * @see {@link EventTarget#trigger} 2357 */ 2358 2359 2360 EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; 2361 var EVENT_MAP; 2362 2363 EventTarget.prototype.queueTrigger = function (event) { 2364 var _this = this; 2365 2366 // only set up EVENT_MAP if it'll be used 2367 if (!EVENT_MAP) { 2368 EVENT_MAP = new Map(); 2369 } 2370 2371 var type = event.type || event; 2372 var map = EVENT_MAP.get(this); 2373 2374 if (!map) { 2375 map = new Map(); 2376 EVENT_MAP.set(this, map); 2377 } 2378 2379 var oldTimeout = map.get(type); 2380 map["delete"](type); 2381 window$1.clearTimeout(oldTimeout); 2382 var timeout = window$1.setTimeout(function () { 2383 // if we cleared out all timeouts for the current target, delete its map 2384 if (map.size === 0) { 2385 map = null; 2386 EVENT_MAP["delete"](_this); 2387 } 2388 2389 _this.trigger(event); 2390 }, 0); 2391 map.set(type, timeout); 2392 }; 2393 2394 /** 2395 * @file mixins/evented.js 2396 * @module evented 2397 */ 2398 /** 2399 * Returns whether or not an object has had the evented mixin applied. 2400 * 2401 * @param {Object} object 2402 * An object to test. 2403 * 2404 * @return {boolean} 2405 * Whether or not the object appears to be evented. 2406 */ 2407 2408 var isEvented = function isEvented(object) { 2409 return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { 2410 return typeof object[k] === 'function'; 2411 });
vendor: 4,139 bytes, lines 2412-2559
2412 }; 2413 /** 2414 * Adds a callback to run after the evented mixin applied. 2415 * 2416 * @param {Object} object 2417 * An object to Add 2418 * @param {Function} callback 2419 * The callback to run. 2420 */ 2421 2422 2423 var addEventedCallback = function addEventedCallback(target, callback) { 2424 if (isEvented(target)) { 2425 callback(); 2426 } else { 2427 if (!target.eventedCallbacks) { 2428 target.eventedCallbacks = []; 2429 } 2430 2431 target.eventedCallbacks.push(callback); 2432 } 2433 }; 2434 /** 2435 * Whether a value is a valid event type - non-empty string or array. 2436 * 2437 * @private 2438 * @param {string|Array} type 2439 * The type value to test. 2440 * 2441 * @return {boolean} 2442 * Whether or not the type is a valid event type. 2443 */ 2444 2445 2446 var isValidEventType = function isValidEventType(type) { 2447 return (// The regex here verifies that the `type` contains at least one non- 2448 // whitespace character. 2449 typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length 2450 ); 2451 }; 2452 /** 2453 * Validates a value to determine if it is a valid event target. Throws if not. 2454 * 2455 * @private 2456 * @throws {Error} 2457 * If the target does not appear to be a valid event target. 2458 * 2459 * @param {Object} target 2460 * The object to test. 2461 */ 2462 2463 2464 var validateTarget = function validateTarget(target) { 2465 if (!target.nodeName && !isEvented(target)) { 2466 throw new Error('Invalid target; must be a DOM node or evented object.'); 2467 } 2468 }; 2469 /** 2470 * Validates a value to determine if it is a valid event target. Throws if not. 2471 * 2472 * @private 2473 * @throws {Error} 2474 * If the type does not appear to be a valid event type. 2475 * 2476 * @param {string|Array} type 2477 * The type to test. 2478 */ 2479 2480 2481 var validateEventType = function validateEventType(type) { 2482 if (!isValidEventType(type)) { 2483 throw new Error('Invalid event type; must be a non-empty string or array.'); 2484 } 2485 }; 2486 /** 2487 * Validates a value to determine if it is a valid listener. Throws if not. 2488 * 2489 * @private 2490 * @throws {Error} 2491 * If the listener is not a function. 2492 * 2493 * @param {Function} listener 2494 * The listener to test. 2495 */ 2496 2497 2498 var validateListener = function validateListener(listener) { 2499 if (typeof listener !== 'function') { 2500 throw new Error('Invalid listener; must be a function.'); 2501 } 2502 }; 2503 /** 2504 * Takes an array of arguments given to `on()` or `one()`, validates them, and 2505 * normalizes them into an object. 2506 * 2507 * @private 2508 * @param {Object} self 2509 * The evented object on which `on()` or `one()` was called. This 2510 * object will be bound as the `this` value for the listener. 2511 * 2512 * @param {Array} args 2513 * An array of arguments passed to `on()` or `one()`. 2514 * 2515 * @return {Object} 2516 * An object containing useful values for `on()` or `one()` calls. 2517 */ 2518 2519 2520 var normalizeListenArgs = function normalizeListenArgs(self, args) { 2521 // If the number of arguments is less than 3, the target is always the 2522 // evented object itself. 2523 var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; 2524 var target; 2525 var type; 2526 var listener; 2527 2528 if (isTargetingSelf) { 2529 target = self.eventBusEl_; // Deal with cases where we got 3 arguments, but we are still listening to 2530 // the evented object itself. 2531 2532 if (args.length >= 3) { 2533 args.shift(); 2534 } 2535 2536 type = args[0]; 2537 listener = args[1]; 2538 } else { 2539 target = args[0]; 2540 type = args[1]; 2541 listener = args[2]; 2542 } 2543 2544 validateTarget(target); 2545 validateEventType(type); 2546 validateListener(listener); 2547 listener = bind(self, listener); 2548 return { 2549 isTargetingSelf: isTargetingSelf, 2550 target: target, 2551 type: type, 2552 listener: listener 2553 }; 2554 }; 2555 /** 2556 * Adds the listener to the event type(s) on the target, normalizing for 2557 * the type of target. 2558 * 2559 * @private
vendor: 6,282 bytes, lines 2560-2719
2560 * @param {Element|Object} target 2561 * A DOM node or evented object. 2562 * 2563 * @param {string} method 2564 * The event binding method to use ("on" or "one"). 2565 * 2566 * @param {string|Array} type 2567 * One or more event type(s). 2568 * 2569 * @param {Function} listener 2570 * A listener function. 2571 */ 2572 2573 2574 var listen = function listen(target, method, type, listener) { 2575 validateTarget(target); 2576 2577 if (target.nodeName) { 2578 Events[method](target, type, listener); 2579 } else { 2580 target[method](type, listener); 2581 } 2582 }; 2583 /** 2584 * Contains methods that provide event capabilities to an object which is passed 2585 * to {@link module:evented|evented}. 2586 * 2587 * @mixin EventedMixin 2588 */ 2589 2590 2591 var EventedMixin = { 2592 /** 2593 * Add a listener to an event (or events) on this object or another evented 2594 * object. 2595 * 2596 * @param {string|Array|Element|Object} targetOrType 2597 * If this is a string or array, it represents the event type(s) 2598 * that will trigger the listener. 2599 * 2600 * Another evented object can be passed here instead, which will 2601 * cause the listener to listen for events on _that_ object. 2602 * 2603 * In either case, the listener's `this` value will be bound to 2604 * this object. 2605 * 2606 * @param {string|Array|Function} typeOrListener 2607 * If the first argument was a string or array, this should be the 2608 * listener function. Otherwise, this is a string or array of event 2609 * type(s). 2610 * 2611 * @param {Function} [listener] 2612 * If the first argument was another evented object, this will be 2613 * the listener function. 2614 */ 2615 on: function on() { 2616 var _this = this; 2617 2618 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 2619 args[_key] = arguments[_key]; 2620 } 2621 2622 var _normalizeListenArgs = normalizeListenArgs(this, args), 2623 isTargetingSelf = _normalizeListenArgs.isTargetingSelf, 2624 target = _normalizeListenArgs.target, 2625 type = _normalizeListenArgs.type, 2626 listener = _normalizeListenArgs.listener; 2627 2628 listen(target, 'on', type, listener); // If this object is listening to another evented object. 2629 2630 if (!isTargetingSelf) { 2631 // If this object is disposed, remove the listener. 2632 var removeListenerOnDispose = function removeListenerOnDispose() { 2633 return _this.off(target, type, listener); 2634 }; // Use the same function ID as the listener so we can remove it later it 2635 // using the ID of the original listener. 2636 2637 2638 removeListenerOnDispose.guid = listener.guid; // Add a listener to the target's dispose event as well. This ensures 2639 // that if the target is disposed BEFORE this object, we remove the 2640 // removal listener that was just added. Otherwise, we create a memory leak. 2641 2642 var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { 2643 return _this.off('dispose', removeListenerOnDispose); 2644 }; // Use the same function ID as the listener so we can remove it later 2645 // it using the ID of the original listener. 2646 2647 2648 removeRemoverOnTargetDispose.guid = listener.guid; 2649 listen(this, 'on', 'dispose', removeListenerOnDispose); 2650 listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); 2651 } 2652 }, 2653 2654 /** 2655 * Add a listener to an event (or events) on this object or another evented 2656 * object. The listener will be called once per event and then removed. 2657 * 2658 * @param {string|Array|Element|Object} targetOrType 2659 * If this is a string or array, it represents the event type(s) 2660 * that will trigger the listener. 2661 * 2662 * Another evented object can be passed here instead, which will 2663 * cause the listener to listen for events on _that_ object. 2664 * 2665 * In either case, the listener's `this` value will be bound to 2666 * this object. 2667 * 2668 * @param {string|Array|Function} typeOrListener 2669 * If the first argument was a string or array, this should be the 2670 * listener function. Otherwise, this is a string or array of event 2671 * type(s). 2672 * 2673 * @param {Function} [listener] 2674 * If the first argument was another evented object, this will be 2675 * the listener function. 2676 */ 2677 one: function one() { 2678 var _this2 = this; 2679 2680 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 2681 args[_key2] = arguments[_key2]; 2682 } 2683 2684 var _normalizeListenArgs2 = normalizeListenArgs(this, args), 2685 isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, 2686 target = _normalizeListenArgs2.target, 2687 type = _normalizeListenArgs2.type, 2688 listener = _normalizeListenArgs2.listener; // Targeting this evented object. 2689 2690 2691 if (isTargetingSelf) { 2692 listen(target, 'one', type, listener); // Targeting another evented object. 2693 } else { 2694 // TODO: This wrapper is incorrect! It should only 2695 // remove the wrapper for the event type that called it. 2696 // Instead all listners are removed on the first trigger! 2697 // see https://github.com/videojs/video.js/issues/5962 2698 var wrapper = function wrapper() { 2699 _this2.off(target, type, wrapper); 2700 2701 for (var _len3 = arguments.length, largs = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 2702 largs[_key3] = arguments[_key3]; 2703 } 2704 2705 listener.apply(null, largs); 2706 }; // Use the same function ID as the listener so we can remove it later 2707 // it using the ID of the original listener. 2708 2709 2710 wrapper.guid = listener.guid; 2711 listen(target, 'one', type, wrapper); 2712 } 2713 }, 2714 2715 /** 2716 * Add a listener to an event (or events) on this object or another evented 2717 * object. The listener will only be called once for the first event that is triggered 2718 * then removed. 2719 *
2720 * @param {string|Array|Element|Object} targetOrType 2721 * If this is a string or array, it represents the event type(s) 2722 * that will trigger the listener. 2723 * 2724 * Another evented object can be passed here instead, which will 2725 * cause the listener to listen for events on _that_ object. 2726 * 2727 * In either case, the listener's `this` value will be bound to 2728 * this object. 2729 * 2730 * @param {string|Array|Function} typeOrListener 2731 * If the first argument was a string or array, this should be the 2732 * listener function. Otherwise, this is a string or array of event 2733 * type(s). 2734 * 2735 * @param {Function} [listener] 2736 * If the first argument was another evented object, this will be 2737 * the listener function. 2738 */ 2739 any: function any() { 2740 var _this3 = this; 2741 2742 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 2743 args[_key4] = arguments[_key4]; 2744 } 2745 2746 var _normalizeListenArgs3 = normalizeListenArgs(this, args), 2747 isTargetingSelf = _normalizeListenArgs3.isTargetingSelf, 2748 target = _normalizeListenArgs3.target, 2749 type = _normalizeListenArgs3.type, 2750 listener = _normalizeListenArgs3.listener; // Targeting this evented object. 2751 2752 2753 if (isTargetingSelf) { 2754 listen(target, 'any', type, listener); // Targeting another evented object. 2755 } else { 2756 var wrapper = function wrapper() { 2757 _this3.off(target, type, wrapper); 2758 2759 for (var _len5 = arguments.length, largs = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 2760 largs[_key5] = arguments[_key5]; 2761 } 2762 2763 listener.apply(null, largs); 2764 }; // Use the same function ID as the listener so we can remove it later 2765 // it using the ID of the original listener. 2766 2767 2768 wrapper.guid = listener.guid; 2769 listen(target, 'any', type, wrapper); 2770 } 2771 }, 2772 2773 /** 2774 * Removes listener(s) from event(s) on an evented object. 2775 * 2776 * @param {string|Array|Element|Object} [targetOrType] 2777 * If this is a string or array, it represents the event type(s). 2778 * 2779 * Another evented object can be passed here instead, in which case 2780 * ALL 3 arguments are _required_. 2781 * 2782 * @param {string|Array|Function} [typeOrListener] 2783 * If the first argument was a string or array, this may be the 2784 * listener function. Otherwise, this is a string or array of event 2785 * type(s). 2786 * 2787 * @param {Function} [listener] 2788 * If the first argument was another evented object, this will be 2789 * the listener function; otherwise, _all_ listeners bound to the 2790 * event type(s) will be removed. 2791 */ 2792 off: function off$1(targetOrType, typeOrListener, listener) { 2793 // Targeting this evented object. 2794 if (!targetOrType || isValidEventType(targetOrType)) { 2795 off(this.eventBusEl_, targetOrType, typeOrListener); // Targeting another evented object. 2796 } else { 2797 var target = targetOrType; 2798 var type = typeOrListener; // Fail fast and in a meaningful way! 2799 2800 validateTarget(target); 2801 validateEventType(type); 2802 validateListener(listener); // Ensure there's at least a guid, even if the function hasn't been used 2803 2804 listener = bind(this, listener); // Remove the dispose listener on this evented object, which was given 2805 // the same guid as the event listener in on(). 2806 2807 this.off('dispose', listener); 2808 2809 if (target.nodeName) { 2810 off(target, type, listener); 2811 off(target, 'dispose', listener); 2812 } else if (isEvented(target)) { 2813 target.off(type, listener); 2814 target.off('dispose', listener); 2815 } 2816 } 2817 }, 2818 2819 /** 2820 * Fire an event on this evented object, causing its listeners to be called. 2821 * 2822 * @param {string|Object} event 2823 * An event type or an object with a type property. 2824 * 2825 * @param {Object} [hash] 2826 * An additional object to pass along to listeners. 2827 * 2828 * @return {boolean} 2829 * Whether or not the default behavior was prevented. 2830 */ 2831 trigger: function trigger$1(event, hash) { 2832 return trigger(this.eventBusEl_, event, hash); 2833 } 2834 }; 2835 /** 2836 * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. 2837 * 2838 * @param {Object} target 2839 * The object to which to add event methods. 2840 * 2841 * @param {Object} [options={}] 2842 * Options for customizing the mixin behavior. 2843 * 2844 * @param {string} [options.eventBusKey] 2845 * By default, adds a `eventBusEl_` DOM element to the target object, 2846 * which is used as an event bus. If the target object already has a 2847 * DOM element that should be used, pass its key here. 2848 * 2849 * @return {Object} 2850 * The target object. 2851 */ 2852 2853 function evented(target, options) { 2854 if (options === void 0) { 2855 options = {}; 2856 } 2857 2858 var _options = options, 2859 eventBusKey = _options.eventBusKey; // Set or create the eventBusEl_. 2860
vendor: 10,461 bytes, lines 2861-3202
2861 if (eventBusKey) { 2862 if (!target[eventBusKey].nodeName) { 2863 throw new Error("The eventBusKey \"" + eventBusKey + "\" does not refer to an element."); 2864 } 2865 2866 target.eventBusEl_ = target[eventBusKey]; 2867 } else { 2868 target.eventBusEl_ = createEl('span', { 2869 className: 'vjs-event-bus' 2870 }); 2871 } 2872 2873 assign(target, EventedMixin); 2874 2875 if (target.eventedCallbacks) { 2876 target.eventedCallbacks.forEach(function (callback) { 2877 callback(); 2878 }); 2879 } // When any evented object is disposed, it removes all its listeners. 2880 2881 2882 target.on('dispose', function () { 2883 target.off(); 2884 window$1.setTimeout(function () { 2885 target.eventBusEl_ = null; 2886 }, 0); 2887 }); 2888 return target; 2889 } 2890 2891 /** 2892 * @file mixins/stateful.js 2893 * @module stateful 2894 */ 2895 /** 2896 * Contains methods that provide statefulness to an object which is passed 2897 * to {@link module:stateful}. 2898 * 2899 * @mixin StatefulMixin 2900 */ 2901 2902 var StatefulMixin = { 2903 /** 2904 * A hash containing arbitrary keys and values representing the state of 2905 * the object. 2906 * 2907 * @type {Object} 2908 */ 2909 state: {}, 2910 2911 /** 2912 * Set the state of an object by mutating its 2913 * {@link module:stateful~StatefulMixin.state|state} object in place. 2914 * 2915 * @fires module:stateful~StatefulMixin#statechanged 2916 * @param {Object|Function} stateUpdates 2917 * A new set of properties to shallow-merge into the plugin state. 2918 * Can be a plain object or a function returning a plain object. 2919 * 2920 * @return {Object|undefined} 2921 * An object containing changes that occurred. If no changes 2922 * occurred, returns `undefined`. 2923 */ 2924 setState: function setState(stateUpdates) { 2925 var _this = this; 2926 2927 // Support providing the `stateUpdates` state as a function. 2928 if (typeof stateUpdates === 'function') { 2929 stateUpdates = stateUpdates(); 2930 } 2931 2932 var changes; 2933 each(stateUpdates, function (value, key) { 2934 // Record the change if the value is different from what's in the 2935 // current state. 2936 if (_this.state[key] !== value) { 2937 changes = changes || {}; 2938 changes[key] = { 2939 from: _this.state[key], 2940 to: value 2941 }; 2942 } 2943 2944 _this.state[key] = value; 2945 }); // Only trigger "statechange" if there were changes AND we have a trigger 2946 // function. This allows us to not require that the target object be an 2947 // evented object. 2948 2949 if (changes && isEvented(this)) { 2950 /** 2951 * An event triggered on an object that is both 2952 * {@link module:stateful|stateful} and {@link module:evented|evented} 2953 * indicating that its state has changed. 2954 * 2955 * @event module:stateful~StatefulMixin#statechanged 2956 * @type {Object} 2957 * @property {Object} changes 2958 * A hash containing the properties that were changed and 2959 * the values they were changed `from` and `to`. 2960 */ 2961 this.trigger({ 2962 changes: changes, 2963 type: 'statechanged' 2964 }); 2965 } 2966 2967 return changes; 2968 } 2969 }; 2970 /** 2971 * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target 2972 * object. 2973 * 2974 * If the target object is {@link module:evented|evented} and has a 2975 * `handleStateChanged` method, that method will be automatically bound to the 2976 * `statechanged` event on itself. 2977 * 2978 * @param {Object} target 2979 * The object to be made stateful. 2980 * 2981 * @param {Object} [defaultState] 2982 * A default set of properties to populate the newly-stateful object's 2983 * `state` property. 2984 * 2985 * @return {Object} 2986 * Returns the `target`. 2987 */ 2988 2989 function stateful(target, defaultState) { 2990 assign(target, StatefulMixin); // This happens after the mixing-in because we need to replace the `state` 2991 // added in that step. 2992 2993 target.state = assign({}, target.state, defaultState); // Auto-bind the `handleStateChanged` method of the target object if it exists. 2994 2995 if (typeof target.handleStateChanged === 'function' && isEvented(target)) { 2996 target.on('statechanged', target.handleStateChanged); 2997 } 2998 2999 return target; 3000 } 3001 3002 /** 3003 * @file string-cases.js 3004 * @module to-lower-case 3005 */ 3006 3007 /** 3008 * Lowercase the first letter of a string. 3009 * 3010 * @param {string} string 3011 * String to be lowercased 3012 * 3013 * @return {string} 3014 * The string with a lowercased first letter 3015 */ 3016 var toLowerCase = function toLowerCase(string) { 3017 if (typeof string !== 'string') { 3018 return string; 3019 } 3020 3021 return string.replace(/./, function (w) { 3022 return w.toLowerCase(); 3023 }); 3024 }; 3025 /** 3026 * Uppercase the first letter of a string. 3027 * 3028 * @param {string} string 3029 * String to be uppercased 3030 * 3031 * @return {string} 3032 * The string with an uppercased first letter 3033 */ 3034 3035 var toTitleCase = function toTitleCase(string) { 3036 if (typeof string !== 'string') { 3037 return string; 3038 } 3039 3040 return string.replace(/./, function (w) { 3041 return w.toUpperCase(); 3042 }); 3043 }; 3044 /** 3045 * Compares the TitleCase versions of the two strings for equality. 3046 * 3047 * @param {string} str1 3048 * The first string to compare 3049 * 3050 * @param {string} str2 3051 * The second string to compare 3052 * 3053 * @return {boolean} 3054 * Whether the TitleCase versions of the strings are equal 3055 */ 3056 3057 var titleCaseEquals = function titleCaseEquals(str1, str2) { 3058 return toTitleCase(str1) === toTitleCase(str2); 3059 }; 3060 3061 /** 3062 * @file merge-options.js 3063 * @module merge-options 3064 */ 3065 /** 3066 * Merge two objects recursively. 3067 * 3068 * Performs a deep merge like 3069 * {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges 3070 * plain objects (not arrays, elements, or anything else). 3071 * 3072 * Non-plain object values will be copied directly from the right-most 3073 * argument. 3074 * 3075 * @static 3076 * @param {Object[]} sources 3077 * One or more objects to merge into a new object. 3078 * 3079 * @return {Object} 3080 * A new object that is the merged result of all sources. 3081 */ 3082 3083 function mergeOptions() { 3084 var result = {}; 3085 3086 for (var _len = arguments.length, sources = new Array(_len), _key = 0; _key < _len; _key++) { 3087 sources[_key] = arguments[_key]; 3088 } 3089 3090 sources.forEach(function (source) { 3091 if (!source) { 3092 return; 3093 } 3094 3095 each(source, function (value, key) { 3096 if (!isPlain(value)) { 3097 result[key] = value; 3098 return; 3099 } 3100 3101 if (!isPlain(result[key])) { 3102 result[key] = {}; 3103 } 3104 3105 result[key] = mergeOptions(result[key], value); 3106 }); 3107 }); 3108 return result; 3109 } 3110 3111 /** 3112 * Player Component - Base class for all UI objects 3113 * 3114 * @file component.js 3115 */ 3116 /** 3117 * Base class for all UI Components. 3118 * Components are UI objects which represent both a javascript object and an element 3119 * in the DOM. They can be children of other components, and can have 3120 * children themselves. 3121 * 3122 * Components can also use methods from {@link EventTarget} 3123 */ 3124 3125 var Component = 3126 /*#__PURE__*/ 3127 function () { 3128 /** 3129 * A callback that is called when a component is ready. Does not have any 3130 * paramters and any callback value will be ignored. 3131 * 3132 * @callback Component~ReadyCallback 3133 * @this Component 3134 */ 3135 3136 /** 3137 * Creates an instance of this class. 3138 * 3139 * @param {Player} player 3140 * The `Player` that this class should be attached to. 3141 * 3142 * @param {Object} [options] 3143 * The key/value store of player options. 3144 * 3145 * @param {Object[]} [options.children] 3146 * An array of children objects to intialize this component with. Children objects have 3147 * a name property that will be used if more than one component of the same type needs to be 3148 * added. 3149 * 3150 * @param {Component~ReadyCallback} [ready] 3151 * Function that gets called when the `Component` is ready. 3152 */ 3153 function Component(player, options, ready) { 3154 // The component might be the player itself and we can't pass `this` to super 3155 if (!player && this.play) { 3156 this.player_ = player = this; // eslint-disable-line 3157 } else { 3158 this.player_ = player; 3159 } // Hold the reference to the parent component via `addChild` method 3160 3161 3162 this.parentComponent_ = null; // Make a copy of prototype.options_ to protect against overriding defaults 3163 3164 this.options_ = mergeOptions({}, this.options_); // Updated options with supplied options 3165 3166 options = this.options_ = mergeOptions(this.options_, options); // Get ID from options or options element if one is supplied 3167 3168 this.id_ = options.id || options.el && options.el.id; // If there was no ID from the options, generate one 3169 3170 if (!this.id_) { 3171 // Don't require the player ID function in the case of mock players 3172 var id = player && player.id && player.id() || 'no_player'; 3173 this.id_ = id + "_component_" + newGUID(); 3174 } 3175 3176 this.name_ = options.name || null; // Create element if one wasn't provided in options 3177 3178 if (options.el) { 3179 this.el_ = options.el; 3180 } else if (options.createEl !== false) { 3181 this.el_ = this.createEl(); 3182 } // if evented is anything except false, we want to mixin in evented 3183 3184 3185 if (options.evented !== false) { 3186 // Make this an evented object and use `el_`, if available, as its event bus 3187 evented(this, { 3188 eventBusKey: this.el_ ? 'el_' : null 3189 }); 3190 } 3191 3192 stateful(this, this.constructor.defaultState); 3193 this.children_ = []; 3194 this.childIndex_ = {}; 3195 this.childNameIndex_ = {}; 3196 this.setTimeoutIds_ = new Set(); 3197 this.setIntervalIds_ = new Set(); 3198 this.rafIds_ = new Set(); 3199 this.clearingTimersOnDispose_ = false; // Add any child components in options 3200 3201 if (options.initChildren !== false) { 3202 this.initChildren();
3203 } 3204 3205 this.ready(ready); // Don't want to trigger ready here or it will before init is actually 3206 // finished for all children that run this constructor 3207 3208 if (options.reportTouchActivity !== false) { 3209 this.enableTouchActivity(); 3210 } 3211 } 3212 /** 3213 * Dispose of the `Component` and all child components. 3214 * 3215 * @fires Component#dispose 3216 */ 3217 3218 3219 var _proto = Component.prototype; 3220 3221 _proto.dispose = function dispose() { 3222 /** 3223 * Triggered when a `Component` is disposed. 3224 * 3225 * @event Component#dispose 3226 * @type {EventTarget~Event} 3227 * 3228 * @property {boolean} [bubbles=false] 3229 * set to false so that the close event does not 3230 * bubble up 3231 */ 3232 this.trigger({ 3233 type: 'dispose', 3234 bubbles: false 3235 }); // Dispose all children. 3236 3237 if (this.children_) { 3238 for (var i = this.children_.length - 1; i >= 0; i--) { 3239 if (this.children_[i].dispose) { 3240 this.children_[i].dispose(); 3241 } 3242 } 3243 } // Delete child references 3244 3245 3246 this.children_ = null; 3247 this.childIndex_ = null; 3248 this.childNameIndex_ = null; 3249 this.parentComponent_ = null; 3250 3251 if (this.el_) { 3252 // Remove element from DOM 3253 if (this.el_.parentNode) { 3254 this.el_.parentNode.removeChild(this.el_); 3255 } 3256 3257 if (DomData.has(this.el_)) { 3258 DomData["delete"](this.el_); 3259 } 3260 3261 this.el_ = null; 3262 } // remove reference to the player after disposing of the element 3263 3264 3265 this.player_ = null; 3266 } 3267 /** 3268 * Return the {@link Player} that the `Component` has attached to. 3269 * 3270 * @return {Player} 3271 * The player that this `Component` has attached to. 3272 */ 3273 ; 3274 3275 _proto.player = function player() { 3276 return this.player_; 3277 } 3278 /** 3279 * Deep merge of options objects with new options. 3280 * > Note: When both `obj` and `options` contain properties whose values are objects. 3281 * The two properties get merged using {@link module:mergeOptions} 3282 * 3283 * @param {Object} obj 3284 * The object that contains new options. 3285 * 3286 * @return {Object} 3287 * A new object of `this.options_` and `obj` merged together. 3288 */ 3289 ; 3290 3291 _proto.options = function options(obj) { 3292 if (!obj) { 3293 return this.options_; 3294 } 3295 3296 this.options_ = mergeOptions(this.options_, obj); 3297 return this.options_; 3298 } 3299 /** 3300 * Get the `Component`s DOM element 3301 * 3302 * @return {Element} 3303 * The DOM element for this `Component`. 3304 */ 3305 ; 3306 3307 _proto.el = function el() { 3308 return this.el_; 3309 } 3310 /** 3311 * Create the `Component`s DOM element. 3312 * 3313 * @param {string} [tagName] 3314 * Element's DOM node type. e.g. 'div' 3315 * 3316 * @param {Object} [properties] 3317 * An object of properties that should be set. 3318 * 3319 * @param {Object} [attributes] 3320 * An object of attributes that should be set. 3321 * 3322 * @return {Element} 3323 * The element that gets created. 3324 */ 3325 ; 3326 3327 _proto.createEl = function createEl$1(tagName, properties, attributes) { 3328 return createEl(tagName, properties, attributes); 3329 } 3330 /** 3331 * Localize a string given the string in english. 3332 * 3333 * If tokens are provided, it'll try and run a simple token replacement on the provided string. 3334 * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array. 3335 * 3336 * If a `defaultValue` is provided, it'll use that over `string`, 3337 * if a value isn't found in provided language files. 3338 * This is useful if you want to have a descriptive key for token replacement 3339 * but have a succinct localized string and not require `en.json` to be included. 3340 * 3341 * Currently, it is used for the progress bar timing. 3342 * ```js 3343 * { 3344 * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" 3345 * } 3346 * ``` 3347 * It is then used like so: 3348 * ```js 3349 * this.localize('progress bar timing: currentTime={1} duration{2}', 3350 * [this.player_.currentTime(), this.player_.duration()], 3351 * '{1} of {2}'); 3352 * ``` 3353 * 3354 * Which outputs something like: `01:23 of 24:56`.
vendor: 43,927 bytes, lines 3355-4624
3355 * 3356 * 3357 * @param {string} string 3358 * The string to localize and the key to lookup in the language files. 3359 * @param {string[]} [tokens] 3360 * If the current item has token replacements, provide the tokens here. 3361 * @param {string} [defaultValue] 3362 * Defaults to `string`. Can be a default value to use for token replacement 3363 * if the lookup key is needed to be separate. 3364 * 3365 * @return {string} 3366 * The localized string or if no localization exists the english string. 3367 */ 3368 ; 3369 3370 _proto.localize = function localize(string, tokens, defaultValue) { 3371 if (defaultValue === void 0) { 3372 defaultValue = string; 3373 } 3374 3375 var code = this.player_.language && this.player_.language(); 3376 var languages = this.player_.languages && this.player_.languages(); 3377 var language = languages && languages[code]; 3378 var primaryCode = code && code.split('-')[0]; 3379 var primaryLang = languages && languages[primaryCode]; 3380 var localizedString = defaultValue; 3381 3382 if (language && language[string]) { 3383 localizedString = language[string]; 3384 } else if (primaryLang && primaryLang[string]) { 3385 localizedString = primaryLang[string]; 3386 } 3387 3388 if (tokens) { 3389 localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { 3390 var value = tokens[index - 1]; 3391 var ret = value; 3392 3393 if (typeof value === 'undefined') { 3394 ret = match; 3395 } 3396 3397 return ret; 3398 }); 3399 } 3400 3401 return localizedString; 3402 } 3403 /** 3404 * Return the `Component`s DOM element. This is where children get inserted. 3405 * This will usually be the the same as the element returned in {@link Component#el}. 3406 * 3407 * @return {Element} 3408 * The content element for this `Component`. 3409 */ 3410 ; 3411 3412 _proto.contentEl = function contentEl() { 3413 return this.contentEl_ || this.el_; 3414 } 3415 /** 3416 * Get this `Component`s ID 3417 * 3418 * @return {string} 3419 * The id of this `Component` 3420 */ 3421 ; 3422 3423 _proto.id = function id() { 3424 return this.id_; 3425 } 3426 /** 3427 * Get the `Component`s name. The name gets used to reference the `Component` 3428 * and is set during registration. 3429 * 3430 * @return {string} 3431 * The name of this `Component`. 3432 */ 3433 ; 3434 3435 _proto.name = function name() { 3436 return this.name_; 3437 } 3438 /** 3439 * Get an array of all child components 3440 * 3441 * @return {Array} 3442 * The children 3443 */ 3444 ; 3445 3446 _proto.children = function children() { 3447 return this.children_; 3448 } 3449 /** 3450 * Returns the child `Component` with the given `id`. 3451 * 3452 * @param {string} id 3453 * The id of the child `Component` to get. 3454 * 3455 * @return {Component|undefined} 3456 * The child `Component` with the given `id` or undefined. 3457 */ 3458 ; 3459 3460 _proto.getChildById = function getChildById(id) { 3461 return this.childIndex_[id]; 3462 } 3463 /** 3464 * Returns the child `Component` with the given `name`. 3465 * 3466 * @param {string} name 3467 * The name of the child `Component` to get. 3468 * 3469 * @return {Component|undefined} 3470 * The child `Component` with the given `name` or undefined. 3471 */ 3472 ; 3473 3474 _proto.getChild = function getChild(name) { 3475 if (!name) { 3476 return; 3477 } 3478 3479 return this.childNameIndex_[name]; 3480 } 3481 /** 3482 * Add a child `Component` inside the current `Component`. 3483 * 3484 * 3485 * @param {string|Component} child 3486 * The name or instance of a child to add. 3487 * 3488 * @param {Object} [options={}] 3489 * The key/value store of options that will get passed to children of 3490 * the child. 3491 * 3492 * @param {number} [index=this.children_.length] 3493 * The index to attempt to add a child into. 3494 * 3495 * @return {Component} 3496 * The `Component` that gets added as a child. When using a string the 3497 * `Component` will get created by this process. 3498 */ 3499 ; 3500 3501 _proto.addChild = function addChild(child, options, index) { 3502 if (options === void 0) { 3503 options = {}; 3504 } 3505 3506 if (index === void 0) { 3507 index = this.children_.length; 3508 } 3509 3510 var component; 3511 var componentName; // If child is a string, create component with options 3512 3513 if (typeof child === 'string') { 3514 componentName = toTitleCase(child); 3515 var componentClassName = options.componentClass || componentName; // Set name through options 3516 3517 options.name = componentName; // Create a new object & element for this controls set 3518 // If there's no .player_, this is a player 3519 3520 var ComponentClass = Component.getComponent(componentClassName); 3521 3522 if (!ComponentClass) { 3523 throw new Error("Component " + componentClassName + " does not exist"); 3524 } // data stored directly on the videojs object may be 3525 // misidentified as a component to retain 3526 // backwards-compatibility with 4.x. check to make sure the 3527 // component class can be instantiated. 3528 3529 3530 if (typeof ComponentClass !== 'function') { 3531 return null; 3532 } 3533 3534 component = new ComponentClass(this.player_ || this, options); // child is a component instance 3535 } else { 3536 component = child; 3537 } 3538 3539 if (component.parentComponent_) { 3540 component.parentComponent_.removeChild(component); 3541 } 3542 3543 this.children_.splice(index, 0, component); 3544 component.parentComponent_ = this; 3545 3546 if (typeof component.id === 'function') { 3547 this.childIndex_[component.id()] = component; 3548 } // If a name wasn't used to create the component, check if we can use the 3549 // name function of the component 3550 3551 3552 componentName = componentName || component.name && toTitleCase(component.name()); 3553 3554 if (componentName) { 3555 this.childNameIndex_[componentName] = component; 3556 this.childNameIndex_[toLowerCase(componentName)] = component; 3557 } // Add the UI object's element to the container div (box) 3558 // Having an element is not required 3559 3560 3561 if (typeof component.el === 'function' && component.el()) { 3562 var childNodes = this.contentEl().children; 3563 var refNode = childNodes[index] || null; 3564 this.contentEl().insertBefore(component.el(), refNode); 3565 } // Return so it can stored on parent object if desired. 3566 3567 3568 return component; 3569 } 3570 /** 3571 * Remove a child `Component` from this `Component`s list of children. Also removes 3572 * the child `Component`s element from this `Component`s element. 3573 * 3574 * @param {Component} component 3575 * The child `Component` to remove. 3576 */ 3577 ; 3578 3579 _proto.removeChild = function removeChild(component) { 3580 if (typeof component === 'string') { 3581 component = this.getChild(component); 3582 } 3583 3584 if (!component || !this.children_) { 3585 return; 3586 } 3587 3588 var childFound = false; 3589 3590 for (var i = this.children_.length - 1; i >= 0; i--) { 3591 if (this.children_[i] === component) { 3592 childFound = true; 3593 this.children_.splice(i, 1); 3594 break; 3595 } 3596 } 3597 3598 if (!childFound) { 3599 return; 3600 } 3601 3602 component.parentComponent_ = null; 3603 this.childIndex_[component.id()] = null; 3604 this.childNameIndex_[toTitleCase(component.name())] = null; 3605 this.childNameIndex_[toLowerCase(component.name())] = null; 3606 var compEl = component.el(); 3607 3608 if (compEl && compEl.parentNode === this.contentEl()) { 3609 this.contentEl().removeChild(component.el()); 3610 } 3611 } 3612 /** 3613 * Add and initialize default child `Component`s based upon options. 3614 */ 3615 ; 3616 3617 _proto.initChildren = function initChildren() { 3618 var _this = this; 3619 3620 var children = this.options_.children; 3621 3622 if (children) { 3623 // `this` is `parent` 3624 var parentOptions = this.options_; 3625 3626 var handleAdd = function handleAdd(child) { 3627 var name = child.name; 3628 var opts = child.opts; // Allow options for children to be set at the parent options 3629 // e.g. videojs(id, { controlBar: false }); 3630 // instead of videojs(id, { children: { controlBar: false }); 3631 3632 if (parentOptions[name] !== undefined) { 3633 opts = parentOptions[name]; 3634 } // Allow for disabling default components 3635 // e.g. options['children']['posterImage'] = false 3636 3637 3638 if (opts === false) { 3639 return; 3640 } // Allow options to be passed as a simple boolean if no configuration 3641 // is necessary. 3642 3643 3644 if (opts === true) { 3645 opts = {}; 3646 } // We also want to pass the original player options 3647 // to each component as well so they don't need to 3648 // reach back into the player for options later. 3649 3650 3651 opts.playerOptions = _this.options_.playerOptions; // Create and add the child component. 3652 // Add a direct reference to the child by name on the parent instance. 3653 // If two of the same component are used, different names should be supplied 3654 // for each 3655 3656 var newChild = _this.addChild(name, opts); 3657 3658 if (newChild) { 3659 _this[name] = newChild; 3660 } 3661 }; // Allow for an array of children details to passed in the options 3662 3663 3664 var workingChildren; 3665 var Tech = Component.getComponent('Tech'); 3666 3667 if (Array.isArray(children)) { 3668 workingChildren = children; 3669 } else { 3670 workingChildren = Object.keys(children); 3671 } 3672 3673 workingChildren // children that are in this.options_ but also in workingChildren would 3674 // give us extra children we do not want. So, we want to filter them out. 3675 .concat(Object.keys(this.options_).filter(function (child) { 3676 return !workingChildren.some(function (wchild) { 3677 if (typeof wchild === 'string') { 3678 return child === wchild; 3679 } 3680 3681 return child === wchild.name; 3682 }); 3683 })).map(function (child) { 3684 var name; 3685 var opts; 3686 3687 if (typeof child === 'string') { 3688 name = child; 3689 opts = children[name] || _this.options_[name] || {}; 3690 } else { 3691 name = child.name; 3692 opts = child; 3693 } 3694 3695 return { 3696 name: name, 3697 opts: opts 3698 }; 3699 }).filter(function (child) { 3700 // we have to make sure that child.name isn't in the techOrder since 3701 // techs are registerd as Components but can't aren't compatible 3702 // See https://github.com/videojs/video.js/issues/2772 3703 var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); 3704 return c && !Tech.isTech(c); 3705 }).forEach(handleAdd); 3706 } 3707 } 3708 /** 3709 * Builds the default DOM class name. Should be overriden by sub-components. 3710 * 3711 * @return {string} 3712 * The DOM class name for this object. 3713 * 3714 * @abstract 3715 */ 3716 ; 3717 3718 _proto.buildCSSClass = function buildCSSClass() { 3719 // Child classes can include a function that does: 3720 // return 'CLASS NAME' + this._super(); 3721 return ''; 3722 } 3723 /** 3724 * Bind a listener to the component's ready state. 3725 * Different from event listeners in that if the ready event has already happened 3726 * it will trigger the function immediately. 3727 * 3728 * @return {Component} 3729 * Returns itself; method can be chained. 3730 */ 3731 ; 3732 3733 _proto.ready = function ready(fn, sync) { 3734 if (sync === void 0) { 3735 sync = false; 3736 } 3737 3738 if (!fn) { 3739 return; 3740 } 3741 3742 if (!this.isReady_) { 3743 this.readyQueue_ = this.readyQueue_ || []; 3744 this.readyQueue_.push(fn); 3745 return; 3746 } 3747 3748 if (sync) { 3749 fn.call(this); 3750 } else { 3751 // Call the function asynchronously by default for consistency 3752 this.setTimeout(fn, 1); 3753 } 3754 } 3755 /** 3756 * Trigger all the ready listeners for this `Component`. 3757 * 3758 * @fires Component#ready 3759 */ 3760 ; 3761 3762 _proto.triggerReady = function triggerReady() { 3763 this.isReady_ = true; // Ensure ready is triggered asynchronously 3764 3765 this.setTimeout(function () { 3766 var readyQueue = this.readyQueue_; // Reset Ready Queue 3767 3768 this.readyQueue_ = []; 3769 3770 if (readyQueue && readyQueue.length > 0) { 3771 readyQueue.forEach(function (fn) { 3772 fn.call(this); 3773 }, this); 3774 } // Allow for using event listeners also 3775 3776 /** 3777 * Triggered when a `Component` is ready. 3778 * 3779 * @event Component#ready 3780 * @type {EventTarget~Event} 3781 */ 3782 3783 3784 this.trigger('ready'); 3785 }, 1); 3786 } 3787 /** 3788 * Find a single DOM element matching a `selector`. This can be within the `Component`s 3789 * `contentEl()` or another custom context. 3790 * 3791 * @param {string} selector 3792 * A valid CSS selector, which will be passed to `querySelector`. 3793 * 3794 * @param {Element|string} [context=this.contentEl()] 3795 * A DOM element within which to query. Can also be a selector string in 3796 * which case the first matching element will get used as context. If 3797 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3798 * nothing it falls back to `document`. 3799 * 3800 * @return {Element|null} 3801 * the dom element that was found, or null 3802 * 3803 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 3804 */ 3805 ; 3806 3807 _proto.$ = function $$1(selector, context) { 3808 return $(selector, context || this.contentEl()); 3809 } 3810 /** 3811 * Finds all DOM element matching a `selector`. This can be within the `Component`s 3812 * `contentEl()` or another custom context. 3813 * 3814 * @param {string} selector 3815 * A valid CSS selector, which will be passed to `querySelectorAll`. 3816 * 3817 * @param {Element|string} [context=this.contentEl()] 3818 * A DOM element within which to query. Can also be a selector string in 3819 * which case the first matching element will get used as context. If 3820 * missing `this.contentEl()` gets used. If `this.contentEl()` returns 3821 * nothing it falls back to `document`. 3822 * 3823 * @return {NodeList} 3824 * a list of dom elements that were found 3825 * 3826 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) 3827 */ 3828 ; 3829 3830 _proto.$$ = function $$$1(selector, context) { 3831 return $$(selector, context || this.contentEl()); 3832 } 3833 /** 3834 * Check if a component's element has a CSS class name. 3835 * 3836 * @param {string} classToCheck 3837 * CSS class name to check. 3838 * 3839 * @return {boolean} 3840 * - True if the `Component` has the class. 3841 * - False if the `Component` does not have the class` 3842 */ 3843 ; 3844 3845 _proto.hasClass = function hasClass$1(classToCheck) { 3846 return hasClass(this.el_, classToCheck); 3847 } 3848 /** 3849 * Add a CSS class name to the `Component`s element. 3850 * 3851 * @param {string} classToAdd 3852 * CSS class name to add 3853 */ 3854 ; 3855 3856 _proto.addClass = function addClass$1(classToAdd) { 3857 addClass(this.el_, classToAdd); 3858 } 3859 /** 3860 * Remove a CSS class name from the `Component`s element. 3861 * 3862 * @param {string} classToRemove 3863 * CSS class name to remove 3864 */ 3865 ; 3866 3867 _proto.removeClass = function removeClass$1(classToRemove) { 3868 removeClass(this.el_, classToRemove); 3869 } 3870 /** 3871 * Add or remove a CSS class name from the component's element. 3872 * - `classToToggle` gets added when {@link Component#hasClass} would return false. 3873 * - `classToToggle` gets removed when {@link Component#hasClass} would return true. 3874 * 3875 * @param {string} classToToggle 3876 * The class to add or remove based on (@link Component#hasClass} 3877 * 3878 * @param {boolean|Dom~predicate} [predicate] 3879 * An {@link Dom~predicate} function or a boolean 3880 */ 3881 ; 3882 3883 _proto.toggleClass = function toggleClass$1(classToToggle, predicate) { 3884 toggleClass(this.el_, classToToggle, predicate); 3885 } 3886 /** 3887 * Show the `Component`s element if it is hidden by removing the 3888 * 'vjs-hidden' class name from it. 3889 */ 3890 ; 3891 3892 _proto.show = function show() { 3893 this.removeClass('vjs-hidden'); 3894 } 3895 /** 3896 * Hide the `Component`s element if it is currently showing by adding the 3897 * 'vjs-hidden` class name to it. 3898 */ 3899 ; 3900 3901 _proto.hide = function hide() { 3902 this.addClass('vjs-hidden'); 3903 } 3904 /** 3905 * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' 3906 * class name to it. Used during fadeIn/fadeOut. 3907 * 3908 * @private 3909 */ 3910 ; 3911 3912 _proto.lockShowing = function lockShowing() { 3913 this.addClass('vjs-lock-showing'); 3914 } 3915 /** 3916 * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' 3917 * class name from it. Used during fadeIn/fadeOut. 3918 * 3919 * @private 3920 */ 3921 ; 3922 3923 _proto.unlockShowing = function unlockShowing() { 3924 this.removeClass('vjs-lock-showing'); 3925 } 3926 /** 3927 * Get the value of an attribute on the `Component`s element. 3928 * 3929 * @param {string} attribute 3930 * Name of the attribute to get the value from. 3931 * 3932 * @return {string|null} 3933 * - The value of the attribute that was asked for. 3934 * - Can be an empty string on some browsers if the attribute does not exist 3935 * or has no value 3936 * - Most browsers will return null if the attibute does not exist or has 3937 * no value. 3938 * 3939 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} 3940 */ 3941 ; 3942 3943 _proto.getAttribute = function getAttribute$1(attribute) { 3944 return getAttribute(this.el_, attribute); 3945 } 3946 /** 3947 * Set the value of an attribute on the `Component`'s element 3948 * 3949 * @param {string} attribute 3950 * Name of the attribute to set. 3951 * 3952 * @param {string} value 3953 * Value to set the attribute to. 3954 * 3955 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} 3956 */ 3957 ; 3958 3959 _proto.setAttribute = function setAttribute$1(attribute, value) { 3960 setAttribute(this.el_, attribute, value); 3961 } 3962 /** 3963 * Remove an attribute from the `Component`s element. 3964 * 3965 * @param {string} attribute 3966 * Name of the attribute to remove. 3967 * 3968 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} 3969 */ 3970 ; 3971 3972 _proto.removeAttribute = function removeAttribute$1(attribute) { 3973 removeAttribute(this.el_, attribute); 3974 } 3975 /** 3976 * Get or set the width of the component based upon the CSS styles. 3977 * See {@link Component#dimension} for more detailed information. 3978 * 3979 * @param {number|string} [num] 3980 * The width that you want to set postfixed with '%', 'px' or nothing. 3981 * 3982 * @param {boolean} [skipListeners] 3983 * Skip the componentresize event trigger 3984 * 3985 * @return {number|string} 3986 * The width when getting, zero if there is no width. Can be a string 3987 * postpixed with '%' or 'px'. 3988 */ 3989 ; 3990 3991 _proto.width = function width(num, skipListeners) { 3992 return this.dimension('width', num, skipListeners); 3993 } 3994 /** 3995 * Get or set the height of the component based upon the CSS styles. 3996 * See {@link Component#dimension} for more detailed information. 3997 * 3998 * @param {number|string} [num] 3999 * The height that you want to set postfixed with '%', 'px' or nothing. 4000 * 4001 * @param {boolean} [skipListeners] 4002 * Skip the componentresize event trigger 4003 * 4004 * @return {number|string} 4005 * The width when getting, zero if there is no width. Can be a string 4006 * postpixed with '%' or 'px'. 4007 */ 4008 ; 4009 4010 _proto.height = function height(num, skipListeners) { 4011 return this.dimension('height', num, skipListeners); 4012 } 4013 /** 4014 * Set both the width and height of the `Component` element at the same time. 4015 * 4016 * @param {number|string} width 4017 * Width to set the `Component`s element to. 4018 * 4019 * @param {number|string} height 4020 * Height to set the `Component`s element to. 4021 */ 4022 ; 4023 4024 _proto.dimensions = function dimensions(width, height) { 4025 // Skip componentresize listeners on width for optimization 4026 this.width(width, true); 4027 this.height(height); 4028 } 4029 /** 4030 * Get or set width or height of the `Component` element. This is the shared code 4031 * for the {@link Component#width} and {@link Component#height}. 4032 * 4033 * Things to know: 4034 * - If the width or height in an number this will return the number postfixed with 'px'. 4035 * - If the width/height is a percent this will return the percent postfixed with '%' 4036 * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function 4037 * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. 4038 * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} 4039 * for more information 4040 * - If you want the computed style of the component, use {@link Component#currentWidth} 4041 * and {@link {Component#currentHeight} 4042 * 4043 * @fires Component#componentresize 4044 * 4045 * @param {string} widthOrHeight 4046 8 'width' or 'height' 4047 * 4048 * @param {number|string} [num] 4049 8 New dimension 4050 * 4051 * @param {boolean} [skipListeners] 4052 * Skip componentresize event trigger 4053 * 4054 * @return {number} 4055 * The dimension when getting or 0 if unset 4056 */ 4057 ; 4058 4059 _proto.dimension = function dimension(widthOrHeight, num, skipListeners) { 4060 if (num !== undefined) { 4061 // Set to zero if null or literally NaN (NaN !== NaN) 4062 if (num === null || num !== num) { 4063 num = 0; 4064 } // Check if using css width/height (% or px) and adjust 4065 4066 4067 if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { 4068 this.el_.style[widthOrHeight] = num; 4069 } else if (num === 'auto') { 4070 this.el_.style[widthOrHeight] = ''; 4071 } else { 4072 this.el_.style[widthOrHeight] = num + 'px'; 4073 } // skipListeners allows us to avoid triggering the resize event when setting both width and height 4074 4075 4076 if (!skipListeners) { 4077 /** 4078 * Triggered when a component is resized. 4079 * 4080 * @event Component#componentresize 4081 * @type {EventTarget~Event} 4082 */ 4083 this.trigger('componentresize'); 4084 } 4085 4086 return; 4087 } // Not setting a value, so getting it 4088 // Make sure element exists 4089 4090 4091 if (!this.el_) { 4092 return 0; 4093 } // Get dimension value from style 4094 4095 4096 var val = this.el_.style[widthOrHeight]; 4097 var pxIndex = val.indexOf('px'); 4098 4099 if (pxIndex !== -1) { 4100 // Return the pixel value with no 'px' 4101 return parseInt(val.slice(0, pxIndex), 10); 4102 } // No px so using % or no style was set, so falling back to offsetWidth/height 4103 // If component has display:none, offset will return 0 4104 // TODO: handle display:none and no dimension style using px 4105 4106 4107 return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); 4108 } 4109 /** 4110 * Get the computed width or the height of the component's element. 4111 * 4112 * Uses `window.getComputedStyle`. 4113 * 4114 * @param {string} widthOrHeight 4115 * A string containing 'width' or 'height'. Whichever one you want to get. 4116 * 4117 * @return {number} 4118 * The dimension that gets asked for or 0 if nothing was set 4119 * for that dimension. 4120 */ 4121 ; 4122 4123 _proto.currentDimension = function currentDimension(widthOrHeight) { 4124 var computedWidthOrHeight = 0; 4125 4126 if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { 4127 throw new Error('currentDimension only accepts width or height value'); 4128 } 4129 4130 computedWidthOrHeight = computedStyle(this.el_, widthOrHeight); // remove 'px' from variable and parse as integer 4131 4132 computedWidthOrHeight = parseFloat(computedWidthOrHeight); // if the computed value is still 0, it's possible that the browser is lying 4133 // and we want to check the offset values. 4134 // This code also runs wherever getComputedStyle doesn't exist. 4135 4136 if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) { 4137 var rule = "offset" + toTitleCase(widthOrHeight); 4138 computedWidthOrHeight = this.el_[rule]; 4139 } 4140 4141 return computedWidthOrHeight; 4142 } 4143 /** 4144 * An object that contains width and height values of the `Component`s 4145 * computed style. Uses `window.getComputedStyle`. 4146 * 4147 * @typedef {Object} Component~DimensionObject 4148 * 4149 * @property {number} width 4150 * The width of the `Component`s computed style. 4151 * 4152 * @property {number} height 4153 * The height of the `Component`s computed style. 4154 */ 4155 4156 /** 4157 * Get an object that contains computed width and height values of the 4158 * component's element. 4159 * 4160 * Uses `window.getComputedStyle`. 4161 * 4162 * @return {Component~DimensionObject} 4163 * The computed dimensions of the component's element. 4164 */ 4165 ; 4166 4167 _proto.currentDimensions = function currentDimensions() { 4168 return { 4169 width: this.currentDimension('width'), 4170 height: this.currentDimension('height') 4171 }; 4172 } 4173 /** 4174 * Get the computed width of the component's element. 4175 * 4176 * Uses `window.getComputedStyle`. 4177 * 4178 * @return {number} 4179 * The computed width of the component's element. 4180 */ 4181 ; 4182 4183 _proto.currentWidth = function currentWidth() { 4184 return this.currentDimension('width'); 4185 } 4186 /** 4187 * Get the computed height of the component's element. 4188 * 4189 * Uses `window.getComputedStyle`. 4190 * 4191 * @return {number} 4192 * The computed height of the component's element. 4193 */ 4194 ; 4195 4196 _proto.currentHeight = function currentHeight() { 4197 return this.currentDimension('height'); 4198 } 4199 /** 4200 * Set the focus to this component 4201 */ 4202 ; 4203 4204 _proto.focus = function focus() { 4205 this.el_.focus(); 4206 } 4207 /** 4208 * Remove the focus from this component 4209 */ 4210 ; 4211 4212 _proto.blur = function blur() { 4213 this.el_.blur(); 4214 } 4215 /** 4216 * When this Component receives a `keydown` event which it does not process, 4217 * it passes the event to the Player for handling. 4218 * 4219 * @param {EventTarget~Event} event 4220 * The `keydown` event that caused this function to be called. 4221 */ 4222 ; 4223 4224 _proto.handleKeyDown = function handleKeyDown(event) { 4225 if (this.player_) { 4226 // We only stop propagation here because we want unhandled events to fall 4227 // back to the browser. 4228 event.stopPropagation(); 4229 this.player_.handleKeyDown(event); 4230 } 4231 } 4232 /** 4233 * Many components used to have a `handleKeyPress` method, which was poorly 4234 * named because it listened to a `keydown` event. This method name now 4235 * delegates to `handleKeyDown`. This means anyone calling `handleKeyPress` 4236 * will not see their method calls stop working. 4237 * 4238 * @param {EventTarget~Event} event 4239 * The event that caused this function to be called. 4240 */ 4241 ; 4242 4243 _proto.handleKeyPress = function handleKeyPress(event) { 4244 this.handleKeyDown(event); 4245 } 4246 /** 4247 * Emit a 'tap' events when touch event support gets detected. This gets used to 4248 * support toggling the controls through a tap on the video. They get enabled 4249 * because every sub-component would have extra overhead otherwise. 4250 * 4251 * @private 4252 * @fires Component#tap 4253 * @listens Component#touchstart 4254 * @listens Component#touchmove 4255 * @listens Component#touchleave 4256 * @listens Component#touchcancel 4257 * @listens Component#touchend 4258 */ 4259 ; 4260 4261 _proto.emitTapEvents = function emitTapEvents() { 4262 // Track the start time so we can determine how long the touch lasted 4263 var touchStart = 0; 4264 var firstTouch = null; // Maximum movement allowed during a touch event to still be considered a tap 4265 // Other popular libs use anywhere from 2 (hammer.js) to 15, 4266 // so 10 seems like a nice, round number. 4267 4268 var tapMovementThreshold = 10; // The maximum length a touch can be while still being considered a tap 4269 4270 var touchTimeThreshold = 200; 4271 var couldBeTap; 4272 this.on('touchstart', function (event) { 4273 // If more than one finger, don't consider treating this as a click 4274 if (event.touches.length === 1) { 4275 // Copy pageX/pageY from the object 4276 firstTouch = { 4277 pageX: event.touches[0].pageX, 4278 pageY: event.touches[0].pageY 4279 }; // Record start time so we can detect a tap vs. "touch and hold" 4280 4281 touchStart = window$1.performance.now(); // Reset couldBeTap tracking 4282 4283 couldBeTap = true; 4284 } 4285 }); 4286 this.on('touchmove', function (event) { 4287 // If more than one finger, don't consider treating this as a click 4288 if (event.touches.length > 1) { 4289 couldBeTap = false; 4290 } else if (firstTouch) { 4291 // Some devices will throw touchmoves for all but the slightest of taps. 4292 // So, if we moved only a small distance, this could still be a tap 4293 var xdiff = event.touches[0].pageX - firstTouch.pageX; 4294 var ydiff = event.touches[0].pageY - firstTouch.pageY; 4295 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); 4296 4297 if (touchDistance > tapMovementThreshold) { 4298 couldBeTap = false; 4299 } 4300 } 4301 }); 4302 4303 var noTap = function noTap() { 4304 couldBeTap = false; 4305 }; // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s 4306 4307 4308 this.on('touchleave', noTap); 4309 this.on('touchcancel', noTap); // When the touch ends, measure how long it took and trigger the appropriate 4310 // event 4311 4312 this.on('touchend', function (event) { 4313 firstTouch = null; // Proceed only if the touchmove/leave/cancel event didn't happen 4314 4315 if (couldBeTap === true) { 4316 // Measure how long the touch lasted 4317 var touchTime = window$1.performance.now() - touchStart; // Make sure the touch was less than the threshold to be considered a tap 4318 4319 if (touchTime < touchTimeThreshold) { 4320 // Don't let browser turn this into a click 4321 event.preventDefault(); 4322 /** 4323 * Triggered when a `Component` is tapped. 4324 * 4325 * @event Component#tap 4326 * @type {EventTarget~Event} 4327 */ 4328 4329 this.trigger('tap'); // It may be good to copy the touchend event object and change the 4330 // type to tap, if the other event properties aren't exact after 4331 // Events.fixEvent runs (e.g. event.target) 4332 } 4333 } 4334 }); 4335 } 4336 /** 4337 * This function reports user activity whenever touch events happen. This can get 4338 * turned off by any sub-components that wants touch events to act another way. 4339 * 4340 * Report user touch activity when touch events occur. User activity gets used to 4341 * determine when controls should show/hide. It is simple when it comes to mouse 4342 * events, because any mouse event should show the controls. So we capture mouse 4343 * events that bubble up to the player and report activity when that happens. 4344 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player 4345 * controls. So touch events can't help us at the player level either. 4346 * 4347 * User activity gets checked asynchronously. So what could happen is a tap event 4348 * on the video turns the controls off. Then the `touchend` event bubbles up to 4349 * the player. Which, if it reported user activity, would turn the controls right 4350 * back on. We also don't want to completely block touch events from bubbling up. 4351 * Furthermore a `touchmove` event and anything other than a tap, should not turn 4352 * controls back on. 4353 * 4354 * @listens Component#touchstart 4355 * @listens Component#touchmove 4356 * @listens Component#touchend 4357 * @listens Component#touchcancel 4358 */ 4359 ; 4360 4361 _proto.enableTouchActivity = function enableTouchActivity() { 4362 // Don't continue if the root player doesn't support reporting user activity 4363 if (!this.player() || !this.player().reportUserActivity) { 4364 return; 4365 } // listener for reporting that the user is active 4366 4367 4368 var report = bind(this.player(), this.player().reportUserActivity); 4369 var touchHolding; 4370 this.on('touchstart', function () { 4371 report(); // For as long as the they are touching the device or have their mouse down, 4372 // we consider them active even if they're not moving their finger or mouse. 4373 // So we want to continue to update that they are active 4374 4375 this.clearInterval(touchHolding); // report at the same interval as activityCheck 4376 4377 touchHolding = this.setInterval(report, 250); 4378 }); 4379 4380 var touchEnd = function touchEnd(event) { 4381 report(); // stop the interval that maintains activity if the touch is holding 4382 4383 this.clearInterval(touchHolding); 4384 }; 4385 4386 this.on('touchmove', report); 4387 this.on('touchend', touchEnd); 4388 this.on('touchcancel', touchEnd); 4389 } 4390 /** 4391 * A callback that has no parameters and is bound into `Component`s context. 4392 * 4393 * @callback Component~GenericCallback 4394 * @this Component 4395 */ 4396 4397 /** 4398 * Creates a function that runs after an `x` millisecond timeout. This function is a 4399 * wrapper around `window.setTimeout`. There are a few reasons to use this one 4400 * instead though: 4401 * 1. It gets cleared via {@link Component#clearTimeout} when 4402 * {@link Component#dispose} gets called. 4403 * 2. The function callback will gets turned into a {@link Component~GenericCallback} 4404 * 4405 * > Note: You can't use `window.clearTimeout` on the id returned by this function. This 4406 * will cause its dispose listener not to get cleaned up! Please use 4407 * {@link Component#clearTimeout} or {@link Component#dispose} instead. 4408 * 4409 * @param {Component~GenericCallback} fn 4410 * The function that will be run after `timeout`. 4411 * 4412 * @param {number} timeout 4413 * Timeout in milliseconds to delay before executing the specified function. 4414 * 4415 * @return {number} 4416 * Returns a timeout ID that gets used to identify the timeout. It can also 4417 * get used in {@link Component#clearTimeout} to clear the timeout that 4418 * was set. 4419 * 4420 * @listens Component#dispose 4421 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} 4422 */ 4423 ; 4424 4425 _proto.setTimeout = function setTimeout(fn, timeout) { 4426 var _this2 = this; 4427 4428 // declare as variables so they are properly available in timeout function 4429 // eslint-disable-next-line 4430 var timeoutId; 4431 fn = bind(this, fn); 4432 this.clearTimersOnDispose_(); 4433 timeoutId = window$1.setTimeout(function () { 4434 if (_this2.setTimeoutIds_.has(timeoutId)) { 4435 _this2.setTimeoutIds_["delete"](timeoutId); 4436 } 4437 4438 fn(); 4439 }, timeout); 4440 this.setTimeoutIds_.add(timeoutId); 4441 return timeoutId; 4442 } 4443 /** 4444 * Clears a timeout that gets created via `window.setTimeout` or 4445 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} 4446 * use this function instead of `window.clearTimout`. If you don't your dispose 4447 * listener will not get cleaned up until {@link Component#dispose}! 4448 * 4449 * @param {number} timeoutId 4450 * The id of the timeout to clear. The return value of 4451 * {@link Component#setTimeout} or `window.setTimeout`. 4452 * 4453 * @return {number} 4454 * Returns the timeout id that was cleared. 4455 * 4456 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} 4457 */ 4458 ; 4459 4460 _proto.clearTimeout = function clearTimeout(timeoutId) { 4461 if (this.setTimeoutIds_.has(timeoutId)) { 4462 this.setTimeoutIds_["delete"](timeoutId); 4463 window$1.clearTimeout(timeoutId); 4464 } 4465 4466 return timeoutId; 4467 } 4468 /** 4469 * Creates a function that gets run every `x` milliseconds. This function is a wrapper 4470 * around `window.setInterval`. There are a few reasons to use this one instead though. 4471 * 1. It gets cleared via {@link Component#clearInterval} when 4472 * {@link Component#dispose} gets called. 4473 * 2. The function callback will be a {@link Component~GenericCallback} 4474 * 4475 * @param {Component~GenericCallback} fn 4476 * The function to run every `x` seconds. 4477 * 4478 * @param {number} interval 4479 * Execute the specified function every `x` milliseconds. 4480 * 4481 * @return {number} 4482 * Returns an id that can be used to identify the interval. It can also be be used in 4483 * {@link Component#clearInterval} to clear the interval. 4484 * 4485 * @listens Component#dispose 4486 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} 4487 */ 4488 ; 4489 4490 _proto.setInterval = function setInterval(fn, interval) { 4491 fn = bind(this, fn); 4492 this.clearTimersOnDispose_(); 4493 var intervalId = window$1.setInterval(fn, interval); 4494 this.setIntervalIds_.add(intervalId); 4495 return intervalId; 4496 } 4497 /** 4498 * Clears an interval that gets created via `window.setInterval` or 4499 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} 4500 * use this function instead of `window.clearInterval`. If you don't your dispose 4501 * listener will not get cleaned up until {@link Component#dispose}! 4502 * 4503 * @param {number} intervalId 4504 * The id of the interval to clear. The return value of 4505 * {@link Component#setInterval} or `window.setInterval`. 4506 * 4507 * @return {number} 4508 * Returns the interval id that was cleared. 4509 * 4510 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} 4511 */ 4512 ; 4513 4514 _proto.clearInterval = function clearInterval(intervalId) { 4515 if (this.setIntervalIds_.has(intervalId)) { 4516 this.setIntervalIds_["delete"](intervalId); 4517 window$1.clearInterval(intervalId); 4518 } 4519 4520 return intervalId; 4521 } 4522 /** 4523 * Queues up a callback to be passed to requestAnimationFrame (rAF), but 4524 * with a few extra bonuses: 4525 * 4526 * - Supports browsers that do not support rAF by falling back to 4527 * {@link Component#setTimeout}. 4528 * 4529 * - The callback is turned into a {@link Component~GenericCallback} (i.e. 4530 * bound to the component). 4531 * 4532 * - Automatic cancellation of the rAF callback is handled if the component 4533 * is disposed before it is called. 4534 * 4535 * @param {Component~GenericCallback} fn 4536 * A function that will be bound to this component and executed just 4537 * before the browser's next repaint. 4538 * 4539 * @return {number} 4540 * Returns an rAF ID that gets used to identify the timeout. It can 4541 * also be used in {@link Component#cancelAnimationFrame} to cancel 4542 * the animation frame callback. 4543 * 4544 * @listens Component#dispose 4545 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} 4546 */ 4547 ; 4548 4549 _proto.requestAnimationFrame = function requestAnimationFrame(fn) { 4550 var _this3 = this; 4551 4552 // Fall back to using a timer. 4553 if (!this.supportsRaf_) { 4554 return this.setTimeout(fn, 1000 / 60); 4555 } 4556 4557 this.clearTimersOnDispose_(); // declare as variables so they are properly available in rAF function 4558 // eslint-disable-next-line 4559 4560 var id; 4561 fn = bind(this, fn); 4562 id = window$1.requestAnimationFrame(function () { 4563 if (_this3.rafIds_.has(id)) { 4564 _this3.rafIds_["delete"](id); 4565 } 4566 4567 fn(); 4568 }); 4569 this.rafIds_.add(id); 4570 return id; 4571 } 4572 /** 4573 * Cancels a queued callback passed to {@link Component#requestAnimationFrame} 4574 * (rAF). 4575 * 4576 * If you queue an rAF callback via {@link Component#requestAnimationFrame}, 4577 * use this function instead of `window.cancelAnimationFrame`. If you don't, 4578 * your dispose listener will not get cleaned up until {@link Component#dispose}! 4579 * 4580 * @param {number} id 4581 * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. 4582 * 4583 * @return {number} 4584 * Returns the rAF ID that was cleared. 4585 * 4586 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} 4587 */ 4588 ; 4589 4590 _proto.cancelAnimationFrame = function cancelAnimationFrame(id) { 4591 // Fall back to using a timer. 4592 if (!this.supportsRaf_) { 4593 return this.clearTimeout(id); 4594 } 4595 4596 if (this.rafIds_.has(id)) { 4597 this.rafIds_["delete"](id); 4598 window$1.cancelAnimationFrame(id); 4599 } 4600 4601 return id; 4602 } 4603 /** 4604 * A function to setup `requestAnimationFrame`, `setTimeout`, 4605 * and `setInterval`, clearing on dispose. 4606 * 4607 * > Previously each timer added and removed dispose listeners on it's own. 4608 * For better performance it was decided to batch them all, and use `Set`s 4609 * to track outstanding timer ids. 4610 * 4611 * @private 4612 */ 4613 ; 4614 4615 _proto.clearTimersOnDispose_ = function clearTimersOnDispose_() { 4616 var _this4 = this; 4617 4618 if (this.clearingTimersOnDispose_) { 4619 return; 4620 } 4621 4622 this.clearingTimersOnDispose_ = true; 4623 this.one('dispose', function () { 4624 [['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEac
4624h(function (_ref) { 4625 var idName = _ref[0], 4626 cancelName = _ref[1]; 4627 4628 _this4[idName].forEach(_this4[cancelName], _this4); 4629 }); 4630 _this4.clearingTimersOnDispose_ = false; 4631 }); 4632 } 4633 /** 4634 * Register a `Component` with `videojs` given the name and the component. 4635 * 4636 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s 4637 * should be registered using {@link Tech.registerTech} or 4638 * {@link videojs:videojs.registerTech}. 4639 * 4640 * > NOTE: This function can also be seen on videojs as 4641 * {@link videojs:videojs.registerComponent}. 4642 * 4643 * @param {string} name 4644 * The name of the `Component` to register. 4645 * 4646 * @param {Component} ComponentToRegister 4647 * The `Component` class to register. 4648 * 4649 * @return {Component} 4650 * The `Component` that was registered. 4651 */ 4652 ; 4653 4654 Component.registerComponent = function registerComponent(name, ComponentToRegister) { 4655 if (typeof name !== 'string' || !name) { 4656 throw new Error("Illegal component name, \"" + name + "\"; must be a non-empty string."); 4657 } 4658 4659 var Tech = Component.getComponent('Tech'); // We need to make sure this check is only done if Tech has been registered. 4660 4661 var isTech = Tech && Tech.isTech(ComponentToRegister); 4662 var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); 4663 4664 if (isTech || !isComp) { 4665 var reason; 4666 4667 if (isTech) { 4668 reason = 'techs must be registered using Tech.registerTech()'; 4669 } else { 4670 reason = 'must be a Component subclass'; 4671 } 4672 4673 throw new Error("Illegal component, \"" + name + "\"; " + reason + "."); 4674 } 4675 4676 name = toTitleCase(name); 4677 4678 if (!Component.components_) { 4679 Component.components_ = {}; 4680 } 4681 4682 var Player = Component.getComponent('Player'); 4683 4684 if (name === 'Player' && Player && Player.players) { 4685 var players = Player.players; 4686 var playerNames = Object.keys(players); // If we have players that were disposed, then their name will still be 4687 // in Players.players. So, we must loop through and verify that the value 4688 // for each item is not null. This allows registration of the Player component 4689 // after all players have been disposed or before any were created. 4690 4691 if (players && playerNames.length > 0 && playerNames.map(function (pname) { 4692 return players[pname]; 4693 }).every(Boolean)) { 4694 throw new Error('Can not register Player component after player has been created.'); 4695 } 4696 } 4697 4698 Component.components_[name] = ComponentToRegister; 4699 Component.components_[toLowerCase(name)] = ComponentToRegister; 4700 return ComponentToRegister; 4701 } 4702 /** 4703 * Get a `Component` based on the name it was registered with. 4704 * 4705 * @param {string} name 4706 * The Name of the component to get. 4707 * 4708 * @return {Component} 4709 * The `Component` that got registered under the given name. 4710 * 4711 * @deprecated In `videojs` 6 this will not return `Component`s that were not 4712 * registered using {@link Component.registerComponent}. Currently we 4713 * check the global `videojs` object for a `Component` name and 4714 * return that if it exists. 4715 */ 4716 ; 4717 4718 Component.getComponent = function getComponent(name) { 4719 if (!name || !Component.components_) { 4720 return; 4721 } 4722 4723 return Component.components_[name]; 4724 }; 4725 4726 return Component; 4727 }(); 4728 /** 4729 * Whether or not this component supports `requestAnimationFrame`. 4730 * 4731 * This is exposed primarily for testing purposes. 4732 * 4733 * @private 4734 * @type {Boolean} 4735 */ 4736 4737 4738 Component.prototype.supportsRaf_ = typeof window$1.requestAnimationFrame === 'function' && typeof window$1.cancelAnimationFrame === 'function'; 4739 Component.registerComponent('Component', Component); 4740 4741 function _inheritsLoose(subClass, superClass) { 4742 subClass.prototype = Object.create(superClass.prototype); 4743 subClass.prototype.constructor = subClass; 4744 subClass.__proto__ = superClass; 4745 } 4746 4747 function _setPrototypeOf(o, p) { 4748 _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 4749 o.__proto__ = p; 4750 return o; 4751 }; 4752 4753 return _setPrototypeOf(o, p); 4754 } 4755 4756 function isNativeReflectConstruct() { 4757 if (typeof Reflect === "undefined" || !Reflect.construct) return false; 4758 if (Reflect.construct.sham) return false; 4759 if (typeof Proxy === "function") return true; 4760 4761 try { 4762 Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 4763 return true; 4764 } catch (e) { 4765 return false; 4766 } 4767 } 4768 4769 function _construct(Parent, args, Class) { 4770 if (isNativeReflectConstruct()) { 4771 _construct = Reflect.construct; 4772 } else { 4773 _construct = function _construct(Parent, args, Class) { 4774 var a = [null]; 4775 a.push.apply(a, args); 4776 var Constructor = Function.bind.apply(Parent, a); 4777 var instance = new Constructor(); 4778 if (Class) _setPrototypeOf(instance, Class.prototype); 4779 return instance; 4780 }; 4781 } 4782 4783 return _construct.apply(null, arguments); 4784 } 4785 4786 function _assertThisInitialized(self) { 4787 if (self === void 0) { 4788 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 4789 } 4790 4791 return self; 4792 } 4793 4794 /** 4795 * @file browser.js 4796 * @module browser 4797 */ 4798 var USER_AGENT = window$1.navigator && window$1.navigator.userAgent || '';
vendor: 13,550 bytes, lines 4799-5281
4799 var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT); 4800 var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null; 4801 /** 4802 * Whether or not this device is an iPad. 4803 * 4804 * @static 4805 * @const 4806 * @type {Boolean} 4807 */ 4808 4809 var IS_IPAD = /iPad/i.test(USER_AGENT); 4810 /** 4811 * Whether or not this device is an iPhone. 4812 * 4813 * @static 4814 * @const 4815 * @type {Boolean} 4816 */ 4817 // The Facebook app's UIWebView identifies as both an iPhone and iPad, so 4818 // to identify iPhones, we need to exclude iPads. 4819 // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/ 4820 4821 var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD; 4822 /** 4823 * Whether or not this device is an iPod. 4824 * 4825 * @static 4826 * @const 4827 * @type {Boolean} 4828 */ 4829 4830 var IS_IPOD = /iPod/i.test(USER_AGENT); 4831 /** 4832 * Whether or not this is an iOS device. 4833 * 4834 * @static 4835 * @const 4836 * @type {Boolean} 4837 */ 4838 4839 var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD; 4840 /** 4841 * The detected iOS version - or `null`. 4842 * 4843 * @static 4844 * @const 4845 * @type {string|null} 4846 */ 4847 4848 var IOS_VERSION = function () { 4849 var match = USER_AGENT.match(/OS (\d+)_/i); 4850 4851 if (match && match[1]) { 4852 return match[1]; 4853 } 4854 4855 return null; 4856 }(); 4857 /** 4858 * Whether or not this is an Android device. 4859 * 4860 * @static 4861 * @const 4862 * @type {Boolean} 4863 */ 4864 4865 var IS_ANDROID = /Android/i.test(USER_AGENT); 4866 /** 4867 * The detected Android version - or `null`. 4868 * 4869 * @static 4870 * @const 4871 * @type {number|string|null} 4872 */ 4873 4874 var ANDROID_VERSION = function () { 4875 // This matches Android Major.Minor.Patch versions 4876 // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned 4877 var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i); 4878 4879 if (!match) { 4880 return null; 4881 } 4882 4883 var major = match[1] && parseFloat(match[1]); 4884 var minor = match[2] && parseFloat(match[2]); 4885 4886 if (major && minor) { 4887 return parseFloat(match[1] + '.' + match[2]); 4888 } else if (major) { 4889 return major; 4890 } 4891 4892 return null; 4893 }(); 4894 /** 4895 * Whether or not this is a native Android browser. 4896 * 4897 * @static 4898 * @const 4899 * @type {Boolean} 4900 */ 4901 4902 var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537; 4903 /** 4904 * Whether or not this is Mozilla Firefox. 4905 * 4906 * @static 4907 * @const 4908 * @type {Boolean} 4909 */ 4910 4911 var IS_FIREFOX = /Firefox/i.test(USER_AGENT); 4912 /** 4913 * Whether or not this is Microsoft Edge. 4914 * 4915 * @static 4916 * @const 4917 * @type {Boolean} 4918 */ 4919 4920 var IS_EDGE = /Edge/i.test(USER_AGENT); 4921 /** 4922 * Whether or not this is Google Chrome. 4923 * 4924 * This will also be `true` for Chrome on iOS, which will have different support 4925 * as it is actually Safari under the hood. 4926 * 4927 * @static 4928 * @const 4929 * @type {Boolean} 4930 */ 4931 4932 var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT)); 4933 /** 4934 * The detected Google Chrome version - or `null`. 4935 * 4936 * @static 4937 * @const 4938 * @type {number|null} 4939 */ 4940 4941 var CHROME_VERSION = function () { 4942 var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/); 4943 4944 if (match && match[2]) { 4945 return parseFloat(match[2]); 4946 } 4947 4948 return null; 4949 }(); 4950 /** 4951 * The detected Internet Explorer version - or `null`. 4952 * 4953 * @static 4954 * @const 4955 * @type {number|null} 4956 */ 4957 4958 var IE_VERSION = function () { 4959 var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT); 4960 var version = result && parseFloat(result[1]); 4961 4962 if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) { 4963 // IE 11 has a different user agent string than other IE versions 4964 version = 11.0; 4965 } 4966 4967 return version; 4968 }(); 4969 /** 4970 * Whether or not this is desktop Safari. 4971 * 4972 * @static 4973 * @const 4974 * @type {Boolean} 4975 */ 4976 4977 var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; 4978 /** 4979 * Whether or not this is any flavor of Safari - including iOS. 4980 * 4981 * @static 4982 * @const 4983 * @type {Boolean} 4984 */ 4985 4986 var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME; 4987 /** 4988 * Whether or not this is a Windows machine. 4989 * 4990 * @static 4991 * @const 4992 * @type {Boolean} 4993 */ 4994 4995 var IS_WINDOWS = /Windows/i.test(USER_AGENT); 4996 /** 4997 * Whether or not this device is touch-enabled. 4998 * 4999 * @static 5000 * @const 5001 * @type {Boolean} 5002 */ 5003 5004 var TOUCH_ENABLED = isReal() && ('ontouchstart' in window$1 || window$1.navigator.maxTouchPoints || window$1.DocumentTouch && window$1.document instanceof window$1.DocumentTouch); 5005 5006 var browser = /*#__PURE__*/Object.freeze({ 5007 IS_IPAD: IS_IPAD, 5008 IS_IPHONE: IS_IPHONE, 5009 IS_IPOD: IS_IPOD, 5010 IS_IOS: IS_IOS, 5011 IOS_VERSION: IOS_VERSION, 5012 IS_ANDROID: IS_ANDROID, 5013 ANDROID_VERSION: ANDROID_VERSION, 5014 IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, 5015 IS_FIREFOX: IS_FIREFOX, 5016 IS_EDGE: IS_EDGE, 5017 IS_CHROME: IS_CHROME, 5018 CHROME_VERSION: CHROME_VERSION, 5019 IE_VERSION: IE_VERSION, 5020 IS_SAFARI: IS_SAFARI, 5021 IS_ANY_SAFARI: IS_ANY_SAFARI, 5022 IS_WINDOWS: IS_WINDOWS, 5023 TOUCH_ENABLED: TOUCH_ENABLED 5024 }); 5025 5026 /** 5027 * @file time-ranges.js 5028 * @module time-ranges 5029 */ 5030 5031 /** 5032 * Returns the time for the specified index at the start or end 5033 * of a TimeRange object. 5034 * 5035 * @typedef {Function} TimeRangeIndex 5036 * 5037 * @param {number} [index=0] 5038 * The range number to return the time for. 5039 * 5040 * @return {number} 5041 * The time offset at the specified index. 5042 * 5043 * @deprecated The index argument must be provided. 5044 * In the future, leaving it out will throw an error. 5045 */ 5046 5047 /** 5048 * An object that contains ranges of time. 5049 * 5050 * @typedef {Object} TimeRange 5051 * 5052 * @property {number} length 5053 * The number of time ranges represented by this object. 5054 * 5055 * @property {module:time-ranges~TimeRangeIndex} start 5056 * Returns the time offset at which a specified time range begins. 5057 * 5058 * @property {module:time-ranges~TimeRangeIndex} end 5059 * Returns the time offset at which a specified time range ends. 5060 * 5061 * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges 5062 */ 5063 5064 /** 5065 * Check if any of the time ranges are over the maximum index. 5066 * 5067 * @private 5068 * @param {string} fnName 5069 * The function name to use for logging 5070 * 5071 * @param {number} index 5072 * The index to check 5073 * 5074 * @param {number} maxIndex 5075 * The maximum possible index 5076 * 5077 * @throws {Error} if the timeRanges provided are over the maxIndex 5078 */ 5079 function rangeCheck(fnName, index, maxIndex) { 5080 if (typeof index !== 'number' || index < 0 || index > maxIndex) { 5081 throw new Error("Failed to execute '" + fnName + "' on 'TimeRanges': The index provided (" + index + ") is non-numeric or out of bounds (0-" + maxIndex + ")."); 5082 } 5083 } 5084 /** 5085 * Get the time for the specified index at the start or end 5086 * of a TimeRange object. 5087 * 5088 * @private 5089 * @param {string} fnName 5090 * The function name to use for logging 5091 * 5092 * @param {string} valueIndex 5093 * The property that should be used to get the time. should be 5094 * 'start' or 'end' 5095 * 5096 * @param {Array} ranges 5097 * An array of time ranges 5098 * 5099 * @param {Array} [rangeIndex=0] 5100 * The index to start the search at 5101 * 5102 * @return {number} 5103 * The time that offset at the specified index. 5104 * 5105 * @deprecated rangeIndex must be set to a value, in the future this will throw an error. 5106 * @throws {Error} if rangeIndex is more than the length of ranges 5107 */ 5108 5109 5110 function getRange(fnName, valueIndex, ranges, rangeIndex) { 5111 rangeCheck(fnName, rangeIndex, ranges.length - 1); 5112 return ranges[rangeIndex][valueIndex]; 5113 } 5114 /** 5115 * Create a time range object given ranges of time. 5116 * 5117 * @private 5118 * @param {Array} [ranges] 5119 * An array of time ranges. 5120 */ 5121 5122 5123 function createTimeRangesObj(ranges) { 5124 if (ranges === undefined || ranges.length === 0) { 5125 return { 5126 length: 0, 5127 start: function start() { 5128 throw new Error('This TimeRanges object is empty'); 5129 }, 5130 end: function end() { 5131 throw new Error('This TimeRanges object is empty'); 5132 } 5133 }; 5134 } 5135 5136 return { 5137 length: ranges.length, 5138 start: getRange.bind(null, 'start', 0, ranges), 5139 end: getRange.bind(null, 'end', 1, ranges) 5140 }; 5141 } 5142 /** 5143 * Create a `TimeRange` object which mimics an 5144 * {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}. 5145 * 5146 * @param {number|Array[]} start 5147 * The start of a single range (a number) or an array of ranges (an 5148 * array of arrays of two numbers each). 5149 * 5150 * @param {number} end 5151 * The end of a single range. Cannot be used with the array form of 5152 * the `start` argument. 5153 */ 5154 5155 5156 function createTimeRanges(start, end) { 5157 if (Array.isArray(start)) { 5158 return createTimeRangesObj(start); 5159 } else if (start === undefined || end === undefined) { 5160 return createTimeRangesObj(); 5161 } 5162 5163 return createTimeRangesObj([[start, end]]); 5164 } 5165 5166 /** 5167 * @file buffer.js 5168 * @module buffer 5169 */ 5170 /** 5171 * Compute the percentage of the media that has been buffered. 5172 * 5173 * @param {TimeRange} buffered 5174 * The current `TimeRange` object representing buffered time ranges 5175 * 5176 * @param {number} duration 5177 * Total duration of the media 5178 * 5179 * @return {number} 5180 * Percent buffered of the total duration in decimal form. 5181 */ 5182 5183 function bufferedPercent(buffered, duration) { 5184 var bufferedDuration = 0; 5185 var start; 5186 var end; 5187 5188 if (!duration) { 5189 return 0; 5190 } 5191 5192 if (!buffered || !buffered.length) { 5193 buffered = createTimeRanges(0, 0); 5194 } 5195 5196 for (var i = 0; i < buffered.length; i++) { 5197 start = buffered.start(i); 5198 end = buffered.end(i); // buffered end can be bigger than duration by a very small fraction 5199 5200 if (end > duration) { 5201 end = duration; 5202 } 5203 5204 bufferedDuration += end - start; 5205 } 5206 5207 return bufferedDuration / duration; 5208 } 5209 5210 /** 5211 * @file fullscreen-api.js 5212 * @module fullscreen-api 5213 * @private 5214 */ 5215 /** 5216 * Store the browser-specific methods for the fullscreen API. 5217 * 5218 * @type {Object} 5219 * @see [Specification]{@link https://fullscreen.spec.whatwg.org} 5220 * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} 5221 */ 5222 5223 var FullscreenApi = { 5224 prefixed: true 5225 }; // browser API methods 5226 5227 var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'], // WebKit 5228 ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen'], // Mozilla 5229 ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror', '-moz-full-screen'], // Microsoft 5230 ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError', '-ms-fullscreen']]; 5231 var specApi = apiMap[0]; 5232 var browserApi; // determine the supported set of functions 5233 5234 for (var i = 0; i < apiMap.length; i++) { 5235 // check for exitFullscreen function 5236 if (apiMap[i][1] in document) { 5237 browserApi = apiMap[i]; 5238 break; 5239 } 5240 } // map the browser API names to the spec API names 5241 5242 5243 if (browserApi) { 5244 for (var _i = 0; _i < browserApi.length; _i++) { 5245 FullscreenApi[specApi[_i]] = browserApi[_i]; 5246 } 5247 5248 FullscreenApi.prefixed = browserApi[0] !== specApi[0]; 5249 } 5250 5251 /** 5252 * @file media-error.js 5253 */ 5254 /** 5255 * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. 5256 * 5257 * @param {number|string|Object|MediaError} value 5258 * This can be of multiple types: 5259 * - number: should be a standard error code 5260 * - string: an error message (the code will be 0) 5261 * - Object: arbitrary properties 5262 * - `MediaError` (native): used to populate a video.js `MediaError` object 5263 * - `MediaError` (video.js): will return itself if it's already a 5264 * video.js `MediaError` object. 5265 * 5266 * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} 5267 * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} 5268 * 5269 * @class MediaError 5270 */ 5271 5272 function MediaError(value) { 5273 // Allow redundant calls to this constructor to avoid having `instanceof` 5274 // checks peppered around the code. 5275 if (value instanceof MediaError) { 5276 return value; 5277 } 5278 5279 if (typeof value === 'number') { 5280 this.code = value; 5281 } else if (typeof value === 'string') {
vendor: 11,383 bytes, lines 5282-5663
5282 // default code is zero, so this is a custom error 5283 this.message = value; 5284 } else if (isObject(value)) { 5285 // We assign the `code` property manually because native `MediaError` objects 5286 // do not expose it as an own/enumerable property of the object. 5287 if (typeof value.code === 'number') { 5288 this.code = value.code; 5289 } 5290 5291 assign(this, value); 5292 } 5293 5294 if (!this.message) { 5295 this.message = MediaError.defaultMessages[this.code] || ''; 5296 } 5297 } 5298 /** 5299 * The error code that refers two one of the defined `MediaError` types 5300 * 5301 * @type {Number} 5302 */ 5303 5304 5305 MediaError.prototype.code = 0; 5306 /** 5307 * An optional message that to show with the error. Message is not part of the HTML5 5308 * video spec but allows for more informative custom errors. 5309 * 5310 * @type {String} 5311 */ 5312 5313 MediaError.prototype.message = ''; 5314 /** 5315 * An optional status code that can be set by plugins to allow even more detail about 5316 * the error. For example a plugin might provide a specific HTTP status code and an 5317 * error message for that code. Then when the plugin gets that error this class will 5318 * know how to display an error message for it. This allows a custom message to show 5319 * up on the `Player` error overlay. 5320 * 5321 * @type {Array} 5322 */ 5323 5324 MediaError.prototype.status = null; 5325 /** 5326 * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the 5327 * specification listed under {@link MediaError} for more information. 5328 * 5329 * @enum {array} 5330 * @readonly 5331 * @property {string} 0 - MEDIA_ERR_CUSTOM 5332 * @property {string} 1 - MEDIA_ERR_ABORTED 5333 * @property {string} 2 - MEDIA_ERR_NETWORK 5334 * @property {string} 3 - MEDIA_ERR_DECODE 5335 * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED 5336 * @property {string} 5 - MEDIA_ERR_ENCRYPTED 5337 */ 5338 5339 MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; 5340 /** 5341 * The default `MediaError` messages based on the {@link MediaError.errorTypes}. 5342 * 5343 * @type {Array} 5344 * @constant 5345 */ 5346 5347 MediaError.defaultMessages = { 5348 1: 'You aborted the media playback', 5349 2: 'A network error caused the media download to fail part-way.', 5350 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', 5351 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', 5352 5: 'The media is encrypted and we do not have the keys to decrypt it.' 5353 }; // Add types as properties on MediaError 5354 // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; 5355 5356 for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { 5357 MediaError[MediaError.errorTypes[errNum]] = errNum; // values should be accessible on both the class and instance 5358 5359 MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; 5360 } // jsdocs for instance/static members added above 5361 5362 var tuple = SafeParseTuple; 5363 5364 function SafeParseTuple(obj, reviver) { 5365 var json; 5366 var error = null; 5367 5368 try { 5369 json = JSON.parse(obj, reviver); 5370 } catch (err) { 5371 error = err; 5372 } 5373 5374 return [error, json]; 5375 } 5376 5377 /** 5378 * Returns whether an object is `Promise`-like (i.e. has a `then` method). 5379 * 5380 * @param {Object} value 5381 * An object that may or may not be `Promise`-like. 5382 * 5383 * @return {boolean} 5384 * Whether or not the object is `Promise`-like. 5385 */ 5386 function isPromise(value) { 5387 return value !== undefined && value !== null && typeof value.then === 'function'; 5388 } 5389 /** 5390 * Silence a Promise-like object. 5391 * 5392 * This is useful for avoiding non-harmful, but potentially confusing "uncaught 5393 * play promise" rejection error messages. 5394 * 5395 * @param {Object} value 5396 * An object that may or may not be `Promise`-like. 5397 */ 5398 5399 function silencePromise(value) { 5400 if (isPromise(value)) { 5401 value.then(null, function (e) {}); 5402 } 5403 } 5404 5405 /** 5406 * @file text-track-list-converter.js Utilities for capturing text track state and 5407 * re-creating tracks based on a capture. 5408 * 5409 * @module text-track-list-converter 5410 */ 5411 5412 /** 5413 * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that 5414 * represents the {@link TextTrack}'s state. 5415 * 5416 * @param {TextTrack} track 5417 * The text track to query. 5418 * 5419 * @return {Object} 5420 * A serializable javascript representation of the TextTrack. 5421 * @private 5422 */ 5423 var trackToJson_ = function trackToJson_(track) { 5424 var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { 5425 if (track[prop]) { 5426 acc[prop] = track[prop]; 5427 } 5428 5429 return acc; 5430 }, { 5431 cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { 5432 return { 5433 startTime: cue.startTime, 5434 endTime: cue.endTime, 5435 text: cue.text, 5436 id: cue.id 5437 }; 5438 }) 5439 }); 5440 return ret; 5441 }; 5442 /** 5443 * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the 5444 * state of all {@link TextTrack}s currently configured. The return array is compatible with 5445 * {@link text-track-list-converter:jsonToTextTracks}. 5446 * 5447 * @param {Tech} tech 5448 * The tech object to query 5449 * 5450 * @return {Array} 5451 * A serializable javascript representation of the {@link Tech}s 5452 * {@link TextTrackList}. 5453 */ 5454 5455 5456 var textTracksToJson = function textTracksToJson(tech) { 5457 var trackEls = tech.$$('track'); 5458 var trackObjs = Array.prototype.map.call(trackEls, function (t) { 5459 return t.track; 5460 }); 5461 var tracks = Array.prototype.map.call(trackEls, function (trackEl) { 5462 var json = trackToJson_(trackEl.track); 5463 5464 if (trackEl.src) { 5465 json.src = trackEl.src; 5466 } 5467 5468 return json; 5469 }); 5470 return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { 5471 return trackObjs.indexOf(track) === -1; 5472 }).map(trackToJson_)); 5473 }; 5474 /** 5475 * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript 5476 * object {@link TextTrack} representations. 5477 * 5478 * @param {Array} json 5479 * An array of `TextTrack` representation objects, like those that would be 5480 * produced by `textTracksToJson`. 5481 * 5482 * @param {Tech} tech 5483 * The `Tech` to create the `TextTrack`s on. 5484 */ 5485 5486 5487 var jsonToTextTracks = function jsonToTextTracks(json, tech) { 5488 json.forEach(function (track) { 5489 var addedTrack = tech.addRemoteTextTrack(track).track; 5490 5491 if (!track.src && track.cues) { 5492 track.cues.forEach(function (cue) { 5493 return addedTrack.addCue(cue); 5494 }); 5495 } 5496 }); 5497 return tech.textTracks(); 5498 }; 5499 5500 var textTrackConverter = { 5501 textTracksToJson: textTracksToJson, 5502 jsonToTextTracks: jsonToTextTracks, 5503 trackToJson_: trackToJson_ 5504 }; 5505 5506 function createCommonjsModule(fn, module) { 5507 return module = { exports: {} }, fn(module, module.exports), module.exports; 5508 } 5509 5510 var keycode = createCommonjsModule(function (module, exports) { 5511 // Source: http://jsfiddle.net/vWx8V/ 5512 // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes 5513 5514 /** 5515 * Conenience method returns corresponding value for given keyName or keyCode. 5516 * 5517 * @param {Mixed} keyCode {Number} or keyName {String} 5518 * @return {Mixed} 5519 * @api public 5520 */ 5521 function keyCode(searchInput) { 5522 // Keyboard Events 5523 if (searchInput && 'object' === typeof searchInput) { 5524 var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode; 5525 if (hasKeyCode) searchInput = hasKeyCode; 5526 } // Numbers 5527 5528 5529 if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string) 5530 5531 var search = String(searchInput); // check codes 5532 5533 var foundNamedKey = codes[search.toLowerCase()]; 5534 if (foundNamedKey) return foundNamedKey; // check aliases 5535 5536 var foundNamedKey = aliases[search.toLowerCase()]; 5537 if (foundNamedKey) return foundNamedKey; // weird character? 5538 5539 if (search.length === 1) return search.charCodeAt(0); 5540 return undefined; 5541 } 5542 /** 5543 * Compares a keyboard event with a given keyCode or keyName. 5544 * 5545 * @param {Event} event Keyboard event that should be tested 5546 * @param {Mixed} keyCode {Number} or keyName {String} 5547 * @return {Boolean} 5548 * @api public 5549 */ 5550 5551 5552 keyCode.isEventKey = function isEventKey(event, nameOrCode) { 5553 if (event && 'object' === typeof event) { 5554 var keyCode = event.which || event.keyCode || event.charCode; 5555 5556 if (keyCode === null || keyCode === undefined) { 5557 return false; 5558 } 5559 5560 if (typeof nameOrCode === 'string') { 5561 // check codes 5562 var foundNamedKey = codes[nameOrCode.toLowerCase()]; 5563 5564 if (foundNamedKey) { 5565 return foundNamedKey === keyCode; 5566 } // check aliases 5567 5568 5569 var foundNamedKey = aliases[nameOrCode.toLowerCase()]; 5570 5571 if (foundNamedKey) { 5572 return foundNamedKey === keyCode; 5573 } 5574 } else if (typeof nameOrCode === 'number') { 5575 return nameOrCode === keyCode; 5576 } 5577 5578 return false; 5579 } 5580 }; 5581 5582 exports = module.exports = keyCode; 5583 /** 5584 * Get by name 5585 * 5586 * exports.code['enter'] // => 13 5587 */ 5588 5589 var codes = exports.code = exports.codes = { 5590 'backspace': 8, 5591 'tab': 9, 5592 'enter': 13, 5593 'shift': 16, 5594 'ctrl': 17, 5595 'alt': 18, 5596 'pause/break': 19, 5597 'caps lock': 20, 5598 'esc': 27, 5599 'space': 32, 5600 'page up': 33, 5601 'page down': 34, 5602 'end': 35, 5603 'home': 36, 5604 'left': 37, 5605 'up': 38, 5606 'right': 39, 5607 'down': 40, 5608 'insert': 45, 5609 'delete': 46, 5610 'command': 91, 5611 'left command': 91, 5612 'right command': 93, 5613 'numpad *': 106, 5614 'numpad +': 107, 5615 'numpad -': 109, 5616 'numpad .': 110, 5617 'numpad /': 111, 5618 'num lock': 144, 5619 'scroll lock': 145, 5620 'my computer': 182, 5621 'my calculator': 183, 5622 ';': 186, 5623 '=': 187, 5624 ',': 188, 5625 '-': 189, 5626 '.': 190, 5627 '/': 191, 5628 '`': 192, 5629 '[': 219, 5630 '\\': 220, 5631 ']': 221, 5632 "'": 222 // Helper aliases 5633 5634 }; 5635 var aliases = exports.aliases = { 5636 'windows': 91, 5637 'â§': 16, 5638 'â¥': 18, 5639 'â': 17, 5640 'â': 91, 5641 'ctl': 17, 5642 'control': 17, 5643 'option': 18, 5644 'pause': 19, 5645 'break': 19, 5646 'caps': 20, 5647 'return': 13, 5648 'escape': 27, 5649 'spc': 32, 5650 'spacebar': 32, 5651 'pgup': 33, 5652 'pgdn': 34, 5653 'ins': 45, 5654 'del': 46, 5655 'cmd': 91 5656 /*! 5657 * Programatically add the following 5658 */ 5659 // lower case chars 5660 5661 }; 5662 5663 for (i = 97; i < 123; i++) {
5664 codes[String.fromCharCode(i)] = i - 32; 5665 } // numbers 5666 5667 5668 for (var i = 48; i < 58; i++) { 5669 codes[i - 48] = i; 5670 } // function keys 5671 5672 5673 for (i = 1; i < 13; i++) { 5674 codes['f' + i] = i + 111; 5675 } // numpad keys 5676 5677 5678 for (i = 0; i < 10; i++) { 5679 codes['numpad ' + i] = i + 96; 5680 } 5681 /** 5682 * Get by code 5683 * 5684 * exports.name[13] // => 'Enter' 5685 */ 5686 5687 5688 var names = exports.names = exports.title = {}; // title for backward compat 5689 // Create reverse mapping 5690 5691 for (i in codes) { 5692 names[codes[i]] = i; 5693 } // Add aliases 5694 5695 5696 for (var alias in aliases) { 5697 codes[alias] = aliases[alias]; 5698 } 5699 }); 5700 var keycode_1 = keycode.code; 5701 var keycode_2 = keycode.codes; 5702 var keycode_3 = keycode.aliases; 5703 var keycode_4 = keycode.names; 5704 var keycode_5 = keycode.title; 5705 5706 var MODAL_CLASS_NAME = 'vjs-modal-dialog'; 5707 /** 5708 * The `ModalDialog` displays over the video and its controls, which blocks 5709 * interaction with the player until it is closed. 5710 * 5711 * Modal dialogs include a "Close" button and will close when that button 5712 * is activated - or when ESC is pressed anywhere. 5713 * 5714 * @extends Component 5715 */ 5716 5717 var ModalDialog = 5718 /*#__PURE__*/ 5719 function (_Component) { 5720 _inheritsLoose(ModalDialog, _Component); 5721 5722 /** 5723 * Create an instance of this class. 5724 * 5725 * @param {Player} player 5726 * The `Player` that this class should be attached to. 5727 * 5728 * @param {Object} [options] 5729 * The key/value store of player options. 5730 * 5731 * @param {Mixed} [options.content=undefined] 5732 * Provide customized content for this modal. 5733 * 5734 * @param {string} [options.description] 5735 * A text description for the modal, primarily for accessibility. 5736 * 5737 * @param {boolean} [options.fillAlways=false] 5738 * Normally, modals are automatically filled only the first time 5739 * they open. This tells the modal to refresh its content 5740 * every time it opens. 5741 * 5742 * @param {string} [options.label] 5743 * A text label for the modal, primarily for accessibility. 5744 * 5745 * @param {boolean} [options.pauseOnOpen=true] 5746 * If `true`, playback will will be paused if playing when 5747 * the modal opens, and resumed when it closes. 5748 * 5749 * @param {boolean} [options.temporary=true] 5750 * If `true`, the modal can only be opened once; it will be 5751 * disposed as soon as it's closed. 5752 * 5753 * @param {boolean} [options.uncloseable=false] 5754 * If `true`, the user will not be able to close the modal 5755 * through the UI in the normal ways. Programmatic closing is 5756 * still possible. 5757 */ 5758 function ModalDialog(player, options) { 5759 var _this; 5760 5761 _this = _Component.call(this, player, options) || this; 5762 _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; 5763 5764 _this.closeable(!_this.options_.uncloseable); 5765 5766 _this.content(_this.options_.content); // Make sure the contentEl is defined AFTER any children are initialized 5767 // because we only want the contents of the modal in the contentEl 5768 // (not the UI elements like the close button). 5769 5770 5771 _this.contentEl_ = createEl('div', { 5772 className: MODAL_CLASS_NAME + "-content" 5773 }, { 5774 role: 'document' 5775 }); 5776 _this.descEl_ = createEl('p', { 5777 className: MODAL_CLASS_NAME + "-description vjs-control-text", 5778 id: _this.el().getAttribute('aria-describedby') 5779 }); 5780 textContent(_this.descEl_, _this.description()); 5781 5782 _this.el_.appendChild(_this.descEl_); 5783 5784 _this.el_.appendChild(_this.contentEl_); 5785 5786 return _this; 5787 } 5788 /** 5789 * Create the `ModalDialog`'s DOM element 5790 * 5791 * @return {Element} 5792 * The DOM element that gets created. 5793 */ 5794 5795 5796 var _proto = ModalDialog.prototype; 5797 5798 _proto.createEl = function createEl() { 5799 return _Component.prototype.createEl.call(this, 'div', { 5800 className: this.buildCSSClass(), 5801 tabIndex: -1 5802 }, { 5803 'aria-describedby': this.id() + "_description", 5804 'aria-hidden': 'true', 5805 'aria-label': this.label(), 5806 'role': 'dialog' 5807 }); 5808 }; 5809 5810 _proto.dispose = function dispose() { 5811 this.contentEl_ = null; 5812 this.descEl_ = null; 5813 this.previouslyActiveEl_ = null; 5814 5815 _Component.prototype.dispose.call(this); 5816 } 5817 /** 5818 * Builds the default DOM `className`. 5819 * 5820 * @return {string} 5821 * The DOM `className` for this object. 5822 */ 5823 ; 5824 5825 _proto.buildCSSClass = function buildCSSClass() { 5826 return MODAL_CLASS_NAME + " vjs-hidden " + _Component.prototype.buildCSSClass.call(this); 5827 } 5828 /**
vendor: 37,031 bytes, lines 5829-7207
5829 * Returns the label string for this modal. Primarily used for accessibility. 5830 * 5831 * @return {string} 5832 * the localized or raw label of this modal. 5833 */ 5834 ; 5835 5836 _proto.label = function label() { 5837 return this.localize(this.options_.label || 'Modal Window'); 5838 } 5839 /** 5840 * Returns the description string for this modal. Primarily used for 5841 * accessibility. 5842 * 5843 * @return {string} 5844 * The localized or raw description of this modal. 5845 */ 5846 ; 5847 5848 _proto.description = function description() { 5849 var desc = this.options_.description || this.localize('This is a modal window.'); // Append a universal closeability message if the modal is closeable. 5850 5851 if (this.closeable()) { 5852 desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); 5853 } 5854 5855 return desc; 5856 } 5857 /** 5858 * Opens the modal. 5859 * 5860 * @fires ModalDialog#beforemodalopen 5861 * @fires ModalDialog#modalopen 5862 */ 5863 ; 5864 5865 _proto.open = function open() { 5866 if (!this.opened_) { 5867 var player = this.player(); 5868 /** 5869 * Fired just before a `ModalDialog` is opened. 5870 * 5871 * @event ModalDialog#beforemodalopen 5872 * @type {EventTarget~Event} 5873 */ 5874 5875 this.trigger('beforemodalopen'); 5876 this.opened_ = true; // Fill content if the modal has never opened before and 5877 // never been filled. 5878 5879 if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { 5880 this.fill(); 5881 } // If the player was playing, pause it and take note of its previously 5882 // playing state. 5883 5884 5885 this.wasPlaying_ = !player.paused(); 5886 5887 if (this.options_.pauseOnOpen && this.wasPlaying_) { 5888 player.pause(); 5889 } 5890 5891 this.on('keydown', this.handleKeyDown); // Hide controls and note if they were enabled. 5892 5893 this.hadControls_ = player.controls(); 5894 player.controls(false); 5895 this.show(); 5896 this.conditionalFocus_(); 5897 this.el().setAttribute('aria-hidden', 'false'); 5898 /** 5899 * Fired just after a `ModalDialog` is opened. 5900 * 5901 * @event ModalDialog#modalopen 5902 * @type {EventTarget~Event} 5903 */ 5904 5905 this.trigger('modalopen'); 5906 this.hasBeenOpened_ = true; 5907 } 5908 } 5909 /** 5910 * If the `ModalDialog` is currently open or closed. 5911 * 5912 * @param {boolean} [value] 5913 * If given, it will open (`true`) or close (`false`) the modal. 5914 * 5915 * @return {boolean} 5916 * the current open state of the modaldialog 5917 */ 5918 ; 5919 5920 _proto.opened = function opened(value) { 5921 if (typeof value === 'boolean') { 5922 this[value ? 'open' : 'close'](); 5923 } 5924 5925 return this.opened_; 5926 } 5927 /** 5928 * Closes the modal, does nothing if the `ModalDialog` is 5929 * not open. 5930 * 5931 * @fires ModalDialog#beforemodalclose 5932 * @fires ModalDialog#modalclose 5933 */ 5934 ; 5935 5936 _proto.close = function close() { 5937 if (!this.opened_) { 5938 return; 5939 } 5940 5941 var player = this.player(); 5942 /** 5943 * Fired just before a `ModalDialog` is closed. 5944 * 5945 * @event ModalDialog#beforemodalclose 5946 * @type {EventTarget~Event} 5947 */ 5948 5949 this.trigger('beforemodalclose'); 5950 this.opened_ = false; 5951 5952 if (this.wasPlaying_ && this.options_.pauseOnOpen) { 5953 player.play(); 5954 } 5955 5956 this.off('keydown', this.handleKeyDown); 5957 5958 if (this.hadControls_) { 5959 player.controls(true); 5960 } 5961 5962 this.hide(); 5963 this.el().setAttribute('aria-hidden', 'true'); 5964 /** 5965 * Fired just after a `ModalDialog` is closed. 5966 * 5967 * @event ModalDialog#modalclose 5968 * @type {EventTarget~Event} 5969 */ 5970 5971 this.trigger('modalclose'); 5972 this.conditionalBlur_(); 5973 5974 if (this.options_.temporary) { 5975 this.dispose(); 5976 } 5977 } 5978 /** 5979 * Check to see if the `ModalDialog` is closeable via the UI. 5980 * 5981 * @param {boolean} [value] 5982 * If given as a boolean, it will set the `closeable` option. 5983 * 5984 * @return {boolean} 5985 * Returns the final value of the closable option. 5986 */ 5987 ; 5988 5989 _proto.closeable = function closeable(value) { 5990 if (typeof value === 'boolean') { 5991 var closeable = this.closeable_ = !!value; 5992 var close = this.getChild('closeButton'); // If this is being made closeable and has no close button, add one. 5993 5994 if (closeable && !close) { 5995 // The close button should be a child of the modal - not its 5996 // content element, so temporarily change the content element. 5997 var temp = this.contentEl_; 5998 this.contentEl_ = this.el_; 5999 close = this.addChild('closeButton', { 6000 controlText: 'Close Modal Dialog' 6001 }); 6002 this.contentEl_ = temp; 6003 this.on(close, 'close', this.close); 6004 } // If this is being made uncloseable and has a close button, remove it. 6005 6006 6007 if (!closeable && close) { 6008 this.off(close, 'close', this.close); 6009 this.removeChild(close); 6010 close.dispose(); 6011 } 6012 } 6013 6014 return this.closeable_; 6015 } 6016 /** 6017 * Fill the modal's content element with the modal's "content" option. 6018 * The content element will be emptied before this change takes place. 6019 */ 6020 ; 6021 6022 _proto.fill = function fill() { 6023 this.fillWith(this.content()); 6024 } 6025 /** 6026 * Fill the modal's content element with arbitrary content. 6027 * The content element will be emptied before this change takes place. 6028 * 6029 * @fires ModalDialog#beforemodalfill 6030 * @fires ModalDialog#modalfill 6031 * 6032 * @param {Mixed} [content] 6033 * The same rules apply to this as apply to the `content` option. 6034 */ 6035 ; 6036 6037 _proto.fillWith = function fillWith(content) { 6038 var contentEl = this.contentEl(); 6039 var parentEl = contentEl.parentNode; 6040 var nextSiblingEl = contentEl.nextSibling; 6041 /** 6042 * Fired just before a `ModalDialog` is filled with content. 6043 * 6044 * @event ModalDialog#beforemodalfill 6045 * @type {EventTarget~Event} 6046 */ 6047 6048 this.trigger('beforemodalfill'); 6049 this.hasBeenFilled_ = true; // Detach the content element from the DOM before performing 6050 // manipulation to avoid modifying the live DOM multiple times. 6051 6052 parentEl.removeChild(contentEl); 6053 this.empty(); 6054 insertContent(contentEl, content); 6055 /** 6056 * Fired just after a `ModalDialog` is filled with content. 6057 * 6058 * @event ModalDialog#modalfill 6059 * @type {EventTarget~Event} 6060 */ 6061 6062 this.trigger('modalfill'); // Re-inject the re-filled content element. 6063 6064 if (nextSiblingEl) { 6065 parentEl.insertBefore(contentEl, nextSiblingEl); 6066 } else { 6067 parentEl.appendChild(contentEl); 6068 } // make sure that the close button is last in the dialog DOM 6069 6070 6071 var closeButton = this.getChild('closeButton'); 6072 6073 if (closeButton) { 6074 parentEl.appendChild(closeButton.el_); 6075 } 6076 } 6077 /** 6078 * Empties the content element. This happens anytime the modal is filled. 6079 * 6080 * @fires ModalDialog#beforemodalempty 6081 * @fires ModalDialog#modalempty 6082 */ 6083 ; 6084 6085 _proto.empty = function empty() { 6086 /** 6087 * Fired just before a `ModalDialog` is emptied. 6088 * 6089 * @event ModalDialog#beforemodalempty 6090 * @type {EventTarget~Event} 6091 */ 6092 this.trigger('beforemodalempty'); 6093 emptyEl(this.contentEl()); 6094 /** 6095 * Fired just after a `ModalDialog` is emptied. 6096 * 6097 * @event ModalDialog#modalempty 6098 * @type {EventTarget~Event} 6099 */ 6100 6101 this.trigger('modalempty'); 6102 } 6103 /** 6104 * Gets or sets the modal content, which gets normalized before being 6105 * rendered into the DOM. 6106 * 6107 * This does not update the DOM or fill the modal, but it is called during 6108 * that process. 6109 * 6110 * @param {Mixed} [value] 6111 * If defined, sets the internal content value to be used on the 6112 * next call(s) to `fill`. This value is normalized before being 6113 * inserted. To "clear" the internal content value, pass `null`. 6114 * 6115 * @return {Mixed} 6116 * The current content of the modal dialog 6117 */ 6118 ; 6119 6120 _proto.content = function content(value) { 6121 if (typeof value !== 'undefined') { 6122 this.content_ = value; 6123 } 6124 6125 return this.content_; 6126 } 6127 /** 6128 * conditionally focus the modal dialog if focus was previously on the player. 6129 * 6130 * @private 6131 */ 6132 ; 6133 6134 _proto.conditionalFocus_ = function conditionalFocus_() { 6135 var activeEl = document.activeElement; 6136 var playerEl = this.player_.el_; 6137 this.previouslyActiveEl_ = null; 6138 6139 if (playerEl.contains(activeEl) || playerEl === activeEl) { 6140 this.previouslyActiveEl_ = activeEl; 6141 this.focus(); 6142 } 6143 } 6144 /** 6145 * conditionally blur the element and refocus the last focused element 6146 * 6147 * @private 6148 */ 6149 ; 6150 6151 _proto.conditionalBlur_ = function conditionalBlur_() { 6152 if (this.previouslyActiveEl_) { 6153 this.previouslyActiveEl_.focus(); 6154 this.previouslyActiveEl_ = null; 6155 } 6156 } 6157 /** 6158 * Keydown handler. Attached when modal is focused. 6159 * 6160 * @listens keydown 6161 */ 6162 ; 6163 6164 _proto.handleKeyDown = function handleKeyDown(event) { 6165 // Do not allow keydowns to reach out of the modal dialog. 6166 event.stopPropagation(); 6167 6168 if (keycode.isEventKey(event, 'Escape') && this.closeable()) { 6169 event.preventDefault(); 6170 this.close(); 6171 return; 6172 } // exit early if it isn't a tab key 6173 6174 6175 if (!keycode.isEventKey(event, 'Tab')) { 6176 return; 6177 } 6178 6179 var focusableEls = this.focusableEls_(); 6180 var activeEl = this.el_.querySelector(':focus'); 6181 var focusIndex; 6182 6183 for (var i = 0; i < focusableEls.length; i++) { 6184 if (activeEl === focusableEls[i]) { 6185 focusIndex = i; 6186 break; 6187 } 6188 } 6189 6190 if (document.activeElement === this.el_) { 6191 focusIndex = 0; 6192 } 6193 6194 if (event.shiftKey && focusIndex === 0) { 6195 focusableEls[focusableEls.length - 1].focus(); 6196 event.preventDefault(); 6197 } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { 6198 focusableEls[0].focus(); 6199 event.preventDefault(); 6200 } 6201 } 6202 /** 6203 * get all focusable elements 6204 * 6205 * @private 6206 */ 6207 ; 6208 6209 _proto.focusableEls_ = function focusableEls_() { 6210 var allChildren = this.el_.querySelectorAll('*'); 6211 return Array.prototype.filter.call(allChildren, function (child) { 6212 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'); 6213 }); 6214 }; 6215 6216 return ModalDialog; 6217 }(Component); 6218 /** 6219 * Default options for `ModalDialog` default options. 6220 * 6221 * @type {Object} 6222 * @private 6223 */ 6224 6225 6226 ModalDialog.prototype.options_ = { 6227 pauseOnOpen: true, 6228 temporary: true 6229 }; 6230 Component.registerComponent('ModalDialog', ModalDialog); 6231 6232 /** 6233 * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and 6234 * {@link VideoTrackList} 6235 * 6236 * @extends EventTarget 6237 */ 6238 6239 var TrackList = 6240 /*#__PURE__*/ 6241 function (_EventTarget) { 6242 _inheritsLoose(TrackList, _EventTarget); 6243 6244 /** 6245 * Create an instance of this class 6246 * 6247 * @param {Track[]} tracks 6248 * A list of tracks to initialize the list with. 6249 * 6250 * @abstract 6251 */ 6252 function TrackList(tracks) { 6253 var _this; 6254 6255 if (tracks === void 0) { 6256 tracks = []; 6257 } 6258 6259 _this = _EventTarget.call(this) || this; 6260 _this.tracks_ = []; 6261 /** 6262 * @memberof TrackList 6263 * @member {number} length 6264 * The current number of `Track`s in the this Trackist. 6265 * @instance 6266 */ 6267 6268 Object.defineProperty(_assertThisInitialized(_this), 'length', { 6269 get: function get() { 6270 return this.tracks_.length; 6271 } 6272 }); 6273 6274 for (var i = 0; i < tracks.length; i++) { 6275 _this.addTrack(tracks[i]); 6276 } 6277 6278 return _this; 6279 } 6280 /** 6281 * Add a {@link Track} to the `TrackList` 6282 * 6283 * @param {Track} track 6284 * The audio, video, or text track to add to the list. 6285 * 6286 * @fires TrackList#addtrack 6287 */ 6288 6289 6290 var _proto = TrackList.prototype; 6291 6292 _proto.addTrack = function addTrack(track) { 6293 var index = this.tracks_.length; 6294 6295 if (!('' + index in this)) { 6296 Object.defineProperty(this, index, { 6297 get: function get() { 6298 return this.tracks_[index]; 6299 } 6300 }); 6301 } // Do not add duplicate tracks 6302 6303 6304 if (this.tracks_.indexOf(track) === -1) { 6305 this.tracks_.push(track); 6306 /** 6307 * Triggered when a track is added to a track list. 6308 * 6309 * @event TrackList#addtrack 6310 * @type {EventTarget~Event} 6311 * @property {Track} track 6312 * A reference to track that was added. 6313 */ 6314 6315 this.trigger({ 6316 track: track, 6317 type: 'addtrack', 6318 target: this 6319 }); 6320 } 6321 } 6322 /** 6323 * Remove a {@link Track} from the `TrackList` 6324 * 6325 * @param {Track} rtrack 6326 * The audio, video, or text track to remove from the list. 6327 * 6328 * @fires TrackList#removetrack 6329 */ 6330 ; 6331 6332 _proto.removeTrack = function removeTrack(rtrack) { 6333 var track; 6334 6335 for (var i = 0, l = this.length; i < l; i++) { 6336 if (this[i] === rtrack) { 6337 track = this[i]; 6338 6339 if (track.off) { 6340 track.off(); 6341 } 6342 6343 this.tracks_.splice(i, 1); 6344 break; 6345 } 6346 } 6347 6348 if (!track) { 6349 return; 6350 } 6351 /** 6352 * Triggered when a track is removed from track list. 6353 * 6354 * @event TrackList#removetrack 6355 * @type {EventTarget~Event} 6356 * @property {Track} track 6357 * A reference to track that was removed. 6358 */ 6359 6360 6361 this.trigger({ 6362 track: track, 6363 type: 'removetrack', 6364 target: this 6365 }); 6366 } 6367 /** 6368 * Get a Track from the TrackList by a tracks id 6369 * 6370 * @param {string} id - the id of the track to get 6371 * @method getTrackById 6372 * @return {Track} 6373 * @private 6374 */ 6375 ; 6376 6377 _proto.getTrackById = function getTrackById(id) { 6378 var result = null; 6379 6380 for (var i = 0, l = this.length; i < l; i++) { 6381 var track = this[i]; 6382 6383 if (track.id === id) { 6384 result = track; 6385 break; 6386 } 6387 } 6388 6389 return result; 6390 }; 6391 6392 return TrackList; 6393 }(EventTarget); 6394 /** 6395 * Triggered when a different track is selected/enabled. 6396 * 6397 * @event TrackList#change 6398 * @type {EventTarget~Event} 6399 */ 6400 6401 /** 6402 * Events that can be called with on + eventName. See {@link EventHandler}. 6403 * 6404 * @property {Object} TrackList#allowedEvents_ 6405 * @private 6406 */ 6407 6408 6409 TrackList.prototype.allowedEvents_ = { 6410 change: 'change', 6411 addtrack: 'addtrack', 6412 removetrack: 'removetrack' 6413 }; // emulate attribute EventHandler support to allow for feature detection 6414 6415 for (var event in TrackList.prototype.allowedEvents_) { 6416 TrackList.prototype['on' + event] = null; 6417 } 6418 6419 /** 6420 * Anywhere we call this function we diverge from the spec 6421 * as we only support one enabled audiotrack at a time 6422 * 6423 * @param {AudioTrackList} list 6424 * list to work on 6425 * 6426 * @param {AudioTrack} track 6427 * The track to skip 6428 * 6429 * @private 6430 */ 6431 6432 var disableOthers = function disableOthers(list, track) { 6433 for (var i = 0; i < list.length; i++) { 6434 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6435 continue; 6436 } // another audio track is enabled, disable it 6437 6438 6439 list[i].enabled = false; 6440 } 6441 }; 6442 /** 6443 * The current list of {@link AudioTrack} for a media file. 6444 * 6445 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} 6446 * @extends TrackList 6447 */ 6448 6449 6450 var AudioTrackList = 6451 /*#__PURE__*/ 6452 function (_TrackList) { 6453 _inheritsLoose(AudioTrackList, _TrackList); 6454 6455 /** 6456 * Create an instance of this class. 6457 * 6458 * @param {AudioTrack[]} [tracks=[]] 6459 * A list of `AudioTrack` to instantiate the list with. 6460 */ 6461 function AudioTrackList(tracks) { 6462 var _this; 6463 6464 if (tracks === void 0) { 6465 tracks = []; 6466 } 6467 6468 // make sure only 1 track is enabled 6469 // sorted from last index to first index 6470 for (var i = tracks.length - 1; i >= 0; i--) { 6471 if (tracks[i].enabled) { 6472 disableOthers(tracks, tracks[i]); 6473 break; 6474 } 6475 } 6476 6477 _this = _TrackList.call(this, tracks) || this; 6478 _this.changing_ = false; 6479 return _this; 6480 } 6481 /** 6482 * Add an {@link AudioTrack} to the `AudioTrackList`. 6483 * 6484 * @param {AudioTrack} track 6485 * The AudioTrack to add to the list 6486 * 6487 * @fires TrackList#addtrack 6488 */ 6489 6490 6491 var _proto = AudioTrackList.prototype; 6492 6493 _proto.addTrack = function addTrack(track) { 6494 var _this2 = this; 6495 6496 if (track.enabled) { 6497 disableOthers(this, track); 6498 } 6499 6500 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6501 6502 6503 if (!track.addEventListener) { 6504 return; 6505 } 6506 6507 track.enabledChange_ = function () { 6508 // when we are disabling other tracks (since we don't support 6509 // more than one track at a time) we will set changing_ 6510 // to true so that we don't trigger additional change events 6511 if (_this2.changing_) { 6512 return; 6513 } 6514 6515 _this2.changing_ = true; 6516 disableOthers(_this2, track); 6517 _this2.changing_ = false; 6518 6519 _this2.trigger('change'); 6520 }; 6521 /** 6522 * @listens AudioTrack#enabledchange 6523 * @fires TrackList#change 6524 */ 6525 6526 6527 track.addEventListener('enabledchange', track.enabledChange_); 6528 }; 6529 6530 _proto.removeTrack = function removeTrack(rtrack) { 6531 _TrackList.prototype.removeTrack.call(this, rtrack); 6532 6533 if (rtrack.removeEventListener && rtrack.enabledChange_) { 6534 rtrack.removeEventListener('enabledchange', rtrack.enabledChange_); 6535 rtrack.enabledChange_ = null; 6536 } 6537 }; 6538 6539 return AudioTrackList; 6540 }(TrackList); 6541 6542 /** 6543 * Un-select all other {@link VideoTrack}s that are selected. 6544 * 6545 * @param {VideoTrackList} list 6546 * list to work on 6547 * 6548 * @param {VideoTrack} track 6549 * The track to skip 6550 * 6551 * @private 6552 */ 6553 6554 var disableOthers$1 = function disableOthers(list, track) { 6555 for (var i = 0; i < list.length; i++) { 6556 if (!Object.keys(list[i]).length || track.id === list[i].id) { 6557 continue; 6558 } // another video track is enabled, disable it 6559 6560 6561 list[i].selected = false; 6562 } 6563 }; 6564 /** 6565 * The current list of {@link VideoTrack} for a video. 6566 * 6567 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} 6568 * @extends TrackList 6569 */ 6570 6571 6572 var VideoTrackList = 6573 /*#__PURE__*/ 6574 function (_TrackList) { 6575 _inheritsLoose(VideoTrackList, _TrackList); 6576 6577 /** 6578 * Create an instance of this class. 6579 * 6580 * @param {VideoTrack[]} [tracks=[]] 6581 * A list of `VideoTrack` to instantiate the list with. 6582 */ 6583 function VideoTrackList(tracks) { 6584 var _this; 6585 6586 if (tracks === void 0) { 6587 tracks = []; 6588 } 6589 6590 // make sure only 1 track is enabled 6591 // sorted from last index to first index 6592 for (var i = tracks.length - 1; i >= 0; i--) { 6593 if (tracks[i].selected) { 6594 disableOthers$1(tracks, tracks[i]); 6595 break; 6596 } 6597 } 6598 6599 _this = _TrackList.call(this, tracks) || this; 6600 _this.changing_ = false; 6601 /** 6602 * @member {number} VideoTrackList#selectedIndex 6603 * The current index of the selected {@link VideoTrack`}. 6604 */ 6605 6606 Object.defineProperty(_assertThisInitialized(_this), 'selectedIndex', { 6607 get: function get() { 6608 for (var _i = 0; _i < this.length; _i++) { 6609 if (this[_i].selected) { 6610 return _i; 6611 } 6612 } 6613 6614 return -1; 6615 }, 6616 set: function set() {} 6617 }); 6618 return _this; 6619 } 6620 /** 6621 * Add a {@link VideoTrack} to the `VideoTrackList`. 6622 * 6623 * @param {VideoTrack} track 6624 * The VideoTrack to add to the list 6625 * 6626 * @fires TrackList#addtrack 6627 */ 6628 6629 6630 var _proto = VideoTrackList.prototype; 6631 6632 _proto.addTrack = function addTrack(track) { 6633 var _this2 = this; 6634 6635 if (track.selected) { 6636 disableOthers$1(this, track); 6637 } 6638 6639 _TrackList.prototype.addTrack.call(this, track); // native tracks don't have this 6640 6641 6642 if (!track.addEventListener) { 6643 return; 6644 } 6645 6646 track.selectedChange_ = function () { 6647 if (_this2.changing_) { 6648 return; 6649 } 6650 6651 _this2.changing_ = true; 6652 disableOthers$1(_this2, track); 6653 _this2.changing_ = false; 6654 6655 _this2.trigger('change'); 6656 }; 6657 /** 6658 * @listens VideoTrack#selectedchange 6659 * @fires TrackList#change 6660 */ 6661 6662 6663 track.addEventListener('selectedchange', track.selectedChange_); 6664 }; 6665 6666 _proto.removeTrack = function removeTrack(rtrack) { 6667 _TrackList.prototype.removeTrack.call(this, rtrack); 6668 6669 if (rtrack.removeEventListener && rtrack.selectedChange_) { 6670 rtrack.removeEventListener('selectedchange', rtrack.selectedChange_); 6671 rtrack.selectedChange_ = null; 6672 } 6673 }; 6674 6675 return VideoTrackList; 6676 }(TrackList); 6677 6678 /** 6679 * The current list of {@link TextTrack} for a media file. 6680 * 6681 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} 6682 * @extends TrackList 6683 */ 6684 6685 var TextTrackList = 6686 /*#__PURE__*/ 6687 function (_TrackList) { 6688 _inheritsLoose(TextTrackList, _TrackList); 6689 6690 function TextTrackList() { 6691 return _TrackList.apply(this, arguments) || this; 6692 } 6693 6694 var _proto = TextTrackList.prototype; 6695 6696 /** 6697 * Add a {@link TextTrack} to the `TextTrackList` 6698 * 6699 * @param {TextTrack} track 6700 * The text track to add to the list. 6701 * 6702 * @fires TrackList#addtrack 6703 */ 6704 _proto.addTrack = function addTrack(track) { 6705 var _this = this; 6706 6707 _TrackList.prototype.addTrack.call(this, track); 6708 6709 if (!this.queueChange_) { 6710 this.queueChange_ = function () { 6711 return _this.queueTrigger('change'); 6712 }; 6713 } 6714 6715 if (!this.triggerSelectedlanguagechange) { 6716 this.triggerSelectedlanguagechange_ = function () { 6717 return _this.trigger('selectedlanguagechange'); 6718 }; 6719 } 6720 /** 6721 * @listens TextTrack#modechange 6722 * @fires TrackList#change 6723 */ 6724 6725 6726 track.addEventListener('modechange', this.queueChange_); 6727 var nonLanguageTextTrackKind = ['metadata', 'chapters']; 6728 6729 if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { 6730 track.addEventListener('modechange', this.triggerSelectedlanguagechange_); 6731 } 6732 }; 6733 6734 _proto.removeTrack = function removeTrack(rtrack) { 6735 _TrackList.prototype.removeTrack.call(this, rtrack); // manually remove the event handlers we added 6736 6737 6738 if (rtrack.removeEventListener) { 6739 if (this.queueChange_) { 6740 rtrack.removeEventListener('modechange', this.queueChange_); 6741 } 6742 6743 if (this.selectedlanguagechange_) { 6744 rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_); 6745 } 6746 } 6747 }; 6748 6749 return TextTrackList; 6750 }(TrackList); 6751 6752 /** 6753 * @file html-track-element-list.js 6754 */ 6755 6756 /** 6757 * The current list of {@link HtmlTrackElement}s. 6758 */ 6759 var HtmlTrackElementList = 6760 /*#__PURE__*/ 6761 function () { 6762 /** 6763 * Create an instance of this class. 6764 * 6765 * @param {HtmlTrackElement[]} [tracks=[]] 6766 * A list of `HtmlTrackElement` to instantiate the list with. 6767 */ 6768 function HtmlTrackElementList(trackElements) { 6769 if (trackElements === void 0) { 6770 trackElements = []; 6771 } 6772 6773 this.trackElements_ = []; 6774 /** 6775 * @memberof HtmlTrackElementList 6776 * @member {number} length 6777 * The current number of `Track`s in the this Trackist. 6778 * @instance 6779 */ 6780 6781 Object.defineProperty(this, 'length', { 6782 get: function get() { 6783 return this.trackElements_.length; 6784 } 6785 }); 6786 6787 for (var i = 0, length = trackElements.length; i < length; i++) { 6788 this.addTrackElement_(trackElements[i]); 6789 } 6790 } 6791 /** 6792 * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` 6793 * 6794 * @param {HtmlTrackElement} trackElement 6795 * The track element to add to the list. 6796 * 6797 * @private 6798 */ 6799 6800 6801 var _proto = HtmlTrackElementList.prototype; 6802 6803 _proto.addTrackElement_ = function addTrackElement_(trackElement) { 6804 var index = this.trackElements_.length; 6805 6806 if (!('' + index in this)) { 6807 Object.defineProperty(this, index, { 6808 get: function get() { 6809 return this.trackElements_[index]; 6810 } 6811 }); 6812 } // Do not add duplicate elements 6813 6814 6815 if (this.trackElements_.indexOf(trackElement) === -1) { 6816 this.trackElements_.push(trackElement); 6817 } 6818 } 6819 /** 6820 * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an 6821 * {@link TextTrack}. 6822 * 6823 * @param {TextTrack} track 6824 * The track associated with a track element. 6825 * 6826 * @return {HtmlTrackElement|undefined} 6827 * The track element that was found or undefined. 6828 * 6829 * @private 6830 */ 6831 ; 6832 6833 _proto.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { 6834 var trackElement_; 6835 6836 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6837 if (track === this.trackElements_[i].track) { 6838 trackElement_ = this.trackElements_[i]; 6839 break; 6840 } 6841 } 6842 6843 return trackElement_; 6844 } 6845 /** 6846 * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` 6847 * 6848 * @param {HtmlTrackElement} trackElement 6849 * The track element to remove from the list. 6850 * 6851 * @private 6852 */ 6853 ; 6854 6855 _proto.removeTrackElement_ = function removeTrackElement_(trackElement) { 6856 for (var i = 0, length = this.trackElements_.length; i < length; i++) { 6857 if (trackElement === this.trackElements_[i]) { 6858 if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') { 6859 this.trackElements_[i].track.off(); 6860 } 6861 6862 if (typeof this.trackElements_[i].off === 'function') { 6863 this.trackElements_[i].off(); 6864 } 6865 6866 this.trackElements_.splice(i, 1); 6867 break; 6868 } 6869 } 6870 }; 6871 6872 return HtmlTrackElementList; 6873 }(); 6874 6875 /** 6876 * @file text-track-cue-list.js 6877 */ 6878 6879 /** 6880 * @typedef {Object} TextTrackCueList~TextTrackCue 6881 * 6882 * @property {string} id 6883 * The unique id for this text track cue 6884 * 6885 * @property {number} startTime 6886 * The start time for this text track cue 6887 * 6888 * @property {number} endTime 6889 * The end time for this text track cue 6890 * 6891 * @property {boolean} pauseOnExit 6892 * Pause when the end time is reached if true. 6893 * 6894 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} 6895 */ 6896 6897 /** 6898 * A List of TextTrackCues. 6899 * 6900 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} 6901 */ 6902 var TextTrackCueList = 6903 /*#__PURE__*/ 6904 function () { 6905 /** 6906 * Create an instance of this class.. 6907 * 6908 * @param {Array} cues 6909 * A list of cues to be initialized with 6910 */ 6911 function TextTrackCueList(cues) { 6912 TextTrackCueList.prototype.setCues_.call(this, cues); 6913 /** 6914 * @memberof TextTrackCueList 6915 * @member {number} length 6916 * The current number of `TextTrackCue`s in the TextTrackCueList. 6917 * @instance 6918 */ 6919 6920 Object.defineProperty(this, 'length', { 6921 get: function get() { 6922 return this.length_; 6923 } 6924 }); 6925 } 6926 /** 6927 * A setter for cues in this list. Creates getters 6928 * an an index for the cues. 6929 * 6930 * @param {Array} cues 6931 * An array of cues to set 6932 * 6933 * @private 6934 */ 6935 6936 6937 var _proto = TextTrackCueList.prototype; 6938 6939 _proto.setCues_ = function setCues_(cues) { 6940 var oldLength = this.length || 0; 6941 var i = 0; 6942 var l = cues.length; 6943 this.cues_ = cues; 6944 this.length_ = cues.length; 6945 6946 var defineProp = function defineProp(index) { 6947 if (!('' + index in this)) { 6948 Object.defineProperty(this, '' + index, { 6949 get: function get() { 6950 return this.cues_[index]; 6951 } 6952 }); 6953 } 6954 }; 6955 6956 if (oldLength < l) { 6957 i = oldLength; 6958 6959 for (; i < l; i++) { 6960 defineProp.call(this, i); 6961 } 6962 } 6963 } 6964 /** 6965 * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. 6966 * 6967 * @param {string} id 6968 * The id of the cue that should be searched for. 6969 * 6970 * @return {TextTrackCueList~TextTrackCue|null} 6971 * A single cue or null if none was found. 6972 */ 6973 ; 6974 6975 _proto.getCueById = function getCueById(id) { 6976 var result = null; 6977 6978 for (var i = 0, l = this.length; i < l; i++) { 6979 var cue = this[i]; 6980 6981 if (cue.id === id) { 6982 result = cue; 6983 break; 6984 } 6985 } 6986 6987 return result; 6988 }; 6989 6990 return TextTrackCueList; 6991 }(); 6992 6993 /** 6994 * @file track-kinds.js 6995 */ 6996 6997 /** 6998 * All possible `VideoTrackKind`s 6999 * 7000 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind 7001 * @typedef VideoTrack~Kind 7002 * @enum 7003 */ 7004 var VideoTrackKind = { 7005 alternative: 'alternative', 7006 captions: 'captions', 7007 main: 'main', 7008 sign: 'sign', 7009 subtitles: 'subtitles', 7010 commentary: 'commentary' 7011 }; 7012 /** 7013 * All possible `AudioTrackKind`s 7014 * 7015 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind 7016 * @typedef AudioTrack~Kind 7017 * @enum 7018 */ 7019 7020 var AudioTrackKind = { 7021 'alternative': 'alternative', 7022 'descriptions': 'descriptions', 7023 'main': 'main', 7024 'main-desc': 'main-desc', 7025 'translation': 'translation', 7026 'commentary': 'commentary' 7027 }; 7028 /** 7029 * All possible `TextTrackKind`s 7030 * 7031 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind 7032 * @typedef TextTrack~Kind 7033 * @enum 7034 */ 7035 7036 var TextTrackKind = { 7037 subtitles: 'subtitles', 7038 captions: 'captions', 7039 descriptions: 'descriptions', 7040 chapters: 'chapters', 7041 metadata: 'metadata' 7042 }; 7043 /** 7044 * All possible `TextTrackMode`s 7045 * 7046 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode 7047 * @typedef TextTrack~Mode 7048 * @enum 7049 */ 7050 7051 var TextTrackMode = { 7052 disabled: 'disabled', 7053 hidden: 'hidden', 7054 showing: 'showing' 7055 }; 7056 7057 /** 7058 * A Track class that contains all of the common functionality for {@link AudioTrack}, 7059 * {@link VideoTrack}, and {@link TextTrack}. 7060 * 7061 * > Note: This class should not be used directly 7062 * 7063 * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} 7064 * @extends EventTarget 7065 * @abstract 7066 */ 7067 7068 var Track = 7069 /*#__PURE__*/ 7070 function (_EventTarget) { 7071 _inheritsLoose(Track, _EventTarget); 7072 7073 /** 7074 * Create an instance of this class. 7075 * 7076 * @param {Object} [options={}] 7077 * Object of option names and values 7078 * 7079 * @param {string} [options.kind=''] 7080 * A valid kind for the track type you are creating. 7081 * 7082 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 7083 * A unique id for this AudioTrack. 7084 * 7085 * @param {string} [options.label=''] 7086 * The menu label for this track. 7087 * 7088 * @param {string} [options.language=''] 7089 * A valid two character language code. 7090 * 7091 * @abstract 7092 */ 7093 function Track(options) { 7094 var _this; 7095 7096 if (options === void 0) { 7097 options = {}; 7098 } 7099 7100 _this = _EventTarget.call(this) || this; 7101 var trackProps = { 7102 id: options.id || 'vjs_track_' + newGUID(), 7103 kind: options.kind || '', 7104 label: options.label || '', 7105 language: options.language || '' 7106 }; 7107 /** 7108 * @memberof Track 7109 * @member {string} id 7110 * The id of this track. Cannot be changed after creation. 7111 * @instance 7112 * 7113 * @readonly 7114 */ 7115 7116 /** 7117 * @memberof Track 7118 * @member {string} kind 7119 * The kind of track that this is. Cannot be changed after creation. 7120 * @instance 7121 * 7122 * @readonly 7123 */ 7124 7125 /** 7126 * @memberof Track 7127 * @member {string} label 7128 * The label of this track. Cannot be changed after creation. 7129 * @instance 7130 * 7131 * @readonly 7132 */ 7133 7134 /** 7135 * @memberof Track 7136 * @member {string} language 7137 * The two letter language code for this track. Cannot be changed after 7138 * creation. 7139 * @instance 7140 * 7141 * @readonly 7142 */ 7143 7144 var _loop = function _loop(key) { 7145 Object.defineProperty(_assertThisInitialized(_this), key, { 7146 get: function get() { 7147 return trackProps[key]; 7148 }, 7149 set: function set() {} 7150 }); 7151 }; 7152 7153 for (var key in trackProps) { 7154 _loop(key); 7155 } 7156 7157 return _this; 7158 } 7159 7160 return Track; 7161 }(EventTarget); 7162 7163 /** 7164 * @file url.js 7165 * @module url 7166 */ 7167 /** 7168 * @typedef {Object} url:URLObject 7169 * 7170 * @property {string} protocol 7171 * The protocol of the url that was parsed. 7172 * 7173 * @property {string} hostname 7174 * The hostname of the url that was parsed. 7175 * 7176 * @property {string} port 7177 * The port of the url that was parsed. 7178 * 7179 * @property {string} pathname 7180 * The pathname of the url that was parsed. 7181 * 7182 * @property {string} search 7183 * The search query of the url that was parsed. 7184 * 7185 * @property {string} hash 7186 * The hash of the url that was parsed. 7187 * 7188 * @property {string} host 7189 * The host of the url that was parsed. 7190 */ 7191 7192 /** 7193 * Resolve and parse the elements of a URL. 7194 * 7195 * @function 7196 * @param {String} url 7197 * The url to parse 7198 * 7199 * @return {url:URLObject} 7200 * An object of url details 7201 */ 7202 7203 var parseUrl = function parseUrl(url) { 7204 var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; // add the url to an anchor and let the browser parse the URL 7205 7206 var a = document.createElement('a'); 7207 a.href = url; // IE8 (and 9?) Fix
7208 // ie8 doesn't parse the URL correctly until the anchor is actually 7209 // added to the body, and an innerHTML is needed to trigger the parsing 7210 7211 var addToBody = a.host === '' && a.protocol !== 'file:'; 7212 var div; 7213 7214 if (addToBody) { 7215 div = document.createElement('div'); 7216 div.innerHTML = "<a href=\"" + url + "\"></a>"; 7217 a = div.firstChild; // prevent the div from affecting layout 7218 7219 div.setAttribute('style', 'display:none; position:absolute;'); 7220 document.body.appendChild(div); 7221 } // Copy the specific URL properties to a new object 7222 // This is also needed for IE8 because the anchor loses its 7223 // properties when it's removed from the dom 7224 7225 7226 var details = {}; 7227 7228 for (var i = 0; i < props.length; i++) { 7229 details[props[i]] = a[props[i]]; 7230 } // IE9 adds the port to the host property unlike everyone else. If 7231 // a port identifier is added for standard ports, strip it. 7232 7233 7234 if (details.protocol === 'http:') { 7235 details.host = details.host.replace(/:80$/, ''); 7236 } 7237 7238 if (details.protocol === 'https:') { 7239 details.host = details.host.replace(/:443$/, ''); 7240 } 7241 7242 if (!details.protocol) { 7243 details.protocol = window$1.location.protocol; 7244 } 7245 7246 if (addToBody) { 7247 document.body.removeChild(div); 7248 } 7249 7250 return details; 7251 }; 7252 /** 7253 * Get absolute version of relative URL. Used to tell Flash the correct URL. 7254 * 7255 * @function 7256 * @param {string} url 7257 * URL to make absolute 7258 * 7259 * @return {string} 7260 * Absolute URL 7261 * 7262 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 7263 */ 7264 7265 var getAbsoluteURL = function getAbsoluteURL(url) { 7266 // Check if absolute URL 7267 if (!url.match(/^https?:\/\//)) { 7268 // Convert to absolute URL. Flash hosted off-site needs an absolute URL. 7269 var div = document.createElement('div'); 7270 div.innerHTML = "<a href=\"" + url + "\">x</a>"; 7271 url = div.firstChild.href; 7272 } 7273 7274 return url; 7275 }; 7276 /** 7277 * Returns the extension of the passed file name. It will return an empty string 7278 * if passed an invalid path. 7279 * 7280 * @function 7281 * @param {string} path 7282 * The fileName path like '/path/to/file.mp4' 7283 * 7284 * @return {string} 7285 * The extension in lower case or an empty string if no 7286 * extension could be found. 7287 */ 7288 7289 var getFileExtension = function getFileExtension(path) { 7290 if (typeof path === 'string') { 7291 var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/; 7292 var pathParts = splitPathRe.exec(path); 7293 7294 if (pathParts) { 7295 return pathParts.pop().toLowerCase(); 7296 } 7297 } 7298 7299 return ''; 7300 }; 7301 /** 7302 * Returns whether the url passed is a cross domain request or not. 7303 * 7304 * @function 7305 * @param {string} url 7306 * The url to check. 7307 * 7308 * @return {boolean} 7309 * Whether it is a cross domain request or not. 7310 */ 7311 7312 var isCrossOrigin = function isCrossOrigin(url) { 7313 var winLoc = window$1.location; 7314 var urlInfo = parseUrl(url); // IE8 protocol relative urls will return ':' for protocol 7315 7316 var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; // Check if url is for another domain/origin 7317 // IE8 doesn't know location.origin, so we won't rely on it here 7318 7319 var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; 7320 return crossOrigin; 7321 }; 7322 7323 var Url = /*#__PURE__*/Object.freeze({ 7324 parseUrl: parseUrl, 7325 getAbsoluteURL: getAbsoluteURL, 7326 getFileExtension: getFileExtension, 7327 isCrossOrigin: isCrossOrigin 7328 }); 7329 7330 var isFunction_1 = isFunction; 7331 var toString$1 = Object.prototype.toString; 7332 7333 function isFunction(fn) { 7334 var string = toString$1.call(fn); 7335 return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && ( // IE8 and below 7336 fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt); 7337 } 7338 7339 /* eslint no-invalid-this: 1 */ 7340 7341 var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; 7342 var slice = Array.prototype.slice; 7343 var toStr = Object.prototype.toString; 7344 var funcType = '[object Function]'; 7345 7346 var implementation = function bind(that) { 7347 var target = this; 7348 7349 if (typeof target !== 'function' || toStr.call(target) !== funcType) { 7350 throw new TypeError(ERROR_MESSAGE + target); 7351 } 7352 7353 var args = slice.call(arguments, 1); 7354 var bound; 7355 7356 var binder = function binder() { 7357 if (this instanceof bound) { 7358 var result = target.apply(this, args.concat(slice.call(arguments))); 7359 7360 if (Object(result) === result) { 7361 return result; 7362 } 7363 7364 return this; 7365 } else { 7366 return target.apply(that, args.concat(slice.call(arguments))); 7367 } 7368 }; 7369 7370 var boundLength = Math.max(0, target.length - args.length); 7371 var boundArgs = []; 7372 7373 for (var i = 0; i < boundLength; i++) { 7374 boundArgs.push('$' + i); 7375 } 7376 7377 bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); 7378 7379 if (target.prototype) { 7380 var Empty = function Empty() {}; 7381 7382 Empty.prototype = target.prototype; 7383 bound.prototype = new Empty(); 7384 Empty.prototype = null; 7385 } 7386 7387 return bound; 7388 }; 7389 7390 var functionBind = Function.prototype.bind || implementation; 7391 7392 var toStr$1 = Object.prototype.toString; 7393 7394 var isArguments = function isArguments(value) { 7395 var str = toStr$1.call(value); 7396 var isArgs = str === '[object Arguments]'; 7397 7398 if (!isArgs) { 7399 isArgs = str !== '[object Array]' && value !== null && typeof value === 'object' && typeof value.length === 'number' && value.length >= 0 && toStr$1.call(value.callee) === '[object Function]'; 7400 } 7401 7402 return isArgs; 7403 }; 7404 7405 var keysShim; 7406 7407 if (!Object.keys) { 7408 // modified from https://github.com/es-shims/es5-shim 7409 var has = Object.prototype.hasOwnProperty; 7410 var toStr$2 = Object.prototype.toString; 7411 var isArgs = isArguments; // eslint-disable-line global-require 7412 7413 var isEnumerable = Object.prototype.propertyIsEnumerable; 7414 var hasDontEnumBug = !isEnumerable.call({ 7415 toString: null 7416 }, 'toString'); 7417 var hasProtoEnumBug = isEnumerable.call(function () {}, 'prototype');
vendor: 8,170 bytes, lines 7418-7695
7418 var dontEnums = ['toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor']; 7419 7420 var equalsConstructorPrototype = function equalsConstructorPrototype(o) { 7421 var ctor = o.constructor; 7422 return ctor && ctor.prototype === o; 7423 }; 7424 7425 var excludedKeys = { 7426 $applicationCache: true, 7427 $console: true, 7428 $external: true, 7429 $frame: true, 7430 $frameElement: true, 7431 $frames: true, 7432 $innerHeight: true, 7433 $innerWidth: true, 7434 $onmozfullscreenchange: true, 7435 $onmozfullscreenerror: true, 7436 $outerHeight: true, 7437 $outerWidth: true, 7438 $pageXOffset: true, 7439 $pageYOffset: true, 7440 $parent: true, 7441 $scrollLeft: true, 7442 $scrollTop: true, 7443 $scrollX: true, 7444 $scrollY: true, 7445 $self: true, 7446 $webkitIndexedDB: true, 7447 $webkitStorageInfo: true, 7448 $window: true 7449 }; 7450 7451 var hasAutomationEqualityBug = function () { 7452 /* global window */ 7453 if (typeof window === 'undefined') { 7454 return false; 7455 } 7456 7457 for (var k in window) { 7458 try { 7459 if (!excludedKeys['$' + k] && has.call(window, k) && window[k] !== null && typeof window[k] === 'object') { 7460 try { 7461 equalsConstructorPrototype(window[k]); 7462 } catch (e) { 7463 return true; 7464 } 7465 } 7466 } catch (e) { 7467 return true; 7468 } 7469 } 7470 7471 return false; 7472 }(); 7473 7474 var equalsConstructorPrototypeIfNotBuggy = function equalsConstructorPrototypeIfNotBuggy(o) { 7475 /* global window */ 7476 if (typeof window === 'undefined' || !hasAutomationEqualityBug) { 7477 return equalsConstructorPrototype(o); 7478 } 7479 7480 try { 7481 return equalsConstructorPrototype(o); 7482 } catch (e) { 7483 return false; 7484 } 7485 }; 7486 7487 keysShim = function keys(object) { 7488 var isObject = object !== null && typeof object === 'object'; 7489 var isFunction = toStr$2.call(object) === '[object Function]'; 7490 var isArguments = isArgs(object); 7491 var isString = isObject && toStr$2.call(object) === '[object String]'; 7492 var theKeys = []; 7493 7494 if (!isObject && !isFunction && !isArguments) { 7495 throw new TypeError('Object.keys called on a non-object'); 7496 } 7497 7498 var skipProto = hasProtoEnumBug && isFunction; 7499 7500 if (isString && object.length > 0 && !has.call(object, 0)) { 7501 for (var i = 0; i < object.length; ++i) { 7502 theKeys.push(String(i)); 7503 } 7504 } 7505 7506 if (isArguments && object.length > 0) { 7507 for (var j = 0; j < object.length; ++j) { 7508 theKeys.push(String(j)); 7509 } 7510 } else { 7511 for (var name in object) { 7512 if (!(skipProto && name === 'prototype') && has.call(object, name)) { 7513 theKeys.push(String(name)); 7514 } 7515 } 7516 } 7517 7518 if (hasDontEnumBug) { 7519 var skipConstructor = equalsConstructorPrototypeIfNotBuggy(object); 7520 7521 for (var k = 0; k < dontEnums.length; ++k) { 7522 if (!(skipConstructor && dontEnums[k] === 'constructor') && has.call(object, dontEnums[k])) { 7523 theKeys.push(dontEnums[k]); 7524 } 7525 } 7526 } 7527 7528 return theKeys; 7529 }; 7530 } 7531 7532 var implementation$1 = keysShim; 7533 7534 var slice$1 = Array.prototype.slice; 7535 var origKeys = Object.keys; 7536 var keysShim$1 = origKeys ? function keys(o) { 7537 return origKeys(o); 7538 } : implementation$1; 7539 var originalKeys = Object.keys; 7540 7541 keysShim$1.shim = function shimObjectKeys() { 7542 if (Object.keys) { 7543 var keysWorksWithArguments = function () { 7544 // Safari 5.0 bug 7545 var args = Object.keys(arguments); 7546 return args && args.length === arguments.length; 7547 }(1, 2); 7548 7549 if (!keysWorksWithArguments) { 7550 Object.keys = function keys(object) { 7551 // eslint-disable-line func-name-matching 7552 if (isArguments(object)) { 7553 return originalKeys(slice$1.call(object)); 7554 } 7555 7556 return originalKeys(object); 7557 }; 7558 } 7559 } else { 7560 Object.keys = keysShim$1; 7561 } 7562 7563 return Object.keys || keysShim$1; 7564 }; 7565 7566 var objectKeys = keysShim$1; 7567 7568 var hasSymbols = typeof Symbol === 'function' && typeof Symbol('foo') === 'symbol'; 7569 var toStr$3 = Object.prototype.toString; 7570 var concat = Array.prototype.concat; 7571 var origDefineProperty = Object.defineProperty; 7572 7573 var isFunction$1 = function isFunction(fn) { 7574 return typeof fn === 'function' && toStr$3.call(fn) === '[object Function]'; 7575 }; 7576 7577 var arePropertyDescriptorsSupported = function arePropertyDescriptorsSupported() { 7578 var obj = {}; 7579 7580 try { 7581 origDefineProperty(obj, 'x', { 7582 enumerable: false, 7583 value: obj 7584 }); // eslint-disable-next-line no-unused-vars, no-restricted-syntax 7585 7586 for (var _ in obj) { 7587 // jscs:ignore disallowUnusedVariables 7588 return false; 7589 } 7590 7591 return obj.x === obj; 7592 } catch (e) { 7593 /* this is IE 8. */ 7594 return false; 7595 } 7596 }; 7597 7598 var supportsDescriptors = origDefineProperty && arePropertyDescriptorsSupported(); 7599 7600 var defineProperty = function defineProperty(object, name, value, predicate) { 7601 if (name in object && (!isFunction$1(predicate) || !predicate())) { 7602 return; 7603 } 7604 7605 if (supportsDescriptors) { 7606 origDefineProperty(object, name, { 7607 configurable: true, 7608 enumerable: false, 7609 value: value, 7610 writable: true 7611 }); 7612 } else { 7613 object[name] = value; 7614 } 7615 }; 7616 7617 var defineProperties = function defineProperties(object, map) { 7618 var predicates = arguments.length > 2 ? arguments[2] : {}; 7619 var props = objectKeys(map); 7620 7621 if (hasSymbols) { 7622 props = concat.call(props, Object.getOwnPropertySymbols(map)); 7623 } 7624 7625 for (var i = 0; i < props.length; i += 1) { 7626 defineProperty(object, props[i], map[props[i]], predicates[props[i]]); 7627 } 7628 }; 7629 7630 defineProperties.supportsDescriptors = !!supportsDescriptors; 7631 var defineProperties_1 = defineProperties; 7632 7633 /* globals 7634 Set, 7635 Map, 7636 WeakSet, 7637 WeakMap, 7638 7639 Promise, 7640 7641 Symbol, 7642 Proxy, 7643 7644 Atomics, 7645 SharedArrayBuffer, 7646 7647 ArrayBuffer, 7648 DataView, 7649 Uint8Array, 7650 Float32Array, 7651 Float64Array, 7652 Int8Array, 7653 Int16Array, 7654 Int32Array, 7655 Uint8ClampedArray, 7656 Uint16Array, 7657 Uint32Array, 7658 */ 7659 7660 var undefined$1; // eslint-disable-line no-shadow-restricted-names 7661 7662 var ThrowTypeError = Object.getOwnPropertyDescriptor ? function () { 7663 return Object.getOwnPropertyDescriptor(arguments, 'callee').get; 7664 }() : function () { 7665 throw new TypeError(); 7666 }; 7667 var hasSymbols$1 = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol'; 7668 7669 var getProto = Object.getPrototypeOf || function (x) { 7670 return x.__proto__; 7671 }; // eslint-disable-line no-proto 7672 7673 var generatorFunction = undefined$1; 7674 7675 var asyncFunction = undefined$1; 7676 7677 var asyncGenFunction = undefined$1; 7678 var TypedArray = typeof Uint8Array === 'undefined' ? undefined$1 : getProto(Uint8Array); 7679 var INTRINSICS = { 7680 '$ %Array%': Array, 7681 '$ %ArrayBuffer%': typeof ArrayBuffer === 'undefined' ? undefined$1 : ArrayBuffer, 7682 '$ %ArrayBufferPrototype%': typeof ArrayBuffer === 'undefined' ? undefined$1 : ArrayBuffer.prototype, 7683 '$ %ArrayIteratorPrototype%': hasSymbols$1 ? getProto([][Symbol.iterator]()) : undefined$1, 7684 '$ %ArrayPrototype%': Array.prototype, 7685 '$ %ArrayProto_entries%': Array.prototype.entries, 7686 '$ %ArrayProto_forEach%': Array.prototype.forEach, 7687 '$ %ArrayProto_keys%': Array.prototype.keys, 7688 '$ %ArrayProto_values%': Array.prototype.values, 7689 '$ %AsyncFromSyncIteratorPrototype%': undefined$1, 7690 '$ %AsyncFunction%': asyncFunction, 7691 '$ %AsyncFunctionPrototype%': undefined$1, 7692 '$ %AsyncGenerator%': undefined$1, 7693 '$ %AsyncGeneratorFunction%': asyncGenFunction, 7694 '$ %AsyncGeneratorPrototype%': undefined$1, 7695 '$ %AsyncIteratorPrototype%': undefined$1,
vendor: 5,733 bytes, lines 7696-7784
7696 '$ %Atomics%': typeof Atomics === 'undefined' ? undefined$1 : Atomics, 7697 '$ %Boolean%': Boolean, 7698 '$ %BooleanPrototype%': Boolean.prototype, 7699 '$ %DataView%': typeof DataView === 'undefined' ? undefined$1 : DataView, 7700 '$ %DataViewPrototype%': typeof DataView === 'undefined' ? undefined$1 : DataView.prototype, 7701 '$ %Date%': Date, 7702 '$ %DatePrototype%': Date.prototype, 7703 '$ %decodeURI%': decodeURI, 7704 '$ %decodeURIComponent%': decodeURIComponent, 7705 '$ %encodeURI%': encodeURI, 7706 '$ %encodeURIComponent%': encodeURIComponent, 7707 '$ %Error%': Error, 7708 '$ %ErrorPrototype%': Error.prototype, 7709 '$ %eval%': eval, 7710 // eslint-disable-line no-eval 7711 '$ %EvalError%': EvalError, 7712 '$ %EvalErrorPrototype%': EvalError.prototype, 7713 '$ %Float32Array%': typeof Float32Array === 'undefined' ? undefined$1 : Float32Array, 7714 '$ %Float32ArrayPrototype%': typeof Float32Array === 'undefined' ? undefined$1 : Float32Array.prototype, 7715 '$ %Float64Array%': typeof Float64Array === 'undefined' ? undefined$1 : Float64Array, 7716 '$ %Float64ArrayPrototype%': typeof Float64Array === 'undefined' ? undefined$1 : Float64Array.prototype, 7717 '$ %Function%': Function, 7718 '$ %FunctionPrototype%': Function.prototype, 7719 '$ %Generator%': undefined$1, 7720 '$ %GeneratorFunction%': generatorFunction, 7721 '$ %GeneratorPrototype%': undefined$1, 7722 '$ %Int8Array%': typeof Int8Array === 'undefined' ? undefined$1 : Int8Array, 7723 '$ %Int8ArrayPrototype%': typeof Int8Array === 'undefined' ? undefined$1 : Int8Array.prototype, 7724 '$ %Int16Array%': typeof Int16Array === 'undefined' ? undefined$1 : Int16Array, 7725 '$ %Int16ArrayPrototype%': typeof Int16Array === 'undefined' ? undefined$1 : Int8Array.prototype, 7726 '$ %Int32Array%': typeof Int32Array === 'undefined' ? undefined$1 : Int32Array, 7727 '$ %Int32ArrayPrototype%': typeof Int32Array === 'undefined' ? undefined$1 : Int32Array.prototype, 7728 '$ %isFinite%': isFinite, 7729 '$ %isNaN%': isNaN, 7730 '$ %IteratorPrototype%': hasSymbols$1 ? getProto(getProto([][Symbol.iterator]())) : undefined$1, 7731 '$ %JSON%': JSON, 7732 '$ %JSONParse%': JSON.parse, 7733 '$ %Map%': typeof Map === 'undefined' ? undefined$1 : Map, 7734 '$ %MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols$1 ? undefined$1 : getProto(new Map()[Symbol.iterator]()), 7735 '$ %MapPrototype%': typeof Map === 'undefined' ? undefined$1 : Map.prototype, 7736 '$ %Math%': Math, 7737 '$ %Number%': Number, 7738 '$ %NumberPrototype%': Number.prototype, 7739 '$ %Object%': Object, 7740 '$ %ObjectPrototype%': Object.prototype, 7741 '$ %ObjProto_toString%': Object.prototype.toString, 7742 '$ %ObjProto_valueOf%': Object.prototype.valueOf, 7743 '$ %parseFloat%': parseFloat, 7744 '$ %parseInt%': parseInt, 7745 '$ %Promise%': typeof Promise === 'undefined' ? undefined$1 : Promise, 7746 '$ %PromisePrototype%': typeof Promise === 'undefined' ? undefined$1 : Promise.prototype, 7747 '$ %PromiseProto_then%': typeof Promise === 'undefined' ? undefined$1 : Promise.prototype.then, 7748 '$ %Promise_all%': typeof Promise === 'undefined' ? undefined$1 : Promise.all, 7749 '$ %Promise_reject%': typeof Promise === 'undefined' ? undefined$1 : Promise.reject, 7750 '$ %Promise_resolve%': typeof Promise === 'undefined' ? undefined$1 : Promise.resolve, 7751 '$ %Proxy%': typeof Proxy === 'undefined' ? undefined$1 : Proxy, 7752 '$ %RangeError%': RangeError, 7753 '$ %RangeErrorPrototype%': RangeError.prototype, 7754 '$ %ReferenceError%': ReferenceError, 7755 '$ %ReferenceErrorPrototype%': ReferenceError.prototype, 7756 '$ %Reflect%': typeof Reflect === 'undefined' ? undefined$1 : Reflect, 7757 '$ %RegExp%': RegExp, 7758 '$ %RegExpPrototype%': RegExp.prototype, 7759 '$ %Set%': typeof Set === 'undefined' ? undefined$1 : Set, 7760 '$ %SetIteratorPrototype%': typeof Set === 'undefined' || !hasSymbols$1 ? undefined$1 : getProto(new Set()[Symbol.iterator]()), 7761 '$ %SetPrototype%': typeof Set === 'undefined' ? undefined$1 : Set.prototype, 7762 '$ %SharedArrayBuffer%': typeof SharedArrayBuffer === 'undefined' ? undefined$1 : SharedArrayBuffer, 7763 '$ %SharedArrayBufferPrototype%': typeof SharedArrayBuffer === 'undefined' ? undefined$1 : SharedArrayBuffer.prototype, 7764 '$ %String%': String, 7765 '$ %StringIteratorPrototype%': hasSymbols$1 ? getProto(''[Symbol.iterator]()) : undefined$1, 7766 '$ %StringPrototype%': String.prototype, 7767 '$ %Symbol%': hasSymbols$1 ? Symbol : undefined$1, 7768 '$ %SymbolPrototype%': hasSymbols$1 ? Symbol.prototype : undefined$1, 7769 '$ %SyntaxError%': SyntaxError, 7770 '$ %SyntaxErrorPrototype%': SyntaxError.prototype, 7771 '$ %ThrowTypeError%': ThrowTypeError, 7772 '$ %TypedArray%': TypedArray, 7773 '$ %TypedArrayPrototype%': TypedArray ? TypedArray.prototype : undefined$1, 7774 '$ %TypeError%': TypeError, 7775 '$ %TypeErrorPrototype%': TypeError.prototype, 7776 '$ %Uint8Array%': typeof Uint8Array === 'undefined' ? undefined$1 : Uint8Array, 7777 '$ %Uint8ArrayPrototype%': typeof Uint8Array === 'undefined' ? undefined$1 : Uint8Array.prototype, 7778 '$ %Uint8ClampedArray%': typeof Uint8ClampedArray === 'undefined' ? undefined$1 : Uint8ClampedArray, 7779 '$ %Uint8ClampedArrayPrototype%': typeof Uint8ClampedArray === 'undefined' ? undefined$1 : Uint8ClampedArray.prototype, 7780 '$ %Uint16Array%': typeof Uint16Array === 'undefined' ? undefined$1 : Uint16Array, 7781 '$ %Uint16ArrayPrototype%': typeof Uint16Array === 'undefined' ? undefined$1 : Uint16Array.prototype, 7782 '$ %Uint32Array%': typeof Uint32Array === 'undefined' ? undefined$1 : Uint32Array, 7783 '$ %Uint32ArrayPrototype%': typeof Uint32Array === 'undefined' ? undefined$1 : Uint32Array.prototype, 7784 '$ %URIError%': URIError,
7785 '$ %URIErrorPrototype%': URIError.prototype, 7786 '$ %WeakMap%': typeof WeakMap === 'undefined' ? undefined$1 : WeakMap, 7787 '$ %WeakMapPrototype%': typeof WeakMap === 'undefined' ? undefined$1 : WeakMap.prototype, 7788 '$ %WeakSet%': typeof WeakSet === 'undefined' ? undefined$1 : WeakSet, 7789 '$ %WeakSetPrototype%': typeof WeakSet === 'undefined' ? undefined$1 : WeakSet.prototype 7790 }; 7791 7792 var GetIntrinsic = function GetIntrinsic(name, allowMissing) { 7793 if (arguments.length > 1 && typeof allowMissing !== 'boolean') { 7794 throw new TypeError('"allowMissing" argument must be a boolean'); 7795 } 7796 7797 var key = '$ ' + name; 7798 7799 if (!(key in INTRINSICS)) { 7800 throw new SyntaxError('intrinsic ' + name + ' does not exist!'); 7801 } // istanbul ignore if // hopefully this is impossible to test :-) 7802 7803 7804 if (typeof INTRINSICS[key] === 'undefined' && !allowMissing) { 7805 throw new TypeError('intrinsic ' + name + ' exists, but is not available. Please file an issue!'); 7806 } 7807 7808 return INTRINSICS[key]; 7809 }; 7810 7811 var src = functionBind.call(Function.call, Object.prototype.hasOwnProperty); 7812 7813 var $TypeError = GetIntrinsic('%TypeError%'); 7814 var $SyntaxError = GetIntrinsic('%SyntaxError%'); 7815 var predicates = { 7816 // https://ecma-international.org/ecma-262/6.0/#sec-property-descriptor-specification-type 7817 'Property Descriptor': function isPropertyDescriptor(ES, Desc) { 7818 if (ES.Type(Desc) !== 'Object') { 7819 return false; 7820 } 7821 7822 var allowed = { 7823 '[[Configurable]]': true, 7824 '[[Enumerable]]': true, 7825 '[[Get]]': true, 7826 '[[Set]]': true, 7827 '[[Value]]': true, 7828 '[[Writable]]': true 7829 }; 7830 7831 for (var key in Desc) { 7832 // eslint-disable-line 7833 if (src(Desc, key) && !allowed[key]) { 7834 return false; 7835 } 7836 } 7837 7838 var isData = src(Desc, '[[Value]]'); 7839 var IsAccessor = src(Desc, '[[Get]]') || src(Desc, '[[Set]]'); 7840 7841 if (isData && IsAccessor) { 7842 throw new $TypeError('Property Descriptors may not be both accessor and data descriptors'); 7843 } 7844 7845 return true; 7846 } 7847 }; 7848 7849 var assertRecord = function assertRecord(ES, recordType, argumentName, value) { 7850 var predicate = predicates[recordType]; 7851 7852 if (typeof predicate !== 'function') { 7853 throw new $SyntaxError('unknown record type: ' + recordType); 7854 } 7855 7856 if (!predicate(ES, value)) { 7857 throw new $TypeError(argumentName + ' must be a ' + recordType); 7858 } 7859 7860 console.log(predicate(ES, value), value); 7861 }; 7862 7863 var _isNaN = Number.isNaN || function isNaN(a) { 7864 return a !== a; 7865 }; 7866 7867 var $isNaN = Number.isNaN || function (a) { 7868 return a !== a; 7869 }; 7870 7871 var _isFinite = Number.isFinite || function (x) { 7872 return typeof x === 'number' && !$isNaN(x) && x !== Infinity && x !== -Infinity; 7873 }; 7874 7875 var sign = function sign(number) { 7876 return number >= 0 ? 1 : -1; 7877 }; 7878 7879 var mod = function mod(number, modulo) { 7880 var remain = number % modulo; 7881 return Math.floor(remain >= 0 ? remain : remain + modulo); 7882 }; 7883 7884 var fnToStr = Function.prototype.toString; 7885 var constructorRegex = /^\s*class\b/; 7886 7887 var isES6ClassFn = function isES6ClassFunction(value) { 7888 try { 7889 var fnStr = fnToStr.call(value); 7890 return constructorRegex.test(fnStr); 7891 } catch (e) { 7892 return false; // not a function 7893 } 7894 }; 7895 7896 var tryFunctionObject = function tryFunctionToStr(value) { 7897 try { 7898 if (isES6ClassFn(value)) { 7899 return false; 7900 } 7901 7902 fnToStr.call(value); 7903 return true; 7904 } catch (e) { 7905 return false; 7906 } 7907 }; 7908 7909 var toStr$4 = Object.prototype.toString; 7910 var fnClass = '[object Function]'; 7911 var genClass = '[object GeneratorFunction]'; 7912 var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol'; 7913 7914 var isCallable = function isCallable(value) { 7915 if (!value) { 7916 return false; 7917 } 7918 7919 if (typeof value !== 'function' && typeof value !== 'object') { 7920 return false; 7921 } 7922 7923 if (typeof value === 'function' && !value.prototype) { 7924 return true; 7925 } 7926 7927 if (hasToStringTag) { 7928 return tryFunctionObject(value); 7929 } 7930 7931 if (isES6ClassFn(value)) { 7932 return false; 7933 } 7934 7935 var strClass = toStr$4.call(value); 7936 return strClass === fnClass || strClass === genClass; 7937 }; 7938 7939 var isPrimitive = function isPrimitive(value) { 7940 return value === null || typeof value !== 'function' && typeof value !== 'object'; 7941 }; 7942 7943 var toStr$5 = Object.prototype.toString; // http://ecma-international.org/ecma-262/5.1/#sec-8.12.8 7944 7945 var ES5internalSlots = { 7946 '[[DefaultValue]]': function DefaultValue(O) { 7947 var actualHint; 7948 7949 if (arguments.length > 1) { 7950 actualHint = arguments[1]; 7951 } else { 7952 actualHint = toStr$5.call(O) === '[object Date]' ? String : Number; 7953 } 7954 7955 if (actualHint === String || actualHint === Number) { 7956 var methods = actualHint === String ? ['toString', 'valueOf'] : ['valueOf', 'toString']; 7957 var value, i; 7958 7959 for (i = 0; i < methods.length; ++i) { 7960 if (isCallable(O[methods[i]])) { 7961 value = O[methods[i]]();
7962 7963 if (isPrimitive(value)) { 7964 return value; 7965 } 7966 } 7967 } 7968 7969 throw new TypeError('No default value'); 7970 } 7971 7972 throw new TypeError('invalid [[DefaultValue]] hint supplied'); 7973 } 7974 }; // http://ecma-international.org/ecma-262/5.1/#sec-9.1 7975 7976 var es5 = function ToPrimitive(input) { 7977 if (isPrimitive(input)) { 7978 return input; 7979 } 7980 7981 if (arguments.length > 1) { 7982 return ES5internalSlots['[[DefaultValue]]'](input, arguments[1]); 7983 } 7984 7985 return ES5internalSlots['[[DefaultValue]]'](input); 7986 }; 7987 7988 var $Object = GetIntrinsic('%Object%'); 7989 var $TypeError$1 = GetIntrinsic('%TypeError%'); 7990 var $String = GetIntrinsic('%String%'); // https://es5.github.io/#x9 7991 7992 var ES5 = { 7993 ToPrimitive: es5, 7994 ToBoolean: function ToBoolean(value) { 7995 return !!value; 7996 }, 7997 ToNumber: function ToNumber(value) { 7998 return +value; // eslint-disable-line no-implicit-coercion 7999 }, 8000 ToInteger: function ToInteger(value) { 8001 var number = this.ToNumber(value); 8002 8003 if (_isNaN(number)) { 8004 return 0; 8005 } 8006 8007 if (number === 0 || !_isFinite(number)) { 8008 return number; 8009 } 8010 8011 return sign(number) * Math.floor(Math.abs(number)); 8012 }, 8013 ToInt32: function ToInt32(x) { 8014 return this.ToNumber(x) >> 0; 8015 }, 8016 ToUint32: function ToUint32(x) { 8017 return this.ToNumber(x) >>> 0; 8018 }, 8019 ToUint16: function ToUint16(value) { 8020 var number = this.ToNumber(value); 8021 8022 if (_isNaN(number) || number === 0 || !_isFinite(number)) { 8023 return 0; 8024 } 8025 8026 var posInt = sign(number) * Math.floor(Math.abs(number)); 8027 return mod(posInt, 0x10000); 8028 }, 8029 ToString: function ToString(value) { 8030 return $String(value); 8031 }, 8032 ToObject: function ToObject(value) { 8033 this.CheckObjectCoercible(value); 8034 return $Object(value); 8035 }, 8036 CheckObjectCoercible: function CheckObjectCoercible(value, optMessage) { 8037 /* jshint eqnull:true */ 8038 if (value == null) { 8039 throw new $TypeError$1(optMessage || 'Cannot call method on ' + value); 8040 } 8041 8042 return value; 8043 }, 8044 IsCallable: isCallable, 8045 SameValue: function SameValue(x, y) { 8046 if (x === y) { 8047 // 0 === -0, but they are not identical. 8048 if (x === 0) { 8049 return 1 / x === 1 / y; 8050 } 8051 8052 return true; 8053 } 8054 8055 return _isNaN(x) && _isNaN(y); 8056 }, 8057 // https://www.ecma-international.org/ecma-262/5.1/#sec-8 8058 Type: function Type(x) { 8059 if (x === null) { 8060 return 'Null'; 8061 } 8062 8063 if (typeof x === 'undefined') { 8064 return 'Undefined'; 8065 } 8066 8067 if (typeof x === 'function' || typeof x === 'object') { 8068 return 'Object'; 8069 } 8070 8071 if (typeof x === 'number') { 8072 return 'Number'; 8073 } 8074 8075 if (typeof x === 'boolean') { 8076 return 'Boolean'; 8077 } 8078 8079 if (typeof x === 'string') { 8080 return 'String'; 8081 } 8082 }, 8083 // https://ecma-international.org/ecma-262/6.0/#sec-property-descriptor-specification-type 8084 IsPropertyDescriptor: function IsPropertyDescriptor(Desc) { 8085 if (this.Type(Desc) !== 'Object') { 8086 return false; 8087 } 8088 8089 var allowed = { 8090 '[[Configurable]]': true, 8091 '[[Enumerable]]': true, 8092 '[[Get]]': true, 8093 '[[Set]]': true, 8094 '[[Value]]': true, 8095 '[[Writable]]': true 8096 }; 8097 8098 for (var key in Desc) { 8099 // eslint-disable-line 8100 if (src(Desc, key) && !allowed[key]) { 8101 return false; 8102 } 8103 } 8104 8105 var isData = src(Desc, '[[Value]]'); 8106 var IsAccessor = src(Desc, '[[Get]]') || src(Desc, '[[Set]]'); 8107 8108 if (isData && IsAccessor) { 8109 throw new $TypeError$1('Property Descriptors may not be both accessor and data descriptors'); 8110 } 8111 8112 return true; 8113 }, 8114 // https://ecma-international.org/ecma-262/5.1/#sec-8.10.1 8115 IsAccessorDescriptor: function IsAccessorDescriptor(Desc) { 8116 if (typeof Desc === 'undefined') { 8117 return false; 8118 } 8119 8120 assertRecord(this, 'Property Descriptor', 'Desc', Desc); 8121 8122 if (!src(Desc, '[[Get]]') && !src(Desc, '[[Set]]')) { 8123 return false; 8124 } 8125 8126 return true; 8127 }, 8128 // https://ecma-international.org/ecma-262/5.1/#sec-8.10.2 8129 IsDataDescriptor: function IsDataDescriptor(Desc) { 8130 if (typeof Desc === 'undefined') { 8131 return false; 8132 } 8133 8134 assertRecord(this, 'Property Descriptor', 'Desc', Desc); 8135 8136 if (!src(Desc, '[[Value]]') && !src(Desc, '[[Writable]]')) { 8137 return false; 8138 } 8139 8140 return true; 8141 }, 8142 // https://ecma-international.org/ecma-262/5.1/#sec-8.10.3 8143 IsGenericDescriptor: function IsGenericDescriptor(Desc) { 8144 if (typeof Desc === 'undefined') { 8145 return false; 8146 } 8147 8148 assertRecord(this, 'Property Descriptor', 'Desc', Desc); 8149 8150 if (!this.IsAccessorDescriptor(Desc) && !this.IsDataDescriptor(Desc)) { 8151 return true; 8152 } 8153 8154 return false; 8155 }, 8156 // https://ecma-international.org/ecma-262/5.1/#sec-8.10.4 8157 FromPropertyDescriptor: function FromPropertyDescriptor(Desc) { 8158 if (typeof Desc === 'undefined') { 8159 return Desc; 8160 } 8161 8162 assertRecord(this, 'Property Descriptor', 'Desc', Desc); 8163 8164 if (this.IsDataDescriptor(Desc)) { 8165 return { 8166 value: Desc['[[Value]]'], 8167 writable: !!Desc['[[Writable]]'], 8168 enumerable: !!Desc['[[Enumerable]]'], 8169 configurable: !!Desc['[[Configurable]]'] 8170 }; 8171 } else if (this.IsAccessorDescriptor(Desc)) { 8172 return { 8173 get: Desc['[[Get]]'], 8174 set: Desc['[[Set]]'], 8175 enumerable: !!Desc['[[Enumerable]]'], 8176 configurable: !!Desc['[[Configurable]]'] 8177 }; 8178 } else { 8179 throw new $TypeError$1('FromPropertyDescriptor must be called with a fully populated Property Descriptor'); 8180 } 8181 }, 8182 // https://ecma-international.org/ecma-262/5.1/#sec-8.10.5 8183 ToPropertyDescriptor: function ToPropertyDescriptor(Obj) { 8184 if (this.Type(Obj) !== 'Object') { 8185 throw new $TypeError$1('ToPropertyDescriptor requires an object'); 8186 } 8187 8188 var desc = {}; 8189 8190 if (src(Obj, 'enumerable')) { 8191 desc['[[Enumerable]]'] = this.ToBoolean(Obj.enumerable); 8192 } 8193 8194 if (src(Obj, 'configurable')) { 8195 desc['[[Configurable]]'] = this.ToBoolean(Obj.configurable); 8196 } 8197 8198 if (src(Obj, 'value')) { 8199 desc['[[Value]]'] = Obj.value; 8200 } 8201 8202 if (src(Obj, 'writable')) { 8203 desc['[[Writable]]'] = this.ToBoolean(Obj.writable); 8204 } 8205 8206 if (src(Obj, 'get')) { 8207 var getter = Obj.get; 8208 8209 if (typeof getter !== 'undefined' && !this.IsCallable(getter)) { 8210 throw new TypeError('getter must be a function'); 8211 } 8212 8213 desc['[[Get]]'] = getter; 8214 } 8215 8216 if (src(Obj, 'set')) { 8217 var setter = Obj.set; 8218
8219 if (typeof setter !== 'undefined' && !this.IsCallable(setter)) { 8220 throw new $TypeError$1('setter must be a function'); 8221 } 8222 8223 desc['[[Set]]'] = setter; 8224 } 8225 8226 if ((src(desc, '[[Get]]') || src(desc, '[[Set]]')) && (src(desc, '[[Value]]') || src(desc, '[[Writable]]'))) { 8227 throw new $TypeError$1('Invalid property descriptor. Cannot both specify accessors and a value or writable attribute'); 8228 } 8229 8230 return desc; 8231 } 8232 }; 8233 var es5$1 = ES5; 8234 8235 var replace = functionBind.call(Function.call, String.prototype.replace); 8236 /* eslint-disable no-control-regex */ 8237 8238 var leftWhitespace = /^[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+/; 8239 var rightWhitespace = /[\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF]+$/; 8240 /* eslint-enable no-control-regex */ 8241 8242 var implementation$2 = function trim() { 8243 var S = es5$1.ToString(es5$1.CheckObjectCoercible(this)); 8244 return replace(replace(S, leftWhitespace, ''), rightWhitespace, ''); 8245 }; 8246 8247 var zeroWidthSpace = "\u200B"; 8248 8249 var polyfill = function getPolyfill() { 8250 if (String.prototype.trim && zeroWidthSpace.trim() === zeroWidthSpace) { 8251 return String.prototype.trim; 8252 } 8253 8254 return implementation$2; 8255 }; 8256 8257 var shim = function shimStringTrim() { 8258 var polyfill$1 = polyfill(); 8259 defineProperties_1(String.prototype, { 8260 trim: polyfill$1 8261 }, { 8262 trim: function testTrim() { 8263 return String.prototype.trim !== polyfill$1; 8264 } 8265 }); 8266 return polyfill$1; 8267 }; 8268 8269 var boundTrim = functionBind.call(Function.call, polyfill()); 8270 defineProperties_1(boundTrim, { 8271 getPolyfill: polyfill, 8272 implementation: implementation$2, 8273 shim: shim 8274 }); 8275 var string_prototype_trim = boundTrim; 8276 8277 var toStr$6 = Object.prototype.toString; 8278 var hasOwnProperty = Object.prototype.hasOwnProperty; 8279 8280 var forEachArray = function forEachArray(array, iterator, receiver) { 8281 for (var i = 0, len = array.length; i < len; i++) { 8282 if (hasOwnProperty.call(array, i)) { 8283 if (receiver == null) { 8284 iterator(array[i], i, array); 8285 } else { 8286 iterator.call(receiver, array[i], i, array); 8287 } 8288 } 8289 } 8290 }; 8291 8292 var forEachString = function forEachString(string, iterator, receiver) { 8293 for (var i = 0, len = string.length; i < len; i++) { 8294 // no such thing as a sparse string. 8295 if (receiver == null) { 8296 iterator(string.charAt(i), i, string); 8297 } else { 8298 iterator.call(receiver, string.charAt(i), i, string); 8299 } 8300 } 8301 }; 8302 8303 var forEachObject = function forEachObject(object, iterator, receiver) { 8304 for (var k in object) { 8305 if (hasOwnProperty.call(object, k)) { 8306 if (receiver == null) { 8307 iterator(object[k], k, object); 8308 } else { 8309 iterator.call(receiver, object[k], k, object); 8310 } 8311 } 8312 } 8313 }; 8314 8315 var forEach = function forEach(list, iterator, thisArg) { 8316 if (!isCallable(iterator)) { 8317 throw new TypeError('iterator must be a function'); 8318 } 8319 8320 var receiver; 8321 8322 if (arguments.length >= 3) { 8323 receiver = thisArg; 8324 } 8325 8326 if (toStr$6.call(list) === '[object Array]') { 8327 forEachArray(list, iterator, receiver); 8328 } else if (typeof list === 'string') { 8329 forEachString(list, iterator, receiver); 8330 } else { 8331 forEachObject(list, iterator, receiver); 8332 } 8333 }; 8334 8335 var forEach_1 = forEach; 8336 8337 var isArray = function isArray(arg) { 8338 return Object.prototype.toString.call(arg) === '[object Array]'; 8339 }; 8340 8341 var parseHeaders = function parseHeaders(headers) { 8342 if (!headers) return {}; 8343 var result = {}; 8344 forEach_1(string_prototype_trim(headers).split('\n'), function (row) { 8345 var index = row.indexOf(':'), 8346 key = string_prototype_trim(row.slice(0, index)).toLowerCase(), 8347 value = string_prototype_trim(row.slice(index + 1)); 8348 8349 if (typeof result[key] === 'undefined') { 8350 result[key] = value; 8351 } else if (isArray(result[key])) { 8352 result[key].push(value); 8353 } else { 8354 result[key] = [result[key], value]; 8355 } 8356 });
vendor: 4,672 bytes, lines 8357-8540
8357 return result; 8358 }; 8359 8360 var immutable = extend; 8361 var hasOwnProperty$1 = Object.prototype.hasOwnProperty; 8362 8363 function extend() { 8364 var target = {}; 8365 8366 for (var i = 0; i < arguments.length; i++) { 8367 var source = arguments[i]; 8368 8369 for (var key in source) { 8370 if (hasOwnProperty$1.call(source, key)) { 8371 target[key] = source[key]; 8372 } 8373 } 8374 } 8375 8376 return target; 8377 } 8378 8379 var xhr = createXHR; 8380 createXHR.XMLHttpRequest = window$1.XMLHttpRequest || noop; 8381 createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window$1.XDomainRequest; 8382 forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) { 8383 createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) { 8384 options = initParams(uri, options, callback); 8385 options.method = method.toUpperCase(); 8386 return _createXHR(options); 8387 }; 8388 }); 8389 8390 function forEachArray$1(array, iterator) { 8391 for (var i = 0; i < array.length; i++) { 8392 iterator(array[i]); 8393 } 8394 } 8395 8396 function isEmpty(obj) { 8397 for (var i in obj) { 8398 if (obj.hasOwnProperty(i)) return false; 8399 } 8400 8401 return true; 8402 } 8403 8404 function initParams(uri, options, callback) { 8405 var params = uri; 8406 8407 if (isFunction_1(options)) { 8408 callback = options; 8409 8410 if (typeof uri === "string") { 8411 params = { 8412 uri: uri 8413 }; 8414 } 8415 } else { 8416 params = immutable(options, { 8417 uri: uri 8418 }); 8419 } 8420 8421 params.callback = callback; 8422 return params; 8423 } 8424 8425 function createXHR(uri, options, callback) { 8426 options = initParams(uri, options, callback); 8427 return _createXHR(options); 8428 } 8429 8430 function _createXHR(options) { 8431 if (typeof options.callback === "undefined") { 8432 throw new Error("callback argument missing"); 8433 } 8434 8435 var called = false; 8436 8437 var callback = function cbOnce(err, response, body) { 8438 if (!called) { 8439 called = true; 8440 options.callback(err, response, body); 8441 } 8442 }; 8443 8444 function readystatechange() { 8445 if (xhr.readyState === 4) { 8446 setTimeout(loadFunc, 0); 8447 } 8448 } 8449 8450 function getBody() { 8451 // Chrome with requestType=blob throws errors arround when even testing access to responseText 8452 var body = undefined; 8453 8454 if (xhr.response) { 8455 body = xhr.response; 8456 } else { 8457 body = xhr.responseText || getXml(xhr); 8458 } 8459 8460 if (isJson) { 8461 try { 8462 body = JSON.parse(body); 8463 } catch (e) {} 8464 } 8465 8466 return body; 8467 } 8468 8469 function errorFunc(evt) { 8470 clearTimeout(timeoutTimer); 8471 8472 if (!(evt instanceof Error)) { 8473 evt = new Error("" + (evt || "Unknown XMLHttpRequest Error")); 8474 } 8475 8476 evt.statusCode = 0; 8477 return callback(evt, failureResponse); 8478 } // will load the data & process the response in a special response object 8479 8480 8481 function loadFunc() { 8482 if (aborted) return; 8483 var status; 8484 clearTimeout(timeoutTimer); 8485 8486 if (options.useXDR && xhr.status === undefined) { 8487 //IE8 CORS GET successful response doesn't have a status field, but body is fine 8488 status = 200; 8489 } else { 8490 status = xhr.status === 1223 ? 204 : xhr.status; 8491 } 8492 8493 var response = failureResponse; 8494 var err = null; 8495 8496 if (status !== 0) { 8497 response = { 8498 body: getBody(), 8499 statusCode: status, 8500 method: method, 8501 headers: {}, 8502 url: uri, 8503 rawRequest: xhr 8504 }; 8505 8506 if (xhr.getAllResponseHeaders) { 8507 //remember xhr can in fact be XDR for CORS in IE 8508 response.headers = parseHeaders(xhr.getAllResponseHeaders()); 8509 } 8510 } else { 8511 err = new Error("Internal XMLHttpRequest Error"); 8512 } 8513 8514 return callback(err, response, response.body); 8515 } 8516 8517 var xhr = options.xhr || null; 8518 8519 if (!xhr) { 8520 if (options.cors || options.useXDR) { 8521 xhr = new createXHR.XDomainRequest(); 8522 } else { 8523 xhr = new createXHR.XMLHttpRequest(); 8524 } 8525 } 8526 8527 var key; 8528 var aborted; 8529 var uri = xhr.url = options.uri || options.url; 8530 var method = xhr.method = options.method || "GET"; 8531 var body = options.body || options.data; 8532 var headers = xhr.headers = options.headers || {}; 8533 var sync = !!options.sync; 8534 var isJson = false; 8535 var timeoutTimer; 8536 var failureResponse = { 8537 body: undefined, 8538 headers: {}, 8539 statusCode: 0, 8540 method: method,
vendor: 25,026 bytes, lines 8541-9396
8541 url: uri, 8542 rawRequest: xhr 8543 }; 8544 8545 if ("json" in options && options.json !== false) { 8546 isJson = true; 8547 headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user 8548 8549 if (method !== "GET" && method !== "HEAD") { 8550 headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user 8551 8552 body = JSON.stringify(options.json === true ? body : options.json); 8553 } 8554 } 8555 8556 xhr.onreadystatechange = readystatechange; 8557 xhr.onload = loadFunc; 8558 xhr.onerror = errorFunc; // IE9 must have onprogress be set to a unique function. 8559 8560 xhr.onprogress = function () {// IE must die 8561 }; 8562 8563 xhr.onabort = function () { 8564 aborted = true; 8565 }; 8566 8567 xhr.ontimeout = errorFunc; 8568 xhr.open(method, uri, !sync, options.username, options.password); //has to be after open 8569 8570 if (!sync) { 8571 xhr.withCredentials = !!options.withCredentials; 8572 } // Cannot set timeout with sync request 8573 // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly 8574 // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent 8575 8576 8577 if (!sync && options.timeout > 0) { 8578 timeoutTimer = setTimeout(function () { 8579 if (aborted) return; 8580 aborted = true; //IE9 may still call readystatechange 8581 8582 xhr.abort("timeout"); 8583 var e = new Error("XMLHttpRequest timeout"); 8584 e.code = "ETIMEDOUT"; 8585 errorFunc(e); 8586 }, options.timeout); 8587 } 8588 8589 if (xhr.setRequestHeader) { 8590 for (key in headers) { 8591 if (headers.hasOwnProperty(key)) { 8592 xhr.setRequestHeader(key, headers[key]); 8593 } 8594 } 8595 } else if (options.headers && !isEmpty(options.headers)) { 8596 throw new Error("Headers cannot be set on an XDomainRequest object"); 8597 } 8598 8599 if ("responseType" in options) { 8600 xhr.responseType = options.responseType; 8601 } 8602 8603 if ("beforeSend" in options && typeof options.beforeSend === "function") { 8604 options.beforeSend(xhr); 8605 } // Microsoft Edge browser sends "undefined" when send is called with undefined value. 8606 // XMLHttpRequest spec says to pass null as body to indicate no body 8607 // See https://github.com/naugtur/xhr/issues/100. 8608 8609 8610 xhr.send(body || null); 8611 return xhr; 8612 } 8613 8614 function getXml(xhr) { 8615 if (xhr.responseType === "document") { 8616 return xhr.responseXML; 8617 } 8618 8619 var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; 8620 8621 if (xhr.responseType === "" && !firefoxBugTakenEffect) { 8622 return xhr.responseXML; 8623 } 8624 8625 return null; 8626 } 8627 8628 function noop() {} 8629 8630 /** 8631 * Takes a webvtt file contents and parses it into cues 8632 * 8633 * @param {string} srcContent 8634 * webVTT file contents 8635 * 8636 * @param {TextTrack} track 8637 * TextTrack to add cues to. Cues come from the srcContent. 8638 * 8639 * @private 8640 */ 8641 8642 var parseCues = function parseCues(srcContent, track) { 8643 var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, window$1.WebVTT.StringDecoder()); 8644 var errors = []; 8645 8646 parser.oncue = function (cue) { 8647 track.addCue(cue); 8648 }; 8649 8650 parser.onparsingerror = function (error) { 8651 errors.push(error); 8652 }; 8653 8654 parser.onflush = function () { 8655 track.trigger({ 8656 type: 'loadeddata', 8657 target: track 8658 }); 8659 }; 8660 8661 parser.parse(srcContent); 8662 8663 if (errors.length > 0) { 8664 if (window$1.console && window$1.console.groupCollapsed) { 8665 window$1.console.groupCollapsed("Text Track parsing errors for " + track.src); 8666 } 8667 8668 errors.forEach(function (error) { 8669 return log.error(error); 8670 }); 8671 8672 if (window$1.console && window$1.console.groupEnd) { 8673 window$1.console.groupEnd(); 8674 } 8675 } 8676 8677 parser.flush(); 8678 }; 8679 /** 8680 * Load a `TextTrack` from a specified url. 8681 * 8682 * @param {string} src 8683 * Url to load track from. 8684 * 8685 * @param {TextTrack} track 8686 * Track to add cues to. Comes from the content at the end of `url`. 8687 * 8688 * @private 8689 */ 8690 8691 8692 var loadTrack = function loadTrack(src, track) { 8693 var opts = { 8694 uri: src 8695 }; 8696 var crossOrigin = isCrossOrigin(src); 8697 8698 if (crossOrigin) { 8699 opts.cors = crossOrigin; 8700 } 8701 8702 xhr(opts, bind(this, function (err, response, responseBody) { 8703 if (err) { 8704 return log.error(err, response); 8705 } 8706 8707 track.loaded_ = true; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 8708 // NOTE: this is only used for the alt/video.novtt.js build 8709 8710 if (typeof window$1.WebVTT !== 'function') { 8711 if (track.tech_) { 8712 // to prevent use before define eslint error, we define loadHandler 8713 // as a let here 8714 track.tech_.any(['vttjsloaded', 'vttjserror'], function (event) { 8715 if (event.type === 'vttjserror') { 8716 log.error("vttjs failed to load, stopping trying to process " + track.src); 8717 return; 8718 } 8719 8720 return parseCues(responseBody, track); 8721 }); 8722 } 8723 } else { 8724 parseCues(responseBody, track); 8725 } 8726 })); 8727 }; 8728 /** 8729 * A representation of a single `TextTrack`. 8730 * 8731 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} 8732 * @extends Track 8733 */ 8734 8735 8736 var TextTrack = 8737 /*#__PURE__*/ 8738 function (_Track) { 8739 _inheritsLoose(TextTrack, _Track); 8740 8741 /** 8742 * Create an instance of this class. 8743 * 8744 * @param {Object} options={} 8745 * Object of option names and values 8746 * 8747 * @param {Tech} options.tech 8748 * A reference to the tech that owns this TextTrack. 8749 * 8750 * @param {TextTrack~Kind} [options.kind='subtitles'] 8751 * A valid text track kind. 8752 * 8753 * @param {TextTrack~Mode} [options.mode='disabled'] 8754 * A valid text track mode. 8755 * 8756 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 8757 * A unique id for this TextTrack. 8758 * 8759 * @param {string} [options.label=''] 8760 * The menu label for this track. 8761 * 8762 * @param {string} [options.language=''] 8763 * A valid two character language code. 8764 * 8765 * @param {string} [options.srclang=''] 8766 * A valid two character language code. An alternative, but deprioritized 8767 * version of `options.language` 8768 * 8769 * @param {string} [options.src] 8770 * A url to TextTrack cues. 8771 * 8772 * @param {boolean} [options.default] 8773 * If this track should default to on or off. 8774 */ 8775 function TextTrack(options) { 8776 var _this; 8777 8778 if (options === void 0) { 8779 options = {}; 8780 } 8781 8782 if (!options.tech) { 8783 throw new Error('A tech was not provided.'); 8784 } 8785 8786 var settings = mergeOptions(options, { 8787 kind: TextTrackKind[options.kind] || 'subtitles', 8788 language: options.language || options.srclang || '' 8789 }); 8790 var mode = TextTrackMode[settings.mode] || 'disabled'; 8791 var default_ = settings["default"]; 8792 8793 if (settings.kind === 'metadata' || settings.kind === 'chapters') { 8794 mode = 'hidden'; 8795 } 8796 8797 _this = _Track.call(this, settings) || this; 8798 _this.tech_ = settings.tech; 8799 _this.cues_ = []; 8800 _this.activeCues_ = []; 8801 var cues = new TextTrackCueList(_this.cues_); 8802 var activeCues = new TextTrackCueList(_this.activeCues_); 8803 var changed = false; 8804 var timeupdateHandler = bind(_assertThisInitialized(_this), function () { 8805 // Accessing this.activeCues for the side-effects of updating itself 8806 // due to its nature as a getter function. Do not remove or cues will 8807 // stop updating! 8808 // Use the setter to prevent deletion from uglify (pure_getters rule) 8809 this.activeCues = this.activeCues; 8810 8811 if (changed) { 8812 this.trigger('cuechange'); 8813 changed = false; 8814 } 8815 }); 8816 8817 if (mode !== 'disabled') { 8818 _this.tech_.ready(function () { 8819 _this.tech_.on('timeupdate', timeupdateHandler); 8820 }, true); 8821 } 8822 8823 Object.defineProperties(_assertThisInitialized(_this), { 8824 /** 8825 * @memberof TextTrack 8826 * @member {boolean} default 8827 * If this track was set to be on or off by default. Cannot be changed after 8828 * creation. 8829 * @instance 8830 * 8831 * @readonly 8832 */ 8833 "default": { 8834 get: function get() { 8835 return default_; 8836 }, 8837 set: function set() {} 8838 }, 8839 8840 /** 8841 * @memberof TextTrack 8842 * @member {string} mode 8843 * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will 8844 * not be set if setting to an invalid mode. 8845 * @instance 8846 * 8847 * @fires TextTrack#modechange 8848 */ 8849 mode: { 8850 get: function get() { 8851 return mode; 8852 }, 8853 set: function set(newMode) { 8854 var _this2 = this; 8855 8856 if (!TextTrackMode[newMode]) { 8857 return; 8858 } 8859 8860 mode = newMode; 8861 8862 if (mode !== 'disabled') { 8863 this.tech_.ready(function () { 8864 _this2.tech_.on('timeupdate', timeupdateHandler); 8865 }, true); 8866 } else { 8867 this.tech_.off('timeupdate', timeupdateHandler); 8868 } 8869 /** 8870 * An event that fires when mode changes on this track. This allows 8871 * the TextTrackList that holds this track to act accordingly. 8872 * 8873 * > Note: This is not part of the spec! 8874 * 8875 * @event TextTrack#modechange 8876 * @type {EventTarget~Event} 8877 */ 8878 8879 8880 this.trigger('modechange'); 8881 } 8882 }, 8883 8884 /** 8885 * @memberof TextTrack 8886 * @member {TextTrackCueList} cues 8887 * The text track cue list for this TextTrack. 8888 * @instance 8889 */ 8890 cues: { 8891 get: function get() { 8892 if (!this.loaded_) { 8893 return null; 8894 } 8895 8896 return cues; 8897 }, 8898 set: function set() {} 8899 }, 8900 8901 /** 8902 * @memberof TextTrack 8903 * @member {TextTrackCueList} activeCues 8904 * The list text track cues that are currently active for this TextTrack. 8905 * @instance 8906 */ 8907 activeCues: { 8908 get: function get() { 8909 if (!this.loaded_) { 8910 return null; 8911 } // nothing to do 8912 8913 8914 if (this.cues.length === 0) { 8915 return activeCues; 8916 } 8917 8918 var ct = this.tech_.currentTime(); 8919 var active = []; 8920 8921 for (var i = 0, l = this.cues.length; i < l; i++) { 8922 var cue = this.cues[i]; 8923 8924 if (cue.startTime <= ct && cue.endTime >= ct) { 8925 active.push(cue); 8926 } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { 8927 active.push(cue); 8928 } 8929 } 8930 8931 changed = false; 8932 8933 if (active.length !== this.activeCues_.length) { 8934 changed = true; 8935 } else { 8936 for (var _i = 0; _i < active.length; _i++) { 8937 if (this.activeCues_.indexOf(active[_i]) === -1) { 8938 changed = true; 8939 } 8940 } 8941 } 8942 8943 this.activeCues_ = active; 8944 activeCues.setCues_(this.activeCues_); 8945 return activeCues; 8946 }, 8947 // /!\ Keep this setter empty (see the timeupdate handler above) 8948 set: function set() {} 8949 } 8950 }); 8951 8952 if (settings.src) { 8953 _this.src = settings.src; 8954 loadTrack(settings.src, _assertThisInitialized(_this)); 8955 } else { 8956 _this.loaded_ = true; 8957 } 8958 8959 return _this; 8960 } 8961 /** 8962 * Add a cue to the internal list of cues. 8963 * 8964 * @param {TextTrack~Cue} cue 8965 * The cue to add to our internal list 8966 */ 8967 8968 8969 var _proto = TextTrack.prototype; 8970 8971 _proto.addCue = function addCue(originalCue) { 8972 var cue = originalCue; 8973 8974 if (window$1.vttjs && !(originalCue instanceof window$1.vttjs.VTTCue)) { 8975 cue = new window$1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); 8976 8977 for (var prop in originalCue) { 8978 if (!(prop in cue)) { 8979 cue[prop] = originalCue[prop]; 8980 } 8981 } // make sure that `id` is copied over 8982 8983 8984 cue.id = originalCue.id; 8985 cue.originalCue_ = originalCue; 8986 } 8987 8988 var tracks = this.tech_.textTracks(); 8989 8990 for (var i = 0; i < tracks.length; i++) { 8991 if (tracks[i] !== this) { 8992 tracks[i].removeCue(cue); 8993 } 8994 } 8995 8996 this.cues_.push(cue); 8997 this.cues.setCues_(this.cues_); 8998 } 8999 /** 9000 * Remove a cue from our internal list 9001 * 9002 * @param {TextTrack~Cue} removeCue 9003 * The cue to remove from our internal list 9004 */ 9005 ; 9006 9007 _proto.removeCue = function removeCue(_removeCue) { 9008 var i = this.cues_.length; 9009 9010 while (i--) { 9011 var cue = this.cues_[i]; 9012 9013 if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { 9014 this.cues_.splice(i, 1); 9015 this.cues.setCues_(this.cues_); 9016 break; 9017 } 9018 } 9019 }; 9020 9021 return TextTrack; 9022 }(Track); 9023 /** 9024 * cuechange - One or more cues in the track have become active or stopped being active. 9025 */ 9026 9027 9028 TextTrack.prototype.allowedEvents_ = { 9029 cuechange: 'cuechange' 9030 }; 9031 9032 /** 9033 * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} 9034 * only one `AudioTrack` in the list will be enabled at a time. 9035 * 9036 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} 9037 * @extends Track 9038 */ 9039 9040 var AudioTrack = 9041 /*#__PURE__*/ 9042 function (_Track) { 9043 _inheritsLoose(AudioTrack, _Track); 9044 9045 /** 9046 * Create an instance of this class. 9047 * 9048 * @param {Object} [options={}] 9049 * Object of option names and values 9050 * 9051 * @param {AudioTrack~Kind} [options.kind=''] 9052 * A valid audio track kind 9053 * 9054 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 9055 * A unique id for this AudioTrack. 9056 * 9057 * @param {string} [options.label=''] 9058 * The menu label for this track. 9059 * 9060 * @param {string} [options.language=''] 9061 * A valid two character language code. 9062 * 9063 * @param {boolean} [options.enabled] 9064 * If this track is the one that is currently playing. If this track is part of 9065 * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. 9066 */ 9067 function AudioTrack(options) { 9068 var _this; 9069 9070 if (options === void 0) { 9071 options = {}; 9072 } 9073 9074 var settings = mergeOptions(options, { 9075 kind: AudioTrackKind[options.kind] || '' 9076 }); 9077 _this = _Track.call(this, settings) || this; 9078 var enabled = false; 9079 /** 9080 * @memberof AudioTrack 9081 * @member {boolean} enabled 9082 * If this `AudioTrack` is enabled or not. When setting this will 9083 * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. 9084 * @instance 9085 * 9086 * @fires VideoTrack#selectedchange 9087 */ 9088 9089 Object.defineProperty(_assertThisInitialized(_this), 'enabled', { 9090 get: function get() { 9091 return enabled; 9092 }, 9093 set: function set(newEnabled) { 9094 // an invalid or unchanged value 9095 if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { 9096 return; 9097 } 9098 9099 enabled = newEnabled; 9100 /** 9101 * An event that fires when enabled changes on this track. This allows 9102 * the AudioTrackList that holds this track to act accordingly. 9103 * 9104 * > Note: This is not part of the spec! Native tracks will do 9105 * this internally without an event. 9106 * 9107 * @event AudioTrack#enabledchange 9108 * @type {EventTarget~Event} 9109 */ 9110 9111 this.trigger('enabledchange'); 9112 } 9113 }); // if the user sets this track to selected then 9114 // set selected to that true value otherwise 9115 // we keep it false 9116 9117 if (settings.enabled) { 9118 _this.enabled = settings.enabled; 9119 } 9120 9121 _this.loaded_ = true; 9122 return _this; 9123 } 9124 9125 return AudioTrack; 9126 }(Track); 9127 9128 /** 9129 * A representation of a single `VideoTrack`. 9130 * 9131 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} 9132 * @extends Track 9133 */ 9134 9135 var VideoTrack = 9136 /*#__PURE__*/ 9137 function (_Track) { 9138 _inheritsLoose(VideoTrack, _Track); 9139 9140 /** 9141 * Create an instance of this class. 9142 * 9143 * @param {Object} [options={}] 9144 * Object of option names and values 9145 * 9146 * @param {string} [options.kind=''] 9147 * A valid {@link VideoTrack~Kind} 9148 * 9149 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 9150 * A unique id for this AudioTrack. 9151 * 9152 * @param {string} [options.label=''] 9153 * The menu label for this track. 9154 * 9155 * @param {string} [options.language=''] 9156 * A valid two character language code. 9157 * 9158 * @param {boolean} [options.selected] 9159 * If this track is the one that is currently playing. 9160 */ 9161 function VideoTrack(options) { 9162 var _this; 9163 9164 if (options === void 0) { 9165 options = {}; 9166 } 9167 9168 var settings = mergeOptions(options, { 9169 kind: VideoTrackKind[options.kind] || '' 9170 }); 9171 _this = _Track.call(this, settings) || this; 9172 var selected = false; 9173 /** 9174 * @memberof VideoTrack 9175 * @member {boolean} selected 9176 * If this `VideoTrack` is selected or not. When setting this will 9177 * fire {@link VideoTrack#selectedchange} if the state of selected changed. 9178 * @instance 9179 * 9180 * @fires VideoTrack#selectedchange 9181 */ 9182 9183 Object.defineProperty(_assertThisInitialized(_this), 'selected', { 9184 get: function get() { 9185 return selected; 9186 }, 9187 set: function set(newSelected) { 9188 // an invalid or unchanged value 9189 if (typeof newSelected !== 'boolean' || newSelected === selected) { 9190 return; 9191 } 9192 9193 selected = newSelected; 9194 /** 9195 * An event that fires when selected changes on this track. This allows 9196 * the VideoTrackList that holds this track to act accordingly. 9197 * 9198 * > Note: This is not part of the spec! Native tracks will do 9199 * this internally without an event. 9200 * 9201 * @event VideoTrack#selectedchange 9202 * @type {EventTarget~Event} 9203 */ 9204 9205 this.trigger('selectedchange'); 9206 } 9207 }); // if the user sets this track to selected then 9208 // set selected to that true value otherwise 9209 // we keep it false 9210 9211 if (settings.selected) { 9212 _this.selected = settings.selected; 9213 } 9214 9215 return _this; 9216 } 9217 9218 return VideoTrack; 9219 }(Track); 9220 9221 /** 9222 * @memberof HTMLTrackElement 9223 * @typedef {HTMLTrackElement~ReadyState} 9224 * @enum {number} 9225 */ 9226 9227 var NONE = 0; 9228 var LOADING = 1; 9229 var LOADED = 2; 9230 var ERROR = 3; 9231 /** 9232 * A single track represented in the DOM. 9233 * 9234 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement} 9235 * @extends EventTarget 9236 */ 9237 9238 var HTMLTrackElement = 9239 /*#__PURE__*/ 9240 function (_EventTarget) { 9241 _inheritsLoose(HTMLTrackElement, _EventTarget); 9242 9243 /** 9244 * Create an instance of this class. 9245 * 9246 * @param {Object} options={} 9247 * Object of option names and values 9248 * 9249 * @param {Tech} options.tech 9250 * A reference to the tech that owns this HTMLTrackElement. 9251 * 9252 * @param {TextTrack~Kind} [options.kind='subtitles'] 9253 * A valid text track kind. 9254 * 9255 * @param {TextTrack~Mode} [options.mode='disabled'] 9256 * A valid text track mode. 9257 * 9258 * @param {string} [options.id='vjs_track_' + Guid.newGUID()] 9259 * A unique id for this TextTrack. 9260 * 9261 * @param {string} [options.label=''] 9262 * The menu label for this track. 9263 * 9264 * @param {string} [options.language=''] 9265 * A valid two character language code. 9266 * 9267 * @param {string} [options.srclang=''] 9268 * A valid two character language code. An alternative, but deprioritized 9269 * vesion of `options.language` 9270 * 9271 * @param {string} [options.src] 9272 * A url to TextTrack cues. 9273 * 9274 * @param {boolean} [options.default] 9275 * If this track should default to on or off. 9276 */ 9277 function HTMLTrackElement(options) { 9278 var _this; 9279 9280 if (options === void 0) { 9281 options = {}; 9282 } 9283 9284 _this = _EventTarget.call(this) || this; 9285 var readyState; 9286 var track = new TextTrack(options); 9287 _this.kind = track.kind; 9288 _this.src = track.src; 9289 _this.srclang = track.language; 9290 _this.label = track.label; 9291 _this["default"] = track["default"]; 9292 Object.defineProperties(_assertThisInitialized(_this), { 9293 /** 9294 * @memberof HTMLTrackElement 9295 * @member {HTMLTrackElement~ReadyState} readyState 9296 * The current ready state of the track element. 9297 * @instance 9298 */ 9299 readyState: { 9300 get: function get() { 9301 return readyState; 9302 } 9303 }, 9304 9305 /** 9306 * @memberof HTMLTrackElement 9307 * @member {TextTrack} track 9308 * The underlying TextTrack object. 9309 * @instance 9310 * 9311 */ 9312 track: { 9313 get: function get() { 9314 return track; 9315 } 9316 } 9317 }); 9318 readyState = NONE; 9319 /** 9320 * @listens TextTrack#loadeddata 9321 * @fires HTMLTrackElement#load 9322 */ 9323 9324 track.addEventListener('loadeddata', function () { 9325 readyState = LOADED; 9326 9327 _this.trigger({ 9328 type: 'load', 9329 target: _assertThisInitialized(_this) 9330 }); 9331 }); 9332 return _this; 9333 } 9334 9335 return HTMLTrackElement; 9336 }(EventTarget); 9337 9338 HTMLTrackElement.prototype.allowedEvents_ = { 9339 load: 'load' 9340 }; 9341 HTMLTrackElement.NONE = NONE; 9342 HTMLTrackElement.LOADING = LOADING; 9343 HTMLTrackElement.LOADED = LOADED; 9344 HTMLTrackElement.ERROR = ERROR; 9345 9346 /* 9347 * This file contains all track properties that are used in 9348 * player.js, tech.js, html5.js and possibly other techs in the future. 9349 */ 9350 9351 var NORMAL = { 9352 audio: { 9353 ListClass: AudioTrackList, 9354 TrackClass: AudioTrack, 9355 capitalName: 'Audio' 9356 }, 9357 video: { 9358 ListClass: VideoTrackList, 9359 TrackClass: VideoTrack, 9360 capitalName: 'Video' 9361 }, 9362 text: { 9363 ListClass: TextTrackList, 9364 TrackClass: TextTrack, 9365 capitalName: 'Text' 9366 } 9367 }; 9368 Object.keys(NORMAL).forEach(function (type) { 9369 NORMAL[type].getterName = type + "Tracks"; 9370 NORMAL[type].privateName = type + "Tracks_"; 9371 }); 9372 var REMOTE = { 9373 remoteText: { 9374 ListClass: TextTrackList, 9375 TrackClass: TextTrack, 9376 capitalName: 'RemoteText', 9377 getterName: 'remoteTextTracks', 9378 privateName: 'remoteTextTracks_' 9379 }, 9380 remoteTextEl: { 9381 ListClass: HtmlTrackElementList, 9382 TrackClass: HTMLTrackElement, 9383 capitalName: 'RemoteTextTrackEls', 9384 getterName: 'remoteTextTrackEls', 9385 privateName: 'remoteTextTrackEls_' 9386 } 9387 }; 9388 var ALL = mergeOptions(NORMAL, REMOTE); 9389 REMOTE.names = Object.keys(REMOTE); 9390 NORMAL.names = Object.keys(NORMAL); 9391 ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); 9392 9393 /** 9394 * Copyright 2013 vtt.js Contributors 9395 * 9396 * Licensed under the Apache License, Version 2.0 (the "License");
vendor: 12,911 bytes, lines 9397-9825
9397 * you may not use this file except in compliance with the License. 9398 * You may obtain a copy of the License at 9399 * 9400 * http://www.apache.org/licenses/LICENSE-2.0 9401 * 9402 * Unless required by applicable law or agreed to in writing, software 9403 * distributed under the License is distributed on an "AS IS" BASIS, 9404 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9405 * See the License for the specific language governing permissions and 9406 * limitations under the License. 9407 */ 9408 9409 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 9410 9411 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 9412 var _objCreate = Object.create || function () { 9413 function F() {} 9414 9415 return function (o) { 9416 if (arguments.length !== 1) { 9417 throw new Error('Object.create shim only accepts one parameter.'); 9418 } 9419 9420 F.prototype = o; 9421 return new F(); 9422 }; 9423 }(); // Creates a new ParserError object from an errorData object. The errorData 9424 // object should have default code and message properties. The default message 9425 // property can be overriden by passing in a message parameter. 9426 // See ParsingError.Errors below for acceptable errors. 9427 9428 9429 function ParsingError(errorData, message) { 9430 this.name = "ParsingError"; 9431 this.code = errorData.code; 9432 this.message = message || errorData.message; 9433 } 9434 9435 ParsingError.prototype = _objCreate(Error.prototype); 9436 ParsingError.prototype.constructor = ParsingError; // ParsingError metadata for acceptable ParsingErrors. 9437 9438 ParsingError.Errors = { 9439 BadSignature: { 9440 code: 0, 9441 message: "Malformed WebVTT signature." 9442 }, 9443 BadTimeStamp: { 9444 code: 1, 9445 message: "Malformed time stamp." 9446 } 9447 }; // Try to parse input as a time stamp. 9448 9449 function parseTimeStamp(input) { 9450 function computeSeconds(h, m, s, f) { 9451 return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; 9452 } 9453 9454 var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/); 9455 9456 if (!m) { 9457 return null; 9458 } 9459 9460 if (m[3]) { 9461 // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] 9462 return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); 9463 } else if (m[1] > 59) { 9464 // Timestamp takes the form of [hours]:[minutes].[milliseconds] 9465 // First position is hours as it's over 59. 9466 return computeSeconds(m[1], m[2], 0, m[4]); 9467 } else { 9468 // Timestamp takes the form of [minutes]:[seconds].[milliseconds] 9469 return computeSeconds(0, m[1], m[2], m[4]); 9470 } 9471 } // A settings object holds key/value pairs and will ignore anything but the first 9472 // assignment to a specific key. 9473 9474 9475 function Settings() { 9476 this.values = _objCreate(null); 9477 } 9478 9479 Settings.prototype = { 9480 // Only accept the first assignment to any key. 9481 set: function set(k, v) { 9482 if (!this.get(k) && v !== "") { 9483 this.values[k] = v; 9484 } 9485 }, 9486 // Return the value for a key, or a default value. 9487 // If 'defaultKey' is passed then 'dflt' is assumed to be an object with 9488 // a number of possible default values as properties where 'defaultKey' is 9489 // the key of the property that will be chosen; otherwise it's assumed to be 9490 // a single value. 9491 get: function get(k, dflt, defaultKey) { 9492 if (defaultKey) { 9493 return this.has(k) ? this.values[k] : dflt[defaultKey]; 9494 } 9495 9496 return this.has(k) ? this.values[k] : dflt; 9497 }, 9498 // Check whether we have a value for a key. 9499 has: function has(k) { 9500 return k in this.values; 9501 }, 9502 // Accept a setting if its one of the given alternatives. 9503 alt: function alt(k, v, a) { 9504 for (var n = 0; n < a.length; ++n) { 9505 if (v === a[n]) { 9506 this.set(k, v); 9507 break; 9508 } 9509 } 9510 }, 9511 // Accept a setting if its a valid (signed) integer. 9512 integer: function integer(k, v) { 9513 if (/^-?\d+$/.test(v)) { 9514 // integer 9515 this.set(k, parseInt(v, 10)); 9516 } 9517 }, 9518 // Accept a setting if its a valid percentage. 9519 percent: function percent(k, v) { 9520 var m; 9521 9522 if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) { 9523 v = parseFloat(v); 9524 9525 if (v >= 0 && v <= 100) { 9526 this.set(k, v); 9527 return true; 9528 } 9529 } 9530 9531 return false; 9532 } 9533 }; // Helper function to parse input into groups separated by 'groupDelim', and 9534 // interprete each group as a key/value pair separated by 'keyValueDelim'. 9535 9536 function parseOptions(input, callback, keyValueDelim, groupDelim) { 9537 var groups = groupDelim ? input.split(groupDelim) : [input]; 9538 9539 for (var i in groups) { 9540 if (typeof groups[i] !== "string") { 9541 continue; 9542 } 9543 9544 var kv = groups[i].split(keyValueDelim); 9545 9546 if (kv.length !== 2) { 9547 continue; 9548 } 9549 9550 var k = kv[0]; 9551 var v = kv[1]; 9552 callback(k, v); 9553 } 9554 } 9555 9556 function parseCue(input, cue, regionList) { 9557 // Remember the original input if we need to throw an error. 9558 var oInput = input; // 4.1 WebVTT timestamp 9559 9560 function consumeTimeStamp() { 9561 var ts = parseTimeStamp(input); 9562 9563 if (ts === null) { 9564 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput); 9565 } // Remove time stamp from input. 9566 9567 9568 input = input.replace(/^[^\sa-zA-Z-]+/, ""); 9569 return ts; 9570 } // 4.4.2 WebVTT cue settings 9571 9572 9573 function consumeCueSettings(input, cue) { 9574 var settings = new Settings(); 9575 parseOptions(input, function (k, v) { 9576 switch (k) { 9577 case "region": 9578 // Find the last region we parsed with the same region id. 9579 for (var i = regionList.length - 1; i >= 0; i--) { 9580 if (regionList[i].id === v) { 9581 settings.set(k, regionList[i].region); 9582 break; 9583 } 9584 } 9585 9586 break; 9587 9588 case "vertical": 9589 settings.alt(k, v, ["rl", "lr"]); 9590 break; 9591 9592 case "line": 9593 var vals = v.split(","), 9594 vals0 = vals[0]; 9595 settings.integer(k, vals0); 9596 settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; 9597 settings.alt(k, vals0, ["auto"]); 9598 9599 if (vals.length === 2) { 9600 settings.alt("lineAlign", vals[1], ["start", "middle", "end"]); 9601 } 9602 9603 break; 9604 9605 case "position": 9606 vals = v.split(","); 9607 settings.percent(k, vals[0]); 9608 9609 if (vals.length === 2) { 9610 settings.alt("positionAlign", vals[1], ["start", "middle", "end"]); 9611 } 9612 9613 break; 9614 9615 case "size": 9616 settings.percent(k, v); 9617 break; 9618 9619 case "align": 9620 settings.alt(k, v, ["start", "middle", "end", "left", "right"]); 9621 break; 9622 } 9623 }, /:/, /\s/); // Apply default values for any missing fields. 9624 9625 cue.region = settings.get("region", null); 9626 cue.vertical = settings.get("vertical", ""); 9627 cue.line = settings.get("line", "auto"); 9628 cue.lineAlign = settings.get("lineAlign", "start"); 9629 cue.snapToLines = settings.get("snapToLines", true); 9630 cue.size = settings.get("size", 100); 9631 cue.align = settings.get("align", "middle"); 9632 cue.position = settings.get("position", { 9633 start: 0, 9634 left: 0, 9635 middle: 50, 9636 end: 100, 9637 right: 100 9638 }, cue.align); 9639 cue.positionAlign = settings.get("positionAlign", { 9640 start: "start", 9641 left: "start", 9642 middle: "middle", 9643 end: "end", 9644 right: "end" 9645 }, cue.align); 9646 } 9647 9648 function skipWhitespace() { 9649 input = input.replace(/^\s+/, ""); 9650 } // 4.1 WebVTT cue timings. 9651 9652 9653 skipWhitespace(); 9654 cue.startTime = consumeTimeStamp(); // (1) collect cue start time 9655 9656 skipWhitespace(); 9657 9658 if (input.substr(0, 3) !== "-->") { 9659 // (3) next characters must match "-->" 9660 throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput); 9661 } 9662 9663 input = input.substr(3); 9664 skipWhitespace(); 9665 cue.endTime = consumeTimeStamp(); // (5) collect cue end time 9666 // 4.1 WebVTT cue settings list. 9667 9668 skipWhitespace(); 9669 consumeCueSettings(input, cue); 9670 } 9671 9672 var ESCAPE = { 9673 "&": "&", 9674 "<": "<", 9675 ">": ">", 9676 "‎": "\u200E", 9677 "‏": "\u200F", 9678 " ": "\xA0" 9679 }; 9680 var TAG_NAME = { 9681 c: "span", 9682 i: "i", 9683 b: "b", 9684 u: "u", 9685 ruby: "ruby", 9686 rt: "rt", 9687 v: "span", 9688 lang: "span" 9689 }; 9690 var TAG_ANNOTATION = { 9691 v: "title", 9692 lang: "lang" 9693 }; 9694 var NEEDS_PARENT = { 9695 rt: "ruby" 9696 }; // Parse content into a document fragment. 9697 9698 function parseContent(window, input) { 9699 function nextToken() { 9700 // Check for end-of-string. 9701 if (!input) { 9702 return null; 9703 } // Consume 'n' characters from the input. 9704 9705 9706 function consume(result) { 9707 input = input.substr(result.length); 9708 return result; 9709 } 9710 9711 var m = input.match(/^([^<]*)(<[^>]*>?)?/); // If there is some text before the next tag, return it, otherwise return 9712 // the tag. 9713 9714 return consume(m[1] ? m[1] : m[2]); 9715 } // Unescape a string 's'. 9716 9717 9718 function unescape1(e) { 9719 return ESCAPE[e]; 9720 } 9721 9722 function unescape(s) { 9723 while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) { 9724 s = s.replace(m[0], unescape1); 9725 } 9726 9727 return s; 9728 } 9729 9730 function shouldAdd(current, element) { 9731 return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName; 9732 } // Create an element for this tag. 9733 9734 9735 function createElement(type, annotation) { 9736 var tagName = TAG_NAME[type]; 9737 9738 if (!tagName) { 9739 return null; 9740 } 9741 9742 var element = window.document.createElement(tagName); 9743 element.localName = tagName; 9744 var name = TAG_ANNOTATION[type]; 9745 9746 if (name && annotation) { 9747 element[name] = annotation.trim(); 9748 } 9749 9750 return element; 9751 } 9752 9753 var rootDiv = window.document.createElement("div"), 9754 current = rootDiv, 9755 t, 9756 tagStack = []; 9757 9758 while ((t = nextToken()) !== null) { 9759 if (t[0] === '<') { 9760 if (t[1] === "/") { 9761 // If the closing tag matches, move back up to the parent node. 9762 if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { 9763 tagStack.pop(); 9764 current = current.parentNode; 9765 } // Otherwise just ignore the end tag. 9766 9767 9768 continue; 9769 } 9770 9771 var ts = parseTimeStamp(t.substr(1, t.length - 2)); 9772 var node; 9773 9774 if (ts) { 9775 // Timestamps are lead nodes as well. 9776 node = window.document.createProcessingInstruction("timestamp", ts); 9777 current.appendChild(node); 9778 continue; 9779 } 9780 9781 var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); // If we can't parse the tag, skip to the next tag. 9782 9783 if (!m) { 9784 continue; 9785 } // Try to construct an element, and ignore the tag if we couldn't. 9786 9787 9788 node = createElement(m[1], m[3]); 9789 9790 if (!node) { 9791 continue; 9792 } // Determine if the tag should be added based on the context of where it 9793 // is placed in the cuetext. 9794 9795 9796 if (!shouldAdd(current, node)) { 9797 continue; 9798 } // Set the class list (as a list of classes, separated by space). 9799 9800 9801 if (m[2]) { 9802 node.className = m[2].substr(1).replace('.', ' '); 9803 } // Append the node to the current node, and enter the scope of the new 9804 // node. 9805 9806 9807 tagStack.push(m[1]); 9808 current.appendChild(node); 9809 current = node; 9810 continue; 9811 } // Text nodes are leaf nodes. 9812 9813 9814 current.appendChild(window.document.createTextNode(unescape(t))); 9815 } 9816 9817 return rootDiv; 9818 } // This is a list of all the Unicode characters that have a strong 9819 // right-to-left category. What this means is that these characters are 9820 // written right-to-left for sure. It was generated by pulling all the strong 9821 // right-to-left characters out of the Unicode data table. That table can 9822 // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt 9823 9824 9825 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], [0x6
vendor: 17,874 bytes, lines 9825-10300
982571, 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]]; 9826 9827 function isStrongRTLChar(charCode) { 9828 for (var i = 0; i < strongRTLRanges.length; i++) { 9829 var currentRange = strongRTLRanges[i]; 9830 9831 if (charCode >= currentRange[0] && charCode <= currentRange[1]) { 9832 return true; 9833 } 9834 } 9835 9836 return false; 9837 } 9838 9839 function determineBidi(cueDiv) { 9840 var nodeStack = [], 9841 text = "", 9842 charCode; 9843 9844 if (!cueDiv || !cueDiv.childNodes) { 9845 return "ltr"; 9846 } 9847 9848 function pushNodes(nodeStack, node) { 9849 for (var i = node.childNodes.length - 1; i >= 0; i--) { 9850 nodeStack.push(node.childNodes[i]); 9851 } 9852 } 9853 9854 function nextTextNode(nodeStack) { 9855 if (!nodeStack || !nodeStack.length) { 9856 return null; 9857 } 9858 9859 var node = nodeStack.pop(), 9860 text = node.textContent || node.innerText; 9861 9862 if (text) { 9863 // TODO: This should match all unicode type B characters (paragraph 9864 // separator characters). See issue #115. 9865 var m = text.match(/^.*(\n|\r)/); 9866 9867 if (m) { 9868 nodeStack.length = 0; 9869 return m[0]; 9870 } 9871 9872 return text; 9873 } 9874 9875 if (node.tagName === "ruby") { 9876 return nextTextNode(nodeStack); 9877 } 9878 9879 if (node.childNodes) { 9880 pushNodes(nodeStack, node); 9881 return nextTextNode(nodeStack); 9882 } 9883 } 9884 9885 pushNodes(nodeStack, cueDiv); 9886 9887 while (text = nextTextNode(nodeStack)) { 9888 for (var i = 0; i < text.length; i++) { 9889 charCode = text.charCodeAt(i); 9890 9891 if (isStrongRTLChar(charCode)) { 9892 return "rtl"; 9893 } 9894 } 9895 } 9896 9897 return "ltr"; 9898 } 9899 9900 function computeLinePos(cue) { 9901 if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) { 9902 return cue.line; 9903 } 9904 9905 if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) { 9906 return -1; 9907 } 9908 9909 var track = cue.track, 9910 trackList = track.textTrackList, 9911 count = 0; 9912 9913 for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { 9914 if (trackList[i].mode === "showing") { 9915 count++; 9916 } 9917 } 9918 9919 return ++count * -1; 9920 } 9921 9922 function StyleBox() {} // Apply styles to a div. If there is no div passed then it defaults to the 9923 // div on 'this'. 9924 9925 9926 StyleBox.prototype.applyStyles = function (styles, div) { 9927 div = div || this.div; 9928 9929 for (var prop in styles) { 9930 if (styles.hasOwnProperty(prop)) { 9931 div.style[prop] = styles[prop]; 9932 } 9933 } 9934 }; 9935 9936 StyleBox.prototype.formatStyle = function (val, unit) { 9937 return val === 0 ? 0 : val + unit; 9938 }; // Constructs the computed display state of the cue (a div). Places the div 9939 // into the overlay which should be a block level element (usually a div). 9940 9941 9942 function CueStyleBox(window, cue, styleOptions) { 9943 StyleBox.call(this); 9944 this.cue = cue; // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will 9945 // have inline positioning and will function as the cue background box. 9946 9947 this.cueDiv = parseContent(window, cue.text); 9948 var styles = { 9949 color: "rgba(255, 255, 255, 1)", 9950 backgroundColor: "rgba(0, 0, 0, 0.8)", 9951 position: "relative", 9952 left: 0, 9953 right: 0, 9954 top: 0, 9955 bottom: 0, 9956 display: "inline", 9957 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9958 unicodeBidi: "plaintext" 9959 }; 9960 this.applyStyles(styles, this.cueDiv); // Create an absolutely positioned div that will be used to position the cue 9961 // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS 9962 // mirrors of them except "middle" which is "center" in CSS. 9963 9964 this.div = window.document.createElement("div"); 9965 styles = { 9966 direction: determineBidi(this.cueDiv), 9967 writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl", 9968 unicodeBidi: "plaintext", 9969 textAlign: cue.align === "middle" ? "center" : cue.align, 9970 font: styleOptions.font, 9971 whiteSpace: "pre-line", 9972 position: "absolute" 9973 }; 9974 this.applyStyles(styles); 9975 this.div.appendChild(this.cueDiv); // Calculate the distance from the reference edge of the viewport to the text 9976 // position of the cue box. The reference edge will be resolved later when 9977 // the box orientation styles are applied. 9978 9979 var textPos = 0; 9980 9981 switch (cue.positionAlign) { 9982 case "start": 9983 textPos = cue.position; 9984 break; 9985 9986 case "middle": 9987 textPos = cue.position - cue.size / 2; 9988 break; 9989 9990 case "end": 9991 textPos = cue.position - cue.size; 9992 break; 9993 } // Horizontal box orientation; textPos is the distance from the left edge of the 9994 // area to the left edge of the box and cue.size is the distance extending to 9995 // the right from there. 9996 9997 9998 if (cue.vertical === "") { 9999 this.applyStyles({ 10000 left: this.formatStyle(textPos, "%"), 10001 width: this.formatStyle(cue.size, "%") 10002 }); // Vertical box orientation; textPos is the distance from the top edge of the 10003 // area to the top edge of the box and cue.size is the height extending 10004 // downwards from there. 10005 } else { 10006 this.applyStyles({ 10007 top: this.formatStyle(textPos, "%"), 10008 height: this.formatStyle(cue.size, "%") 10009 }); 10010 } 10011 10012 this.move = function (box) { 10013 this.applyStyles({ 10014 top: this.formatStyle(box.top, "px"), 10015 bottom: this.formatStyle(box.bottom, "px"), 10016 left: this.formatStyle(box.left, "px"), 10017 right: this.formatStyle(box.right, "px"), 10018 height: this.formatStyle(box.height, "px"), 10019 width: this.formatStyle(box.width, "px") 10020 }); 10021 }; 10022 } 10023 10024 CueStyleBox.prototype = _objCreate(StyleBox.prototype); 10025 CueStyleBox.prototype.constructor = CueStyleBox; // Represents the co-ordinates of an Element in a way that we can easily 10026 // compute things with such as if it overlaps or intersects with another Element. 10027 // Can initialize it with either a StyleBox or another BoxPosition. 10028 10029 function BoxPosition(obj) { 10030 // Either a BoxPosition was passed in and we need to copy it, or a StyleBox 10031 // was passed in and we need to copy the results of 'getBoundingClientRect' 10032 // as the object returned is readonly. All co-ordinate values are in reference 10033 // to the viewport origin (top left). 10034 var lh, height, width, top; 10035 10036 if (obj.div) { 10037 height = obj.div.offsetHeight; 10038 width = obj.div.offsetWidth; 10039 top = obj.div.offsetTop; 10040 var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects(); 10041 obj = obj.div.getBoundingClientRect(); // In certain cases the outter div will be slightly larger then the sum of 10042 // the inner div's lines. This could be due to bold text, etc, on some platforms. 10043 // In this case we should get the average line height and use that. This will 10044 // result in the desired behaviour. 10045 10046 lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0; 10047 } 10048 10049 this.left = obj.left; 10050 this.right = obj.right; 10051 this.top = obj.top || top; 10052 this.height = obj.height || height; 10053 this.bottom = obj.bottom || top + (obj.height || height); 10054 this.width = obj.width || width; 10055 this.lineHeight = lh !== undefined ? lh : obj.lineHeight; 10056 } // Move the box along a particular axis. Optionally pass in an amount to move 10057 // the box. If no amount is passed then the default is the line height of the 10058 // box. 10059 10060 10061 BoxPosition.prototype.move = function (axis, toMove) { 10062 toMove = toMove !== undefined ? toMove : this.lineHeight; 10063 10064 switch (axis) { 10065 case "+x": 10066 this.left += toMove; 10067 this.right += toMove; 10068 break; 10069 10070 case "-x": 10071 this.left -= toMove; 10072 this.right -= toMove; 10073 break; 10074 10075 case "+y": 10076 this.top += toMove; 10077 this.bottom += toMove; 10078 break; 10079 10080 case "-y": 10081 this.top -= toMove; 10082 this.bottom -= toMove; 10083 break; 10084 } 10085 }; // Check if this box overlaps another box, b2. 10086 10087 10088 BoxPosition.prototype.overlaps = function (b2) { 10089 return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top; 10090 }; // Check if this box overlaps any other boxes in boxes. 10091 10092 10093 BoxPosition.prototype.overlapsAny = function (boxes) { 10094 for (var i = 0; i < boxes.length; i++) { 10095 if (this.overlaps(boxes[i])) { 10096 return true; 10097 } 10098 } 10099 10100 return false; 10101 }; // Check if this box is within another box. 10102 10103 10104 BoxPosition.prototype.within = function (container) { 10105 return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right; 10106 }; // Check if this box is entirely within the container or it is overlapping 10107 // on the edge opposite of the axis direction passed. For example, if "+x" is 10108 // passed and the box is overlapping on the left edge of the container, then 10109 // return true. 10110 10111 10112 BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) { 10113 switch (axis) { 10114 case "+x": 10115 return this.left < container.left; 10116 10117 case "-x": 10118 return this.right > container.right; 10119 10120 case "+y": 10121 return this.top < container.top; 10122 10123 case "-y": 10124 return this.bottom > container.bottom; 10125 } 10126 }; // Find the percentage of the area that this box is overlapping with another 10127 // box. 10128 10129 10130 BoxPosition.prototype.intersectPercentage = function (b2) { 10131 var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), 10132 y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), 10133 intersectArea = x * y; 10134 return intersectArea / (this.height * this.width); 10135 }; // Convert the positions from this box to CSS compatible positions using 10136 // the reference container's positions. This has to be done because this 10137 // box's positions are in reference to the viewport origin, whereas, CSS 10138 // values are in referecne to their respective edges. 10139 10140 10141 BoxPosition.prototype.toCSSCompatValues = function (reference) { 10142 return { 10143 top: this.top - reference.top, 10144 bottom: reference.bottom - this.bottom, 10145 left: this.left - reference.left, 10146 right: reference.right - this.right, 10147 height: this.height, 10148 width: this.width 10149 }; 10150 }; // Get an object that represents the box's position without anything extra. 10151 // Can pass a StyleBox, HTMLElement, or another BoxPositon. 10152 10153 10154 BoxPosition.getSimpleBoxPosition = function (obj) { 10155 var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; 10156 var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; 10157 var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; 10158 obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj; 10159 var ret = { 10160 left: obj.left, 10161 right: obj.right, 10162 top: obj.top || top, 10163 height: obj.height || height, 10164 bottom: obj.bottom || top + (obj.height || height), 10165 width: obj.width || width 10166 }; 10167 return ret; 10168 }; // Move a StyleBox to its specified, or next best, position. The containerBox 10169 // is the box that contains the StyleBox, such as a div. boxPositions are 10170 // a list of other boxes that the styleBox can't overlap with. 10171 10172 10173 function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { 10174 // Find the best position for a cue box, b, on the video. The axis parameter 10175 // is a list of axis, the order of which, it will move the box along. For example: 10176 // Passing ["+x", "-x"] will move the box first along the x axis in the positive 10177 // direction. If it doesn't find a good position for it there it will then move 10178 // it along the x axis in the negative direction. 10179 function findBestPosition(b, axis) { 10180 var bestPosition, 10181 specifiedPosition = new BoxPosition(b), 10182 percentage = 1; // Highest possible so the first thing we get is better. 10183 10184 for (var i = 0; i < axis.length; i++) { 10185 while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) { 10186 b.move(axis[i]); 10187 } // We found a spot where we aren't overlapping anything. This is our 10188 // best position. 10189 10190 10191 if (b.within(containerBox)) { 10192 return b; 10193 } 10194 10195 var p = b.intersectPercentage(containerBox); // If we're outside the container box less then we were on our last try 10196 // then remember this position as the best position. 10197 10198 if (percentage > p) { 10199 bestPosition = new BoxPosition(b); 10200 percentage = p; 10201 } // Reset the box position to the specified position. 10202 10203 10204 b = new BoxPosition(specifiedPosition); 10205 } 10206 10207 return bestPosition || specifiedPosition; 10208 } 10209 10210 var boxPosition = new BoxPosition(styleBox), 10211 cue = styleBox.cue, 10212 linePos = computeLinePos(cue), 10213 axis = []; // If we have a line number to align the cue to. 10214 10215 if (cue.snapToLines) { 10216 var size; 10217 10218 switch (cue.vertical) { 10219 case "": 10220 axis = ["+y", "-y"]; 10221 size = "height"; 10222 break; 10223 10224 case "rl": 10225 axis = ["+x", "-x"]; 10226 size = "width"; 10227 break; 10228 10229 case "lr": 10230 axis = ["-x", "+x"]; 10231 size = "width"; 10232 break; 10233 } 10234 10235 var step = boxPosition.lineHeight, 10236 position = step * Math.round(linePos), 10237 maxPosition = containerBox[size] + step, 10238 initialAxis = axis[0]; // If the specified intial position is greater then the max position then 10239 // clamp the box to the amount of steps it would take for the box to 10240 // reach the max position. 10241 10242 if (Math.abs(position) > maxPosition) { 10243 position = position < 0 ? -1 : 1; 10244 position *= Math.ceil(maxPosition / step) * step; 10245 } // If computed line position returns negative then line numbers are 10246 // relative to the bottom of the video instead of the top. Therefore, we 10247 // need to increase our initial position by the length or width of the 10248 // video, depending on the writing direction, and reverse our axis directions. 10249 10250 10251 if (linePos < 0) { 10252 position += cue.vertical === "" ? containerBox.height : containerBox.width; 10253 axis = axis.reverse(); 10254 } // Move the box to the specified position. This may not be its best 10255 // position. 10256 10257 10258 boxPosition.move(initialAxis, position); 10259 } else { 10260 // If we have a percentage line value for the cue. 10261 var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100; 10262 10263 switch (cue.lineAlign) { 10264 case "middle": 10265 linePos -= calculatedPercentage / 2; 10266 break; 10267 10268 case "end": 10269 linePos -= calculatedPercentage; 10270 break; 10271 } // Apply initial line position to the cue box. 10272 10273 10274 switch (cue.vertical) { 10275 case "": 10276 styleBox.applyStyles({ 10277 top: styleBox.formatStyle(linePos, "%") 10278 }); 10279 break; 10280 10281 case "rl": 10282 styleBox.applyStyles({ 10283 left: styleBox.formatStyle(linePos, "%") 10284 }); 10285 break; 10286 10287 case "lr": 10288 styleBox.applyStyles({ 10289 right: styleBox.formatStyle(linePos, "%") 10290 }); 10291 break; 10292 } 10293 10294 axis = ["+y", "-x", "+x", "-y"]; // Get the box position again after we've applied the specified positioning 10295 // to it. 10296 10297 boxPosition = new BoxPosition(styleBox); 10298 } 10299 10300 var bestPosition = findBestPosition(boxPosition, axis);
vendor: 12,961 bytes, lines 10301-10717
10301 styleBox.move(bestPosition.toCSSCompatValues(containerBox)); 10302 } 10303 10304 function WebVTT$1() {} // Nothing 10305 // Helper to allow strings to be decoded instead of the default binary utf8 data. 10306 10307 10308 WebVTT$1.StringDecoder = function () { 10309 return { 10310 decode: function decode(data) { 10311 if (!data) { 10312 return ""; 10313 } 10314 10315 if (typeof data !== "string") { 10316 throw new Error("Error - expected string data."); 10317 } 10318 10319 return decodeURIComponent(encodeURIComponent(data)); 10320 } 10321 }; 10322 }; 10323 10324 WebVTT$1.convertCueToDOMTree = function (window, cuetext) { 10325 if (!window || !cuetext) { 10326 return null; 10327 } 10328 10329 return parseContent(window, cuetext); 10330 }; 10331 10332 var FONT_SIZE_PERCENT = 0.05; 10333 var FONT_STYLE = "sans-serif"; 10334 var CUE_BACKGROUND_PADDING = "1.5%"; // Runs the processing model over the cues and regions passed to it. 10335 // @param overlay A block level element (usually a div) that the computed cues 10336 // and regions will be placed into. 10337 10338 WebVTT$1.processCues = function (window, cues, overlay) { 10339 if (!window || !cues || !overlay) { 10340 return null; 10341 } // Remove all previous children. 10342 10343 10344 while (overlay.firstChild) { 10345 overlay.removeChild(overlay.firstChild); 10346 } 10347 10348 var paddedOverlay = window.document.createElement("div"); 10349 paddedOverlay.style.position = "absolute"; 10350 paddedOverlay.style.left = "0"; 10351 paddedOverlay.style.right = "0"; 10352 paddedOverlay.style.top = "0"; 10353 paddedOverlay.style.bottom = "0"; 10354 paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; 10355 overlay.appendChild(paddedOverlay); // Determine if we need to compute the display states of the cues. This could 10356 // be the case if a cue's state has been changed since the last computation or 10357 // if it has not been computed yet. 10358 10359 function shouldCompute(cues) { 10360 for (var i = 0; i < cues.length; i++) { 10361 if (cues[i].hasBeenReset || !cues[i].displayState) { 10362 return true; 10363 } 10364 } 10365 10366 return false; 10367 } // We don't need to recompute the cues' display states. Just reuse them. 10368 10369 10370 if (!shouldCompute(cues)) { 10371 for (var i = 0; i < cues.length; i++) { 10372 paddedOverlay.appendChild(cues[i].displayState); 10373 } 10374 10375 return; 10376 } 10377 10378 var boxPositions = [], 10379 containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), 10380 fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; 10381 var styleOptions = { 10382 font: fontSize + "px " + FONT_STYLE 10383 }; 10384 10385 (function () { 10386 var styleBox, cue; 10387 10388 for (var i = 0; i < cues.length; i++) { 10389 cue = cues[i]; // Compute the intial position and styles of the cue div. 10390 10391 styleBox = new CueStyleBox(window, cue, styleOptions); 10392 paddedOverlay.appendChild(styleBox.div); // Move the cue div to it's correct line position. 10393 10394 moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); // Remember the computed div so that we don't have to recompute it later 10395 // if we don't have too. 10396 10397 cue.displayState = styleBox.div; 10398 boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); 10399 } 10400 })(); 10401 }; 10402 10403 WebVTT$1.Parser = function (window, vttjs, decoder) { 10404 if (!decoder) { 10405 decoder = vttjs; 10406 vttjs = {}; 10407 } 10408 10409 if (!vttjs) { 10410 vttjs = {}; 10411 } 10412 10413 this.window = window; 10414 this.vttjs = vttjs; 10415 this.state = "INITIAL"; 10416 this.buffer = ""; 10417 this.decoder = decoder || new TextDecoder("utf8"); 10418 this.regionList = []; 10419 }; 10420 10421 WebVTT$1.Parser.prototype = { 10422 // If the error is a ParsingError then report it to the consumer if 10423 // possible. If it's not a ParsingError then throw it like normal. 10424 reportOrThrowError: function reportOrThrowError(e) { 10425 if (e instanceof ParsingError) { 10426 this.onparsingerror && this.onparsingerror(e); 10427 } else { 10428 throw e; 10429 } 10430 }, 10431 parse: function parse(data) { 10432 var self = this; // If there is no data then we won't decode it, but will just try to parse 10433 // whatever is in buffer already. This may occur in circumstances, for 10434 // example when flush() is called. 10435 10436 if (data) { 10437 // Try to decode the data that we received. 10438 self.buffer += self.decoder.decode(data, { 10439 stream: true 10440 }); 10441 } 10442 10443 function collectNextLine() { 10444 var buffer = self.buffer; 10445 var pos = 0; 10446 10447 while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { 10448 ++pos; 10449 } 10450 10451 var line = buffer.substr(0, pos); // Advance the buffer early in case we fail below. 10452 10453 if (buffer[pos] === '\r') { 10454 ++pos; 10455 } 10456 10457 if (buffer[pos] === '\n') { 10458 ++pos; 10459 } 10460 10461 self.buffer = buffer.substr(pos); 10462 return line; 10463 } // 3.4 WebVTT region and WebVTT region settings syntax 10464 10465 10466 function parseRegion(input) { 10467 var settings = new Settings(); 10468 parseOptions(input, function (k, v) { 10469 switch (k) { 10470 case "id": 10471 settings.set(k, v); 10472 break; 10473 10474 case "width": 10475 settings.percent(k, v); 10476 break; 10477 10478 case "lines": 10479 settings.integer(k, v); 10480 break; 10481 10482 case "regionanchor": 10483 case "viewportanchor": 10484 var xy = v.split(','); 10485 10486 if (xy.length !== 2) { 10487 break; 10488 } // We have to make sure both x and y parse, so use a temporary 10489 // settings object here. 10490 10491 10492 var anchor = new Settings(); 10493 anchor.percent("x", xy[0]); 10494 anchor.percent("y", xy[1]); 10495 10496 if (!anchor.has("x") || !anchor.has("y")) { 10497 break; 10498 } 10499 10500 settings.set(k + "X", anchor.get("x")); 10501 settings.set(k + "Y", anchor.get("y")); 10502 break; 10503 10504 case "scroll": 10505 settings.alt(k, v, ["up"]); 10506 break; 10507 } 10508 }, /=/, /\s/); // Create the region, using default values for any values that were not 10509 // specified. 10510 10511 if (settings.has("id")) { 10512 var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); 10513 region.width = settings.get("width", 100); 10514 region.lines = settings.get("lines", 3); 10515 region.regionAnchorX = settings.get("regionanchorX", 0); 10516 region.regionAnchorY = settings.get("regionanchorY", 100); 10517 region.viewportAnchorX = settings.get("viewportanchorX", 0); 10518 region.viewportAnchorY = settings.get("viewportanchorY", 100); 10519 region.scroll = settings.get("scroll", ""); // Register the region. 10520 10521 self.onregion && self.onregion(region); // Remember the VTTRegion for later in case we parse any VTTCues that 10522 // reference it. 10523 10524 self.regionList.push({ 10525 id: settings.get("id"), 10526 region: region 10527 }); 10528 } 10529 } // draft-pantos-http-live-streaming-20 10530 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 10531 // 3.5 WebVTT 10532 10533 10534 function parseTimestampMap(input) { 10535 var settings = new Settings(); 10536 parseOptions(input, function (k, v) { 10537 switch (k) { 10538 case "MPEGT": 10539 settings.integer(k + 'S', v); 10540 break; 10541 10542 case "LOCA": 10543 settings.set(k + 'L', parseTimeStamp(v)); 10544 break; 10545 } 10546 }, /[^\d]:/, /,/); 10547 self.ontimestampmap && self.ontimestampmap({ 10548 "MPEGTS": settings.get("MPEGTS"), 10549 "LOCAL": settings.get("LOCAL") 10550 }); 10551 } // 3.2 WebVTT metadata header syntax 10552 10553 10554 function parseHeader(input) { 10555 if (input.match(/X-TIMESTAMP-MAP/)) { 10556 // This line contains HLS X-TIMESTAMP-MAP metadata 10557 parseOptions(input, function (k, v) { 10558 switch (k) { 10559 case "X-TIMESTAMP-MAP": 10560 parseTimestampMap(v); 10561 break; 10562 } 10563 }, /=/); 10564 } else { 10565 parseOptions(input, function (k, v) { 10566 switch (k) { 10567 case "Region": 10568 // 3.3 WebVTT region metadata header syntax 10569 parseRegion(v); 10570 break; 10571 } 10572 }, /:/); 10573 } 10574 } // 5.1 WebVTT file parsing. 10575 10576 10577 try { 10578 var line; 10579 10580 if (self.state === "INITIAL") { 10581 // We can't start parsing until we have the first line. 10582 if (!/\r\n|\n/.test(self.buffer)) { 10583 return this; 10584 } 10585 10586 line = collectNextLine(); 10587 var m = line.match(/^WEBVTT([ \t].*)?$/); 10588 10589 if (!m || !m[0]) { 10590 throw new ParsingError(ParsingError.Errors.BadSignature); 10591 } 10592 10593 self.state = "HEADER"; 10594 } 10595 10596 var alreadyCollectedLine = false; 10597 10598 while (self.buffer) { 10599 // We can't parse a line until we have the full line. 10600 if (!/\r\n|\n/.test(self.buffer)) { 10601 return this; 10602 } 10603 10604 if (!alreadyCollectedLine) { 10605 line = collectNextLine(); 10606 } else { 10607 alreadyCollectedLine = false; 10608 } 10609 10610 switch (self.state) { 10611 case "HEADER": 10612 // 13-18 - Allow a header (metadata) under the WEBVTT line. 10613 if (/:/.test(line)) { 10614 parseHeader(line); 10615 } else if (!line) { 10616 // An empty line terminates the header and starts the body (cues). 10617 self.state = "ID"; 10618 } 10619 10620 continue; 10621 10622 case "NOTE": 10623 // Ignore NOTE blocks. 10624 if (!line) { 10625 self.state = "ID"; 10626 } 10627 10628 continue; 10629 10630 case "ID": 10631 // Check for the start of NOTE blocks. 10632 if (/^NOTE($|[ \t])/.test(line)) { 10633 self.state = "NOTE"; 10634 break; 10635 } // 19-29 - Allow any number of line terminators, then initialize new cue values. 10636 10637 10638 if (!line) { 10639 continue; 10640 } 10641 10642 self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); 10643 self.state = "CUE"; // 30-39 - Check if self line contains an optional identifier or timing data. 10644 10645 if (line.indexOf("-->") === -1) { 10646 self.cue.id = line; 10647 continue; 10648 } 10649 10650 // Process line as start of a cue. 10651 10652 /*falls through*/ 10653 10654 case "CUE": 10655 // 40 - Collect cue timings and settings. 10656 try { 10657 parseCue(line, self.cue, self.regionList); 10658 } catch (e) { 10659 self.reportOrThrowError(e); // In case of an error ignore rest of the cue. 10660 10661 self.cue = null; 10662 self.state = "BADCUE"; 10663 continue; 10664 } 10665 10666 self.state = "CUETEXT"; 10667 continue; 10668 10669 case "CUETEXT": 10670 var hasSubstring = line.indexOf("-->") !== -1; // 34 - If we have an empty line then report the cue. 10671 // 35 - If we have the special substring '-->' then report the cue, 10672 // but do not collect the line as we need to process the current 10673 // one as a new cue. 10674 10675 if (!line || hasSubstring && (alreadyCollectedLine = true)) { 10676 // We are done parsing self cue. 10677 self.oncue && self.oncue(self.cue); 10678 self.cue = null; 10679 self.state = "ID"; 10680 continue; 10681 } 10682 10683 if (self.cue.text) { 10684 self.cue.text += "\n"; 10685 } 10686 10687 self.cue.text += line; 10688 continue; 10689 10690 case "BADCUE": 10691 // BADCUE 10692 // 54-62 - Collect and discard the remaining cue. 10693 if (!line) { 10694 self.state = "ID"; 10695 } 10696 10697 continue; 10698 } 10699 } 10700 } catch (e) { 10701 self.reportOrThrowError(e); // If we are currently parsing a cue, report what we have. 10702 10703 if (self.state === "CUETEXT" && self.cue && self.oncue) { 10704 self.oncue(self.cue); 10705 } 10706 10707 self.cue = null; // Enter BADWEBVTT state if header was not parsed correctly otherwise 10708 // another exception occurred so enter BADCUE state. 10709 10710 self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; 10711 } 10712 10713 return this; 10714 }, 10715 flush: function flush() { 10716 var self = this; 10717
vendor: 5,482 bytes, lines 10718-10916
10718 try { 10719 // Finish decoding the stream. 10720 self.buffer += self.decoder.decode(); // Synthesize the end of the current cue or region. 10721 10722 if (self.cue || self.state === "HEADER") { 10723 self.buffer += "\n\n"; 10724 self.parse(); 10725 } // If we've flushed, parsed, and we're still on the INITIAL state then 10726 // that means we don't have enough of the stream to parse the first 10727 // line. 10728 10729 10730 if (self.state === "INITIAL") { 10731 throw new ParsingError(ParsingError.Errors.BadSignature); 10732 } 10733 } catch (e) { 10734 self.reportOrThrowError(e); 10735 } 10736 10737 self.onflush && self.onflush(); 10738 return this; 10739 } 10740 }; 10741 var vtt = WebVTT$1; 10742 10743 /** 10744 * Copyright 2013 vtt.js Contributors 10745 * 10746 * Licensed under the Apache License, Version 2.0 (the "License"); 10747 * you may not use this file except in compliance with the License. 10748 * You may obtain a copy of the License at 10749 * 10750 * http://www.apache.org/licenses/LICENSE-2.0 10751 * 10752 * Unless required by applicable law or agreed to in writing, software 10753 * distributed under the License is distributed on an "AS IS" BASIS, 10754 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10755 * See the License for the specific language governing permissions and 10756 * limitations under the License. 10757 */ 10758 var autoKeyword = "auto"; 10759 var directionSetting = { 10760 "": 1, 10761 "lr": 1, 10762 "rl": 1 10763 }; 10764 var alignSetting = { 10765 "start": 1, 10766 "middle": 1, 10767 "end": 1, 10768 "left": 1, 10769 "right": 1 10770 }; 10771 10772 function findDirectionSetting(value) { 10773 if (typeof value !== "string") { 10774 return false; 10775 } 10776 10777 var dir = directionSetting[value.toLowerCase()]; 10778 return dir ? value.toLowerCase() : false; 10779 } 10780 10781 function findAlignSetting(value) { 10782 if (typeof value !== "string") { 10783 return false; 10784 } 10785 10786 var align = alignSetting[value.toLowerCase()]; 10787 return align ? value.toLowerCase() : false; 10788 } 10789 10790 function VTTCue(startTime, endTime, text) { 10791 /** 10792 * Shim implementation specific properties. These properties are not in 10793 * the spec. 10794 */ 10795 // Lets us know when the VTTCue's data has changed in such a way that we need 10796 // to recompute its display state. This lets us compute its display state 10797 // lazily. 10798 this.hasBeenReset = false; 10799 /** 10800 * VTTCue and TextTrackCue properties 10801 * http://dev.w3.org/html5/webvtt/#vttcue-interface 10802 */ 10803 10804 var _id = ""; 10805 var _pauseOnExit = false; 10806 var _startTime = startTime; 10807 var _endTime = endTime; 10808 var _text = text; 10809 var _region = null; 10810 var _vertical = ""; 10811 var _snapToLines = true; 10812 var _line = "auto"; 10813 var _lineAlign = "start"; 10814 var _position = 50; 10815 var _positionAlign = "middle"; 10816 var _size = 50; 10817 var _align = "middle"; 10818 Object.defineProperties(this, { 10819 "id": { 10820 enumerable: true, 10821 get: function get() { 10822 return _id; 10823 }, 10824 set: function set(value) { 10825 _id = "" + value; 10826 } 10827 }, 10828 "pauseOnExit": { 10829 enumerable: true, 10830 get: function get() { 10831 return _pauseOnExit; 10832 }, 10833 set: function set(value) { 10834 _pauseOnExit = !!value; 10835 } 10836 }, 10837 "startTime": { 10838 enumerable: true, 10839 get: function get() { 10840 return _startTime; 10841 }, 10842 set: function set(value) { 10843 if (typeof value !== "number") { 10844 throw new TypeError("Start time must be set to a number."); 10845 } 10846 10847 _startTime = value; 10848 this.hasBeenReset = true; 10849 } 10850 }, 10851 "endTime": { 10852 enumerable: true, 10853 get: function get() { 10854 return _endTime; 10855 }, 10856 set: function set(value) { 10857 if (typeof value !== "number") { 10858 throw new TypeError("End time must be set to a number."); 10859 } 10860 10861 _endTime = value; 10862 this.hasBeenReset = true; 10863 } 10864 }, 10865 "text": { 10866 enumerable: true, 10867 get: function get() { 10868 return _text; 10869 }, 10870 set: function set(value) { 10871 _text = "" + value; 10872 this.hasBeenReset = true; 10873 } 10874 }, 10875 "region": { 10876 enumerable: true, 10877 get: function get() { 10878 return _region; 10879 }, 10880 set: function set(value) { 10881 _region = value; 10882 this.hasBeenReset = true; 10883 } 10884 }, 10885 "vertical": { 10886 enumerable: true, 10887 get: function get() { 10888 return _vertical; 10889 }, 10890 set: function set(value) { 10891 var setting = findDirectionSetting(value); // Have to check for false because the setting an be an empty string. 10892 10893 if (setting === false) { 10894 throw new SyntaxError("An invalid or illegal string was specified."); 10895 } 10896 10897 _vertical = setting; 10898 this.hasBeenReset = true; 10899 } 10900 }, 10901 "snapToLines": { 10902 enumerable: true, 10903 get: function get() { 10904 return _snapToLines; 10905 }, 10906 set: function set(value) { 10907 _snapToLines = !!value; 10908 this.hasBeenReset = true; 10909 } 10910 }, 10911 "line": { 10912 enumerable: true, 10913 get: function get() { 10914 return _line; 10915 }, 10916 set: function set(value) {
vendor: 4,585 bytes, lines 10917-11082
10917 if (typeof value !== "number" && value !== autoKeyword) { 10918 throw new SyntaxError("An invalid number or illegal string was specified."); 10919 } 10920 10921 _line = value; 10922 this.hasBeenReset = true; 10923 } 10924 }, 10925 "lineAlign": { 10926 enumerable: true, 10927 get: function get() { 10928 return _lineAlign; 10929 }, 10930 set: function set(value) { 10931 var setting = findAlignSetting(value); 10932 10933 if (!setting) { 10934 throw new SyntaxError("An invalid or illegal string was specified."); 10935 } 10936 10937 _lineAlign = setting; 10938 this.hasBeenReset = true; 10939 } 10940 }, 10941 "position": { 10942 enumerable: true, 10943 get: function get() { 10944 return _position; 10945 }, 10946 set: function set(value) { 10947 if (value < 0 || value > 100) { 10948 throw new Error("Position must be between 0 and 100."); 10949 } 10950 10951 _position = value; 10952 this.hasBeenReset = true; 10953 } 10954 }, 10955 "positionAlign": { 10956 enumerable: true, 10957 get: function get() { 10958 return _positionAlign; 10959 }, 10960 set: function set(value) { 10961 var setting = findAlignSetting(value); 10962 10963 if (!setting) { 10964 throw new SyntaxError("An invalid or illegal string was specified."); 10965 } 10966 10967 _positionAlign = setting; 10968 this.hasBeenReset = true; 10969 } 10970 }, 10971 "size": { 10972 enumerable: true, 10973 get: function get() { 10974 return _size; 10975 }, 10976 set: function set(value) { 10977 if (value < 0 || value > 100) { 10978 throw new Error("Size must be between 0 and 100."); 10979 } 10980 10981 _size = value; 10982 this.hasBeenReset = true; 10983 } 10984 }, 10985 "align": { 10986 enumerable: true, 10987 get: function get() { 10988 return _align; 10989 }, 10990 set: function set(value) { 10991 var setting = findAlignSetting(value); 10992 10993 if (!setting) { 10994 throw new SyntaxError("An invalid or illegal string was specified."); 10995 } 10996 10997 _align = setting; 10998 this.hasBeenReset = true; 10999 } 11000 } 11001 }); 11002 /** 11003 * Other <track> spec defined properties 11004 */ 11005 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state 11006 11007 this.displayState = undefined; 11008 } 11009 /** 11010 * VTTCue methods 11011 */ 11012 11013 11014 VTTCue.prototype.getCueAsHTML = function () { 11015 // Assume WebVTT.convertCueToDOMTree is on the global. 11016 return WebVTT.convertCueToDOMTree(window, this.text); 11017 }; 11018 11019 var vttcue = VTTCue; 11020 11021 /** 11022 * Copyright 2013 vtt.js Contributors 11023 * 11024 * Licensed under the Apache License, Version 2.0 (the "License"); 11025 * you may not use this file except in compliance with the License. 11026 * You may obtain a copy of the License at 11027 * 11028 * http://www.apache.org/licenses/LICENSE-2.0 11029 * 11030 * Unless required by applicable law or agreed to in writing, software 11031 * distributed under the License is distributed on an "AS IS" BASIS, 11032 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11033 * See the License for the specific language governing permissions and 11034 * limitations under the License. 11035 */ 11036 var scrollSetting = { 11037 "": true, 11038 "up": true 11039 }; 11040 11041 function findScrollSetting(value) { 11042 if (typeof value !== "string") { 11043 return false; 11044 } 11045 11046 var scroll = scrollSetting[value.toLowerCase()]; 11047 return scroll ? value.toLowerCase() : false; 11048 } 11049 11050 function isValidPercentValue(value) { 11051 return typeof value === "number" && value >= 0 && value <= 100; 11052 } // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface 11053 11054 11055 function VTTRegion() { 11056 var _width = 100; 11057 var _lines = 3; 11058 var _regionAnchorX = 0; 11059 var _regionAnchorY = 100; 11060 var _viewportAnchorX = 0; 11061 var _viewportAnchorY = 100; 11062 var _scroll = ""; 11063 Object.defineProperties(this, { 11064 "width": { 11065 enumerable: true, 11066 get: function get() { 11067 return _width; 11068 }, 11069 set: function set(value) { 11070 if (!isValidPercentValue(value)) { 11071 throw new Error("Width must be between 0 and 100."); 11072 } 11073 11074 _width = value; 11075 } 11076 }, 11077 "lines": { 11078 enumerable: true, 11079 get: function get() { 11080 return _lines; 11081 }, 11082 set: function set(value) {
11083 if (typeof value !== "number") { 11084 throw new TypeError("Lines must be set to a number."); 11085 } 11086 11087 _lines = value; 11088 } 11089 }, 11090 "regionAnchorY": { 11091 enumerable: true, 11092 get: function get() { 11093 return _regionAnchorY; 11094 }, 11095 set: function set(value) { 11096 if (!isValidPercentValue(value)) { 11097 throw new Error("RegionAnchorX must be between 0 and 100."); 11098 } 11099 11100 _regionAnchorY = value; 11101 } 11102 }, 11103 "regionAnchorX": { 11104 enumerable: true, 11105 get: function get() { 11106 return _regionAnchorX; 11107 }, 11108 set: function set(value) { 11109 if (!isValidPercentValue(value)) { 11110 throw new Error("RegionAnchorY must be between 0 and 100."); 11111 } 11112 11113 _regionAnchorX = value; 11114 } 11115 }, 11116 "viewportAnchorY": { 11117 enumerable: true, 11118 get: function get() { 11119 return _viewportAnchorY; 11120 }, 11121 set: function set(value) { 11122 if (!isValidPercentValue(value)) { 11123 throw new Error("ViewportAnchorY must be between 0 and 100."); 11124 } 11125 11126 _viewportAnchorY = value; 11127 } 11128 }, 11129 "viewportAnchorX": { 11130 enumerable: true, 11131 get: function get() { 11132 return _viewportAnchorX; 11133 }, 11134 set: function set(value) { 11135 if (!isValidPercentValue(value)) { 11136 throw new Error("ViewportAnchorX must be between 0 and 100."); 11137 } 11138 11139 _viewportAnchorX = value; 11140 } 11141 }, 11142 "scroll": { 11143 enumerable: true, 11144 get: function get() { 11145 return _scroll; 11146 }, 11147 set: function set(value) { 11148 var setting = findScrollSetting(value); // Have to check for false as an empty string is a legal value. 11149 11150 if (setting === false) { 11151 throw new SyntaxError("An invalid or illegal string was specified."); 11152 } 11153 11154 _scroll = setting; 11155 } 11156 } 11157 }); 11158 } 11159 11160 var vttregion = VTTRegion; 11161 11162 var browserIndex = createCommonjsModule(function (module) { 11163 /** 11164 * Copyright 2013 vtt.js Contributors 11165 * 11166 * Licensed under the Apache License, Version 2.0 (the "License"); 11167 * you may not use this file except in compliance with the License. 11168 * You may obtain a copy of the License at 11169 * 11170 * http://www.apache.org/licenses/LICENSE-2.0 11171 * 11172 * Unless required by applicable law or agreed to in writing, software 11173 * distributed under the License is distributed on an "AS IS" BASIS, 11174 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11175 * See the License for the specific language governing permissions and 11176 * limitations under the License. 11177 */ 11178 // Default exports for Node. Export the extended versions of VTTCue and 11179 // VTTRegion in Node since we likely want the capability to convert back and 11180 // forth between JSON. If we don't then it's not that big of a deal since we're 11181 // off browser. 11182 var vttjs = module.exports = { 11183 WebVTT: vtt, 11184 VTTCue: vttcue, 11185 VTTRegion: vttregion 11186 }; 11187 window$1.vttjs = vttjs; 11188 window$1.WebVTT = vttjs.WebVTT; 11189 var cueShim = vttjs.VTTCue; 11190 var regionShim = vttjs.VTTRegion; 11191 var nativeVTTCue = window$1.VTTCue; 11192 var nativeVTTRegion = window$1.VTTRegion; 11193 11194 vttjs.shim = function () { 11195 window$1.VTTCue = cueShim; 11196 window$1.VTTRegion = regionShim; 11197 }; 11198 11199 vttjs.restore = function () { 11200 window$1.VTTCue = nativeVTTCue; 11201 window$1.VTTRegion = nativeVTTRegion; 11202 }; 11203 11204 if (!window$1.VTTCue) { 11205 vttjs.shim(); 11206 } 11207 }); 11208 var browserIndex_1 = browserIndex.WebVTT; 11209 var browserIndex_2 = browserIndex.VTTCue; 11210 var browserIndex_3 = browserIndex.VTTRegion; 11211 11212 /** 11213 * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string 11214 * that just contains the src url alone. 11215 * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` 11216 * `var SourceString = 'http://example.com/some-video.mp4';` 11217 * 11218 * @typedef {Object|string} Tech~SourceObject 11219 * 11220 * @property {string} src 11221 * The url to the source 11222 * 11223 * @property {string} type 11224 * The mime type of the source 11225 */ 11226 11227 /** 11228 * A function used by {@link Tech} to create a new {@link TextTrack}. 11229 * 11230 * @private 11231 * 11232 * @param {Tech} self 11233 * An instance of the Tech class. 11234 * 11235 * @param {string} kind 11236 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 11237 * 11238 * @param {string} [label] 11239 * Label to identify the text track 11240 * 11241 * @param {string} [language] 11242 * Two letter language abbreviation 11243 * 11244 * @param {Object} [options={}] 11245 * An object with additional text track options 11246 * 11247 * @return {TextTrack} 11248 * The text track that was created. 11249 */ 11250 11251 function createTrackHelper(self, kind, label, language, options) { 11252 if (options === void 0) { 11253 options = {}; 11254 } 11255 11256 var tracks = self.textTracks(); 11257 options.kind = kind; 11258 11259 if (label) { 11260 options.label = label; 11261 } 11262 11263 if (language) { 11264 options.language = language; 11265 } 11266 11267 options.tech = self; 11268 var track = new ALL.text.TrackClass(options); 11269 tracks.addTrack(track); 11270 return track; 11271 } 11272 /** 11273 * This is the base class for media playback technology controllers, such as 11274 * {@link Flash} and {@link HTML5} 11275 * 11276 * @extends Component 11277 */ 11278 11279 11280 var Tech = 11281 /*#__PURE__*/ 11282 function (_Component) { 11283 _inheritsLoose(Tech, _Component); 11284 11285 /** 11286 * Create an instance of this Tech. 11287 * 11288 * @param {Object} [options] 11289 * The key/value store of player options. 11290 * 11291 * @param {Component~ReadyCallback} ready 11292 * Callback function to call when the `HTML5` Tech is ready. 11293 */ 11294 function Tech(options, ready) { 11295 var _this; 11296 11297 if (options === void 0) { 11298 options = {}; 11299 } 11300 11301 if (ready === void 0) { 11302 ready = function ready() {}; 11303 } 11304 11305 // we don't want the tech to report user activity automatically.
vendor: 38,923 bytes, lines 11306-12602
11306 // This is done manually in addControlsListeners 11307 options.reportTouchActivity = false; 11308 _this = _Component.call(this, null, options, ready) || this; // keep track of whether the current source has played at all to 11309 // implement a very limited played() 11310 11311 _this.hasStarted_ = false; 11312 11313 _this.on('playing', function () { 11314 this.hasStarted_ = true; 11315 }); 11316 11317 _this.on('loadstart', function () { 11318 this.hasStarted_ = false; 11319 }); 11320 11321 ALL.names.forEach(function (name) { 11322 var props = ALL[name]; 11323 11324 if (options && options[props.getterName]) { 11325 _this[props.privateName] = options[props.getterName]; 11326 } 11327 }); // Manually track progress in cases where the browser/flash player doesn't report it. 11328 11329 if (!_this.featuresProgressEvents) { 11330 _this.manualProgressOn(); 11331 } // Manually track timeupdates in cases where the browser/flash player doesn't report it. 11332 11333 11334 if (!_this.featuresTimeupdateEvents) { 11335 _this.manualTimeUpdatesOn(); 11336 } 11337 11338 ['Text', 'Audio', 'Video'].forEach(function (track) { 11339 if (options["native" + track + "Tracks"] === false) { 11340 _this["featuresNative" + track + "Tracks"] = false; 11341 } 11342 }); 11343 11344 if (options.nativeCaptions === false || options.nativeTextTracks === false) { 11345 _this.featuresNativeTextTracks = false; 11346 } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { 11347 _this.featuresNativeTextTracks = true; 11348 } 11349 11350 if (!_this.featuresNativeTextTracks) { 11351 _this.emulateTextTracks(); 11352 } 11353 11354 _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); 11355 11356 _this.initTrackListeners(); // Turn on component tap events only if not using native controls 11357 11358 11359 if (!options.nativeControlsForTouch) { 11360 _this.emitTapEvents(); 11361 } 11362 11363 if (_this.constructor) { 11364 _this.name_ = _this.constructor.name || 'Unknown Tech'; 11365 } 11366 11367 return _this; 11368 } 11369 /** 11370 * A special function to trigger source set in a way that will allow player 11371 * to re-trigger if the player or tech are not ready yet. 11372 * 11373 * @fires Tech#sourceset 11374 * @param {string} src The source string at the time of the source changing. 11375 */ 11376 11377 11378 var _proto = Tech.prototype; 11379 11380 _proto.triggerSourceset = function triggerSourceset(src) { 11381 var _this2 = this; 11382 11383 if (!this.isReady_) { 11384 // on initial ready we have to trigger source set 11385 // 1ms after ready so that player can watch for it. 11386 this.one('ready', function () { 11387 return _this2.setTimeout(function () { 11388 return _this2.triggerSourceset(src); 11389 }, 1); 11390 }); 11391 } 11392 /** 11393 * Fired when the source is set on the tech causing the media element 11394 * to reload. 11395 * 11396 * @see {@link Player#event:sourceset} 11397 * @event Tech#sourceset 11398 * @type {EventTarget~Event} 11399 */ 11400 11401 11402 this.trigger({ 11403 src: src, 11404 type: 'sourceset' 11405 }); 11406 } 11407 /* Fallbacks for unsupported event types 11408 ================================================================================ */ 11409 11410 /** 11411 * Polyfill the `progress` event for browsers that don't support it natively. 11412 * 11413 * @see {@link Tech#trackProgress} 11414 */ 11415 ; 11416 11417 _proto.manualProgressOn = function manualProgressOn() { 11418 this.on('durationchange', this.onDurationChange); 11419 this.manualProgress = true; // Trigger progress watching when a source begins loading 11420 11421 this.one('ready', this.trackProgress); 11422 } 11423 /** 11424 * Turn off the polyfill for `progress` events that was created in 11425 * {@link Tech#manualProgressOn} 11426 */ 11427 ; 11428 11429 _proto.manualProgressOff = function manualProgressOff() { 11430 this.manualProgress = false; 11431 this.stopTrackingProgress(); 11432 this.off('durationchange', this.onDurationChange); 11433 } 11434 /** 11435 * This is used to trigger a `progress` event when the buffered percent changes. It 11436 * sets an interval function that will be called every 500 milliseconds to check if the 11437 * buffer end percent has changed. 11438 * 11439 * > This function is called by {@link Tech#manualProgressOn} 11440 * 11441 * @param {EventTarget~Event} event 11442 * The `ready` event that caused this to run. 11443 * 11444 * @listens Tech#ready 11445 * @fires Tech#progress 11446 */ 11447 ; 11448 11449 _proto.trackProgress = function trackProgress(event) { 11450 this.stopTrackingProgress(); 11451 this.progressInterval = this.setInterval(bind(this, function () { 11452 // Don't trigger unless buffered amount is greater than last time 11453 var numBufferedPercent = this.bufferedPercent(); 11454 11455 if (this.bufferedPercent_ !== numBufferedPercent) { 11456 /** 11457 * See {@link Player#progress} 11458 * 11459 * @event Tech#progress 11460 * @type {EventTarget~Event} 11461 */ 11462 this.trigger('progress'); 11463 } 11464 11465 this.bufferedPercent_ = numBufferedPercent; 11466 11467 if (numBufferedPercent === 1) { 11468 this.stopTrackingProgress(); 11469 } 11470 }), 500); 11471 } 11472 /** 11473 * Update our internal duration on a `durationchange` event by calling 11474 * {@link Tech#duration}. 11475 * 11476 * @param {EventTarget~Event} event 11477 * The `durationchange` event that caused this to run. 11478 * 11479 * @listens Tech#durationchange 11480 */ 11481 ; 11482 11483 _proto.onDurationChange = function onDurationChange(event) { 11484 this.duration_ = this.duration(); 11485 } 11486 /** 11487 * Get and create a `TimeRange` object for buffering. 11488 * 11489 * @return {TimeRange} 11490 * The time range object that was created. 11491 */ 11492 ; 11493 11494 _proto.buffered = function buffered() { 11495 return createTimeRanges(0, 0); 11496 } 11497 /** 11498 * Get the percentage of the current video that is currently buffered. 11499 * 11500 * @return {number} 11501 * A number from 0 to 1 that represents the decimal percentage of the 11502 * video that is buffered. 11503 * 11504 */ 11505 ; 11506 11507 _proto.bufferedPercent = function bufferedPercent$1() { 11508 return bufferedPercent(this.buffered(), this.duration_); 11509 } 11510 /** 11511 * Turn off the polyfill for `progress` events that was created in 11512 * {@link Tech#manualProgressOn} 11513 * Stop manually tracking progress events by clearing the interval that was set in 11514 * {@link Tech#trackProgress}. 11515 */ 11516 ; 11517 11518 _proto.stopTrackingProgress = function stopTrackingProgress() { 11519 this.clearInterval(this.progressInterval); 11520 } 11521 /** 11522 * Polyfill the `timeupdate` event for browsers that don't support it. 11523 * 11524 * @see {@link Tech#trackCurrentTime} 11525 */ 11526 ; 11527 11528 _proto.manualTimeUpdatesOn = function manualTimeUpdatesOn() { 11529 this.manualTimeUpdates = true; 11530 this.on('play', this.trackCurrentTime); 11531 this.on('pause', this.stopTrackingCurrentTime); 11532 } 11533 /** 11534 * Turn off the polyfill for `timeupdate` events that was created in 11535 * {@link Tech#manualTimeUpdatesOn} 11536 */ 11537 ; 11538 11539 _proto.manualTimeUpdatesOff = function manualTimeUpdatesOff() { 11540 this.manualTimeUpdates = false; 11541 this.stopTrackingCurrentTime(); 11542 this.off('play', this.trackCurrentTime); 11543 this.off('pause', this.stopTrackingCurrentTime); 11544 } 11545 /** 11546 * Sets up an interval function to track current time and trigger `timeupdate` every 11547 * 250 milliseconds. 11548 * 11549 * @listens Tech#play 11550 * @triggers Tech#timeupdate 11551 */ 11552 ; 11553 11554 _proto.trackCurrentTime = function trackCurrentTime() { 11555 if (this.currentTimeInterval) { 11556 this.stopTrackingCurrentTime(); 11557 } 11558 11559 this.currentTimeInterval = this.setInterval(function () { 11560 /** 11561 * Triggered at an interval of 250ms to indicated that time is passing in the video. 11562 * 11563 * @event Tech#timeupdate 11564 * @type {EventTarget~Event} 11565 */ 11566 this.trigger({ 11567 type: 'timeupdate', 11568 target: this, 11569 manuallyTriggered: true 11570 }); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 11571 }, 250); 11572 } 11573 /** 11574 * Stop the interval function created in {@link Tech#trackCurrentTime} so that the 11575 * `timeupdate` event is no longer triggered. 11576 * 11577 * @listens {Tech#pause} 11578 */ 11579 ; 11580 11581 _proto.stopTrackingCurrentTime = function stopTrackingCurrentTime() { 11582 this.clearInterval(this.currentTimeInterval); // #1002 - if the video ends right before the next timeupdate would happen, 11583 // the progress bar won't make it all the way to the end 11584 11585 this.trigger({ 11586 type: 'timeupdate', 11587 target: this, 11588 manuallyTriggered: true 11589 }); 11590 } 11591 /** 11592 * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, 11593 * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. 11594 * 11595 * @fires Component#dispose 11596 */ 11597 ; 11598 11599 _proto.dispose = function dispose() { 11600 // clear out all tracks because we can't reuse them between techs 11601 this.clearTracks(NORMAL.names); // Turn off any manual progress or timeupdate tracking 11602 11603 if (this.manualProgress) { 11604 this.manualProgressOff(); 11605 } 11606 11607 if (this.manualTimeUpdates) { 11608 this.manualTimeUpdatesOff(); 11609 } 11610 11611 _Component.prototype.dispose.call(this); 11612 } 11613 /** 11614 * Clear out a single `TrackList` or an array of `TrackLists` given their names. 11615 * 11616 * > Note: Techs without source handlers should call this between sources for `video` 11617 * & `audio` tracks. You don't want to use them between tracks! 11618 * 11619 * @param {string[]|string} types 11620 * TrackList names to clear, valid names are `video`, `audio`, and 11621 * `text`. 11622 */ 11623 ; 11624 11625 _proto.clearTracks = function clearTracks(types) { 11626 var _this3 = this; 11627 11628 types = [].concat(types); // clear out all tracks because we can't reuse them between techs 11629 11630 types.forEach(function (type) { 11631 var list = _this3[type + "Tracks"]() || []; 11632 var i = list.length; 11633 11634 while (i--) { 11635 var track = list[i]; 11636 11637 if (type === 'text') { 11638 _this3.removeRemoteTextTrack(track); 11639 } 11640 11641 list.removeTrack(track); 11642 } 11643 }); 11644 } 11645 /** 11646 * Remove any TextTracks added via addRemoteTextTrack that are 11647 * flagged for automatic garbage collection 11648 */ 11649 ; 11650 11651 _proto.cleanupAutoTextTracks = function cleanupAutoTextTracks() { 11652 var list = this.autoRemoteTextTracks_ || []; 11653 var i = list.length; 11654 11655 while (i--) { 11656 var track = list[i]; 11657 this.removeRemoteTextTrack(track); 11658 } 11659 } 11660 /** 11661 * Reset the tech, which will removes all sources and reset the internal readyState. 11662 * 11663 * @abstract 11664 */ 11665 ; 11666 11667 _proto.reset = function reset() {} 11668 /** 11669 * Get or set an error on the Tech. 11670 * 11671 * @param {MediaError} [err] 11672 * Error to set on the Tech 11673 * 11674 * @return {MediaError|null} 11675 * The current error object on the tech, or null if there isn't one. 11676 */ 11677 ; 11678 11679 _proto.error = function error(err) { 11680 if (err !== undefined) { 11681 this.error_ = new MediaError(err); 11682 this.trigger('error'); 11683 } 11684 11685 return this.error_; 11686 } 11687 /** 11688 * Returns the `TimeRange`s that have been played through for the current source. 11689 * 11690 * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. 11691 * It only checks whether the source has played at all or not. 11692 * 11693 * @return {TimeRange} 11694 * - A single time range if this video has played 11695 * - An empty set of ranges if not. 11696 */ 11697 ; 11698 11699 _proto.played = function played() { 11700 if (this.hasStarted_) { 11701 return createTimeRanges(0, 0); 11702 } 11703 11704 return createTimeRanges(); 11705 } 11706 /** 11707 * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was 11708 * previously called. 11709 * 11710 * @fires Tech#timeupdate 11711 */ 11712 ; 11713 11714 _proto.setCurrentTime = function setCurrentTime() { 11715 // improve the accuracy of manual timeupdates 11716 if (this.manualTimeUpdates) { 11717 /** 11718 * A manual `timeupdate` event. 11719 * 11720 * @event Tech#timeupdate 11721 * @type {EventTarget~Event} 11722 */ 11723 this.trigger({ 11724 type: 'timeupdate', 11725 target: this, 11726 manuallyTriggered: true 11727 }); 11728 } 11729 } 11730 /** 11731 * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and 11732 * {@link TextTrackList} events. 11733 * 11734 * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. 11735 * 11736 * @fires Tech#audiotrackchange 11737 * @fires Tech#videotrackchange 11738 * @fires Tech#texttrackchange 11739 */ 11740 ; 11741 11742 _proto.initTrackListeners = function initTrackListeners() { 11743 var _this4 = this; 11744 11745 /** 11746 * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} 11747 * 11748 * @event Tech#audiotrackchange 11749 * @type {EventTarget~Event} 11750 */ 11751 11752 /** 11753 * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} 11754 * 11755 * @event Tech#videotrackchange 11756 * @type {EventTarget~Event} 11757 */ 11758 11759 /** 11760 * Triggered when tracks are added or removed on the Tech {@link TextTrackList} 11761 * 11762 * @event Tech#texttrackchange 11763 * @type {EventTarget~Event} 11764 */ 11765 NORMAL.names.forEach(function (name) { 11766 var props = NORMAL[name]; 11767 11768 var trackListChanges = function trackListChanges() { 11769 _this4.trigger(name + "trackchange"); 11770 }; 11771 11772 var tracks = _this4[props.getterName](); 11773 11774 tracks.addEventListener('removetrack', trackListChanges); 11775 tracks.addEventListener('addtrack', trackListChanges); 11776 11777 _this4.on('dispose', function () { 11778 tracks.removeEventListener('removetrack', trackListChanges); 11779 tracks.removeEventListener('addtrack', trackListChanges); 11780 }); 11781 }); 11782 } 11783 /** 11784 * Emulate TextTracks using vtt.js if necessary 11785 * 11786 * @fires Tech#vttjsloaded 11787 * @fires Tech#vttjserror 11788 */ 11789 ; 11790 11791 _proto.addWebVttScript_ = function addWebVttScript_() { 11792 var _this5 = this; 11793 11794 if (window$1.WebVTT) { 11795 return; 11796 } // Initially, Tech.el_ is a child of a dummy-div wait until the Component system 11797 // signals that the Tech is ready at which point Tech.el_ is part of the DOM 11798 // before inserting the WebVTT script 11799 11800 11801 if (document.body.contains(this.el())) { 11802 // load via require if available and vtt.js script location was not passed in 11803 // as an option. novtt builds will turn the above require call into an empty object 11804 // which will cause this if check to always fail. 11805 if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { 11806 this.trigger('vttjsloaded'); 11807 return; 11808 } // load vtt.js via the script location option or the cdn of no location was 11809 // passed in 11810 11811 11812 var script = document.createElement('script'); 11813 script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js'; 11814 11815 script.onload = function () { 11816 /** 11817 * Fired when vtt.js is loaded. 11818 * 11819 * @event Tech#vttjsloaded 11820 * @type {EventTarget~Event} 11821 */ 11822 _this5.trigger('vttjsloaded'); 11823 }; 11824 11825 script.onerror = function () { 11826 /** 11827 * Fired when vtt.js was not loaded due to an error 11828 * 11829 * @event Tech#vttjsloaded 11830 * @type {EventTarget~Event} 11831 */ 11832 _this5.trigger('vttjserror'); 11833 }; 11834 11835 this.on('dispose', function () { 11836 script.onload = null; 11837 script.onerror = null; 11838 }); // but have not loaded yet and we set it to true before the inject so that 11839 // we don't overwrite the injected window.WebVTT if it loads right away 11840 11841 window$1.WebVTT = true; 11842 this.el().parentNode.appendChild(script); 11843 } else { 11844 this.ready(this.addWebVttScript_); 11845 } 11846 } 11847 /** 11848 * Emulate texttracks 11849 * 11850 */ 11851 ; 11852 11853 _proto.emulateTextTracks = function emulateTextTracks() { 11854 var _this6 = this; 11855 11856 var tracks = this.textTracks(); 11857 var remoteTracks = this.remoteTextTracks(); 11858 11859 var handleAddTrack = function handleAddTrack(e) { 11860 return tracks.addTrack(e.track); 11861 }; 11862 11863 var handleRemoveTrack = function handleRemoveTrack(e) { 11864 return tracks.removeTrack(e.track); 11865 }; 11866 11867 remoteTracks.on('addtrack', handleAddTrack); 11868 remoteTracks.on('removetrack', handleRemoveTrack); 11869 this.addWebVttScript_(); 11870 11871 var updateDisplay = function updateDisplay() { 11872 return _this6.trigger('texttrackchange'); 11873 }; 11874 11875 var textTracksChanges = function textTracksChanges() { 11876 updateDisplay(); 11877 11878 for (var i = 0; i < tracks.length; i++) { 11879 var track = tracks[i]; 11880 track.removeEventListener('cuechange', updateDisplay); 11881 11882 if (track.mode === 'showing') { 11883 track.addEventListener('cuechange', updateDisplay); 11884 } 11885 } 11886 }; 11887 11888 textTracksChanges(); 11889 tracks.addEventListener('change', textTracksChanges); 11890 tracks.addEventListener('addtrack', textTracksChanges); 11891 tracks.addEventListener('removetrack', textTracksChanges); 11892 this.on('dispose', function () { 11893 remoteTracks.off('addtrack', handleAddTrack); 11894 remoteTracks.off('removetrack', handleRemoveTrack); 11895 tracks.removeEventListener('change', textTracksChanges); 11896 tracks.removeEventListener('addtrack', textTracksChanges); 11897 tracks.removeEventListener('removetrack', textTracksChanges); 11898 11899 for (var i = 0; i < tracks.length; i++) { 11900 var track = tracks[i]; 11901 track.removeEventListener('cuechange', updateDisplay); 11902 } 11903 }); 11904 } 11905 /** 11906 * Create and returns a remote {@link TextTrack} object. 11907 * 11908 * @param {string} kind 11909 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 11910 * 11911 * @param {string} [label] 11912 * Label to identify the text track 11913 * 11914 * @param {string} [language] 11915 * Two letter language abbreviation 11916 * 11917 * @return {TextTrack} 11918 * The TextTrack that gets created. 11919 */ 11920 ; 11921 11922 _proto.addTextTrack = function addTextTrack(kind, label, language) { 11923 if (!kind) { 11924 throw new Error('TextTrack kind is required but was not provided'); 11925 } 11926 11927 return createTrackHelper(this, kind, label, language); 11928 } 11929 /** 11930 * Create an emulated TextTrack for use by addRemoteTextTrack 11931 * 11932 * This is intended to be overridden by classes that inherit from 11933 * Tech in order to create native or custom TextTracks. 11934 * 11935 * @param {Object} options 11936 * The object should contain the options to initialize the TextTrack with. 11937 * 11938 * @param {string} [options.kind] 11939 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 11940 * 11941 * @param {string} [options.label]. 11942 * Label to identify the text track 11943 * 11944 * @param {string} [options.language] 11945 * Two letter language abbreviation. 11946 * 11947 * @return {HTMLTrackElement} 11948 * The track element that gets created. 11949 */ 11950 ; 11951 11952 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 11953 var track = mergeOptions(options, { 11954 tech: this 11955 }); 11956 return new REMOTE.remoteTextEl.TrackClass(track); 11957 } 11958 /** 11959 * Creates a remote text track object and returns an html track element. 11960 * 11961 * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. 11962 * 11963 * @param {Object} options 11964 * See {@link Tech#createRemoteTextTrack} for more detailed properties. 11965 * 11966 * @param {boolean} [manualCleanup=true] 11967 * - When false: the TextTrack will be automatically removed from the video 11968 * element whenever the source changes 11969 * - When True: The TextTrack will have to be cleaned up manually 11970 * 11971 * @return {HTMLTrackElement} 11972 * An Html Track Element. 11973 * 11974 * @deprecated The default functionality for this function will be equivalent 11975 * to "manualCleanup=false" in the future. The manualCleanup parameter will 11976 * also be removed. 11977 */ 11978 ; 11979 11980 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 11981 var _this7 = this; 11982 11983 if (options === void 0) { 11984 options = {}; 11985 } 11986 11987 var htmlTrackElement = this.createRemoteTextTrack(options); 11988 11989 if (manualCleanup !== true && manualCleanup !== false) { 11990 // deprecation warning 11991 log.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); 11992 manualCleanup = true; 11993 } // store HTMLTrackElement and TextTrack to remote list 11994 11995 11996 this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); 11997 this.remoteTextTracks().addTrack(htmlTrackElement.track); 11998 11999 if (manualCleanup !== true) { 12000 // create the TextTrackList if it doesn't exist 12001 this.ready(function () { 12002 return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); 12003 }); 12004 } 12005 12006 return htmlTrackElement; 12007 } 12008 /** 12009 * Remove a remote text track from the remote `TextTrackList`. 12010 * 12011 * @param {TextTrack} track 12012 * `TextTrack` to remove from the `TextTrackList` 12013 */ 12014 ; 12015 12016 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 12017 var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); // remove HTMLTrackElement and TextTrack from remote list 12018 12019 this.remoteTextTrackEls().removeTrackElement_(trackElement); 12020 this.remoteTextTracks().removeTrack(track); 12021 this.autoRemoteTextTracks_.removeTrack(track); 12022 } 12023 /** 12024 * Gets available media playback quality metrics as specified by the W3C's Media 12025 * Playback Quality API. 12026 * 12027 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 12028 * 12029 * @return {Object} 12030 * An object with supported media playback quality metrics 12031 * 12032 * @abstract 12033 */ 12034 ; 12035 12036 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 12037 return {}; 12038 } 12039 /** 12040 * Attempt to create a floating video window always on top of other windows 12041 * so that users may continue consuming media while they interact with other 12042 * content sites, or applications on their device. 12043 * 12044 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 12045 * 12046 * @return {Promise|undefined} 12047 * A promise with a Picture-in-Picture window if the browser supports 12048 * Promises (or one was passed in as an option). It returns undefined 12049 * otherwise. 12050 * 12051 * @abstract 12052 */ 12053 ; 12054 12055 _proto.requestPictureInPicture = function requestPictureInPicture() { 12056 var PromiseClass = this.options_.Promise || window$1.Promise; 12057 12058 if (PromiseClass) { 12059 return PromiseClass.reject(); 12060 } 12061 } 12062 /** 12063 * A method to set a poster from a `Tech`. 12064 * 12065 * @abstract 12066 */ 12067 ; 12068 12069 _proto.setPoster = function setPoster() {} 12070 /** 12071 * A method to check for the presence of the 'playsinline' <video> attribute. 12072 * 12073 * @abstract 12074 */ 12075 ; 12076 12077 _proto.playsinline = function playsinline() {} 12078 /** 12079 * A method to set or unset the 'playsinline' <video> attribute. 12080 * 12081 * @abstract 12082 */ 12083 ; 12084 12085 _proto.setPlaysinline = function setPlaysinline() {} 12086 /** 12087 * Attempt to force override of native audio tracks. 12088 * 12089 * @param {boolean} override - If set to true native audio will be overridden, 12090 * otherwise native audio will potentially be used. 12091 * 12092 * @abstract 12093 */ 12094 ; 12095 12096 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks() {} 12097 /** 12098 * Attempt to force override of native video tracks. 12099 * 12100 * @param {boolean} override - If set to true native video will be overridden, 12101 * otherwise native video will potentially be used. 12102 * 12103 * @abstract 12104 */ 12105 ; 12106 12107 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks() {} 12108 /* 12109 * Check if the tech can support the given mime-type. 12110 * 12111 * The base tech does not support any type, but source handlers might 12112 * overwrite this. 12113 * 12114 * @param {string} type 12115 * The mimetype to check for support 12116 * 12117 * @return {string} 12118 * 'probably', 'maybe', or empty string 12119 * 12120 * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType} 12121 * 12122 * @abstract 12123 */ 12124 ; 12125 12126 _proto.canPlayType = function canPlayType() { 12127 return ''; 12128 } 12129 /** 12130 * Check if the type is supported by this tech. 12131 * 12132 * The base tech does not support any type, but source handlers might 12133 * overwrite this. 12134 * 12135 * @param {string} type 12136 * The media type to check 12137 * @return {string} Returns the native video element's response 12138 */ 12139 ; 12140 12141 Tech.canPlayType = function canPlayType() { 12142 return ''; 12143 } 12144 /** 12145 * Check if the tech can support the given source 12146 * 12147 * @param {Object} srcObj 12148 * The source object 12149 * @param {Object} options 12150 * The options passed to the tech 12151 * @return {string} 'probably', 'maybe', or '' (empty string) 12152 */ 12153 ; 12154 12155 Tech.canPlaySource = function canPlaySource(srcObj, options) { 12156 return Tech.canPlayType(srcObj.type); 12157 } 12158 /* 12159 * Return whether the argument is a Tech or not. 12160 * Can be passed either a Class like `Html5` or a instance like `player.tech_` 12161 * 12162 * @param {Object} component 12163 * The item to check 12164 * 12165 * @return {boolean} 12166 * Whether it is a tech or not 12167 * - True if it is a tech 12168 * - False if it is not 12169 */ 12170 ; 12171 12172 Tech.isTech = function isTech(component) { 12173 return component.prototype instanceof Tech || component instanceof Tech || component === Tech; 12174 } 12175 /** 12176 * Registers a `Tech` into a shared list for videojs. 12177 * 12178 * @param {string} name 12179 * Name of the `Tech` to register. 12180 * 12181 * @param {Object} tech 12182 * The `Tech` class to register. 12183 */ 12184 ; 12185 12186 Tech.registerTech = function registerTech(name, tech) { 12187 if (!Tech.techs_) { 12188 Tech.techs_ = {}; 12189 } 12190 12191 if (!Tech.isTech(tech)) { 12192 throw new Error("Tech " + name + " must be a Tech"); 12193 } 12194 12195 if (!Tech.canPlayType) { 12196 throw new Error('Techs must have a static canPlayType method on them'); 12197 } 12198 12199 if (!Tech.canPlaySource) { 12200 throw new Error('Techs must have a static canPlaySource method on them'); 12201 } 12202 12203 name = toTitleCase(name); 12204 Tech.techs_[name] = tech; 12205 Tech.techs_[toLowerCase(name)] = tech; 12206 12207 if (name !== 'Tech') { 12208 // camel case the techName for use in techOrder 12209 Tech.defaultTechOrder_.push(name); 12210 } 12211 12212 return tech; 12213 } 12214 /** 12215 * Get a `Tech` from the shared list by name. 12216 * 12217 * @param {string} name 12218 * `camelCase` or `TitleCase` name of the Tech to get 12219 * 12220 * @return {Tech|undefined} 12221 * The `Tech` or undefined if there was no tech with the name requested. 12222 */ 12223 ; 12224 12225 Tech.getTech = function getTech(name) { 12226 if (!name) { 12227 return; 12228 } 12229 12230 if (Tech.techs_ && Tech.techs_[name]) { 12231 return Tech.techs_[name]; 12232 } 12233 12234 name = toTitleCase(name); 12235 12236 if (window$1 && window$1.videojs && window$1.videojs[name]) { 12237 log.warn("The " + name + " tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)"); 12238 return window$1.videojs[name]; 12239 } 12240 }; 12241 12242 return Tech; 12243 }(Component); 12244 /** 12245 * Get the {@link VideoTrackList} 12246 * 12247 * @returns {VideoTrackList} 12248 * @method Tech.prototype.videoTracks 12249 */ 12250 12251 /** 12252 * Get the {@link AudioTrackList} 12253 * 12254 * @returns {AudioTrackList} 12255 * @method Tech.prototype.audioTracks 12256 */ 12257 12258 /** 12259 * Get the {@link TextTrackList} 12260 * 12261 * @returns {TextTrackList} 12262 * @method Tech.prototype.textTracks 12263 */ 12264 12265 /** 12266 * Get the remote element {@link TextTrackList} 12267 * 12268 * @returns {TextTrackList} 12269 * @method Tech.prototype.remoteTextTracks 12270 */ 12271 12272 /** 12273 * Get the remote element {@link HtmlTrackElementList} 12274 * 12275 * @returns {HtmlTrackElementList} 12276 * @method Tech.prototype.remoteTextTrackEls 12277 */ 12278 12279 12280 ALL.names.forEach(function (name) { 12281 var props = ALL[name]; 12282 12283 Tech.prototype[props.getterName] = function () { 12284 this[props.privateName] = this[props.privateName] || new props.ListClass(); 12285 return this[props.privateName]; 12286 }; 12287 }); 12288 /** 12289 * List of associated text tracks 12290 * 12291 * @type {TextTrackList} 12292 * @private 12293 * @property Tech#textTracks_ 12294 */ 12295 12296 /** 12297 * List of associated audio tracks. 12298 * 12299 * @type {AudioTrackList} 12300 * @private 12301 * @property Tech#audioTracks_ 12302 */ 12303 12304 /** 12305 * List of associated video tracks. 12306 * 12307 * @type {VideoTrackList} 12308 * @private 12309 * @property Tech#videoTracks_ 12310 */ 12311 12312 /** 12313 * Boolean indicating whether the `Tech` supports volume control. 12314 * 12315 * @type {boolean} 12316 * @default 12317 */ 12318 12319 Tech.prototype.featuresVolumeControl = true; 12320 /** 12321 * Boolean indicating whether the `Tech` supports muting volume. 12322 * 12323 * @type {bolean} 12324 * @default 12325 */ 12326 12327 Tech.prototype.featuresMuteControl = true; 12328 /** 12329 * Boolean indicating whether the `Tech` supports fullscreen resize control. 12330 * Resizing plugins using request fullscreen reloads the plugin 12331 * 12332 * @type {boolean} 12333 * @default 12334 */ 12335 12336 Tech.prototype.featuresFullscreenResize = false; 12337 /** 12338 * Boolean indicating whether the `Tech` supports changing the speed at which the video 12339 * plays. Examples: 12340 * - Set player to play 2x (twice) as fast 12341 * - Set player to play 0.5x (half) as fast 12342 * 12343 * @type {boolean} 12344 * @default 12345 */ 12346 12347 Tech.prototype.featuresPlaybackRate = false; 12348 /** 12349 * Boolean indicating whether the `Tech` supports the `progress` event. This is currently 12350 * not triggered by video-js-swf. This will be used to determine if 12351 * {@link Tech#manualProgressOn} should be called. 12352 * 12353 * @type {boolean} 12354 * @default 12355 */ 12356 12357 Tech.prototype.featuresProgressEvents = false; 12358 /** 12359 * Boolean indicating whether the `Tech` supports the `sourceset` event. 12360 * 12361 * A tech should set this to `true` and then use {@link Tech#triggerSourceset} 12362 * to trigger a {@link Tech#event:sourceset} at the earliest time after getting 12363 * a new source. 12364 * 12365 * @type {boolean} 12366 * @default 12367 */ 12368 12369 Tech.prototype.featuresSourceset = false; 12370 /** 12371 * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently 12372 * not triggered by video-js-swf. This will be used to determine if 12373 * {@link Tech#manualTimeUpdates} should be called. 12374 * 12375 * @type {boolean} 12376 * @default 12377 */ 12378 12379 Tech.prototype.featuresTimeupdateEvents = false; 12380 /** 12381 * Boolean indicating whether the `Tech` supports the native `TextTrack`s. 12382 * This will help us integrate with native `TextTrack`s if the browser supports them. 12383 * 12384 * @type {boolean} 12385 * @default 12386 */ 12387 12388 Tech.prototype.featuresNativeTextTracks = false; 12389 /** 12390 * A functional mixin for techs that want to use the Source Handler pattern. 12391 * Source handlers are scripts for handling specific formats. 12392 * The source handler pattern is used for adaptive formats (HLS, DASH) that 12393 * manually load video data and feed it into a Source Buffer (Media Source Extensions) 12394 * Example: `Tech.withSourceHandlers.call(MyTech);` 12395 * 12396 * @param {Tech} _Tech 12397 * The tech to add source handler functions to. 12398 * 12399 * @mixes Tech~SourceHandlerAdditions 12400 */ 12401 12402 Tech.withSourceHandlers = function (_Tech) { 12403 /** 12404 * Register a source handler 12405 * 12406 * @param {Function} handler 12407 * The source handler class 12408 * 12409 * @param {number} [index] 12410 * Register it at the following index 12411 */ 12412 _Tech.registerSourceHandler = function (handler, index) { 12413 var handlers = _Tech.sourceHandlers; 12414 12415 if (!handlers) { 12416 handlers = _Tech.sourceHandlers = []; 12417 } 12418 12419 if (index === undefined) { 12420 // add to the end of the list 12421 index = handlers.length; 12422 } 12423 12424 handlers.splice(index, 0, handler); 12425 }; 12426 /** 12427 * Check if the tech can support the given type. Also checks the 12428 * Techs sourceHandlers. 12429 * 12430 * @param {string} type 12431 * The mimetype to check. 12432 * 12433 * @return {string} 12434 * 'probably', 'maybe', or '' (empty string) 12435 */ 12436 12437 12438 _Tech.canPlayType = function (type) { 12439 var handlers = _Tech.sourceHandlers || []; 12440 var can; 12441 12442 for (var i = 0; i < handlers.length; i++) { 12443 can = handlers[i].canPlayType(type); 12444 12445 if (can) { 12446 return can; 12447 } 12448 } 12449 12450 return ''; 12451 }; 12452 /** 12453 * Returns the first source handler that supports the source. 12454 * 12455 * TODO: Answer question: should 'probably' be prioritized over 'maybe' 12456 * 12457 * @param {Tech~SourceObject} source 12458 * The source object 12459 * 12460 * @param {Object} options 12461 * The options passed to the tech 12462 * 12463 * @return {SourceHandler|null} 12464 * The first source handler that supports the source or null if 12465 * no SourceHandler supports the source 12466 */ 12467 12468 12469 _Tech.selectSourceHandler = function (source, options) { 12470 var handlers = _Tech.sourceHandlers || []; 12471 var can; 12472 12473 for (var i = 0; i < handlers.length; i++) { 12474 can = handlers[i].canHandleSource(source, options); 12475 12476 if (can) { 12477 return handlers[i]; 12478 } 12479 } 12480 12481 return null; 12482 }; 12483 /** 12484 * Check if the tech can support the given source. 12485 * 12486 * @param {Tech~SourceObject} srcObj 12487 * The source object 12488 * 12489 * @param {Object} options 12490 * The options passed to the tech 12491 * 12492 * @return {string} 12493 * 'probably', 'maybe', or '' (empty string) 12494 */ 12495 12496 12497 _Tech.canPlaySource = function (srcObj, options) { 12498 var sh = _Tech.selectSourceHandler(srcObj, options); 12499 12500 if (sh) { 12501 return sh.canHandleSource(srcObj, options); 12502 } 12503 12504 return ''; 12505 }; 12506 /** 12507 * When using a source handler, prefer its implementation of 12508 * any function normally provided by the tech. 12509 */ 12510 12511 12512 var deferrable = ['seekable', 'seeking', 'duration']; 12513 /** 12514 * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable 12515 * function if it exists, with a fallback to the Techs seekable function. 12516 * 12517 * @method _Tech.seekable 12518 */ 12519 12520 /** 12521 * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration 12522 * function if it exists, otherwise it will fallback to the techs duration function. 12523 * 12524 * @method _Tech.duration 12525 */ 12526 12527 deferrable.forEach(function (fnName) { 12528 var originalFn = this[fnName]; 12529 12530 if (typeof originalFn !== 'function') { 12531 return; 12532 } 12533 12534 this[fnName] = function () { 12535 if (this.sourceHandler_ && this.sourceHandler_[fnName]) { 12536 return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments); 12537 } 12538 12539 return originalFn.apply(this, arguments); 12540 }; 12541 }, _Tech.prototype); 12542 /** 12543 * Create a function for setting the source using a source object 12544 * and source handlers. 12545 * Should never be called unless a source handler was found. 12546 * 12547 * @param {Tech~SourceObject} source 12548 * A source object with src and type keys 12549 */ 12550 12551 _Tech.prototype.setSource = function (source) { 12552 var sh = _Tech.selectSourceHandler(source, this.options_); 12553 12554 if (!sh) { 12555 // Fall back to a native source hander when unsupported sources are 12556 // deliberately set 12557 if (_Tech.nativeSourceHandler) { 12558 sh = _Tech.nativeSourceHandler; 12559 } else { 12560 log.error('No source handler found for the current source.'); 12561 } 12562 } // Dispose any existing source handler 12563 12564 12565 this.disposeSourceHandler(); 12566 this.off('dispose', this.disposeSourceHandler); 12567 12568 if (sh !== _Tech.nativeSourceHandler) { 12569 this.currentSource_ = source; 12570 } 12571 12572 this.sourceHandler_ = sh.handleSource(source, this, this.options_); 12573 this.one('dispose', this.disposeSourceHandler); 12574 }; 12575 /** 12576 * Clean up any existing SourceHandlers and listeners when the Tech is disposed. 12577 * 12578 * @listens Tech#dispose 12579 */ 12580 12581 12582 _Tech.prototype.disposeSourceHandler = function () { 12583 // if we have a source and get another one 12584 // then we are loading something new 12585 // than clear all of our current tracks 12586 if (this.currentSource_) { 12587 this.clearTracks(['audio', 'video']); 12588 this.currentSource_ = null; 12589 } // always clean up auto-text tracks 12590 12591 12592 this.cleanupAutoTextTracks(); 12593 12594 if (this.sourceHandler_) { 12595 if (this.sourceHandler_.dispose) { 12596 this.sourceHandler_.dispose(); 12597 } 12598 12599 this.sourceHandler_ = null; 12600 } 12601 }; 12602 };
vendor: 4,491 bytes, lines 12602-12752
12602 // The base Tech class needs to be registered as a Component. It is the only 12603 // Tech that can be registered as a Component. 12604 12605 12606 Component.registerComponent('Tech', Tech); 12607 Tech.registerTech('Tech', Tech); 12608 /** 12609 * A list of techs that should be added to techOrder on Players 12610 * 12611 * @private 12612 */ 12613 12614 Tech.defaultTechOrder_ = []; 12615 12616 /** 12617 * @file middleware.js 12618 * @module middleware 12619 */ 12620 var middlewares = {}; 12621 var middlewareInstances = {}; 12622 var TERMINATOR = {}; 12623 /** 12624 * A middleware object is a plain JavaScript object that has methods that 12625 * match the {@link Tech} methods found in the lists of allowed 12626 * {@link module:middleware.allowedGetters|getters}, 12627 * {@link module:middleware.allowedSetters|setters}, and 12628 * {@link module:middleware.allowedMediators|mediators}. 12629 * 12630 * @typedef {Object} MiddlewareObject 12631 */ 12632 12633 /** 12634 * A middleware factory function that should return a 12635 * {@link module:middleware~MiddlewareObject|MiddlewareObject}. 12636 * 12637 * This factory will be called for each player when needed, with the player 12638 * passed in as an argument. 12639 * 12640 * @callback MiddlewareFactory 12641 * @param {Player} player 12642 * A Video.js player. 12643 */ 12644 12645 /** 12646 * Define a middleware that the player should use by way of a factory function 12647 * that returns a middleware object. 12648 * 12649 * @param {string} type 12650 * The MIME type to match or `"*"` for all MIME types. 12651 * 12652 * @param {MiddlewareFactory} middleware 12653 * A middleware factory function that will be executed for 12654 * matching types. 12655 */ 12656 12657 function use(type, middleware) { 12658 middlewares[type] = middlewares[type] || []; 12659 middlewares[type].push(middleware); 12660 } 12661 /** 12662 * Asynchronously sets a source using middleware by recursing through any 12663 * matching middlewares and calling `setSource` on each, passing along the 12664 * previous returned value each time. 12665 * 12666 * @param {Player} player 12667 * A {@link Player} instance. 12668 * 12669 * @param {Tech~SourceObject} src 12670 * A source object. 12671 * 12672 * @param {Function} 12673 * The next middleware to run. 12674 */ 12675 12676 function setSource(player, src, next) { 12677 player.setTimeout(function () { 12678 return setSourceHelper(src, middlewares[src.type], next, player); 12679 }, 1); 12680 } 12681 /** 12682 * When the tech is set, passes the tech to each middleware's `setTech` method. 12683 * 12684 * @param {Object[]} middleware 12685 * An array of middleware instances. 12686 * 12687 * @param {Tech} tech 12688 * A Video.js tech. 12689 */ 12690 12691 function setTech(middleware, tech) { 12692 middleware.forEach(function (mw) { 12693 return mw.setTech && mw.setTech(tech); 12694 }); 12695 } 12696 /** 12697 * Calls a getter on the tech first, through each middleware 12698 * from right to left to the player. 12699 * 12700 * @param {Object[]} middleware 12701 * An array of middleware instances. 12702 * 12703 * @param {Tech} tech 12704 * The current tech. 12705 * 12706 * @param {string} method 12707 * A method name. 12708 * 12709 * @return {Mixed} 12710 * The final value from the tech after middleware has intercepted it. 12711 */ 12712 12713 function get(middleware, tech, method) { 12714 return middleware.reduceRight(middlewareIterator(method), tech[method]()); 12715 } 12716 /** 12717 * Takes the argument given to the player and calls the setter method on each 12718 * middleware from left to right to the tech. 12719 * 12720 * @param {Object[]} middleware 12721 * An array of middleware instances. 12722 * 12723 * @param {Tech} tech 12724 * The current tech. 12725 * 12726 * @param {string} method 12727 * A method name. 12728 * 12729 * @param {Mixed} arg 12730 * The value to set on the tech. 12731 * 12732 * @return {Mixed} 12733 * The return value of the `method` of the `tech`. 12734 */ 12735 12736 function set(middleware, tech, method, arg) { 12737 return tech[method](middleware.reduce(middlewareIterator(method), arg)); 12738 } 12739 /** 12740 * Takes the argument given to the player and calls the `call` version of the 12741 * method on each middleware from left to right. 12742 * 12743 * Then, call the passed in method on the tech and return the result unchanged 12744 * back to the player, through middleware, this time from right to left. 12745 * 12746 * @param {Object[]} middleware 12747 * An array of middleware instances. 12748 * 12749 * @param {Tech} tech 12750 * The current tech. 12751 * 12752 * @param {string} method
vendor: 52,193 bytes, lines 12753-14541
12753 * A method name. 12754 * 12755 * @param {Mixed} arg 12756 * The value to set on the tech. 12757 * 12758 * @return {Mixed} 12759 * The return value of the `method` of the `tech`, regardless of the 12760 * return values of middlewares. 12761 */ 12762 12763 function mediate(middleware, tech, method, arg) { 12764 if (arg === void 0) { 12765 arg = null; 12766 } 12767 12768 var callMethod = 'call' + toTitleCase(method); 12769 var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg); 12770 var terminated = middlewareValue === TERMINATOR; // deprecated. The `null` return value should instead return TERMINATOR to 12771 // prevent confusion if a techs method actually returns null. 12772 12773 var returnValue = terminated ? null : tech[method](middlewareValue); 12774 executeRight(middleware, method, returnValue, terminated); 12775 return returnValue; 12776 } 12777 /** 12778 * Enumeration of allowed getters where the keys are method names. 12779 * 12780 * @type {Object} 12781 */ 12782 12783 var allowedGetters = { 12784 buffered: 1, 12785 currentTime: 1, 12786 duration: 1, 12787 seekable: 1, 12788 played: 1, 12789 paused: 1, 12790 volume: 1 12791 }; 12792 /** 12793 * Enumeration of allowed setters where the keys are method names. 12794 * 12795 * @type {Object} 12796 */ 12797 12798 var allowedSetters = { 12799 setCurrentTime: 1, 12800 setVolume: 1 12801 }; 12802 /** 12803 * Enumeration of allowed mediators where the keys are method names. 12804 * 12805 * @type {Object} 12806 */ 12807 12808 var allowedMediators = { 12809 play: 1, 12810 pause: 1 12811 }; 12812 12813 function middlewareIterator(method) { 12814 return function (value, mw) { 12815 // if the previous middleware terminated, pass along the termination 12816 if (value === TERMINATOR) { 12817 return TERMINATOR; 12818 } 12819 12820 if (mw[method]) { 12821 return mw[method](value); 12822 } 12823 12824 return value; 12825 }; 12826 } 12827 12828 function executeRight(mws, method, value, terminated) { 12829 for (var i = mws.length - 1; i >= 0; i--) { 12830 var mw = mws[i]; 12831 12832 if (mw[method]) { 12833 mw[method](terminated, value); 12834 } 12835 } 12836 } 12837 /** 12838 * Clear the middleware cache for a player. 12839 * 12840 * @param {Player} player 12841 * A {@link Player} instance. 12842 */ 12843 12844 12845 function clearCacheForPlayer(player) { 12846 middlewareInstances[player.id()] = null; 12847 } 12848 /** 12849 * { 12850 * [playerId]: [[mwFactory, mwInstance], ...] 12851 * } 12852 * 12853 * @private 12854 */ 12855 12856 function getOrCreateFactory(player, mwFactory) { 12857 var mws = middlewareInstances[player.id()]; 12858 var mw = null; 12859 12860 if (mws === undefined || mws === null) { 12861 mw = mwFactory(player); 12862 middlewareInstances[player.id()] = [[mwFactory, mw]]; 12863 return mw; 12864 } 12865 12866 for (var i = 0; i < mws.length; i++) { 12867 var _mws$i = mws[i], 12868 mwf = _mws$i[0], 12869 mwi = _mws$i[1]; 12870 12871 if (mwf !== mwFactory) { 12872 continue; 12873 } 12874 12875 mw = mwi; 12876 } 12877 12878 if (mw === null) { 12879 mw = mwFactory(player); 12880 mws.push([mwFactory, mw]); 12881 } 12882 12883 return mw; 12884 } 12885 12886 function setSourceHelper(src, middleware, next, player, acc, lastRun) { 12887 if (src === void 0) { 12888 src = {}; 12889 } 12890 12891 if (middleware === void 0) { 12892 middleware = []; 12893 } 12894 12895 if (acc === void 0) { 12896 acc = []; 12897 } 12898 12899 if (lastRun === void 0) { 12900 lastRun = false; 12901 } 12902 12903 var _middleware = middleware, 12904 mwFactory = _middleware[0], 12905 mwrest = _middleware.slice(1); // if mwFactory is a string, then we're at a fork in the road 12906 12907 12908 if (typeof mwFactory === 'string') { 12909 setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun); // if we have an mwFactory, call it with the player to get the mw, 12910 // then call the mw's setSource method 12911 } else if (mwFactory) { 12912 var mw = getOrCreateFactory(player, mwFactory); // if setSource isn't present, implicitly select this middleware 12913 12914 if (!mw.setSource) { 12915 acc.push(mw); 12916 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12917 } 12918 12919 mw.setSource(assign({}, src), function (err, _src) { 12920 // something happened, try the next middleware on the current level 12921 // make sure to use the old src 12922 if (err) { 12923 return setSourceHelper(src, mwrest, next, player, acc, lastRun); 12924 } // we've succeeded, now we need to go deeper 12925 12926 12927 acc.push(mw); // if it's the same type, continue down the current chain 12928 // otherwise, we want to go down the new chain 12929 12930 setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun); 12931 }); 12932 } else if (mwrest.length) { 12933 setSourceHelper(src, mwrest, next, player, acc, lastRun); 12934 } else if (lastRun) { 12935 next(src, acc); 12936 } else { 12937 setSourceHelper(src, middlewares['*'], next, player, acc, true); 12938 } 12939 } 12940 12941 /** 12942 * Mimetypes 12943 * 12944 * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm 12945 * @typedef Mimetypes~Kind 12946 * @enum 12947 */ 12948 12949 var MimetypesKind = { 12950 opus: 'video/ogg', 12951 ogv: 'video/ogg', 12952 mp4: 'video/mp4', 12953 mov: 'video/mp4', 12954 m4v: 'video/mp4', 12955 mkv: 'video/x-matroska', 12956 m4a: 'audio/mp4', 12957 mp3: 'audio/mpeg', 12958 aac: 'audio/aac', 12959 oga: 'audio/ogg', 12960 m3u8: 'application/x-mpegURL', 12961 jpg: 'image/jpeg', 12962 jpeg: 'image/jpeg', 12963 gif: 'image/gif', 12964 png: 'image/png', 12965 svg: 'image/svg+xml', 12966 webp: 'image/webp' 12967 }; 12968 /** 12969 * Get the mimetype of a given src url if possible 12970 * 12971 * @param {string} src 12972 * The url to the src 12973 * 12974 * @return {string} 12975 * return the mimetype if it was known or empty string otherwise 12976 */ 12977 12978 var getMimetype = function getMimetype(src) { 12979 if (src === void 0) { 12980 src = ''; 12981 } 12982 12983 var ext = getFileExtension(src); 12984 var mimetype = MimetypesKind[ext.toLowerCase()]; 12985 return mimetype || ''; 12986 }; 12987 /** 12988 * Find the mime type of a given source string if possible. Uses the player 12989 * source cache. 12990 * 12991 * @param {Player} player 12992 * The player object 12993 * 12994 * @param {string} src 12995 * The source string 12996 * 12997 * @return {string} 12998 * The type that was found 12999 */ 13000 13001 var findMimetype = function findMimetype(player, src) { 13002 if (!src) { 13003 return ''; 13004 } // 1. check for the type in the `source` cache 13005 13006 13007 if (player.cache_.source.src === src && player.cache_.source.type) { 13008 return player.cache_.source.type; 13009 } // 2. see if we have this source in our `currentSources` cache 13010 13011 13012 var matchingSources = player.cache_.sources.filter(function (s) { 13013 return s.src === src; 13014 }); 13015 13016 if (matchingSources.length) { 13017 return matchingSources[0].type; 13018 } // 3. look for the src url in source elements and use the type there 13019 13020 13021 var sources = player.$$('source'); 13022 13023 for (var i = 0; i < sources.length; i++) { 13024 var s = sources[i]; 13025 13026 if (s.type && s.src && s.src === src) { 13027 return s.type; 13028 } 13029 } // 4. finally fallback to our list of mime types based on src url extension 13030 13031 13032 return getMimetype(src); 13033 }; 13034 13035 /** 13036 * @module filter-source 13037 */ 13038 /** 13039 * Filter out single bad source objects or multiple source objects in an 13040 * array. Also flattens nested source object arrays into a 1 dimensional 13041 * array of source objects. 13042 * 13043 * @param {Tech~SourceObject|Tech~SourceObject[]} src 13044 * The src object to filter 13045 * 13046 * @return {Tech~SourceObject[]} 13047 * An array of sourceobjects containing only valid sources 13048 * 13049 * @private 13050 */ 13051 13052 var filterSource = function filterSource(src) { 13053 // traverse array 13054 if (Array.isArray(src)) { 13055 var newsrc = []; 13056 src.forEach(function (srcobj) { 13057 srcobj = filterSource(srcobj); 13058 13059 if (Array.isArray(srcobj)) { 13060 newsrc = newsrc.concat(srcobj); 13061 } else if (isObject(srcobj)) { 13062 newsrc.push(srcobj); 13063 } 13064 }); 13065 src = newsrc; 13066 } else if (typeof src === 'string' && src.trim()) { 13067 // convert string into object 13068 src = [fixSource({ 13069 src: src 13070 })]; 13071 } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) { 13072 // src is already valid 13073 src = [fixSource(src)]; 13074 } else { 13075 // invalid source, turn it into an empty array 13076 src = []; 13077 } 13078 13079 return src; 13080 }; 13081 /** 13082 * Checks src mimetype, adding it when possible 13083 * 13084 * @param {Tech~SourceObject} src 13085 * The src object to check 13086 * @return {Tech~SourceObject} 13087 * src Object with known type 13088 */ 13089 13090 13091 function fixSource(src) { 13092 if (!src.type) { 13093 var mimetype = getMimetype(src.src); 13094 13095 if (mimetype) { 13096 src.type = mimetype; 13097 } 13098 } 13099 13100 return src; 13101 } 13102 13103 /** 13104 * The `MediaLoader` is the `Component` that decides which playback technology to load 13105 * when a player is initialized. 13106 * 13107 * @extends Component 13108 */ 13109 13110 var MediaLoader = 13111 /*#__PURE__*/ 13112 function (_Component) { 13113 _inheritsLoose(MediaLoader, _Component); 13114 13115 /** 13116 * Create an instance of this class. 13117 * 13118 * @param {Player} player 13119 * The `Player` that this class should attach to. 13120 * 13121 * @param {Object} [options] 13122 * The key/value store of player options. 13123 * 13124 * @param {Component~ReadyCallback} [ready] 13125 * The function that is run when this component is ready. 13126 */ 13127 function MediaLoader(player, options, ready) { 13128 var _this; 13129 13130 // MediaLoader has no element 13131 var options_ = mergeOptions({ 13132 createEl: false 13133 }, options); 13134 _this = _Component.call(this, player, options_, ready) || this; // If there are no sources when the player is initialized, 13135 // load the first supported playback technology. 13136 13137 if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) { 13138 for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) { 13139 var techName = toTitleCase(j[i]); 13140 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 13141 // Remove once that deprecated behavior is removed. 13142 13143 if (!techName) { 13144 tech = Component.getComponent(techName); 13145 } // Check if the browser supports this technology 13146 13147 13148 if (tech && tech.isSupported()) { 13149 player.loadTech_(techName); 13150 break; 13151 } 13152 } 13153 } else { 13154 // Loop through playback technologies (HTML5, Flash) and check for support. 13155 // Then load the best source. 13156 // A few assumptions here: 13157 // All playback technologies respect preload false. 13158 player.src(options.playerOptions.sources); 13159 } 13160 13161 return _this; 13162 } 13163 13164 return MediaLoader; 13165 }(Component); 13166 13167 Component.registerComponent('MediaLoader', MediaLoader); 13168 13169 /** 13170 * Component which is clickable or keyboard actionable, but is not a 13171 * native HTML button. 13172 * 13173 * @extends Component 13174 */ 13175 13176 var ClickableComponent = 13177 /*#__PURE__*/ 13178 function (_Component) { 13179 _inheritsLoose(ClickableComponent, _Component); 13180 13181 /** 13182 * Creates an instance of this class. 13183 * 13184 * @param {Player} player 13185 * The `Player` that this class should be attached to. 13186 * 13187 * @param {Object} [options] 13188 * The key/value store of player options. 13189 */ 13190 function ClickableComponent(player, options) { 13191 var _this; 13192 13193 _this = _Component.call(this, player, options) || this; 13194 13195 _this.emitTapEvents(); 13196 13197 _this.enable(); 13198 13199 return _this; 13200 } 13201 /** 13202 * Create the `ClickableComponent`s DOM element. 13203 * 13204 * @param {string} [tag=div] 13205 * The element's node type. 13206 * 13207 * @param {Object} [props={}] 13208 * An object of properties that should be set on the element. 13209 * 13210 * @param {Object} [attributes={}] 13211 * An object of attributes that should be set on the element. 13212 * 13213 * @return {Element} 13214 * The element that gets created. 13215 */ 13216 13217 13218 var _proto = ClickableComponent.prototype; 13219 13220 _proto.createEl = function createEl(tag, props, attributes) { 13221 if (tag === void 0) { 13222 tag = 'div'; 13223 } 13224 13225 if (props === void 0) { 13226 props = {}; 13227 } 13228 13229 if (attributes === void 0) { 13230 attributes = {}; 13231 } 13232 13233 props = assign({ 13234 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 13235 className: this.buildCSSClass(), 13236 tabIndex: 0 13237 }, props); 13238 13239 if (tag === 'button') { 13240 log.error("Creating a ClickableComponent with an HTML element of " + tag + " is not supported; use a Button instead."); 13241 } // Add ARIA attributes for clickable element which is not a native HTML button 13242 13243 13244 attributes = assign({ 13245 role: 'button' 13246 }, attributes); 13247 this.tabIndex_ = props.tabIndex; 13248 13249 var el = _Component.prototype.createEl.call(this, tag, props, attributes); 13250 13251 this.createControlTextEl(el); 13252 return el; 13253 }; 13254 13255 _proto.dispose = function dispose() { 13256 // remove controlTextEl_ on dispose 13257 this.controlTextEl_ = null; 13258 13259 _Component.prototype.dispose.call(this); 13260 } 13261 /** 13262 * Create a control text element on this `ClickableComponent` 13263 * 13264 * @param {Element} [el] 13265 * Parent element for the control text. 13266 * 13267 * @return {Element} 13268 * The control text element that gets created. 13269 */ 13270 ; 13271 13272 _proto.createControlTextEl = function createControlTextEl(el) { 13273 this.controlTextEl_ = createEl('span', { 13274 className: 'vjs-control-text' 13275 }, { 13276 // let the screen reader user know that the text of the element may change 13277 'aria-live': 'polite' 13278 }); 13279 13280 if (el) { 13281 el.appendChild(this.controlTextEl_); 13282 } 13283 13284 this.controlText(this.controlText_, el); 13285 return this.controlTextEl_; 13286 } 13287 /** 13288 * Get or set the localize text to use for the controls on the `ClickableComponent`. 13289 * 13290 * @param {string} [text] 13291 * Control text for element. 13292 * 13293 * @param {Element} [el=this.el()] 13294 * Element to set the title on. 13295 * 13296 * @return {string} 13297 * - The control text when getting 13298 */ 13299 ; 13300 13301 _proto.controlText = function controlText(text, el) { 13302 if (el === void 0) { 13303 el = this.el(); 13304 } 13305 13306 if (text === undefined) { 13307 return this.controlText_ || 'Need Text'; 13308 } 13309 13310 var localizedText = this.localize(text); 13311 this.controlText_ = text; 13312 textContent(this.controlTextEl_, localizedText); 13313 13314 if (!this.nonIconControl) { 13315 // Set title attribute if only an icon is shown 13316 el.setAttribute('title', localizedText); 13317 } 13318 } 13319 /** 13320 * Builds the default DOM `className`. 13321 * 13322 * @return {string} 13323 * The DOM `className` for this object. 13324 */ 13325 ; 13326 13327 _proto.buildCSSClass = function buildCSSClass() { 13328 return "vjs-control vjs-button " + _Component.prototype.buildCSSClass.call(this); 13329 } 13330 /** 13331 * Enable this `ClickableComponent` 13332 */ 13333 ; 13334 13335 _proto.enable = function enable() { 13336 if (!this.enabled_) { 13337 this.enabled_ = true; 13338 this.removeClass('vjs-disabled'); 13339 this.el_.setAttribute('aria-disabled', 'false'); 13340 13341 if (typeof this.tabIndex_ !== 'undefined') { 13342 this.el_.setAttribute('tabIndex', this.tabIndex_); 13343 } 13344 13345 this.on(['tap', 'click'], this.handleClick); 13346 this.on('keydown', this.handleKeyDown); 13347 } 13348 } 13349 /** 13350 * Disable this `ClickableComponent` 13351 */ 13352 ; 13353 13354 _proto.disable = function disable() { 13355 this.enabled_ = false; 13356 this.addClass('vjs-disabled'); 13357 this.el_.setAttribute('aria-disabled', 'true'); 13358 13359 if (typeof this.tabIndex_ !== 'undefined') { 13360 this.el_.removeAttribute('tabIndex'); 13361 } 13362 13363 this.off('mouseover', this.handleMouseOver); 13364 this.off('mouseout', this.handleMouseOut); 13365 this.off(['tap', 'click'], this.handleClick); 13366 this.off('keydown', this.handleKeyDown); 13367 } 13368 /** 13369 * Event handler that is called when a `ClickableComponent` receives a 13370 * `click` or `tap` event. 13371 * 13372 * @param {EventTarget~Event} event 13373 * The `tap` or `click` event that caused this function to be called. 13374 * 13375 * @listens tap 13376 * @listens click 13377 * @abstract 13378 */ 13379 ; 13380 13381 _proto.handleClick = function handleClick(event) {} 13382 /** 13383 * Event handler that is called when a `ClickableComponent` receives a 13384 * `keydown` event. 13385 * 13386 * By default, if the key is Space or Enter, it will trigger a `click` event. 13387 * 13388 * @param {EventTarget~Event} event 13389 * The `keydown` event that caused this function to be called. 13390 * 13391 * @listens keydown 13392 */ 13393 ; 13394 13395 _proto.handleKeyDown = function handleKeyDown(event) { 13396 // Support Space or Enter key operation to fire a click event. Also, 13397 // prevent the event from propagating through the DOM and triggering 13398 // Player hotkeys. 13399 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 13400 event.preventDefault(); 13401 event.stopPropagation(); 13402 this.trigger('click'); 13403 } else { 13404 // Pass keypress handling up for unsupported keys 13405 _Component.prototype.handleKeyDown.call(this, event); 13406 } 13407 }; 13408 13409 return ClickableComponent; 13410 }(Component); 13411 13412 Component.registerComponent('ClickableComponent', ClickableComponent); 13413 13414 /** 13415 * A `ClickableComponent` that handles showing the poster image for the player. 13416 * 13417 * @extends ClickableComponent 13418 */ 13419 13420 var PosterImage = 13421 /*#__PURE__*/ 13422 function (_ClickableComponent) { 13423 _inheritsLoose(PosterImage, _ClickableComponent); 13424 13425 /** 13426 * Create an instance of this class. 13427 * 13428 * @param {Player} player 13429 * The `Player` that this class should attach to. 13430 * 13431 * @param {Object} [options] 13432 * The key/value store of player options. 13433 */ 13434 function PosterImage(player, options) { 13435 var _this; 13436 13437 _this = _ClickableComponent.call(this, player, options) || this; 13438 13439 _this.update(); 13440 13441 player.on('posterchange', bind(_assertThisInitialized(_this), _this.update)); 13442 return _this; 13443 } 13444 /** 13445 * Clean up and dispose of the `PosterImage`. 13446 */ 13447 13448 13449 var _proto = PosterImage.prototype; 13450 13451 _proto.dispose = function dispose() { 13452 this.player().off('posterchange', this.update); 13453 13454 _ClickableComponent.prototype.dispose.call(this); 13455 } 13456 /** 13457 * Create the `PosterImage`s DOM element. 13458 * 13459 * @return {Element} 13460 * The element that gets created. 13461 */ 13462 ; 13463 13464 _proto.createEl = function createEl$1() { 13465 var el = createEl('div', { 13466 className: 'vjs-poster', 13467 // Don't want poster to be tabbable. 13468 tabIndex: -1 13469 }); 13470 return el; 13471 } 13472 /** 13473 * An {@link EventTarget~EventListener} for {@link Player#posterchange} events. 13474 * 13475 * @listens Player#posterchange 13476 * 13477 * @param {EventTarget~Event} [event] 13478 * The `Player#posterchange` event that triggered this function. 13479 */ 13480 ; 13481 13482 _proto.update = function update(event) { 13483 var url = this.player().poster(); 13484 this.setSrc(url); // If there's no poster source we should display:none on this component 13485 // so it's not still clickable or right-clickable 13486 13487 if (url) { 13488 this.show(); 13489 } else { 13490 this.hide(); 13491 } 13492 } 13493 /** 13494 * Set the source of the `PosterImage` depending on the display method. 13495 * 13496 * @param {string} url 13497 * The URL to the source for the `PosterImage`. 13498 */ 13499 ; 13500 13501 _proto.setSrc = function setSrc(url) { 13502 var backgroundImage = ''; // Any falsy value should stay as an empty string, otherwise 13503 // this will throw an extra error 13504 13505 if (url) { 13506 backgroundImage = "url(\"" + url + "\")"; 13507 } 13508 13509 this.el_.style.backgroundImage = backgroundImage; 13510 } 13511 /** 13512 * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See 13513 * {@link ClickableComponent#handleClick} for instances where this will be triggered. 13514 * 13515 * @listens tap 13516 * @listens click 13517 * @listens keydown 13518 * 13519 * @param {EventTarget~Event} event 13520 + The `click`, `tap` or `keydown` event that caused this function to be called. 13521 */ 13522 ; 13523 13524 _proto.handleClick = function handleClick(event) { 13525 // We don't want a click to trigger playback when controls are disabled 13526 if (!this.player_.controls()) { 13527 return; 13528 } 13529 13530 if (this.player_.tech(true)) { 13531 this.player_.tech(true).focus(); 13532 } 13533 13534 if (this.player_.paused()) { 13535 silencePromise(this.player_.play()); 13536 } else { 13537 this.player_.pause(); 13538 } 13539 }; 13540 13541 return PosterImage; 13542 }(ClickableComponent); 13543 13544 Component.registerComponent('PosterImage', PosterImage); 13545 13546 var darkGray = '#222'; 13547 var lightGray = '#ccc'; 13548 var fontMap = { 13549 monospace: 'monospace', 13550 sansSerif: 'sans-serif', 13551 serif: 'serif', 13552 monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace', 13553 monospaceSerif: '"Courier New", monospace', 13554 proportionalSansSerif: 'sans-serif', 13555 proportionalSerif: 'serif', 13556 casual: '"Comic Sans MS", Impact, fantasy', 13557 script: '"Monotype Corsiva", cursive', 13558 smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif' 13559 }; 13560 /** 13561 * Construct an rgba color from a given hex color code. 13562 * 13563 * @param {number} color 13564 * Hex number for color, like #f0e or #f604e2. 13565 * 13566 * @param {number} opacity 13567 * Value for opacity, 0.0 - 1.0. 13568 * 13569 * @return {string} 13570 * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'. 13571 */ 13572 13573 function constructColor(color, opacity) { 13574 var hex; 13575 13576 if (color.length === 4) { 13577 // color looks like "#f0e" 13578 hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3]; 13579 } else if (color.length === 7) { 13580 // color looks like "#f604e2" 13581 hex = color.slice(1); 13582 } else { 13583 throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.'); 13584 } 13585 13586 return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')'; 13587 } 13588 /** 13589 * Try to update the style of a DOM element. Some style changes will throw an error, 13590 * particularly in IE8. Those should be noops. 13591 * 13592 * @param {Element} el 13593 * The DOM element to be styled. 13594 * 13595 * @param {string} style 13596 * The CSS property on the element that should be styled. 13597 * 13598 * @param {string} rule 13599 * The style rule that should be applied to the property. 13600 * 13601 * @private 13602 */ 13603 13604 function tryUpdateStyle(el, style, rule) { 13605 try { 13606 el.style[style] = rule; 13607 } catch (e) { 13608 // Satisfies linter. 13609 return; 13610 } 13611 } 13612 /** 13613 * The component for displaying text track cues. 13614 * 13615 * @extends Component 13616 */ 13617 13618 13619 var TextTrackDisplay = 13620 /*#__PURE__*/ 13621 function (_Component) { 13622 _inheritsLoose(TextTrackDisplay, _Component); 13623 13624 /** 13625 * Creates an instance of this class. 13626 * 13627 * @param {Player} player 13628 * The `Player` that this class should be attached to. 13629 * 13630 * @param {Object} [options] 13631 * The key/value store of player options. 13632 * 13633 * @param {Component~ReadyCallback} [ready] 13634 * The function to call when `TextTrackDisplay` is ready. 13635 */ 13636 function TextTrackDisplay(player, options, ready) { 13637 var _this; 13638 13639 _this = _Component.call(this, player, options, ready) || this; 13640 var updateDisplayHandler = bind(_assertThisInitialized(_this), _this.updateDisplay); 13641 player.on('loadstart', bind(_assertThisInitialized(_this), _this.toggleDisplay)); 13642 player.on('texttrackchange', updateDisplayHandler); 13643 player.on('loadedmetadata', bind(_assertThisInitialized(_this), _this.preselectTrack)); // This used to be called during player init, but was causing an error 13644 // if a track should show by default and the display hadn't loaded yet. 13645 // Should probably be moved to an external track loader when we support 13646 // tracks that don't need a display. 13647 13648 player.ready(bind(_assertThisInitialized(_this), function () { 13649 if (player.tech_ && player.tech_.featuresNativeTextTracks) { 13650 this.hide(); 13651 return; 13652 } 13653 13654 player.on('fullscreenchange', updateDisplayHandler); 13655 player.on('playerresize', updateDisplayHandler); 13656 window$1.addEventListener('orientationchange', updateDisplayHandler); 13657 player.on('dispose', function () { 13658 return window$1.removeEventListener('orientationchange', updateDisplayHandler); 13659 }); 13660 var tracks = this.options_.playerOptions.tracks || []; 13661 13662 for (var i = 0; i < tracks.length; i++) { 13663 this.player_.addRemoteTextTrack(tracks[i], true); 13664 } 13665 13666 this.preselectTrack(); 13667 })); 13668 return _this; 13669 } 13670 /** 13671 * Preselect a track following this precedence: 13672 * - matches the previously selected {@link TextTrack}'s language and kind 13673 * - matches the previously selected {@link TextTrack}'s language only 13674 * - is the first default captions track 13675 * - is the first default descriptions track 13676 * 13677 * @listens Player#loadstart 13678 */ 13679 13680 13681 var _proto = TextTrackDisplay.prototype; 13682 13683 _proto.preselectTrack = function preselectTrack() { 13684 var modes = { 13685 captions: 1, 13686 subtitles: 1 13687 }; 13688 var trackList = this.player_.textTracks(); 13689 var userPref = this.player_.cache_.selectedLanguage; 13690 var firstDesc; 13691 var firstCaptions; 13692 var preferredTrack; 13693 13694 for (var i = 0; i < trackList.length; i++) { 13695 var track = trackList[i]; 13696 13697 if (userPref && userPref.enabled && userPref.language && userPref.language === track.language && track.kind in modes) { 13698 // Always choose the track that matches both language and kind 13699 if (track.kind === userPref.kind) { 13700 preferredTrack = track; // or choose the first track that matches language 13701 } else if (!preferredTrack) { 13702 preferredTrack = track; 13703 } // clear everything if offTextTrackMenuItem was clicked 13704 13705 } else if (userPref && !userPref.enabled) { 13706 preferredTrack = null; 13707 firstDesc = null; 13708 firstCaptions = null; 13709 } else if (track["default"]) { 13710 if (track.kind === 'descriptions' && !firstDesc) { 13711 firstDesc = track; 13712 } else if (track.kind in modes && !firstCaptions) { 13713 firstCaptions = track; 13714 } 13715 } 13716 } // The preferredTrack matches the user preference and takes 13717 // precedence over all the other tracks. 13718 // So, display the preferredTrack before the first default track 13719 // and the subtitles/captions track before the descriptions track 13720 13721 13722 if (preferredTrack) { 13723 preferredTrack.mode = 'showing'; 13724 } else if (firstCaptions) { 13725 firstCaptions.mode = 'showing'; 13726 } else if (firstDesc) { 13727 firstDesc.mode = 'showing'; 13728 } 13729 } 13730 /** 13731 * Turn display of {@link TextTrack}'s from the current state into the other state. 13732 * There are only two states: 13733 * - 'shown' 13734 * - 'hidden' 13735 * 13736 * @listens Player#loadstart 13737 */ 13738 ; 13739 13740 _proto.toggleDisplay = function toggleDisplay() { 13741 if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) { 13742 this.hide(); 13743 } else { 13744 this.show(); 13745 } 13746 } 13747 /** 13748 * Create the {@link Component}'s DOM element. 13749 * 13750 * @return {Element} 13751 * The element that was created. 13752 */ 13753 ; 13754 13755 _proto.createEl = function createEl() { 13756 return _Component.prototype.createEl.call(this, 'div', { 13757 className: 'vjs-text-track-display' 13758 }, { 13759 'aria-live': 'off', 13760 'aria-atomic': 'true' 13761 }); 13762 } 13763 /** 13764 * Clear all displayed {@link TextTrack}s. 13765 */ 13766 ; 13767 13768 _proto.clearDisplay = function clearDisplay() { 13769 if (typeof window$1.WebVTT === 'function') { 13770 window$1.WebVTT.processCues(window$1, [], this.el_); 13771 } 13772 } 13773 /** 13774 * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or 13775 * a {@link Player#fullscreenchange} is fired. 13776 * 13777 * @listens Player#texttrackchange 13778 * @listens Player#fullscreenchange 13779 */ 13780 ; 13781 13782 _proto.updateDisplay = function updateDisplay() { 13783 var tracks = this.player_.textTracks(); 13784 var allowMultipleShowingTracks = this.options_.allowMultipleShowingTracks; 13785 this.clearDisplay(); 13786 13787 if (allowMultipleShowingTracks) { 13788 var showingTracks = []; 13789 13790 for (var _i = 0; _i < tracks.length; ++_i) { 13791 var track = tracks[_i]; 13792 13793 if (track.mode !== 'showing') { 13794 continue; 13795 } 13796 13797 showingTracks.push(track); 13798 } 13799 13800 this.updateForTrack(showingTracks); 13801 return; 13802 } // Track display prioritization model: if multiple tracks are 'showing', 13803 // display the first 'subtitles' or 'captions' track which is 'showing', 13804 // otherwise display the first 'descriptions' track which is 'showing' 13805 13806 13807 var descriptionsTrack = null; 13808 var captionsSubtitlesTrack = null; 13809 var i = tracks.length; 13810 13811 while (i--) { 13812 var _track = tracks[i]; 13813 13814 if (_track.mode === 'showing') { 13815 if (_track.kind === 'descriptions') { 13816 descriptionsTrack = _track; 13817 } else { 13818 captionsSubtitlesTrack = _track; 13819 } 13820 } 13821 } 13822 13823 if (captionsSubtitlesTrack) { 13824 if (this.getAttribute('aria-live') !== 'off') { 13825 this.setAttribute('aria-live', 'off'); 13826 } 13827 13828 this.updateForTrack(captionsSubtitlesTrack); 13829 } else if (descriptionsTrack) { 13830 if (this.getAttribute('aria-live') !== 'assertive') { 13831 this.setAttribute('aria-live', 'assertive'); 13832 } 13833 13834 this.updateForTrack(descriptionsTrack); 13835 } 13836 } 13837 /** 13838 * Style {@Link TextTrack} activeCues according to {@Link TextTrackSettings}. 13839 * 13840 * @param {TextTrack} track 13841 * Text track object containing active cues to style. 13842 */ 13843 ; 13844 13845 _proto.updateDisplayState = function updateDisplayState(track) { 13846 var overrides = this.player_.textTrackSettings.getValues(); 13847 var cues = track.activeCues; 13848 var i = cues.length; 13849 13850 while (i--) { 13851 var cue = cues[i]; 13852 13853 if (!cue) { 13854 continue; 13855 } 13856 13857 var cueDiv = cue.displayState; 13858 13859 if (overrides.color) { 13860 cueDiv.firstChild.style.color = overrides.color; 13861 } 13862 13863 if (overrides.textOpacity) { 13864 tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity)); 13865 } 13866 13867 if (overrides.backgroundColor) { 13868 cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor; 13869 } 13870 13871 if (overrides.backgroundOpacity) { 13872 tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity)); 13873 } 13874 13875 if (overrides.windowColor) { 13876 if (overrides.windowOpacity) { 13877 tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity)); 13878 } else { 13879 cueDiv.style.backgroundColor = overrides.windowColor; 13880 } 13881 } 13882 13883 if (overrides.edgeStyle) { 13884 if (overrides.edgeStyle === 'dropshadow') { 13885 cueDiv.firstChild.style.textShadow = "2px 2px 3px " + darkGray + ", 2px 2px 4px " + darkGray + ", 2px 2px 5px " + darkGray; 13886 } else if (overrides.edgeStyle === 'raised') { 13887 cueDiv.firstChild.style.textShadow = "1px 1px " + darkGray + ", 2px 2px " + darkGray + ", 3px 3px " + darkGray; 13888 } else if (overrides.edgeStyle === 'depressed') { 13889 cueDiv.firstChild.style.textShadow = "1px 1px " + lightGray + ", 0 1px " + lightGray + ", -1px -1px " + darkGray + ", 0 -1px " + darkGray; 13890 } else if (overrides.edgeStyle === 'uniform') { 13891 cueDiv.firstChild.style.textShadow = "0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray + ", 0 0 4px " + darkGray; 13892 } 13893 } 13894 13895 if (overrides.fontPercent && overrides.fontPercent !== 1) { 13896 var fontSize = window$1.parseFloat(cueDiv.style.fontSize); 13897 cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px'; 13898 cueDiv.style.height = 'auto'; 13899 cueDiv.style.top = 'auto'; 13900 cueDiv.style.bottom = '2px'; 13901 } 13902 13903 if (overrides.fontFamily && overrides.fontFamily !== 'default') { 13904 if (overrides.fontFamily === 'small-caps') { 13905 cueDiv.firstChild.style.fontVariant = 'small-caps'; 13906 } else { 13907 cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily]; 13908 } 13909 } 13910 } 13911 } 13912 /** 13913 * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}. 13914 * 13915 * @param {TextTrack|TextTrack[]} tracks 13916 * Text track object or text track array to be added to the list. 13917 */ 13918 ; 13919 13920 _proto.updateForTrack = function updateForTrack(tracks) { 13921 if (!Array.isArray(tracks)) { 13922 tracks = [tracks]; 13923 } 13924 13925 if (typeof window$1.WebVTT !== 'function' || tracks.every(function (track) { 13926 return !track.activeCues; 13927 })) { 13928 return; 13929 } 13930 13931 var cues = []; // push all active track cues 13932 13933 for (var i = 0; i < tracks.length; ++i) { 13934 var track = tracks[i]; 13935 13936 for (var j = 0; j < track.activeCues.length; ++j) { 13937 cues.push(track.activeCues[j]); 13938 } 13939 } // removes all cues before it processes new ones 13940 13941 13942 window$1.WebVTT.processCues(window$1, cues, this.el_); // add unique class to each language text track & add settings styling if necessary 13943 13944 for (var _i2 = 0; _i2 < tracks.length; ++_i2) { 13945 var _track2 = tracks[_i2]; 13946 13947 for (var _j = 0; _j < _track2.activeCues.length; ++_j) { 13948 var cueEl = _track2.activeCues[_j].displayState; 13949 addClass(cueEl, 'vjs-text-track-cue'); 13950 addClass(cueEl, 'vjs-text-track-cue-' + (_track2.language ? _track2.language : _i2)); 13951 } 13952 13953 if (this.player_.textTrackSettings) { 13954 this.updateDisplayState(_track2); 13955 } 13956 } 13957 }; 13958 13959 return TextTrackDisplay; 13960 }(Component); 13961 13962 Component.registerComponent('TextTrackDisplay', TextTrackDisplay); 13963 13964 /** 13965 * A loading spinner for use during waiting/loading events. 13966 * 13967 * @extends Component 13968 */ 13969 13970 var LoadingSpinner = 13971 /*#__PURE__*/ 13972 function (_Component) { 13973 _inheritsLoose(LoadingSpinner, _Component); 13974 13975 function LoadingSpinner() { 13976 return _Component.apply(this, arguments) || this; 13977 } 13978 13979 var _proto = LoadingSpinner.prototype; 13980 13981 /** 13982 * Create the `LoadingSpinner`s DOM element. 13983 * 13984 * @return {Element} 13985 * The dom element that gets created. 13986 */ 13987 _proto.createEl = function createEl$1() { 13988 var isAudio = this.player_.isAudio(); 13989 var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player'); 13990 var controlText = createEl('span', { 13991 className: 'vjs-control-text', 13992 innerHTML: this.localize('{1} is loading.', [playerType]) 13993 }); 13994 13995 var el = _Component.prototype.createEl.call(this, 'div', { 13996 className: 'vjs-loading-spinner', 13997 dir: 'ltr' 13998 }); 13999 14000 el.appendChild(controlText); 14001 return el; 14002 }; 14003 14004 return LoadingSpinner; 14005 }(Component); 14006 14007 Component.registerComponent('LoadingSpinner', LoadingSpinner); 14008 14009 /** 14010 * Base class for all buttons. 14011 * 14012 * @extends ClickableComponent 14013 */ 14014 14015 var Button = 14016 /*#__PURE__*/ 14017 function (_ClickableComponent) { 14018 _inheritsLoose(Button, _ClickableComponent); 14019 14020 function Button() { 14021 return _ClickableComponent.apply(this, arguments) || this; 14022 } 14023 14024 var _proto = Button.prototype; 14025 14026 /** 14027 * Create the `Button`s DOM element. 14028 * 14029 * @param {string} [tag="button"] 14030 * The element's node type. This argument is IGNORED: no matter what 14031 * is passed, it will always create a `button` element. 14032 * 14033 * @param {Object} [props={}] 14034 * An object of properties that should be set on the element. 14035 * 14036 * @param {Object} [attributes={}] 14037 * An object of attributes that should be set on the element. 14038 * 14039 * @return {Element} 14040 * The element that gets created. 14041 */ 14042 _proto.createEl = function createEl(tag, props, attributes) { 14043 if (props === void 0) { 14044 props = {}; 14045 } 14046 14047 if (attributes === void 0) { 14048 attributes = {}; 14049 } 14050 14051 tag = 'button'; 14052 props = assign({ 14053 innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>', 14054 className: this.buildCSSClass() 14055 }, props); // Add attributes for button element 14056 14057 attributes = assign({ 14058 // Necessary since the default button type is "submit" 14059 type: 'button' 14060 }, attributes); 14061 var el = Component.prototype.createEl.call(this, tag, props, attributes); 14062 this.createControlTextEl(el); 14063 return el; 14064 } 14065 /** 14066 * Add a child `Component` inside of this `Button`. 14067 * 14068 * @param {string|Component} child 14069 * The name or instance of a child to add. 14070 * 14071 * @param {Object} [options={}] 14072 * The key/value store of options that will get passed to children of 14073 * the child. 14074 * 14075 * @return {Component} 14076 * The `Component` that gets added as a child. When using a string the 14077 * `Component` will get created by this process. 14078 * 14079 * @deprecated since version 5 14080 */ 14081 ; 14082 14083 _proto.addChild = function addChild(child, options) { 14084 if (options === void 0) { 14085 options = {}; 14086 } 14087 14088 var className = this.constructor.name; 14089 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 14090 14091 return Component.prototype.addChild.call(this, child, options); 14092 } 14093 /** 14094 * Enable the `Button` element so that it can be activated or clicked. Use this with 14095 * {@link Button#disable}. 14096 */ 14097 ; 14098 14099 _proto.enable = function enable() { 14100 _ClickableComponent.prototype.enable.call(this); 14101 14102 this.el_.removeAttribute('disabled'); 14103 } 14104 /** 14105 * Disable the `Button` element so that it cannot be activated or clicked. Use this with 14106 * {@link Button#enable}. 14107 */ 14108 ; 14109 14110 _proto.disable = function disable() { 14111 _ClickableComponent.prototype.disable.call(this); 14112 14113 this.el_.setAttribute('disabled', 'disabled'); 14114 } 14115 /** 14116 * This gets called when a `Button` has focus and `keydown` is triggered via a key 14117 * press. 14118 * 14119 * @param {EventTarget~Event} event 14120 * The event that caused this function to get called. 14121 * 14122 * @listens keydown 14123 */ 14124 ; 14125 14126 _proto.handleKeyDown = function handleKeyDown(event) { 14127 // Ignore Space or Enter key operation, which is handled by the browser for 14128 // a button - though not for its super class, ClickableComponent. Also, 14129 // prevent the event from propagating through the DOM and triggering Player 14130 // hotkeys. We do not preventDefault here because we _want_ the browser to 14131 // handle it. 14132 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 14133 event.stopPropagation(); 14134 return; 14135 } // Pass keypress handling up for unsupported keys 14136 14137 14138 _ClickableComponent.prototype.handleKeyDown.call(this, event); 14139 }; 14140 14141 return Button; 14142 }(ClickableComponent); 14143 14144 Component.registerComponent('Button', Button); 14145 14146 /** 14147 * The initial play button that shows before the video has played. The hiding of the 14148 * `BigPlayButton` get done via CSS and `Player` states. 14149 * 14150 * @extends Button 14151 */ 14152 14153 var BigPlayButton = 14154 /*#__PURE__*/ 14155 function (_Button) { 14156 _inheritsLoose(BigPlayButton, _Button); 14157 14158 function BigPlayButton(player, options) { 14159 var _this; 14160 14161 _this = _Button.call(this, player, options) || this; 14162 _this.mouseused_ = false; 14163 14164 _this.on('mousedown', _this.handleMouseDown); 14165 14166 return _this; 14167 } 14168 /** 14169 * Builds the default DOM `className`. 14170 * 14171 * @return {string} 14172 * The DOM `className` for this object. Always returns 'vjs-big-play-button'. 14173 */ 14174 14175 14176 var _proto = BigPlayButton.prototype; 14177 14178 _proto.buildCSSClass = function buildCSSClass() { 14179 return 'vjs-big-play-button'; 14180 } 14181 /** 14182 * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent} 14183 * for more detailed information on what a click can be. 14184 * 14185 * @param {EventTarget~Event} event 14186 * The `keydown`, `tap`, or `click` event that caused this function to be 14187 * called. 14188 * 14189 * @listens tap 14190 * @listens click 14191 */ 14192 ; 14193 14194 _proto.handleClick = function handleClick(event) { 14195 var playPromise = this.player_.play(); // exit early if clicked via the mouse 14196 14197 if (this.mouseused_ && event.clientX && event.clientY) { 14198 silencePromise(playPromise); 14199 14200 if (this.player_.tech(true)) { 14201 this.player_.tech(true).focus(); 14202 } 14203 14204 return; 14205 } 14206 14207 var cb = this.player_.getChild('controlBar'); 14208 var playToggle = cb && cb.getChild('playToggle'); 14209 14210 if (!playToggle) { 14211 this.player_.tech(true).focus(); 14212 return; 14213 } 14214 14215 var playFocus = function playFocus() { 14216 return playToggle.focus(); 14217 }; 14218 14219 if (isPromise(playPromise)) { 14220 playPromise.then(playFocus, function () {}); 14221 } else { 14222 this.setTimeout(playFocus, 1); 14223 } 14224 }; 14225 14226 _proto.handleKeyDown = function handleKeyDown(event) { 14227 this.mouseused_ = false; 14228 14229 _Button.prototype.handleKeyDown.call(this, event); 14230 }; 14231 14232 _proto.handleMouseDown = function handleMouseDown(event) { 14233 this.mouseused_ = true; 14234 }; 14235 14236 return BigPlayButton; 14237 }(Button); 14238 /** 14239 * The text that should display over the `BigPlayButton`s controls. Added to for localization. 14240 * 14241 * @type {string} 14242 * @private 14243 */ 14244 14245 14246 BigPlayButton.prototype.controlText_ = 'Play Video'; 14247 Component.registerComponent('BigPlayButton', BigPlayButton); 14248 14249 /** 14250 * The `CloseButton` is a `{@link Button}` that fires a `close` event when 14251 * it gets clicked. 14252 * 14253 * @extends Button 14254 */ 14255 14256 var CloseButton = 14257 /*#__PURE__*/ 14258 function (_Button) { 14259 _inheritsLoose(CloseButton, _Button); 14260 14261 /** 14262 * Creates an instance of the this class. 14263 * 14264 * @param {Player} player 14265 * The `Player` that this class should be attached to. 14266 * 14267 * @param {Object} [options] 14268 * The key/value store of player options. 14269 */ 14270 function CloseButton(player, options) { 14271 var _this; 14272 14273 _this = _Button.call(this, player, options) || this; 14274 14275 _this.controlText(options && options.controlText || _this.localize('Close')); 14276 14277 return _this; 14278 } 14279 /** 14280 * Builds the default DOM `className`. 14281 * 14282 * @return {string} 14283 * The DOM `className` for this object. 14284 */ 14285 14286 14287 var _proto = CloseButton.prototype; 14288 14289 _proto.buildCSSClass = function buildCSSClass() { 14290 return "vjs-close-button " + _Button.prototype.buildCSSClass.call(this); 14291 } 14292 /** 14293 * This gets called when a `CloseButton` gets clicked. See 14294 * {@link ClickableComponent#handleClick} for more information on when 14295 * this will be triggered 14296 * 14297 * @param {EventTarget~Event} event 14298 * The `keydown`, `tap`, or `click` event that caused this function to be 14299 * called. 14300 * 14301 * @listens tap 14302 * @listens click 14303 * @fires CloseButton#close 14304 */ 14305 ; 14306 14307 _proto.handleClick = function handleClick(event) { 14308 /** 14309 * Triggered when the a `CloseButton` is clicked. 14310 * 14311 * @event CloseButton#close 14312 * @type {EventTarget~Event} 14313 * 14314 * @property {boolean} [bubbles=false] 14315 * set to false so that the close event does not 14316 * bubble up to parents if there is no listener 14317 */ 14318 this.trigger({ 14319 type: 'close', 14320 bubbles: false 14321 }); 14322 } 14323 /** 14324 * Event handler that is called when a `CloseButton` receives a 14325 * `keydown` event. 14326 * 14327 * By default, if the key is Esc, it will trigger a `click` event. 14328 * 14329 * @param {EventTarget~Event} event 14330 * The `keydown` event that caused this function to be called. 14331 * 14332 * @listens keydown 14333 */ 14334 ; 14335 14336 _proto.handleKeyDown = function handleKeyDown(event) { 14337 // Esc button will trigger `click` event 14338 if (keycode.isEventKey(event, 'Esc')) { 14339 event.preventDefault(); 14340 event.stopPropagation(); 14341 this.trigger('click'); 14342 } else { 14343 // Pass keypress handling up for unsupported keys 14344 _Button.prototype.handleKeyDown.call(this, event); 14345 } 14346 }; 14347 14348 return CloseButton; 14349 }(Button); 14350 14351 Component.registerComponent('CloseButton', CloseButton); 14352 14353 /** 14354 * Button to toggle between play and pause. 14355 * 14356 * @extends Button 14357 */ 14358 14359 var PlayToggle = 14360 /*#__PURE__*/ 14361 function (_Button) { 14362 _inheritsLoose(PlayToggle, _Button); 14363 14364 /** 14365 * Creates an instance of this class. 14366 * 14367 * @param {Player} player 14368 * The `Player` that this class should be attached to. 14369 * 14370 * @param {Object} [options={}] 14371 * The key/value store of player options. 14372 */ 14373 function PlayToggle(player, options) { 14374 var _this; 14375 14376 if (options === void 0) { 14377 options = {}; 14378 } 14379 14380 _this = _Button.call(this, player, options) || this; // show or hide replay icon 14381 14382 options.replay = options.replay === undefined || options.replay; 14383 14384 _this.on(player, 'play', _this.handlePlay); 14385 14386 _this.on(player, 'pause', _this.handlePause); 14387 14388 if (options.replay) { 14389 _this.on(player, 'ended', _this.handleEnded); 14390 } 14391 14392 return _this; 14393 } 14394 /** 14395 * Builds the default DOM `className`. 14396 * 14397 * @return {string} 14398 * The DOM `className` for this object. 14399 */ 14400 14401 14402 var _proto = PlayToggle.prototype; 14403 14404 _proto.buildCSSClass = function buildCSSClass() { 14405 return "vjs-play-control " + _Button.prototype.buildCSSClass.call(this); 14406 } 14407 /** 14408 * This gets called when an `PlayToggle` is "clicked". See 14409 * {@link ClickableComponent} for more detailed information on what a click can be. 14410 * 14411 * @param {EventTarget~Event} [event] 14412 * The `keydown`, `tap`, or `click` event that caused this function to be 14413 * called. 14414 * 14415 * @listens tap 14416 * @listens click 14417 */ 14418 ; 14419 14420 _proto.handleClick = function handleClick(event) { 14421 if (this.player_.paused()) { 14422 this.player_.play(); 14423 } else { 14424 this.player_.pause(); 14425 } 14426 } 14427 /** 14428 * This gets called once after the video has ended and the user seeks so that 14429 * we can change the replay button back to a play button. 14430 * 14431 * @param {EventTarget~Event} [event] 14432 * The event that caused this function to run. 14433 * 14434 * @listens Player#seeked 14435 */ 14436 ; 14437 14438 _proto.handleSeeked = function handleSeeked(event) { 14439 this.removeClass('vjs-ended'); 14440 14441 if (this.player_.paused()) { 14442 this.handlePause(event); 14443 } else { 14444 this.handlePlay(event); 14445 } 14446 } 14447 /** 14448 * Add the vjs-playing class to the element so it can change appearance. 14449 * 14450 * @param {EventTarget~Event} [event] 14451 * The event that caused this function to run. 14452 * 14453 * @listens Player#play 14454 */ 14455 ; 14456 14457 _proto.handlePlay = function handlePlay(event) { 14458 this.removeClass('vjs-ended'); 14459 this.removeClass('vjs-paused'); 14460 this.addClass('vjs-playing'); // change the button text to "Pause" 14461 14462 this.controlText('Pause'); 14463 } 14464 /** 14465 * Add the vjs-paused class to the element so it can change appearance. 14466 * 14467 * @param {EventTarget~Event} [event] 14468 * The event that caused this function to run. 14469 * 14470 * @listens Player#pause 14471 */ 14472 ; 14473 14474 _proto.handlePause = function handlePause(event) { 14475 this.removeClass('vjs-playing'); 14476 this.addClass('vjs-paused'); // change the button text to "Play" 14477 14478 this.controlText('Play'); 14479 } 14480 /** 14481 * Add the vjs-ended class to the element so it can change appearance 14482 * 14483 * @param {EventTarget~Event} [event] 14484 * The event that caused this function to run. 14485 * 14486 * @listens Player#ended 14487 */ 14488 ; 14489 14490 _proto.handleEnded = function handleEnded(event) { 14491 this.removeClass('vjs-playing'); 14492 this.addClass('vjs-ended'); // change the button text to "Replay" 14493 14494 this.controlText('Replay'); // on the next seek remove the replay button 14495 14496 this.one(this.player_, 'seeked', this.handleSeeked); 14497 }; 14498 14499 return PlayToggle; 14500 }(Button); 14501 /** 14502 * The text that should display over the `PlayToggle`s controls. Added for localization. 14503 * 14504 * @type {string} 14505 * @private 14506 */ 14507 14508 14509 PlayToggle.prototype.controlText_ = 'Play'; 14510 Component.registerComponent('PlayToggle', PlayToggle); 14511 14512 /** 14513 * @file format-time.js 14514 * @module format-time 14515 */ 14516 14517 /** 14518 * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in 14519 * seconds) will force a number of leading zeros to cover the length of the 14520 * guide. 14521 * 14522 * @private 14523 * @param {number} seconds 14524 * Number of seconds to be turned into a string 14525 * 14526 * @param {number} guide 14527 * Number (in seconds) to model the string after 14528 * 14529 * @return {string} 14530 * Time formatted as H:MM:SS or M:SS 14531 */ 14532 var defaultImplementation = function defaultImplementation(seconds, guide) { 14533 seconds = seconds < 0 ? 0 : seconds; 14534 var s = Math.floor(seconds % 60); 14535 var m = Math.floor(seconds / 60 % 60); 14536 var h = Math.floor(seconds / 3600); 14537 var gm = Math.floor(guide / 60 % 60); 14538 var gh = Math.floor(guide / 3600); // handle invalid times 14539 14540 if (isNaN(seconds) || seconds === Infinity) { 14541 // '-' is false for all relational operators (e.g. <, >
vendor: 6,494 bytes, lines 14541-14761
14541=) so this setting 14542 // will add the minimum number of fields specified by the guide 14543 h = m = s = '-'; 14544 } // Check if we need to show hours 14545 14546 14547 h = h > 0 || gh > 0 ? h + ':' : ''; // If hours are showing, we may need to add a leading zero. 14548 // Always show at least one digit of minutes. 14549 14550 m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':'; // Check if leading zero is need for seconds 14551 14552 s = s < 10 ? '0' + s : s; 14553 return h + m + s; 14554 }; // Internal pointer to the current implementation. 14555 14556 14557 var implementation$3 = defaultImplementation; 14558 /** 14559 * Replaces the default formatTime implementation with a custom implementation. 14560 * 14561 * @param {Function} customImplementation 14562 * A function which will be used in place of the default formatTime 14563 * implementation. Will receive the current time in seconds and the 14564 * guide (in seconds) as arguments. 14565 */ 14566 14567 function setFormatTime(customImplementation) { 14568 implementation$3 = customImplementation; 14569 } 14570 /** 14571 * Resets formatTime to the default implementation. 14572 */ 14573 14574 function resetFormatTime() { 14575 implementation$3 = defaultImplementation; 14576 } 14577 /** 14578 * Delegates to either the default time formatting function or a custom 14579 * function supplied via `setFormatTime`. 14580 * 14581 * Formats seconds as a time string (H:MM:SS or M:SS). Supplying a 14582 * guide (in seconds) will force a number of leading zeros to cover the 14583 * length of the guide. 14584 * 14585 * @static 14586 * @example formatTime(125, 600) === "02:05" 14587 * @param {number} seconds 14588 * Number of seconds to be turned into a string 14589 * 14590 * @param {number} guide 14591 * Number (in seconds) to model the string after 14592 * 14593 * @return {string} 14594 * Time formatted as H:MM:SS or M:SS 14595 */ 14596 14597 function formatTime(seconds, guide) { 14598 if (guide === void 0) { 14599 guide = seconds; 14600 } 14601 14602 return implementation$3(seconds, guide); 14603 } 14604 14605 /** 14606 * Displays time information about the video 14607 * 14608 * @extends Component 14609 */ 14610 14611 var TimeDisplay = 14612 /*#__PURE__*/ 14613 function (_Component) { 14614 _inheritsLoose(TimeDisplay, _Component); 14615 14616 /** 14617 * Creates an instance of this class. 14618 * 14619 * @param {Player} player 14620 * The `Player` that this class should be attached to. 14621 * 14622 * @param {Object} [options] 14623 * The key/value store of player options. 14624 */ 14625 function TimeDisplay(player, options) { 14626 var _this; 14627 14628 _this = _Component.call(this, player, options) || this; 14629 _this.throttledUpdateContent = throttle(bind(_assertThisInitialized(_this), _this.updateContent), UPDATE_REFRESH_INTERVAL); 14630 14631 _this.on(player, 'timeupdate', _this.throttledUpdateContent); 14632 14633 return _this; 14634 } 14635 /** 14636 * Create the `Component`'s DOM element 14637 * 14638 * @return {Element} 14639 * The element that was created. 14640 */ 14641 14642 14643 var _proto = TimeDisplay.prototype; 14644 14645 _proto.createEl = function createEl$1() { 14646 var className = this.buildCSSClass(); 14647 14648 var el = _Component.prototype.createEl.call(this, 'div', { 14649 className: className + " vjs-time-control vjs-control", 14650 innerHTML: "<span class=\"vjs-control-text\" role=\"presentation\">" + this.localize(this.labelText_) + "\xA0</span>" 14651 }); 14652 14653 this.contentEl_ = createEl('span', { 14654 className: className + "-display" 14655 }, { 14656 // tell screen readers not to automatically read the time as it changes 14657 'aria-live': 'off', 14658 // span elements have no implicit role, but some screen readers (notably VoiceOver) 14659 // treat them as a break between items in the DOM when using arrow keys 14660 // (or left-to-right swipes on iOS) to read contents of a page. Using 14661 // role='presentation' causes VoiceOver to NOT treat this span as a break. 14662 'role': 'presentation' 14663 }); 14664 this.updateTextNode_(); 14665 el.appendChild(this.contentEl_); 14666 return el; 14667 }; 14668 14669 _proto.dispose = function dispose() { 14670 this.contentEl_ = null; 14671 this.textNode_ = null; 14672 14673 _Component.prototype.dispose.call(this); 14674 } 14675 /** 14676 * Updates the "remaining time" text node with new content using the 14677 * contents of the `formattedTime_` property. 14678 * 14679 * @private 14680 */ 14681 ; 14682 14683 _proto.updateTextNode_ = function updateTextNode_() { 14684 if (!this.contentEl_) { 14685 return; 14686 } 14687 14688 while (this.contentEl_.firstChild) { 14689 this.contentEl_.removeChild(this.contentEl_.firstChild); 14690 } 14691 14692 this.textNode_ = document.createTextNode(this.formattedTime_ || this.formatTime_(0)); 14693 this.contentEl_.appendChild(this.textNode_); 14694 } 14695 /** 14696 * Generates a formatted time for this component to use in display. 14697 * 14698 * @param {number} time 14699 * A numeric time, in seconds. 14700 * 14701 * @return {string} 14702 * A formatted time 14703 * 14704 * @private 14705 */ 14706 ; 14707 14708 _proto.formatTime_ = function formatTime_(time) { 14709 return formatTime(time); 14710 } 14711 /** 14712 * Updates the time display text node if it has what was passed in changed 14713 * the formatted time. 14714 * 14715 * @param {number} time 14716 * The time to update to 14717 * 14718 * @private 14719 */ 14720 ; 14721 14722 _proto.updateFormattedTime_ = function updateFormattedTime_(time) { 14723 var formattedTime = this.formatTime_(time); 14724 14725 if (formattedTime === this.formattedTime_) { 14726 return; 14727 } 14728 14729 this.formattedTime_ = formattedTime; 14730 this.requestAnimationFrame(this.updateTextNode_); 14731 } 14732 /** 14733 * To be filled out in the child class, should update the displayed time 14734 * in accordance with the fact that the current time has changed. 14735 * 14736 * @param {EventTarget~Event} [event] 14737 * The `timeupdate` event that caused this to run. 14738 * 14739 * @listens Player#timeupdate 14740 */ 14741 ; 14742 14743 _proto.updateContent = function updateContent(event) {}; 14744 14745 return TimeDisplay; 14746 }(Component); 14747 /** 14748 * The text that is added to the `TimeDisplay` for screen reader users. 14749 * 14750 * @type {string} 14751 * @private 14752 */ 14753 14754 14755 TimeDisplay.prototype.labelText_ = 'Time'; 14756 /** 14757 * The text that should display over the `TimeDisplay`s controls. Added to for localization. 14758 * 14759 * @type {string} 14760 * @private 14761 *
14762 * @deprecated in v7; controlText_ is not used in non-active display Components 14763 */ 14764 14765 TimeDisplay.prototype.controlText_ = 'Time'; 14766 Component.registerComponent('TimeDisplay', TimeDisplay); 14767 14768 /** 14769 * Displays the current time 14770 * 14771 * @extends Component 14772 */ 14773 14774 var CurrentTimeDisplay = 14775 /*#__PURE__*/ 14776 function (_TimeDisplay) { 14777 _inheritsLoose(CurrentTimeDisplay, _TimeDisplay); 14778 14779 /** 14780 * Creates an instance of this class. 14781 * 14782 * @param {Player} player 14783 * The `Player` that this class should be attached to. 14784 * 14785 * @param {Object} [options] 14786 * The key/value store of player options. 14787 */ 14788 function CurrentTimeDisplay(player, options) { 14789 var _this; 14790 14791 _this = _TimeDisplay.call(this, player, options) || this; 14792 14793 _this.on(player, 'ended', _this.handleEnded); 14794 14795 return _this; 14796 } 14797 /** 14798 * Builds the default DOM `className`. 14799 * 14800 * @return {string} 14801 * The DOM `className` for this object. 14802 */ 14803 14804 14805 var _proto = CurrentTimeDisplay.prototype; 14806 14807 _proto.buildCSSClass = function buildCSSClass() { 14808 return 'vjs-current-time'; 14809 } 14810 /** 14811 * Update current time display 14812 * 14813 * @param {EventTarget~Event} [event] 14814 * The `timeupdate` event that caused this function to run. 14815 * 14816 * @listens Player#timeupdate 14817 */ 14818 ; 14819 14820 _proto.updateContent = function updateContent(event) { 14821 // Allows for smooth scrubbing, when player can't keep up. 14822 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 14823 this.updateFormattedTime_(time); 14824 } 14825 /** 14826 * When the player fires ended there should be no time left. Sadly 14827 * this is not always the case, lets make it seem like that is the case 14828 * for users. 14829 * 14830 * @param {EventTarget~Event} [event] 14831 * The `ended` event that caused this to run. 14832 * 14833 * @listens Player#ended 14834 */ 14835 ; 14836 14837 _proto.handleEnded = function handleEnded(event) { 14838 if (!this.player_.duration()) { 14839 return; 14840 } 14841 14842 this.updateFormattedTime_(this.player_.duration()); 14843 }; 14844 14845 return CurrentTimeDisplay; 14846 }(TimeDisplay); 14847 /** 14848 * The text that is added to the `CurrentTimeDisplay` for screen reader users. 14849 * 14850 * @type {string} 14851 * @private 14852 */ 14853 14854 14855 CurrentTimeDisplay.prototype.labelText_ = 'Current Time'; 14856 /** 14857 * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization. 14858 * 14859 * @type {string} 14860 * @private 14861 * 14862 * @deprecated in v7; controlText_ is not used in non-active display Components 14863 */ 14864 14865 CurrentTimeDisplay.prototype.controlText_ = 'Current Time'; 14866 Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); 14867 14868 /** 14869 * Displays the duration 14870 * 14871 * @extends Component 14872 */ 14873 14874 var DurationDisplay = 14875 /*#__PURE__*/ 14876 function (_TimeDisplay) { 14877 _inheritsLoose(DurationDisplay, _TimeDisplay); 14878 14879 /** 14880 * Creates an instance of this class. 14881 * 14882 * @param {Player} player 14883 * The `Player` that this class should be attached to. 14884 * 14885 * @param {Object} [options] 14886 * The key/value store of player options. 14887 */ 14888 function DurationDisplay(player, options) { 14889 var _this; 14890 14891 _this = _TimeDisplay.call(this, player, options) || this; // we do not want to/need to throttle duration changes, 14892 // as they should always display the changed duration as 14893 // it has changed 14894 14895 _this.on(player, 'durationchange', _this.updateContent); // Listen to loadstart because the player duration is reset when a new media element is loaded, 14896 // but the durationchange on the user agent will not fire. 14897 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 14898 14899 14900 _this.on(player, 'loadstart', _this.updateContent); // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
vendor: 129,789 bytes, lines 14901-19300
14901 // listeners could have broken dependent applications/libraries. These 14902 // can likely be removed for 7.0. 14903 14904 14905 _this.on(player, 'loadedmetadata', _this.throttledUpdateContent); 14906 14907 return _this; 14908 } 14909 /** 14910 * Builds the default DOM `className`. 14911 * 14912 * @return {string} 14913 * The DOM `className` for this object. 14914 */ 14915 14916 14917 var _proto = DurationDisplay.prototype; 14918 14919 _proto.buildCSSClass = function buildCSSClass() { 14920 return 'vjs-duration'; 14921 } 14922 /** 14923 * Update duration time display. 14924 * 14925 * @param {EventTarget~Event} [event] 14926 * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused 14927 * this function to be called. 14928 * 14929 * @listens Player#durationchange 14930 * @listens Player#timeupdate 14931 * @listens Player#loadedmetadata 14932 */ 14933 ; 14934 14935 _proto.updateContent = function updateContent(event) { 14936 var duration = this.player_.duration(); 14937 14938 if (this.duration_ !== duration) { 14939 this.duration_ = duration; 14940 this.updateFormattedTime_(duration); 14941 } 14942 }; 14943 14944 return DurationDisplay; 14945 }(TimeDisplay); 14946 /** 14947 * The text that is added to the `DurationDisplay` for screen reader users. 14948 * 14949 * @type {string} 14950 * @private 14951 */ 14952 14953 14954 DurationDisplay.prototype.labelText_ = 'Duration'; 14955 /** 14956 * The text that should display over the `DurationDisplay`s controls. Added to for localization. 14957 * 14958 * @type {string} 14959 * @private 14960 * 14961 * @deprecated in v7; controlText_ is not used in non-active display Components 14962 */ 14963 14964 DurationDisplay.prototype.controlText_ = 'Duration'; 14965 Component.registerComponent('DurationDisplay', DurationDisplay); 14966 14967 /** 14968 * The separator between the current time and duration. 14969 * Can be hidden if it's not needed in the design. 14970 * 14971 * @extends Component 14972 */ 14973 14974 var TimeDivider = 14975 /*#__PURE__*/ 14976 function (_Component) { 14977 _inheritsLoose(TimeDivider, _Component); 14978 14979 function TimeDivider() { 14980 return _Component.apply(this, arguments) || this; 14981 } 14982 14983 var _proto = TimeDivider.prototype; 14984 14985 /** 14986 * Create the component's DOM element 14987 * 14988 * @return {Element} 14989 * The element that was created. 14990 */ 14991 _proto.createEl = function createEl() { 14992 return _Component.prototype.createEl.call(this, 'div', { 14993 className: 'vjs-time-control vjs-time-divider', 14994 innerHTML: '<div><span>/</span></div>' 14995 }, { 14996 // this element and its contents can be hidden from assistive techs since 14997 // it is made extraneous by the announcement of the control text 14998 // for the current time and duration displays 14999 'aria-hidden': true 15000 }); 15001 }; 15002 15003 return TimeDivider; 15004 }(Component); 15005 15006 Component.registerComponent('TimeDivider', TimeDivider); 15007 15008 /** 15009 * Displays the time left in the video 15010 * 15011 * @extends Component 15012 */ 15013 15014 var RemainingTimeDisplay = 15015 /*#__PURE__*/ 15016 function (_TimeDisplay) { 15017 _inheritsLoose(RemainingTimeDisplay, _TimeDisplay); 15018 15019 /** 15020 * Creates an instance of this class. 15021 * 15022 * @param {Player} player 15023 * The `Player` that this class should be attached to. 15024 * 15025 * @param {Object} [options] 15026 * The key/value store of player options. 15027 */ 15028 function RemainingTimeDisplay(player, options) { 15029 var _this; 15030 15031 _this = _TimeDisplay.call(this, player, options) || this; 15032 15033 _this.on(player, 'durationchange', _this.throttledUpdateContent); 15034 15035 _this.on(player, 'ended', _this.handleEnded); 15036 15037 return _this; 15038 } 15039 /** 15040 * Builds the default DOM `className`. 15041 * 15042 * @return {string} 15043 * The DOM `className` for this object. 15044 */ 15045 15046 15047 var _proto = RemainingTimeDisplay.prototype; 15048 15049 _proto.buildCSSClass = function buildCSSClass() { 15050 return 'vjs-remaining-time'; 15051 } 15052 /** 15053 * Create the `Component`'s DOM element with the "minus" characted prepend to the time 15054 * 15055 * @return {Element} 15056 * The element that was created. 15057 */ 15058 ; 15059 15060 _proto.createEl = function createEl$1() { 15061 var el = _TimeDisplay.prototype.createEl.call(this); 15062 15063 el.insertBefore(createEl('span', {}, { 15064 'aria-hidden': true 15065 }, '-'), this.contentEl_); 15066 return el; 15067 } 15068 /** 15069 * Update remaining time display. 15070 * 15071 * @param {EventTarget~Event} [event] 15072 * The `timeupdate` or `durationchange` event that caused this to run. 15073 * 15074 * @listens Player#timeupdate 15075 * @listens Player#durationchange 15076 */ 15077 ; 15078 15079 _proto.updateContent = function updateContent(event) { 15080 if (typeof this.player_.duration() !== 'number') { 15081 return; 15082 } // @deprecated We should only use remainingTimeDisplay 15083 // as of video.js 7 15084 15085 15086 if (this.player_.remainingTimeDisplay) { 15087 this.updateFormattedTime_(this.player_.remainingTimeDisplay()); 15088 } else { 15089 this.updateFormattedTime_(this.player_.remainingTime()); 15090 } 15091 } 15092 /** 15093 * When the player fires ended there should be no time left. Sadly 15094 * this is not always the case, lets make it seem like that is the case 15095 * for users. 15096 * 15097 * @param {EventTarget~Event} [event] 15098 * The `ended` event that caused this to run. 15099 * 15100 * @listens Player#ended 15101 */ 15102 ; 15103 15104 _proto.handleEnded = function handleEnded(event) { 15105 if (!this.player_.duration()) { 15106 return; 15107 } 15108 15109 this.updateFormattedTime_(0); 15110 }; 15111 15112 return RemainingTimeDisplay; 15113 }(TimeDisplay); 15114 /** 15115 * The text that is added to the `RemainingTimeDisplay` for screen reader users. 15116 * 15117 * @type {string} 15118 * @private 15119 */ 15120 15121 15122 RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time'; 15123 /** 15124 * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization. 15125 * 15126 * @type {string} 15127 * @private 15128 * 15129 * @deprecated in v7; controlText_ is not used in non-active display Components 15130 */ 15131 15132 RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time'; 15133 Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); 15134 15135 /** 15136 * Displays the live indicator when duration is Infinity. 15137 * 15138 * @extends Component 15139 */ 15140 15141 var LiveDisplay = 15142 /*#__PURE__*/ 15143 function (_Component) { 15144 _inheritsLoose(LiveDisplay, _Component); 15145 15146 /** 15147 * Creates an instance of this class. 15148 * 15149 * @param {Player} player 15150 * The `Player` that this class should be attached to. 15151 * 15152 * @param {Object} [options] 15153 * The key/value store of player options. 15154 */ 15155 function LiveDisplay(player, options) { 15156 var _this; 15157 15158 _this = _Component.call(this, player, options) || this; 15159 15160 _this.updateShowing(); 15161 15162 _this.on(_this.player(), 'durationchange', _this.updateShowing); 15163 15164 return _this; 15165 } 15166 /** 15167 * Create the `Component`'s DOM element 15168 * 15169 * @return {Element} 15170 * The element that was created. 15171 */ 15172 15173 15174 var _proto = LiveDisplay.prototype; 15175 15176 _proto.createEl = function createEl$1() { 15177 var el = _Component.prototype.createEl.call(this, 'div', { 15178 className: 'vjs-live-control vjs-control' 15179 }); 15180 15181 this.contentEl_ = createEl('div', { 15182 className: 'vjs-live-display', 15183 innerHTML: "<span class=\"vjs-control-text\">" + this.localize('Stream Type') + "\xA0</span>" + this.localize('LIVE') 15184 }, { 15185 'aria-live': 'off' 15186 }); 15187 el.appendChild(this.contentEl_); 15188 return el; 15189 }; 15190 15191 _proto.dispose = function dispose() { 15192 this.contentEl_ = null; 15193 15194 _Component.prototype.dispose.call(this); 15195 } 15196 /** 15197 * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide 15198 * it accordingly 15199 * 15200 * @param {EventTarget~Event} [event] 15201 * The {@link Player#durationchange} event that caused this function to run. 15202 * 15203 * @listens Player#durationchange 15204 */ 15205 ; 15206 15207 _proto.updateShowing = function updateShowing(event) { 15208 if (this.player().duration() === Infinity) { 15209 this.show(); 15210 } else { 15211 this.hide(); 15212 } 15213 }; 15214 15215 return LiveDisplay; 15216 }(Component); 15217 15218 Component.registerComponent('LiveDisplay', LiveDisplay); 15219 15220 /** 15221 * Displays the live indicator when duration is Infinity. 15222 * 15223 * @extends Component 15224 */ 15225 15226 var SeekToLive = 15227 /*#__PURE__*/ 15228 function (_Button) { 15229 _inheritsLoose(SeekToLive, _Button); 15230 15231 /** 15232 * Creates an instance of this class. 15233 * 15234 * @param {Player} player 15235 * The `Player` that this class should be attached to. 15236 * 15237 * @param {Object} [options] 15238 * The key/value store of player options. 15239 */ 15240 function SeekToLive(player, options) { 15241 var _this; 15242 15243 _this = _Button.call(this, player, options) || this; 15244 15245 _this.updateLiveEdgeStatus(); 15246 15247 if (_this.player_.liveTracker) { 15248 _this.on(_this.player_.liveTracker, 'liveedgechange', _this.updateLiveEdgeStatus); 15249 } 15250 15251 return _this; 15252 } 15253 /** 15254 * Create the `Component`'s DOM element 15255 * 15256 * @return {Element} 15257 * The element that was created. 15258 */ 15259 15260 15261 var _proto = SeekToLive.prototype; 15262 15263 _proto.createEl = function createEl$1() { 15264 var el = _Button.prototype.createEl.call(this, 'button', { 15265 className: 'vjs-seek-to-live-control vjs-control' 15266 }); 15267 15268 this.textEl_ = createEl('span', { 15269 className: 'vjs-seek-to-live-text', 15270 innerHTML: this.localize('LIVE') 15271 }, { 15272 'aria-hidden': 'true' 15273 }); 15274 el.appendChild(this.textEl_); 15275 return el; 15276 } 15277 /** 15278 * Update the state of this button if we are at the live edge 15279 * or not 15280 */ 15281 ; 15282 15283 _proto.updateLiveEdgeStatus = function updateLiveEdgeStatus(e) { 15284 // default to live edge 15285 if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) { 15286 this.setAttribute('aria-disabled', true); 15287 this.addClass('vjs-at-live-edge'); 15288 this.controlText('Seek to live, currently playing live'); 15289 } else { 15290 this.setAttribute('aria-disabled', false); 15291 this.removeClass('vjs-at-live-edge'); 15292 this.controlText('Seek to live, currently behind live'); 15293 } 15294 } 15295 /** 15296 * On click bring us as near to the live point as possible. 15297 * This requires that we wait for the next `live-seekable-change` 15298 * event which will happen every segment length seconds. 15299 */ 15300 ; 15301 15302 _proto.handleClick = function handleClick() { 15303 this.player_.liveTracker.seekToLiveEdge(); 15304 } 15305 /** 15306 * Dispose of the element and stop tracking 15307 */ 15308 ; 15309 15310 _proto.dispose = function dispose() { 15311 if (this.player_.liveTracker) { 15312 this.off(this.player_.liveTracker, 'liveedgechange', this.updateLiveEdgeStatus); 15313 } 15314 15315 this.textEl_ = null; 15316 15317 _Button.prototype.dispose.call(this); 15318 }; 15319 15320 return SeekToLive; 15321 }(Button); 15322 15323 SeekToLive.prototype.controlText_ = 'Seek to live, currently playing live'; 15324 Component.registerComponent('SeekToLive', SeekToLive); 15325 15326 /** 15327 * The base functionality for a slider. Can be vertical or horizontal. 15328 * For instance the volume bar or the seek bar on a video is a slider. 15329 * 15330 * @extends Component 15331 */ 15332 15333 var Slider = 15334 /*#__PURE__*/ 15335 function (_Component) { 15336 _inheritsLoose(Slider, _Component); 15337 15338 /** 15339 * Create an instance of this class 15340 * 15341 * @param {Player} player 15342 * The `Player` that this class should be attached to. 15343 * 15344 * @param {Object} [options] 15345 * The key/value store of player options. 15346 */ 15347 function Slider(player, options) { 15348 var _this; 15349 15350 _this = _Component.call(this, player, options) || this; // Set property names to bar to match with the child Slider class is looking for 15351 15352 _this.bar = _this.getChild(_this.options_.barName); // Set a horizontal or vertical class on the slider depending on the slider type 15353 15354 _this.vertical(!!_this.options_.vertical); 15355 15356 _this.enable(); 15357 15358 return _this; 15359 } 15360 /** 15361 * Are controls are currently enabled for this slider or not. 15362 * 15363 * @return {boolean} 15364 * true if controls are enabled, false otherwise 15365 */ 15366 15367 15368 var _proto = Slider.prototype; 15369 15370 _proto.enabled = function enabled() { 15371 return this.enabled_; 15372 } 15373 /** 15374 * Enable controls for this slider if they are disabled 15375 */ 15376 ; 15377 15378 _proto.enable = function enable() { 15379 if (this.enabled()) { 15380 return; 15381 } 15382 15383 this.on('mousedown', this.handleMouseDown); 15384 this.on('touchstart', this.handleMouseDown); 15385 this.on('keydown', this.handleKeyDown); 15386 this.on('click', this.handleClick); // TODO: deprecated, controlsvisible does not seem to be fired 15387 15388 this.on(this.player_, 'controlsvisible', this.update); 15389 15390 if (this.playerEvent) { 15391 this.on(this.player_, this.playerEvent, this.update); 15392 } 15393 15394 this.removeClass('disabled'); 15395 this.setAttribute('tabindex', 0); 15396 this.enabled_ = true; 15397 } 15398 /** 15399 * Disable controls for this slider if they are enabled 15400 */ 15401 ; 15402 15403 _proto.disable = function disable() { 15404 if (!this.enabled()) { 15405 return; 15406 } 15407 15408 var doc = this.bar.el_.ownerDocument; 15409 this.off('mousedown', this.handleMouseDown); 15410 this.off('touchstart', this.handleMouseDown); 15411 this.off('keydown', this.handleKeyDown); 15412 this.off('click', this.handleClick); 15413 this.off(this.player_, 'controlsvisible', this.update); 15414 this.off(doc, 'mousemove', this.handleMouseMove); 15415 this.off(doc, 'mouseup', this.handleMouseUp); 15416 this.off(doc, 'touchmove', this.handleMouseMove); 15417 this.off(doc, 'touchend', this.handleMouseUp); 15418 this.removeAttribute('tabindex'); 15419 this.addClass('disabled'); 15420 15421 if (this.playerEvent) { 15422 this.off(this.player_, this.playerEvent, this.update); 15423 } 15424 15425 this.enabled_ = false; 15426 } 15427 /** 15428 * Create the `Slider`s DOM element. 15429 * 15430 * @param {string} type 15431 * Type of element to create. 15432 * 15433 * @param {Object} [props={}] 15434 * List of properties in Object form. 15435 * 15436 * @param {Object} [attributes={}] 15437 * list of attributes in Object form. 15438 * 15439 * @return {Element} 15440 * The element that gets created. 15441 */ 15442 ; 15443 15444 _proto.createEl = function createEl(type, props, attributes) { 15445 if (props === void 0) { 15446 props = {}; 15447 } 15448 15449 if (attributes === void 0) { 15450 attributes = {}; 15451 } 15452 15453 // Add the slider element class to all sub classes 15454 props.className = props.className + ' vjs-slider'; 15455 props = assign({ 15456 tabIndex: 0 15457 }, props); 15458 attributes = assign({ 15459 'role': 'slider', 15460 'aria-valuenow': 0, 15461 'aria-valuemin': 0, 15462 'aria-valuemax': 100, 15463 'tabIndex': 0 15464 }, attributes); 15465 return _Component.prototype.createEl.call(this, type, props, attributes); 15466 } 15467 /** 15468 * Handle `mousedown` or `touchstart` events on the `Slider`. 15469 * 15470 * @param {EventTarget~Event} event 15471 * `mousedown` or `touchstart` event that triggered this function 15472 * 15473 * @listens mousedown 15474 * @listens touchstart 15475 * @fires Slider#slideractive 15476 */ 15477 ; 15478 15479 _proto.handleMouseDown = function handleMouseDown(event) { 15480 var doc = this.bar.el_.ownerDocument; 15481 15482 if (event.type === 'mousedown') { 15483 event.preventDefault(); 15484 } // Do not call preventDefault() on touchstart in Chrome 15485 // to avoid console warnings. Use a 'touch-action: none' style 15486 // instead to prevent unintented scrolling. 15487 // https://developers.google.com/web/updates/2017/01/scrolling-intervention 15488 15489 15490 if (event.type === 'touchstart' && !IS_CHROME) { 15491 event.preventDefault(); 15492 } 15493 15494 blockTextSelection(); 15495 this.addClass('vjs-sliding'); 15496 /** 15497 * Triggered when the slider is in an active state 15498 * 15499 * @event Slider#slideractive 15500 * @type {EventTarget~Event} 15501 */ 15502 15503 this.trigger('slideractive'); 15504 this.on(doc, 'mousemove', this.handleMouseMove); 15505 this.on(doc, 'mouseup', this.handleMouseUp); 15506 this.on(doc, 'touchmove', this.handleMouseMove); 15507 this.on(doc, 'touchend', this.handleMouseUp); 15508 this.handleMouseMove(event); 15509 } 15510 /** 15511 * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`. 15512 * The `mousemove` and `touchmove` events will only only trigger this function during 15513 * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and 15514 * {@link Slider#handleMouseUp}. 15515 * 15516 * @param {EventTarget~Event} event 15517 * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered 15518 * this function 15519 * 15520 * @listens mousemove 15521 * @listens touchmove 15522 */ 15523 ; 15524 15525 _proto.handleMouseMove = function handleMouseMove(event) {} 15526 /** 15527 * Handle `mouseup` or `touchend` events on the `Slider`. 15528 * 15529 * @param {EventTarget~Event} event 15530 * `mouseup` or `touchend` event that triggered this function. 15531 * 15532 * @listens touchend 15533 * @listens mouseup 15534 * @fires Slider#sliderinactive 15535 */ 15536 ; 15537 15538 _proto.handleMouseUp = function handleMouseUp() { 15539 var doc = this.bar.el_.ownerDocument; 15540 unblockTextSelection(); 15541 this.removeClass('vjs-sliding'); 15542 /** 15543 * Triggered when the slider is no longer in an active state. 15544 * 15545 * @event Slider#sliderinactive 15546 * @type {EventTarget~Event} 15547 */ 15548 15549 this.trigger('sliderinactive'); 15550 this.off(doc, 'mousemove', this.handleMouseMove); 15551 this.off(doc, 'mouseup', this.handleMouseUp); 15552 this.off(doc, 'touchmove', this.handleMouseMove); 15553 this.off(doc, 'touchend', this.handleMouseUp); 15554 this.update(); 15555 } 15556 /** 15557 * Update the progress bar of the `Slider`. 15558 * 15559 * @return {number} 15560 * The percentage of progress the progress bar represents as a 15561 * number from 0 to 1. 15562 */ 15563 ; 15564 15565 _proto.update = function update() { 15566 // In VolumeBar init we have a setTimeout for update that pops and update 15567 // to the end of the execution stack. The player is destroyed before then 15568 // update will cause an error 15569 if (!this.el_) { 15570 return; 15571 } // If scrubbing, we could use a cached value to make the handle keep up 15572 // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but 15573 // some flash players are slow, so we might want to utilize this later. 15574 // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration(); 15575 15576 15577 var progress = this.getPercent(); 15578 var bar = this.bar; // If there's no bar... 15579 15580 if (!bar) { 15581 return; 15582 } // Protect against no duration and other division issues 15583 15584 15585 if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) { 15586 progress = 0; 15587 } // Convert to a percentage for setting 15588 15589 15590 var percentage = (progress * 100).toFixed(2) + '%'; 15591 var style = bar.el().style; // Set the new bar width or height 15592 15593 var sizeKey = this.vertical() ? 'height' : 'width'; 15594 15595 if (style[sizeKey] !== percentage) { 15596 style[sizeKey] = percentage; 15597 } 15598 15599 return progress; 15600 } 15601 /** 15602 * Calculate distance for slider 15603 * 15604 * @param {EventTarget~Event} event 15605 * The event that caused this function to run. 15606 * 15607 * @return {number} 15608 * The current position of the Slider. 15609 * - position.x for vertical `Slider`s 15610 * - position.y for horizontal `Slider`s 15611 */ 15612 ; 15613 15614 _proto.calculateDistance = function calculateDistance(event) { 15615 var position = getPointerPosition(this.el_, event); 15616 15617 if (this.vertical()) { 15618 return position.y; 15619 } 15620 15621 return position.x; 15622 } 15623 /** 15624 * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down 15625 * arrow keys. This function will only be called when the slider has focus. See 15626 * {@link Slider#handleFocus} and {@link Slider#handleBlur}. 15627 * 15628 * @param {EventTarget~Event} event 15629 * the `keydown` event that caused this function to run. 15630 * 15631 * @listens keydown 15632 */ 15633 ; 15634 15635 _proto.handleKeyDown = function handleKeyDown(event) { 15636 // Left and Down Arrows 15637 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 15638 event.preventDefault(); 15639 event.stopPropagation(); 15640 this.stepBack(); // Up and Right Arrows 15641 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 15642 event.preventDefault(); 15643 event.stopPropagation(); 15644 this.stepForward(); 15645 } else { 15646 // Pass keydown handling up for unsupported keys 15647 _Component.prototype.handleKeyDown.call(this, event); 15648 } 15649 } 15650 /** 15651 * Listener for click events on slider, used to prevent clicks 15652 * from bubbling up to parent elements like button menus. 15653 * 15654 * @param {Object} event 15655 * Event that caused this object to run 15656 */ 15657 ; 15658 15659 _proto.handleClick = function handleClick(event) { 15660 event.stopPropagation(); 15661 event.preventDefault(); 15662 } 15663 /** 15664 * Get/set if slider is horizontal for vertical 15665 * 15666 * @param {boolean} [bool] 15667 * - true if slider is vertical, 15668 * - false is horizontal 15669 * 15670 * @return {boolean} 15671 * - true if slider is vertical, and getting 15672 * - false if the slider is horizontal, and getting 15673 */ 15674 ; 15675 15676 _proto.vertical = function vertical(bool) { 15677 if (bool === undefined) { 15678 return this.vertical_ || false; 15679 } 15680 15681 this.vertical_ = !!bool; 15682 15683 if (this.vertical_) { 15684 this.addClass('vjs-slider-vertical'); 15685 } else { 15686 this.addClass('vjs-slider-horizontal'); 15687 } 15688 }; 15689 15690 return Slider; 15691 }(Component); 15692 15693 Component.registerComponent('Slider', Slider); 15694 15695 /** 15696 * Shows loading progress 15697 * 15698 * @extends Component 15699 */ 15700 15701 var LoadProgressBar = 15702 /*#__PURE__*/ 15703 function (_Component) { 15704 _inheritsLoose(LoadProgressBar, _Component); 15705 15706 /** 15707 * Creates an instance of this class. 15708 * 15709 * @param {Player} player 15710 * The `Player` that this class should be attached to. 15711 * 15712 * @param {Object} [options] 15713 * The key/value store of player options. 15714 */ 15715 function LoadProgressBar(player, options) { 15716 var _this; 15717 15718 _this = _Component.call(this, player, options) || this; 15719 _this.partEls_ = []; 15720 15721 _this.on(player, 'progress', _this.update); 15722 15723 return _this; 15724 } 15725 /** 15726 * Create the `Component`'s DOM element 15727 * 15728 * @return {Element} 15729 * The element that was created. 15730 */ 15731 15732 15733 var _proto = LoadProgressBar.prototype; 15734 15735 _proto.createEl = function createEl() { 15736 return _Component.prototype.createEl.call(this, 'div', { 15737 className: 'vjs-load-progress', 15738 innerHTML: "<span class=\"vjs-control-text\"><span>" + this.localize('Loaded') + "</span>: <span class=\"vjs-control-text-loaded-percentage\">0%</span></span>" 15739 }); 15740 }; 15741 15742 _proto.dispose = function dispose() { 15743 this.partEls_ = null; 15744 15745 _Component.prototype.dispose.call(this); 15746 } 15747 /** 15748 * Update progress bar 15749 * 15750 * @param {EventTarget~Event} [event] 15751 * The `progress` event that caused this function to run. 15752 * 15753 * @listens Player#progress 15754 */ 15755 ; 15756 15757 _proto.update = function update(event) { 15758 var liveTracker = this.player_.liveTracker; 15759 var buffered = this.player_.buffered(); 15760 var duration = liveTracker && liveTracker.isLive() ? liveTracker.seekableEnd() : this.player_.duration(); 15761 var bufferedEnd = this.player_.bufferedEnd(); 15762 var children = this.partEls_; 15763 var controlTextPercentage = this.$('.vjs-control-text-loaded-percentage'); // get the percent width of a time compared to the total end 15764 15765 var percentify = function percentify(time, end, rounded) { 15766 // no NaN 15767 var percent = time / end || 0; 15768 percent = (percent >= 1 ? 1 : percent) * 100; 15769 15770 if (rounded) { 15771 percent = percent.toFixed(2); 15772 } 15773 15774 return percent + '%'; 15775 }; // update the width of the progress bar 15776 15777 15778 this.el_.style.width = percentify(bufferedEnd, duration); // update the control-text 15779 15780 textContent(controlTextPercentage, percentify(bufferedEnd, duration, true)); // add child elements to represent the individual buffered time ranges 15781 15782 for (var i = 0; i < buffered.length; i++) { 15783 var start = buffered.start(i); 15784 var end = buffered.end(i); 15785 var part = children[i]; 15786 15787 if (!part) { 15788 part = this.el_.appendChild(createEl()); 15789 children[i] = part; 15790 } // set the percent based on the width of the progress bar (bufferedEnd) 15791 15792 15793 part.style.left = percentify(start, bufferedEnd); 15794 part.style.width = percentify(end - start, bufferedEnd); 15795 } // remove unused buffered range elements 15796 15797 15798 for (var _i = children.length; _i > buffered.length; _i--) { 15799 this.el_.removeChild(children[_i - 1]); 15800 } 15801 15802 children.length = buffered.length; 15803 }; 15804 15805 return LoadProgressBar; 15806 }(Component); 15807 15808 Component.registerComponent('LoadProgressBar', LoadProgressBar); 15809 15810 /** 15811 * Time tooltips display a time above the progress bar. 15812 * 15813 * @extends Component 15814 */ 15815 15816 var TimeTooltip = 15817 /*#__PURE__*/ 15818 function (_Component) { 15819 _inheritsLoose(TimeTooltip, _Component); 15820 15821 /** 15822 * Creates an instance of this class. 15823 * 15824 * @param {Player} player 15825 * The {@link Player} that this class should be attached to. 15826 * 15827 * @param {Object} [options] 15828 * The key/value store of player options. 15829 */ 15830 function TimeTooltip(player, options) { 15831 var _this; 15832 15833 _this = _Component.call(this, player, options) || this; 15834 _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 15835 return _this; 15836 } 15837 /** 15838 * Create the time tooltip DOM element 15839 * 15840 * @return {Element} 15841 * The element that was created. 15842 */ 15843 15844 15845 var _proto = TimeTooltip.prototype; 15846 15847 _proto.createEl = function createEl() { 15848 return _Component.prototype.createEl.call(this, 'div', { 15849 className: 'vjs-time-tooltip' 15850 }, { 15851 'aria-hidden': 'true' 15852 }); 15853 } 15854 /** 15855 * Updates the position of the time tooltip relative to the `SeekBar`. 15856 * 15857 * @param {Object} seekBarRect 15858 * The `ClientRect` for the {@link SeekBar} element. 15859 * 15860 * @param {number} seekBarPoint 15861 * A number from 0 to 1, representing a horizontal reference point 15862 * from the left edge of the {@link SeekBar} 15863 */ 15864 ; 15865 15866 _proto.update = function update(seekBarRect, seekBarPoint, content) { 15867 var tooltipRect = getBoundingClientRect(this.el_); 15868 var playerRect = getBoundingClientRect(this.player_.el()); 15869 var seekBarPointPx = seekBarRect.width * seekBarPoint; // do nothing if either rect isn't available 15870 // for example, if the player isn't in the DOM for testing 15871 15872 if (!playerRect || !tooltipRect) { 15873 return; 15874 } // This is the space left of the `seekBarPoint` available within the bounds 15875 // of the player. We calculate any gap between the left edge of the player 15876 // and the left edge of the `SeekBar` and add the number of pixels in the 15877 // `SeekBar` before hitting the `seekBarPoint` 15878 15879 15880 var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; // This is the space right of the `seekBarPoint` available within the bounds 15881 // of the player. We calculate the number of pixels from the `seekBarPoint` 15882 // to the right edge of the `SeekBar` and add to that any gap between the 15883 // right edge of the `SeekBar` and the player. 15884 15885 var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); // This is the number of pixels by which the tooltip will need to be pulled 15886 // further to the right to center it over the `seekBarPoint`. 15887 15888 var pullTooltipBy = tooltipRect.width / 2; // Adjust the `pullTooltipBy` distance to the left or right depending on 15889 // the results of the space calculations above. 15890 15891 if (spaceLeftOfPoint < pullTooltipBy) { 15892 pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; 15893 } else if (spaceRightOfPoint < pullTooltipBy) { 15894 pullTooltipBy = spaceRightOfPoint; 15895 } // Due to the imprecision of decimal/ratio based calculations and varying 15896 // rounding behaviors, there are cases where the spacing adjustment is off 15897 // by a pixel or two. This adds insurance to these calculations. 15898 15899 15900 if (pullTooltipBy < 0) { 15901 pullTooltipBy = 0; 15902 } else if (pullTooltipBy > tooltipRect.width) { 15903 pullTooltipBy = tooltipRect.width; 15904 } 15905 15906 this.el_.style.right = "-" + pullTooltipBy + "px"; 15907 this.write(content); 15908 } 15909 /** 15910 * Write the time to the tooltip DOM element. 15911 * 15912 * @param {string} content 15913 * The formatted time for the tooltip. 15914 */ 15915 ; 15916 15917 _proto.write = function write(content) { 15918 textContent(this.el_, content); 15919 } 15920 /** 15921 * Updates the position of the time tooltip relative to the `SeekBar`. 15922 * 15923 * @param {Object} seekBarRect 15924 * The `ClientRect` for the {@link SeekBar} element. 15925 * 15926 * @param {number} seekBarPoint 15927 * A number from 0 to 1, representing a horizontal reference point 15928 * from the left edge of the {@link SeekBar} 15929 * 15930 * @param {number} time 15931 * The time to update the tooltip to, not used during live playback 15932 * 15933 * @param {Function} cb 15934 * A function that will be called during the request animation frame 15935 * for tooltips that need to do additional animations from the default 15936 */ 15937 ; 15938 15939 _proto.updateTime = function updateTime(seekBarRect, seekBarPoint, time, cb) { 15940 var _this2 = this; 15941 15942 // If there is an existing rAF ID, cancel it so we don't over-queue. 15943 if (this.rafId_) { 15944 this.cancelAnimationFrame(this.rafId_); 15945 } 15946 15947 this.rafId_ = this.requestAnimationFrame(function () { 15948 var content; 15949 15950 var duration = _this2.player_.duration(); 15951 15952 if (_this2.player_.liveTracker && _this2.player_.liveTracker.isLive()) { 15953 var liveWindow = _this2.player_.liveTracker.liveWindow(); 15954 15955 var secondsBehind = liveWindow - seekBarPoint * liveWindow; 15956 content = (secondsBehind < 1 ? '' : '-') + formatTime(secondsBehind, liveWindow); 15957 } else { 15958 content = formatTime(time, duration); 15959 } 15960 15961 _this2.update(seekBarRect, seekBarPoint, content); 15962 15963 if (cb) { 15964 cb(); 15965 } 15966 }); 15967 }; 15968 15969 return TimeTooltip; 15970 }(Component); 15971 15972 Component.registerComponent('TimeTooltip', TimeTooltip); 15973 15974 /** 15975 * Used by {@link SeekBar} to display media playback progress as part of the 15976 * {@link ProgressControl}. 15977 * 15978 * @extends Component 15979 */ 15980 15981 var PlayProgressBar = 15982 /*#__PURE__*/ 15983 function (_Component) { 15984 _inheritsLoose(PlayProgressBar, _Component); 15985 15986 /** 15987 * Creates an instance of this class. 15988 * 15989 * @param {Player} player 15990 * The {@link Player} that this class should be attached to. 15991 * 15992 * @param {Object} [options] 15993 * The key/value store of player options. 15994 */ 15995 function PlayProgressBar(player, options) { 15996 var _this; 15997 15998 _this = _Component.call(this, player, options) || this; 15999 _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 16000 return _this; 16001 } 16002 /** 16003 * Create the the DOM element for this class. 16004 * 16005 * @return {Element} 16006 * The element that was created. 16007 */ 16008 16009 16010 var _proto = PlayProgressBar.prototype; 16011 16012 _proto.createEl = function createEl() { 16013 return _Component.prototype.createEl.call(this, 'div', { 16014 className: 'vjs-play-progress vjs-slider-bar' 16015 }, { 16016 'aria-hidden': 'true' 16017 }); 16018 } 16019 /** 16020 * Enqueues updates to its own DOM as well as the DOM of its 16021 * {@link TimeTooltip} child. 16022 * 16023 * @param {Object} seekBarRect 16024 * The `ClientRect` for the {@link SeekBar} element. 16025 * 16026 * @param {number} seekBarPoint 16027 * A number from 0 to 1, representing a horizontal reference point 16028 * from the left edge of the {@link SeekBar} 16029 */ 16030 ; 16031 16032 _proto.update = function update(seekBarRect, seekBarPoint) { 16033 var timeTooltip = this.getChild('timeTooltip'); 16034 16035 if (!timeTooltip) { 16036 return; 16037 } 16038 16039 var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 16040 timeTooltip.updateTime(seekBarRect, seekBarPoint, time); 16041 }; 16042 16043 return PlayProgressBar; 16044 }(Component); 16045 /** 16046 * Default options for {@link PlayProgressBar}. 16047 * 16048 * @type {Object} 16049 * @private 16050 */ 16051 16052 16053 PlayProgressBar.prototype.options_ = { 16054 children: [] 16055 }; // Time tooltips should not be added to a player on mobile devices 16056 16057 if (!IS_IOS && !IS_ANDROID) { 16058 PlayProgressBar.prototype.options_.children.push('timeTooltip'); 16059 } 16060 16061 Component.registerComponent('PlayProgressBar', PlayProgressBar); 16062 16063 /** 16064 * The {@link MouseTimeDisplay} component tracks mouse movement over the 16065 * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} 16066 * indicating the time which is represented by a given point in the 16067 * {@link ProgressControl}. 16068 * 16069 * @extends Component 16070 */ 16071 16072 var MouseTimeDisplay = 16073 /*#__PURE__*/ 16074 function (_Component) { 16075 _inheritsLoose(MouseTimeDisplay, _Component); 16076 16077 /** 16078 * Creates an instance of this class. 16079 * 16080 * @param {Player} player 16081 * The {@link Player} that this class should be attached to. 16082 * 16083 * @param {Object} [options] 16084 * The key/value store of player options. 16085 */ 16086 function MouseTimeDisplay(player, options) { 16087 var _this; 16088 16089 _this = _Component.call(this, player, options) || this; 16090 _this.update = throttle(bind(_assertThisInitialized(_this), _this.update), UPDATE_REFRESH_INTERVAL); 16091 return _this; 16092 } 16093 /** 16094 * Create the DOM element for this class. 16095 * 16096 * @return {Element} 16097 * The element that was created. 16098 */ 16099 16100 16101 var _proto = MouseTimeDisplay.prototype; 16102 16103 _proto.createEl = function createEl() { 16104 return _Component.prototype.createEl.call(this, 'div', { 16105 className: 'vjs-mouse-display' 16106 }); 16107 } 16108 /** 16109 * Enqueues updates to its own DOM as well as the DOM of its 16110 * {@link TimeTooltip} child. 16111 * 16112 * @param {Object} seekBarRect 16113 * The `ClientRect` for the {@link SeekBar} element. 16114 * 16115 * @param {number} seekBarPoint 16116 * A number from 0 to 1, representing a horizontal reference point 16117 * from the left edge of the {@link SeekBar} 16118 */ 16119 ; 16120 16121 _proto.update = function update(seekBarRect, seekBarPoint) { 16122 var _this2 = this; 16123 16124 var time = seekBarPoint * this.player_.duration(); 16125 this.getChild('timeTooltip').updateTime(seekBarRect, seekBarPoint, time, function () { 16126 _this2.el_.style.left = seekBarRect.width * seekBarPoint + "px"; 16127 }); 16128 }; 16129 16130 return MouseTimeDisplay; 16131 }(Component); 16132 /** 16133 * Default options for `MouseTimeDisplay` 16134 * 16135 * @type {Object} 16136 * @private 16137 */ 16138 16139 16140 MouseTimeDisplay.prototype.options_ = { 16141 children: ['timeTooltip'] 16142 }; 16143 Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); 16144 16145 var STEP_SECONDS = 5; // The multiplier of STEP_SECONDS that PgUp/PgDown move the timeline. 16146 16147 var PAGE_KEY_MULTIPLIER = 12; // The interval at which the bar should update as it progresses. 16148 16149 var UPDATE_REFRESH_INTERVAL$1 = 30; 16150 /** 16151 * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} 16152 * as its `bar`. 16153 * 16154 * @extends Slider 16155 */ 16156 16157 var SeekBar = 16158 /*#__PURE__*/ 16159 function (_Slider) { 16160 _inheritsLoose(SeekBar, _Slider); 16161 16162 /** 16163 * Creates an instance of this class. 16164 * 16165 * @param {Player} player 16166 * The `Player` that this class should be attached to. 16167 * 16168 * @param {Object} [options] 16169 * The key/value store of player options. 16170 */ 16171 function SeekBar(player, options) { 16172 var _this; 16173 16174 _this = _Slider.call(this, player, options) || this; 16175 16176 _this.setEventHandlers_(); 16177 16178 return _this; 16179 } 16180 /** 16181 * Sets the event handlers 16182 * 16183 * @private 16184 */ 16185 16186 16187 var _proto = SeekBar.prototype; 16188 16189 _proto.setEventHandlers_ = function setEventHandlers_() { 16190 this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL$1); 16191 this.on(this.player_, 'timeupdate', this.update); 16192 this.on(this.player_, 'ended', this.handleEnded); 16193 this.on(this.player_, 'durationchange', this.update); 16194 16195 if (this.player_.liveTracker) { 16196 this.on(this.player_.liveTracker, 'liveedgechange', this.update); 16197 } // when playing, let's ensure we smoothly update the play progress bar 16198 // via an interval 16199 16200 16201 this.updateInterval = null; 16202 this.on(this.player_, ['playing'], this.enableInterval_); 16203 this.on(this.player_, ['ended', 'pause', 'waiting'], this.disableInterval_); // we don't need to update the play progress if the document is hidden, 16204 // also, this causes the CPU to spike and eventually crash the page on IE11. 16205 16206 if ('hidden' in document && 'visibilityState' in document) { 16207 this.on(document, 'visibilitychange', this.toggleVisibility_); 16208 } 16209 }; 16210 16211 _proto.toggleVisibility_ = function toggleVisibility_(e) { 16212 if (document.hidden) { 16213 this.disableInterval_(e); 16214 } else { 16215 this.enableInterval_(); // we just switched back to the page and someone may be looking, so, update ASAP 16216 16217 this.requestAnimationFrame(this.update); 16218 } 16219 }; 16220 16221 _proto.enableInterval_ = function enableInterval_() { 16222 var _this2 = this; 16223 16224 this.clearInterval(this.updateInterval); 16225 this.updateInterval = this.setInterval(function () { 16226 _this2.requestAnimationFrame(_this2.update); 16227 }, UPDATE_REFRESH_INTERVAL$1); 16228 }; 16229 16230 _proto.disableInterval_ = function disableInterval_(e) { 16231 if (this.player_.liveTracker && this.player_.liveTracker.isLive() && e.type !== 'ended') { 16232 return; 16233 } 16234 16235 this.clearInterval(this.updateInterval); 16236 } 16237 /** 16238 * Create the `Component`'s DOM element 16239 * 16240 * @return {Element} 16241 * The element that was created. 16242 */ 16243 ; 16244 16245 _proto.createEl = function createEl() { 16246 return _Slider.prototype.createEl.call(this, 'div', { 16247 className: 'vjs-progress-holder' 16248 }, { 16249 'aria-label': this.localize('Progress Bar') 16250 }); 16251 } 16252 /** 16253 * This function updates the play progress bar and accessibility 16254 * attributes to whatever is passed in. 16255 * 16256 * @param {number} currentTime 16257 * The currentTime value that should be used for accessibility 16258 * 16259 * @param {number} percent 16260 * The percentage as a decimal that the bar should be filled from 0-1. 16261 * 16262 * @private 16263 */ 16264 ; 16265 16266 _proto.update_ = function update_(currentTime, percent) { 16267 var liveTracker = this.player_.liveTracker; 16268 var duration = this.player_.duration(); 16269 16270 if (liveTracker && liveTracker.isLive()) { 16271 duration = this.player_.liveTracker.liveCurrentTime(); 16272 } // machine readable value of progress bar (percentage complete) 16273 16274 16275 this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); // human readable value of progress bar (time complete) 16276 16277 this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}')); // Update the `PlayProgressBar`. 16278 16279 if (this.bar) { 16280 this.bar.update(getBoundingClientRect(this.el_), percent); 16281 } 16282 } 16283 /** 16284 * Update the seek bar's UI. 16285 * 16286 * @param {EventTarget~Event} [event] 16287 * The `timeupdate` or `ended` event that caused this to run. 16288 * 16289 * @listens Player#timeupdate 16290 * 16291 * @return {number} 16292 * The current percent at a number from 0-1 16293 */ 16294 ; 16295 16296 _proto.update = function update(event) { 16297 // if the offsetParent is null, then this element is hidden, in which case 16298 // we don't need to update it. 16299 if (this.el().offsetParent === null) { 16300 return; 16301 } 16302 16303 var percent = _Slider.prototype.update.call(this); 16304 16305 this.update_(this.getCurrentTime_(), percent); 16306 return percent; 16307 } 16308 /** 16309 * Get the value of current time but allows for smooth scrubbing, 16310 * when player can't keep up. 16311 * 16312 * @return {number} 16313 * The current time value to display 16314 * 16315 * @private 16316 */ 16317 ; 16318 16319 _proto.getCurrentTime_ = function getCurrentTime_() { 16320 return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); 16321 } 16322 /** 16323 * We want the seek bar to be full on ended 16324 * no matter what the actual internal values are. so we force it. 16325 * 16326 * @param {EventTarget~Event} [event] 16327 * The `timeupdate` or `ended` event that caused this to run. 16328 * 16329 * @listens Player#ended 16330 */ 16331 ; 16332 16333 _proto.handleEnded = function handleEnded(event) { 16334 this.update_(this.player_.duration(), 1); 16335 } 16336 /** 16337 * Get the percentage of media played so far. 16338 * 16339 * @return {number} 16340 * The percentage of media played so far (0 to 1). 16341 */ 16342 ; 16343 16344 _proto.getPercent = function getPercent() { 16345 var currentTime = this.getCurrentTime_(); 16346 var percent; 16347 var liveTracker = this.player_.liveTracker; 16348 16349 if (liveTracker && liveTracker.isLive()) { 16350 percent = (currentTime - liveTracker.seekableStart()) / liveTracker.liveWindow(); // prevent the percent from changing at the live edge 16351 16352 if (liveTracker.atLiveEdge()) { 16353 percent = 1; 16354 } 16355 } else { 16356 percent = currentTime / this.player_.duration(); 16357 } 16358 16359 return percent >= 1 ? 1 : percent || 0; 16360 } 16361 /** 16362 * Handle mouse down on seek bar 16363 * 16364 * @param {EventTarget~Event} event 16365 * The `mousedown` event that caused this to run. 16366 * 16367 * @listens mousedown 16368 */ 16369 ; 16370 16371 _proto.handleMouseDown = function handleMouseDown(event) { 16372 if (!isSingleLeftClick(event)) { 16373 return; 16374 } // Stop event propagation to prevent double fire in progress-control.js 16375 16376 16377 event.stopPropagation(); 16378 this.player_.scrubbing(true); 16379 this.videoWasPlaying = !this.player_.paused(); 16380 this.player_.pause(); 16381 16382 _Slider.prototype.handleMouseDown.call(this, event); 16383 } 16384 /** 16385 * Handle mouse move on seek bar 16386 * 16387 * @param {EventTarget~Event} event 16388 * The `mousemove` event that caused this to run. 16389 * 16390 * @listens mousemove 16391 */ 16392 ; 16393 16394 _proto.handleMouseMove = function handleMouseMove(event) { 16395 if (!isSingleLeftClick(event)) { 16396 return; 16397 } 16398 16399 var newTime; 16400 var distance = this.calculateDistance(event); 16401 var liveTracker = this.player_.liveTracker; 16402 16403 if (!liveTracker || !liveTracker.isLive()) { 16404 newTime = distance * this.player_.duration(); // Don't let video end while scrubbing. 16405 16406 if (newTime === this.player_.duration()) { 16407 newTime = newTime - 0.1; 16408 } 16409 } else { 16410 var seekableStart = liveTracker.seekableStart(); 16411 var seekableEnd = liveTracker.liveCurrentTime(); 16412 newTime = seekableStart + distance * liveTracker.liveWindow(); // Don't let video end while scrubbing. 16413 16414 if (newTime >= seekableEnd) { 16415 newTime = seekableEnd; 16416 } // Compensate for precision differences so that currentTime is not less 16417 // than seekable start 16418 16419 16420 if (newTime <= seekableStart) { 16421 newTime = seekableStart + 0.1; 16422 } // On android seekableEnd can be Infinity sometimes, 16423 // this will cause newTime to be Infinity, which is 16424 // not a valid currentTime. 16425 16426 16427 if (newTime === Infinity) { 16428 return; 16429 } 16430 } // Set new time (tell player to seek to new time) 16431 16432 16433 this.player_.currentTime(newTime); 16434 }; 16435 16436 _proto.enable = function enable() { 16437 _Slider.prototype.enable.call(this); 16438 16439 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 16440 16441 if (!mouseTimeDisplay) { 16442 return; 16443 } 16444 16445 mouseTimeDisplay.show(); 16446 }; 16447 16448 _proto.disable = function disable() { 16449 _Slider.prototype.disable.call(this); 16450 16451 var mouseTimeDisplay = this.getChild('mouseTimeDisplay'); 16452 16453 if (!mouseTimeDisplay) { 16454 return; 16455 } 16456 16457 mouseTimeDisplay.hide(); 16458 } 16459 /** 16460 * Handle mouse up on seek bar 16461 * 16462 * @param {EventTarget~Event} event 16463 * The `mouseup` event that caused this to run. 16464 * 16465 * @listens mouseup 16466 */ 16467 ; 16468 16469 _proto.handleMouseUp = function handleMouseUp(event) { 16470 _Slider.prototype.handleMouseUp.call(this, event); // Stop event propagation to prevent double fire in progress-control.js 16471 16472 16473 if (event) { 16474 event.stopPropagation(); 16475 } 16476 16477 this.player_.scrubbing(false); 16478 /** 16479 * Trigger timeupdate because we're done seeking and the time has changed. 16480 * This is particularly useful for if the player is paused to time the time displays. 16481 * 16482 * @event Tech#timeupdate 16483 * @type {EventTarget~Event} 16484 */ 16485 16486 this.player_.trigger({ 16487 type: 'timeupdate', 16488 target: this, 16489 manuallyTriggered: true 16490 }); 16491 16492 if (this.videoWasPlaying) { 16493 silencePromise(this.player_.play()); 16494 } 16495 } 16496 /** 16497 * Move more quickly fast forward for keyboard-only users 16498 */ 16499 ; 16500 16501 _proto.stepForward = function stepForward() { 16502 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); 16503 } 16504 /** 16505 * Move more quickly rewind for keyboard-only users 16506 */ 16507 ; 16508 16509 _proto.stepBack = function stepBack() { 16510 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); 16511 } 16512 /** 16513 * Toggles the playback state of the player 16514 * This gets called when enter or space is used on the seekbar 16515 * 16516 * @param {EventTarget~Event} event 16517 * The `keydown` event that caused this function to be called 16518 * 16519 */ 16520 ; 16521 16522 _proto.handleAction = function handleAction(event) { 16523 if (this.player_.paused()) { 16524 this.player_.play(); 16525 } else { 16526 this.player_.pause(); 16527 } 16528 } 16529 /** 16530 * Called when this SeekBar has focus and a key gets pressed down. 16531 * Supports the following keys: 16532 * 16533 * Space or Enter key fire a click event 16534 * Home key moves to start of the timeline 16535 * End key moves to end of the timeline 16536 * Digit "0" through "9" keys move to 0%, 10% ... 80%, 90% of the timeline 16537 * PageDown key moves back a larger step than ArrowDown 16538 * PageUp key moves forward a large step 16539 * 16540 * @param {EventTarget~Event} event 16541 * The `keydown` event that caused this function to be called. 16542 * 16543 * @listens keydown 16544 */ 16545 ; 16546 16547 _proto.handleKeyDown = function handleKeyDown(event) { 16548 if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { 16549 event.preventDefault(); 16550 event.stopPropagation(); 16551 this.handleAction(event); 16552 } else if (keycode.isEventKey(event, 'Home')) { 16553 event.preventDefault(); 16554 event.stopPropagation(); 16555 this.player_.currentTime(0); 16556 } else if (keycode.isEventKey(event, 'End')) { 16557 event.preventDefault(); 16558 event.stopPropagation(); 16559 this.player_.currentTime(this.player_.duration()); 16560 } else if (/^[0-9]$/.test(keycode(event))) { 16561 event.preventDefault(); 16562 event.stopPropagation(); 16563 var gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0; 16564 this.player_.currentTime(this.player_.duration() * gotoFraction); 16565 } else if (keycode.isEventKey(event, 'PgDn')) { 16566 event.preventDefault(); 16567 event.stopPropagation(); 16568 this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS * PAGE_KEY_MULTIPLIER); 16569 } else if (keycode.isEventKey(event, 'PgUp')) { 16570 event.preventDefault(); 16571 event.stopPropagation(); 16572 this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS * PAGE_KEY_MULTIPLIER); 16573 } else { 16574 // Pass keydown handling up for unsupported keys 16575 _Slider.prototype.handleKeyDown.call(this, event); 16576 } 16577 }; 16578 16579 return SeekBar; 16580 }(Slider); 16581 /** 16582 * Default options for the `SeekBar` 16583 * 16584 * @type {Object} 16585 * @private 16586 */ 16587 16588 16589 SeekBar.prototype.options_ = { 16590 children: ['loadProgressBar', 'playProgressBar'], 16591 barName: 'playProgressBar' 16592 }; // MouseTimeDisplay tooltips should not be added to a player on mobile devices 16593 16594 if (!IS_IOS && !IS_ANDROID) { 16595 SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); 16596 } 16597 16598 Component.registerComponent('SeekBar', SeekBar); 16599 16600 /** 16601 * The Progress Control component contains the seek bar, load progress, 16602 * and play progress. 16603 * 16604 * @extends Component 16605 */ 16606 16607 var ProgressControl = 16608 /*#__PURE__*/ 16609 function (_Component) { 16610 _inheritsLoose(ProgressControl, _Component); 16611 16612 /** 16613 * Creates an instance of this class. 16614 * 16615 * @param {Player} player 16616 * The `Player` that this class should be attached to. 16617 * 16618 * @param {Object} [options] 16619 * The key/value store of player options. 16620 */ 16621 function ProgressControl(player, options) { 16622 var _this; 16623 16624 _this = _Component.call(this, player, options) || this; 16625 _this.handleMouseMove = throttle(bind(_assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 16626 _this.throttledHandleMouseSeek = throttle(bind(_assertThisInitialized(_this), _this.handleMouseSeek), UPDATE_REFRESH_INTERVAL); 16627 16628 _this.enable(); 16629 16630 return _this; 16631 } 16632 /** 16633 * Create the `Component`'s DOM element 16634 * 16635 * @return {Element} 16636 * The element that was created. 16637 */ 16638 16639 16640 var _proto = ProgressControl.prototype; 16641 16642 _proto.createEl = function createEl() { 16643 return _Component.prototype.createEl.call(this, 'div', { 16644 className: 'vjs-progress-control vjs-control' 16645 }); 16646 } 16647 /** 16648 * When the mouse moves over the `ProgressControl`, the pointer position 16649 * gets passed down to the `MouseTimeDisplay` component. 16650 * 16651 * @param {EventTarget~Event} event 16652 * The `mousemove` event that caused this function to run. 16653 * 16654 * @listen mousemove 16655 */ 16656 ; 16657 16658 _proto.handleMouseMove = function handleMouseMove(event) { 16659 var seekBar = this.getChild('seekBar'); 16660 16661 if (seekBar) { 16662 var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); 16663 var seekBarEl = seekBar.el(); 16664 var seekBarRect = getBoundingClientRect(seekBarEl); 16665 var seekBarPoint = getPointerPosition(seekBarEl, event).x; // The default skin has a gap on either side of the `SeekBar`. This means 16666 // that it's possible to trigger this behavior outside the boundaries of 16667 // the `SeekBar`. This ensures we stay within it at all times. 16668 16669 if (seekBarPoint > 1) { 16670 seekBarPoint = 1; 16671 } else if (seekBarPoint < 0) { 16672 seekBarPoint = 0; 16673 } 16674 16675 if (mouseTimeDisplay) { 16676 mouseTimeDisplay.update(seekBarRect, seekBarPoint); 16677 } 16678 } 16679 } 16680 /** 16681 * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. 16682 * 16683 * @method ProgressControl#throttledHandleMouseSeek 16684 * @param {EventTarget~Event} event 16685 * The `mousemove` event that caused this function to run. 16686 * 16687 * @listen mousemove 16688 * @listen touchmove 16689 */ 16690 16691 /** 16692 * Handle `mousemove` or `touchmove` events on the `ProgressControl`. 16693 * 16694 * @param {EventTarget~Event} event 16695 * `mousedown` or `touchstart` event that triggered this function 16696 * 16697 * @listens mousemove 16698 * @listens touchmove 16699 */ 16700 ; 16701 16702 _proto.handleMouseSeek = function handleMouseSeek(event) { 16703 var seekBar = this.getChild('seekBar'); 16704 16705 if (seekBar) { 16706 seekBar.handleMouseMove(event); 16707 } 16708 } 16709 /** 16710 * Are controls are currently enabled for this progress control. 16711 * 16712 * @return {boolean} 16713 * true if controls are enabled, false otherwise 16714 */ 16715 ; 16716 16717 _proto.enabled = function enabled() { 16718 return this.enabled_; 16719 } 16720 /** 16721 * Disable all controls on the progress control and its children 16722 */ 16723 ; 16724 16725 _proto.disable = function disable() { 16726 this.children().forEach(function (child) { 16727 return child.disable && child.disable(); 16728 }); 16729 16730 if (!this.enabled()) { 16731 return; 16732 } 16733 16734 this.off(['mousedown', 'touchstart'], this.handleMouseDown); 16735 this.off(this.el_, 'mousemove', this.handleMouseMove); 16736 this.handleMouseUp(); 16737 this.addClass('disabled'); 16738 this.enabled_ = false; 16739 } 16740 /** 16741 * Enable all controls on the progress control and its children 16742 */ 16743 ; 16744 16745 _proto.enable = function enable() { 16746 this.children().forEach(function (child) { 16747 return child.enable && child.enable(); 16748 }); 16749 16750 if (this.enabled()) { 16751 return; 16752 } 16753 16754 this.on(['mousedown', 'touchstart'], this.handleMouseDown); 16755 this.on(this.el_, 'mousemove', this.handleMouseMove); 16756 this.removeClass('disabled'); 16757 this.enabled_ = true; 16758 } 16759 /** 16760 * Handle `mousedown` or `touchstart` events on the `ProgressControl`. 16761 * 16762 * @param {EventTarget~Event} event 16763 * `mousedown` or `touchstart` event that triggered this function 16764 * 16765 * @listens mousedown 16766 * @listens touchstart 16767 */ 16768 ; 16769 16770 _proto.handleMouseDown = function handleMouseDown(event) { 16771 var doc = this.el_.ownerDocument; 16772 var seekBar = this.getChild('seekBar'); 16773 16774 if (seekBar) { 16775 seekBar.handleMouseDown(event); 16776 } 16777 16778 this.on(doc, 'mousemove', this.throttledHandleMouseSeek); 16779 this.on(doc, 'touchmove', this.throttledHandleMouseSeek); 16780 this.on(doc, 'mouseup', this.handleMouseUp); 16781 this.on(doc, 'touchend', this.handleMouseUp); 16782 } 16783 /** 16784 * Handle `mouseup` or `touchend` events on the `ProgressControl`. 16785 * 16786 * @param {EventTarget~Event} event 16787 * `mouseup` or `touchend` event that triggered this function. 16788 * 16789 * @listens touchend 16790 * @listens mouseup 16791 */ 16792 ; 16793 16794 _proto.handleMouseUp = function handleMouseUp(event) { 16795 var doc = this.el_.ownerDocument; 16796 var seekBar = this.getChild('seekBar'); 16797 16798 if (seekBar) { 16799 seekBar.handleMouseUp(event); 16800 } 16801 16802 this.off(doc, 'mousemove', this.throttledHandleMouseSeek); 16803 this.off(doc, 'touchmove', this.throttledHandleMouseSeek); 16804 this.off(doc, 'mouseup', this.handleMouseUp); 16805 this.off(doc, 'touchend', this.handleMouseUp); 16806 }; 16807 16808 return ProgressControl; 16809 }(Component); 16810 /** 16811 * Default options for `ProgressControl` 16812 * 16813 * @type {Object} 16814 * @private 16815 */ 16816 16817 16818 ProgressControl.prototype.options_ = { 16819 children: ['seekBar'] 16820 }; 16821 Component.registerComponent('ProgressControl', ProgressControl); 16822 16823 /** 16824 * Toggle Picture-in-Picture mode 16825 * 16826 * @extends Button 16827 */ 16828 16829 var PictureInPictureToggle = 16830 /*#__PURE__*/ 16831 function (_Button) { 16832 _inheritsLoose(PictureInPictureToggle, _Button); 16833 16834 /** 16835 * Creates an instance of this class. 16836 * 16837 * @param {Player} player 16838 * The `Player` that this class should be attached to. 16839 * 16840 * @param {Object} [options] 16841 * The key/value store of player options. 16842 * 16843 * @listens Player#enterpictureinpicture 16844 * @listens Player#leavepictureinpicture 16845 */ 16846 function PictureInPictureToggle(player, options) { 16847 var _this; 16848 16849 _this = _Button.call(this, player, options) || this; 16850 16851 _this.on(player, ['enterpictureinpicture', 'leavepictureinpicture'], _this.handlePictureInPictureChange); // TODO: Activate button on player loadedmetadata event. 16852 // TODO: Deactivate button on player emptied event. 16853 // TODO: Deactivate button if disablepictureinpicture attribute is present. 16854 16855 16856 if (!document.pictureInPictureEnabled) { 16857 _this.disable(); 16858 } 16859 16860 return _this; 16861 } 16862 /** 16863 * Builds the default DOM `className`. 16864 * 16865 * @return {string} 16866 * The DOM `className` for this object. 16867 */ 16868 16869 16870 var _proto = PictureInPictureToggle.prototype; 16871 16872 _proto.buildCSSClass = function buildCSSClass() { 16873 return "vjs-picture-in-picture-control " + _Button.prototype.buildCSSClass.call(this); 16874 } 16875 /** 16876 * Handles enterpictureinpicture and leavepictureinpicture on the player and change control text accordingly. 16877 * 16878 * @param {EventTarget~Event} [event] 16879 * The {@link Player#enterpictureinpicture} or {@link Player#leavepictureinpicture} event that caused this function to be 16880 * called. 16881 * 16882 * @listens Player#enterpictureinpicture 16883 * @listens Player#leavepictureinpicture 16884 */ 16885 ; 16886 16887 _proto.handlePictureInPictureChange = function handlePictureInPictureChange(event) { 16888 if (this.player_.isInPictureInPicture()) { 16889 this.controlText('Exit Picture-in-Picture'); 16890 } else { 16891 this.controlText('Picture-in-Picture'); 16892 } 16893 } 16894 /** 16895 * This gets called when an `PictureInPictureToggle` is "clicked". See 16896 * {@link ClickableComponent} for more detailed information on what a click can be. 16897 * 16898 * @param {EventTarget~Event} [event] 16899 * The `keydown`, `tap`, or `click` event that caused this function to be 16900 * called. 16901 * 16902 * @listens tap 16903 * @listens click 16904 */ 16905 ; 16906 16907 _proto.handleClick = function handleClick(event) { 16908 if (!this.player_.isInPictureInPicture()) { 16909 this.player_.requestPictureInPicture(); 16910 } else { 16911 this.player_.exitPictureInPicture(); 16912 } 16913 }; 16914 16915 return PictureInPictureToggle; 16916 }(Button); 16917 /** 16918 * The text that should display over the `PictureInPictureToggle`s controls. Added for localization. 16919 * 16920 * @type {string} 16921 * @private 16922 */ 16923 16924 16925 PictureInPictureToggle.prototype.controlText_ = 'Picture-in-Picture'; 16926 Component.registerComponent('PictureInPictureToggle', PictureInPictureToggle); 16927 16928 /** 16929 * Toggle fullscreen video 16930 * 16931 * @extends Button 16932 */ 16933 16934 var FullscreenToggle = 16935 /*#__PURE__*/ 16936 function (_Button) { 16937 _inheritsLoose(FullscreenToggle, _Button); 16938 16939 /** 16940 * Creates an instance of this class. 16941 * 16942 * @param {Player} player 16943 * The `Player` that this class should be attached to. 16944 * 16945 * @param {Object} [options] 16946 * The key/value store of player options. 16947 */ 16948 function FullscreenToggle(player, options) { 16949 var _this; 16950 16951 _this = _Button.call(this, player, options) || this; 16952 16953 _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); 16954 16955 if (document[player.fsApi_.fullscreenEnabled] === false) { 16956 _this.disable(); 16957 } 16958 16959 return _this; 16960 } 16961 /** 16962 * Builds the default DOM `className`. 16963 * 16964 * @return {string} 16965 * The DOM `className` for this object. 16966 */ 16967 16968 16969 var _proto = FullscreenToggle.prototype; 16970 16971 _proto.buildCSSClass = function buildCSSClass() { 16972 return "vjs-fullscreen-control " + _Button.prototype.buildCSSClass.call(this); 16973 } 16974 /** 16975 * Handles fullscreenchange on the player and change control text accordingly. 16976 * 16977 * @param {EventTarget~Event} [event] 16978 * The {@link Player#fullscreenchange} event that caused this function to be 16979 * called. 16980 * 16981 * @listens Player#fullscreenchange 16982 */ 16983 ; 16984 16985 _proto.handleFullscreenChange = function handleFullscreenChange(event) { 16986 if (this.player_.isFullscreen()) { 16987 this.controlText('Non-Fullscreen'); 16988 } else { 16989 this.controlText('Fullscreen'); 16990 } 16991 } 16992 /** 16993 * This gets called when an `FullscreenToggle` is "clicked". See 16994 * {@link ClickableComponent} for more detailed information on what a click can be. 16995 * 16996 * @param {EventTarget~Event} [event] 16997 * The `keydown`, `tap`, or `click` event that caused this function to be 16998 * called. 16999 * 17000 * @listens tap 17001 * @listens click 17002 */ 17003 ; 17004 17005 _proto.handleClick = function handleClick(event) { 17006 if (!this.player_.isFullscreen()) { 17007 this.player_.requestFullscreen(); 17008 } else { 17009 this.player_.exitFullscreen(); 17010 } 17011 }; 17012 17013 return FullscreenToggle; 17014 }(Button); 17015 /** 17016 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 17017 * 17018 * @type {string} 17019 * @private 17020 */ 17021 17022 17023 FullscreenToggle.prototype.controlText_ = 'Fullscreen'; 17024 Component.registerComponent('FullscreenToggle', FullscreenToggle); 17025 17026 /** 17027 * Check if volume control is supported and if it isn't hide the 17028 * `Component` that was passed using the `vjs-hidden` class. 17029 * 17030 * @param {Component} self 17031 * The component that should be hidden if volume is unsupported 17032 * 17033 * @param {Player} player 17034 * A reference to the player 17035 * 17036 * @private 17037 */ 17038 var checkVolumeSupport = function checkVolumeSupport(self, player) { 17039 // hide volume controls when they're not supported by the current tech 17040 if (player.tech_ && !player.tech_.featuresVolumeControl) { 17041 self.addClass('vjs-hidden'); 17042 } 17043 17044 self.on(player, 'loadstart', function () { 17045 if (!player.tech_.featuresVolumeControl) { 17046 self.addClass('vjs-hidden'); 17047 } else { 17048 self.removeClass('vjs-hidden'); 17049 } 17050 }); 17051 }; 17052 17053 /** 17054 * Shows volume level 17055 * 17056 * @extends Component 17057 */ 17058 17059 var VolumeLevel = 17060 /*#__PURE__*/ 17061 function (_Component) { 17062 _inheritsLoose(VolumeLevel, _Component); 17063 17064 function VolumeLevel() { 17065 return _Component.apply(this, arguments) || this; 17066 } 17067 17068 var _proto = VolumeLevel.prototype; 17069 17070 /** 17071 * Create the `Component`'s DOM element 17072 * 17073 * @return {Element} 17074 * The element that was created. 17075 */ 17076 _proto.createEl = function createEl() { 17077 return _Component.prototype.createEl.call(this, 'div', { 17078 className: 'vjs-volume-level', 17079 innerHTML: '<span class="vjs-control-text"></span>' 17080 }); 17081 }; 17082 17083 return VolumeLevel; 17084 }(Component); 17085 17086 Component.registerComponent('VolumeLevel', VolumeLevel); 17087 17088 /** 17089 * The bar that contains the volume level and can be clicked on to adjust the level 17090 * 17091 * @extends Slider 17092 */ 17093 17094 var VolumeBar = 17095 /*#__PURE__*/ 17096 function (_Slider) { 17097 _inheritsLoose(VolumeBar, _Slider); 17098 17099 /** 17100 * Creates an instance of this class. 17101 * 17102 * @param {Player} player 17103 * The `Player` that this class should be attached to. 17104 * 17105 * @param {Object} [options] 17106 * The key/value store of player options. 17107 */ 17108 function VolumeBar(player, options) { 17109 var _this; 17110 17111 _this = _Slider.call(this, player, options) || this; 17112 17113 _this.on('slideractive', _this.updateLastVolume_); 17114 17115 _this.on(player, 'volumechange', _this.updateARIAAttributes); 17116 17117 player.ready(function () { 17118 return _this.updateARIAAttributes(); 17119 }); 17120 return _this; 17121 } 17122 /** 17123 * Create the `Component`'s DOM element 17124 * 17125 * @return {Element} 17126 * The element that was created. 17127 */ 17128 17129 17130 var _proto = VolumeBar.prototype; 17131 17132 _proto.createEl = function createEl() { 17133 return _Slider.prototype.createEl.call(this, 'div', { 17134 className: 'vjs-volume-bar vjs-slider-bar' 17135 }, { 17136 'aria-label': this.localize('Volume Level'), 17137 'aria-live': 'polite' 17138 }); 17139 } 17140 /** 17141 * Handle mouse down on volume bar 17142 * 17143 * @param {EventTarget~Event} event 17144 * The `mousedown` event that caused this to run. 17145 * 17146 * @listens mousedown 17147 */ 17148 ; 17149 17150 _proto.handleMouseDown = function handleMouseDown(event) { 17151 if (!isSingleLeftClick(event)) { 17152 return; 17153 } 17154 17155 _Slider.prototype.handleMouseDown.call(this, event); 17156 } 17157 /** 17158 * Handle movement events on the {@link VolumeMenuButton}. 17159 * 17160 * @param {EventTarget~Event} event 17161 * The event that caused this function to run. 17162 * 17163 * @listens mousemove 17164 */ 17165 ; 17166 17167 _proto.handleMouseMove = function handleMouseMove(event) { 17168 if (!isSingleLeftClick(event)) { 17169 return; 17170 } 17171 17172 this.checkMuted(); 17173 this.player_.volume(this.calculateDistance(event)); 17174 } 17175 /** 17176 * If the player is muted unmute it. 17177 */ 17178 ; 17179 17180 _proto.checkMuted = function checkMuted() { 17181 if (this.player_.muted()) { 17182 this.player_.muted(false); 17183 } 17184 } 17185 /** 17186 * Get percent of volume level 17187 * 17188 * @return {number} 17189 * Volume level percent as a decimal number. 17190 */ 17191 ; 17192 17193 _proto.getPercent = function getPercent() { 17194 if (this.player_.muted()) { 17195 return 0; 17196 } 17197 17198 return this.player_.volume(); 17199 } 17200 /** 17201 * Increase volume level for keyboard users 17202 */ 17203 ; 17204 17205 _proto.stepForward = function stepForward() { 17206 this.checkMuted(); 17207 this.player_.volume(this.player_.volume() + 0.1); 17208 } 17209 /** 17210 * Decrease volume level for keyboard users 17211 */ 17212 ; 17213 17214 _proto.stepBack = function stepBack() { 17215 this.checkMuted(); 17216 this.player_.volume(this.player_.volume() - 0.1); 17217 } 17218 /** 17219 * Update ARIA accessibility attributes 17220 * 17221 * @param {EventTarget~Event} [event] 17222 * The `volumechange` event that caused this function to run. 17223 * 17224 * @listens Player#volumechange 17225 */ 17226 ; 17227 17228 _proto.updateARIAAttributes = function updateARIAAttributes(event) { 17229 var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); 17230 this.el_.setAttribute('aria-valuenow', ariaValue); 17231 this.el_.setAttribute('aria-valuetext', ariaValue + '%'); 17232 } 17233 /** 17234 * Returns the current value of the player volume as a percentage 17235 * 17236 * @private 17237 */ 17238 ; 17239 17240 _proto.volumeAsPercentage_ = function volumeAsPercentage_() { 17241 return Math.round(this.player_.volume() * 100); 17242 } 17243 /** 17244 * When user starts dragging the VolumeBar, store the volume and listen for 17245 * the end of the drag. When the drag ends, if the volume was set to zero, 17246 * set lastVolume to the stored volume. 17247 * 17248 * @listens slideractive 17249 * @private 17250 */ 17251 ; 17252 17253 _proto.updateLastVolume_ = function updateLastVolume_() { 17254 var _this2 = this; 17255 17256 var volumeBeforeDrag = this.player_.volume(); 17257 this.one('sliderinactive', function () { 17258 if (_this2.player_.volume() === 0) { 17259 _this2.player_.lastVolume_(volumeBeforeDrag); 17260 } 17261 }); 17262 }; 17263 17264 return VolumeBar; 17265 }(Slider); 17266 /** 17267 * Default options for the `VolumeBar` 17268 * 17269 * @type {Object} 17270 * @private 17271 */ 17272 17273 17274 VolumeBar.prototype.options_ = { 17275 children: ['volumeLevel'], 17276 barName: 'volumeLevel' 17277 }; 17278 /** 17279 * Call the update event for this Slider when this event happens on the player. 17280 * 17281 * @type {string} 17282 */ 17283 17284 VolumeBar.prototype.playerEvent = 'volumechange'; 17285 Component.registerComponent('VolumeBar', VolumeBar); 17286 17287 /** 17288 * The component for controlling the volume level 17289 * 17290 * @extends Component 17291 */ 17292 17293 var VolumeControl = 17294 /*#__PURE__*/ 17295 function (_Component) { 17296 _inheritsLoose(VolumeControl, _Component); 17297 17298 /** 17299 * Creates an instance of this class. 17300 * 17301 * @param {Player} player 17302 * The `Player` that this class should be attached to. 17303 * 17304 * @param {Object} [options={}] 17305 * The key/value store of player options. 17306 */ 17307 function VolumeControl(player, options) { 17308 var _this; 17309 17310 if (options === void 0) { 17311 options = {}; 17312 } 17313 17314 options.vertical = options.vertical || false; // Pass the vertical option down to the VolumeBar if 17315 // the VolumeBar is turned on. 17316 17317 if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) { 17318 options.volumeBar = options.volumeBar || {}; 17319 options.volumeBar.vertical = options.vertical; 17320 } 17321 17322 _this = _Component.call(this, player, options) || this; // hide this control if volume support is missing 17323 17324 checkVolumeSupport(_assertThisInitialized(_this), player); 17325 _this.throttledHandleMouseMove = throttle(bind(_assertThisInitialized(_this), _this.handleMouseMove), UPDATE_REFRESH_INTERVAL); 17326 17327 _this.on('mousedown', _this.handleMouseDown); 17328 17329 _this.on('touchstart', _this.handleMouseDown); // while the slider is active (the mouse has been pressed down and 17330 // is dragging) or in focus we do not want to hide the VolumeBar 17331 17332 17333 _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { 17334 _this.volumeBar.addClass('vjs-slider-active'); 17335 17336 _this.addClass('vjs-slider-active'); 17337 17338 _this.trigger('slideractive'); 17339 }); 17340 17341 _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { 17342 _this.volumeBar.removeClass('vjs-slider-active'); 17343 17344 _this.removeClass('vjs-slider-active'); 17345 17346 _this.trigger('sliderinactive'); 17347 }); 17348 17349 return _this; 17350 } 17351 /** 17352 * Create the `Component`'s DOM element 17353 * 17354 * @return {Element} 17355 * The element that was created. 17356 */ 17357 17358 17359 var _proto = VolumeControl.prototype; 17360 17361 _proto.createEl = function createEl() { 17362 var orientationClass = 'vjs-volume-horizontal'; 17363 17364 if (this.options_.vertical) { 17365 orientationClass = 'vjs-volume-vertical'; 17366 } 17367 17368 return _Component.prototype.createEl.call(this, 'div', { 17369 className: "vjs-volume-control vjs-control " + orientationClass 17370 }); 17371 } 17372 /** 17373 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 17374 * 17375 * @param {EventTarget~Event} event 17376 * `mousedown` or `touchstart` event that triggered this function 17377 * 17378 * @listens mousedown 17379 * @listens touchstart 17380 */ 17381 ; 17382 17383 _proto.handleMouseDown = function handleMouseDown(event) { 17384 var doc = this.el_.ownerDocument; 17385 this.on(doc, 'mousemove', this.throttledHandleMouseMove); 17386 this.on(doc, 'touchmove', this.throttledHandleMouseMove); 17387 this.on(doc, 'mouseup', this.handleMouseUp); 17388 this.on(doc, 'touchend', this.handleMouseUp); 17389 } 17390 /** 17391 * Handle `mouseup` or `touchend` events on the `VolumeControl`. 17392 * 17393 * @param {EventTarget~Event} event 17394 * `mouseup` or `touchend` event that triggered this function. 17395 * 17396 * @listens touchend 17397 * @listens mouseup 17398 */ 17399 ; 17400 17401 _proto.handleMouseUp = function handleMouseUp(event) { 17402 var doc = this.el_.ownerDocument; 17403 this.off(doc, 'mousemove', this.throttledHandleMouseMove); 17404 this.off(doc, 'touchmove', this.throttledHandleMouseMove); 17405 this.off(doc, 'mouseup', this.handleMouseUp); 17406 this.off(doc, 'touchend', this.handleMouseUp); 17407 } 17408 /** 17409 * Handle `mousedown` or `touchstart` events on the `VolumeControl`. 17410 * 17411 * @param {EventTarget~Event} event 17412 * `mousedown` or `touchstart` event that triggered this function 17413 * 17414 * @listens mousedown 17415 * @listens touchstart 17416 */ 17417 ; 17418 17419 _proto.handleMouseMove = function handleMouseMove(event) { 17420 this.volumeBar.handleMouseMove(event); 17421 }; 17422 17423 return VolumeControl; 17424 }(Component); 17425 /** 17426 * Default options for the `VolumeControl` 17427 * 17428 * @type {Object} 17429 * @private 17430 */ 17431 17432 17433 VolumeControl.prototype.options_ = { 17434 children: ['volumeBar'] 17435 }; 17436 Component.registerComponent('VolumeControl', VolumeControl); 17437 17438 /** 17439 * Check if muting volume is supported and if it isn't hide the mute toggle 17440 * button. 17441 * 17442 * @param {Component} self 17443 * A reference to the mute toggle button 17444 * 17445 * @param {Player} player 17446 * A reference to the player 17447 * 17448 * @private 17449 */ 17450 var checkMuteSupport = function checkMuteSupport(self, player) { 17451 // hide mute toggle button if it's not supported by the current tech 17452 if (player.tech_ && !player.tech_.featuresMuteControl) { 17453 self.addClass('vjs-hidden'); 17454 } 17455 17456 self.on(player, 'loadstart', function () { 17457 if (!player.tech_.featuresMuteControl) { 17458 self.addClass('vjs-hidden'); 17459 } else { 17460 self.removeClass('vjs-hidden'); 17461 } 17462 }); 17463 }; 17464 17465 /** 17466 * A button component for muting the audio. 17467 * 17468 * @extends Button 17469 */ 17470 17471 var MuteToggle = 17472 /*#__PURE__*/ 17473 function (_Button) { 17474 _inheritsLoose(MuteToggle, _Button); 17475 17476 /** 17477 * Creates an instance of this class. 17478 * 17479 * @param {Player} player 17480 * The `Player` that this class should be attached to. 17481 * 17482 * @param {Object} [options] 17483 * The key/value store of player options. 17484 */ 17485 function MuteToggle(player, options) { 17486 var _this; 17487 17488 _this = _Button.call(this, player, options) || this; // hide this control if volume support is missing 17489 17490 checkMuteSupport(_assertThisInitialized(_this), player); 17491 17492 _this.on(player, ['loadstart', 'volumechange'], _this.update); 17493 17494 return _this; 17495 } 17496 /** 17497 * Builds the default DOM `className`. 17498 * 17499 * @return {string} 17500 * The DOM `className` for this object. 17501 */ 17502 17503 17504 var _proto = MuteToggle.prototype; 17505 17506 _proto.buildCSSClass = function buildCSSClass() { 17507 return "vjs-mute-control " + _Button.prototype.buildCSSClass.call(this); 17508 } 17509 /** 17510 * This gets called when an `MuteToggle` is "clicked". See 17511 * {@link ClickableComponent} for more detailed information on what a click can be. 17512 * 17513 * @param {EventTarget~Event} [event] 17514 * The `keydown`, `tap`, or `click` event that caused this function to be 17515 * called. 17516 * 17517 * @listens tap 17518 * @listens click 17519 */ 17520 ; 17521 17522 _proto.handleClick = function handleClick(event) { 17523 var vol = this.player_.volume(); 17524 var lastVolume = this.player_.lastVolume_(); 17525 17526 if (vol === 0) { 17527 var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; 17528 this.player_.volume(volumeToSet); 17529 this.player_.muted(false); 17530 } else { 17531 this.player_.muted(this.player_.muted() ? false : true); 17532 } 17533 } 17534 /** 17535 * Update the `MuteToggle` button based on the state of `volume` and `muted` 17536 * on the player. 17537 * 17538 * @param {EventTarget~Event} [event] 17539 * The {@link Player#loadstart} event if this function was called 17540 * through an event. 17541 * 17542 * @listens Player#loadstart 17543 * @listens Player#volumechange 17544 */ 17545 ; 17546 17547 _proto.update = function update(event) { 17548 this.updateIcon_(); 17549 this.updateControlText_(); 17550 } 17551 /** 17552 * Update the appearance of the `MuteToggle` icon. 17553 * 17554 * Possible states (given `level` variable below): 17555 * - 0: crossed out 17556 * - 1: zero bars of volume 17557 * - 2: one bar of volume 17558 * - 3: two bars of volume 17559 * 17560 * @private 17561 */ 17562 ; 17563 17564 _proto.updateIcon_ = function updateIcon_() { 17565 var vol = this.player_.volume(); 17566 var level = 3; // in iOS when a player is loaded with muted attribute 17567 // and volume is changed with a native mute button 17568 // we want to make sure muted state is updated 17569 17570 if (IS_IOS && this.player_.tech_ && this.player_.tech_.el_) { 17571 this.player_.muted(this.player_.tech_.el_.muted); 17572 } 17573 17574 if (vol === 0 || this.player_.muted()) { 17575 level = 0; 17576 } else if (vol < 0.33) { 17577 level = 1; 17578 } else if (vol < 0.67) { 17579 level = 2; 17580 } // TODO improve muted icon classes 17581 17582 17583 for (var i = 0; i < 4; i++) { 17584 removeClass(this.el_, "vjs-vol-" + i); 17585 } 17586 17587 addClass(this.el_, "vjs-vol-" + level); 17588 } 17589 /** 17590 * If `muted` has changed on the player, update the control text 17591 * (`title` attribute on `vjs-mute-control` element and content of 17592 * `vjs-control-text` element). 17593 * 17594 * @private 17595 */ 17596 ; 17597 17598 _proto.updateControlText_ = function updateControlText_() { 17599 var soundOff = this.player_.muted() || this.player_.volume() === 0; 17600 var text = soundOff ? 'Unmute' : 'Mute'; 17601 17602 if (this.controlText() !== text) { 17603 this.controlText(text); 17604 } 17605 }; 17606 17607 return MuteToggle; 17608 }(Button); 17609 /** 17610 * The text that should display over the `MuteToggle`s controls. Added for localization. 17611 * 17612 * @type {string} 17613 * @private 17614 */ 17615 17616 17617 MuteToggle.prototype.controlText_ = 'Mute'; 17618 Component.registerComponent('MuteToggle', MuteToggle); 17619 17620 /** 17621 * A Component to contain the MuteToggle and VolumeControl so that 17622 * they can work together. 17623 * 17624 * @extends Component 17625 */ 17626 17627 var VolumePanel = 17628 /*#__PURE__*/ 17629 function (_Component) { 17630 _inheritsLoose(VolumePanel, _Component); 17631 17632 /** 17633 * Creates an instance of this class. 17634 * 17635 * @param {Player} player 17636 * The `Player` that this class should be attached to. 17637 * 17638 * @param {Object} [options={}] 17639 * The key/value store of player options. 17640 */ 17641 function VolumePanel(player, options) { 17642 var _this; 17643 17644 if (options === void 0) { 17645 options = {}; 17646 } 17647 17648 if (typeof options.inline !== 'undefined') { 17649 options.inline = options.inline; 17650 } else { 17651 options.inline = true; 17652 } // pass the inline option down to the VolumeControl as vertical if 17653 // the VolumeControl is on. 17654 17655 17656 if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) { 17657 options.volumeControl = options.volumeControl || {}; 17658 options.volumeControl.vertical = !options.inline; 17659 } 17660 17661 _this = _Component.call(this, player, options) || this; 17662 17663 _this.on(player, ['loadstart'], _this.volumePanelState_); 17664 17665 _this.on(_this.muteToggle, 'keyup', _this.handleKeyPress); 17666 17667 _this.on(_this.volumeControl, 'keyup', _this.handleVolumeControlKeyUp); 17668 17669 _this.on('keydown', _this.handleKeyPress); 17670 17671 _this.on('mouseover', _this.handleMouseOver); 17672 17673 _this.on('mouseout', _this.handleMouseOut); // while the slider is active (the mouse has been pressed down and 17674 // is dragging) we do not want to hide the VolumeBar 17675 17676 17677 _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); 17678 17679 _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); 17680 17681 return _this; 17682 } 17683 /** 17684 * Add vjs-slider-active class to the VolumePanel 17685 * 17686 * @listens VolumeControl#slideractive 17687 * @private 17688 */ 17689 17690 17691 var _proto = VolumePanel.prototype; 17692 17693 _proto.sliderActive_ = function sliderActive_() { 17694 this.addClass('vjs-slider-active'); 17695 } 17696 /** 17697 * Removes vjs-slider-active class to the VolumePanel 17698 * 17699 * @listens VolumeControl#sliderinactive 17700 * @private 17701 */ 17702 ; 17703 17704 _proto.sliderInactive_ = function sliderInactive_() { 17705 this.removeClass('vjs-slider-active'); 17706 } 17707 /** 17708 * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel 17709 * depending on MuteToggle and VolumeControl state 17710 * 17711 * @listens Player#loadstart 17712 * @private 17713 */ 17714 ; 17715 17716 _proto.volumePanelState_ = function volumePanelState_() { 17717 // hide volume panel if neither volume control or mute toggle 17718 // are displayed 17719 if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) { 17720 this.addClass('vjs-hidden'); 17721 } // if only mute toggle is visible we don't want 17722 // volume panel expanding when hovered or active 17723 17724 17725 if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) { 17726 this.addClass('vjs-mute-toggle-only'); 17727 } 17728 } 17729 /** 17730 * Create the `Component`'s DOM element 17731 * 17732 * @return {Element} 17733 * The element that was created. 17734 */ 17735 ; 17736 17737 _proto.createEl = function createEl() { 17738 var orientationClass = 'vjs-volume-panel-horizontal'; 17739 17740 if (!this.options_.inline) { 17741 orientationClass = 'vjs-volume-panel-vertical'; 17742 } 17743 17744 return _Component.prototype.createEl.call(this, 'div', { 17745 className: "vjs-volume-panel vjs-control " + orientationClass 17746 }); 17747 } 17748 /** 17749 * Dispose of the `volume-panel` and all child components. 17750 */ 17751 ; 17752 17753 _proto.dispose = function dispose() { 17754 this.handleMouseOut(); 17755 17756 _Component.prototype.dispose.call(this); 17757 } 17758 /** 17759 * Handles `keyup` events on the `VolumeControl`, looking for ESC, which closes 17760 * the volume panel and sets focus on `MuteToggle`. 17761 * 17762 * @param {EventTarget~Event} event 17763 * The `keyup` event that caused this function to be called. 17764 * 17765 * @listens keyup 17766 */ 17767 ; 17768 17769 _proto.handleVolumeControlKeyUp = function handleVolumeControlKeyUp(event) { 17770 if (keycode.isEventKey(event, 'Esc')) { 17771 this.muteToggle.focus(); 17772 } 17773 } 17774 /** 17775 * This gets called when a `VolumePanel` gains hover via a `mouseover` event. 17776 * Turns on listening for `mouseover` event. When they happen it 17777 * calls `this.handleMouseOver`. 17778 * 17779 * @param {EventTarget~Event} event 17780 * The `mouseover` event that caused this function to be called. 17781 * 17782 * @listens mouseover 17783 */ 17784 ; 17785 17786 _proto.handleMouseOver = function handleMouseOver(event) { 17787 this.addClass('vjs-hover'); 17788 on(document, 'keyup', bind(this, this.handleKeyPress)); 17789 } 17790 /** 17791 * This gets called when a `VolumePanel` gains hover via a `mouseout` event. 17792 * Turns on listening for `mouseout` event. When they happen it 17793 * calls `this.handleMouseOut`. 17794 * 17795 * @param {EventTarget~Event} event 17796 * The `mouseout` event that caused this function to be called. 17797 * 17798 * @listens mouseout 17799 */ 17800 ; 17801 17802 _proto.handleMouseOut = function handleMouseOut(event) { 17803 this.removeClass('vjs-hover'); 17804 off(document, 'keyup', bind(this, this.handleKeyPress)); 17805 } 17806 /** 17807 * Handles `keyup` event on the document or `keydown` event on the `VolumePanel`, 17808 * looking for ESC, which hides the `VolumeControl`. 17809 * 17810 * @param {EventTarget~Event} event 17811 * The keypress that triggered this event. 17812 * 17813 * @listens keydown | keyup 17814 */ 17815 ; 17816 17817 _proto.handleKeyPress = function handleKeyPress(event) { 17818 if (keycode.isEventKey(event, 'Esc')) { 17819 this.handleMouseOut(); 17820 } 17821 }; 17822 17823 return VolumePanel; 17824 }(Component); 17825 /** 17826 * Default options for the `VolumeControl` 17827 * 17828 * @type {Object} 17829 * @private 17830 */ 17831 17832 17833 VolumePanel.prototype.options_ = { 17834 children: ['muteToggle', 'volumeControl'] 17835 }; 17836 Component.registerComponent('VolumePanel', VolumePanel); 17837 17838 /** 17839 * The Menu component is used to build popup menus, including subtitle and 17840 * captions selection menus. 17841 * 17842 * @extends Component 17843 */ 17844 17845 var Menu = 17846 /*#__PURE__*/ 17847 function (_Component) { 17848 _inheritsLoose(Menu, _Component); 17849 17850 /** 17851 * Create an instance of this class. 17852 * 17853 * @param {Player} player 17854 * the player that this component should attach to 17855 * 17856 * @param {Object} [options] 17857 * Object of option names and values 17858 * 17859 */ 17860 function Menu(player, options) { 17861 var _this; 17862 17863 _this = _Component.call(this, player, options) || this; 17864 17865 if (options) { 17866 _this.menuButton_ = options.menuButton; 17867 } 17868 17869 _this.focusedChild_ = -1; 17870 17871 _this.on('keydown', _this.handleKeyDown); // All the menu item instances share the same blur handler provided by the menu container. 17872 17873 17874 _this.boundHandleBlur_ = bind(_assertThisInitialized(_this), _this.handleBlur); 17875 _this.boundHandleTapClick_ = bind(_assertThisInitialized(_this), _this.handleTapClick); 17876 return _this; 17877 } 17878 /** 17879 * Add event listeners to the {@link MenuItem}. 17880 * 17881 * @param {Object} component 17882 * The instance of the `MenuItem` to add listeners to. 17883 * 17884 */ 17885 17886 17887 var _proto = Menu.prototype; 17888 17889 _proto.addEventListenerForItem = function addEventListenerForItem(component) { 17890 if (!(component instanceof Component)) { 17891 return; 17892 } 17893 17894 this.on(component, 'blur', this.boundHandleBlur_); 17895 this.on(component, ['tap', 'click'], this.boundHandleTapClick_); 17896 } 17897 /** 17898 * Remove event listeners from the {@link MenuItem}. 17899 * 17900 * @param {Object} component 17901 * The instance of the `MenuItem` to remove listeners. 17902 * 17903 */ 17904 ; 17905 17906 _proto.removeEventListenerForItem = function removeEventListenerForItem(component) { 17907 if (!(component instanceof Component)) { 17908 return; 17909 } 17910 17911 this.off(component, 'blur', this.boundHandleBlur_); 17912 this.off(component, ['tap', 'click'], this.boundHandleTapClick_); 17913 } 17914 /** 17915 * This method will be called indirectly when the component has been added 17916 * before the component adds to the new menu instance by `addItem`. 17917 * In this case, the original menu instance will remove the component 17918 * by calling `removeChild`. 17919 * 17920 * @param {Object} component 17921 * The instance of the `MenuItem` 17922 */ 17923 ; 17924 17925 _proto.removeChild = function removeChild(component) { 17926 if (typeof component === 'string') { 17927 component = this.getChild(component); 17928 } 17929 17930 this.removeEventListenerForItem(component); 17931 17932 _Component.prototype.removeChild.call(this, component); 17933 } 17934 /** 17935 * Add a {@link MenuItem} to the menu. 17936 * 17937 * @param {Object|string} component 17938 * The name or instance of the `MenuItem` to add. 17939 * 17940 */ 17941 ; 17942 17943 _proto.addItem = function addItem(component) { 17944 var childComponent = this.addChild(component); 17945 17946 if (childComponent) { 17947 this.addEventListenerForItem(childComponent); 17948 } 17949 } 17950 /** 17951 * Create the `Menu`s DOM element. 17952 * 17953 * @return {Element} 17954 * the element that was created 17955 */ 17956 ; 17957 17958 _proto.createEl = function createEl$1() { 17959 var contentElType = this.options_.contentElType || 'ul'; 17960 this.contentEl_ = createEl(contentElType, { 17961 className: 'vjs-menu-content' 17962 }); 17963 this.contentEl_.setAttribute('role', 'menu'); 17964 17965 var el = _Component.prototype.createEl.call(this, 'div', { 17966 append: this.contentEl_, 17967 className: 'vjs-menu' 17968 }); 17969 17970 el.appendChild(this.contentEl_); // Prevent clicks from bubbling up. Needed for Menu Buttons, 17971 // where a click on the parent is significant 17972 17973 on(el, 'click', function (event) { 17974 event.preventDefault(); 17975 event.stopImmediatePropagation(); 17976 }); 17977 return el; 17978 }; 17979 17980 _proto.dispose = function dispose() { 17981 this.contentEl_ = null; 17982 this.boundHandleBlur_ = null; 17983 this.boundHandleTapClick_ = null; 17984 17985 _Component.prototype.dispose.call(this); 17986 } 17987 /** 17988 * Called when a `MenuItem` loses focus. 17989 * 17990 * @param {EventTarget~Event} event 17991 * The `blur` event that caused this function to be called. 17992 * 17993 * @listens blur 17994 */ 17995 ; 17996 17997 _proto.handleBlur = function handleBlur(event) { 17998 var relatedTarget = event.relatedTarget || document.activeElement; // Close menu popup when a user clicks outside the menu 17999 18000 if (!this.children().some(function (element) { 18001 return element.el() === relatedTarget; 18002 })) { 18003 var btn = this.menuButton_; 18004 18005 if (btn && btn.buttonPressed_ && relatedTarget !== btn.el().firstChild) { 18006 btn.unpressButton(); 18007 } 18008 } 18009 } 18010 /** 18011 * Called when a `MenuItem` gets clicked or tapped. 18012 * 18013 * @param {EventTarget~Event} event 18014 * The `click` or `tap` event that caused this function to be called. 18015 * 18016 * @listens click,tap 18017 */ 18018 ; 18019 18020 _proto.handleTapClick = function handleTapClick(event) { 18021 // Unpress the associated MenuButton, and move focus back to it 18022 if (this.menuButton_) { 18023 this.menuButton_.unpressButton(); 18024 var childComponents = this.children(); 18025 18026 if (!Array.isArray(childComponents)) { 18027 return; 18028 } 18029 18030 var foundComponent = childComponents.filter(function (component) { 18031 return component.el() === event.target; 18032 })[0]; 18033 18034 if (!foundComponent) { 18035 return; 18036 } // don't focus menu button if item is a caption settings item 18037 // because focus will move elsewhere 18038 18039 18040 if (foundComponent.name() !== 'CaptionSettingsMenuItem') { 18041 this.menuButton_.focus(); 18042 } 18043 } 18044 } 18045 /** 18046 * Handle a `keydown` event on this menu. This listener is added in the constructor. 18047 * 18048 * @param {EventTarget~Event} event 18049 * A `keydown` event that happened on the menu. 18050 * 18051 * @listens keydown 18052 */ 18053 ; 18054 18055 _proto.handleKeyDown = function handleKeyDown(event) { 18056 // Left and Down Arrows 18057 if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) { 18058 event.preventDefault(); 18059 event.stopPropagation(); 18060 this.stepForward(); // Up and Right Arrows 18061 } else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) { 18062 event.preventDefault(); 18063 event.stopPropagation(); 18064 this.stepBack(); 18065 } 18066 } 18067 /** 18068 * Move to next (lower) menu item for keyboard users. 18069 */ 18070 ; 18071 18072 _proto.stepForward = function stepForward() { 18073 var stepChild = 0; 18074 18075 if (this.focusedChild_ !== undefined) { 18076 stepChild = this.focusedChild_ + 1; 18077 } 18078 18079 this.focus(stepChild); 18080 } 18081 /** 18082 * Move to previous (higher) menu item for keyboard users. 18083 */ 18084 ; 18085 18086 _proto.stepBack = function stepBack() { 18087 var stepChild = 0; 18088 18089 if (this.focusedChild_ !== undefined) { 18090 stepChild = this.focusedChild_ - 1; 18091 } 18092 18093 this.focus(stepChild); 18094 } 18095 /** 18096 * Set focus on a {@link MenuItem} in the `Menu`. 18097 * 18098 * @param {Object|string} [item=0] 18099 * Index of child item set focus on. 18100 */ 18101 ; 18102 18103 _proto.focus = function focus(item) { 18104 if (item === void 0) { 18105 item = 0; 18106 } 18107 18108 var children = this.children().slice(); 18109 var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); 18110 18111 if (haveTitle) { 18112 children.shift(); 18113 } 18114 18115 if (children.length > 0) { 18116 if (item < 0) { 18117 item = 0; 18118 } else if (item >= children.length) { 18119 item = children.length - 1; 18120 } 18121 18122 this.focusedChild_ = item; 18123 children[item].el_.focus(); 18124 } 18125 }; 18126 18127 return Menu; 18128 }(Component); 18129 18130 Component.registerComponent('Menu', Menu); 18131 18132 /** 18133 * A `MenuButton` class for any popup {@link Menu}. 18134 * 18135 * @extends Component 18136 */ 18137 18138 var MenuButton = 18139 /*#__PURE__*/ 18140 function (_Component) { 18141 _inheritsLoose(MenuButton, _Component); 18142 18143 /** 18144 * Creates an instance of this class. 18145 * 18146 * @param {Player} player 18147 * The `Player` that this class should be attached to. 18148 * 18149 * @param {Object} [options={}] 18150 * The key/value store of player options. 18151 */ 18152 function MenuButton(player, options) { 18153 var _this; 18154 18155 if (options === void 0) { 18156 options = {}; 18157 } 18158 18159 _this = _Component.call(this, player, options) || this; 18160 _this.menuButton_ = new Button(player, options); 18161 18162 _this.menuButton_.controlText(_this.controlText_); 18163 18164 _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); // Add buildCSSClass values to the button, not the wrapper 18165 18166 18167 var buttonClass = Button.prototype.buildCSSClass(); 18168 _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; 18169 18170 _this.menuButton_.removeClass('vjs-control'); 18171 18172 _this.addChild(_this.menuButton_); 18173 18174 _this.update(); 18175 18176 _this.enabled_ = true; 18177 18178 _this.on(_this.menuButton_, 'tap', _this.handleClick); 18179 18180 _this.on(_this.menuButton_, 'click', _this.handleClick); 18181 18182 _this.on(_this.menuButton_, 'keydown', _this.handleKeyDown); 18183 18184 _this.on(_this.menuButton_, 'mouseenter', function () { 18185 _this.addClass('vjs-hover'); 18186 18187 _this.menu.show(); 18188 18189 on(document, 'keyup', bind(_assertThisInitialized(_this), _this.handleMenuKeyUp)); 18190 }); 18191 18192 _this.on('mouseleave', _this.handleMouseLeave); 18193 18194 _this.on('keydown', _this.handleSubmenuKeyDown); 18195 18196 return _this; 18197 } 18198 /** 18199 * Update the menu based on the current state of its items. 18200 */ 18201 18202 18203 var _proto = MenuButton.prototype; 18204 18205 _proto.update = function update() { 18206 var menu = this.createMenu(); 18207 18208 if (this.menu) { 18209 this.menu.dispose(); 18210 this.removeChild(this.menu); 18211 } 18212 18213 this.menu = menu; 18214 this.addChild(menu); 18215 /** 18216 * Track the state of the menu button 18217 * 18218 * @type {Boolean} 18219 * @private 18220 */ 18221 18222 this.buttonPressed_ = false; 18223 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 18224 18225 if (this.items && this.items.length <= this.hideThreshold_) { 18226 this.hide(); 18227 } else { 18228 this.show(); 18229 } 18230 } 18231 /** 18232 * Create the menu and add all items to it. 18233 * 18234 * @return {Menu} 18235 * The constructed menu 18236 */ 18237 ; 18238 18239 _proto.createMenu = function createMenu() { 18240 var menu = new Menu(this.player_, { 18241 menuButton: this 18242 }); 18243 /** 18244 * Hide the menu if the number of items is less than or equal to this threshold. This defaults 18245 * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list 18246 * it here because every time we run `createMenu` we need to reset the value. 18247 * 18248 * @protected 18249 * @type {Number} 18250 */ 18251 18252 this.hideThreshold_ = 0; // Add a title list item to the top 18253 18254 if (this.options_.title) { 18255 var titleEl = createEl('li', { 18256 className: 'vjs-menu-title', 18257 innerHTML: toTitleCase(this.options_.title), 18258 tabIndex: -1 18259 }); 18260 this.hideThreshold_ += 1; 18261 var titleComponent = new Component(this.player_, { 18262 el: titleEl 18263 }); 18264 menu.addItem(titleComponent); 18265 } 18266 18267 this.items = this.createItems(); 18268 18269 if (this.items) { 18270 // Add menu items to the menu 18271 for (var i = 0; i < this.items.length; i++) { 18272 menu.addItem(this.items[i]); 18273 } 18274 } 18275 18276 return menu; 18277 } 18278 /** 18279 * Create the list of menu items. Specific to each subclass. 18280 * 18281 * @abstract 18282 */ 18283 ; 18284 18285 _proto.createItems = function createItems() {} 18286 /** 18287 * Create the `MenuButtons`s DOM element. 18288 * 18289 * @return {Element} 18290 * The element that gets created. 18291 */ 18292 ; 18293 18294 _proto.createEl = function createEl() { 18295 return _Component.prototype.createEl.call(this, 'div', { 18296 className: this.buildWrapperCSSClass() 18297 }, {}); 18298 } 18299 /** 18300 * Allow sub components to stack CSS class names for the wrapper element 18301 * 18302 * @return {string} 18303 * The constructed wrapper DOM `className` 18304 */ 18305 ; 18306 18307 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 18308 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 18309 18310 if (this.options_.inline === true) { 18311 menuButtonClass += '-inline'; 18312 } else { 18313 menuButtonClass += '-popup'; 18314 } // TODO: Fix the CSS so that this isn't necessary 18315 18316 18317 var buttonClass = Button.prototype.buildCSSClass(); 18318 return "vjs-menu-button " + menuButtonClass + " " + buttonClass + " " + _Component.prototype.buildCSSClass.call(this); 18319 } 18320 /** 18321 * Builds the default DOM `className`. 18322 * 18323 * @return {string} 18324 * The DOM `className` for this object. 18325 */ 18326 ; 18327 18328 _proto.buildCSSClass = function buildCSSClass() { 18329 var menuButtonClass = 'vjs-menu-button'; // If the inline option is passed, we want to use different styles altogether. 18330 18331 if (this.options_.inline === true) { 18332 menuButtonClass += '-inline'; 18333 } else { 18334 menuButtonClass += '-popup'; 18335 } 18336 18337 return "vjs-menu-button " + menuButtonClass + " " + _Component.prototype.buildCSSClass.call(this); 18338 } 18339 /** 18340 * Get or set the localized control text that will be used for accessibility. 18341 * 18342 * > NOTE: This will come from the internal `menuButton_` element. 18343 * 18344 * @param {string} [text] 18345 * Control text for element. 18346 * 18347 * @param {Element} [el=this.menuButton_.el()] 18348 * Element to set the title on. 18349 * 18350 * @return {string} 18351 * - The control text when getting 18352 */ 18353 ; 18354 18355 _proto.controlText = function controlText(text, el) { 18356 if (el === void 0) { 18357 el = this.menuButton_.el(); 18358 } 18359 18360 return this.menuButton_.controlText(text, el); 18361 } 18362 /** 18363 * Dispose of the `menu-button` and all child components. 18364 */ 18365 ; 18366 18367 _proto.dispose = function dispose() { 18368 this.handleMouseLeave(); 18369 18370 _Component.prototype.dispose.call(this); 18371 } 18372 /** 18373 * Handle a click on a `MenuButton`. 18374 * See {@link ClickableComponent#handleClick} for instances where this is called. 18375 * 18376 * @param {EventTarget~Event} event 18377 * The `keydown`, `tap`, or `click` event that caused this function to be 18378 * called. 18379 * 18380 * @listens tap 18381 * @listens click 18382 */ 18383 ; 18384 18385 _proto.handleClick = function handleClick(event) { 18386 if (this.buttonPressed_) { 18387 this.unpressButton(); 18388 } else { 18389 this.pressButton(); 18390 } 18391 } 18392 /** 18393 * Handle `mouseleave` for `MenuButton`. 18394 * 18395 * @param {EventTarget~Event} event 18396 * The `mouseleave` event that caused this function to be called. 18397 * 18398 * @listens mouseleave 18399 */ 18400 ; 18401 18402 _proto.handleMouseLeave = function handleMouseLeave(event) { 18403 this.removeClass('vjs-hover'); 18404 off(document, 'keyup', bind(this, this.handleMenuKeyUp)); 18405 } 18406 /** 18407 * Set the focus to the actual button, not to this element 18408 */ 18409 ; 18410 18411 _proto.focus = function focus() { 18412 this.menuButton_.focus(); 18413 } 18414 /** 18415 * Remove the focus from the actual button, not this element 18416 */ 18417 ; 18418 18419 _proto.blur = function blur() { 18420 this.menuButton_.blur(); 18421 } 18422 /** 18423 * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See 18424 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 18425 * 18426 * @param {EventTarget~Event} event 18427 * The `keydown` event that caused this function to be called. 18428 * 18429 * @listens keydown 18430 */ 18431 ; 18432 18433 _proto.handleKeyDown = function handleKeyDown(event) { 18434 // Escape or Tab unpress the 'button' 18435 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 18436 if (this.buttonPressed_) { 18437 this.unpressButton(); 18438 } // Don't preventDefault for Tab key - we still want to lose focus 18439 18440 18441 if (!keycode.isEventKey(event, 'Tab')) { 18442 event.preventDefault(); // Set focus back to the menu button's button 18443 18444 this.menuButton_.focus(); 18445 } // Up Arrow or Down Arrow also 'press' the button to open the menu 18446 18447 } else if (keycode.isEventKey(event, 'Up') || keycode.isEventKey(event, 'Down')) { 18448 if (!this.buttonPressed_) { 18449 event.preventDefault(); 18450 this.pressButton(); 18451 } 18452 } 18453 } 18454 /** 18455 * Handle a `keyup` event on a `MenuButton`. The listener for this is added in 18456 * the constructor. 18457 * 18458 * @param {EventTarget~Event} event 18459 * Key press event 18460 * 18461 * @listens keyup 18462 */ 18463 ; 18464 18465 _proto.handleMenuKeyUp = function handleMenuKeyUp(event) { 18466 // Escape hides popup menu 18467 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 18468 this.removeClass('vjs-hover'); 18469 } 18470 } 18471 /** 18472 * This method name now delegates to `handleSubmenuKeyDown`. This means 18473 * anyone calling `handleSubmenuKeyPress` will not see their method calls 18474 * stop working. 18475 * 18476 * @param {EventTarget~Event} event 18477 * The event that caused this function to be called. 18478 */ 18479 ; 18480 18481 _proto.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { 18482 this.handleSubmenuKeyDown(event); 18483 } 18484 /** 18485 * Handle a `keydown` event on a sub-menu. The listener for this is added in 18486 * the constructor. 18487 * 18488 * @param {EventTarget~Event} event 18489 * Key press event 18490 * 18491 * @listens keydown 18492 */ 18493 ; 18494 18495 _proto.handleSubmenuKeyDown = function handleSubmenuKeyDown(event) { 18496 // Escape or Tab unpress the 'button' 18497 if (keycode.isEventKey(event, 'Esc') || keycode.isEventKey(event, 'Tab')) { 18498 if (this.buttonPressed_) { 18499 this.unpressButton(); 18500 } // Don't preventDefault for Tab key - we still want to lose focus 18501 18502 18503 if (!keycode.isEventKey(event, 'Tab')) { 18504 event.preventDefault(); // Set focus back to the menu button's button 18505 18506 this.menuButton_.focus(); 18507 } 18508 } 18509 } 18510 /** 18511 * Put the current `MenuButton` into a pressed state. 18512 */ 18513 ; 18514 18515 _proto.pressButton = function pressButton() { 18516 if (this.enabled_) { 18517 this.buttonPressed_ = true; 18518 this.menu.show(); 18519 this.menu.lockShowing(); 18520 this.menuButton_.el_.setAttribute('aria-expanded', 'true'); // set the focus into the submenu, except on iOS where it is resulting in 18521 // undesired scrolling behavior when the player is in an iframe 18522 18523 if (IS_IOS && isInFrame()) { 18524 // Return early so that the menu isn't focused 18525 return; 18526 } 18527 18528 this.menu.focus(); 18529 } 18530 } 18531 /** 18532 * Take the current `MenuButton` out of a pressed state. 18533 */ 18534 ; 18535 18536 _proto.unpressButton = function unpressButton() { 18537 if (this.enabled_) { 18538 this.buttonPressed_ = false; 18539 this.menu.unlockShowing(); 18540 this.menu.hide(); 18541 this.menuButton_.el_.setAttribute('aria-expanded', 'false'); 18542 } 18543 } 18544 /** 18545 * Disable the `MenuButton`. Don't allow it to be clicked. 18546 */ 18547 ; 18548 18549 _proto.disable = function disable() { 18550 this.unpressButton(); 18551 this.enabled_ = false; 18552 this.addClass('vjs-disabled'); 18553 this.menuButton_.disable(); 18554 } 18555 /** 18556 * Enable the `MenuButton`. Allow it to be clicked. 18557 */ 18558 ; 18559 18560 _proto.enable = function enable() { 18561 this.enabled_ = true; 18562 this.removeClass('vjs-disabled'); 18563 this.menuButton_.enable(); 18564 }; 18565 18566 return MenuButton; 18567 }(Component); 18568 18569 Component.registerComponent('MenuButton', MenuButton); 18570 18571 /** 18572 * The base class for buttons that toggle specific track types (e.g. subtitles). 18573 * 18574 * @extends MenuButton 18575 */ 18576 18577 var TrackButton = 18578 /*#__PURE__*/ 18579 function (_MenuButton) { 18580 _inheritsLoose(TrackButton, _MenuButton); 18581 18582 /** 18583 * Creates an instance of this class. 18584 * 18585 * @param {Player} player 18586 * The `Player` that this class should be attached to. 18587 * 18588 * @param {Object} [options] 18589 * The key/value store of player options. 18590 */ 18591 function TrackButton(player, options) { 18592 var _this; 18593 18594 var tracks = options.tracks; 18595 _this = _MenuButton.call(this, player, options) || this; 18596 18597 if (_this.items.length <= 1) { 18598 _this.hide(); 18599 } 18600 18601 if (!tracks) { 18602 return _assertThisInitialized(_this); 18603 } 18604 18605 var updateHandler = bind(_assertThisInitialized(_this), _this.update); 18606 tracks.addEventListener('removetrack', updateHandler); 18607 tracks.addEventListener('addtrack', updateHandler); 18608 18609 _this.player_.on('ready', updateHandler); 18610 18611 _this.player_.on('dispose', function () { 18612 tracks.removeEventListener('removetrack', updateHandler); 18613 tracks.removeEventListener('addtrack', updateHandler); 18614 }); 18615 18616 return _this; 18617 } 18618 18619 return TrackButton; 18620 }(MenuButton); 18621 18622 Component.registerComponent('TrackButton', TrackButton); 18623 18624 /** 18625 * @file menu-keys.js 18626 */ 18627 18628 /** 18629 * All keys used for operation of a menu (`MenuButton`, `Menu`, and `MenuItem`) 18630 * Note that 'Enter' and 'Space' are not included here (otherwise they would 18631 * prevent the `MenuButton` and `MenuItem` from being keyboard-clickable) 18632 * @typedef MenuKeys 18633 * @array 18634 */ 18635 var MenuKeys = ['Tab', 'Esc', 'Up', 'Down', 'Right', 'Left']; 18636 18637 /** 18638 * The component for a menu item. `<li>` 18639 * 18640 * @extends ClickableComponent 18641 */ 18642 18643 var MenuItem = 18644 /*#__PURE__*/ 18645 function (_ClickableComponent) { 18646 _inheritsLoose(MenuItem, _ClickableComponent); 18647 18648 /** 18649 * Creates an instance of the this class. 18650 * 18651 * @param {Player} player 18652 * The `Player` that this class should be attached to. 18653 * 18654 * @param {Object} [options={}] 18655 * The key/value store of player options. 18656 * 18657 */ 18658 function MenuItem(player, options) { 18659 var _this; 18660 18661 _this = _ClickableComponent.call(this, player, options) || this; 18662 _this.selectable = options.selectable; 18663 _this.isSelected_ = options.selected || false; 18664 _this.multiSelectable = options.multiSelectable; 18665 18666 _this.selected(_this.isSelected_); 18667 18668 if (_this.selectable) { 18669 if (_this.multiSelectable) { 18670 _this.el_.setAttribute('role', 'menuitemcheckbox'); 18671 } else { 18672 _this.el_.setAttribute('role', 'menuitemradio'); 18673 } 18674 } else { 18675 _this.el_.setAttribute('role', 'menuitem'); 18676 } 18677 18678 return _this; 18679 } 18680 /** 18681 * Create the `MenuItem's DOM element 18682 * 18683 * @param {string} [type=li] 18684 * Element's node type, not actually used, always set to `li`. 18685 * 18686 * @param {Object} [props={}] 18687 * An object of properties that should be set on the element 18688 * 18689 * @param {Object} [attrs={}] 18690 * An object of attributes that should be set on the element 18691 * 18692 * @return {Element} 18693 * The element that gets created. 18694 */ 18695 18696 18697 var _proto = MenuItem.prototype; 18698 18699 _proto.createEl = function createEl(type, props, attrs) { 18700 // The control is textual, not just an icon 18701 this.nonIconControl = true; 18702 return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ 18703 className: 'vjs-menu-item', 18704 innerHTML: "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label) + "</span>", 18705 tabIndex: -1 18706 }, props), attrs); 18707 } 18708 /** 18709 * Ignore keys which are used by the menu, but pass any other ones up. See 18710 * {@link ClickableComponent#handleKeyDown} for instances where this is called. 18711 * 18712 * @param {EventTarget~Event} event 18713 * The `keydown` event that caused this function to be called. 18714 * 18715 * @listens keydown 18716 */ 18717 ; 18718 18719 _proto.handleKeyDown = function handleKeyDown(event) { 18720 if (!MenuKeys.some(function (key) { 18721 return keycode.isEventKey(event, key); 18722 })) { 18723 // Pass keydown handling up for unused keys 18724 _ClickableComponent.prototype.handleKeyDown.call(this, event); 18725 } 18726 } 18727 /** 18728 * Any click on a `MenuItem` puts it into the selected state. 18729 * See {@link ClickableComponent#handleClick} for instances where this is called. 18730 * 18731 * @param {EventTarget~Event} event 18732 * The `keydown`, `tap`, or `click` event that caused this function to be 18733 * called. 18734 * 18735 * @listens tap 18736 * @listens click 18737 */ 18738 ; 18739 18740 _proto.handleClick = function handleClick(event) { 18741 this.selected(true); 18742 } 18743 /** 18744 * Set the state for this menu item as selected or not. 18745 * 18746 * @param {boolean} selected 18747 * if the menu item is selected or not 18748 */ 18749 ; 18750 18751 _proto.selected = function selected(_selected) { 18752 if (this.selectable) { 18753 if (_selected) { 18754 this.addClass('vjs-selected'); 18755 this.el_.setAttribute('aria-checked', 'true'); // aria-checked isn't fully supported by browsers/screen readers, 18756 // so indicate selected state to screen reader in the control text. 18757 18758 this.controlText(', selected'); 18759 this.isSelected_ = true; 18760 } else { 18761 this.removeClass('vjs-selected'); 18762 this.el_.setAttribute('aria-checked', 'false'); // Indicate un-selected state to screen reader 18763 18764 this.controlText(''); 18765 this.isSelected_ = false; 18766 } 18767 } 18768 }; 18769 18770 return MenuItem; 18771 }(ClickableComponent); 18772 18773 Component.registerComponent('MenuItem', MenuItem); 18774 18775 /** 18776 * The specific menu item type for selecting a language within a text track kind 18777 * 18778 * @extends MenuItem 18779 */ 18780 18781 var TextTrackMenuItem = 18782 /*#__PURE__*/ 18783 function (_MenuItem) { 18784 _inheritsLoose(TextTrackMenuItem, _MenuItem); 18785 18786 /** 18787 * Creates an instance of this class. 18788 * 18789 * @param {Player} player 18790 * The `Player` that this class should be attached to. 18791 * 18792 * @param {Object} [options] 18793 * The key/value store of player options. 18794 */ 18795 function TextTrackMenuItem(player, options) { 18796 var _this; 18797 18798 var track = options.track; 18799 var tracks = player.textTracks(); // Modify options for parent MenuItem class's init. 18800 18801 options.label = track.label || track.language || 'Unknown'; 18802 options.selected = track.mode === 'showing'; 18803 _this = _MenuItem.call(this, player, options) || this; 18804 _this.track = track; // Determine the relevant kind(s) of tracks for this component and filter 18805 // out empty kinds. 18806 18807 _this.kinds = (options.kinds || [options.kind || _this.track.kind]).filter(Boolean); 18808 18809 var changeHandler = function changeHandler() { 18810 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 18811 args[_key] = arguments[_key]; 18812 } 18813 18814 _this.handleTracksChange.apply(_assertThisInitialized(_this), args); 18815 }; 18816 18817 var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() { 18818 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 18819 args[_key2] = arguments[_key2]; 18820 } 18821 18822 _this.handleSelectedLanguageChange.apply(_assertThisInitialized(_this), args); 18823 }; 18824 18825 player.on(['loadstart', 'texttrackchange'], changeHandler); 18826 tracks.addEventListener('change', changeHandler); 18827 tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 18828 18829 _this.on('dispose', function () { 18830 player.off(['loadstart', 'texttrackchange'], changeHandler); 18831 tracks.removeEventListener('change', changeHandler); 18832 tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); 18833 }); // iOS7 doesn't dispatch change events to TextTrackLists when an 18834 // associated track's mode changes. Without something like 18835 // Object.observe() (also not present on iOS7), it's not 18836 // possible to detect changes to the mode attribute and polyfill 18837 // the change event. As a poor substitute, we manually dispatch 18838 // change events whenever the controls modify the mode. 18839 18840 18841 if (tracks.onchange === undefined) { 18842 var event; 18843 18844 _this.on(['tap', 'click'], function () { 18845 if (typeof window$1.Event !== 'object') { 18846 // Android 2.3 throws an Illegal Constructor error for window.Event 18847 try { 18848 event = new window$1.Event('change'); 18849 } catch (err) {// continue regardless of error 18850 } 18851 } 18852 18853 if (!event) { 18854 event = document.createEvent('Event'); 18855 event.initEvent('change', true, true); 18856 } 18857 18858 tracks.dispatchEvent(event); 18859 }); 18860 } // set the default state based on current tracks 18861 18862 18863 _this.handleTracksChange(); 18864 18865 return _this; 18866 } 18867 /** 18868 * This gets called when an `TextTrackMenuItem` is "clicked". See 18869 * {@link ClickableComponent} for more detailed information on what a click can be. 18870 * 18871 * @param {EventTarget~Event} event 18872 * The `keydown`, `tap`, or `click` event that caused this function to be 18873 * called. 18874 * 18875 * @listens tap 18876 * @listens click 18877 */ 18878 18879 18880 var _proto = TextTrackMenuItem.prototype; 18881 18882 _proto.handleClick = function handleClick(event) { 18883 var referenceTrack = this.track; 18884 var tracks = this.player_.textTracks(); 18885 18886 _MenuItem.prototype.handleClick.call(this, event); 18887 18888 if (!tracks) { 18889 return; 18890 } 18891 18892 for (var i = 0; i < tracks.length; i++) { 18893 var track = tracks[i]; // If the track from the text tracks list is not of the right kind, 18894 // skip it. We do not want to affect tracks of incompatible kind(s). 18895 18896 if (this.kinds.indexOf(track.kind) === -1) { 18897 continue; 18898 } // If this text track is the component's track and it is not showing, 18899 // set it to showing. 18900 18901 18902 if (track === referenceTrack) { 18903 if (track.mode !== 'showing') { 18904 track.mode = 'showing'; 18905 } // If this text track is not the component's track and it is not 18906 // disabled, set it to disabled. 18907 18908 } else if (track.mode !== 'disabled') { 18909 track.mode = 'disabled'; 18910 } 18911 } 18912 } 18913 /** 18914 * Handle text track list change 18915 * 18916 * @param {EventTarget~Event} event 18917 * The `change` event that caused this function to be called. 18918 * 18919 * @listens TextTrackList#change 18920 */ 18921 ; 18922 18923 _proto.handleTracksChange = function handleTracksChange(event) { 18924 var shouldBeSelected = this.track.mode === 'showing'; // Prevent redundant selected() calls because they may cause 18925 // screen readers to read the appended control text unnecessarily 18926 18927 if (shouldBeSelected !== this.isSelected_) { 18928 this.selected(shouldBeSelected); 18929 } 18930 }; 18931 18932 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 18933 if (this.track.mode === 'showing') { 18934 var selectedLanguage = this.player_.cache_.selectedLanguage; // Don't replace the kind of track across the same language 18935 18936 if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { 18937 return; 18938 } 18939 18940 this.player_.cache_.selectedLanguage = { 18941 enabled: true, 18942 language: this.track.language, 18943 kind: this.track.kind 18944 }; 18945 } 18946 }; 18947 18948 _proto.dispose = function dispose() { 18949 // remove reference to track object on dispose 18950 this.track = null; 18951 18952 _MenuItem.prototype.dispose.call(this); 18953 }; 18954 18955 return TextTrackMenuItem; 18956 }(MenuItem); 18957 18958 Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); 18959 18960 /** 18961 * A special menu item for turning of a specific type of text track 18962 * 18963 * @extends TextTrackMenuItem 18964 */ 18965 18966 var OffTextTrackMenuItem = 18967 /*#__PURE__*/ 18968 function (_TextTrackMenuItem) { 18969 _inheritsLoose(OffTextTrackMenuItem, _TextTrackMenuItem); 18970 18971 /** 18972 * Creates an instance of this class. 18973 * 18974 * @param {Player} player 18975 * The `Player` that this class should be attached to. 18976 * 18977 * @param {Object} [options] 18978 * The key/value store of player options. 18979 */ 18980 function OffTextTrackMenuItem(player, options) { 18981 // Create pseudo track info 18982 // Requires options['kind'] 18983 options.track = { 18984 player: player, 18985 // it is no longer necessary to store `kind` or `kinds` on the track itself 18986 // since they are now stored in the `kinds` property of all instances of 18987 // TextTrackMenuItem, but this will remain for backwards compatibility 18988 kind: options.kind, 18989 kinds: options.kinds, 18990 "default": false, 18991 mode: 'disabled' 18992 }; 18993 18994 if (!options.kinds) { 18995 options.kinds = [options.kind]; 18996 } 18997 18998 if (options.label) { 18999 options.track.label = options.label; 19000 } else { 19001 options.track.label = options.kinds.join(' and ') + ' off'; 19002 } // MenuItem is selectable 19003 19004 19005 options.selectable = true; // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 19006 19007 options.multiSelectable = false; 19008 return _TextTrackMenuItem.call(this, player, options) || this; 19009 } 19010 /** 19011 * Handle text track change 19012 * 19013 * @param {EventTarget~Event} event 19014 * The event that caused this function to run 19015 */ 19016 19017 19018 var _proto = OffTextTrackMenuItem.prototype; 19019 19020 _proto.handleTracksChange = function handleTracksChange(event) { 19021 var tracks = this.player().textTracks(); 19022 var shouldBeSelected = true; 19023 19024 for (var i = 0, l = tracks.length; i < l; i++) { 19025 var track = tracks[i]; 19026 19027 if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { 19028 shouldBeSelected = false; 19029 break; 19030 } 19031 } // Prevent redundant selected() calls because they may cause 19032 // screen readers to read the appended control text unnecessarily 19033 19034 19035 if (shouldBeSelected !== this.isSelected_) { 19036 this.selected(shouldBeSelected); 19037 } 19038 }; 19039 19040 _proto.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { 19041 var tracks = this.player().textTracks(); 19042 var allHidden = true; 19043 19044 for (var i = 0, l = tracks.length; i < l; i++) { 19045 var track = tracks[i]; 19046 19047 if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { 19048 allHidden = false; 19049 break; 19050 } 19051 } 19052 19053 if (allHidden) { 19054 this.player_.cache_.selectedLanguage = { 19055 enabled: false 19056 }; 19057 } 19058 }; 19059 19060 return OffTextTrackMenuItem; 19061 }(TextTrackMenuItem); 19062 19063 Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); 19064 19065 /** 19066 * The base class for buttons that toggle specific text track types (e.g. subtitles) 19067 * 19068 * @extends MenuButton 19069 */ 19070 19071 var TextTrackButton = 19072 /*#__PURE__*/ 19073 function (_TrackButton) { 19074 _inheritsLoose(TextTrackButton, _TrackButton); 19075 19076 /** 19077 * Creates an instance of this class. 19078 * 19079 * @param {Player} player 19080 * The `Player` that this class should be attached to. 19081 * 19082 * @param {Object} [options={}] 19083 * The key/value store of player options. 19084 */ 19085 function TextTrackButton(player, options) { 19086 if (options === void 0) { 19087 options = {}; 19088 } 19089 19090 options.tracks = player.textTracks(); 19091 return _TrackButton.call(this, player, options) || this; 19092 } 19093 /** 19094 * Create a menu item for each text track 19095 * 19096 * @param {TextTrackMenuItem[]} [items=[]] 19097 * Existing array of items to use during creation 19098 * 19099 * @return {TextTrackMenuItem[]} 19100 * Array of menu items that were created 19101 */ 19102 19103 19104 var _proto = TextTrackButton.prototype; 19105 19106 _proto.createItems = function createItems(items, TrackMenuItem) { 19107 if (items === void 0) { 19108 items = []; 19109 } 19110 19111 if (TrackMenuItem === void 0) { 19112 TrackMenuItem = TextTrackMenuItem; 19113 } 19114 19115 // Label is an override for the [track] off label 19116 // USed to localise captions/subtitles 19117 var label; 19118 19119 if (this.label_) { 19120 label = this.label_ + " off"; 19121 } // Add an OFF menu item to turn all tracks off 19122 19123 19124 items.push(new OffTextTrackMenuItem(this.player_, { 19125 kinds: this.kinds_, 19126 kind: this.kind_, 19127 label: label 19128 })); 19129 this.hideThreshold_ += 1; 19130 var tracks = this.player_.textTracks(); 19131 19132 if (!Array.isArray(this.kinds_)) { 19133 this.kinds_ = [this.kind_]; 19134 } 19135 19136 for (var i = 0; i < tracks.length; i++) { 19137 var track = tracks[i]; // only add tracks that are of an appropriate kind and have a label 19138 19139 if (this.kinds_.indexOf(track.kind) > -1) { 19140 var item = new TrackMenuItem(this.player_, { 19141 track: track, 19142 kinds: this.kinds_, 19143 kind: this.kind_, 19144 // MenuItem is selectable 19145 selectable: true, 19146 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 19147 multiSelectable: false 19148 }); 19149 item.addClass("vjs-" + track.kind + "-menu-item"); 19150 items.push(item); 19151 } 19152 } 19153 19154 return items; 19155 }; 19156 19157 return TextTrackButton; 19158 }(TrackButton); 19159 19160 Component.registerComponent('TextTrackButton', TextTrackButton); 19161 19162 /** 19163 * The chapter track menu item 19164 * 19165 * @extends MenuItem 19166 */ 19167 19168 var ChaptersTrackMenuItem = 19169 /*#__PURE__*/ 19170 function (_MenuItem) { 19171 _inheritsLoose(ChaptersTrackMenuItem, _MenuItem); 19172 19173 /** 19174 * Creates an instance of this class. 19175 * 19176 * @param {Player} player 19177 * The `Player` that this class should be attached to. 19178 * 19179 * @param {Object} [options] 19180 * The key/value store of player options. 19181 */ 19182 function ChaptersTrackMenuItem(player, options) { 19183 var _this; 19184 19185 var track = options.track; 19186 var cue = options.cue; 19187 var currentTime = player.currentTime(); // Modify options for parent MenuItem class's init. 19188 19189 options.selectable = true; 19190 options.multiSelectable = false; 19191 options.label = cue.text; 19192 options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; 19193 _this = _MenuItem.call(this, player, options) || this; 19194 _this.track = track; 19195 _this.cue = cue; 19196 track.addEventListener('cuechange', bind(_assertThisInitialized(_this), _this.update)); 19197 return _this; 19198 } 19199 /** 19200 * This gets called when an `ChaptersTrackMenuItem` is "clicked". See 19201 * {@link ClickableComponent} for more detailed information on what a click can be. 19202 * 19203 * @param {EventTarget~Event} [event] 19204 * The `keydown`, `tap`, or `click` event that caused this function to be 19205 * called. 19206 * 19207 * @listens tap 19208 * @listens click 19209 */ 19210 19211 19212 var _proto = ChaptersTrackMenuItem.prototype; 19213 19214 _proto.handleClick = function handleClick(event) { 19215 _MenuItem.prototype.handleClick.call(this); 19216 19217 this.player_.currentTime(this.cue.startTime); 19218 this.update(this.cue.startTime); 19219 } 19220 /** 19221 * Update chapter menu item 19222 * 19223 * @param {EventTarget~Event} [event] 19224 * The `cuechange` event that caused this function to run. 19225 * 19226 * @listens TextTrack#cuechange 19227 */ 19228 ; 19229 19230 _proto.update = function update(event) { 19231 var cue = this.cue; 19232 var currentTime = this.player_.currentTime(); // vjs.log(currentTime, cue.startTime); 19233 19234 this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); 19235 }; 19236 19237 return ChaptersTrackMenuItem; 19238 }(MenuItem); 19239 19240 Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); 19241 19242 /** 19243 * The button component for toggling and selecting chapters 19244 * Chapters act much differently than other text tracks 19245 * Cues are navigation vs. other tracks of alternative languages 19246 * 19247 * @extends TextTrackButton 19248 */ 19249 19250 var ChaptersButton = 19251 /*#__PURE__*/ 19252 function (_TextTrackButton) { 19253 _inheritsLoose(ChaptersButton, _TextTrackButton); 19254 19255 /** 19256 * Creates an instance of this class. 19257 * 19258 * @param {Player} player 19259 * The `Player` that this class should be attached to. 19260 * 19261 * @param {Object} [options] 19262 * The key/value store of player options. 19263 * 19264 * @param {Component~ReadyCallback} [ready] 19265 * The function to call when this function is ready. 19266 */ 19267 function ChaptersButton(player, options, ready) { 19268 return _TextTrackButton.call(this, player, options, ready) || this; 19269 } 19270 /** 19271 * Builds the default DOM `className`. 19272 * 19273 * @return {string} 19274 * The DOM `className` for this object. 19275 */ 19276 19277 19278 var _proto = ChaptersButton.prototype; 19279 19280 _proto.buildCSSClass = function buildCSSClass() { 19281 return "vjs-chapters-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19282 }; 19283 19284 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19285 return "vjs-chapters-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19286 } 19287 /** 19288 * Update the menu based on the current state of its items. 19289 * 19290 * @param {EventTarget~Event} [event] 19291 * An event that triggered this function to run. 19292 * 19293 * @listens TextTrackList#addtrack 19294 * @listens TextTrackList#removetrack 19295 * @listens TextTrackList#change 19296 */ 19297 ; 19298 19299 _proto.update = function update(event) { 19300 if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
vendor: 1,553 bytes, lines 19301-19354
19301 this.setTrack(this.findChaptersTrack()); 19302 } 19303 19304 _TextTrackButton.prototype.update.call(this); 19305 } 19306 /** 19307 * Set the currently selected track for the chapters button. 19308 * 19309 * @param {TextTrack} track 19310 * The new track to select. Nothing will change if this is the currently selected 19311 * track. 19312 */ 19313 ; 19314 19315 _proto.setTrack = function setTrack(track) { 19316 if (this.track_ === track) { 19317 return; 19318 } 19319 19320 if (!this.updateHandler_) { 19321 this.updateHandler_ = this.update.bind(this); 19322 } // here this.track_ refers to the old track instance 19323 19324 19325 if (this.track_) { 19326 var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 19327 19328 if (remoteTextTrackEl) { 19329 remoteTextTrackEl.removeEventListener('load', this.updateHandler_); 19330 } 19331 19332 this.track_ = null; 19333 } 19334 19335 this.track_ = track; // here this.track_ refers to the new track instance 19336 19337 if (this.track_) { 19338 this.track_.mode = 'hidden'; 19339 19340 var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); 19341 19342 if (_remoteTextTrackEl) { 19343 _remoteTextTrackEl.addEventListener('load', this.updateHandler_); 19344 } 19345 } 19346 } 19347 /** 19348 * Find the track object that is currently in use by this ChaptersButton 19349 * 19350 * @return {TextTrack|undefined} 19351 * The current track or undefined if none was found. 19352 */ 19353 ; 19354
vendor: 22,466 bytes, lines 19355-20145
19355 _proto.findChaptersTrack = function findChaptersTrack() { 19356 var tracks = this.player_.textTracks() || []; 19357 19358 for (var i = tracks.length - 1; i >= 0; i--) { 19359 // We will always choose the last track as our chaptersTrack 19360 var track = tracks[i]; 19361 19362 if (track.kind === this.kind_) { 19363 return track; 19364 } 19365 } 19366 } 19367 /** 19368 * Get the caption for the ChaptersButton based on the track label. This will also 19369 * use the current tracks localized kind as a fallback if a label does not exist. 19370 * 19371 * @return {string} 19372 * The tracks current label or the localized track kind. 19373 */ 19374 ; 19375 19376 _proto.getMenuCaption = function getMenuCaption() { 19377 if (this.track_ && this.track_.label) { 19378 return this.track_.label; 19379 } 19380 19381 return this.localize(toTitleCase(this.kind_)); 19382 } 19383 /** 19384 * Create menu from chapter track 19385 * 19386 * @return {Menu} 19387 * New menu for the chapter buttons 19388 */ 19389 ; 19390 19391 _proto.createMenu = function createMenu() { 19392 this.options_.title = this.getMenuCaption(); 19393 return _TextTrackButton.prototype.createMenu.call(this); 19394 } 19395 /** 19396 * Create a menu item for each text track 19397 * 19398 * @return {TextTrackMenuItem[]} 19399 * Array of menu items 19400 */ 19401 ; 19402 19403 _proto.createItems = function createItems() { 19404 var items = []; 19405 19406 if (!this.track_) { 19407 return items; 19408 } 19409 19410 var cues = this.track_.cues; 19411 19412 if (!cues) { 19413 return items; 19414 } 19415 19416 for (var i = 0, l = cues.length; i < l; i++) { 19417 var cue = cues[i]; 19418 var mi = new ChaptersTrackMenuItem(this.player_, { 19419 track: this.track_, 19420 cue: cue 19421 }); 19422 items.push(mi); 19423 } 19424 19425 return items; 19426 }; 19427 19428 return ChaptersButton; 19429 }(TextTrackButton); 19430 /** 19431 * `kind` of TextTrack to look for to associate it with this menu. 19432 * 19433 * @type {string} 19434 * @private 19435 */ 19436 19437 19438 ChaptersButton.prototype.kind_ = 'chapters'; 19439 /** 19440 * The text that should display over the `ChaptersButton`s controls. Added for localization. 19441 * 19442 * @type {string} 19443 * @private 19444 */ 19445 19446 ChaptersButton.prototype.controlText_ = 'Chapters'; 19447 Component.registerComponent('ChaptersButton', ChaptersButton); 19448 19449 /** 19450 * The button component for toggling and selecting descriptions 19451 * 19452 * @extends TextTrackButton 19453 */ 19454 19455 var DescriptionsButton = 19456 /*#__PURE__*/ 19457 function (_TextTrackButton) { 19458 _inheritsLoose(DescriptionsButton, _TextTrackButton); 19459 19460 /** 19461 * Creates an instance of this class. 19462 * 19463 * @param {Player} player 19464 * The `Player` that this class should be attached to. 19465 * 19466 * @param {Object} [options] 19467 * The key/value store of player options. 19468 * 19469 * @param {Component~ReadyCallback} [ready] 19470 * The function to call when this component is ready. 19471 */ 19472 function DescriptionsButton(player, options, ready) { 19473 var _this; 19474 19475 _this = _TextTrackButton.call(this, player, options, ready) || this; 19476 var tracks = player.textTracks(); 19477 var changeHandler = bind(_assertThisInitialized(_this), _this.handleTracksChange); 19478 tracks.addEventListener('change', changeHandler); 19479 19480 _this.on('dispose', function () { 19481 tracks.removeEventListener('change', changeHandler); 19482 }); 19483 19484 return _this; 19485 } 19486 /** 19487 * Handle text track change 19488 * 19489 * @param {EventTarget~Event} event 19490 * The event that caused this function to run 19491 * 19492 * @listens TextTrackList#change 19493 */ 19494 19495 19496 var _proto = DescriptionsButton.prototype; 19497 19498 _proto.handleTracksChange = function handleTracksChange(event) { 19499 var tracks = this.player().textTracks(); 19500 var disabled = false; // Check whether a track of a different kind is showing 19501 19502 for (var i = 0, l = tracks.length; i < l; i++) { 19503 var track = tracks[i]; 19504 19505 if (track.kind !== this.kind_ && track.mode === 'showing') { 19506 disabled = true; 19507 break; 19508 } 19509 } // If another track is showing, disable this menu button 19510 19511 19512 if (disabled) { 19513 this.disable(); 19514 } else { 19515 this.enable(); 19516 } 19517 } 19518 /** 19519 * Builds the default DOM `className`. 19520 * 19521 * @return {string} 19522 * The DOM `className` for this object. 19523 */ 19524 ; 19525 19526 _proto.buildCSSClass = function buildCSSClass() { 19527 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19528 }; 19529 19530 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19531 return "vjs-descriptions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19532 }; 19533 19534 return DescriptionsButton; 19535 }(TextTrackButton); 19536 /** 19537 * `kind` of TextTrack to look for to associate it with this menu. 19538 * 19539 * @type {string} 19540 * @private 19541 */ 19542 19543 19544 DescriptionsButton.prototype.kind_ = 'descriptions'; 19545 /** 19546 * The text that should display over the `DescriptionsButton`s controls. Added for localization. 19547 * 19548 * @type {string} 19549 * @private 19550 */ 19551 19552 DescriptionsButton.prototype.controlText_ = 'Descriptions'; 19553 Component.registerComponent('DescriptionsButton', DescriptionsButton); 19554 19555 /** 19556 * The button component for toggling and selecting subtitles 19557 * 19558 * @extends TextTrackButton 19559 */ 19560 19561 var SubtitlesButton = 19562 /*#__PURE__*/ 19563 function (_TextTrackButton) { 19564 _inheritsLoose(SubtitlesButton, _TextTrackButton); 19565 19566 /** 19567 * Creates an instance of this class. 19568 * 19569 * @param {Player} player 19570 * The `Player` that this class should be attached to. 19571 * 19572 * @param {Object} [options] 19573 * The key/value store of player options. 19574 * 19575 * @param {Component~ReadyCallback} [ready] 19576 * The function to call when this component is ready. 19577 */ 19578 function SubtitlesButton(player, options, ready) { 19579 return _TextTrackButton.call(this, player, options, ready) || this; 19580 } 19581 /** 19582 * Builds the default DOM `className`. 19583 * 19584 * @return {string} 19585 * The DOM `className` for this object. 19586 */ 19587 19588 19589 var _proto = SubtitlesButton.prototype; 19590 19591 _proto.buildCSSClass = function buildCSSClass() { 19592 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19593 }; 19594 19595 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19596 return "vjs-subtitles-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19597 }; 19598 19599 return SubtitlesButton; 19600 }(TextTrackButton); 19601 /** 19602 * `kind` of TextTrack to look for to associate it with this menu. 19603 * 19604 * @type {string} 19605 * @private 19606 */ 19607 19608 19609 SubtitlesButton.prototype.kind_ = 'subtitles'; 19610 /** 19611 * The text that should display over the `SubtitlesButton`s controls. Added for localization. 19612 * 19613 * @type {string} 19614 * @private 19615 */ 19616 19617 SubtitlesButton.prototype.controlText_ = 'Subtitles'; 19618 Component.registerComponent('SubtitlesButton', SubtitlesButton); 19619 19620 /** 19621 * The menu item for caption track settings menu 19622 * 19623 * @extends TextTrackMenuItem 19624 */ 19625 19626 var CaptionSettingsMenuItem = 19627 /*#__PURE__*/ 19628 function (_TextTrackMenuItem) { 19629 _inheritsLoose(CaptionSettingsMenuItem, _TextTrackMenuItem); 19630 19631 /** 19632 * Creates an instance of this class. 19633 * 19634 * @param {Player} player 19635 * The `Player` that this class should be attached to. 19636 * 19637 * @param {Object} [options] 19638 * The key/value store of player options. 19639 */ 19640 function CaptionSettingsMenuItem(player, options) { 19641 var _this; 19642 19643 options.track = { 19644 player: player, 19645 kind: options.kind, 19646 label: options.kind + ' settings', 19647 selectable: false, 19648 "default": false, 19649 mode: 'disabled' 19650 }; // CaptionSettingsMenuItem has no concept of 'selected' 19651 19652 options.selectable = false; 19653 options.name = 'CaptionSettingsMenuItem'; 19654 _this = _TextTrackMenuItem.call(this, player, options) || this; 19655 19656 _this.addClass('vjs-texttrack-settings'); 19657 19658 _this.controlText(', opens ' + options.kind + ' settings dialog'); 19659 19660 return _this; 19661 } 19662 /** 19663 * This gets called when an `CaptionSettingsMenuItem` is "clicked". See 19664 * {@link ClickableComponent} for more detailed information on what a click can be. 19665 * 19666 * @param {EventTarget~Event} [event] 19667 * The `keydown`, `tap`, or `click` event that caused this function to be 19668 * called. 19669 * 19670 * @listens tap 19671 * @listens click 19672 */ 19673 19674 19675 var _proto = CaptionSettingsMenuItem.prototype; 19676 19677 _proto.handleClick = function handleClick(event) { 19678 this.player().getChild('textTrackSettings').open(); 19679 }; 19680 19681 return CaptionSettingsMenuItem; 19682 }(TextTrackMenuItem); 19683 19684 Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); 19685 19686 /** 19687 * The button component for toggling and selecting captions 19688 * 19689 * @extends TextTrackButton 19690 */ 19691 19692 var CaptionsButton = 19693 /*#__PURE__*/ 19694 function (_TextTrackButton) { 19695 _inheritsLoose(CaptionsButton, _TextTrackButton); 19696 19697 /** 19698 * Creates an instance of this class. 19699 * 19700 * @param {Player} player 19701 * The `Player` that this class should be attached to. 19702 * 19703 * @param {Object} [options] 19704 * The key/value store of player options. 19705 * 19706 * @param {Component~ReadyCallback} [ready] 19707 * The function to call when this component is ready. 19708 */ 19709 function CaptionsButton(player, options, ready) { 19710 return _TextTrackButton.call(this, player, options, ready) || this; 19711 } 19712 /** 19713 * Builds the default DOM `className`. 19714 * 19715 * @return {string} 19716 * The DOM `className` for this object. 19717 */ 19718 19719 19720 var _proto = CaptionsButton.prototype; 19721 19722 _proto.buildCSSClass = function buildCSSClass() { 19723 return "vjs-captions-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19724 }; 19725 19726 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19727 return "vjs-captions-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19728 } 19729 /** 19730 * Create caption menu items 19731 * 19732 * @return {CaptionSettingsMenuItem[]} 19733 * The array of current menu items. 19734 */ 19735 ; 19736 19737 _proto.createItems = function createItems() { 19738 var items = []; 19739 19740 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 19741 items.push(new CaptionSettingsMenuItem(this.player_, { 19742 kind: this.kind_ 19743 })); 19744 this.hideThreshold_ += 1; 19745 } 19746 19747 return _TextTrackButton.prototype.createItems.call(this, items); 19748 }; 19749 19750 return CaptionsButton; 19751 }(TextTrackButton); 19752 /** 19753 * `kind` of TextTrack to look for to associate it with this menu. 19754 * 19755 * @type {string} 19756 * @private 19757 */ 19758 19759 19760 CaptionsButton.prototype.kind_ = 'captions'; 19761 /** 19762 * The text that should display over the `CaptionsButton`s controls. Added for localization. 19763 * 19764 * @type {string} 19765 * @private 19766 */ 19767 19768 CaptionsButton.prototype.controlText_ = 'Captions'; 19769 Component.registerComponent('CaptionsButton', CaptionsButton); 19770 19771 /** 19772 * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles 19773 * in the SubsCapsMenu. 19774 * 19775 * @extends TextTrackMenuItem 19776 */ 19777 19778 var SubsCapsMenuItem = 19779 /*#__PURE__*/ 19780 function (_TextTrackMenuItem) { 19781 _inheritsLoose(SubsCapsMenuItem, _TextTrackMenuItem); 19782 19783 function SubsCapsMenuItem() { 19784 return _TextTrackMenuItem.apply(this, arguments) || this; 19785 } 19786 19787 var _proto = SubsCapsMenuItem.prototype; 19788 19789 _proto.createEl = function createEl(type, props, attrs) { 19790 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 19791 19792 if (this.options_.track.kind === 'captions') { 19793 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Captions') + "</span>\n "; 19794 } 19795 19796 innerHTML += '</span>'; 19797 19798 var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({ 19799 innerHTML: innerHTML 19800 }, props), attrs); 19801 19802 return el; 19803 }; 19804 19805 return SubsCapsMenuItem; 19806 }(TextTrackMenuItem); 19807 19808 Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); 19809 19810 /** 19811 * The button component for toggling and selecting captions and/or subtitles 19812 * 19813 * @extends TextTrackButton 19814 */ 19815 19816 var SubsCapsButton = 19817 /*#__PURE__*/ 19818 function (_TextTrackButton) { 19819 _inheritsLoose(SubsCapsButton, _TextTrackButton); 19820 19821 function SubsCapsButton(player, options) { 19822 var _this; 19823 19824 if (options === void 0) { 19825 options = {}; 19826 } 19827 19828 _this = _TextTrackButton.call(this, player, options) || this; // Although North America uses "captions" in most cases for 19829 // "captions and subtitles" other locales use "subtitles" 19830 19831 _this.label_ = 'subtitles'; 19832 19833 if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { 19834 _this.label_ = 'captions'; 19835 } 19836 19837 _this.menuButton_.controlText(toTitleCase(_this.label_)); 19838 19839 return _this; 19840 } 19841 /** 19842 * Builds the default DOM `className`. 19843 * 19844 * @return {string} 19845 * The DOM `className` for this object. 19846 */ 19847 19848 19849 var _proto = SubsCapsButton.prototype; 19850 19851 _proto.buildCSSClass = function buildCSSClass() { 19852 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildCSSClass.call(this); 19853 }; 19854 19855 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 19856 return "vjs-subs-caps-button " + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); 19857 } 19858 /** 19859 * Create caption/subtitles menu items 19860 * 19861 * @return {CaptionSettingsMenuItem[]} 19862 * The array of current menu items. 19863 */ 19864 ; 19865 19866 _proto.createItems = function createItems() { 19867 var items = []; 19868 19869 if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) { 19870 items.push(new CaptionSettingsMenuItem(this.player_, { 19871 kind: this.label_ 19872 })); 19873 this.hideThreshold_ += 1; 19874 } 19875 19876 items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem); 19877 return items; 19878 }; 19879 19880 return SubsCapsButton; 19881 }(TextTrackButton); 19882 /** 19883 * `kind`s of TextTrack to look for to associate it with this menu. 19884 * 19885 * @type {array} 19886 * @private 19887 */ 19888 19889 19890 SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; 19891 /** 19892 * The text that should display over the `SubsCapsButton`s controls. 19893 * 19894 * 19895 * @type {string} 19896 * @private 19897 */ 19898 19899 SubsCapsButton.prototype.controlText_ = 'Subtitles'; 19900 Component.registerComponent('SubsCapsButton', SubsCapsButton); 19901 19902 /** 19903 * An {@link AudioTrack} {@link MenuItem} 19904 * 19905 * @extends MenuItem 19906 */ 19907 19908 var AudioTrackMenuItem = 19909 /*#__PURE__*/ 19910 function (_MenuItem) { 19911 _inheritsLoose(AudioTrackMenuItem, _MenuItem); 19912 19913 /** 19914 * Creates an instance of this class. 19915 * 19916 * @param {Player} player 19917 * The `Player` that this class should be attached to. 19918 * 19919 * @param {Object} [options] 19920 * The key/value store of player options. 19921 */ 19922 function AudioTrackMenuItem(player, options) { 19923 var _this; 19924 19925 var track = options.track; 19926 var tracks = player.audioTracks(); // Modify options for parent MenuItem class's init. 19927 19928 options.label = track.label || track.language || 'Unknown'; 19929 options.selected = track.enabled; 19930 _this = _MenuItem.call(this, player, options) || this; 19931 _this.track = track; 19932 19933 _this.addClass("vjs-" + track.kind + "-menu-item"); 19934 19935 var changeHandler = function changeHandler() { 19936 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 19937 args[_key] = arguments[_key]; 19938 } 19939 19940 _this.handleTracksChange.apply(_assertThisInitialized(_this), args); 19941 }; 19942 19943 tracks.addEventListener('change', changeHandler); 19944 19945 _this.on('dispose', function () { 19946 tracks.removeEventListener('change', changeHandler); 19947 }); 19948 19949 return _this; 19950 } 19951 19952 var _proto = AudioTrackMenuItem.prototype; 19953 19954 _proto.createEl = function createEl(type, props, attrs) { 19955 var innerHTML = "<span class=\"vjs-menu-item-text\">" + this.localize(this.options_.label); 19956 19957 if (this.options_.track.kind === 'main-desc') { 19958 innerHTML += "\n <span aria-hidden=\"true\" class=\"vjs-icon-placeholder\"></span>\n <span class=\"vjs-control-text\"> " + this.localize('Descriptions') + "</span>\n "; 19959 } 19960 19961 innerHTML += '</span>'; 19962 19963 var el = _MenuItem.prototype.createEl.call(this, type, assign({ 19964 innerHTML: innerHTML 19965 }, props), attrs); 19966 19967 return el; 19968 } 19969 /** 19970 * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} 19971 * for more detailed information on what a click can be. 19972 * 19973 * @param {EventTarget~Event} [event] 19974 * The `keydown`, `tap`, or `click` event that caused this function to be 19975 * called. 19976 * 19977 * @listens tap 19978 * @listens click 19979 */ 19980 ; 19981 19982 _proto.handleClick = function handleClick(event) { 19983 var tracks = this.player_.audioTracks(); 19984 19985 _MenuItem.prototype.handleClick.call(this, event); 19986 19987 for (var i = 0; i < tracks.length; i++) { 19988 var track = tracks[i]; 19989 track.enabled = track === this.track; 19990 } 19991 } 19992 /** 19993 * Handle any {@link AudioTrack} change. 19994 * 19995 * @param {EventTarget~Event} [event] 19996 * The {@link AudioTrackList#change} event that caused this to run. 19997 * 19998 * @listens AudioTrackList#change 19999 */ 20000 ; 20001 20002 _proto.handleTracksChange = function handleTracksChange(event) { 20003 this.selected(this.track.enabled); 20004 }; 20005 20006 return AudioTrackMenuItem; 20007 }(MenuItem); 20008 20009 Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); 20010 20011 /** 20012 * The base class for buttons that toggle specific {@link AudioTrack} types. 20013 * 20014 * @extends TrackButton 20015 */ 20016 20017 var AudioTrackButton = 20018 /*#__PURE__*/ 20019 function (_TrackButton) { 20020 _inheritsLoose(AudioTrackButton, _TrackButton); 20021 20022 /** 20023 * Creates an instance of this class. 20024 * 20025 * @param {Player} player 20026 * The `Player` that this class should be attached to. 20027 * 20028 * @param {Object} [options={}] 20029 * The key/value store of player options. 20030 */ 20031 function AudioTrackButton(player, options) { 20032 if (options === void 0) { 20033 options = {}; 20034 } 20035 20036 options.tracks = player.audioTracks(); 20037 return _TrackButton.call(this, player, options) || this; 20038 } 20039 /** 20040 * Builds the default DOM `className`. 20041 * 20042 * @return {string} 20043 * The DOM `className` for this object. 20044 */ 20045 20046 20047 var _proto = AudioTrackButton.prototype; 20048 20049 _proto.buildCSSClass = function buildCSSClass() { 20050 return "vjs-audio-button " + _TrackButton.prototype.buildCSSClass.call(this); 20051 }; 20052 20053 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 20054 return "vjs-audio-button " + _TrackButton.prototype.buildWrapperCSSClass.call(this); 20055 } 20056 /** 20057 * Create a menu item for each audio track 20058 * 20059 * @param {AudioTrackMenuItem[]} [items=[]] 20060 * An array of existing menu items to use. 20061 * 20062 * @return {AudioTrackMenuItem[]} 20063 * An array of menu items 20064 */ 20065 ; 20066 20067 _proto.createItems = function createItems(items) { 20068 if (items === void 0) { 20069 items = []; 20070 } 20071 20072 // if there's only one audio track, there no point in showing it 20073 this.hideThreshold_ = 1; 20074 var tracks = this.player_.audioTracks(); 20075 20076 for (var i = 0; i < tracks.length; i++) { 20077 var track = tracks[i]; 20078 items.push(new AudioTrackMenuItem(this.player_, { 20079 track: track, 20080 // MenuItem is selectable 20081 selectable: true, 20082 // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time) 20083 multiSelectable: false 20084 })); 20085 } 20086 20087 return items; 20088 }; 20089 20090 return AudioTrackButton; 20091 }(TrackButton); 20092 /** 20093 * The text that should display over the `AudioTrackButton`s controls. Added for localization. 20094 * 20095 * @type {string} 20096 * @private 20097 */ 20098 20099 20100 AudioTrackButton.prototype.controlText_ = 'Audio Track'; 20101 Component.registerComponent('AudioTrackButton', AudioTrackButton); 20102 20103 /** 20104 * The specific menu item type for selecting a playback rate. 20105 * 20106 * @extends MenuItem 20107 */ 20108 20109 var PlaybackRateMenuItem = 20110 /*#__PURE__*/ 20111 function (_MenuItem) { 20112 _inheritsLoose(PlaybackRateMenuItem, _MenuItem); 20113 20114 /** 20115 * Creates an instance of this class. 20116 * 20117 * @param {Player} player 20118 * The `Player` that this class should be attached to. 20119 * 20120 * @param {Object} [options] 20121 * The key/value store of player options. 20122 */ 20123 function PlaybackRateMenuItem(player, options) { 20124 var _this; 20125 20126 var label = options.rate; 20127 var rate = parseFloat(label, 10); // Modify options for parent MenuItem class's init. 20128 20129 options.label = label; 20130 options.selected = rate === 1; 20131 options.selectable = true; 20132 options.multiSelectable = false; 20133 _this = _MenuItem.call(this, player, options) || this; 20134 _this.label = label; 20135 _this.rate = rate; 20136 20137 _this.on(player, 'ratechange', _this.update); 20138 20139 return _this; 20140 } 20141 /** 20142 * This gets called when an `PlaybackRateMenuItem` is "clicked". See 20143 * {@link ClickableComponent} for more detailed information on what a click can be. 20144 * 20145 * @param {EventTarget~Event} [event]
20146 * The `keydown`, `tap`, or `click` event that caused this function to be 20147 * called. 20148 * 20149 * @listens tap 20150 * @listens click 20151 */ 20152 20153 20154 var _proto = PlaybackRateMenuItem.prototype; 20155 20156 _proto.handleClick = function handleClick(event) { 20157 _MenuItem.prototype.handleClick.call(this); 20158 20159 this.player().playbackRate(this.rate); 20160 } 20161 /** 20162 * Update the PlaybackRateMenuItem when the playbackrate changes. 20163 * 20164 * @param {EventTarget~Event} [event] 20165 * The `ratechange` event that caused this function to run. 20166 * 20167 * @listens Player#ratechange 20168 */ 20169 ; 20170 20171 _proto.update = function update(event) { 20172 this.selected(this.player().playbackRate() === this.rate); 20173 }; 20174 20175 return PlaybackRateMenuItem; 20176 }(MenuItem); 20177 /** 20178 * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. 20179 * 20180 * @type {string} 20181 * @private 20182 */ 20183 20184 20185 PlaybackRateMenuItem.prototype.contentElType = 'button'; 20186 Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); 20187 20188 /** 20189 * The component for controlling the playback rate. 20190 * 20191 * @extends MenuButton 20192 */ 20193 20194 var PlaybackRateMenuButton = 20195 /*#__PURE__*/ 20196 function (_MenuButton) { 20197 _inheritsLoose(PlaybackRateMenuButton, _MenuButton); 20198 20199 /** 20200 * Creates an instance of this class. 20201 * 20202 * @param {Player} player 20203 * The `Player` that this class should be attached to. 20204 * 20205 * @param {Object} [options] 20206 * The key/value store of player options. 20207 */ 20208 function PlaybackRateMenuButton(player, options) { 20209 var _this; 20210 20211 _this = _MenuButton.call(this, player, options) || this; 20212 20213 _this.updateVisibility(); 20214 20215 _this.updateLabel(); 20216 20217 _this.on(player, 'loadstart', _this.updateVisibility); 20218 20219 _this.on(player, 'ratechange', _this.updateLabel); 20220 20221 return _this; 20222 } 20223 /** 20224 * Create the `Component`'s DOM element 20225 * 20226 * @return {Element} 20227 * The element that was created. 20228 */ 20229 20230 20231 var _proto = PlaybackRateMenuButton.prototype; 20232 20233 _proto.createEl = function createEl$1() { 20234 var el = _MenuButton.prototype.createEl.call(this); 20235 20236 this.labelEl_ = createEl('div', { 20237 className: 'vjs-playback-rate-value', 20238 innerHTML: '1x' 20239 }); 20240 el.appendChild(this.labelEl_); 20241 return el; 20242 }; 20243 20244 _proto.dispose = function dispose() { 20245 this.labelEl_ = null; 20246 20247 _MenuButton.prototype.dispose.call(this); 20248 } 20249 /** 20250 * Builds the default DOM `className`. 20251 * 20252 * @return {string} 20253 * The DOM `className` for this object. 20254 */ 20255 ; 20256 20257 _proto.buildCSSClass = function buildCSSClass() { 20258 return "vjs-playback-rate " + _MenuButton.prototype.buildCSSClass.call(this); 20259 }; 20260 20261 _proto.buildWrapperCSSClass = function buildWrapperCSSClass() { 20262 return "vjs-playback-rate " + _MenuButton.prototype.buildWrapperCSSClass.call(this); 20263 } 20264 /** 20265 * Create the playback rate menu 20266 * 20267 * @return {Menu} 20268 * Menu object populated with {@link PlaybackRateMenuItem}s 20269 */ 20270 ; 20271 20272 _proto.createMenu = function createMenu() { 20273 var menu = new Menu(this.player()); 20274 var rates = this.playbackRates(); 20275 20276 if (rates) { 20277 for (var i = rates.length - 1; i >= 0; i--) { 20278 menu.addChild(new PlaybackRateMenuItem(this.player(), { 20279 rate: rates[i] + 'x' 20280 })); 20281 } 20282 } 20283 20284 return menu; 20285 } 20286 /** 20287 * Updates ARIA accessibility attributes 20288 */ 20289 ; 20290 20291 _proto.updateARIAAttributes = function updateARIAAttributes() { 20292 // Current playback rate 20293 this.el().setAttribute('aria-valuenow', this.player().playbackRate()); 20294 } 20295 /** 20296 * This gets called when an `PlaybackRateMenuButton` is "clicked". See 20297 * {@link ClickableComponent} for more detailed information on what a click can be. 20298 * 20299 * @param {EventTarget~Event} [event]
vendor: 32,734 bytes, lines 20300-21353
20300 * The `keydown`, `tap`, or `click` event that caused this function to be 20301 * called. 20302 * 20303 * @listens tap 20304 * @listens click 20305 */ 20306 ; 20307 20308 _proto.handleClick = function handleClick(event) { 20309 // select next rate option 20310 var currentRate = this.player().playbackRate(); 20311 var rates = this.playbackRates(); // this will select first one if the last one currently selected 20312 20313 var newRate = rates[0]; 20314 20315 for (var i = 0; i < rates.length; i++) { 20316 if (rates[i] > currentRate) { 20317 newRate = rates[i]; 20318 break; 20319 } 20320 } 20321 20322 this.player().playbackRate(newRate); 20323 } 20324 /** 20325 * Get possible playback rates 20326 * 20327 * @return {Array} 20328 * All possible playback rates 20329 */ 20330 ; 20331 20332 _proto.playbackRates = function playbackRates() { 20333 return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; 20334 } 20335 /** 20336 * Get whether playback rates is supported by the tech 20337 * and an array of playback rates exists 20338 * 20339 * @return {boolean} 20340 * Whether changing playback rate is supported 20341 */ 20342 ; 20343 20344 _proto.playbackRateSupported = function playbackRateSupported() { 20345 return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; 20346 } 20347 /** 20348 * Hide playback rate controls when they're no playback rate options to select 20349 * 20350 * @param {EventTarget~Event} [event] 20351 * The event that caused this function to run. 20352 * 20353 * @listens Player#loadstart 20354 */ 20355 ; 20356 20357 _proto.updateVisibility = function updateVisibility(event) { 20358 if (this.playbackRateSupported()) { 20359 this.removeClass('vjs-hidden'); 20360 } else { 20361 this.addClass('vjs-hidden'); 20362 } 20363 } 20364 /** 20365 * Update button label when rate changed 20366 * 20367 * @param {EventTarget~Event} [event] 20368 * The event that caused this function to run. 20369 * 20370 * @listens Player#ratechange 20371 */ 20372 ; 20373 20374 _proto.updateLabel = function updateLabel(event) { 20375 if (this.playbackRateSupported()) { 20376 this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; 20377 } 20378 }; 20379 20380 return PlaybackRateMenuButton; 20381 }(MenuButton); 20382 /** 20383 * The text that should display over the `FullscreenToggle`s controls. Added for localization. 20384 * 20385 * @type {string} 20386 * @private 20387 */ 20388 20389 20390 PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; 20391 Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); 20392 20393 /** 20394 * Just an empty spacer element that can be used as an append point for plugins, etc. 20395 * Also can be used to create space between elements when necessary. 20396 * 20397 * @extends Component 20398 */ 20399 20400 var Spacer = 20401 /*#__PURE__*/ 20402 function (_Component) { 20403 _inheritsLoose(Spacer, _Component); 20404 20405 function Spacer() { 20406 return _Component.apply(this, arguments) || this; 20407 } 20408 20409 var _proto = Spacer.prototype; 20410 20411 /** 20412 * Builds the default DOM `className`. 20413 * 20414 * @return {string} 20415 * The DOM `className` for this object. 20416 */ 20417 _proto.buildCSSClass = function buildCSSClass() { 20418 return "vjs-spacer " + _Component.prototype.buildCSSClass.call(this); 20419 } 20420 /** 20421 * Create the `Component`'s DOM element 20422 * 20423 * @return {Element} 20424 * The element that was created. 20425 */ 20426 ; 20427 20428 _proto.createEl = function createEl() { 20429 return _Component.prototype.createEl.call(this, 'div', { 20430 className: this.buildCSSClass() 20431 }); 20432 }; 20433 20434 return Spacer; 20435 }(Component); 20436 20437 Component.registerComponent('Spacer', Spacer); 20438 20439 /** 20440 * Spacer specifically meant to be used as an insertion point for new plugins, etc. 20441 * 20442 * @extends Spacer 20443 */ 20444 20445 var CustomControlSpacer = 20446 /*#__PURE__*/ 20447 function (_Spacer) { 20448 _inheritsLoose(CustomControlSpacer, _Spacer); 20449 20450 function CustomControlSpacer() { 20451 return _Spacer.apply(this, arguments) || this; 20452 } 20453 20454 var _proto = CustomControlSpacer.prototype; 20455 20456 /** 20457 * Builds the default DOM `className`. 20458 * 20459 * @return {string} 20460 * The DOM `className` for this object. 20461 */ 20462 _proto.buildCSSClass = function buildCSSClass() { 20463 return "vjs-custom-control-spacer " + _Spacer.prototype.buildCSSClass.call(this); 20464 } 20465 /** 20466 * Create the `Component`'s DOM element 20467 * 20468 * @return {Element} 20469 * The element that was created. 20470 */ 20471 ; 20472 20473 _proto.createEl = function createEl() { 20474 var el = _Spacer.prototype.createEl.call(this, { 20475 className: this.buildCSSClass() 20476 }); // No-flex/table-cell mode requires there be some content 20477 // in the cell to fill the remaining space of the table. 20478 20479 20480 el.innerHTML = "\xA0"; 20481 return el; 20482 }; 20483 20484 return CustomControlSpacer; 20485 }(Spacer); 20486 20487 Component.registerComponent('CustomControlSpacer', CustomControlSpacer); 20488 20489 /** 20490 * Container of main controls. 20491 * 20492 * @extends Component 20493 */ 20494 20495 var ControlBar = 20496 /*#__PURE__*/ 20497 function (_Component) { 20498 _inheritsLoose(ControlBar, _Component); 20499 20500 function ControlBar() { 20501 return _Component.apply(this, arguments) || this; 20502 } 20503 20504 var _proto = ControlBar.prototype; 20505 20506 /** 20507 * Create the `Component`'s DOM element 20508 * 20509 * @return {Element} 20510 * The element that was created. 20511 */ 20512 _proto.createEl = function createEl() { 20513 return _Component.prototype.createEl.call(this, 'div', { 20514 className: 'vjs-control-bar', 20515 dir: 'ltr' 20516 }); 20517 }; 20518 20519 return ControlBar; 20520 }(Component); 20521 /** 20522 * Default options for `ControlBar` 20523 * 20524 * @type {Object} 20525 * @private 20526 */ 20527 20528 20529 ControlBar.prototype.options_ = { 20530 children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'seekToLive', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] 20531 }; 20532 20533 if ('exitPictureInPicture' in document) { 20534 ControlBar.prototype.options_.children.splice(ControlBar.prototype.options_.children.length - 1, 0, 'pictureInPictureToggle'); 20535 } 20536 20537 Component.registerComponent('ControlBar', ControlBar); 20538 20539 /** 20540 * A display that indicates an error has occurred. This means that the video 20541 * is unplayable. 20542 * 20543 * @extends ModalDialog 20544 */ 20545 20546 var ErrorDisplay = 20547 /*#__PURE__*/ 20548 function (_ModalDialog) { 20549 _inheritsLoose(ErrorDisplay, _ModalDialog); 20550 20551 /** 20552 * Creates an instance of this class. 20553 * 20554 * @param {Player} player 20555 * The `Player` that this class should be attached to. 20556 * 20557 * @param {Object} [options] 20558 * The key/value store of player options. 20559 */ 20560 function ErrorDisplay(player, options) { 20561 var _this; 20562 20563 _this = _ModalDialog.call(this, player, options) || this; 20564 20565 _this.on(player, 'error', _this.open); 20566 20567 return _this; 20568 } 20569 /** 20570 * Builds the default DOM `className`. 20571 * 20572 * @return {string} 20573 * The DOM `className` for this object. 20574 * 20575 * @deprecated Since version 5. 20576 */ 20577 20578 20579 var _proto = ErrorDisplay.prototype; 20580 20581 _proto.buildCSSClass = function buildCSSClass() { 20582 return "vjs-error-display " + _ModalDialog.prototype.buildCSSClass.call(this); 20583 } 20584 /** 20585 * Gets the localized error message based on the `Player`s error. 20586 * 20587 * @return {string} 20588 * The `Player`s error message localized or an empty string. 20589 */ 20590 ; 20591 20592 _proto.content = function content() { 20593 var error = this.player().error(); 20594 return error ? this.localize(error.message) : ''; 20595 }; 20596 20597 return ErrorDisplay; 20598 }(ModalDialog); 20599 /** 20600 * The default options for an `ErrorDisplay`. 20601 * 20602 * @private 20603 */ 20604 20605 20606 ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, { 20607 pauseOnOpen: false, 20608 fillAlways: true, 20609 temporary: false, 20610 uncloseable: true 20611 }); 20612 Component.registerComponent('ErrorDisplay', ErrorDisplay); 20613 20614 var LOCAL_STORAGE_KEY = 'vjs-text-track-settings'; 20615 var COLOR_BLACK = ['#000', 'Black']; 20616 var COLOR_BLUE = ['#00F', 'Blue']; 20617 var COLOR_CYAN = ['#0FF', 'Cyan']; 20618 var COLOR_GREEN = ['#0F0', 'Green']; 20619 var COLOR_MAGENTA = ['#F0F', 'Magenta']; 20620 var COLOR_RED = ['#F00', 'Red']; 20621 var COLOR_WHITE = ['#FFF', 'White']; 20622 var COLOR_YELLOW = ['#FF0', 'Yellow']; 20623 var OPACITY_OPAQUE = ['1', 'Opaque']; 20624 var OPACITY_SEMI = ['0.5', 'Semi-Transparent']; 20625 var OPACITY_TRANS = ['0', 'Transparent']; // Configuration for the various <select> elements in the DOM of this component. 20626 // 20627 // Possible keys include: 20628 // 20629 // `default`: 20630 // The default option index. Only needs to be provided if not zero. 20631 // `parser`: 20632 // A function which is used to parse the value from the selected option in 20633 // a customized way. 20634 // `selector`: 20635 // The selector used to find the associated <select> element. 20636 20637 var selectConfigs = { 20638 backgroundColor: { 20639 selector: '.vjs-bg-color > select', 20640 id: 'captions-background-color-%s', 20641 label: 'Color', 20642 options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 20643 }, 20644 backgroundOpacity: { 20645 selector: '.vjs-bg-opacity > select', 20646 id: 'captions-background-opacity-%s', 20647 label: 'Transparency', 20648 options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS] 20649 }, 20650 color: { 20651 selector: '.vjs-fg-color > select', 20652 id: 'captions-foreground-color-%s', 20653 label: 'Color', 20654 options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN] 20655 }, 20656 edgeStyle: { 20657 selector: '.vjs-edge-style > select', 20658 id: '%s', 20659 label: 'Text Edge Style', 20660 options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']] 20661 }, 20662 fontFamily: { 20663 selector: '.vjs-font-family > select', 20664 id: 'captions-font-family-%s', 20665 label: 'Font Family', 20666 options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']] 20667 }, 20668 fontPercent: { 20669 selector: '.vjs-font-percent > select', 20670 id: 'captions-font-size-%s', 20671 label: 'Font Size', 20672 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%']], 20673 "default": 2, 20674 parser: function parser(v) { 20675 return v === '1.00' ? null : Number(v); 20676 } 20677 }, 20678 textOpacity: { 20679 selector: '.vjs-text-opacity > select', 20680 id: 'captions-foreground-opacity-%s', 20681 label: 'Transparency', 20682 options: [OPACITY_OPAQUE, OPACITY_SEMI] 20683 }, 20684 // Options for this object are defined below. 20685 windowColor: { 20686 selector: '.vjs-window-color > select', 20687 id: 'captions-window-color-%s', 20688 label: 'Color' 20689 }, 20690 // Options for this object are defined below. 20691 windowOpacity: { 20692 selector: '.vjs-window-opacity > select', 20693 id: 'captions-window-opacity-%s', 20694 label: 'Transparency', 20695 options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE] 20696 } 20697 }; 20698 selectConfigs.windowColor.options = selectConfigs.backgroundColor.options; 20699 /** 20700 * Get the actual value of an option. 20701 * 20702 * @param {string} value 20703 * The value to get 20704 * 20705 * @param {Function} [parser] 20706 * Optional function to adjust the value. 20707 * 20708 * @return {Mixed} 20709 * - Will be `undefined` if no value exists 20710 * - Will be `undefined` if the given value is "none". 20711 * - Will be the actual value otherwise. 20712 * 20713 * @private 20714 */ 20715 20716 function parseOptionValue(value, parser) { 20717 if (parser) { 20718 value = parser(value); 20719 } 20720 20721 if (value && value !== 'none') { 20722 return value; 20723 } 20724 } 20725 /** 20726 * Gets the value of the selected <option> element within a <select> element. 20727 * 20728 * @param {Element} el 20729 * the element to look in 20730 * 20731 * @param {Function} [parser] 20732 * Optional function to adjust the value. 20733 * 20734 * @return {Mixed} 20735 * - Will be `undefined` if no value exists 20736 * - Will be `undefined` if the given value is "none". 20737 * - Will be the actual value otherwise. 20738 * 20739 * @private 20740 */ 20741 20742 20743 function getSelectedOptionValue(el, parser) { 20744 var value = el.options[el.options.selectedIndex].value; 20745 return parseOptionValue(value, parser); 20746 } 20747 /** 20748 * Sets the selected <option> element within a <select> element based on a 20749 * given value. 20750 * 20751 * @param {Element} el 20752 * The element to look in. 20753 * 20754 * @param {string} value 20755 * the property to look on. 20756 * 20757 * @param {Function} [parser] 20758 * Optional function to adjust the value before comparing. 20759 * 20760 * @private 20761 */ 20762 20763 20764 function setSelectedOption(el, value, parser) { 20765 if (!value) { 20766 return; 20767 } 20768 20769 for (var i = 0; i < el.options.length; i++) { 20770 if (parseOptionValue(el.options[i].value, parser) === value) { 20771 el.selectedIndex = i; 20772 break; 20773 } 20774 } 20775 } 20776 /** 20777 * Manipulate Text Tracks settings. 20778 * 20779 * @extends ModalDialog 20780 */ 20781 20782 20783 var TextTrackSettings = 20784 /*#__PURE__*/ 20785 function (_ModalDialog) { 20786 _inheritsLoose(TextTrackSettings, _ModalDialog); 20787 20788 /** 20789 * Creates an instance of this class. 20790 * 20791 * @param {Player} player 20792 * The `Player` that this class should be attached to. 20793 * 20794 * @param {Object} [options] 20795 * The key/value store of player options. 20796 */ 20797 function TextTrackSettings(player, options) { 20798 var _this; 20799 20800 options.temporary = false; 20801 _this = _ModalDialog.call(this, player, options) || this; 20802 _this.updateDisplay = bind(_assertThisInitialized(_this), _this.updateDisplay); // fill the modal and pretend we have opened it 20803 20804 _this.fill(); 20805 20806 _this.hasBeenOpened_ = _this.hasBeenFilled_ = true; 20807 _this.endDialog = createEl('p', { 20808 className: 'vjs-control-text', 20809 textContent: _this.localize('End of dialog window.') 20810 }); 20811 20812 _this.el().appendChild(_this.endDialog); 20813 20814 _this.setDefaults(); // Grab `persistTextTrackSettings` from the player options if not passed in child options 20815 20816 20817 if (options.persistTextTrackSettings === undefined) { 20818 _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings; 20819 } 20820 20821 _this.on(_this.$('.vjs-done-button'), 'click', function () { 20822 _this.saveSettings(); 20823 20824 _this.close(); 20825 }); 20826 20827 _this.on(_this.$('.vjs-default-button'), 'click', function () { 20828 _this.setDefaults(); 20829 20830 _this.updateDisplay(); 20831 }); 20832 20833 each(selectConfigs, function (config) { 20834 _this.on(_this.$(config.selector), 'change', _this.updateDisplay); 20835 }); 20836 20837 if (_this.options_.persistTextTrackSettings) { 20838 _this.restoreSettings(); 20839 } 20840 20841 return _this; 20842 } 20843 20844 var _proto = TextTrackSettings.prototype; 20845 20846 _proto.dispose = function dispose() { 20847 this.endDialog = null; 20848 20849 _ModalDialog.prototype.dispose.call(this); 20850 } 20851 /** 20852 * Create a <select> element with configured options. 20853 * 20854 * @param {string} key 20855 * Configuration key to use during creation. 20856 * 20857 * @return {string} 20858 * An HTML string. 20859 * 20860 * @private 20861 */ 20862 ; 20863 20864 _proto.createElSelect_ = function createElSelect_(key, legendId, type) { 20865 var _this2 = this; 20866 20867 if (legendId === void 0) { 20868 legendId = ''; 20869 } 20870 20871 if (type === void 0) { 20872 type = 'label'; 20873 } 20874 20875 var config = selectConfigs[key]; 20876 var id = config.id.replace('%s', this.id_); 20877 var selectLabelledbyIds = [legendId, id].join(' ').trim(); 20878 return ["<" + type + " id=\"" + id + "\" class=\"" + (type === 'label' ? 'vjs-label' : '') + "\">", this.localize(config.label), "</" + type + ">", "<select aria-labelledby=\"" + selectLabelledbyIds + "\">"].concat(config.options.map(function (o) { 20879 var optionId = id + '-' + o[1].replace(/\W+/g, ''); 20880 return ["<option id=\"" + optionId + "\" value=\"" + o[0] + "\" ", "aria-labelledby=\"" + selectLabelledbyIds + " " + optionId + "\">", _this2.localize(o[1]), '</option>'].join(''); 20881 })).concat('</select>').join(''); 20882 } 20883 /** 20884 * Create foreground color element for the component 20885 * 20886 * @return {string} 20887 * An HTML string. 20888 * 20889 * @private 20890 */ 20891 ; 20892 20893 _proto.createElFgColor_ = function createElFgColor_() { 20894 var legendId = "captions-text-legend-" + this.id_; 20895 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(''); 20896 } 20897 /** 20898 * Create background color element for the component 20899 * 20900 * @return {string} 20901 * An HTML string. 20902 * 20903 * @private 20904 */ 20905 ; 20906 20907 _proto.createElBgColor_ = function createElBgColor_() { 20908 var legendId = "captions-background-" + this.id_; 20909 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(''); 20910 } 20911 /** 20912 * Create window color element for the component 20913 * 20914 * @return {string} 20915 * An HTML string. 20916 * 20917 * @private 20918 */ 20919 ; 20920 20921 _proto.createElWinColor_ = function createElWinColor_() { 20922 var legendId = "captions-window-" + this.id_; 20923 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(''); 20924 } 20925 /** 20926 * Create color elements for the component 20927 * 20928 * @return {Element} 20929 * The element that was created 20930 * 20931 * @private 20932 */ 20933 ; 20934 20935 _proto.createElColors_ = function createElColors_() { 20936 return createEl('div', { 20937 className: 'vjs-track-settings-colors', 20938 innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('') 20939 }); 20940 } 20941 /** 20942 * Create font elements for the component 20943 * 20944 * @return {Element} 20945 * The element that was created. 20946 * 20947 * @private 20948 */ 20949 ; 20950 20951 _proto.createElFont_ = function createElFont_() { 20952 return createEl('div', { 20953 className: 'vjs-track-settings-font', 20954 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('') 20955 }); 20956 } 20957 /** 20958 * Create controls for the component 20959 * 20960 * @return {Element} 20961 * The element that was created. 20962 * 20963 * @private 20964 */ 20965 ; 20966 20967 _proto.createElControls_ = function createElControls_() { 20968 var defaultsDescription = this.localize('restore all settings to the default values'); 20969 return createEl('div', { 20970 className: 'vjs-track-settings-controls', 20971 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('') 20972 }); 20973 }; 20974 20975 _proto.content = function content() { 20976 return [this.createElColors_(), this.createElFont_(), this.createElControls_()]; 20977 }; 20978 20979 _proto.label = function label() { 20980 return this.localize('Caption Settings Dialog'); 20981 }; 20982 20983 _proto.description = function description() { 20984 return this.localize('Beginning of dialog window. Escape will cancel and close the window.'); 20985 }; 20986 20987 _proto.buildCSSClass = function buildCSSClass() { 20988 return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings'; 20989 } 20990 /** 20991 * Gets an object of text track settings (or null). 20992 * 20993 * @return {Object} 20994 * An object with config values parsed from the DOM or localStorage. 20995 */ 20996 ; 20997 20998 _proto.getValues = function getValues() { 20999 var _this3 = this; 21000 21001 return reduce(selectConfigs, function (accum, config, key) { 21002 var value = getSelectedOptionValue(_this3.$(config.selector), config.parser); 21003 21004 if (value !== undefined) { 21005 accum[key] = value; 21006 } 21007 21008 return accum; 21009 }, {}); 21010 } 21011 /** 21012 * Sets text track settings from an object of values. 21013 * 21014 * @param {Object} values 21015 * An object with config values parsed from the DOM or localStorage. 21016 */ 21017 ; 21018 21019 _proto.setValues = function setValues(values) { 21020 var _this4 = this; 21021 21022 each(selectConfigs, function (config, key) { 21023 setSelectedOption(_this4.$(config.selector), values[key], config.parser); 21024 }); 21025 } 21026 /** 21027 * Sets all `<select>` elements to their default values. 21028 */ 21029 ; 21030 21031 _proto.setDefaults = function setDefaults() { 21032 var _this5 = this; 21033 21034 each(selectConfigs, function (config) { 21035 var index = config.hasOwnProperty('default') ? config["default"] : 0; 21036 _this5.$(config.selector).selectedIndex = index; 21037 }); 21038 } 21039 /** 21040 * Restore texttrack settings from localStorage 21041 */ 21042 ; 21043 21044 _proto.restoreSettings = function restoreSettings() { 21045 var values; 21046 21047 try { 21048 values = JSON.parse(window$1.localStorage.getItem(LOCAL_STORAGE_KEY)); 21049 } catch (err) { 21050 log.warn(err); 21051 } 21052 21053 if (values) { 21054 this.setValues(values); 21055 } 21056 } 21057 /** 21058 * Save text track settings to localStorage 21059 */ 21060 ; 21061 21062 _proto.saveSettings = function saveSettings() { 21063 if (!this.options_.persistTextTrackSettings) { 21064 return; 21065 } 21066 21067 var values = this.getValues(); 21068 21069 try { 21070 if (Object.keys(values).length) { 21071 window$1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values)); 21072 } else { 21073 window$1.localStorage.removeItem(LOCAL_STORAGE_KEY); 21074 } 21075 } catch (err) { 21076 log.warn(err); 21077 } 21078 } 21079 /** 21080 * Update display of text track settings 21081 */ 21082 ; 21083 21084 _proto.updateDisplay = function updateDisplay() { 21085 var ttDisplay = this.player_.getChild('textTrackDisplay'); 21086 21087 if (ttDisplay) { 21088 ttDisplay.updateDisplay(); 21089 } 21090 } 21091 /** 21092 * conditionally blur the element and refocus the captions button 21093 * 21094 * @private 21095 */ 21096 ; 21097 21098 _proto.conditionalBlur_ = function conditionalBlur_() { 21099 this.previouslyActiveEl_ = null; 21100 var cb = this.player_.controlBar; 21101 var subsCapsBtn = cb && cb.subsCapsButton; 21102 var ccBtn = cb && cb.captionsButton; 21103 21104 if (subsCapsBtn) { 21105 subsCapsBtn.focus(); 21106 } else if (ccBtn) { 21107 ccBtn.focus(); 21108 } 21109 }; 21110 21111 return TextTrackSettings; 21112 }(ModalDialog); 21113 21114 Component.registerComponent('TextTrackSettings', TextTrackSettings); 21115 21116 /** 21117 * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions. 21118 * 21119 * 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}. 21120 * 21121 * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option. 21122 * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below. 21123 * @example <caption>How to disable the resize manager</caption> 21124 * const player = videojs('#vid', { 21125 * resizeManager: false 21126 * }); 21127 * 21128 * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification} 21129 * 21130 * @extends Component 21131 */ 21132 21133 var ResizeManager = 21134 /*#__PURE__*/ 21135 function (_Component) { 21136 _inheritsLoose(ResizeManager, _Component); 21137 21138 /** 21139 * Create the ResizeManager. 21140 * 21141 * @param {Object} player 21142 * The `Player` that this class should be attached to. 21143 * 21144 * @param {Object} [options] 21145 * The key/value store of ResizeManager options. 21146 * 21147 * @param {Object} [options.ResizeObserver] 21148 * A polyfill for ResizeObserver can be passed in here. 21149 * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback. 21150 */ 21151 function ResizeManager(player, options) { 21152 var _this; 21153 21154 var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window$1.ResizeObserver; // if `null` was passed, we want to disable the ResizeObserver 21155 21156 if (options.ResizeObserver === null) { 21157 RESIZE_OBSERVER_AVAILABLE = false; 21158 } // Only create an element when ResizeObserver isn't available 21159 21160 21161 var options_ = mergeOptions({ 21162 createEl: !RESIZE_OBSERVER_AVAILABLE, 21163 reportTouchActivity: false 21164 }, options); 21165 _this = _Component.call(this, player, options_) || this; 21166 _this.ResizeObserver = options.ResizeObserver || window$1.ResizeObserver; 21167 _this.loadListener_ = null; 21168 _this.resizeObserver_ = null; 21169 _this.debouncedHandler_ = debounce(function () { 21170 _this.resizeHandler(); 21171 }, 100, false, _assertThisInitialized(_this)); 21172 21173 if (RESIZE_OBSERVER_AVAILABLE) { 21174 _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_); 21175 21176 _this.resizeObserver_.observe(player.el()); 21177 } else { 21178 _this.loadListener_ = function () { 21179 if (!_this.el_ || !_this.el_.contentWindow) { 21180 return; 21181 } 21182 21183 var debouncedHandler_ = _this.debouncedHandler_; 21184 21185 var unloadListener_ = _this.unloadListener_ = function () { 21186 off(this, 'resize', debouncedHandler_); 21187 off(this, 'unload', unloadListener_); 21188 unloadListener_ = null; 21189 }; // safari and edge can unload the iframe before resizemanager dispose 21190 // we have to dispose of event handlers correctly before that happens 21191 21192 21193 on(_this.el_.contentWindow, 'unload', unloadListener_); 21194 on(_this.el_.contentWindow, 'resize', debouncedHandler_); 21195 }; 21196 21197 _this.one('load', _this.loadListener_); 21198 } 21199 21200 return _this; 21201 } 21202 21203 var _proto = ResizeManager.prototype; 21204 21205 _proto.createEl = function createEl() { 21206 return _Component.prototype.createEl.call(this, 'iframe', { 21207 className: 'vjs-resize-manager', 21208 tabIndex: -1 21209 }, { 21210 'aria-hidden': 'true' 21211 }); 21212 } 21213 /** 21214 * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver 21215 * 21216 * @fires Player#playerresize 21217 */ 21218 ; 21219 21220 _proto.resizeHandler = function resizeHandler() { 21221 /** 21222 * Called when the player size has changed 21223 * 21224 * @event Player#playerresize 21225 * @type {EventTarget~Event} 21226 */ 21227 // make sure player is still around to trigger 21228 // prevents this from causing an error after dispose 21229 if (!this.player_ || !this.player_.trigger) { 21230 return; 21231 } 21232 21233 this.player_.trigger('playerresize'); 21234 }; 21235 21236 _proto.dispose = function dispose() { 21237 if (this.debouncedHandler_) { 21238 this.debouncedHandler_.cancel(); 21239 } 21240 21241 if (this.resizeObserver_) { 21242 if (this.player_.el()) { 21243 this.resizeObserver_.unobserve(this.player_.el()); 21244 } 21245 21246 this.resizeObserver_.disconnect(); 21247 } 21248 21249 if (this.loadListener_) { 21250 this.off('load', this.loadListener_); 21251 } 21252 21253 if (this.el_ && this.el_.contentWindow && this.unloadListener_) { 21254 this.unloadListener_.call(this.el_.contentWindow); 21255 } 21256 21257 this.ResizeObserver = null; 21258 this.resizeObserver = null; 21259 this.debouncedHandler_ = null; 21260 this.loadListener_ = null; 21261 21262 _Component.prototype.dispose.call(this); 21263 }; 21264 21265 return ResizeManager; 21266 }(Component); 21267 21268 Component.registerComponent('ResizeManager', ResizeManager); 21269 21270 /** 21271 * Computes the median of an array. 21272 * 21273 * @param {number[]} arr 21274 * Input array of numbers. 21275 * 21276 * @return {number} 21277 * Median value. 21278 */ 21279 var median = function median(arr) { 21280 var mid = Math.floor(arr.length / 2); 21281 var sortedList = [].concat(arr).sort(function (a, b) { 21282 return a - b; 21283 }); 21284 return arr.length % 2 !== 0 ? sortedList[mid] : (sortedList[mid - 1] + sortedList[mid]) / 2; 21285 }; 21286 21287 /* track when we are at the live edge, and other helpers for live playback */ 21288 21289 var LiveTracker = 21290 /*#__PURE__*/ 21291 function (_Component) { 21292 _inheritsLoose(LiveTracker, _Component); 21293 21294 function LiveTracker(player, options) { 21295 var _this; 21296 21297 // LiveTracker does not need an element 21298 var options_ = mergeOptions({ 21299 createEl: false 21300 }, options); 21301 _this = _Component.call(this, player, options_) || this; 21302 21303 _this.reset_(); 21304 21305 _this.on(_this.player_, 'durationchange', _this.handleDurationchange); // we don't need to track live playback if the document is hidden, 21306 // also, tracking when the document is hidden can 21307 // cause the CPU to spike and eventually crash the page on IE11. 21308 21309 21310 if (IE_VERSION && 'hidden' in document && 'visibilityState' in document) { 21311 _this.on(document, 'visibilitychange', _this.handleVisibilityChange); 21312 } 21313 21314 return _this; 21315 } 21316 21317 var _proto = LiveTracker.prototype; 21318 21319 _proto.handleVisibilityChange = function handleVisibilityChange() { 21320 if (this.player_.duration() !== Infinity) { 21321 return; 21322 } 21323 21324 if (document.hidden) { 21325 this.stopTracking(); 21326 } else { 21327 this.startTracking(); 21328 } 21329 }; 21330 21331 _proto.isBehind_ = function isBehind_() { 21332 // don't report that we are behind until a timeupdate has been seen 21333 if (!this.timeupdateSeen_) { 21334 return false; 21335 } 21336 21337 var liveCurrentTime = this.liveCurrentTime(); 21338 var currentTime = this.player_.currentTime(); // the live edge window is the amount of seconds away from live 21339 // that a player can be, but still be considered live. 21340 // we add 0.07 because the live tracking happens every 30ms 21341 // and we want some wiggle room for short segment live playback 21342 21343 var liveEdgeWindow = this.seekableIncrement_ * 2 + 0.07; // on Android liveCurrentTime can bee Infinity, because seekableEnd 21344 // can be Infinity, so we handle that case. 21345 21346 return liveCurrentTime !== Infinity && liveCurrentTime - liveEdgeWindow >= currentTime; 21347 } // all the functionality for tracking when seek end changes 21348 // and for tracking how far past seek end we should be 21349 ; 21350 21351 _proto.trackLive_ = function trackLive_() { 21352 this.pastSeekEnd_ = this.pastSeekEnd_; 21353 var seekable = this.player_.seekable();
21353 // skip undefined seekable 21354 21355 if (!seekable || !seekable.length) { 21356 return; 21357 } 21358 21359 var newSeekEnd = this.seekableEnd(); // we can only tell if we are behind live, when seekable changes 21360 // once we detect that seekable has changed we check the new seek 21361 // end against current time, with a fudge value of half a second. 21362 21363 if (newSeekEnd !== this.lastSeekEnd_) { 21364 if (this.lastSeekEnd_) { 21365 // we try to get the best fit value for the seeking increment 21366 // variable from the last 12 values. 21367 this.seekableIncrementList_ = this.seekableIncrementList_.slice(-11); 21368 this.seekableIncrementList_.push(Math.abs(newSeekEnd - this.lastSeekEnd_)); 21369 21370 if (this.seekableIncrementList_.length > 3) { 21371 this.seekableIncrement_ = median(this.seekableIncrementList_); 21372 } 21373 } 21374 21375 this.pastSeekEnd_ = 0; 21376 this.lastSeekEnd_ = newSeekEnd; 21377 this.trigger('seekableendchange'); 21378 } 21379 21380 this.pastSeekEnd_ = this.pastSeekEnd() + 0.03; 21381 21382 if (this.isBehind_() !== this.behindLiveEdge()) { 21383 this.behindLiveEdge_ = this.isBehind_(); 21384 this.trigger('liveedgechange'); 21385 } 21386 } 21387 /** 21388 * handle a durationchange event on the player 21389 * and start/stop tracking accordingly. 21390 */ 21391 ; 21392 21393 _proto.handleDurationchange = function handleDurationchange() { 21394 if (this.player_.duration() === Infinity) { 21395 this.startTracking(); 21396 } else { 21397 this.stopTracking(); 21398 } 21399 } 21400 /** 21401 * start tracking live playback 21402 */ 21403 ; 21404 21405 _proto.startTracking = function startTracking() { 21406 var _this2 = this; 21407 21408 if (this.isTracking()) { 21409 return; 21410 } // If we haven't seen a timeupdate, we need to check whether playback 21411 // began before this component started tracking. This can happen commonly 21412 // when using autoplay. 21413 21414 21415 if (!this.timeupdateSeen_) { 21416 this.timeupdateSeen_ = this.player_.hasStarted(); 21417 } 21418 21419 this.trackingInterval_ = this.setInterval(this.trackLive_, 30); 21420 this.trackLive_(); 21421 this.on(this.player_, 'play', this.trackLive_); 21422 this.on(this.player_, 'pause', this.trackLive_); // this is to prevent showing that we are not live 21423 // before a video starts to play 21424 21425 if (!this.timeupdateSeen_) { 21426 this.one(this.player_, 'play', this.handlePlay); 21427 21428 this.handleTimeupdate = function () { 21429 _this2.timeupdateSeen_ = true; 21430 _this2.handleTimeupdate = null; 21431 }; 21432 21433 this.one(this.player_, 'timeupdate', this.handleTimeupdate); 21434 } 21435 }; 21436 21437 _proto.handlePlay = function handlePlay() { 21438 this.one(this.player_, 'timeupdate', this.seekToLiveEdge); 21439 } 21440 /** 21441 * Stop tracking, and set all internal variables to 21442 * their initial value. 21443 */ 21444 ; 21445 21446 _proto.reset_ = function reset_() { 21447 this.pastSeekEnd_ = 0; 21448 this.lastSeekEnd_ = null; 21449 this.behindLiveEdge_ = null; 21450 this.timeupdateSeen_ = false; 21451 this.clearInterval(this.trackingInterval_); 21452 this.trackingInterval_ = null; 21453 this.seekableIncrement_ = 12; 21454 this.seekableIncrementList_ = []; 21455 this.off(this.player_, 'play', this.trackLive_); 21456 this.off(this.player_, 'pause', this.trackLive_); 21457 this.off(this.player_, 'play', this.handlePlay); 21458 this.off(this.player_, 'timeupdate', this.seekToLiveEdge); 21459 21460 if (this.handleTimeupdate) { 21461 this.off(this.player_, 'timeupdate', this.handleTimeupdate); 21462 this.handleTimeupdate = null; 21463 } 21464 } 21465 /** 21466 * stop tracking live playback 21467 */ 21468 ; 21469 21470 _proto.stopTracking = function stopTracking() { 21471 if (!this.isTracking()) { 21472 return; 21473 } 21474 21475 this.reset_(); 21476 } 21477 /** 21478 * A helper to get the player seekable end 21479 * so that we don't have to null check everywhere 21480 */ 21481 ; 21482 21483 _proto.seekableEnd = function seekableEnd() { 21484 var seekable = this.player_.seekable(); 21485 var seekableEnds = []; 21486 var i = seekable ? seekable.length : 0; 21487 21488 while (i--) { 21489 seekableEnds.push(seekable.end(i)); 21490 } // grab the furthest seekable end after sorting, or if there are none 21491 // default to Infinity 21492 21493 21494 return seekableEnds.length ? seekableEnds.sort()[seekableEnds.length - 1] : Infinity; 21495 } 21496 /** 21497 * A helper to get the player seekable start 21498 * so that we don't have to null check everywhere 21499 */ 21500 ; 21501 21502 _proto.seekableStart = function seekableStart() { 21503 var seekable = this.player_.seekable(); 21504 var seekableStarts = []; 21505 var i = seekable ? seekable.length : 0; 21506 21507 while (i--) { 21508 seekableStarts.push(seekable.start(i)); 21509 } // grab the first seekable start after sorting, or if there are none 21510 // default to 0 21511 21512 21513 return seekableStarts.length ? seekableStarts.sort()[0] : 0; 21514 } 21515 /** 21516 * Get the live time window 21517 */ 21518 ; 21519 21520 _proto.liveWindow = function liveWindow() { 21521 var liveCurrentTime = this.liveCurrentTime(); 21522 21523 if (liveCurrentTime === Infinity) { 21524 return Infinity; 21525 } 21526 21527 return liveCurrentTime - this.seekableStart(); 21528 } 21529 /** 21530 * Determines if the player is live, only checks if this component 21531 * is tracking live playback or not 21532 */ 21533 ; 21534 21535 _proto.isLive = function isLive() { 21536 return this.isTracking(); 21537 } 21538 /** 21539 * Determines if currentTime is at the live edge and won't fall behind 21540 * on each seekableendchange 21541 */ 21542 ; 21543 21544 _proto.atLiveEdge = function atLiveEdge() { 21545 return !this.behindLiveEdge(); 21546 } 21547 /** 21548 * get what we expect the live current time to be 21549 */ 21550 ; 21551 21552 _proto.liveCurrentTime = function liveCurrentTime() { 21553 return this.pastSeekEnd() + this.seekableEnd(); 21554 } 21555 /** 21556 * Returns how far past seek end we expect current time to be 21557 */ 21558 ; 21559 21560 _proto.pastSeekEnd = function pastSeekEnd() { 21561 return this.pastSeekEnd_; 21562 } 21563 /** 21564 * If we are currently behind the live edge, aka currentTime will be 21565 * behind on a seekableendchange 21566 */ 21567 ; 21568 21569 _proto.behindLiveEdge = function behindLiveEdge() { 21570 return this.behindLiveEdge_; 21571 }; 21572 21573 _proto.isTracking = function isTracking() { 21574 return typeof this.trackingInterval_ === 'number'; 21575 } 21576 /** 21577 * Seek to the live edge if we are behind the live edge 21578 */ 21579 ; 21580 21581 _proto.seekToLiveEdge = function seekToLiveEdge() { 21582 if (this.atLiveEdge()) { 21583 return; 21584 } 21585 21586 this.player_.currentTime(this.liveCurrentTime()); 21587 21588 if (this.player_.paused()) { 21589 this.player_.play(); 21590 } 21591 }; 21592 21593 _proto.dispose = function dispose() { 21594 this.stopTracking(); 21595 21596 _Component.prototype.dispose.call(this); 21597 }; 21598 21599 return LiveTracker; 21600 }(Component); 21601 21602 Component.registerComponent('LiveTracker', LiveTracker); 21603 21604 /** 21605 * This function is used to fire a sourceset when there is something 21606 * similar to `mediaEl.load()` being called. It will try to find the source via 21607 * the `src` attribute and then the `<source>` elements. It will then fire `sourceset` 21608 * with the source that was found or empty string if we cannot know. If it cannot 21609 * find a source then `sourceset` will not be fired. 21610 * 21611 * @param {Html5} tech 21612 * The tech object that sourceset was setup on 21613 * 21614 * @return {boolean} 21615 * returns false if the sourceset was not fired and true otherwise. 21616 */ 21617 21618 var sourcesetLoad = function sourcesetLoad(tech) { 21619 var el = tech.el(); // if `el.src` is set, that source will be loaded. 21620 21621 if (el.hasAttribute('src')) { 21622 tech.triggerSourceset(el.src); 21623 return true; 21624 } 21625 /** 21626 * Since there isn't a src property on the media element, source elements will be used for 21627 * implementing the source selection algorithm. This happens asynchronously and 21628 * for most cases were there is more than one source we cannot tell what source will 21629 * be loaded, without re-implementing the source selection algorithm. At this time we are not 21630 * going to do that. There are three special cases that we do handle here though: 21631 * 21632 * 1. If there are no sources, do not fire `sourceset`. 21633 * 2. If there is only one `<source>` with a `src` property/attribute that is our `src` 21634 * 3. If there is more than one `<source>` but all of them have the same `src` url. 21635 * That will be our src. 21636 */ 21637 21638 21639 var sources = tech.$$('source'); 21640 var srcUrls = []; 21641 var src = ''; // if there are no sources, do not fire sourceset 21642 21643 if (!sources.length) { 21644 return false; 21645 }
vendor: 21,813 bytes, lines 21645-22286
21645 // only count valid/non-duplicate source elements 21646 21647 21648 for (var i = 0; i < sources.length; i++) { 21649 var url = sources[i].src; 21650 21651 if (url && srcUrls.indexOf(url) === -1) { 21652 srcUrls.push(url); 21653 } 21654 } // there were no valid sources 21655 21656 21657 if (!srcUrls.length) { 21658 return false; 21659 } // there is only one valid source element url 21660 // use that 21661 21662 21663 if (srcUrls.length === 1) { 21664 src = srcUrls[0]; 21665 } 21666 21667 tech.triggerSourceset(src); 21668 return true; 21669 }; 21670 /** 21671 * our implementation of an `innerHTML` descriptor for browsers 21672 * that do not have one. 21673 */ 21674 21675 21676 var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', { 21677 get: function get() { 21678 return this.cloneNode(true).innerHTML; 21679 }, 21680 set: function set(v) { 21681 // make a dummy node to use innerHTML on 21682 var dummy = document.createElement(this.nodeName.toLowerCase()); // set innerHTML to the value provided 21683 21684 dummy.innerHTML = v; // make a document fragment to hold the nodes from dummy 21685 21686 var docFrag = document.createDocumentFragment(); // copy all of the nodes created by the innerHTML on dummy 21687 // to the document fragment 21688 21689 while (dummy.childNodes.length) { 21690 docFrag.appendChild(dummy.childNodes[0]); 21691 } // remove content 21692 21693 21694 this.innerText = ''; // now we add all of that html in one by appending the 21695 // document fragment. This is how innerHTML does it. 21696 21697 window$1.Element.prototype.appendChild.call(this, docFrag); // then return the result that innerHTML's setter would 21698 21699 return this.innerHTML; 21700 } 21701 }); 21702 /** 21703 * Get a property descriptor given a list of priorities and the 21704 * property to get. 21705 */ 21706 21707 var getDescriptor = function getDescriptor(priority, prop) { 21708 var descriptor = {}; 21709 21710 for (var i = 0; i < priority.length; i++) { 21711 descriptor = Object.getOwnPropertyDescriptor(priority[i], prop); 21712 21713 if (descriptor && descriptor.set && descriptor.get) { 21714 break; 21715 } 21716 } 21717 21718 descriptor.enumerable = true; 21719 descriptor.configurable = true; 21720 return descriptor; 21721 }; 21722 21723 var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) { 21724 return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, window$1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML'); 21725 }; 21726 /** 21727 * Patches browser internal functions so that we can tell synchronously 21728 * if a `<source>` was appended to the media element. For some reason this 21729 * causes a `sourceset` if the the media element is ready and has no source. 21730 * This happens when: 21731 * - The page has just loaded and the media element does not have a source. 21732 * - The media element was emptied of all sources, then `load()` was called. 21733 * 21734 * It does this by patching the following functions/properties when they are supported: 21735 * 21736 * - `append()` - can be used to add a `<source>` element to the media element 21737 * - `appendChild()` - can be used to add a `<source>` element to the media element 21738 * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element 21739 * - `innerHTML` - can be used to add a `<source>` element to the media element 21740 * 21741 * @param {Html5} tech 21742 * The tech object that sourceset is being setup on. 21743 */ 21744 21745 21746 var firstSourceWatch = function firstSourceWatch(tech) { 21747 var el = tech.el(); // make sure firstSourceWatch isn't setup twice. 21748 21749 if (el.resetSourceWatch_) { 21750 return; 21751 } 21752 21753 var old = {}; 21754 var innerDescriptor = getInnerHTMLDescriptor(tech); 21755 21756 var appendWrapper = function appendWrapper(appendFn) { 21757 return function () { 21758 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 21759 args[_key] = arguments[_key]; 21760 } 21761 21762 var retval = appendFn.apply(el, args); 21763 sourcesetLoad(tech); 21764 return retval; 21765 }; 21766 }; 21767 21768 ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) { 21769 if (!el[k]) { 21770 return; 21771 } // store the old function 21772 21773 21774 old[k] = el[k]; // call the old function with a sourceset if a source 21775 // was loaded 21776 21777 el[k] = appendWrapper(old[k]); 21778 }); 21779 Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, { 21780 set: appendWrapper(innerDescriptor.set) 21781 })); 21782 21783 el.resetSourceWatch_ = function () { 21784 el.resetSourceWatch_ = null; 21785 Object.keys(old).forEach(function (k) { 21786 el[k] = old[k]; 21787 }); 21788 Object.defineProperty(el, 'innerHTML', innerDescriptor); 21789 }; // on the first sourceset, we need to revert our changes 21790 21791 21792 tech.one('sourceset', el.resetSourceWatch_); 21793 }; 21794 /** 21795 * our implementation of a `src` descriptor for browsers 21796 * that do not have one. 21797 */ 21798 21799 21800 var srcDescriptorPolyfill = Object.defineProperty({}, 'src', { 21801 get: function get() { 21802 if (this.hasAttribute('src')) { 21803 return getAbsoluteURL(window$1.Element.prototype.getAttribute.call(this, 'src')); 21804 } 21805 21806 return ''; 21807 }, 21808 set: function set(v) { 21809 window$1.Element.prototype.setAttribute.call(this, 'src', v); 21810 return v; 21811 } 21812 }); 21813 21814 var getSrcDescriptor = function getSrcDescriptor(tech) { 21815 return getDescriptor([tech.el(), window$1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src'); 21816 }; 21817 /** 21818 * setup `sourceset` handling on the `Html5` tech. This function 21819 * patches the following element properties/functions: 21820 * 21821 * - `src` - to determine when `src` is set 21822 * - `setAttribute()` - to determine when `src` is set 21823 * - `load()` - this re-triggers the source selection algorithm, and can 21824 * cause a sourceset. 21825 * 21826 * If there is no source when we are adding `sourceset` support or during a `load()` 21827 * we also patch the functions listed in `firstSourceWatch`. 21828 * 21829 * @param {Html5} tech 21830 * The tech to patch 21831 */ 21832 21833 21834 var setupSourceset = function setupSourceset(tech) { 21835 if (!tech.featuresSourceset) { 21836 return; 21837 } 21838 21839 var el = tech.el(); // make sure sourceset isn't setup twice. 21840 21841 if (el.resetSourceset_) { 21842 return; 21843 } 21844 21845 var srcDescriptor = getSrcDescriptor(tech); 21846 var oldSetAttribute = el.setAttribute; 21847 var oldLoad = el.load; 21848 Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, { 21849 set: function set(v) { 21850 var retval = srcDescriptor.set.call(el, v); // we use the getter here to get the actual value set on src 21851 21852 tech.triggerSourceset(el.src); 21853 return retval; 21854 } 21855 })); 21856 21857 el.setAttribute = function (n, v) { 21858 var retval = oldSetAttribute.call(el, n, v); 21859 21860 if (/src/i.test(n)) { 21861 tech.triggerSourceset(el.src); 21862 } 21863 21864 return retval; 21865 }; 21866 21867 el.load = function () { 21868 var retval = oldLoad.call(el); // if load was called, but there was no source to fire 21869 // sourceset on. We have to watch for a source append 21870 // as that can trigger a `sourceset` when the media element 21871 // has no source 21872 21873 if (!sourcesetLoad(tech)) { 21874 tech.triggerSourceset(''); 21875 firstSourceWatch(tech); 21876 } 21877 21878 return retval; 21879 }; 21880 21881 if (el.currentSrc) { 21882 tech.triggerSourceset(el.currentSrc); 21883 } else if (!sourcesetLoad(tech)) { 21884 firstSourceWatch(tech); 21885 } 21886 21887 el.resetSourceset_ = function () { 21888 el.resetSourceset_ = null; 21889 el.load = oldLoad; 21890 el.setAttribute = oldSetAttribute; 21891 Object.defineProperty(el, 'src', srcDescriptor); 21892 21893 if (el.resetSourceWatch_) { 21894 el.resetSourceWatch_(); 21895 } 21896 }; 21897 }; 21898 21899 /** 21900 * HTML5 Media Controller - Wrapper for HTML5 Media API 21901 * 21902 * @mixes Tech~SourceHandlerAdditions 21903 * @extends Tech 21904 */ 21905 21906 var Html5 = 21907 /*#__PURE__*/ 21908 function (_Tech) { 21909 _inheritsLoose(Html5, _Tech); 21910 21911 /** 21912 * Create an instance of this Tech. 21913 * 21914 * @param {Object} [options] 21915 * The key/value store of player options. 21916 * 21917 * @param {Component~ReadyCallback} ready 21918 * Callback function to call when the `HTML5` Tech is ready. 21919 */ 21920 function Html5(options, ready) { 21921 var _this; 21922 21923 _this = _Tech.call(this, options, ready) || this; 21924 var source = options.source; 21925 var crossoriginTracks = false; // Set the source if one is provided 21926 // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted) 21927 // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source 21928 // anyway so the error gets fired. 21929 21930 if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) { 21931 _this.setSource(source); 21932 } else { 21933 _this.handleLateInit_(_this.el_); 21934 } // setup sourceset after late sourceset/init 21935 21936 21937 if (options.enableSourceset) { 21938 _this.setupSourcesetHandling_(); 21939 } 21940 21941 if (_this.el_.hasChildNodes()) { 21942 var nodes = _this.el_.childNodes; 21943 var nodesLength = nodes.length; 21944 var removeNodes = []; 21945 21946 while (nodesLength--) { 21947 var node = nodes[nodesLength]; 21948 var nodeName = node.nodeName.toLowerCase(); 21949 21950 if (nodeName === 'track') { 21951 if (!_this.featuresNativeTextTracks) { 21952 // Empty video tag tracks so the built-in player doesn't use them also. 21953 // This may not be fast enough to stop HTML5 browsers from reading the tags 21954 // so we'll need to turn off any default tracks if we're manually doing 21955 // captions and subtitles. videoElement.textTracks 21956 removeNodes.push(node); 21957 } else { 21958 // store HTMLTrackElement and TextTrack to remote list 21959 _this.remoteTextTrackEls().addTrackElement_(node); 21960 21961 _this.remoteTextTracks().addTrack(node.track); 21962 21963 _this.textTracks().addTrack(node.track); 21964 21965 if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) { 21966 crossoriginTracks = true; 21967 } 21968 } 21969 } 21970 } 21971 21972 for (var i = 0; i < removeNodes.length; i++) { 21973 _this.el_.removeChild(removeNodes[i]); 21974 } 21975 } 21976 21977 _this.proxyNativeTracks_(); 21978 21979 if (_this.featuresNativeTextTracks && crossoriginTracks) { 21980 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.'); 21981 } // prevent iOS Safari from disabling metadata text tracks during native playback 21982 21983 21984 _this.restoreMetadataTracksInIOSNativePlayer_(); // Determine if native controls should be used 21985 // Our goal should be to get the custom controls on mobile solid everywhere 21986 // so we can remove this all together. Right now this will block custom 21987 // controls on touch enabled laptops like the Chrome Pixel 21988 21989 21990 if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) { 21991 _this.setControls(true); 21992 } // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen` 21993 // into a `fullscreenchange` event 21994 21995 21996 _this.proxyWebkitFullscreen_(); 21997 21998 _this.triggerReady(); 21999 22000 return _this; 22001 } 22002 /** 22003 * Dispose of `HTML5` media element and remove all tracks. 22004 */ 22005 22006 22007 var _proto = Html5.prototype; 22008 22009 _proto.dispose = function dispose() { 22010 if (this.el_ && this.el_.resetSourceset_) { 22011 this.el_.resetSourceset_(); 22012 } 22013 22014 Html5.disposeMediaElement(this.el_); 22015 this.options_ = null; // tech will handle clearing of the emulated track list 22016 22017 _Tech.prototype.dispose.call(this); 22018 } 22019 /** 22020 * Modify the media element so that we can detect when 22021 * the source is changed. Fires `sourceset` just after the source has changed 22022 */ 22023 ; 22024 22025 _proto.setupSourcesetHandling_ = function setupSourcesetHandling_() { 22026 setupSourceset(this); 22027 } 22028 /** 22029 * When a captions track is enabled in the iOS Safari native player, all other 22030 * tracks are disabled (including metadata tracks), which nulls all of their 22031 * associated cue points. This will restore metadata tracks to their pre-fullscreen 22032 * state in those cases so that cue points are not needlessly lost. 22033 * 22034 * @private 22035 */ 22036 ; 22037 22038 _proto.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() { 22039 var textTracks = this.textTracks(); 22040 var metadataTracksPreFullscreenState; // captures a snapshot of every metadata track's current state 22041 22042 var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() { 22043 metadataTracksPreFullscreenState = []; 22044 22045 for (var i = 0; i < textTracks.length; i++) { 22046 var track = textTracks[i]; 22047 22048 if (track.kind === 'metadata') { 22049 metadataTracksPreFullscreenState.push({ 22050 track: track, 22051 storedMode: track.mode 22052 }); 22053 } 22054 } 22055 }; // snapshot each metadata track's initial state, and update the snapshot 22056 // each time there is a track 'change' event 22057 22058 22059 takeMetadataTrackSnapshot(); 22060 textTracks.addEventListener('change', takeMetadataTrackSnapshot); 22061 this.on('dispose', function () { 22062 return textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 22063 }); 22064 22065 var restoreTrackMode = function restoreTrackMode() { 22066 for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) { 22067 var storedTrack = metadataTracksPreFullscreenState[i]; 22068 22069 if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) { 22070 storedTrack.track.mode = storedTrack.storedMode; 22071 } 22072 } // we only want this handler to be executed on the first 'change' event 22073 22074 22075 textTracks.removeEventListener('change', restoreTrackMode); 22076 }; // when we enter fullscreen playback, stop updating the snapshot and 22077 // restore all track modes to their pre-fullscreen state 22078 22079 22080 this.on('webkitbeginfullscreen', function () { 22081 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); // remove the listener before adding it just in case it wasn't previously removed 22082 22083 textTracks.removeEventListener('change', restoreTrackMode); 22084 textTracks.addEventListener('change', restoreTrackMode); 22085 }); // start updating the snapshot again after leaving fullscreen 22086 22087 this.on('webkitendfullscreen', function () { 22088 // remove the listener before adding it just in case it wasn't previously removed 22089 textTracks.removeEventListener('change', takeMetadataTrackSnapshot); 22090 textTracks.addEventListener('change', takeMetadataTrackSnapshot); // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback 22091 22092 textTracks.removeEventListener('change', restoreTrackMode); 22093 }); 22094 } 22095 /** 22096 * Attempt to force override of tracks for the given type 22097 * 22098 * @param {string} type - Track type to override, possible values include 'Audio', 22099 * 'Video', and 'Text'. 22100 * @param {boolean} override - If set to true native audio/video will be overridden, 22101 * otherwise native audio/video will potentially be used. 22102 * @private 22103 */ 22104 ; 22105 22106 _proto.overrideNative_ = function overrideNative_(type, override) { 22107 var _this2 = this; 22108 22109 // If there is no behavioral change don't add/remove listeners 22110 if (override !== this["featuresNative" + type + "Tracks"]) { 22111 return; 22112 } 22113 22114 var lowerCaseType = type.toLowerCase(); 22115 22116 if (this[lowerCaseType + "TracksListeners_"]) { 22117 Object.keys(this[lowerCaseType + "TracksListeners_"]).forEach(function (eventName) { 22118 var elTracks = _this2.el()[lowerCaseType + "Tracks"]; 22119 22120 elTracks.removeEventListener(eventName, _this2[lowerCaseType + "TracksListeners_"][eventName]); 22121 }); 22122 } 22123 22124 this["featuresNative" + type + "Tracks"] = !override; 22125 this[lowerCaseType + "TracksListeners_"] = null; 22126 this.proxyNativeTracksForType_(lowerCaseType); 22127 } 22128 /** 22129 * Attempt to force override of native audio tracks. 22130 * 22131 * @param {boolean} override - If set to true native audio will be overridden, 22132 * otherwise native audio will potentially be used. 22133 */ 22134 ; 22135 22136 _proto.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) { 22137 this.overrideNative_('Audio', override); 22138 } 22139 /** 22140 * Attempt to force override of native video tracks. 22141 * 22142 * @param {boolean} override - If set to true native video will be overridden, 22143 * otherwise native video will potentially be used. 22144 */ 22145 ; 22146 22147 _proto.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) { 22148 this.overrideNative_('Video', override); 22149 } 22150 /** 22151 * Proxy native track list events for the given type to our track 22152 * lists if the browser we are playing in supports that type of track list. 22153 * 22154 * @param {string} name - Track type; values include 'audio', 'video', and 'text' 22155 * @private 22156 */ 22157 ; 22158 22159 _proto.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) { 22160 var _this3 = this; 22161 22162 var props = NORMAL[name]; 22163 var elTracks = this.el()[props.getterName]; 22164 var techTracks = this[props.getterName](); 22165 22166 if (!this["featuresNative" + props.capitalName + "Tracks"] || !elTracks || !elTracks.addEventListener) { 22167 return; 22168 } 22169 22170 var listeners = { 22171 change: function change(e) { 22172 techTracks.trigger({ 22173 type: 'change', 22174 target: techTracks, 22175 currentTarget: techTracks, 22176 srcElement: techTracks 22177 }); 22178 }, 22179 addtrack: function addtrack(e) { 22180 techTracks.addTrack(e.track); 22181 }, 22182 removetrack: function removetrack(e) { 22183 techTracks.removeTrack(e.track); 22184 } 22185 }; 22186 22187 var removeOldTracks = function removeOldTracks() { 22188 var removeTracks = []; 22189 22190 for (var i = 0; i < techTracks.length; i++) { 22191 var found = false; 22192 22193 for (var j = 0; j < elTracks.length; j++) { 22194 if (elTracks[j] === techTracks[i]) { 22195 found = true; 22196 break; 22197 } 22198 } 22199 22200 if (!found) { 22201 removeTracks.push(techTracks[i]); 22202 } 22203 } 22204 22205 while (removeTracks.length) { 22206 techTracks.removeTrack(removeTracks.shift()); 22207 } 22208 }; 22209 22210 this[props.getterName + 'Listeners_'] = listeners; 22211 Object.keys(listeners).forEach(function (eventName) { 22212 var listener = listeners[eventName]; 22213 elTracks.addEventListener(eventName, listener); 22214 22215 _this3.on('dispose', function (e) { 22216 return elTracks.removeEventListener(eventName, listener); 22217 }); 22218 }); // Remove (native) tracks that are not used anymore 22219 22220 this.on('loadstart', removeOldTracks); 22221 this.on('dispose', function (e) { 22222 return _this3.off('loadstart', removeOldTracks); 22223 }); 22224 } 22225 /** 22226 * Proxy all native track list events to our track lists if the browser we are playing 22227 * in supports that type of track list. 22228 * 22229 * @private 22230 */ 22231 ; 22232 22233 _proto.proxyNativeTracks_ = function proxyNativeTracks_() { 22234 var _this4 = this; 22235 22236 NORMAL.names.forEach(function (name) { 22237 _this4.proxyNativeTracksForType_(name); 22238 }); 22239 } 22240 /** 22241 * Create the `Html5` Tech's DOM element. 22242 * 22243 * @return {Element} 22244 * The element that gets created. 22245 */ 22246 ; 22247 22248 _proto.createEl = function createEl() { 22249 var el = this.options_.tag; // Check if this browser supports moving the element into the box. 22250 // On the iPhone video will break if you move the element, 22251 // So we have to create a brand new element. 22252 // If we ingested the player div, we do not need to move the media element. 22253 22254 if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) { 22255 // If the original tag is still there, clone and remove it. 22256 if (el) { 22257 var clone = el.cloneNode(true); 22258 22259 if (el.parentNode) { 22260 el.parentNode.insertBefore(clone, el); 22261 } 22262 22263 Html5.disposeMediaElement(el); 22264 el = clone; 22265 } else { 22266 el = document.createElement('video'); // determine if native controls should be used 22267 var tagAttributes = this.options_.tag && getAttributes(this.options_.tag); 22268 var attributes = mergeOptions({}, tagAttributes); 22269 22270 if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) { 22271 delete attributes.controls; 22272 } 22273 22274 setAttributes(el, assign(attributes, { 22275 id: this.options_.techId, 22276 "class": 'vjs-tech' 22277 })); 22278 } 22279 22280 el.playerId = this.options_.playerId; 22281 } 22282 22283 if (typeof this.options_.preload !== 'undefined') { 22284 setAttribute(el, 'preload', this.options_.preload); 22285 } // Update specific tag settings, in case they were overridden 22286 // `autoplay` has to be *last* so that `muted` and `playsinline` are present
vendor: 4,273 bytes, lines 22287-22408
22287 // when iOS/Safari or other browsers attempt to autoplay. 22288 22289 22290 var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay']; 22291 22292 for (var i = 0; i < settingsAttrs.length; i++) { 22293 var attr = settingsAttrs[i]; 22294 var value = this.options_[attr]; 22295 22296 if (typeof value !== 'undefined') { 22297 if (value) { 22298 setAttribute(el, attr, attr); 22299 } else { 22300 removeAttribute(el, attr); 22301 } 22302 22303 el[attr] = value; 22304 } 22305 } 22306 22307 return el; 22308 } 22309 /** 22310 * This will be triggered if the loadstart event has already fired, before videojs was 22311 * ready. Two known examples of when this can happen are: 22312 * 1. If we're loading the playback object after it has started loading 22313 * 2. The media is already playing the (often with autoplay on) then 22314 * 22315 * This function will fire another loadstart so that videojs can catchup. 22316 * 22317 * @fires Tech#loadstart 22318 * 22319 * @return {undefined} 22320 * returns nothing. 22321 */ 22322 ; 22323 22324 _proto.handleLateInit_ = function handleLateInit_(el) { 22325 if (el.networkState === 0 || el.networkState === 3) { 22326 // The video element hasn't started loading the source yet 22327 // or didn't find a source 22328 return; 22329 } 22330 22331 if (el.readyState === 0) { 22332 // NetworkState is set synchronously BUT loadstart is fired at the 22333 // end of the current stack, usually before setInterval(fn, 0). 22334 // So at this point we know loadstart may have already fired or is 22335 // about to fire, and either way the player hasn't seen it yet. 22336 // We don't want to fire loadstart prematurely here and cause a 22337 // double loadstart so we'll wait and see if it happens between now 22338 // and the next loop, and fire it if not. 22339 // HOWEVER, we also want to make sure it fires before loadedmetadata 22340 // which could also happen between now and the next loop, so we'll 22341 // watch for that also. 22342 var loadstartFired = false; 22343 22344 var setLoadstartFired = function setLoadstartFired() { 22345 loadstartFired = true; 22346 }; 22347 22348 this.on('loadstart', setLoadstartFired); 22349 22350 var triggerLoadstart = function triggerLoadstart() { 22351 // We did miss the original loadstart. Make sure the player 22352 // sees loadstart before loadedmetadata 22353 if (!loadstartFired) { 22354 this.trigger('loadstart'); 22355 } 22356 }; 22357 22358 this.on('loadedmetadata', triggerLoadstart); 22359 this.ready(function () { 22360 this.off('loadstart', setLoadstartFired); 22361 this.off('loadedmetadata', triggerLoadstart); 22362 22363 if (!loadstartFired) { 22364 // We did miss the original native loadstart. Fire it now. 22365 this.trigger('loadstart'); 22366 } 22367 }); 22368 return; 22369 } // From here on we know that loadstart already fired and we missed it. 22370 // The other readyState events aren't as much of a problem if we double 22371 // them, so not going to go to as much trouble as loadstart to prevent 22372 // that unless we find reason to. 22373 22374 22375 var eventsToTrigger = ['loadstart']; // loadedmetadata: newly equal to HAVE_METADATA (1) or greater 22376 22377 eventsToTrigger.push('loadedmetadata'); // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater 22378 22379 if (el.readyState >= 2) { 22380 eventsToTrigger.push('loadeddata'); 22381 } // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater 22382 22383 22384 if (el.readyState >= 3) { 22385 eventsToTrigger.push('canplay'); 22386 } // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4) 22387 22388 22389 if (el.readyState >= 4) { 22390 eventsToTrigger.push('canplaythrough'); 22391 } // We still need to give the player time to add event listeners 22392 22393 22394 this.ready(function () { 22395 eventsToTrigger.forEach(function (type) { 22396 this.trigger(type); 22397 }, this); 22398 }); 22399 } 22400 /** 22401 * Set current time for the `HTML5` tech. 22402 * 22403 * @param {number} seconds 22404 * Set the current time of the media to this. 22405 */ 22406 ; 22407 22408 _proto.setCurrentTime = function setCurrentTime(seconds) {
vendor: 12,562 bytes, lines 22409-22799
22409 try { 22410 this.el_.currentTime = seconds; 22411 } catch (e) { 22412 log(e, 'Video is not ready. (Video.js)'); // this.warning(VideoJS.warnings.videoNotReady); 22413 } 22414 } 22415 /** 22416 * Get the current duration of the HTML5 media element. 22417 * 22418 * @return {number} 22419 * The duration of the media or 0 if there is no duration. 22420 */ 22421 ; 22422 22423 _proto.duration = function duration() { 22424 var _this5 = this; 22425 22426 // Android Chrome will report duration as Infinity for VOD HLS until after 22427 // playback has started, which triggers the live display erroneously. 22428 // Return NaN if playback has not started and trigger a durationupdate once 22429 // the duration can be reliably known. 22430 if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) { 22431 // Wait for the first `timeupdate` with currentTime > 0 - there may be 22432 // several with 0 22433 var checkProgress = function checkProgress() { 22434 if (_this5.el_.currentTime > 0) { 22435 // Trigger durationchange for genuinely live video 22436 if (_this5.el_.duration === Infinity) { 22437 _this5.trigger('durationchange'); 22438 } 22439 22440 _this5.off('timeupdate', checkProgress); 22441 } 22442 }; 22443 22444 this.on('timeupdate', checkProgress); 22445 return NaN; 22446 } 22447 22448 return this.el_.duration || NaN; 22449 } 22450 /** 22451 * Get the current width of the HTML5 media element. 22452 * 22453 * @return {number} 22454 * The width of the HTML5 media element. 22455 */ 22456 ; 22457 22458 _proto.width = function width() { 22459 return this.el_.offsetWidth; 22460 } 22461 /** 22462 * Get the current height of the HTML5 media element. 22463 * 22464 * @return {number} 22465 * The height of the HTML5 media element. 22466 */ 22467 ; 22468 22469 _proto.height = function height() { 22470 return this.el_.offsetHeight; 22471 } 22472 /** 22473 * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into 22474 * `fullscreenchange` event. 22475 * 22476 * @private 22477 * @fires fullscreenchange 22478 * @listens webkitendfullscreen 22479 * @listens webkitbeginfullscreen 22480 * @listens webkitbeginfullscreen 22481 */ 22482 ; 22483 22484 _proto.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() { 22485 var _this6 = this; 22486 22487 if (!('webkitDisplayingFullscreen' in this.el_)) { 22488 return; 22489 } 22490 22491 var endFn = function endFn() { 22492 this.trigger('fullscreenchange', { 22493 isFullscreen: false 22494 }); 22495 }; 22496 22497 var beginFn = function beginFn() { 22498 if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') { 22499 this.one('webkitendfullscreen', endFn); 22500 this.trigger('fullscreenchange', { 22501 isFullscreen: true 22502 }); 22503 } 22504 }; 22505 22506 this.on('webkitbeginfullscreen', beginFn); 22507 this.on('dispose', function () { 22508 _this6.off('webkitbeginfullscreen', beginFn); 22509 22510 _this6.off('webkitendfullscreen', endFn); 22511 }); 22512 } 22513 /** 22514 * Check if fullscreen is supported on the current playback device. 22515 * 22516 * @return {boolean} 22517 * - True if fullscreen is supported. 22518 * - False if fullscreen is not supported. 22519 */ 22520 ; 22521 22522 _proto.supportsFullScreen = function supportsFullScreen() { 22523 if (typeof this.el_.webkitEnterFullScreen === 'function') { 22524 var userAgent = window$1.navigator && window$1.navigator.userAgent || ''; // Seems to be broken in Chromium/Chrome && Safari in Leopard 22525 22526 if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) { 22527 return true; 22528 } 22529 } 22530 22531 return false; 22532 } 22533 /** 22534 * Request that the `HTML5` Tech enter fullscreen. 22535 */ 22536 ; 22537 22538 _proto.enterFullScreen = function enterFullScreen() { 22539 var video = this.el_; 22540 22541 if (video.paused && video.networkState <= video.HAVE_METADATA) { 22542 // attempt to prime the video element for programmatic access 22543 // this isn't necessary on the desktop but shouldn't hurt 22544 this.el_.play(); // playing and pausing synchronously during the transition to fullscreen 22545 // can get iOS ~6.1 devices into a play/pause loop 22546 22547 this.setTimeout(function () { 22548 video.pause(); 22549 video.webkitEnterFullScreen(); 22550 }, 0); 22551 } else { 22552 video.webkitEnterFullScreen(); 22553 } 22554 } 22555 /** 22556 * Request that the `HTML5` Tech exit fullscreen. 22557 */ 22558 ; 22559 22560 _proto.exitFullScreen = function exitFullScreen() { 22561 this.el_.webkitExitFullScreen(); 22562 } 22563 /** 22564 * Create a floating video window always on top of other windows so that users may 22565 * continue consuming media while they interact with other content sites, or 22566 * applications on their device. 22567 * 22568 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 22569 * 22570 * @return {Promise} 22571 * A promise with a Picture-in-Picture window. 22572 */ 22573 ; 22574 22575 _proto.requestPictureInPicture = function requestPictureInPicture() { 22576 return this.el_.requestPictureInPicture(); 22577 } 22578 /** 22579 * A getter/setter for the `Html5` Tech's source object. 22580 * > Note: Please use {@link Html5#setSource} 22581 * 22582 * @param {Tech~SourceObject} [src] 22583 * The source object you want to set on the `HTML5` techs element. 22584 * 22585 * @return {Tech~SourceObject|undefined} 22586 * - The current source object when a source is not passed in. 22587 * - undefined when setting 22588 * 22589 * @deprecated Since version 5. 22590 */ 22591 ; 22592 22593 _proto.src = function src(_src) { 22594 if (_src === undefined) { 22595 return this.el_.src; 22596 } // Setting src through `src` instead of `setSrc` will be deprecated 22597 22598 22599 this.setSrc(_src); 22600 } 22601 /** 22602 * Reset the tech by removing all sources and then calling 22603 * {@link Html5.resetMediaElement}. 22604 */ 22605 ; 22606 22607 _proto.reset = function reset() { 22608 Html5.resetMediaElement(this.el_); 22609 } 22610 /** 22611 * Get the current source on the `HTML5` Tech. Falls back to returning the source from 22612 * the HTML5 media element. 22613 * 22614 * @return {Tech~SourceObject} 22615 * The current source object from the HTML5 tech. With a fallback to the 22616 * elements source. 22617 */ 22618 ; 22619 22620 _proto.currentSrc = function currentSrc() { 22621 if (this.currentSource_) { 22622 return this.currentSource_.src; 22623 } 22624 22625 return this.el_.currentSrc; 22626 } 22627 /** 22628 * Set controls attribute for the HTML5 media Element. 22629 * 22630 * @param {string} val 22631 * Value to set the controls attribute to 22632 */ 22633 ; 22634 22635 _proto.setControls = function setControls(val) { 22636 this.el_.controls = !!val; 22637 } 22638 /** 22639 * Create and returns a remote {@link TextTrack} object. 22640 * 22641 * @param {string} kind 22642 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) 22643 * 22644 * @param {string} [label] 22645 * Label to identify the text track 22646 * 22647 * @param {string} [language] 22648 * Two letter language abbreviation 22649 * 22650 * @return {TextTrack} 22651 * The TextTrack that gets created. 22652 */ 22653 ; 22654 22655 _proto.addTextTrack = function addTextTrack(kind, label, language) { 22656 if (!this.featuresNativeTextTracks) { 22657 return _Tech.prototype.addTextTrack.call(this, kind, label, language); 22658 } 22659 22660 return this.el_.addTextTrack(kind, label, language); 22661 } 22662 /** 22663 * Creates either native TextTrack or an emulated TextTrack depending 22664 * on the value of `featuresNativeTextTracks` 22665 * 22666 * @param {Object} options 22667 * The object should contain the options to initialize the TextTrack with. 22668 * 22669 * @param {string} [options.kind] 22670 * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). 22671 * 22672 * @param {string} [options.label] 22673 * Label to identify the text track 22674 * 22675 * @param {string} [options.language] 22676 * Two letter language abbreviation. 22677 * 22678 * @param {boolean} [options.default] 22679 * Default this track to on. 22680 * 22681 * @param {string} [options.id] 22682 * The internal id to assign this track. 22683 * 22684 * @param {string} [options.src] 22685 * A source url for the track. 22686 * 22687 * @return {HTMLTrackElement} 22688 * The track element that gets created. 22689 */ 22690 ; 22691 22692 _proto.createRemoteTextTrack = function createRemoteTextTrack(options) { 22693 if (!this.featuresNativeTextTracks) { 22694 return _Tech.prototype.createRemoteTextTrack.call(this, options); 22695 } 22696 22697 var htmlTrackElement = document.createElement('track'); 22698 22699 if (options.kind) { 22700 htmlTrackElement.kind = options.kind; 22701 } 22702 22703 if (options.label) { 22704 htmlTrackElement.label = options.label; 22705 } 22706 22707 if (options.language || options.srclang) { 22708 htmlTrackElement.srclang = options.language || options.srclang; 22709 } 22710 22711 if (options["default"]) { 22712 htmlTrackElement["default"] = options["default"]; 22713 } 22714 22715 if (options.id) { 22716 htmlTrackElement.id = options.id; 22717 } 22718 22719 if (options.src) { 22720 htmlTrackElement.src = options.src; 22721 } 22722 22723 return htmlTrackElement; 22724 } 22725 /** 22726 * Creates a remote text track object and returns an html track element. 22727 * 22728 * @param {Object} options The object should contain values for 22729 * kind, language, label, and src (location of the WebVTT file) 22730 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 22731 * automatically removed from the video element whenever the source changes 22732 * @return {HTMLTrackElement} An Html Track Element. 22733 * This can be an emulated {@link HTMLTrackElement} or a native one. 22734 * @deprecated The default value of the "manualCleanup" parameter will default 22735 * to "false" in upcoming versions of Video.js 22736 */ 22737 ; 22738 22739 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 22740 var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup); 22741 22742 if (this.featuresNativeTextTracks) { 22743 this.el().appendChild(htmlTrackElement); 22744 } 22745 22746 return htmlTrackElement; 22747 } 22748 /** 22749 * Remove remote `TextTrack` from `TextTrackList` object 22750 * 22751 * @param {TextTrack} track 22752 * `TextTrack` object to remove 22753 */ 22754 ; 22755 22756 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(track) { 22757 _Tech.prototype.removeRemoteTextTrack.call(this, track); 22758 22759 if (this.featuresNativeTextTracks) { 22760 var tracks = this.$$('track'); 22761 var i = tracks.length; 22762 22763 while (i--) { 22764 if (track === tracks[i] || track === tracks[i].track) { 22765 this.el().removeChild(tracks[i]); 22766 } 22767 } 22768 } 22769 } 22770 /** 22771 * Gets available media playback quality metrics as specified by the W3C's Media 22772 * Playback Quality API. 22773 * 22774 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 22775 * 22776 * @return {Object} 22777 * An object with supported media playback quality metrics 22778 */ 22779 ; 22780 22781 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 22782 if (typeof this.el().getVideoPlaybackQuality === 'function') { 22783 return this.el().getVideoPlaybackQuality(); 22784 } 22785 22786 var videoPlaybackQuality = {}; 22787 22788 if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') { 22789 videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount; 22790 videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount; 22791 } 22792 22793 if (window$1.performance && typeof window$1.performance.now === 'function') { 22794 videoPlaybackQuality.creationTime = window$1.performance.now(); 22795 } else if (window$1.performance && window$1.performance.timing && typeof window$1.performance.timing.navigationStart === 'number') { 22796 videoPlaybackQuality.creationTime = window$1.Date.now() - window$1.performance.timing.navigationStart; 22797 } 22798 22799 return videoPlaybackQuality;
vendor: 24,456 bytes, lines 22800-23513
22800 }; 22801 22802 return Html5; 22803 }(Tech); 22804 /* HTML5 Support Testing ---------------------------------------------------- */ 22805 22806 22807 if (isReal()) { 22808 /** 22809 * Element for testing browser HTML5 media capabilities 22810 * 22811 * @type {Element} 22812 * @constant 22813 * @private 22814 */ 22815 Html5.TEST_VID = document.createElement('video'); 22816 var track = document.createElement('track'); 22817 track.kind = 'captions'; 22818 track.srclang = 'en'; 22819 track.label = 'English'; 22820 Html5.TEST_VID.appendChild(track); 22821 } 22822 /** 22823 * Check if HTML5 media is supported by this browser/device. 22824 * 22825 * @return {boolean} 22826 * - True if HTML5 media is supported. 22827 * - False if HTML5 media is not supported. 22828 */ 22829 22830 22831 Html5.isSupported = function () { 22832 // IE with no Media Player is a LIAR! (#984) 22833 try { 22834 Html5.TEST_VID.volume = 0.5; 22835 } catch (e) { 22836 return false; 22837 } 22838 22839 return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType); 22840 }; 22841 /** 22842 * Check if the tech can support the given type 22843 * 22844 * @param {string} type 22845 * The mimetype to check 22846 * @return {string} 'probably', 'maybe', or '' (empty string) 22847 */ 22848 22849 22850 Html5.canPlayType = function (type) { 22851 return Html5.TEST_VID.canPlayType(type); 22852 }; 22853 /** 22854 * Check if the tech can support the given source 22855 * 22856 * @param {Object} srcObj 22857 * The source object 22858 * @param {Object} options 22859 * The options passed to the tech 22860 * @return {string} 'probably', 'maybe', or '' (empty string) 22861 */ 22862 22863 22864 Html5.canPlaySource = function (srcObj, options) { 22865 return Html5.canPlayType(srcObj.type); 22866 }; 22867 /** 22868 * Check if the volume can be changed in this browser/device. 22869 * Volume cannot be changed in a lot of mobile devices. 22870 * Specifically, it can't be changed from 1 on iOS. 22871 * 22872 * @return {boolean} 22873 * - True if volume can be controlled 22874 * - False otherwise 22875 */ 22876 22877 22878 Html5.canControlVolume = function () { 22879 // IE will error if Windows Media Player not installed #3315 22880 try { 22881 var volume = Html5.TEST_VID.volume; 22882 Html5.TEST_VID.volume = volume / 2 + 0.1; 22883 return volume !== Html5.TEST_VID.volume; 22884 } catch (e) { 22885 return false; 22886 } 22887 }; 22888 /** 22889 * Check if the volume can be muted in this browser/device. 22890 * Some devices, e.g. iOS, don't allow changing volume 22891 * but permits muting/unmuting. 22892 * 22893 * @return {bolean} 22894 * - True if volume can be muted 22895 * - False otherwise 22896 */ 22897 22898 22899 Html5.canMuteVolume = function () { 22900 try { 22901 var muted = Html5.TEST_VID.muted; // in some versions of iOS muted property doesn't always 22902 // work, so we want to set both property and attribute 22903 22904 Html5.TEST_VID.muted = !muted; 22905 22906 if (Html5.TEST_VID.muted) { 22907 setAttribute(Html5.TEST_VID, 'muted', 'muted'); 22908 } else { 22909 removeAttribute(Html5.TEST_VID, 'muted', 'muted'); 22910 } 22911 22912 return muted !== Html5.TEST_VID.muted; 22913 } catch (e) { 22914 return false; 22915 } 22916 }; 22917 /** 22918 * Check if the playback rate can be changed in this browser/device. 22919 * 22920 * @return {boolean} 22921 * - True if playback rate can be controlled 22922 * - False otherwise 22923 */ 22924 22925 22926 Html5.canControlPlaybackRate = function () { 22927 // Playback rate API is implemented in Android Chrome, but doesn't do anything 22928 // https://github.com/videojs/video.js/issues/3180 22929 if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) { 22930 return false; 22931 } // IE will error if Windows Media Player not installed #3315 22932 22933 22934 try { 22935 var playbackRate = Html5.TEST_VID.playbackRate; 22936 Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1; 22937 return playbackRate !== Html5.TEST_VID.playbackRate; 22938 } catch (e) { 22939 return false; 22940 } 22941 }; 22942 /** 22943 * Check if we can override a video/audio elements attributes, with 22944 * Object.defineProperty. 22945 * 22946 * @return {boolean} 22947 * - True if builtin attributes can be overridden 22948 * - False otherwise 22949 */ 22950 22951 22952 Html5.canOverrideAttributes = function () { 22953 // if we cannot overwrite the src/innerHTML property, there is no support 22954 // iOS 7 safari for instance cannot do this. 22955 try { 22956 var noop = function noop() {}; 22957 22958 Object.defineProperty(document.createElement('video'), 'src', { 22959 get: noop, 22960 set: noop 22961 }); 22962 Object.defineProperty(document.createElement('audio'), 'src', { 22963 get: noop, 22964 set: noop 22965 }); 22966 Object.defineProperty(document.createElement('video'), 'innerHTML', { 22967 get: noop, 22968 set: noop 22969 }); 22970 Object.defineProperty(document.createElement('audio'), 'innerHTML', { 22971 get: noop, 22972 set: noop 22973 }); 22974 } catch (e) { 22975 return false; 22976 } 22977 22978 return true; 22979 }; 22980 /** 22981 * Check to see if native `TextTrack`s are supported by this browser/device. 22982 * 22983 * @return {boolean} 22984 * - True if native `TextTrack`s are supported. 22985 * - False otherwise 22986 */ 22987 22988 22989 Html5.supportsNativeTextTracks = function () { 22990 return IS_ANY_SAFARI || IS_IOS && IS_CHROME; 22991 }; 22992 /** 22993 * Check to see if native `VideoTrack`s are supported by this browser/device 22994 * 22995 * @return {boolean} 22996 * - True if native `VideoTrack`s are supported. 22997 * - False otherwise 22998 */ 22999 23000 23001 Html5.supportsNativeVideoTracks = function () { 23002 return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks); 23003 }; 23004 /** 23005 * Check to see if native `AudioTrack`s are supported by this browser/device 23006 * 23007 * @return {boolean} 23008 * - True if native `AudioTrack`s are supported. 23009 * - False otherwise 23010 */ 23011 23012 23013 Html5.supportsNativeAudioTracks = function () { 23014 return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks); 23015 }; 23016 /** 23017 * An array of events available on the Html5 tech. 23018 * 23019 * @private 23020 * @type {Array} 23021 */ 23022 23023 23024 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']; 23025 /** 23026 * Boolean indicating whether the `Tech` supports volume control. 23027 * 23028 * @type {boolean} 23029 * @default {@link Html5.canControlVolume} 23030 */ 23031 23032 Html5.prototype.featuresVolumeControl = Html5.canControlVolume(); 23033 /** 23034 * Boolean indicating whether the `Tech` supports muting volume. 23035 * 23036 * @type {bolean} 23037 * @default {@link Html5.canMuteVolume} 23038 */ 23039 23040 Html5.prototype.featuresMuteControl = Html5.canMuteVolume(); 23041 /** 23042 * Boolean indicating whether the `Tech` supports changing the speed at which the media 23043 * plays. Examples: 23044 * - Set player to play 2x (twice) as fast 23045 * - Set player to play 0.5x (half) as fast 23046 * 23047 * @type {boolean} 23048 * @default {@link Html5.canControlPlaybackRate} 23049 */ 23050 23051 Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate(); 23052 /** 23053 * Boolean indicating whether the `Tech` supports the `sourceset` event. 23054 * 23055 * @type {boolean} 23056 * @default 23057 */ 23058 23059 Html5.prototype.featuresSourceset = Html5.canOverrideAttributes(); 23060 /** 23061 * Boolean indicating whether the `HTML5` tech currently supports the media element 23062 * moving in the DOM. iOS breaks if you move the media element, so this is set this to 23063 * false there. Everywhere else this should be true. 23064 * 23065 * @type {boolean} 23066 * @default 23067 */ 23068 23069 Html5.prototype.movingMediaElementInDOM = !IS_IOS; // TODO: Previous comment: No longer appears to be used. Can probably be removed. 23070 // Is this true? 23071 23072 /** 23073 * Boolean indicating whether the `HTML5` tech currently supports automatic media resize 23074 * when going into fullscreen. 23075 * 23076 * @type {boolean} 23077 * @default 23078 */ 23079 23080 Html5.prototype.featuresFullscreenResize = true; 23081 /** 23082 * Boolean indicating whether the `HTML5` tech currently supports the progress event. 23083 * If this is false, manual `progress` events will be triggered instead. 23084 * 23085 * @type {boolean} 23086 * @default 23087 */ 23088 23089 Html5.prototype.featuresProgressEvents = true; 23090 /** 23091 * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event. 23092 * If this is false, manual `timeupdate` events will be triggered instead. 23093 * 23094 * @default 23095 */ 23096 23097 Html5.prototype.featuresTimeupdateEvents = true; 23098 /** 23099 * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s. 23100 * 23101 * @type {boolean} 23102 * @default {@link Html5.supportsNativeTextTracks} 23103 */ 23104 23105 Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks(); 23106 /** 23107 * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s. 23108 * 23109 * @type {boolean} 23110 * @default {@link Html5.supportsNativeVideoTracks} 23111 */ 23112 23113 Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks(); 23114 /** 23115 * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s. 23116 * 23117 * @type {boolean} 23118 * @default {@link Html5.supportsNativeAudioTracks} 23119 */ 23120 23121 Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks(); // HTML5 Feature detection and Device Fixes --------------------------------- // 23122 23123 var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType; 23124 var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i; 23125 23126 Html5.patchCanPlayType = function () { 23127 // Android 4.0 and above can play HLS to some extent but it reports being unable to do so 23128 // Firefox and Chrome report correctly 23129 if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) { 23130 Html5.TEST_VID.constructor.prototype.canPlayType = function (type) { 23131 if (type && mpegurlRE.test(type)) { 23132 return 'maybe'; 23133 } 23134 23135 return canPlayType.call(this, type); 23136 }; 23137 } 23138 }; 23139 23140 Html5.unpatchCanPlayType = function () { 23141 var r = Html5.TEST_VID.constructor.prototype.canPlayType; 23142 Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType; 23143 return r; 23144 }; // by default, patch the media element 23145 23146 23147 Html5.patchCanPlayType(); 23148 23149 Html5.disposeMediaElement = function (el) { 23150 if (!el) { 23151 return; 23152 } 23153 23154 if (el.parentNode) { 23155 el.parentNode.removeChild(el); 23156 } // remove any child track or source nodes to prevent their loading 23157 23158 23159 while (el.hasChildNodes()) { 23160 el.removeChild(el.firstChild); 23161 } // remove any src reference. not setting `src=''` because that causes a warning 23162 // in firefox 23163 23164 23165 el.removeAttribute('src'); // force the media element to update its loading state by calling load() 23166 // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793) 23167 23168 if (typeof el.load === 'function') { 23169 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 23170 (function () { 23171 try { 23172 el.load(); 23173 } catch (e) {// not supported 23174 } 23175 })(); 23176 } 23177 }; 23178 23179 Html5.resetMediaElement = function (el) { 23180 if (!el) { 23181 return; 23182 } 23183 23184 var sources = el.querySelectorAll('source'); 23185 var i = sources.length; 23186 23187 while (i--) { 23188 el.removeChild(sources[i]); 23189 } // remove any src reference. 23190 // not setting `src=''` because that throws an error 23191 23192 23193 el.removeAttribute('src'); 23194 23195 if (typeof el.load === 'function') { 23196 // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473) 23197 (function () { 23198 try { 23199 el.load(); 23200 } catch (e) {// satisfy linter 23201 } 23202 })(); 23203 } 23204 }; 23205 /* Native HTML5 element property wrapping ----------------------------------- */ 23206 // Wrap native boolean attributes with getters that check both property and attribute 23207 // The list is as followed: 23208 // muted, defaultMuted, autoplay, controls, loop, playsinline 23209 23210 23211 [ 23212 /** 23213 * Get the value of `muted` from the media element. `muted` indicates 23214 * that the volume for the media should be set to silent. This does not actually change 23215 * the `volume` attribute. 23216 * 23217 * @method Html5#muted 23218 * @return {boolean} 23219 * - True if the value of `volume` should be ignored and the audio set to silent. 23220 * - False if the value of `volume` should be used. 23221 * 23222 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 23223 */ 23224 'muted', 23225 /** 23226 * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates 23227 * whether the media should start muted or not. Only changes the default state of the 23228 * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the 23229 * current state. 23230 * 23231 * @method Html5#defaultMuted 23232 * @return {boolean} 23233 * - The value of `defaultMuted` from the media element. 23234 * - True indicates that the media should start muted. 23235 * - False indicates that the media should not start muted 23236 * 23237 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 23238 */ 23239 'defaultMuted', 23240 /** 23241 * Get the value of `autoplay` from the media element. `autoplay` indicates 23242 * that the media should start to play as soon as the page is ready. 23243 * 23244 * @method Html5#autoplay 23245 * @return {boolean} 23246 * - The value of `autoplay` from the media element. 23247 * - True indicates that the media should start as soon as the page loads. 23248 * - False indicates that the media should not start as soon as the page loads. 23249 * 23250 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 23251 */ 23252 'autoplay', 23253 /** 23254 * Get the value of `controls` from the media element. `controls` indicates 23255 * whether the native media controls should be shown or hidden. 23256 * 23257 * @method Html5#controls 23258 * @return {boolean} 23259 * - The value of `controls` from the media element. 23260 * - True indicates that native controls should be showing. 23261 * - False indicates that native controls should be hidden. 23262 * 23263 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls} 23264 */ 23265 'controls', 23266 /** 23267 * Get the value of `loop` from the media element. `loop` indicates 23268 * that the media should return to the start of the media and continue playing once 23269 * it reaches the end. 23270 * 23271 * @method Html5#loop 23272 * @return {boolean} 23273 * - The value of `loop` from the media element. 23274 * - True indicates that playback should seek back to start once 23275 * the end of a media is reached. 23276 * - False indicates that playback should not loop back to the start when the 23277 * end of the media is reached. 23278 * 23279 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 23280 */ 23281 'loop', 23282 /** 23283 * Get the value of `playsinline` from the media element. `playsinline` indicates 23284 * to the browser that non-fullscreen playback is preferred when fullscreen 23285 * playback is the native default, such as in iOS Safari. 23286 * 23287 * @method Html5#playsinline 23288 * @return {boolean} 23289 * - The value of `playsinline` from the media element. 23290 * - True indicates that the media should play inline. 23291 * - False indicates that the media should not play inline. 23292 * 23293 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 23294 */ 23295 'playsinline'].forEach(function (prop) { 23296 Html5.prototype[prop] = function () { 23297 return this.el_[prop] || this.el_.hasAttribute(prop); 23298 }; 23299 }); // Wrap native boolean attributes with setters that set both property and attribute 23300 // The list is as followed: 23301 // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline 23302 // setControls is special-cased above 23303 23304 [ 23305 /** 23306 * Set the value of `muted` on the media element. `muted` indicates that the current 23307 * audio level should be silent. 23308 * 23309 * @method Html5#setMuted 23310 * @param {boolean} muted 23311 * - True if the audio should be set to silent 23312 * - False otherwise 23313 * 23314 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted} 23315 */ 23316 'muted', 23317 /** 23318 * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current 23319 * audio level should be silent, but will only effect the muted level on intial playback.. 23320 * 23321 * @method Html5.prototype.setDefaultMuted 23322 * @param {boolean} defaultMuted 23323 * - True if the audio should be set to silent 23324 * - False otherwise 23325 * 23326 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted} 23327 */ 23328 'defaultMuted', 23329 /** 23330 * Set the value of `autoplay` on the media element. `autoplay` indicates 23331 * that the media should start to play as soon as the page is ready. 23332 * 23333 * @method Html5#setAutoplay 23334 * @param {boolean} autoplay 23335 * - True indicates that the media should start as soon as the page loads. 23336 * - False indicates that the media should not start as soon as the page loads. 23337 * 23338 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay} 23339 */ 23340 'autoplay', 23341 /** 23342 * Set the value of `loop` on the media element. `loop` indicates 23343 * that the media should return to the start of the media and continue playing once 23344 * it reaches the end. 23345 * 23346 * @method Html5#setLoop 23347 * @param {boolean} loop 23348 * - True indicates that playback should seek back to start once 23349 * the end of a media is reached. 23350 * - False indicates that playback should not loop back to the start when the 23351 * end of the media is reached. 23352 * 23353 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop} 23354 */ 23355 'loop', 23356 /** 23357 * Set the value of `playsinline` from the media element. `playsinline` indicates 23358 * to the browser that non-fullscreen playback is preferred when fullscreen 23359 * playback is the native default, such as in iOS Safari. 23360 * 23361 * @method Html5#setPlaysinline 23362 * @param {boolean} playsinline 23363 * - True indicates that the media should play inline. 23364 * - False indicates that the media should not play inline. 23365 * 23366 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 23367 */ 23368 'playsinline'].forEach(function (prop) { 23369 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 23370 this.el_[prop] = v; 23371 23372 if (v) { 23373 this.el_.setAttribute(prop, prop); 23374 } else { 23375 this.el_.removeAttribute(prop); 23376 } 23377 }; 23378 }); // Wrap native properties with a getter 23379 // The list is as followed 23380 // paused, currentTime, buffered, volume, poster, preload, error, seeking 23381 // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState 23382 // readyState, videoWidth, videoHeight 23383 23384 [ 23385 /** 23386 * Get the value of `paused` from the media element. `paused` indicates whether the media element 23387 * is currently paused or not. 23388 * 23389 * @method Html5#paused 23390 * @return {boolean} 23391 * The value of `paused` from the media element. 23392 * 23393 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused} 23394 */ 23395 'paused', 23396 /** 23397 * Get the value of `currentTime` from the media element. `currentTime` indicates 23398 * the current second that the media is at in playback. 23399 * 23400 * @method Html5#currentTime 23401 * @return {number} 23402 * The value of `currentTime` from the media element. 23403 * 23404 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime} 23405 */ 23406 'currentTime', 23407 /** 23408 * Get the value of `buffered` from the media element. `buffered` is a `TimeRange` 23409 * object that represents the parts of the media that are already downloaded and 23410 * available for playback. 23411 * 23412 * @method Html5#buffered 23413 * @return {TimeRange} 23414 * The value of `buffered` from the media element. 23415 * 23416 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered} 23417 */ 23418 'buffered', 23419 /** 23420 * Get the value of `volume` from the media element. `volume` indicates 23421 * the current playback volume of audio for a media. `volume` will be a value from 0 23422 * (silent) to 1 (loudest and default). 23423 * 23424 * @method Html5#volume 23425 * @return {number} 23426 * The value of `volume` from the media element. Value will be between 0-1. 23427 * 23428 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 23429 */ 23430 'volume', 23431 /** 23432 * Get the value of `poster` from the media element. `poster` indicates 23433 * that the url of an image file that can/will be shown when no media data is available. 23434 * 23435 * @method Html5#poster 23436 * @return {string} 23437 * The value of `poster` from the media element. Value will be a url to an 23438 * image. 23439 * 23440 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster} 23441 */ 23442 'poster', 23443 /** 23444 * Get the value of `preload` from the media element. `preload` indicates 23445 * what should download before the media is interacted with. It can have the following 23446 * values: 23447 * - none: nothing should be downloaded 23448 * - metadata: poster and the first few frames of the media may be downloaded to get 23449 * media dimensions and other metadata 23450 * - auto: allow the media and metadata for the media to be downloaded before 23451 * interaction 23452 * 23453 * @method Html5#preload 23454 * @return {string} 23455 * The value of `preload` from the media element. Will be 'none', 'metadata', 23456 * or 'auto'. 23457 * 23458 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 23459 */ 23460 'preload', 23461 /** 23462 * Get the value of the `error` from the media element. `error` indicates any 23463 * MediaError that may have occurred during playback. If error returns null there is no 23464 * current error. 23465 * 23466 * @method Html5#error 23467 * @return {MediaError|null} 23468 * The value of `error` from the media element. Will be `MediaError` if there 23469 * is a current error and null otherwise. 23470 * 23471 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error} 23472 */ 23473 'error', 23474 /** 23475 * Get the value of `seeking` from the media element. `seeking` indicates whether the 23476 * media is currently seeking to a new position or not. 23477 * 23478 * @method Html5#seeking 23479 * @return {boolean} 23480 * - The value of `seeking` from the media element. 23481 * - True indicates that the media is currently seeking to a new position. 23482 * - False indicates that the media is not seeking to a new position at this time. 23483 * 23484 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking} 23485 */ 23486 'seeking', 23487 /** 23488 * Get the value of `seekable` from the media element. `seekable` returns a 23489 * `TimeRange` object indicating ranges of time that can currently be `seeked` to. 23490 * 23491 * @method Html5#seekable 23492 * @return {TimeRange} 23493 * The value of `seekable` from the media element. A `TimeRange` object 23494 * indicating the current ranges of time that can be seeked to. 23495 * 23496 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable} 23497 */ 23498 'seekable', 23499 /** 23500 * Get the value of `ended` from the media element. `ended` indicates whether 23501 * the media has reached the end or not. 23502 * 23503 * @method Html5#ended 23504 * @return {boolean} 23505 * - The value of `ended` from the media element. 23506 * - True indicates that the media has ended. 23507 * - False indicates that the media has not ended. 23508 * 23509 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended} 23510 */ 23511 'ended', 23512 /** 23513 * Get the value of `playbackRate` from the media element. `playbackRate` indicates
23514 * the rate at which the media is currently playing back. Examples: 23515 * - if playbackRate is set to 2, media will play twice as fast. 23516 * - if playbackRate is set to 0.5, media will play half as fast. 23517 * 23518 * @method Html5#playbackRate 23519 * @return {number} 23520 * The value of `playbackRate` from the media element. A number indicating 23521 * the current playback speed of the media, where 1 is normal speed. 23522 * 23523 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 23524 */ 23525 'playbackRate', 23526 /** 23527 * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates 23528 * the rate at which the media is currently playing back. This value will not indicate the current 23529 * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that. 23530 * 23531 * Examples: 23532 * - if defaultPlaybackRate is set to 2, media will play twice as fast. 23533 * - if defaultPlaybackRate is set to 0.5, media will play half as fast. 23534 * 23535 * @method Html5.prototype.defaultPlaybackRate 23536 * @return {number} 23537 * The value of `defaultPlaybackRate` from the media element. A number indicating 23538 * the current playback speed of the media, where 1 is normal speed. 23539 * 23540 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 23541 */ 23542 'defaultPlaybackRate', 23543 /** 23544 * Get the value of `played` from the media element. `played` returns a `TimeRange` 23545 * object representing points in the media timeline that have been played. 23546 * 23547 * @method Html5#played 23548 * @return {TimeRange} 23549 * The value of `played` from the media element. A `TimeRange` object indicating 23550 * the ranges of time that have been played. 23551 * 23552 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played} 23553 */ 23554 'played', 23555 /** 23556 * Get the value of `networkState` from the media element. `networkState` indicates 23557 * the current network state. It returns an enumeration from the following list: 23558 * - 0: NETWORK_EMPTY 23559 * - 1: NETWORK_IDLE 23560 * - 2: NETWORK_LOADING 23561 * - 3: NETWORK_NO_SOURCE 23562 * 23563 * @method Html5#networkState 23564 * @return {number} 23565 * The value of `networkState` from the media element. This will be a number 23566 * from the list in the description. 23567 * 23568 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate} 23569 */ 23570 'networkState', 23571 /** 23572 * Get the value of `readyState` from the media element. `readyState` indicates 23573 * the current state of the media element. It returns an enumeration from the 23574 * following list: 23575 * - 0: HAVE_NOTHING 23576 * - 1: HAVE_METADATA 23577 * - 2: HAVE_CURRENT_DATA 23578 * - 3: HAVE_FUTURE_DATA 23579 * - 4: HAVE_ENOUGH_DATA 23580 * 23581 * @method Html5#readyState 23582 * @return {number} 23583 * The value of `readyState` from the media element. This will be a number 23584 * from the list in the description. 23585 * 23586 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states} 23587 */ 23588 'readyState', 23589 /** 23590 * Get the value of `videoWidth` from the video element. `videoWidth` indicates 23591 * the current width of the video in css pixels. 23592 * 23593 * @method Html5#videoWidth 23594 * @return {number} 23595 * The value of `videoWidth` from the video element. This will be a number 23596 * in css pixels. 23597 * 23598 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 23599 */ 23600 'videoWidth', 23601 /** 23602 * Get the value of `videoHeight` from the video element. `videoHeight` indicates 23603 * the current height of the video in css pixels. 23604 * 23605 * @method Html5#videoHeight 23606 * @return {number} 23607 * The value of `videoHeight` from the video element. This will be a number 23608 * in css pixels. 23609 * 23610 * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth} 23611 */ 23612 'videoHeight'].forEach(function (prop) { 23613 Html5.prototype[prop] = function () { 23614 return this.el_[prop]; 23615 }; 23616 }); // Wrap native properties with a setter in this format: 23617 // set + toTitleCase(name) 23618 // The list is as follows: 23619 // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate 23620 23621 [ 23622 /** 23623 * Set the value of `volume` on the media element. `volume` indicates the current 23624 * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and 23625 * so on. 23626 * 23627 * @method Html5#setVolume 23628 * @param {number} percentAsDecimal 23629 * The volume percent as a decimal. Valid range is from 0-1. 23630 * 23631 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume} 23632 */ 23633 'volume', 23634 /** 23635 * Set the value of `src` on the media element. `src` indicates the current 23636 * {@link Tech~SourceObject} for the media. 23637 * 23638 * @method Html5#setSrc 23639 * @param {Tech~SourceObject} src 23640 * The source object to set as the current source. 23641 * 23642 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src} 23643 */ 23644 'src', 23645 /** 23646 * Set the value of `poster` on the media element. `poster` is the url to 23647 * an image file that can/will be shown when no media data is available. 23648 * 23649 * @method Html5#setPoster 23650 * @param {string} poster 23651 * The url to an image that should be used as the `poster` for the media 23652 * element. 23653 * 23654 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster} 23655 */ 23656 'poster', 23657 /** 23658 * Set the value of `preload` on the media element. `preload` indicates 23659 * what should download before the media is interacted with. It can have the following 23660 * values: 23661 * - none: nothing should be downloaded 23662 * - metadata: poster and the first few frames of the media may be downloaded to get 23663 * media dimensions and other metadata 23664 * - auto: allow the media and metadata for the media to be downloaded before 23665 * interaction 23666 * 23667 * @method Html5#setPreload 23668 * @param {string} preload 23669 * The value of `preload` to set on the media element. Must be 'none', 'metadata', 23670 * or 'auto'. 23671 * 23672 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload} 23673 */ 23674 'preload', 23675 /** 23676 * Set the value of `playbackRate` on the media element. `playbackRate` indicates 23677 * the rate at which the media should play back. Examples: 23678 * - if playbackRate is set to 2, media will play twice as fast. 23679 * - if playbackRate is set to 0.5, media will play half as fast. 23680 * 23681 * @method Html5#setPlaybackRate 23682 * @return {number} 23683 * The value of `playbackRate` from the media element. A number indicating 23684 * the current playback speed of the media, where 1 is normal speed. 23685 * 23686 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate} 23687 */ 23688 'playbackRate', 23689 /** 23690 * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates 23691 * the rate at which the media should play back upon initial startup. Changing this value 23692 * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}. 23693 * 23694 * Example Values: 23695 * - if playbackRate is set to 2, media will play twice as fast. 23696 * - if playbackRate is set to 0.5, media will play half as fast. 23697 * 23698 * @method Html5.prototype.setDefaultPlaybackRate 23699 * @return {number} 23700 * The value of `defaultPlaybackRate` from the media element. A number indicating 23701 * the current playback speed of the media, where 1 is normal speed. 23702 * 23703 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate} 23704 */ 23705 'defaultPlaybackRate'].forEach(function (prop) { 23706 Html5.prototype['set' + toTitleCase(prop)] = function (v) { 23707 this.el_[prop] = v; 23708 }; 23709 }); // wrap native functions with a function 23710 // The list is as follows: 23711 // pause, load, play 23712 23713 [ 23714 /** 23715 * A wrapper around the media elements `pause` function. This will call the `HTML5` 23716 * media elements `pause` function. 23717 * 23718 * @method Html5#pause 23719 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause} 23720 */ 23721 'pause', 23722 /** 23723 * A wrapper around the media elements `load` function. This will call the `HTML5`s 23724 * media element `load` function. 23725 * 23726 * @method Html5#load 23727 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load} 23728 */ 23729 'load', 23730 /** 23731 * A wrapper around the media elements `play` function. This will call the `HTML5`s 23732 * media element `play` function. 23733 * 23734 * @method Html5#play 23735 * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play} 23736 */ 23737 'play'].forEach(function (prop) { 23738 Html5.prototype[prop] = function () { 23739 return this.el_[prop](); 23740 }; 23741 }); 23742 Tech.withSourceHandlers(Html5); 23743 /**
23744 * Native source handler for Html5, simply passes the source to the media element. 23745 * 23746 * @property {Tech~SourceObject} source 23747 * The source object 23748 * 23749 * @property {Html5} tech 23750 * The instance of the HTML5 tech. 23751 */ 23752 23753 Html5.nativeSourceHandler = {}; 23754 /** 23755 * Check if the media element can play the given mime type. 23756 * 23757 * @param {string} type 23758 * The mimetype to check 23759 * 23760 * @return {string} 23761 * 'probably', 'maybe', or '' (empty string) 23762 */ 23763 23764 Html5.nativeSourceHandler.canPlayType = function (type) { 23765 // IE without MediaPlayer throws an error (#519) 23766 try { 23767 return Html5.TEST_VID.canPlayType(type); 23768 } catch (e) { 23769 return ''; 23770 } 23771 }; 23772 /** 23773 * Check if the media element can handle a source natively. 23774 * 23775 * @param {Tech~SourceObject} source 23776 * The source object 23777 * 23778 * @param {Object} [options] 23779 * Options to be passed to the tech. 23780 * 23781 * @return {string} 23782 * 'probably', 'maybe', or '' (empty string). 23783 */ 23784 23785 23786 Html5.nativeSourceHandler.canHandleSource = function (source, options) { 23787 // If a type was provided we should rely on that 23788 if (source.type) { 23789 return Html5.nativeSourceHandler.canPlayType(source.type); // If no type, fall back to checking 'video/[EXTENSION]' 23790 } else if (source.src) { 23791 var ext = getFileExtension(source.src); 23792 return Html5.nativeSourceHandler.canPlayType("video/" + ext); 23793 } 23794 23795 return ''; 23796 }; 23797 /** 23798 * Pass the source to the native media element. 23799 * 23800 * @param {Tech~SourceObject} source 23801 * The source object 23802 * 23803 * @param {Html5} tech 23804 * The instance of the Html5 tech 23805 * 23806 * @param {Object} [options] 23807 * The options to pass to the source 23808 */ 23809 23810 23811 Html5.nativeSourceHandler.handleSource = function (source, tech, options) { 23812 tech.setSrc(source.src); 23813 }; 23814 /** 23815 * A noop for the native dispose function, as cleanup is not needed. 23816 */ 23817 23818 23819 Html5.nativeSourceHandler.dispose = function () {}; // Register the native source handler 23820 23821 23822 Html5.registerSourceHandler(Html5.nativeSourceHandler); 23823 Tech.registerTech('Html5', Html5); 23824 23825 // on the player when they happen 23826 23827 var TECH_EVENTS_RETRIGGER = [ 23828 /** 23829 * Fired while the user agent is downloading media data. 23830 * 23831 * @event Player#progress 23832 * @type {EventTarget~Event} 23833 */ 23834 23835 /** 23836 * Retrigger the `progress` event that was triggered by the {@link Tech}. 23837 * 23838 * @private 23839 * @method Player#handleTechProgress_ 23840 * @fires Player#progress 23841 * @listens Tech#progress 23842 */ 23843 'progress', 23844 /** 23845 * Fires when the loading of an audio/video is aborted. 23846 * 23847 * @event Player#abort 23848 * @type {EventTarget~Event} 23849 */ 23850 23851 /** 23852 * Retrigger the `abort` event that was triggered by the {@link Tech}. 23853 * 23854 * @private 23855 * @method Player#handleTechAbort_ 23856 * @fires Player#abort 23857 * @listens Tech#abort 23858 */ 23859 'abort', 23860 /** 23861 * Fires when the browser is intentionally not getting media data. 23862 * 23863 * @event Player#suspend 23864 * @type {EventTarget~Event} 23865 */ 23866 23867 /** 23868 * Retrigger the `suspend` event that was triggered by the {@link Tech}. 23869 * 23870 * @private 23871 * @method Player#handleTechSuspend_ 23872 * @fires Player#suspend 23873 * @listens Tech#suspend 23874 */ 23875 'suspend', 23876 /** 23877 * Fires when the current playlist is empty. 23878 * 23879 * @event Player#emptied 23880 * @type {EventTarget~Event} 23881 */ 23882 23883 /** 23884 * Retrigger the `emptied` event that was triggered by the {@link Tech}. 23885 * 23886 * @private 23887 * @method Player#handleTechEmptied_ 23888 * @fires Player#emptied 23889 * @listens Tech#emptied 23890 */ 23891 'emptied', 23892 /** 23893 * Fires when the browser is trying to get media data, but data is not available. 23894 * 23895 * @event Player#stalled 23896 * @type {EventTarget~Event} 23897 */ 23898 23899 /** 23900 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 23901 * 23902 * @private 23903 * @method Player#handleTechStalled_ 23904 * @fires Player#stalled 23905 * @listens Tech#stalled 23906 */ 23907 'stalled', 23908 /** 23909 * Fires when the browser has loaded meta data for the audio/video. 23910 * 23911 * @event Player#loadedmetadata 23912 * @type {EventTarget~Event} 23913 */ 23914 23915 /** 23916 * Retrigger the `stalled` event that was triggered by the {@link Tech}. 23917 * 23918 * @private 23919 * @method Player#handleTechLoadedmetadata_ 23920 * @fires Player#loadedmetadata 23921 * @listens Tech#loadedmetadata 23922 */ 23923 'loadedmetadata', 23924 /** 23925 * Fires when the browser has loaded the current frame of the audio/video. 23926 * 23927 * @event Player#loadeddata 23928 * @type {event} 23929 */ 23930 23931 /** 23932 * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. 23933 * 23934 * @private 23935 * @method Player#handleTechLoaddeddata_ 23936 * @fires Player#loadeddata 23937 * @listens Tech#loadeddata 23938 */ 23939 'loadeddata', 23940 /** 23941 * Fires when the current playback position has changed. 23942 * 23943 * @event Player#timeupdate 23944 * @type {event} 23945 */ 23946 23947 /** 23948 * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. 23949 * 23950 * @private 23951 * @method Player#handleTechTimeUpdate_ 23952 * @fires Player#timeupdate 23953 * @listens Tech#timeupdate 23954 */ 23955 'timeupdate', 23956 /** 23957 * Fires when the video's intrinsic dimensions change 23958 * 23959 * @event Player#resize 23960 * @type {event} 23961 */ 23962 23963 /** 23964 * Retrigger the `resize` event that was triggered by the {@link Tech}. 23965 * 23966 * @private 23967 * @method Player#handleTechResize_ 23968 * @fires Player#resize 23969 * @listens Tech#resize 23970 */ 23971 'resize',
vendor: 24,112 bytes, lines 23972-24701
23972 /** 23973 * Fires when the volume has been changed 23974 * 23975 * @event Player#volumechange 23976 * @type {event} 23977 */ 23978 23979 /** 23980 * Retrigger the `volumechange` event that was triggered by the {@link Tech}. 23981 * 23982 * @private 23983 * @method Player#handleTechVolumechange_ 23984 * @fires Player#volumechange 23985 * @listens Tech#volumechange 23986 */ 23987 'volumechange', 23988 /** 23989 * Fires when the text track has been changed 23990 * 23991 * @event Player#texttrackchange 23992 * @type {event} 23993 */ 23994 23995 /** 23996 * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. 23997 * 23998 * @private 23999 * @method Player#handleTechTexttrackchange_ 24000 * @fires Player#texttrackchange 24001 * @listens Tech#texttrackchange 24002 */ 24003 'texttrackchange']; // events to queue when playback rate is zero 24004 // this is a hash for the sole purpose of mapping non-camel-cased event names 24005 // to camel-cased function names 24006 24007 var TECH_EVENTS_QUEUE = { 24008 canplay: 'CanPlay', 24009 canplaythrough: 'CanPlayThrough', 24010 playing: 'Playing', 24011 seeked: 'Seeked' 24012 }; 24013 var BREAKPOINT_ORDER = ['tiny', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'huge']; 24014 var BREAKPOINT_CLASSES = {}; // grep: vjs-layout-tiny 24015 // grep: vjs-layout-x-small 24016 // grep: vjs-layout-small 24017 // grep: vjs-layout-medium 24018 // grep: vjs-layout-large 24019 // grep: vjs-layout-x-large 24020 // grep: vjs-layout-huge 24021 24022 BREAKPOINT_ORDER.forEach(function (k) { 24023 var v = k.charAt(0) === 'x' ? "x-" + k.substring(1) : k; 24024 BREAKPOINT_CLASSES[k] = "vjs-layout-" + v; 24025 }); 24026 var DEFAULT_BREAKPOINTS = { 24027 tiny: 210, 24028 xsmall: 320, 24029 small: 425, 24030 medium: 768, 24031 large: 1440, 24032 xlarge: 2560, 24033 huge: Infinity 24034 }; 24035 /** 24036 * An instance of the `Player` class is created when any of the Video.js setup methods 24037 * are used to initialize a video. 24038 * 24039 * After an instance has been created it can be accessed globally in two ways: 24040 * 1. By calling `videojs('example_video_1');` 24041 * 2. By using it directly via `videojs.players.example_video_1;` 24042 * 24043 * @extends Component 24044 */ 24045 24046 var Player = 24047 /*#__PURE__*/ 24048 function (_Component) { 24049 _inheritsLoose(Player, _Component); 24050 24051 /** 24052 * Create an instance of this class. 24053 * 24054 * @param {Element} tag 24055 * The original video DOM element used for configuring options. 24056 * 24057 * @param {Object} [options] 24058 * Object of option names and values. 24059 * 24060 * @param {Component~ReadyCallback} [ready] 24061 * Ready callback function. 24062 */ 24063 function Player(tag, options, ready) { 24064 var _this; 24065 24066 // Make sure tag ID exists 24067 tag.id = tag.id || options.id || "vjs_video_" + newGUID(); // Set Options 24068 // The options argument overrides options set in the video tag 24069 // which overrides globally set options. 24070 // This latter part coincides with the load order 24071 // (tag must exist before Player) 24072 24073 options = assign(Player.getTagSettings(tag), options); // Delay the initialization of children because we need to set up 24074 // player properties first, and can't use `this` before `super()` 24075 24076 options.initChildren = false; // Same with creating the element 24077 24078 options.createEl = false; // don't auto mixin the evented mixin 24079 24080 options.evented = false; // we don't want the player to report touch activity on itself 24081 // see enableTouchActivity in Component 24082 24083 options.reportTouchActivity = false; // If language is not set, get the closest lang attribute 24084 24085 if (!options.language) { 24086 if (typeof tag.closest === 'function') { 24087 var closest = tag.closest('[lang]'); 24088 24089 if (closest && closest.getAttribute) { 24090 options.language = closest.getAttribute('lang'); 24091 } 24092 } else { 24093 var element = tag; 24094 24095 while (element && element.nodeType === 1) { 24096 if (getAttributes(element).hasOwnProperty('lang')) { 24097 options.language = element.getAttribute('lang'); 24098 break; 24099 } 24100 24101 element = element.parentNode; 24102 } 24103 } 24104 } // Run base component initializing with new options 24105 24106 24107 _this = _Component.call(this, null, options, ready) || this; // Create bound methods for document listeners. 24108 24109 _this.boundDocumentFullscreenChange_ = bind(_assertThisInitialized(_this), _this.documentFullscreenChange_); 24110 _this.boundFullWindowOnEscKey_ = bind(_assertThisInitialized(_this), _this.fullWindowOnEscKey); // create logger 24111 24112 _this.log = createLogger$1(_this.id_); // Hold our own reference to fullscreen api so it can be mocked in tests 24113 24114 _this.fsApi_ = FullscreenApi; // Tracks when a tech changes the poster 24115 24116 _this.isPosterFromTech_ = false; // Holds callback info that gets queued when playback rate is zero 24117 // and a seek is happening 24118 24119 _this.queuedCallbacks_ = []; // Turn off API access because we're loading a new tech that might load asynchronously 24120 24121 _this.isReady_ = false; // Init state hasStarted_ 24122 24123 _this.hasStarted_ = false; // Init state userActive_ 24124 24125 _this.userActive_ = false; // if the global option object was accidentally blown away by 24126 // someone, bail early with an informative error 24127 24128 if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { 24129 throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); 24130 } // Store the original tag used to set options 24131 24132 24133 _this.tag = tag; // Store the tag attributes used to restore html5 element 24134 24135 _this.tagAttributes = tag && getAttributes(tag); // Update current language 24136 24137 _this.language(_this.options_.language); // Update Supported Languages 24138 24139 24140 if (options.languages) { 24141 // Normalise player option languages to lowercase 24142 var languagesToLower = {}; 24143 Object.getOwnPropertyNames(options.languages).forEach(function (name) { 24144 languagesToLower[name.toLowerCase()] = options.languages[name]; 24145 }); 24146 _this.languages_ = languagesToLower; 24147 } else { 24148 _this.languages_ = Player.prototype.options_.languages; 24149 } 24150 24151 _this.resetCache_(); // Set poster 24152 24153 24154 _this.poster_ = options.poster || ''; // Set controls 24155 24156 _this.controls_ = !!options.controls; // Original tag settings stored in options 24157 // now remove immediately so native controls don't flash. 24158 // May be turned back on by HTML5 tech if nativeControlsForTouch is true 24159 24160 tag.controls = false; 24161 tag.removeAttribute('controls'); 24162 _this.changingSrc_ = false; 24163 _this.playCallbacks_ = []; 24164 _this.playTerminatedQueue_ = []; // the attribute overrides the option 24165 24166 if (tag.hasAttribute('autoplay')) { 24167 _this.autoplay(true); 24168 } else { 24169 // otherwise use the setter to validate and 24170 // set the correct value. 24171 _this.autoplay(_this.options_.autoplay); 24172 } // check plugins 24173 24174 24175 if (options.plugins) { 24176 Object.keys(options.plugins).forEach(function (name) { 24177 if (typeof _this[name] !== 'function') { 24178 throw new Error("plugin \"" + name + "\" does not exist"); 24179 } 24180 }); 24181 } 24182 /* 24183 * Store the internal state of scrubbing 24184 * 24185 * @private 24186 * @return {Boolean} True if the user is scrubbing 24187 */ 24188 24189 24190 _this.scrubbing_ = false; 24191 _this.el_ = _this.createEl(); // Make this an evented object and use `el_` as its event bus. 24192 24193 evented(_assertThisInitialized(_this), { 24194 eventBusKey: 'el_' 24195 }); 24196 24197 if (_this.fluid_) { 24198 _this.on('playerreset', _this.updateStyleEl_); 24199 } // We also want to pass the original player options to each component and plugin 24200 // as well so they don't need to reach back into the player for options later. 24201 // We also need to do another copy of this.options_ so we don't end up with 24202 // an infinite loop. 24203 24204 24205 var playerOptionsCopy = mergeOptions(_this.options_); // Load plugins 24206 24207 if (options.plugins) { 24208 Object.keys(options.plugins).forEach(function (name) { 24209 _this[name](options.plugins[name]); 24210 }); 24211 } 24212 24213 _this.options_.playerOptions = playerOptionsCopy; 24214 _this.middleware_ = []; 24215 24216 _this.initChildren(); // Set isAudio based on whether or not an audio tag was used 24217 24218 24219 _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); // Update controls className. Can't do this when the controls are initially 24220 // set because the element doesn't exist yet. 24221 24222 24223 if (_this.controls()) { 24224 _this.addClass('vjs-controls-enabled'); 24225 } else { 24226 _this.addClass('vjs-controls-disabled'); 24227 } // Set ARIA label and region role depending on player type 24228 24229 24230 _this.el_.setAttribute('role', 'region'); 24231 24232 if (_this.isAudio()) { 24233 _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); 24234 } else { 24235 _this.el_.setAttribute('aria-label', _this.localize('Video Player')); 24236 } 24237 24238 if (_this.isAudio()) { 24239 _this.addClass('vjs-audio'); 24240 } 24241 24242 if (_this.flexNotSupported_()) { 24243 _this.addClass('vjs-no-flex'); 24244 } // TODO: Make this smarter. Toggle user state between touching/mousing 24245 // using events, since devices can have both touch and mouse events. 24246 // TODO: Make this check be performed again when the window switches between monitors 24247 // (See https://github.com/videojs/video.js/issues/5683) 24248 24249 24250 if (TOUCH_ENABLED) { 24251 _this.addClass('vjs-touch-enabled'); 24252 } // iOS Safari has broken hover handling 24253 24254 24255 if (!IS_IOS) { 24256 _this.addClass('vjs-workinghover'); 24257 } // Make player easily findable by ID 24258 24259 24260 Player.players[_this.id_] = _assertThisInitialized(_this); // Add a major version class to aid css in plugins 24261 24262 var majorVersion = version.split('.')[0]; 24263 24264 _this.addClass("vjs-v" + majorVersion); // When the player is first initialized, trigger activity so components 24265 // like the control bar show themselves if needed 24266 24267 24268 _this.userActive(true); 24269 24270 _this.reportUserActivity(); 24271 24272 _this.one('play', _this.listenForUserActivity_); 24273 24274 _this.on('stageclick', _this.handleStageClick_); 24275 24276 _this.on('keydown', _this.handleKeyDown); 24277 24278 _this.breakpoints(_this.options_.breakpoints); 24279 24280 _this.responsive(_this.options_.responsive); 24281 24282 return _this; 24283 } 24284 /** 24285 * Destroys the video player and does any necessary cleanup. 24286 * 24287 * This is especially helpful if you are dynamically adding and removing videos 24288 * to/from the DOM. 24289 * 24290 * @fires Player#dispose 24291 */ 24292 24293 24294 var _proto = Player.prototype; 24295 24296 _proto.dispose = function dispose() { 24297 var _this2 = this; 24298 24299 /** 24300 * Called when the player is being disposed of. 24301 * 24302 * @event Player#dispose 24303 * @type {EventTarget~Event} 24304 */ 24305 this.trigger('dispose'); // prevent dispose from being called twice 24306 24307 this.off('dispose'); // Make sure all player-specific document listeners are unbound. This is 24308 24309 off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_); 24310 off(document, 'keydown', this.boundFullWindowOnEscKey_); 24311 24312 if (this.styleEl_ && this.styleEl_.parentNode) { 24313 this.styleEl_.parentNode.removeChild(this.styleEl_); 24314 this.styleEl_ = null; 24315 } // Kill reference to this player 24316 24317 24318 Player.players[this.id_] = null; 24319 24320 if (this.tag && this.tag.player) { 24321 this.tag.player = null; 24322 } 24323 24324 if (this.el_ && this.el_.player) { 24325 this.el_.player = null; 24326 } 24327 24328 if (this.tech_) { 24329 this.tech_.dispose(); 24330 this.isPosterFromTech_ = false; 24331 this.poster_ = ''; 24332 } 24333 24334 if (this.playerElIngest_) { 24335 this.playerElIngest_ = null; 24336 } 24337 24338 if (this.tag) { 24339 this.tag = null; 24340 } 24341 24342 clearCacheForPlayer(this); // remove all event handlers for track lists 24343 // all tracks and track listeners are removed on 24344 // tech dispose 24345 24346 ALL.names.forEach(function (name) { 24347 var props = ALL[name]; 24348 24349 var list = _this2[props.getterName](); // if it is not a native list 24350 // we have to manually remove event listeners 24351 24352 24353 if (list && list.off) { 24354 list.off(); 24355 } 24356 }); // the actual .el_ is removed here 24357 24358 _Component.prototype.dispose.call(this); 24359 } 24360 /** 24361 * Create the `Player`'s DOM element. 24362 * 24363 * @return {Element} 24364 * The DOM element that gets created. 24365 */ 24366 ; 24367 24368 _proto.createEl = function createEl() { 24369 var tag = this.tag; 24370 var el; 24371 var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); 24372 var divEmbed = this.tag.tagName.toLowerCase() === 'video-js'; 24373 24374 if (playerElIngest) { 24375 el = this.el_ = tag.parentNode; 24376 } else if (!divEmbed) { 24377 el = this.el_ = _Component.prototype.createEl.call(this, 'div'); 24378 } // Copy over all the attributes from the tag, including ID and class 24379 // ID will now reference player box, not the video tag 24380 24381 24382 var attrs = getAttributes(tag); 24383 24384 if (divEmbed) { 24385 el = this.el_ = tag; 24386 tag = this.tag = document.createElement('video'); 24387 24388 while (el.children.length) { 24389 tag.appendChild(el.firstChild); 24390 } 24391 24392 if (!hasClass(el, 'video-js')) { 24393 addClass(el, 'video-js'); 24394 } 24395 24396 el.appendChild(tag); 24397 playerElIngest = this.playerElIngest_ = el; // move properties over from our custom `video-js` element 24398 // to our new `video` element. This will move things like 24399 // `src` or `controls` that were set via js before the player 24400 // was initialized. 24401 24402 Object.keys(el).forEach(function (k) { 24403 tag[k] = el[k]; 24404 }); 24405 } // set tabindex to -1 to remove the video element from the focus order 24406 24407 24408 tag.setAttribute('tabindex', '-1'); 24409 attrs.tabindex = '-1'; // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button), and 24410 // for the same issue with Chrome (on Windows) with JAWS. 24411 // See https://github.com/FreedomScientific/VFO-standards-support/issues/78 24412 // Note that we can't detect if JAWS is being used, but this ARIA attribute 24413 // doesn't change behavior of IE11 or Chrome if JAWS is not being used 24414 24415 if (IE_VERSION || IS_CHROME && IS_WINDOWS) { 24416 tag.setAttribute('role', 'application'); 24417 attrs.role = 'application'; 24418 } // Remove width/height attrs from tag so CSS can make it 100% width/height 24419 24420 24421 tag.removeAttribute('width'); 24422 tag.removeAttribute('height'); 24423 24424 if ('width' in attrs) { 24425 delete attrs.width; 24426 } 24427 24428 if ('height' in attrs) { 24429 delete attrs.height; 24430 } 24431 24432 Object.getOwnPropertyNames(attrs).forEach(function (attr) { 24433 // don't copy over the class attribute to the player element when we're in a div embed 24434 // the class is already set up properly in the divEmbed case 24435 // and we want to make sure that the `video-js` class doesn't get lost 24436 if (!(divEmbed && attr === 'class')) { 24437 el.setAttribute(attr, attrs[attr]); 24438 } 24439 24440 if (divEmbed) { 24441 tag.setAttribute(attr, attrs[attr]); 24442 } 24443 }); // Update tag id/class for use as HTML5 playback tech 24444 // Might think we should do this after embedding in container so .vjs-tech class 24445 // doesn't flash 100% width/height, but class only applies with .video-js parent 24446 24447 tag.playerId = tag.id; 24448 tag.id += '_html5_api'; 24449 tag.className = 'vjs-tech'; // Make player findable on elements 24450 24451 tag.player = el.player = this; // Default state of video is paused 24452 24453 this.addClass('vjs-paused'); // Add a style element in the player that we'll use to set the width/height 24454 // of the player in a way that's still overrideable by CSS, just like the 24455 // video element 24456 24457 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true) { 24458 this.styleEl_ = createStyleElement('vjs-styles-dimensions'); 24459 var defaultsStyleEl = $('.vjs-styles-defaults'); 24460 var head = $('head'); 24461 head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); 24462 } 24463 24464 this.fill_ = false; 24465 this.fluid_ = false; // Pass in the width/height/aspectRatio options which will update the style el 24466 24467 this.width(this.options_.width); 24468 this.height(this.options_.height); 24469 this.fill(this.options_.fill); 24470 this.fluid(this.options_.fluid); 24471 this.aspectRatio(this.options_.aspectRatio); // Hide any links within the video/audio tag, 24472 // because IE doesn't hide them completely from screen readers. 24473 24474 var links = tag.getElementsByTagName('a'); 24475 24476 for (var i = 0; i < links.length; i++) { 24477 var linkEl = links.item(i); 24478 addClass(linkEl, 'vjs-hidden'); 24479 linkEl.setAttribute('hidden', 'hidden'); 24480 } // insertElFirst seems to cause the networkState to flicker from 3 to 2, so 24481 // keep track of the original for later so we can know if the source originally failed 24482 24483 24484 tag.initNetworkState_ = tag.networkState; // Wrap video tag in div (el/box) container 24485 24486 if (tag.parentNode && !playerElIngest) { 24487 tag.parentNode.insertBefore(el, tag); 24488 } // insert the tag as the first child of the player element 24489 // then manually add it to the children array so that this.addChild 24490 // will work properly for other components 24491 // 24492 // Breaks iPhone, fixed in HTML5 setup. 24493 24494 24495 prependTo(tag, el); 24496 this.children_.unshift(tag); // Set lang attr on player to ensure CSS :lang() in consistent with player 24497 // if it's been set to something different to the doc 24498 24499 this.el_.setAttribute('lang', this.language_); 24500 this.el_ = el; 24501 return el; 24502 } 24503 /** 24504 * A getter/setter for the `Player`'s width. Returns the player's configured value. 24505 * To get the current width use `currentWidth()`. 24506 * 24507 * @param {number} [value] 24508 * The value to set the `Player`'s width to. 24509 * 24510 * @return {number} 24511 * The current width of the `Player` when getting. 24512 */ 24513 ; 24514 24515 _proto.width = function width(value) { 24516 return this.dimension('width', value); 24517 } 24518 /** 24519 * A getter/setter for the `Player`'s height. Returns the player's configured value. 24520 * To get the current height use `currentheight()`. 24521 * 24522 * @param {number} [value] 24523 * The value to set the `Player`'s heigth to. 24524 * 24525 * @return {number} 24526 * The current height of the `Player` when getting. 24527 */ 24528 ; 24529 24530 _proto.height = function height(value) { 24531 return this.dimension('height', value); 24532 } 24533 /** 24534 * A getter/setter for the `Player`'s width & height. 24535 * 24536 * @param {string} dimension 24537 * This string can be: 24538 * - 'width' 24539 * - 'height' 24540 * 24541 * @param {number} [value] 24542 * Value for dimension specified in the first argument. 24543 * 24544 * @return {number} 24545 * The dimension arguments value when getting (width/height). 24546 */ 24547 ; 24548 24549 _proto.dimension = function dimension(_dimension, value) { 24550 var privDimension = _dimension + '_'; 24551 24552 if (value === undefined) { 24553 return this[privDimension] || 0; 24554 } 24555 24556 if (value === '') { 24557 // If an empty string is given, reset the dimension to be automatic 24558 this[privDimension] = undefined; 24559 this.updateStyleEl_(); 24560 return; 24561 } 24562 24563 var parsedVal = parseFloat(value); 24564 24565 if (isNaN(parsedVal)) { 24566 log.error("Improper value \"" + value + "\" supplied for for " + _dimension); 24567 return; 24568 } 24569 24570 this[privDimension] = parsedVal; 24571 this.updateStyleEl_(); 24572 } 24573 /** 24574 * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. 24575 * 24576 * Turning this on will turn off fill mode. 24577 * 24578 * @param {boolean} [bool] 24579 * - A value of true adds the class. 24580 * - A value of false removes the class. 24581 * - No value will be a getter. 24582 * 24583 * @return {boolean|undefined} 24584 * - The value of fluid when getting. 24585 * - `undefined` when setting. 24586 */ 24587 ; 24588 24589 _proto.fluid = function fluid(bool) { 24590 if (bool === undefined) { 24591 return !!this.fluid_; 24592 } 24593 24594 this.fluid_ = !!bool; 24595 24596 if (isEvented(this)) { 24597 this.off('playerreset', this.updateStyleEl_); 24598 } 24599 24600 if (bool) { 24601 this.addClass('vjs-fluid'); 24602 this.fill(false); 24603 addEventedCallback(function () { 24604 this.on('playerreset', this.updateStyleEl_); 24605 }); 24606 } else { 24607 this.removeClass('vjs-fluid'); 24608 } 24609 24610 this.updateStyleEl_(); 24611 } 24612 /** 24613 * A getter/setter/toggler for the vjs-fill `className` on the `Player`. 24614 * 24615 * Turning this on will turn off fluid mode. 24616 * 24617 * @param {boolean} [bool] 24618 * - A value of true adds the class. 24619 * - A value of false removes the class. 24620 * - No value will be a getter. 24621 * 24622 * @return {boolean|undefined} 24623 * - The value of fluid when getting. 24624 * - `undefined` when setting. 24625 */ 24626 ; 24627 24628 _proto.fill = function fill(bool) { 24629 if (bool === undefined) { 24630 return !!this.fill_; 24631 } 24632 24633 this.fill_ = !!bool; 24634 24635 if (bool) { 24636 this.addClass('vjs-fill'); 24637 this.fluid(false); 24638 } else { 24639 this.removeClass('vjs-fill'); 24640 } 24641 } 24642 /** 24643 * Get/Set the aspect ratio 24644 * 24645 * @param {string} [ratio] 24646 * Aspect ratio for player 24647 * 24648 * @return {string|undefined} 24649 * returns the current aspect ratio when getting 24650 */ 24651 24652 /** 24653 * A getter/setter for the `Player`'s aspect ratio. 24654 * 24655 * @param {string} [ratio] 24656 * The value to set the `Player's aspect ratio to. 24657 * 24658 * @return {string|undefined} 24659 * - The current aspect ratio of the `Player` when getting. 24660 * - undefined when setting 24661 */ 24662 ; 24663 24664 _proto.aspectRatio = function aspectRatio(ratio) { 24665 if (ratio === undefined) { 24666 return this.aspectRatio_; 24667 } // Check for width:height format 24668 24669 24670 if (!/^\d+\:\d+$/.test(ratio)) { 24671 throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); 24672 } 24673 24674 this.aspectRatio_ = ratio; // We're assuming if you set an aspect ratio you want fluid mode, 24675 // because in fixed mode you could calculate width and height yourself. 24676 24677 this.fluid(true); 24678 this.updateStyleEl_(); 24679 } 24680 /** 24681 * Update styles of the `Player` element (height, width and aspect ratio). 24682 * 24683 * @private 24684 * @listens Tech#loadedmetadata 24685 */ 24686 ; 24687 24688 _proto.updateStyleEl_ = function updateStyleEl_() { 24689 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE === true) { 24690 var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; 24691 24692 var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; 24693 24694 var techEl = this.tech_ && this.tech_.el(); 24695 24696 if (techEl) { 24697 if (_width >= 0) { 24698 techEl.width = _width; 24699 } 24700 24701 if (_height >= 0) {
vendor: 9,471 bytes, lines 24702-24937
24702 techEl.height = _height; 24703 } 24704 } 24705 24706 return; 24707 } 24708 24709 var width; 24710 var height; 24711 var aspectRatio; 24712 var idClass; // The aspect ratio is either used directly or to calculate width and height. 24713 24714 if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { 24715 // Use any aspectRatio that's been specifically set 24716 aspectRatio = this.aspectRatio_; 24717 } else if (this.videoWidth() > 0) { 24718 // Otherwise try to get the aspect ratio from the video metadata 24719 aspectRatio = this.videoWidth() + ':' + this.videoHeight(); 24720 } else { 24721 // Or use a default. The video element's is 2:1, but 16:9 is more common. 24722 aspectRatio = '16:9'; 24723 } // Get the ratio as a decimal we can use to calculate dimensions 24724 24725 24726 var ratioParts = aspectRatio.split(':'); 24727 var ratioMultiplier = ratioParts[1] / ratioParts[0]; 24728 24729 if (this.width_ !== undefined) { 24730 // Use any width that's been specifically set 24731 width = this.width_; 24732 } else if (this.height_ !== undefined) { 24733 // Or calulate the width from the aspect ratio if a height has been set 24734 width = this.height_ / ratioMultiplier; 24735 } else { 24736 // Or use the video's metadata, or use the video el's default of 300 24737 width = this.videoWidth() || 300; 24738 } 24739 24740 if (this.height_ !== undefined) { 24741 // Use any height that's been specifically set 24742 height = this.height_; 24743 } else { 24744 // Otherwise calculate the height from the ratio and the width 24745 height = width * ratioMultiplier; 24746 } // Ensure the CSS class is valid by starting with an alpha character 24747 24748 24749 if (/^[^a-zA-Z]/.test(this.id())) { 24750 idClass = 'dimensions-' + this.id(); 24751 } else { 24752 idClass = this.id() + '-dimensions'; 24753 } // Ensure the right class is still on the player for the style element 24754 24755 24756 this.addClass(idClass); 24757 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 "); 24758 } 24759 /** 24760 * Load/Create an instance of playback {@link Tech} including element 24761 * and API methods. Then append the `Tech` element in `Player` as a child. 24762 * 24763 * @param {string} techName 24764 * name of the playback technology 24765 * 24766 * @param {string} source 24767 * video source 24768 * 24769 * @private 24770 */ 24771 ; 24772 24773 _proto.loadTech_ = function loadTech_(techName, source) { 24774 var _this3 = this; 24775 24776 // Pause and remove current playback technology 24777 if (this.tech_) { 24778 this.unloadTech_(); 24779 } 24780 24781 var titleTechName = toTitleCase(techName); 24782 var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); // get rid of the HTML5 video tag as soon as we are using another tech 24783 24784 if (titleTechName !== 'Html5' && this.tag) { 24785 Tech.getTech('Html5').disposeMediaElement(this.tag); 24786 this.tag.player = null; 24787 this.tag = null; 24788 } 24789 24790 this.techName_ = titleTechName; // Turn off API access because we're loading a new tech that might load asynchronously 24791 24792 this.isReady_ = false; // if autoplay is a string we pass false to the tech 24793 // because the player is going to handle autoplay on `loadstart` 24794 24795 var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay(); // Grab tech-specific options from player options and add source and parent element to use. 24796 24797 var techOptions = { 24798 source: source, 24799 autoplay: autoplay, 24800 'nativeControlsForTouch': this.options_.nativeControlsForTouch, 24801 'playerId': this.id(), 24802 'techId': this.id() + "_" + camelTechName + "_api", 24803 'playsinline': this.options_.playsinline, 24804 'preload': this.options_.preload, 24805 'loop': this.options_.loop, 24806 'muted': this.options_.muted, 24807 'poster': this.poster(), 24808 'language': this.language(), 24809 'playerElIngest': this.playerElIngest_ || false, 24810 'vtt.js': this.options_['vtt.js'], 24811 'canOverridePoster': !!this.options_.techCanOverridePoster, 24812 'enableSourceset': this.options_.enableSourceset, 24813 'Promise': this.options_.Promise 24814 }; 24815 ALL.names.forEach(function (name) { 24816 var props = ALL[name]; 24817 techOptions[props.getterName] = _this3[props.privateName]; 24818 }); 24819 assign(techOptions, this.options_[titleTechName]); 24820 assign(techOptions, this.options_[camelTechName]); 24821 assign(techOptions, this.options_[techName.toLowerCase()]); 24822 24823 if (this.tag) { 24824 techOptions.tag = this.tag; 24825 } 24826 24827 if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { 24828 techOptions.startTime = this.cache_.currentTime; 24829 } // Initialize tech instance 24830 24831 24832 var TechClass = Tech.getTech(techName); 24833 24834 if (!TechClass) { 24835 throw new Error("No Tech named '" + titleTechName + "' exists! '" + titleTechName + "' should be registered using videojs.registerTech()'"); 24836 } 24837 24838 this.tech_ = new TechClass(techOptions); // player.triggerReady is always async, so don't need this to be async 24839 24840 this.tech_.ready(bind(this, this.handleTechReady_), true); 24841 textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_); // Listen to all HTML5-defined events and trigger them on the player 24842 24843 TECH_EVENTS_RETRIGGER.forEach(function (event) { 24844 _this3.on(_this3.tech_, event, _this3["handleTech" + toTitleCase(event) + "_"]); 24845 }); 24846 Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) { 24847 _this3.on(_this3.tech_, event, function (eventObj) { 24848 if (_this3.tech_.playbackRate() === 0 && _this3.tech_.seeking()) { 24849 _this3.queuedCallbacks_.push({ 24850 callback: _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"].bind(_this3), 24851 event: eventObj 24852 }); 24853 24854 return; 24855 } 24856 24857 _this3["handleTech" + TECH_EVENTS_QUEUE[event] + "_"](eventObj); 24858 }); 24859 }); 24860 this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); 24861 this.on(this.tech_, 'sourceset', this.handleTechSourceset_); 24862 this.on(this.tech_, 'waiting', this.handleTechWaiting_); 24863 this.on(this.tech_, 'ended', this.handleTechEnded_); 24864 this.on(this.tech_, 'seeking', this.handleTechSeeking_); 24865 this.on(this.tech_, 'play', this.handleTechPlay_); 24866 this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); 24867 this.on(this.tech_, 'pause', this.handleTechPause_); 24868 this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); 24869 this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); 24870 this.on(this.tech_, 'enterpictureinpicture', this.handleTechEnterPictureInPicture_); 24871 this.on(this.tech_, 'leavepictureinpicture', this.handleTechLeavePictureInPicture_); 24872 this.on(this.tech_, 'error', this.handleTechError_); 24873 this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); 24874 this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); 24875 this.on(this.tech_, 'textdata', this.handleTechTextData_); 24876 this.on(this.tech_, 'ratechange', this.handleTechRateChange_); 24877 this.usingNativeControls(this.techGet_('controls')); 24878 24879 if (this.controls() && !this.usingNativeControls()) { 24880 this.addTechControlsListeners_(); 24881 } // Add the tech element in the DOM if it was not already there 24882 // Make sure to not insert the original video element if using Html5 24883 24884 24885 if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { 24886 prependTo(this.tech_.el(), this.el()); 24887 } // Get rid of the original video tag reference after the first tech is loaded 24888 24889 24890 if (this.tag) { 24891 this.tag.player = null; 24892 this.tag = null; 24893 } 24894 } 24895 /** 24896 * Unload and dispose of the current playback {@link Tech}. 24897 * 24898 * @private 24899 */ 24900 ; 24901 24902 _proto.unloadTech_ = function unloadTech_() { 24903 var _this4 = this; 24904 24905 // Save the current text tracks so that we can reuse the same text tracks with the next tech 24906 ALL.names.forEach(function (name) { 24907 var props = ALL[name]; 24908 _this4[props.privateName] = _this4[props.getterName](); 24909 }); 24910 this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_); 24911 this.isReady_ = false; 24912 this.tech_.dispose(); 24913 this.tech_ = false; 24914 24915 if (this.isPosterFromTech_) { 24916 this.poster_ = ''; 24917 this.trigger('posterchange'); 24918 } 24919 24920 this.isPosterFromTech_ = false; 24921 } 24922 /** 24923 * Return a reference to the current {@link Tech}. 24924 * It will print a warning by default about the danger of using the tech directly 24925 * but any argument that is passed in will silence the warning. 24926 * 24927 * @param {*} [safety] 24928 * Anything passed in to silence the warning 24929 * 24930 * @return {Tech} 24931 * The Tech 24932 */ 24933 ; 24934 24935 _proto.tech = function tech(safety) { 24936 if (safety === undefined) { 24937 log.warn('Using the tech directly can be dangerous. I hope you know
vendor: 9,281 bytes, lines 24937-25198
24937what you\'re doing.\n' + 'See https://github.com/videojs/video.js/issues/2617 for more info.\n'); 24938 } 24939 24940 return this.tech_; 24941 } 24942 /** 24943 * Set up click and touch listeners for the playback element 24944 * 24945 * - On desktops: a click on the video itself will toggle playback 24946 * - On mobile devices: a click on the video toggles controls 24947 * which is done by toggling the user state between active and 24948 * inactive 24949 * - A tap can signal that a user has become active or has become inactive 24950 * e.g. a quick tap on an iPhone movie should reveal the controls. Another 24951 * quick tap should hide them again (signaling the user is in an inactive 24952 * viewing state) 24953 * - In addition to this, we still want the user to be considered inactive after 24954 * a few seconds of inactivity. 24955 * 24956 * > Note: the only part of iOS interaction we can't mimic with this setup 24957 * is a touch and hold on the video element counting as activity in order to 24958 * keep the controls showing, but that shouldn't be an issue. A touch and hold 24959 * on any controls will still keep the user active 24960 * 24961 * @private 24962 */ 24963 ; 24964 24965 _proto.addTechControlsListeners_ = function addTechControlsListeners_() { 24966 // Make sure to remove all the previous listeners in case we are called multiple times. 24967 this.removeTechControlsListeners_(); // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do 24968 // trigger mousedown/up. 24969 // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object 24970 // Any touch events are set to block the mousedown event from happening 24971 24972 this.on(this.tech_, 'mouseup', this.handleTechClick_); 24973 this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_); // If the controls were hidden we don't want that to change without a tap event 24974 // so we'll check if the controls were already showing before reporting user 24975 // activity 24976 24977 this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); 24978 this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); 24979 this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); // The tap listener needs to come after the touchend listener because the tap 24980 // listener cancels out any reportedUserActivity when setting userActive(false) 24981 24982 this.on(this.tech_, 'tap', this.handleTechTap_); 24983 } 24984 /** 24985 * Remove the listeners used for click and tap controls. This is needed for 24986 * toggling to controls disabled, where a tap/touch should do nothing. 24987 * 24988 * @private 24989 */ 24990 ; 24991 24992 _proto.removeTechControlsListeners_ = function removeTechControlsListeners_() { 24993 // We don't want to just use `this.off()` because there might be other needed 24994 // listeners added by techs that extend this. 24995 this.off(this.tech_, 'tap', this.handleTechTap_); 24996 this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); 24997 this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); 24998 this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); 24999 this.off(this.tech_, 'mouseup', this.handleTechClick_); 25000 this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_); 25001 } 25002 /** 25003 * Player waits for the tech to be ready 25004 * 25005 * @private 25006 */ 25007 ; 25008 25009 _proto.handleTechReady_ = function handleTechReady_() { 25010 this.triggerReady(); // Keep the same volume as before 25011 25012 if (this.cache_.volume) { 25013 this.techCall_('setVolume', this.cache_.volume); 25014 } // Look if the tech found a higher resolution poster while loading 25015 25016 25017 this.handleTechPosterChange_(); // Update the duration if available 25018 25019 this.handleTechDurationChange_(); 25020 } 25021 /** 25022 * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This 25023 * function will also trigger {@link Player#firstplay} if it is the first loadstart 25024 * for a video. 25025 * 25026 * @fires Player#loadstart 25027 * @fires Player#firstplay 25028 * @listens Tech#loadstart 25029 * @private 25030 */ 25031 ; 25032 25033 _proto.handleTechLoadStart_ = function handleTechLoadStart_() { 25034 // TODO: Update to use `emptied` event instead. See #1277. 25035 this.removeClass('vjs-ended'); 25036 this.removeClass('vjs-seeking'); // reset the error state 25037 25038 this.error(null); // Update the duration 25039 25040 this.handleTechDurationChange_(); // If it's already playing we want to trigger a firstplay event now. 25041 // The firstplay event relies on both the play and loadstart events 25042 // which can happen in any order for a new source 25043 25044 if (!this.paused()) { 25045 /** 25046 * Fired when the user agent begins looking for media data 25047 * 25048 * @event Player#loadstart 25049 * @type {EventTarget~Event} 25050 */ 25051 this.trigger('loadstart'); 25052 this.trigger('firstplay'); 25053 } else { 25054 // reset the hasStarted state 25055 this.hasStarted(false); 25056 this.trigger('loadstart'); 25057 } // autoplay happens after loadstart for the browser, 25058 // so we mimic that behavior 25059 25060 25061 this.manualAutoplay_(this.autoplay()); 25062 } 25063 /** 25064 * Handle autoplay string values, rather than the typical boolean 25065 * values that should be handled by the tech. Note that this is not 25066 * part of any specification. Valid values and what they do can be 25067 * found on the autoplay getter at Player#autoplay() 25068 */ 25069 ; 25070 25071 _proto.manualAutoplay_ = function manualAutoplay_(type) { 25072 var _this5 = this; 25073 25074 if (!this.tech_ || typeof type !== 'string') { 25075 return; 25076 } 25077 25078 var muted = function muted() { 25079 var previouslyMuted = _this5.muted(); 25080 25081 _this5.muted(true); 25082 25083 var restoreMuted = function restoreMuted() { 25084 _this5.muted(previouslyMuted); 25085 }; // restore muted on play terminatation 25086 25087 25088 _this5.playTerminatedQueue_.push(restoreMuted); 25089 25090 var mutedPromise = _this5.play(); 25091 25092 if (!isPromise(mutedPromise)) { 25093 return; 25094 } 25095 25096 return mutedPromise["catch"](restoreMuted); 25097 }; 25098 25099 var promise; // if muted defaults to true 25100 // the only thing we can do is call play 25101 25102 if (type === 'any' && this.muted() !== true) { 25103 promise = this.play(); 25104 25105 if (isPromise(promise)) { 25106 promise = promise["catch"](muted); 25107 } 25108 } else if (type === 'muted' && this.muted() !== true) { 25109 promise = muted(); 25110 } else { 25111 promise = this.play(); 25112 } 25113 25114 if (!isPromise(promise)) { 25115 return; 25116 } 25117 25118 return promise.then(function () { 25119 _this5.trigger({ 25120 type: 'autoplay-success', 25121 autoplay: type 25122 }); 25123 })["catch"](function (e) { 25124 _this5.trigger({ 25125 type: 'autoplay-failure', 25126 autoplay: type 25127 }); 25128 }); 25129 } 25130 /** 25131 * Update the internal source caches so that we return the correct source from 25132 * `src()`, `currentSource()`, and `currentSources()`. 25133 * 25134 * > Note: `currentSources` will not be updated if the source that is passed in exists 25135 * in the current `currentSources` cache. 25136 * 25137 * 25138 * @param {Tech~SourceObject} srcObj 25139 * A string or object source to update our caches to. 25140 */ 25141 ; 25142 25143 _proto.updateSourceCaches_ = function updateSourceCaches_(srcObj) { 25144 if (srcObj === void 0) { 25145 srcObj = ''; 25146 } 25147 25148 var src = srcObj; 25149 var type = ''; 25150 25151 if (typeof src !== 'string') { 25152 src = srcObj.src; 25153 type = srcObj.type; 25154 } // make sure all the caches are set to default values 25155 // to prevent null checking 25156 25157 25158 this.cache_.source = this.cache_.source || {}; 25159 this.cache_.sources = this.cache_.sources || []; // try to get the type of the src that was passed in 25160 25161 if (src && !type) { 25162 type = findMimetype(this, src); 25163 } // update `currentSource` cache always 25164 25165 25166 this.cache_.source = mergeOptions({}, srcObj, { 25167 src: src, 25168 type: type 25169 }); 25170 var matchingSources = this.cache_.sources.filter(function (s) { 25171 return s.src && s.src === src; 25172 }); 25173 var sourceElSources = []; 25174 var sourceEls = this.$$('source'); 25175 var matchingSourceEls = []; 25176 25177 for (var i = 0; i < sourceEls.length; i++) { 25178 var sourceObj = getAttributes(sourceEls[i]); 25179 sourceElSources.push(sourceObj); 25180 25181 if (sourceObj.src && sourceObj.src === src) { 25182 matchingSourceEls.push(sourceObj.src); 25183 } 25184 } // if we have matching source els but not matching sources 25185 // the current source cache is not up to date 25186 25187 25188 if (matchingSourceEls.length && !matchingSources.length) { 25189 this.cache_.sources = sourceElSources; // if we don't have matching source or source els set the 25190 // sources cache to the `currentSource` cache 25191 } else if (!matchingSources.length) { 25192 this.cache_.sources = [this.cache_.source]; 25193 } // update the tech `src` cache 25194 25195 25196 this.cache_.src = src; 25197 } 25198 /**
25199 * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech} 25200 * causing the media element to reload. 25201 * 25202 * It will fire for the initial source and each subsequent source. 25203 * This event is a custom event from Video.js and is triggered by the {@link Tech}. 25204 * 25205 * The event object for this event contains a `src` property that will contain the source 25206 * that was available when the event was triggered. This is generally only necessary if Video.js 25207 * is switching techs while the source was being changed. 25208 * 25209 * It is also fired when `load` is called on the player (or media element) 25210 * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`} 25211 * says that the resource selection algorithm needs to be aborted and restarted. 25212 * In this case, it is very likely that the `src` property will be set to the 25213 * empty string `""` to indicate we do not know what the source will be but 25214 * that it is changing. 25215 * 25216 * *This event is currently still experimental and may change in minor releases.* 25217 * __To use this, pass `enableSourceset` option to the player.__ 25218 * 25219 * @event Player#sourceset 25220 * @type {EventTarget~Event} 25221 * @prop {string} src 25222 * The source url available when the `sourceset` was triggered. 25223 * It will be an empty string if we cannot know what the source is 25224 * but know that the source will change. 25225 */ 25226 25227 /** 25228 * Retrigger the `sourceset` event that was triggered by the {@link Tech}. 25229 * 25230 * @fires Player#sourceset 25231 * @listens Tech#sourceset 25232 * @private 25233 */ 25234 ; 25235 25236 _proto.handleTechSourceset_ = function handleTechSourceset_(event) { 25237 var _this6 = this; 25238 25239 // only update the source cache when the source 25240 // was not updated using the player api 25241 if (!this.changingSrc_) { 25242 var updateSourceCaches = function updateSourceCaches(src) { 25243 return _this6.updateSourceCaches_(src); 25244 }; 25245 25246 var playerSrc = this.currentSource().src; 25247 var eventSrc = event.src; // if we have a playerSrc that is not a blob, and a tech src that is a blob 25248 25249 if (playerSrc && !/^blob:/.test(playerSrc) && /^blob:/.test(eventSrc)) { 25250 // if both the tech source and the player source were updated we assume 25251 // something like @videojs/http-streaming did the sourceset and skip updating the source cache. 25252 if (!this.lastSource_ || this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc) { 25253 updateSourceCaches = function updateSourceCaches() {}; 25254 } 25255 } // update the source to the intial source right away 25256 // in some cases this will be empty string 25257 25258 25259 updateSourceCaches(eventSrc); // if the `sourceset` `src` was an empty string 25260 // wait for a `loadstart` to update the cache to `currentSrc`. 25261 // If a sourceset happens before a `loadstart`, we reset the state 25262 25263 if (!event.src) { 25264 this.tech_.any(['sourceset', 'loadstart'], function (e) { 25265 // if a sourceset happens before a `loadstart` there 25266 // is nothing to do as this `handleTechSourceset_` 25267 // will be called again and this will be handled there. 25268 if (e.type === 'sourceset') { 25269 return; 25270 } 25271 25272 var techSrc = _this6.techGet('currentSrc'); 25273 25274 _this6.lastSource_.tech = techSrc; 25275 25276 _this6.updateSourceCaches_(techSrc); 25277 }); 25278 } 25279 } 25280 25281 this.lastSource_ = { 25282 player: this.currentSource().src, 25283 tech: event.src 25284 }; 25285 this.trigger({ 25286 src: event.src, 25287 type: 'sourceset' 25288 }); 25289 } 25290 /** 25291 * Add/remove the vjs-has-started class 25292 * 25293 * @fires Player#firstplay 25294 * 25295 * @param {boolean} request 25296 * - true: adds the class 25297 * - false: remove the class 25298 * 25299 * @return {boolean} 25300 * the boolean value of hasStarted_ 25301 */ 25302 ; 25303 25304 _proto.hasStarted = function hasStarted(request) { 25305 if (request === undefined) { 25306 // act as getter, if we have no request to change 25307 return this.hasStarted_; 25308 } 25309 25310 if (request === this.hasStarted_) { 25311 return; 25312 } 25313 25314 this.hasStarted_ = request; 25315 25316 if (this.hasStarted_) { 25317 this.addClass('vjs-has-started'); 25318 this.trigger('firstplay'); 25319 } else { 25320 this.removeClass('vjs-has-started'); 25321 } 25322 } 25323 /** 25324 * Fired whenever the media begins or resumes playback 25325 * 25326 * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} 25327 * @fires Player#play 25328 * @listens Tech#play 25329 * @private 25330 */ 25331 ; 25332 25333 _proto.handleTechPlay_ = function handleTechPlay_() { 25334 this.removeClass('vjs-ended'); 25335 this.removeClass('vjs-paused'); 25336 this.addClass('vjs-playing'); // hide the poster when the user hits play 25337 25338 this.hasStarted(true); 25339 /** 25340 * Triggered whenever an {@link Tech#play} event happens. Indicates that 25341 * playback has started or resumed. 25342 * 25343 * @event Player#play 25344 * @type {EventTarget~Event} 25345 */ 25346 25347 this.trigger('play'); 25348 } 25349 /** 25350 * Retrigger the `ratechange` event that was triggered by the {@link Tech}. 25351 * 25352 * If there were any events queued while the playback rate was zero, fire 25353 * those events now. 25354 * 25355 * @private 25356 * @method Player#handleTechRateChange_ 25357 * @fires Player#ratechange 25358 * @listens Tech#ratechange 25359 */ 25360 ; 25361 25362 _proto.handleTechRateChange_ = function handleTechRateChange_() { 25363 if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) { 25364 this.queuedCallbacks_.forEach(function (queued) { 25365 return queued.callback(queued.event); 25366 }); 25367 this.queuedCallbacks_ = []; 25368 } 25369 25370 this.cache_.lastPlaybackRate = this.tech_.playbackRate(); 25371 /** 25372 * Fires when the playing speed of the audio/video is changed 25373 * 25374 * @event Player#ratechange 25375 * @type {event} 25376 */ 25377 25378 this.trigger('ratechange'); 25379 } 25380 /** 25381 * Retrigger the `waiting` event that was triggered by the {@link Tech}. 25382 * 25383 * @fires Player#waiting 25384 * @listens Tech#waiting 25385 * @private 25386 */ 25387 ; 25388 25389 _proto.handleTechWaiting_ = function handleTechWaiting_() { 25390 var _this7 = this; 25391 25392 this.addClass('vjs-waiting'); 25393 /** 25394 * A readyState change on the DOM element has caused playback to stop. 25395 * 25396 * @event Player#waiting 25397 * @type {EventTarget~Event} 25398 */ 25399 25400 this.trigger('waiting'); // Browsers may emit a timeupdate event after a waiting event. In order to prevent 25401 // premature removal of the waiting class, wait for the time to change. 25402 25403 var timeWhenWaiting = this.currentTime(); 25404 25405 var timeUpdateListener = function timeUpdateListener() { 25406 if (timeWhenWaiting !== _this7.currentTime()) { 25407 _this7.removeClass('vjs-waiting'); 25408 25409 _this7.off('timeupdate', timeUpdateListener); 25410 } 25411 }; 25412 25413 this.on('timeupdate', timeUpdateListener); 25414 } 25415 /** 25416 * Retrigger the `canplay` event that was triggered by the {@link Tech}. 25417 * > Note: This is not consistent between browsers. See #1351 25418 * 25419 * @fires Player#canplay 25420 * @listens Tech#canplay 25421 * @private 25422 */ 25423 ; 25424 25425 _proto.handleTechCanPlay_ = function handleTechCanPlay_() { 25426 this.removeClass('vjs-waiting'); 25427 /** 25428 * The media has a readyState of HAVE_FUTURE_DATA or greater. 25429 * 25430 * @event Player#canplay 25431 * @type {EventTarget~Event} 25432 */ 25433 25434 this.trigger('canplay'); 25435 } 25436 /** 25437 * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. 25438 * 25439 * @fires Player#canplaythrough 25440 * @listens Tech#canplaythrough 25441 * @private 25442 */ 25443 ; 25444 25445 _proto.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { 25446 this.removeClass('vjs-waiting'); 25447 /** 25448 * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the 25449 * entire media file can be played without buffering. 25450 * 25451 * @event Player#canplaythrough 25452 * @type {EventTarget~Event} 25453 */ 25454 25455 this.trigger('canplaythrough'); 25456 } 25457 /** 25458 * Retrigger the `playing` event that was triggered by the {@link Tech}. 25459 * 25460 * @fires Player#playing 25461 * @listens Tech#playing 25462 * @private 25463 */ 25464 ; 25465 25466 _proto.handleTechPlaying_ = function handleTechPlaying_() { 25467 this.removeClass('vjs-waiting'); 25468 /** 25469 * The media is no longer blocked from playback, and has started playing. 25470 * 25471 * @event Player#playing 25472 * @type {EventTarget~Event} 25473 */ 25474 25475 this.trigger('playing'); 25476 } 25477 /** 25478 * Retrigger the `seeking` event that was triggered by the {@link Tech}. 25479 * 25480 * @fires Player#seeking 25481 * @listens Tech#seeking 25482 * @private 25483 */ 25484 ; 25485 25486 _proto.handleTechSeeking_ = function handleTechSeeking_() { 25487 this.addClass('vjs-seeking'); 25488 /** 25489 * Fired whenever the player is jumping to a new time 25490 * 25491 * @event Player#seeking 25492 * @type {EventTarget~Event} 25493 */ 25494 25495 this.trigger('seeking'); 25496 } 25497 /** 25498 * Retrigger the `seeked` event that was triggered by the {@link Tech}. 25499 * 25500 * @fires Player#seeked 25501 * @listens Tech#seeked 25502 * @private 25503 */ 25504 ; 25505 25506 _proto.handleTechSeeked_ = function handleTechSeeked_() { 25507 this.removeClass('vjs-seeking'); 25508 this.removeClass('vjs-ended'); 25509 /** 25510 * Fired when the player has finished jumping to a new time 25511 * 25512 * @event Player#seeked 25513 * @type {EventTarget~Event} 25514 */ 25515 25516 this.trigger('seeked'); 25517 } 25518 /** 25519 * Retrigger the `firstplay` event that was triggered by the {@link Tech}. 25520 * 25521 * @fires Player#firstplay 25522 * @listens Tech#firstplay 25523 * @deprecated As of 6.0 firstplay event is deprecated. 25524 * As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. 25525 * @private 25526 */ 25527 ; 25528 25529 _proto.handleTechFirstPlay_ = function handleTechFirstPlay_() { 25530 // If the first starttime attribute is specified 25531 // then we will start at the given offset in seconds 25532 if (this.options_.starttime) { 25533 log.warn('Passing the `starttime` option to the player will be deprecated
vendor: 44,481 bytes, lines 25533-26948
25533in 6.0'); 25534 this.currentTime(this.options_.starttime); 25535 } 25536 25537 this.addClass('vjs-has-started'); 25538 /** 25539 * Fired the first time a video is played. Not part of the HLS spec, and this is 25540 * probably not the best implementation yet, so use sparingly. If you don't have a 25541 * reason to prevent playback, use `myPlayer.one('play');` instead. 25542 * 25543 * @event Player#firstplay 25544 * @deprecated As of 6.0 firstplay event is deprecated. 25545 * @type {EventTarget~Event} 25546 */ 25547 25548 this.trigger('firstplay'); 25549 } 25550 /** 25551 * Retrigger the `pause` event that was triggered by the {@link Tech}. 25552 * 25553 * @fires Player#pause 25554 * @listens Tech#pause 25555 * @private 25556 */ 25557 ; 25558 25559 _proto.handleTechPause_ = function handleTechPause_() { 25560 this.removeClass('vjs-playing'); 25561 this.addClass('vjs-paused'); 25562 /** 25563 * Fired whenever the media has been paused 25564 * 25565 * @event Player#pause 25566 * @type {EventTarget~Event} 25567 */ 25568 25569 this.trigger('pause'); 25570 } 25571 /** 25572 * Retrigger the `ended` event that was triggered by the {@link Tech}. 25573 * 25574 * @fires Player#ended 25575 * @listens Tech#ended 25576 * @private 25577 */ 25578 ; 25579 25580 _proto.handleTechEnded_ = function handleTechEnded_() { 25581 this.addClass('vjs-ended'); 25582 25583 if (this.options_.loop) { 25584 this.currentTime(0); 25585 this.play(); 25586 } else if (!this.paused()) { 25587 this.pause(); 25588 } 25589 /** 25590 * Fired when the end of the media resource is reached (currentTime == duration) 25591 * 25592 * @event Player#ended 25593 * @type {EventTarget~Event} 25594 */ 25595 25596 25597 this.trigger('ended'); 25598 } 25599 /** 25600 * Fired when the duration of the media resource is first known or changed 25601 * 25602 * @listens Tech#durationchange 25603 * @private 25604 */ 25605 ; 25606 25607 _proto.handleTechDurationChange_ = function handleTechDurationChange_() { 25608 this.duration(this.techGet_('duration')); 25609 } 25610 /** 25611 * Handle a click on the media element to play/pause 25612 * 25613 * @param {EventTarget~Event} event 25614 * the event that caused this function to trigger 25615 * 25616 * @listens Tech#mouseup 25617 * @private 25618 */ 25619 ; 25620 25621 _proto.handleTechClick_ = function handleTechClick_(event) { 25622 if (!isSingleLeftClick(event)) { 25623 return; 25624 } // When controls are disabled a click should not toggle playback because 25625 // the click is considered a control 25626 25627 25628 if (!this.controls_) { 25629 return; 25630 } 25631 25632 if (this.paused()) { 25633 silencePromise(this.play()); 25634 } else { 25635 this.pause(); 25636 } 25637 } 25638 /** 25639 * Handle a double-click on the media element to enter/exit fullscreen 25640 * 25641 * @param {EventTarget~Event} event 25642 * the event that caused this function to trigger 25643 * 25644 * @listens Tech#dblclick 25645 * @private 25646 */ 25647 ; 25648 25649 _proto.handleTechDoubleClick_ = function handleTechDoubleClick_(event) { 25650 if (!this.controls_) { 25651 return; 25652 } // we do not want to toggle fullscreen state 25653 // when double-clicking inside a control bar or a modal 25654 25655 25656 var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) { 25657 return el.contains(event.target); 25658 }); 25659 25660 if (!inAllowedEls) { 25661 /* 25662 * options.userActions.doubleClick 25663 * 25664 * If `undefined` or `true`, double-click toggles fullscreen if controls are present 25665 * Set to `false` to disable double-click handling 25666 * Set to a function to substitute an external double-click handler 25667 */ 25668 if (this.options_ === undefined || this.options_.userActions === undefined || this.options_.userActions.doubleClick === undefined || this.options_.userActions.doubleClick !== false) { 25669 if (this.options_ !== undefined && this.options_.userActions !== undefined && typeof this.options_.userActions.doubleClick === 'function') { 25670 this.options_.userActions.doubleClick.call(this, event); 25671 } else if (this.isFullscreen()) { 25672 this.exitFullscreen(); 25673 } else { 25674 this.requestFullscreen(); 25675 } 25676 } 25677 } 25678 } 25679 /** 25680 * Handle a tap on the media element. It will toggle the user 25681 * activity state, which hides and shows the controls. 25682 * 25683 * @listens Tech#tap 25684 * @private 25685 */ 25686 ; 25687 25688 _proto.handleTechTap_ = function handleTechTap_() { 25689 this.userActive(!this.userActive()); 25690 } 25691 /** 25692 * Handle touch to start 25693 * 25694 * @listens Tech#touchstart 25695 * @private 25696 */ 25697 ; 25698 25699 _proto.handleTechTouchStart_ = function handleTechTouchStart_() { 25700 this.userWasActive = this.userActive(); 25701 } 25702 /** 25703 * Handle touch to move 25704 * 25705 * @listens Tech#touchmove 25706 * @private 25707 */ 25708 ; 25709 25710 _proto.handleTechTouchMove_ = function handleTechTouchMove_() { 25711 if (this.userWasActive) { 25712 this.reportUserActivity(); 25713 } 25714 } 25715 /** 25716 * Handle touch to end 25717 * 25718 * @param {EventTarget~Event} event 25719 * the touchend event that triggered 25720 * this function 25721 * 25722 * @listens Tech#touchend 25723 * @private 25724 */ 25725 ; 25726 25727 _proto.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { 25728 // Stop the mouse events from also happening 25729 event.preventDefault(); 25730 } 25731 /** 25732 * native click events on the SWF aren't triggered on IE11, Win8.1RT 25733 * use stageclick events triggered from inside the SWF instead 25734 * 25735 * @private 25736 * @listens stageclick 25737 */ 25738 ; 25739 25740 _proto.handleStageClick_ = function handleStageClick_() { 25741 this.reportUserActivity(); 25742 } 25743 /** 25744 * @private 25745 */ 25746 ; 25747 25748 _proto.toggleFullscreenClass_ = function toggleFullscreenClass_() { 25749 if (this.isFullscreen()) { 25750 this.addClass('vjs-fullscreen'); 25751 } else { 25752 this.removeClass('vjs-fullscreen'); 25753 } 25754 } 25755 /** 25756 * when the document fschange event triggers it calls this 25757 */ 25758 ; 25759 25760 _proto.documentFullscreenChange_ = function documentFullscreenChange_(e) { 25761 var el = this.el(); 25762 var isFs = document[this.fsApi_.fullscreenElement] === el; 25763 25764 if (!isFs && el.matches) { 25765 isFs = el.matches(':' + this.fsApi_.fullscreen); 25766 } else if (!isFs && el.msMatchesSelector) { 25767 isFs = el.msMatchesSelector(':' + this.fsApi_.fullscreen); 25768 } 25769 25770 this.isFullscreen(isFs); // If cancelling fullscreen, remove event listener. 25771 25772 if (this.isFullscreen() === false) { 25773 off(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_); 25774 } 25775 25776 if (this.fsApi_.prefixed) { 25777 /** 25778 * @event Player#fullscreenchange 25779 * @type {EventTarget~Event} 25780 */ 25781 this.trigger('fullscreenchange'); 25782 } 25783 } 25784 /** 25785 * Handle Tech Fullscreen Change 25786 * 25787 * @param {EventTarget~Event} event 25788 * the fullscreenchange event that triggered this function 25789 * 25790 * @param {Object} data 25791 * the data that was sent with the event 25792 * 25793 * @private 25794 * @listens Tech#fullscreenchange 25795 * @fires Player#fullscreenchange 25796 */ 25797 ; 25798 25799 _proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { 25800 if (data) { 25801 this.isFullscreen(data.isFullscreen); 25802 } 25803 /** 25804 * Fired when going in and out of fullscreen. 25805 * 25806 * @event Player#fullscreenchange 25807 * @type {EventTarget~Event} 25808 */ 25809 25810 25811 this.trigger('fullscreenchange'); 25812 } 25813 /** 25814 * @private 25815 */ 25816 ; 25817 25818 _proto.togglePictureInPictureClass_ = function togglePictureInPictureClass_() { 25819 if (this.isInPictureInPicture()) { 25820 this.addClass('vjs-picture-in-picture'); 25821 } else { 25822 this.removeClass('vjs-picture-in-picture'); 25823 } 25824 } 25825 /** 25826 * Handle Tech Enter Picture-in-Picture. 25827 * 25828 * @param {EventTarget~Event} event 25829 * the enterpictureinpicture event that triggered this function 25830 * 25831 * @private 25832 * @listens Tech#enterpictureinpicture 25833 */ 25834 ; 25835 25836 _proto.handleTechEnterPictureInPicture_ = function handleTechEnterPictureInPicture_(event) { 25837 this.isInPictureInPicture(true); 25838 } 25839 /** 25840 * Handle Tech Leave Picture-in-Picture. 25841 * 25842 * @param {EventTarget~Event} event 25843 * the leavepictureinpicture event that triggered this function 25844 * 25845 * @private 25846 * @listens Tech#leavepictureinpicture 25847 */ 25848 ; 25849 25850 _proto.handleTechLeavePictureInPicture_ = function handleTechLeavePictureInPicture_(event) { 25851 this.isInPictureInPicture(false); 25852 } 25853 /** 25854 * Fires when an error occurred during the loading of an audio/video. 25855 * 25856 * @private 25857 * @listens Tech#error 25858 */ 25859 ; 25860 25861 _proto.handleTechError_ = function handleTechError_() { 25862 var error = this.tech_.error(); 25863 this.error(error); 25864 } 25865 /** 25866 * Retrigger the `textdata` event that was triggered by the {@link Tech}. 25867 * 25868 * @fires Player#textdata 25869 * @listens Tech#textdata 25870 * @private 25871 */ 25872 ; 25873 25874 _proto.handleTechTextData_ = function handleTechTextData_() { 25875 var data = null; 25876 25877 if (arguments.length > 1) { 25878 data = arguments[1]; 25879 } 25880 /** 25881 * Fires when we get a textdata event from tech 25882 * 25883 * @event Player#textdata 25884 * @type {EventTarget~Event} 25885 */ 25886 25887 25888 this.trigger('textdata', data); 25889 } 25890 /** 25891 * Get object for cached values. 25892 * 25893 * @return {Object} 25894 * get the current object cache 25895 */ 25896 ; 25897 25898 _proto.getCache = function getCache() { 25899 return this.cache_; 25900 } 25901 /** 25902 * Resets the internal cache object. 25903 * 25904 * Using this function outside the player constructor or reset method may 25905 * have unintended side-effects. 25906 * 25907 * @private 25908 */ 25909 ; 25910 25911 _proto.resetCache_ = function resetCache_() { 25912 this.cache_ = { 25913 // Right now, the currentTime is not _really_ cached because it is always 25914 // retrieved from the tech (see: currentTime). However, for completeness, 25915 // we set it to zero here to ensure that if we do start actually caching 25916 // it, we reset it along with everything else. 25917 currentTime: 0, 25918 inactivityTimeout: this.options_.inactivityTimeout, 25919 duration: NaN, 25920 lastVolume: 1, 25921 lastPlaybackRate: this.defaultPlaybackRate(), 25922 media: null, 25923 src: '', 25924 source: {}, 25925 sources: [], 25926 volume: 1 25927 }; 25928 } 25929 /** 25930 * Pass values to the playback tech 25931 * 25932 * @param {string} [method] 25933 * the method to call 25934 * 25935 * @param {Object} arg 25936 * the argument to pass 25937 * 25938 * @private 25939 */ 25940 ; 25941 25942 _proto.techCall_ = function techCall_(method, arg) { 25943 // If it's not ready yet, call method when it is 25944 this.ready(function () { 25945 if (method in allowedSetters) { 25946 return set(this.middleware_, this.tech_, method, arg); 25947 } else if (method in allowedMediators) { 25948 return mediate(this.middleware_, this.tech_, method, arg); 25949 } 25950 25951 try { 25952 if (this.tech_) { 25953 this.tech_[method](arg); 25954 } 25955 } catch (e) { 25956 log(e); 25957 throw e; 25958 } 25959 }, true); 25960 } 25961 /** 25962 * Get calls can't wait for the tech, and sometimes don't need to. 25963 * 25964 * @param {string} method 25965 * Tech method 25966 * 25967 * @return {Function|undefined} 25968 * the method or undefined 25969 * 25970 * @private 25971 */ 25972 ; 25973 25974 _proto.techGet_ = function techGet_(method) { 25975 if (!this.tech_ || !this.tech_.isReady_) { 25976 return; 25977 } 25978 25979 if (method in allowedGetters) { 25980 return get(this.middleware_, this.tech_, method); 25981 } else if (method in allowedMediators) { 25982 return mediate(this.middleware_, this.tech_, method); 25983 } // Flash likes to die and reload when you hide or reposition it. 25984 // In these cases the object methods go away and we get errors. 25985 // When that happens we'll catch the errors and inform tech that it's not ready any more. 25986 25987 25988 try { 25989 return this.tech_[method](); 25990 } catch (e) { 25991 // When building additional tech libs, an expected method may not be defined yet 25992 if (this.tech_[method] === undefined) { 25993 log("Video.js: " + method + " method not defined for " + this.techName_ + " playback technology.", e); 25994 throw e; 25995 } // When a method isn't available on the object it throws a TypeError 25996 25997 25998 if (e.name === 'TypeError') { 25999 log("Video.js: " + method + " unavailable on " + this.techName_ + " playback technology element.", e); 26000 this.tech_.isReady_ = false; 26001 throw e; 26002 } // If error unknown, just log and throw 26003 26004 26005 log(e); 26006 throw e; 26007 } 26008 } 26009 /** 26010 * Attempt to begin playback at the first opportunity. 26011 * 26012 * @return {Promise|undefined} 26013 * Returns a promise if the browser supports Promises (or one 26014 * was passed in as an option). This promise will be resolved on 26015 * the return value of play. If this is undefined it will fulfill the 26016 * promise chain otherwise the promise chain will be fulfilled when 26017 * the promise from play is fulfilled. 26018 */ 26019 ; 26020 26021 _proto.play = function play() { 26022 var _this8 = this; 26023 26024 var PromiseClass = this.options_.Promise || window$1.Promise; 26025 26026 if (PromiseClass) { 26027 return new PromiseClass(function (resolve) { 26028 _this8.play_(resolve); 26029 }); 26030 } 26031 26032 return this.play_(); 26033 } 26034 /** 26035 * The actual logic for play, takes a callback that will be resolved on the 26036 * return value of play. This allows us to resolve to the play promise if there 26037 * is one on modern browsers. 26038 * 26039 * @private 26040 * @param {Function} [callback] 26041 * The callback that should be called when the techs play is actually called 26042 */ 26043 ; 26044 26045 _proto.play_ = function play_(callback) { 26046 var _this9 = this; 26047 26048 if (callback === void 0) { 26049 callback = silencePromise; 26050 } 26051 26052 this.playCallbacks_.push(callback); 26053 var isSrcReady = Boolean(!this.changingSrc_ && (this.src() || this.currentSrc())); // treat calls to play_ somewhat like the `one` event function 26054 26055 if (this.waitToPlay_) { 26056 this.off(['ready', 'loadstart'], this.waitToPlay_); 26057 this.waitToPlay_ = null; 26058 } // if the player/tech is not ready or the src itself is not ready 26059 // queue up a call to play on `ready` or `loadstart` 26060 26061 26062 if (!this.isReady_ || !isSrcReady) { 26063 this.waitToPlay_ = function (e) { 26064 _this9.play_(); 26065 }; 26066 26067 this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod 26068 // in that case, we need to prime the video element by calling load so it'll be ready in time 26069 26070 if (!isSrcReady && (IS_ANY_SAFARI || IS_IOS)) { 26071 this.load(); 26072 } 26073 26074 return; 26075 } // If the player/tech is ready and we have a source, we can attempt playback. 26076 26077 26078 var val = this.techGet_('play'); // play was terminated if the returned value is null 26079 26080 if (val === null) { 26081 this.runPlayTerminatedQueue_(); 26082 } else { 26083 this.runPlayCallbacks_(val); 26084 } 26085 } 26086 /** 26087 * These functions will be run when if play is terminated. If play 26088 * runPlayCallbacks_ is run these function will not be run. This allows us 26089 * to differenciate between a terminated play and an actual call to play. 26090 */ 26091 ; 26092 26093 _proto.runPlayTerminatedQueue_ = function runPlayTerminatedQueue_() { 26094 var queue = this.playTerminatedQueue_.slice(0); 26095 this.playTerminatedQueue_ = []; 26096 queue.forEach(function (q) { 26097 q(); 26098 }); 26099 } 26100 /** 26101 * When a callback to play is delayed we have to run these 26102 * callbacks when play is actually called on the tech. This function 26103 * runs the callbacks that were delayed and accepts the return value 26104 * from the tech. 26105 * 26106 * @param {undefined|Promise} val 26107 * The return value from the tech. 26108 */ 26109 ; 26110 26111 _proto.runPlayCallbacks_ = function runPlayCallbacks_(val) { 26112 var callbacks = this.playCallbacks_.slice(0); 26113 this.playCallbacks_ = []; // clear play terminatedQueue since we finished a real play 26114 26115 this.playTerminatedQueue_ = []; 26116 callbacks.forEach(function (cb) { 26117 cb(val); 26118 }); 26119 } 26120 /** 26121 * Pause the video playback 26122 * 26123 * @return {Player} 26124 * A reference to the player object this function was called on 26125 */ 26126 ; 26127 26128 _proto.pause = function pause() { 26129 this.techCall_('pause'); 26130 } 26131 /** 26132 * Check if the player is paused or has yet to play 26133 * 26134 * @return {boolean} 26135 * - false: if the media is currently playing 26136 * - true: if media is not currently playing 26137 */ 26138 ; 26139 26140 _proto.paused = function paused() { 26141 // The initial state of paused should be true (in Safari it's actually false) 26142 return this.techGet_('paused') === false ? false : true; 26143 } 26144 /** 26145 * Get a TimeRange object representing the current ranges of time that the user 26146 * has played. 26147 * 26148 * @return {TimeRange} 26149 * A time range object that represents all the increments of time that have 26150 * been played. 26151 */ 26152 ; 26153 26154 _proto.played = function played() { 26155 return this.techGet_('played') || createTimeRanges(0, 0); 26156 } 26157 /** 26158 * Returns whether or not the user is "scrubbing". Scrubbing is 26159 * when the user has clicked the progress bar handle and is 26160 * dragging it along the progress bar. 26161 * 26162 * @param {boolean} [isScrubbing] 26163 * whether the user is or is not scrubbing 26164 * 26165 * @return {boolean} 26166 * The value of scrubbing when getting 26167 */ 26168 ; 26169 26170 _proto.scrubbing = function scrubbing(isScrubbing) { 26171 if (typeof isScrubbing === 'undefined') { 26172 return this.scrubbing_; 26173 } 26174 26175 this.scrubbing_ = !!isScrubbing; 26176 26177 if (isScrubbing) { 26178 this.addClass('vjs-scrubbing'); 26179 } else { 26180 this.removeClass('vjs-scrubbing'); 26181 } 26182 } 26183 /** 26184 * Get or set the current time (in seconds) 26185 * 26186 * @param {number|string} [seconds] 26187 * The time to seek to in seconds 26188 * 26189 * @return {number} 26190 * - the current time in seconds when getting 26191 */ 26192 ; 26193 26194 _proto.currentTime = function currentTime(seconds) { 26195 if (typeof seconds !== 'undefined') { 26196 if (seconds < 0) { 26197 seconds = 0; 26198 } 26199 26200 this.techCall_('setCurrentTime', seconds); 26201 return; 26202 } // cache last currentTime and return. default to 0 seconds 26203 // 26204 // Caching the currentTime is meant to prevent a massive amount of reads on the tech's 26205 // currentTime when scrubbing, but may not provide much performance benefit afterall. 26206 // Should be tested. Also something has to read the actual current time or the cache will 26207 // never get updated. 26208 26209 26210 this.cache_.currentTime = this.techGet_('currentTime') || 0; 26211 return this.cache_.currentTime; 26212 } 26213 /** 26214 * Normally gets the length in time of the video in seconds; 26215 * in all but the rarest use cases an argument will NOT be passed to the method 26216 * 26217 * > **NOTE**: The video must have started loading before the duration can be 26218 * known, and in the case of Flash, may not be known until the video starts 26219 * playing. 26220 * 26221 * @fires Player#durationchange 26222 * 26223 * @param {number} [seconds] 26224 * The duration of the video to set in seconds 26225 * 26226 * @return {number} 26227 * - The duration of the video in seconds when getting 26228 */ 26229 ; 26230 26231 _proto.duration = function duration(seconds) { 26232 if (seconds === undefined) { 26233 // return NaN if the duration is not known 26234 return this.cache_.duration !== undefined ? this.cache_.duration : NaN; 26235 } 26236 26237 seconds = parseFloat(seconds); // Standardize on Infinity for signaling video is live 26238 26239 if (seconds < 0) { 26240 seconds = Infinity; 26241 } 26242 26243 if (seconds !== this.cache_.duration) { 26244 // Cache the last set value for optimized scrubbing (esp. Flash) 26245 this.cache_.duration = seconds; 26246 26247 if (seconds === Infinity) { 26248 this.addClass('vjs-live'); 26249 26250 if (this.options_.liveui && this.player_.liveTracker) { 26251 this.addClass('vjs-liveui'); 26252 } 26253 } else { 26254 this.removeClass('vjs-live'); 26255 this.removeClass('vjs-liveui'); 26256 } 26257 26258 if (!isNaN(seconds)) { 26259 // Do not fire durationchange unless the duration value is known. 26260 // @see [Spec]{@link https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm} 26261 26262 /** 26263 * @event Player#durationchange 26264 * @type {EventTarget~Event} 26265 */ 26266 this.trigger('durationchange'); 26267 } 26268 } 26269 } 26270 /** 26271 * Calculates how much time is left in the video. Not part 26272 * of the native video API. 26273 * 26274 * @return {number} 26275 * The time remaining in seconds 26276 */ 26277 ; 26278 26279 _proto.remainingTime = function remainingTime() { 26280 return this.duration() - this.currentTime(); 26281 } 26282 /** 26283 * A remaining time function that is intented to be used when 26284 * the time is to be displayed directly to the user. 26285 * 26286 * @return {number} 26287 * The rounded time remaining in seconds 26288 */ 26289 ; 26290 26291 _proto.remainingTimeDisplay = function remainingTimeDisplay() { 26292 return Math.floor(this.duration()) - Math.floor(this.currentTime()); 26293 } // 26294 // Kind of like an array of portions of the video that have been downloaded. 26295 26296 /** 26297 * Get a TimeRange object with an array of the times of the video 26298 * that have been downloaded. If you just want the percent of the 26299 * video that's been downloaded, use bufferedPercent. 26300 * 26301 * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} 26302 * 26303 * @return {TimeRange} 26304 * A mock TimeRange object (following HTML spec) 26305 */ 26306 ; 26307 26308 _proto.buffered = function buffered() { 26309 var buffered = this.techGet_('buffered'); 26310 26311 if (!buffered || !buffered.length) { 26312 buffered = createTimeRanges(0, 0); 26313 } 26314 26315 return buffered; 26316 } 26317 /** 26318 * Get the percent (as a decimal) of the video that's been downloaded. 26319 * This method is not a part of the native HTML video API. 26320 * 26321 * @return {number} 26322 * A decimal between 0 and 1 representing the percent 26323 * that is buffered 0 being 0% and 1 being 100% 26324 */ 26325 ; 26326 26327 _proto.bufferedPercent = function bufferedPercent$1() { 26328 return bufferedPercent(this.buffered(), this.duration()); 26329 } 26330 /** 26331 * Get the ending time of the last buffered time range 26332 * This is used in the progress bar to encapsulate all time ranges. 26333 * 26334 * @return {number} 26335 * The end of the last buffered time range 26336 */ 26337 ; 26338 26339 _proto.bufferedEnd = function bufferedEnd() { 26340 var buffered = this.buffered(); 26341 var duration = this.duration(); 26342 var end = buffered.end(buffered.length - 1); 26343 26344 if (end > duration) { 26345 end = duration; 26346 } 26347 26348 return end; 26349 } 26350 /** 26351 * Get or set the current volume of the media 26352 * 26353 * @param {number} [percentAsDecimal] 26354 * The new volume as a decimal percent: 26355 * - 0 is muted/0%/off 26356 * - 1.0 is 100%/full 26357 * - 0.5 is half volume or 50% 26358 * 26359 * @return {number} 26360 * The current volume as a percent when getting 26361 */ 26362 ; 26363 26364 _proto.volume = function volume(percentAsDecimal) { 26365 var vol; 26366 26367 if (percentAsDecimal !== undefined) { 26368 // Force value to between 0 and 1 26369 vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); 26370 this.cache_.volume = vol; 26371 this.techCall_('setVolume', vol); 26372 26373 if (vol > 0) { 26374 this.lastVolume_(vol); 26375 } 26376 26377 return; 26378 } // Default to 1 when returning current volume. 26379 26380 26381 vol = parseFloat(this.techGet_('volume')); 26382 return isNaN(vol) ? 1 : vol; 26383 } 26384 /** 26385 * Get the current muted state, or turn mute on or off 26386 * 26387 * @param {boolean} [muted] 26388 * - true to mute 26389 * - false to unmute 26390 * 26391 * @return {boolean} 26392 * - true if mute is on and getting 26393 * - false if mute is off and getting 26394 */ 26395 ; 26396 26397 _proto.muted = function muted(_muted) { 26398 if (_muted !== undefined) { 26399 this.techCall_('setMuted', _muted); 26400 return; 26401 } 26402 26403 return this.techGet_('muted') || false; 26404 } 26405 /** 26406 * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted 26407 * indicates the state of muted on initial playback. 26408 * 26409 * ```js 26410 * var myPlayer = videojs('some-player-id'); 26411 * 26412 * myPlayer.src("http://www.example.com/path/to/video.mp4"); 26413 * 26414 * // get, should be false 26415 * console.log(myPlayer.defaultMuted()); 26416 * // set to true 26417 * myPlayer.defaultMuted(true); 26418 * // get should be true 26419 * console.log(myPlayer.defaultMuted()); 26420 * ``` 26421 * 26422 * @param {boolean} [defaultMuted] 26423 * - true to mute 26424 * - false to unmute 26425 * 26426 * @return {boolean|Player} 26427 * - true if defaultMuted is on and getting 26428 * - false if defaultMuted is off and getting 26429 * - A reference to the current player when setting 26430 */ 26431 ; 26432 26433 _proto.defaultMuted = function defaultMuted(_defaultMuted) { 26434 if (_defaultMuted !== undefined) { 26435 return this.techCall_('setDefaultMuted', _defaultMuted); 26436 } 26437 26438 return this.techGet_('defaultMuted') || false; 26439 } 26440 /** 26441 * Get the last volume, or set it 26442 * 26443 * @param {number} [percentAsDecimal] 26444 * The new last volume as a decimal percent: 26445 * - 0 is muted/0%/off 26446 * - 1.0 is 100%/full 26447 * - 0.5 is half volume or 50% 26448 * 26449 * @return {number} 26450 * the current value of lastVolume as a percent when getting 26451 * 26452 * @private 26453 */ 26454 ; 26455 26456 _proto.lastVolume_ = function lastVolume_(percentAsDecimal) { 26457 if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { 26458 this.cache_.lastVolume = percentAsDecimal; 26459 return; 26460 } 26461 26462 return this.cache_.lastVolume; 26463 } 26464 /** 26465 * Check if current tech can support native fullscreen 26466 * (e.g. with built in controls like iOS, so not our flash swf) 26467 * 26468 * @return {boolean} 26469 * if native fullscreen is supported 26470 */ 26471 ; 26472 26473 _proto.supportsFullScreen = function supportsFullScreen() { 26474 return this.techGet_('supportsFullScreen') || false; 26475 } 26476 /** 26477 * Check if the player is in fullscreen mode or tell the player that it 26478 * is or is not in fullscreen mode. 26479 * 26480 * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official 26481 * property and instead document.fullscreenElement is used. But isFullscreen is 26482 * still a valuable property for internal player workings. 26483 * 26484 * @param {boolean} [isFS] 26485 * Set the players current fullscreen state 26486 * 26487 * @return {boolean} 26488 * - true if fullscreen is on and getting 26489 * - false if fullscreen is off and getting 26490 */ 26491 ; 26492 26493 _proto.isFullscreen = function isFullscreen(isFS) { 26494 if (isFS !== undefined) { 26495 this.isFullscreen_ = !!isFS; 26496 this.toggleFullscreenClass_(); 26497 return; 26498 } 26499 26500 return !!this.isFullscreen_; 26501 } 26502 /** 26503 * Increase the size of the video to full screen 26504 * In some browsers, full screen is not supported natively, so it enters 26505 * "full window mode", where the video fills the browser window. 26506 * In browsers and devices that support native full screen, sometimes the 26507 * browser's default controls will be shown, and not the Video.js custom skin. 26508 * This includes most mobile devices (iOS, Android) and older versions of 26509 * Safari. 26510 * 26511 * @param {Object} [fullscreenOptions] 26512 * Override the player fullscreen options 26513 * 26514 * @fires Player#fullscreenchange 26515 */ 26516 ; 26517 26518 _proto.requestFullscreen = function requestFullscreen(fullscreenOptions) { 26519 var fsOptions; 26520 this.isFullscreen(true); 26521 26522 if (this.fsApi_.requestFullscreen) { 26523 // the browser supports going fullscreen at the element level so we can 26524 // take the controls fullscreen as well as the video 26525 // Trigger fullscreenchange event after change 26526 // We have to specifically add this each time, and remove 26527 // when canceling fullscreen. Otherwise if there's multiple 26528 // players on a page, they would all be reacting to the same fullscreen 26529 // events 26530 on(document, this.fsApi_.fullscreenchange, this.boundDocumentFullscreenChange_); // only pass FullscreenOptions to requestFullscreen if it isn't prefixed 26531 26532 if (!this.fsApi_.prefixed) { 26533 fsOptions = this.options_.fullscreen && this.options_.fullscreen.options || {}; 26534 26535 if (fullscreenOptions !== undefined) { 26536 fsOptions = fullscreenOptions; 26537 } 26538 } 26539 26540 silencePromise(this.el_[this.fsApi_.requestFullscreen](fsOptions)); 26541 } else if (this.tech_.supportsFullScreen()) { 26542 // we can't take the video.js controls fullscreen but we can go fullscreen 26543 // with native controls 26544 this.techCall_('enterFullScreen'); 26545 } else { 26546 // fullscreen isn't supported so we'll just stretch the video element to 26547 // fill the viewport 26548 this.enterFullWindow(); 26549 /** 26550 * @event Player#fullscreenchange 26551 * @type {EventTarget~Event} 26552 */ 26553 26554 this.trigger('fullscreenchange'); 26555 } 26556 } 26557 /** 26558 * Return the video to its normal size after having been in full screen mode 26559 * 26560 * @fires Player#fullscreenchange 26561 */ 26562 ; 26563 26564 _proto.exitFullscreen = function exitFullscreen() { 26565 this.isFullscreen(false); // Check for browser element fullscreen support 26566 26567 if (this.fsApi_.requestFullscreen) { 26568 silencePromise(document[this.fsApi_.exitFullscreen]()); 26569 } else if (this.tech_.supportsFullScreen()) { 26570 this.techCall_('exitFullScreen'); 26571 } else { 26572 this.exitFullWindow(); 26573 /** 26574 * @event Player#fullscreenchange 26575 * @type {EventTarget~Event} 26576 */ 26577 26578 this.trigger('fullscreenchange'); 26579 } 26580 } 26581 /** 26582 * When fullscreen isn't supported we can stretch the 26583 * video container to as wide as the browser will let us. 26584 * 26585 * @fires Player#enterFullWindow 26586 */ 26587 ; 26588 26589 _proto.enterFullWindow = function enterFullWindow() { 26590 this.isFullWindow = true; // Storing original doc overflow value to return to when fullscreen is off 26591 26592 this.docOrigOverflow = document.documentElement.style.overflow; // Add listener for esc key to exit fullscreen 26593 26594 on(document, 'keydown', this.boundFullWindowOnEscKey_); // Hide any scroll bars 26595 26596 document.documentElement.style.overflow = 'hidden'; // Apply fullscreen styles 26597 26598 addClass(document.body, 'vjs-full-window'); 26599 /** 26600 * @event Player#enterFullWindow 26601 * @type {EventTarget~Event} 26602 */ 26603 26604 this.trigger('enterFullWindow'); 26605 } 26606 /** 26607 * Check for call to either exit full window or 26608 * full screen on ESC key 26609 * 26610 * @param {string} event 26611 * Event to check for key press 26612 */ 26613 ; 26614 26615 _proto.fullWindowOnEscKey = function fullWindowOnEscKey(event) { 26616 if (keycode.isEventKey(event, 'Esc')) { 26617 if (this.isFullscreen() === true) { 26618 this.exitFullscreen(); 26619 } else { 26620 this.exitFullWindow(); 26621 } 26622 } 26623 } 26624 /** 26625 * Exit full window 26626 * 26627 * @fires Player#exitFullWindow 26628 */ 26629 ; 26630 26631 _proto.exitFullWindow = function exitFullWindow() { 26632 this.isFullWindow = false; 26633 off(document, 'keydown', this.boundFullWindowOnEscKey_); // Unhide scroll bars. 26634 26635 document.documentElement.style.overflow = this.docOrigOverflow; // Remove fullscreen styles 26636 26637 removeClass(document.body, 'vjs-full-window'); // Resize the box, controller, and poster to original sizes 26638 // this.positionAll(); 26639 26640 /** 26641 * @event Player#exitFullWindow 26642 * @type {EventTarget~Event} 26643 */ 26644 26645 this.trigger('exitFullWindow'); 26646 } 26647 /** 26648 * Check if the player is in Picture-in-Picture mode or tell the player that it 26649 * is or is not in Picture-in-Picture mode. 26650 * 26651 * @param {boolean} [isPiP] 26652 * Set the players current Picture-in-Picture state 26653 * 26654 * @return {boolean} 26655 * - true if Picture-in-Picture is on and getting 26656 * - false if Picture-in-Picture is off and getting 26657 */ 26658 ; 26659 26660 _proto.isInPictureInPicture = function isInPictureInPicture(isPiP) { 26661 if (isPiP !== undefined) { 26662 this.isInPictureInPicture_ = !!isPiP; 26663 this.togglePictureInPictureClass_(); 26664 return; 26665 } 26666 26667 return !!this.isInPictureInPicture_; 26668 } 26669 /** 26670 * Create a floating video window always on top of other windows so that users may 26671 * continue consuming media while they interact with other content sites, or 26672 * applications on their device. 26673 * 26674 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 26675 * 26676 * @fires Player#enterpictureinpicture 26677 * 26678 * @return {Promise} 26679 * A promise with a Picture-in-Picture window. 26680 */ 26681 ; 26682 26683 _proto.requestPictureInPicture = function requestPictureInPicture() { 26684 if ('pictureInPictureEnabled' in document) { 26685 /** 26686 * This event fires when the player enters picture in picture mode 26687 * 26688 * @event Player#enterpictureinpicture 26689 * @type {EventTarget~Event} 26690 */ 26691 return this.techGet_('requestPictureInPicture'); 26692 } 26693 } 26694 /** 26695 * Exit Picture-in-Picture mode. 26696 * 26697 * @see [Spec]{@link https://wicg.github.io/picture-in-picture} 26698 * 26699 * @fires Player#leavepictureinpicture 26700 * 26701 * @return {Promise} 26702 * A promise. 26703 */ 26704 ; 26705 26706 _proto.exitPictureInPicture = function exitPictureInPicture() { 26707 if ('pictureInPictureEnabled' in document) { 26708 /** 26709 * This event fires when the player leaves picture in picture mode 26710 * 26711 * @event Player#leavepictureinpicture 26712 * @type {EventTarget~Event} 26713 */ 26714 return document.exitPictureInPicture(); 26715 } 26716 } 26717 /** 26718 * Called when this Player has focus and a key gets pressed down, or when 26719 * any Component of this player receives a key press that it doesn't handle. 26720 * This allows player-wide hotkeys (either as defined below, or optionally 26721 * by an external function). 26722 * 26723 * @param {EventTarget~Event} event 26724 * The `keydown` event that caused this function to be called. 26725 * 26726 * @listens keydown 26727 */ 26728 ; 26729 26730 _proto.handleKeyDown = function handleKeyDown(event) { 26731 var userActions = this.options_.userActions; // Bail out if hotkeys are not configured. 26732 26733 if (!userActions || !userActions.hotkeys) { 26734 return; 26735 } // Function that determines whether or not to exclude an element from 26736 // hotkeys handling. 26737 26738 26739 var excludeElement = function excludeElement(el) { 26740 var tagName = el.tagName.toLowerCase(); // The first and easiest test is for `contenteditable` elements. 26741 26742 if (el.isContentEditable) { 26743 return true; 26744 } // Inputs matching these types will still trigger hotkey handling as 26745 // they are not text inputs. 26746 26747 26748 var allowedInputTypes = ['button', 'checkbox', 'hidden', 'radio', 'reset', 'submit']; 26749 26750 if (tagName === 'input') { 26751 return allowedInputTypes.indexOf(el.type) === -1; 26752 } // The final test is by tag name. These tags will be excluded entirely. 26753 26754 26755 var excludedTags = ['textarea']; 26756 return excludedTags.indexOf(tagName) !== -1; 26757 }; // Bail out if the user is focused on an interactive form element. 26758 26759 26760 if (excludeElement(this.el_.ownerDocument.activeElement)) { 26761 return; 26762 } 26763 26764 if (typeof userActions.hotkeys === 'function') { 26765 userActions.hotkeys.call(this, event); 26766 } else { 26767 this.handleHotkeys(event); 26768 } 26769 } 26770 /** 26771 * Called when this Player receives a hotkey keydown event. 26772 * Supported player-wide hotkeys are: 26773 * 26774 * f - toggle fullscreen 26775 * m - toggle mute 26776 * k or Space - toggle play/pause 26777 * 26778 * @param {EventTarget~Event} event 26779 * The `keydown` event that caused this function to be called. 26780 */ 26781 ; 26782 26783 _proto.handleHotkeys = function handleHotkeys(event) { 26784 var hotkeys = this.options_.userActions ? this.options_.userActions.hotkeys : {}; // set fullscreenKey, muteKey, playPauseKey from `hotkeys`, use defaults if not set 26785 26786 var _hotkeys$fullscreenKe = hotkeys.fullscreenKey, 26787 fullscreenKey = _hotkeys$fullscreenKe === void 0 ? function (keydownEvent) { 26788 return keycode.isEventKey(keydownEvent, 'f'); 26789 } : _hotkeys$fullscreenKe, 26790 _hotkeys$muteKey = hotkeys.muteKey, 26791 muteKey = _hotkeys$muteKey === void 0 ? function (keydownEvent) { 26792 return keycode.isEventKey(keydownEvent, 'm'); 26793 } : _hotkeys$muteKey, 26794 _hotkeys$playPauseKey = hotkeys.playPauseKey, 26795 playPauseKey = _hotkeys$playPauseKey === void 0 ? function (keydownEvent) { 26796 return keycode.isEventKey(keydownEvent, 'k') || keycode.isEventKey(keydownEvent, 'Space'); 26797 } : _hotkeys$playPauseKey; 26798 26799 if (fullscreenKey.call(this, event)) { 26800 event.preventDefault(); 26801 event.stopPropagation(); 26802 var FSToggle = Component.getComponent('FullscreenToggle'); 26803 26804 if (document[this.fsApi_.fullscreenEnabled] !== false) { 26805 FSToggle.prototype.handleClick.call(this, event); 26806 } 26807 } else if (muteKey.call(this, event)) { 26808 event.preventDefault(); 26809 event.stopPropagation(); 26810 var MuteToggle = Component.getComponent('MuteToggle'); 26811 MuteToggle.prototype.handleClick.call(this, event); 26812 } else if (playPauseKey.call(this, event)) { 26813 event.preventDefault(); 26814 event.stopPropagation(); 26815 var PlayToggle = Component.getComponent('PlayToggle'); 26816 PlayToggle.prototype.handleClick.call(this, event); 26817 } 26818 } 26819 /** 26820 * Check whether the player can play a given mimetype 26821 * 26822 * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype 26823 * 26824 * @param {string} type 26825 * The mimetype to check 26826 * 26827 * @return {string} 26828 * 'probably', 'maybe', or '' (empty string) 26829 */ 26830 ; 26831 26832 _proto.canPlayType = function canPlayType(type) { 26833 var can; // Loop through each playback technology in the options order 26834 26835 for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { 26836 var techName = j[i]; 26837 var tech = Tech.getTech(techName); // Support old behavior of techs being registered as components. 26838 // Remove once that deprecated behavior is removed. 26839 26840 if (!tech) { 26841 tech = Component.getComponent(techName); 26842 } // Check if the current tech is defined before continuing 26843 26844 26845 if (!tech) { 26846 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 26847 continue; 26848 } // Check if the browser supports this technology 26849 26850 26851 if (tech.isSupported()) { 26852 can = tech.canPlayType(type); 26853 26854 if (can) { 26855 return can; 26856 } 26857 } 26858 } 26859 26860 return ''; 26861 } 26862 /** 26863 * Select source based on tech-order or source-order 26864 * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, 26865 * defaults to tech-order selection 26866 * 26867 * @param {Array} sources 26868 * The sources for a media asset 26869 * 26870 * @return {Object|boolean} 26871 * Object of source and tech order or false 26872 */ 26873 ; 26874 26875 _proto.selectSource = function selectSource(sources) { 26876 var _this10 = this; 26877 26878 // Get only the techs specified in `techOrder` that exist and are supported by the 26879 // current platform 26880 var techs = this.options_.techOrder.map(function (techName) { 26881 return [techName, Tech.getTech(techName)]; 26882 }).filter(function (_ref) { 26883 var techName = _ref[0], 26884 tech = _ref[1]; 26885 26886 // Check if the current tech is defined before continuing 26887 if (tech) { 26888 // Check if the browser supports this technology 26889 return tech.isSupported(); 26890 } 26891 26892 log.error("The \"" + techName + "\" tech is undefined. Skipped browser support check for that tech."); 26893 return false; 26894 }); // Iterate over each `innerArray` element once per `outerArray` element and execute 26895 // `tester` with both. If `tester` returns a non-falsy value, exit early and return 26896 // that value. 26897 26898 var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { 26899 var found; 26900 outerArray.some(function (outerChoice) { 26901 return innerArray.some(function (innerChoice) { 26902 found = tester(outerChoice, innerChoice); 26903 26904 if (found) { 26905 return true; 26906 } 26907 }); 26908 }); 26909 return found; 26910 }; 26911 26912 var foundSourceAndTech; 26913 26914 var flip = function flip(fn) { 26915 return function (a, b) { 26916 return fn(b, a); 26917 }; 26918 }; 26919 26920 var finder = function finder(_ref2, source) { 26921 var techName = _ref2[0], 26922 tech = _ref2[1]; 26923 26924 if (tech.canPlaySource(source, _this10.options_[techName.toLowerCase()])) { 26925 return { 26926 source: source, 26927 tech: techName 26928 }; 26929 } 26930 }; // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources 26931 // to select from them based on their priority. 26932 26933 26934 if (this.options_.sourceOrder) { 26935 // Source-first ordering 26936 foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); 26937 } else { 26938 // Tech-first ordering 26939 foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); 26940 } 26941 26942 return foundSourceAndTech || false; 26943 } 26944 /** 26945 * Get or set the video source. 26946 * 26947 * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source] 26948 * A SourceObject, an array of SourceObjects, or a string referencing
vendor: 4,455 bytes, lines 26949-27084
26949 * a URL to a media source. It is _highly recommended_ that an object 26950 * or array of objects is used here, so that source selection 26951 * algorithms can take the `type` into account. 26952 * 26953 * If not provided, this method acts as a getter. 26954 * 26955 * @return {string|undefined} 26956 * If the `source` argument is missing, returns the current source 26957 * URL. Otherwise, returns nothing/undefined. 26958 */ 26959 ; 26960 26961 _proto.src = function src(source) { 26962 var _this11 = this; 26963 26964 // getter usage 26965 if (typeof source === 'undefined') { 26966 return this.cache_.src || ''; 26967 } // filter out invalid sources and turn our source into 26968 // an array of source objects 26969 26970 26971 var sources = filterSource(source); // if a source was passed in then it is invalid because 26972 // it was filtered to a zero length Array. So we have to 26973 // show an error 26974 26975 if (!sources.length) { 26976 this.setTimeout(function () { 26977 this.error({ 26978 code: 4, 26979 message: this.localize(this.options_.notSupportedMessage) 26980 }); 26981 }, 0); 26982 return; 26983 } // intial sources 26984 26985 26986 this.changingSrc_ = true; 26987 this.cache_.sources = sources; 26988 this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware 26989 26990 setSource(this, sources[0], function (middlewareSource, mws) { 26991 _this11.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since 26992 // the source that is selected could be out of order from the cache update above this callback. 26993 26994 _this11.cache_.sources = sources; 26995 26996 _this11.updateSourceCaches_(middlewareSource); 26997 26998 var err = _this11.src_(middlewareSource); 26999 27000 if (err) { 27001 if (sources.length > 1) { 27002 return _this11.src(sources.slice(1)); 27003 } 27004 27005 _this11.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers 27006 27007 _this11.setTimeout(function () { 27008 this.error({ 27009 code: 4, 27010 message: this.localize(this.options_.notSupportedMessage) 27011 }); 27012 }, 0); // we could not find an appropriate tech, but let's still notify the delegate that this is it 27013 // this needs a better comment about why this is needed 27014 27015 27016 _this11.triggerReady(); 27017 27018 return; 27019 } 27020 27021 setTech(mws, _this11.tech_); 27022 }); 27023 } 27024 /** 27025 * Set the source object on the tech, returns a boolean that indicates whether 27026 * there is a tech that can play the source or not 27027 * 27028 * @param {Tech~SourceObject} source 27029 * The source object to set on the Tech 27030 * 27031 * @return {boolean} 27032 * - True if there is no Tech to playback this source 27033 * - False otherwise 27034 * 27035 * @private 27036 */ 27037 ; 27038 27039 _proto.src_ = function src_(source) { 27040 var _this12 = this; 27041 27042 var sourceTech = this.selectSource([source]); 27043 27044 if (!sourceTech) { 27045 return true; 27046 } 27047 27048 if (!titleCaseEquals(sourceTech.tech, this.techName_)) { 27049 this.changingSrc_ = true; // load this technology with the chosen source 27050 27051 this.loadTech_(sourceTech.tech, sourceTech.source); 27052 this.tech_.ready(function () { 27053 _this12.changingSrc_ = false; 27054 }); 27055 return false; 27056 } // wait until the tech is ready to set the source 27057 // and set it synchronously if possible (#2326) 27058 27059 27060 this.ready(function () { 27061 // The setSource tech method was added with source handlers 27062 // so older techs won't support it 27063 // We need to check the direct prototype for the case where subclasses 27064 // of the tech do not support source handlers 27065 if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { 27066 this.techCall_('setSource', source); 27067 } else { 27068 this.techCall_('src', source.src); 27069 } 27070 27071 this.changingSrc_ = false; 27072 }, true); 27073 return false; 27074 } 27075 /** 27076 * Begin loading the src data. 27077 */ 27078 ; 27079 27080 _proto.load = function load() { 27081 this.techCall_('load'); 27082 } 27083 /** 27084 * Reset the player. Loads the first tech in the techOrder,
vendor: 37,047 bytes, lines 27085-28236
27085 * removes all the text tracks in the existing `tech`, 27086 * and calls `reset` on the `tech`. 27087 */ 27088 ; 27089 27090 _proto.reset = function reset() { 27091 var _this13 = this; 27092 27093 var PromiseClass = this.options_.Promise || window$1.Promise; 27094 27095 if (this.paused() || !PromiseClass) { 27096 this.doReset_(); 27097 } else { 27098 var playPromise = this.play(); 27099 silencePromise(playPromise.then(function () { 27100 return _this13.doReset_(); 27101 })); 27102 } 27103 }; 27104 27105 _proto.doReset_ = function doReset_() { 27106 if (this.tech_) { 27107 this.tech_.clearTracks('text'); 27108 } 27109 27110 this.resetCache_(); 27111 this.poster(''); 27112 this.loadTech_(this.options_.techOrder[0], null); 27113 this.techCall_('reset'); 27114 this.resetControlBarUI_(); 27115 27116 if (isEvented(this)) { 27117 this.trigger('playerreset'); 27118 } 27119 } 27120 /** 27121 * Reset Control Bar's UI by calling sub-methods that reset 27122 * all of Control Bar's components 27123 */ 27124 ; 27125 27126 _proto.resetControlBarUI_ = function resetControlBarUI_() { 27127 this.resetProgressBar_(); 27128 this.resetPlaybackRate_(); 27129 this.resetVolumeBar_(); 27130 } 27131 /** 27132 * Reset tech's progress so progress bar is reset in the UI 27133 */ 27134 ; 27135 27136 _proto.resetProgressBar_ = function resetProgressBar_() { 27137 this.currentTime(0); 27138 var _this$controlBar = this.controlBar, 27139 durationDisplay = _this$controlBar.durationDisplay, 27140 remainingTimeDisplay = _this$controlBar.remainingTimeDisplay; 27141 27142 if (durationDisplay) { 27143 durationDisplay.updateContent(); 27144 } 27145 27146 if (remainingTimeDisplay) { 27147 remainingTimeDisplay.updateContent(); 27148 } 27149 } 27150 /** 27151 * Reset Playback ratio 27152 */ 27153 ; 27154 27155 _proto.resetPlaybackRate_ = function resetPlaybackRate_() { 27156 this.playbackRate(this.defaultPlaybackRate()); 27157 this.handleTechRateChange_(); 27158 } 27159 /** 27160 * Reset Volume bar 27161 */ 27162 ; 27163 27164 _proto.resetVolumeBar_ = function resetVolumeBar_() { 27165 this.volume(1.0); 27166 this.trigger('volumechange'); 27167 } 27168 /** 27169 * Returns all of the current source objects. 27170 * 27171 * @return {Tech~SourceObject[]} 27172 * The current source objects 27173 */ 27174 ; 27175 27176 _proto.currentSources = function currentSources() { 27177 var source = this.currentSource(); 27178 var sources = []; // assume `{}` or `{ src }` 27179 27180 if (Object.keys(source).length !== 0) { 27181 sources.push(source); 27182 } 27183 27184 return this.cache_.sources || sources; 27185 } 27186 /** 27187 * Returns the current source object. 27188 * 27189 * @return {Tech~SourceObject} 27190 * The current source object 27191 */ 27192 ; 27193 27194 _proto.currentSource = function currentSource() { 27195 return this.cache_.source || {}; 27196 } 27197 /** 27198 * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4 27199 * Can be used in conjunction with `currentType` to assist in rebuilding the current source object. 27200 * 27201 * @return {string} 27202 * The current source 27203 */ 27204 ; 27205 27206 _proto.currentSrc = function currentSrc() { 27207 return this.currentSource() && this.currentSource().src || ''; 27208 } 27209 /** 27210 * Get the current source type e.g. video/mp4 27211 * This can allow you rebuild the current source object so that you could load the same 27212 * source and tech later 27213 * 27214 * @return {string} 27215 * The source MIME type 27216 */ 27217 ; 27218 27219 _proto.currentType = function currentType() { 27220 return this.currentSource() && this.currentSource().type || ''; 27221 } 27222 /** 27223 * Get or set the preload attribute 27224 * 27225 * @param {boolean} [value] 27226 * - true means that we should preload 27227 * - false means that we should not preload 27228 * 27229 * @return {string} 27230 * The preload attribute value when getting 27231 */ 27232 ; 27233 27234 _proto.preload = function preload(value) { 27235 if (value !== undefined) { 27236 this.techCall_('setPreload', value); 27237 this.options_.preload = value; 27238 return; 27239 } 27240 27241 return this.techGet_('preload'); 27242 } 27243 /** 27244 * Get or set the autoplay option. When this is a boolean it will 27245 * modify the attribute on the tech. When this is a string the attribute on 27246 * the tech will be removed and `Player` will handle autoplay on loadstarts. 27247 * 27248 * @param {boolean|string} [value] 27249 * - true: autoplay using the browser behavior 27250 * - false: do not autoplay 27251 * - 'play': call play() on every loadstart 27252 * - 'muted': call muted() then play() on every loadstart 27253 * - 'any': call play() on every loadstart. if that fails call muted() then play(). 27254 * - *: values other than those listed here will be set `autoplay` to true 27255 * 27256 * @return {boolean|string} 27257 * The current value of autoplay when getting 27258 */ 27259 ; 27260 27261 _proto.autoplay = function autoplay(value) { 27262 // getter usage 27263 if (value === undefined) { 27264 return this.options_.autoplay || false; 27265 } 27266 27267 var techAutoplay; // if the value is a valid string set it to that 27268 27269 if (typeof value === 'string' && /(any|play|muted)/.test(value)) { 27270 this.options_.autoplay = value; 27271 this.manualAutoplay_(value); 27272 techAutoplay = false; // any falsy value sets autoplay to false in the browser, 27273 // lets do the same 27274 } else if (!value) { 27275 this.options_.autoplay = false; // any other value (ie truthy) sets autoplay to true 27276 } else { 27277 this.options_.autoplay = true; 27278 } 27279 27280 techAutoplay = typeof techAutoplay === 'undefined' ? this.options_.autoplay : techAutoplay; // if we don't have a tech then we do not queue up 27281 // a setAutoplay call on tech ready. We do this because the 27282 // autoplay option will be passed in the constructor and we 27283 // do not need to set it twice 27284 27285 if (this.tech_) { 27286 this.techCall_('setAutoplay', techAutoplay); 27287 } 27288 } 27289 /** 27290 * Set or unset the playsinline attribute. 27291 * Playsinline tells the browser that non-fullscreen playback is preferred. 27292 * 27293 * @param {boolean} [value] 27294 * - true means that we should try to play inline by default 27295 * - false means that we should use the browser's default playback mode, 27296 * which in most cases is inline. iOS Safari is a notable exception 27297 * and plays fullscreen by default. 27298 * 27299 * @return {string|Player} 27300 * - the current value of playsinline 27301 * - the player when setting 27302 * 27303 * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline} 27304 */ 27305 ; 27306 27307 _proto.playsinline = function playsinline(value) { 27308 if (value !== undefined) { 27309 this.techCall_('setPlaysinline', value); 27310 this.options_.playsinline = value; 27311 return this; 27312 } 27313 27314 return this.techGet_('playsinline'); 27315 } 27316 /** 27317 * Get or set the loop attribute on the video element. 27318 * 27319 * @param {boolean} [value] 27320 * - true means that we should loop the video 27321 * - false means that we should not loop the video 27322 * 27323 * @return {boolean} 27324 * The current value of loop when getting 27325 */ 27326 ; 27327 27328 _proto.loop = function loop(value) { 27329 if (value !== undefined) { 27330 this.techCall_('setLoop', value); 27331 this.options_.loop = value; 27332 return; 27333 } 27334 27335 return this.techGet_('loop'); 27336 } 27337 /** 27338 * Get or set the poster image source url 27339 * 27340 * @fires Player#posterchange 27341 * 27342 * @param {string} [src] 27343 * Poster image source URL 27344 * 27345 * @return {string} 27346 * The current value of poster when getting 27347 */ 27348 ; 27349 27350 _proto.poster = function poster(src) { 27351 if (src === undefined) { 27352 return this.poster_; 27353 } // The correct way to remove a poster is to set as an empty string 27354 // other falsey values will throw errors 27355 27356 27357 if (!src) { 27358 src = ''; 27359 } 27360 27361 if (src === this.poster_) { 27362 return; 27363 } // update the internal poster variable 27364 27365 27366 this.poster_ = src; // update the tech's poster 27367 27368 this.techCall_('setPoster', src); 27369 this.isPosterFromTech_ = false; // alert components that the poster has been set 27370 27371 /** 27372 * This event fires when the poster image is changed on the player. 27373 * 27374 * @event Player#posterchange 27375 * @type {EventTarget~Event} 27376 */ 27377 27378 this.trigger('posterchange'); 27379 } 27380 /** 27381 * Some techs (e.g. YouTube) can provide a poster source in an 27382 * asynchronous way. We want the poster component to use this 27383 * poster source so that it covers up the tech's controls. 27384 * (YouTube's play button). However we only want to use this 27385 * source if the player user hasn't set a poster through 27386 * the normal APIs. 27387 * 27388 * @fires Player#posterchange 27389 * @listens Tech#posterchange 27390 * @private 27391 */ 27392 ; 27393 27394 _proto.handleTechPosterChange_ = function handleTechPosterChange_() { 27395 if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) { 27396 var newPoster = this.tech_.poster() || ''; 27397 27398 if (newPoster !== this.poster_) { 27399 this.poster_ = newPoster; 27400 this.isPosterFromTech_ = true; // Let components know the poster has changed 27401 27402 this.trigger('posterchange'); 27403 } 27404 } 27405 } 27406 /** 27407 * Get or set whether or not the controls are showing. 27408 * 27409 * @fires Player#controlsenabled 27410 * 27411 * @param {boolean} [bool] 27412 * - true to turn controls on 27413 * - false to turn controls off 27414 * 27415 * @return {boolean} 27416 * The current value of controls when getting 27417 */ 27418 ; 27419 27420 _proto.controls = function controls(bool) { 27421 if (bool === undefined) { 27422 return !!this.controls_; 27423 } 27424 27425 bool = !!bool; // Don't trigger a change event unless it actually changed 27426 27427 if (this.controls_ === bool) { 27428 return; 27429 } 27430 27431 this.controls_ = bool; 27432 27433 if (this.usingNativeControls()) { 27434 this.techCall_('setControls', bool); 27435 } 27436 27437 if (this.controls_) { 27438 this.removeClass('vjs-controls-disabled'); 27439 this.addClass('vjs-controls-enabled'); 27440 /** 27441 * @event Player#controlsenabled 27442 * @type {EventTarget~Event} 27443 */ 27444 27445 this.trigger('controlsenabled'); 27446 27447 if (!this.usingNativeControls()) { 27448 this.addTechControlsListeners_(); 27449 } 27450 } else { 27451 this.removeClass('vjs-controls-enabled'); 27452 this.addClass('vjs-controls-disabled'); 27453 /** 27454 * @event Player#controlsdisabled 27455 * @type {EventTarget~Event} 27456 */ 27457 27458 this.trigger('controlsdisabled'); 27459 27460 if (!this.usingNativeControls()) { 27461 this.removeTechControlsListeners_(); 27462 } 27463 } 27464 } 27465 /** 27466 * Toggle native controls on/off. Native controls are the controls built into 27467 * devices (e.g. default iPhone controls), Flash, or other techs 27468 * (e.g. Vimeo Controls) 27469 * **This should only be set by the current tech, because only the tech knows 27470 * if it can support native controls** 27471 * 27472 * @fires Player#usingnativecontrols 27473 * @fires Player#usingcustomcontrols 27474 * 27475 * @param {boolean} [bool] 27476 * - true to turn native controls on 27477 * - false to turn native controls off 27478 * 27479 * @return {boolean} 27480 * The current value of native controls when getting 27481 */ 27482 ; 27483 27484 _proto.usingNativeControls = function usingNativeControls(bool) { 27485 if (bool === undefined) { 27486 return !!this.usingNativeControls_; 27487 } 27488 27489 bool = !!bool; // Don't trigger a change event unless it actually changed 27490 27491 if (this.usingNativeControls_ === bool) { 27492 return; 27493 } 27494 27495 this.usingNativeControls_ = bool; 27496 27497 if (this.usingNativeControls_) { 27498 this.addClass('vjs-using-native-controls'); 27499 /** 27500 * player is using the native device controls 27501 * 27502 * @event Player#usingnativecontrols 27503 * @type {EventTarget~Event} 27504 */ 27505 27506 this.trigger('usingnativecontrols'); 27507 } else { 27508 this.removeClass('vjs-using-native-controls'); 27509 /** 27510 * player is using the custom HTML controls 27511 * 27512 * @event Player#usingcustomcontrols 27513 * @type {EventTarget~Event} 27514 */ 27515 27516 this.trigger('usingcustomcontrols'); 27517 } 27518 } 27519 /** 27520 * Set or get the current MediaError 27521 * 27522 * @fires Player#error 27523 * 27524 * @param {MediaError|string|number} [err] 27525 * A MediaError or a string/number to be turned 27526 * into a MediaError 27527 * 27528 * @return {MediaError|null} 27529 * The current MediaError when getting (or null) 27530 */ 27531 ; 27532 27533 _proto.error = function error(err) { 27534 if (err === undefined) { 27535 return this.error_ || null; 27536 } // Suppress the first error message for no compatible source until 27537 // user interaction 27538 27539 27540 if (this.options_.suppressNotSupportedError && err && err.message && err.message === this.localize(this.options_.notSupportedMessage)) { 27541 var triggerSuppressedError = function triggerSuppressedError() { 27542 this.error(err); 27543 }; 27544 27545 this.options_.suppressNotSupportedError = false; 27546 this.any(['click', 'touchstart'], triggerSuppressedError); 27547 this.one('loadstart', function () { 27548 this.off(['click', 'touchstart'], triggerSuppressedError); 27549 }); 27550 return; 27551 } // restoring to default 27552 27553 27554 if (err === null) { 27555 this.error_ = err; 27556 this.removeClass('vjs-error'); 27557 27558 if (this.errorDisplay) { 27559 this.errorDisplay.close(); 27560 } 27561 27562 return; 27563 } 27564 27565 this.error_ = new MediaError(err); // add the vjs-error classname to the player 27566 27567 this.addClass('vjs-error'); // log the name of the error type and any message 27568 // IE11 logs "[object object]" and required you to expand message to see error object 27569 27570 log.error("(CODE:" + this.error_.code + " " + MediaError.errorTypes[this.error_.code] + ")", this.error_.message, this.error_); 27571 /** 27572 * @event Player#error 27573 * @type {EventTarget~Event} 27574 */ 27575 27576 this.trigger('error'); 27577 return; 27578 } 27579 /** 27580 * Report user activity 27581 * 27582 * @param {Object} event 27583 * Event object 27584 */ 27585 ; 27586 27587 _proto.reportUserActivity = function reportUserActivity(event) { 27588 this.userActivity_ = true; 27589 } 27590 /** 27591 * Get/set if user is active 27592 * 27593 * @fires Player#useractive 27594 * @fires Player#userinactive 27595 * 27596 * @param {boolean} [bool] 27597 * - true if the user is active 27598 * - false if the user is inactive 27599 * 27600 * @return {boolean} 27601 * The current value of userActive when getting 27602 */ 27603 ; 27604 27605 _proto.userActive = function userActive(bool) { 27606 if (bool === undefined) { 27607 return this.userActive_; 27608 } 27609 27610 bool = !!bool; 27611 27612 if (bool === this.userActive_) { 27613 return; 27614 } 27615 27616 this.userActive_ = bool; 27617 27618 if (this.userActive_) { 27619 this.userActivity_ = true; 27620 this.removeClass('vjs-user-inactive'); 27621 this.addClass('vjs-user-active'); 27622 /** 27623 * @event Player#useractive 27624 * @type {EventTarget~Event} 27625 */ 27626 27627 this.trigger('useractive'); 27628 return; 27629 } // Chrome/Safari/IE have bugs where when you change the cursor it can 27630 // trigger a mousemove event. This causes an issue when you're hiding 27631 // the cursor when the user is inactive, and a mousemove signals user 27632 // activity. Making it impossible to go into inactive mode. Specifically 27633 // this happens in fullscreen when we really need to hide the cursor. 27634 // 27635 // When this gets resolved in ALL browsers it can be removed 27636 // https://code.google.com/p/chromium/issues/detail?id=103041 27637 27638 27639 if (this.tech_) { 27640 this.tech_.one('mousemove', function (e) { 27641 e.stopPropagation(); 27642 e.preventDefault(); 27643 }); 27644 } 27645 27646 this.userActivity_ = false; 27647 this.removeClass('vjs-user-active'); 27648 this.addClass('vjs-user-inactive'); 27649 /** 27650 * @event Player#userinactive 27651 * @type {EventTarget~Event} 27652 */ 27653 27654 this.trigger('userinactive'); 27655 } 27656 /** 27657 * Listen for user activity based on timeout value 27658 * 27659 * @private 27660 */ 27661 ; 27662 27663 _proto.listenForUserActivity_ = function listenForUserActivity_() { 27664 var mouseInProgress; 27665 var lastMoveX; 27666 var lastMoveY; 27667 var handleActivity = bind(this, this.reportUserActivity); 27668 27669 var handleMouseMove = function handleMouseMove(e) { 27670 // #1068 - Prevent mousemove spamming 27671 // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970 27672 if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) { 27673 lastMoveX = e.screenX; 27674 lastMoveY = e.screenY; 27675 handleActivity(); 27676 } 27677 }; 27678 27679 var handleMouseDown = function handleMouseDown() { 27680 handleActivity(); // For as long as the they are touching the device or have their mouse down, 27681 // we consider them active even if they're not moving their finger or mouse. 27682 // So we want to continue to update that they are active 27683 27684 this.clearInterval(mouseInProgress); // Setting userActivity=true now and setting the interval to the same time 27685 // as the activityCheck interval (250) should ensure we never miss the 27686 // next activityCheck 27687 27688 mouseInProgress = this.setInterval(handleActivity, 250); 27689 }; 27690 27691 var handleMouseUp = function handleMouseUp(event) { 27692 handleActivity(); // Stop the interval that maintains activity if the mouse/touch is down 27693 27694 this.clearInterval(mouseInProgress); 27695 }; // Any mouse movement will be considered user activity 27696 27697 27698 this.on('mousedown', handleMouseDown); 27699 this.on('mousemove', handleMouseMove); 27700 this.on('mouseup', handleMouseUp); 27701 var controlBar = this.getChild('controlBar'); // Fixes bug on Android & iOS where when tapping progressBar (when control bar is displayed) 27702 // controlBar would no longer be hidden by default timeout. 27703 27704 if (controlBar && !IS_IOS && !IS_ANDROID) { 27705 controlBar.on('mouseenter', function (event) { 27706 this.player().cache_.inactivityTimeout = this.player().options_.inactivityTimeout; 27707 this.player().options_.inactivityTimeout = 0; 27708 }); 27709 controlBar.on('mouseleave', function (event) { 27710 this.player().options_.inactivityTimeout = this.player().cache_.inactivityTimeout; 27711 }); 27712 } // Listen for keyboard navigation 27713 // Shouldn't need to use inProgress interval because of key repeat 27714 27715 27716 this.on('keydown', handleActivity); 27717 this.on('keyup', handleActivity); // Run an interval every 250 milliseconds instead of stuffing everything into 27718 // the mousemove/touchmove function itself, to prevent performance degradation. 27719 // `this.reportUserActivity` simply sets this.userActivity_ to true, which 27720 // then gets picked up by this loop 27721 // http://ejohn.org/blog/learning-from-twitter/ 27722 27723 var inactivityTimeout; 27724 this.setInterval(function () { 27725 // Check to see if mouse/touch activity has happened 27726 if (!this.userActivity_) { 27727 return; 27728 } // Reset the activity tracker 27729 27730 27731 this.userActivity_ = false; // If the user state was inactive, set the state to active 27732 27733 this.userActive(true); // Clear any existing inactivity timeout to start the timer over 27734 27735 this.clearTimeout(inactivityTimeout); 27736 var timeout = this.options_.inactivityTimeout; 27737 27738 if (timeout <= 0) { 27739 return; 27740 } // In <timeout> milliseconds, if no more activity has occurred the 27741 // user will be considered inactive 27742 27743 27744 inactivityTimeout = this.setTimeout(function () { 27745 // Protect against the case where the inactivityTimeout can trigger just 27746 // before the next user activity is picked up by the activity check loop 27747 // causing a flicker 27748 if (!this.userActivity_) { 27749 this.userActive(false); 27750 } 27751 }, timeout); 27752 }, 250); 27753 } 27754 /** 27755 * Gets or sets the current playback rate. A playback rate of 27756 * 1.0 represents normal speed and 0.5 would indicate half-speed 27757 * playback, for instance. 27758 * 27759 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate 27760 * 27761 * @param {number} [rate] 27762 * New playback rate to set. 27763 * 27764 * @return {number} 27765 * The current playback rate when getting or 1.0 27766 */ 27767 ; 27768 27769 _proto.playbackRate = function playbackRate(rate) { 27770 if (rate !== undefined) { 27771 // NOTE: this.cache_.lastPlaybackRate is set from the tech handler 27772 // that is registered above 27773 this.techCall_('setPlaybackRate', rate); 27774 return; 27775 } 27776 27777 if (this.tech_ && this.tech_.featuresPlaybackRate) { 27778 return this.cache_.lastPlaybackRate || this.techGet_('playbackRate'); 27779 } 27780 27781 return 1.0; 27782 } 27783 /** 27784 * Gets or sets the current default playback rate. A default playback rate of 27785 * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance. 27786 * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not 27787 * not the current playbackRate. 27788 * 27789 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate 27790 * 27791 * @param {number} [rate] 27792 * New default playback rate to set. 27793 * 27794 * @return {number|Player} 27795 * - The default playback rate when getting or 1.0 27796 * - the player when setting 27797 */ 27798 ; 27799 27800 _proto.defaultPlaybackRate = function defaultPlaybackRate(rate) { 27801 if (rate !== undefined) { 27802 return this.techCall_('setDefaultPlaybackRate', rate); 27803 } 27804 27805 if (this.tech_ && this.tech_.featuresPlaybackRate) { 27806 return this.techGet_('defaultPlaybackRate'); 27807 } 27808 27809 return 1.0; 27810 } 27811 /** 27812 * Gets or sets the audio flag 27813 * 27814 * @param {boolean} bool 27815 * - true signals that this is an audio player 27816 * - false signals that this is not an audio player 27817 * 27818 * @return {boolean} 27819 * The current value of isAudio when getting 27820 */ 27821 ; 27822 27823 _proto.isAudio = function isAudio(bool) { 27824 if (bool !== undefined) { 27825 this.isAudio_ = !!bool; 27826 return; 27827 } 27828 27829 return !!this.isAudio_; 27830 } 27831 /** 27832 * A helper method for adding a {@link TextTrack} to our 27833 * {@link TextTrackList}. 27834 * 27835 * In addition to the W3C settings we allow adding additional info through options. 27836 * 27837 * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack 27838 * 27839 * @param {string} [kind] 27840 * the kind of TextTrack you are adding 27841 * 27842 * @param {string} [label] 27843 * the label to give the TextTrack label 27844 * 27845 * @param {string} [language] 27846 * the language to set on the TextTrack 27847 * 27848 * @return {TextTrack|undefined} 27849 * the TextTrack that was added or undefined 27850 * if there is no tech 27851 */ 27852 ; 27853 27854 _proto.addTextTrack = function addTextTrack(kind, label, language) { 27855 if (this.tech_) { 27856 return this.tech_.addTextTrack(kind, label, language); 27857 } 27858 } 27859 /** 27860 * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will 27861 * automatically removed from the video element whenever the source changes, unless 27862 * manualCleanup is set to false. 27863 * 27864 * @param {Object} options 27865 * Options to pass to {@link HTMLTrackElement} during creation. See 27866 * {@link HTMLTrackElement} for object properties that you should use. 27867 * 27868 * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be 27869 * 27870 * @return {HtmlTrackElement} 27871 * the HTMLTrackElement that was created and added 27872 * to the HtmlTrackElementList and the remote 27873 * TextTrackList 27874 * 27875 * @deprecated The default value of the "manualCleanup" parameter will default 27876 * to "false" in upcoming versions of Video.js 27877 */ 27878 ; 27879 27880 _proto.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) { 27881 if (this.tech_) { 27882 return this.tech_.addRemoteTextTrack(options, manualCleanup); 27883 } 27884 } 27885 /** 27886 * Remove a remote {@link TextTrack} from the respective 27887 * {@link TextTrackList} and {@link HtmlTrackElementList}. 27888 * 27889 * @param {Object} track 27890 * Remote {@link TextTrack} to remove 27891 * 27892 * @return {undefined} 27893 * does not return anything 27894 */ 27895 ; 27896 27897 _proto.removeRemoteTextTrack = function removeRemoteTextTrack(obj) { 27898 if (obj === void 0) { 27899 obj = {}; 27900 } 27901 27902 var _obj = obj, 27903 track = _obj.track; 27904 27905 if (!track) { 27906 track = obj; 27907 } // destructure the input into an object with a track argument, defaulting to arguments[0] 27908 // default the whole argument to an empty object if nothing was passed in 27909 27910 27911 if (this.tech_) { 27912 return this.tech_.removeRemoteTextTrack(track); 27913 } 27914 } 27915 /** 27916 * Gets available media playback quality metrics as specified by the W3C's Media 27917 * Playback Quality API. 27918 * 27919 * @see [Spec]{@link https://wicg.github.io/media-playback-quality} 27920 * 27921 * @return {Object|undefined} 27922 * An object with supported media playback quality metrics or undefined if there 27923 * is no tech or the tech does not support it. 27924 */ 27925 ; 27926 27927 _proto.getVideoPlaybackQuality = function getVideoPlaybackQuality() { 27928 return this.techGet_('getVideoPlaybackQuality'); 27929 } 27930 /** 27931 * Get video width 27932 * 27933 * @return {number} 27934 * current video width 27935 */ 27936 ; 27937 27938 _proto.videoWidth = function videoWidth() { 27939 return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0; 27940 } 27941 /** 27942 * Get video height 27943 * 27944 * @return {number} 27945 * current video height 27946 */ 27947 ; 27948 27949 _proto.videoHeight = function videoHeight() { 27950 return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0; 27951 } 27952 /** 27953 * The player's language code 27954 * NOTE: The language should be set in the player options if you want the 27955 * the controls to be built with a specific language. Changing the language 27956 * later will not update controls text. 27957 * 27958 * @param {string} [code] 27959 * the language code to set the player to 27960 * 27961 * @return {string} 27962 * The current language code when getting 27963 */ 27964 ; 27965 27966 _proto.language = function language(code) { 27967 if (code === undefined) { 27968 return this.language_; 27969 } 27970 27971 this.language_ = String(code).toLowerCase(); 27972 } 27973 /** 27974 * Get the player's language dictionary 27975 * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time 27976 * Languages specified directly in the player options have precedence 27977 * 27978 * @return {Array} 27979 * An array of of supported languages 27980 */ 27981 ; 27982 27983 _proto.languages = function languages() { 27984 return mergeOptions(Player.prototype.options_.languages, this.languages_); 27985 } 27986 /** 27987 * returns a JavaScript object reperesenting the current track 27988 * information. **DOES not return it as JSON** 27989 * 27990 * @return {Object} 27991 * Object representing the current of track info 27992 */ 27993 ; 27994 27995 _proto.toJSON = function toJSON() { 27996 var options = mergeOptions(this.options_); 27997 var tracks = options.tracks; 27998 options.tracks = []; 27999 28000 for (var i = 0; i < tracks.length; i++) { 28001 var track = tracks[i]; // deep merge tracks and null out player so no circular references 28002 28003 track = mergeOptions(track); 28004 track.player = undefined; 28005 options.tracks[i] = track; 28006 } 28007 28008 return options; 28009 } 28010 /** 28011 * Creates a simple modal dialog (an instance of the {@link ModalDialog} 28012 * component) that immediately overlays the player with arbitrary 28013 * content and removes itself when closed. 28014 * 28015 * @param {string|Function|Element|Array|null} content 28016 * Same as {@link ModalDialog#content}'s param of the same name. 28017 * The most straight-forward usage is to provide a string or DOM 28018 * element. 28019 * 28020 * @param {Object} [options] 28021 * Extra options which will be passed on to the {@link ModalDialog}. 28022 * 28023 * @return {ModalDialog} 28024 * the {@link ModalDialog} that was created 28025 */ 28026 ; 28027 28028 _proto.createModal = function createModal(content, options) { 28029 var _this14 = this; 28030 28031 options = options || {}; 28032 options.content = content || ''; 28033 var modal = new ModalDialog(this, options); 28034 this.addChild(modal); 28035 modal.on('dispose', function () { 28036 _this14.removeChild(modal); 28037 }); 28038 modal.open(); 28039 return modal; 28040 } 28041 /** 28042 * Change breakpoint classes when the player resizes. 28043 * 28044 * @private 28045 */ 28046 ; 28047 28048 _proto.updateCurrentBreakpoint_ = function updateCurrentBreakpoint_() { 28049 if (!this.responsive()) { 28050 return; 28051 } 28052 28053 var currentBreakpoint = this.currentBreakpoint(); 28054 var currentWidth = this.currentWidth(); 28055 28056 for (var i = 0; i < BREAKPOINT_ORDER.length; i++) { 28057 var candidateBreakpoint = BREAKPOINT_ORDER[i]; 28058 var maxWidth = this.breakpoints_[candidateBreakpoint]; 28059 28060 if (currentWidth <= maxWidth) { 28061 // The current breakpoint did not change, nothing to do. 28062 if (currentBreakpoint === candidateBreakpoint) { 28063 return; 28064 } // Only remove a class if there is a current breakpoint. 28065 28066 28067 if (currentBreakpoint) { 28068 this.removeClass(BREAKPOINT_CLASSES[currentBreakpoint]); 28069 } 28070 28071 this.addClass(BREAKPOINT_CLASSES[candidateBreakpoint]); 28072 this.breakpoint_ = candidateBreakpoint; 28073 break; 28074 } 28075 } 28076 } 28077 /** 28078 * Removes the current breakpoint. 28079 * 28080 * @private 28081 */ 28082 ; 28083 28084 _proto.removeCurrentBreakpoint_ = function removeCurrentBreakpoint_() { 28085 var className = this.currentBreakpointClass(); 28086 this.breakpoint_ = ''; 28087 28088 if (className) { 28089 this.removeClass(className); 28090 } 28091 } 28092 /** 28093 * Get or set breakpoints on the player. 28094 * 28095 * Calling this method with an object or `true` will remove any previous 28096 * custom breakpoints and start from the defaults again. 28097 * 28098 * @param {Object|boolean} [breakpoints] 28099 * If an object is given, it can be used to provide custom 28100 * breakpoints. If `true` is given, will set default breakpoints. 28101 * If this argument is not given, will simply return the current 28102 * breakpoints. 28103 * 28104 * @param {number} [breakpoints.tiny] 28105 * The maximum width for the "vjs-layout-tiny" class. 28106 * 28107 * @param {number} [breakpoints.xsmall] 28108 * The maximum width for the "vjs-layout-x-small" class. 28109 * 28110 * @param {number} [breakpoints.small] 28111 * The maximum width for the "vjs-layout-small" class. 28112 * 28113 * @param {number} [breakpoints.medium] 28114 * The maximum width for the "vjs-layout-medium" class. 28115 * 28116 * @param {number} [breakpoints.large] 28117 * The maximum width for the "vjs-layout-large" class. 28118 * 28119 * @param {number} [breakpoints.xlarge] 28120 * The maximum width for the "vjs-layout-x-large" class. 28121 * 28122 * @param {number} [breakpoints.huge] 28123 * The maximum width for the "vjs-layout-huge" class. 28124 * 28125 * @return {Object} 28126 * An object mapping breakpoint names to maximum width values. 28127 */ 28128 ; 28129 28130 _proto.breakpoints = function breakpoints(_breakpoints) { 28131 // Used as a getter. 28132 if (_breakpoints === undefined) { 28133 return assign(this.breakpoints_); 28134 } 28135 28136 this.breakpoint_ = ''; 28137 this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, _breakpoints); // When breakpoint definitions change, we need to update the currently 28138 // selected breakpoint. 28139 28140 this.updateCurrentBreakpoint_(); // Clone the breakpoints before returning. 28141 28142 return assign(this.breakpoints_); 28143 } 28144 /** 28145 * Get or set a flag indicating whether or not this player should adjust 28146 * its UI based on its dimensions. 28147 * 28148 * @param {boolean} value 28149 * Should be `true` if the player should adjust its UI based on its 28150 * dimensions; otherwise, should be `false`. 28151 * 28152 * @return {boolean} 28153 * Will be `true` if this player should adjust its UI based on its 28154 * dimensions; otherwise, will be `false`. 28155 */ 28156 ; 28157 28158 _proto.responsive = function responsive(value) { 28159 // Used as a getter. 28160 if (value === undefined) { 28161 return this.responsive_; 28162 } 28163 28164 value = Boolean(value); 28165 var current = this.responsive_; // Nothing changed. 28166 28167 if (value === current) { 28168 return; 28169 } // The value actually changed, set it. 28170 28171 28172 this.responsive_ = value; // Start listening for breakpoints and set the initial breakpoint if the 28173 // player is now responsive. 28174 28175 if (value) { 28176 this.on('playerresize', this.updateCurrentBreakpoint_); 28177 this.updateCurrentBreakpoint_(); // Stop listening for breakpoints if the player is no longer responsive. 28178 } else { 28179 this.off('playerresize', this.updateCurrentBreakpoint_); 28180 this.removeCurrentBreakpoint_(); 28181 } 28182 28183 return value; 28184 } 28185 /** 28186 * Get current breakpoint name, if any. 28187 * 28188 * @return {string} 28189 * If there is currently a breakpoint set, returns a the key from the 28190 * breakpoints object matching it. Otherwise, returns an empty string. 28191 */ 28192 ; 28193 28194 _proto.currentBreakpoint = function currentBreakpoint() { 28195 return this.breakpoint_; 28196 } 28197 /** 28198 * Get the current breakpoint class name. 28199 * 28200 * @return {string} 28201 * The matching class name (e.g. `"vjs-layout-tiny"` or 28202 * `"vjs-layout-large"`) for the current breakpoint. Empty string if 28203 * there is no current breakpoint. 28204 */ 28205 ; 28206 28207 _proto.currentBreakpointClass = function currentBreakpointClass() { 28208 return BREAKPOINT_CLASSES[this.breakpoint_] || ''; 28209 } 28210 /** 28211 * An object that describes a single piece of media. 28212 * 28213 * Properties that are not part of this type description will be retained; so, 28214 * this can be viewed as a generic metadata storage mechanism as well. 28215 * 28216 * @see {@link https://wicg.github.io/mediasession/#the-mediametadata-interface} 28217 * @typedef {Object} Player~MediaObject 28218 * 28219 * @property {string} [album] 28220 * Unused, except if this object is passed to the `MediaSession` 28221 * API. 28222 * 28223 * @property {string} [artist] 28224 * Unused, except if this object is passed to the `MediaSession` 28225 * API. 28226 * 28227 * @property {Object[]} [artwork] 28228 * Unused, except if this object is passed to the `MediaSession` 28229 * API. If not specified, will be populated via the `poster`, if 28230 * available. 28231 * 28232 * @property {string} [poster] 28233 * URL to an image that will display before playback. 28234 * 28235 * @property {Tech~SourceObject|Tech~SourceObject[]|string} [src] 28236 * A single source object, an array of source objects, or a string
28237 * referencing a URL to a media source. It is _highly recommended_ 28238 * that an object or array of objects is used here, so that source 28239 * selection algorithms can take the `type` into account. 28240 * 28241 * @property {string} [title] 28242 * Unused, except if this object is passed to the `MediaSession` 28243 * API. 28244 * 28245 * @property {Object[]} [textTracks] 28246 * An array of objects to be used to create text tracks, following 28247 * the {@link https://www.w3.org/TR/html50/embedded-content-0.html#the-track-element|native track element format}. 28248 * For ease of removal, these will be created as "remote" text 28249 * tracks and set to automatically clean up on source changes. 28250 * 28251 * These objects may have properties like `src`, `kind`, `label`, 28252 * and `language`, see {@link Tech#createRemoteTextTrack}. 28253 */ 28254 28255 /** 28256 * Populate the player using a {@link Player~MediaObject|MediaObject}. 28257 * 28258 * @param {Player~MediaObject} media 28259 * A media object. 28260 * 28261 * @param {Function} ready 28262 * A callback to be called when the player is ready. 28263 */ 28264 ; 28265 28266 _proto.loadMedia = function loadMedia(media, ready) { 28267 var _this15 = this; 28268 28269 if (!media || typeof media !== 'object') { 28270 return; 28271 } 28272 28273 this.reset(); // Clone the media object so it cannot be mutated from outside. 28274 28275 this.cache_.media = mergeOptions(media); 28276 var _this$cache_$media = this.cache_.media, 28277 artwork = _this$cache_$media.artwork, 28278 poster = _this$cache_$media.poster, 28279 src = _this$cache_$media.src, 28280 textTracks = _this$cache_$media.textTracks; // If `artwork` is not given, create it using `poster`. 28281 28282 if (!artwork && poster) { 28283 this.cache_.media.artwork = [{ 28284 src: poster, 28285 type: getMimetype(poster) 28286 }]; 28287 } 28288 28289 if (src) { 28290 this.src(src); 28291 } 28292 28293 if (poster) { 28294 this.poster(poster); 28295 } 28296 28297 if (Array.isArray(textTracks)) { 28298 textTracks.forEach(function (tt) { 28299 return _this15.addRemoteTextTrack(tt, false); 28300 }); 28301 } 28302 28303 this.ready(ready); 28304 } 28305 /** 28306 * Get a clone of the current {@link Player~MediaObject} for this player. 28307 * 28308 * If the `loadMedia` method has not been used, will attempt to return a 28309 * {@link Player~MediaObject} based on the current state of the player. 28310 * 28311 * @return {Player~MediaObject} 28312 */ 28313 ; 28314 28315 _proto.getMedia = function getMedia() { 28316 if (!this.cache_.media) { 28317 var poster = this.poster(); 28318 var src = this.currentSources(); 28319 var textTracks = Array.prototype.map.call(this.remoteTextTracks(), function (tt) { 28320 return { 28321 kind: tt.kind, 28322 label: tt.label, 28323 language: tt.language, 28324 src: tt.src 28325 }; 28326 }); 28327 var media = { 28328 src: src, 28329 textTracks: textTracks 28330 }; 28331 28332 if (poster) { 28333 media.poster = poster; 28334 media.artwork = [{ 28335 src: media.poster, 28336 type: getMimetype(media.poster) 28337 }]; 28338 } 28339 28340 return media; 28341 } 28342 28343 return mergeOptions(this.cache_.media); 28344 } 28345 /** 28346 * Gets tag settings 28347 * 28348 * @param {Element} tag 28349 * The player tag 28350 * 28351 * @return {Object} 28352 * An object containing all of the settings 28353 * for a player tag 28354 */ 28355 ; 28356 28357 Player.getTagSettings = function getTagSettings(tag) { 28358 var baseOptions = { 28359 sources: [], 28360 tracks: [] 28361 }; 28362 var tagOptions = getAttributes(tag); 28363 var dataSetup = tagOptions['data-setup']; 28364 28365 if (hasClass(tag, 'vjs-fill')) { 28366 tagOptions.fill = true; 28367 } 28368 28369 if (hasClass(tag, 'vjs-fluid')) { 28370 tagOptions.fluid = true; 28371 } // Check if data-setup attr exists. 28372 28373 if (dataSetup !== null) { 28374 // Parse options JSON 28375 // If empty string, make it a parsable json object. 28376 var _safeParseTuple = tuple(dataSetup || '{}'), 28377 err = _safeParseTuple[0], 28378 data = _safeParseTuple[1];
vendor: 7,667 bytes, lines 28379-28608
28379 28380 if (err) { 28381 log.error(err); 28382 } 28383 28384 assign(tagOptions, data); 28385 } 28386 28387 assign(baseOptions, tagOptions); // Get tag children settings 28388 28389 if (tag.hasChildNodes()) { 28390 var children = tag.childNodes; 28391 28392 for (var i = 0, j = children.length; i < j; i++) { 28393 var child = children[i]; // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/ 28394 28395 var childName = child.nodeName.toLowerCase(); 28396 28397 if (childName === 'source') { 28398 baseOptions.sources.push(getAttributes(child)); 28399 } else if (childName === 'track') { 28400 baseOptions.tracks.push(getAttributes(child)); 28401 } 28402 } 28403 } 28404 28405 return baseOptions; 28406 } 28407 /** 28408 * Determine whether or not flexbox is supported 28409 * 28410 * @return {boolean} 28411 * - true if flexbox is supported 28412 * - false if flexbox is not supported 28413 */ 28414 ; 28415 28416 _proto.flexNotSupported_ = function flexNotSupported_() { 28417 var elem = document.createElement('i'); // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more 28418 // common flex features that we can rely on when checking for flex support. 28419 28420 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 28421 'msFlexOrder' in elem.style); 28422 }; 28423 28424 return Player; 28425 }(Component); 28426 /** 28427 * Get the {@link VideoTrackList} 28428 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist 28429 * 28430 * @return {VideoTrackList} 28431 * the current video track list 28432 * 28433 * @method Player.prototype.videoTracks 28434 */ 28435 28436 /** 28437 * Get the {@link AudioTrackList} 28438 * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist 28439 * 28440 * @return {AudioTrackList} 28441 * the current audio track list 28442 * 28443 * @method Player.prototype.audioTracks 28444 */ 28445 28446 /** 28447 * Get the {@link TextTrackList} 28448 * 28449 * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks 28450 * 28451 * @return {TextTrackList} 28452 * the current text track list 28453 * 28454 * @method Player.prototype.textTracks 28455 */ 28456 28457 /** 28458 * Get the remote {@link TextTrackList} 28459 * 28460 * @return {TextTrackList} 28461 * The current remote text track list 28462 * 28463 * @method Player.prototype.remoteTextTracks 28464 */ 28465 28466 /** 28467 * Get the remote {@link HtmlTrackElementList} tracks. 28468 * 28469 * @return {HtmlTrackElementList} 28470 * The current remote text track element list 28471 * 28472 * @method Player.prototype.remoteTextTrackEls 28473 */ 28474 28475 28476 ALL.names.forEach(function (name) { 28477 var props = ALL[name]; 28478 28479 Player.prototype[props.getterName] = function () { 28480 if (this.tech_) { 28481 return this.tech_[props.getterName](); 28482 } // if we have not yet loadTech_, we create {video,audio,text}Tracks_ 28483 // these will be passed to the tech during loading 28484 28485 28486 this[props.privateName] = this[props.privateName] || new props.ListClass(); 28487 return this[props.privateName]; 28488 }; 28489 }); 28490 /** 28491 * Global enumeration of players. 28492 * 28493 * The keys are the player IDs and the values are either the {@link Player} 28494 * instance or `null` for disposed players. 28495 * 28496 * @type {Object} 28497 */ 28498 28499 Player.players = {}; 28500 var navigator = window$1.navigator; 28501 /* 28502 * Player instance options, surfaced using options 28503 * options = Player.prototype.options_ 28504 * Make changes in options, not here. 28505 * 28506 * @type {Object} 28507 * @private 28508 */ 28509 28510 Player.prototype.options_ = { 28511 // Default order of fallback technology 28512 techOrder: Tech.defaultTechOrder_, 28513 html5: {}, 28514 flash: {}, 28515 // default inactivity timeout 28516 inactivityTimeout: 2000, 28517 // default playback rates 28518 playbackRates: [], 28519 // Add playback rate selection by adding rates 28520 // 'playbackRates': [0.5, 1, 1.5, 2], 28521 liveui: false, 28522 // Included control sets 28523 children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'liveTracker', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'], 28524 language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en', 28525 // locales and their language translations 28526 languages: {}, 28527 // Default message to show when a video cannot be played. 28528 notSupportedMessage: 'No compatible source was found for this media.', 28529 fullscreen: { 28530 options: { 28531 navigationUI: 'hide' 28532 } 28533 }, 28534 breakpoints: {}, 28535 responsive: false 28536 }; 28537 [ 28538 /** 28539 * Returns whether or not the player is in the "ended" state. 28540 * 28541 * @return {Boolean} True if the player is in the ended state, false if not. 28542 * @method Player#ended 28543 */ 28544 'ended', 28545 /** 28546 * Returns whether or not the player is in the "seeking" state. 28547 * 28548 * @return {Boolean} True if the player is in the seeking state, false if not. 28549 * @method Player#seeking 28550 */ 28551 'seeking', 28552 /** 28553 * Returns the TimeRanges of the media that are currently available 28554 * for seeking to. 28555 * 28556 * @return {TimeRanges} the seekable intervals of the media timeline 28557 * @method Player#seekable 28558 */ 28559 'seekable', 28560 /** 28561 * Returns the current state of network activity for the element, from 28562 * the codes in the list below. 28563 * - NETWORK_EMPTY (numeric value 0) 28564 * The element has not yet been initialised. All attributes are in 28565 * their initial states. 28566 * - NETWORK_IDLE (numeric value 1) 28567 * The element's resource selection algorithm is active and has 28568 * selected a resource, but it is not actually using the network at 28569 * this time. 28570 * - NETWORK_LOADING (numeric value 2) 28571 * The user agent is actively trying to download data. 28572 * - NETWORK_NO_SOURCE (numeric value 3) 28573 * The element's resource selection algorithm is active, but it has 28574 * not yet found a resource to use. 28575 * 28576 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states 28577 * @return {number} the current network activity state 28578 * @method Player#networkState 28579 */ 28580 'networkState', 28581 /** 28582 * Returns a value that expresses the current state of the element 28583 * with respect to rendering the current playback position, from the 28584 * codes in the list below. 28585 * - HAVE_NOTHING (numeric value 0) 28586 * No information regarding the media resource is available. 28587 * - HAVE_METADATA (numeric value 1) 28588 * Enough of the resource has been obtained that the duration of the 28589 * resource is available. 28590 * - HAVE_CURRENT_DATA (numeric value 2) 28591 * Data for the immediate current playback position is available. 28592 * - HAVE_FUTURE_DATA (numeric value 3) 28593 * Data for the immediate current playback position is available, as 28594 * well as enough data for the user agent to advance the current 28595 * playback position in the direction of playback. 28596 * - HAVE_ENOUGH_DATA (numeric value 4) 28597 * The user agent estimates that enough data is available for 28598 * playback to proceed uninterrupted. 28599 * 28600 * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate 28601 * @return {number} the current playback rendering state 28602 * @method Player#readyState 28603 */ 28604 'readyState'].forEach(function (fn) { 28605 Player.prototype[fn] = function () { 28606 return this.techGet_(fn); 28607 }; 28608 });
vendor: 5,424 bytes, lines 28609-28806
28609 TECH_EVENTS_RETRIGGER.forEach(function (event) { 28610 Player.prototype["handleTech" + toTitleCase(event) + "_"] = function () { 28611 return this.trigger(event); 28612 }; 28613 }); 28614 /** 28615 * Fired when the player has initial duration and dimension information 28616 * 28617 * @event Player#loadedmetadata 28618 * @type {EventTarget~Event} 28619 */ 28620 28621 /** 28622 * Fired when the player has downloaded data at the current playback position 28623 * 28624 * @event Player#loadeddata 28625 * @type {EventTarget~Event} 28626 */ 28627 28628 /** 28629 * Fired when the current playback position has changed * 28630 * During playback this is fired every 15-250 milliseconds, depending on the 28631 * playback technology in use. 28632 * 28633 * @event Player#timeupdate 28634 * @type {EventTarget~Event} 28635 */ 28636 28637 /** 28638 * Fired when the volume changes 28639 * 28640 * @event Player#volumechange 28641 * @type {EventTarget~Event} 28642 */ 28643 28644 /** 28645 * Reports whether or not a player has a plugin available. 28646 * 28647 * This does not report whether or not the plugin has ever been initialized 28648 * on this player. For that, [usingPlugin]{@link Player#usingPlugin}. 28649 * 28650 * @method Player#hasPlugin 28651 * @param {string} name 28652 * The name of a plugin. 28653 * 28654 * @return {boolean} 28655 * Whether or not this player has the requested plugin available. 28656 */ 28657 28658 /** 28659 * Reports whether or not a player is using a plugin by name. 28660 * 28661 * For basic plugins, this only reports whether the plugin has _ever_ been 28662 * initialized on this player. 28663 * 28664 * @method Player#usingPlugin 28665 * @param {string} name 28666 * The name of a plugin. 28667 * 28668 * @return {boolean} 28669 * Whether or not this player is using the requested plugin. 28670 */ 28671 28672 Component.registerComponent('Player', Player); 28673 28674 /** 28675 * The base plugin name. 28676 * 28677 * @private 28678 * @constant 28679 * @type {string} 28680 */ 28681 28682 var BASE_PLUGIN_NAME = 'plugin'; 28683 /** 28684 * The key on which a player's active plugins cache is stored. 28685 * 28686 * @private 28687 * @constant 28688 * @type {string} 28689 */ 28690 28691 var PLUGIN_CACHE_KEY = 'activePlugins_'; 28692 /** 28693 * Stores registered plugins in a private space. 28694 * 28695 * @private 28696 * @type {Object} 28697 */ 28698 28699 var pluginStorage = {}; 28700 /** 28701 * Reports whether or not a plugin has been registered. 28702 * 28703 * @private 28704 * @param {string} name 28705 * The name of a plugin. 28706 * 28707 * @return {boolean} 28708 * Whether or not the plugin has been registered. 28709 */ 28710 28711 var pluginExists = function pluginExists(name) { 28712 return pluginStorage.hasOwnProperty(name); 28713 }; 28714 /** 28715 * Get a single registered plugin by name. 28716 * 28717 * @private 28718 * @param {string} name 28719 * The name of a plugin. 28720 * 28721 * @return {Function|undefined} 28722 * The plugin (or undefined). 28723 */ 28724 28725 28726 var getPlugin = function getPlugin(name) { 28727 return pluginExists(name) ? pluginStorage[name] : undefined; 28728 }; 28729 /** 28730 * Marks a plugin as "active" on a player. 28731 * 28732 * Also, ensures that the player has an object for tracking active plugins. 28733 * 28734 * @private 28735 * @param {Player} player 28736 * A Video.js player instance. 28737 * 28738 * @param {string} name 28739 * The name of a plugin. 28740 */ 28741 28742 28743 var markPluginAsActive = function markPluginAsActive(player, name) { 28744 player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}; 28745 player[PLUGIN_CACHE_KEY][name] = true; 28746 }; 28747 /** 28748 * Triggers a pair of plugin setup events. 28749 * 28750 * @private 28751 * @param {Player} player 28752 * A Video.js player instance. 28753 * 28754 * @param {Plugin~PluginEventHash} hash 28755 * A plugin event hash. 28756 * 28757 * @param {boolean} [before] 28758 * If true, prefixes the event name with "before". In other words, 28759 * use this to trigger "beforepluginsetup" instead of "pluginsetup". 28760 */ 28761 28762 28763 var triggerSetupEvent = function triggerSetupEvent(player, hash, before) { 28764 var eventName = (before ? 'before' : '') + 'pluginsetup'; 28765 player.trigger(eventName, hash); 28766 player.trigger(eventName + ':' + hash.name, hash); 28767 }; 28768 /** 28769 * Takes a basic plugin function and returns a wrapper function which marks 28770 * on the player that the plugin has been activated. 28771 * 28772 * @private 28773 * @param {string} name 28774 * The name of the plugin. 28775 * 28776 * @param {Function} plugin 28777 * The basic plugin. 28778 * 28779 * @return {Function} 28780 * A wrapper function for the given plugin. 28781 */ 28782 28783 28784 var createBasicPlugin = function createBasicPlugin(name, plugin) { 28785 var basicPluginWrapper = function basicPluginWrapper() { 28786 // We trigger the "beforepluginsetup" and "pluginsetup" events on the player 28787 // regardless, but we want the hash to be consistent with the hash provided 28788 // for advanced plugins. 28789 // 28790 // The only potentially counter-intuitive thing here is the `instance` in 28791 // the "pluginsetup" event is the value returned by the `plugin` function. 28792 triggerSetupEvent(this, { 28793 name: name, 28794 plugin: plugin, 28795 instance: null 28796 }, true); 28797 var instance = plugin.apply(this, arguments); 28798 markPluginAsActive(this, name); 28799 triggerSetupEvent(this, { 28800 name: name, 28801 plugin: plugin, 28802 instance: instance 28803 }); 28804 return instance; 28805 }; 28806
vendor: 9,188 bytes, lines 28807-29088
28807 Object.keys(plugin).forEach(function (prop) { 28808 basicPluginWrapper[prop] = plugin[prop]; 28809 }); 28810 return basicPluginWrapper; 28811 }; 28812 /** 28813 * Takes a plugin sub-class and returns a factory function for generating 28814 * instances of it. 28815 * 28816 * This factory function will replace itself with an instance of the requested 28817 * sub-class of Plugin. 28818 * 28819 * @private 28820 * @param {string} name 28821 * The name of the plugin. 28822 * 28823 * @param {Plugin} PluginSubClass 28824 * The advanced plugin. 28825 * 28826 * @return {Function} 28827 */ 28828 28829 28830 var createPluginFactory = function createPluginFactory(name, PluginSubClass) { 28831 // Add a `name` property to the plugin prototype so that each plugin can 28832 // refer to itself by name. 28833 PluginSubClass.prototype.name = name; 28834 return function () { 28835 triggerSetupEvent(this, { 28836 name: name, 28837 plugin: PluginSubClass, 28838 instance: null 28839 }, true); 28840 28841 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 28842 args[_key] = arguments[_key]; 28843 } 28844 28845 var instance = _construct(PluginSubClass, [this].concat(args)); // The plugin is replaced by a function that returns the current instance. 28846 28847 28848 this[name] = function () { 28849 return instance; 28850 }; 28851 28852 triggerSetupEvent(this, instance.getEventHash()); 28853 return instance; 28854 }; 28855 }; 28856 /** 28857 * Parent class for all advanced plugins. 28858 * 28859 * @mixes module:evented~EventedMixin 28860 * @mixes module:stateful~StatefulMixin 28861 * @fires Player#beforepluginsetup 28862 * @fires Player#beforepluginsetup:$name 28863 * @fires Player#pluginsetup 28864 * @fires Player#pluginsetup:$name 28865 * @listens Player#dispose 28866 * @throws {Error} 28867 * If attempting to instantiate the base {@link Plugin} class 28868 * directly instead of via a sub-class. 28869 */ 28870 28871 28872 var Plugin = 28873 /*#__PURE__*/ 28874 function () { 28875 /** 28876 * Creates an instance of this class. 28877 * 28878 * Sub-classes should call `super` to ensure plugins are properly initialized. 28879 * 28880 * @param {Player} player 28881 * A Video.js player instance. 28882 */ 28883 function Plugin(player) { 28884 if (this.constructor === Plugin) { 28885 throw new Error('Plugin must be sub-classed; not directly instantiated.'); 28886 } 28887 28888 this.player = player; // Make this object evented, but remove the added `trigger` method so we 28889 // use the prototype version instead. 28890 28891 evented(this); 28892 delete this.trigger; 28893 stateful(this, this.constructor.defaultState); 28894 markPluginAsActive(player, this.name); // Auto-bind the dispose method so we can use it as a listener and unbind 28895 // it later easily. 28896 28897 this.dispose = bind(this, this.dispose); // If the player is disposed, dispose the plugin. 28898 28899 player.on('dispose', this.dispose); 28900 } 28901 /** 28902 * Get the version of the plugin that was set on <pluginName>.VERSION 28903 */ 28904 28905 28906 var _proto = Plugin.prototype; 28907 28908 _proto.version = function version() { 28909 return this.constructor.VERSION; 28910 } 28911 /** 28912 * Each event triggered by plugins includes a hash of additional data with 28913 * conventional properties. 28914 * 28915 * This returns that object or mutates an existing hash. 28916 * 28917 * @param {Object} [hash={}] 28918 * An object to be used as event an event hash. 28919 * 28920 * @return {Plugin~PluginEventHash} 28921 * An event hash object with provided properties mixed-in. 28922 */ 28923 ; 28924 28925 _proto.getEventHash = function getEventHash(hash) { 28926 if (hash === void 0) { 28927 hash = {}; 28928 } 28929 28930 hash.name = this.name; 28931 hash.plugin = this.constructor; 28932 hash.instance = this; 28933 return hash; 28934 } 28935 /** 28936 * Triggers an event on the plugin object and overrides 28937 * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}. 28938 * 28939 * @param {string|Object} event 28940 * An event type or an object with a type property. 28941 * 28942 * @param {Object} [hash={}] 28943 * Additional data hash to merge with a 28944 * {@link Plugin~PluginEventHash|PluginEventHash}. 28945 * 28946 * @return {boolean} 28947 * Whether or not default was prevented. 28948 */ 28949 ; 28950 28951 _proto.trigger = function trigger$1(event, hash) { 28952 if (hash === void 0) { 28953 hash = {}; 28954 } 28955 28956 return trigger(this.eventBusEl_, event, this.getEventHash(hash)); 28957 } 28958 /** 28959 * Handles "statechanged" events on the plugin. No-op by default, override by 28960 * subclassing. 28961 * 28962 * @abstract 28963 * @param {Event} e 28964 * An event object provided by a "statechanged" event. 28965 * 28966 * @param {Object} e.changes 28967 * An object describing changes that occurred with the "statechanged" 28968 * event. 28969 */ 28970 ; 28971 28972 _proto.handleStateChanged = function handleStateChanged(e) {} 28973 /** 28974 * Disposes a plugin. 28975 * 28976 * Subclasses can override this if they want, but for the sake of safety, 28977 * it's probably best to subscribe the "dispose" event. 28978 * 28979 * @fires Plugin#dispose 28980 */ 28981 ; 28982 28983 _proto.dispose = function dispose() { 28984 var name = this.name, 28985 player = this.player; 28986 /** 28987 * Signals that a advanced plugin is about to be disposed. 28988 * 28989 * @event Plugin#dispose 28990 * @type {EventTarget~Event} 28991 */ 28992 28993 this.trigger('dispose'); 28994 this.off(); 28995 player.off('dispose', this.dispose); // Eliminate any possible sources of leaking memory by clearing up 28996 // references between the player and the plugin instance and nulling out 28997 // the plugin's state and replacing methods with a function that throws. 28998 28999 player[PLUGIN_CACHE_KEY][name] = false; 29000 this.player = this.state = null; // Finally, replace the plugin name on the player with a new factory 29001 // function, so that the plugin is ready to be set up again. 29002 29003 player[name] = createPluginFactory(name, pluginStorage[name]); 29004 } 29005 /** 29006 * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`). 29007 * 29008 * @param {string|Function} plugin 29009 * If a string, matches the name of a plugin. If a function, will be 29010 * tested directly. 29011 * 29012 * @return {boolean} 29013 * Whether or not a plugin is a basic plugin. 29014 */ 29015 ; 29016 29017 Plugin.isBasic = function isBasic(plugin) { 29018 var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin; 29019 return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype); 29020 } 29021 /** 29022 * Register a Video.js plugin. 29023 * 29024 * @param {string} name 29025 * The name of the plugin to be registered. Must be a string and 29026 * must not match an existing plugin or a method on the `Player` 29027 * prototype. 29028 * 29029 * @param {Function} plugin 29030 * A sub-class of `Plugin` or a function for basic plugins. 29031 * 29032 * @return {Function} 29033 * For advanced plugins, a factory function for that plugin. For 29034 * basic plugins, a wrapper function that initializes the plugin. 29035 */ 29036 ; 29037 29038 Plugin.registerPlugin = function registerPlugin(name, plugin) { 29039 if (typeof name !== 'string') { 29040 throw new Error("Illegal plugin name, \"" + name + "\", must be a string, was " + typeof name + "."); 29041 } 29042 29043 if (pluginExists(name)) { 29044 log.warn("A plugin named \"" + name + "\" already exists. You may want to avoid re-registering plugins!"); 29045 } else if (Player.prototype.hasOwnProperty(name)) { 29046 throw new Error("Illegal plugin name, \"" + name + "\", cannot share a name with an existing player method!"); 29047 } 29048 29049 if (typeof plugin !== 'function') { 29050 throw new Error("Illegal plugin for \"" + name + "\", must be a function, was " + typeof plugin + "."); 29051 } 29052 29053 pluginStorage[name] = plugin; // Add a player prototype method for all sub-classed plugins (but not for 29054 // the base Plugin class). 29055 29056 if (name !== BASE_PLUGIN_NAME) { 29057 if (Plugin.isBasic(plugin)) { 29058 Player.prototype[name] = createBasicPlugin(name, plugin); 29059 } else { 29060 Player.prototype[name] = createPluginFactory(name, plugin); 29061 } 29062 } 29063 29064 return plugin; 29065 } 29066 /** 29067 * De-register a Video.js plugin. 29068 * 29069 * @param {string} name 29070 * The name of the plugin to be de-registered. Must be a string that 29071 * matches an existing plugin. 29072 * 29073 * @throws {Error} 29074 * If an attempt is made to de-register the base plugin. 29075 */ 29076 ; 29077 29078 Plugin.deregisterPlugin = function deregisterPlugin(name) { 29079 if (name === BASE_PLUGIN_NAME) { 29080 throw new Error('Cannot de-register base plugin.'); 29081 } 29082 29083 if (pluginExists(name)) { 29084 delete pluginStorage[name]; 29085 delete Player.prototype[name]; 29086 } 29087 } 29088 /**
29089 * Gets an object containing multiple Video.js plugins. 29090 * 29091 * @param {Array} [names] 29092 * If provided, should be an array of plugin names. Defaults to _all_ 29093 * plugin names. 29094 * 29095 * @return {Object|undefined} 29096 * An object containing plugin(s) associated with their name(s) or 29097 * `undefined` if no matching plugins exist). 29098 */ 29099 ; 29100 29101 Plugin.getPlugins = function getPlugins(names) { 29102 if (names === void 0) { 29103 names = Object.keys(pluginStorage); 29104 } 29105 29106 var result; 29107 names.forEach(function (name) { 29108 var plugin = getPlugin(name); 29109 29110 if (plugin) { 29111 result = result || {}; 29112 result[name] = plugin; 29113 } 29114 }); 29115 return result; 29116 } 29117 /** 29118 * Gets a plugin's version, if available 29119 * 29120 * @param {string} name 29121 * The name of a plugin. 29122 * 29123 * @return {string} 29124 * The plugin's version or an empty string. 29125 */ 29126 ; 29127 29128 Plugin.getPluginVersion = function getPluginVersion(name) { 29129 var plugin = getPlugin(name); 29130 return plugin && plugin.VERSION || ''; 29131 }; 29132 29133 return Plugin; 29134 }(); 29135 /** 29136 * Gets a plugin by name if it exists. 29137 * 29138 * @static 29139 * @method getPlugin 29140 * @memberOf Plugin 29141 * @param {string} name 29142 * The name of a plugin. 29143 * 29144 * @returns {Function|undefined} 29145 * The plugin (or `undefined`). 29146 */ 29147 29148 29149 Plugin.getPlugin = getPlugin; 29150 /** 29151 * The name of the base plugin class as it is registered. 29152 * 29153 * @type {string} 29154 */ 29155 29156 Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME; 29157 Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin); 29158 /** 29159 * Documented in player.js 29160 * 29161 * @ignore 29162 */ 29163 29164 Player.prototype.usingPlugin = function (name) { 29165 return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true; 29166 }; 29167 /** 29168 * Documented in player.js 29169 * 29170 * @ignore 29171 */ 29172 29173 29174 Player.prototype.hasPlugin = function (name) { 29175 return !!pluginExists(name); 29176 }; 29177 /** 29178 * Signals that a plugin is about to be set up on a player. 29179 * 29180 * @event Player#beforepluginsetup 29181 * @type {Plugin~PluginEventHash} 29182 */ 29183 29184 /** 29185 * Signals that a plugin is about to be set up on a player - by name. The name 29186 * is the name of the plugin. 29187 * 29188 * @event Player#beforepluginsetup:$name 29189 * @type {Plugin~PluginEventHash} 29190 */ 29191 29192 /** 29193 * Signals that a plugin has just been set up on a player. 29194 * 29195 * @event Player#pluginsetup 29196 * @type {Plugin~PluginEventHash} 29197 */ 29198 29199 /** 29200 * Signals that a plugin has just been set up on a player - by name. The name 29201 * is the name of the plugin. 29202 * 29203 * @event Player#pluginsetup:$name 29204 * @type {Plugin~PluginEventHash} 29205 */ 29206 29207 /** 29208 * @typedef {Object} Plugin~PluginEventHash 29209 * 29210 * @property {string} instance 29211 * For basic plugins, the return value of the plugin function. For 29212 * advanced plugins, the plugin instance on which the event is fired. 29213 * 29214 * @property {string} name 29215 * The name of the plugin. 29216 * 29217 * @property {string} plugin 29218 * For basic plugins, the plugin function. For advanced plugins, the 29219 * plugin class/constructor. 29220 */ 29221 29222 /** 29223 * @file extend.js 29224 * @module extend 29225 */ 29226 29227 /** 29228 * A combination of node inherits and babel's inherits (after transpile). 29229 * Both work the same but node adds `super_` to the subClass 29230 * and Bable adds the superClass as __proto__. Both seem useful. 29231 * 29232 * @param {Object} subClass 29233 * The class to inherit to 29234 * 29235 * @param {Object} superClass 29236 * The class to inherit from 29237 * 29238 * @private 29239 */ 29240 var _inherits = function _inherits(subClass, superClass) { 29241 if (typeof superClass !== 'function' && superClass !== null) { 29242 throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); 29243 } 29244 29245 subClass.prototype = Object.create(superClass && superClass.prototype, { 29246 constructor: { 29247 value: subClass, 29248 enumerable: false, 29249 writable: true, 29250 configurable: true 29251 } 29252 });
vendor: 4,752 bytes, lines 29253-29398
29253 29254 if (superClass) { 29255 // node 29256 subClass.super_ = superClass; 29257 } 29258 }; 29259 /** 29260 * Used to subclass an existing class by emulating ES subclassing using the 29261 * `extends` keyword. 29262 * 29263 * @function 29264 * @example 29265 * var MyComponent = videojs.extend(videojs.getComponent('Component'), { 29266 * myCustomMethod: function() { 29267 * // Do things in my method. 29268 * } 29269 * }); 29270 * 29271 * @param {Function} superClass 29272 * The class to inherit from 29273 * 29274 * @param {Object} [subClassMethods={}] 29275 * Methods of the new class 29276 * 29277 * @return {Function} 29278 * The new class with subClassMethods that inherited superClass. 29279 */ 29280 29281 29282 var extend$1 = function extend(superClass, subClassMethods) { 29283 if (subClassMethods === void 0) { 29284 subClassMethods = {}; 29285 } 29286 29287 var subClass = function subClass() { 29288 superClass.apply(this, arguments); 29289 }; 29290 29291 var methods = {}; 29292 29293 if (typeof subClassMethods === 'object') { 29294 if (subClassMethods.constructor !== Object.prototype.constructor) { 29295 subClass = subClassMethods.constructor; 29296 } 29297 29298 methods = subClassMethods; 29299 } else if (typeof subClassMethods === 'function') { 29300 subClass = subClassMethods; 29301 } 29302 29303 _inherits(subClass, superClass); // Extend subObj's prototype with functions and other properties from props 29304 29305 29306 for (var name in methods) { 29307 if (methods.hasOwnProperty(name)) { 29308 subClass.prototype[name] = methods[name]; 29309 } 29310 } 29311 29312 return subClass; 29313 }; 29314 29315 /** 29316 * @file video.js 29317 * @module videojs 29318 */ 29319 /** 29320 * Normalize an `id` value by trimming off a leading `#` 29321 * 29322 * @private 29323 * @param {string} id 29324 * A string, maybe with a leading `#`. 29325 * 29326 * @return {string} 29327 * The string, without any leading `#`. 29328 */ 29329 29330 var normalizeId = function normalizeId(id) { 29331 return id.indexOf('#') === 0 ? id.slice(1) : id; 29332 }; 29333 /** 29334 * The `videojs()` function doubles as the main function for users to create a 29335 * {@link Player} instance as well as the main library namespace. 29336 * 29337 * It can also be used as a getter for a pre-existing {@link Player} instance. 29338 * However, we _strongly_ recommend using `videojs.getPlayer()` for this 29339 * purpose because it avoids any potential for unintended initialization. 29340 * 29341 * Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149) 29342 * of our JSDoc template, we cannot properly document this as both a function 29343 * and a namespace, so its function signature is documented here. 29344 * 29345 * #### Arguments 29346 * ##### id 29347 * string|Element, **required** 29348 * 29349 * Video element or video element ID. 29350 * 29351 * ##### options 29352 * Object, optional 29353 * 29354 * Options object for providing settings. 29355 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 29356 * 29357 * ##### ready 29358 * {@link Component~ReadyCallback}, optional 29359 * 29360 * A function to be called when the {@link Player} and {@link Tech} are ready. 29361 * 29362 * #### Return Value 29363 * 29364 * The `videojs()` function returns a {@link Player} instance. 29365 * 29366 * @namespace 29367 * 29368 * @borrows AudioTrack as AudioTrack 29369 * @borrows Component.getComponent as getComponent 29370 * @borrows module:computed-style~computedStyle as computedStyle 29371 * @borrows module:events.on as on 29372 * @borrows module:events.one as one 29373 * @borrows module:events.off as off 29374 * @borrows module:events.trigger as trigger 29375 * @borrows EventTarget as EventTarget 29376 * @borrows module:extend~extend as extend 29377 * @borrows module:fn.bind as bind 29378 * @borrows module:format-time.formatTime as formatTime 29379 * @borrows module:format-time.resetFormatTime as resetFormatTime 29380 * @borrows module:format-time.setFormatTime as setFormatTime 29381 * @borrows module:merge-options.mergeOptions as mergeOptions 29382 * @borrows module:middleware.use as use 29383 * @borrows Player.players as players 29384 * @borrows Plugin.registerPlugin as registerPlugin 29385 * @borrows Plugin.deregisterPlugin as deregisterPlugin 29386 * @borrows Plugin.getPlugins as getPlugins 29387 * @borrows Plugin.getPlugin as getPlugin 29388 * @borrows Plugin.getPluginVersion as getPluginVersion 29389 * @borrows Tech.getTech as getTech 29390 * @borrows Tech.registerTech as registerTech 29391 * @borrows TextTrack as TextTrack 29392 * @borrows module:time-ranges.createTimeRanges as createTimeRange 29393 * @borrows module:time-ranges.createTimeRanges as createTimeRanges 29394 * @borrows module:url.isCrossOrigin as isCrossOrigin 29395 * @borrows module:url.parseUrl as parseUrl 29396 * @borrows VideoTrack as VideoTrack 29397 * 29398 * @param {string|Element}
29398 id 29399 * Video element or video element ID. 29400 * 29401 * @param {Object} [options] 29402 * Options object for providing settings. 29403 * See: [Options Guide](https://docs.videojs.com/tutorial-options.html). 29404 * 29405 * @param {Component~ReadyCallback} [ready] 29406 * A function to be called when the {@link Player} and {@link Tech} are 29407 * ready. 29408 * 29409 * @return {Player} 29410 * The `videojs()` function returns a {@link Player|Player} instance. 29411 */ 29412 29413 29414 function videojs$1(id, options, ready) { 29415 var player = videojs$1.getPlayer(id); 29416 29417 if (player) { 29418 if (options) { 29419 log.warn("Player \"" + id + "\" is already initialised. Options will not be applied."); 29420 } 29421 29422 if (ready) { 29423 player.ready(ready); 29424 } 29425 29426 return player; 29427 } 29428 29429 var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id; 29430 29431 if (!isEl(el)) { 29432 throw new TypeError('The element or ID supplied is not valid. (videojs)'); 29433 } // document.body.contains(el) will only check if el is contained within that one document. 29434 // This causes problems for elements in iframes. 29435 // Instead, use the element's ownerDocument instead of the global document. 29436 // This will make sure that the element is indeed in the dom of that document. 29437 // Additionally, check that the document in question has a default view. 29438 // If the document is no longer attached to the dom, the defaultView of the document will be null. 29439 29440 29441 if (!el.ownerDocument.defaultView || !el.ownerDocument.body.contains(el)) { 29442 log.warn('The element supplied is not included in the DOM'); 29443 } 29444 29445 options = options || {}; 29446 videojs$1.hooks('beforesetup').forEach(function (hookFunction) { 29447 var opts = hookFunction(el, mergeOptions(options)); 29448 29449 if (!isObject(opts) || Array.isArray(opts)) { 29450 log.error('please return an object in beforesetup hooks'); 29451 return; 29452 } 29453 29454 options = mergeOptions(options, opts); 29455 }); // We get the current "Player" component here in case an integration has 29456 // replaced it with a custom player. 29457 29458 var PlayerComponent = Component.getComponent('Player'); 29459 player = new PlayerComponent(el, options, ready); 29460 videojs$1.hooks('setup').forEach(function (hookFunction) { 29461 return hookFunction(player); 29462 }); 29463 return player; 29464 } 29465 /** 29466 * An Object that contains lifecycle hooks as keys which point to an array 29467 * of functions that are run when a lifecycle is triggered 29468 * 29469 * @private 29470 */ 29471 29472 29473 videojs$1.hooks_ = {}; 29474 /** 29475 * Get a list of hooks for a specific lifecycle 29476 * 29477 * @param {string} type 29478 * the lifecyle to get hooks from 29479 * 29480 * @param {Function|Function[]} [fn] 29481 * Optionally add a hook (or hooks) to the lifecycle that your are getting. 29482 * 29483 * @return {Array} 29484 * an array of hooks, or an empty array if there are none. 29485 */ 29486 29487 videojs$1.hooks = function (type, fn) { 29488 videojs$1.hooks_[type] = videojs$1.hooks_[type] || []; 29489 29490 if (fn) { 29491 videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn); 29492 } 29493 29494 return videojs$1.hooks_[type]; 29495 }; 29496 /** 29497 * Add a function hook to a specific videojs lifecycle. 29498 * 29499 * @param {string} type 29500 * the lifecycle to hook the function to. 29501 * 29502 * @param {Function|Function[]} 29503 * The function or array of functions to attach. 29504 */ 29505 29506 29507 videojs$1.hook = function (type, fn) { 29508 videojs$1.hooks(type, fn); 29509 }; 29510 /** 29511 * Add a function hook that will only run once to a specific videojs lifecycle. 29512 * 29513 * @param {string} type 29514 * the lifecycle to hook the function to. 29515 * 29516 * @param {Function|Function[]} 29517 * The function or array of functions to attach. 29518 */ 29519 29520 29521 videojs$1.hookOnce = function (type, fn) { 29522 videojs$1.hooks(type, [].concat(fn).map(function (original) { 29523 var wrapper = function wrapper() { 29524 videojs$1.removeHook(type, wrapper); 29525 return original.apply(void 0, arguments); 29526 }; 29527 29528 return wrapper; 29529 })); 29530 }; 29531 /** 29532 * Remove a hook from a specific videojs lifecycle. 29533 * 29534 * @param {string} type 29535 * the lifecycle that the function hooked to 29536 * 29537 * @param {Function} fn 29538 * The hooked function to remove 29539 * 29540 * @return {boolean} 29541 * The function that was removed or undef 29542 */ 29543 29544
vendor: 13,799 bytes, lines 29545-29947
29545 videojs$1.removeHook = function (type, fn) { 29546 var index = videojs$1.hooks(type).indexOf(fn); 29547 29548 if (index <= -1) { 29549 return false; 29550 } 29551 29552 videojs$1.hooks_[type] = videojs$1.hooks_[type].slice(); 29553 videojs$1.hooks_[type].splice(index, 1); 29554 return true; 29555 }; // Add default styles 29556 29557 29558 if (window$1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) { 29559 var style = $('.vjs-styles-defaults'); 29560 29561 if (!style) { 29562 style = createStyleElement('vjs-styles-defaults'); 29563 var head = $('head'); 29564 29565 if (head) { 29566 head.insertBefore(style, head.firstChild); 29567 } 29568 29569 setTextContent(style, "\n .video-js {\n width: 100%;\n height: 100%;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n "); 29570 } 29571 } // Run Auto-load players 29572 // You have to wait at least once in case this script is loaded after your 29573 // video in the DOM (weird behavior only with minified version) 29574 29575 29576 autoSetupTimeout(1, videojs$1); 29577 /** 29578 * Current Video.js version. Follows [semantic versioning](https://semver.org/). 29579 * 29580 * @type {string} 29581 */ 29582 29583 videojs$1.VERSION = version; 29584 /** 29585 * The global options object. These are the settings that take effect 29586 * if no overrides are specified when the player is created. 29587 * 29588 * @type {Object} 29589 */ 29590 29591 videojs$1.options = Player.prototype.options_; 29592 /** 29593 * Get an object with the currently created players, keyed by player ID 29594 * 29595 * @return {Object} 29596 * The created players 29597 */ 29598 29599 videojs$1.getPlayers = function () { 29600 return Player.players; 29601 }; 29602 /** 29603 * Get a single player based on an ID or DOM element. 29604 * 29605 * This is useful if you want to check if an element or ID has an associated 29606 * Video.js player, but not create one if it doesn't. 29607 * 29608 * @param {string|Element} id 29609 * An HTML element - `<video>`, `<audio>`, or `<video-js>` - 29610 * or a string matching the `id` of such an element. 29611 * 29612 * @return {Player|undefined} 29613 * A player instance or `undefined` if there is no player instance 29614 * matching the argument. 29615 */ 29616 29617 29618 videojs$1.getPlayer = function (id) { 29619 var players = Player.players; 29620 var tag; 29621 29622 if (typeof id === 'string') { 29623 var nId = normalizeId(id); 29624 var player = players[nId]; 29625 29626 if (player) { 29627 return player; 29628 } 29629 29630 tag = $('#' + nId); 29631 } else { 29632 tag = id; 29633 } 29634 29635 if (isEl(tag)) { 29636 var _tag = tag, 29637 _player = _tag.player, 29638 playerId = _tag.playerId; // Element may have a `player` property referring to an already created 29639 // player instance. If so, return that. 29640 29641 if (_player || players[playerId]) { 29642 return _player || players[playerId]; 29643 } 29644 } 29645 }; 29646 /** 29647 * Returns an array of all current players. 29648 * 29649 * @return {Array} 29650 * An array of all players. The array will be in the order that 29651 * `Object.keys` provides, which could potentially vary between 29652 * JavaScript engines. 29653 * 29654 */ 29655 29656 29657 videojs$1.getAllPlayers = function () { 29658 return (// Disposed players leave a key with a `null` value, so we need to make sure 29659 // we filter those out. 29660 Object.keys(Player.players).map(function (k) { 29661 return Player.players[k]; 29662 }).filter(Boolean) 29663 ); 29664 }; 29665 29666 videojs$1.players = Player.players; 29667 videojs$1.getComponent = Component.getComponent; 29668 /** 29669 * Register a component so it can referred to by name. Used when adding to other 29670 * components, either through addChild `component.addChild('myComponent')` or through 29671 * default children options `{ children: ['myComponent'] }`. 29672 * 29673 * > NOTE: You could also just initialize the component before adding. 29674 * `component.addChild(new MyComponent());` 29675 * 29676 * @param {string} name 29677 * The class name of the component 29678 * 29679 * @param {Component} comp 29680 * The component class 29681 * 29682 * @return {Component} 29683 * The newly registered component 29684 */ 29685 29686 videojs$1.registerComponent = function (name, comp) { 29687 if (Tech.isTech(comp)) { 29688 log.warn("The " + name + " tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)"); 29689 } 29690 29691 Component.registerComponent.call(Component, name, comp); 29692 }; 29693 29694 videojs$1.getTech = Tech.getTech; 29695 videojs$1.registerTech = Tech.registerTech; 29696 videojs$1.use = use; 29697 /** 29698 * An object that can be returned by a middleware to signify 29699 * that the middleware is being terminated. 29700 * 29701 * @type {object} 29702 * @property {object} middleware.TERMINATOR 29703 */ 29704 29705 Object.defineProperty(videojs$1, 'middleware', { 29706 value: {}, 29707 writeable: false, 29708 enumerable: true 29709 }); 29710 Object.defineProperty(videojs$1.middleware, 'TERMINATOR', { 29711 value: TERMINATOR, 29712 writeable: false, 29713 enumerable: true 29714 }); 29715 /** 29716 * A reference to the {@link module:browser|browser utility module} as an object. 29717 * 29718 * @type {Object} 29719 * @see {@link module:browser|browser} 29720 */ 29721 29722 videojs$1.browser = browser; 29723 /** 29724 * Use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED} instead; only 29725 * included for backward-compatibility with 4.x. 29726 * 29727 * @deprecated Since version 5.0, use {@link module:browser.TOUCH_ENABLED|browser.TOUCH_ENABLED instead. 29728 * @type {boolean} 29729 */ 29730 29731 videojs$1.TOUCH_ENABLED = TOUCH_ENABLED; 29732 videojs$1.extend = extend$1; 29733 videojs$1.mergeOptions = mergeOptions; 29734 videojs$1.bind = bind; 29735 videojs$1.registerPlugin = Plugin.registerPlugin; 29736 videojs$1.deregisterPlugin = Plugin.deregisterPlugin; 29737 /** 29738 * Deprecated method to register a plugin with Video.js 29739 * 29740 * @deprecated videojs.plugin() is deprecated; use videojs.registerPlugin() instead 29741 * 29742 * @param {string} name 29743 * The plugin name 29744 * 29745 * @param {Plugin|Function} plugin 29746 * The plugin sub-class or function 29747 */ 29748 29749 videojs$1.plugin = function (name, plugin) { 29750 log.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead'); 29751 return Plugin.registerPlugin(name, plugin); 29752 }; 29753 29754 videojs$1.getPlugins = Plugin.getPlugins; 29755 videojs$1.getPlugin = Plugin.getPlugin; 29756 videojs$1.getPluginVersion = Plugin.getPluginVersion; 29757 /** 29758 * Adding languages so that they're available to all players. 29759 * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });` 29760 * 29761 * @param {string} code 29762 * The language code or dictionary property 29763 * 29764 * @param {Object} data 29765 * The data values to be translated 29766 * 29767 * @return {Object} 29768 * The resulting language dictionary object 29769 */ 29770 29771 videojs$1.addLanguage = function (code, data) { 29772 var _mergeOptions; 29773 29774 code = ('' + code).toLowerCase(); 29775 videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions)); 29776 return videojs$1.options.languages[code]; 29777 }; 29778 /** 29779 * A reference to the {@link module:log|log utility module} as an object. 29780 * 29781 * @type {Function} 29782 * @see {@link module:log|log} 29783 */ 29784 29785 29786 videojs$1.log = log; 29787 videojs$1.createLogger = createLogger$1; 29788 videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges; 29789 videojs$1.formatTime = formatTime; 29790 videojs$1.setFormatTime = setFormatTime; 29791 videojs$1.resetFormatTime = resetFormatTime; 29792 videojs$1.parseUrl = parseUrl; 29793 videojs$1.isCrossOrigin = isCrossOrigin; 29794 videojs$1.EventTarget = EventTarget; 29795 videojs$1.on = on; 29796 videojs$1.one = one; 29797 videojs$1.off = off; 29798 videojs$1.trigger = trigger; 29799 /** 29800 * A cross-browser XMLHttpRequest wrapper. 29801 * 29802 * @function 29803 * @param {Object} options 29804 * Settings for the request. 29805 * 29806 * @return {XMLHttpRequest|XDomainRequest} 29807 * The request object. 29808 * 29809 * @see https://github.com/Raynos/xhr 29810 */ 29811 29812 videojs$1.xhr = xhr; 29813 videojs$1.TextTrack = TextTrack; 29814 videojs$1.AudioTrack = AudioTrack; 29815 videojs$1.VideoTrack = VideoTrack; 29816 ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) { 29817 videojs$1[k] = function () { 29818 log.warn("videojs." + k + "() is deprecated; use videojs.dom." + k + "() instead"); 29819 return Dom[k].apply(null, arguments); 29820 }; 29821 }); 29822 videojs$1.computedStyle = computedStyle; 29823 /** 29824 * A reference to the {@link module:dom|DOM utility module} as an object. 29825 * 29826 * @type {Object} 29827 * @see {@link module:dom|dom} 29828 */ 29829 29830 videojs$1.dom = Dom; 29831 /** 29832 * A reference to the {@link module:url|URL utility module} as an object. 29833 * 29834 * @type {Object} 29835 * @see {@link module:url|url} 29836 */ 29837 29838 videojs$1.url = Url; 29839 29840 var urlToolkit = createCommonjsModule(function (module, exports) { 29841 // see https://tools.ietf.org/html/rfc1808 29842 29843 /* jshint ignore:start */ 29844 (function (root) { 29845 /* jshint ignore:end */ 29846 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; 29847 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; 29848 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 29849 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; 29850 var URLToolkit = { 29851 // jshint ignore:line 29852 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 29853 // E.g 29854 // With opts.alwaysNormalize = false (default, spec compliant) 29855 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 29856 // With opts.alwaysNormalize = true (not spec compliant) 29857 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 29858 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 29859 opts = opts || {}; // remove any remaining space and CRLF 29860 29861 baseURL = baseURL.trim(); 29862 relativeURL = relativeURL.trim(); 29863 29864 if (!relativeURL) { 29865 // 2a) If the embedded URL is entirely empty, it inherits the 29866 // entire base URL (i.e., is set equal to the base URL) 29867 // and we are done. 29868 if (!opts.alwaysNormalize) { 29869 return baseURL; 29870 } 29871 29872 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 29873 29874 if (!basePartsForNormalise) { 29875 throw new Error('Error trying to parse base URL.'); 29876 } 29877 29878 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 29879 return URLToolkit.buildURLFromParts(basePartsForNormalise); 29880 } 29881 29882 var relativeParts = URLToolkit.parseURL(relativeURL); 29883 29884 if (!relativeParts) { 29885 throw new Error('Error trying to parse relative URL.'); 29886 } 29887 29888 if (relativeParts.scheme) { 29889 // 2b) If the embedded URL starts with a scheme name, it is 29890 // interpreted as an absolute URL and we are done. 29891 if (!opts.alwaysNormalize) { 29892 return relativeURL; 29893 } 29894 29895 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 29896 return URLToolkit.buildURLFromParts(relativeParts); 29897 } 29898 29899 var baseParts = URLToolkit.parseURL(baseURL); 29900 29901 if (!baseParts) { 29902 throw new Error('Error trying to parse base URL.'); 29903 } 29904 29905 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 29906 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 29907 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 29908 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 29909 baseParts.netLoc = pathParts[1]; 29910 baseParts.path = pathParts[2]; 29911 } 29912 29913 if (baseParts.netLoc && !baseParts.path) { 29914 baseParts.path = '/'; 29915 } 29916 29917 var builtParts = { 29918 // 2c) Otherwise, the embedded URL inherits the scheme of 29919 // the base URL. 29920 scheme: baseParts.scheme, 29921 netLoc: relativeParts.netLoc, 29922 path: null, 29923 params: relativeParts.params, 29924 query: relativeParts.query, 29925 fragment: relativeParts.fragment 29926 }; 29927 29928 if (!relativeParts.netLoc) { 29929 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 29930 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 29931 // (if any) of the base URL. 29932 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 29933 // path is not relative and we skip to Step 7. 29934 29935 if (relativeParts.path[0] !== '/') { 29936 if (!relativeParts.path) { 29937 // 5) If the embedded URL path is empty (and not preceded by a 29938 // slash), then the embedded URL inherits the base URL path 29939 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 29940 // step 7; otherwise, it inherits the <params> of the base 29941 // URL (if any) and 29942 29943 if (!relativeParts.params) { 29944 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 29945 // step 7; otherwise, it inherits the <query> of the base 29946 // URL (if any) and we skip to step 7. 29947
vendor: 45,450 bytes, lines 29948-31442
29948 if (!relativeParts.query) { 29949 builtParts.query = baseParts.query; 29950 } 29951 } 29952 } else { 29953 // 6) The last segment of the base URL's path (anything 29954 // following the rightmost slash "/", or the entire path if no 29955 // slash is present) is removed and the embedded URL's path is 29956 // appended in its place. 29957 var baseURLPath = baseParts.path; 29958 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 29959 builtParts.path = URLToolkit.normalizePath(newPath); 29960 } 29961 } 29962 } 29963 29964 if (builtParts.path === null) { 29965 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 29966 } 29967 29968 return URLToolkit.buildURLFromParts(builtParts); 29969 }, 29970 parseURL: function parseURL(url) { 29971 var parts = URL_REGEX.exec(url); 29972 29973 if (!parts) { 29974 return null; 29975 } 29976 29977 return { 29978 scheme: parts[1] || '', 29979 netLoc: parts[2] || '', 29980 path: parts[3] || '', 29981 params: parts[4] || '', 29982 query: parts[5] || '', 29983 fragment: parts[6] || '' 29984 }; 29985 }, 29986 normalizePath: function normalizePath(path) { 29987 // The following operations are 29988 // then applied, in order, to the new path: 29989 // 6a) All occurrences of "./", where "." is a complete path 29990 // segment, are removed. 29991 // 6b) If the path ends with "." as a complete path segment, 29992 // that "." is removed. 29993 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 29994 // complete path segment not equal to "..", are removed. 29995 // Removal of these path segments is performed iteratively, 29996 // removing the leftmost matching pattern on each iteration, 29997 // until no matching pattern remains. 29998 // 6d) If the path ends with "<segment>/..", where <segment> is a 29999 // complete path segment not equal to "..", that 30000 // "<segment>/.." is removed. 30001 30002 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line 30003 30004 30005 return path.split('').reverse().join(''); 30006 }, 30007 buildURLFromParts: function buildURLFromParts(parts) { 30008 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 30009 } 30010 }; 30011 /* jshint ignore:start */ 30012 30013 module.exports = URLToolkit; 30014 })(); 30015 /* jshint ignore:end */ 30016 30017 }); 30018 30019 /*! @name m3u8-parser @version 4.4.0 @license Apache-2.0 */ 30020 30021 function _extends() { 30022 _extends = Object.assign || function (target) { 30023 for (var i = 1; i < arguments.length; i++) { 30024 var source = arguments[i]; 30025 30026 for (var key in source) { 30027 if (Object.prototype.hasOwnProperty.call(source, key)) { 30028 target[key] = source[key]; 30029 } 30030 } 30031 } 30032 30033 return target; 30034 }; 30035 30036 return _extends.apply(this, arguments); 30037 } 30038 30039 function _inheritsLoose$1(subClass, superClass) { 30040 subClass.prototype = Object.create(superClass.prototype); 30041 subClass.prototype.constructor = subClass; 30042 subClass.__proto__ = superClass; 30043 } 30044 30045 function _assertThisInitialized$1(self) { 30046 if (self === void 0) { 30047 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 30048 } 30049 30050 return self; 30051 } 30052 /** 30053 * @file stream.js 30054 */ 30055 30056 /** 30057 * A lightweight readable stream implementation that handles event dispatching. 30058 * 30059 * @class Stream 30060 */ 30061 30062 30063 var Stream = 30064 /*#__PURE__*/ 30065 function () { 30066 function Stream() { 30067 this.listeners = {}; 30068 } 30069 /** 30070 * Add a listener for a specified event type. 30071 * 30072 * @param {string} type the event name 30073 * @param {Function} listener the callback to be invoked when an event of 30074 * the specified type occurs 30075 */ 30076 30077 30078 var _proto = Stream.prototype; 30079 30080 _proto.on = function on(type, listener) { 30081 if (!this.listeners[type]) { 30082 this.listeners[type] = []; 30083 } 30084 30085 this.listeners[type].push(listener); 30086 } 30087 /** 30088 * Remove a listener for a specified event type. 30089 * 30090 * @param {string} type the event name 30091 * @param {Function} listener a function previously registered for this 30092 * type of event through `on` 30093 * @return {boolean} if we could turn it off or not 30094 */ 30095 ; 30096 30097 _proto.off = function off(type, listener) { 30098 if (!this.listeners[type]) { 30099 return false; 30100 } 30101 30102 var index = this.listeners[type].indexOf(listener); 30103 this.listeners[type].splice(index, 1); 30104 return index > -1; 30105 } 30106 /** 30107 * Trigger an event of the specified type on this stream. Any additional 30108 * arguments to this function are passed as parameters to event listeners. 30109 * 30110 * @param {string} type the event name 30111 */ 30112 ; 30113 30114 _proto.trigger = function trigger(type) { 30115 var callbacks = this.listeners[type]; 30116 var i; 30117 var length; 30118 var args; 30119 30120 if (!callbacks) { 30121 return; 30122 } // Slicing the arguments on every invocation of this method 30123 // can add a significant amount of overhead. Avoid the 30124 // intermediate object creation for the common case of a 30125 // single callback argument 30126 30127 30128 if (arguments.length === 2) { 30129 length = callbacks.length; 30130 30131 for (i = 0; i < length; ++i) { 30132 callbacks[i].call(this, arguments[1]); 30133 } 30134 } else { 30135 args = Array.prototype.slice.call(arguments, 1); 30136 length = callbacks.length; 30137 30138 for (i = 0; i < length; ++i) { 30139 callbacks[i].apply(this, args); 30140 } 30141 } 30142 } 30143 /** 30144 * Destroys the stream and cleans up. 30145 */ 30146 ; 30147 30148 _proto.dispose = function dispose() { 30149 this.listeners = {}; 30150 } 30151 /** 30152 * Forwards all `data` events on this stream to the destination stream. The 30153 * destination stream should provide a method `push` to receive the data 30154 * events as they arrive. 30155 * 30156 * @param {Stream} destination the stream that will receive all `data` events 30157 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 30158 */ 30159 ; 30160 30161 _proto.pipe = function pipe(destination) { 30162 this.on('data', function (data) { 30163 destination.push(data); 30164 }); 30165 }; 30166 30167 return Stream; 30168 }(); 30169 /** 30170 * A stream that buffers string input and generates a `data` event for each 30171 * line. 30172 * 30173 * @class LineStream 30174 * @extends Stream 30175 */ 30176 30177 30178 var LineStream = 30179 /*#__PURE__*/ 30180 function (_Stream) { 30181 _inheritsLoose$1(LineStream, _Stream); 30182 30183 function LineStream() { 30184 var _this; 30185 30186 _this = _Stream.call(this) || this; 30187 _this.buffer = ''; 30188 return _this; 30189 } 30190 /** 30191 * Add new data to be parsed. 30192 * 30193 * @param {string} data the text to process 30194 */ 30195 30196 30197 var _proto = LineStream.prototype; 30198 30199 _proto.push = function push(data) { 30200 var nextNewline; 30201 this.buffer += data; 30202 nextNewline = this.buffer.indexOf('\n'); 30203 30204 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 30205 this.trigger('data', this.buffer.substring(0, nextNewline)); 30206 this.buffer = this.buffer.substring(nextNewline + 1); 30207 } 30208 }; 30209 30210 return LineStream; 30211 }(Stream); 30212 /** 30213 * "forgiving" attribute list psuedo-grammar: 30214 * attributes -> keyvalue (',' keyvalue)* 30215 * keyvalue -> key '=' value 30216 * key -> [^=]* 30217 * value -> '"' [^"]* '"' | [^,]* 30218 */ 30219 30220 30221 var attributeSeparator = function attributeSeparator() { 30222 var key = '[^=]*'; 30223 var value = '"[^"]*"|[^,]*'; 30224 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 30225 return new RegExp('(?:^|,)(' + keyvalue + ')'); 30226 }; 30227 /** 30228 * Parse attributes from a line given the separator 30229 * 30230 * @param {string} attributes the attribute line to parse 30231 */ 30232 30233 30234 var parseAttributes = function parseAttributes(attributes) { 30235 // split the string using attributes as the separator 30236 var attrs = attributes.split(attributeSeparator()); 30237 var result = {}; 30238 var i = attrs.length; 30239 var attr; 30240 30241 while (i--) { 30242 // filter out unmatched portions of the string 30243 if (attrs[i] === '') { 30244 continue; 30245 } // split the key and value 30246 30247 30248 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value 30249 30250 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 30251 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 30252 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 30253 result[attr[0]] = attr[1]; 30254 } 30255 30256 return result; 30257 }; 30258 /** 30259 * A line-level M3U8 parser event stream. It expects to receive input one 30260 * line at a time and performs a context-free parse of its contents. A stream 30261 * interpretation of a manifest can be useful if the manifest is expected to 30262 * be too large to fit comfortably into memory or the entirety of the input 30263 * is not immediately available. Otherwise, it's probably much easier to work 30264 * with a regular `Parser` object. 30265 * 30266 * Produces `data` events with an object that captures the parser's 30267 * interpretation of the input. That object has a property `tag` that is one 30268 * of `uri`, `comment`, or `tag`. URIs only have a single additional 30269 * property, `line`, which captures the entirety of the input without 30270 * interpretation. Comments similarly have a single additional property 30271 * `text` which is the input without the leading `#`. 30272 * 30273 * Tags always have a property `tagType` which is the lower-cased version of 30274 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 30275 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 30276 * tags are given the tag type `unknown` and a single additional property 30277 * `data` with the remainder of the input. 30278 * 30279 * @class ParseStream 30280 * @extends Stream 30281 */ 30282 30283 30284 var ParseStream = 30285 /*#__PURE__*/ 30286 function (_Stream) { 30287 _inheritsLoose$1(ParseStream, _Stream); 30288 30289 function ParseStream() { 30290 var _this; 30291 30292 _this = _Stream.call(this) || this; 30293 _this.customParsers = []; 30294 _this.tagMappers = []; 30295 return _this; 30296 } 30297 /** 30298 * Parses an additional line of input. 30299 * 30300 * @param {string} line a single line of an M3U8 file to parse 30301 */ 30302 30303 30304 var _proto = ParseStream.prototype; 30305 30306 _proto.push = function push(line) { 30307 var _this2 = this; 30308 30309 var match; 30310 var event; // strip whitespace 30311 30312 line = line.trim(); 30313 30314 if (line.length === 0) { 30315 // ignore empty lines 30316 return; 30317 } // URIs 30318 30319 30320 if (line[0] !== '#') { 30321 this.trigger('data', { 30322 type: 'uri', 30323 uri: line 30324 }); 30325 return; 30326 } // map tags 30327 30328 30329 var newLines = this.tagMappers.reduce(function (acc, mapper) { 30330 var mappedLine = mapper(line); // skip if unchanged 30331 30332 if (mappedLine === line) { 30333 return acc; 30334 } 30335 30336 return acc.concat([mappedLine]); 30337 }, [line]); 30338 newLines.forEach(function (newLine) { 30339 for (var i = 0; i < _this2.customParsers.length; i++) { 30340 if (_this2.customParsers[i].call(_this2, newLine)) { 30341 return; 30342 } 30343 } // Comments 30344 30345 30346 if (newLine.indexOf('#EXT') !== 0) { 30347 _this2.trigger('data', { 30348 type: 'comment', 30349 text: newLine.slice(1) 30350 }); 30351 30352 return; 30353 } // strip off any carriage returns here so the regex matching 30354 // doesn't have to account for them. 30355 30356 30357 newLine = newLine.replace('\r', ''); // Tags 30358 30359 match = /^#EXTM3U/.exec(newLine); 30360 30361 if (match) { 30362 _this2.trigger('data', { 30363 type: 'tag', 30364 tagType: 'm3u' 30365 }); 30366 30367 return; 30368 } 30369 30370 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(newLine); 30371 30372 if (match) { 30373 event = { 30374 type: 'tag', 30375 tagType: 'inf' 30376 }; 30377 30378 if (match[1]) { 30379 event.duration = parseFloat(match[1]); 30380 } 30381 30382 if (match[2]) { 30383 event.title = match[2]; 30384 } 30385 30386 _this2.trigger('data', event); 30387 30388 return; 30389 } 30390 30391 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(newLine); 30392 30393 if (match) { 30394 event = { 30395 type: 'tag', 30396 tagType: 'targetduration' 30397 }; 30398 30399 if (match[1]) { 30400 event.duration = parseInt(match[1], 10); 30401 } 30402 30403 _this2.trigger('data', event); 30404 30405 return; 30406 } 30407 30408 match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(newLine); 30409 30410 if (match) { 30411 event = { 30412 type: 'tag', 30413 tagType: 'totalduration' 30414 }; 30415 30416 if (match[1]) { 30417 event.duration = parseInt(match[1], 10); 30418 } 30419 30420 _this2.trigger('data', event); 30421 30422 return; 30423 } 30424 30425 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(newLine); 30426 30427 if (match) { 30428 event = { 30429 type: 'tag', 30430 tagType: 'version' 30431 }; 30432 30433 if (match[1]) { 30434 event.version = parseInt(match[1], 10); 30435 } 30436 30437 _this2.trigger('data', event); 30438 30439 return; 30440 } 30441 30442 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 30443 30444 if (match) { 30445 event = { 30446 type: 'tag', 30447 tagType: 'media-sequence' 30448 }; 30449 30450 if (match[1]) { 30451 event.number = parseInt(match[1], 10); 30452 } 30453 30454 _this2.trigger('data', event); 30455 30456 return; 30457 } 30458 30459 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(newLine); 30460 30461 if (match) { 30462 event = { 30463 type: 'tag', 30464 tagType: 'discontinuity-sequence' 30465 }; 30466 30467 if (match[1]) { 30468 event.number = parseInt(match[1], 10); 30469 } 30470 30471 _this2.trigger('data', event); 30472 30473 return; 30474 } 30475 30476 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(newLine); 30477 30478 if (match) { 30479 event = { 30480 type: 'tag', 30481 tagType: 'playlist-type' 30482 }; 30483 30484 if (match[1]) { 30485 event.playlistType = match[1]; 30486 } 30487 30488 _this2.trigger('data', event); 30489 30490 return; 30491 } 30492 30493 match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(newLine); 30494 30495 if (match) { 30496 event = { 30497 type: 'tag', 30498 tagType: 'byterange' 30499 }; 30500 30501 if (match[1]) { 30502 event.length = parseInt(match[1], 10); 30503 } 30504 30505 if (match[2]) { 30506 event.offset = parseInt(match[2], 10); 30507 } 30508 30509 _this2.trigger('data', event); 30510 30511 return; 30512 } 30513 30514 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(newLine); 30515 30516 if (match) { 30517 event = { 30518 type: 'tag', 30519 tagType: 'allow-cache' 30520 }; 30521 30522 if (match[1]) { 30523 event.allowed = !/NO/.test(match[1]); 30524 } 30525 30526 _this2.trigger('data', event); 30527 30528 return; 30529 } 30530 30531 match = /^#EXT-X-MAP:?(.*)$/.exec(newLine); 30532 30533 if (match) { 30534 event = { 30535 type: 'tag', 30536 tagType: 'map' 30537 }; 30538 30539 if (match[1]) { 30540 var attributes = parseAttributes(match[1]); 30541 30542 if (attributes.URI) { 30543 event.uri = attributes.URI; 30544 } 30545 30546 if (attributes.BYTERANGE) { 30547 var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'), 30548 length = _attributes$BYTERANGE[0], 30549 offset = _attributes$BYTERANGE[1]; 30550 30551 event.byterange = {}; 30552 30553 if (length) { 30554 event.byterange.length = parseInt(length, 10); 30555 } 30556 30557 if (offset) { 30558 event.byterange.offset = parseInt(offset, 10); 30559 } 30560 } 30561 } 30562 30563 _this2.trigger('data', event); 30564 30565 return; 30566 } 30567 30568 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(newLine); 30569 30570 if (match) { 30571 event = { 30572 type: 'tag', 30573 tagType: 'stream-inf' 30574 }; 30575 30576 if (match[1]) { 30577 event.attributes = parseAttributes(match[1]); 30578 30579 if (event.attributes.RESOLUTION) { 30580 var split = event.attributes.RESOLUTION.split('x'); 30581 var resolution = {}; 30582 30583 if (split[0]) { 30584 resolution.width = parseInt(split[0], 10); 30585 } 30586 30587 if (split[1]) { 30588 resolution.height = parseInt(split[1], 10); 30589 } 30590 30591 event.attributes.RESOLUTION = resolution; 30592 } 30593 30594 if (event.attributes.BANDWIDTH) { 30595 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 30596 } 30597 30598 if (event.attributes['PROGRAM-ID']) { 30599 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 30600 } 30601 } 30602 30603 _this2.trigger('data', event); 30604 30605 return; 30606 } 30607 30608 match = /^#EXT-X-MEDIA:?(.*)$/.exec(newLine); 30609 30610 if (match) { 30611 event = { 30612 type: 'tag', 30613 tagType: 'media' 30614 }; 30615 30616 if (match[1]) { 30617 event.attributes = parseAttributes(match[1]); 30618 } 30619 30620 _this2.trigger('data', event); 30621 30622 return; 30623 } 30624 30625 match = /^#EXT-X-ENDLIST/.exec(newLine); 30626 30627 if (match) { 30628 _this2.trigger('data', { 30629 type: 'tag', 30630 tagType: 'endlist' 30631 }); 30632 30633 return; 30634 } 30635 30636 match = /^#EXT-X-DISCONTINUITY/.exec(newLine); 30637 30638 if (match) { 30639 _this2.trigger('data', { 30640 type: 'tag', 30641 tagType: 'discontinuity' 30642 }); 30643 30644 return; 30645 } 30646 30647 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(newLine); 30648 30649 if (match) { 30650 event = { 30651 type: 'tag', 30652 tagType: 'program-date-time' 30653 }; 30654 30655 if (match[1]) { 30656 event.dateTimeString = match[1]; 30657 event.dateTimeObject = new Date(match[1]); 30658 } 30659 30660 _this2.trigger('data', event); 30661 30662 return; 30663 } 30664 30665 match = /^#EXT-X-KEY:?(.*)$/.exec(newLine); 30666 30667 if (match) { 30668 event = { 30669 type: 'tag', 30670 tagType: 'key' 30671 }; 30672 30673 if (match[1]) { 30674 event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array 30675 30676 if (event.attributes.IV) { 30677 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 30678 event.attributes.IV = event.attributes.IV.substring(2); 30679 } 30680 30681 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 30682 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 30683 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 30684 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 30685 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 30686 event.attributes.IV = new Uint32Array(event.attributes.IV); 30687 } 30688 } 30689 30690 _this2.trigger('data', event); 30691 30692 return; 30693 } 30694 30695 match = /^#EXT-X-START:?(.*)$/.exec(newLine); 30696 30697 if (match) { 30698 event = { 30699 type: 'tag', 30700 tagType: 'start' 30701 }; 30702 30703 if (match[1]) { 30704 event.attributes = parseAttributes(match[1]); 30705 event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']); 30706 event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE); 30707 } 30708 30709 _this2.trigger('data', event); 30710 30711 return; 30712 } 30713 30714 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(newLine); 30715 30716 if (match) { 30717 event = { 30718 type: 'tag', 30719 tagType: 'cue-out-cont' 30720 }; 30721 30722 if (match[1]) { 30723 event.data = match[1]; 30724 } else { 30725 event.data = ''; 30726 } 30727 30728 _this2.trigger('data', event); 30729 30730 return; 30731 } 30732 30733 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(newLine); 30734 30735 if (match) { 30736 event = { 30737 type: 'tag', 30738 tagType: 'cue-out' 30739 }; 30740 30741 if (match[1]) { 30742 event.data = match[1]; 30743 } else { 30744 event.data = ''; 30745 } 30746 30747 _this2.trigger('data', event); 30748 30749 return; 30750 } 30751 30752 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine); 30753 30754 if (match) { 30755 event = { 30756 type: 'tag', 30757 tagType: 'cue-in' 30758 }; 30759 30760 if (match[1]) { 30761 event.data = match[1]; 30762 } else { 30763 event.data = ''; 30764 } 30765 30766 _this2.trigger('data', event); 30767 30768 return; 30769 } // unknown tag type 30770 30771 30772 _this2.trigger('data', { 30773 type: 'tag', 30774 data: newLine.slice(4) 30775 }); 30776 }); 30777 } 30778 /** 30779 * Add a parser for custom headers 30780 * 30781 * @param {Object} options a map of options for the added parser 30782 * @param {RegExp} options.expression a regular expression to match the custom header 30783 * @param {string} options.customType the custom type to register to the output 30784 * @param {Function} [options.dataParser] function to parse the line into an object 30785 * @param {boolean} [options.segment] should tag data be attached to the segment object 30786 */ 30787 ; 30788 30789 _proto.addParser = function addParser(_ref) { 30790 var _this3 = this; 30791 30792 var expression = _ref.expression, 30793 customType = _ref.customType, 30794 dataParser = _ref.dataParser, 30795 segment = _ref.segment; 30796 30797 if (typeof dataParser !== 'function') { 30798 dataParser = function dataParser(line) { 30799 return line; 30800 }; 30801 } 30802 30803 this.customParsers.push(function (line) { 30804 var match = expression.exec(line); 30805 30806 if (match) { 30807 _this3.trigger('data', { 30808 type: 'custom', 30809 data: dataParser(line), 30810 customType: customType, 30811 segment: segment 30812 }); 30813 30814 return true; 30815 } 30816 }); 30817 } 30818 /** 30819 * Add a custom header mapper 30820 * 30821 * @param {Object} options 30822 * @param {RegExp} options.expression a regular expression to match the custom header 30823 * @param {Function} options.map function to translate tag into a different tag 30824 */ 30825 ; 30826 30827 _proto.addTagMapper = function addTagMapper(_ref2) { 30828 var expression = _ref2.expression, 30829 map = _ref2.map; 30830 30831 var mapFn = function mapFn(line) { 30832 if (expression.test(line)) { 30833 return map(line); 30834 } 30835 30836 return line; 30837 }; 30838 30839 this.tagMappers.push(mapFn); 30840 }; 30841 30842 return ParseStream; 30843 }(Stream); 30844 30845 function decodeB64ToUint8Array(b64Text) { 30846 var decodedString = window$1.atob(b64Text || ''); 30847 var array = new Uint8Array(decodedString.length); 30848 30849 for (var i = 0; i < decodedString.length; i++) { 30850 array[i] = decodedString.charCodeAt(i); 30851 } 30852 30853 return array; 30854 } 30855 /** 30856 * A parser for M3U8 files. The current interpretation of the input is 30857 * exposed as a property `manifest` on parser objects. It's just two lines to 30858 * create and parse a manifest once you have the contents available as a string: 30859 * 30860 * ```js 30861 * var parser = new m3u8.Parser(); 30862 * parser.push(xhr.responseText); 30863 * ``` 30864 * 30865 * New input can later be applied to update the manifest object by calling 30866 * `push` again. 30867 * 30868 * The parser attempts to create a usable manifest object even if the 30869 * underlying input is somewhat nonsensical. It emits `info` and `warning` 30870 * events during the parse if it encounters input that seems invalid or 30871 * requires some property of the manifest object to be defaulted. 30872 * 30873 * @class Parser 30874 * @extends Stream 30875 */ 30876 30877 30878 var Parser = 30879 /*#__PURE__*/ 30880 function (_Stream) { 30881 _inheritsLoose$1(Parser, _Stream); 30882 30883 function Parser() { 30884 var _this; 30885 30886 _this = _Stream.call(this) || this; 30887 _this.lineStream = new LineStream(); 30888 _this.parseStream = new ParseStream(); 30889 30890 _this.lineStream.pipe(_this.parseStream); 30891 /* eslint-disable consistent-this */ 30892 30893 30894 var self = _assertThisInitialized$1(_this); 30895 /* eslint-enable consistent-this */ 30896 30897 30898 var uris = []; 30899 var currentUri = {}; // if specified, the active EXT-X-MAP definition 30900 30901 var currentMap; // if specified, the active decryption key 30902 30903 var _key; 30904 30905 var noop = function noop() {}; 30906 30907 var defaultMediaGroups = { 30908 'AUDIO': {}, 30909 'VIDEO': {}, 30910 'CLOSED-CAPTIONS': {}, 30911 'SUBTITLES': {} 30912 }; // This is the Widevine UUID from DASH IF IOP. The same exact string is 30913 // used in MPDs with Widevine encrypted streams. 30914 30915 var widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities 30916 30917 var currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data 30918 30919 _this.manifest = { 30920 allowCache: true, 30921 discontinuityStarts: [], 30922 segments: [] 30923 }; // update the manifest with the m3u8 entry from the parse stream 30924 30925 _this.parseStream.on('data', function (entry) { 30926 var mediaGroup; 30927 var rendition; 30928 ({ 30929 tag: function tag() { 30930 // switch based on the tag type 30931 (({ 30932 'allow-cache': function allowCache() { 30933 this.manifest.allowCache = entry.allowed; 30934 30935 if (!('allowed' in entry)) { 30936 this.trigger('info', { 30937 message: 'defaulting allowCache to YES' 30938 }); 30939 this.manifest.allowCache = true; 30940 } 30941 }, 30942 byterange: function byterange() { 30943 var byterange = {}; 30944 30945 if ('length' in entry) { 30946 currentUri.byterange = byterange; 30947 byterange.length = entry.length; 30948 30949 if (!('offset' in entry)) { 30950 this.trigger('info', { 30951 message: 'defaulting offset to zero' 30952 }); 30953 entry.offset = 0; 30954 } 30955 } 30956 30957 if ('offset' in entry) { 30958 currentUri.byterange = byterange; 30959 byterange.offset = entry.offset; 30960 } 30961 }, 30962 endlist: function endlist() { 30963 this.manifest.endList = true; 30964 }, 30965 inf: function inf() { 30966 if (!('mediaSequence' in this.manifest)) { 30967 this.manifest.mediaSequence = 0; 30968 this.trigger('info', { 30969 message: 'defaulting media sequence to zero' 30970 }); 30971 } 30972 30973 if (!('discontinuitySequence' in this.manifest)) { 30974 this.manifest.discontinuitySequence = 0; 30975 this.trigger('info', { 30976 message: 'defaulting discontinuity sequence to zero' 30977 }); 30978 } 30979 30980 if (entry.duration > 0) { 30981 currentUri.duration = entry.duration; 30982 } 30983 30984 if (entry.duration === 0) { 30985 currentUri.duration = 0.01; 30986 this.trigger('info', { 30987 message: 'updating zero segment duration to a small value' 30988 }); 30989 } 30990 30991 this.manifest.segments = uris; 30992 }, 30993 key: function key() { 30994 if (!entry.attributes) { 30995 this.trigger('warn', { 30996 message: 'ignoring key declaration without attribute list' 30997 }); 30998 return; 30999 } // clear the active encryption key 31000 31001 31002 if (entry.attributes.METHOD === 'NONE') { 31003 _key = null; 31004 return; 31005 } 31006 31007 if (!entry.attributes.URI) { 31008 this.trigger('warn', { 31009 message: 'ignoring key declaration without URI' 31010 }); 31011 return; 31012 } // check if the content is encrypted for Widevine 31013 // Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf 31014 31015 31016 if (entry.attributes.KEYFORMAT === widevineUuid) { 31017 var VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC']; 31018 31019 if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) { 31020 this.trigger('warn', { 31021 message: 'invalid key method provided for Widevine' 31022 }); 31023 return; 31024 } 31025 31026 if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') { 31027 this.trigger('warn', { 31028 message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead' 31029 }); 31030 } 31031 31032 if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') { 31033 this.trigger('warn', { 31034 message: 'invalid key URI provided for Widevine' 31035 }); 31036 return; 31037 } 31038 31039 if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) { 31040 this.trigger('warn', { 31041 message: 'invalid key ID provided for Widevine' 31042 }); 31043 return; 31044 } // if Widevine key attributes are valid, store them as `contentProtection` 31045 // on the manifest to emulate Widevine tag structure in a DASH mpd 31046 31047 31048 this.manifest.contentProtection = { 31049 'com.widevine.alpha': { 31050 attributes: { 31051 schemeIdUri: entry.attributes.KEYFORMAT, 31052 // remove '0x' from the key id string 31053 keyId: entry.attributes.KEYID.substring(2) 31054 }, 31055 // decode the base64-encoded PSSH box 31056 pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1]) 31057 } 31058 }; 31059 return; 31060 } 31061 31062 if (!entry.attributes.METHOD) { 31063 this.trigger('warn', { 31064 message: 'defaulting key method to AES-128' 31065 }); 31066 } // setup an encryption key for upcoming segments 31067 31068 31069 _key = { 31070 method: entry.attributes.METHOD || 'AES-128', 31071 uri: entry.attributes.URI 31072 }; 31073 31074 if (typeof entry.attributes.IV !== 'undefined') { 31075 _key.iv = entry.attributes.IV; 31076 } 31077 }, 31078 'media-sequence': function mediaSequence() { 31079 if (!isFinite(entry.number)) { 31080 this.trigger('warn', { 31081 message: 'ignoring invalid media sequence: ' + entry.number 31082 }); 31083 return; 31084 } 31085 31086 this.manifest.mediaSequence = entry.number; 31087 }, 31088 'discontinuity-sequence': function discontinuitySequence() { 31089 if (!isFinite(entry.number)) { 31090 this.trigger('warn', { 31091 message: 'ignoring invalid discontinuity sequence: ' + entry.number 31092 }); 31093 return; 31094 } 31095 31096 this.manifest.discontinuitySequence = entry.number; 31097 currentTimeline = entry.number; 31098 }, 31099 'playlist-type': function playlistType() { 31100 if (!/VOD|EVENT/.test(entry.playlistType)) { 31101 this.trigger('warn', { 31102 message: 'ignoring unknown playlist type: ' + entry.playlist 31103 }); 31104 return; 31105 } 31106 31107 this.manifest.playlistType = entry.playlistType; 31108 }, 31109 map: function map() { 31110 currentMap = {}; 31111 31112 if (entry.uri) { 31113 currentMap.uri = entry.uri; 31114 } 31115 31116 if (entry.byterange) { 31117 currentMap.byterange = entry.byterange; 31118 } 31119 }, 31120 'stream-inf': function streamInf() { 31121 this.manifest.playlists = uris; 31122 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 31123 31124 if (!entry.attributes) { 31125 this.trigger('warn', { 31126 message: 'ignoring empty stream-inf attributes' 31127 }); 31128 return; 31129 } 31130 31131 if (!currentUri.attributes) { 31132 currentUri.attributes = {}; 31133 } 31134 31135 _extends(currentUri.attributes, entry.attributes); 31136 }, 31137 media: function media() { 31138 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 31139 31140 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 31141 this.trigger('warn', { 31142 message: 'ignoring incomplete or missing media group' 31143 }); 31144 return; 31145 } // find the media group, creating defaults as necessary 31146 31147 31148 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 31149 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 31150 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata 31151 31152 rendition = { 31153 "default": /yes/i.test(entry.attributes.DEFAULT) 31154 }; 31155 31156 if (rendition["default"]) { 31157 rendition.autoselect = true; 31158 } else { 31159 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 31160 } 31161 31162 if (entry.attributes.LANGUAGE) { 31163 rendition.language = entry.attributes.LANGUAGE; 31164 } 31165 31166 if (entry.attributes.URI) { 31167 rendition.uri = entry.attributes.URI; 31168 } 31169 31170 if (entry.attributes['INSTREAM-ID']) { 31171 rendition.instreamId = entry.attributes['INSTREAM-ID']; 31172 } 31173 31174 if (entry.attributes.CHARACTERISTICS) { 31175 rendition.characteristics = entry.attributes.CHARACTERISTICS; 31176 } 31177 31178 if (entry.attributes.FORCED) { 31179 rendition.forced = /yes/i.test(entry.attributes.FORCED); 31180 } // insert the new rendition 31181 31182 31183 mediaGroup[entry.attributes.NAME] = rendition; 31184 }, 31185 discontinuity: function discontinuity() { 31186 currentTimeline += 1; 31187 currentUri.discontinuity = true; 31188 this.manifest.discontinuityStarts.push(uris.length); 31189 }, 31190 'program-date-time': function programDateTime() { 31191 if (typeof this.manifest.dateTimeString === 'undefined') { 31192 // PROGRAM-DATE-TIME is a media-segment tag, but for backwards 31193 // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag 31194 // to the manifest object 31195 // TODO: Consider removing this in future major version 31196 this.manifest.dateTimeString = entry.dateTimeString; 31197 this.manifest.dateTimeObject = entry.dateTimeObject; 31198 } 31199 31200 currentUri.dateTimeString = entry.dateTimeString; 31201 currentUri.dateTimeObject = entry.dateTimeObject; 31202 }, 31203 targetduration: function targetduration() { 31204 if (!isFinite(entry.duration) || entry.duration < 0) { 31205 this.trigger('warn', { 31206 message: 'ignoring invalid target duration: ' + entry.duration 31207 }); 31208 return; 31209 } 31210 31211 this.manifest.targetDuration = entry.duration; 31212 }, 31213 totalduration: function totalduration() { 31214 if (!isFinite(entry.duration) || entry.duration < 0) { 31215 this.trigger('warn', { 31216 message: 'ignoring invalid total duration: ' + entry.duration 31217 }); 31218 return; 31219 } 31220 31221 this.manifest.totalDuration = entry.duration; 31222 }, 31223 start: function start() { 31224 if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) { 31225 this.trigger('warn', { 31226 message: 'ignoring start declaration without appropriate attribute list' 31227 }); 31228 return; 31229 } 31230 31231 this.manifest.start = { 31232 timeOffset: entry.attributes['TIME-OFFSET'], 31233 precise: entry.attributes.PRECISE 31234 }; 31235 }, 31236 'cue-out': function cueOut() { 31237 currentUri.cueOut = entry.data; 31238 }, 31239 'cue-out-cont': function cueOutCont() { 31240 currentUri.cueOutCont = entry.data; 31241 }, 31242 'cue-in': function cueIn() { 31243 currentUri.cueIn = entry.data; 31244 } 31245 })[entry.tagType] || noop).call(self); 31246 }, 31247 uri: function uri() { 31248 currentUri.uri = entry.uri; 31249 uris.push(currentUri); // if no explicit duration was declared, use the target duration 31250 31251 if (this.manifest.targetDuration && !('duration' in currentUri)) { 31252 this.trigger('warn', { 31253 message: 'defaulting segment duration to the target duration' 31254 }); 31255 currentUri.duration = this.manifest.targetDuration; 31256 } // annotate with encryption information, if necessary 31257 31258 31259 if (_key) { 31260 currentUri.key = _key; 31261 } 31262 31263 currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary 31264 31265 if (currentMap) { 31266 currentUri.map = currentMap; 31267 } // prepare for the next URI 31268 31269 31270 currentUri = {}; 31271 }, 31272 comment: function comment() {// comments are not important for playback 31273 }, 31274 custom: function custom() { 31275 // if this is segment-level data attach the output to the segment 31276 if (entry.segment) { 31277 currentUri.custom = currentUri.custom || {}; 31278 currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object 31279 } else { 31280 this.manifest.custom = this.manifest.custom || {}; 31281 this.manifest.custom[entry.customType] = entry.data; 31282 } 31283 } 31284 })[entry.type].call(self); 31285 }); 31286 31287 return _this; 31288 } 31289 /** 31290 * Parse the input string and update the manifest object. 31291 * 31292 * @param {string} chunk a potentially incomplete portion of the manifest 31293 */ 31294 31295 31296 var _proto = Parser.prototype; 31297 31298 _proto.push = function push(chunk) { 31299 this.lineStream.push(chunk); 31300 } 31301 /** 31302 * Flush any remaining input. This can be handy if the last line of an M3U8 31303 * manifest did not contain a trailing newline but the file has been 31304 * completely received. 31305 */ 31306 ; 31307 31308 _proto.end = function end() { 31309 // flush any buffered input 31310 this.lineStream.push('\n'); 31311 } 31312 /** 31313 * Add an additional parser for non-standard tags 31314 * 31315 * @param {Object} options a map of options for the added parser 31316 * @param {RegExp} options.expression a regular expression to match the custom header 31317 * @param {string} options.type the type to register to the output 31318 * @param {Function} [options.dataParser] function to parse the line into an object 31319 * @param {boolean} [options.segment] should tag data be attached to the segment object 31320 */ 31321 ; 31322 31323 _proto.addParser = function addParser(options) { 31324 this.parseStream.addParser(options); 31325 } 31326 /** 31327 * Add a custom header mapper 31328 * 31329 * @param {Object} options 31330 * @param {RegExp} options.expression a regular expression to match the custom header 31331 * @param {Function} options.map function to translate tag into a different tag 31332 */ 31333 ; 31334 31335 _proto.addTagMapper = function addTagMapper(options) { 31336 this.parseStream.addTagMapper(options); 31337 }; 31338 31339 return Parser; 31340 }(Stream); 31341 31342 /*! @name mpd-parser @version 0.8.1 @license Apache-2.0 */ 31343 31344 var isObject$1 = function isObject(obj) { 31345 return !!obj && typeof obj === 'object'; 31346 }; 31347 31348 var merge = function merge() { 31349 for (var _len = arguments.length, objects = new Array(_len), _key = 0; _key < _len; _key++) { 31350 objects[_key] = arguments[_key]; 31351 } 31352 31353 return objects.reduce(function (result, source) { 31354 Object.keys(source).forEach(function (key) { 31355 if (Array.isArray(result[key]) && Array.isArray(source[key])) { 31356 result[key] = result[key].concat(source[key]); 31357 } else if (isObject$1(result[key]) && isObject$1(source[key])) { 31358 result[key] = merge(result[key], source[key]); 31359 } else { 31360 result[key] = source[key]; 31361 } 31362 }); 31363 return result; 31364 }, {}); 31365 }; 31366 31367 var values = function values(o) { 31368 return Object.keys(o).map(function (k) { 31369 return o[k]; 31370 }); 31371 }; 31372 31373 var range = function range(start, end) { 31374 var result = []; 31375 31376 for (var i = start; i < end; i++) { 31377 result.push(i); 31378 } 31379 31380 return result; 31381 }; 31382 31383 var flatten = function flatten(lists) { 31384 return lists.reduce(function (x, y) { 31385 return x.concat(y); 31386 }, []); 31387 }; 31388 31389 var from = function from(list) { 31390 if (!list.length) { 31391 return []; 31392 } 31393 31394 var result = []; 31395 31396 for (var i = 0; i < list.length; i++) { 31397 result.push(list[i]); 31398 } 31399 31400 return result; 31401 }; 31402 31403 var findIndexes = function findIndexes(l, key) { 31404 return l.reduce(function (a, e, i) { 31405 if (e[key]) { 31406 a.push(i); 31407 } 31408 31409 return a; 31410 }, []); 31411 }; 31412 31413 var errors = { 31414 INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD', 31415 DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST', 31416 DASH_INVALID_XML: 'DASH_INVALID_XML', 31417 NO_BASE_URL: 'NO_BASE_URL', 31418 MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION', 31419 SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED', 31420 UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME' 31421 }; 31422 31423 function createCommonjsModule$1(fn, module) { 31424 return module = { 31425 exports: {} 31426 }, fn(module, module.exports), module.exports; 31427 } 31428 31429 var urlToolkit$1 = createCommonjsModule$1(function (module, exports) { 31430 // see https://tools.ietf.org/html/rfc1808 31431 31432 /* jshint ignore:start */ 31433 (function (root) { 31434 /* jshint ignore:end */ 31435 var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/?#]*)?((?:[^\/\?#]*\/)*.*?)??(;.*?)?(\?.*?)?(#.*?)?$/; 31436 var FIRST_SEGMENT_REGEX = /^([^\/?#]*)(.*)$/; 31437 var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; 31438 var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g; 31439 var URLToolkit = { 31440 // jshint ignore:line 31441 // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // 31442 // E.g
vendor: 4,132 bytes, lines 31443-31536
31443 // With opts.alwaysNormalize = false (default, spec compliant) 31444 // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g 31445 // With opts.alwaysNormalize = true (not spec compliant) 31446 // http://a.com/b/cd + /e/f/../g => http://a.com/e/g 31447 buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) { 31448 opts = opts || {}; // remove any remaining space and CRLF 31449 31450 baseURL = baseURL.trim(); 31451 relativeURL = relativeURL.trim(); 31452 31453 if (!relativeURL) { 31454 // 2a) If the embedded URL is entirely empty, it inherits the 31455 // entire base URL (i.e., is set equal to the base URL) 31456 // and we are done. 31457 if (!opts.alwaysNormalize) { 31458 return baseURL; 31459 } 31460 31461 var basePartsForNormalise = URLToolkit.parseURL(baseURL); 31462 31463 if (!basePartsForNormalise) { 31464 throw new Error('Error trying to parse base URL.'); 31465 } 31466 31467 basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path); 31468 return URLToolkit.buildURLFromParts(basePartsForNormalise); 31469 } 31470 31471 var relativeParts = URLToolkit.parseURL(relativeURL); 31472 31473 if (!relativeParts) { 31474 throw new Error('Error trying to parse relative URL.'); 31475 } 31476 31477 if (relativeParts.scheme) { 31478 // 2b) If the embedded URL starts with a scheme name, it is 31479 // interpreted as an absolute URL and we are done. 31480 if (!opts.alwaysNormalize) { 31481 return relativeURL; 31482 } 31483 31484 relativeParts.path = URLToolkit.normalizePath(relativeParts.path); 31485 return URLToolkit.buildURLFromParts(relativeParts); 31486 } 31487 31488 var baseParts = URLToolkit.parseURL(baseURL); 31489 31490 if (!baseParts) { 31491 throw new Error('Error trying to parse base URL.'); 31492 } 31493 31494 if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { 31495 // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc 31496 // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' 31497 var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); 31498 baseParts.netLoc = pathParts[1]; 31499 baseParts.path = pathParts[2]; 31500 } 31501 31502 if (baseParts.netLoc && !baseParts.path) { 31503 baseParts.path = '/'; 31504 } 31505 31506 var builtParts = { 31507 // 2c) Otherwise, the embedded URL inherits the scheme of 31508 // the base URL. 31509 scheme: baseParts.scheme, 31510 netLoc: relativeParts.netLoc, 31511 path: null, 31512 params: relativeParts.params, 31513 query: relativeParts.query, 31514 fragment: relativeParts.fragment 31515 }; 31516 31517 if (!relativeParts.netLoc) { 31518 // 3) If the embedded URL's <net_loc> is non-empty, we skip to 31519 // Step 7. Otherwise, the embedded URL inherits the <net_loc> 31520 // (if any) of the base URL. 31521 builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the 31522 // path is not relative and we skip to Step 7. 31523 31524 if (relativeParts.path[0] !== '/') { 31525 if (!relativeParts.path) { 31526 // 5) If the embedded URL path is empty (and not preceded by a 31527 // slash), then the embedded URL inherits the base URL path 31528 builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to 31529 // step 7; otherwise, it inherits the <params> of the base 31530 // URL (if any) and 31531 31532 if (!relativeParts.params) { 31533 builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to 31534 // step 7; otherwise, it inherits the <query> of the base 31535 // URL (if any) and we skip to step 7. 31536
vendor: 4,111 bytes, lines 31537-31639
31537 if (!relativeParts.query) { 31538 builtParts.query = baseParts.query; 31539 } 31540 } 31541 } else { 31542 // 6) The last segment of the base URL's path (anything 31543 // following the rightmost slash "/", or the entire path if no 31544 // slash is present) is removed and the embedded URL's path is 31545 // appended in its place. 31546 var baseURLPath = baseParts.path; 31547 var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; 31548 builtParts.path = URLToolkit.normalizePath(newPath); 31549 } 31550 } 31551 } 31552 31553 if (builtParts.path === null) { 31554 builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; 31555 } 31556 31557 return URLToolkit.buildURLFromParts(builtParts); 31558 }, 31559 parseURL: function parseURL(url) { 31560 var parts = URL_REGEX.exec(url); 31561 31562 if (!parts) { 31563 return null; 31564 } 31565 31566 return { 31567 scheme: parts[1] || '', 31568 netLoc: parts[2] || '', 31569 path: parts[3] || '', 31570 params: parts[4] || '', 31571 query: parts[5] || '', 31572 fragment: parts[6] || '' 31573 }; 31574 }, 31575 normalizePath: function normalizePath(path) { 31576 // The following operations are 31577 // then applied, in order, to the new path: 31578 // 6a) All occurrences of "./", where "." is a complete path 31579 // segment, are removed. 31580 // 6b) If the path ends with "." as a complete path segment, 31581 // that "." is removed. 31582 path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a 31583 // complete path segment not equal to "..", are removed. 31584 // Removal of these path segments is performed iteratively, 31585 // removing the leftmost matching pattern on each iteration, 31586 // until no matching pattern remains. 31587 // 6d) If the path ends with "<segment>/..", where <segment> is a 31588 // complete path segment not equal to "..", that 31589 // "<segment>/.." is removed. 31590 31591 while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line 31592 31593 31594 return path.split('').reverse().join(''); 31595 }, 31596 buildURLFromParts: function buildURLFromParts(parts) { 31597 return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment; 31598 } 31599 }; 31600 /* jshint ignore:start */ 31601 31602 module.exports = URLToolkit; 31603 })(); 31604 /* jshint ignore:end */ 31605 31606 }); 31607 31608 var resolveUrl = function resolveUrl(baseUrl, relativeUrl) { 31609 // return early if we don't need to resolve 31610 if (/^[a-z]+:/i.test(relativeUrl)) { 31611 return relativeUrl; 31612 } // if the base URL is relative then combine with the current location 31613 31614 31615 if (!/\/\//i.test(baseUrl)) { 31616 baseUrl = urlToolkit$1.buildAbsoluteURL(window$1.location.href, baseUrl); 31617 } 31618 31619 return urlToolkit$1.buildAbsoluteURL(baseUrl, relativeUrl); 31620 }; 31621 /** 31622 * @typedef {Object} SingleUri 31623 * @property {string} uri - relative location of segment 31624 * @property {string} resolvedUri - resolved location of segment 31625 * @property {Object} byterange - Object containing information on how to make byte range 31626 * requests following byte-range-spec per RFC2616. 31627 * @property {String} byterange.length - length of range request 31628 * @property {String} byterange.offset - byte offset of range request 31629 * 31630 * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 31631 */ 31632 31633 /** 31634 * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object 31635 * that conforms to how m3u8-parser is structured 31636 * 31637 * @see https://github.com/videojs/m3u8-parser 31638 * 31639 * @param {string} baseUrl - baseUrl provided by <BaseUrl>
31639 nodes 31640 * @param {string} source - source url for segment 31641 * @param {string} range - optional range used for range calls, 31642 * follows RFC 2616, Clause 14.35.1 31643 * @return {SingleUri} full segment information transformed into a format similar 31644 * to m3u8-parser 31645 */ 31646 31647 31648 var urlTypeToSegment = function urlTypeToSegment(_ref) { 31649 var _ref$baseUrl = _ref.baseUrl, 31650 baseUrl = _ref$baseUrl === void 0 ? '' : _ref$baseUrl, 31651 _ref$source = _ref.source, 31652 source = _ref$source === void 0 ? '' : _ref$source, 31653 _ref$range = _ref.range, 31654 range = _ref$range === void 0 ? '' : _ref$range, 31655 _ref$indexRange = _ref.indexRange, 31656 indexRange = _ref$indexRange === void 0 ? '' : _ref$indexRange; 31657 var segment = { 31658 uri: source, 31659 resolvedUri: resolveUrl(baseUrl || '', source) 31660 }; 31661 31662 if (range || indexRange) { 31663 var rangeStr = range ? range : indexRange; 31664 var ranges = rangeStr.split('-'); 31665 var startRange = parseInt(ranges[0], 10); 31666 var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to 31667 // RFC 2616, Clause 14.35.1 31668 31669 segment.byterange = { 31670 length: endRange - startRange + 1, 31671 offset: startRange 31672 }; 31673 } 31674 31675 return segment; 31676 }; 31677 31678 var byteRangeToString = function byteRangeToString(byterange) { 31679 // `endRange` is one less than `offset + length` because the HTTP range 31680 // header uses inclusive ranges 31681 var endRange = byterange.offset + byterange.length - 1; 31682 return byterange.offset + "-" + endRange; 31683 }; 31684 /** 31685 * Functions for calculating the range of available segments in static and dynamic 31686 * manifests. 31687 */ 31688 31689 31690 var segmentRange = { 31691 /** 31692 * Returns the entire range of available segments for a static MPD 31693 * 31694 * @param {Object} attributes 31695 * Inheritied MPD attributes 31696 * @return {{ start: number, end: number }} 31697 * The start and end numbers for available segments 31698 */ 31699 "static": function _static(attributes) { 31700 var duration = attributes.duration, 31701 _attributes$timescale = attributes.timescale, 31702 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 31703 sourceDuration = attributes.sourceDuration; 31704 return { 31705 start: 0, 31706 end: Math.ceil(sourceDuration / (duration / timescale)) 31707 }; 31708 }, 31709 31710 /** 31711 * Returns the current live window range of available segments for a dynamic MPD 31712 * 31713 * @param {Object} attributes 31714 * Inheritied MPD attributes 31715 * @return {{ start: number, end: number }} 31716 * The start and end numbers for available segments 31717 */ 31718 dynamic: function dynamic(attributes) { 31719 var NOW = attributes.NOW, 31720 clientOffset = attributes.clientOffset, 31721 availabilityStartTime = attributes.availabilityStartTime, 31722 _attributes$timescale2 = attributes.timescale, 31723 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 31724 duration = attributes.duration, 31725 _attributes$start = attributes.start, 31726 start = _attributes$start === void 0 ? 0 : _attributes$start, 31727 _attributes$minimumUp = attributes.minimumUpdatePeriod, 31728 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp, 31729 _attributes$timeShift = attributes.timeShiftBufferDepth, 31730 timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift; 31731 var now = (NOW + clientOffset) / 1000; 31732 var periodStartWC = availabilityStartTime + start; 31733 var periodEndWC = now + minimumUpdatePeriod; 31734 var periodDuration = periodEndWC - periodStartWC; 31735 var segmentCount = Math.ceil(periodDuration * timescale / duration); 31736 var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration); 31737 var availableEnd = Math.floor((now - periodStartWC) * timescale / duration); 31738 return { 31739 start: Math.max(0, availableStart), 31740 end: Math.min(segmentCount, availableEnd) 31741 }; 31742 } 31743 }; 31744 /** 31745 * Maps a range of numbers to objects with information needed to build the corresponding 31746 * segment list 31747 * 31748 * @name toSegmentsCallback 31749 * @function 31750 * @param {number} number 31751 * Number of the segment 31752 * @param {number} index 31753 * Index of the number in the range list 31754 * @return {{ number: Number, duration: Number, timeline: Number, time: Number }} 31755 * Object with segment timing and duration info 31756 */ 31757 31758 /** 31759 * Returns a callback for Array.prototype.map for mapping a range of numbers to 31760 * information needed to build the segment list. 31761 * 31762 * @param {Object} attributes 31763 * Inherited MPD attributes 31764 * @return {toSegmentsCallback} 31765 * Callback map function 31766 */ 31767 31768 var toSegments = function toSegments(attributes) { 31769 return function (number, index) { 31770 var duration = attributes.duration, 31771 _attributes$timescale3 = attributes.timescale, 31772 timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3, 31773 periodIndex = attributes.periodIndex, 31774 _attributes$startNumb = attributes.startNumber, 31775 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb; 31776 return { 31777 number: startNumber + number, 31778 duration: duration / timescale, 31779 timeline: periodIndex, 31780 time: index * duration 31781 }; 31782 }; 31783 }; 31784 /** 31785 * Returns a list of objects containing segment timing and duration info used for 31786 * building the list of segments. This uses the @duration attribute specified 31787 * in the MPD manifest to derive the range of segments. 31788 * 31789 * @param {Object} attributes 31790 * Inherited MPD attributes 31791 * @return {{number: number, duration: number, time: number, timeline: number}[]} 31792 * List of Objects with segment timing and duration info 31793 */ 31794 31795 31796 var parseByDuration = function parseByDuration(attributes) { 31797 var _attributes$type = attributes.type, 31798 type = _attributes$type === void 0 ? 'static' : _attributes$type, 31799 duration = attributes.duration, 31800 _attributes$timescale4 = attributes.timescale, 31801 timescale = _attributes$timescale4 === void 0 ? 1 : _attributes$timescale4, 31802 sourceDuration = attributes.sourceDuration; 31803 31804 var _segmentRange$type = segmentRange[type](attributes), 31805 start = _segmentRange$type.start, 31806 end = _segmentRange$type.end; 31807 31808 var segments = range(start, end).map(toSegments(attributes)); 31809 31810 if (type === 'static') { 31811 var index = segments.length - 1; // final segment may be less than full segment duration 31812 31813 segments[index].duration = sourceDuration - duration / timescale * index; 31814 } 31815 31816 return segments; 31817 }; 31818 /** 31819 * Translates SegmentBase into a set of segments. 31820 * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 31821 * node should be translated into segment. 31822 * 31823 * @param {Object} attributes 31824 * Object containing all inherited attributes from parent elements with attribute 31825 * names as keys 31826 * @return {Object.<Array>} list of segments 31827 */ 31828 31829 31830 var segmentsFromBase = function segmentsFromBase(attributes) { 31831 var baseUrl = attributes.baseUrl, 31832 _attributes$initializ = attributes.initialization, 31833 initialization = _attributes$initializ === void 0 ? {}
31833 : _attributes$initializ, 31834 sourceDuration = attributes.sourceDuration, 31835 _attributes$timescale = attributes.timescale, 31836 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 31837 _attributes$indexRang = attributes.indexRange, 31838 indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang, 31839 duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1) 31840 31841 if (!baseUrl) { 31842 throw new Error(errors.NO_BASE_URL); 31843 } 31844 31845 var initSegment = urlTypeToSegment({ 31846 baseUrl: baseUrl, 31847 source: initialization.sourceURL, 31848 range: initialization.range 31849 }); 31850 var segment = urlTypeToSegment({ 31851 baseUrl: baseUrl, 31852 source: baseUrl, 31853 indexRange: indexRange 31854 }); 31855 segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source 31856 // (since SegmentBase is only for one total segment) 31857 31858 if (duration) { 31859 var segmentTimeInfo = parseByDuration(attributes); 31860 31861 if (segmentTimeInfo.length) { 31862 segment.duration = segmentTimeInfo[0].duration; 31863 segment.timeline = segmentTimeInfo[0].timeline; 31864 } 31865 } else if (sourceDuration) { 31866 segment.duration = sourceDuration / timescale; 31867 segment.timeline = 0; 31868 } // This is used for mediaSequence 31869 31870 31871 segment.number = 0; 31872 return [segment]; 31873 }; 31874 /** 31875 * Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist 31876 * according to the sidx information given. 31877 * 31878 * playlist.sidx has metadadata about the sidx where-as the sidx param 31879 * is the parsed sidx box itself. 31880 * 31881 * @param {Object} playlist the playlist to update the sidx information for 31882 * @param {Object} sidx the parsed sidx box 31883 * @return {Object} the playlist object with the updated sidx information 31884 */ 31885 31886 31887 var addSegmentsToPlaylist = function addSegmentsToPlaylist(playlist, sidx, baseUrl) { 31888 // Retain init segment information 31889 var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial master manifest parsing 31890 31891 var sourceDuration = playlist.sidx.duration; // Retain source timeline 31892 31893 var timeline = playlist.timeline || 0; 31894 var sidxByteRange = playlist.sidx.byterange; 31895 var sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx 31896 31897 var timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes 31898 31899 var mediaReferences = sidx.references.filter(function (r) { 31900 return r.referenceType !== 1; 31901 }); 31902 var segments = []; // firstOffset is the offset from the end of the sidx box 31903 31904 var startIndex = sidxEnd + sidx.firstOffset; 31905 31906 for (var i = 0; i < mediaReferences.length; i++) { 31907 var reference = sidx.references[i]; // size of the referenced (sub)segment 31908 31909 var size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale 31910 // this will be converted to seconds when generating segments 31911 31912 var duration = reference.subsegmentDuration; // should be an inclusive range 31913 31914 var endIndex = startIndex + size - 1; 31915 var indexRange = startIndex + "-" + endIndex; 31916 var attributes = { 31917 baseUrl: baseUrl, 31918 timescale: timescale, 31919 timeline: timeline, 31920 // this is used in parseByDuration 31921 periodIndex: timeline, 31922 duration: duration, 31923 sourceDuration: sourceDuration, 31924 indexRange: indexRange 31925 }; 31926 var segment = segmentsFromBase(attributes)[0]; 31927 31928 if (initSegment) { 31929 segment.map = initSegment; 31930 } 31931 31932 segments.push(segment); 31933 startIndex += size; 31934 } 31935 31936 playlist.segments = segments; 31937 return playlist; 31938 }; 31939 31940 var mergeDiscontiguousPlaylists = function mergeDiscontiguousPlaylists(playlists) { 31941 var mergedPlaylists = values(playlists.reduce(function (acc, playlist) { 31942 // assuming playlist IDs are the same across periods 31943 // TODO: handle multiperiod where representation sets are not the same 31944 // across periods 31945 var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first 31946 31947 if (acc[name]) { 31948 var _acc$name$segments; // first segment of subsequent periods signal a discontinuity 31949 31950 31951 if (playlist.segments[0]) { 31952 playlist.segments[0].discontinuity = true; 31953 } 31954 31955 (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content 31956 // has the same contentProtection 31957 31958 31959 if (playlist.attributes.contentProtection) { 31960 acc[name].attributes.contentProtection = playlist.attributes.contentProtection; 31961 } 31962 } else { 31963 // first Period 31964 acc[name] = playlist; 31965 } 31966 31967 return acc; 31968 }, {})); 31969 return mergedPlaylists.map(function (playlist) { 31970 playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity'); 31971 return playlist; 31972 });
vendor: 16,384 bytes, lines 31973-32457
31973 }; 31974 31975 var addSegmentInfoFromSidx = function addSegmentInfoFromSidx(playlists, sidxMapping) { 31976 if (sidxMapping === void 0) { 31977 sidxMapping = {}; 31978 } 31979 31980 if (!Object.keys(sidxMapping).length) { 31981 return playlists; 31982 } 31983 31984 for (var i in playlists) { 31985 var playlist = playlists[i]; 31986 31987 if (!playlist.sidx) { 31988 continue; 31989 } 31990 31991 var sidxKey = playlist.sidx.uri + '-' + byteRangeToString(playlist.sidx.byterange); 31992 var sidxMatch = sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx; 31993 31994 if (playlist.sidx && sidxMatch) { 31995 addSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri); 31996 } 31997 } 31998 31999 return playlists; 32000 }; 32001 32002 var formatAudioPlaylist = function formatAudioPlaylist(_ref) { 32003 var _attributes; 32004 32005 var attributes = _ref.attributes, 32006 segments = _ref.segments, 32007 sidx = _ref.sidx; 32008 var playlist = { 32009 attributes: (_attributes = { 32010 NAME: attributes.id, 32011 BANDWIDTH: attributes.bandwidth, 32012 CODECS: attributes.codecs 32013 }, _attributes['PROGRAM-ID'] = 1, _attributes), 32014 uri: '', 32015 endList: (attributes.type || 'static') === 'static', 32016 timeline: attributes.periodIndex, 32017 resolvedUri: '', 32018 targetDuration: attributes.duration, 32019 segments: segments, 32020 mediaSequence: segments.length ? segments[0].number : 1 32021 }; 32022 32023 if (attributes.contentProtection) { 32024 playlist.contentProtection = attributes.contentProtection; 32025 } 32026 32027 if (sidx) { 32028 playlist.sidx = sidx; 32029 } 32030 32031 return playlist; 32032 }; 32033 32034 var formatVttPlaylist = function formatVttPlaylist(_ref2) { 32035 var _attributes2; 32036 32037 var attributes = _ref2.attributes, 32038 segments = _ref2.segments; 32039 32040 if (typeof segments === 'undefined') { 32041 // vtt tracks may use single file in BaseURL 32042 segments = [{ 32043 uri: attributes.baseUrl, 32044 timeline: attributes.periodIndex, 32045 resolvedUri: attributes.baseUrl || '', 32046 duration: attributes.sourceDuration, 32047 number: 0 32048 }]; // targetDuration should be the same duration as the only segment 32049 32050 attributes.duration = attributes.sourceDuration; 32051 } 32052 32053 return { 32054 attributes: (_attributes2 = { 32055 NAME: attributes.id, 32056 BANDWIDTH: attributes.bandwidth 32057 }, _attributes2['PROGRAM-ID'] = 1, _attributes2), 32058 uri: '', 32059 endList: (attributes.type || 'static') === 'static', 32060 timeline: attributes.periodIndex, 32061 resolvedUri: attributes.baseUrl || '', 32062 targetDuration: attributes.duration, 32063 segments: segments, 32064 mediaSequence: segments.length ? segments[0].number : 1 32065 }; 32066 }; 32067 32068 var organizeAudioPlaylists = function organizeAudioPlaylists(playlists, sidxMapping) { 32069 if (sidxMapping === void 0) { 32070 sidxMapping = {}; 32071 } 32072 32073 var mainPlaylist; 32074 var formattedPlaylists = playlists.reduce(function (a, playlist) { 32075 var role = playlist.attributes.role && playlist.attributes.role.value || ''; 32076 var language = playlist.attributes.lang || ''; 32077 var label = 'main'; 32078 32079 if (language) { 32080 var roleLabel = role ? " (" + role + ")" : ''; 32081 label = "" + playlist.attributes.lang + roleLabel; 32082 } // skip if we already have the highest quality audio for a language 32083 32084 32085 if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) { 32086 return a; 32087 } 32088 32089 a[label] = { 32090 language: language, 32091 autoselect: true, 32092 "default": role === 'main', 32093 playlists: addSegmentInfoFromSidx([formatAudioPlaylist(playlist)], sidxMapping), 32094 uri: '' 32095 }; 32096 32097 if (typeof mainPlaylist === 'undefined' && role === 'main') { 32098 mainPlaylist = playlist; 32099 mainPlaylist["default"] = true; 32100 } 32101 32102 return a; 32103 }, {}); // if no playlists have role "main", mark the first as main 32104 32105 if (!mainPlaylist) { 32106 var firstLabel = Object.keys(formattedPlaylists)[0]; 32107 formattedPlaylists[firstLabel]["default"] = true; 32108 } 32109 32110 return formattedPlaylists; 32111 }; 32112 32113 var organizeVttPlaylists = function organizeVttPlaylists(playlists, sidxMapping) { 32114 if (sidxMapping === void 0) { 32115 sidxMapping = {}; 32116 } 32117 32118 return playlists.reduce(function (a, playlist) { 32119 var label = playlist.attributes.lang || 'text'; // skip if we already have subtitles 32120 32121 if (a[label]) { 32122 return a; 32123 } 32124 32125 a[label] = { 32126 language: label, 32127 "default": false, 32128 autoselect: false, 32129 playlists: addSegmentInfoFromSidx([formatVttPlaylist(playlist)], sidxMapping), 32130 uri: '' 32131 }; 32132 return a; 32133 }, {}); 32134 }; 32135 32136 var formatVideoPlaylist = function formatVideoPlaylist(_ref3) { 32137 var _attributes3; 32138 32139 var attributes = _ref3.attributes, 32140 segments = _ref3.segments, 32141 sidx = _ref3.sidx; 32142 var playlist = { 32143 attributes: (_attributes3 = { 32144 NAME: attributes.id, 32145 AUDIO: 'audio', 32146 SUBTITLES: 'subs', 32147 RESOLUTION: { 32148 width: attributes.width, 32149 height: attributes.height 32150 }, 32151 CODECS: attributes.codecs, 32152 BANDWIDTH: attributes.bandwidth 32153 }, _attributes3['PROGRAM-ID'] = 1, _attributes3), 32154 uri: '', 32155 endList: (attributes.type || 'static') === 'static', 32156 timeline: attributes.periodIndex, 32157 resolvedUri: '', 32158 targetDuration: attributes.duration, 32159 segments: segments, 32160 mediaSequence: segments.length ? segments[0].number : 1 32161 }; 32162 32163 if (attributes.contentProtection) { 32164 playlist.contentProtection = attributes.contentProtection; 32165 } 32166 32167 if (sidx) { 32168 playlist.sidx = sidx; 32169 } 32170 32171 return playlist; 32172 }; 32173 32174 var toM3u8 = function toM3u8(dashPlaylists, sidxMapping) { 32175 var _mediaGroups; 32176 32177 if (sidxMapping === void 0) { 32178 sidxMapping = {}; 32179 } 32180 32181 if (!dashPlaylists.length) { 32182 return {}; 32183 } // grab all master attributes 32184 32185 32186 var _dashPlaylists$0$attr = dashPlaylists[0].attributes, 32187 duration = _dashPlaylists$0$attr.sourceDuration, 32188 _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod, 32189 minimumUpdatePeriod = _dashPlaylists$0$attr2 === void 0 ? 0 : _dashPlaylists$0$attr2; 32190 32191 var videoOnly = function videoOnly(_ref4) { 32192 var attributes = _ref4.attributes; 32193 return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video'; 32194 }; 32195 32196 var audioOnly = function audioOnly(_ref5) { 32197 var attributes = _ref5.attributes; 32198 return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio'; 32199 }; 32200 32201 var vttOnly = function vttOnly(_ref6) { 32202 var attributes = _ref6.attributes; 32203 return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text'; 32204 }; 32205 32206 var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist); 32207 var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly)); 32208 var vttPlaylists = dashPlaylists.filter(vttOnly); 32209 var master = { 32210 allowCache: true, 32211 discontinuityStarts: [], 32212 segments: [], 32213 endList: true, 32214 mediaGroups: (_mediaGroups = { 32215 AUDIO: {}, 32216 VIDEO: {} 32217 }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups), 32218 uri: '', 32219 duration: duration, 32220 playlists: addSegmentInfoFromSidx(videoPlaylists, sidxMapping), 32221 minimumUpdatePeriod: minimumUpdatePeriod * 1000 32222 }; 32223 32224 if (audioPlaylists.length) { 32225 master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping); 32226 } 32227 32228 if (vttPlaylists.length) { 32229 master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping); 32230 } 32231 32232 return master; 32233 }; 32234 /** 32235 * Calculates the R (repetition) value for a live stream (for the final segment 32236 * in a manifest where the r value is negative 1) 32237 * 32238 * @param {Object} attributes 32239 * Object containing all inherited attributes from parent elements with attribute 32240 * names as keys 32241 * @param {number} time 32242 * current time (typically the total time up until the final segment) 32243 * @param {number} duration 32244 * duration property for the given <S /> 32245 * 32246 * @return {number} 32247 * R value to reach the end of the given period 32248 */ 32249 32250 32251 var getLiveRValue = function getLiveRValue(attributes, time, duration) { 32252 var NOW = attributes.NOW, 32253 clientOffset = attributes.clientOffset, 32254 availabilityStartTime = attributes.availabilityStartTime, 32255 _attributes$timescale = attributes.timescale, 32256 timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale, 32257 _attributes$start = attributes.start, 32258 start = _attributes$start === void 0 ? 0 : _attributes$start, 32259 _attributes$minimumUp = attributes.minimumUpdatePeriod, 32260 minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp; 32261 var now = (NOW + clientOffset) / 1000; 32262 var periodStartWC = availabilityStartTime + start; 32263 var periodEndWC = now + minimumUpdatePeriod; 32264 var periodDuration = periodEndWC - periodStartWC; 32265 return Math.ceil((periodDuration * timescale - time) / duration); 32266 }; 32267 /** 32268 * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment 32269 * timing and duration 32270 * 32271 * @param {Object} attributes 32272 * Object containing all inherited attributes from parent elements with attribute 32273 * names as keys 32274 * @param {Object[]} segmentTimeline 32275 * List of objects representing the attributes of each S element contained within 32276 * 32277 * @return {{number: number, duration: number, time: number, timeline: number}[]} 32278 * List of Objects with segment timing and duration info 32279 */ 32280 32281 32282 var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) { 32283 var _attributes$type = attributes.type, 32284 type = _attributes$type === void 0 ? 'static' : _attributes$type, 32285 _attributes$minimumUp2 = attributes.minimumUpdatePeriod, 32286 minimumUpdatePeriod = _attributes$minimumUp2 === void 0 ? 0 : _attributes$minimumUp2, 32287 _attributes$media = attributes.media, 32288 media = _attributes$media === void 0 ? '' : _attributes$media, 32289 sourceDuration = attributes.sourceDuration, 32290 _attributes$timescale2 = attributes.timescale, 32291 timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2, 32292 _attributes$startNumb = attributes.startNumber, 32293 startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb, 32294 timeline = attributes.periodIndex; 32295 var segments = []; 32296 var time = -1; 32297 32298 for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) { 32299 var S = segmentTimeline[sIndex]; 32300 var duration = S.d; 32301 var repeat = S.r || 0; 32302 var segmentTime = S.t || 0; 32303 32304 if (time < 0) { 32305 // first segment 32306 time = segmentTime; 32307 } 32308 32309 if (segmentTime && segmentTime > time) { 32310 // discontinuity 32311 // TODO: How to handle this type of discontinuity 32312 // timeline++ here would treat it like HLS discontuity and content would 32313 // get appended without gap 32314 // E.G. 32315 // <S t="0" d="1" /> 32316 // <S d="1" /> 32317 // <S d="1" /> 32318 // <S t="5" d="1" /> 32319 // would have $Time$ values of [0, 1, 2, 5] 32320 // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY) 32321 // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP) 32322 // does the value of sourceDuration consider this when calculating arbitrary 32323 // negative @r repeat value? 32324 // E.G. Same elements as above with this added at the end 32325 // <S d="1" r="-1" /> 32326 // with a sourceDuration of 10 32327 // Would the 2 gaps be included in the time duration calculations resulting in 32328 // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments 32329 // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ? 32330 time = segmentTime; 32331 } 32332 32333 var count = void 0; 32334 32335 if (repeat < 0) { 32336 var nextS = sIndex + 1; 32337 32338 if (nextS === segmentTimeline.length) { 32339 // last segment 32340 if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) { 32341 count = getLiveRValue(attributes, time, duration); 32342 } else { 32343 // TODO: This may be incorrect depending on conclusion of TODO above 32344 count = (sourceDuration * timescale - time) / duration; 32345 } 32346 } else { 32347 count = (segmentTimeline[nextS].t - time) / duration; 32348 } 32349 } else { 32350 count = repeat + 1; 32351 } 32352 32353 var end = startNumber + segments.length + count; 32354 var number = startNumber + segments.length; 32355 32356 while (number < end) { 32357 segments.push({ 32358 number: number, 32359 duration: duration / timescale, 32360 time: time, 32361 timeline: timeline 32362 }); 32363 time += duration; 32364 number++; 32365 } 32366 } 32367 32368 return segments; 32369 }; 32370 32371 var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g; 32372 /** 32373 * Replaces template identifiers with corresponding values. To be used as the callback 32374 * for String.prototype.replace 32375 * 32376 * @name replaceCallback 32377 * @function 32378 * @param {string} match 32379 * Entire match of identifier 32380 * @param {string} identifier 32381 * Name of matched identifier 32382 * @param {string} format 32383 * Format tag string. Its presence indicates that padding is expected 32384 * @param {string} width 32385 * Desired length of the replaced value. Values less than this width shall be left 32386 * zero padded 32387 * @return {string} 32388 * Replacement for the matched identifier 32389 */ 32390 32391 /** 32392 * Returns a function to be used as a callback for String.prototype.replace to replace 32393 * template identifiers 32394 * 32395 * @param {Obect} values 32396 * Object containing values that shall be used to replace known identifiers 32397 * @param {number} values.RepresentationID 32398 * Value of the Representation@id attribute 32399 * @param {number} values.Number 32400 * Number of the corresponding segment 32401 * @param {number} values.Bandwidth 32402 * Value of the Representation@bandwidth attribute. 32403 * @param {number} values.Time 32404 * Timestamp value of the corresponding segment 32405 * @return {replaceCallback} 32406 * Callback to be used with String.prototype.replace to replace identifiers 32407 */ 32408 32409 var identifierReplacement = function identifierReplacement(values) { 32410 return function (match, identifier, format, width) { 32411 if (match === '$$') { 32412 // escape sequence 32413 return '$'; 32414 } 32415 32416 if (typeof values[identifier] === 'undefined') { 32417 return match; 32418 } 32419 32420 var value = '' + values[identifier]; 32421 32422 if (identifier === 'RepresentationID') { 32423 // Format tag shall not be present with RepresentationID 32424 return value; 32425 } 32426 32427 if (!format) { 32428 width = 1; 32429 } else { 32430 width = parseInt(width, 10); 32431 } 32432 32433 if (value.length >= width) { 32434 return value; 32435 } 32436 32437 return "" + new Array(width - value.length + 1).join('0') + value; 32438 }; 32439 }; 32440 /** 32441 * Constructs a segment url from a template string 32442 * 32443 * @param {string} url 32444 * Template string to construct url from 32445 * @param {Obect} values 32446 * Object containing values that shall be used to replace known identifiers 32447 * @param {number} values.RepresentationID 32448 * Value of the Representation@id attribute 32449 * @param {number} values.Number 32450 * Number of the corresponding segment 32451 * @param {number} values.Bandwidth 32452 * Value of the Representation@bandwidth attribute. 32453 * @param {number} values.Time 32454 * Timestamp value of the corresponding segment 32455 * @return {string} 32456 * Segment url with identifiers replaced 32457
vendor: 8,586 bytes, lines 32457-32684
32457*/ 32458 32459 32460 var constructTemplateUrl = function constructTemplateUrl(url, values) { 32461 return url.replace(identifierPattern, identifierReplacement(values)); 32462 }; 32463 /** 32464 * Generates a list of objects containing timing and duration information about each 32465 * segment needed to generate segment uris and the complete segment object 32466 * 32467 * @param {Object} attributes 32468 * Object containing all inherited attributes from parent elements with attribute 32469 * names as keys 32470 * @param {Object[]|undefined} segmentTimeline 32471 * List of objects representing the attributes of each S element contained within 32472 * the SegmentTimeline element 32473 * @return {{number: number, duration: number, time: number, timeline: number}[]} 32474 * List of Objects with segment timing and duration info 32475 */ 32476 32477 32478 var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) { 32479 if (!attributes.duration && !segmentTimeline) { 32480 // if neither @duration or SegmentTimeline are present, then there shall be exactly 32481 // one media segment 32482 return [{ 32483 number: attributes.startNumber || 1, 32484 duration: attributes.sourceDuration, 32485 time: 0, 32486 timeline: attributes.periodIndex 32487 }]; 32488 } 32489 32490 if (attributes.duration) { 32491 return parseByDuration(attributes); 32492 } 32493 32494 return parseByTimeline(attributes, segmentTimeline); 32495 }; 32496 /** 32497 * Generates a list of segments using information provided by the SegmentTemplate element 32498 * 32499 * @param {Object} attributes 32500 * Object containing all inherited attributes from parent elements with attribute 32501 * names as keys 32502 * @param {Object[]|undefined} segmentTimeline 32503 * List of objects representing the attributes of each S element contained within 32504 * the SegmentTimeline element 32505 * @return {Object[]} 32506 * List of segment objects 32507 */ 32508 32509 32510 var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) { 32511 var templateValues = { 32512 RepresentationID: attributes.id, 32513 Bandwidth: attributes.bandwidth || 0 32514 }; 32515 var _attributes$initializ = attributes.initialization, 32516 initialization = _attributes$initializ === void 0 ? { 32517 sourceURL: '', 32518 range: '' 32519 } : _attributes$initializ; 32520 var mapSegment = urlTypeToSegment({ 32521 baseUrl: attributes.baseUrl, 32522 source: constructTemplateUrl(initialization.sourceURL, templateValues), 32523 range: initialization.range 32524 }); 32525 var segments = parseTemplateInfo(attributes, segmentTimeline); 32526 return segments.map(function (segment) { 32527 templateValues.Number = segment.number; 32528 templateValues.Time = segment.time; 32529 var uri = constructTemplateUrl(attributes.media || '', templateValues); 32530 return { 32531 uri: uri, 32532 timeline: segment.timeline, 32533 duration: segment.duration, 32534 resolvedUri: resolveUrl(attributes.baseUrl || '', uri), 32535 map: mapSegment, 32536 number: segment.number 32537 }; 32538 }); 32539 }; 32540 /** 32541 * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14) 32542 * to an object that matches the output of a segment in videojs/mpd-parser 32543 * 32544 * @param {Object} attributes 32545 * Object containing all inherited attributes from parent elements with attribute 32546 * names as keys 32547 * @param {Object} segmentUrl 32548 * <SegmentURL> node to translate into a segment object 32549 * @return {Object} translated segment object 32550 */ 32551 32552 32553 var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) { 32554 var baseUrl = attributes.baseUrl, 32555 _attributes$initializ = attributes.initialization, 32556 initialization = _attributes$initializ === void 0 ? {} : _attributes$initializ; 32557 var initSegment = urlTypeToSegment({ 32558 baseUrl: baseUrl, 32559 source: initialization.sourceURL, 32560 range: initialization.range 32561 }); 32562 var segment = urlTypeToSegment({ 32563 baseUrl: baseUrl, 32564 source: segmentUrl.media, 32565 range: segmentUrl.mediaRange 32566 }); 32567 segment.map = initSegment; 32568 return segment; 32569 }; 32570 /** 32571 * Generates a list of segments using information provided by the SegmentList element 32572 * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each 32573 * node should be translated into segment. 32574 * 32575 * @param {Object} attributes 32576 * Object containing all inherited attributes from parent elements with attribute 32577 * names as keys 32578 * @param {Object[]|undefined} segmentTimeline 32579 * List of objects representing the attributes of each S element contained within 32580 * the SegmentTimeline element 32581 * @return {Object.<Array>} list of segments 32582 */ 32583 32584 32585 var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) { 32586 var duration = attributes.duration, 32587 _attributes$segmentUr = attributes.segmentUrls, 32588 segmentUrls = _attributes$segmentUr === void 0 ? [] : _attributes$segmentUr; // Per spec (5.3.9.2.1) no way to determine segment duration OR 32589 // if both SegmentTimeline and @duration are defined, it is outside of spec. 32590 32591 if (!duration && !segmentTimeline || duration && segmentTimeline) { 32592 throw new Error(errors.SEGMENT_TIME_UNSPECIFIED); 32593 } 32594 32595 var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) { 32596 return SegmentURLToSegmentObject(attributes, segmentUrlObject); 32597 }); 32598 var segmentTimeInfo; 32599 32600 if (duration) { 32601 segmentTimeInfo = parseByDuration(attributes); 32602 } 32603 32604 if (segmentTimeline) { 32605 segmentTimeInfo = parseByTimeline(attributes, segmentTimeline); 32606 } 32607 32608 var segments = segmentTimeInfo.map(function (segmentTime, index) { 32609 if (segmentUrlMap[index]) { 32610 var segment = segmentUrlMap[index]; 32611 segment.timeline = segmentTime.timeline; 32612 segment.duration = segmentTime.duration; 32613 segment.number = segmentTime.number; 32614 return segment; 32615 } // Since we're mapping we should get rid of any blank segments (in case 32616 // the given SegmentTimeline is handling for more elements than we have 32617 // SegmentURLs for). 32618 32619 }).filter(function (segment) { 32620 return segment; 32621 }); 32622 return segments; 32623 }; 32624 32625 var generateSegments = function generateSegments(_ref) { 32626 var attributes = _ref.attributes, 32627 segmentInfo = _ref.segmentInfo; 32628 var segmentAttributes; 32629 var segmentsFn; 32630 32631 if (segmentInfo.template) { 32632 segmentsFn = segmentsFromTemplate; 32633 segmentAttributes = merge(attributes, segmentInfo.template); 32634 } else if (segmentInfo.base) { 32635 segmentsFn = segmentsFromBase; 32636 segmentAttributes = merge(attributes, segmentInfo.base); 32637 } else if (segmentInfo.list) { 32638 segmentsFn = segmentsFromList; 32639 segmentAttributes = merge(attributes, segmentInfo.list); 32640 } 32641 32642 var segmentsInfo = { 32643 attributes: attributes 32644 }; 32645 32646 if (!segmentsFn) { 32647 return segmentsInfo; 32648 } 32649 32650 var segments = segmentsFn(segmentAttributes, segmentInfo.timeline); // The @duration attribute will be used to determin the playlist's targetDuration which 32651 // must be in seconds. Since we've generated the segment list, we no longer need 32652 // @duration to be in @timescale units, so we can convert it here. 32653 32654 if (segmentAttributes.duration) { 32655 var _segmentAttributes = segmentAttributes, 32656 duration = _segmentAttributes.duration, 32657 _segmentAttributes$ti = _segmentAttributes.timescale, 32658 timescale = _segmentAttributes$ti === void 0 ? 1 : _segmentAttributes$ti; 32659 segmentAttributes.duration = duration / timescale; 32660 } else if (segments.length) { 32661 // if there is no @duration attribute, use the largest segment duration as 32662 // as target duration 32663 segmentAttributes.duration = segments.reduce(function (max, segment) { 32664 return Math.max(max, Math.ceil(segment.duration)); 32665 }, 0); 32666 } else { 32667 segmentAttributes.duration = 0; 32668 } 32669 32670 segmentsInfo.attributes = segmentAttributes; 32671 segmentsInfo.segments = segments; // This is a sidx box without actual segment information 32672 32673 if (segmentInfo.base && segmentAttributes.indexRange) { 32674 segmentsInfo.sidx = segments[0]; 32675 segmentsInfo.segments = []; 32676 } 32677 32678 return segmentsInfo; 32679 }; 32680 32681 var toPlaylists = function toPlaylists(representations) { 32682 return representations.map(generateSegments); 32683 }; 32684
vendor: 13,526 bytes, lines 32685-33081
32685 var findChildren = function findChildren(element, name) { 32686 return from(element.childNodes).filter(function (_ref) { 32687 var tagName = _ref.tagName; 32688 return tagName === name; 32689 }); 32690 }; 32691 32692 var getContent = function getContent(element) { 32693 return element.textContent.trim(); 32694 }; 32695 32696 var parseDuration = function parseDuration(str) { 32697 var SECONDS_IN_YEAR = 365 * 24 * 60 * 60; 32698 var SECONDS_IN_MONTH = 30 * 24 * 60 * 60; 32699 var SECONDS_IN_DAY = 24 * 60 * 60; 32700 var SECONDS_IN_HOUR = 60 * 60; 32701 var SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S 32702 32703 var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/; 32704 var match = durationRegex.exec(str); 32705 32706 if (!match) { 32707 return 0; 32708 } 32709 32710 var _match$slice = match.slice(1), 32711 year = _match$slice[0], 32712 month = _match$slice[1], 32713 day = _match$slice[2], 32714 hour = _match$slice[3], 32715 minute = _match$slice[4], 32716 second = _match$slice[5]; 32717 32718 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); 32719 }; 32720 32721 var parseDate = function parseDate(str) { 32722 // Date format without timezone according to ISO 8601 32723 // YYY-MM-DDThh:mm:ss.ssssss 32724 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 32725 // expressed by ending with 'Z' 32726 32727 if (dateRegex.test(str)) { 32728 str += 'Z'; 32729 } 32730 32731 return Date.parse(str); 32732 }; 32733 32734 var parsers = { 32735 /** 32736 * Specifies the duration of the entire Media Presentation. Format is a duration string 32737 * as specified in ISO 8601 32738 * 32739 * @param {string} value 32740 * value of attribute as a string 32741 * @return {number} 32742 * The duration in seconds 32743 */ 32744 mediaPresentationDuration: function mediaPresentationDuration(value) { 32745 return parseDuration(value); 32746 }, 32747 32748 /** 32749 * Specifies the Segment availability start time for all Segments referred to in this 32750 * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability 32751 * time. Format is a date string as specified in ISO 8601 32752 * 32753 * @param {string} value 32754 * value of attribute as a string 32755 * @return {number} 32756 * The date as seconds from unix epoch 32757 */ 32758 availabilityStartTime: function availabilityStartTime(value) { 32759 return parseDate(value) / 1000; 32760 }, 32761 32762 /** 32763 * Specifies the smallest period between potential changes to the MPD. Format is a 32764 * duration string as specified in ISO 8601 32765 * 32766 * @param {string} value 32767 * value of attribute as a string 32768 * @return {number} 32769 * The duration in seconds 32770 */ 32771 minimumUpdatePeriod: function minimumUpdatePeriod(value) { 32772 return parseDuration(value); 32773 }, 32774 32775 /** 32776 * Specifies the duration of the smallest time shifting buffer for any Representation 32777 * in the MPD. Format is a duration string as specified in ISO 8601 32778 * 32779 * @param {string} value 32780 * value of attribute as a string 32781 * @return {number} 32782 * The duration in seconds 32783 */ 32784 timeShiftBufferDepth: function timeShiftBufferDepth(value) { 32785 return parseDuration(value); 32786 }, 32787 32788 /** 32789 * Specifies the PeriodStart time of the Period relative to the availabilityStarttime. 32790 * Format is a duration string as specified in ISO 8601 32791 * 32792 * @param {string} value 32793 * value of attribute as a string 32794 * @return {number} 32795 * The duration in seconds 32796 */ 32797 start: function start(value) { 32798 return parseDuration(value); 32799 }, 32800 32801 /** 32802 * Specifies the width of the visual presentation 32803 * 32804 * @param {string} value 32805 * value of attribute as a string 32806 * @return {number} 32807 * The parsed width 32808 */ 32809 width: function width(value) { 32810 return parseInt(value, 10); 32811 }, 32812 32813 /** 32814 * Specifies the height of the visual presentation 32815 * 32816 * @param {string} value 32817 * value of attribute as a string 32818 * @return {number} 32819 * The parsed height 32820 */ 32821 height: function height(value) { 32822 return parseInt(value, 10); 32823 }, 32824 32825 /** 32826 * Specifies the bitrate of the representation 32827 * 32828 * @param {string} value 32829 * value of attribute as a string 32830 * @return {number} 32831 * The parsed bandwidth 32832 */ 32833 bandwidth: function bandwidth(value) { 32834 return parseInt(value, 10); 32835 }, 32836 32837 /** 32838 * Specifies the number of the first Media Segment in this Representation in the Period 32839 * 32840 * @param {string} value 32841 * value of attribute as a string 32842 * @return {number} 32843 * The parsed number 32844 */ 32845 startNumber: function startNumber(value) { 32846 return parseInt(value, 10); 32847 }, 32848 32849 /** 32850 * Specifies the timescale in units per seconds 32851 * 32852 * @param {string} value 32853 * value of attribute as a string 32854 * @return {number} 32855 * The aprsed timescale 32856 */ 32857 timescale: function timescale(value) { 32858 return parseInt(value, 10); 32859 }, 32860 32861 /** 32862 * Specifies the constant approximate Segment duration 32863 * NOTE: The <Period> element also contains an @duration attribute. This duration 32864 * specifies the duration of the Period. This attribute is currently not 32865 * supported by the rest of the parser, however we still check for it to prevent 32866 * errors. 32867 * 32868 * @param {string} value 32869 * value of attribute as a string 32870 * @return {number} 32871 * The parsed duration 32872 */ 32873 duration: function duration(value) { 32874 var parsedValue = parseInt(value, 10); 32875 32876 if (isNaN(parsedValue)) { 32877 return parseDuration(value); 32878 } 32879 32880 return parsedValue; 32881 }, 32882 32883 /** 32884 * Specifies the Segment duration, in units of the value of the @timescale. 32885 * 32886 * @param {string} value 32887 * value of attribute as a string 32888 * @return {number} 32889 * The parsed duration 32890 */ 32891 d: function d(value) { 32892 return parseInt(value, 10); 32893 }, 32894 32895 /** 32896 * Specifies the MPD start time, in @timescale units, the first Segment in the series 32897 * starts relative to the beginning of the Period 32898 * 32899 * @param {string} value 32900 * value of attribute as a string 32901 * @return {number} 32902 * The parsed time 32903 */ 32904 t: function t(value) { 32905 return parseInt(value, 10); 32906 }, 32907 32908 /** 32909 * Specifies the repeat count of the number of following contiguous Segments with the 32910 * same duration expressed by the value of @d 32911 * 32912 * @param {string} value 32913 * value of attribute as a string 32914 * @return {number} 32915 * The parsed number 32916 */ 32917 r: function r(value) { 32918 return parseInt(value, 10); 32919 }, 32920 32921 /** 32922 * Default parser for all other attributes. Acts as a no-op and just returns the value 32923 * as a string 32924 * 32925 * @param {string} value 32926 * value of attribute as a string 32927 * @return {string} 32928 * Unparsed value 32929 */ 32930 DEFAULT: function DEFAULT(value) { 32931 return value; 32932 } 32933 }; 32934 /** 32935 * Gets all the attributes and values of the provided node, parses attributes with known 32936 * types, and returns an object with attribute names mapped to values. 32937 * 32938 * @param {Node} el 32939 * The node to parse attributes from 32940 * @return {Object} 32941 * Object with all attributes of el parsed 32942 */ 32943 32944 var parseAttributes$1 = function parseAttributes(el) { 32945 if (!(el && el.attributes)) { 32946 return {}; 32947 } 32948 32949 return from(el.attributes).reduce(function (a, e) { 32950 var parseFn = parsers[e.name] || parsers.DEFAULT; 32951 a[e.name] = parseFn(e.value); 32952 return a; 32953 }, {}); 32954 }; 32955 32956 function decodeB64ToUint8Array$1(b64Text) { 32957 var decodedString = window$1.atob(b64Text); 32958 var array = new Uint8Array(decodedString.length); 32959 32960 for (var i = 0; i < decodedString.length; i++) { 32961 array[i] = decodedString.charCodeAt(i); 32962 } 32963 32964 return array; 32965 } 32966 32967 var keySystemsMap = { 32968 'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey', 32969 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha', 32970 'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready', 32971 'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime' 32972 }; 32973 /** 32974 * Builds a list of urls that is the product of the reference urls and BaseURL values 32975 * 32976 * @param {string[]} referenceUrls 32977 * List of reference urls to resolve to 32978 * @param {Node[]} baseUrlElements 32979 * List of BaseURL nodes from the mpd 32980 * @return {string[]} 32981 * List of resolved urls 32982 */ 32983 32984 var buildBaseUrls = function buildBaseUrls(referenceUrls, baseUrlElements) { 32985 if (!baseUrlElements.length) { 32986 return referenceUrls; 32987 } 32988 32989 return flatten(referenceUrls.map(function (reference) { 32990 return baseUrlElements.map(function (baseUrlElement) { 32991 return resolveUrl(reference, getContent(baseUrlElement)); 32992 }); 32993 })); 32994 }; 32995 /** 32996 * Contains all Segment information for its containing AdaptationSet 32997 * 32998 * @typedef {Object} SegmentInformation 32999 * @property {Object|undefined} template 33000 * Contains the attributes for the SegmentTemplate node 33001 * @property {Object[]|undefined} timeline 33002 * Contains a list of atrributes for each S node within the SegmentTimeline node 33003 * @property {Object|undefined} list 33004 * Contains the attributes for the SegmentList node 33005 * @property {Object|undefined} base 33006 * Contains the attributes for the SegmentBase node 33007 */ 33008 33009 /** 33010 * Returns all available Segment information contained within the AdaptationSet node 33011 * 33012 * @param {Node} adaptationSet 33013 * The AdaptationSet node to get Segment information from 33014 * @return {SegmentInformation} 33015 * The Segment information contained within the provided AdaptationSet 33016 */ 33017 33018 33019 var getSegmentInformation = function getSegmentInformation(adaptationSet) { 33020 var segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0]; 33021 var segmentList = findChildren(adaptationSet, 'SegmentList')[0]; 33022 var segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(function (s) { 33023 return merge({ 33024 tag: 'SegmentURL' 33025 }, parseAttributes$1(s)); 33026 }); 33027 var segmentBase = findChildren(adaptationSet, 'SegmentBase')[0]; 33028 var segmentTimelineParentNode = segmentList || segmentTemplate; 33029 var segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0]; 33030 var segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate; 33031 var segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both 33032 // @initialization and an <Initialization> node. @initialization can be templated, 33033 // while the node can have a url and range specified. If the <SegmentTemplate> has 33034 // both @initialization and an <Initialization> subelement we opt to override with 33035 // the node, as this interaction is not defined in the spec. 33036 33037 var template = segmentTemplate && parseAttributes$1(segmentTemplate); 33038 33039 if (template && segmentInitialization) { 33040 template.initialization = segmentInitialization && parseAttributes$1(segmentInitialization); 33041 } else if (template && template.initialization) { 33042 // If it is @initialization we convert it to an object since this is the format that 33043 // later functions will rely on for the initialization segment. This is only valid 33044 // for <SegmentTemplate> 33045 template.initialization = { 33046 sourceURL: template.initialization 33047 }; 33048 } 33049 33050 var segmentInfo = { 33051 template: template, 33052 timeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(function (s) { 33053 return parseAttributes$1(s); 33054 }), 33055 list: segmentList && merge(parseAttributes$1(segmentList), { 33056 segmentUrls: segmentUrls, 33057 initialization: parseAttributes$1(segmentInitialization) 33058 }), 33059 base: segmentBase && merge(parseAttributes$1(segmentBase), { 33060 initialization: parseAttributes$1(segmentInitialization) 33061 }) 33062 }; 33063 Object.keys(segmentInfo).forEach(function (key) { 33064 if (!segmentInfo[key]) { 33065 delete segmentInfo[key]; 33066 } 33067 }); 33068 return segmentInfo; 33069 }; 33070 /** 33071 * Contains Segment information and attributes needed to construct a Playlist object 33072 * from a Representation 33073 * 33074 * @typedef {Object} RepresentationInformation 33075 * @property {SegmentInformation} segmentInfo 33076 * Segment information for this Representation 33077 * @property {Object} attributes 33078 * Inherited attributes for this Representation 33079 */ 33080 33081 /**
33082 * Maps a Representation node to an object containing Segment information and attributes 33083 * 33084 * @name inheritBaseUrlsCallback 33085 * @function 33086 * @param {Node} representation 33087 * Representation node from the mpd 33088 * @return {RepresentationInformation} 33089 * Representation information needed to construct a Playlist object 33090 */ 33091 33092 /** 33093 * Returns a callback for Array.prototype.map for mapping Representation nodes to 33094 * Segment information and attributes using inherited BaseURL nodes. 33095 * 33096 * @param {Object} adaptationSetAttributes 33097 * Contains attributes inherited by the AdaptationSet 33098 * @param {string[]} adaptationSetBaseUrls 33099 * Contains list of resolved base urls inherited by the AdaptationSet 33100 * @param {SegmentInformation} adaptationSetSegmentInfo 33101 * Contains Segment information for the AdaptationSet 33102 * @return {inheritBaseUrlsCallback} 33103 * Callback map function 33104 */ 33105 33106 33107 var inheritBaseUrls = function inheritBaseUrls(adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) { 33108 return function (representation) { 33109 var repBaseUrlElements = findChildren(representation, 'BaseURL'); 33110 var repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements); 33111 var attributes = merge(adaptationSetAttributes, parseAttributes$1(representation)); 33112 var representationSegmentInfo = getSegmentInformation(representation); 33113 return repBaseUrls.map(function (baseUrl) { 33114 return { 33115 segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo), 33116 attributes: merge(attributes, { 33117 baseUrl: baseUrl 33118 }) 33119 }; 33120 }); 33121 }; 33122 }; 33123 /** 33124 * Tranforms a series of content protection nodes to
33125 * an object containing pssh data by key system 33126 * 33127 * @param {Node[]} contentProtectionNodes 33128 * Content protection nodes 33129 * @return {Object} 33130 * Object containing pssh data by key system 33131 */ 33132 33133 33134 var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) { 33135 return contentProtectionNodes.reduce(function (acc, node) { 33136 var attributes = parseAttributes$1(node); 33137 var keySystem = keySystemsMap[attributes.schemeIdUri]; 33138 33139 if (keySystem) { 33140 acc[keySystem] = { 33141 attributes: attributes 33142 }; 33143 var psshNode = findChildren(node, 'cenc:pssh')[0]; 33144 33145 if (psshNode) { 33146 var pssh = getContent(psshNode); 33147 var psshBuffer = pssh && decodeB64ToUint8Array$1(pssh); 33148 acc[keySystem].pssh = psshBuffer; 33149 } 33150 } 33151 33152 return acc; 33153 }, {}); 33154 }; 33155 /** 33156 * Maps an AdaptationSet node to a list of Representation information objects 33157 * 33158 * @name toRepresentationsCallback 33159 * @function 33160 * @param {Node} adaptationSet 33161 * AdaptationSet node from the mpd 33162 * @return {RepresentationInformation[]} 33163 * List of objects containing Representaion information 33164 */ 33165 33166 /** 33167 * Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of 33168 * Representation information objects 33169 * 33170 * @param {Object} periodAttributes 33171 * Contains attributes inherited by the Period 33172 * @param {string[]} periodBaseUrls 33173 * Contains list of resolved base urls inherited by the Period 33174 * @param {string[]} periodSegmentInfo 33175 * Contains Segment Information at the period level 33176 * @return {toRepresentationsCallback} 33177 * Callback map function 33178 */ 33179 33180 33181 var toRepresentations = function toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo) { 33182 return function (adaptationSet) { 33183 var adaptationSetAttributes = parseAttributes$1(adaptationSet); 33184 var adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL')); 33185 var role = findChildren(adaptationSet, 'Role')[0]; 33186 var roleAttributes = { 33187 role: parseAttributes$1(role) 33188 }; 33189 var attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes); 33190 var contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection')); 33191 33192 if (Object.keys(contentProtection).length) { 33193 attrs = merge(attrs, { 33194 contentProtection: contentProtection 33195 }); 33196 } 33197 33198 var segmentInfo = getSegmentInformation(adaptationSet); 33199 var representations = findChildren(adaptationSet, 'Representation'); 33200 var adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo); 33201 return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo))); 33202 }; 33203 }; 33204 /** 33205 * Maps an Period node to a list of Representation inforamtion objects for all 33206 * AdaptationSet nodes contained within the Period 33207 * 33208 * @name toAdaptationSetsCallback 33209 * @function 33210 * @param {Node} period 33211 * Period node from the mpd 33212 * @param {number} periodIndex 33213 * Index of the Period within the mpd 33214 * @return {RepresentationInformation[]} 33215 * List of objects containing Representaion information 33216 */ 33217 33218 /** 33219 * Returns a callback for Array.prototype.map for mapping Period nodes to a list of 33220 * Representation information objects 33221 * 33222 * @param {Object} mpdAttributes 33223 * Contains attributes inherited by the mpd 33224 * @param {string[]} mpdBaseUrls 33225 * Contains list of resolved base urls inherited by the mpd 33226 * @return {toAdaptationSetsCallback} 33227 * Callback map function 33228 */ 33229 33230 33231 var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) { 33232 return function (period, index) { 33233 var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period, 'BaseURL')); 33234 var periodAtt = parseAttributes$1(period); 33235 var parsedPeriodId = parseInt(periodAtt.id, 10); // fallback to mapping index if Period@id is not a number 33236 33237 var periodIndex = window$1.isNaN(parsedPeriodId) ? index : parsedPeriodId; 33238 var periodAttributes = merge(mpdAttributes, { 33239 periodIndex: periodIndex 33240 }); 33241 var adaptationSets = findChildren(period, 'AdaptationSet'); 33242 var periodSegmentInfo = getSegmentInformation(period); 33243 return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo))); 33244 }; 33245 }; 33246 /** 33247 * Traverses the mpd xml tree to generate a list of Representation information objects 33248 * that have inherited attributes from parent nodes 33249 * 33250 * @param {Node} mpd 33251 * The root node of the mpd 33252 * @param {Object} options 33253 * Available options for inheritAttributes 33254 * @param {string} options.manifestUri 33255 * The uri source of the mpd 33256 * @param {number} options.NOW
vendor: 6,393 bytes, lines 33257-33456
33257 * Current time per DASH IOP. Default is current time in ms since epoch 33258 * @param {number} options.clientOffset 33259 * Client time difference from NOW (in milliseconds) 33260 * @return {RepresentationInformation[]} 33261 * List of objects containing Representation information 33262 */ 33263 33264 33265 var inheritAttributes = function inheritAttributes(mpd, options) { 33266 if (options === void 0) { 33267 options = {}; 33268 } 33269 33270 var _options = options, 33271 _options$manifestUri = _options.manifestUri, 33272 manifestUri = _options$manifestUri === void 0 ? '' : _options$manifestUri, 33273 _options$NOW = _options.NOW, 33274 NOW = _options$NOW === void 0 ? Date.now() : _options$NOW, 33275 _options$clientOffset = _options.clientOffset, 33276 clientOffset = _options$clientOffset === void 0 ? 0 : _options$clientOffset; 33277 var periods = findChildren(mpd, 'Period'); 33278 33279 if (!periods.length) { 33280 throw new Error(errors.INVALID_NUMBER_OF_PERIOD); 33281 } 33282 33283 var mpdAttributes = parseAttributes$1(mpd); 33284 var mpdBaseUrls = buildBaseUrls([manifestUri], findChildren(mpd, 'BaseURL')); 33285 mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0; 33286 mpdAttributes.NOW = NOW; 33287 mpdAttributes.clientOffset = clientOffset; 33288 return flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))); 33289 }; 33290 33291 var stringToMpdXml = function stringToMpdXml(manifestString) { 33292 if (manifestString === '') { 33293 throw new Error(errors.DASH_EMPTY_MANIFEST); 33294 } 33295 33296 var parser = new window$1.DOMParser(); 33297 var xml = parser.parseFromString(manifestString, 'application/xml'); 33298 var mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null; 33299 33300 if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) { 33301 throw new Error(errors.DASH_INVALID_XML); 33302 } 33303 33304 return mpd; 33305 }; 33306 /** 33307 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 33308 * 33309 * @param {string} mpd 33310 * XML string of the MPD manifest 33311 * @return {Object|null} 33312 * Attributes of UTCTiming node specified in the manifest. Null if none found 33313 */ 33314 33315 33316 var parseUTCTimingScheme = function parseUTCTimingScheme(mpd) { 33317 var UTCTimingNode = findChildren(mpd, 'UTCTiming')[0]; 33318 33319 if (!UTCTimingNode) { 33320 return null; 33321 } 33322 33323 var attributes = parseAttributes$1(UTCTimingNode); 33324 33325 switch (attributes.schemeIdUri) { 33326 case 'urn:mpeg:dash:utc:http-head:2014': 33327 case 'urn:mpeg:dash:utc:http-head:2012': 33328 attributes.method = 'HEAD'; 33329 break; 33330 33331 case 'urn:mpeg:dash:utc:http-xsdate:2014': 33332 case 'urn:mpeg:dash:utc:http-iso:2014': 33333 case 'urn:mpeg:dash:utc:http-xsdate:2012': 33334 case 'urn:mpeg:dash:utc:http-iso:2012': 33335 attributes.method = 'GET'; 33336 break; 33337 33338 case 'urn:mpeg:dash:utc:direct:2014': 33339 case 'urn:mpeg:dash:utc:direct:2012': 33340 attributes.method = 'DIRECT'; 33341 attributes.value = Date.parse(attributes.value); 33342 break; 33343 33344 case 'urn:mpeg:dash:utc:http-ntp:2014': 33345 case 'urn:mpeg:dash:utc:ntp:2014': 33346 case 'urn:mpeg:dash:utc:sntp:2014': 33347 default: 33348 throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME); 33349 } 33350 33351 return attributes; 33352 }; 33353 33354 var parse = function parse(manifestString, options) { 33355 if (options === void 0) { 33356 options = {}; 33357 } 33358 33359 return toM3u8(toPlaylists(inheritAttributes(stringToMpdXml(manifestString), options)), options.sidxMapping); 33360 }; 33361 /** 33362 * Parses the manifest for a UTCTiming node, returning the nodes attributes if found 33363 * 33364 * @param {string} manifestString 33365 * XML string of the MPD manifest 33366 * @return {Object|null} 33367 * Attributes of UTCTiming node specified in the manifest. Null if none found 33368 */ 33369 33370 33371 var parseUTCTiming = function parseUTCTiming(manifestString) { 33372 return parseUTCTimingScheme(stringToMpdXml(manifestString)); 33373 }; 33374 33375 /** 33376 * mux.js 33377 * 33378 * Copyright (c) Brightcove 33379 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 33380 */ 33381 var toUnsigned = function toUnsigned(value) { 33382 return value >>> 0; 33383 }; 33384 33385 var toHexString = function toHexString(value) { 33386 return ('00' + value.toString(16)).slice(-2); 33387 }; 33388 33389 var bin = { 33390 toUnsigned: toUnsigned, 33391 toHexString: toHexString 33392 }; 33393 33394 var toUnsigned$1 = bin.toUnsigned; 33395 var toHexString$1 = bin.toHexString; 33396 33397 var _findBox, parseType, timescale, startTime, getVideoTrackIds, getTracks; // Find the data for a box specified by its path 33398 33399 33400 _findBox = function findBox(data, path) { 33401 var results = [], 33402 i, 33403 size, 33404 type, 33405 end, 33406 subresults; 33407 33408 if (!path.length) { 33409 // short-circuit the search for empty paths 33410 return null; 33411 } 33412 33413 for (i = 0; i < data.byteLength;) { 33414 size = toUnsigned$1(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]); 33415 type = parseType(data.subarray(i + 4, i + 8)); 33416 end = size > 1 ? i + size : data.byteLength; 33417 33418 if (type === path[0]) { 33419 if (path.length === 1) { 33420 // this is the end of the path and we've found the box we were 33421 // looking for 33422 results.push(data.subarray(i + 8, end)); 33423 } else { 33424 // recursively search for the next box along the path 33425 subresults = _findBox(data.subarray(i + 8, end), path.slice(1)); 33426 33427 if (subresults.length) { 33428 results = results.concat(subresults); 33429 } 33430 } 33431 } 33432 33433 i = end; 33434 } // we've finished searching all of data 33435 33436 33437 return results; 33438 }; 33439 /** 33440 * Returns the string representation of an ASCII encoded four byte buffer. 33441 * @param buffer {Uint8Array} a four-byte buffer to translate 33442 * @return {string} the corresponding string 33443 */ 33444 33445 33446 parseType = function parseType(buffer) { 33447 var result = ''; 33448 result += String.fromCharCode(buffer[0]); 33449 result += String.fromCharCode(buffer[1]); 33450 result += String.fromCharCode(buffer[2]); 33451 result += String.fromCharCode(buffer[3]); 33452 return result; 33453 }; 33454 /** 33455 * Parses an MP4 initialization segment and extracts the timescale 33456 * values for any declared tracks. Timescale values indicate the
vendor: 4,143 bytes, lines 33457-33575
33457 * number of clock ticks per second to assume for time-based values 33458 * elsewhere in the MP4. 33459 * 33460 * To determine the start time of an MP4, you need two pieces of 33461 * information: the timescale unit and the earliest base media decode 33462 * time. Multiple timescales can be specified within an MP4 but the 33463 * base media decode time is always expressed in the timescale from 33464 * the media header box for the track: 33465 * ``` 33466 * moov > trak > mdia > mdhd.timescale 33467 * ``` 33468 * @param init {Uint8Array} the bytes of the init segment 33469 * @return {object} a hash of track ids to timescale values or null if 33470 * the init segment is malformed. 33471 */ 33472 33473 33474 timescale = function timescale(init) { 33475 var result = {}, 33476 traks = _findBox(init, ['moov', 'trak']); // mdhd timescale 33477 33478 33479 return traks.reduce(function (result, trak) { 33480 var tkhd, version, index, id, mdhd; 33481 tkhd = _findBox(trak, ['tkhd'])[0]; 33482 33483 if (!tkhd) { 33484 return null; 33485 } 33486 33487 version = tkhd[0]; 33488 index = version === 0 ? 12 : 20; 33489 id = toUnsigned$1(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]); 33490 mdhd = _findBox(trak, ['mdia', 'mdhd'])[0]; 33491 33492 if (!mdhd) { 33493 return null; 33494 } 33495 33496 version = mdhd[0]; 33497 index = version === 0 ? 12 : 20; 33498 result[id] = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 33499 return result; 33500 }, result); 33501 }; 33502 /** 33503 * Determine the base media decode start time, in seconds, for an MP4 33504 * fragment. If multiple fragments are specified, the earliest time is 33505 * returned. 33506 * 33507 * The base media decode time can be parsed from track fragment 33508 * metadata: 33509 * ``` 33510 * moof > traf > tfdt.baseMediaDecodeTime 33511 * ``` 33512 * It requires the timescale value from the mdhd to interpret. 33513 * 33514 * @param timescale {object} a hash of track ids to timescale values. 33515 * @return {number} the earliest base media decode start time for the 33516 * fragment, in seconds 33517 */ 33518 33519 33520 startTime = function startTime(timescale, fragment) { 33521 var trafs, baseTimes, result; // we need info from two childrend of each track fragment box 33522 33523 trafs = _findBox(fragment, ['moof', 'traf']); // determine the start times for each track 33524 33525 baseTimes = [].concat.apply([], trafs.map(function (traf) { 33526 return _findBox(traf, ['tfhd']).map(function (tfhd) { 33527 var id, scale, baseTime; // get the track id from the tfhd 33528 33529 id = toUnsigned$1(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified 33530 33531 scale = timescale[id] || 90e3; // get the base media decode time from the tfdt 33532 33533 baseTime = _findBox(traf, ['tfdt']).map(function (tfdt) { 33534 var version, result; 33535 version = tfdt[0]; 33536 result = toUnsigned$1(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]); 33537 33538 if (version === 1) { 33539 result *= Math.pow(2, 32); 33540 result += toUnsigned$1(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]); 33541 } 33542 33543 return result; 33544 })[0]; 33545 baseTime = baseTime || Infinity; // convert base time to seconds 33546 33547 return baseTime / scale; 33548 }); 33549 })); // return the minimum 33550 33551 result = Math.min.apply(null, baseTimes); 33552 return isFinite(result) ? result : 0; 33553 }; 33554 /** 33555 * Find the trackIds of the video tracks in this source. 33556 * Found by parsing the Handler Reference and Track Header Boxes: 33557 * moov > trak > mdia > hdlr 33558 * moov > trak > tkhd 33559 * 33560 * @param {Uint8Array} init - The bytes of the init segment for this source 33561 * @return {Number[]} A list of trackIds 33562 * 33563 * @see ISO-BMFF-12/2015, Section 8.4.3 33564 **/ 33565 33566 33567 getVideoTrackIds = function getVideoTrackIds(init) { 33568 var traks = _findBox(init, ['moov', 'trak']); 33569 33570 var videoTrackIds = []; 33571 traks.forEach(function (trak) { 33572 var hdlrs = _findBox(trak, ['mdia', 'hdlr']); 33573 33574 var tkhds = _findBox(trak, ['tkhd']); 33575
vendor: 12,860 bytes, lines 33576-33972
33576 hdlrs.forEach(function (hdlr, index) { 33577 var handlerType = parseType(hdlr.subarray(8, 12)); 33578 var tkhd = tkhds[index]; 33579 var view; 33580 var version; 33581 var trackId; 33582 33583 if (handlerType === 'vide') { 33584 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 33585 version = view.getUint8(0); 33586 trackId = version === 0 ? view.getUint32(12) : view.getUint32(20); 33587 videoTrackIds.push(trackId); 33588 } 33589 }); 33590 }); 33591 return videoTrackIds; 33592 }; 33593 /** 33594 * Get all the video, audio, and hint tracks from a non fragmented 33595 * mp4 segment 33596 */ 33597 33598 33599 getTracks = function getTracks(init) { 33600 var traks = _findBox(init, ['moov', 'trak']); 33601 33602 var tracks = []; 33603 traks.forEach(function (trak) { 33604 var track = {}; 33605 33606 var tkhd = _findBox(trak, ['tkhd'])[0]; 33607 33608 var view, version; // id 33609 33610 if (tkhd) { 33611 view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength); 33612 version = view.getUint8(0); 33613 track.id = version === 0 ? view.getUint32(12) : view.getUint32(20); 33614 } 33615 33616 var hdlr = _findBox(trak, ['mdia', 'hdlr'])[0]; // type 33617 33618 33619 if (hdlr) { 33620 var type = parseType(hdlr.subarray(8, 12)); 33621 33622 if (type === 'vide') { 33623 track.type = 'video'; 33624 } else if (type === 'soun') { 33625 track.type = 'audio'; 33626 } else { 33627 track.type = type; 33628 } 33629 } // codec 33630 33631 33632 var stsd = _findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; 33633 33634 if (stsd) { 33635 var sampleDescriptions = stsd.subarray(8); // gives the codec type string 33636 33637 track.codec = parseType(sampleDescriptions.subarray(4, 8)); 33638 33639 var codecBox = _findBox(sampleDescriptions, [track.codec])[0]; 33640 33641 var codecConfig, codecConfigType; 33642 33643 if (codecBox) { 33644 // https://tools.ietf.org/html/rfc6381#section-3.3 33645 if (/^[a-z]vc[1-9]$/i.test(track.codec)) { 33646 // we don't need anything but the "config" parameter of the 33647 // avc1 codecBox 33648 codecConfig = codecBox.subarray(78); 33649 codecConfigType = parseType(codecConfig.subarray(4, 8)); 33650 33651 if (codecConfigType === 'avcC' && codecConfig.length > 11) { 33652 track.codec += '.'; // left padded with zeroes for single digit hex 33653 // profile idc 33654 33655 track.codec += toHexString$1(codecConfig[9]); // the byte containing the constraint_set flags 33656 33657 track.codec += toHexString$1(codecConfig[10]); // level idc 33658 33659 track.codec += toHexString$1(codecConfig[11]); 33660 } else { 33661 // TODO: show a warning that we couldn't parse the codec 33662 // and are using the default 33663 track.codec = 'avc1.4d400d'; 33664 } 33665 } else if (/^mp4[a,v]$/i.test(track.codec)) { 33666 // we do not need anything but the streamDescriptor of the mp4a codecBox 33667 codecConfig = codecBox.subarray(28); 33668 codecConfigType = parseType(codecConfig.subarray(4, 8)); 33669 33670 if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) { 33671 track.codec += '.' + toHexString$1(codecConfig[19]); // this value is only a single digit 33672 33673 track.codec += '.' + toHexString$1(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, ''); 33674 } else { 33675 // TODO: show a warning that we couldn't parse the codec 33676 // and are using the default 33677 track.codec = 'mp4a.40.2'; 33678 } 33679 } 33680 } 33681 } 33682 33683 var mdhd = _findBox(trak, ['mdia', 'mdhd'])[0]; 33684 33685 if (mdhd && tkhd) { 33686 var index = version === 0 ? 12 : 20; 33687 track.timescale = toUnsigned$1(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]); 33688 } 33689 33690 tracks.push(track); 33691 }); 33692 return tracks; 33693 }; 33694 33695 var probe = { 33696 findBox: _findBox, 33697 parseType: parseType, 33698 timescale: timescale, 33699 startTime: startTime, 33700 videoTrackIds: getVideoTrackIds, 33701 tracks: getTracks 33702 }; 33703 33704 var inspectMp4, 33705 _textifyMp, 33706 parseType$1 = probe.parseType, 33707 parseMp4Date = function parseMp4Date(seconds) { 33708 return new Date(seconds * 1000 - 2082844800000); 33709 }, 33710 parseSampleFlags = function parseSampleFlags(flags) { 33711 return { 33712 isLeading: (flags[0] & 0x0c) >>> 2, 33713 dependsOn: flags[0] & 0x03, 33714 isDependedOn: (flags[1] & 0xc0) >>> 6, 33715 hasRedundancy: (flags[1] & 0x30) >>> 4, 33716 paddingValue: (flags[1] & 0x0e) >>> 1, 33717 isNonSyncSample: flags[1] & 0x01, 33718 degradationPriority: flags[2] << 8 | flags[3] 33719 }; 33720 }, 33721 nalParse = function nalParse(avcStream) { 33722 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 33723 result = [], 33724 i, 33725 length; 33726 33727 for (i = 0; i + 4 < avcStream.length; i += length) { 33728 length = avcView.getUint32(i); 33729 i += 4; // bail if this doesn't appear to be an H264 stream 33730 33731 if (length <= 0) { 33732 result.push('<span style=\'color:red;\'>MALFORMED DATA</span>'); 33733 continue; 33734 } 33735 33736 switch (avcStream[i] & 0x1F) { 33737 case 0x01: 33738 result.push('slice_layer_without_partitioning_rbsp'); 33739 break; 33740 33741 case 0x05: 33742 result.push('slice_layer_without_partitioning_rbsp_idr'); 33743 break; 33744 33745 case 0x06: 33746 result.push('sei_rbsp'); 33747 break; 33748 33749 case 0x07: 33750 result.push('seq_parameter_set_rbsp'); 33751 break; 33752 33753 case 0x08: 33754 result.push('pic_parameter_set_rbsp'); 33755 break; 33756 33757 case 0x09: 33758 result.push('access_unit_delimiter_rbsp'); 33759 break; 33760 33761 default: 33762 result.push('UNKNOWN NAL - ' + avcStream[i] & 0x1F); 33763 break; 33764 } 33765 } 33766 33767 return result; 33768 }, 33769 // registry of handlers for individual mp4 box types 33770 parse$1 = { 33771 // codingname, not a first-class box type. stsd entries share the 33772 // same format as real boxes so the parsing infrastructure can be 33773 // shared 33774 avc1: function avc1(data) { 33775 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 33776 return { 33777 dataReferenceIndex: view.getUint16(6), 33778 width: view.getUint16(24), 33779 height: view.getUint16(26), 33780 horizresolution: view.getUint16(28) + view.getUint16(30) / 16, 33781 vertresolution: view.getUint16(32) + view.getUint16(34) / 16, 33782 frameCount: view.getUint16(40), 33783 depth: view.getUint16(74), 33784 config: inspectMp4(data.subarray(78, data.byteLength)) 33785 }; 33786 }, 33787 avcC: function avcC(data) { 33788 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33789 result = { 33790 configurationVersion: data[0], 33791 avcProfileIndication: data[1], 33792 profileCompatibility: data[2], 33793 avcLevelIndication: data[3], 33794 lengthSizeMinusOne: data[4] & 0x03, 33795 sps: [], 33796 pps: [] 33797 }, 33798 numOfSequenceParameterSets = data[5] & 0x1f, 33799 numOfPictureParameterSets, 33800 nalSize, 33801 offset, 33802 i; // iterate past any SPSs 33803 33804 offset = 6; 33805 33806 for (i = 0; i < numOfSequenceParameterSets; i++) { 33807 nalSize = view.getUint16(offset); 33808 offset += 2; 33809 result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 33810 offset += nalSize; 33811 } // iterate past any PPSs 33812 33813 33814 numOfPictureParameterSets = data[offset]; 33815 offset++; 33816 33817 for (i = 0; i < numOfPictureParameterSets; i++) { 33818 nalSize = view.getUint16(offset); 33819 offset += 2; 33820 result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize))); 33821 offset += nalSize; 33822 } 33823 33824 return result; 33825 }, 33826 btrt: function btrt(data) { 33827 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 33828 return { 33829 bufferSizeDB: view.getUint32(0), 33830 maxBitrate: view.getUint32(4), 33831 avgBitrate: view.getUint32(8) 33832 }; 33833 }, 33834 esds: function esds(data) { 33835 return { 33836 version: data[0], 33837 flags: new Uint8Array(data.subarray(1, 4)), 33838 esId: data[6] << 8 | data[7], 33839 streamPriority: data[8] & 0x1f, 33840 decoderConfig: { 33841 objectProfileIndication: data[11], 33842 streamType: data[12] >>> 2 & 0x3f, 33843 bufferSize: data[13] << 16 | data[14] << 8 | data[15], 33844 maxBitrate: data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19], 33845 avgBitrate: data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23], 33846 decoderConfigDescriptor: { 33847 tag: data[24], 33848 length: data[25], 33849 audioObjectType: data[26] >>> 3 & 0x1f, 33850 samplingFrequencyIndex: (data[26] & 0x07) << 1 | data[27] >>> 7 & 0x01, 33851 channelConfiguration: data[27] >>> 3 & 0x0f 33852 } 33853 } 33854 }; 33855 }, 33856 ftyp: function ftyp(data) { 33857 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33858 result = { 33859 majorBrand: parseType$1(data.subarray(0, 4)), 33860 minorVersion: view.getUint32(4), 33861 compatibleBrands: [] 33862 }, 33863 i = 8; 33864 33865 while (i < data.byteLength) { 33866 result.compatibleBrands.push(parseType$1(data.subarray(i, i + 4))); 33867 i += 4; 33868 } 33869 33870 return result; 33871 }, 33872 dinf: function dinf(data) { 33873 return { 33874 boxes: inspectMp4(data) 33875 }; 33876 }, 33877 dref: function dref(data) { 33878 return { 33879 version: data[0], 33880 flags: new Uint8Array(data.subarray(1, 4)), 33881 dataReferences: inspectMp4(data.subarray(8)) 33882 }; 33883 }, 33884 hdlr: function hdlr(data) { 33885 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33886 result = { 33887 version: view.getUint8(0), 33888 flags: new Uint8Array(data.subarray(1, 4)), 33889 handlerType: parseType$1(data.subarray(8, 12)), 33890 name: '' 33891 }, 33892 i = 8; // parse out the name field 33893 33894 for (i = 24; i < data.byteLength; i++) { 33895 if (data[i] === 0x00) { 33896 // the name field is null-terminated 33897 i++; 33898 break; 33899 } 33900 33901 result.name += String.fromCharCode(data[i]); 33902 } // decode UTF-8 to javascript's internal representation 33903 // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html 33904 33905 33906 result.name = decodeURIComponent(escape(result.name)); 33907 return result; 33908 }, 33909 mdat: function mdat(data) { 33910 return { 33911 byteLength: data.byteLength, 33912 nals: nalParse(data) 33913 }; 33914 }, 33915 mdhd: function mdhd(data) { 33916 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33917 i = 4, 33918 language, 33919 result = { 33920 version: view.getUint8(0), 33921 flags: new Uint8Array(data.subarray(1, 4)), 33922 language: '' 33923 }; 33924 33925 if (result.version === 1) { 33926 i += 4; 33927 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 33928 33929 i += 8; 33930 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 33931 33932 i += 4; 33933 result.timescale = view.getUint32(i); 33934 i += 8; 33935 result.duration = view.getUint32(i); // truncating top 4 bytes 33936 } else { 33937 result.creationTime = parseMp4Date(view.getUint32(i)); 33938 i += 4; 33939 result.modificationTime = parseMp4Date(view.getUint32(i)); 33940 i += 4; 33941 result.timescale = view.getUint32(i); 33942 i += 4; 33943 result.duration = view.getUint32(i); 33944 } 33945 33946 i += 4; // language is stored as an ISO-639-2/T code in an array of three 5-bit fields 33947 // each field is the packed difference between its ASCII value and 0x60 33948 33949 language = view.getUint16(i); 33950 result.language += String.fromCharCode((language >> 10) + 0x60); 33951 result.language += String.fromCharCode(((language & 0x03e0) >> 5) + 0x60); 33952 result.language += String.fromCharCode((language & 0x1f) + 0x60); 33953 return result; 33954 }, 33955 mdia: function mdia(data) { 33956 return { 33957 boxes: inspectMp4(data) 33958 }; 33959 }, 33960 mfhd: function mfhd(data) { 33961 return { 33962 version: data[0], 33963 flags: new Uint8Array(data.subarray(1, 4)), 33964 sequenceNumber: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] 33965 }; 33966 }, 33967 minf: function minf(data) { 33968 return { 33969 boxes: inspectMp4(data) 33970 }; 33971 }, 33972 // codingname, not a first-class box type. stsd entries share the
vendor: 31,486 bytes, lines 33973-34974
33973 // same format as real boxes so the parsing infrastructure can be 33974 // shared 33975 mp4a: function mp4a(data) { 33976 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 33977 result = { 33978 // 6 bytes reserved 33979 dataReferenceIndex: view.getUint16(6), 33980 // 4 + 4 bytes reserved 33981 channelcount: view.getUint16(16), 33982 samplesize: view.getUint16(18), 33983 // 2 bytes pre_defined 33984 // 2 bytes reserved 33985 samplerate: view.getUint16(24) + view.getUint16(26) / 65536 33986 }; // if there are more bytes to process, assume this is an ISO/IEC 33987 // 14496-14 MP4AudioSampleEntry and parse the ESDBox 33988 33989 if (data.byteLength > 28) { 33990 result.streamDescriptor = inspectMp4(data.subarray(28))[0]; 33991 } 33992 33993 return result; 33994 }, 33995 moof: function moof(data) { 33996 return { 33997 boxes: inspectMp4(data) 33998 }; 33999 }, 34000 moov: function moov(data) { 34001 return { 34002 boxes: inspectMp4(data) 34003 }; 34004 }, 34005 mvex: function mvex(data) { 34006 return { 34007 boxes: inspectMp4(data) 34008 }; 34009 }, 34010 mvhd: function mvhd(data) { 34011 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34012 i = 4, 34013 result = { 34014 version: view.getUint8(0), 34015 flags: new Uint8Array(data.subarray(1, 4)) 34016 }; 34017 34018 if (result.version === 1) { 34019 i += 4; 34020 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 34021 34022 i += 8; 34023 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 34024 34025 i += 4; 34026 result.timescale = view.getUint32(i); 34027 i += 8; 34028 result.duration = view.getUint32(i); // truncating top 4 bytes 34029 } else { 34030 result.creationTime = parseMp4Date(view.getUint32(i)); 34031 i += 4; 34032 result.modificationTime = parseMp4Date(view.getUint32(i)); 34033 i += 4; 34034 result.timescale = view.getUint32(i); 34035 i += 4; 34036 result.duration = view.getUint32(i); 34037 } 34038 34039 i += 4; // convert fixed-point, base 16 back to a number 34040 34041 result.rate = view.getUint16(i) + view.getUint16(i + 2) / 16; 34042 i += 4; 34043 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 34044 i += 2; 34045 i += 2; 34046 i += 2 * 4; 34047 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 34048 i += 9 * 4; 34049 i += 6 * 4; 34050 result.nextTrackId = view.getUint32(i); 34051 return result; 34052 }, 34053 pdin: function pdin(data) { 34054 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 34055 return { 34056 version: view.getUint8(0), 34057 flags: new Uint8Array(data.subarray(1, 4)), 34058 rate: view.getUint32(4), 34059 initialDelay: view.getUint32(8) 34060 }; 34061 }, 34062 sdtp: function sdtp(data) { 34063 var result = { 34064 version: data[0], 34065 flags: new Uint8Array(data.subarray(1, 4)), 34066 samples: [] 34067 }, 34068 i; 34069 34070 for (i = 4; i < data.byteLength; i++) { 34071 result.samples.push({ 34072 dependsOn: (data[i] & 0x30) >> 4, 34073 isDependedOn: (data[i] & 0x0c) >> 2, 34074 hasRedundancy: data[i] & 0x03 34075 }); 34076 } 34077 34078 return result; 34079 }, 34080 sidx: function sidx(data) { 34081 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34082 result = { 34083 version: data[0], 34084 flags: new Uint8Array(data.subarray(1, 4)), 34085 references: [], 34086 referenceId: view.getUint32(4), 34087 timescale: view.getUint32(8), 34088 earliestPresentationTime: view.getUint32(12), 34089 firstOffset: view.getUint32(16) 34090 }, 34091 referenceCount = view.getUint16(22), 34092 i; 34093 34094 for (i = 24; referenceCount; i += 12, referenceCount--) { 34095 result.references.push({ 34096 referenceType: (data[i] & 0x80) >>> 7, 34097 referencedSize: view.getUint32(i) & 0x7FFFFFFF, 34098 subsegmentDuration: view.getUint32(i + 4), 34099 startsWithSap: !!(data[i + 8] & 0x80), 34100 sapType: (data[i + 8] & 0x70) >>> 4, 34101 sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF 34102 }); 34103 } 34104 34105 return result; 34106 }, 34107 smhd: function smhd(data) { 34108 return { 34109 version: data[0], 34110 flags: new Uint8Array(data.subarray(1, 4)), 34111 balance: data[4] + data[5] / 256 34112 }; 34113 }, 34114 stbl: function stbl(data) { 34115 return { 34116 boxes: inspectMp4(data) 34117 }; 34118 }, 34119 stco: function stco(data) { 34120 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34121 result = { 34122 version: data[0], 34123 flags: new Uint8Array(data.subarray(1, 4)), 34124 chunkOffsets: [] 34125 }, 34126 entryCount = view.getUint32(4), 34127 i; 34128 34129 for (i = 8; entryCount; i += 4, entryCount--) { 34130 result.chunkOffsets.push(view.getUint32(i)); 34131 } 34132 34133 return result; 34134 }, 34135 stsc: function stsc(data) { 34136 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34137 entryCount = view.getUint32(4), 34138 result = { 34139 version: data[0], 34140 flags: new Uint8Array(data.subarray(1, 4)), 34141 sampleToChunks: [] 34142 }, 34143 i; 34144 34145 for (i = 8; entryCount; i += 12, entryCount--) { 34146 result.sampleToChunks.push({ 34147 firstChunk: view.getUint32(i), 34148 samplesPerChunk: view.getUint32(i + 4), 34149 sampleDescriptionIndex: view.getUint32(i + 8) 34150 }); 34151 } 34152 34153 return result; 34154 }, 34155 stsd: function stsd(data) { 34156 return { 34157 version: data[0], 34158 flags: new Uint8Array(data.subarray(1, 4)), 34159 sampleDescriptions: inspectMp4(data.subarray(8)) 34160 }; 34161 }, 34162 stsz: function stsz(data) { 34163 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34164 result = { 34165 version: data[0], 34166 flags: new Uint8Array(data.subarray(1, 4)), 34167 sampleSize: view.getUint32(4), 34168 entries: [] 34169 }, 34170 i; 34171 34172 for (i = 12; i < data.byteLength; i += 4) { 34173 result.entries.push(view.getUint32(i)); 34174 } 34175 34176 return result; 34177 }, 34178 stts: function stts(data) { 34179 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34180 result = { 34181 version: data[0], 34182 flags: new Uint8Array(data.subarray(1, 4)), 34183 timeToSamples: [] 34184 }, 34185 entryCount = view.getUint32(4), 34186 i; 34187 34188 for (i = 8; entryCount; i += 8, entryCount--) { 34189 result.timeToSamples.push({ 34190 sampleCount: view.getUint32(i), 34191 sampleDelta: view.getUint32(i + 4) 34192 }); 34193 } 34194 34195 return result; 34196 }, 34197 styp: function styp(data) { 34198 return parse$1.ftyp(data); 34199 }, 34200 tfdt: function tfdt(data) { 34201 var result = { 34202 version: data[0], 34203 flags: new Uint8Array(data.subarray(1, 4)), 34204 baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7] 34205 }; 34206 34207 if (result.version === 1) { 34208 result.baseMediaDecodeTime *= Math.pow(2, 32); 34209 result.baseMediaDecodeTime += data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]; 34210 } 34211 34212 return result; 34213 }, 34214 tfhd: function tfhd(data) { 34215 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34216 result = { 34217 version: data[0], 34218 flags: new Uint8Array(data.subarray(1, 4)), 34219 trackId: view.getUint32(4) 34220 }, 34221 baseDataOffsetPresent = result.flags[2] & 0x01, 34222 sampleDescriptionIndexPresent = result.flags[2] & 0x02, 34223 defaultSampleDurationPresent = result.flags[2] & 0x08, 34224 defaultSampleSizePresent = result.flags[2] & 0x10, 34225 defaultSampleFlagsPresent = result.flags[2] & 0x20, 34226 durationIsEmpty = result.flags[0] & 0x010000, 34227 defaultBaseIsMoof = result.flags[0] & 0x020000, 34228 i; 34229 i = 8; 34230 34231 if (baseDataOffsetPresent) { 34232 i += 4; // truncate top 4 bytes 34233 // FIXME: should we read the full 64 bits? 34234 34235 result.baseDataOffset = view.getUint32(12); 34236 i += 4; 34237 } 34238 34239 if (sampleDescriptionIndexPresent) { 34240 result.sampleDescriptionIndex = view.getUint32(i); 34241 i += 4; 34242 } 34243 34244 if (defaultSampleDurationPresent) { 34245 result.defaultSampleDuration = view.getUint32(i); 34246 i += 4; 34247 } 34248 34249 if (defaultSampleSizePresent) { 34250 result.defaultSampleSize = view.getUint32(i); 34251 i += 4; 34252 } 34253 34254 if (defaultSampleFlagsPresent) { 34255 result.defaultSampleFlags = view.getUint32(i); 34256 } 34257 34258 if (durationIsEmpty) { 34259 result.durationIsEmpty = true; 34260 } 34261 34262 if (!baseDataOffsetPresent && defaultBaseIsMoof) { 34263 result.baseDataOffsetIsMoof = true; 34264 } 34265 34266 return result; 34267 }, 34268 tkhd: function tkhd(data) { 34269 var view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34270 i = 4, 34271 result = { 34272 version: view.getUint8(0), 34273 flags: new Uint8Array(data.subarray(1, 4)) 34274 }; 34275 34276 if (result.version === 1) { 34277 i += 4; 34278 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 34279 34280 i += 8; 34281 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes 34282 34283 i += 4; 34284 result.trackId = view.getUint32(i); 34285 i += 4; 34286 i += 8; 34287 result.duration = view.getUint32(i); // truncating top 4 bytes 34288 } else { 34289 result.creationTime = parseMp4Date(view.getUint32(i)); 34290 i += 4; 34291 result.modificationTime = parseMp4Date(view.getUint32(i)); 34292 i += 4; 34293 result.trackId = view.getUint32(i); 34294 i += 4; 34295 i += 4; 34296 result.duration = view.getUint32(i); 34297 } 34298 34299 i += 4; 34300 i += 2 * 4; 34301 result.layer = view.getUint16(i); 34302 i += 2; 34303 result.alternateGroup = view.getUint16(i); 34304 i += 2; // convert fixed-point, base 16 back to a number 34305 34306 result.volume = view.getUint8(i) + view.getUint8(i + 1) / 8; 34307 i += 2; 34308 i += 2; 34309 result.matrix = new Uint32Array(data.subarray(i, i + 9 * 4)); 34310 i += 9 * 4; 34311 result.width = view.getUint16(i) + view.getUint16(i + 2) / 16; 34312 i += 4; 34313 result.height = view.getUint16(i) + view.getUint16(i + 2) / 16; 34314 return result; 34315 }, 34316 traf: function traf(data) { 34317 return { 34318 boxes: inspectMp4(data) 34319 }; 34320 }, 34321 trak: function trak(data) { 34322 return { 34323 boxes: inspectMp4(data) 34324 }; 34325 }, 34326 trex: function trex(data) { 34327 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 34328 return { 34329 version: data[0], 34330 flags: new Uint8Array(data.subarray(1, 4)), 34331 trackId: view.getUint32(4), 34332 defaultSampleDescriptionIndex: view.getUint32(8), 34333 defaultSampleDuration: view.getUint32(12), 34334 defaultSampleSize: view.getUint32(16), 34335 sampleDependsOn: data[20] & 0x03, 34336 sampleIsDependedOn: (data[21] & 0xc0) >> 6, 34337 sampleHasRedundancy: (data[21] & 0x30) >> 4, 34338 samplePaddingValue: (data[21] & 0x0e) >> 1, 34339 sampleIsDifferenceSample: !!(data[21] & 0x01), 34340 sampleDegradationPriority: view.getUint16(22) 34341 }; 34342 }, 34343 trun: function trun(data) { 34344 var result = { 34345 version: data[0], 34346 flags: new Uint8Array(data.subarray(1, 4)), 34347 samples: [] 34348 }, 34349 view = new DataView(data.buffer, data.byteOffset, data.byteLength), 34350 // Flag interpretation 34351 dataOffsetPresent = result.flags[2] & 0x01, 34352 // compare with 2nd byte of 0x1 34353 firstSampleFlagsPresent = result.flags[2] & 0x04, 34354 // compare with 2nd byte of 0x4 34355 sampleDurationPresent = result.flags[1] & 0x01, 34356 // compare with 2nd byte of 0x100 34357 sampleSizePresent = result.flags[1] & 0x02, 34358 // compare with 2nd byte of 0x200 34359 sampleFlagsPresent = result.flags[1] & 0x04, 34360 // compare with 2nd byte of 0x400 34361 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, 34362 // compare with 2nd byte of 0x800 34363 sampleCount = view.getUint32(4), 34364 offset = 8, 34365 sample; 34366 34367 if (dataOffsetPresent) { 34368 // 32 bit signed integer 34369 result.dataOffset = view.getInt32(offset); 34370 offset += 4; 34371 } // Overrides the flags for the first sample only. The order of 34372 // optional values will be: duration, size, compositionTimeOffset 34373 34374 34375 if (firstSampleFlagsPresent && sampleCount) { 34376 sample = { 34377 flags: parseSampleFlags(data.subarray(offset, offset + 4)) 34378 }; 34379 offset += 4; 34380 34381 if (sampleDurationPresent) { 34382 sample.duration = view.getUint32(offset); 34383 offset += 4; 34384 } 34385 34386 if (sampleSizePresent) { 34387 sample.size = view.getUint32(offset); 34388 offset += 4; 34389 } 34390 34391 if (sampleCompositionTimeOffsetPresent) { 34392 // Note: this should be a signed int if version is 1 34393 sample.compositionTimeOffset = view.getUint32(offset); 34394 offset += 4; 34395 } 34396 34397 result.samples.push(sample); 34398 sampleCount--; 34399 } 34400 34401 while (sampleCount--) { 34402 sample = {}; 34403 34404 if (sampleDurationPresent) { 34405 sample.duration = view.getUint32(offset); 34406 offset += 4; 34407 } 34408 34409 if (sampleSizePresent) { 34410 sample.size = view.getUint32(offset); 34411 offset += 4; 34412 } 34413 34414 if (sampleFlagsPresent) { 34415 sample.flags = parseSampleFlags(data.subarray(offset, offset + 4)); 34416 offset += 4; 34417 } 34418 34419 if (sampleCompositionTimeOffsetPresent) { 34420 // Note: this should be a signed int if version is 1 34421 sample.compositionTimeOffset = view.getUint32(offset); 34422 offset += 4; 34423 } 34424 34425 result.samples.push(sample); 34426 } 34427 34428 return result; 34429 }, 34430 'url ': function url(data) { 34431 return { 34432 version: data[0], 34433 flags: new Uint8Array(data.subarray(1, 4)) 34434 }; 34435 }, 34436 vmhd: function vmhd(data) { 34437 var view = new DataView(data.buffer, data.byteOffset, data.byteLength); 34438 return { 34439 version: data[0], 34440 flags: new Uint8Array(data.subarray(1, 4)), 34441 graphicsmode: view.getUint16(4), 34442 opcolor: new Uint16Array([view.getUint16(6), view.getUint16(8), view.getUint16(10)]) 34443 }; 34444 } 34445 }; 34446 /** 34447 * Return a javascript array of box objects parsed from an ISO base 34448 * media file. 34449 * @param data {Uint8Array} the binary data of the media to be inspected 34450 * @return {array} a javascript array of potentially nested box objects 34451 */ 34452 34453 34454 inspectMp4 = function inspectMp4(data) { 34455 var i = 0, 34456 result = [], 34457 view, 34458 size, 34459 type, 34460 end, 34461 box; // Convert data from Uint8Array to ArrayBuffer, to follow Dataview API 34462 34463 var ab = new ArrayBuffer(data.length); 34464 var v = new Uint8Array(ab); 34465 34466 for (var z = 0; z < data.length; ++z) { 34467 v[z] = data[z]; 34468 } 34469 34470 view = new DataView(ab); 34471 34472 while (i < data.byteLength) { 34473 // parse box data 34474 size = view.getUint32(i); 34475 type = parseType$1(data.subarray(i + 4, i + 8)); 34476 end = size > 1 ? i + size : data.byteLength; // parse type-specific data 34477 34478 box = (parse$1[type] || function (data) { 34479 return { 34480 data: data 34481 }; 34482 })(data.subarray(i + 8, end)); 34483 34484 box.size = size; 34485 box.type = type; // store this box and move to the next 34486 34487 result.push(box); 34488 i = end; 34489 } 34490 34491 return result; 34492 }; 34493 /** 34494 * Returns a textual representation of the javascript represtentation 34495 * of an MP4 file. You can use it as an alternative to 34496 * JSON.stringify() to compare inspected MP4s. 34497 * @param inspectedMp4 {array} the parsed array of boxes in an MP4 34498 * file 34499 * @param depth {number} (optional) the number of ancestor boxes of 34500 * the elements of inspectedMp4. Assumed to be zero if unspecified. 34501 * @return {string} a text representation of the parsed MP4 34502 */ 34503 34504 34505 _textifyMp = function textifyMp4(inspectedMp4, depth) { 34506 var indent; 34507 depth = depth || 0; 34508 indent = new Array(depth * 2 + 1).join(' '); // iterate over all the boxes 34509 34510 return inspectedMp4.map(function (box, index) { 34511 // list the box type first at the current indentation level 34512 return indent + box.type + '\n' + // the type is already included and handle child boxes separately 34513 Object.keys(box).filter(function (key) { 34514 return key !== 'type' && key !== 'boxes'; // output all the box properties 34515 }).map(function (key) { 34516 var prefix = indent + ' ' + key + ': ', 34517 value = box[key]; // print out raw bytes as hexademical 34518 34519 if (value instanceof Uint8Array || value instanceof Uint32Array) { 34520 var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)).map(function (_byte) { 34521 return ' ' + ('00' + _byte.toString(16)).slice(-2); 34522 }).join('').match(/.{1,24}/g); 34523 34524 if (!bytes) { 34525 return prefix + '<>'; 34526 } 34527 34528 if (bytes.length === 1) { 34529 return prefix + '<' + bytes.join('').slice(1) + '>'; 34530 } 34531 34532 return prefix + '<\n' + bytes.map(function (line) { 34533 return indent + ' ' + line; 34534 }).join('\n') + '\n' + indent + ' >'; 34535 } // stringify generic objects 34536 34537 34538 return prefix + JSON.stringify(value, null, 2).split('\n').map(function (line, index) { 34539 if (index === 0) { 34540 return line; 34541 } 34542 34543 return indent + ' ' + line; 34544 }).join('\n'); 34545 }).join('\n') + ( // recursively textify the child boxes 34546 box.boxes ? '\n' + _textifyMp(box.boxes, depth + 1) : ''); 34547 }).join('\n'); 34548 }; 34549 34550 var mp4Inspector = { 34551 inspect: inspectMp4, 34552 textify: _textifyMp, 34553 parseTfdt: parse$1.tfdt, 34554 parseHdlr: parse$1.hdlr, 34555 parseTfhd: parse$1.tfhd, 34556 parseTrun: parse$1.trun, 34557 parseSidx: parse$1.sidx 34558 }; 34559 34560 /** 34561 * mux.js 34562 * 34563 * Copyright (c) Brightcove 34564 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 34565 * 34566 * Reads in-band caption information from a video elementary 34567 * stream. Captions must follow the CEA-708 standard for injection 34568 * into an MPEG-2 transport streams. 34569 * @see https://en.wikipedia.org/wiki/CEA-708 34570 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 34571 */ 34572 // payload type field to indicate how they are to be 34573 // interpreted. CEAS-708 caption content is always transmitted with 34574 // payload type 0x04. 34575 34576 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 34577 RBSP_TRAILING_BITS = 128; 34578 /** 34579 * Parse a supplemental enhancement information (SEI) NAL unit. 34580 * Stops parsing once a message of type ITU T T35 has been found. 34581 * 34582 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 34583 * @return {object} the parsed SEI payload 34584 * @see Rec. ITU-T H.264, 7.3.2.3.1 34585 */ 34586 34587 var parseSei = function parseSei(bytes) { 34588 var i = 0, 34589 result = { 34590 payloadType: -1, 34591 payloadSize: 0 34592 }, 34593 payloadType = 0, 34594 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 34595 34596 while (i < bytes.byteLength) { 34597 // stop once we have hit the end of the sei_rbsp 34598 if (bytes[i] === RBSP_TRAILING_BITS) { 34599 break; 34600 } // Parse payload type 34601 34602 34603 while (bytes[i] === 0xFF) { 34604 payloadType += 255; 34605 i++; 34606 } 34607 34608 payloadType += bytes[i++]; // Parse payload size 34609 34610 while (bytes[i] === 0xFF) { 34611 payloadSize += 255; 34612 i++; 34613 } 34614 34615 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 34616 // there can only ever be one caption message in a frame's sei 34617 34618 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 34619 result.payloadType = payloadType; 34620 result.payloadSize = payloadSize; 34621 result.payload = bytes.subarray(i, i + payloadSize); 34622 break; 34623 } // skip the payload and parse the next message 34624 34625 34626 i += payloadSize; 34627 payloadType = 0; 34628 payloadSize = 0; 34629 } 34630 34631 return result; 34632 }; // see ANSI/SCTE 128-1 (2013), section 8.1 34633 34634 34635 var parseUserData = function parseUserData(sei) { 34636 // itu_t_t35_contry_code must be 181 (United States) for 34637 // captions 34638 if (sei.payload[0] !== 181) { 34639 return null; 34640 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 34641 34642 34643 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 34644 return null; 34645 } // the user_identifier should be "GA94" to indicate ATSC1 data 34646 34647 34648 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 34649 return null; 34650 } // finally, user_data_type_code should be 0x03 for caption data 34651 34652 34653 if (sei.payload[7] !== 0x03) { 34654 return null; 34655 } // return the user_data_type_structure and strip the trailing 34656 // marker bits 34657 34658 34659 return sei.payload.subarray(8, sei.payload.length - 1); 34660 }; // see CEA-708-D, section 4.4 34661 34662 34663 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 34664 var results = [], 34665 i, 34666 count, 34667 offset, 34668 data; // if this is just filler, return immediately 34669 34670 if (!(userData[0] & 0x40)) { 34671 return results; 34672 } // parse out the cc_data_1 and cc_data_2 fields 34673 34674 34675 count = userData[0] & 0x1f; 34676 34677 for (i = 0; i < count; i++) { 34678 offset = i * 3; 34679 data = { 34680 type: userData[offset + 2] & 0x03, 34681 pts: pts 34682 }; // capture cc data when cc_valid is 1 34683 34684 if (userData[offset + 2] & 0x04) { 34685 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 34686 results.push(data); 34687 } 34688 } 34689 34690 return results; 34691 }; 34692 34693 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 34694 var length = data.byteLength, 34695 emulationPreventionBytesPositions = [], 34696 i = 1, 34697 newLength, 34698 newData; // Find all `Emulation Prevention Bytes` 34699 34700 while (i < length - 2) { 34701 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 34702 emulationPreventionBytesPositions.push(i + 2); 34703 i += 2; 34704 } else { 34705 i++; 34706 } 34707 } // If no Emulation Prevention Bytes were found just return the original 34708 // array 34709 34710 34711 if (emulationPreventionBytesPositions.length === 0) { 34712 return data; 34713 } // Create a new array to hold the NAL unit data 34714 34715 34716 newLength = length - emulationPreventionBytesPositions.length; 34717 newData = new Uint8Array(newLength); 34718 var sourceIndex = 0; 34719 34720 for (i = 0; i < newLength; sourceIndex++, i++) { 34721 if (sourceIndex === emulationPreventionBytesPositions[0]) { 34722 // Skip this byte 34723 sourceIndex++; // Remove this position index 34724 34725 emulationPreventionBytesPositions.shift(); 34726 } 34727 34728 newData[i] = data[sourceIndex]; 34729 } 34730 34731 return newData; 34732 }; // exports 34733 34734 34735 var captionPacketParser = { 34736 parseSei: parseSei, 34737 parseUserData: parseUserData, 34738 parseCaptionPackets: parseCaptionPackets, 34739 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 34740 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 34741 }; 34742 34743 /** 34744 * mux.js 34745 * 34746 * Copyright (c) Brightcove 34747 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 34748 * 34749 * A lightweight readable stream implemention that handles event dispatching. 34750 * Objects that inherit from streams should call init in their constructors. 34751 */ 34752 34753 var Stream$1 = function Stream() { 34754 this.init = function () { 34755 var listeners = {}; 34756 /** 34757 * Add a listener for a specified event type. 34758 * @param type {string} the event name 34759 * @param listener {function} the callback to be invoked when an event of 34760 * the specified type occurs 34761 */ 34762 34763 this.on = function (type, listener) { 34764 if (!listeners[type]) { 34765 listeners[type] = []; 34766 } 34767 34768 listeners[type] = listeners[type].concat(listener); 34769 }; 34770 /** 34771 * Remove a listener for a specified event type. 34772 * @param type {string} the event name 34773 * @param listener {function} a function previously registered for this 34774 * type of event through `on` 34775 */ 34776 34777 34778 this.off = function (type, listener) { 34779 var index; 34780 34781 if (!listeners[type]) { 34782 return false; 34783 } 34784 34785 index = listeners[type].indexOf(listener); 34786 listeners[type] = listeners[type].slice(); 34787 listeners[type].splice(index, 1); 34788 return index > -1; 34789 }; 34790 /** 34791 * Trigger an event of the specified type on this stream. Any additional 34792 * arguments to this function are passed as parameters to event listeners. 34793 * @param type {string} the event name 34794 */ 34795 34796 34797 this.trigger = function (type) { 34798 var callbacks, i, length, args; 34799 callbacks = listeners[type]; 34800 34801 if (!callbacks) { 34802 return; 34803 } // Slicing the arguments on every invocation of this method 34804 // can add a significant amount of overhead. Avoid the 34805 // intermediate object creation for the common case of a 34806 // single callback argument 34807 34808 34809 if (arguments.length === 2) { 34810 length = callbacks.length; 34811 34812 for (i = 0; i < length; ++i) { 34813 callbacks[i].call(this, arguments[1]); 34814 } 34815 } else { 34816 args = []; 34817 i = arguments.length; 34818 34819 for (i = 1; i < arguments.length; ++i) { 34820 args.push(arguments[i]); 34821 } 34822 34823 length = callbacks.length; 34824 34825 for (i = 0; i < length; ++i) { 34826 callbacks[i].apply(this, args); 34827 } 34828 } 34829 }; 34830 /** 34831 * Destroys the stream and cleans up. 34832 */ 34833 34834 34835 this.dispose = function () { 34836 listeners = {}; 34837 }; 34838 }; 34839 }; 34840 /** 34841 * Forwards all `data` events on this stream to the destination stream. The 34842 * destination stream should provide a method `push` to receive the data 34843 * events as they arrive. 34844 * @param destination {stream} the stream that will receive all `data` events 34845 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 34846 * when the current stream emits a 'done' event 34847 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 34848 */ 34849 34850 34851 Stream$1.prototype.pipe = function (destination) { 34852 this.on('data', function (data) { 34853 destination.push(data); 34854 }); 34855 this.on('done', function (flushSource) { 34856 destination.flush(flushSource); 34857 }); 34858 this.on('partialdone', function (flushSource) { 34859 destination.partialFlush(flushSource); 34860 }); 34861 this.on('endedtimeline', function (flushSource) { 34862 destination.endTimeline(flushSource); 34863 }); 34864 this.on('reset', function (flushSource) { 34865 destination.reset(flushSource); 34866 }); 34867 return destination; 34868 }; // Default stream functions that are expected to be overridden to perform 34869 // actual work. These are provided by the prototype as a sort of no-op 34870 // implementation so that we don't have to check for their existence in the 34871 // `pipe` function above. 34872 34873 34874 Stream$1.prototype.push = function (data) { 34875 this.trigger('data', data); 34876 }; 34877 34878 Stream$1.prototype.flush = function (flushSource) { 34879 this.trigger('done', flushSource); 34880 }; 34881 34882 Stream$1.prototype.partialFlush = function (flushSource) { 34883 this.trigger('partialdone', flushSource); 34884 }; 34885 34886 Stream$1.prototype.endTimeline = function (flushSource) { 34887 this.trigger('endedtimeline', flushSource); 34888 }; 34889 34890 Stream$1.prototype.reset = function (flushSource) { 34891 this.trigger('reset', flushSource); 34892 }; 34893 34894 var stream = Stream$1; 34895 34896 // Link To Transport 34897 // ----------------- 34898 34899 34900 var CaptionStream = function CaptionStream() { 34901 CaptionStream.prototype.init.call(this); 34902 this.captionPackets_ = []; 34903 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 34904 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 34905 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 34906 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 34907 ]; 34908 this.reset(); // forward data and done events from CCs to this CaptionStream 34909 34910 this.ccStreams_.forEach(function (cc) { 34911 cc.on('data', this.trigger.bind(this, 'data')); 34912 cc.on('partialdone', this.trigger.bind(this, 'partialdone')); 34913 cc.on('done', this.trigger.bind(this, 'done')); 34914 }, this); 34915 }; 34916 34917 CaptionStream.prototype = new stream(); 34918 34919 CaptionStream.prototype.push = function (event) { 34920 var sei, userData, newCaptionPackets; // only examine SEI NALs 34921 34922 if (event.nalUnitType !== 'sei_rbsp') { 34923 return; 34924 } // parse the sei 34925 34926 34927 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 34928 34929 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 34930 return; 34931 } // parse out the user data payload 34932 34933 34934 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 34935 34936 if (!userData) { 34937 return; 34938 } // Sometimes, the same segment # will be downloaded twice. To stop the 34939 // caption data from being processed twice, we track the latest dts we've 34940 // received and ignore everything with a dts before that. However, since 34941 // data for a specific dts can be split across packets on either side of 34942 // a segment boundary, we need to make sure we *don't* ignore the packets 34943 // from the *next* segment that have dts === this.latestDts_. By constantly 34944 // tracking the number of packets received with dts === this.latestDts_, we 34945 // know how many should be ignored once we start receiving duplicates. 34946 34947 34948 if (event.dts < this.latestDts_) { 34949 // We've started getting older data, so set the flag. 34950 this.ignoreNextEqualDts_ = true; 34951 return; 34952 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 34953 this.numSameDts_--; 34954 34955 if (!this.numSameDts_) { 34956 // We've received the last duplicate packet, time to start processing again 34957 this.ignoreNextEqualDts_ = false; 34958 } 34959 34960 return; 34961 } // parse out CC data packets and save them for later 34962 34963 34964 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 34965 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 34966 34967 if (this.latestDts_ !== event.dts) { 34968 this.numSameDts_ = 0; 34969 } 34970 34971 this.numSameDts_++; 34972 this.latestDts_ = event.dts; 34973 }; 34974
vendor: 4,788 bytes, lines 34975-35108
34975 CaptionStream.prototype.flushCCStreams = function (flushType) { 34976 this.ccStreams_.forEach(function (cc) { 34977 return flushType === 'flush' ? cc.flush() : cc.partialFlush(); 34978 }, this); 34979 }; 34980 34981 CaptionStream.prototype.flushStream = function (flushType) { 34982 // make sure we actually parsed captions before proceeding 34983 if (!this.captionPackets_.length) { 34984 this.flushCCStreams(flushType); 34985 return; 34986 } // In Chrome, the Array#sort function is not stable so add a 34987 // presortIndex that we can use to ensure we get a stable-sort 34988 34989 34990 this.captionPackets_.forEach(function (elem, idx) { 34991 elem.presortIndex = idx; 34992 }); // sort caption byte-pairs based on their PTS values 34993 34994 this.captionPackets_.sort(function (a, b) { 34995 if (a.pts === b.pts) { 34996 return a.presortIndex - b.presortIndex; 34997 } 34998 34999 return a.pts - b.pts; 35000 }); 35001 this.captionPackets_.forEach(function (packet) { 35002 if (packet.type < 2) { 35003 // Dispatch packet to the right Cea608Stream 35004 this.dispatchCea608Packet(packet); 35005 } // this is where an 'else' would go for a dispatching packets 35006 // to a theoretical Cea708Stream that handles SERVICEn data 35007 35008 }, this); 35009 this.captionPackets_.length = 0; 35010 this.flushCCStreams(flushType); 35011 }; 35012 35013 CaptionStream.prototype.flush = function () { 35014 return this.flushStream('flush'); 35015 }; // Only called if handling partial data 35016 35017 35018 CaptionStream.prototype.partialFlush = function () { 35019 return this.flushStream('partialFlush'); 35020 }; 35021 35022 CaptionStream.prototype.reset = function () { 35023 this.latestDts_ = null; 35024 this.ignoreNextEqualDts_ = false; 35025 this.numSameDts_ = 0; 35026 this.activeCea608Channel_ = [null, null]; 35027 this.ccStreams_.forEach(function (ccStream) { 35028 ccStream.reset(); 35029 }); 35030 }; // From the CEA-608 spec: 35031 35032 /* 35033 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed 35034 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is 35035 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair 35036 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the 35037 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) 35038 * to switch to captioning or Text. 35039 */ 35040 // With that in mind, we ignore any data between an XDS control code and a 35041 // subsequent closed-captioning control code. 35042 35043 35044 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 35045 // NOTE: packet.type is the CEA608 field 35046 if (this.setsTextOrXDSActive(packet)) { 35047 this.activeCea608Channel_[packet.type] = null; 35048 } else if (this.setsChannel1Active(packet)) { 35049 this.activeCea608Channel_[packet.type] = 0; 35050 } else if (this.setsChannel2Active(packet)) { 35051 this.activeCea608Channel_[packet.type] = 1; 35052 } 35053 35054 if (this.activeCea608Channel_[packet.type] === null) { 35055 // If we haven't received anything to set the active channel, or the 35056 // packets are Text/XDS data, discard the data; we don't want jumbled 35057 // captions 35058 return; 35059 } 35060 35061 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 35062 }; 35063 35064 CaptionStream.prototype.setsChannel1Active = function (packet) { 35065 return (packet.ccData & 0x7800) === 0x1000; 35066 }; 35067 35068 CaptionStream.prototype.setsChannel2Active = function (packet) { 35069 return (packet.ccData & 0x7800) === 0x1800; 35070 }; 35071 35072 CaptionStream.prototype.setsTextOrXDSActive = function (packet) { 35073 return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a; 35074 }; // ---------------------- 35075 // Session to Application 35076 // ---------------------- 35077 // This hash maps non-ASCII, special, and extended character codes to their 35078 // proper Unicode equivalent. The first keys that are only a single byte 35079 // are the non-standard ASCII characters, which simply map the CEA608 byte 35080 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 35081 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 35082 // can be performed regardless of the field and data channel on which the 35083 // character code was received. 35084 35085 35086 var CHARACTER_TRANSLATION = { 35087 0x2a: 0xe1, 35088 // á 35089 0x5c: 0xe9, 35090 // é 35091 0x5e: 0xed, 35092 // à 35093 0x5f: 0xf3, 35094 // ó 35095 0x60: 0xfa, 35096 // ú 35097 0x7b: 0xe7, 35098 // ç 35099 0x7c: 0xf7, 35100 // ÷ 35101 0x7d: 0xd1, 35102 // à 35103 0x7e: 0xf1, 35104 // ñ 35105 0x7f: 0x2588, 35106 // â 35107 0x0130: 0xae, 35108 // ®
35109 0x0131: 0xb0, 35110 // ° 35111 0x0132: 0xbd, 35112 // ½ 35113 0x0133: 0xbf, 35114 // ¿ 35115 0x0134: 0x2122, 35116 // ⢠35117 0x0135: 0xa2, 35118 // ¢ 35119 0x0136: 0xa3, 35120 // £ 35121 0x0137: 0x266a, 35122 // ⪠35123 0x0138: 0xe0, 35124 // à 35125 0x0139: 0xa0, 35126 // 35127 0x013a: 0xe8, 35128 // è 35129 0x013b: 0xe2, 35130 // â 35131 0x013c: 0xea, 35132 // ê 35133 0x013d: 0xee, 35134 // î 35135 0x013e: 0xf4, 35136 // ô 35137 0x013f: 0xfb, 35138 // û 35139 0x0220: 0xc1, 35140 // à 35141 0x0221: 0xc9, 35142 // à 35143 0x0222: 0xd3, 35144 // à 35145 0x0223: 0xda, 35146 // à 35147 0x0224: 0xdc, 35148 // à 35149 0x0225: 0xfc, 35150 // ü 35151 0x0226: 0x2018, 35152 // â 35153 0x0227: 0xa1, 35154 // ¡ 35155 0x0228: 0x2a, 35156 // * 35157 0x0229: 0x27, 35158 // ' 35159 0x022a: 0x2014, 35160 // â 35161 0x022b: 0xa9, 35162 // © 35163 0x022c: 0x2120, 35164 // â 35165 0x022d: 0x2022, 35166 // ⢠35167 0x022e: 0x201c, 35168 // â 35169 0x022f: 0x201d, 35170 // â 35171 0x0230: 0xc0, 35172 // à 35173 0x0231: 0xc2, 35174 // à 35175 0x0232: 0xc7, 35176 // à 35177 0x0233: 0xc8, 35178 // à 35179 0x0234: 0xca, 35180 // à 35181 0x0235: 0xcb, 35182 // à 35183 0x0236: 0xeb, 35184 // ë 35185 0x0237: 0xce, 35186 // à 35187 0x0238: 0xcf, 35188 // à 35189 0x0239: 0xef, 35190 // ï 35191 0x023a: 0xd4, 35192 // à 35193 0x023b: 0xd9, 35194 // à 35195 0x023c: 0xf9, 35196 // ù 35197 0x023d: 0xdb, 35198 // à 35199 0x023e: 0xab, 35200 // « 35201 0x023f: 0xbb, 35202 // » 35203 0x0320: 0xc3, 35204 // à 35205 0x0321: 0xe3, 35206 // ã 35207 0x0322: 0xcd, 35208 // à 35209 0x0323: 0xcc, 35210 // à 35211 0x0324: 0xec, 35212 // ì 35213 0x0325: 0xd2, 35214 // à 35215 0x0326: 0xf2, 35216 // ò 35217 0x0327: 0xd5, 35218 // à 35219 0x0328: 0xf5, 35220 // õ 35221 0x0329: 0x7b, 35222 // { 35223 0x032a: 0x7d, 35224 // } 35225 0x032b: 0x5c, 35226 // \ 35227 0x032c: 0x5e, 35228 // ^ 35229 0x032d: 0x5f, 35230 // _ 35231 0x032e: 0x7c, 35232 // | 35233 0x032f: 0x7e, 35234 // ~ 35235 0x0330: 0xc4, 35236 // à 35237 0x0331: 0xe4,
vendor: 18,474 bytes, lines 35238-35737
35238 // ä 35239 0x0332: 0xd6, 35240 // à 35241 0x0333: 0xf6, 35242 // ö 35243 0x0334: 0xdf, 35244 // à 35245 0x0335: 0xa5, 35246 // Â¥ 35247 0x0336: 0xa4, 35248 // ¤ 35249 0x0337: 0x2502, 35250 // â 35251 0x0338: 0xc5, 35252 // à 35253 0x0339: 0xe5, 35254 // Ã¥ 35255 0x033a: 0xd8, 35256 // à 35257 0x033b: 0xf8, 35258 // ø 35259 0x033c: 0x250c, 35260 // â 35261 0x033d: 0x2510, 35262 // â 35263 0x033e: 0x2514, 35264 // â 35265 0x033f: 0x2518 // â 35266 35267 }; 35268 35269 var getCharFromCode = function getCharFromCode(code) { 35270 if (code === null) { 35271 return ''; 35272 } 35273 35274 code = CHARACTER_TRANSLATION[code] || code; 35275 return String.fromCharCode(code); 35276 }; // the index of the last row in a CEA-608 display buffer 35277 35278 35279 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 35280 // getting it through bit logic. 35281 35282 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 35283 // cells. The "bottom" row is the last element in the outer array. 35284 35285 var createDisplayBuffer = function createDisplayBuffer() { 35286 var result = [], 35287 i = BOTTOM_ROW + 1; 35288 35289 while (i--) { 35290 result.push(''); 35291 } 35292 35293 return result; 35294 }; 35295 35296 var Cea608Stream = function Cea608Stream(field, dataChannel) { 35297 Cea608Stream.prototype.init.call(this); 35298 this.field_ = field || 0; 35299 this.dataChannel_ = dataChannel || 0; 35300 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 35301 this.setConstants(); 35302 this.reset(); 35303 35304 this.push = function (packet) { 35305 var data, swap, char0, char1, text; // remove the parity bits 35306 35307 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 35308 35309 if (data === this.lastControlCode_) { 35310 this.lastControlCode_ = null; 35311 return; 35312 } // Store control codes 35313 35314 35315 if ((data & 0xf000) === 0x1000) { 35316 this.lastControlCode_ = data; 35317 } else if (data !== this.PADDING_) { 35318 this.lastControlCode_ = null; 35319 } 35320 35321 char0 = data >>> 8; 35322 char1 = data & 0xff; 35323 35324 if (data === this.PADDING_) { 35325 return; 35326 } else if (data === this.RESUME_CAPTION_LOADING_) { 35327 this.mode_ = 'popOn'; 35328 } else if (data === this.END_OF_CAPTION_) { 35329 // If an EOC is received while in paint-on mode, the displayed caption 35330 // text should be swapped to non-displayed memory as if it was a pop-on 35331 // caption. Because of that, we should explicitly switch back to pop-on 35332 // mode 35333 this.mode_ = 'popOn'; 35334 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 35335 35336 this.flushDisplayed(packet.pts); // flip memory 35337 35338 swap = this.displayed_; 35339 this.displayed_ = this.nonDisplayed_; 35340 this.nonDisplayed_ = swap; // start measuring the time to display the caption 35341 35342 this.startPts_ = packet.pts; 35343 } else if (data === this.ROLL_UP_2_ROWS_) { 35344 this.rollUpRows_ = 2; 35345 this.setRollUp(packet.pts); 35346 } else if (data === this.ROLL_UP_3_ROWS_) { 35347 this.rollUpRows_ = 3; 35348 this.setRollUp(packet.pts); 35349 } else if (data === this.ROLL_UP_4_ROWS_) { 35350 this.rollUpRows_ = 4; 35351 this.setRollUp(packet.pts); 35352 } else if (data === this.CARRIAGE_RETURN_) { 35353 this.clearFormatting(packet.pts); 35354 this.flushDisplayed(packet.pts); 35355 this.shiftRowsUp_(); 35356 this.startPts_ = packet.pts; 35357 } else if (data === this.BACKSPACE_) { 35358 if (this.mode_ === 'popOn') { 35359 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 35360 } else { 35361 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 35362 } 35363 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 35364 this.flushDisplayed(packet.pts); 35365 this.displayed_ = createDisplayBuffer(); 35366 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 35367 this.nonDisplayed_ = createDisplayBuffer(); 35368 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 35369 if (this.mode_ !== 'paintOn') { 35370 // NOTE: This should be removed when proper caption positioning is 35371 // implemented 35372 this.flushDisplayed(packet.pts); 35373 this.displayed_ = createDisplayBuffer(); 35374 } 35375 35376 this.mode_ = 'paintOn'; 35377 this.startPts_ = packet.pts; // Append special characters to caption text 35378 } else if (this.isSpecialCharacter(char0, char1)) { 35379 // Bitmask char0 so that we can apply character transformations 35380 // regardless of field and data channel. 35381 // Then byte-shift to the left and OR with char1 so we can pass the 35382 // entire character code to `getCharFromCode`. 35383 char0 = (char0 & 0x03) << 8; 35384 text = getCharFromCode(char0 | char1); 35385 this[this.mode_](packet.pts, text); 35386 this.column_++; // Append extended characters to caption text 35387 } else if (this.isExtCharacter(char0, char1)) { 35388 // Extended characters always follow their "non-extended" equivalents. 35389 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 35390 // decoders are supposed to drop the "è", while compliant decoders 35391 // backspace the "e" and insert "è". 35392 // Delete the previous character 35393 if (this.mode_ === 'popOn') { 35394 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 35395 } else { 35396 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 35397 } // Bitmask char0 so that we can apply character transformations 35398 // regardless of field and data channel. 35399 // Then byte-shift to the left and OR with char1 so we can pass the 35400 // entire character code to `getCharFromCode`. 35401 35402 35403 char0 = (char0 & 0x03) << 8; 35404 text = getCharFromCode(char0 | char1); 35405 this[this.mode_](packet.pts, text); 35406 this.column_++; // Process mid-row codes 35407 } else if (this.isMidRowCode(char0, char1)) { 35408 // Attributes are not additive, so clear all formatting 35409 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 35410 // should be replaced with spaces, so add one now 35411 35412 this[this.mode_](packet.pts, ' '); 35413 this.column_++; 35414 35415 if ((char1 & 0xe) === 0xe) { 35416 this.addFormatting(packet.pts, ['i']); 35417 } 35418 35419 if ((char1 & 0x1) === 0x1) { 35420 this.addFormatting(packet.pts, ['u']); 35421 } // Detect offset control codes and adjust cursor 35422 35423 } else if (this.isOffsetControlCode(char0, char1)) { 35424 // Cursor position is set by indent PAC (see below) in 4-column 35425 // increments, with an additional offset code of 1-3 to reach any 35426 // of the 32 columns specified by CEA-608. So all we need to do 35427 // here is increment the column cursor by the given offset. 35428 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 35429 } else if (this.isPAC(char0, char1)) { 35430 // There's no logic for PAC -> row mapping, so we have to just 35431 // find the row code in an array and use its index :( 35432 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 35433 35434 if (this.mode_ === 'rollUp') { 35435 // This implies that the base row is incorrectly set. 35436 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 35437 // of roll-up rows set. 35438 if (row - this.rollUpRows_ + 1 < 0) { 35439 row = this.rollUpRows_ - 1; 35440 } 35441 35442 this.setRollUp(packet.pts, row); 35443 } 35444 35445 if (row !== this.row_) { 35446 // formatting is only persistent for current row 35447 this.clearFormatting(packet.pts); 35448 this.row_ = row; 35449 } // All PACs can apply underline, so detect and apply 35450 // (All odd-numbered second bytes set underline) 35451 35452 35453 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 35454 this.addFormatting(packet.pts, ['u']); 35455 } 35456 35457 if ((data & 0x10) === 0x10) { 35458 // We've got an indent level code. Each successive even number 35459 // increments the column cursor by 4, so we can get the desired 35460 // column position by bit-shifting to the right (to get n/2) 35461 // and multiplying by 4. 35462 this.column_ = ((data & 0xe) >> 1) * 4; 35463 } 35464 35465 if (this.isColorPAC(char1)) { 35466 // it's a color code, though we only support white, which 35467 // can be either normal or italicized. white italics can be 35468 // either 0x4e or 0x6e depending on the row, so we just 35469 // bitwise-and with 0xe to see if italics should be turned on 35470 if ((char1 & 0xe) === 0xe) { 35471 this.addFormatting(packet.pts, ['i']); 35472 } 35473 } // We have a normal character in char0, and possibly one in char1 35474 35475 } else if (this.isNormalChar(char0)) { 35476 if (char1 === 0x00) { 35477 char1 = null; 35478 } 35479 35480 text = getCharFromCode(char0); 35481 text += getCharFromCode(char1); 35482 this[this.mode_](packet.pts, text); 35483 this.column_ += text.length; 35484 } // finish data processing 35485 35486 }; 35487 }; 35488 35489 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 35490 // display buffer 35491 35492 Cea608Stream.prototype.flushDisplayed = function (pts) { 35493 var content = this.displayed_ // remove spaces from the start and end of the string 35494 .map(function (row) { 35495 try { 35496 return row.trim(); 35497 } catch (e) { 35498 // Ordinarily, this shouldn't happen. However, caption 35499 // parsing errors should not throw exceptions and 35500 // break playback. 35501 // eslint-disable-next-line no-console 35502 console.error('Skipping malformed caption.'); 35503 return ''; 35504 } 35505 }) // combine all text rows to display in one cue 35506 .join('\n') // and remove blank rows from the start and end, but not the middle 35507 .replace(/^\n+|\n+$/g, ''); 35508 35509 if (content.length) { 35510 this.trigger('data', { 35511 startPts: this.startPts_, 35512 endPts: pts, 35513 text: content, 35514 stream: this.name_ 35515 }); 35516 } 35517 }; 35518 /** 35519 * Zero out the data, used for startup and on seek 35520 */ 35521 35522 35523 Cea608Stream.prototype.reset = function () { 35524 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 35525 // actually display captions. If a caption is shifted to a row 35526 // with a lower index than this, it is cleared from the display 35527 // buffer 35528 35529 this.topRow_ = 0; 35530 this.startPts_ = 0; 35531 this.displayed_ = createDisplayBuffer(); 35532 this.nonDisplayed_ = createDisplayBuffer(); 35533 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 35534 35535 this.column_ = 0; 35536 this.row_ = BOTTOM_ROW; 35537 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 35538 35539 this.formatting_ = []; 35540 }; 35541 /** 35542 * Sets up control code and related constants for this instance 35543 */ 35544 35545 35546 Cea608Stream.prototype.setConstants = function () { 35547 // The following attributes have these uses: 35548 // ext_ : char0 for mid-row codes, and the base for extended 35549 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 35550 // extended codes) 35551 // control_: char0 for control codes, except byte-shifted to the 35552 // left so that we can do this.control_ | CONTROL_CODE 35553 // offset_: char0 for tab offset codes 35554 // 35555 // It's also worth noting that control codes, and _only_ control codes, 35556 // differ between field 1 and field2. Field 2 control codes are always 35557 // their field 1 value plus 1. That's why there's the "| field" on the 35558 // control value. 35559 if (this.dataChannel_ === 0) { 35560 this.BASE_ = 0x10; 35561 this.EXT_ = 0x11; 35562 this.CONTROL_ = (0x14 | this.field_) << 8; 35563 this.OFFSET_ = 0x17; 35564 } else if (this.dataChannel_ === 1) { 35565 this.BASE_ = 0x18; 35566 this.EXT_ = 0x19; 35567 this.CONTROL_ = (0x1c | this.field_) << 8; 35568 this.OFFSET_ = 0x1f; 35569 } // Constants for the LSByte command codes recognized by Cea608Stream. This 35570 // list is not exhaustive. For a more comprehensive listing and semantics see 35571 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 35572 // Padding 35573 35574 35575 this.PADDING_ = 0x0000; // Pop-on Mode 35576 35577 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 35578 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 35579 35580 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 35581 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 35582 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 35583 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 35584 35585 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 35586 35587 this.BACKSPACE_ = this.CONTROL_ | 0x21; 35588 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 35589 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 35590 }; 35591 /** 35592 * Detects if the 2-byte packet data is a special character 35593 * 35594 * Special characters have a second byte in the range 0x30 to 0x3f, 35595 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 35596 * data channel 2). 35597 * 35598 * @param {Integer} char0 The first byte 35599 * @param {Integer} char1 The second byte 35600 * @return {Boolean} Whether the 2 bytes are an special character 35601 */ 35602 35603 35604 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 35605 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 35606 }; 35607 /** 35608 * Detects if the 2-byte packet data is an extended character 35609 * 35610 * Extended characters have a second byte in the range 0x20 to 0x3f, 35611 * with the first byte being 0x12 or 0x13 (for data channel 1) or 35612 * 0x1a or 0x1b (for data channel 2). 35613 * 35614 * @param {Integer} char0 The first byte 35615 * @param {Integer} char1 The second byte 35616 * @return {Boolean} Whether the 2 bytes are an extended character 35617 */ 35618 35619 35620 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 35621 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 35622 }; 35623 /** 35624 * Detects if the 2-byte packet is a mid-row code 35625 * 35626 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 35627 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 35628 * channel 2). 35629 * 35630 * @param {Integer} char0 The first byte 35631 * @param {Integer} char1 The second byte 35632 * @return {Boolean} Whether the 2 bytes are a mid-row code 35633 */ 35634 35635 35636 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 35637 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 35638 }; 35639 /** 35640 * Detects if the 2-byte packet is an offset control code 35641 * 35642 * Offset control codes have a second byte in the range 0x21 to 0x23, 35643 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 35644 * data channel 2). 35645 * 35646 * @param {Integer} char0 The first byte 35647 * @param {Integer} char1 The second byte 35648 * @return {Boolean} Whether the 2 bytes are an offset control code 35649 */ 35650 35651 35652 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 35653 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 35654 }; 35655 /** 35656 * Detects if the 2-byte packet is a Preamble Address Code 35657 * 35658 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 35659 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 35660 * range 0x40 to 0x7f. 35661 * 35662 * @param {Integer} char0 The first byte 35663 * @param {Integer} char1 The second byte 35664 * @return {Boolean} Whether the 2 bytes are a PAC 35665 */ 35666 35667 35668 Cea608Stream.prototype.isPAC = function (char0, char1) { 35669 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 35670 }; 35671 /** 35672 * Detects if a packet's second byte is in the range of a PAC color code 35673 * 35674 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 35675 * 0x60 to 0x6f. 35676 * 35677 * @param {Integer} char1 The second byte 35678 * @return {Boolean} Whether the byte is a color PAC 35679 */ 35680 35681 35682 Cea608Stream.prototype.isColorPAC = function (char1) { 35683 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 35684 }; 35685 /** 35686 * Detects if a single byte is in the range of a normal character 35687 * 35688 * Normal text bytes are in the range 0x20 to 0x7f. 35689 * 35690 * @param {Integer} char The byte 35691 * @return {Boolean} Whether the byte is a normal character 35692 */ 35693 35694 35695 Cea608Stream.prototype.isNormalChar = function (_char) { 35696 return _char >= 0x20 && _char <= 0x7f; 35697 }; 35698 /** 35699 * Configures roll-up 35700 * 35701 * @param {Integer} pts Current PTS 35702 * @param {Integer} newBaseRow Used by PACs to slide the current window to 35703 * a new position 35704 */ 35705 35706 35707 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 35708 // Reset the base row to the bottom row when switching modes 35709 if (this.mode_ !== 'rollUp') { 35710 this.row_ = BOTTOM_ROW; 35711 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 35712 35713 this.flushDisplayed(pts); 35714 this.nonDisplayed_ = createDisplayBuffer(); 35715 this.displayed_ = createDisplayBuffer(); 35716 } 35717 35718 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 35719 // move currently displayed captions (up or down) to the new base row 35720 for (var i = 0; i < this.rollUpRows_; i++) { 35721 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 35722 this.displayed_[this.row_ - i] = ''; 35723 } 35724 } 35725 35726 if (newBaseRow === undefined) { 35727 newBaseRow = this.row_; 35728 } 35729 35730 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 35731 }; // Adds the opening HTML tag for the passed character to the caption text, 35732 // and keeps track of it for later closing 35733 35734 35735 Cea608Stream.prototype.addFormatting = function (pts, format) { 35736 this.formatting_ = this.formatting_.concat(format); 35737 var text = format.reduce(function (text, format) {
vendor: 2,997 bytes, lines 35738-35838
35738 return text + '<' + format + '>'; 35739 }, ''); 35740 this[this.mode_](pts, text); 35741 }; // Adds HTML closing tags for current formatting to caption text and 35742 // clears remembered formatting 35743 35744 35745 Cea608Stream.prototype.clearFormatting = function (pts) { 35746 if (!this.formatting_.length) { 35747 return; 35748 } 35749 35750 var text = this.formatting_.reverse().reduce(function (text, format) { 35751 return text + '</' + format + '>'; 35752 }, ''); 35753 this.formatting_ = []; 35754 this[this.mode_](pts, text); 35755 }; // Mode Implementations 35756 35757 35758 Cea608Stream.prototype.popOn = function (pts, text) { 35759 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 35760 35761 baseRow += text; 35762 this.nonDisplayed_[this.row_] = baseRow; 35763 }; 35764 35765 Cea608Stream.prototype.rollUp = function (pts, text) { 35766 var baseRow = this.displayed_[this.row_]; 35767 baseRow += text; 35768 this.displayed_[this.row_] = baseRow; 35769 }; 35770 35771 Cea608Stream.prototype.shiftRowsUp_ = function () { 35772 var i; // clear out inactive rows 35773 35774 for (i = 0; i < this.topRow_; i++) { 35775 this.displayed_[i] = ''; 35776 } 35777 35778 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 35779 this.displayed_[i] = ''; 35780 } // shift displayed rows up 35781 35782 35783 for (i = this.topRow_; i < this.row_; i++) { 35784 this.displayed_[i] = this.displayed_[i + 1]; 35785 } // clear out the bottom row 35786 35787 35788 this.displayed_[this.row_] = ''; 35789 }; 35790 35791 Cea608Stream.prototype.paintOn = function (pts, text) { 35792 var baseRow = this.displayed_[this.row_]; 35793 baseRow += text; 35794 this.displayed_[this.row_] = baseRow; 35795 }; // exports 35796 35797 35798 var captionStream = { 35799 CaptionStream: CaptionStream, 35800 Cea608Stream: Cea608Stream 35801 }; 35802 35803 var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes; 35804 var CaptionStream$1 = captionStream.CaptionStream; 35805 /** 35806 * Maps an offset in the mdat to a sample based on the the size of the samples. 35807 * Assumes that `parseSamples` has been called first. 35808 * 35809 * @param {Number} offset - The offset into the mdat 35810 * @param {Object[]} samples - An array of samples, parsed using `parseSamples` 35811 * @return {?Object} The matching sample, or null if no match was found. 35812 * 35813 * @see ISO-BMFF-12/2015, Section 8.8.8 35814 **/ 35815 35816 var mapToSample = function mapToSample(offset, samples) { 35817 var approximateOffset = offset; 35818 35819 for (var i = 0; i < samples.length; i++) { 35820 var sample = samples[i]; 35821 35822 if (approximateOffset < sample.size) { 35823 return sample; 35824 } 35825 35826 approximateOffset -= sample.size; 35827 } 35828 35829 return null; 35830 }; 35831 /** 35832 * Finds SEI nal units contained in a Media Data Box. 35833 * Assumes that `parseSamples` has been called first. 35834 * 35835 * @param {Uint8Array} avcStream - The bytes of the mdat 35836 * @param {Object[]} samples - The samples parsed out by `parseSamples` 35837 * @param {Number} trackId - The trackId of this video track 35838 * @return {Object[]}
vendor: 4,299 bytes, lines 35838-35963
35838 seiNals - the parsed SEI NALUs found. 35839 * The contents of the seiNal should match what is expected by 35840 * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts) 35841 * 35842 * @see ISO-BMFF-12/2015, Section 8.1.1 35843 * @see Rec. ITU-T H.264, 7.3.2.3.1 35844 **/ 35845 35846 35847 var findSeiNals = function findSeiNals(avcStream, samples, trackId) { 35848 var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength), 35849 result = [], 35850 seiNal, 35851 i, 35852 length, 35853 lastMatchedSample; 35854 35855 for (i = 0; i + 4 < avcStream.length; i += length) { 35856 length = avcView.getUint32(i); 35857 i += 4; // Bail if this doesn't appear to be an H264 stream 35858 35859 if (length <= 0) { 35860 continue; 35861 } 35862 35863 switch (avcStream[i] & 0x1F) { 35864 case 0x06: 35865 var data = avcStream.subarray(i + 1, i + 1 + length); 35866 var matchingSample = mapToSample(i, samples); 35867 seiNal = { 35868 nalUnitType: 'sei_rbsp', 35869 size: length, 35870 data: data, 35871 escapedRBSP: discardEmulationPreventionBytes$1(data), 35872 trackId: trackId 35873 }; 35874 35875 if (matchingSample) { 35876 seiNal.pts = matchingSample.pts; 35877 seiNal.dts = matchingSample.dts; 35878 lastMatchedSample = matchingSample; 35879 } else { 35880 // If a matching sample cannot be found, use the last 35881 // sample's values as they should be as close as possible 35882 seiNal.pts = lastMatchedSample.pts; 35883 seiNal.dts = lastMatchedSample.dts; 35884 } 35885 35886 result.push(seiNal); 35887 break; 35888 35889 default: 35890 break; 35891 } 35892 } 35893 35894 return result; 35895 }; 35896 /** 35897 * Parses sample information out of Track Run Boxes and calculates 35898 * the absolute presentation and decode timestamps of each sample. 35899 * 35900 * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed 35901 * @param {Number} baseMediaDecodeTime - base media decode time from tfdt 35902 @see ISO-BMFF-12/2015, Section 8.8.12 35903 * @param {Object} tfhd - The parsed Track Fragment Header 35904 * @see inspect.parseTfhd 35905 * @return {Object[]} the parsed samples 35906 * 35907 * @see ISO-BMFF-12/2015, Section 8.8.8 35908 **/ 35909 35910 35911 var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) { 35912 var currentDts = baseMediaDecodeTime; 35913 var defaultSampleDuration = tfhd.defaultSampleDuration || 0; 35914 var defaultSampleSize = tfhd.defaultSampleSize || 0; 35915 var trackId = tfhd.trackId; 35916 var allSamples = []; 35917 truns.forEach(function (trun) { 35918 // Note: We currently do not parse the sample table as well 35919 // as the trun. It's possible some sources will require this. 35920 // moov > trak > mdia > minf > stbl 35921 var trackRun = mp4Inspector.parseTrun(trun); 35922 var samples = trackRun.samples; 35923 samples.forEach(function (sample) { 35924 if (sample.duration === undefined) { 35925 sample.duration = defaultSampleDuration; 35926 } 35927 35928 if (sample.size === undefined) { 35929 sample.size = defaultSampleSize; 35930 } 35931 35932 sample.trackId = trackId; 35933 sample.dts = currentDts; 35934 35935 if (sample.compositionTimeOffset === undefined) { 35936 sample.compositionTimeOffset = 0; 35937 } 35938 35939 sample.pts = currentDts + sample.compositionTimeOffset; 35940 currentDts += sample.duration; 35941 }); 35942 allSamples = allSamples.concat(samples); 35943 }); 35944 return allSamples; 35945 }; 35946 /** 35947 * Parses out caption nals from an FMP4 segment's video tracks. 35948 * 35949 * @param {Uint8Array} segment - The bytes of a single segment 35950 * @param {Number} videoTrackId - The trackId of a video track in the segment 35951 * @return {Object.<Number, Object[]>} A mapping of video trackId to 35952 * a list of seiNals found in that track 35953 **/ 35954 35955 35956 var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) { 35957 // To get the samples 35958 var trafs = probe.findBox(segment, ['moof', 'traf']); // To get SEI NAL units 35959 35960 var mdats = probe.findBox(segment, ['mdat']); 35961 var captionNals = {}; 35962 var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs 35963
vendor: 3,771 bytes, lines 35964-36066
35964 mdats.forEach(function (mdat, index) { 35965 var matchingTraf = trafs[index]; 35966 mdatTrafPairs.push({ 35967 mdat: mdat, 35968 traf: matchingTraf 35969 }); 35970 }); 35971 mdatTrafPairs.forEach(function (pair) { 35972 var mdat = pair.mdat; 35973 var traf = pair.traf; 35974 var tfhd = probe.findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf 35975 35976 var headerInfo = mp4Inspector.parseTfhd(tfhd[0]); 35977 var trackId = headerInfo.trackId; 35978 var tfdt = probe.findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf 35979 35980 var baseMediaDecodeTime = tfdt.length > 0 ? mp4Inspector.parseTfdt(tfdt[0]).baseMediaDecodeTime : 0; 35981 var truns = probe.findBox(traf, ['trun']); 35982 var samples; 35983 var seiNals; // Only parse video data for the chosen video track 35984 35985 if (videoTrackId === trackId && truns.length > 0) { 35986 samples = parseSamples(truns, baseMediaDecodeTime, headerInfo); 35987 seiNals = findSeiNals(mdat, samples, trackId); 35988 35989 if (!captionNals[trackId]) { 35990 captionNals[trackId] = []; 35991 } 35992 35993 captionNals[trackId] = captionNals[trackId].concat(seiNals); 35994 } 35995 }); 35996 return captionNals; 35997 }; 35998 /** 35999 * Parses out inband captions from an MP4 container and returns 36000 * caption objects that can be used by WebVTT and the TextTrack API. 36001 * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue 36002 * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack 36003 * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first 36004 * 36005 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 36006 * @param {Number} trackId - The id of the video track to parse 36007 * @param {Number} timescale - The timescale for the video track from the init segment 36008 * 36009 * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks 36010 * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds 36011 * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds 36012 * @return {String} parsedCaptions[].text - The visible content of the caption 36013 **/ 36014 36015 36016 var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) { 36017 var seiNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 36018 36019 if (trackId === null) { 36020 return null; 36021 } 36022 36023 seiNals = parseCaptionNals(segment, trackId); 36024 return { 36025 seiNals: seiNals[trackId], 36026 timescale: timescale 36027 }; 36028 }; 36029 /** 36030 * Converts SEI NALUs into captions that can be used by video.js 36031 **/ 36032 36033 36034 var CaptionParser = function CaptionParser() { 36035 var isInitialized = false; 36036 var captionStream; // Stores segments seen before trackId and timescale are set 36037 36038 var segmentCache; // Stores video track ID of the track being parsed 36039 36040 var trackId; // Stores the timescale of the track being parsed 36041 36042 var timescale; // Stores captions parsed so far 36043 36044 var parsedCaptions; // Stores whether we are receiving partial data or not 36045 36046 var parsingPartial; 36047 /** 36048 * A method to indicate whether a CaptionParser has been initalized 36049 * @returns {Boolean} 36050 **/ 36051 36052 this.isInitialized = function () { 36053 return isInitialized; 36054 }; 36055 /** 36056 * Initializes the underlying CaptionStream, SEI NAL parsing 36057 * and management, and caption collection 36058 **/ 36059 36060 36061 this.init = function (options) { 36062 captionStream = new CaptionStream$1(); 36063 isInitialized = true; 36064 parsingPartial = options ? options.isPartial : false; // Collect dispatched captions 36065 36066 captionStream.on('data', function (event) {
vendor: 4,550 bytes, lines 36067-36216
36067 // Convert to seconds in the source's timescale 36068 event.startTime = event.startPts / timescale; 36069 event.endTime = event.endPts / timescale; 36070 parsedCaptions.captions.push(event); 36071 parsedCaptions.captionStreams[event.stream] = true; 36072 }); 36073 }; 36074 /** 36075 * Determines if a new video track will be selected 36076 * or if the timescale changed 36077 * @return {Boolean} 36078 **/ 36079 36080 36081 this.isNewInit = function (videoTrackIds, timescales) { 36082 if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) { 36083 return false; 36084 } 36085 36086 return trackId !== videoTrackIds[0] || timescale !== timescales[trackId]; 36087 }; 36088 /** 36089 * Parses out SEI captions and interacts with underlying 36090 * CaptionStream to return dispatched captions 36091 * 36092 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions 36093 * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment 36094 * @param {Object.<Number, Number>} timescales - The timescales found in the init segment 36095 * @see parseEmbeddedCaptions 36096 * @see m2ts/caption-stream.js 36097 **/ 36098 36099 36100 this.parse = function (segment, videoTrackIds, timescales) { 36101 var parsedData; 36102 36103 if (!this.isInitialized()) { 36104 return null; // This is not likely to be a video segment 36105 } else if (!videoTrackIds || !timescales) { 36106 return null; 36107 } else if (this.isNewInit(videoTrackIds, timescales)) { 36108 // Use the first video track only as there is no 36109 // mechanism to switch to other video tracks 36110 trackId = videoTrackIds[0]; 36111 timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment 36112 // data until we have one. 36113 // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there 36114 } else if (trackId === null || !timescale) { 36115 segmentCache.push(segment); 36116 return null; 36117 } // Now that a timescale and trackId is set, parse cached segments 36118 36119 36120 while (segmentCache.length > 0) { 36121 var cachedSegment = segmentCache.shift(); 36122 this.parse(cachedSegment, videoTrackIds, timescales); 36123 } 36124 36125 parsedData = parseEmbeddedCaptions(segment, trackId, timescale); 36126 36127 if (parsedData === null || !parsedData.seiNals) { 36128 return null; 36129 } 36130 36131 this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched 36132 36133 this.flushStream(); 36134 return parsedCaptions; 36135 }; 36136 /** 36137 * Pushes SEI NALUs onto CaptionStream 36138 * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals` 36139 * Assumes that `parseCaptionNals` has been called first 36140 * @see m2ts/caption-stream.js 36141 **/ 36142 36143 36144 this.pushNals = function (nals) { 36145 if (!this.isInitialized() || !nals || nals.length === 0) { 36146 return null; 36147 } 36148 36149 nals.forEach(function (nal) { 36150 captionStream.push(nal); 36151 }); 36152 }; 36153 /** 36154 * Flushes underlying CaptionStream to dispatch processed, displayable captions 36155 * @see m2ts/caption-stream.js 36156 **/ 36157 36158 36159 this.flushStream = function () { 36160 if (!this.isInitialized()) { 36161 return null; 36162 } 36163 36164 if (!parsingPartial) { 36165 captionStream.flush(); 36166 } else { 36167 captionStream.partialFlush(); 36168 } 36169 }; 36170 /** 36171 * Reset caption buckets for new data 36172 **/ 36173 36174 36175 this.clearParsedCaptions = function () { 36176 parsedCaptions.captions = []; 36177 parsedCaptions.captionStreams = {}; 36178 }; 36179 /** 36180 * Resets underlying CaptionStream 36181 * @see m2ts/caption-stream.js 36182 **/ 36183 36184 36185 this.resetCaptionStream = function () { 36186 if (!this.isInitialized()) { 36187 return null; 36188 } 36189 36190 captionStream.reset(); 36191 }; 36192 /** 36193 * Convenience method to clear all captions flushed from the 36194 * CaptionStream and still being parsed 36195 * @see m2ts/caption-stream.js 36196 **/ 36197 36198 36199 this.clearAllCaptions = function () { 36200 this.clearParsedCaptions(); 36201 this.resetCaptionStream(); 36202 }; 36203 /** 36204 * Reset caption parser 36205 **/ 36206 36207 36208 this.reset = function () { 36209 segmentCache = []; 36210 trackId = null; 36211 timescale = null; 36212 36213 if (!parsedCaptions) { 36214 parsedCaptions = { 36215 captions: [], 36216 // CC1, CC2, CC3, CC4
vendor: 7,392 bytes, lines 36217-36450
36217 captionStreams: {} 36218 }; 36219 } else { 36220 this.clearParsedCaptions(); 36221 } 36222 36223 this.resetCaptionStream(); 36224 }; 36225 36226 this.reset(); 36227 }; 36228 36229 var captionParser = CaptionParser; 36230 36231 /** 36232 * mux.js 36233 * 36234 * Copyright (c) Brightcove 36235 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 36236 */ 36237 36238 var streamTypes = { 36239 H264_STREAM_TYPE: 0x1B, 36240 ADTS_STREAM_TYPE: 0x0F, 36241 METADATA_STREAM_TYPE: 0x15 36242 }; 36243 36244 var MAX_TS = 8589934592; 36245 var RO_THRESH = 4294967296; 36246 var TYPE_SHARED = 'shared'; 36247 36248 var handleRollover = function handleRollover(value, reference) { 36249 var direction = 1; 36250 36251 if (value > reference) { 36252 // If the current timestamp value is greater than our reference timestamp and we detect a 36253 // timestamp rollover, this means the roll over is happening in the opposite direction. 36254 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 36255 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 36256 // rollover point. In loading this segment, the timestamp values will be very large, 36257 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 36258 // the time stamp to be `value - 2^33`. 36259 direction = -1; 36260 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 36261 // cause an incorrect adjustment. 36262 36263 36264 while (Math.abs(reference - value) > RO_THRESH) { 36265 value += direction * MAX_TS; 36266 } 36267 36268 return value; 36269 }; 36270 36271 var TimestampRolloverStream = function TimestampRolloverStream(type) { 36272 var lastDTS, referenceDTS; 36273 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 36274 // video and audio. We could use `undefined` here, but having a string 36275 // makes debugging a little clearer. 36276 36277 this.type_ = type || TYPE_SHARED; 36278 36279 this.push = function (data) { 36280 // Any "shared" rollover streams will accept _all_ data. Otherwise, 36281 // streams will only accept data that matches their type. 36282 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 36283 return; 36284 } 36285 36286 if (referenceDTS === undefined) { 36287 referenceDTS = data.dts; 36288 } 36289 36290 data.dts = handleRollover(data.dts, referenceDTS); 36291 data.pts = handleRollover(data.pts, referenceDTS); 36292 lastDTS = data.dts; 36293 this.trigger('data', data); 36294 }; 36295 36296 this.flush = function () { 36297 referenceDTS = lastDTS; 36298 this.trigger('done'); 36299 }; 36300 36301 this.endTimeline = function () { 36302 this.flush(); 36303 this.trigger('endedtimeline'); 36304 }; 36305 36306 this.discontinuity = function () { 36307 referenceDTS = void 0; 36308 lastDTS = void 0; 36309 }; 36310 36311 this.reset = function () { 36312 this.discontinuity(); 36313 this.trigger('reset'); 36314 }; 36315 }; 36316 36317 TimestampRolloverStream.prototype = new stream(); 36318 var timestampRolloverStream = { 36319 TimestampRolloverStream: TimestampRolloverStream, 36320 handleRollover: handleRollover 36321 }; 36322 36323 var parsePid = function parsePid(packet) { 36324 var pid = packet[1] & 0x1f; 36325 pid <<= 8; 36326 pid |= packet[2]; 36327 return pid; 36328 }; 36329 36330 var parsePayloadUnitStartIndicator = function parsePayloadUnitStartIndicator(packet) { 36331 return !!(packet[1] & 0x40); 36332 }; 36333 36334 var parseAdaptionField = function parseAdaptionField(packet) { 36335 var offset = 0; // if an adaption field is present, its length is specified by the 36336 // fifth byte of the TS packet header. The adaptation field is 36337 // used to add stuffing to PES packets that don't fill a complete 36338 // TS packet, and to specify some forms of timing and control data 36339 // that we do not currently use. 36340 36341 if ((packet[3] & 0x30) >>> 4 > 0x01) { 36342 offset += packet[4] + 1; 36343 } 36344 36345 return offset; 36346 }; 36347 36348 var parseType$2 = function parseType(packet, pmtPid) { 36349 var pid = parsePid(packet); 36350 36351 if (pid === 0) { 36352 return 'pat'; 36353 } else if (pid === pmtPid) { 36354 return 'pmt'; 36355 } else if (pmtPid) { 36356 return 'pes'; 36357 } 36358 36359 return null; 36360 }; 36361 36362 var parsePat = function parsePat(packet) { 36363 var pusi = parsePayloadUnitStartIndicator(packet); 36364 var offset = 4 + parseAdaptionField(packet); 36365 36366 if (pusi) { 36367 offset += packet[offset] + 1; 36368 } 36369 36370 return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; 36371 }; 36372 36373 var parsePmt = function parsePmt(packet) { 36374 var programMapTable = {}; 36375 var pusi = parsePayloadUnitStartIndicator(packet); 36376 var payloadOffset = 4 + parseAdaptionField(packet); 36377 36378 if (pusi) { 36379 payloadOffset += packet[payloadOffset] + 1; 36380 } // PMTs can be sent ahead of the time when they should actually 36381 // take effect. We don't believe this should ever be the case 36382 // for HLS but we'll ignore "forward" PMT declarations if we see 36383 // them. Future PMT declarations have the current_next_indicator 36384 // set to zero. 36385 36386 36387 if (!(packet[payloadOffset + 5] & 0x01)) { 36388 return; 36389 } 36390 36391 var sectionLength, tableEnd, programInfoLength; // the mapping table ends at the end of the current section 36392 36393 sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; 36394 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 36395 // long the program info descriptors are 36396 36397 programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; // advance the offset to the first entry in the mapping table 36398 36399 var offset = 12 + programInfoLength; 36400 36401 while (offset < tableEnd) { 36402 var i = payloadOffset + offset; // add an entry that maps the elementary_pid to the stream_type 36403 36404 programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; // move to the next table entry 36405 // skip past the elementary stream descriptors, if present 36406 36407 offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; 36408 } 36409 36410 return programMapTable; 36411 }; 36412 36413 var parsePesType = function parsePesType(packet, programMapTable) { 36414 var pid = parsePid(packet); 36415 var type = programMapTable[pid]; 36416 36417 switch (type) { 36418 case streamTypes.H264_STREAM_TYPE: 36419 return 'video'; 36420 36421 case streamTypes.ADTS_STREAM_TYPE: 36422 return 'audio'; 36423 36424 case streamTypes.METADATA_STREAM_TYPE: 36425 return 'timed-metadata'; 36426 36427 default: 36428 return null; 36429 } 36430 }; 36431 36432 var parsePesTime = function parsePesTime(packet) { 36433 var pusi = parsePayloadUnitStartIndicator(packet); 36434 36435 if (!pusi) { 36436 return null; 36437 } 36438 36439 var offset = 4 + parseAdaptionField(packet); 36440 36441 if (offset >= packet.byteLength) { 36442 // From the H 222.0 MPEG-TS spec 36443 // "For transport stream packets carrying PES packets, stuffing is needed when there 36444 // is insufficient PES packet data to completely fill the transport stream packet 36445 // payload bytes. Stuffing is accomplished by defining an adaptation field longer than 36446 // the sum of the lengths of the data elements in it, so that the payload bytes 36447 // remaining after the adaptation field exactly accommodates the available PES packet 36448 // data." 36449 // 36450 // If the offset is >
vendor: 10,559 bytes, lines 36450-36777
36450= the length of the packet, then the packet contains no data 36451 // and instead is just adaption field stuffing bytes 36452 return null; 36453 } 36454 36455 var pes = null; 36456 var ptsDtsFlags; // PES packets may be annotated with a PTS value, or a PTS value 36457 // and a DTS value. Determine what combination of values is 36458 // available to work with. 36459 36460 ptsDtsFlags = packet[offset + 7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 36461 // performs all bitwise operations on 32-bit integers but javascript 36462 // supports a much greater range (52-bits) of integer using standard 36463 // mathematical operations. 36464 // We construct a 31-bit value using bitwise operators over the 31 36465 // most significant bits and then multiply by 4 (equal to a left-shift 36466 // of 2) before we add the final 2 least significant bits of the 36467 // timestamp (equal to an OR.) 36468 36469 if (ptsDtsFlags & 0xC0) { 36470 pes = {}; // the PTS and DTS are not written out directly. For information 36471 // on how they are encoded, see 36472 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 36473 36474 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; 36475 pes.pts *= 4; // Left shift by 2 36476 36477 pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs 36478 36479 pes.dts = pes.pts; 36480 36481 if (ptsDtsFlags & 0x40) { 36482 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; 36483 pes.dts *= 4; // Left shift by 2 36484 36485 pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs 36486 } 36487 } 36488 36489 return pes; 36490 }; 36491 36492 var parseNalUnitType = function parseNalUnitType(type) { 36493 switch (type) { 36494 case 0x05: 36495 return 'slice_layer_without_partitioning_rbsp_idr'; 36496 36497 case 0x06: 36498 return 'sei_rbsp'; 36499 36500 case 0x07: 36501 return 'seq_parameter_set_rbsp'; 36502 36503 case 0x08: 36504 return 'pic_parameter_set_rbsp'; 36505 36506 case 0x09: 36507 return 'access_unit_delimiter_rbsp'; 36508 36509 default: 36510 return null; 36511 } 36512 }; 36513 36514 var videoPacketContainsKeyFrame = function videoPacketContainsKeyFrame(packet) { 36515 var offset = 4 + parseAdaptionField(packet); 36516 var frameBuffer = packet.subarray(offset); 36517 var frameI = 0; 36518 var frameSyncPoint = 0; 36519 var foundKeyFrame = false; 36520 var nalType; // advance the sync point to a NAL start, if necessary 36521 36522 for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { 36523 if (frameBuffer[frameSyncPoint + 2] === 1) { 36524 // the sync point is properly aligned 36525 frameI = frameSyncPoint + 5; 36526 break; 36527 } 36528 } 36529 36530 while (frameI < frameBuffer.byteLength) { 36531 // look at the current byte to determine if we've hit the end of 36532 // a NAL unit boundary 36533 switch (frameBuffer[frameI]) { 36534 case 0: 36535 // skip past non-sync sequences 36536 if (frameBuffer[frameI - 1] !== 0) { 36537 frameI += 2; 36538 break; 36539 } else if (frameBuffer[frameI - 2] !== 0) { 36540 frameI++; 36541 break; 36542 } 36543 36544 if (frameSyncPoint + 3 !== frameI - 2) { 36545 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 36546 36547 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 36548 foundKeyFrame = true; 36549 } 36550 } // drop trailing zeroes 36551 36552 36553 do { 36554 frameI++; 36555 } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); 36556 36557 frameSyncPoint = frameI - 2; 36558 frameI += 3; 36559 break; 36560 36561 case 1: 36562 // skip past non-sync sequences 36563 if (frameBuffer[frameI - 1] !== 0 || frameBuffer[frameI - 2] !== 0) { 36564 frameI += 3; 36565 break; 36566 } 36567 36568 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 36569 36570 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 36571 foundKeyFrame = true; 36572 } 36573 36574 frameSyncPoint = frameI - 2; 36575 frameI += 3; 36576 break; 36577 36578 default: 36579 // the current byte isn't a one or zero, so it cannot be part 36580 // of a sync sequence 36581 frameI += 3; 36582 break; 36583 } 36584 } 36585 36586 frameBuffer = frameBuffer.subarray(frameSyncPoint); 36587 frameI -= frameSyncPoint; 36588 frameSyncPoint = 0; // parse the final nal 36589 36590 if (frameBuffer && frameBuffer.byteLength > 3) { 36591 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 36592 36593 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 36594 foundKeyFrame = true; 36595 } 36596 } 36597 36598 return foundKeyFrame; 36599 }; 36600 36601 var probe$1 = { 36602 parseType: parseType$2, 36603 parsePat: parsePat, 36604 parsePmt: parsePmt, 36605 parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, 36606 parsePesType: parsePesType, 36607 parsePesTime: parsePesTime, 36608 videoPacketContainsKeyFrame: videoPacketContainsKeyFrame 36609 }; 36610 36611 /** 36612 * mux.js 36613 * 36614 * Copyright (c) Brightcove 36615 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 36616 * 36617 * Utilities to detect basic properties and metadata about Aac data. 36618 */ 36619 36620 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 36621 36622 var isLikelyAacData = function isLikelyAacData(data) { 36623 if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) { 36624 return true; 36625 } 36626 36627 return false; 36628 }; 36629 36630 var parseSyncSafeInteger = function parseSyncSafeInteger(data) { 36631 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 36632 }; // return a percent-encoded representation of the specified byte range 36633 // @see http://en.wikipedia.org/wiki/Percent-encoding 36634 36635 36636 var percentEncode = function percentEncode(bytes, start, end) { 36637 var i, 36638 result = ''; 36639 36640 for (i = start; i < end; i++) { 36641 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 36642 } 36643 36644 return result; 36645 }; // return the string representation of the specified byte range, 36646 // interpreted as ISO-8859-1. 36647 36648 36649 var parseIso88591 = function parseIso88591(bytes, start, end) { 36650 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 36651 }; 36652 36653 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 36654 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 36655 flags = header[byteIndex + 5], 36656 footerPresent = (flags & 16) >> 4; 36657 36658 if (footerPresent) { 36659 return returnSize + 20; 36660 } 36661 36662 return returnSize + 10; 36663 }; 36664 36665 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 36666 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 36667 middle = header[byteIndex + 4] << 3, 36668 highTwo = header[byteIndex + 3] & 0x3 << 11; 36669 return highTwo | middle | lowThree; 36670 }; 36671 36672 var parseType$3 = function parseType(header, byteIndex) { 36673 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 36674 return 'timed-metadata'; 36675 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 36676 return 'audio'; 36677 } 36678 36679 return null; 36680 }; 36681 36682 var parseSampleRate = function parseSampleRate(packet) { 36683 var i = 0; 36684 36685 while (i + 5 < packet.length) { 36686 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 36687 // If a valid header was not found, jump one forward and attempt to 36688 // find a valid ADTS header starting at the next byte 36689 i++; 36690 continue; 36691 } 36692 36693 return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2]; 36694 } 36695 36696 return null; 36697 }; 36698 36699 var parseAacTimestamp = function parseAacTimestamp(packet) { 36700 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 36701 36702 frameStart = 10; 36703 36704 if (packet[5] & 0x40) { 36705 // advance the frame start past the extended header 36706 frameStart += 4; // header size field 36707 36708 frameStart += parseSyncSafeInteger(packet.subarray(10, 14)); 36709 } // parse one or more ID3 frames 36710 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 36711 36712 36713 do { 36714 // determine the number of bytes in this frame 36715 frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8)); 36716 36717 if (frameSize < 1) { 36718 return null; 36719 } 36720 36721 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 36722 36723 if (frameHeader === 'PRIV') { 36724 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 36725 36726 for (var i = 0; i < frame.byteLength; i++) { 36727 if (frame[i] === 0) { 36728 var owner = parseIso88591(frame, 0, i); 36729 36730 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 36731 var d = frame.subarray(i + 1); 36732 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 36733 size *= 4; 36734 size += d[7] & 0x03; 36735 return size; 36736 } 36737 36738 break; 36739 } 36740 } 36741 } 36742 36743 frameStart += 10; // advance past the frame header 36744 36745 frameStart += frameSize; // advance past the frame body 36746 } while (frameStart < packet.byteLength); 36747 36748 return null; 36749 }; 36750 36751 var utils = { 36752 isLikelyAacData: isLikelyAacData, 36753 parseId3TagSize: parseId3TagSize, 36754 parseAdtsSize: parseAdtsSize, 36755 parseType: parseType$3, 36756 parseSampleRate: parseSampleRate, 36757 parseAacTimestamp: parseAacTimestamp 36758 }; 36759 36760 /** 36761 * mux.js 36762 * 36763 * Copyright (c) Brightcove 36764 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 36765 */ 36766 var ONE_SECOND_IN_TS = 90000, 36767 // 90kHz clock 36768 secondsToVideoTs, 36769 secondsToAudioTs, 36770 videoTsToSeconds, 36771 audioTsToSeconds, 36772 audioTsToVideoTs, 36773 videoTsToAudioTs, 36774 metadataTsToSeconds; 36775 36776 secondsToVideoTs = function secondsToVideoTs(seconds) { 36777 return seconds * ONE_SECOND_IN_TS;
vendor: 10,932 bytes, lines 36778-37117
36778 }; 36779 36780 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 36781 return seconds * sampleRate; 36782 }; 36783 36784 videoTsToSeconds = function videoTsToSeconds(timestamp) { 36785 return timestamp / ONE_SECOND_IN_TS; 36786 }; 36787 36788 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 36789 return timestamp / sampleRate; 36790 }; 36791 36792 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 36793 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 36794 }; 36795 36796 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 36797 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 36798 }; 36799 /** 36800 * Adjust ID3 tag or caption timing information by the timeline pts values 36801 * (if keepOriginalTimestamps is false) and convert to seconds 36802 */ 36803 36804 36805 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) { 36806 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 36807 }; 36808 36809 var clock = { 36810 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 36811 secondsToVideoTs: secondsToVideoTs, 36812 secondsToAudioTs: secondsToAudioTs, 36813 videoTsToSeconds: videoTsToSeconds, 36814 audioTsToSeconds: audioTsToSeconds, 36815 audioTsToVideoTs: audioTsToVideoTs, 36816 videoTsToAudioTs: videoTsToAudioTs, 36817 metadataTsToSeconds: metadataTsToSeconds 36818 }; 36819 36820 var handleRollover$1 = timestampRolloverStream.handleRollover; 36821 var probe$2 = {}; 36822 probe$2.ts = probe$1; 36823 probe$2.aac = utils; 36824 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS; 36825 var MP2T_PACKET_LENGTH = 188, 36826 // bytes 36827 SYNC_BYTE = 0x47; 36828 /** 36829 * walks through segment data looking for pat and pmt packets to parse out 36830 * program map table information 36831 */ 36832 36833 var parsePsi_ = function parsePsi_(bytes, pmt) { 36834 var startIndex = 0, 36835 endIndex = MP2T_PACKET_LENGTH, 36836 packet, 36837 type; 36838 36839 while (endIndex < bytes.byteLength) { 36840 // Look for a pair of start and end sync bytes in the data.. 36841 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 36842 // We found a packet 36843 packet = bytes.subarray(startIndex, endIndex); 36844 type = probe$2.ts.parseType(packet, pmt.pid); 36845 36846 switch (type) { 36847 case 'pat': 36848 if (!pmt.pid) { 36849 pmt.pid = probe$2.ts.parsePat(packet); 36850 } 36851 36852 break; 36853 36854 case 'pmt': 36855 if (!pmt.table) { 36856 pmt.table = probe$2.ts.parsePmt(packet); 36857 } 36858 36859 break; 36860 36861 default: 36862 break; 36863 } // Found the pat and pmt, we can stop walking the segment 36864 36865 36866 if (pmt.pid && pmt.table) { 36867 return; 36868 } 36869 36870 startIndex += MP2T_PACKET_LENGTH; 36871 endIndex += MP2T_PACKET_LENGTH; 36872 continue; 36873 } // If we get here, we have somehow become de-synchronized and we need to step 36874 // forward one byte at a time until we find a pair of sync bytes that denote 36875 // a packet 36876 36877 36878 startIndex++; 36879 endIndex++; 36880 } 36881 }; 36882 /** 36883 * walks through the segment data from the start and end to get timing information 36884 * for the first and last audio pes packets 36885 */ 36886 36887 36888 var parseAudioPes_ = function parseAudioPes_(bytes, pmt, result) { 36889 var startIndex = 0, 36890 endIndex = MP2T_PACKET_LENGTH, 36891 packet, 36892 type, 36893 pesType, 36894 pusi, 36895 parsed; 36896 var endLoop = false; // Start walking from start of segment to get first audio packet 36897 36898 while (endIndex <= bytes.byteLength) { 36899 // Look for a pair of start and end sync bytes in the data.. 36900 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 36901 // We found a packet 36902 packet = bytes.subarray(startIndex, endIndex); 36903 type = probe$2.ts.parseType(packet, pmt.pid); 36904 36905 switch (type) { 36906 case 'pes': 36907 pesType = probe$2.ts.parsePesType(packet, pmt.table); 36908 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 36909 36910 if (pesType === 'audio' && pusi) { 36911 parsed = probe$2.ts.parsePesTime(packet); 36912 36913 if (parsed) { 36914 parsed.type = 'audio'; 36915 result.audio.push(parsed); 36916 endLoop = true; 36917 } 36918 } 36919 36920 break; 36921 36922 default: 36923 break; 36924 } 36925 36926 if (endLoop) { 36927 break; 36928 } 36929 36930 startIndex += MP2T_PACKET_LENGTH; 36931 endIndex += MP2T_PACKET_LENGTH; 36932 continue; 36933 } // If we get here, we have somehow become de-synchronized and we need to step 36934 // forward one byte at a time until we find a pair of sync bytes that denote 36935 // a packet 36936 36937 36938 startIndex++; 36939 endIndex++; 36940 } // Start walking from end of segment to get last audio packet 36941 36942 36943 endIndex = bytes.byteLength; 36944 startIndex = endIndex - MP2T_PACKET_LENGTH; 36945 endLoop = false; 36946 36947 while (startIndex >= 0) { 36948 // Look for a pair of start and end sync bytes in the data.. 36949 if (bytes[startIndex] === SYNC_BYTE && (bytes[endIndex] === SYNC_BYTE || endIndex === bytes.byteLength)) { 36950 // We found a packet 36951 packet = bytes.subarray(startIndex, endIndex); 36952 type = probe$2.ts.parseType(packet, pmt.pid); 36953 36954 switch (type) { 36955 case 'pes': 36956 pesType = probe$2.ts.parsePesType(packet, pmt.table); 36957 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 36958 36959 if (pesType === 'audio' && pusi) { 36960 parsed = probe$2.ts.parsePesTime(packet); 36961 36962 if (parsed) { 36963 parsed.type = 'audio'; 36964 result.audio.push(parsed); 36965 endLoop = true; 36966 } 36967 } 36968 36969 break; 36970 36971 default: 36972 break; 36973 } 36974 36975 if (endLoop) { 36976 break; 36977 } 36978 36979 startIndex -= MP2T_PACKET_LENGTH; 36980 endIndex -= MP2T_PACKET_LENGTH; 36981 continue; 36982 } // If we get here, we have somehow become de-synchronized and we need to step 36983 // forward one byte at a time until we find a pair of sync bytes that denote 36984 // a packet 36985 36986 36987 startIndex--; 36988 endIndex--; 36989 } 36990 }; 36991 /** 36992 * walks through the segment data from the start and end to get timing information 36993 * for the first and last video pes packets as well as timing information for the first 36994 * key frame. 36995 */ 36996 36997 36998 var parseVideoPes_ = function parseVideoPes_(bytes, pmt, result) { 36999 var startIndex = 0, 37000 endIndex = MP2T_PACKET_LENGTH, 37001 packet, 37002 type, 37003 pesType, 37004 pusi, 37005 parsed, 37006 frame, 37007 i, 37008 pes; 37009 var endLoop = false; 37010 var currentFrame = { 37011 data: [], 37012 size: 0 37013 }; // Start walking from start of segment to get first video packet 37014 37015 while (endIndex < bytes.byteLength) { 37016 // Look for a pair of start and end sync bytes in the data.. 37017 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 37018 // We found a packet 37019 packet = bytes.subarray(startIndex, endIndex); 37020 type = probe$2.ts.parseType(packet, pmt.pid); 37021 37022 switch (type) { 37023 case 'pes': 37024 pesType = probe$2.ts.parsePesType(packet, pmt.table); 37025 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 37026 37027 if (pesType === 'video') { 37028 if (pusi && !endLoop) { 37029 parsed = probe$2.ts.parsePesTime(packet); 37030 37031 if (parsed) { 37032 parsed.type = 'video'; 37033 result.video.push(parsed); 37034 endLoop = true; 37035 } 37036 } 37037 37038 if (!result.firstKeyFrame) { 37039 if (pusi) { 37040 if (currentFrame.size !== 0) { 37041 frame = new Uint8Array(currentFrame.size); 37042 i = 0; 37043 37044 while (currentFrame.data.length) { 37045 pes = currentFrame.data.shift(); 37046 frame.set(pes, i); 37047 i += pes.byteLength; 37048 } 37049 37050 if (probe$2.ts.videoPacketContainsKeyFrame(frame)) { 37051 var firstKeyFrame = probe$2.ts.parsePesTime(frame); // PTS/DTS may not be available. Simply *not* setting 37052 // the keyframe seems to work fine with HLS playback 37053 // and definitely preferable to a crash with TypeError... 37054 37055 if (firstKeyFrame) { 37056 result.firstKeyFrame = firstKeyFrame; 37057 result.firstKeyFrame.type = 'video'; 37058 } else { 37059 // eslint-disable-next-line 37060 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.'); 37061 } 37062 } 37063 37064 currentFrame.size = 0; 37065 } 37066 } 37067 37068 currentFrame.data.push(packet); 37069 currentFrame.size += packet.byteLength; 37070 } 37071 } 37072 37073 break; 37074 37075 default: 37076 break; 37077 } 37078 37079 if (endLoop && result.firstKeyFrame) { 37080 break; 37081 } 37082 37083 startIndex += MP2T_PACKET_LENGTH; 37084 endIndex += MP2T_PACKET_LENGTH; 37085 continue; 37086 } // If we get here, we have somehow become de-synchronized and we need to step 37087 // forward one byte at a time until we find a pair of sync bytes that denote 37088 // a packet 37089 37090 37091 startIndex++; 37092 endIndex++; 37093 } // Start walking from end of segment to get last video packet 37094 37095 37096 endIndex = bytes.byteLength; 37097 startIndex = endIndex - MP2T_PACKET_LENGTH; 37098 endLoop = false; 37099 37100 while (startIndex >= 0) { 37101 // Look for a pair of start and end sync bytes in the data.. 37102 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 37103 // We found a packet 37104 packet = bytes.subarray(startIndex, endIndex); 37105 type = probe$2.ts.parseType(packet, pmt.pid); 37106 37107 switch (type) { 37108 case 'pes': 37109 pesType = probe$2.ts.parsePesType(packet, pmt.table); 37110 pusi = probe$2.ts.parsePayloadUnitStartIndicator(packet); 37111 37112 if (pesType === 'video' && pusi) { 37113 parsed = probe$2.ts.parsePesTime(packet); 37114 37115 if (parsed) { 37116 parsed.type = 'video'; 37117 result.video.push(parsed);
vendor: 42,122 bytes, lines 37118-38413
37118 endLoop = true; 37119 } 37120 } 37121 37122 break; 37123 37124 default: 37125 break; 37126 } 37127 37128 if (endLoop) { 37129 break; 37130 } 37131 37132 startIndex -= MP2T_PACKET_LENGTH; 37133 endIndex -= MP2T_PACKET_LENGTH; 37134 continue; 37135 } // If we get here, we have somehow become de-synchronized and we need to step 37136 // forward one byte at a time until we find a pair of sync bytes that denote 37137 // a packet 37138 37139 37140 startIndex--; 37141 endIndex--; 37142 } 37143 }; 37144 /** 37145 * Adjusts the timestamp information for the segment to account for 37146 * rollover and convert to seconds based on pes packet timescale (90khz clock) 37147 */ 37148 37149 37150 var adjustTimestamp_ = function adjustTimestamp_(segmentInfo, baseTimestamp) { 37151 if (segmentInfo.audio && segmentInfo.audio.length) { 37152 var audioBaseTimestamp = baseTimestamp; 37153 37154 if (typeof audioBaseTimestamp === 'undefined') { 37155 audioBaseTimestamp = segmentInfo.audio[0].dts; 37156 } 37157 37158 segmentInfo.audio.forEach(function (info) { 37159 info.dts = handleRollover$1(info.dts, audioBaseTimestamp); 37160 info.pts = handleRollover$1(info.pts, audioBaseTimestamp); // time in seconds 37161 37162 info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; 37163 info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; 37164 }); 37165 } 37166 37167 if (segmentInfo.video && segmentInfo.video.length) { 37168 var videoBaseTimestamp = baseTimestamp; 37169 37170 if (typeof videoBaseTimestamp === 'undefined') { 37171 videoBaseTimestamp = segmentInfo.video[0].dts; 37172 } 37173 37174 segmentInfo.video.forEach(function (info) { 37175 info.dts = handleRollover$1(info.dts, videoBaseTimestamp); 37176 info.pts = handleRollover$1(info.pts, videoBaseTimestamp); // time in seconds 37177 37178 info.dtsTime = info.dts / ONE_SECOND_IN_TS$1; 37179 info.ptsTime = info.pts / ONE_SECOND_IN_TS$1; 37180 }); 37181 37182 if (segmentInfo.firstKeyFrame) { 37183 var frame = segmentInfo.firstKeyFrame; 37184 frame.dts = handleRollover$1(frame.dts, videoBaseTimestamp); 37185 frame.pts = handleRollover$1(frame.pts, videoBaseTimestamp); // time in seconds 37186 37187 frame.dtsTime = frame.dts / ONE_SECOND_IN_TS$1; 37188 frame.ptsTime = frame.dts / ONE_SECOND_IN_TS$1; 37189 } 37190 } 37191 }; 37192 /** 37193 * inspects the aac data stream for start and end time information 37194 */ 37195 37196 37197 var inspectAac_ = function inspectAac_(bytes) { 37198 var endLoop = false, 37199 audioCount = 0, 37200 sampleRate = null, 37201 timestamp = null, 37202 frameSize = 0, 37203 byteIndex = 0, 37204 packet; 37205 37206 while (bytes.length - byteIndex >= 3) { 37207 var type = probe$2.aac.parseType(bytes, byteIndex); 37208 37209 switch (type) { 37210 case 'timed-metadata': 37211 // Exit early because we don't have enough to parse 37212 // the ID3 tag header 37213 if (bytes.length - byteIndex < 10) { 37214 endLoop = true; 37215 break; 37216 } 37217 37218 frameSize = probe$2.aac.parseId3TagSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 37219 // to emit a full packet 37220 37221 if (frameSize > bytes.length) { 37222 endLoop = true; 37223 break; 37224 } 37225 37226 if (timestamp === null) { 37227 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 37228 timestamp = probe$2.aac.parseAacTimestamp(packet); 37229 } 37230 37231 byteIndex += frameSize; 37232 break; 37233 37234 case 'audio': 37235 // Exit early because we don't have enough to parse 37236 // the ADTS frame header 37237 if (bytes.length - byteIndex < 7) { 37238 endLoop = true; 37239 break; 37240 } 37241 37242 frameSize = probe$2.aac.parseAdtsSize(bytes, byteIndex); // Exit early if we don't have enough in the buffer 37243 // to emit a full packet 37244 37245 if (frameSize > bytes.length) { 37246 endLoop = true; 37247 break; 37248 } 37249 37250 if (sampleRate === null) { 37251 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 37252 sampleRate = probe$2.aac.parseSampleRate(packet); 37253 } 37254 37255 audioCount++; 37256 byteIndex += frameSize; 37257 break; 37258 37259 default: 37260 byteIndex++; 37261 break; 37262 } 37263 37264 if (endLoop) { 37265 return null; 37266 } 37267 } 37268 37269 if (sampleRate === null || timestamp === null) { 37270 return null; 37271 } 37272 37273 var audioTimescale = ONE_SECOND_IN_TS$1 / sampleRate; 37274 var result = { 37275 audio: [{ 37276 type: 'audio', 37277 dts: timestamp, 37278 pts: timestamp 37279 }, { 37280 type: 'audio', 37281 dts: timestamp + audioCount * 1024 * audioTimescale, 37282 pts: timestamp + audioCount * 1024 * audioTimescale 37283 }] 37284 }; 37285 return result; 37286 }; 37287 /** 37288 * inspects the transport stream segment data for start and end time information 37289 * of the audio and video tracks (when present) as well as the first key frame's 37290 * start time. 37291 */ 37292 37293 37294 var inspectTs_ = function inspectTs_(bytes) { 37295 var pmt = { 37296 pid: null, 37297 table: null 37298 }; 37299 var result = {}; 37300 parsePsi_(bytes, pmt); 37301 37302 for (var pid in pmt.table) { 37303 if (pmt.table.hasOwnProperty(pid)) { 37304 var type = pmt.table[pid]; 37305 37306 switch (type) { 37307 case streamTypes.H264_STREAM_TYPE: 37308 result.video = []; 37309 parseVideoPes_(bytes, pmt, result); 37310 37311 if (result.video.length === 0) { 37312 delete result.video; 37313 } 37314 37315 break; 37316 37317 case streamTypes.ADTS_STREAM_TYPE: 37318 result.audio = []; 37319 parseAudioPes_(bytes, pmt, result); 37320 37321 if (result.audio.length === 0) { 37322 delete result.audio; 37323 } 37324 37325 break; 37326 37327 default: 37328 break; 37329 } 37330 } 37331 } 37332 37333 return result; 37334 }; 37335 /** 37336 * Inspects segment byte data and returns an object with start and end timing information 37337 * 37338 * @param {Uint8Array} bytes The segment byte data 37339 * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame 37340 * timestamps for rollover. This value must be in 90khz clock. 37341 * @return {Object} Object containing start and end frame timing info of segment. 37342 */ 37343 37344 37345 var inspect = function inspect(bytes, baseTimestamp) { 37346 var isAacData = probe$2.aac.isLikelyAacData(bytes); 37347 var result; 37348 37349 if (isAacData) { 37350 result = inspectAac_(bytes); 37351 } else { 37352 result = inspectTs_(bytes); 37353 } 37354 37355 if (!result || !result.audio && !result.video) { 37356 return null; 37357 } 37358 37359 adjustTimestamp_(result, baseTimestamp); 37360 return result; 37361 }; 37362 37363 var tsInspector = { 37364 inspect: inspect, 37365 parseAudioPes_: parseAudioPes_ 37366 }; 37367 37368 /* 37369 * pkcs7.pad 37370 * https://github.com/brightcove/pkcs7 37371 * 37372 * Copyright (c) 2014 Brightcove 37373 * Licensed under the apache2 license. 37374 */ 37375 /** 37376 * Returns the subarray of a Uint8Array without PKCS#7 padding. 37377 * @param padded {Uint8Array} unencrypted bytes that have been padded 37378 * @return {Uint8Array} the unpadded bytes 37379 * @see http://tools.ietf.org/html/rfc5652 37380 */ 37381 37382 function unpad(padded) { 37383 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 37384 } 37385 37386 var classCallCheck = function classCallCheck(instance, Constructor) { 37387 if (!(instance instanceof Constructor)) { 37388 throw new TypeError("Cannot call a class as a function"); 37389 } 37390 }; 37391 37392 var createClass = function () { 37393 function defineProperties(target, props) { 37394 for (var i = 0; i < props.length; i++) { 37395 var descriptor = props[i]; 37396 descriptor.enumerable = descriptor.enumerable || false; 37397 descriptor.configurable = true; 37398 if ("value" in descriptor) descriptor.writable = true; 37399 Object.defineProperty(target, descriptor.key, descriptor); 37400 } 37401 } 37402 37403 return function (Constructor, protoProps, staticProps) { 37404 if (protoProps) defineProperties(Constructor.prototype, protoProps); 37405 if (staticProps) defineProperties(Constructor, staticProps); 37406 return Constructor; 37407 }; 37408 }(); 37409 37410 var inherits = function inherits(subClass, superClass) { 37411 if (typeof superClass !== "function" && superClass !== null) { 37412 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 37413 } 37414 37415 subClass.prototype = Object.create(superClass && superClass.prototype, { 37416 constructor: { 37417 value: subClass, 37418 enumerable: false, 37419 writable: true, 37420 configurable: true 37421 } 37422 }); 37423 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 37424 }; 37425 37426 var possibleConstructorReturn = function possibleConstructorReturn(self, call) { 37427 if (!self) { 37428 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 37429 } 37430 37431 return call && (typeof call === "object" || typeof call === "function") ? call : self; 37432 }; 37433 /** 37434 * @file aes.js 37435 * 37436 * This file contains an adaptation of the AES decryption algorithm 37437 * from the Standford Javascript Cryptography Library. That work is 37438 * covered by the following copyright and permissions notice: 37439 * 37440 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 37441 * All rights reserved. 37442 * 37443 * Redistribution and use in source and binary forms, with or without 37444 * modification, are permitted provided that the following conditions are 37445 * met: 37446 * 37447 * 1. Redistributions of source code must retain the above copyright 37448 * notice, this list of conditions and the following disclaimer. 37449 * 37450 * 2. Redistributions in binary form must reproduce the above 37451 * copyright notice, this list of conditions and the following 37452 * disclaimer in the documentation and/or other materials provided 37453 * with the distribution. 37454 * 37455 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 37456 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 37457 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37458 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 37459 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 37460 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37461 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 37462 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 37463 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 37464 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 37465 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37466 * 37467 * The views and conclusions contained in the software and documentation 37468 * are those of the authors and should not be interpreted as representing 37469 * official policies, either expressed or implied, of the authors. 37470 */ 37471 37472 /** 37473 * Expand the S-box tables. 37474 * 37475 * @private 37476 */ 37477 37478 37479 var precompute = function precompute() { 37480 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 37481 var encTable = tables[0]; 37482 var decTable = tables[1]; 37483 var sbox = encTable[4]; 37484 var sboxInv = decTable[4]; 37485 var i = void 0; 37486 var x = void 0; 37487 var xInv = void 0; 37488 var d = []; 37489 var th = []; 37490 var x2 = void 0; 37491 var x4 = void 0; 37492 var x8 = void 0; 37493 var s = void 0; 37494 var tEnc = void 0; 37495 var tDec = void 0; // Compute double and third tables 37496 37497 for (i = 0; i < 256; i++) { 37498 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 37499 } 37500 37501 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 37502 // Compute sbox 37503 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 37504 s = s >> 8 ^ s & 255 ^ 99; 37505 sbox[x] = s; 37506 sboxInv[s] = x; // Compute MixColumns 37507 37508 x8 = d[x4 = d[x2 = d[x]]]; 37509 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 37510 tEnc = d[s] * 0x101 ^ s * 0x1010100; 37511 37512 for (i = 0; i < 4; i++) { 37513 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 37514 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 37515 } 37516 } // Compactify. Considerable speedup on Firefox. 37517 37518 37519 for (i = 0; i < 5; i++) { 37520 encTable[i] = encTable[i].slice(0); 37521 decTable[i] = decTable[i].slice(0); 37522 } 37523 37524 return tables; 37525 }; 37526 37527 var aesTables = null; 37528 /** 37529 * Schedule out an AES key for both encryption and decryption. This 37530 * is a low-level class. Use a cipher mode to do bulk encryption. 37531 * 37532 * @class AES 37533 * @param key {Array} The key as an array of 4, 6 or 8 words. 37534 */ 37535 37536 var AES = function () { 37537 function AES(key) { 37538 classCallCheck(this, AES); 37539 /** 37540 * The expanded S-box and inverse S-box tables. These will be computed 37541 * on the client so that we don't have to send them down the wire. 37542 * 37543 * There are two tables, _tables[0] is for encryption and 37544 * _tables[1] is for decryption. 37545 * 37546 * The first 4 sub-tables are the expanded S-box with MixColumns. The 37547 * last (_tables[01][4]) is the S-box itself. 37548 * 37549 * @private 37550 */ 37551 // if we have yet to precompute the S-box tables 37552 // do so now 37553 37554 if (!aesTables) { 37555 aesTables = precompute(); 37556 } // then make a copy of that object for use 37557 37558 37559 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()]]; 37560 var i = void 0; 37561 var j = void 0; 37562 var tmp = void 0; 37563 var encKey = void 0; 37564 var decKey = void 0; 37565 var sbox = this._tables[0][4]; 37566 var decTable = this._tables[1]; 37567 var keyLen = key.length; 37568 var rcon = 1; 37569 37570 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 37571 throw new Error('Invalid aes key size'); 37572 } 37573 37574 encKey = key.slice(0); 37575 decKey = []; 37576 this._key = [encKey, decKey]; // schedule encryption keys 37577 37578 for (i = keyLen; i < 4 * keyLen + 28; i++) { 37579 tmp = encKey[i - 1]; // apply sbox 37580 37581 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 37582 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 37583 37584 if (i % keyLen === 0) { 37585 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 37586 rcon = rcon << 1 ^ (rcon >> 7) * 283; 37587 } 37588 } 37589 37590 encKey[i] = encKey[i - keyLen] ^ tmp; 37591 } // schedule decryption keys 37592 37593 37594 for (j = 0; i; j++, i--) { 37595 tmp = encKey[j & 3 ? i : i - 4]; 37596 37597 if (i <= 4 || j < 4) { 37598 decKey[j] = tmp; 37599 } else { 37600 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 37601 } 37602 } 37603 } 37604 /** 37605 * Decrypt 16 bytes, specified as four 32-bit words. 37606 * 37607 * @param {Number} encrypted0 the first word to decrypt 37608 * @param {Number} encrypted1 the second word to decrypt 37609 * @param {Number} encrypted2 the third word to decrypt 37610 * @param {Number} encrypted3 the fourth word to decrypt 37611 * @param {Int32Array} out the array to write the decrypted words 37612 * into 37613 * @param {Number} offset the offset into the output array to start 37614 * writing results 37615 * @return {Array} The plaintext. 37616 */ 37617 37618 37619 AES.prototype.decrypt = function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 37620 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 37621 37622 var a = encrypted0 ^ key[0]; 37623 var b = encrypted3 ^ key[1]; 37624 var c = encrypted2 ^ key[2]; 37625 var d = encrypted1 ^ key[3]; 37626 var a2 = void 0; 37627 var b2 = void 0; 37628 var c2 = void 0; // key.length === 2 ? 37629 37630 var nInnerRounds = key.length / 4 - 2; 37631 var i = void 0; 37632 var kIndex = 4; 37633 var table = this._tables[1]; // load up the tables 37634 37635 var table0 = table[0]; 37636 var table1 = table[1]; 37637 var table2 = table[2]; 37638 var table3 = table[3]; 37639 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 37640 37641 for (i = 0; i < nInnerRounds; i++) { 37642 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 37643 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 37644 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 37645 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 37646 kIndex += 4; 37647 a = a2; 37648 b = b2; 37649 c = c2; 37650 } // Last round. 37651 37652 37653 for (i = 0; i < 4; i++) { 37654 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 37655 a2 = a; 37656 a = b; 37657 b = c; 37658 c = d; 37659 d = a2; 37660 } 37661 }; 37662 37663 return AES; 37664 }(); 37665 /** 37666 * @file stream.js 37667 */ 37668 37669 /** 37670 * A lightweight readable stream implemention that handles event dispatching. 37671 * 37672 * @class Stream 37673 */ 37674 37675 37676 var Stream$2 = function () { 37677 function Stream() { 37678 classCallCheck(this, Stream); 37679 this.listeners = {}; 37680 } 37681 /** 37682 * Add a listener for a specified event type. 37683 * 37684 * @param {String} type the event name 37685 * @param {Function} listener the callback to be invoked when an event of 37686 * the specified type occurs 37687 */ 37688 37689 37690 Stream.prototype.on = function on(type, listener) { 37691 if (!this.listeners[type]) { 37692 this.listeners[type] = []; 37693 } 37694 37695 this.listeners[type].push(listener); 37696 }; 37697 /** 37698 * Remove a listener for a specified event type. 37699 * 37700 * @param {String} type the event name 37701 * @param {Function} listener a function previously registered for this 37702 * type of event through `on` 37703 * @return {Boolean} if we could turn it off or not 37704 */ 37705 37706 37707 Stream.prototype.off = function off(type, listener) { 37708 if (!this.listeners[type]) { 37709 return false; 37710 } 37711 37712 var index = this.listeners[type].indexOf(listener); 37713 this.listeners[type].splice(index, 1); 37714 return index > -1; 37715 }; 37716 /** 37717 * Trigger an event of the specified type on this stream. Any additional 37718 * arguments to this function are passed as parameters to event listeners. 37719 * 37720 * @param {String} type the event name 37721 */ 37722 37723 37724 Stream.prototype.trigger = function trigger(type) { 37725 var callbacks = this.listeners[type]; 37726 37727 if (!callbacks) { 37728 return; 37729 } // Slicing the arguments on every invocation of this method 37730 // can add a significant amount of overhead. Avoid the 37731 // intermediate object creation for the common case of a 37732 // single callback argument 37733 37734 37735 if (arguments.length === 2) { 37736 var length = callbacks.length; 37737 37738 for (var i = 0; i < length; ++i) { 37739 callbacks[i].call(this, arguments[1]); 37740 } 37741 } else { 37742 var args = Array.prototype.slice.call(arguments, 1); 37743 var _length = callbacks.length; 37744 37745 for (var _i = 0; _i < _length; ++_i) { 37746 callbacks[_i].apply(this, args); 37747 } 37748 } 37749 }; 37750 /** 37751 * Destroys the stream and cleans up. 37752 */ 37753 37754 37755 Stream.prototype.dispose = function dispose() { 37756 this.listeners = {}; 37757 }; 37758 /** 37759 * Forwards all `data` events on this stream to the destination stream. The 37760 * destination stream should provide a method `push` to receive the data 37761 * events as they arrive. 37762 * 37763 * @param {Stream} destination the stream that will receive all `data` events 37764 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 37765 */ 37766 37767 37768 Stream.prototype.pipe = function pipe(destination) { 37769 this.on('data', function (data) { 37770 destination.push(data); 37771 }); 37772 }; 37773 37774 return Stream; 37775 }(); 37776 /** 37777 * @file async-stream.js 37778 */ 37779 37780 /** 37781 * A wrapper around the Stream class to use setTiemout 37782 * and run stream "jobs" Asynchronously 37783 * 37784 * @class AsyncStream 37785 * @extends Stream 37786 */ 37787 37788 37789 var AsyncStream = function (_Stream) { 37790 inherits(AsyncStream, _Stream); 37791 37792 function AsyncStream() { 37793 classCallCheck(this, AsyncStream); 37794 37795 var _this = possibleConstructorReturn(this, _Stream.call(this, Stream$2)); 37796 37797 _this.jobs = []; 37798 _this.delay = 1; 37799 _this.timeout_ = null; 37800 return _this; 37801 } 37802 /** 37803 * process an async job 37804 * 37805 * @private 37806 */ 37807 37808 37809 AsyncStream.prototype.processJob_ = function processJob_() { 37810 this.jobs.shift()(); 37811 37812 if (this.jobs.length) { 37813 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 37814 } else { 37815 this.timeout_ = null; 37816 } 37817 }; 37818 /** 37819 * push a job into the stream 37820 * 37821 * @param {Function} job the job to push into the stream 37822 */ 37823 37824 37825 AsyncStream.prototype.push = function push(job) { 37826 this.jobs.push(job); 37827 37828 if (!this.timeout_) { 37829 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 37830 } 37831 }; 37832 37833 return AsyncStream; 37834 }(Stream$2); 37835 /** 37836 * @file decrypter.js 37837 * 37838 * An asynchronous implementation of AES-128 CBC decryption with 37839 * PKCS#7 padding. 37840 */ 37841 37842 /** 37843 * Convert network-order (big-endian) bytes into their little-endian 37844 * representation. 37845 */ 37846 37847 37848 var ntoh = function ntoh(word) { 37849 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 37850 }; 37851 /** 37852 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 37853 * 37854 * @param {Uint8Array} encrypted the encrypted bytes 37855 * @param {Uint32Array} key the bytes of the decryption key 37856 * @param {Uint32Array} initVector the initialization vector (IV) to 37857 * use for the first round of CBC. 37858 * @return {Uint8Array} the decrypted bytes 37859 * 37860 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 37861 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 37862 * @see https://tools.ietf.org/html/rfc2315 37863 */ 37864 37865 37866 var decrypt = function decrypt(encrypted, key, initVector) { 37867 // word-level access to the encrypted bytes 37868 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 37869 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 37870 37871 var decrypted = new Uint8Array(encrypted.byteLength); 37872 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 37873 // decrypted data 37874 37875 var init0 = void 0; 37876 var init1 = void 0; 37877 var init2 = void 0; 37878 var init3 = void 0; 37879 var encrypted0 = void 0; 37880 var encrypted1 = void 0; 37881 var encrypted2 = void 0; 37882 var encrypted3 = void 0; // iteration variable 37883 37884 var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the 37885 // passed-in reference and easier access 37886 37887 init0 = initVector[0]; 37888 init1 = initVector[1]; 37889 init2 = initVector[2]; 37890 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 37891 // to each decrypted block 37892 37893 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 37894 // convert big-endian (network order) words into little-endian 37895 // (javascript order) 37896 encrypted0 = ntoh(encrypted32[wordIx]); 37897 encrypted1 = ntoh(encrypted32[wordIx + 1]); 37898 encrypted2 = ntoh(encrypted32[wordIx + 2]); 37899 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 37900 37901 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 37902 // plaintext 37903 37904 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 37905 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 37906 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 37907 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 37908 37909 init0 = encrypted0; 37910 init1 = encrypted1; 37911 init2 = encrypted2; 37912 init3 = encrypted3; 37913 } 37914 37915 return decrypted; 37916 }; 37917 /** 37918 * The `Decrypter` class that manages decryption of AES 37919 * data through `AsyncStream` objects and the `decrypt` 37920 * function 37921 * 37922 * @param {Uint8Array} encrypted the encrypted bytes 37923 * @param {Uint32Array} key the bytes of the decryption key 37924 * @param {Uint32Array} initVector the initialization vector (IV) to 37925 * @param {Function} done the function to run when done 37926 * @class Decrypter 37927 */ 37928 37929 37930 var Decrypter = function () { 37931 function Decrypter(encrypted, key, initVector, done) { 37932 classCallCheck(this, Decrypter); 37933 var step = Decrypter.STEP; 37934 var encrypted32 = new Int32Array(encrypted.buffer); 37935 var decrypted = new Uint8Array(encrypted.byteLength); 37936 var i = 0; 37937 this.asyncStream_ = new AsyncStream(); // split up the encryption job and do the individual chunks asynchronously 37938 37939 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 37940 37941 for (i = step; i < encrypted32.length; i += step) { 37942 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 37943 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 37944 } // invoke the done() callback when everything is finished 37945 37946 37947 this.asyncStream_.push(function () { 37948 // remove pkcs#7 padding from the decrypted bytes 37949 done(null, unpad(decrypted)); 37950 }); 37951 } 37952 /** 37953 * a getter for step the maximum number of bytes to process at one time 37954 * 37955 * @return {Number} the value of step 32000 37956 */ 37957 37958 /** 37959 * @private 37960 */ 37961 37962 37963 Decrypter.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 37964 return function () { 37965 var bytes = decrypt(encrypted, key, initVector); 37966 decrypted.set(bytes, encrypted.byteOffset); 37967 }; 37968 }; 37969 37970 createClass(Decrypter, null, [{ 37971 key: 'STEP', 37972 get: function get$$1() { 37973 // 4 * 8000; 37974 return 32000; 37975 } 37976 }]); 37977 return Decrypter; 37978 }(); 37979 37980 /** 37981 * @videojs/http-streaming 37982 * @version 1.10.6 37983 * @copyright 2019 Brightcove, Inc 37984 * @license Apache-2.0 37985 */ 37986 /** 37987 * @file resolve-url.js - Handling how URLs are resolved and manipulated 37988 */ 37989 37990 var resolveUrl$1 = function resolveUrl(baseURL, relativeURL) { 37991 // return early if we don't need to resolve 37992 if (/^[a-z]+:/i.test(relativeURL)) { 37993 return relativeURL; 37994 } // if the base URL is relative then combine with the current location 37995 37996 37997 if (!/\/\//i.test(baseURL)) { 37998 baseURL = urlToolkit.buildAbsoluteURL(window$1.location.href, baseURL); 37999 } 38000 38001 return urlToolkit.buildAbsoluteURL(baseURL, relativeURL); 38002 }; 38003 /** 38004 * Checks whether xhr request was redirected and returns correct url depending 38005 * on `handleManifestRedirects` option 38006 * 38007 * @api private 38008 * 38009 * @param {String} url - an url being requested 38010 * @param {XMLHttpRequest} req - xhr request result 38011 * 38012 * @return {String} 38013 */ 38014 38015 38016 var resolveManifestRedirect = function resolveManifestRedirect(handleManifestRedirect, url, req) { 38017 // To understand how the responseURL below is set and generated: 38018 // - https://fetch.spec.whatwg.org/#concept-response-url 38019 // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling 38020 if (handleManifestRedirect && req.responseURL && url !== req.responseURL) { 38021 return req.responseURL; 38022 } 38023 38024 return url; 38025 }; 38026 38027 var classCallCheck$1 = function classCallCheck(instance, Constructor) { 38028 if (!(instance instanceof Constructor)) { 38029 throw new TypeError("Cannot call a class as a function"); 38030 } 38031 }; 38032 38033 var createClass$1 = function () { 38034 function defineProperties(target, props) { 38035 for (var i = 0; i < props.length; i++) { 38036 var descriptor = props[i]; 38037 descriptor.enumerable = descriptor.enumerable || false; 38038 descriptor.configurable = true; 38039 if ("value" in descriptor) descriptor.writable = true; 38040 Object.defineProperty(target, descriptor.key, descriptor); 38041 } 38042 } 38043 38044 return function (Constructor, protoProps, staticProps) { 38045 if (protoProps) defineProperties(Constructor.prototype, protoProps); 38046 if (staticProps) defineProperties(Constructor, staticProps); 38047 return Constructor; 38048 }; 38049 }(); 38050 38051 var get$1 = function get(object, property, receiver) { 38052 if (object === null) object = Function.prototype; 38053 var desc = Object.getOwnPropertyDescriptor(object, property); 38054 38055 if (desc === undefined) { 38056 var parent = Object.getPrototypeOf(object); 38057 38058 if (parent === null) { 38059 return undefined; 38060 } else { 38061 return get(parent, property, receiver); 38062 } 38063 } else if ("value" in desc) { 38064 return desc.value; 38065 } else { 38066 var getter = desc.get; 38067 38068 if (getter === undefined) { 38069 return undefined; 38070 } 38071 38072 return getter.call(receiver); 38073 } 38074 }; 38075 38076 var inherits$1 = function inherits(subClass, superClass) { 38077 if (typeof superClass !== "function" && superClass !== null) { 38078 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 38079 } 38080 38081 subClass.prototype = Object.create(superClass && superClass.prototype, { 38082 constructor: { 38083 value: subClass, 38084 enumerable: false, 38085 writable: true, 38086 configurable: true 38087 } 38088 }); 38089 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 38090 }; 38091 38092 var possibleConstructorReturn$1 = function possibleConstructorReturn(self, call) { 38093 if (!self) { 38094 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 38095 } 38096 38097 return call && (typeof call === "object" || typeof call === "function") ? call : self; 38098 }; 38099 38100 var slicedToArray = function () { 38101 function sliceIterator(arr, i) { 38102 var _arr = []; 38103 var _n = true; 38104 var _d = false; 38105 var _e = undefined; 38106 38107 try { 38108 for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 38109 _arr.push(_s.value); 38110 38111 if (i && _arr.length === i) break; 38112 } 38113 } catch (err) { 38114 _d = true; 38115 _e = err; 38116 } finally { 38117 try { 38118 if (!_n && _i["return"]) _i["return"](); 38119 } finally { 38120 if (_d) throw _e; 38121 } 38122 } 38123 38124 return _arr; 38125 } 38126 38127 return function (arr, i) { 38128 if (Array.isArray(arr)) { 38129 return arr; 38130 } else if (Symbol.iterator in Object(arr)) { 38131 return sliceIterator(arr, i); 38132 } else { 38133 throw new TypeError("Invalid attempt to destructure non-iterable instance"); 38134 } 38135 }; 38136 }(); 38137 /** 38138 * @file playlist-loader.js 38139 * 38140 * A state machine that manages the loading, caching, and updating of 38141 * M3U8 playlists. 38142 * 38143 */ 38144 38145 38146 var mergeOptions$1 = videojs$1.mergeOptions, 38147 EventTarget$1 = videojs$1.EventTarget, 38148 log$1 = videojs$1.log; 38149 /** 38150 * Loops through all supported media groups in master and calls the provided 38151 * callback for each group 38152 * 38153 * @param {Object} master 38154 * The parsed master manifest object 38155 * @param {Function} callback 38156 * Callback to call for each media group 38157 */ 38158 38159 var forEachMediaGroup = function forEachMediaGroup(master, callback) { 38160 ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) { 38161 for (var groupKey in master.mediaGroups[mediaType]) { 38162 for (var labelKey in master.mediaGroups[mediaType][groupKey]) { 38163 var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey]; 38164 callback(mediaProperties, mediaType, groupKey, labelKey); 38165 } 38166 } 38167 }); 38168 }; 38169 /** 38170 * Returns a new array of segments that is the result of merging 38171 * properties from an older list of segments onto an updated 38172 * list. No properties on the updated playlist will be overridden. 38173 * 38174 * @param {Array} original the outdated list of segments 38175 * @param {Array} update the updated list of segments 38176 * @param {Number=} offset the index of the first update 38177 * segment in the original segment list. For non-live playlists, 38178 * this should always be zero and does not need to be 38179 * specified. For live playlists, it should be the difference 38180 * between the media sequence numbers in the original and updated 38181 * playlists. 38182 * @return a list of merged segment objects 38183 */ 38184 38185 38186 var updateSegments = function updateSegments(original, update, offset) { 38187 var result = update.slice(); 38188 offset = offset || 0; 38189 var length = Math.min(original.length, update.length + offset); 38190 38191 for (var i = offset; i < length; i++) { 38192 result[i - offset] = mergeOptions$1(original[i], result[i - offset]); 38193 } 38194 38195 return result; 38196 }; 38197 38198 var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) { 38199 if (!segment.resolvedUri) { 38200 segment.resolvedUri = resolveUrl$1(baseUri, segment.uri); 38201 } 38202 38203 if (segment.key && !segment.key.resolvedUri) { 38204 segment.key.resolvedUri = resolveUrl$1(baseUri, segment.key.uri); 38205 } 38206 38207 if (segment.map && !segment.map.resolvedUri) { 38208 segment.map.resolvedUri = resolveUrl$1(baseUri, segment.map.uri); 38209 } 38210 }; 38211 /** 38212 * Returns a new master playlist that is the result of merging an 38213 * updated media playlist into the original version. If the 38214 * updated media playlist does not match any of the playlist 38215 * entries in the original master playlist, null is returned. 38216 * 38217 * @param {Object} master a parsed master M3U8 object 38218 * @param {Object} media a parsed media M3U8 object 38219 * @return {Object} a new object that represents the original 38220 * master playlist with the updated media playlist merged in, or 38221 * null if the merge produced no change. 38222 */ 38223 38224 38225 var updateMaster = function updateMaster(master, media) { 38226 var result = mergeOptions$1(master, {}); 38227 var playlist = result.playlists[media.uri]; 38228 38229 if (!playlist) { 38230 return null; 38231 } // consider the playlist unchanged if the number of segments is equal, the media 38232 // sequence number is unchanged, and this playlist hasn't become the end of the playlist 38233 38234 38235 if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.endList === media.endList && playlist.mediaSequence === media.mediaSequence) { 38236 return null; 38237 } 38238 38239 var mergedPlaylist = mergeOptions$1(playlist, media); // if the update could overlap existing segment information, merge the two segment lists 38240 38241 if (playlist.segments) { 38242 mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); 38243 } // resolve any segment URIs to prevent us from having to do it later 38244 38245 38246 mergedPlaylist.segments.forEach(function (segment) { 38247 resolveSegmentUris(segment, mergedPlaylist.resolvedUri); 38248 }); // TODO Right now in the playlists array there are two references to each playlist, one 38249 // that is referenced by index, and one by URI. The index reference may no longer be 38250 // necessary. 38251 38252 for (var i = 0; i < result.playlists.length; i++) { 38253 if (result.playlists[i].uri === media.uri) { 38254 result.playlists[i] = mergedPlaylist; 38255 } 38256 } 38257 38258 result.playlists[media.uri] = mergedPlaylist; 38259 return result; 38260 }; 38261 38262 var setupMediaPlaylists = function setupMediaPlaylists(master) { 38263 // setup by-URI lookups and resolve media playlist URIs 38264 var i = master.playlists.length; 38265 38266 while (i--) { 38267 var playlist = master.playlists[i]; 38268 master.playlists[playlist.uri] = playlist; 38269 playlist.resolvedUri = resolveUrl$1(master.uri, playlist.uri); 38270 playlist.id = i; 38271 38272 if (!playlist.attributes) { 38273 // Although the spec states an #EXT-X-STREAM-INF tag MUST have a 38274 // BANDWIDTH attribute, we can play the stream without it. This means a poorly 38275 // formatted master playlist may not have an attribute list. An attributes 38276 // property is added here to prevent undefined references when we encounter 38277 // this scenario. 38278 playlist.attributes = {}; 38279 log$1.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.'); 38280 } 38281 } 38282 }; 38283 38284 var resolveMediaGroupUris = function resolveMediaGroupUris(master) { 38285 forEachMediaGroup(master, function (properties) { 38286 if (properties.uri) { 38287 properties.resolvedUri = resolveUrl$1(master.uri, properties.uri); 38288 } 38289 }); 38290 }; 38291 /** 38292 * Calculates the time to wait before refreshing a live playlist 38293 * 38294 * @param {Object} media 38295 * The current media 38296 * @param {Boolean} update 38297 * True if there were any updates from the last refresh, false otherwise 38298 * @return {Number} 38299 * The time in ms to wait before refreshing the live playlist 38300 */ 38301 38302 38303 var refreshDelay = function refreshDelay(media, update) { 38304 var lastSegment = media.segments[media.segments.length - 1]; 38305 var delay = void 0; 38306 38307 if (update && lastSegment && lastSegment.duration) { 38308 delay = lastSegment.duration * 1000; 38309 } else { 38310 // if the playlist is unchanged since the last reload or last segment duration 38311 // cannot be determined, try again after half the target duration 38312 delay = (media.targetDuration || 10) * 500; 38313 } 38314 38315 return delay; 38316 }; 38317 /** 38318 * Load a playlist from a remote location 38319 * 38320 * @class PlaylistLoader 38321 * @extends Stream 38322 * @param {String} srcUrl the url to start with 38323 * @param {Boolean} withCredentials the withCredentials xhr option 38324 * @constructor 38325 */ 38326 38327 38328 var PlaylistLoader = function (_EventTarget) { 38329 inherits$1(PlaylistLoader, _EventTarget); 38330 38331 function PlaylistLoader(srcUrl, hls) { 38332 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 38333 classCallCheck$1(this, PlaylistLoader); 38334 38335 var _this = possibleConstructorReturn$1(this, (PlaylistLoader.__proto__ || Object.getPrototypeOf(PlaylistLoader)).call(this)); 38336 38337 var _options$withCredenti = options.withCredentials, 38338 withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti, 38339 _options$handleManife = options.handleManifestRedirects, 38340 handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife; 38341 _this.srcUrl = srcUrl; 38342 _this.hls_ = hls; 38343 _this.withCredentials = withCredentials; 38344 _this.handleManifestRedirects = handleManifestRedirects; 38345 var hlsOptions = hls.options_; 38346 _this.customTagParsers = hlsOptions && hlsOptions.customTagParsers || []; 38347 _this.customTagMappers = hlsOptions && hlsOptions.customTagMappers || []; 38348 38349 if (!_this.srcUrl) { 38350 throw new Error('A non-empty playlist URL is required'); 38351 } // initialize the loader state 38352 38353 38354 _this.state = 'HAVE_NOTHING'; // live playlist staleness timeout 38355 38356 _this.on('mediaupdatetimeout', function () { 38357 if (_this.state !== 'HAVE_METADATA') { 38358 // only refresh the media playlist if no other activity is going on 38359 return; 38360 } 38361 38362 _this.state = 'HAVE_CURRENT_METADATA'; 38363 _this.request = _this.hls_.xhr({ 38364 uri: resolveUrl$1(_this.master.uri, _this.media().uri), 38365 withCredentials: _this.withCredentials 38366 }, function (error, req) { 38367 // disposed 38368 if (!_this.request) { 38369 return; 38370 } 38371 38372 if (error) { 38373 return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA'); 38374 } 38375 38376 _this.haveMetadata(_this.request, _this.media().uri); 38377 }); 38378 }); 38379 38380 return _this; 38381 } 38382 38383 createClass$1(PlaylistLoader, [{ 38384 key: 'playlistRequestError', 38385 value: function playlistRequestError(xhr, url, startingState) { 38386 // any in-flight request is now finished 38387 this.request = null; 38388 38389 if (startingState) { 38390 this.state = startingState; 38391 } 38392 38393 this.error = { 38394 playlist: this.master.playlists[url], 38395 status: xhr.status, 38396 message: 'HLS playlist request error at URL: ' + url + '.', 38397 responseText: xhr.responseText, 38398 code: xhr.status >= 500 ? 4 : 2 38399 }; 38400 this.trigger('error'); 38401 } // update the playlist loader's state in response to a new or 38402 // updated playlist. 38403 38404 }, { 38405 key: 'haveMetadata', 38406 value: function haveMetadata(xhr, url) { 38407 var _this2 = this; // any in-flight request is now finished 38408 38409 38410 this.request = null; 38411 this.state = 'HAVE_METADATA'; 38412 var parser = new Parser(); // adding custom tag parsers 38413
vendor: 5,588 bytes, lines 38414-38578
38414 this.customTagParsers.forEach(function (customParser) { 38415 return parser.addParser(customParser); 38416 }); // adding custom tag mappers 38417 38418 this.customTagMappers.forEach(function (mapper) { 38419 return parser.addTagMapper(mapper); 38420 }); 38421 parser.push(xhr.responseText); 38422 parser.end(); 38423 parser.manifest.uri = url; // m3u8-parser does not attach an attributes property to media playlists so make 38424 // sure that the property is attached to avoid undefined reference errors 38425 38426 parser.manifest.attributes = parser.manifest.attributes || {}; // merge this playlist into the master 38427 38428 var update = updateMaster(this.master, parser.manifest); 38429 this.targetDuration = parser.manifest.targetDuration; 38430 38431 if (update) { 38432 this.master = update; 38433 this.media_ = this.master.playlists[parser.manifest.uri]; 38434 } else { 38435 this.trigger('playlistunchanged'); 38436 } // refresh live playlists after a target duration passes 38437 38438 38439 if (!this.media().endList) { 38440 window$1.clearTimeout(this.mediaUpdateTimeout); 38441 this.mediaUpdateTimeout = window$1.setTimeout(function () { 38442 _this2.trigger('mediaupdatetimeout'); 38443 }, refreshDelay(this.media(), !!update)); 38444 } 38445 38446 this.trigger('loadedplaylist'); 38447 } 38448 /** 38449 * Abort any outstanding work and clean up. 38450 */ 38451 38452 }, { 38453 key: 'dispose', 38454 value: function dispose() { 38455 this.stopRequest(); 38456 window$1.clearTimeout(this.mediaUpdateTimeout); 38457 window$1.clearTimeout(this.finalRenditionTimeout); 38458 } 38459 }, { 38460 key: 'stopRequest', 38461 value: function stopRequest() { 38462 if (this.request) { 38463 var oldRequest = this.request; 38464 this.request = null; 38465 oldRequest.onreadystatechange = null; 38466 oldRequest.abort(); 38467 } 38468 } 38469 /** 38470 * When called without any arguments, returns the currently 38471 * active media playlist. When called with a single argument, 38472 * triggers the playlist loader to asynchronously switch to the 38473 * specified media playlist. Calling this method while the 38474 * loader is in the HAVE_NOTHING causes an error to be emitted 38475 * but otherwise has no effect. 38476 * 38477 * @param {Object=} playlist the parsed media playlist 38478 * object to switch to 38479 * @param {Boolean=} is this the last available playlist 38480 * 38481 * @return {Playlist} the current loaded media 38482 */ 38483 38484 }, { 38485 key: 'media', 38486 value: function media(playlist, isFinalRendition) { 38487 var _this3 = this; // getter 38488 38489 38490 if (!playlist) { 38491 return this.media_; 38492 } // setter 38493 38494 38495 if (this.state === 'HAVE_NOTHING') { 38496 throw new Error('Cannot switch media playlist from ' + this.state); 38497 } // find the playlist object if the target playlist has been 38498 // specified by URI 38499 38500 38501 if (typeof playlist === 'string') { 38502 if (!this.master.playlists[playlist]) { 38503 throw new Error('Unknown playlist URI: ' + playlist); 38504 } 38505 38506 playlist = this.master.playlists[playlist]; 38507 } 38508 38509 window$1.clearTimeout(this.finalRenditionTimeout); 38510 38511 if (isFinalRendition) { 38512 var delay = playlist.targetDuration / 2 * 1000 || 5 * 1000; 38513 this.finalRenditionTimeout = window$1.setTimeout(this.media.bind(this, playlist, false), delay); 38514 return; 38515 } 38516 38517 var startingState = this.state; 38518 var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to fully loaded playlists immediately 38519 38520 if (this.master.playlists[playlist.uri].endList) { 38521 // abort outstanding playlist requests 38522 if (this.request) { 38523 this.request.onreadystatechange = null; 38524 this.request.abort(); 38525 this.request = null; 38526 } 38527 38528 this.state = 'HAVE_METADATA'; 38529 this.media_ = playlist; // trigger media change if the active media has been updated 38530 38531 if (mediaChange) { 38532 this.trigger('mediachanging'); 38533 this.trigger('mediachange'); 38534 } 38535 38536 return; 38537 } // switching to the active playlist is a no-op 38538 38539 38540 if (!mediaChange) { 38541 return; 38542 } 38543 38544 this.state = 'SWITCHING_MEDIA'; // there is already an outstanding playlist request 38545 38546 if (this.request) { 38547 if (playlist.resolvedUri === this.request.url) { 38548 // requesting to switch to the same playlist multiple times 38549 // has no effect after the first 38550 return; 38551 } 38552 38553 this.request.onreadystatechange = null; 38554 this.request.abort(); 38555 this.request = null; 38556 } // request the new playlist 38557 38558 38559 if (this.media_) { 38560 this.trigger('mediachanging'); 38561 } 38562 38563 this.request = this.hls_.xhr({ 38564 uri: playlist.resolvedUri, 38565 withCredentials: this.withCredentials 38566 }, function (error, req) { 38567 // disposed 38568 if (!_this3.request) { 38569 return; 38570 } 38571 38572 playlist.resolvedUri = resolveManifestRedirect(_this3.handleManifestRedirects, playlist.resolvedUri, req); 38573 38574 if (error) { 38575 return _this3.playlistRequestError(_this3.request, playlist.uri, startingState); 38576 } 38577 38578 _this3.haveMetadata(req, playlist.uri);
vendor: 29,113 bytes, lines 38578-39517
38578 // fire loadedmetadata the first time a media playlist is loaded 38579 38580 38581 if (startingState === 'HAVE_MASTER') { 38582 _this3.trigger('loadedmetadata'); 38583 } else { 38584 _this3.trigger('mediachange'); 38585 } 38586 }); 38587 } 38588 /** 38589 * pause loading of the playlist 38590 */ 38591 38592 }, { 38593 key: 'pause', 38594 value: function pause() { 38595 this.stopRequest(); 38596 window$1.clearTimeout(this.mediaUpdateTimeout); 38597 38598 if (this.state === 'HAVE_NOTHING') { 38599 // If we pause the loader before any data has been retrieved, its as if we never 38600 // started, so reset to an unstarted state. 38601 this.started = false; 38602 } // Need to restore state now that no activity is happening 38603 38604 38605 if (this.state === 'SWITCHING_MEDIA') { 38606 // if the loader was in the process of switching media, it should either return to 38607 // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media 38608 // playlist yet. This is determined by the existence of loader.media_ 38609 if (this.media_) { 38610 this.state = 'HAVE_METADATA'; 38611 } else { 38612 this.state = 'HAVE_MASTER'; 38613 } 38614 } else if (this.state === 'HAVE_CURRENT_METADATA') { 38615 this.state = 'HAVE_METADATA'; 38616 } 38617 } 38618 /** 38619 * start loading of the playlist 38620 */ 38621 38622 }, { 38623 key: 'load', 38624 value: function load(isFinalRendition) { 38625 var _this4 = this; 38626 38627 window$1.clearTimeout(this.mediaUpdateTimeout); 38628 var media = this.media(); 38629 38630 if (isFinalRendition) { 38631 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 38632 this.mediaUpdateTimeout = window$1.setTimeout(function () { 38633 return _this4.load(); 38634 }, delay); 38635 return; 38636 } 38637 38638 if (!this.started) { 38639 this.start(); 38640 return; 38641 } 38642 38643 if (media && !media.endList) { 38644 this.trigger('mediaupdatetimeout'); 38645 } else { 38646 this.trigger('loadedplaylist'); 38647 } 38648 } 38649 /** 38650 * start loading of the playlist 38651 */ 38652 38653 }, { 38654 key: 'start', 38655 value: function start() { 38656 var _this5 = this; 38657 38658 this.started = true; // request the specified URL 38659 38660 this.request = this.hls_.xhr({ 38661 uri: this.srcUrl, 38662 withCredentials: this.withCredentials 38663 }, function (error, req) { 38664 // disposed 38665 if (!_this5.request) { 38666 return; 38667 } // clear the loader's request reference 38668 38669 38670 _this5.request = null; 38671 38672 if (error) { 38673 _this5.error = { 38674 status: req.status, 38675 message: 'HLS playlist request error at URL: ' + _this5.srcUrl + '.', 38676 responseText: req.responseText, 38677 // MEDIA_ERR_NETWORK 38678 code: 2 38679 }; 38680 38681 if (_this5.state === 'HAVE_NOTHING') { 38682 _this5.started = false; 38683 } 38684 38685 return _this5.trigger('error'); 38686 } 38687 38688 var parser = new Parser(); // adding custom tag parsers 38689 38690 _this5.customTagParsers.forEach(function (customParser) { 38691 return parser.addParser(customParser); 38692 }); // adding custom tag mappers 38693 38694 38695 _this5.customTagMappers.forEach(function (mapper) { 38696 return parser.addTagMapper(mapper); 38697 }); 38698 38699 parser.push(req.responseText); 38700 parser.end(); 38701 _this5.state = 'HAVE_MASTER'; 38702 _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req); 38703 parser.manifest.uri = _this5.srcUrl; // loaded a master playlist 38704 38705 if (parser.manifest.playlists) { 38706 _this5.master = parser.manifest; 38707 setupMediaPlaylists(_this5.master); 38708 resolveMediaGroupUris(_this5.master); 38709 38710 _this5.trigger('loadedplaylist'); 38711 38712 if (!_this5.request) { 38713 // no media playlist was specifically selected so start 38714 // from the first listed one 38715 _this5.media(parser.manifest.playlists[0]); 38716 } 38717 38718 return; 38719 } // loaded a media playlist 38720 // infer a master playlist if none was previously requested 38721 38722 38723 _this5.master = { 38724 mediaGroups: { 38725 'AUDIO': {}, 38726 'VIDEO': {}, 38727 'CLOSED-CAPTIONS': {}, 38728 'SUBTITLES': {} 38729 }, 38730 uri: window$1.location.href, 38731 playlists: [{ 38732 uri: _this5.srcUrl, 38733 id: 0, 38734 resolvedUri: _this5.srcUrl, 38735 // m3u8-parser does not attach an attributes property to media playlists so make 38736 // sure that the property is attached to avoid undefined reference errors 38737 attributes: {} 38738 }] 38739 }; 38740 _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0]; 38741 38742 _this5.haveMetadata(req, _this5.srcUrl); 38743 38744 return _this5.trigger('loadedmetadata'); 38745 }); 38746 } 38747 }]); 38748 return PlaylistLoader; 38749 }(EventTarget$1); 38750 /** 38751 * @file playlist.js 38752 * 38753 * Playlist related utilities. 38754 */ 38755 38756 38757 var createTimeRange = videojs$1.createTimeRange; 38758 /** 38759 * walk backward until we find a duration we can use 38760 * or return a failure 38761 * 38762 * @param {Playlist} playlist the playlist to walk through 38763 * @param {Number} endSequence the mediaSequence to stop walking on 38764 */ 38765 38766 var backwardDuration = function backwardDuration(playlist, endSequence) { 38767 var result = 0; 38768 var i = endSequence - playlist.mediaSequence; // if a start time is available for segment immediately following 38769 // the interval, use it 38770 38771 var segment = playlist.segments[i]; // Walk backward until we find the latest segment with timeline 38772 // information that is earlier than endSequence 38773 38774 if (segment) { 38775 if (typeof segment.start !== 'undefined') { 38776 return { 38777 result: segment.start, 38778 precise: true 38779 }; 38780 } 38781 38782 if (typeof segment.end !== 'undefined') { 38783 return { 38784 result: segment.end - segment.duration, 38785 precise: true 38786 }; 38787 } 38788 } 38789 38790 while (i--) { 38791 segment = playlist.segments[i]; 38792 38793 if (typeof segment.end !== 'undefined') { 38794 return { 38795 result: result + segment.end, 38796 precise: true 38797 }; 38798 } 38799 38800 result += segment.duration; 38801 38802 if (typeof segment.start !== 'undefined') { 38803 return { 38804 result: result + segment.start, 38805 precise: true 38806 }; 38807 } 38808 } 38809 38810 return { 38811 result: result, 38812 precise: false 38813 }; 38814 }; 38815 /** 38816 * walk forward until we find a duration we can use 38817 * or return a failure 38818 * 38819 * @param {Playlist} playlist the playlist to walk through 38820 * @param {Number} endSequence the mediaSequence to stop walking on 38821 */ 38822 38823 38824 var forwardDuration = function forwardDuration(playlist, endSequence) { 38825 var result = 0; 38826 var segment = void 0; 38827 var i = endSequence - playlist.mediaSequence; // Walk forward until we find the earliest segment with timeline 38828 // information 38829 38830 for (; i < playlist.segments.length; i++) { 38831 segment = playlist.segments[i]; 38832 38833 if (typeof segment.start !== 'undefined') { 38834 return { 38835 result: segment.start - result, 38836 precise: true 38837 }; 38838 } 38839 38840 result += segment.duration; 38841 38842 if (typeof segment.end !== 'undefined') { 38843 return { 38844 result: segment.end - result, 38845 precise: true 38846 }; 38847 } 38848 } // indicate we didn't find a useful duration estimate 38849 38850 38851 return { 38852 result: -1, 38853 precise: false 38854 }; 38855 }; 38856 /** 38857 * Calculate the media duration from the segments associated with a 38858 * playlist. The duration of a subinterval of the available segments 38859 * may be calculated by specifying an end index. 38860 * 38861 * @param {Object} playlist a media playlist object 38862 * @param {Number=} endSequence an exclusive upper boundary 38863 * for the playlist. Defaults to playlist length. 38864 * @param {Number} expired the amount of time that has dropped 38865 * off the front of the playlist in a live scenario 38866 * @return {Number} the duration between the first available segment 38867 * and end index. 38868 */ 38869 38870 38871 var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 38872 var backward = void 0; 38873 var forward = void 0; 38874 38875 if (typeof endSequence === 'undefined') { 38876 endSequence = playlist.mediaSequence + playlist.segments.length; 38877 } 38878 38879 if (endSequence < playlist.mediaSequence) { 38880 return 0; 38881 } // do a backward walk to estimate the duration 38882 38883 38884 backward = backwardDuration(playlist, endSequence); 38885 38886 if (backward.precise) { 38887 // if we were able to base our duration estimate on timing 38888 // information provided directly from the Media Source, return 38889 // it 38890 return backward.result; 38891 } // walk forward to see if a precise duration estimate can be made 38892 // that way 38893 38894 38895 forward = forwardDuration(playlist, endSequence); 38896 38897 if (forward.precise) { 38898 // we found a segment that has been buffered and so it's 38899 // position is known precisely 38900 return forward.result; 38901 } // return the less-precise, playlist-based duration estimate 38902 38903 38904 return backward.result + expired; 38905 }; 38906 /** 38907 * Calculates the duration of a playlist. If a start and end index 38908 * are specified, the duration will be for the subset of the media 38909 * timeline between those two indices. The total duration for live 38910 * playlists is always Infinity. 38911 * 38912 * @param {Object} playlist a media playlist object 38913 * @param {Number=} endSequence an exclusive upper 38914 * boundary for the playlist. Defaults to the playlist media 38915 * sequence number plus its length. 38916 * @param {Number=} expired the amount of time that has 38917 * dropped off the front of the playlist in a live scenario 38918 * @return {Number} the duration between the start index and end 38919 * index. 38920 */ 38921 38922 38923 var duration = function duration(playlist, endSequence, expired) { 38924 if (!playlist) { 38925 return 0; 38926 } 38927 38928 if (typeof expired !== 'number') { 38929 expired = 0; 38930 } // if a slice of the total duration is not requested, use 38931 // playlist-level duration indicators when they're present 38932 38933 38934 if (typeof endSequence === 'undefined') { 38935 // if present, use the duration specified in the playlist 38936 if (playlist.totalDuration) { 38937 return playlist.totalDuration; 38938 } // duration should be Infinity for live playlists 38939 38940 38941 if (!playlist.endList) { 38942 return window$1.Infinity; 38943 } 38944 } // calculate the total duration based on the segment durations 38945 38946 38947 return intervalDuration(playlist, endSequence, expired); 38948 }; 38949 /** 38950 * Calculate the time between two indexes in the current playlist 38951 * neight the start- nor the end-index need to be within the current 38952 * playlist in which case, the targetDuration of the playlist is used 38953 * to approximate the durations of the segments 38954 * 38955 * @param {Object} playlist a media playlist object 38956 * @param {Number} startIndex 38957 * @param {Number} endIndex 38958 * @return {Number} the number of seconds between startIndex and endIndex 38959 */ 38960 38961 38962 var sumDurations = function sumDurations(playlist, startIndex, endIndex) { 38963 var durations = 0; 38964 38965 if (startIndex > endIndex) { 38966 var _ref = [endIndex, startIndex]; 38967 startIndex = _ref[0]; 38968 endIndex = _ref[1]; 38969 } 38970 38971 if (startIndex < 0) { 38972 for (var i = startIndex; i < Math.min(0, endIndex); i++) { 38973 durations += playlist.targetDuration; 38974 } 38975 38976 startIndex = 0; 38977 } 38978 38979 for (var _i = startIndex; _i < endIndex; _i++) { 38980 durations += playlist.segments[_i].duration; 38981 } 38982 38983 return durations; 38984 }; 38985 /** 38986 * Determines the media index of the segment corresponding to the safe edge of the live 38987 * window which is the duration of the last segment plus 2 target durations from the end 38988 * of the playlist. 38989 * 38990 * @param {Object} playlist 38991 * a media playlist object 38992 * @return {Number} 38993 * The media index of the segment at the safe live point. 0 if there is no "safe" 38994 * point. 38995 * @function safeLiveIndex 38996 */ 38997 38998 38999 var safeLiveIndex = function safeLiveIndex(playlist) { 39000 if (!playlist.segments.length) { 39001 return 0; 39002 } 39003 39004 var i = playlist.segments.length - 1; 39005 var distanceFromEnd = playlist.segments[i].duration || playlist.targetDuration; 39006 var safeDistance = distanceFromEnd + playlist.targetDuration * 2; 39007 39008 while (i--) { 39009 distanceFromEnd += playlist.segments[i].duration; 39010 39011 if (distanceFromEnd >= safeDistance) { 39012 break; 39013 } 39014 } 39015 39016 return Math.max(0, i); 39017 }; 39018 /** 39019 * Calculates the playlist end time 39020 * 39021 * @param {Object} playlist a media playlist object 39022 * @param {Number=} expired the amount of time that has 39023 * dropped off the front of the playlist in a live scenario 39024 * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the 39025 * playlist end calculation should consider the safe live end 39026 * (truncate the playlist end by three segments). This is normally 39027 * used for calculating the end of the playlist's seekable range. 39028 * @returns {Number} the end time of playlist 39029 * @function playlistEnd 39030 */ 39031 39032 39033 var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) { 39034 if (!playlist || !playlist.segments) { 39035 return null; 39036 } 39037 39038 if (playlist.endList) { 39039 return duration(playlist); 39040 } 39041 39042 if (expired === null) { 39043 return null; 39044 } 39045 39046 expired = expired || 0; 39047 var endSequence = useSafeLiveEnd ? safeLiveIndex(playlist) : playlist.segments.length; 39048 return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); 39049 }; 39050 /** 39051 * Calculates the interval of time that is currently seekable in a 39052 * playlist. The returned time ranges are relative to the earliest 39053 * moment in the specified playlist that is still available. A full 39054 * seekable implementation for live streams would need to offset 39055 * these values by the duration of content that has expired from the 39056 * stream. 39057 * 39058 * @param {Object} playlist a media playlist object 39059 * dropped off the front of the playlist in a live scenario 39060 * @param {Number=} expired the amount of time that has 39061 * dropped off the front of the playlist in a live scenario 39062 * @return {TimeRanges} the periods of time that are valid targets 39063 * for seeking 39064 */ 39065 39066 39067 var seekable = function seekable(playlist, expired) { 39068 var useSafeLiveEnd = true; 39069 var seekableStart = expired || 0; 39070 var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd); 39071 39072 if (seekableEnd === null) { 39073 return createTimeRange(); 39074 } 39075 39076 return createTimeRange(seekableStart, seekableEnd); 39077 }; 39078 39079 var isWholeNumber = function isWholeNumber(num) { 39080 return num - Math.floor(num) === 0; 39081 }; 39082 39083 var roundSignificantDigit = function roundSignificantDigit(increment, num) { 39084 // If we have a whole number, just add 1 to it 39085 if (isWholeNumber(num)) { 39086 return num + increment * 0.1; 39087 } 39088 39089 var numDecimalDigits = num.toString().split('.')[1].length; 39090 39091 for (var i = 1; i <= numDecimalDigits; i++) { 39092 var scale = Math.pow(10, i); 39093 var temp = num * scale; 39094 39095 if (isWholeNumber(temp) || i === numDecimalDigits) { 39096 return (temp + increment) / scale; 39097 } 39098 } 39099 }; 39100 39101 var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1); 39102 var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1); 39103 /** 39104 * Determine the index and estimated starting time of the segment that 39105 * contains a specified playback position in a media playlist. 39106 * 39107 * @param {Object} playlist the media playlist to query 39108 * @param {Number} currentTime The number of seconds since the earliest 39109 * possible position to determine the containing segment for 39110 * @param {Number} startIndex 39111 * @param {Number} startTime 39112 * @return {Object} 39113 */ 39114 39115 var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) { 39116 var i = void 0; 39117 var segment = void 0; 39118 var numSegments = playlist.segments.length; 39119 var time = currentTime - startTime; 39120 39121 if (time < 0) { 39122 // Walk backward from startIndex in the playlist, adding durations 39123 // until we find a segment that contains `time` and return it 39124 if (startIndex > 0) { 39125 for (i = startIndex - 1; i >= 0; i--) { 39126 segment = playlist.segments[i]; 39127 time += floorLeastSignificantDigit(segment.duration); 39128 39129 if (time > 0) { 39130 return { 39131 mediaIndex: i, 39132 startTime: startTime - sumDurations(playlist, startIndex, i) 39133 }; 39134 } 39135 } 39136 } // We were unable to find a good segment within the playlist 39137 // so select the first segment 39138 39139 39140 return { 39141 mediaIndex: 0, 39142 startTime: currentTime 39143 }; 39144 } // When startIndex is negative, we first walk forward to first segment 39145 // adding target durations. If we "run out of time" before getting to 39146 // the first segment, return the first segment 39147 39148 39149 if (startIndex < 0) { 39150 for (i = startIndex; i < 0; i++) { 39151 time -= playlist.targetDuration; 39152 39153 if (time < 0) { 39154 return { 39155 mediaIndex: 0, 39156 startTime: currentTime 39157 }; 39158 } 39159 } 39160 39161 startIndex = 0; 39162 } // Walk forward from startIndex in the playlist, subtracting durations 39163 // until we find a segment that contains `time` and return it 39164 39165 39166 for (i = startIndex; i < numSegments; i++) { 39167 segment = playlist.segments[i]; 39168 time -= ceilLeastSignificantDigit(segment.duration); 39169 39170 if (time < 0) { 39171 return { 39172 mediaIndex: i, 39173 startTime: startTime + sumDurations(playlist, startIndex, i) 39174 }; 39175 } 39176 } // We are out of possible candidates so load the last one... 39177 39178 39179 return { 39180 mediaIndex: numSegments - 1, 39181 startTime: currentTime 39182 }; 39183 }; 39184 /** 39185 * Check whether the playlist is blacklisted or not. 39186 * 39187 * @param {Object} playlist the media playlist object 39188 * @return {boolean} whether the playlist is blacklisted or not 39189 * @function isBlacklisted 39190 */ 39191 39192 39193 var isBlacklisted = function isBlacklisted(playlist) { 39194 return playlist.excludeUntil && playlist.excludeUntil > Date.now(); 39195 }; 39196 /** 39197 * Check whether the playlist is compatible with current playback configuration or has 39198 * been blacklisted permanently for being incompatible. 39199 * 39200 * @param {Object} playlist the media playlist object 39201 * @return {boolean} whether the playlist is incompatible or not 39202 * @function isIncompatible 39203 */ 39204 39205 39206 var isIncompatible = function isIncompatible(playlist) { 39207 return playlist.excludeUntil && playlist.excludeUntil === Infinity; 39208 }; 39209 /** 39210 * Check whether the playlist is enabled or not. 39211 * 39212 * @param {Object} playlist the media playlist object 39213 * @return {boolean} whether the playlist is enabled or not 39214 * @function isEnabled 39215 */ 39216 39217 39218 var isEnabled = function isEnabled(playlist) { 39219 var blacklisted = isBlacklisted(playlist); 39220 return !playlist.disabled && !blacklisted; 39221 }; 39222 /** 39223 * Check whether the playlist has been manually disabled through the representations api. 39224 * 39225 * @param {Object} playlist the media playlist object 39226 * @return {boolean} whether the playlist is disabled manually or not 39227 * @function isDisabled 39228 */ 39229 39230 39231 var isDisabled = function isDisabled(playlist) { 39232 return playlist.disabled; 39233 }; 39234 /** 39235 * Returns whether the current playlist is an AES encrypted HLS stream 39236 * 39237 * @return {Boolean} true if it's an AES encrypted HLS stream 39238 */ 39239 39240 39241 var isAes = function isAes(media) { 39242 for (var i = 0; i < media.segments.length; i++) { 39243 if (media.segments[i].key) { 39244 return true; 39245 } 39246 } 39247 39248 return false; 39249 }; 39250 /** 39251 * Returns whether the current playlist contains fMP4 39252 * 39253 * @return {Boolean} true if the playlist contains fMP4 39254 */ 39255 39256 39257 var isFmp4 = function isFmp4(media) { 39258 for (var i = 0; i < media.segments.length; i++) { 39259 if (media.segments[i].map) { 39260 return true; 39261 } 39262 } 39263 39264 return false; 39265 }; 39266 /** 39267 * Checks if the playlist has a value for the specified attribute 39268 * 39269 * @param {String} attr 39270 * Attribute to check for 39271 * @param {Object} playlist 39272 * The media playlist object 39273 * @return {Boolean} 39274 * Whether the playlist contains a value for the attribute or not 39275 * @function hasAttribute 39276 */ 39277 39278 39279 var hasAttribute = function hasAttribute(attr, playlist) { 39280 return playlist.attributes && playlist.attributes[attr]; 39281 }; 39282 /** 39283 * Estimates the time required to complete a segment download from the specified playlist 39284 * 39285 * @param {Number} segmentDuration 39286 * Duration of requested segment 39287 * @param {Number} bandwidth 39288 * Current measured bandwidth of the player 39289 * @param {Object} playlist 39290 * The media playlist object 39291 * @param {Number=} bytesReceived 39292 * Number of bytes already received for the request. Defaults to 0 39293 * @return {Number|NaN} 39294 * The estimated time to request the segment. NaN if bandwidth information for 39295 * the given playlist is unavailable 39296 * @function estimateSegmentRequestTime 39297 */ 39298 39299 39300 var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) { 39301 var bytesReceived = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; 39302 39303 if (!hasAttribute('BANDWIDTH', playlist)) { 39304 return NaN; 39305 } 39306 39307 var size = segmentDuration * playlist.attributes.BANDWIDTH; 39308 return (size - bytesReceived * 8) / bandwidth; 39309 }; 39310 /* 39311 * Returns whether the current playlist is the lowest rendition 39312 * 39313 * @return {Boolean} true if on lowest rendition 39314 */ 39315 39316 39317 var isLowestEnabledRendition = function isLowestEnabledRendition(master, media) { 39318 if (master.playlists.length === 1) { 39319 return true; 39320 } 39321 39322 var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; 39323 return master.playlists.filter(function (playlist) { 39324 if (!isEnabled(playlist)) { 39325 return false; 39326 } 39327 39328 return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth; 39329 }).length === 0; 39330 }; // exports 39331 39332 39333 var Playlist = { 39334 duration: duration, 39335 seekable: seekable, 39336 safeLiveIndex: safeLiveIndex, 39337 getMediaInfoForTime: getMediaInfoForTime, 39338 isEnabled: isEnabled, 39339 isDisabled: isDisabled, 39340 isBlacklisted: isBlacklisted, 39341 isIncompatible: isIncompatible, 39342 playlistEnd: playlistEnd, 39343 isAes: isAes, 39344 isFmp4: isFmp4, 39345 hasAttribute: hasAttribute, 39346 estimateSegmentRequestTime: estimateSegmentRequestTime, 39347 isLowestEnabledRendition: isLowestEnabledRendition 39348 }; 39349 /** 39350 * @file xhr.js 39351 */ 39352 39353 var videojsXHR = videojs$1.xhr, 39354 mergeOptions$1$1 = videojs$1.mergeOptions; 39355 39356 var xhrFactory = function xhrFactory() { 39357 var xhr = function XhrFunction(options, callback) { 39358 // Add a default timeout for all hls requests 39359 options = mergeOptions$1$1({ 39360 timeout: 45e3 39361 }, options); // Allow an optional user-specified function to modify the option 39362 // object before we construct the xhr request 39363 39364 var beforeRequest = XhrFunction.beforeRequest || videojs$1.Hls.xhr.beforeRequest; 39365 39366 if (beforeRequest && typeof beforeRequest === 'function') { 39367 var newOptions = beforeRequest(options); 39368 39369 if (newOptions) { 39370 options = newOptions; 39371 } 39372 } 39373 39374 var request = videojsXHR(options, function (error, response) { 39375 var reqResponse = request.response; 39376 39377 if (!error && reqResponse) { 39378 request.responseTime = Date.now(); 39379 request.roundTripTime = request.responseTime - request.requestTime; 39380 request.bytesReceived = reqResponse.byteLength || reqResponse.length; 39381 39382 if (!request.bandwidth) { 39383 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 39384 } 39385 } 39386 39387 if (response.headers) { 39388 request.responseHeaders = response.headers; 39389 } // videojs.xhr now uses a specific code on the error 39390 // object to signal that a request has timed out instead 39391 // of setting a boolean on the request object 39392 39393 39394 if (error && error.code === 'ETIMEDOUT') { 39395 request.timedout = true; 39396 } // videojs.xhr no longer considers status codes outside of 200 and 0 39397 // (for file uris) to be errors, but the old XHR did, so emulate that 39398 // behavior. Status 206 may be used in response to byterange requests. 39399 39400 39401 if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 39402 error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))); 39403 } 39404 39405 callback(error, request); 39406 }); 39407 var originalAbort = request.abort; 39408 39409 request.abort = function () { 39410 request.aborted = true; 39411 return originalAbort.apply(request, arguments); 39412 }; 39413 39414 request.uri = options.uri; 39415 request.requestTime = Date.now(); 39416 return request; 39417 }; 39418 39419 return xhr; 39420 }; 39421 /** 39422 * Turns segment byterange into a string suitable for use in 39423 * HTTP Range requests 39424 * 39425 * @param {Object} byterange - an object with two values defining the start and end 39426 * of a byte-range 39427 */ 39428 39429 39430 var byterangeStr = function byterangeStr(byterange) { 39431 var byterangeStart = void 0; 39432 var byterangeEnd = void 0; // `byterangeEnd` is one less than `offset + length` because the HTTP range 39433 // header uses inclusive ranges 39434 39435 byterangeEnd = byterange.offset + byterange.length - 1; 39436 byterangeStart = byterange.offset; 39437 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 39438 }; 39439 /** 39440 * Defines headers for use in the xhr request for a particular segment. 39441 * 39442 * @param {Object} segment - a simplified copy of the segmentInfo object 39443 * from SegmentLoader 39444 */ 39445 39446 39447 var segmentXhrHeaders = function segmentXhrHeaders(segment) { 39448 var headers = {}; 39449 39450 if (segment.byterange) { 39451 headers.Range = byterangeStr(segment.byterange); 39452 } 39453 39454 return headers; 39455 }; 39456 /** 39457 * @file bin-utils.js 39458 */ 39459 39460 /** 39461 * convert a TimeRange to text 39462 * 39463 * @param {TimeRange} range the timerange to use for conversion 39464 * @param {Number} i the iterator on the range to convert 39465 */ 39466 39467 39468 var textRange = function textRange(range, i) { 39469 return range.start(i) + '-' + range.end(i); 39470 }; 39471 /** 39472 * format a number as hex string 39473 * 39474 * @param {Number} e The number 39475 * @param {Number} i the iterator 39476 */ 39477 39478 39479 var formatHexString = function formatHexString(e, i) { 39480 var value = e.toString(16); 39481 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 39482 }; 39483 39484 var formatAsciiString = function formatAsciiString(e) { 39485 if (e >= 0x20 && e < 0x7e) { 39486 return String.fromCharCode(e); 39487 } 39488 39489 return '.'; 39490 }; 39491 /** 39492 * Creates an object for sending to a web worker modifying properties that are TypedArrays 39493 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 39494 * 39495 * @param {Object} message 39496 * Object of properties and values to send to the web worker 39497 * @return {Object} 39498 * Modified message with TypedArray values expanded 39499 * @function createTransferableMessage 39500 */ 39501 39502 39503 var createTransferableMessage = function createTransferableMessage(message) { 39504 var transferable = {}; 39505 Object.keys(message).forEach(function (key) { 39506 var value = message[key]; 39507 39508 if (ArrayBuffer.isView(value)) { 39509 transferable[key] = { 39510 bytes: value.buffer, 39511 byteOffset: value.byteOffset, 39512 byteLength: value.byteLength 39513 }; 39514 } else { 39515 transferable[key] = value; 39516 } 39517 });
vendor: 17,592 bytes, lines 39518-40007
39518 return transferable; 39519 }; 39520 /** 39521 * Returns a unique string identifier for a media initialization 39522 * segment. 39523 */ 39524 39525 39526 var initSegmentId = function initSegmentId(initSegment) { 39527 var byterange = initSegment.byterange || { 39528 length: Infinity, 39529 offset: 0 39530 }; 39531 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 39532 }; 39533 /** 39534 * Returns a unique string identifier for a media segment key. 39535 */ 39536 39537 39538 var segmentKeyId = function segmentKeyId(key) { 39539 return key.resolvedUri; 39540 }; 39541 /** 39542 * utils to help dump binary data to the console 39543 */ 39544 39545 39546 var hexDump = function hexDump(data) { 39547 var bytes = Array.prototype.slice.call(data); 39548 var step = 16; 39549 var result = ''; 39550 var hex = void 0; 39551 var ascii = void 0; 39552 39553 for (var j = 0; j < bytes.length / step; j++) { 39554 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 39555 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 39556 result += hex + ' ' + ascii + '\n'; 39557 } 39558 39559 return result; 39560 }; 39561 39562 var tagDump = function tagDump(_ref) { 39563 var bytes = _ref.bytes; 39564 return hexDump(bytes); 39565 }; 39566 39567 var textRanges = function textRanges(ranges) { 39568 var result = ''; 39569 var i = void 0; 39570 39571 for (i = 0; i < ranges.length; i++) { 39572 result += textRange(ranges, i) + ' '; 39573 } 39574 39575 return result; 39576 }; 39577 39578 var utils$1 = 39579 /*#__PURE__*/ 39580 Object.freeze({ 39581 createTransferableMessage: createTransferableMessage, 39582 initSegmentId: initSegmentId, 39583 segmentKeyId: segmentKeyId, 39584 hexDump: hexDump, 39585 tagDump: tagDump, 39586 textRanges: textRanges 39587 }); // TODO handle fmp4 case where the timing info is accurate and doesn't involve transmux 39588 // Add 25% to the segment duration to account for small discrepencies in segment timing. 39589 // 25% was arbitrarily chosen, and may need to be refined over time. 39590 39591 var SEGMENT_END_FUDGE_PERCENT = 0.25; 39592 /** 39593 * Converts a player time (any time that can be gotten/set from player.currentTime(), 39594 * e.g., any time within player.seekable().start(0) to player.seekable().end(0)) to a 39595 * program time (any time referencing the real world (e.g., EXT-X-PROGRAM-DATE-TIME)). 39596 * 39597 * The containing segment is required as the EXT-X-PROGRAM-DATE-TIME serves as an "anchor 39598 * point" (a point where we have a mapping from program time to player time, with player 39599 * time being the post transmux start of the segment). 39600 * 39601 * For more details, see [this doc](../../docs/program-time-from-player-time.md). 39602 * 39603 * @param {Number} playerTime the player time 39604 * @param {Object} segment the segment which contains the player time 39605 * @return {Date} program time 39606 */ 39607 39608 var playerTimeToProgramTime = function playerTimeToProgramTime(playerTime, segment) { 39609 if (!segment.dateTimeObject) { 39610 // Can't convert without an "anchor point" for the program time (i.e., a time that can 39611 // be used to map the start of a segment with a real world time). 39612 return null; 39613 } 39614 39615 var transmuxerPrependedSeconds = segment.videoTimingInfo.transmuxerPrependedSeconds; 39616 var transmuxedStart = segment.videoTimingInfo.transmuxedPresentationStart; // get the start of the content from before old content is prepended 39617 39618 var startOfSegment = transmuxedStart + transmuxerPrependedSeconds; 39619 var offsetFromSegmentStart = playerTime - startOfSegment; 39620 return new Date(segment.dateTimeObject.getTime() + offsetFromSegmentStart * 1000); 39621 }; 39622 39623 var originalSegmentVideoDuration = function originalSegmentVideoDuration(videoTimingInfo) { 39624 return videoTimingInfo.transmuxedPresentationEnd - videoTimingInfo.transmuxedPresentationStart - videoTimingInfo.transmuxerPrependedSeconds; 39625 }; 39626 /** 39627 * Finds a segment that contains the time requested given as an ISO-8601 string. The 39628 * returned segment might be an estimate or an accurate match. 39629 * 39630 * @param {String} programTime The ISO-8601 programTime to find a match for 39631 * @param {Object} playlist A playlist object to search within 39632 */ 39633 39634 39635 var findSegmentForProgramTime = function findSegmentForProgramTime(programTime, playlist) { 39636 // Assumptions: 39637 // - verifyProgramDateTimeTags has already been run 39638 // - live streams have been started 39639 var dateTimeObject = void 0; 39640 39641 try { 39642 dateTimeObject = new Date(programTime); 39643 } catch (e) { 39644 return null; 39645 } 39646 39647 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 39648 return null; 39649 } 39650 39651 var segment = playlist.segments[0]; 39652 39653 if (dateTimeObject < segment.dateTimeObject) { 39654 // Requested time is before stream start. 39655 return null; 39656 } 39657 39658 for (var i = 0; i < playlist.segments.length - 1; i++) { 39659 segment = playlist.segments[i]; 39660 var nextSegmentStart = playlist.segments[i + 1].dateTimeObject; 39661 39662 if (dateTimeObject < nextSegmentStart) { 39663 break; 39664 } 39665 } 39666 39667 var lastSegment = playlist.segments[playlist.segments.length - 1]; 39668 var lastSegmentStart = lastSegment.dateTimeObject; 39669 var lastSegmentDuration = lastSegment.videoTimingInfo ? originalSegmentVideoDuration(lastSegment.videoTimingInfo) : lastSegment.duration + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT; 39670 var lastSegmentEnd = new Date(lastSegmentStart.getTime() + lastSegmentDuration * 1000); 39671 39672 if (dateTimeObject > lastSegmentEnd) { 39673 // Beyond the end of the stream, or our best guess of the end of the stream. 39674 return null; 39675 } 39676 39677 if (dateTimeObject > lastSegmentStart) { 39678 segment = lastSegment; 39679 } 39680 39681 return { 39682 segment: segment, 39683 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : Playlist.duration(playlist, playlist.mediaSequence + playlist.segments.indexOf(segment)), 39684 // Although, given that all segments have accurate date time objects, the segment 39685 // selected should be accurate, unless the video has been transmuxed at some point 39686 // (determined by the presence of the videoTimingInfo object), the segment's "player 39687 // time" (the start time in the player) can't be considered accurate. 39688 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 39689 }; 39690 }; 39691 /** 39692 * Finds a segment that contains the given player time(in seconds). 39693 * 39694 * @param {Number} time The player time to find a match for 39695 * @param {Object} playlist A playlist object to search within 39696 */ 39697 39698 39699 var findSegmentForPlayerTime = function findSegmentForPlayerTime(time, playlist) { 39700 // Assumptions: 39701 // - there will always be a segment.duration 39702 // - we can start from zero 39703 // - segments are in time order 39704 if (!playlist || !playlist.segments || playlist.segments.length === 0) { 39705 return null; 39706 } 39707 39708 var segmentEnd = 0; 39709 var segment = void 0; 39710 39711 for (var i = 0; i < playlist.segments.length; i++) { 39712 segment = playlist.segments[i]; // videoTimingInfo is set after the segment is downloaded and transmuxed, and 39713 // should contain the most accurate values we have for the segment's player times. 39714 // 39715 // Use the accurate transmuxedPresentationEnd value if it is available, otherwise fall 39716 // back to an estimate based on the manifest derived (inaccurate) segment.duration, to 39717 // calculate an end value. 39718 39719 segmentEnd = segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationEnd : segmentEnd + segment.duration; 39720 39721 if (time <= segmentEnd) { 39722 break; 39723 } 39724 } 39725 39726 var lastSegment = playlist.segments[playlist.segments.length - 1]; 39727 39728 if (lastSegment.videoTimingInfo && lastSegment.videoTimingInfo.transmuxedPresentationEnd < time) { 39729 // The time requested is beyond the stream end. 39730 return null; 39731 } 39732 39733 if (time > segmentEnd) { 39734 // The time is within or beyond the last segment. 39735 // 39736 // Check to see if the time is beyond a reasonable guess of the end of the stream. 39737 if (time > segmentEnd + lastSegment.duration * SEGMENT_END_FUDGE_PERCENT) { 39738 // Technically, because the duration value is only an estimate, the time may still 39739 // exist in the last segment, however, there isn't enough information to make even 39740 // a reasonable estimate. 39741 return null; 39742 } 39743 39744 segment = lastSegment; 39745 } 39746 39747 return { 39748 segment: segment, 39749 estimatedStart: segment.videoTimingInfo ? segment.videoTimingInfo.transmuxedPresentationStart : segmentEnd - segment.duration, 39750 // Because videoTimingInfo is only set after transmux, it is the only way to get 39751 // accurate timing values. 39752 type: segment.videoTimingInfo ? 'accurate' : 'estimate' 39753 }; 39754 }; 39755 /** 39756 * Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds. 39757 * If the offset returned is positive, the programTime occurs after the 39758 * comparisonTimestamp. 39759 * If the offset is negative, the programTime occurs before the comparisonTimestamp. 39760 * 39761 * @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against 39762 * @param {String} programTime The programTime as an ISO-8601 string 39763 * @return {Number} offset 39764 */ 39765 39766 39767 var getOffsetFromTimestamp = function getOffsetFromTimestamp(comparisonTimeStamp, programTime) { 39768 var segmentDateTime = void 0; 39769 var programDateTime = void 0; 39770 39771 try { 39772 segmentDateTime = new Date(comparisonTimeStamp); 39773 programDateTime = new Date(programTime); 39774 } catch (e) {// TODO handle error 39775 } 39776 39777 var segmentTimeEpoch = segmentDateTime.getTime(); 39778 var programTimeEpoch = programDateTime.getTime(); 39779 return (programTimeEpoch - segmentTimeEpoch) / 1000; 39780 }; 39781 /** 39782 * Checks that all segments in this playlist have programDateTime tags. 39783 * 39784 * @param {Object} playlist A playlist object 39785 */ 39786 39787 39788 var verifyProgramDateTimeTags = function verifyProgramDateTimeTags(playlist) { 39789 if (!playlist.segments || playlist.segments.length === 0) { 39790 return false; 39791 } 39792 39793 for (var i = 0; i < playlist.segments.length; i++) { 39794 var segment = playlist.segments[i]; 39795 39796 if (!segment.dateTimeObject) { 39797 return false; 39798 } 39799 } 39800 39801 return true; 39802 }; 39803 /** 39804 * Returns the programTime of the media given a playlist and a playerTime. 39805 * The playlist must have programDateTime tags for a programDateTime tag to be returned. 39806 * If the segments containing the time requested have not been buffered yet, an estimate 39807 * may be returned to the callback. 39808 * 39809 * @param {Object} args 39810 * @param {Object} args.playlist A playlist object to search within 39811 * @param {Number} time A playerTime in seconds 39812 * @param {Function} callback(err, programTime) 39813 * @returns {String} err.message A detailed error message 39814 * @returns {Object} programTime 39815 * @returns {Number} programTime.mediaSeconds The streamTime in seconds 39816 * @returns {String} programTime.programDateTime The programTime as an ISO-8601 String 39817 */ 39818 39819 39820 var getProgramTime = function getProgramTime(_ref) { 39821 var playlist = _ref.playlist, 39822 _ref$time = _ref.time, 39823 time = _ref$time === undefined ? undefined : _ref$time, 39824 callback = _ref.callback; 39825 39826 if (!callback) { 39827 throw new Error('getProgramTime: callback must be provided'); 39828 } 39829 39830 if (!playlist || time === undefined) { 39831 return callback({ 39832 message: 'getProgramTime: playlist and time must be provided' 39833 }); 39834 } 39835 39836 var matchedSegment = findSegmentForPlayerTime(time, playlist); 39837 39838 if (!matchedSegment) { 39839 return callback({ 39840 message: 'valid programTime was not found' 39841 }); 39842 } 39843 39844 if (matchedSegment.type === 'estimate') { 39845 return callback({ 39846 message: 'Accurate programTime could not be determined.' + ' Please seek to e.seekTime and try again', 39847 seekTime: matchedSegment.estimatedStart 39848 }); 39849 } 39850 39851 var programTimeObject = { 39852 mediaSeconds: time 39853 }; 39854 var programTime = playerTimeToProgramTime(time, matchedSegment.segment); 39855 39856 if (programTime) { 39857 programTimeObject.programDateTime = programTime.toISOString(); 39858 } 39859 39860 return callback(null, programTimeObject); 39861 }; 39862 /** 39863 * Seeks in the player to a time that matches the given programTime ISO-8601 string. 39864 * 39865 * @param {Object} args 39866 * @param {String} args.programTime A programTime to seek to as an ISO-8601 String 39867 * @param {Object} args.playlist A playlist to look within 39868 * @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2. 39869 * @param {Function} args.seekTo A method to perform a seek 39870 * @param {Boolean} args.pauseAfterSeek Whether to end in a paused state after seeking. Default is true. 39871 * @param {Object} args.tech The tech to seek on 39872 * @param {Function} args.callback(err, newTime) A callback to return the new time to 39873 * @returns {String} err.message A detailed error message 39874 * @returns {Number} newTime The exact time that was seeked to in seconds 39875 */ 39876 39877 39878 var seekToProgramTime = function seekToProgramTime(_ref2) { 39879 var programTime = _ref2.programTime, 39880 playlist = _ref2.playlist, 39881 _ref2$retryCount = _ref2.retryCount, 39882 retryCount = _ref2$retryCount === undefined ? 2 : _ref2$retryCount, 39883 seekTo = _ref2.seekTo, 39884 _ref2$pauseAfterSeek = _ref2.pauseAfterSeek, 39885 pauseAfterSeek = _ref2$pauseAfterSeek === undefined ? true : _ref2$pauseAfterSeek, 39886 tech = _ref2.tech, 39887 callback = _ref2.callback; 39888 39889 if (!callback) { 39890 throw new Error('seekToProgramTime: callback must be provided'); 39891 } 39892 39893 if (typeof programTime === 'undefined' || !playlist || !seekTo) { 39894 return callback({ 39895 message: 'seekToProgramTime: programTime, seekTo and playlist must be provided' 39896 }); 39897 } 39898 39899 if (!playlist.endList && !tech.hasStarted_) { 39900 return callback({ 39901 message: 'player must be playing a live stream to start buffering' 39902 }); 39903 } 39904 39905 if (!verifyProgramDateTimeTags(playlist)) { 39906 return callback({ 39907 message: 'programDateTime tags must be provided in the manifest ' + playlist.resolvedUri 39908 }); 39909 } 39910 39911 var matchedSegment = findSegmentForProgramTime(programTime, playlist); // no match 39912 39913 if (!matchedSegment) { 39914 return callback({ 39915 message: programTime + ' was not found in the stream' 39916 }); 39917 } 39918 39919 var segment = matchedSegment.segment; 39920 var mediaOffset = getOffsetFromTimestamp(segment.dateTimeObject, programTime); 39921 39922 if (matchedSegment.type === 'estimate') { 39923 // we've run out of retries 39924 if (retryCount === 0) { 39925 return callback({ 39926 message: programTime + ' is not buffered yet. Try again' 39927 }); 39928 } 39929 39930 seekTo(matchedSegment.estimatedStart + mediaOffset); 39931 tech.one('seeked', function () { 39932 seekToProgramTime({ 39933 programTime: programTime, 39934 playlist: playlist, 39935 retryCount: retryCount - 1, 39936 seekTo: seekTo, 39937 pauseAfterSeek: pauseAfterSeek, 39938 tech: tech, 39939 callback: callback 39940 }); 39941 }); 39942 return; 39943 } // Since the segment.start value is determined from the buffered end or ending time 39944 // of the prior segment, the seekToTime doesn't need to account for any transmuxer 39945 // modifications. 39946 39947 39948 var seekToTime = segment.start + mediaOffset; 39949 39950 var seekedCallback = function seekedCallback() { 39951 return callback(null, tech.currentTime()); 39952 }; // listen for seeked event 39953 39954 39955 tech.one('seeked', seekedCallback); // pause before seeking as video.js will restore this state 39956 39957 if (pauseAfterSeek) { 39958 tech.pause(); 39959 } 39960 39961 seekTo(seekToTime); 39962 }; 39963 /** 39964 * ranges 39965 * 39966 * Utilities for working with TimeRanges. 39967 * 39968 */ 39969 // Fudge factor to account for TimeRanges rounding 39970 39971 39972 var TIME_FUDGE_FACTOR = 1 / 30; // Comparisons between time values such as current time and the end of the buffered range 39973 // can be misleading because of precision differences or when the current media has poorly 39974 // aligned audio and video, which can cause values to be slightly off from what you would 39975 // expect. This value is what we consider to be safe to use in such comparisons to account 39976 // for these scenarios. 39977 39978 var SAFE_TIME_DELTA = TIME_FUDGE_FACTOR * 3; 39979 39980 var filterRanges = function filterRanges(timeRanges, predicate) { 39981 var results = []; 39982 var i = void 0; 39983 39984 if (timeRanges && timeRanges.length) { 39985 // Search for ranges that match the predicate 39986 for (i = 0; i < timeRanges.length; i++) { 39987 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 39988 results.push([timeRanges.start(i), timeRanges.end(i)]); 39989 } 39990 } 39991 } 39992 39993 return videojs$1.createTimeRanges(results); 39994 }; 39995 /** 39996 * Attempts to find the buffered TimeRange that contains the specified 39997 * time. 39998 * @param {TimeRanges} buffered - the TimeRanges object to query 39999 * @param {number} time - the time to filter on. 40000 * @returns {TimeRanges} a new TimeRanges object 40001 */ 40002 40003 40004 var findRange = function findRange(buffered, time) { 40005 return filterRanges(buffered, function (start, end) { 40006 return start - SAFE_TIME_DELTA <= time && end + SAFE_TIME_DELTA >= time; 40007 });
vendor: 20,100 bytes, lines 40008-40642
40008 }; 40009 /** 40010 * Returns the TimeRanges that begin later than the specified time. 40011 * @param {TimeRanges} timeRanges - the TimeRanges object to query 40012 * @param {number} time - the time to filter on. 40013 * @returns {TimeRanges} a new TimeRanges object. 40014 */ 40015 40016 40017 var findNextRange = function findNextRange(timeRanges, time) { 40018 return filterRanges(timeRanges, function (start) { 40019 return start - TIME_FUDGE_FACTOR >= time; 40020 }); 40021 }; 40022 /** 40023 * Returns gaps within a list of TimeRanges 40024 * @param {TimeRanges} buffered - the TimeRanges object 40025 * @return {TimeRanges} a TimeRanges object of gaps 40026 */ 40027 40028 40029 var findGaps = function findGaps(buffered) { 40030 if (buffered.length < 2) { 40031 return videojs$1.createTimeRanges(); 40032 } 40033 40034 var ranges = []; 40035 40036 for (var i = 1; i < buffered.length; i++) { 40037 var start = buffered.end(i - 1); 40038 var end = buffered.start(i); 40039 ranges.push([start, end]); 40040 } 40041 40042 return videojs$1.createTimeRanges(ranges); 40043 }; 40044 /** 40045 * Gets a human readable string for a TimeRange 40046 * 40047 * @param {TimeRange} range 40048 * @returns {String} a human readable string 40049 */ 40050 40051 40052 var printableRange = function printableRange(range) { 40053 var strArr = []; 40054 40055 if (!range || !range.length) { 40056 return ''; 40057 } 40058 40059 for (var i = 0; i < range.length; i++) { 40060 strArr.push(range.start(i) + ' => ' + range.end(i)); 40061 } 40062 40063 return strArr.join(', '); 40064 }; 40065 /** 40066 * Calculates the amount of time left in seconds until the player hits the end of the 40067 * buffer and causes a rebuffer 40068 * 40069 * @param {TimeRange} buffered 40070 * The state of the buffer 40071 * @param {Numnber} currentTime 40072 * The current time of the player 40073 * @param {Number} playbackRate 40074 * The current playback rate of the player. Defaults to 1. 40075 * @return {Number} 40076 * Time until the player has to start rebuffering in seconds. 40077 * @function timeUntilRebuffer 40078 */ 40079 40080 40081 var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) { 40082 var playbackRate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; 40083 var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0; 40084 return (bufferedEnd - currentTime) / playbackRate; 40085 }; 40086 /** 40087 * Converts a TimeRanges object into an array representation 40088 * @param {TimeRanges} timeRanges 40089 * @returns {Array} 40090 */ 40091 40092 40093 var timeRangesToArray = function timeRangesToArray(timeRanges) { 40094 var timeRangesList = []; 40095 40096 for (var i = 0; i < timeRanges.length; i++) { 40097 timeRangesList.push({ 40098 start: timeRanges.start(i), 40099 end: timeRanges.end(i) 40100 }); 40101 } 40102 40103 return timeRangesList; 40104 }; 40105 /** 40106 * @file create-text-tracks-if-necessary.js 40107 */ 40108 40109 /** 40110 * Create text tracks on video.js if they exist on a segment. 40111 * 40112 * @param {Object} sourceBuffer the VSB or FSB 40113 * @param {Object} mediaSource the HTML media source 40114 * @param {Object} segment the segment that may contain the text track 40115 * @private 40116 */ 40117 40118 40119 var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) { 40120 var player = mediaSource.player_; // create an in-band caption track if one is present in the segment 40121 40122 if (segment.captions && segment.captions.length) { 40123 if (!sourceBuffer.inbandTextTracks_) { 40124 sourceBuffer.inbandTextTracks_ = {}; 40125 } 40126 40127 for (var trackId in segment.captionStreams) { 40128 if (!sourceBuffer.inbandTextTracks_[trackId]) { 40129 player.tech_.trigger({ 40130 type: 'usage', 40131 name: 'hls-608' 40132 }); 40133 var track = player.textTracks().getTrackById(trackId); 40134 40135 if (track) { 40136 // Resuse an existing track with a CC# id because this was 40137 // very likely created by videojs-contrib-hls from information 40138 // in the m3u8 for us to use 40139 sourceBuffer.inbandTextTracks_[trackId] = track; 40140 } else { 40141 // Otherwise, create a track with the default `CC#` label and 40142 // without a language 40143 sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({ 40144 kind: 'captions', 40145 id: trackId, 40146 label: trackId 40147 }, false).track; 40148 } 40149 } 40150 } 40151 } 40152 40153 if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) { 40154 sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({ 40155 kind: 'metadata', 40156 label: 'Timed Metadata' 40157 }, false).track; 40158 sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType; 40159 } 40160 }; 40161 /** 40162 * @file remove-cues-from-track.js 40163 */ 40164 40165 /** 40166 * Remove cues from a track on video.js. 40167 * 40168 * @param {Double} start start of where we should remove the cue 40169 * @param {Double} end end of where the we should remove the cue 40170 * @param {Object} track the text track to remove the cues from 40171 * @private 40172 */ 40173 40174 40175 var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 40176 var i = void 0; 40177 var cue = void 0; 40178 40179 if (!track) { 40180 return; 40181 } 40182 40183 if (!track.cues) { 40184 return; 40185 } 40186 40187 i = track.cues.length; 40188 40189 while (i--) { 40190 cue = track.cues[i]; // Remove any overlapping cue 40191 40192 if (cue.startTime <= end && cue.endTime >= start) { 40193 track.removeCue(cue); 40194 } 40195 } 40196 }; 40197 /** 40198 * @file add-text-track-data.js 40199 */ 40200 40201 /** 40202 * Define properties on a cue for backwards compatability, 40203 * but warn the user that the way that they are using it 40204 * is depricated and will be removed at a later date. 40205 * 40206 * @param {Cue} cue the cue to add the properties on 40207 * @private 40208 */ 40209 40210 40211 var deprecateOldCue = function deprecateOldCue(cue) { 40212 Object.defineProperties(cue.frame, { 40213 id: { 40214 get: function get() { 40215 videojs$1.log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 40216 return cue.value.key; 40217 } 40218 }, 40219 value: { 40220 get: function get() { 40221 videojs$1.log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 40222 return cue.value.data; 40223 } 40224 }, 40225 privateData: { 40226 get: function get() { 40227 videojs$1.log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 40228 return cue.value.data; 40229 } 40230 } 40231 }); 40232 }; 40233 40234 var durationOfVideo = function durationOfVideo(duration) { 40235 var dur = void 0; 40236 40237 if (isNaN(duration) || Math.abs(duration) === Infinity) { 40238 dur = Number.MAX_VALUE; 40239 } else { 40240 dur = duration; 40241 } 40242 40243 return dur; 40244 }; 40245 /** 40246 * Add text track data to a source handler given the captions and 40247 * metadata from the buffer. 40248 * 40249 * @param {Object} sourceHandler the virtual source buffer 40250 * @param {Array} captionArray an array of caption data 40251 * @param {Array} metadataArray an array of meta data 40252 * @private 40253 */ 40254 40255 40256 var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) { 40257 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 40258 40259 if (captionArray) { 40260 captionArray.forEach(function (caption) { 40261 var track = caption.stream; 40262 this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text)); 40263 }, sourceHandler); 40264 } 40265 40266 if (metadataArray) { 40267 var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration); 40268 metadataArray.forEach(function (metadata) { 40269 var time = metadata.cueTime + this.timestampOffset; // if time isn't a finite number between 0 and Infinity, like NaN, 40270 // ignore this bit of metadata. 40271 // This likely occurs when you have an non-timed ID3 tag like TIT2, 40272 // which is the "Title/Songname/Content description" frame 40273 40274 if (typeof time !== 'number' || window$1.isNaN(time) || time < 0 || !(time < Infinity)) { 40275 return; 40276 } 40277 40278 metadata.frames.forEach(function (frame) { 40279 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 40280 cue.frame = frame; 40281 cue.value = frame; 40282 deprecateOldCue(cue); 40283 this.metadataTrack_.addCue(cue); 40284 }, this); 40285 }, sourceHandler); // Updating the metadeta cues so that 40286 // the endTime of each cue is the startTime of the next cue 40287 // the endTime of last cue is the duration of the video 40288 40289 if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) { 40290 var cues = sourceHandler.metadataTrack_.cues; 40291 var cuesArray = []; // Create a copy of the TextTrackCueList... 40292 // ...disregarding cues with a falsey value 40293 40294 for (var i = 0; i < cues.length; i++) { 40295 if (cues[i]) { 40296 cuesArray.push(cues[i]); 40297 } 40298 } // Group cues by their startTime value 40299 40300 40301 var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) { 40302 var timeSlot = obj[cue.startTime] || []; 40303 timeSlot.push(cue); 40304 obj[cue.startTime] = timeSlot; 40305 return obj; 40306 }, {}); // Sort startTimes by ascending order 40307 40308 var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) { 40309 return Number(a) - Number(b); 40310 }); // Map each cue group's endTime to the next group's startTime 40311 40312 sortedStartTimes.forEach(function (startTime, idx) { 40313 var cueGroup = cuesGroupedByStartTime[startTime]; 40314 var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; // Map each cue's endTime the next group's startTime 40315 40316 cueGroup.forEach(function (cue) { 40317 cue.endTime = nextTime; 40318 }); 40319 }); 40320 } 40321 } 40322 }; 40323 40324 var win = typeof window !== 'undefined' ? window : {}, 40325 TARGET = typeof Symbol === 'undefined' ? '__target' : Symbol(), 40326 SCRIPT_TYPE = 'application/javascript', 40327 BlobBuilder = win.BlobBuilder || win.WebKitBlobBuilder || win.MozBlobBuilder || win.MSBlobBuilder, 40328 URL = win.URL || win.webkitURL || URL && URL.msURL, 40329 Worker = win.Worker; 40330 /** 40331 * Returns a wrapper around Web Worker code that is constructible. 40332 * 40333 * @function shimWorker 40334 * 40335 * @param { String } filename The name of the file 40336 * @param { Function } fn Function wrapping the code of the worker 40337 */ 40338 40339 function shimWorker(filename, fn) { 40340 return function ShimWorker(forceFallback) { 40341 var o = this; 40342 40343 if (!fn) { 40344 return new Worker(filename); 40345 } else if (Worker && !forceFallback) { 40346 // Convert the function's inner code to a string to construct the worker 40347 var source = fn.toString().replace(/^function.+?{/, '').slice(0, -1), 40348 objURL = createSourceObject(source); 40349 this[TARGET] = new Worker(objURL); 40350 wrapTerminate(this[TARGET], objURL); 40351 return this[TARGET]; 40352 } else { 40353 var selfShim = { 40354 postMessage: function postMessage(m) { 40355 if (o.onmessage) { 40356 setTimeout(function () { 40357 o.onmessage({ 40358 data: m, 40359 target: selfShim 40360 }); 40361 }); 40362 } 40363 } 40364 }; 40365 fn.call(selfShim); 40366 40367 this.postMessage = function (m) { 40368 setTimeout(function () { 40369 selfShim.onmessage({ 40370 data: m, 40371 target: o 40372 }); 40373 }); 40374 }; 40375 40376 this.isThisThread = true; 40377 } 40378 }; 40379 } // Test Worker capabilities 40380 40381 40382 if (Worker) { 40383 var testWorker, 40384 objURL = createSourceObject('self.onmessage = function () {}'), 40385 testArray = new Uint8Array(1); 40386 40387 try { 40388 testWorker = new Worker(objURL); // Native browser on some Samsung devices throws for transferables, let's detect it 40389 40390 testWorker.postMessage(testArray, [testArray.buffer]); 40391 } catch (e) { 40392 Worker = null; 40393 } finally { 40394 URL.revokeObjectURL(objURL); 40395 40396 if (testWorker) { 40397 testWorker.terminate(); 40398 } 40399 } 40400 } 40401 40402 function createSourceObject(str) { 40403 try { 40404 return URL.createObjectURL(new Blob([str], { 40405 type: SCRIPT_TYPE 40406 })); 40407 } catch (e) { 40408 var blob = new BlobBuilder(); 40409 blob.append(str); 40410 return URL.createObjectURL(blob.getBlob(type)); 40411 } 40412 } 40413 40414 function wrapTerminate(worker, objURL) { 40415 if (!worker || !objURL) return; 40416 var term = worker.terminate; 40417 worker.objURL = objURL; 40418 40419 worker.terminate = function () { 40420 if (worker.objURL) URL.revokeObjectURL(worker.objURL); 40421 term.call(worker); 40422 }; 40423 } 40424 40425 var TransmuxWorker = new shimWorker("./transmuxer-worker.worker.js", function (window, document$$1) { 40426 var self = this; 40427 40428 var transmuxerWorker = function () { 40429 /** 40430 * mux.js 40431 * 40432 * Copyright (c) Brightcove 40433 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 40434 * 40435 * A lightweight readable stream implemention that handles event dispatching. 40436 * Objects that inherit from streams should call init in their constructors. 40437 */ 40438 var Stream = function Stream() { 40439 this.init = function () { 40440 var listeners = {}; 40441 /** 40442 * Add a listener for a specified event type. 40443 * @param type {string} the event name 40444 * @param listener {function} the callback to be invoked when an event of 40445 * the specified type occurs 40446 */ 40447 40448 this.on = function (type, listener) { 40449 if (!listeners[type]) { 40450 listeners[type] = []; 40451 } 40452 40453 listeners[type] = listeners[type].concat(listener); 40454 }; 40455 /** 40456 * Remove a listener for a specified event type. 40457 * @param type {string} the event name 40458 * @param listener {function} a function previously registered for this 40459 * type of event through `on` 40460 */ 40461 40462 40463 this.off = function (type, listener) { 40464 var index; 40465 40466 if (!listeners[type]) { 40467 return false; 40468 } 40469 40470 index = listeners[type].indexOf(listener); 40471 listeners[type] = listeners[type].slice(); 40472 listeners[type].splice(index, 1); 40473 return index > -1; 40474 }; 40475 /** 40476 * Trigger an event of the specified type on this stream. Any additional 40477 * arguments to this function are passed as parameters to event listeners. 40478 * @param type {string} the event name 40479 */ 40480 40481 40482 this.trigger = function (type) { 40483 var callbacks, i, length, args; 40484 callbacks = listeners[type]; 40485 40486 if (!callbacks) { 40487 return; 40488 } // Slicing the arguments on every invocation of this method 40489 // can add a significant amount of overhead. Avoid the 40490 // intermediate object creation for the common case of a 40491 // single callback argument 40492 40493 40494 if (arguments.length === 2) { 40495 length = callbacks.length; 40496 40497 for (i = 0; i < length; ++i) { 40498 callbacks[i].call(this, arguments[1]); 40499 } 40500 } else { 40501 args = []; 40502 i = arguments.length; 40503 40504 for (i = 1; i < arguments.length; ++i) { 40505 args.push(arguments[i]); 40506 } 40507 40508 length = callbacks.length; 40509 40510 for (i = 0; i < length; ++i) { 40511 callbacks[i].apply(this, args); 40512 } 40513 } 40514 }; 40515 /** 40516 * Destroys the stream and cleans up. 40517 */ 40518 40519 40520 this.dispose = function () { 40521 listeners = {}; 40522 }; 40523 }; 40524 }; 40525 /** 40526 * Forwards all `data` events on this stream to the destination stream. The 40527 * destination stream should provide a method `push` to receive the data 40528 * events as they arrive. 40529 * @param destination {stream} the stream that will receive all `data` events 40530 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 40531 * when the current stream emits a 'done' event 40532 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 40533 */ 40534 40535 40536 Stream.prototype.pipe = function (destination) { 40537 this.on('data', function (data) { 40538 destination.push(data); 40539 }); 40540 this.on('done', function (flushSource) { 40541 destination.flush(flushSource); 40542 }); 40543 this.on('partialdone', function (flushSource) { 40544 destination.partialFlush(flushSource); 40545 }); 40546 this.on('endedtimeline', function (flushSource) { 40547 destination.endTimeline(flushSource); 40548 }); 40549 this.on('reset', function (flushSource) { 40550 destination.reset(flushSource); 40551 }); 40552 return destination; 40553 }; // Default stream functions that are expected to be overridden to perform 40554 // actual work. These are provided by the prototype as a sort of no-op 40555 // implementation so that we don't have to check for their existence in the 40556 // `pipe` function above. 40557 40558 40559 Stream.prototype.push = function (data) { 40560 this.trigger('data', data); 40561 }; 40562 40563 Stream.prototype.flush = function (flushSource) { 40564 this.trigger('done', flushSource); 40565 }; 40566 40567 Stream.prototype.partialFlush = function (flushSource) { 40568 this.trigger('partialdone', flushSource); 40569 }; 40570 40571 Stream.prototype.endTimeline = function (flushSource) { 40572 this.trigger('endedtimeline', flushSource); 40573 }; 40574 40575 Stream.prototype.reset = function (flushSource) { 40576 this.trigger('reset', flushSource); 40577 }; 40578 40579 var stream = Stream; 40580 /** 40581 * mux.js 40582 * 40583 * Copyright (c) Brightcove 40584 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 40585 * 40586 * Functions that generate fragmented MP4s suitable for use with Media 40587 * Source Extensions. 40588 */ 40589 40590 var UINT32_MAX = Math.pow(2, 32) - 1; 40591 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 40592 40593 (function () { 40594 var i; 40595 types = { 40596 avc1: [], 40597 // codingname 40598 avcC: [], 40599 btrt: [], 40600 dinf: [], 40601 dref: [], 40602 esds: [], 40603 ftyp: [], 40604 hdlr: [], 40605 mdat: [], 40606 mdhd: [], 40607 mdia: [], 40608 mfhd: [], 40609 minf: [], 40610 moof: [], 40611 moov: [], 40612 mp4a: [], 40613 // codingname 40614 mvex: [], 40615 mvhd: [], 40616 pasp: [], 40617 sdtp: [], 40618 smhd: [], 40619 stbl: [], 40620 stco: [], 40621 stsc: [], 40622 stsd: [], 40623 stsz: [], 40624 stts: [], 40625 styp: [], 40626 tfdt: [], 40627 tfhd: [], 40628 traf: [], 40629 trak: [], 40630 trun: [], 40631 trex: [], 40632 tkhd: [], 40633 vmhd: [] 40634 }; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 40635 // don't throw an error 40636 40637 if (typeof Uint8Array === 'undefined') { 40638 return; 40639 } 40640 40641 for (i in types) { 40642 if (types.hasOwnProperty(i)) {
vendor: 16,384 bytes, lines 40643-41012
40643 types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; 40644 } 40645 } 40646 40647 MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]); 40648 AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]); 40649 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 40650 VIDEO_HDLR = new Uint8Array([0x00, // version 0 40651 0x00, 0x00, 0x00, // flags 40652 0x00, 0x00, 0x00, 0x00, // pre_defined 40653 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 40654 0x00, 0x00, 0x00, 0x00, // reserved 40655 0x00, 0x00, 0x00, 0x00, // reserved 40656 0x00, 0x00, 0x00, 0x00, // reserved 40657 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 40658 ]); 40659 AUDIO_HDLR = new Uint8Array([0x00, // version 0 40660 0x00, 0x00, 0x00, // flags 40661 0x00, 0x00, 0x00, 0x00, // pre_defined 40662 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 40663 0x00, 0x00, 0x00, 0x00, // reserved 40664 0x00, 0x00, 0x00, 0x00, // reserved 40665 0x00, 0x00, 0x00, 0x00, // reserved 40666 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 40667 ]); 40668 HDLR_TYPES = { 40669 video: VIDEO_HDLR, 40670 audio: AUDIO_HDLR 40671 }; 40672 DREF = new Uint8Array([0x00, // version 0 40673 0x00, 0x00, 0x00, // flags 40674 0x00, 0x00, 0x00, 0x01, // entry_count 40675 0x00, 0x00, 0x00, 0x0c, // entry_size 40676 0x75, 0x72, 0x6c, 0x20, // 'url' type 40677 0x00, // version 0 40678 0x00, 0x00, 0x01 // entry_flags 40679 ]); 40680 SMHD = new Uint8Array([0x00, // version 40681 0x00, 0x00, 0x00, // flags 40682 0x00, 0x00, // balance, 0 means centered 40683 0x00, 0x00 // reserved 40684 ]); 40685 STCO = new Uint8Array([0x00, // version 40686 0x00, 0x00, 0x00, // flags 40687 0x00, 0x00, 0x00, 0x00 // entry_count 40688 ]); 40689 STSC = STCO; 40690 STSZ = new Uint8Array([0x00, // version 40691 0x00, 0x00, 0x00, // flags 40692 0x00, 0x00, 0x00, 0x00, // sample_size 40693 0x00, 0x00, 0x00, 0x00 // sample_count 40694 ]); 40695 STTS = STCO; 40696 VMHD = new Uint8Array([0x00, // version 40697 0x00, 0x00, 0x01, // flags 40698 0x00, 0x00, // graphicsmode 40699 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor 40700 ]); 40701 })(); 40702 40703 box = function box(type) { 40704 var payload = [], 40705 size = 0, 40706 i, 40707 result, 40708 view; 40709 40710 for (i = 1; i < arguments.length; i++) { 40711 payload.push(arguments[i]); 40712 } 40713 40714 i = payload.length; // calculate the total size we need to allocate 40715 40716 while (i--) { 40717 size += payload[i].byteLength; 40718 } 40719 40720 result = new Uint8Array(size + 8); 40721 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 40722 view.setUint32(0, result.byteLength); 40723 result.set(type, 4); // copy the payload into the result 40724 40725 for (i = 0, size = 8; i < payload.length; i++) { 40726 result.set(payload[i], size); 40727 size += payload[i].byteLength; 40728 } 40729 40730 return result; 40731 }; 40732 40733 dinf = function dinf() { 40734 return box(types.dinf, box(types.dref, DREF)); 40735 }; 40736 40737 esds = function esds(track) { 40738 return box(types.esds, new Uint8Array([0x00, // version 40739 0x00, 0x00, 0x00, // flags 40740 // ES_Descriptor 40741 0x03, // tag, ES_DescrTag 40742 0x19, // length 40743 0x00, 0x00, // ES_ID 40744 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 40745 // DecoderConfigDescriptor 40746 0x04, // tag, DecoderConfigDescrTag 40747 0x11, // length 40748 0x40, // object type 40749 0x15, // streamType 40750 0x00, 0x06, 0x00, // bufferSizeDB 40751 0x00, 0x00, 0xda, 0xc0, // maxBitrate 40752 0x00, 0x00, 0xda, 0xc0, // avgBitrate 40753 // DecoderSpecificInfo 40754 0x05, // tag, DecoderSpecificInfoTag 40755 0x02, // length 40756 // ISO/IEC 14496-3, AudioSpecificConfig 40757 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 40758 track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig 40759 ])); 40760 }; 40761 40762 ftyp = function ftyp() { 40763 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 40764 }; 40765 40766 hdlr = function hdlr(type) { 40767 return box(types.hdlr, HDLR_TYPES[type]); 40768 }; 40769 40770 mdat = function mdat(data) { 40771 return box(types.mdat, data); 40772 }; 40773 40774 mdhd = function mdhd(track) { 40775 var result = new Uint8Array([0x00, // version 0 40776 0x00, 0x00, 0x00, // flags 40777 0x00, 0x00, 0x00, 0x02, // creation_time 40778 0x00, 0x00, 0x00, 0x03, // modification_time 40779 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 40780 track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration 40781 0x55, 0xc4, // 'und' language (undetermined) 40782 0x00, 0x00]); // Use the sample rate from the track metadata, when it is 40783 // defined. The sample rate can be parsed out of an ADTS header, for 40784 // instance. 40785 40786 if (track.samplerate) { 40787 result[12] = track.samplerate >>> 24 & 0xFF; 40788 result[13] = track.samplerate >>> 16 & 0xFF; 40789 result[14] = track.samplerate >>> 8 & 0xFF; 40790 result[15] = track.samplerate & 0xFF; 40791 } 40792 40793 return box(types.mdhd, result); 40794 }; 40795 40796 mdia = function mdia(track) { 40797 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 40798 }; 40799 40800 mfhd = function mfhd(sequenceNumber) { 40801 return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags 40802 (sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number 40803 ])); 40804 }; 40805 40806 minf = function minf(track) { 40807 return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track)); 40808 }; 40809 40810 moof = function moof(sequenceNumber, tracks) { 40811 var trackFragments = [], 40812 i = tracks.length; // build traf boxes for each track fragment 40813 40814 while (i--) { 40815 trackFragments[i] = traf(tracks[i]); 40816 } 40817 40818 return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments)); 40819 }; 40820 /** 40821 * Returns a movie box. 40822 * @param tracks {array} the tracks associated with this movie 40823 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 40824 */ 40825 40826 40827 moov = function moov(tracks) { 40828 var i = tracks.length, 40829 boxes = []; 40830 40831 while (i--) { 40832 boxes[i] = trak(tracks[i]); 40833 } 40834 40835 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 40836 }; 40837 40838 mvex = function mvex(tracks) { 40839 var i = tracks.length, 40840 boxes = []; 40841 40842 while (i--) { 40843 boxes[i] = trex(tracks[i]); 40844 } 40845 40846 return box.apply(null, [types.mvex].concat(boxes)); 40847 }; 40848 40849 mvhd = function mvhd(duration) { 40850 var bytes = new Uint8Array([0x00, // version 0 40851 0x00, 0x00, 0x00, // flags 40852 0x00, 0x00, 0x00, 0x01, // creation_time 40853 0x00, 0x00, 0x00, 0x02, // modification_time 40854 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 40855 (duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration 40856 0x00, 0x01, 0x00, 0x00, // 1.0 rate 40857 0x01, 0x00, // 1.0 volume 40858 0x00, 0x00, // reserved 40859 0x00, 0x00, 0x00, 0x00, // reserved 40860 0x00, 0x00, 0x00, 0x00, // reserved 40861 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 40862 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 40863 0xff, 0xff, 0xff, 0xff // next_track_ID 40864 ]); 40865 return box(types.mvhd, bytes); 40866 }; 40867 40868 sdtp = function sdtp(track) { 40869 var samples = track.samples || [], 40870 bytes = new Uint8Array(4 + samples.length), 40871 flags, 40872 i; // leave the full box header (4 bytes) all zero 40873 // write the sample table 40874 40875 for (i = 0; i < samples.length; i++) { 40876 flags = samples[i].flags; 40877 bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; 40878 } 40879 40880 return box(types.sdtp, bytes); 40881 }; 40882 40883 stbl = function stbl(track) { 40884 return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO)); 40885 }; 40886 40887 (function () { 40888 var videoSample, audioSample; 40889 40890 stsd = function stsd(track) { 40891 return box(types.stsd, new Uint8Array([0x00, // version 0 40892 0x00, 0x00, 0x00, // flags 40893 0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track)); 40894 }; 40895 40896 videoSample = function videoSample(track) { 40897 var sps = track.sps || [], 40898 pps = track.pps || [], 40899 sequenceParameterSets = [], 40900 pictureParameterSets = [], 40901 i, 40902 avc1Box; // assemble the SPSs 40903 40904 for (i = 0; i < sps.length; i++) { 40905 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 40906 sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength 40907 40908 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 40909 } // assemble the PPSs 40910 40911 40912 for (i = 0; i < pps.length; i++) { 40913 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 40914 pictureParameterSets.push(pps[i].byteLength & 0xFF); 40915 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 40916 } 40917 40918 avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 40919 0x00, 0x01, // data_reference_index 40920 0x00, 0x00, // pre_defined 40921 0x00, 0x00, // reserved 40922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined 40923 (track.width & 0xff00) >> 8, track.width & 0xff, // width 40924 (track.height & 0xff00) >> 8, track.height & 0xff, // height 40925 0x00, 0x48, 0x00, 0x00, // horizresolution 40926 0x00, 0x48, 0x00, 0x00, // vertresolution 40927 0x00, 0x00, 0x00, 0x00, // reserved 40928 0x00, 0x01, // frame_count 40929 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 40930 0x00, 0x18, // depth = 24 40931 0x11, 0x11 // pre_defined = -1 40932 ]), box(types.avcC, new Uint8Array([0x01, // configurationVersion 40933 track.profileIdc, // AVCProfileIndication 40934 track.profileCompatibility, // profile_compatibility 40935 track.levelIdc, // AVCLevelIndication 40936 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 40937 ].concat([sps.length], // numOfSequenceParameterSets 40938 sequenceParameterSets, // "SPS" 40939 [pps.length], // numOfPictureParameterSets 40940 pictureParameterSets // "PPS" 40941 ))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 40942 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 40943 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate 40944 ]))]; 40945 40946 if (track.sarRatio) { 40947 var hSpacing = track.sarRatio[0], 40948 vSpacing = track.sarRatio[1]; 40949 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]))); 40950 } 40951 40952 return box.apply(null, avc1Box); 40953 }; 40954 40955 audioSample = function audioSample(track) { 40956 return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12 40957 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 40958 0x00, 0x01, // data_reference_index 40959 // AudioSampleEntry, ISO/IEC 14496-12 40960 0x00, 0x00, 0x00, 0x00, // reserved 40961 0x00, 0x00, 0x00, 0x00, // reserved 40962 (track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount 40963 (track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize 40964 0x00, 0x00, // pre_defined 40965 0x00, 0x00, // reserved 40966 (track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16 40967 // MP4AudioSampleEntry, ISO/IEC 14496-14 40968 ]), esds(track)); 40969 }; 40970 })(); 40971 40972 tkhd = function tkhd(track) { 40973 var result = new Uint8Array([0x00, // version 0 40974 0x00, 0x00, 0x07, // flags 40975 0x00, 0x00, 0x00, 0x00, // creation_time 40976 0x00, 0x00, 0x00, 0x00, // modification_time 40977 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 40978 0x00, 0x00, 0x00, 0x00, // reserved 40979 (track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration 40980 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved 40981 0x00, 0x00, // layer 40982 0x00, 0x00, // alternate_group 40983 0x01, 0x00, // non-audio track volume 40984 0x00, 0x00, // reserved 40985 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 40986 (track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width 40987 (track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height 40988 ]); 40989 return box(types.tkhd, result); 40990 }; 40991 /** 40992 * Generate a track fragment (traf) box. A traf box collects metadata 40993 * about tracks in a movie fragment (moof) box. 40994 */ 40995 40996 40997 traf = function traf(track) { 40998 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 40999 trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0 41000 0x00, 0x00, 0x3a, // flags 41001 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 41002 0x00, 0x00, 0x00, 0x01, // sample_description_index 41003 0x00, 0x00, 0x00, 0x00, // default_sample_duration 41004 0x00, 0x00, 0x00, 0x00, // default_sample_size 41005 0x00, 0x00, 0x00, 0x00 // default_sample_flags 41006 ])); 41007 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 41008 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 41009 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1 41010 0x00, 0x00, 0x00, // flags 41011 // baseMediaDecodeTime 41012 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 41012-41257
410120xFF])); // the data offset specifies the number of bytes from the start of 41013 // the containing moof to the first payload byte of the associated 41014 // mdat 41015 41016 dataOffset = 32 + // tfhd 41017 20 + // tfdt 41018 8 + // traf header 41019 16 + // mfhd 41020 8 + // moof header 41021 8; // mdat header 41022 // audio tracks require less metadata 41023 41024 if (track.type === 'audio') { 41025 trackFragmentRun = trun(track, dataOffset); 41026 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun); 41027 } // video tracks should contain an independent and disposable samples 41028 // box (sdtp) 41029 // generate one and adjust offsets to match 41030 41031 41032 sampleDependencyTable = sdtp(track); 41033 trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset); 41034 return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable); 41035 }; 41036 /** 41037 * Generate a track box. 41038 * @param track {object} a track definition 41039 * @return {Uint8Array} the track box 41040 */ 41041 41042 41043 trak = function trak(track) { 41044 track.duration = track.duration || 0xffffffff; 41045 return box(types.trak, tkhd(track), mdia(track)); 41046 }; 41047 41048 trex = function trex(track) { 41049 var result = new Uint8Array([0x00, // version 0 41050 0x00, 0x00, 0x00, // flags 41051 (track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID 41052 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 41053 0x00, 0x00, 0x00, 0x00, // default_sample_duration 41054 0x00, 0x00, 0x00, 0x00, // default_sample_size 41055 0x00, 0x01, 0x00, 0x01 // default_sample_flags 41056 ]); // the last two bytes of default_sample_flags is the sample 41057 // degradation priority, a hint about the importance of this sample 41058 // relative to others. Lower the degradation priority for all sample 41059 // types other than video. 41060 41061 if (track.type !== 'video') { 41062 result[result.length - 1] = 0x00; 41063 } 41064 41065 return box(types.trex, result); 41066 }; 41067 41068 (function () { 41069 var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a 41070 // duration is present for the first sample, it will be present for 41071 // all subsequent samples. 41072 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 41073 41074 trunHeader = function trunHeader(samples, offset) { 41075 var durationPresent = 0, 41076 sizePresent = 0, 41077 flagsPresent = 0, 41078 compositionTimeOffset = 0; // trun flag constants 41079 41080 if (samples.length) { 41081 if (samples[0].duration !== undefined) { 41082 durationPresent = 0x1; 41083 } 41084 41085 if (samples[0].size !== undefined) { 41086 sizePresent = 0x2; 41087 } 41088 41089 if (samples[0].flags !== undefined) { 41090 flagsPresent = 0x4; 41091 } 41092 41093 if (samples[0].compositionTimeOffset !== undefined) { 41094 compositionTimeOffset = 0x8; 41095 } 41096 } 41097 41098 return [0x00, // version 0 41099 0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags 41100 (samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count 41101 (offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset 41102 ]; 41103 }; 41104 41105 videoTrun = function videoTrun(track, offset) { 41106 var bytes, samples, sample, i; 41107 samples = track.samples || []; 41108 offset += 8 + 12 + 16 * samples.length; 41109 bytes = trunHeader(samples, offset); 41110 41111 for (i = 0; i < samples.length; i++) { 41112 sample = samples[i]; 41113 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 41114 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF, // sample_size 41115 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 41116 (sample.compositionTimeOffset & 0xFF000000) >>> 24, (sample.compositionTimeOffset & 0xFF0000) >>> 16, (sample.compositionTimeOffset & 0xFF00) >>> 8, sample.compositionTimeOffset & 0xFF // sample_composition_time_offset 41117 ]); 41118 } 41119 41120 return box(types.trun, new Uint8Array(bytes)); 41121 }; 41122 41123 audioTrun = function audioTrun(track, offset) { 41124 var bytes, samples, sample, i; 41125 samples = track.samples || []; 41126 offset += 8 + 12 + 8 * samples.length; 41127 bytes = trunHeader(samples, offset); 41128 41129 for (i = 0; i < samples.length; i++) { 41130 sample = samples[i]; 41131 bytes = bytes.concat([(sample.duration & 0xFF000000) >>> 24, (sample.duration & 0xFF0000) >>> 16, (sample.duration & 0xFF00) >>> 8, sample.duration & 0xFF, // sample_duration 41132 (sample.size & 0xFF000000) >>> 24, (sample.size & 0xFF0000) >>> 16, (sample.size & 0xFF00) >>> 8, sample.size & 0xFF]); // sample_size 41133 } 41134 41135 return box(types.trun, new Uint8Array(bytes)); 41136 }; 41137 41138 trun = function trun(track, offset) { 41139 if (track.type === 'audio') { 41140 return audioTrun(track, offset); 41141 } 41142 41143 return videoTrun(track, offset); 41144 }; 41145 })(); 41146 41147 var mp4Generator = { 41148 ftyp: ftyp, 41149 mdat: mdat, 41150 moof: moof, 41151 moov: moov, 41152 initSegment: function initSegment(tracks) { 41153 var fileType = ftyp(), 41154 movie = moov(tracks), 41155 result; 41156 result = new Uint8Array(fileType.byteLength + movie.byteLength); 41157 result.set(fileType); 41158 result.set(movie, fileType.byteLength); 41159 return result; 41160 } 41161 }; 41162 /** 41163 * mux.js 41164 * 41165 * Copyright (c) Brightcove 41166 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 41167 */ 41168 // Convert an array of nal units into an array of frames with each frame being 41169 // composed of the nal units that make up that frame 41170 // Also keep track of cummulative data about the frame from the nal units such 41171 // as the frame duration, starting pts, etc. 41172 41173 var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) { 41174 var i, 41175 currentNal, 41176 currentFrame = [], 41177 frames = []; // TODO added for LHLS, make sure this is OK 41178 41179 frames.byteLength = 0; 41180 frames.nalCount = 0; 41181 frames.duration = 0; 41182 currentFrame.byteLength = 0; 41183 41184 for (i = 0; i < nalUnits.length; i++) { 41185 currentNal = nalUnits[i]; // Split on 'aud'-type nal units 41186 41187 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 41188 // Since the very first nal unit is expected to be an AUD 41189 // only push to the frames array when currentFrame is not empty 41190 if (currentFrame.length) { 41191 currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK 41192 41193 frames.byteLength += currentFrame.byteLength; 41194 frames.nalCount += currentFrame.length; 41195 frames.duration += currentFrame.duration; 41196 frames.push(currentFrame); 41197 } 41198 41199 currentFrame = [currentNal]; 41200 currentFrame.byteLength = currentNal.data.byteLength; 41201 currentFrame.pts = currentNal.pts; 41202 currentFrame.dts = currentNal.dts; 41203 } else { 41204 // Specifically flag key frames for ease of use later 41205 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 41206 currentFrame.keyFrame = true; 41207 } 41208 41209 currentFrame.duration = currentNal.dts - currentFrame.dts; 41210 currentFrame.byteLength += currentNal.data.byteLength; 41211 currentFrame.push(currentNal); 41212 } 41213 } // For the last frame, use the duration of the previous frame if we 41214 // have nothing better to go on 41215 41216 41217 if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) { 41218 currentFrame.duration = frames[frames.length - 1].duration; 41219 } // Push the final frame 41220 // TODO added for LHLS, make sure this is OK 41221 41222 41223 frames.byteLength += currentFrame.byteLength; 41224 frames.nalCount += currentFrame.length; 41225 frames.duration += currentFrame.duration; 41226 frames.push(currentFrame); 41227 return frames; 41228 }; // Convert an array of frames into an array of Gop with each Gop being composed 41229 // of the frames that make up that Gop 41230 // Also keep track of cummulative data about the Gop from the frames such as the 41231 // Gop duration, starting pts, etc. 41232 41233 41234 var groupFramesIntoGops = function groupFramesIntoGops(frames) { 41235 var i, 41236 currentFrame, 41237 currentGop = [], 41238 gops = []; // We must pre-set some of the values on the Gop since we 41239 // keep running totals of these values 41240 41241 currentGop.byteLength = 0; 41242 currentGop.nalCount = 0; 41243 currentGop.duration = 0; 41244 currentGop.pts = frames[0].pts; 41245 currentGop.dts = frames[0].dts; // store some metadata about all the Gops 41246 41247 gops.byteLength = 0; 41248 gops.nalCount = 0; 41249 gops.duration = 0; 41250 gops.pts = frames[0].pts; 41251 gops.dts = frames[0].dts; 41252 41253 for (i = 0; i < frames.length; i++) { 41254 currentFrame = frames[i]; 41255 41256 if (currentFrame.keyFrame) { 41257 // Since the very first frame is expected to be an keyframe
vendor: 7,922 bytes, lines 41258-41490
41258 // only push to the gops array when currentGop is not empty 41259 if (currentGop.length) { 41260 gops.push(currentGop); 41261 gops.byteLength += currentGop.byteLength; 41262 gops.nalCount += currentGop.nalCount; 41263 gops.duration += currentGop.duration; 41264 } 41265 41266 currentGop = [currentFrame]; 41267 currentGop.nalCount = currentFrame.length; 41268 currentGop.byteLength = currentFrame.byteLength; 41269 currentGop.pts = currentFrame.pts; 41270 currentGop.dts = currentFrame.dts; 41271 currentGop.duration = currentFrame.duration; 41272 } else { 41273 currentGop.duration += currentFrame.duration; 41274 currentGop.nalCount += currentFrame.length; 41275 currentGop.byteLength += currentFrame.byteLength; 41276 currentGop.push(currentFrame); 41277 } 41278 } 41279 41280 if (gops.length && currentGop.duration <= 0) { 41281 currentGop.duration = gops[gops.length - 1].duration; 41282 } 41283 41284 gops.byteLength += currentGop.byteLength; 41285 gops.nalCount += currentGop.nalCount; 41286 gops.duration += currentGop.duration; // push the final Gop 41287 41288 gops.push(currentGop); 41289 return gops; 41290 }; 41291 /* 41292 * Search for the first keyframe in the GOPs and throw away all frames 41293 * until that keyframe. Then extend the duration of the pulled keyframe 41294 * and pull the PTS and DTS of the keyframe so that it covers the time 41295 * range of the frames that were disposed. 41296 * 41297 * @param {Array} gops video GOPs 41298 * @returns {Array} modified video GOPs 41299 */ 41300 41301 41302 var extendFirstKeyFrame = function extendFirstKeyFrame(gops) { 41303 var currentGop; 41304 41305 if (!gops[0][0].keyFrame && gops.length > 1) { 41306 // Remove the first GOP 41307 currentGop = gops.shift(); 41308 gops.byteLength -= currentGop.byteLength; 41309 gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the 41310 // first gop to cover the time period of the 41311 // frames we just removed 41312 41313 gops[0][0].dts = currentGop.dts; 41314 gops[0][0].pts = currentGop.pts; 41315 gops[0][0].duration += currentGop.duration; 41316 } 41317 41318 return gops; 41319 }; 41320 /** 41321 * Default sample object 41322 * see ISO/IEC 14496-12:2012, section 8.6.4.3 41323 */ 41324 41325 41326 var createDefaultSample = function createDefaultSample() { 41327 return { 41328 size: 0, 41329 flags: { 41330 isLeading: 0, 41331 dependsOn: 1, 41332 isDependedOn: 0, 41333 hasRedundancy: 0, 41334 degradationPriority: 0, 41335 isNonSyncSample: 1 41336 } 41337 }; 41338 }; 41339 /* 41340 * Collates information from a video frame into an object for eventual 41341 * entry into an MP4 sample table. 41342 * 41343 * @param {Object} frame the video frame 41344 * @param {Number} dataOffset the byte offset to position the sample 41345 * @return {Object} object containing sample table info for a frame 41346 */ 41347 41348 41349 var sampleForFrame = function sampleForFrame(frame, dataOffset) { 41350 var sample = createDefaultSample(); 41351 sample.dataOffset = dataOffset; 41352 sample.compositionTimeOffset = frame.pts - frame.dts; 41353 sample.duration = frame.duration; 41354 sample.size = 4 * frame.length; // Space for nal unit size 41355 41356 sample.size += frame.byteLength; 41357 41358 if (frame.keyFrame) { 41359 sample.flags.dependsOn = 2; 41360 sample.flags.isNonSyncSample = 0; 41361 } 41362 41363 return sample; 41364 }; // generate the track's sample table from an array of gops 41365 41366 41367 var generateSampleTable = function generateSampleTable(gops, baseDataOffset) { 41368 var h, 41369 i, 41370 sample, 41371 currentGop, 41372 currentFrame, 41373 dataOffset = baseDataOffset || 0, 41374 samples = []; 41375 41376 for (h = 0; h < gops.length; h++) { 41377 currentGop = gops[h]; 41378 41379 for (i = 0; i < currentGop.length; i++) { 41380 currentFrame = currentGop[i]; 41381 sample = sampleForFrame(currentFrame, dataOffset); 41382 dataOffset += sample.size; 41383 samples.push(sample); 41384 } 41385 } 41386 41387 return samples; 41388 }; // generate the track's raw mdat data from an array of gops 41389 41390 41391 var concatenateNalData = function concatenateNalData(gops) { 41392 var h, 41393 i, 41394 j, 41395 currentGop, 41396 currentFrame, 41397 currentNal, 41398 dataOffset = 0, 41399 nalsByteLength = gops.byteLength, 41400 numberOfNals = gops.nalCount, 41401 totalByteLength = nalsByteLength + 4 * numberOfNals, 41402 data = new Uint8Array(totalByteLength), 41403 view = new DataView(data.buffer); // For each Gop.. 41404 41405 for (h = 0; h < gops.length; h++) { 41406 currentGop = gops[h]; // For each Frame.. 41407 41408 for (i = 0; i < currentGop.length; i++) { 41409 currentFrame = currentGop[i]; // For each NAL.. 41410 41411 for (j = 0; j < currentFrame.length; j++) { 41412 currentNal = currentFrame[j]; 41413 view.setUint32(dataOffset, currentNal.data.byteLength); 41414 dataOffset += 4; 41415 data.set(currentNal.data, dataOffset); 41416 dataOffset += currentNal.data.byteLength; 41417 } 41418 } 41419 } 41420 41421 return data; 41422 }; // generate the track's sample table from a frame 41423 41424 41425 var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) { 41426 var sample, 41427 dataOffset = baseDataOffset || 0, 41428 samples = []; 41429 sample = sampleForFrame(frame, dataOffset); 41430 samples.push(sample); 41431 return samples; 41432 }; // generate the track's raw mdat data from a frame 41433 41434 41435 var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) { 41436 var i, 41437 currentNal, 41438 dataOffset = 0, 41439 nalsByteLength = frame.byteLength, 41440 numberOfNals = frame.length, 41441 totalByteLength = nalsByteLength + 4 * numberOfNals, 41442 data = new Uint8Array(totalByteLength), 41443 view = new DataView(data.buffer); // For each NAL.. 41444 41445 for (i = 0; i < frame.length; i++) { 41446 currentNal = frame[i]; 41447 view.setUint32(dataOffset, currentNal.data.byteLength); 41448 dataOffset += 4; 41449 data.set(currentNal.data, dataOffset); 41450 dataOffset += currentNal.data.byteLength; 41451 } 41452 41453 return data; 41454 }; 41455 41456 var frameUtils = { 41457 groupNalsIntoFrames: groupNalsIntoFrames, 41458 groupFramesIntoGops: groupFramesIntoGops, 41459 extendFirstKeyFrame: extendFirstKeyFrame, 41460 generateSampleTable: generateSampleTable, 41461 concatenateNalData: concatenateNalData, 41462 generateSampleTableForFrame: generateSampleTableForFrame, 41463 concatenateNalDataForFrame: concatenateNalDataForFrame 41464 }; 41465 /** 41466 * mux.js 41467 * 41468 * Copyright (c) Brightcove 41469 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 41470 */ 41471 41472 var highPrefix = [33, 16, 5, 32, 164, 27]; 41473 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 41474 41475 var zeroFill = function zeroFill(count) { 41476 var a = []; 41477 41478 while (count--) { 41479 a.push(0); 41480 } 41481 41482 return a; 41483 }; 41484 41485 var makeTable = function makeTable(metaTable) { 41486 return Object.keys(metaTable).reduce(function (obj, key) { 41487 obj[key] = new Uint8Array(metaTable[key].reduce(function (arr, part) { 41488 return arr.concat(part); 41489 }, [])); 41490 return obj;
vendor: 8,797 bytes, lines 41491-41718
41491 }, {}); 41492 }; // Frames-of-silence to use for filling in missing AAC frames 41493 41494 41495 var coneOfSilence = { 41496 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 41497 88200: [highPrefix, [231], zeroFill(170), [56]], 41498 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 41499 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 41500 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 41501 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 41502 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 41503 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 41504 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]], 41505 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]], 41506 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 41507 }; 41508 var silence = makeTable(coneOfSilence); 41509 /** 41510 * mux.js 41511 * 41512 * Copyright (c) Brightcove 41513 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 41514 */ 41515 41516 var ONE_SECOND_IN_TS = 90000, 41517 // 90kHz clock 41518 secondsToVideoTs, 41519 secondsToAudioTs, 41520 videoTsToSeconds, 41521 audioTsToSeconds, 41522 audioTsToVideoTs, 41523 videoTsToAudioTs, 41524 metadataTsToSeconds; 41525 41526 secondsToVideoTs = function secondsToVideoTs(seconds) { 41527 return seconds * ONE_SECOND_IN_TS; 41528 }; 41529 41530 secondsToAudioTs = function secondsToAudioTs(seconds, sampleRate) { 41531 return seconds * sampleRate; 41532 }; 41533 41534 videoTsToSeconds = function videoTsToSeconds(timestamp) { 41535 return timestamp / ONE_SECOND_IN_TS; 41536 }; 41537 41538 audioTsToSeconds = function audioTsToSeconds(timestamp, sampleRate) { 41539 return timestamp / sampleRate; 41540 }; 41541 41542 audioTsToVideoTs = function audioTsToVideoTs(timestamp, sampleRate) { 41543 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 41544 }; 41545 41546 videoTsToAudioTs = function videoTsToAudioTs(timestamp, sampleRate) { 41547 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 41548 }; 41549 /** 41550 * Adjust ID3 tag or caption timing information by the timeline pts values 41551 * (if keepOriginalTimestamps is false) and convert to seconds 41552 */ 41553 41554 41555 metadataTsToSeconds = function metadataTsToSeconds(timestamp, timelineStartPts, keepOriginalTimestamps) { 41556 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts); 41557 }; 41558 41559 var clock = { 41560 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS, 41561 secondsToVideoTs: secondsToVideoTs, 41562 secondsToAudioTs: secondsToAudioTs, 41563 videoTsToSeconds: videoTsToSeconds, 41564 audioTsToSeconds: audioTsToSeconds, 41565 audioTsToVideoTs: audioTsToVideoTs, 41566 videoTsToAudioTs: videoTsToAudioTs, 41567 metadataTsToSeconds: metadataTsToSeconds 41568 }; 41569 /** 41570 * mux.js 41571 * 41572 * Copyright (c) Brightcove 41573 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 41574 */ 41575 41576 /** 41577 * Sum the `byteLength` properties of the data in each AAC frame 41578 */ 41579 41580 var sumFrameByteLengths = function sumFrameByteLengths(array) { 41581 var i, 41582 currentObj, 41583 sum = 0; // sum the byteLength's all each nal unit in the frame 41584 41585 for (i = 0; i < array.length; i++) { 41586 currentObj = array[i]; 41587 sum += currentObj.data.byteLength; 41588 } 41589 41590 return sum; 41591 }; // Possibly pad (prefix) the audio track with silence if appending this track 41592 // would lead to the introduction of a gap in the audio buffer 41593 41594 41595 var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) { 41596 var baseMediaDecodeTimeTs, 41597 frameDuration = 0, 41598 audioGapDuration = 0, 41599 audioFillFrameCount = 0, 41600 audioFillDuration = 0, 41601 silentFrame, 41602 i, 41603 firstFrame; 41604 41605 if (!frames.length) { 41606 return; 41607 } 41608 41609 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills 41610 41611 frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024)); 41612 41613 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 41614 // insert the shortest possible amount (audio gap or audio to video gap) 41615 audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap 41616 41617 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 41618 audioFillDuration = audioFillFrameCount * frameDuration; 41619 } // don't attempt to fill gaps smaller than a single frame or larger 41620 // than a half second 41621 41622 41623 if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) { 41624 return; 41625 } 41626 41627 silentFrame = silence[track.samplerate]; 41628 41629 if (!silentFrame) { 41630 // we don't have a silent frame pregenerated for the sample rate, so use a frame 41631 // from the content instead 41632 silentFrame = frames[0].data; 41633 } 41634 41635 for (i = 0; i < audioFillFrameCount; i++) { 41636 firstFrame = frames[0]; 41637 frames.splice(0, 0, { 41638 data: silentFrame, 41639 dts: firstFrame.dts - frameDuration, 41640 pts: firstFrame.pts - frameDuration 41641 }); 41642 } 41643 41644 track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 41645 }; // If the audio segment extends before the earliest allowed dts 41646 // value, remove AAC frames until starts at or after the earliest 41647 // allowed DTS so that we don't end up with a negative baseMedia- 41648 // DecodeTime for the audio track 41649 41650 41651 var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) { 41652 if (track.minSegmentDts >= earliestAllowedDts) { 41653 return adtsFrames; 41654 } // We will need to recalculate the earliest segment Dts 41655 41656 41657 track.minSegmentDts = Infinity; 41658 return adtsFrames.filter(function (currentFrame) { 41659 // If this is an allowed frame, keep it and record it's Dts 41660 if (currentFrame.dts >= earliestAllowedDts) { 41661 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 41662 track.minSegmentPts = track.minSegmentDts; 41663 return true; 41664 } // Otherwise, discard it 41665 41666 41667 return false; 41668 }); 41669 }; // generate the track's raw mdat data from an array of frames 41670 41671 41672 var generateSampleTable$1 = function generateSampleTable(frames) { 41673 var i, 41674 currentFrame, 41675 samples = []; 41676 41677 for (i = 0; i < frames.length; i++) { 41678 currentFrame = frames[i]; 41679 samples.push({ 41680 size: currentFrame.data.byteLength, 41681 duration: 1024 // For AAC audio, all samples contain 1024 samples 41682 41683 }); 41684 } 41685 41686 return samples; 41687 }; // generate the track's sample table from an array of frames 41688 41689 41690 var concatenateFrameData = function concatenateFrameData(frames) { 41691 var i, 41692 currentFrame, 41693 dataOffset = 0, 41694 data = new Uint8Array(sumFrameByteLengths(frames)); 41695 41696 for (i = 0; i < frames.length; i++) { 41697 currentFrame = frames[i]; 41698 data.set(currentFrame.data, dataOffset); 41699 dataOffset += currentFrame.data.byteLength; 41700 } 41701 41702 return data; 41703 }; 41704 41705 var audioFrameUtils = { 41706 prefixWithSilence: prefixWithSilence, 41707 trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts, 41708 generateSampleTable: generateSampleTable$1, 41709 concatenateFrameData: concatenateFrameData 41710 }; 41711 /** 41712 * mux.js 41713 * 41714 * Copyright (c) Brightcove 41715 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 41716 */ 41717 41718 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS;
vendor: 12,931 bytes, lines 41719-42077
41719 /** 41720 * Store information about the start and end of the track and the 41721 * duration for each frame/sample we process in order to calculate 41722 * the baseMediaDecodeTime 41723 */ 41724 41725 var collectDtsInfo = function collectDtsInfo(track, data) { 41726 if (typeof data.pts === 'number') { 41727 if (track.timelineStartInfo.pts === undefined) { 41728 track.timelineStartInfo.pts = data.pts; 41729 } 41730 41731 if (track.minSegmentPts === undefined) { 41732 track.minSegmentPts = data.pts; 41733 } else { 41734 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 41735 } 41736 41737 if (track.maxSegmentPts === undefined) { 41738 track.maxSegmentPts = data.pts; 41739 } else { 41740 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 41741 } 41742 } 41743 41744 if (typeof data.dts === 'number') { 41745 if (track.timelineStartInfo.dts === undefined) { 41746 track.timelineStartInfo.dts = data.dts; 41747 } 41748 41749 if (track.minSegmentDts === undefined) { 41750 track.minSegmentDts = data.dts; 41751 } else { 41752 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 41753 } 41754 41755 if (track.maxSegmentDts === undefined) { 41756 track.maxSegmentDts = data.dts; 41757 } else { 41758 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 41759 } 41760 } 41761 }; 41762 /** 41763 * Clear values used to calculate the baseMediaDecodeTime between 41764 * tracks 41765 */ 41766 41767 41768 var clearDtsInfo = function clearDtsInfo(track) { 41769 delete track.minSegmentDts; 41770 delete track.maxSegmentDts; 41771 delete track.minSegmentPts; 41772 delete track.maxSegmentPts; 41773 }; 41774 /** 41775 * Calculate the track's baseMediaDecodeTime based on the earliest 41776 * DTS the transmuxer has ever seen and the minimum DTS for the 41777 * current track 41778 * @param track {object} track metadata configuration 41779 * @param keepOriginalTimestamps {boolean} If true, keep the timestamps 41780 * in the source; false to adjust the first segment to start at 0. 41781 */ 41782 41783 41784 var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) { 41785 var baseMediaDecodeTime, 41786 scale, 41787 minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero. 41788 41789 if (!keepOriginalTimestamps) { 41790 minSegmentDts -= track.timelineStartInfo.dts; 41791 } // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 41792 // we want the start of the first segment to be placed 41793 41794 41795 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first 41796 41797 baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative 41798 41799 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 41800 41801 if (track.type === 'audio') { 41802 // Audio has a different clock equal to the sampling_rate so we need to 41803 // scale the PTS values into the clock rate of the track 41804 scale = track.samplerate / ONE_SECOND_IN_TS$1; 41805 baseMediaDecodeTime *= scale; 41806 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 41807 } 41808 41809 return baseMediaDecodeTime; 41810 }; 41811 41812 var trackDecodeInfo = { 41813 clearDtsInfo: clearDtsInfo, 41814 calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime, 41815 collectDtsInfo: collectDtsInfo 41816 }; 41817 /** 41818 * mux.js 41819 * 41820 * Copyright (c) Brightcove 41821 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 41822 * 41823 * Reads in-band caption information from a video elementary 41824 * stream. Captions must follow the CEA-708 standard for injection 41825 * into an MPEG-2 transport streams. 41826 * @see https://en.wikipedia.org/wiki/CEA-708 41827 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 41828 */ 41829 // Supplemental enhancement information (SEI) NAL units have a 41830 // payload type field to indicate how they are to be 41831 // interpreted. CEAS-708 caption content is always transmitted with 41832 // payload type 0x04. 41833 41834 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 41835 RBSP_TRAILING_BITS = 128; 41836 /** 41837 * Parse a supplemental enhancement information (SEI) NAL unit. 41838 * Stops parsing once a message of type ITU T T35 has been found. 41839 * 41840 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 41841 * @return {object} the parsed SEI payload 41842 * @see Rec. ITU-T H.264, 7.3.2.3.1 41843 */ 41844 41845 var parseSei = function parseSei(bytes) { 41846 var i = 0, 41847 result = { 41848 payloadType: -1, 41849 payloadSize: 0 41850 }, 41851 payloadType = 0, 41852 payloadSize = 0; // go through the sei_rbsp parsing each each individual sei_message 41853 41854 while (i < bytes.byteLength) { 41855 // stop once we have hit the end of the sei_rbsp 41856 if (bytes[i] === RBSP_TRAILING_BITS) { 41857 break; 41858 } // Parse payload type 41859 41860 41861 while (bytes[i] === 0xFF) { 41862 payloadType += 255; 41863 i++; 41864 } 41865 41866 payloadType += bytes[i++]; // Parse payload size 41867 41868 while (bytes[i] === 0xFF) { 41869 payloadSize += 255; 41870 i++; 41871 } 41872 41873 payloadSize += bytes[i++]; // this sei_message is a 608/708 caption so save it and break 41874 // there can only ever be one caption message in a frame's sei 41875 41876 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 41877 result.payloadType = payloadType; 41878 result.payloadSize = payloadSize; 41879 result.payload = bytes.subarray(i, i + payloadSize); 41880 break; 41881 } // skip the payload and parse the next message 41882 41883 41884 i += payloadSize; 41885 payloadType = 0; 41886 payloadSize = 0; 41887 } 41888 41889 return result; 41890 }; // see ANSI/SCTE 128-1 (2013), section 8.1 41891 41892 41893 var parseUserData = function parseUserData(sei) { 41894 // itu_t_t35_contry_code must be 181 (United States) for 41895 // captions 41896 if (sei.payload[0] !== 181) { 41897 return null; 41898 } // itu_t_t35_provider_code should be 49 (ATSC) for captions 41899 41900 41901 if ((sei.payload[1] << 8 | sei.payload[2]) !== 49) { 41902 return null; 41903 } // the user_identifier should be "GA94" to indicate ATSC1 data 41904 41905 41906 if (String.fromCharCode(sei.payload[3], sei.payload[4], sei.payload[5], sei.payload[6]) !== 'GA94') { 41907 return null; 41908 } // finally, user_data_type_code should be 0x03 for caption data 41909 41910 41911 if (sei.payload[7] !== 0x03) { 41912 return null; 41913 } // return the user_data_type_structure and strip the trailing 41914 // marker bits 41915 41916 41917 return sei.payload.subarray(8, sei.payload.length - 1); 41918 }; // see CEA-708-D, section 4.4 41919 41920 41921 var parseCaptionPackets = function parseCaptionPackets(pts, userData) { 41922 var results = [], 41923 i, 41924 count, 41925 offset, 41926 data; // if this is just filler, return immediately 41927 41928 if (!(userData[0] & 0x40)) { 41929 return results; 41930 } // parse out the cc_data_1 and cc_data_2 fields 41931 41932 41933 count = userData[0] & 0x1f; 41934 41935 for (i = 0; i < count; i++) { 41936 offset = i * 3; 41937 data = { 41938 type: userData[offset + 2] & 0x03, 41939 pts: pts 41940 }; // capture cc data when cc_valid is 1 41941 41942 if (userData[offset + 2] & 0x04) { 41943 data.ccData = userData[offset + 3] << 8 | userData[offset + 4]; 41944 results.push(data); 41945 } 41946 } 41947 41948 return results; 41949 }; 41950 41951 var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 41952 var length = data.byteLength, 41953 emulationPreventionBytesPositions = [], 41954 i = 1, 41955 newLength, 41956 newData; // Find all `Emulation Prevention Bytes` 41957 41958 while (i < length - 2) { 41959 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 41960 emulationPreventionBytesPositions.push(i + 2); 41961 i += 2; 41962 } else { 41963 i++; 41964 } 41965 } // If no Emulation Prevention Bytes were found just return the original 41966 // array 41967 41968 41969 if (emulationPreventionBytesPositions.length === 0) { 41970 return data; 41971 } // Create a new array to hold the NAL unit data 41972 41973 41974 newLength = length - emulationPreventionBytesPositions.length; 41975 newData = new Uint8Array(newLength); 41976 var sourceIndex = 0; 41977 41978 for (i = 0; i < newLength; sourceIndex++, i++) { 41979 if (sourceIndex === emulationPreventionBytesPositions[0]) { 41980 // Skip this byte 41981 sourceIndex++; // Remove this position index 41982 41983 emulationPreventionBytesPositions.shift(); 41984 } 41985 41986 newData[i] = data[sourceIndex]; 41987 } 41988 41989 return newData; 41990 }; // exports 41991 41992 41993 var captionPacketParser = { 41994 parseSei: parseSei, 41995 parseUserData: parseUserData, 41996 parseCaptionPackets: parseCaptionPackets, 41997 discardEmulationPreventionBytes: discardEmulationPreventionBytes, 41998 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35 41999 }; // ----------------- 42000 // Link To Transport 42001 // ----------------- 42002 42003 var CaptionStream = function CaptionStream() { 42004 CaptionStream.prototype.init.call(this); 42005 this.captionPackets_ = []; 42006 this.ccStreams_ = [new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 42007 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 42008 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 42009 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 42010 ]; 42011 this.reset(); // forward data and done events from CCs to this CaptionStream 42012 42013 this.ccStreams_.forEach(function (cc) { 42014 cc.on('data', this.trigger.bind(this, 'data')); 42015 cc.on('partialdone', this.trigger.bind(this, 'partialdone')); 42016 cc.on('done', this.trigger.bind(this, 'done')); 42017 }, this); 42018 }; 42019 42020 CaptionStream.prototype = new stream(); 42021 42022 CaptionStream.prototype.push = function (event) { 42023 var sei, userData, newCaptionPackets; // only examine SEI NALs 42024 42025 if (event.nalUnitType !== 'sei_rbsp') { 42026 return; 42027 } // parse the sei 42028 42029 42030 sei = captionPacketParser.parseSei(event.escapedRBSP); // ignore everything but user_data_registered_itu_t_t35 42031 42032 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) { 42033 return; 42034 } // parse out the user data payload 42035 42036 42037 userData = captionPacketParser.parseUserData(sei); // ignore unrecognized userData 42038 42039 if (!userData) { 42040 return; 42041 } // Sometimes, the same segment # will be downloaded twice. To stop the 42042 // caption data from being processed twice, we track the latest dts we've 42043 // received and ignore everything with a dts before that. However, since 42044 // data for a specific dts can be split across packets on either side of 42045 // a segment boundary, we need to make sure we *don't* ignore the packets 42046 // from the *next* segment that have dts === this.latestDts_. By constantly 42047 // tracking the number of packets received with dts === this.latestDts_, we 42048 // know how many should be ignored once we start receiving duplicates. 42049 42050 42051 if (event.dts < this.latestDts_) { 42052 // We've started getting older data, so set the flag. 42053 this.ignoreNextEqualDts_ = true; 42054 return; 42055 } else if (event.dts === this.latestDts_ && this.ignoreNextEqualDts_) { 42056 this.numSameDts_--; 42057 42058 if (!this.numSameDts_) { 42059 // We've received the last duplicate packet, time to start processing again 42060 this.ignoreNextEqualDts_ = false; 42061 } 42062 42063 return; 42064 } // parse out CC data packets and save them for later 42065 42066 42067 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData); 42068 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets); 42069 42070 if (this.latestDts_ !== event.dts) { 42071 this.numSameDts_ = 0; 42072 } 42073 42074 this.numSameDts_++; 42075 this.latestDts_ = event.dts; 42076 }; 42077
vendor: 5,244 bytes, lines 42078-42211
42078 CaptionStream.prototype.flushCCStreams = function (flushType) { 42079 this.ccStreams_.forEach(function (cc) { 42080 return flushType === 'flush' ? cc.flush() : cc.partialFlush(); 42081 }, this); 42082 }; 42083 42084 CaptionStream.prototype.flushStream = function (flushType) { 42085 // make sure we actually parsed captions before proceeding 42086 if (!this.captionPackets_.length) { 42087 this.flushCCStreams(flushType); 42088 return; 42089 } // In Chrome, the Array#sort function is not stable so add a 42090 // presortIndex that we can use to ensure we get a stable-sort 42091 42092 42093 this.captionPackets_.forEach(function (elem, idx) { 42094 elem.presortIndex = idx; 42095 }); // sort caption byte-pairs based on their PTS values 42096 42097 this.captionPackets_.sort(function (a, b) { 42098 if (a.pts === b.pts) { 42099 return a.presortIndex - b.presortIndex; 42100 } 42101 42102 return a.pts - b.pts; 42103 }); 42104 this.captionPackets_.forEach(function (packet) { 42105 if (packet.type < 2) { 42106 // Dispatch packet to the right Cea608Stream 42107 this.dispatchCea608Packet(packet); 42108 } // this is where an 'else' would go for a dispatching packets 42109 // to a theoretical Cea708Stream that handles SERVICEn data 42110 42111 }, this); 42112 this.captionPackets_.length = 0; 42113 this.flushCCStreams(flushType); 42114 }; 42115 42116 CaptionStream.prototype.flush = function () { 42117 return this.flushStream('flush'); 42118 }; // Only called if handling partial data 42119 42120 42121 CaptionStream.prototype.partialFlush = function () { 42122 return this.flushStream('partialFlush'); 42123 }; 42124 42125 CaptionStream.prototype.reset = function () { 42126 this.latestDts_ = null; 42127 this.ignoreNextEqualDts_ = false; 42128 this.numSameDts_ = 0; 42129 this.activeCea608Channel_ = [null, null]; 42130 this.ccStreams_.forEach(function (ccStream) { 42131 ccStream.reset(); 42132 }); 42133 }; // From the CEA-608 spec: 42134 42135 /* 42136 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed 42137 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is 42138 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair 42139 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the 42140 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD) 42141 * to switch to captioning or Text. 42142 */ 42143 // With that in mind, we ignore any data between an XDS control code and a 42144 // subsequent closed-captioning control code. 42145 42146 42147 CaptionStream.prototype.dispatchCea608Packet = function (packet) { 42148 // NOTE: packet.type is the CEA608 field 42149 if (this.setsTextOrXDSActive(packet)) { 42150 this.activeCea608Channel_[packet.type] = null; 42151 } else if (this.setsChannel1Active(packet)) { 42152 this.activeCea608Channel_[packet.type] = 0; 42153 } else if (this.setsChannel2Active(packet)) { 42154 this.activeCea608Channel_[packet.type] = 1; 42155 } 42156 42157 if (this.activeCea608Channel_[packet.type] === null) { 42158 // If we haven't received anything to set the active channel, or the 42159 // packets are Text/XDS data, discard the data; we don't want jumbled 42160 // captions 42161 return; 42162 } 42163 42164 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet); 42165 }; 42166 42167 CaptionStream.prototype.setsChannel1Active = function (packet) { 42168 return (packet.ccData & 0x7800) === 0x1000; 42169 }; 42170 42171 CaptionStream.prototype.setsChannel2Active = function (packet) { 42172 return (packet.ccData & 0x7800) === 0x1800; 42173 }; 42174 42175 CaptionStream.prototype.setsTextOrXDSActive = function (packet) { 42176 return (packet.ccData & 0x7100) === 0x0100 || (packet.ccData & 0x78fe) === 0x102a || (packet.ccData & 0x78fe) === 0x182a; 42177 }; // ---------------------- 42178 // Session to Application 42179 // ---------------------- 42180 // This hash maps non-ASCII, special, and extended character codes to their 42181 // proper Unicode equivalent. The first keys that are only a single byte 42182 // are the non-standard ASCII characters, which simply map the CEA608 byte 42183 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608 42184 // character codes, but have their MSB bitmasked with 0x03 so that a lookup 42185 // can be performed regardless of the field and data channel on which the 42186 // character code was received. 42187 42188 42189 var CHARACTER_TRANSLATION = { 42190 0x2a: 0xe1, 42191 // á 42192 0x5c: 0xe9, 42193 // é 42194 0x5e: 0xed, 42195 // à 42196 0x5f: 0xf3, 42197 // ó 42198 0x60: 0xfa, 42199 // ú 42200 0x7b: 0xe7, 42201 // ç 42202 0x7c: 0xf7, 42203 // ÷ 42204 0x7d: 0xd1, 42205 // à 42206 0x7e: 0xf1, 42207 // ñ 42208 0x7f: 0x2588, 42209 // â 42210 0x0130: 0xae, 42211 // ®
42212 0x0131: 0xb0, 42213 // ° 42214 0x0132: 0xbd, 42215 // ½ 42216 0x0133: 0xbf, 42217 // ¿ 42218 0x0134: 0x2122, 42219 // ⢠42220 0x0135: 0xa2, 42221 // ¢ 42222 0x0136: 0xa3, 42223 // £ 42224 0x0137: 0x266a, 42225 // ⪠42226 0x0138: 0xe0, 42227 // à 42228 0x0139: 0xa0, 42229 // 42230 0x013a: 0xe8, 42231 // è 42232 0x013b: 0xe2, 42233 // â 42234 0x013c: 0xea, 42235 // ê 42236 0x013d: 0xee, 42237 // î 42238 0x013e: 0xf4, 42239 // ô 42240 0x013f: 0xfb, 42241 // û 42242 0x0220: 0xc1, 42243 // à 42244 0x0221: 0xc9, 42245 // à 42246 0x0222: 0xd3, 42247 // à 42248 0x0223: 0xda, 42249 // à 42250 0x0224: 0xdc, 42251 // à 42252 0x0225: 0xfc, 42253 // ü 42254 0x0226: 0x2018, 42255 // â 42256 0x0227: 0xa1, 42257 // ¡ 42258 0x0228: 0x2a, 42259 // * 42260 0x0229: 0x27, 42261 // ' 42262 0x022a: 0x2014, 42263 // â 42264 0x022b: 0xa9, 42265 // © 42266 0x022c: 0x2120, 42267 // â 42268 0x022d: 0x2022, 42269 // ⢠42270 0x022e: 0x201c, 42271 // â 42272 0x022f: 0x201d, 42273 // â 42274 0x0230: 0xc0, 42275 // à 42276 0x0231: 0xc2, 42277 // à 42278 0x0232: 0xc7, 42279 // à 42280 0x0233: 0xc8, 42281 // à 42282 0x0234: 0xca, 42283 // à 42284 0x0235: 0xcb, 42285 // à 42286 0x0236: 0xeb, 42287 // ë 42288 0x0237: 0xce, 42289 // à 42290 0x0238: 0xcf, 42291 // à 42292 0x0239: 0xef, 42293 // ï 42294 0x023a: 0xd4, 42295 // à 42296 0x023b: 0xd9, 42297 // à 42298 0x023c: 0xf9, 42299 // ù 42300 0x023d: 0xdb, 42301 // à 42302 0x023e: 0xab, 42303 // « 42304 0x023f: 0xbb, 42305 // » 42306 0x0320: 0xc3, 42307 // à 42308 0x0321: 0xe3, 42309 // ã 42310 0x0322: 0xcd, 42311 // à 42312 0x0323: 0xcc, 42313 // à 42314 0x0324: 0xec, 42315 // ì 42316 0x0325: 0xd2, 42317 // à 42318 0x0326: 0xf2, 42319 // ò 42320 0x0327: 0xd5, 42321 // à 42322 0x0328: 0xf5, 42323 // õ 42324 0x0329: 0x7b, 42325 // { 42326 0x032a: 0x7d, 42327 // } 42328 0x032b: 0x5c, 42329 // \ 42330 0x032c: 0x5e, 42331 // ^ 42332 0x032d: 0x5f, 42333 // _ 42334 0x032e: 0x7c, 42335 // | 42336 0x032f: 0x7e, 42337 // ~ 42338 0x0330: 0xc4, 42339 // à 42340 0x0331: 0xe4,
vendor: 20,174 bytes, lines 42341-42840
42341 // ä 42342 0x0332: 0xd6, 42343 // à 42344 0x0333: 0xf6, 42345 // ö 42346 0x0334: 0xdf, 42347 // à 42348 0x0335: 0xa5, 42349 // Â¥ 42350 0x0336: 0xa4, 42351 // ¤ 42352 0x0337: 0x2502, 42353 // â 42354 0x0338: 0xc5, 42355 // à 42356 0x0339: 0xe5, 42357 // Ã¥ 42358 0x033a: 0xd8, 42359 // à 42360 0x033b: 0xf8, 42361 // ø 42362 0x033c: 0x250c, 42363 // â 42364 0x033d: 0x2510, 42365 // â 42366 0x033e: 0x2514, 42367 // â 42368 0x033f: 0x2518 // â 42369 42370 }; 42371 42372 var getCharFromCode = function getCharFromCode(code) { 42373 if (code === null) { 42374 return ''; 42375 } 42376 42377 code = CHARACTER_TRANSLATION[code] || code; 42378 return String.fromCharCode(code); 42379 }; // the index of the last row in a CEA-608 display buffer 42380 42381 42382 var BOTTOM_ROW = 14; // This array is used for mapping PACs -> row #, since there's no way of 42383 // getting it through bit logic. 42384 42385 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 42386 // cells. The "bottom" row is the last element in the outer array. 42387 42388 var createDisplayBuffer = function createDisplayBuffer() { 42389 var result = [], 42390 i = BOTTOM_ROW + 1; 42391 42392 while (i--) { 42393 result.push(''); 42394 } 42395 42396 return result; 42397 }; 42398 42399 var Cea608Stream = function Cea608Stream(field, dataChannel) { 42400 Cea608Stream.prototype.init.call(this); 42401 this.field_ = field || 0; 42402 this.dataChannel_ = dataChannel || 0; 42403 this.name_ = 'CC' + ((this.field_ << 1 | this.dataChannel_) + 1); 42404 this.setConstants(); 42405 this.reset(); 42406 42407 this.push = function (packet) { 42408 var data, swap, char0, char1, text; // remove the parity bits 42409 42410 data = packet.ccData & 0x7f7f; // ignore duplicate control codes; the spec demands they're sent twice 42411 42412 if (data === this.lastControlCode_) { 42413 this.lastControlCode_ = null; 42414 return; 42415 } // Store control codes 42416 42417 42418 if ((data & 0xf000) === 0x1000) { 42419 this.lastControlCode_ = data; 42420 } else if (data !== this.PADDING_) { 42421 this.lastControlCode_ = null; 42422 } 42423 42424 char0 = data >>> 8; 42425 char1 = data & 0xff; 42426 42427 if (data === this.PADDING_) { 42428 return; 42429 } else if (data === this.RESUME_CAPTION_LOADING_) { 42430 this.mode_ = 'popOn'; 42431 } else if (data === this.END_OF_CAPTION_) { 42432 // If an EOC is received while in paint-on mode, the displayed caption 42433 // text should be swapped to non-displayed memory as if it was a pop-on 42434 // caption. Because of that, we should explicitly switch back to pop-on 42435 // mode 42436 this.mode_ = 'popOn'; 42437 this.clearFormatting(packet.pts); // if a caption was being displayed, it's gone now 42438 42439 this.flushDisplayed(packet.pts); // flip memory 42440 42441 swap = this.displayed_; 42442 this.displayed_ = this.nonDisplayed_; 42443 this.nonDisplayed_ = swap; // start measuring the time to display the caption 42444 42445 this.startPts_ = packet.pts; 42446 } else if (data === this.ROLL_UP_2_ROWS_) { 42447 this.rollUpRows_ = 2; 42448 this.setRollUp(packet.pts); 42449 } else if (data === this.ROLL_UP_3_ROWS_) { 42450 this.rollUpRows_ = 3; 42451 this.setRollUp(packet.pts); 42452 } else if (data === this.ROLL_UP_4_ROWS_) { 42453 this.rollUpRows_ = 4; 42454 this.setRollUp(packet.pts); 42455 } else if (data === this.CARRIAGE_RETURN_) { 42456 this.clearFormatting(packet.pts); 42457 this.flushDisplayed(packet.pts); 42458 this.shiftRowsUp_(); 42459 this.startPts_ = packet.pts; 42460 } else if (data === this.BACKSPACE_) { 42461 if (this.mode_ === 'popOn') { 42462 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 42463 } else { 42464 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 42465 } 42466 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 42467 this.flushDisplayed(packet.pts); 42468 this.displayed_ = createDisplayBuffer(); 42469 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 42470 this.nonDisplayed_ = createDisplayBuffer(); 42471 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 42472 if (this.mode_ !== 'paintOn') { 42473 // NOTE: This should be removed when proper caption positioning is 42474 // implemented 42475 this.flushDisplayed(packet.pts); 42476 this.displayed_ = createDisplayBuffer(); 42477 } 42478 42479 this.mode_ = 'paintOn'; 42480 this.startPts_ = packet.pts; // Append special characters to caption text 42481 } else if (this.isSpecialCharacter(char0, char1)) { 42482 // Bitmask char0 so that we can apply character transformations 42483 // regardless of field and data channel. 42484 // Then byte-shift to the left and OR with char1 so we can pass the 42485 // entire character code to `getCharFromCode`. 42486 char0 = (char0 & 0x03) << 8; 42487 text = getCharFromCode(char0 | char1); 42488 this[this.mode_](packet.pts, text); 42489 this.column_++; // Append extended characters to caption text 42490 } else if (this.isExtCharacter(char0, char1)) { 42491 // Extended characters always follow their "non-extended" equivalents. 42492 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 42493 // decoders are supposed to drop the "è", while compliant decoders 42494 // backspace the "e" and insert "è". 42495 // Delete the previous character 42496 if (this.mode_ === 'popOn') { 42497 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 42498 } else { 42499 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1); 42500 } // Bitmask char0 so that we can apply character transformations 42501 // regardless of field and data channel. 42502 // Then byte-shift to the left and OR with char1 so we can pass the 42503 // entire character code to `getCharFromCode`. 42504 42505 42506 char0 = (char0 & 0x03) << 8; 42507 text = getCharFromCode(char0 | char1); 42508 this[this.mode_](packet.pts, text); 42509 this.column_++; // Process mid-row codes 42510 } else if (this.isMidRowCode(char0, char1)) { 42511 // Attributes are not additive, so clear all formatting 42512 this.clearFormatting(packet.pts); // According to the standard, mid-row codes 42513 // should be replaced with spaces, so add one now 42514 42515 this[this.mode_](packet.pts, ' '); 42516 this.column_++; 42517 42518 if ((char1 & 0xe) === 0xe) { 42519 this.addFormatting(packet.pts, ['i']); 42520 } 42521 42522 if ((char1 & 0x1) === 0x1) { 42523 this.addFormatting(packet.pts, ['u']); 42524 } // Detect offset control codes and adjust cursor 42525 42526 } else if (this.isOffsetControlCode(char0, char1)) { 42527 // Cursor position is set by indent PAC (see below) in 4-column 42528 // increments, with an additional offset code of 1-3 to reach any 42529 // of the 32 columns specified by CEA-608. So all we need to do 42530 // here is increment the column cursor by the given offset. 42531 this.column_ += char1 & 0x03; // Detect PACs (Preamble Address Codes) 42532 } else if (this.isPAC(char0, char1)) { 42533 // There's no logic for PAC -> row mapping, so we have to just 42534 // find the row code in an array and use its index :( 42535 var row = ROWS.indexOf(data & 0x1f20); // Configure the caption window if we're in roll-up mode 42536 42537 if (this.mode_ === 'rollUp') { 42538 // This implies that the base row is incorrectly set. 42539 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number 42540 // of roll-up rows set. 42541 if (row - this.rollUpRows_ + 1 < 0) { 42542 row = this.rollUpRows_ - 1; 42543 } 42544 42545 this.setRollUp(packet.pts, row); 42546 } 42547 42548 if (row !== this.row_) { 42549 // formatting is only persistent for current row 42550 this.clearFormatting(packet.pts); 42551 this.row_ = row; 42552 } // All PACs can apply underline, so detect and apply 42553 // (All odd-numbered second bytes set underline) 42554 42555 42556 if (char1 & 0x1 && this.formatting_.indexOf('u') === -1) { 42557 this.addFormatting(packet.pts, ['u']); 42558 } 42559 42560 if ((data & 0x10) === 0x10) { 42561 // We've got an indent level code. Each successive even number 42562 // increments the column cursor by 4, so we can get the desired 42563 // column position by bit-shifting to the right (to get n/2) 42564 // and multiplying by 4. 42565 this.column_ = ((data & 0xe) >> 1) * 4; 42566 } 42567 42568 if (this.isColorPAC(char1)) { 42569 // it's a color code, though we only support white, which 42570 // can be either normal or italicized. white italics can be 42571 // either 0x4e or 0x6e depending on the row, so we just 42572 // bitwise-and with 0xe to see if italics should be turned on 42573 if ((char1 & 0xe) === 0xe) { 42574 this.addFormatting(packet.pts, ['i']); 42575 } 42576 } // We have a normal character in char0, and possibly one in char1 42577 42578 } else if (this.isNormalChar(char0)) { 42579 if (char1 === 0x00) { 42580 char1 = null; 42581 } 42582 42583 text = getCharFromCode(char0); 42584 text += getCharFromCode(char1); 42585 this[this.mode_](packet.pts, text); 42586 this.column_ += text.length; 42587 } // finish data processing 42588 42589 }; 42590 }; 42591 42592 Cea608Stream.prototype = new stream(); // Trigger a cue point that captures the current state of the 42593 // display buffer 42594 42595 Cea608Stream.prototype.flushDisplayed = function (pts) { 42596 var content = this.displayed_ // remove spaces from the start and end of the string 42597 .map(function (row) { 42598 try { 42599 return row.trim(); 42600 } catch (e) { 42601 // Ordinarily, this shouldn't happen. However, caption 42602 // parsing errors should not throw exceptions and 42603 // break playback. 42604 // eslint-disable-next-line no-console 42605 console.error('Skipping malformed caption.'); 42606 return ''; 42607 } 42608 }) // combine all text rows to display in one cue 42609 .join('\n') // and remove blank rows from the start and end, but not the middle 42610 .replace(/^\n+|\n+$/g, ''); 42611 42612 if (content.length) { 42613 this.trigger('data', { 42614 startPts: this.startPts_, 42615 endPts: pts, 42616 text: content, 42617 stream: this.name_ 42618 }); 42619 } 42620 }; 42621 /** 42622 * Zero out the data, used for startup and on seek 42623 */ 42624 42625 42626 Cea608Stream.prototype.reset = function () { 42627 this.mode_ = 'popOn'; // When in roll-up mode, the index of the last row that will 42628 // actually display captions. If a caption is shifted to a row 42629 // with a lower index than this, it is cleared from the display 42630 // buffer 42631 42632 this.topRow_ = 0; 42633 this.startPts_ = 0; 42634 this.displayed_ = createDisplayBuffer(); 42635 this.nonDisplayed_ = createDisplayBuffer(); 42636 this.lastControlCode_ = null; // Track row and column for proper line-breaking and spacing 42637 42638 this.column_ = 0; 42639 this.row_ = BOTTOM_ROW; 42640 this.rollUpRows_ = 2; // This variable holds currently-applied formatting 42641 42642 this.formatting_ = []; 42643 }; 42644 /** 42645 * Sets up control code and related constants for this instance 42646 */ 42647 42648 42649 Cea608Stream.prototype.setConstants = function () { 42650 // The following attributes have these uses: 42651 // ext_ : char0 for mid-row codes, and the base for extended 42652 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 42653 // extended codes) 42654 // control_: char0 for control codes, except byte-shifted to the 42655 // left so that we can do this.control_ | CONTROL_CODE 42656 // offset_: char0 for tab offset codes 42657 // 42658 // It's also worth noting that control codes, and _only_ control codes, 42659 // differ between field 1 and field2. Field 2 control codes are always 42660 // their field 1 value plus 1. That's why there's the "| field" on the 42661 // control value. 42662 if (this.dataChannel_ === 0) { 42663 this.BASE_ = 0x10; 42664 this.EXT_ = 0x11; 42665 this.CONTROL_ = (0x14 | this.field_) << 8; 42666 this.OFFSET_ = 0x17; 42667 } else if (this.dataChannel_ === 1) { 42668 this.BASE_ = 0x18; 42669 this.EXT_ = 0x19; 42670 this.CONTROL_ = (0x1c | this.field_) << 8; 42671 this.OFFSET_ = 0x1f; 42672 } // Constants for the LSByte command codes recognized by Cea608Stream. This 42673 // list is not exhaustive. For a more comprehensive listing and semantics see 42674 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 42675 // Padding 42676 42677 42678 this.PADDING_ = 0x0000; // Pop-on Mode 42679 42680 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 42681 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; // Roll-up Mode 42682 42683 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 42684 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 42685 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 42686 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; // paint-on mode 42687 42688 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; // Erasure 42689 42690 this.BACKSPACE_ = this.CONTROL_ | 0x21; 42691 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 42692 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 42693 }; 42694 /** 42695 * Detects if the 2-byte packet data is a special character 42696 * 42697 * Special characters have a second byte in the range 0x30 to 0x3f, 42698 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 42699 * data channel 2). 42700 * 42701 * @param {Integer} char0 The first byte 42702 * @param {Integer} char1 The second byte 42703 * @return {Boolean} Whether the 2 bytes are an special character 42704 */ 42705 42706 42707 Cea608Stream.prototype.isSpecialCharacter = function (char0, char1) { 42708 return char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f; 42709 }; 42710 /** 42711 * Detects if the 2-byte packet data is an extended character 42712 * 42713 * Extended characters have a second byte in the range 0x20 to 0x3f, 42714 * with the first byte being 0x12 or 0x13 (for data channel 1) or 42715 * 0x1a or 0x1b (for data channel 2). 42716 * 42717 * @param {Integer} char0 The first byte 42718 * @param {Integer} char1 The second byte 42719 * @return {Boolean} Whether the 2 bytes are an extended character 42720 */ 42721 42722 42723 Cea608Stream.prototype.isExtCharacter = function (char0, char1) { 42724 return (char0 === this.EXT_ + 1 || char0 === this.EXT_ + 2) && char1 >= 0x20 && char1 <= 0x3f; 42725 }; 42726 /** 42727 * Detects if the 2-byte packet is a mid-row code 42728 * 42729 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 42730 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 42731 * channel 2). 42732 * 42733 * @param {Integer} char0 The first byte 42734 * @param {Integer} char1 The second byte 42735 * @return {Boolean} Whether the 2 bytes are a mid-row code 42736 */ 42737 42738 42739 Cea608Stream.prototype.isMidRowCode = function (char0, char1) { 42740 return char0 === this.EXT_ && char1 >= 0x20 && char1 <= 0x2f; 42741 }; 42742 /** 42743 * Detects if the 2-byte packet is an offset control code 42744 * 42745 * Offset control codes have a second byte in the range 0x21 to 0x23, 42746 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 42747 * data channel 2). 42748 * 42749 * @param {Integer} char0 The first byte 42750 * @param {Integer} char1 The second byte 42751 * @return {Boolean} Whether the 2 bytes are an offset control code 42752 */ 42753 42754 42755 Cea608Stream.prototype.isOffsetControlCode = function (char0, char1) { 42756 return char0 === this.OFFSET_ && char1 >= 0x21 && char1 <= 0x23; 42757 }; 42758 /** 42759 * Detects if the 2-byte packet is a Preamble Address Code 42760 * 42761 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 42762 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 42763 * range 0x40 to 0x7f. 42764 * 42765 * @param {Integer} char0 The first byte 42766 * @param {Integer} char1 The second byte 42767 * @return {Boolean} Whether the 2 bytes are a PAC 42768 */ 42769 42770 42771 Cea608Stream.prototype.isPAC = function (char0, char1) { 42772 return char0 >= this.BASE_ && char0 < this.BASE_ + 8 && char1 >= 0x40 && char1 <= 0x7f; 42773 }; 42774 /** 42775 * Detects if a packet's second byte is in the range of a PAC color code 42776 * 42777 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 42778 * 0x60 to 0x6f. 42779 * 42780 * @param {Integer} char1 The second byte 42781 * @return {Boolean} Whether the byte is a color PAC 42782 */ 42783 42784 42785 Cea608Stream.prototype.isColorPAC = function (char1) { 42786 return char1 >= 0x40 && char1 <= 0x4f || char1 >= 0x60 && char1 <= 0x7f; 42787 }; 42788 /** 42789 * Detects if a single byte is in the range of a normal character 42790 * 42791 * Normal text bytes are in the range 0x20 to 0x7f. 42792 * 42793 * @param {Integer} char The byte 42794 * @return {Boolean} Whether the byte is a normal character 42795 */ 42796 42797 42798 Cea608Stream.prototype.isNormalChar = function (_char) { 42799 return _char >= 0x20 && _char <= 0x7f; 42800 }; 42801 /** 42802 * Configures roll-up 42803 * 42804 * @param {Integer} pts Current PTS 42805 * @param {Integer} newBaseRow Used by PACs to slide the current window to 42806 * a new position 42807 */ 42808 42809 42810 Cea608Stream.prototype.setRollUp = function (pts, newBaseRow) { 42811 // Reset the base row to the bottom row when switching modes 42812 if (this.mode_ !== 'rollUp') { 42813 this.row_ = BOTTOM_ROW; 42814 this.mode_ = 'rollUp'; // Spec says to wipe memories when switching to roll-up 42815 42816 this.flushDisplayed(pts); 42817 this.nonDisplayed_ = createDisplayBuffer(); 42818 this.displayed_ = createDisplayBuffer(); 42819 } 42820 42821 if (newBaseRow !== undefined && newBaseRow !== this.row_) { 42822 // move currently displayed captions (up or down) to the new base row 42823 for (var i = 0; i < this.rollUpRows_; i++) { 42824 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i]; 42825 this.displayed_[this.row_ - i] = ''; 42826 } 42827 } 42828 42829 if (newBaseRow === undefined) { 42830 newBaseRow = this.row_; 42831 } 42832 42833 this.topRow_ = newBaseRow - this.rollUpRows_ + 1; 42834 }; // Adds the opening HTML tag for the passed character to the caption text, 42835 // and keeps track of it for later closing 42836 42837 42838 Cea608Stream.prototype.addFormatting = function (pts, format) { 42839 this.formatting_ = this.formatting_.concat(format); 42840 var text = format.reduce(function (text, format) {
vendor: 16,176 bytes, lines 42841-43283
42841 return text + '<' + format + '>'; 42842 }, ''); 42843 this[this.mode_](pts, text); 42844 }; // Adds HTML closing tags for current formatting to caption text and 42845 // clears remembered formatting 42846 42847 42848 Cea608Stream.prototype.clearFormatting = function (pts) { 42849 if (!this.formatting_.length) { 42850 return; 42851 } 42852 42853 var text = this.formatting_.reverse().reduce(function (text, format) { 42854 return text + '</' + format + '>'; 42855 }, ''); 42856 this.formatting_ = []; 42857 this[this.mode_](pts, text); 42858 }; // Mode Implementations 42859 42860 42861 Cea608Stream.prototype.popOn = function (pts, text) { 42862 var baseRow = this.nonDisplayed_[this.row_]; // buffer characters 42863 42864 baseRow += text; 42865 this.nonDisplayed_[this.row_] = baseRow; 42866 }; 42867 42868 Cea608Stream.prototype.rollUp = function (pts, text) { 42869 var baseRow = this.displayed_[this.row_]; 42870 baseRow += text; 42871 this.displayed_[this.row_] = baseRow; 42872 }; 42873 42874 Cea608Stream.prototype.shiftRowsUp_ = function () { 42875 var i; // clear out inactive rows 42876 42877 for (i = 0; i < this.topRow_; i++) { 42878 this.displayed_[i] = ''; 42879 } 42880 42881 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) { 42882 this.displayed_[i] = ''; 42883 } // shift displayed rows up 42884 42885 42886 for (i = this.topRow_; i < this.row_; i++) { 42887 this.displayed_[i] = this.displayed_[i + 1]; 42888 } // clear out the bottom row 42889 42890 42891 this.displayed_[this.row_] = ''; 42892 }; 42893 42894 Cea608Stream.prototype.paintOn = function (pts, text) { 42895 var baseRow = this.displayed_[this.row_]; 42896 baseRow += text; 42897 this.displayed_[this.row_] = baseRow; 42898 }; // exports 42899 42900 42901 var captionStream = { 42902 CaptionStream: CaptionStream, 42903 Cea608Stream: Cea608Stream 42904 }; 42905 /** 42906 * mux.js 42907 * 42908 * Copyright (c) Brightcove 42909 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 42910 */ 42911 42912 var streamTypes = { 42913 H264_STREAM_TYPE: 0x1B, 42914 ADTS_STREAM_TYPE: 0x0F, 42915 METADATA_STREAM_TYPE: 0x15 42916 }; 42917 var MAX_TS = 8589934592; 42918 var RO_THRESH = 4294967296; 42919 var TYPE_SHARED = 'shared'; 42920 42921 var handleRollover = function handleRollover(value, reference) { 42922 var direction = 1; 42923 42924 if (value > reference) { 42925 // If the current timestamp value is greater than our reference timestamp and we detect a 42926 // timestamp rollover, this means the roll over is happening in the opposite direction. 42927 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 42928 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 42929 // rollover point. In loading this segment, the timestamp values will be very large, 42930 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 42931 // the time stamp to be `value - 2^33`. 42932 direction = -1; 42933 } // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 42934 // cause an incorrect adjustment. 42935 42936 42937 while (Math.abs(reference - value) > RO_THRESH) { 42938 value += direction * MAX_TS; 42939 } 42940 42941 return value; 42942 }; 42943 42944 var TimestampRolloverStream = function TimestampRolloverStream(type) { 42945 var lastDTS, referenceDTS; 42946 TimestampRolloverStream.prototype.init.call(this); // The "shared" type is used in cases where a stream will contain muxed 42947 // video and audio. We could use `undefined` here, but having a string 42948 // makes debugging a little clearer. 42949 42950 this.type_ = type || TYPE_SHARED; 42951 42952 this.push = function (data) { 42953 // Any "shared" rollover streams will accept _all_ data. Otherwise, 42954 // streams will only accept data that matches their type. 42955 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) { 42956 return; 42957 } 42958 42959 if (referenceDTS === undefined) { 42960 referenceDTS = data.dts; 42961 } 42962 42963 data.dts = handleRollover(data.dts, referenceDTS); 42964 data.pts = handleRollover(data.pts, referenceDTS); 42965 lastDTS = data.dts; 42966 this.trigger('data', data); 42967 }; 42968 42969 this.flush = function () { 42970 referenceDTS = lastDTS; 42971 this.trigger('done'); 42972 }; 42973 42974 this.endTimeline = function () { 42975 this.flush(); 42976 this.trigger('endedtimeline'); 42977 }; 42978 42979 this.discontinuity = function () { 42980 referenceDTS = void 0; 42981 lastDTS = void 0; 42982 }; 42983 42984 this.reset = function () { 42985 this.discontinuity(); 42986 this.trigger('reset'); 42987 }; 42988 }; 42989 42990 TimestampRolloverStream.prototype = new stream(); 42991 var timestampRolloverStream = { 42992 TimestampRolloverStream: TimestampRolloverStream, 42993 handleRollover: handleRollover 42994 }; 42995 42996 var percentEncode = function percentEncode(bytes, start, end) { 42997 var i, 42998 result = ''; 42999 43000 for (i = start; i < end; i++) { 43001 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 43002 } 43003 43004 return result; 43005 }, 43006 // return the string representation of the specified byte range, 43007 // interpreted as UTf-8. 43008 parseUtf8 = function parseUtf8(bytes, start, end) { 43009 return decodeURIComponent(percentEncode(bytes, start, end)); 43010 }, 43011 // return the string representation of the specified byte range, 43012 // interpreted as ISO-8859-1. 43013 parseIso88591 = function parseIso88591(bytes, start, end) { 43014 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 43015 }, 43016 parseSyncSafeInteger = function parseSyncSafeInteger(data) { 43017 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 43018 }, 43019 tagParsers = { 43020 TXXX: function TXXX(tag) { 43021 var i; 43022 43023 if (tag.data[0] !== 3) { 43024 // ignore frames with unrecognized character encodings 43025 return; 43026 } 43027 43028 for (i = 1; i < tag.data.length; i++) { 43029 if (tag.data[i] === 0) { 43030 // parse the text fields 43031 tag.description = parseUtf8(tag.data, 1, i); // do not include the null terminator in the tag value 43032 43033 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 43034 break; 43035 } 43036 } 43037 43038 tag.data = tag.value; 43039 }, 43040 WXXX: function WXXX(tag) { 43041 var i; 43042 43043 if (tag.data[0] !== 3) { 43044 // ignore frames with unrecognized character encodings 43045 return; 43046 } 43047 43048 for (i = 1; i < tag.data.length; i++) { 43049 if (tag.data[i] === 0) { 43050 // parse the description and URL fields 43051 tag.description = parseUtf8(tag.data, 1, i); 43052 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 43053 break; 43054 } 43055 } 43056 }, 43057 PRIV: function PRIV(tag) { 43058 var i; 43059 43060 for (i = 0; i < tag.data.length; i++) { 43061 if (tag.data[i] === 0) { 43062 // parse the description and URL fields 43063 tag.owner = parseIso88591(tag.data, 0, i); 43064 break; 43065 } 43066 } 43067 43068 tag.privateData = tag.data.subarray(i + 1); 43069 tag.data = tag.privateData; 43070 } 43071 }, 43072 _MetadataStream; 43073 43074 _MetadataStream = function MetadataStream(options) { 43075 var settings = { 43076 debug: !!(options && options.debug), 43077 // the bytes of the program-level descriptor field in MP2T 43078 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 43079 // program element descriptors" 43080 descriptor: options && options.descriptor 43081 }, 43082 // the total size in bytes of the ID3 tag being parsed 43083 tagSize = 0, 43084 // tag data that is not complete enough to be parsed 43085 buffer = [], 43086 // the total number of bytes currently in the buffer 43087 bufferSize = 0, 43088 i; 43089 43090 _MetadataStream.prototype.init.call(this); // calculate the text track in-band metadata track dispatch type 43091 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 43092 43093 43094 this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16); 43095 43096 if (settings.descriptor) { 43097 for (i = 0; i < settings.descriptor.length; i++) { 43098 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 43099 } 43100 } 43101 43102 this.push = function (chunk) { 43103 var tag, frameStart, frameSize, frame, i, frameHeader; 43104 43105 if (chunk.type !== 'timed-metadata') { 43106 return; 43107 } // if data_alignment_indicator is set in the PES header, 43108 // we must have the start of a new ID3 tag. Assume anything 43109 // remaining in the buffer was malformed and throw it out 43110 43111 43112 if (chunk.dataAlignmentIndicator) { 43113 bufferSize = 0; 43114 buffer.length = 0; 43115 } // ignore events that don't look like ID3 data 43116 43117 43118 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))) { 43119 if (settings.debug) { 43120 // eslint-disable-next-line no-console 43121 console.log('Skipping unrecognized metadata packet'); 43122 } 43123 43124 return; 43125 } // add this chunk to the data we've collected so far 43126 43127 43128 buffer.push(chunk); 43129 bufferSize += chunk.data.byteLength; // grab the size of the entire frame from the ID3 header 43130 43131 if (buffer.length === 1) { 43132 // the frame size is transmitted as a 28-bit integer in the 43133 // last four bytes of the ID3 header. 43134 // The most significant bit of each byte is dropped and the 43135 // results concatenated to recover the actual value. 43136 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); // ID3 reports the tag size excluding the header but it's more 43137 // convenient for our comparisons to include it 43138 43139 tagSize += 10; 43140 } // if the entire frame has not arrived, wait for more data 43141 43142 43143 if (bufferSize < tagSize) { 43144 return; 43145 } // collect the entire frame so it can be parsed 43146 43147 43148 tag = { 43149 data: new Uint8Array(tagSize), 43150 frames: [], 43151 pts: buffer[0].pts, 43152 dts: buffer[0].dts 43153 }; 43154 43155 for (i = 0; i < tagSize;) { 43156 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 43157 i += buffer[0].data.byteLength; 43158 bufferSize -= buffer[0].data.byteLength; 43159 buffer.shift(); 43160 } // find the start of the first frame and the end of the tag 43161 43162 43163 frameStart = 10; 43164 43165 if (tag.data[5] & 0x40) { 43166 // advance the frame start past the extended header 43167 frameStart += 4; // header size field 43168 43169 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); // clip any padding off the end 43170 43171 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 43172 } // parse one or more ID3 frames 43173 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 43174 43175 43176 do { 43177 // determine the number of bytes in this frame 43178 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 43179 43180 if (frameSize < 1) { 43181 // eslint-disable-next-line no-console 43182 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 43183 } 43184 43185 frameHeader = String.fromCharCode(tag.data[frameStart], tag.data[frameStart + 1], tag.data[frameStart + 2], tag.data[frameStart + 3]); 43186 frame = { 43187 id: frameHeader, 43188 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 43189 }; 43190 frame.key = frame.id; 43191 43192 if (tagParsers[frame.id]) { 43193 tagParsers[frame.id](frame); // handle the special PRIV frame used to indicate the start 43194 // time for raw AAC data 43195 43196 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 43197 var d = frame.data, 43198 size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 43199 size *= 4; 43200 size += d[7] & 0x03; 43201 frame.timeStamp = size; // in raw AAC, all subsequent data will be timestamped based 43202 // on the value of this frame 43203 // we couldn't have known the appropriate pts and dts before 43204 // parsing this ID3 tag so set those values now 43205 43206 if (tag.pts === undefined && tag.dts === undefined) { 43207 tag.pts = frame.timeStamp; 43208 tag.dts = frame.timeStamp; 43209 } 43210 43211 this.trigger('timestamp', frame); 43212 } 43213 } 43214 43215 tag.frames.push(frame); 43216 frameStart += 10; // advance past the frame header 43217 43218 frameStart += frameSize; // advance past the frame body 43219 } while (frameStart < tagSize); 43220 43221 this.trigger('data', tag); 43222 }; 43223 }; 43224 43225 _MetadataStream.prototype = new stream(); 43226 var metadataStream = _MetadataStream; 43227 var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream; // object types 43228 43229 var _TransportPacketStream, _TransportParseStream, _ElementaryStream; // constants 43230 43231 43232 var MP2T_PACKET_LENGTH = 188, 43233 // bytes 43234 SYNC_BYTE = 0x47; 43235 /** 43236 * Splits an incoming stream of binary data into MPEG-2 Transport 43237 * Stream packets. 43238 */ 43239 43240 _TransportPacketStream = function TransportPacketStream() { 43241 var buffer = new Uint8Array(MP2T_PACKET_LENGTH), 43242 bytesInBuffer = 0; 43243 43244 _TransportPacketStream.prototype.init.call(this); // Deliver new bytes to the stream. 43245 43246 /** 43247 * Split a stream of data into M2TS packets 43248 **/ 43249 43250 43251 this.push = function (bytes) { 43252 var startIndex = 0, 43253 endIndex = MP2T_PACKET_LENGTH, 43254 everything; // If there are bytes remaining from the last segment, prepend them to the 43255 // bytes that were pushed in 43256 43257 if (bytesInBuffer) { 43258 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 43259 everything.set(buffer.subarray(0, bytesInBuffer)); 43260 everything.set(bytes, bytesInBuffer); 43261 bytesInBuffer = 0; 43262 } else { 43263 everything = bytes; 43264 } // While we have enough data for a packet 43265 43266 43267 while (endIndex < everything.byteLength) { 43268 // Look for a pair of start and end sync bytes in the data.. 43269 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 43270 // We found a packet so emit it and jump one whole packet forward in 43271 // the stream 43272 this.trigger('data', everything.subarray(startIndex, endIndex)); 43273 startIndex += MP2T_PACKET_LENGTH; 43274 endIndex += MP2T_PACKET_LENGTH; 43275 continue; 43276 } // If we get here, we have somehow become de-synchronized and we need to step 43277 // forward one byte at a time until we find a pair of sync bytes that denote 43278 // a packet 43279 43280 43281 startIndex++; 43282 endIndex++; 43283 } // If there was some data left over at the end of the segment that couldn't
vendor: 17,064 bytes, lines 43284-43719
43284 // possibly be a whole packet, keep it because it might be the start of a packet 43285 // that continues in the next segment 43286 43287 43288 if (startIndex < everything.byteLength) { 43289 buffer.set(everything.subarray(startIndex), 0); 43290 bytesInBuffer = everything.byteLength - startIndex; 43291 } 43292 }; 43293 /** 43294 * Passes identified M2TS packets to the TransportParseStream to be parsed 43295 **/ 43296 43297 43298 this.flush = function () { 43299 // If the buffer contains a whole packet when we are being flushed, emit it 43300 // and empty the buffer. Otherwise hold onto the data because it may be 43301 // important for decoding the next segment 43302 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 43303 this.trigger('data', buffer); 43304 bytesInBuffer = 0; 43305 } 43306 43307 this.trigger('done'); 43308 }; 43309 43310 this.endTimeline = function () { 43311 this.flush(); 43312 this.trigger('endedtimeline'); 43313 }; 43314 43315 this.reset = function () { 43316 bytesInBuffer = 0; 43317 this.trigger('reset'); 43318 }; 43319 }; 43320 43321 _TransportPacketStream.prototype = new stream(); 43322 /** 43323 * Accepts an MP2T TransportPacketStream and emits data events with parsed 43324 * forms of the individual transport stream packets. 43325 */ 43326 43327 _TransportParseStream = function TransportParseStream() { 43328 var parsePsi, parsePat, parsePmt, self; 43329 43330 _TransportParseStream.prototype.init.call(this); 43331 43332 self = this; 43333 this.packetsWaitingForPmt = []; 43334 this.programMapTable = undefined; 43335 43336 parsePsi = function parsePsi(payload, psi) { 43337 var offset = 0; // PSI packets may be split into multiple sections and those 43338 // sections may be split into multiple packets. If a PSI 43339 // section starts in this packet, the payload_unit_start_indicator 43340 // will be true and the first byte of the payload will indicate 43341 // the offset from the current position to the start of the 43342 // section. 43343 43344 if (psi.payloadUnitStartIndicator) { 43345 offset += payload[offset] + 1; 43346 } 43347 43348 if (psi.type === 'pat') { 43349 parsePat(payload.subarray(offset), psi); 43350 } else { 43351 parsePmt(payload.subarray(offset), psi); 43352 } 43353 }; 43354 43355 parsePat = function parsePat(payload, pat) { 43356 pat.section_number = payload[7]; // eslint-disable-line camelcase 43357 43358 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 43359 // skip the PSI header and parse the first PMT entry 43360 43361 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 43362 pat.pmtPid = self.pmtPid; 43363 }; 43364 /** 43365 * Parse out the relevant fields of a Program Map Table (PMT). 43366 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 43367 * packet. The first byte in this array should be the table_id 43368 * field. 43369 * @param pmt {object} the object that should be decorated with 43370 * fields parsed from the PMT. 43371 */ 43372 43373 43374 parsePmt = function parsePmt(payload, pmt) { 43375 var sectionLength, tableEnd, programInfoLength, offset; // PMTs can be sent ahead of the time when they should actually 43376 // take effect. We don't believe this should ever be the case 43377 // for HLS but we'll ignore "forward" PMT declarations if we see 43378 // them. Future PMT declarations have the current_next_indicator 43379 // set to zero. 43380 43381 if (!(payload[5] & 0x01)) { 43382 return; 43383 } // overwrite any existing program map table 43384 43385 43386 self.programMapTable = { 43387 video: null, 43388 audio: null, 43389 'timed-metadata': {} 43390 }; // the mapping table ends at the end of the current section 43391 43392 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 43393 tableEnd = 3 + sectionLength - 4; // to determine where the table is, we have to figure out how 43394 // long the program info descriptors are 43395 43396 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; // advance the offset to the first entry in the mapping table 43397 43398 offset = 12 + programInfoLength; 43399 43400 while (offset < tableEnd) { 43401 var streamType = payload[offset]; 43402 var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]; // only map a single elementary_pid for audio and video stream types 43403 // TODO: should this be done for metadata too? for now maintain behavior of 43404 // multiple metadata streams 43405 43406 if (streamType === streamTypes.H264_STREAM_TYPE && self.programMapTable.video === null) { 43407 self.programMapTable.video = pid; 43408 } else if (streamType === streamTypes.ADTS_STREAM_TYPE && self.programMapTable.audio === null) { 43409 self.programMapTable.audio = pid; 43410 } else if (streamType === streamTypes.METADATA_STREAM_TYPE) { 43411 // map pid to stream type for metadata streams 43412 self.programMapTable['timed-metadata'][pid] = streamType; 43413 } // move to the next table entry 43414 // skip past the elementary stream descriptors, if present 43415 43416 43417 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 43418 } // record the map on the packet as well 43419 43420 43421 pmt.programMapTable = self.programMapTable; 43422 }; 43423 /** 43424 * Deliver a new MP2T packet to the next stream in the pipeline. 43425 */ 43426 43427 43428 this.push = function (packet) { 43429 var result = {}, 43430 offset = 4; 43431 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); // pid is a 13-bit field starting at the last bit of packet[1] 43432 43433 result.pid = packet[1] & 0x1f; 43434 result.pid <<= 8; 43435 result.pid |= packet[2]; // if an adaption field is present, its length is specified by the 43436 // fifth byte of the TS packet header. The adaptation field is 43437 // used to add stuffing to PES packets that don't fill a complete 43438 // TS packet, and to specify some forms of timing and control data 43439 // that we do not currently use. 43440 43441 if ((packet[3] & 0x30) >>> 4 > 0x01) { 43442 offset += packet[offset] + 1; 43443 } // parse the rest of the packet based on the type 43444 43445 43446 if (result.pid === 0) { 43447 result.type = 'pat'; 43448 parsePsi(packet.subarray(offset), result); 43449 this.trigger('data', result); 43450 } else if (result.pid === this.pmtPid) { 43451 result.type = 'pmt'; 43452 parsePsi(packet.subarray(offset), result); 43453 this.trigger('data', result); // if there are any packets waiting for a PMT to be found, process them now 43454 43455 while (this.packetsWaitingForPmt.length) { 43456 this.processPes_.apply(this, this.packetsWaitingForPmt.shift()); 43457 } 43458 } else if (this.programMapTable === undefined) { 43459 // When we have not seen a PMT yet, defer further processing of 43460 // PES packets until one has been parsed 43461 this.packetsWaitingForPmt.push([packet, offset, result]); 43462 } else { 43463 this.processPes_(packet, offset, result); 43464 } 43465 }; 43466 43467 this.processPes_ = function (packet, offset, result) { 43468 // set the appropriate stream type 43469 if (result.pid === this.programMapTable.video) { 43470 result.streamType = streamTypes.H264_STREAM_TYPE; 43471 } else if (result.pid === this.programMapTable.audio) { 43472 result.streamType = streamTypes.ADTS_STREAM_TYPE; 43473 } else { 43474 // if not video or audio, it is timed-metadata or unknown 43475 // if unknown, streamType will be undefined 43476 result.streamType = this.programMapTable['timed-metadata'][result.pid]; 43477 } 43478 43479 result.type = 'pes'; 43480 result.data = packet.subarray(offset); 43481 this.trigger('data', result); 43482 }; 43483 }; 43484 43485 _TransportParseStream.prototype = new stream(); 43486 _TransportParseStream.STREAM_TYPES = { 43487 h264: 0x1b, 43488 adts: 0x0f 43489 }; 43490 /** 43491 * Reconsistutes program elementary stream (PES) packets from parsed 43492 * transport stream packets. That is, if you pipe an 43493 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 43494 * events will be events which capture the bytes for individual PES 43495 * packets plus relevant metadata that has been extracted from the 43496 * container. 43497 */ 43498 43499 _ElementaryStream = function ElementaryStream() { 43500 var self = this, 43501 // PES packet fragments 43502 video = { 43503 data: [], 43504 size: 0 43505 }, 43506 audio = { 43507 data: [], 43508 size: 0 43509 }, 43510 timedMetadata = { 43511 data: [], 43512 size: 0 43513 }, 43514 programMapTable, 43515 parsePes = function parsePes(payload, pes) { 43516 var ptsDtsFlags; // get the packet length, this will be 0 for video 43517 43518 pes.packetLength = 6 + (payload[4] << 8 | payload[5]); // find out if this packets starts a new keyframe 43519 43520 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; // PES packets may be annotated with a PTS value, or a PTS value 43521 // and a DTS value. Determine what combination of values is 43522 // available to work with. 43523 43524 ptsDtsFlags = payload[7]; // PTS and DTS are normally stored as a 33-bit number. Javascript 43525 // performs all bitwise operations on 32-bit integers but javascript 43526 // supports a much greater range (52-bits) of integer using standard 43527 // mathematical operations. 43528 // We construct a 31-bit value using bitwise operators over the 31 43529 // most significant bits and then multiply by 4 (equal to a left-shift 43530 // of 2) before we add the final 2 least significant bits of the 43531 // timestamp (equal to an OR.) 43532 43533 if (ptsDtsFlags & 0xC0) { 43534 // the PTS and DTS are not written out directly. For information 43535 // on how they are encoded, see 43536 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 43537 pes.pts = (payload[9] & 0x0E) << 27 | (payload[10] & 0xFF) << 20 | (payload[11] & 0xFE) << 12 | (payload[12] & 0xFF) << 5 | (payload[13] & 0xFE) >>> 3; 43538 pes.pts *= 4; // Left shift by 2 43539 43540 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 43541 43542 pes.dts = pes.pts; 43543 43544 if (ptsDtsFlags & 0x40) { 43545 pes.dts = (payload[14] & 0x0E) << 27 | (payload[15] & 0xFF) << 20 | (payload[16] & 0xFE) << 12 | (payload[17] & 0xFF) << 5 | (payload[18] & 0xFE) >>> 3; 43546 pes.dts *= 4; // Left shift by 2 43547 43548 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 43549 } 43550 } // the data section starts immediately after the PES header. 43551 // pes_header_data_length specifies the number of header bytes 43552 // that follow the last byte of the field. 43553 43554 43555 pes.data = payload.subarray(9 + payload[8]); 43556 }, 43557 43558 /** 43559 * Pass completely parsed PES packets to the next stream in the pipeline 43560 **/ 43561 flushStream = function flushStream(stream$$1, type, forceFlush) { 43562 var packetData = new Uint8Array(stream$$1.size), 43563 event = { 43564 type: type 43565 }, 43566 i = 0, 43567 offset = 0, 43568 packetFlushable = false, 43569 fragment; // do nothing if there is not enough buffered data for a complete 43570 // PES header 43571 43572 if (!stream$$1.data.length || stream$$1.size < 9) { 43573 return; 43574 } 43575 43576 event.trackId = stream$$1.data[0].pid; // reassemble the packet 43577 43578 for (i = 0; i < stream$$1.data.length; i++) { 43579 fragment = stream$$1.data[i]; 43580 packetData.set(fragment.data, offset); 43581 offset += fragment.data.byteLength; 43582 } // parse assembled packet's PES header 43583 43584 43585 parsePes(packetData, event); // non-video PES packets MUST have a non-zero PES_packet_length 43586 // check that there is enough stream data to fill the packet 43587 43588 packetFlushable = type === 'video' || event.packetLength <= stream$$1.size; // flush pending packets if the conditions are right 43589 43590 if (forceFlush || packetFlushable) { 43591 stream$$1.size = 0; 43592 stream$$1.data.length = 0; 43593 } // only emit packets that are complete. this is to avoid assembling 43594 // incomplete PES packets due to poor segmentation 43595 43596 43597 if (packetFlushable) { 43598 self.trigger('data', event); 43599 } 43600 }; 43601 43602 _ElementaryStream.prototype.init.call(this); 43603 /** 43604 * Identifies M2TS packet types and parses PES packets using metadata 43605 * parsed from the PMT 43606 **/ 43607 43608 43609 this.push = function (data) { 43610 ({ 43611 pat: function pat() {// we have to wait for the PMT to arrive as well before we 43612 // have any meaningful metadata 43613 }, 43614 pes: function pes() { 43615 var stream$$1, streamType; 43616 43617 switch (data.streamType) { 43618 case streamTypes.H264_STREAM_TYPE: 43619 case streamTypes.H264_STREAM_TYPE: 43620 stream$$1 = video; 43621 streamType = 'video'; 43622 break; 43623 43624 case streamTypes.ADTS_STREAM_TYPE: 43625 stream$$1 = audio; 43626 streamType = 'audio'; 43627 break; 43628 43629 case streamTypes.METADATA_STREAM_TYPE: 43630 stream$$1 = timedMetadata; 43631 streamType = 'timed-metadata'; 43632 break; 43633 43634 default: 43635 // ignore unknown stream types 43636 return; 43637 } // if a new packet is starting, we can flush the completed 43638 // packet 43639 43640 43641 if (data.payloadUnitStartIndicator) { 43642 flushStream(stream$$1, streamType, true); 43643 } // buffer this fragment until we are sure we've received the 43644 // complete payload 43645 43646 43647 stream$$1.data.push(data); 43648 stream$$1.size += data.data.byteLength; 43649 }, 43650 pmt: function pmt() { 43651 var event = { 43652 type: 'metadata', 43653 tracks: [] 43654 }; 43655 programMapTable = data.programMapTable; // translate audio and video streams to tracks 43656 43657 if (programMapTable.video !== null) { 43658 event.tracks.push({ 43659 timelineStartInfo: { 43660 baseMediaDecodeTime: 0 43661 }, 43662 id: +programMapTable.video, 43663 codec: 'avc', 43664 type: 'video' 43665 }); 43666 } 43667 43668 if (programMapTable.audio !== null) { 43669 event.tracks.push({ 43670 timelineStartInfo: { 43671 baseMediaDecodeTime: 0 43672 }, 43673 id: +programMapTable.audio, 43674 codec: 'adts', 43675 type: 'audio' 43676 }); 43677 } 43678 43679 self.trigger('data', event); 43680 } 43681 })[data.type](); 43682 }; 43683 43684 this.reset = function () { 43685 video.size = 0; 43686 video.data.length = 0; 43687 audio.size = 0; 43688 audio.data.length = 0; 43689 this.trigger('reset'); 43690 }; 43691 /** 43692 * Flush any remaining input. Video PES packets may be of variable 43693 * length. Normally, the start of a new video packet can trigger the 43694 * finalization of the previous packet. That is not possible if no 43695 * more video is forthcoming, however. In that case, some other 43696 * mechanism (like the end of the file) has to be employed. When it is 43697 * clear that no additional data is forthcoming, calling this method 43698 * will flush the buffered packets. 43699 */ 43700 43701 43702 this.flushStreams_ = function () { 43703 // !!THIS ORDER IS IMPORTANT!! 43704 // video first then audio 43705 flushStream(video, 'video'); 43706 flushStream(audio, 'audio'); 43707 flushStream(timedMetadata, 'timed-metadata'); 43708 }; 43709 43710 this.flush = function () { 43711 this.flushStreams_(); 43712 this.trigger('done'); 43713 }; 43714 }; 43715 43716 _ElementaryStream.prototype = new stream(); 43717 var m2ts = { 43718 PAT_PID: 0x0000, 43719 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
vendor: 6,108 bytes, lines 43720-43887
43720 TransportPacketStream: _TransportPacketStream, 43721 TransportParseStream: _TransportParseStream, 43722 ElementaryStream: _ElementaryStream, 43723 TimestampRolloverStream: TimestampRolloverStream$1, 43724 CaptionStream: captionStream.CaptionStream, 43725 Cea608Stream: captionStream.Cea608Stream, 43726 MetadataStream: metadataStream 43727 }; 43728 43729 for (var type in streamTypes) { 43730 if (streamTypes.hasOwnProperty(type)) { 43731 m2ts[type] = streamTypes[type]; 43732 } 43733 } 43734 43735 var m2ts_1 = m2ts; 43736 var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS; 43737 43738 var _AdtsStream; 43739 43740 var ADTS_SAMPLING_FREQUENCIES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 43741 /* 43742 * Accepts a ElementaryStream and emits data events with parsed 43743 * AAC Audio Frames of the individual packets. Input audio in ADTS 43744 * format is unpacked and re-emitted as AAC frames. 43745 * 43746 * @see http://wiki.multimedia.cx/index.php?title=ADTS 43747 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 43748 */ 43749 43750 _AdtsStream = function AdtsStream(handlePartialSegments) { 43751 var buffer, 43752 frameNum = 0; 43753 43754 _AdtsStream.prototype.init.call(this); 43755 43756 this.push = function (packet) { 43757 var i = 0, 43758 frameLength, 43759 protectionSkipBytes, 43760 frameEnd, 43761 oldBuffer, 43762 sampleCount, 43763 adtsFrameDuration; 43764 43765 if (!handlePartialSegments) { 43766 frameNum = 0; 43767 } 43768 43769 if (packet.type !== 'audio') { 43770 // ignore non-audio data 43771 return; 43772 } // Prepend any data in the buffer to the input data so that we can parse 43773 // aac frames the cross a PES packet boundary 43774 43775 43776 if (buffer) { 43777 oldBuffer = buffer; 43778 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 43779 buffer.set(oldBuffer); 43780 buffer.set(packet.data, oldBuffer.byteLength); 43781 } else { 43782 buffer = packet.data; 43783 } // unpack any ADTS frames which have been fully received 43784 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 43785 43786 43787 while (i + 5 < buffer.length) { 43788 // Look for the start of an ADTS header.. 43789 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 43790 // If a valid header was not found, jump one forward and attempt to 43791 // find a valid ADTS header starting at the next byte 43792 i++; 43793 continue; 43794 } // The protection skip bit tells us if we have 2 bytes of CRC data at the 43795 // end of the ADTS header 43796 43797 43798 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; // Frame length is a 13 bit integer starting 16 bits from the 43799 // end of the sync sequence 43800 43801 frameLength = (buffer[i + 3] & 0x03) << 11 | buffer[i + 4] << 3 | (buffer[i + 5] & 0xe0) >> 5; 43802 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 43803 adtsFrameDuration = sampleCount * ONE_SECOND_IN_TS$2 / ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 43804 frameEnd = i + frameLength; // If we don't have enough data to actually finish this ADTS frame, return 43805 // and wait for more data 43806 43807 if (buffer.byteLength < frameEnd) { 43808 return; 43809 } // Otherwise, deliver the complete AAC frame 43810 43811 43812 this.trigger('data', { 43813 pts: packet.pts + frameNum * adtsFrameDuration, 43814 dts: packet.dts + frameNum * adtsFrameDuration, 43815 sampleCount: sampleCount, 43816 audioobjecttype: (buffer[i + 2] >>> 6 & 0x03) + 1, 43817 channelcount: (buffer[i + 2] & 1) << 2 | (buffer[i + 3] & 0xc0) >>> 6, 43818 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 43819 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 43820 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 43821 samplesize: 16, 43822 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 43823 }); 43824 frameNum++; // If the buffer is empty, clear it and return 43825 43826 if (buffer.byteLength === frameEnd) { 43827 buffer = undefined; 43828 return; 43829 } // Remove the finished frame from the buffer and start the process again 43830 43831 43832 buffer = buffer.subarray(frameEnd); 43833 } 43834 }; 43835 43836 this.flush = function () { 43837 frameNum = 0; 43838 this.trigger('done'); 43839 }; 43840 43841 this.reset = function () { 43842 buffer = void 0; 43843 this.trigger('reset'); 43844 }; 43845 43846 this.endTimeline = function () { 43847 buffer = void 0; 43848 this.trigger('endedtimeline'); 43849 }; 43850 }; 43851 43852 _AdtsStream.prototype = new stream(); 43853 var adts = _AdtsStream; 43854 /** 43855 * mux.js 43856 * 43857 * Copyright (c) Brightcove 43858 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 43859 */ 43860 43861 var ExpGolomb; 43862 /** 43863 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 43864 * scheme used by h264. 43865 */ 43866 43867 ExpGolomb = function ExpGolomb(workingData) { 43868 var // the number of bytes left to examine in workingData 43869 workingBytesAvailable = workingData.byteLength, 43870 // the current word being examined 43871 workingWord = 0, 43872 // :uint 43873 // the number of bits left to examine in the current word 43874 workingBitsAvailable = 0; // :uint; 43875 // ():uint 43876 43877 this.length = function () { 43878 return 8 * workingBytesAvailable; 43879 }; // ():uint 43880 43881 43882 this.bitsAvailable = function () { 43883 return 8 * workingBytesAvailable + workingBitsAvailable; 43884 }; // ():void 43885 43886 43887 this.loadWord = function () {
vendor: 8,404 bytes, lines 43888-44165
43888 var position = workingData.byteLength - workingBytesAvailable, 43889 workingBytes = new Uint8Array(4), 43890 availableBytes = Math.min(4, workingBytesAvailable); 43891 43892 if (availableBytes === 0) { 43893 throw new Error('no bytes available'); 43894 } 43895 43896 workingBytes.set(workingData.subarray(position, position + availableBytes)); 43897 workingWord = new DataView(workingBytes.buffer).getUint32(0); // track the amount of workingData that has been processed 43898 43899 workingBitsAvailable = availableBytes * 8; 43900 workingBytesAvailable -= availableBytes; 43901 }; // (count:int):void 43902 43903 43904 this.skipBits = function (count) { 43905 var skipBytes; // :int 43906 43907 if (workingBitsAvailable > count) { 43908 workingWord <<= count; 43909 workingBitsAvailable -= count; 43910 } else { 43911 count -= workingBitsAvailable; 43912 skipBytes = Math.floor(count / 8); 43913 count -= skipBytes * 8; 43914 workingBytesAvailable -= skipBytes; 43915 this.loadWord(); 43916 workingWord <<= count; 43917 workingBitsAvailable -= count; 43918 } 43919 }; // (size:int):uint 43920 43921 43922 this.readBits = function (size) { 43923 var bits = Math.min(workingBitsAvailable, size), 43924 // :uint 43925 valu = workingWord >>> 32 - bits; // :uint 43926 // if size > 31, handle error 43927 43928 workingBitsAvailable -= bits; 43929 43930 if (workingBitsAvailable > 0) { 43931 workingWord <<= bits; 43932 } else if (workingBytesAvailable > 0) { 43933 this.loadWord(); 43934 } 43935 43936 bits = size - bits; 43937 43938 if (bits > 0) { 43939 return valu << bits | this.readBits(bits); 43940 } 43941 43942 return valu; 43943 }; // ():uint 43944 43945 43946 this.skipLeadingZeros = function () { 43947 var leadingZeroCount; // :uint 43948 43949 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 43950 if ((workingWord & 0x80000000 >>> leadingZeroCount) !== 0) { 43951 // the first bit of working word is 1 43952 workingWord <<= leadingZeroCount; 43953 workingBitsAvailable -= leadingZeroCount; 43954 return leadingZeroCount; 43955 } 43956 } // we exhausted workingWord and still have not found a 1 43957 43958 43959 this.loadWord(); 43960 return leadingZeroCount + this.skipLeadingZeros(); 43961 }; // ():void 43962 43963 43964 this.skipUnsignedExpGolomb = function () { 43965 this.skipBits(1 + this.skipLeadingZeros()); 43966 }; // ():void 43967 43968 43969 this.skipExpGolomb = function () { 43970 this.skipBits(1 + this.skipLeadingZeros()); 43971 }; // ():uint 43972 43973 43974 this.readUnsignedExpGolomb = function () { 43975 var clz = this.skipLeadingZeros(); // :uint 43976 43977 return this.readBits(clz + 1) - 1; 43978 }; // ():int 43979 43980 43981 this.readExpGolomb = function () { 43982 var valu = this.readUnsignedExpGolomb(); // :int 43983 43984 if (0x01 & valu) { 43985 // the number is odd if the low order bit is set 43986 return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 43987 } 43988 43989 return -1 * (valu >>> 1); // divide by two then make it negative 43990 }; // Some convenience functions 43991 // :Boolean 43992 43993 43994 this.readBoolean = function () { 43995 return this.readBits(1) === 1; 43996 }; // ():int 43997 43998 43999 this.readUnsignedByte = function () { 44000 return this.readBits(8); 44001 }; 44002 44003 this.loadWord(); 44004 }; 44005 44006 var expGolomb = ExpGolomb; 44007 44008 var _H264Stream, _NalByteStream; 44009 44010 var PROFILES_WITH_OPTIONAL_SPS_DATA; 44011 /** 44012 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 44013 */ 44014 44015 _NalByteStream = function NalByteStream() { 44016 var syncPoint = 0, 44017 i, 44018 buffer; 44019 44020 _NalByteStream.prototype.init.call(this); 44021 /* 44022 * Scans a byte stream and triggers a data event with the NAL units found. 44023 * @param {Object} data Event received from H264Stream 44024 * @param {Uint8Array} data.data The h264 byte stream to be scanned 44025 * 44026 * @see H264Stream.push 44027 */ 44028 44029 44030 this.push = function (data) { 44031 var swapBuffer; 44032 44033 if (!buffer) { 44034 buffer = data.data; 44035 } else { 44036 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 44037 swapBuffer.set(buffer); 44038 swapBuffer.set(data.data, buffer.byteLength); 44039 buffer = swapBuffer; 44040 } 44041 44042 var len = buffer.byteLength; // Rec. ITU-T H.264, Annex B 44043 // scan for NAL unit boundaries 44044 // a match looks like this: 44045 // 0 0 1 .. NAL .. 0 0 1 44046 // ^ sync point ^ i 44047 // or this: 44048 // 0 0 1 .. NAL .. 0 0 0 44049 // ^ sync point ^ i 44050 // advance the sync point to a NAL start, if necessary 44051 44052 for (; syncPoint < len - 3; syncPoint++) { 44053 if (buffer[syncPoint + 2] === 1) { 44054 // the sync point is properly aligned 44055 i = syncPoint + 5; 44056 break; 44057 } 44058 } 44059 44060 while (i < len) { 44061 // look at the current byte to determine if we've hit the end of 44062 // a NAL unit boundary 44063 switch (buffer[i]) { 44064 case 0: 44065 // skip past non-sync sequences 44066 if (buffer[i - 1] !== 0) { 44067 i += 2; 44068 break; 44069 } else if (buffer[i - 2] !== 0) { 44070 i++; 44071 break; 44072 } // deliver the NAL unit if it isn't empty 44073 44074 44075 if (syncPoint + 3 !== i - 2) { 44076 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 44077 } // drop trailing zeroes 44078 44079 44080 do { 44081 i++; 44082 } while (buffer[i] !== 1 && i < len); 44083 44084 syncPoint = i - 2; 44085 i += 3; 44086 break; 44087 44088 case 1: 44089 // skip past non-sync sequences 44090 if (buffer[i - 1] !== 0 || buffer[i - 2] !== 0) { 44091 i += 3; 44092 break; 44093 } // deliver the NAL unit 44094 44095 44096 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 44097 syncPoint = i - 2; 44098 i += 3; 44099 break; 44100 44101 default: 44102 // the current byte isn't a one or zero, so it cannot be part 44103 // of a sync sequence 44104 i += 3; 44105 break; 44106 } 44107 } // filter out the NAL units that were delivered 44108 44109 44110 buffer = buffer.subarray(syncPoint); 44111 i -= syncPoint; 44112 syncPoint = 0; 44113 }; 44114 44115 this.reset = function () { 44116 buffer = null; 44117 syncPoint = 0; 44118 this.trigger('reset'); 44119 }; 44120 44121 this.flush = function () { 44122 // deliver the last buffered NAL unit 44123 if (buffer && buffer.byteLength > 3) { 44124 this.trigger('data', buffer.subarray(syncPoint + 3)); 44125 } // reset the stream state 44126 44127 44128 buffer = null; 44129 syncPoint = 0; 44130 this.trigger('done'); 44131 }; 44132 44133 this.endTimeline = function () { 44134 this.flush(); 44135 this.trigger('endedtimeline'); 44136 }; 44137 }; 44138 44139 _NalByteStream.prototype = new stream(); // values of profile_idc that indicate additional fields are included in the SPS 44140 // see Recommendation ITU-T H.264 (4/2013), 44141 // 7.3.2.1.1 Sequence parameter set data syntax 44142 44143 PROFILES_WITH_OPTIONAL_SPS_DATA = { 44144 100: true, 44145 110: true, 44146 122: true, 44147 244: true, 44148 44: true, 44149 83: true, 44150 86: true, 44151 118: true, 44152 128: true, 44153 138: true, 44154 139: true, 44155 134: true 44156 }; 44157 /** 44158 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 44159 * events. 44160 */ 44161 44162 _H264Stream = function H264Stream() { 44163 var nalByteStream = new _NalByteStream(), 44164 self, 44165 trackId,
44166 currentPts, 44167 currentDts, 44168 discardEmulationPreventionBytes, 44169 readSequenceParameterSet, 44170 skipScalingList; 44171 44172 _H264Stream.prototype.init.call(this); 44173 44174 self = this; 44175 /* 44176 * Pushes a packet from a stream onto the NalByteStream 44177 * 44178 * @param {Object} packet - A packet received from a stream 44179 * @param {Uint8Array} packet.data - The raw bytes of the packet 44180 * @param {Number} packet.dts - Decode timestamp of the packet 44181 * @param {Number} packet.pts - Presentation timestamp of the packet 44182 * @param {Number} packet.trackId - The id of the h264 track this packet came from 44183 * @param {('video'|'audio')} packet.type - The type of packet 44184 * 44185 */ 44186 44187 this.push = function (packet) { 44188 if (packet.type !== 'video') { 44189 return; 44190 } 44191 44192 trackId = packet.trackId; 44193 currentPts = packet.pts; 44194 currentDts = packet.dts; 44195 nalByteStream.push(packet); 44196 }; 44197 /* 44198 * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps 44199 * for the NALUs to the next stream component. 44200 * Also, preprocess caption and sequence parameter NALUs. 44201 * 44202 * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push` 44203 * @see NalByteStream.push 44204 */ 44205 44206 44207 nalByteStream.on('data', function (data) { 44208 var event = { 44209 trackId: trackId,
vendor: 16,384 bytes, lines 44210-44680
44210 pts: currentPts, 44211 dts: currentDts, 44212 data: data 44213 }; 44214 44215 switch (data[0] & 0x1f) { 44216 case 0x05: 44217 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 44218 break; 44219 44220 case 0x06: 44221 event.nalUnitType = 'sei_rbsp'; 44222 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 44223 break; 44224 44225 case 0x07: 44226 event.nalUnitType = 'seq_parameter_set_rbsp'; 44227 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 44228 event.config = readSequenceParameterSet(event.escapedRBSP); 44229 break; 44230 44231 case 0x08: 44232 event.nalUnitType = 'pic_parameter_set_rbsp'; 44233 break; 44234 44235 case 0x09: 44236 event.nalUnitType = 'access_unit_delimiter_rbsp'; 44237 break; 44238 44239 default: 44240 break; 44241 } // This triggers data on the H264Stream 44242 44243 44244 self.trigger('data', event); 44245 }); 44246 nalByteStream.on('done', function () { 44247 self.trigger('done'); 44248 }); 44249 nalByteStream.on('partialdone', function () { 44250 self.trigger('partialdone'); 44251 }); 44252 nalByteStream.on('reset', function () { 44253 self.trigger('reset'); 44254 }); 44255 nalByteStream.on('endedtimeline', function () { 44256 self.trigger('endedtimeline'); 44257 }); 44258 44259 this.flush = function () { 44260 nalByteStream.flush(); 44261 }; 44262 44263 this.partialFlush = function () { 44264 nalByteStream.partialFlush(); 44265 }; 44266 44267 this.reset = function () { 44268 nalByteStream.reset(); 44269 }; 44270 44271 this.endTimeline = function () { 44272 nalByteStream.endTimeline(); 44273 }; 44274 /** 44275 * Advance the ExpGolomb decoder past a scaling list. The scaling 44276 * list is optionally transmitted as part of a sequence parameter 44277 * set and is not relevant to transmuxing. 44278 * @param count {number} the number of entries in this scaling list 44279 * @param expGolombDecoder {object} an ExpGolomb pointed to the 44280 * start of a scaling list 44281 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 44282 */ 44283 44284 44285 skipScalingList = function skipScalingList(count, expGolombDecoder) { 44286 var lastScale = 8, 44287 nextScale = 8, 44288 j, 44289 deltaScale; 44290 44291 for (j = 0; j < count; j++) { 44292 if (nextScale !== 0) { 44293 deltaScale = expGolombDecoder.readExpGolomb(); 44294 nextScale = (lastScale + deltaScale + 256) % 256; 44295 } 44296 44297 lastScale = nextScale === 0 ? lastScale : nextScale; 44298 } 44299 }; 44300 /** 44301 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 44302 * Sequence Payload" 44303 * @param data {Uint8Array} the bytes of a RBSP from a NAL 44304 * unit 44305 * @return {Uint8Array} the RBSP without any Emulation 44306 * Prevention Bytes 44307 */ 44308 44309 44310 discardEmulationPreventionBytes = function discardEmulationPreventionBytes(data) { 44311 var length = data.byteLength, 44312 emulationPreventionBytesPositions = [], 44313 i = 1, 44314 newLength, 44315 newData; // Find all `Emulation Prevention Bytes` 44316 44317 while (i < length - 2) { 44318 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 44319 emulationPreventionBytesPositions.push(i + 2); 44320 i += 2; 44321 } else { 44322 i++; 44323 } 44324 } // If no Emulation Prevention Bytes were found just return the original 44325 // array 44326 44327 44328 if (emulationPreventionBytesPositions.length === 0) { 44329 return data; 44330 } // Create a new array to hold the NAL unit data 44331 44332 44333 newLength = length - emulationPreventionBytesPositions.length; 44334 newData = new Uint8Array(newLength); 44335 var sourceIndex = 0; 44336 44337 for (i = 0; i < newLength; sourceIndex++, i++) { 44338 if (sourceIndex === emulationPreventionBytesPositions[0]) { 44339 // Skip this byte 44340 sourceIndex++; // Remove this position index 44341 44342 emulationPreventionBytesPositions.shift(); 44343 } 44344 44345 newData[i] = data[sourceIndex]; 44346 } 44347 44348 return newData; 44349 }; 44350 /** 44351 * Read a sequence parameter set and return some interesting video 44352 * properties. A sequence parameter set is the H264 metadata that 44353 * describes the properties of upcoming video frames. 44354 * @param data {Uint8Array} the bytes of a sequence parameter set 44355 * @return {object} an object with configuration parsed from the 44356 * sequence parameter set, including the dimensions of the 44357 * associated video frames. 44358 */ 44359 44360 44361 readSequenceParameterSet = function readSequenceParameterSet(data) { 44362 var frameCropLeftOffset = 0, 44363 frameCropRightOffset = 0, 44364 frameCropTopOffset = 0, 44365 frameCropBottomOffset = 0, 44366 sarScale = 1, 44367 expGolombDecoder, 44368 profileIdc, 44369 levelIdc, 44370 profileCompatibility, 44371 chromaFormatIdc, 44372 picOrderCntType, 44373 numRefFramesInPicOrderCntCycle, 44374 picWidthInMbsMinus1, 44375 picHeightInMapUnitsMinus1, 44376 frameMbsOnlyFlag, 44377 scalingListCount, 44378 sarRatio, 44379 aspectRatioIdc, 44380 i; 44381 expGolombDecoder = new expGolomb(data); 44382 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 44383 44384 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 44385 44386 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 44387 44388 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 44389 // some profiles have more optional data we don't need 44390 44391 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 44392 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 44393 44394 if (chromaFormatIdc === 3) { 44395 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 44396 } 44397 44398 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 44399 44400 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 44401 44402 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 44403 44404 if (expGolombDecoder.readBoolean()) { 44405 // seq_scaling_matrix_present_flag 44406 scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; 44407 44408 for (i = 0; i < scalingListCount; i++) { 44409 if (expGolombDecoder.readBoolean()) { 44410 // seq_scaling_list_present_flag[ i ] 44411 if (i < 6) { 44412 skipScalingList(16, expGolombDecoder); 44413 } else { 44414 skipScalingList(64, expGolombDecoder); 44415 } 44416 } 44417 } 44418 } 44419 } 44420 44421 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 44422 44423 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 44424 44425 if (picOrderCntType === 0) { 44426 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 44427 } else if (picOrderCntType === 1) { 44428 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 44429 44430 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 44431 44432 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 44433 44434 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 44435 44436 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 44437 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 44438 } 44439 } 44440 44441 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 44442 44443 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 44444 44445 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 44446 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 44447 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 44448 44449 if (frameMbsOnlyFlag === 0) { 44450 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 44451 } 44452 44453 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 44454 44455 if (expGolombDecoder.readBoolean()) { 44456 // frame_cropping_flag 44457 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 44458 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 44459 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 44460 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 44461 } 44462 44463 if (expGolombDecoder.readBoolean()) { 44464 // vui_parameters_present_flag 44465 if (expGolombDecoder.readBoolean()) { 44466 // aspect_ratio_info_present_flag 44467 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 44468 44469 switch (aspectRatioIdc) { 44470 case 1: 44471 sarRatio = [1, 1]; 44472 break; 44473 44474 case 2: 44475 sarRatio = [12, 11]; 44476 break; 44477 44478 case 3: 44479 sarRatio = [10, 11]; 44480 break; 44481 44482 case 4: 44483 sarRatio = [16, 11]; 44484 break; 44485 44486 case 5: 44487 sarRatio = [40, 33]; 44488 break; 44489 44490 case 6: 44491 sarRatio = [24, 11]; 44492 break; 44493 44494 case 7: 44495 sarRatio = [20, 11]; 44496 break; 44497 44498 case 8: 44499 sarRatio = [32, 11]; 44500 break; 44501 44502 case 9: 44503 sarRatio = [80, 33]; 44504 break; 44505 44506 case 10: 44507 sarRatio = [18, 11]; 44508 break; 44509 44510 case 11: 44511 sarRatio = [15, 11]; 44512 break; 44513 44514 case 12: 44515 sarRatio = [64, 33]; 44516 break; 44517 44518 case 13: 44519 sarRatio = [160, 99]; 44520 break; 44521 44522 case 14: 44523 sarRatio = [4, 3]; 44524 break; 44525 44526 case 15: 44527 sarRatio = [3, 2]; 44528 break; 44529 44530 case 16: 44531 sarRatio = [2, 1]; 44532 break; 44533 44534 case 255: 44535 { 44536 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte(), expGolombDecoder.readUnsignedByte() << 8 | expGolombDecoder.readUnsignedByte()]; 44537 break; 44538 } 44539 } 44540 44541 if (sarRatio) { 44542 sarScale = sarRatio[0] / sarRatio[1]; 44543 } 44544 } 44545 } 44546 44547 return { 44548 profileIdc: profileIdc, 44549 levelIdc: levelIdc, 44550 profileCompatibility: profileCompatibility, 44551 width: Math.ceil(((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 44552 height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - frameCropTopOffset * 2 - frameCropBottomOffset * 2, 44553 sarRatio: sarRatio 44554 }; 44555 }; 44556 }; 44557 44558 _H264Stream.prototype = new stream(); 44559 var h264 = { 44560 H264Stream: _H264Stream, 44561 NalByteStream: _NalByteStream 44562 }; 44563 /** 44564 * mux.js 44565 * 44566 * Copyright (c) Brightcove 44567 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE 44568 * 44569 * Utilities to detect basic properties and metadata about Aac data. 44570 */ 44571 44572 var ADTS_SAMPLING_FREQUENCIES$1 = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; 44573 44574 var isLikelyAacData = function isLikelyAacData(data) { 44575 if (data[0] === 'I'.charCodeAt(0) && data[1] === 'D'.charCodeAt(0) && data[2] === '3'.charCodeAt(0)) { 44576 return true; 44577 } 44578 44579 return false; 44580 }; 44581 44582 var parseSyncSafeInteger$1 = function parseSyncSafeInteger(data) { 44583 return data[0] << 21 | data[1] << 14 | data[2] << 7 | data[3]; 44584 }; // return a percent-encoded representation of the specified byte range 44585 // @see http://en.wikipedia.org/wiki/Percent-encoding 44586 44587 44588 var percentEncode$1 = function percentEncode(bytes, start, end) { 44589 var i, 44590 result = ''; 44591 44592 for (i = start; i < end; i++) { 44593 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 44594 } 44595 44596 return result; 44597 }; // return the string representation of the specified byte range, 44598 // interpreted as ISO-8859-1. 44599 44600 44601 var parseIso88591$1 = function parseIso88591(bytes, start, end) { 44602 return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line 44603 }; 44604 44605 var parseId3TagSize = function parseId3TagSize(header, byteIndex) { 44606 var returnSize = header[byteIndex + 6] << 21 | header[byteIndex + 7] << 14 | header[byteIndex + 8] << 7 | header[byteIndex + 9], 44607 flags = header[byteIndex + 5], 44608 footerPresent = (flags & 16) >> 4; 44609 44610 if (footerPresent) { 44611 return returnSize + 20; 44612 } 44613 44614 return returnSize + 10; 44615 }; 44616 44617 var parseAdtsSize = function parseAdtsSize(header, byteIndex) { 44618 var lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 44619 middle = header[byteIndex + 4] << 3, 44620 highTwo = header[byteIndex + 3] & 0x3 << 11; 44621 return highTwo | middle | lowThree; 44622 }; 44623 44624 var parseType = function parseType(header, byteIndex) { 44625 if (header[byteIndex] === 'I'.charCodeAt(0) && header[byteIndex + 1] === 'D'.charCodeAt(0) && header[byteIndex + 2] === '3'.charCodeAt(0)) { 44626 return 'timed-metadata'; 44627 } else if (header[byteIndex] & 0xff === 0xff && (header[byteIndex + 1] & 0xf0) === 0xf0) { 44628 return 'audio'; 44629 } 44630 44631 return null; 44632 }; 44633 44634 var parseSampleRate = function parseSampleRate(packet) { 44635 var i = 0; 44636 44637 while (i + 5 < packet.length) { 44638 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 44639 // If a valid header was not found, jump one forward and attempt to 44640 // find a valid ADTS header starting at the next byte 44641 i++; 44642 continue; 44643 } 44644 44645 return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2]; 44646 } 44647 44648 return null; 44649 }; 44650 44651 var parseAacTimestamp = function parseAacTimestamp(packet) { 44652 var frameStart, frameSize, frame, frameHeader; // find the start of the first frame and the end of the tag 44653 44654 frameStart = 10; 44655 44656 if (packet[5] & 0x40) { 44657 // advance the frame start past the extended header 44658 frameStart += 4; // header size field 44659 44660 frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14)); 44661 } // parse one or more ID3 frames 44662 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 44663 44664 44665 do { 44666 // determine the number of bytes in this frame 44667 frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8)); 44668 44669 if (frameSize < 1) { 44670 return null; 44671 } 44672 44673 frameHeader = String.fromCharCode(packet[frameStart], packet[frameStart + 1], packet[frameStart + 2], packet[frameStart + 3]); 44674 44675 if (frameHeader === 'PRIV') { 44676 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 44677 44678 for (var i = 0; i < frame.byteLength; i++) { 44679 if (frame[i] === 0) { 44680 var owner = parseIso88591$1(frame,
vendor: 5,833 bytes, lines 44680-44852
446800, i); 44681 44682 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 44683 var d = frame.subarray(i + 1); 44684 var size = (d[3] & 0x01) << 30 | d[4] << 22 | d[5] << 14 | d[6] << 6 | d[7] >>> 2; 44685 size *= 4; 44686 size += d[7] & 0x03; 44687 return size; 44688 } 44689 44690 break; 44691 } 44692 } 44693 } 44694 44695 frameStart += 10; // advance past the frame header 44696 44697 frameStart += frameSize; // advance past the frame body 44698 } while (frameStart < packet.byteLength); 44699 44700 return null; 44701 }; 44702 44703 var utils = { 44704 isLikelyAacData: isLikelyAacData, 44705 parseId3TagSize: parseId3TagSize, 44706 parseAdtsSize: parseAdtsSize, 44707 parseType: parseType, 44708 parseSampleRate: parseSampleRate, 44709 parseAacTimestamp: parseAacTimestamp 44710 }; // Constants 44711 44712 var _AacStream; 44713 /** 44714 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 44715 */ 44716 44717 44718 _AacStream = function AacStream() { 44719 var everything = new Uint8Array(), 44720 timeStamp = 0; 44721 44722 _AacStream.prototype.init.call(this); 44723 44724 this.setTimestamp = function (timestamp) { 44725 timeStamp = timestamp; 44726 }; 44727 44728 this.push = function (bytes) { 44729 var frameSize = 0, 44730 byteIndex = 0, 44731 bytesLeft, 44732 chunk, 44733 packet, 44734 tempLength; // If there are bytes remaining from the last segment, prepend them to the 44735 // bytes that were pushed in 44736 44737 if (everything.length) { 44738 tempLength = everything.length; 44739 everything = new Uint8Array(bytes.byteLength + tempLength); 44740 everything.set(everything.subarray(0, tempLength)); 44741 everything.set(bytes, tempLength); 44742 } else { 44743 everything = bytes; 44744 } 44745 44746 while (everything.length - byteIndex >= 3) { 44747 if (everything[byteIndex] === 'I'.charCodeAt(0) && everything[byteIndex + 1] === 'D'.charCodeAt(0) && everything[byteIndex + 2] === '3'.charCodeAt(0)) { 44748 // Exit early because we don't have enough to parse 44749 // the ID3 tag header 44750 if (everything.length - byteIndex < 10) { 44751 break; 44752 } // check framesize 44753 44754 44755 frameSize = utils.parseId3TagSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 44756 // to emit a full packet 44757 // Add to byteIndex to support multiple ID3 tags in sequence 44758 44759 if (byteIndex + frameSize > everything.length) { 44760 break; 44761 } 44762 44763 chunk = { 44764 type: 'timed-metadata', 44765 data: everything.subarray(byteIndex, byteIndex + frameSize) 44766 }; 44767 this.trigger('data', chunk); 44768 byteIndex += frameSize; 44769 continue; 44770 } else if ((everything[byteIndex] & 0xff) === 0xff && (everything[byteIndex + 1] & 0xf0) === 0xf0) { 44771 // Exit early because we don't have enough to parse 44772 // the ADTS frame header 44773 if (everything.length - byteIndex < 7) { 44774 break; 44775 } 44776 44777 frameSize = utils.parseAdtsSize(everything, byteIndex); // Exit early if we don't have enough in the buffer 44778 // to emit a full packet 44779 44780 if (byteIndex + frameSize > everything.length) { 44781 break; 44782 } 44783 44784 packet = { 44785 type: 'audio', 44786 data: everything.subarray(byteIndex, byteIndex + frameSize), 44787 pts: timeStamp, 44788 dts: timeStamp 44789 }; 44790 this.trigger('data', packet); 44791 byteIndex += frameSize; 44792 continue; 44793 } 44794 44795 byteIndex++; 44796 } 44797 44798 bytesLeft = everything.length - byteIndex; 44799 44800 if (bytesLeft > 0) { 44801 everything = everything.subarray(byteIndex); 44802 } else { 44803 everything = new Uint8Array(); 44804 } 44805 }; 44806 44807 this.reset = function () { 44808 everything = new Uint8Array(); 44809 this.trigger('reset'); 44810 }; 44811 44812 this.endTimeline = function () { 44813 everything = new Uint8Array(); 44814 this.trigger('endedtimeline'); 44815 }; 44816 }; 44817 44818 _AacStream.prototype = new stream(); 44819 var aac = _AacStream; 44820 var H264Stream = h264.H264Stream; 44821 var isLikelyAacData$1 = utils.isLikelyAacData; 44822 var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS; // constants 44823 44824 var AUDIO_PROPERTIES = ['audioobjecttype', 'channelcount', 'samplerate', 'samplingfrequencyindex', 'samplesize']; 44825 var VIDEO_PROPERTIES = ['width', 'height', 'profileIdc', 'levelIdc', 'profileCompatibility', 'sarRatio']; // object types 44826 44827 var _VideoSegmentStream, _AudioSegmentStream, _Transmuxer, _CoalesceStream; 44828 /** 44829 * Compare two arrays (even typed) for same-ness 44830 */ 44831 44832 44833 var arrayEquals = function arrayEquals(a, b) { 44834 var i; 44835 44836 if (a.length !== b.length) { 44837 return false; 44838 } // compare the value of each element in the array 44839 44840 44841 for (i = 0; i < a.length; i++) { 44842 if (a[i] !== b[i]) { 44843 return false; 44844 } 44845 } 44846 44847 return true; 44848 }; 44849 44850 var generateVideoSegmentTimingInfo = function generateVideoSegmentTimingInfo(baseMediaDecodeTime, startDts, startPts, endDts, endPts, prependedContentDuration) { 44851 var ptsOffsetFromDts = startPts - startDts, 44852 decodeDuration = endDts - startDts,
vendor: 6,058 bytes, lines 44853-44995
44853 presentationDuration = endPts - startPts; // The PTS and DTS values are based on the actual stream times from the segment, 44854 // however, the player time values will reflect a start from the baseMediaDecodeTime. 44855 // In order to provide relevant values for the player times, base timing info on the 44856 // baseMediaDecodeTime and the DTS and PTS durations of the segment. 44857 44858 return { 44859 start: { 44860 dts: baseMediaDecodeTime, 44861 pts: baseMediaDecodeTime + ptsOffsetFromDts 44862 }, 44863 end: { 44864 dts: baseMediaDecodeTime + decodeDuration, 44865 pts: baseMediaDecodeTime + presentationDuration 44866 }, 44867 prependedContentDuration: prependedContentDuration, 44868 baseMediaDecodeTime: baseMediaDecodeTime 44869 }; 44870 }; 44871 /** 44872 * Constructs a single-track, ISO BMFF media segment from AAC data 44873 * events. The output of this stream can be fed to a SourceBuffer 44874 * configured with a suitable initialization segment. 44875 * @param track {object} track metadata configuration 44876 * @param options {object} transmuxer options object 44877 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 44878 * in the source; false to adjust the first segment to start at 0. 44879 */ 44880 44881 44882 _AudioSegmentStream = function AudioSegmentStream(track, options) { 44883 var adtsFrames = [], 44884 sequenceNumber = 0, 44885 earliestAllowedDts = 0, 44886 audioAppendStartTs = 0, 44887 videoBaseMediaDecodeTime = Infinity; 44888 options = options || {}; 44889 44890 _AudioSegmentStream.prototype.init.call(this); 44891 44892 this.push = function (data) { 44893 trackDecodeInfo.collectDtsInfo(track, data); 44894 44895 if (track) { 44896 AUDIO_PROPERTIES.forEach(function (prop) { 44897 track[prop] = data[prop]; 44898 }); 44899 } // buffer audio data until end() is called 44900 44901 44902 adtsFrames.push(data); 44903 }; 44904 44905 this.setEarliestDts = function (earliestDts) { 44906 earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; 44907 }; 44908 44909 this.setVideoBaseMediaDecodeTime = function (baseMediaDecodeTime) { 44910 videoBaseMediaDecodeTime = baseMediaDecodeTime; 44911 }; 44912 44913 this.setAudioAppendStart = function (timestamp) { 44914 audioAppendStartTs = timestamp; 44915 }; 44916 44917 this.flush = function () { 44918 var frames, moof, mdat, boxes, frameDuration; // return early if no audio data has been observed 44919 44920 if (adtsFrames.length === 0) { 44921 this.trigger('done', 'AudioSegmentStream'); 44922 return; 44923 } 44924 44925 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts); 44926 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 44927 audioFrameUtils.prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime); // we have to build the index from byte locations to 44928 // samples (that is, adts frames) in the audio data 44929 44930 track.samples = audioFrameUtils.generateSampleTable(frames); // concatenate the audio data to constuct the mdat 44931 44932 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames)); 44933 adtsFrames = []; 44934 moof = mp4Generator.moof(sequenceNumber, [track]); 44935 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // bump the sequence number for next time 44936 44937 sequenceNumber++; 44938 boxes.set(moof); 44939 boxes.set(mdat, moof.byteLength); 44940 trackDecodeInfo.clearDtsInfo(track); 44941 frameDuration = Math.ceil(ONE_SECOND_IN_TS$3 * 1024 / track.samplerate); // TODO this check was added to maintain backwards compatibility (particularly with 44942 // tests) on adding the timingInfo event. However, it seems unlikely that there's a 44943 // valid use-case where an init segment/data should be triggered without associated 44944 // frames. Leaving for now, but should be looked into. 44945 44946 if (frames.length) { 44947 this.trigger('timingInfo', { 44948 start: frames[0].dts, 44949 end: frames[0].dts + frames.length * frameDuration 44950 }); 44951 } 44952 44953 this.trigger('data', { 44954 track: track, 44955 boxes: boxes 44956 }); 44957 this.trigger('done', 'AudioSegmentStream'); 44958 }; 44959 44960 this.reset = function () { 44961 trackDecodeInfo.clearDtsInfo(track); 44962 adtsFrames = []; 44963 this.trigger('reset'); 44964 }; 44965 }; 44966 44967 _AudioSegmentStream.prototype = new stream(); 44968 /** 44969 * Constructs a single-track, ISO BMFF media segment from H264 data 44970 * events. The output of this stream can be fed to a SourceBuffer 44971 * configured with a suitable initialization segment. 44972 * @param track {object} track metadata configuration 44973 * @param options {object} transmuxer options object 44974 * @param options.alignGopsAtEnd {boolean} If true, start from the end of the 44975 * gopsToAlignWith list when attempting to align gop pts 44976 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 44977 * in the source; false to adjust the first segment to start at 0. 44978 */ 44979 44980 _VideoSegmentStream = function VideoSegmentStream(track, options) { 44981 var sequenceNumber = 0, 44982 nalUnits = [], 44983 gopsToAlignWith = [], 44984 config, 44985 pps; 44986 options = options || {}; 44987 44988 _VideoSegmentStream.prototype.init.call(this); 44989 44990 delete track.minPTS; 44991 this.gopCache_ = []; 44992 /** 44993 * Constructs a ISO BMFF segment given H264 nalUnits 44994 * @param {Object} nalUnit A data event representing a nalUnit 44995 * @param {String} nalUnit.nalUnitType
vendor: 12,823 bytes, lines 44996-45319
44996 * @param {Object} nalUnit.config Properties for a mp4 track 44997 * @param {Uint8Array} nalUnit.data The nalUnit bytes 44998 * @see lib/codecs/h264.js 44999 **/ 45000 45001 this.push = function (nalUnit) { 45002 trackDecodeInfo.collectDtsInfo(track, nalUnit); // record the track config 45003 45004 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 45005 config = nalUnit.config; 45006 track.sps = [nalUnit.data]; 45007 VIDEO_PROPERTIES.forEach(function (prop) { 45008 track[prop] = config[prop]; 45009 }, this); 45010 } 45011 45012 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && !pps) { 45013 pps = nalUnit.data; 45014 track.pps = [nalUnit.data]; 45015 } // buffer video until flush() is called 45016 45017 45018 nalUnits.push(nalUnit); 45019 }; 45020 /** 45021 * Pass constructed ISO BMFF track and boxes on to the 45022 * next stream in the pipeline 45023 **/ 45024 45025 45026 this.flush = function () { 45027 var frames, 45028 gopForFusion, 45029 gops, 45030 moof, 45031 mdat, 45032 boxes, 45033 prependedContentDuration = 0, 45034 firstGop, 45035 lastGop; // Throw away nalUnits at the start of the byte stream until 45036 // we find the first AUD 45037 45038 while (nalUnits.length) { 45039 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 45040 break; 45041 } 45042 45043 nalUnits.shift(); 45044 } // Return early if no video data has been observed 45045 45046 45047 if (nalUnits.length === 0) { 45048 this.resetStream_(); 45049 this.trigger('done', 'VideoSegmentStream'); 45050 return; 45051 } // Organize the raw nal-units into arrays that represent 45052 // higher-level constructs such as frames and gops 45053 // (group-of-pictures) 45054 45055 45056 frames = frameUtils.groupNalsIntoFrames(nalUnits); 45057 gops = frameUtils.groupFramesIntoGops(frames); // If the first frame of this fragment is not a keyframe we have 45058 // a problem since MSE (on Chrome) requires a leading keyframe. 45059 // 45060 // We have two approaches to repairing this situation: 45061 // 1) GOP-FUSION: 45062 // This is where we keep track of the GOPS (group-of-pictures) 45063 // from previous fragments and attempt to find one that we can 45064 // prepend to the current fragment in order to create a valid 45065 // fragment. 45066 // 2) KEYFRAME-PULLING: 45067 // Here we search for the first keyframe in the fragment and 45068 // throw away all the frames between the start of the fragment 45069 // and that keyframe. We then extend the duration and pull the 45070 // PTS of the keyframe forward so that it covers the time range 45071 // of the frames that were disposed of. 45072 // 45073 // #1 is far prefereable over #2 which can cause "stuttering" but 45074 // requires more things to be just right. 45075 45076 if (!gops[0][0].keyFrame) { 45077 // Search for a gop for fusion from our gopCache 45078 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 45079 45080 if (gopForFusion) { 45081 // in order to provide more accurate timing information about the segment, save 45082 // the number of seconds prepended to the original segment due to GOP fusion 45083 prependedContentDuration = gopForFusion.duration; 45084 gops.unshift(gopForFusion); // Adjust Gops' metadata to account for the inclusion of the 45085 // new gop at the beginning 45086 45087 gops.byteLength += gopForFusion.byteLength; 45088 gops.nalCount += gopForFusion.nalCount; 45089 gops.pts = gopForFusion.pts; 45090 gops.dts = gopForFusion.dts; 45091 gops.duration += gopForFusion.duration; 45092 } else { 45093 // If we didn't find a candidate gop fall back to keyframe-pulling 45094 gops = frameUtils.extendFirstKeyFrame(gops); 45095 } 45096 } // Trim gops to align with gopsToAlignWith 45097 45098 45099 if (gopsToAlignWith.length) { 45100 var alignedGops; 45101 45102 if (options.alignGopsAtEnd) { 45103 alignedGops = this.alignGopsAtEnd_(gops); 45104 } else { 45105 alignedGops = this.alignGopsAtStart_(gops); 45106 } 45107 45108 if (!alignedGops) { 45109 // save all the nals in the last GOP into the gop cache 45110 this.gopCache_.unshift({ 45111 gop: gops.pop(), 45112 pps: track.pps, 45113 sps: track.sps 45114 }); // Keep a maximum of 6 GOPs in the cache 45115 45116 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 45117 45118 nalUnits = []; // return early no gops can be aligned with desired gopsToAlignWith 45119 45120 this.resetStream_(); 45121 this.trigger('done', 'VideoSegmentStream'); 45122 return; 45123 } // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct 45124 // when recalculated before sending off to CoalesceStream 45125 45126 45127 trackDecodeInfo.clearDtsInfo(track); 45128 gops = alignedGops; 45129 } 45130 45131 trackDecodeInfo.collectDtsInfo(track, gops); // First, we have to build the index from byte locations to 45132 // samples (that is, frames) in the video data 45133 45134 track.samples = frameUtils.generateSampleTable(gops); // Concatenate the video data and construct the mdat 45135 45136 mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops)); 45137 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(track, options.keepOriginalTimestamps); 45138 this.trigger('processedGopsInfo', gops.map(function (gop) { 45139 return { 45140 pts: gop.pts, 45141 dts: gop.dts, 45142 byteLength: gop.byteLength 45143 }; 45144 })); 45145 firstGop = gops[0]; 45146 lastGop = gops[gops.length - 1]; 45147 this.trigger('segmentTimingInfo', generateVideoSegmentTimingInfo(track.baseMediaDecodeTime, firstGop.dts, firstGop.pts, lastGop.dts + lastGop.duration, lastGop.pts + lastGop.duration, prependedContentDuration)); 45148 this.trigger('timingInfo', { 45149 start: gops[0].dts, 45150 end: gops[gops.length - 1].dts + gops[gops.length - 1].duration 45151 }); // save all the nals in the last GOP into the gop cache 45152 45153 this.gopCache_.unshift({ 45154 gop: gops.pop(), 45155 pps: track.pps, 45156 sps: track.sps 45157 }); // Keep a maximum of 6 GOPs in the cache 45158 45159 this.gopCache_.length = Math.min(6, this.gopCache_.length); // Clear nalUnits 45160 45161 nalUnits = []; 45162 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 45163 this.trigger('timelineStartInfo', track.timelineStartInfo); 45164 moof = mp4Generator.moof(sequenceNumber, [track]); // it would be great to allocate this array up front instead of 45165 // throwing away hundreds of media segment fragments 45166 45167 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); // Bump the sequence number for next time 45168 45169 sequenceNumber++; 45170 boxes.set(moof); 45171 boxes.set(mdat, moof.byteLength); 45172 this.trigger('data', { 45173 track: track, 45174 boxes: boxes 45175 }); 45176 this.resetStream_(); // Continue with the flush process now 45177 45178 this.trigger('done', 'VideoSegmentStream'); 45179 }; 45180 45181 this.reset = function () { 45182 this.resetStream_(); 45183 nalUnits = []; 45184 this.gopCache_.length = 0; 45185 gopsToAlignWith.length = 0; 45186 this.trigger('reset'); 45187 }; 45188 45189 this.resetStream_ = function () { 45190 trackDecodeInfo.clearDtsInfo(track); // reset config and pps because they may differ across segments 45191 // for instance, when we are rendition switching 45192 45193 config = undefined; 45194 pps = undefined; 45195 }; // Search for a candidate Gop for gop-fusion from the gop cache and 45196 // return it or return null if no good candidate was found 45197 45198 45199 this.getGopForFusion_ = function (nalUnit) { 45200 var halfSecond = 45000, 45201 // Half-a-second in a 90khz clock 45202 allowableOverlap = 10000, 45203 // About 3 frames @ 30fps 45204 nearestDistance = Infinity, 45205 dtsDistance, 45206 nearestGopObj, 45207 currentGop, 45208 currentGopObj, 45209 i; // Search for the GOP nearest to the beginning of this nal unit 45210 45211 for (i = 0; i < this.gopCache_.length; i++) { 45212 currentGopObj = this.gopCache_[i]; 45213 currentGop = currentGopObj.gop; // Reject Gops with different SPS or PPS 45214 45215 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 45216 continue; 45217 } // Reject Gops that would require a negative baseMediaDecodeTime 45218 45219 45220 if (currentGop.dts < track.timelineStartInfo.dts) { 45221 continue; 45222 } // The distance between the end of the gop and the start of the nalUnit 45223 45224 45225 dtsDistance = nalUnit.dts - currentGop.dts - currentGop.duration; // Only consider GOPS that start before the nal unit and end within 45226 // a half-second of the nal unit 45227 45228 if (dtsDistance >= -allowableOverlap && dtsDistance <= halfSecond) { 45229 // Always use the closest GOP we found if there is more than 45230 // one candidate 45231 if (!nearestGopObj || nearestDistance > dtsDistance) { 45232 nearestGopObj = currentGopObj; 45233 nearestDistance = dtsDistance; 45234 } 45235 } 45236 } 45237 45238 if (nearestGopObj) { 45239 return nearestGopObj.gop; 45240 } 45241 45242 return null; 45243 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 45244 // of gopsToAlignWith starting from the START of the list 45245 45246 45247 this.alignGopsAtStart_ = function (gops) { 45248 var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops; 45249 byteLength = gops.byteLength; 45250 nalCount = gops.nalCount; 45251 duration = gops.duration; 45252 alignIndex = gopIndex = 0; 45253 45254 while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) { 45255 align = gopsToAlignWith[alignIndex]; 45256 gop = gops[gopIndex]; 45257 45258 if (align.pts === gop.pts) { 45259 break; 45260 } 45261 45262 if (gop.pts > align.pts) { 45263 // this current gop starts after the current gop we want to align on, so increment 45264 // align index 45265 alignIndex++; 45266 continue; 45267 } // current gop starts before the current gop we want to align on. so increment gop 45268 // index 45269 45270 45271 gopIndex++; 45272 byteLength -= gop.byteLength; 45273 nalCount -= gop.nalCount; 45274 duration -= gop.duration; 45275 } 45276 45277 if (gopIndex === 0) { 45278 // no gops to trim 45279 return gops; 45280 } 45281 45282 if (gopIndex === gops.length) { 45283 // all gops trimmed, skip appending all gops 45284 return null; 45285 } 45286 45287 alignedGops = gops.slice(gopIndex); 45288 alignedGops.byteLength = byteLength; 45289 alignedGops.duration = duration; 45290 alignedGops.nalCount = nalCount; 45291 alignedGops.pts = alignedGops[0].pts; 45292 alignedGops.dts = alignedGops[0].dts; 45293 return alignedGops; 45294 }; // trim gop list to the first gop found that has a matching pts with a gop in the list 45295 // of gopsToAlignWith starting from the END of the list 45296 45297 45298 this.alignGopsAtEnd_ = function (gops) { 45299 var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound; 45300 alignIndex = gopsToAlignWith.length - 1; 45301 gopIndex = gops.length - 1; 45302 alignEndIndex = null; 45303 matchFound = false; 45304 45305 while (alignIndex >= 0 && gopIndex >= 0) { 45306 align = gopsToAlignWith[alignIndex]; 45307 gop = gops[gopIndex]; 45308 45309 if (align.pts === gop.pts) { 45310 matchFound = true; 45311 break; 45312 } 45313 45314 if (align.pts > gop.pts) { 45315 alignIndex--; 45316 continue; 45317 } 45318 45319 if (alignIndex === gopsToAlignWith.length - 1) {
vendor: 4,564 bytes, lines 45320-45450
45320 // gop.pts is greater than the last alignment candidate. If no match is found 45321 // by the end of this loop, we still want to append gops that come after this 45322 // point 45323 alignEndIndex = gopIndex; 45324 } 45325 45326 gopIndex--; 45327 } 45328 45329 if (!matchFound && alignEndIndex === null) { 45330 return null; 45331 } 45332 45333 var trimIndex; 45334 45335 if (matchFound) { 45336 trimIndex = gopIndex; 45337 } else { 45338 trimIndex = alignEndIndex; 45339 } 45340 45341 if (trimIndex === 0) { 45342 return gops; 45343 } 45344 45345 var alignedGops = gops.slice(trimIndex); 45346 var metadata = alignedGops.reduce(function (total, gop) { 45347 total.byteLength += gop.byteLength; 45348 total.duration += gop.duration; 45349 total.nalCount += gop.nalCount; 45350 return total; 45351 }, { 45352 byteLength: 0, 45353 duration: 0, 45354 nalCount: 0 45355 }); 45356 alignedGops.byteLength = metadata.byteLength; 45357 alignedGops.duration = metadata.duration; 45358 alignedGops.nalCount = metadata.nalCount; 45359 alignedGops.pts = alignedGops[0].pts; 45360 alignedGops.dts = alignedGops[0].dts; 45361 return alignedGops; 45362 }; 45363 45364 this.alignGopsWith = function (newGopsToAlignWith) { 45365 gopsToAlignWith = newGopsToAlignWith; 45366 }; 45367 }; 45368 45369 _VideoSegmentStream.prototype = new stream(); 45370 /** 45371 * A Stream that can combine multiple streams (ie. audio & video) 45372 * into a single output segment for MSE. Also supports audio-only 45373 * and video-only streams. 45374 * @param options {object} transmuxer options object 45375 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps 45376 * in the source; false to adjust the first segment to start at media timeline start. 45377 */ 45378 45379 _CoalesceStream = function CoalesceStream(options, metadataStream) { 45380 // Number of Tracks per output segment 45381 // If greater than 1, we combine multiple 45382 // tracks into a single segment 45383 this.numberOfTracks = 0; 45384 this.metadataStream = metadataStream; 45385 options = options || {}; 45386 45387 if (typeof options.remux !== 'undefined') { 45388 this.remuxTracks = !!options.remux; 45389 } else { 45390 this.remuxTracks = true; 45391 } 45392 45393 if (typeof options.keepOriginalTimestamps === 'boolean') { 45394 this.keepOriginalTimestamps = options.keepOriginalTimestamps; 45395 } else { 45396 this.keepOriginalTimestamps = false; 45397 } 45398 45399 this.pendingTracks = []; 45400 this.videoTrack = null; 45401 this.pendingBoxes = []; 45402 this.pendingCaptions = []; 45403 this.pendingMetadata = []; 45404 this.pendingBytes = 0; 45405 this.emittedTracks = 0; 45406 45407 _CoalesceStream.prototype.init.call(this); // Take output from multiple 45408 45409 45410 this.push = function (output) { 45411 // buffer incoming captions until the associated video segment 45412 // finishes 45413 if (output.text) { 45414 return this.pendingCaptions.push(output); 45415 } // buffer incoming id3 tags until the final flush 45416 45417 45418 if (output.frames) { 45419 return this.pendingMetadata.push(output); 45420 } // Add this track to the list of pending tracks and store 45421 // important information required for the construction of 45422 // the final segment 45423 45424 45425 this.pendingTracks.push(output.track); 45426 this.pendingBytes += output.boxes.byteLength; // TODO: is there an issue for this against chrome? 45427 // We unshift audio and push video because 45428 // as of Chrome 75 when switching from 45429 // one init segment to another if the video 45430 // mdat does not appear after the audio mdat 45431 // only audio will play for the duration of our transmux. 45432 45433 if (output.track.type === 'video') { 45434 this.videoTrack = output.track; 45435 this.pendingBoxes.push(output.boxes); 45436 } 45437 45438 if (output.track.type === 'audio') { 45439 this.audioTrack = output.track; 45440 this.pendingBoxes.unshift(output.boxes); 45441 } 45442 }; 45443 }; 45444 45445 _CoalesceStream.prototype = new stream(); 45446 45447 _CoalesceStream.prototype.flush = function (flushSource) { 45448 var offset = 0, 45449 event = { 45450 captions: [],
vendor: 4,830 bytes, lines 45451-45556
45451 captionStreams: {}, 45452 metadata: [], 45453 info: {} 45454 }, 45455 caption, 45456 id3, 45457 initSegment, 45458 timelineStartPts = 0, 45459 i; 45460 45461 if (this.pendingTracks.length < this.numberOfTracks) { 45462 if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') { 45463 // Return because we haven't received a flush from a data-generating 45464 // portion of the segment (meaning that we have only recieved meta-data 45465 // or captions.) 45466 return; 45467 } else if (this.remuxTracks) { 45468 // Return until we have enough tracks from the pipeline to remux (if we 45469 // are remuxing audio and video into a single MP4) 45470 return; 45471 } else if (this.pendingTracks.length === 0) { 45472 // In the case where we receive a flush without any data having been 45473 // received we consider it an emitted track for the purposes of coalescing 45474 // `done` events. 45475 // We do this for the case where there is an audio and video track in the 45476 // segment but no audio data. (seen in several playlists with alternate 45477 // audio tracks and no audio present in the main TS segments.) 45478 this.emittedTracks++; 45479 45480 if (this.emittedTracks >= this.numberOfTracks) { 45481 this.trigger('done'); 45482 this.emittedTracks = 0; 45483 } 45484 45485 return; 45486 } 45487 } 45488 45489 if (this.videoTrack) { 45490 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 45491 VIDEO_PROPERTIES.forEach(function (prop) { 45492 event.info[prop] = this.videoTrack[prop]; 45493 }, this); 45494 } else if (this.audioTrack) { 45495 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 45496 AUDIO_PROPERTIES.forEach(function (prop) { 45497 event.info[prop] = this.audioTrack[prop]; 45498 }, this); 45499 } 45500 45501 if (this.videoTrack || this.audioTrack) { 45502 if (this.pendingTracks.length === 1) { 45503 event.type = this.pendingTracks[0].type; 45504 } else { 45505 event.type = 'combined'; 45506 } 45507 45508 this.emittedTracks += this.pendingTracks.length; 45509 initSegment = mp4Generator.initSegment(this.pendingTracks); // Create a new typed array to hold the init segment 45510 45511 event.initSegment = new Uint8Array(initSegment.byteLength); // Create an init segment containing a moov 45512 // and track definitions 45513 45514 event.initSegment.set(initSegment); // Create a new typed array to hold the moof+mdats 45515 45516 event.data = new Uint8Array(this.pendingBytes); // Append each moof+mdat (one per track) together 45517 45518 for (i = 0; i < this.pendingBoxes.length; i++) { 45519 event.data.set(this.pendingBoxes[i], offset); 45520 offset += this.pendingBoxes[i].byteLength; 45521 } // Translate caption PTS times into second offsets to match the 45522 // video timeline for the segment, and add track info 45523 45524 45525 for (i = 0; i < this.pendingCaptions.length; i++) { 45526 caption = this.pendingCaptions[i]; 45527 caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, this.keepOriginalTimestamps); 45528 caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, this.keepOriginalTimestamps); 45529 event.captionStreams[caption.stream] = true; 45530 event.captions.push(caption); 45531 } // Translate ID3 frame PTS times into second offsets to match the 45532 // video timeline for the segment 45533 45534 45535 for (i = 0; i < this.pendingMetadata.length; i++) { 45536 id3 = this.pendingMetadata[i]; 45537 id3.cueTime = clock.metadataTsToSeconds(id3.pts, timelineStartPts, this.keepOriginalTimestamps); 45538 event.metadata.push(id3); 45539 } // We add this to every single emitted segment even though we only need 45540 // it for the first 45541 45542 45543 event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state 45544 45545 this.pendingTracks.length = 0; 45546 this.videoTrack = null; 45547 this.pendingBoxes.length = 0; 45548 this.pendingCaptions.length = 0; 45549 this.pendingBytes = 0; 45550 this.pendingMetadata.length = 0; // Emit the built segment 45551 // We include captions and ID3 tags for backwards compatibility, 45552 // ideally we should send only video and audio in the data event 45553 45554 this.trigger('data', event); // Emit each caption to the outside world 45555 // Ideally, this would happen immediately on parsing captions, 45556 // but we need to ensure that video data is sent back first
vendor: 14,618 bytes, lines 45557-45889
45557 // so that caption timing can be adjusted to match video timing 45558 45559 for (i = 0; i < event.captions.length; i++) { 45560 caption = event.captions[i]; 45561 this.trigger('caption', caption); 45562 } // Emit each id3 tag to the outside world 45563 // Ideally, this would happen immediately on parsing the tag, 45564 // but we need to ensure that video data is sent back first 45565 // so that ID3 frame timing can be adjusted to match video timing 45566 45567 45568 for (i = 0; i < event.metadata.length; i++) { 45569 id3 = event.metadata[i]; 45570 this.trigger('id3Frame', id3); 45571 } 45572 } // Only emit `done` if all tracks have been flushed and emitted 45573 45574 45575 if (this.emittedTracks >= this.numberOfTracks) { 45576 this.trigger('done'); 45577 this.emittedTracks = 0; 45578 } 45579 }; 45580 45581 _CoalesceStream.prototype.setRemux = function (val) { 45582 this.remuxTracks = val; 45583 }; 45584 /** 45585 * A Stream that expects MP2T binary data as input and produces 45586 * corresponding media segments, suitable for use with Media Source 45587 * Extension (MSE) implementations that support the ISO BMFF byte 45588 * stream format, like Chrome. 45589 */ 45590 45591 45592 _Transmuxer = function Transmuxer(options) { 45593 var self = this, 45594 hasFlushed = true, 45595 videoTrack, 45596 audioTrack; 45597 45598 _Transmuxer.prototype.init.call(this); 45599 45600 options = options || {}; 45601 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 45602 this.transmuxPipeline_ = {}; 45603 45604 this.setupAacPipeline = function () { 45605 var pipeline = {}; 45606 this.transmuxPipeline_ = pipeline; 45607 pipeline.type = 'aac'; 45608 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 45609 45610 pipeline.aacStream = new aac(); 45611 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio'); 45612 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata'); 45613 pipeline.adtsStream = new adts(); 45614 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 45615 pipeline.headOfPipeline = pipeline.aacStream; 45616 pipeline.aacStream.pipe(pipeline.audioTimestampRolloverStream).pipe(pipeline.adtsStream); 45617 pipeline.aacStream.pipe(pipeline.timedMetadataTimestampRolloverStream).pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); 45618 pipeline.metadataStream.on('timestamp', function (frame) { 45619 pipeline.aacStream.setTimestamp(frame.timeStamp); 45620 }); 45621 pipeline.aacStream.on('data', function (data) { 45622 if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { 45623 audioTrack = audioTrack || { 45624 timelineStartInfo: { 45625 baseMediaDecodeTime: self.baseMediaDecodeTime 45626 }, 45627 codec: 'adts', 45628 type: 'audio' 45629 }; // hook up the audio segment stream to the first track with aac data 45630 45631 pipeline.coalesceStream.numberOfTracks++; 45632 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 45633 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 45634 45635 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 45636 } // emit pmt info 45637 45638 45639 self.trigger('trackinfo', { 45640 hasAudio: !!audioTrack, 45641 hasVideo: !!videoTrack 45642 }); 45643 }); // Re-emit any data coming from the coalesce stream to the outside world 45644 45645 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); // Let the consumer know we have finished flushing the entire pipeline 45646 45647 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 45648 }; 45649 45650 this.setupTsPipeline = function () { 45651 var pipeline = {}; 45652 this.transmuxPipeline_ = pipeline; 45653 pipeline.type = 'ts'; 45654 pipeline.metadataStream = new m2ts_1.MetadataStream(); // set up the parsing pipeline 45655 45656 pipeline.packetStream = new m2ts_1.TransportPacketStream(); 45657 pipeline.parseStream = new m2ts_1.TransportParseStream(); 45658 pipeline.elementaryStream = new m2ts_1.ElementaryStream(); 45659 pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream(); 45660 pipeline.adtsStream = new adts(); 45661 pipeline.h264Stream = new H264Stream(); 45662 pipeline.captionStream = new m2ts_1.CaptionStream(); 45663 pipeline.coalesceStream = new _CoalesceStream(options, pipeline.metadataStream); 45664 pipeline.headOfPipeline = pipeline.packetStream; // disassemble MPEG2-TS packets into elementary streams 45665 45666 pipeline.packetStream.pipe(pipeline.parseStream).pipe(pipeline.elementaryStream).pipe(pipeline.timestampRolloverStream); // !!THIS ORDER IS IMPORTANT!! 45667 // demux the streams 45668 45669 pipeline.timestampRolloverStream.pipe(pipeline.h264Stream); 45670 pipeline.timestampRolloverStream.pipe(pipeline.adtsStream); 45671 pipeline.timestampRolloverStream.pipe(pipeline.metadataStream).pipe(pipeline.coalesceStream); // Hook up CEA-608/708 caption stream 45672 45673 pipeline.h264Stream.pipe(pipeline.captionStream).pipe(pipeline.coalesceStream); 45674 pipeline.elementaryStream.on('data', function (data) { 45675 var i; 45676 45677 if (data.type === 'metadata') { 45678 i = data.tracks.length; // scan the tracks listed in the metadata 45679 45680 while (i--) { 45681 if (!videoTrack && data.tracks[i].type === 'video') { 45682 videoTrack = data.tracks[i]; 45683 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 45684 } else if (!audioTrack && data.tracks[i].type === 'audio') { 45685 audioTrack = data.tracks[i]; 45686 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 45687 } 45688 } // hook up the video segment stream to the first track with h264 data 45689 45690 45691 if (videoTrack && !pipeline.videoSegmentStream) { 45692 pipeline.coalesceStream.numberOfTracks++; 45693 pipeline.videoSegmentStream = new _VideoSegmentStream(videoTrack, options); 45694 pipeline.videoSegmentStream.on('timelineStartInfo', function (timelineStartInfo) { 45695 // When video emits timelineStartInfo data after a flush, we forward that 45696 // info to the AudioSegmentStream, if it exists, because video timeline 45697 // data takes precedence. 45698 if (audioTrack) { 45699 audioTrack.timelineStartInfo = timelineStartInfo; // On the first segment we trim AAC frames that exist before the 45700 // very earliest DTS we have seen in video because Chrome will 45701 // interpret any video track with a baseMediaDecodeTime that is 45702 // non-zero as a gap. 45703 45704 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); 45705 } 45706 }); 45707 pipeline.videoSegmentStream.on('processedGopsInfo', self.trigger.bind(self, 'gopInfo')); 45708 pipeline.videoSegmentStream.on('segmentTimingInfo', self.trigger.bind(self, 'videoSegmentTimingInfo')); 45709 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function (baseMediaDecodeTime) { 45710 if (audioTrack) { 45711 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 45712 } 45713 }); 45714 pipeline.videoSegmentStream.on('timingInfo', self.trigger.bind(self, 'videoTimingInfo')); // Set up the final part of the video pipeline 45715 45716 pipeline.h264Stream.pipe(pipeline.videoSegmentStream).pipe(pipeline.coalesceStream); 45717 } 45718 45719 if (audioTrack && !pipeline.audioSegmentStream) { 45720 // hook up the audio segment stream to the first track with aac data 45721 pipeline.coalesceStream.numberOfTracks++; 45722 pipeline.audioSegmentStream = new _AudioSegmentStream(audioTrack, options); 45723 pipeline.audioSegmentStream.on('timingInfo', self.trigger.bind(self, 'audioTimingInfo')); // Set up the final part of the audio pipeline 45724 45725 pipeline.adtsStream.pipe(pipeline.audioSegmentStream).pipe(pipeline.coalesceStream); 45726 } // emit pmt info 45727 45728 45729 self.trigger('trackinfo', { 45730 hasAudio: !!audioTrack, 45731 hasVideo: !!videoTrack 45732 }); 45733 } 45734 }); // Re-emit any data coming from the coalesce stream to the outside world 45735 45736 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 45737 pipeline.coalesceStream.on('id3Frame', function (id3Frame) { 45738 id3Frame.dispatchType = pipeline.metadataStream.dispatchType; 45739 self.trigger('id3Frame', id3Frame); 45740 }); 45741 pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption')); // Let the consumer know we have finished flushing the entire pipeline 45742 45743 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 45744 }; // hook up the segment streams once track metadata is delivered 45745 45746 45747 this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) { 45748 var pipeline = this.transmuxPipeline_; 45749 45750 if (!options.keepOriginalTimestamps) { 45751 this.baseMediaDecodeTime = baseMediaDecodeTime; 45752 } 45753 45754 if (audioTrack) { 45755 audioTrack.timelineStartInfo.dts = undefined; 45756 audioTrack.timelineStartInfo.pts = undefined; 45757 trackDecodeInfo.clearDtsInfo(audioTrack); 45758 45759 if (!options.keepOriginalTimestamps) { 45760 audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 45761 } 45762 45763 if (pipeline.audioTimestampRolloverStream) { 45764 pipeline.audioTimestampRolloverStream.discontinuity(); 45765 } 45766 } 45767 45768 if (videoTrack) { 45769 if (pipeline.videoSegmentStream) { 45770 pipeline.videoSegmentStream.gopCache_ = []; 45771 } 45772 45773 videoTrack.timelineStartInfo.dts = undefined; 45774 videoTrack.timelineStartInfo.pts = undefined; 45775 trackDecodeInfo.clearDtsInfo(videoTrack); 45776 pipeline.captionStream.reset(); 45777 45778 if (!options.keepOriginalTimestamps) { 45779 videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 45780 } 45781 } 45782 45783 if (pipeline.timestampRolloverStream) { 45784 pipeline.timestampRolloverStream.discontinuity(); 45785 } 45786 }; 45787 45788 this.setAudioAppendStart = function (timestamp) { 45789 if (audioTrack) { 45790 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 45791 } 45792 }; 45793 45794 this.setRemux = function (val) { 45795 var pipeline = this.transmuxPipeline_; 45796 options.remux = val; 45797 45798 if (pipeline && pipeline.coalesceStream) { 45799 pipeline.coalesceStream.setRemux(val); 45800 } 45801 }; 45802 45803 this.alignGopsWith = function (gopsToAlignWith) { 45804 if (videoTrack && this.transmuxPipeline_.videoSegmentStream) { 45805 this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith); 45806 } 45807 }; // feed incoming data to the front of the parsing pipeline 45808 45809 45810 this.push = function (data) { 45811 if (hasFlushed) { 45812 var isAac = isLikelyAacData$1(data); 45813 45814 if (isAac && this.transmuxPipeline_.type !== 'aac') { 45815 this.setupAacPipeline(); 45816 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 45817 this.setupTsPipeline(); 45818 } 45819 45820 hasFlushed = false; 45821 } 45822 45823 this.transmuxPipeline_.headOfPipeline.push(data); 45824 }; // flush any buffered data 45825 45826 45827 this.flush = function () { 45828 hasFlushed = true; // Start at the top of the pipeline and flush all pending work 45829 45830 this.transmuxPipeline_.headOfPipeline.flush(); 45831 }; 45832 45833 this.endTimeline = function () { 45834 this.transmuxPipeline_.headOfPipeline.endTimeline(); 45835 }; 45836 45837 this.reset = function () { 45838 if (this.transmuxPipeline_.headOfPipeline) { 45839 this.transmuxPipeline_.headOfPipeline.reset(); 45840 } 45841 }; // Caption data has to be reset when seeking outside buffered range 45842 45843 45844 this.resetCaptions = function () { 45845 if (this.transmuxPipeline_.captionStream) { 45846 this.transmuxPipeline_.captionStream.reset(); 45847 } 45848 }; 45849 }; 45850 45851 _Transmuxer.prototype = new stream(); 45852 var transmuxer = { 45853 Transmuxer: _Transmuxer, 45854 VideoSegmentStream: _VideoSegmentStream, 45855 AudioSegmentStream: _AudioSegmentStream, 45856 AUDIO_PROPERTIES: AUDIO_PROPERTIES, 45857 VIDEO_PROPERTIES: VIDEO_PROPERTIES, 45858 // exported for testing 45859 generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo 45860 }; 45861 45862 var classCallCheck = function classCallCheck(instance, Constructor) { 45863 if (!(instance instanceof Constructor)) { 45864 throw new TypeError("Cannot call a class as a function"); 45865 } 45866 }; 45867 45868 var createClass = function () { 45869 function defineProperties(target, props) { 45870 for (var i = 0; i < props.length; i++) { 45871 var descriptor = props[i]; 45872 descriptor.enumerable = descriptor.enumerable || false; 45873 descriptor.configurable = true; 45874 if ("value" in descriptor) descriptor.writable = true; 45875 Object.defineProperty(target, descriptor.key, descriptor); 45876 } 45877 } 45878 45879 return function (Constructor, protoProps, staticProps) { 45880 if (protoProps) defineProperties(Constructor.prototype, protoProps); 45881 if (staticProps) defineProperties(Constructor, staticProps); 45882 return Constructor; 45883 }; 45884 }(); 45885 /** 45886 * @file transmuxer-worker.js 45887 */ 45888 45889 /**
vendor: 32,238 bytes, lines 45890-46850
45890 * Re-emits transmuxer events by converting them into messages to the 45891 * world outside the worker. 45892 * 45893 * @param {Object} transmuxer the transmuxer to wire events on 45894 * @private 45895 */ 45896 45897 45898 var wireTransmuxerEvents = function wireTransmuxerEvents(self, transmuxer$$1) { 45899 transmuxer$$1.on('data', function (segment) { 45900 // transfer ownership of the underlying ArrayBuffer 45901 // instead of doing a copy to save memory 45902 // ArrayBuffers are transferable but generic TypedArrays are not 45903 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 45904 var initArray = segment.initSegment; 45905 segment.initSegment = { 45906 data: initArray.buffer, 45907 byteOffset: initArray.byteOffset, 45908 byteLength: initArray.byteLength 45909 }; 45910 var typedArray = segment.data; 45911 segment.data = typedArray.buffer; 45912 self.postMessage({ 45913 action: 'data', 45914 segment: segment, 45915 byteOffset: typedArray.byteOffset, 45916 byteLength: typedArray.byteLength 45917 }, [segment.data]); 45918 }); 45919 45920 if (transmuxer$$1.captionStream) { 45921 transmuxer$$1.captionStream.on('data', function (caption) { 45922 self.postMessage({ 45923 action: 'caption', 45924 data: caption 45925 }); 45926 }); 45927 } 45928 45929 transmuxer$$1.on('done', function (data) { 45930 self.postMessage({ 45931 action: 'done' 45932 }); 45933 }); 45934 transmuxer$$1.on('gopInfo', function (gopInfo) { 45935 self.postMessage({ 45936 action: 'gopInfo', 45937 gopInfo: gopInfo 45938 }); 45939 }); 45940 transmuxer$$1.on('videoSegmentTimingInfo', function (videoSegmentTimingInfo) { 45941 self.postMessage({ 45942 action: 'videoSegmentTimingInfo', 45943 videoSegmentTimingInfo: videoSegmentTimingInfo 45944 }); 45945 }); 45946 }; 45947 /** 45948 * All incoming messages route through this hash. If no function exists 45949 * to handle an incoming message, then we ignore the message. 45950 * 45951 * @class MessageHandlers 45952 * @param {Object} options the options to initialize with 45953 */ 45954 45955 45956 var MessageHandlers = function () { 45957 function MessageHandlers(self, options) { 45958 classCallCheck(this, MessageHandlers); 45959 this.options = options || {}; 45960 this.self = self; 45961 this.init(); 45962 } 45963 /** 45964 * initialize our web worker and wire all the events. 45965 */ 45966 45967 45968 createClass(MessageHandlers, [{ 45969 key: 'init', 45970 value: function init() { 45971 if (this.transmuxer) { 45972 this.transmuxer.dispose(); 45973 } 45974 45975 this.transmuxer = new transmuxer.Transmuxer(this.options); 45976 wireTransmuxerEvents(this.self, this.transmuxer); 45977 } 45978 /** 45979 * Adds data (a ts segment) to the start of the transmuxer pipeline for 45980 * processing. 45981 * 45982 * @param {ArrayBuffer} data data to push into the muxer 45983 */ 45984 45985 }, { 45986 key: 'push', 45987 value: function push(data) { 45988 // Cast array buffer to correct type for transmuxer 45989 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 45990 this.transmuxer.push(segment); 45991 } 45992 /** 45993 * Recreate the transmuxer so that the next segment added via `push` 45994 * start with a fresh transmuxer. 45995 */ 45996 45997 }, { 45998 key: 'reset', 45999 value: function reset() { 46000 this.init(); 46001 } 46002 /** 46003 * Set the value that will be used as the `baseMediaDecodeTime` time for the 46004 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 46005 * set relative to the first based on the PTS values. 46006 * 46007 * @param {Object} data used to set the timestamp offset in the muxer 46008 */ 46009 46010 }, { 46011 key: 'setTimestampOffset', 46012 value: function setTimestampOffset(data) { 46013 var timestampOffset = data.timestampOffset || 0; 46014 this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000)); 46015 } 46016 }, { 46017 key: 'setAudioAppendStart', 46018 value: function setAudioAppendStart(data) { 46019 this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000)); 46020 } 46021 /** 46022 * Forces the pipeline to finish processing the last segment and emit it's 46023 * results. 46024 * 46025 * @param {Object} data event data, not really used 46026 */ 46027 46028 }, { 46029 key: 'flush', 46030 value: function flush(data) { 46031 this.transmuxer.flush(); 46032 } 46033 }, { 46034 key: 'resetCaptions', 46035 value: function resetCaptions() { 46036 this.transmuxer.resetCaptions(); 46037 } 46038 }, { 46039 key: 'alignGopsWith', 46040 value: function alignGopsWith(data) { 46041 this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice()); 46042 } 46043 }]); 46044 return MessageHandlers; 46045 }(); 46046 /** 46047 * Our web wroker interface so that things can talk to mux.js 46048 * that will be running in a web worker. the scope is passed to this by 46049 * webworkify. 46050 * 46051 * @param {Object} self the scope for the web worker 46052 */ 46053 46054 46055 var TransmuxerWorker = function TransmuxerWorker(self) { 46056 self.onmessage = function (event) { 46057 if (event.data.action === 'init' && event.data.options) { 46058 this.messageHandlers = new MessageHandlers(self, event.data.options); 46059 return; 46060 } 46061 46062 if (!this.messageHandlers) { 46063 this.messageHandlers = new MessageHandlers(self); 46064 } 46065 46066 if (event.data && event.data.action && event.data.action !== 'init') { 46067 if (this.messageHandlers[event.data.action]) { 46068 this.messageHandlers[event.data.action](event.data); 46069 } 46070 } 46071 }; 46072 }; 46073 46074 var transmuxerWorker = new TransmuxerWorker(self); 46075 return transmuxerWorker; 46076 }(); 46077 }); 46078 /** 46079 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to 46080 * codec strings, or translating codec strings into objects that can be examined. 46081 */ 46082 // Default codec parameters if none were provided for video and/or audio 46083 46084 var defaultCodecs = { 46085 videoCodec: 'avc1', 46086 videoObjectTypeIndicator: '.4d400d', 46087 // AAC-LC 46088 audioProfile: '2' 46089 }; 46090 /** 46091 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 46092 * `avc1.<hhhhhh>` 46093 * 46094 * @param {Array} codecs an array of codec strings to fix 46095 * @return {Array} the translated codec array 46096 * @private 46097 */ 46098 46099 var translateLegacyCodecs = function translateLegacyCodecs(codecs) { 46100 return codecs.map(function (codec) { 46101 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 46102 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 46103 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 46104 return 'avc1.' + profileHex + '00' + avcLevelHex; 46105 }); 46106 }); 46107 }; 46108 /** 46109 * Parses a codec string to retrieve the number of codecs specified, 46110 * the video codec and object type indicator, and the audio profile. 46111 */ 46112 46113 46114 var parseCodecs = function parseCodecs() { 46115 var codecs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 46116 var result = { 46117 codecCount: 0 46118 }; 46119 var parsed = void 0; 46120 result.codecCount = codecs.split(',').length; 46121 result.codecCount = result.codecCount || 2; // parse the video codec 46122 46123 parsed = /(^|\s|,)+(avc[13])([^ ,]*)/i.exec(codecs); 46124 46125 if (parsed) { 46126 result.videoCodec = parsed[2]; 46127 result.videoObjectTypeIndicator = parsed[3]; 46128 } // parse the last field of the audio codec 46129 46130 46131 result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs); 46132 result.audioProfile = result.audioProfile && result.audioProfile[2]; 46133 return result; 46134 }; 46135 /** 46136 * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the 46137 * standard `avc1.<hhhhhh>`. 46138 * 46139 * @param codecString {String} the codec string 46140 * @return {String} the codec string with old apple-style codecs replaced 46141 * 46142 * @private 46143 */ 46144 46145 46146 var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) { 46147 return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) { 46148 return translateLegacyCodecs([match])[0]; 46149 }); 46150 }; 46151 /** 46152 * Build a media mime-type string from a set of parameters 46153 * @param {String} type either 'audio' or 'video' 46154 * @param {String} container either 'mp2t' or 'mp4' 46155 * @param {Array} codecs an array of codec strings to add 46156 * @return {String} a valid media mime-type 46157 */ 46158 46159 46160 var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) { 46161 // The codecs array is filtered so that falsey values are 46162 // dropped and don't cause Array#join to create spurious 46163 // commas 46164 return type + '/' + container + '; codecs="' + codecs.filter(function (c) { 46165 return !!c; 46166 }).join(', ') + '"'; 46167 }; 46168 /** 46169 * Returns the type container based on information in the playlist 46170 * @param {Playlist} media the current media playlist 46171 * @return {String} a valid media container type 46172 */ 46173 46174 46175 var getContainerType = function getContainerType(media) { 46176 // An initialization segment means the media playlist is an iframe 46177 // playlist or is using the mp4 container. We don't currently 46178 // support iframe playlists, so assume this is signalling mp4 46179 // fragments. 46180 if (media.segments && media.segments.length && media.segments[0].map) { 46181 return 'mp4'; 46182 } 46183 46184 return 'mp2t'; 46185 }; 46186 /** 46187 * Returns a set of codec strings parsed from the playlist or the default 46188 * codec strings if no codecs were specified in the playlist 46189 * @param {Playlist} media the current media playlist 46190 * @return {Object} an object with the video and audio codecs 46191 */ 46192 46193 46194 var getCodecs = function getCodecs(media) { 46195 // if the codecs were explicitly specified, use them instead of the 46196 // defaults 46197 var mediaAttributes = media.attributes || {}; 46198 46199 if (mediaAttributes.CODECS) { 46200 return parseCodecs(mediaAttributes.CODECS); 46201 } 46202 46203 return defaultCodecs; 46204 }; 46205 46206 var audioProfileFromDefault = function audioProfileFromDefault(master, audioGroupId) { 46207 if (!master.mediaGroups.AUDIO || !audioGroupId) { 46208 return null; 46209 } 46210 46211 var audioGroup = master.mediaGroups.AUDIO[audioGroupId]; 46212 46213 if (!audioGroup) { 46214 return null; 46215 } 46216 46217 for (var name in audioGroup) { 46218 var audioType = audioGroup[name]; 46219 46220 if (audioType["default"] && audioType.playlists) { 46221 // codec should be the same for all playlists within the audio type 46222 return parseCodecs(audioType.playlists[0].attributes.CODECS).audioProfile; 46223 } 46224 } 46225 46226 return null; 46227 }; 46228 /** 46229 * Calculates the MIME type strings for a working configuration of 46230 * SourceBuffers to play variant streams in a master playlist. If 46231 * there is no possible working configuration, an empty array will be 46232 * returned. 46233 * 46234 * @param master {Object} the m3u8 object for the master playlist 46235 * @param media {Object} the m3u8 object for the variant playlist 46236 * @return {Array} the MIME type strings. If the array has more than 46237 * one entry, the first element should be applied to the video 46238 * SourceBuffer and the second to the audio SourceBuffer. 46239 * 46240 * @private 46241 */ 46242 46243 46244 var mimeTypesForPlaylist = function mimeTypesForPlaylist(master, media) { 46245 var containerType = getContainerType(media); 46246 var codecInfo = getCodecs(media); 46247 var mediaAttributes = media.attributes || {}; // Default condition for a traditional HLS (no demuxed audio/video) 46248 46249 var isMuxed = true; 46250 var isMaat = false; 46251 46252 if (!media) { 46253 // Not enough information 46254 return []; 46255 } 46256 46257 if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) { 46258 var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; // Handle the case where we are in a multiple-audio track scenario 46259 46260 if (audioGroup) { 46261 isMaat = true; // Start with the everything demuxed then... 46262 46263 isMuxed = false; // ...check to see if any audio group tracks are muxed (ie. lacking a uri) 46264 46265 for (var groupId in audioGroup) { 46266 // either a uri is present (if the case of HLS and an external playlist), or 46267 // playlists is present (in the case of DASH where we don't have external audio 46268 // playlists) 46269 if (!audioGroup[groupId].uri && !audioGroup[groupId].playlists) { 46270 isMuxed = true; 46271 break; 46272 } 46273 } 46274 } 46275 } // HLS with multiple-audio tracks must always get an audio codec. 46276 // Put another way, there is no way to have a video-only multiple-audio HLS! 46277 46278 46279 if (isMaat && !codecInfo.audioProfile) { 46280 if (!isMuxed) { 46281 // It is possible for codecs to be specified on the audio media group playlist but 46282 // not on the rendition playlist. This is mostly the case for DASH, where audio and 46283 // video are always separate (and separately specified). 46284 codecInfo.audioProfile = audioProfileFromDefault(master, mediaAttributes.AUDIO); 46285 } 46286 46287 if (!codecInfo.audioProfile) { 46288 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)'); 46289 codecInfo.audioProfile = defaultCodecs.audioProfile; 46290 } 46291 } // Generate the final codec strings from the codec object generated above 46292 46293 46294 var codecStrings = {}; 46295 46296 if (codecInfo.videoCodec) { 46297 codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator; 46298 } 46299 46300 if (codecInfo.audioProfile) { 46301 codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile; 46302 } // Finally, make and return an array with proper mime-types depending on 46303 // the configuration 46304 46305 46306 var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]); 46307 var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]); 46308 var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]); 46309 46310 if (isMaat) { 46311 if (!isMuxed && codecStrings.video) { 46312 return [justVideo, justAudio]; 46313 } 46314 46315 if (!isMuxed && !codecStrings.video) { 46316 // There is no muxed content and no video codec string, so this is an audio only 46317 // stream with alternate audio. 46318 return [justAudio, justAudio]; 46319 } // There exists the possiblity that this will return a `video/container` 46320 // mime-type for the first entry in the array even when there is only audio. 46321 // This doesn't appear to be a problem and simplifies the code. 46322 46323 46324 return [bothVideoAudio, justAudio]; 46325 } // If there is no video codec at all, always just return a single 46326 // audio/<container> mime-type 46327 46328 46329 if (!codecStrings.video) { 46330 return [justAudio]; 46331 } // When not using separate audio media groups, audio and video is 46332 // *always* muxed 46333 46334 46335 return [bothVideoAudio]; 46336 }; 46337 /** 46338 * Parse a content type header into a type and parameters 46339 * object 46340 * 46341 * @param {String} type the content type header 46342 * @return {Object} the parsed content-type 46343 * @private 46344 */ 46345 46346 46347 var parseContentType = function parseContentType(type) { 46348 var object = { 46349 type: '', 46350 parameters: {} 46351 }; 46352 var parameters = type.trim().split(';'); // first parameter should always be content-type 46353 46354 object.type = parameters.shift().trim(); 46355 parameters.forEach(function (parameter) { 46356 var pair = parameter.trim().split('='); 46357 46358 if (pair.length > 1) { 46359 var name = pair[0].replace(/"/g, '').trim(); 46360 var value = pair[1].replace(/"/g, '').trim(); 46361 object.parameters[name] = value; 46362 } 46363 }); 46364 return object; 46365 }; 46366 /** 46367 * Check if a codec string refers to an audio codec. 46368 * 46369 * @param {String} codec codec string to check 46370 * @return {Boolean} if this is an audio codec 46371 * @private 46372 */ 46373 46374 46375 var isAudioCodec = function isAudioCodec(codec) { 46376 return /mp4a\.\d+.\d+/i.test(codec); 46377 }; 46378 /** 46379 * Check if a codec string refers to a video codec. 46380 * 46381 * @param {String} codec codec string to check 46382 * @return {Boolean} if this is a video codec 46383 * @private 46384 */ 46385 46386 46387 var isVideoCodec = function isVideoCodec(codec) { 46388 return /avc1\.[\da-f]+/i.test(codec); 46389 }; 46390 /** 46391 * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in 46392 * front of current time. 46393 * 46394 * @param {Array} buffer 46395 * The current buffer of gop information 46396 * @param {Number} currentTime 46397 * The current time 46398 * @param {Double} mapping 46399 * Offset to map display time to stream presentation time 46400 * @return {Array} 46401 * List of gops considered safe to append over 46402 */ 46403 46404 46405 var gopsSafeToAlignWith = function gopsSafeToAlignWith(buffer, currentTime, mapping) { 46406 if (typeof currentTime === 'undefined' || currentTime === null || !buffer.length) { 46407 return []; 46408 } // pts value for current time + 3 seconds to give a bit more wiggle room 46409 46410 46411 var currentTimePts = Math.ceil((currentTime - mapping + 3) * 90000); 46412 var i = void 0; 46413 46414 for (i = 0; i < buffer.length; i++) { 46415 if (buffer[i].pts > currentTimePts) { 46416 break; 46417 } 46418 } 46419 46420 return buffer.slice(i); 46421 }; 46422 /** 46423 * Appends gop information (timing and byteLength) received by the transmuxer for the 46424 * gops appended in the last call to appendBuffer 46425 * 46426 * @param {Array} buffer 46427 * The current buffer of gop information 46428 * @param {Array} gops 46429 * List of new gop information 46430 * @param {boolean} replace 46431 * If true, replace the buffer with the new gop information. If false, append the 46432 * new gop information to the buffer in the right location of time. 46433 * @return {Array} 46434 * Updated list of gop information 46435 */ 46436 46437 46438 var updateGopBuffer = function updateGopBuffer(buffer, gops, replace) { 46439 if (!gops.length) { 46440 return buffer; 46441 } 46442 46443 if (replace) { 46444 // If we are in safe append mode, then completely overwrite the gop buffer 46445 // with the most recent appeneded data. This will make sure that when appending 46446 // future segments, we only try to align with gops that are both ahead of current 46447 // time and in the last segment appended. 46448 return gops.slice(); 46449 } 46450 46451 var start = gops[0].pts; 46452 var i = 0; 46453 46454 for (i; i < buffer.length; i++) { 46455 if (buffer[i].pts >= start) { 46456 break; 46457 } 46458 } 46459 46460 return buffer.slice(0, i).concat(gops); 46461 }; 46462 /** 46463 * Removes gop information in buffer that overlaps with provided start and end 46464 * 46465 * @param {Array} buffer 46466 * The current buffer of gop information 46467 * @param {Double} start 46468 * position to start the remove at 46469 * @param {Double} end 46470 * position to end the remove at 46471 * @param {Double} mapping 46472 * Offset to map display time to stream presentation time 46473 */ 46474 46475 46476 var removeGopBuffer = function removeGopBuffer(buffer, start, end, mapping) { 46477 var startPts = Math.ceil((start - mapping) * 90000); 46478 var endPts = Math.ceil((end - mapping) * 90000); 46479 var updatedBuffer = buffer.slice(); 46480 var i = buffer.length; 46481 46482 while (i--) { 46483 if (buffer[i].pts <= endPts) { 46484 break; 46485 } 46486 } 46487 46488 if (i === -1) { 46489 // no removal because end of remove range is before start of buffer 46490 return updatedBuffer; 46491 } 46492 46493 var j = i + 1; 46494 46495 while (j--) { 46496 if (buffer[j].pts <= startPts) { 46497 break; 46498 } 46499 } // clamp remove range start to 0 index 46500 46501 46502 j = Math.max(j, 0); 46503 updatedBuffer.splice(j, i - j + 1); 46504 return updatedBuffer; 46505 }; 46506 46507 var buffered = function buffered(videoBuffer, audioBuffer, audioDisabled) { 46508 var start = null; 46509 var end = null; 46510 var arity = 0; 46511 var extents = []; 46512 var ranges = []; // neither buffer has been created yet 46513 46514 if (!videoBuffer && !audioBuffer) { 46515 return videojs$1.createTimeRange(); 46516 } // only one buffer is configured 46517 46518 46519 if (!videoBuffer) { 46520 return audioBuffer.buffered; 46521 } 46522 46523 if (!audioBuffer) { 46524 return videoBuffer.buffered; 46525 } // both buffers are configured 46526 46527 46528 if (audioDisabled) { 46529 return videoBuffer.buffered; 46530 } // both buffers are empty 46531 46532 46533 if (videoBuffer.buffered.length === 0 && audioBuffer.buffered.length === 0) { 46534 return videojs$1.createTimeRange(); 46535 } // Handle the case where we have both buffers and create an 46536 // intersection of the two 46537 46538 46539 var videoBuffered = videoBuffer.buffered; 46540 var audioBuffered = audioBuffer.buffered; 46541 var count = videoBuffered.length; // A) Gather up all start and end times 46542 46543 while (count--) { 46544 extents.push({ 46545 time: videoBuffered.start(count), 46546 type: 'start' 46547 }); 46548 extents.push({ 46549 time: videoBuffered.end(count), 46550 type: 'end' 46551 }); 46552 } 46553 46554 count = audioBuffered.length; 46555 46556 while (count--) { 46557 extents.push({ 46558 time: audioBuffered.start(count), 46559 type: 'start' 46560 }); 46561 extents.push({ 46562 time: audioBuffered.end(count), 46563 type: 'end' 46564 }); 46565 } // B) Sort them by time 46566 46567 46568 extents.sort(function (a, b) { 46569 return a.time - b.time; 46570 }); // C) Go along one by one incrementing arity for start and decrementing 46571 // arity for ends 46572 46573 for (count = 0; count < extents.length; count++) { 46574 if (extents[count].type === 'start') { 46575 arity++; // D) If arity is ever incremented to 2 we are entering an 46576 // overlapping range 46577 46578 if (arity === 2) { 46579 start = extents[count].time; 46580 } 46581 } else if (extents[count].type === 'end') { 46582 arity--; // E) If arity is ever decremented to 1 we leaving an 46583 // overlapping range 46584 46585 if (arity === 1) { 46586 end = extents[count].time; 46587 } 46588 } // F) Record overlapping ranges 46589 46590 46591 if (start !== null && end !== null) { 46592 ranges.push([start, end]); 46593 start = null; 46594 end = null; 46595 } 46596 } 46597 46598 return videojs$1.createTimeRanges(ranges); 46599 }; 46600 /** 46601 * @file virtual-source-buffer.js 46602 */ 46603 46604 46605 var ONE_SECOND_IN_TS$2 = 90000; // We create a wrapper around the SourceBuffer so that we can manage the 46606 // state of the `updating` property manually. We have to do this because 46607 // Firefox changes `updating` to false long before triggering `updateend` 46608 // events and that was causing strange problems in videojs-contrib-hls 46609 46610 var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) { 46611 var sourceBuffer = mediaSource.addSourceBuffer(mimeType); 46612 var wrapper = Object.create(null); 46613 wrapper.updating = false; 46614 wrapper.realBuffer_ = sourceBuffer; 46615 46616 var _loop = function _loop(key) { 46617 if (typeof sourceBuffer[key] === 'function') { 46618 wrapper[key] = function () { 46619 return sourceBuffer[key].apply(sourceBuffer, arguments); 46620 }; 46621 } else if (typeof wrapper[key] === 'undefined') { 46622 Object.defineProperty(wrapper, key, { 46623 get: function get$$1() { 46624 return sourceBuffer[key]; 46625 }, 46626 set: function set$$1(v) { 46627 return sourceBuffer[key] = v; 46628 } 46629 }); 46630 } 46631 }; 46632 46633 for (var key in sourceBuffer) { 46634 _loop(key); 46635 } 46636 46637 return wrapper; 46638 }; 46639 /** 46640 * VirtualSourceBuffers exist so that we can transmux non native formats 46641 * into a native format, but keep the same api as a native source buffer. 46642 * It creates a transmuxer, that works in its own thread (a web worker) and 46643 * that transmuxer muxes the data into a native format. VirtualSourceBuffer will 46644 * then send all of that data to the naive sourcebuffer so that it is 46645 * indestinguishable from a natively supported format. 46646 * 46647 * @param {HtmlMediaSource} mediaSource the parent mediaSource 46648 * @param {Array} codecs array of codecs that we will be dealing with 46649 * @class VirtualSourceBuffer 46650 * @extends video.js.EventTarget 46651 */ 46652 46653 46654 var VirtualSourceBuffer = function (_videojs$EventTarget) { 46655 inherits$1(VirtualSourceBuffer, _videojs$EventTarget); 46656 46657 function VirtualSourceBuffer(mediaSource, codecs) { 46658 classCallCheck$1(this, VirtualSourceBuffer); 46659 46660 var _this = possibleConstructorReturn$1(this, (VirtualSourceBuffer.__proto__ || Object.getPrototypeOf(VirtualSourceBuffer)).call(this, videojs$1.EventTarget)); 46661 46662 _this.timestampOffset_ = 0; 46663 _this.pendingBuffers_ = []; 46664 _this.bufferUpdating_ = false; 46665 _this.mediaSource_ = mediaSource; 46666 _this.codecs_ = codecs; 46667 _this.audioCodec_ = null; 46668 _this.videoCodec_ = null; 46669 _this.audioDisabled_ = false; 46670 _this.appendAudioInitSegment_ = true; 46671 _this.gopBuffer_ = []; 46672 _this.timeMapping_ = 0; 46673 _this.safeAppend_ = videojs$1.browser.IE_VERSION >= 11; 46674 var options = { 46675 remux: false, 46676 alignGopsAtEnd: _this.safeAppend_ 46677 }; 46678 46679 _this.codecs_.forEach(function (codec) { 46680 if (isAudioCodec(codec)) { 46681 _this.audioCodec_ = codec; 46682 } else if (isVideoCodec(codec)) { 46683 _this.videoCodec_ = codec; 46684 } 46685 }); // append muxed segments to their respective native buffers as 46686 // soon as they are available 46687 46688 46689 _this.transmuxer_ = new TransmuxWorker(); 46690 46691 _this.transmuxer_.postMessage({ 46692 action: 'init', 46693 options: options 46694 }); 46695 46696 _this.transmuxer_.onmessage = function (event) { 46697 if (event.data.action === 'data') { 46698 return _this.data_(event); 46699 } 46700 46701 if (event.data.action === 'done') { 46702 return _this.done_(event); 46703 } 46704 46705 if (event.data.action === 'gopInfo') { 46706 return _this.appendGopInfo_(event); 46707 } 46708 46709 if (event.data.action === 'videoSegmentTimingInfo') { 46710 return _this.videoSegmentTimingInfo_(event.data.videoSegmentTimingInfo); 46711 } 46712 }; // this timestampOffset is a property with the side-effect of resetting 46713 // baseMediaDecodeTime in the transmuxer on the setter 46714 46715 46716 Object.defineProperty(_this, 'timestampOffset', { 46717 get: function get$$1() { 46718 return this.timestampOffset_; 46719 }, 46720 set: function set$$1(val) { 46721 if (typeof val === 'number' && val >= 0) { 46722 this.timestampOffset_ = val; 46723 this.appendAudioInitSegment_ = true; // reset gop buffer on timestampoffset as this signals a change in timeline 46724 46725 this.gopBuffer_.length = 0; 46726 this.timeMapping_ = 0; // We have to tell the transmuxer to set the baseMediaDecodeTime to 46727 // the desired timestampOffset for the next segment 46728 46729 this.transmuxer_.postMessage({ 46730 action: 'setTimestampOffset', 46731 timestampOffset: val 46732 }); 46733 } 46734 } 46735 }); // setting the append window affects both source buffers 46736 46737 Object.defineProperty(_this, 'appendWindowStart', { 46738 get: function get$$1() { 46739 return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart; 46740 }, 46741 set: function set$$1(start) { 46742 if (this.videoBuffer_) { 46743 this.videoBuffer_.appendWindowStart = start; 46744 } 46745 46746 if (this.audioBuffer_) { 46747 this.audioBuffer_.appendWindowStart = start; 46748 } 46749 } 46750 }); // this buffer is "updating" if either of its native buffers are 46751 46752 Object.defineProperty(_this, 'updating', { 46753 get: function get$$1() { 46754 return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating); 46755 } 46756 }); // the buffered property is the intersection of the buffered 46757 // ranges of the native source buffers 46758 46759 Object.defineProperty(_this, 'buffered', { 46760 get: function get$$1() { 46761 return buffered(this.videoBuffer_, this.audioBuffer_, this.audioDisabled_); 46762 } 46763 }); 46764 return _this; 46765 } 46766 /** 46767 * When we get a data event from the transmuxer 46768 * we call this function and handle the data that 46769 * was sent to us 46770 * 46771 * @private 46772 * @param {Event} event the data event from the transmuxer 46773 */ 46774 46775 46776 createClass$1(VirtualSourceBuffer, [{ 46777 key: 'data_', 46778 value: function data_(event) { 46779 var segment = event.data.segment; // Cast ArrayBuffer to TypedArray 46780 46781 segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength); 46782 segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength); 46783 createTextTracksIfNecessary(this, this.mediaSource_, segment); // Add the segments to the pendingBuffers array 46784 46785 this.pendingBuffers_.push(segment); 46786 return; 46787 } 46788 /** 46789 * When we get a done event from the transmuxer 46790 * we call this function and we process all 46791 * of the pending data that we have been saving in the 46792 * data_ function 46793 * 46794 * @private 46795 * @param {Event} event the done event from the transmuxer 46796 */ 46797 46798 }, { 46799 key: 'done_', 46800 value: function done_(event) { 46801 // Don't process and append data if the mediaSource is closed 46802 if (this.mediaSource_.readyState === 'closed') { 46803 this.pendingBuffers_.length = 0; 46804 return; 46805 } // All buffers should have been flushed from the muxer 46806 // start processing anything we have received 46807 46808 46809 this.processPendingSegments_(); 46810 return; 46811 } 46812 }, { 46813 key: 'videoSegmentTimingInfo_', 46814 value: function videoSegmentTimingInfo_(timingInfo) { 46815 var timingInfoInSeconds = { 46816 start: { 46817 decode: timingInfo.start.dts / ONE_SECOND_IN_TS$2, 46818 presentation: timingInfo.start.pts / ONE_SECOND_IN_TS$2 46819 }, 46820 end: { 46821 decode: timingInfo.end.dts / ONE_SECOND_IN_TS$2, 46822 presentation: timingInfo.end.pts / ONE_SECOND_IN_TS$2 46823 }, 46824 baseMediaDecodeTime: timingInfo.baseMediaDecodeTime / ONE_SECOND_IN_TS$2 46825 }; 46826 46827 if (timingInfo.prependedContentDuration) { 46828 timingInfoInSeconds.prependedContentDuration = timingInfo.prependedContentDuration / ONE_SECOND_IN_TS$2; 46829 } 46830 46831 this.trigger({ 46832 type: 'videoSegmentTimingInfo', 46833 videoSegmentTimingInfo: timingInfoInSeconds 46834 }); 46835 } 46836 /** 46837 * Create our internal native audio/video source buffers and add 46838 * event handlers to them with the following conditions: 46839 * 1. they do not already exist on the mediaSource 46840 * 2. this VSB has a codec for them 46841 * 46842 * @private 46843 */ 46844 46845 }, { 46846 key: 'createRealSourceBuffers_', 46847 value: function createRealSourceBuffers_() { 46848 var _this2 = this; 46849 46850 var types = ['audio', 'video'];
46851 types.forEach(function (type) { 46852 // Don't create a SourceBuffer of this type if we don't have a 46853 // codec for it 46854 if (!_this2[type + 'Codec_']) { 46855 return; 46856 } // Do nothing if a SourceBuffer of this type already exists 46857 46858 46859 if (_this2[type + 'Buffer_']) { 46860 return; 46861 } 46862 46863 var buffer = null; // If the mediasource already has a SourceBuffer for the codec 46864 // use that 46865 46866 if (_this2.mediaSource_[type + 'Buffer_']) { 46867 buffer = _this2.mediaSource_[type + 'Buffer_']; // In multiple audio track cases, the audio source buffer is disabled 46868 // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier 46869 // than createRealSourceBuffers_ is called to create the second 46870 // VirtualSourceBuffer because that happens as a side-effect of 46871 // videojs-contrib-hls starting the audioSegmentLoader. As a result, 46872 // the audioBuffer is essentially "ownerless" and no one will toggle
vendor: 30,121 bytes, lines 46873-47698
46873 // the `updating` state back to false once the `updateend` event is received 46874 // 46875 // Setting `updating` to false manually will work around this 46876 // situation and allow work to continue 46877 46878 buffer.updating = false; 46879 } else { 46880 var codecProperty = type + 'Codec_'; 46881 var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"'; 46882 buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType); 46883 _this2.mediaSource_[type + 'Buffer_'] = buffer; 46884 } 46885 46886 _this2[type + 'Buffer_'] = buffer; // Wire up the events to the SourceBuffer 46887 46888 ['update', 'updatestart', 'updateend'].forEach(function (event) { 46889 buffer.addEventListener(event, function () { 46890 // if audio is disabled 46891 if (type === 'audio' && _this2.audioDisabled_) { 46892 return; 46893 } 46894 46895 if (event === 'updateend') { 46896 _this2[type + 'Buffer_'].updating = false; 46897 } 46898 46899 var shouldTrigger = types.every(function (t) { 46900 // skip checking audio's updating status if audio 46901 // is not enabled 46902 if (t === 'audio' && _this2.audioDisabled_) { 46903 return true; 46904 } // if the other type is updating we don't trigger 46905 46906 46907 if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) { 46908 return false; 46909 } 46910 46911 return true; 46912 }); 46913 46914 if (shouldTrigger) { 46915 return _this2.trigger(event); 46916 } 46917 }); 46918 }); 46919 }); 46920 } 46921 /** 46922 * Emulate the native mediasource function, but our function will 46923 * send all of the proposed segments to the transmuxer so that we 46924 * can transmux them before we append them to our internal 46925 * native source buffers in the correct format. 46926 * 46927 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 46928 * @param {Uint8Array} segment the segment to append to the buffer 46929 */ 46930 46931 }, { 46932 key: 'appendBuffer', 46933 value: function appendBuffer(segment) { 46934 // Start the internal "updating" state 46935 this.bufferUpdating_ = true; 46936 46937 if (this.audioBuffer_ && this.audioBuffer_.buffered.length) { 46938 var audioBuffered = this.audioBuffer_.buffered; 46939 this.transmuxer_.postMessage({ 46940 action: 'setAudioAppendStart', 46941 appendStart: audioBuffered.end(audioBuffered.length - 1) 46942 }); 46943 } 46944 46945 if (this.videoBuffer_) { 46946 this.transmuxer_.postMessage({ 46947 action: 'alignGopsWith', 46948 gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_, this.mediaSource_.player_ ? this.mediaSource_.player_.currentTime() : null, this.timeMapping_) 46949 }); 46950 } 46951 46952 this.transmuxer_.postMessage({ 46953 action: 'push', 46954 // Send the typed-array of data as an ArrayBuffer so that 46955 // it can be sent as a "Transferable" and avoid the costly 46956 // memory copy 46957 data: segment.buffer, 46958 // To recreate the original typed-array, we need information 46959 // about what portion of the ArrayBuffer it was a view into 46960 byteOffset: segment.byteOffset, 46961 byteLength: segment.byteLength 46962 }, [segment.buffer]); 46963 this.transmuxer_.postMessage({ 46964 action: 'flush' 46965 }); 46966 } 46967 /** 46968 * Appends gop information (timing and byteLength) received by the transmuxer for the 46969 * gops appended in the last call to appendBuffer 46970 * 46971 * @param {Event} event 46972 * The gopInfo event from the transmuxer 46973 * @param {Array} event.data.gopInfo 46974 * List of gop info to append 46975 */ 46976 46977 }, { 46978 key: 'appendGopInfo_', 46979 value: function appendGopInfo_(event) { 46980 this.gopBuffer_ = updateGopBuffer(this.gopBuffer_, event.data.gopInfo, this.safeAppend_); 46981 } 46982 /** 46983 * Emulate the native mediasource function and remove parts 46984 * of the buffer from any of our internal buffers that exist 46985 * 46986 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 46987 * @param {Double} start position to start the remove at 46988 * @param {Double} end position to end the remove at 46989 */ 46990 46991 }, { 46992 key: 'remove', 46993 value: function remove(start, end) { 46994 if (this.videoBuffer_) { 46995 this.videoBuffer_.updating = true; 46996 this.videoBuffer_.remove(start, end); 46997 this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_); 46998 } 46999 47000 if (!this.audioDisabled_ && this.audioBuffer_) { 47001 this.audioBuffer_.updating = true; 47002 this.audioBuffer_.remove(start, end); 47003 } // Remove Metadata Cues (id3) 47004 47005 47006 removeCuesFromTrack(start, end, this.metadataTrack_); // Remove Any Captions 47007 47008 if (this.inbandTextTracks_) { 47009 for (var track in this.inbandTextTracks_) { 47010 removeCuesFromTrack(start, end, this.inbandTextTracks_[track]); 47011 } 47012 } 47013 } 47014 /** 47015 * Process any segments that the muxer has output 47016 * Concatenate segments together based on type and append them into 47017 * their respective sourceBuffers 47018 * 47019 * @private 47020 */ 47021 47022 }, { 47023 key: 'processPendingSegments_', 47024 value: function processPendingSegments_() { 47025 var sortedSegments = { 47026 video: { 47027 segments: [], 47028 bytes: 0 47029 }, 47030 audio: { 47031 segments: [], 47032 bytes: 0 47033 }, 47034 captions: [], 47035 metadata: [] 47036 }; 47037 47038 if (!this.pendingBuffers_.length) { 47039 // We are no longer in the internal "updating" state 47040 this.trigger('updateend'); 47041 this.bufferUpdating_ = false; 47042 return; 47043 } // Sort segments into separate video/audio arrays and 47044 // keep track of their total byte lengths 47045 47046 47047 sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) { 47048 var type = segment.type; 47049 var data = segment.data; 47050 var initSegment = segment.initSegment; 47051 segmentObj[type].segments.push(data); 47052 segmentObj[type].bytes += data.byteLength; 47053 segmentObj[type].initSegment = initSegment; // Gather any captions into a single array 47054 47055 if (segment.captions) { 47056 segmentObj.captions = segmentObj.captions.concat(segment.captions); 47057 } 47058 47059 if (segment.info) { 47060 segmentObj[type].info = segment.info; 47061 } // Gather any metadata into a single array 47062 47063 47064 if (segment.metadata) { 47065 segmentObj.metadata = segmentObj.metadata.concat(segment.metadata); 47066 } 47067 47068 return segmentObj; 47069 }, sortedSegments); // Create the real source buffers if they don't exist by now since we 47070 // finally are sure what tracks are contained in the source 47071 47072 if (!this.videoBuffer_ && !this.audioBuffer_) { 47073 // Remove any codecs that may have been specified by default but 47074 // are no longer applicable now 47075 if (sortedSegments.video.bytes === 0) { 47076 this.videoCodec_ = null; 47077 } 47078 47079 if (sortedSegments.audio.bytes === 0) { 47080 this.audioCodec_ = null; 47081 } 47082 47083 this.createRealSourceBuffers_(); 47084 } 47085 47086 if (sortedSegments.audio.info) { 47087 this.mediaSource_.trigger({ 47088 type: 'audioinfo', 47089 info: sortedSegments.audio.info 47090 }); 47091 } 47092 47093 if (sortedSegments.video.info) { 47094 this.mediaSource_.trigger({ 47095 type: 'videoinfo', 47096 info: sortedSegments.video.info 47097 }); 47098 } 47099 47100 if (this.appendAudioInitSegment_) { 47101 if (!this.audioDisabled_ && this.audioBuffer_) { 47102 sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment); 47103 sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength; 47104 } 47105 47106 this.appendAudioInitSegment_ = false; 47107 } 47108 47109 var triggerUpdateend = false; // Merge multiple video and audio segments into one and append 47110 47111 if (this.videoBuffer_ && sortedSegments.video.bytes) { 47112 sortedSegments.video.segments.unshift(sortedSegments.video.initSegment); 47113 sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength; 47114 this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_); 47115 } else if (this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) { 47116 // The transmuxer did not return any bytes of video, meaning it was all trimmed 47117 // for gop alignment. Since we have a video buffer and audio is disabled, updateend 47118 // will never be triggered by this source buffer, which will cause contrib-hls 47119 // to be stuck forever waiting for updateend. If audio is not disabled, updateend 47120 // will be triggered by the audio buffer, which will be sent upwards since the video 47121 // buffer will not be in an updating state. 47122 triggerUpdateend = true; 47123 } // Add text-track data for all 47124 47125 47126 addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata); 47127 47128 if (!this.audioDisabled_ && this.audioBuffer_) { 47129 this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_); 47130 } 47131 47132 this.pendingBuffers_.length = 0; 47133 47134 if (triggerUpdateend) { 47135 this.trigger('updateend'); 47136 } // We are no longer in the internal "updating" state 47137 47138 47139 this.bufferUpdating_ = false; 47140 } 47141 /** 47142 * Combine all segments into a single Uint8Array and then append them 47143 * to the destination buffer 47144 * 47145 * @param {Object} segmentObj 47146 * @param {SourceBuffer} destinationBuffer native source buffer to append data to 47147 * @private 47148 */ 47149 47150 }, { 47151 key: 'concatAndAppendSegments_', 47152 value: function concatAndAppendSegments_(segmentObj, destinationBuffer) { 47153 var offset = 0; 47154 var tempBuffer = void 0; 47155 47156 if (segmentObj.bytes) { 47157 tempBuffer = new Uint8Array(segmentObj.bytes); // Combine the individual segments into one large typed-array 47158 47159 segmentObj.segments.forEach(function (segment) { 47160 tempBuffer.set(segment, offset); 47161 offset += segment.byteLength; 47162 }); 47163 47164 try { 47165 destinationBuffer.updating = true; 47166 destinationBuffer.appendBuffer(tempBuffer); 47167 } catch (error) { 47168 if (this.mediaSource_.player_) { 47169 this.mediaSource_.player_.error({ 47170 code: -3, 47171 type: 'APPEND_BUFFER_ERR', 47172 message: error.message, 47173 originalError: error 47174 }); 47175 } 47176 } 47177 } 47178 } 47179 /** 47180 * Emulate the native mediasource function. abort any soureBuffer 47181 * actions and throw out any un-appended data. 47182 * 47183 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 47184 */ 47185 47186 }, { 47187 key: 'abort', 47188 value: function abort() { 47189 if (this.videoBuffer_) { 47190 this.videoBuffer_.abort(); 47191 } 47192 47193 if (!this.audioDisabled_ && this.audioBuffer_) { 47194 this.audioBuffer_.abort(); 47195 } 47196 47197 if (this.transmuxer_) { 47198 this.transmuxer_.postMessage({ 47199 action: 'reset' 47200 }); 47201 } 47202 47203 this.pendingBuffers_.length = 0; 47204 this.bufferUpdating_ = false; 47205 } 47206 }]); 47207 return VirtualSourceBuffer; 47208 }(videojs$1.EventTarget); 47209 /** 47210 * @file html-media-source.js 47211 */ 47212 47213 /** 47214 * Our MediaSource implementation in HTML, mimics native 47215 * MediaSource where/if possible. 47216 * 47217 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 47218 * @class HtmlMediaSource 47219 * @extends videojs.EventTarget 47220 */ 47221 47222 47223 var HtmlMediaSource = function (_videojs$EventTarget) { 47224 inherits$1(HtmlMediaSource, _videojs$EventTarget); 47225 47226 function HtmlMediaSource() { 47227 classCallCheck$1(this, HtmlMediaSource); 47228 47229 var _this = possibleConstructorReturn$1(this, (HtmlMediaSource.__proto__ || Object.getPrototypeOf(HtmlMediaSource)).call(this)); 47230 47231 var property = void 0; 47232 _this.nativeMediaSource_ = new window$1.MediaSource(); // delegate to the native MediaSource's methods by default 47233 47234 for (property in _this.nativeMediaSource_) { 47235 if (!(property in HtmlMediaSource.prototype) && typeof _this.nativeMediaSource_[property] === 'function') { 47236 _this[property] = _this.nativeMediaSource_[property].bind(_this.nativeMediaSource_); 47237 } 47238 } // emulate `duration` and `seekable` until seeking can be 47239 // handled uniformly for live streams 47240 // see https://github.com/w3c/media-source/issues/5 47241 47242 47243 _this.duration_ = NaN; 47244 Object.defineProperty(_this, 'duration', { 47245 get: function get$$1() { 47246 if (this.duration_ === Infinity) { 47247 return this.duration_; 47248 } 47249 47250 return this.nativeMediaSource_.duration; 47251 }, 47252 set: function set$$1(duration) { 47253 this.duration_ = duration; 47254 47255 if (duration !== Infinity) { 47256 this.nativeMediaSource_.duration = duration; 47257 return; 47258 } 47259 } 47260 }); 47261 Object.defineProperty(_this, 'seekable', { 47262 get: function get$$1() { 47263 if (this.duration_ === Infinity) { 47264 return videojs$1.createTimeRanges([[0, this.nativeMediaSource_.duration]]); 47265 } 47266 47267 return this.nativeMediaSource_.seekable; 47268 } 47269 }); 47270 Object.defineProperty(_this, 'readyState', { 47271 get: function get$$1() { 47272 return this.nativeMediaSource_.readyState; 47273 } 47274 }); 47275 Object.defineProperty(_this, 'activeSourceBuffers', { 47276 get: function get$$1() { 47277 return this.activeSourceBuffers_; 47278 } 47279 }); // the list of virtual and native SourceBuffers created by this 47280 // MediaSource 47281 47282 _this.sourceBuffers = []; 47283 _this.activeSourceBuffers_ = []; 47284 /** 47285 * update the list of active source buffers based upon various 47286 * imformation from HLS and video.js 47287 * 47288 * @private 47289 */ 47290 47291 _this.updateActiveSourceBuffers_ = function () { 47292 // Retain the reference but empty the array 47293 _this.activeSourceBuffers_.length = 0; // If there is only one source buffer, then it will always be active and audio will 47294 // be disabled based on the codec of the source buffer 47295 47296 if (_this.sourceBuffers.length === 1) { 47297 var sourceBuffer = _this.sourceBuffers[0]; 47298 sourceBuffer.appendAudioInitSegment_ = true; 47299 sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_; 47300 47301 _this.activeSourceBuffers_.push(sourceBuffer); 47302 47303 return; 47304 } // There are 2 source buffers, a combined (possibly video only) source buffer and 47305 // and an audio only source buffer. 47306 // By default, the audio in the combined virtual source buffer is enabled 47307 // and the audio-only source buffer (if it exists) is disabled. 47308 47309 47310 var disableCombined = false; 47311 var disableAudioOnly = true; // TODO: maybe we can store the sourcebuffers on the track objects? 47312 // safari may do something like this 47313 47314 for (var i = 0; i < _this.player_.audioTracks().length; i++) { 47315 var track = _this.player_.audioTracks()[i]; 47316 47317 if (track.enabled && track.kind !== 'main') { 47318 // The enabled track is an alternate audio track so disable the audio in 47319 // the combined source buffer and enable the audio-only source buffer. 47320 disableCombined = true; 47321 disableAudioOnly = false; 47322 break; 47323 } 47324 } 47325 47326 _this.sourceBuffers.forEach(function (sourceBuffer, index) { 47327 /* eslinst-disable */ 47328 // TODO once codecs are required, we can switch to using the codecs to determine 47329 // what stream is the video stream, rather than relying on videoTracks 47330 47331 /* eslinst-enable */ 47332 sourceBuffer.appendAudioInitSegment_ = true; 47333 47334 if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 47335 // combined 47336 sourceBuffer.audioDisabled_ = disableCombined; 47337 } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) { 47338 // If the "combined" source buffer is video only, then we do not want 47339 // disable the audio-only source buffer (this is mostly for demuxed 47340 // audio and video hls) 47341 sourceBuffer.audioDisabled_ = true; 47342 disableAudioOnly = false; 47343 } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 47344 // audio only 47345 // In the case of audio only with alternate audio and disableAudioOnly is true 47346 // this means we want to disable the audio on the alternate audio sourcebuffer 47347 // but not the main "combined" source buffer. The "combined" source buffer is 47348 // always at index 0, so this ensures audio won't be disabled in both source 47349 // buffers. 47350 sourceBuffer.audioDisabled_ = index ? disableAudioOnly : !disableAudioOnly; 47351 47352 if (sourceBuffer.audioDisabled_) { 47353 return; 47354 } 47355 } 47356 47357 _this.activeSourceBuffers_.push(sourceBuffer); 47358 }); 47359 }; 47360 47361 _this.onPlayerMediachange_ = function () { 47362 _this.sourceBuffers.forEach(function (sourceBuffer) { 47363 sourceBuffer.appendAudioInitSegment_ = true; 47364 }); 47365 }; 47366 47367 _this.onHlsReset_ = function () { 47368 _this.sourceBuffers.forEach(function (sourceBuffer) { 47369 if (sourceBuffer.transmuxer_) { 47370 sourceBuffer.transmuxer_.postMessage({ 47371 action: 'resetCaptions' 47372 }); 47373 } 47374 }); 47375 }; 47376 47377 _this.onHlsSegmentTimeMapping_ = function (event) { 47378 _this.sourceBuffers.forEach(function (buffer) { 47379 return buffer.timeMapping_ = event.mapping; 47380 }); 47381 }; // Re-emit MediaSource events on the polyfill 47382 47383 47384 ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) { 47385 this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this)); 47386 }, _this); // capture the associated player when the MediaSource is 47387 // successfully attached 47388 47389 _this.on('sourceopen', function (event) { 47390 // Get the player this MediaSource is attached to 47391 var video = document.querySelector('[src="' + _this.url_ + '"]'); 47392 47393 if (!video) { 47394 return; 47395 } 47396 47397 _this.player_ = videojs$1(video.parentNode); 47398 47399 if (!_this.player_) { 47400 return; 47401 } // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader 47402 // resets its state and flushes the buffer 47403 47404 47405 _this.player_.tech_.on('hls-reset', _this.onHlsReset_); // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main 47406 // SegmentLoader inspects an MTS segment and has an accurate stream to display 47407 // time mapping 47408 47409 47410 _this.player_.tech_.on('hls-segment-time-mapping', _this.onHlsSegmentTimeMapping_); 47411 47412 if (_this.player_.audioTracks && _this.player_.audioTracks()) { 47413 _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_); 47414 47415 _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_); 47416 47417 _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_); 47418 } 47419 47420 _this.player_.on('mediachange', _this.onPlayerMediachange_); 47421 }); 47422 47423 _this.on('sourceended', function (event) { 47424 var duration = durationOfVideo(_this.duration); 47425 47426 for (var i = 0; i < _this.sourceBuffers.length; i++) { 47427 var sourcebuffer = _this.sourceBuffers[i]; 47428 var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues; 47429 47430 if (cues && cues.length) { 47431 cues[cues.length - 1].endTime = duration; 47432 } 47433 } 47434 }); // explicitly terminate any WebWorkers that were created 47435 // by SourceHandlers 47436 47437 47438 _this.on('sourceclose', function (event) { 47439 this.sourceBuffers.forEach(function (sourceBuffer) { 47440 if (sourceBuffer.transmuxer_) { 47441 sourceBuffer.transmuxer_.terminate(); 47442 } 47443 }); 47444 this.sourceBuffers.length = 0; 47445 47446 if (!this.player_) { 47447 return; 47448 } 47449 47450 if (this.player_.audioTracks && this.player_.audioTracks()) { 47451 this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_); 47452 this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_); 47453 this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_); 47454 } // We can only change this if the player hasn't been disposed of yet 47455 // because `off` eventually tries to use the el_ property. If it has 47456 // been disposed of, then don't worry about it because there are no 47457 // event handlers left to unbind anyway 47458 47459 47460 if (this.player_.el_) { 47461 this.player_.off('mediachange', this.onPlayerMediachange_); 47462 } 47463 47464 if (this.player_.tech_ && this.player_.tech_.el_) { 47465 this.player_.tech_.off('hls-reset', this.onHlsReset_); 47466 this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_); 47467 } 47468 }); 47469 47470 return _this; 47471 } 47472 /** 47473 * Add a range that that can now be seeked to. 47474 * 47475 * @param {Double} start where to start the addition 47476 * @param {Double} end where to end the addition 47477 * @private 47478 */ 47479 47480 47481 createClass$1(HtmlMediaSource, [{ 47482 key: 'addSeekableRange_', 47483 value: function addSeekableRange_(start, end) { 47484 var error = void 0; 47485 47486 if (this.duration !== Infinity) { 47487 error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity'); 47488 error.name = 'InvalidStateError'; 47489 error.code = 11; 47490 throw error; 47491 } 47492 47493 if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) { 47494 this.nativeMediaSource_.duration = end; 47495 } 47496 } 47497 /** 47498 * Add a source buffer to the media source. 47499 * 47500 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 47501 * @param {String} type the content-type of the content 47502 * @return {Object} the created source buffer 47503 */ 47504 47505 }, { 47506 key: 'addSourceBuffer', 47507 value: function addSourceBuffer(type) { 47508 var buffer = void 0; 47509 var parsedType = parseContentType(type); // Create a VirtualSourceBuffer to transmux MPEG-2 transport 47510 // stream segments into fragmented MP4s 47511 47512 if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) { 47513 var codecs = []; 47514 47515 if (parsedType.parameters && parsedType.parameters.codecs) { 47516 codecs = parsedType.parameters.codecs.split(','); 47517 codecs = translateLegacyCodecs(codecs); 47518 codecs = codecs.filter(function (codec) { 47519 return isAudioCodec(codec) || isVideoCodec(codec); 47520 }); 47521 } 47522 47523 if (codecs.length === 0) { 47524 codecs = ['avc1.4d400d', 'mp4a.40.2']; 47525 } 47526 47527 buffer = new VirtualSourceBuffer(this, codecs); 47528 47529 if (this.sourceBuffers.length !== 0) { 47530 // If another VirtualSourceBuffer already exists, then we are creating a 47531 // SourceBuffer for an alternate audio track and therefore we know that 47532 // the source has both an audio and video track. 47533 // That means we should trigger the manual creation of the real 47534 // SourceBuffers instead of waiting for the transmuxer to return data 47535 this.sourceBuffers[0].createRealSourceBuffers_(); 47536 buffer.createRealSourceBuffers_(); // Automatically disable the audio on the first source buffer if 47537 // a second source buffer is ever created 47538 47539 this.sourceBuffers[0].audioDisabled_ = true; 47540 } 47541 } else { 47542 // delegate to the native implementation 47543 buffer = this.nativeMediaSource_.addSourceBuffer(type); 47544 } 47545 47546 this.sourceBuffers.push(buffer); 47547 return buffer; 47548 } 47549 }]); 47550 return HtmlMediaSource; 47551 }(videojs$1.EventTarget); 47552 /** 47553 * @file videojs-contrib-media-sources.js 47554 */ 47555 47556 47557 var urlCount = 0; // ------------ 47558 // Media Source 47559 // ------------ 47560 // store references to the media sources so they can be connected 47561 // to a video element (a swf object) 47562 // TODO: can we store this somewhere local to this module? 47563 47564 videojs$1.mediaSources = {}; 47565 /** 47566 * Provide a method for a swf object to notify JS that a 47567 * media source is now open. 47568 * 47569 * @param {String} msObjectURL string referencing the MSE Object URL 47570 * @param {String} swfId the swf id 47571 */ 47572 47573 var open = function open(msObjectURL, swfId) { 47574 var mediaSource = videojs$1.mediaSources[msObjectURL]; 47575 47576 if (mediaSource) { 47577 mediaSource.trigger({ 47578 type: 'sourceopen', 47579 swfId: swfId 47580 }); 47581 } else { 47582 throw new Error('Media Source not found (Video.js)'); 47583 } 47584 }; 47585 /** 47586 * Check to see if the native MediaSource object exists and supports 47587 * an MP4 container with both H.264 video and AAC-LC audio. 47588 * 47589 * @return {Boolean} if native media sources are supported 47590 */ 47591 47592 47593 var supportsNativeMediaSources = function supportsNativeMediaSources() { 47594 return !!window$1.MediaSource && !!window$1.MediaSource.isTypeSupported && window$1.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"'); 47595 }; 47596 /** 47597 * An emulation of the MediaSource API so that we can support 47598 * native and non-native functionality. returns an instance of 47599 * HtmlMediaSource. 47600 * 47601 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource 47602 */ 47603 47604 47605 var MediaSource = function MediaSource() { 47606 this.MediaSource = { 47607 open: open, 47608 supportsNativeMediaSources: supportsNativeMediaSources 47609 }; 47610 47611 if (supportsNativeMediaSources()) { 47612 return new HtmlMediaSource(); 47613 } 47614 47615 throw new Error('Cannot use create a virtual MediaSource for this video'); 47616 }; 47617 47618 MediaSource.open = open; 47619 MediaSource.supportsNativeMediaSources = supportsNativeMediaSources; 47620 /** 47621 * A wrapper around the native URL for our MSE object 47622 * implementation, this object is exposed under videojs.URL 47623 * 47624 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL 47625 */ 47626 47627 var URL$1 = { 47628 /** 47629 * A wrapper around the native createObjectURL for our objects. 47630 * This function maps a native or emulated mediaSource to a blob 47631 * url so that it can be loaded into video.js 47632 * 47633 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL 47634 * @param {MediaSource} object the object to create a blob url to 47635 */ 47636 createObjectURL: function createObjectURL(object) { 47637 var objectUrlPrefix = 'blob:vjs-media-source/'; 47638 var url = void 0; // use the native MediaSource to generate an object URL 47639 47640 if (object instanceof HtmlMediaSource) { 47641 url = window$1.URL.createObjectURL(object.nativeMediaSource_); 47642 object.url_ = url; 47643 return url; 47644 } // if the object isn't an emulated MediaSource, delegate to the 47645 // native implementation 47646 47647 47648 if (!(object instanceof HtmlMediaSource)) { 47649 url = window$1.URL.createObjectURL(object); 47650 object.url_ = url; 47651 return url; 47652 } // build a URL that can be used to map back to the emulated 47653 // MediaSource 47654 47655 47656 url = objectUrlPrefix + urlCount; 47657 urlCount++; // setup the mapping back to object 47658 47659 videojs$1.mediaSources[url] = object; 47660 return url; 47661 } 47662 }; 47663 videojs$1.MediaSource = MediaSource; 47664 videojs$1.URL = URL$1; 47665 var EventTarget$1$1 = videojs$1.EventTarget, 47666 mergeOptions$2 = videojs$1.mergeOptions; 47667 /** 47668 * Returns a new master manifest that is the result of merging an updated master manifest 47669 * into the original version. 47670 * 47671 * @param {Object} oldMaster 47672 * The old parsed mpd object 47673 * @param {Object} newMaster 47674 * The updated parsed mpd object 47675 * @return {Object} 47676 * A new object representing the original master manifest with the updated media 47677 * playlists merged in 47678 */ 47679 47680 var updateMaster$1 = function updateMaster$$1(oldMaster, newMaster) { 47681 var noChanges = void 0; 47682 var update = mergeOptions$2(oldMaster, { 47683 // These are top level properties that can be updated 47684 duration: newMaster.duration, 47685 minimumUpdatePeriod: newMaster.minimumUpdatePeriod 47686 }); // First update the playlists in playlist list 47687 47688 for (var i = 0; i < newMaster.playlists.length; i++) { 47689 var playlistUpdate = updateMaster(update, newMaster.playlists[i]); 47690 47691 if (playlistUpdate) { 47692 update = playlistUpdate; 47693 } else { 47694 noChanges = true; 47695 } 47696 } // Then update media group playlists 47697 47698
47699 forEachMediaGroup(newMaster, function (properties, type, group, label) { 47700 if (properties.playlists && properties.playlists.length) { 47701 var uri = properties.playlists[0].uri; 47702 47703 var _playlistUpdate = updateMaster(update, properties.playlists[0]); 47704 47705 if (_playlistUpdate) { 47706 update = _playlistUpdate; // update the playlist reference within media groups 47707 47708 update.mediaGroups[type][group][label].playlists[0] = update.playlists[uri]; 47709 noChanges = false; 47710 } 47711 } 47712 }); 47713 47714 if (noChanges) { 47715 return null; 47716 } 47717 47718 return update; 47719 }; 47720 47721 var generateSidxKey = function generateSidxKey(sidxInfo) { 47722 // should be non-inclusive 47723 var sidxByteRangeEnd = sidxInfo.byterange.offset + sidxInfo.byterange.length - 1; 47724 return sidxInfo.uri + '-' + sidxInfo.byterange.offset + '-' + sidxByteRangeEnd; 47725 }; // SIDX should be equivalent if the URI and byteranges of the SIDX match. 47726 // If the SIDXs have maps, the two maps should match, 47727 // both `a` and `b` missing SIDXs is considered matching. 47728 // If `a` or `b` but not both have a map, they aren't matching. 47729 47730 47731 var equivalentSidx = function equivalentSidx(a, b) { 47732 var neitherMap = Boolean(!a.map && !b.map); 47733 var equivalentMap = neitherMap || Boolean(a.map && b.map && a.map.byterange.offset === b.map.byterange.offset && a.map.byterange.length === b.map.byterange.length); 47734 return equivalentMap && a.uri === b.uri && a.byterange.offset === b.byterange.offset && a.byterange.length === b.byterange.length; 47735 }; // exported for testing 47736 47737 47738 var compareSidxEntry = function compareSidxEntry(playlists, oldSidxMapping) { 47739 var newSidxMapping = {}; 47740 47741 for (var uri in playlists) { 47742 var playlist = playlists[uri]; 47743 var currentSidxInfo = playlist.sidx; 47744 47745 if (currentSidxInfo) { 47746 var key = generateSidxKey(currentSidxInfo); 47747 47748 if (!oldSidxMapping[key]) { 47749 break; 47750 } 47751 47752 var savedSidxInfo = oldSidxMapping[key].sidxInfo; 47753 47754 if (equivalentSidx(savedSidxInfo, currentSidxInfo)) { 47755 newSidxMapping[key] = oldSidxMapping[key]; 47756 } 47757 } 47758 } 47759 47760 return newSidxMapping; 47761 }; 47762 /** 47763 * A function that filters out changed items as they need to be requested separately. 47764 * 47765 * The method is exported for testing 47766 * 47767 * @param {Object} masterXml the mpd XML 47768 * @param {string} srcUrl the mpd url 47769 * @param {Date} clientOffset a time difference between server and client (passed through and not used) 47770 * @param {Object} oldSidxMapping the SIDX to compare against 47771 */ 47772 47773 47774 var filterChangedSidxMappings = function filterChangedSidxMappings(masterXml, srcUrl, clientOffset, oldSidxMapping) { 47775 // Don't pass current sidx mapping 47776 var master = parse(masterXml, { 47777 manifestUri: srcUrl, 47778 clientOffset: clientOffset 47779 }); 47780 var videoSidx = compareSidxEntry(master.playlists, oldSidxMapping); 47781 var mediaGroupSidx = videoSidx; 47782 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 47783 if (properties.playlists && properties.playlists.length) { 47784 var playlists = properties.playlists; 47785 mediaGroupSidx = mergeOptions$2(mediaGroupSidx, compareSidxEntry(playlists, oldSidxMapping)); 47786 } 47787 }); 47788 return mediaGroupSidx; 47789 }; // exported for testing 47790 47791 47792 var requestSidx_ = function requestSidx_(sidxRange, playlist, xhr, options, finishProcessingFn) { 47793 var sidxInfo = { 47794 // resolve the segment URL relative to the playlist 47795 uri: resolveManifestRedirect(options.handleManifestRedirects, sidxRange.resolvedUri), 47796 // resolvedUri: sidxRange.resolvedUri, 47797 byterange: sidxRange.byterange, 47798 // the segment's playlist 47799 playlist: playlist 47800 }; 47801 var sidxRequestOptions = videojs$1.mergeOptions(sidxInfo, { 47802 responseType: 'arraybuffer', 47803 headers: segmentXhrHeaders(sidxInfo) 47804 }); 47805 return xhr(sidxRequestOptions, finishProcessingFn); 47806 }; 47807 47808 var DashPlaylistLoader = function (_EventTarget) { 47809 inherits$1(DashPlaylistLoader, _EventTarget); // DashPlaylistLoader must accept either a src url or a playlist because subsequent 47810 // playlist loader setups from media groups will expect to be able to pass a playlist 47811 // (since there aren't external URLs to media playlists with DASH) 47812 47813 function DashPlaylistLoader(srcUrlOrPlaylist, hls) { 47814 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 47815 var masterPlaylistLoader = arguments[3]; 47816 classCallCheck$1(this, DashPlaylistLoader); 47817 47818 var _this = possibleConstructorReturn$1(this, (DashPlaylistLoader.__proto__ || Object.getPrototypeOf(DashPlaylistLoader)).call(this)); 47819 47820 var _options$withCredenti = options.withCredentials, 47821 withCredentials = _options$withCredenti === undefined ? false : _options$withCredenti, 47822 _options$handleManife = options.handleManifestRedirects, 47823 handleManifestRedirects = _options$handleManife === undefined ? false : _options$handleManife; 47824 _this.hls_ = hls; 47825 _this.withCredentials = withCredentials; 47826 _this.handleManifestRedirects = handleManifestRedirects; 47827 47828 if (!srcUrlOrPlaylist) { 47829 throw new Error('A non-empty playlist URL or playlist is required'); 47830 } // event naming? 47831 47832 47833 _this.on('minimumUpdatePeriod', function () { 47834 _this.refreshXml_(); 47835 }); // live playlist staleness timeout 47836 47837 47838 _this.on('mediaupdatetimeout', function () { 47839 _this.refreshMedia_(_this.media().uri); 47840 }); 47841 47842 _this.state = 'HAVE_NOTHING'; 47843 _this.loadedPlaylists_ = {}; // initialize the loader state 47844 // The masterPlaylistLoader will be created with a string 47845 47846 if (typeof srcUrlOrPlaylist === 'string') { 47847 _this.srcUrl = srcUrlOrPlaylist; // TODO: reset sidxMapping between period changes 47848 // once multi-period is refactored 47849 47850 _this.sidxMapping_ = {}; 47851 return possibleConstructorReturn$1(_this); 47852 } 47853 47854 _this.setupChildLoader(masterPlaylistLoader, srcUrlOrPlaylist); 47855 47856 return _this; 47857 } 47858 47859 createClass$1(DashPlaylistLoader, [{ 47860 key: 'setupChildLoader', 47861 value: function setupChildLoader(masterPlaylistLoader, playlist) { 47862 this.masterPlaylistLoader_ = masterPlaylistLoader; 47863 this.childPlaylist_ = playlist; 47864 } 47865 }, { 47866 key: 'dispose', 47867 value: function dispose() { 47868 this.stopRequest(); 47869 this.loadedPlaylists_ = {}; 47870 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 47871 window$1.clearTimeout(this.mediaRequest_); 47872 window$1.clearTimeout(this.mediaUpdateTimeout); 47873 } 47874 }, { 47875 key: 'hasPendingRequest', 47876 value: function hasPendingRequest() { 47877 return this.request || this.mediaRequest_; 47878 } 47879 }, { 47880 key: 'stopRequest', 47881 value: function stopRequest() { 47882 if (this.request) { 47883 var oldRequest = this.request; 47884 this.request = null; 47885 oldRequest.onreadystatechange = null; 47886 oldRequest.abort(); 47887 } 47888 } 47889 }, { 47890 key: 'sidxRequestFinished_', 47891 value: function sidxRequestFinished_(playlist, master, startingState, doneFn) { 47892 var _this2 = this; 47893 47894 return function (err, request) { 47895 // disposed 47896 if (!_this2.request) { 47897 return; 47898 } // pending request is cleared 47899 47900 47901 _this2.request = null; 47902 47903 if (err) { 47904 _this2.error = { 47905 status: request.status, 47906 message: 'DASH playlist request error at URL: ' + playlist.uri, 47907 response: request.response, 47908 // MEDIA_ERR_NETWORK 47909 code: 2 47910 }; 47911 47912 if (startingState) { 47913 _this2.state = startingState; 47914 } 47915 47916 _this2.trigger('error'); 47917 47918 return doneFn(master, null); 47919 } 47920 47921 var bytes = new Uint8Array(request.response); 47922 var sidx = mp4Inspector.parseSidx(bytes.subarray(8)); 47923 return doneFn(master, sidx); 47924 }; 47925 } 47926 }, { 47927 key: 'media', 47928 value: function media(playlist) { 47929 var _this3 = this; // getter 47930 47931 47932 if (!playlist) { 47933 return this.media_; 47934 } // setter 47935 47936 47937 if (this.state === 'HAVE_NOTHING') { 47938 throw new Error('Cannot switch media playlist from ' + this.state); 47939 } 47940 47941 var startingState = this.state; // find the playlist object if the target playlist has been specified by URI 47942 47943 if (typeof playlist === 'string') { 47944 if (!this.master.playlists[playlist]) { 47945 throw new Error('Unknown playlist URI: ' + playlist); 47946 } 47947 47948 playlist = this.master.playlists[playlist]; 47949 } 47950 47951 var mediaChange = !this.media_ || playlist.uri !== this.media_.uri; // switch to previously loaded playlists immediately 47952 47953 if (mediaChange && this.loadedPlaylists_[playlist.uri] && this.loadedPlaylists_[playlist.uri].endList) { 47954 this.state = 'HAVE_METADATA'; 47955 this.media_ = playlist; // trigger media change if the active media has been updated 47956 47957 if (mediaChange) { 47958 this.trigger('mediachanging'); 47959 this.trigger('mediachange'); 47960 } 47961 47962 return; 47963 } // switching to the active playlist is a no-op 47964 47965 47966 if (!mediaChange) { 47967 return; 47968 } // switching from an already loaded playlist 47969 47970 47971 if (this.media_) { 47972 this.trigger('mediachanging'); 47973 } 47974 47975 if (!playlist.sidx) { 47976 // Continue asynchronously if there is no sidx 47977 // wait one tick to allow haveMaster to run first on a child loader
47978 this.mediaRequest_ = window$1.setTimeout(this.haveMetadata.bind(this, { 47979 startingState: startingState, 47980 playlist: playlist 47981 }), 0); // exit early and don't do sidx work 47982 47983 return; 47984 } // we have sidx mappings 47985 47986 47987 var oldMaster = void 0; 47988 var sidxMapping = void 0; // sidxMapping is used when parsing the masterXml, so store 47989 // it on the masterPlaylistLoader 47990 47991 if (this.masterPlaylistLoader_) { 47992 oldMaster = this.masterPlaylistLoader_.master; 47993 sidxMapping = this.masterPlaylistLoader_.sidxMapping_; 47994 } else { 47995 oldMaster = this.master; 47996 sidxMapping = this.sidxMapping_; 47997 } 47998 47999 var sidxKey = generateSidxKey(playlist.sidx); 48000 sidxMapping[sidxKey] = { 48001 sidxInfo: playlist.sidx 48002 }; 48003 this.request = requestSidx_(playlist.sidx, playlist, this.hls_.xhr, { 48004 handleManifestRedirects: this.handleManifestRedirects 48005 }, this.sidxRequestFinished_(playlist, oldMaster, startingState, function (newMaster, sidx) { 48006 if (!newMaster || !sidx) { 48007 throw new Error('failed to request sidx'); 48008 } // update loader's sidxMapping with parsed sidx box 48009 48010 48011 sidxMapping[sidxKey].sidx = sidx; // everything is ready just continue to haveMetadata 48012 48013 _this3.haveMetadata({ 48014 startingState: startingState, 48015 playlist: newMaster.playlists[playlist.uri] 48016 }); 48017 })); 48018 } 48019 }, { 48020 key: 'haveMetadata', 48021 value: function haveMetadata(_ref) { 48022 var startingState = _ref.startingState, 48023 playlist = _ref.playlist; 48024 this.state = 'HAVE_METADATA'; 48025 this.loadedPlaylists_[playlist.uri] = playlist; 48026 this.mediaRequest_ = null; // This will trigger loadedplaylist 48027 48028 this.refreshMedia_(playlist.uri); // fire loadedmetadata the first time a media playlist is loaded 48029 // to resolve setup of media groups 48030 48031 if (startingState === 'HAVE_MASTER') { 48032 this.trigger('loadedmetadata'); 48033 } else { 48034 // trigger media change if the active media has been updated 48035 this.trigger('mediachange'); 48036 } 48037 } 48038 }, { 48039 key: 'pause', 48040 value: function pause() { 48041 this.stopRequest(); 48042 window$1.clearTimeout(this.mediaUpdateTimeout); 48043 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 48044 48045 if (this.state === 'HAVE_NOTHING') { 48046 // If we pause the loader before any data has been retrieved, its as if we never 48047 // started, so reset to an unstarted state. 48048 this.started = false; 48049 } 48050 } 48051 }, { 48052 key: 'load', 48053 value: function load(isFinalRendition) { 48054 var _this4 = this; 48055 48056 window$1.clearTimeout(this.mediaUpdateTimeout); 48057 window$1.clearTimeout(this.minimumUpdatePeriodTimeout_); 48058 var media = this.media(); 48059 48060 if (isFinalRendition) { 48061 var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 48062 this.mediaUpdateTimeout = window$1.setTimeout(function () { 48063 return _this4.load(); 48064 }, delay); 48065 return; 48066 } // because the playlists are internal to the manifest, load should either load the 48067 // main manifest, or do nothing but trigger an event 48068 48069 48070 if (!this.started) { 48071 this.start(); 48072 return; 48073 } 48074 48075 this.trigger('loadedplaylist'); 48076 } 48077 /** 48078 * Parses the master xml string and updates playlist uri references 48079 * 48080 * @return {Object} 48081 * The parsed mpd manifest object 48082 */ 48083 48084 }, { 48085 key: 'parseMasterXml', 48086 value: function parseMasterXml() { 48087 var master = parse(this.masterXml_, { 48088 manifestUri: this.srcUrl, 48089 clientOffset: this.clientOffset_, 48090 sidxMapping: this.sidxMapping_ 48091 }); 48092 master.uri = this.srcUrl; // Set up phony URIs for the playlists since we won't have external URIs for DASH 48093 // but reference playlists by their URI throughout the project 48094 // TODO: Should we create the dummy uris in mpd-parser as well (leaning towards yes). 48095 48096 for (var i = 0; i < master.playlists.length; i++) { 48097 var phonyUri = 'placeholder-uri-' + i; 48098 master.playlists[i].uri = phonyUri; // set up by URI references 48099 48100 master.playlists[phonyUri] = master.playlists[i]; 48101 } // set up phony URIs for the media group playlists since we won't have external 48102 // URIs for DASH but reference playlists by their URI throughout the project 48103 48104
vendor: 25,700 bytes, lines 48105-48875
48105 forEachMediaGroup(master, function (properties, mediaType, groupKey, labelKey) { 48106 if (properties.playlists && properties.playlists.length) { 48107 var _phonyUri = 'placeholder-uri-' + mediaType + '-' + groupKey + '-' + labelKey; 48108 48109 properties.playlists[0].uri = _phonyUri; // setup URI references 48110 48111 master.playlists[_phonyUri] = properties.playlists[0]; 48112 } 48113 }); 48114 setupMediaPlaylists(master); 48115 resolveMediaGroupUris(master); 48116 return master; 48117 } 48118 }, { 48119 key: 'start', 48120 value: function start() { 48121 var _this5 = this; 48122 48123 this.started = true; // We don't need to request the master manifest again 48124 // Call this asynchronously to match the xhr request behavior below 48125 48126 if (this.masterPlaylistLoader_) { 48127 this.mediaRequest_ = window$1.setTimeout(this.haveMaster_.bind(this), 0); 48128 return; 48129 } // request the specified URL 48130 48131 48132 this.request = this.hls_.xhr({ 48133 uri: this.srcUrl, 48134 withCredentials: this.withCredentials 48135 }, function (error, req) { 48136 // disposed 48137 if (!_this5.request) { 48138 return; 48139 } // clear the loader's request reference 48140 48141 48142 _this5.request = null; 48143 48144 if (error) { 48145 _this5.error = { 48146 status: req.status, 48147 message: 'DASH playlist request error at URL: ' + _this5.srcUrl, 48148 responseText: req.responseText, 48149 // MEDIA_ERR_NETWORK 48150 code: 2 48151 }; 48152 48153 if (_this5.state === 'HAVE_NOTHING') { 48154 _this5.started = false; 48155 } 48156 48157 return _this5.trigger('error'); 48158 } 48159 48160 _this5.masterXml_ = req.responseText; 48161 48162 if (req.responseHeaders && req.responseHeaders.date) { 48163 _this5.masterLoaded_ = Date.parse(req.responseHeaders.date); 48164 } else { 48165 _this5.masterLoaded_ = Date.now(); 48166 } 48167 48168 _this5.srcUrl = resolveManifestRedirect(_this5.handleManifestRedirects, _this5.srcUrl, req); 48169 48170 _this5.syncClientServerClock_(_this5.onClientServerClockSync_.bind(_this5)); 48171 }); 48172 } 48173 /** 48174 * Parses the master xml for UTCTiming node to sync the client clock to the server 48175 * clock. If the UTCTiming node requires a HEAD or GET request, that request is made. 48176 * 48177 * @param {Function} done 48178 * Function to call when clock sync has completed 48179 */ 48180 48181 }, { 48182 key: 'syncClientServerClock_', 48183 value: function syncClientServerClock_(done) { 48184 var _this6 = this; 48185 48186 var utcTiming = parseUTCTiming(this.masterXml_); // No UTCTiming element found in the mpd. Use Date header from mpd request as the 48187 // server clock 48188 48189 if (utcTiming === null) { 48190 this.clientOffset_ = this.masterLoaded_ - Date.now(); 48191 return done(); 48192 } 48193 48194 if (utcTiming.method === 'DIRECT') { 48195 this.clientOffset_ = utcTiming.value - Date.now(); 48196 return done(); 48197 } 48198 48199 this.request = this.hls_.xhr({ 48200 uri: resolveUrl$1(this.srcUrl, utcTiming.value), 48201 method: utcTiming.method, 48202 withCredentials: this.withCredentials 48203 }, function (error, req) { 48204 // disposed 48205 if (!_this6.request) { 48206 return; 48207 } 48208 48209 if (error) { 48210 // sync request failed, fall back to using date header from mpd 48211 // TODO: log warning 48212 _this6.clientOffset_ = _this6.masterLoaded_ - Date.now(); 48213 return done(); 48214 } 48215 48216 var serverTime = void 0; 48217 48218 if (utcTiming.method === 'HEAD') { 48219 if (!req.responseHeaders || !req.responseHeaders.date) { 48220 // expected date header not preset, fall back to using date header from mpd 48221 // TODO: log warning 48222 serverTime = _this6.masterLoaded_; 48223 } else { 48224 serverTime = Date.parse(req.responseHeaders.date); 48225 } 48226 } else { 48227 serverTime = Date.parse(req.responseText); 48228 } 48229 48230 _this6.clientOffset_ = serverTime - Date.now(); 48231 done(); 48232 }); 48233 } 48234 }, { 48235 key: 'haveMaster_', 48236 value: function haveMaster_() { 48237 this.state = 'HAVE_MASTER'; // clear media request 48238 48239 this.mediaRequest_ = null; 48240 48241 if (!this.masterPlaylistLoader_) { 48242 this.master = this.parseMasterXml(); // We have the master playlist at this point, so 48243 // trigger this to allow MasterPlaylistController 48244 // to make an initial playlist selection 48245 48246 this.trigger('loadedplaylist'); 48247 } else if (!this.media_) { 48248 // no media playlist was specifically selected so select 48249 // the one the child playlist loader was created with 48250 this.media(this.childPlaylist_); 48251 } 48252 } 48253 /** 48254 * Handler for after client/server clock synchronization has happened. Sets up 48255 * xml refresh timer if specificed by the manifest. 48256 */ 48257 48258 }, { 48259 key: 'onClientServerClockSync_', 48260 value: function onClientServerClockSync_() { 48261 var _this7 = this; 48262 48263 this.haveMaster_(); 48264 48265 if (!this.hasPendingRequest() && !this.media_) { 48266 this.media(this.master.playlists[0]); 48267 } // TODO: minimumUpdatePeriod can have a value of 0. Currently the manifest will not 48268 // be refreshed when this is the case. The inter-op guide says that when the 48269 // minimumUpdatePeriod is 0, the manifest should outline all currently available 48270 // segments, but future segments may require an update. I think a good solution 48271 // would be to update the manifest at the same rate that the media playlists 48272 // are "refreshed", i.e. every targetDuration. 48273 48274 48275 if (this.master && this.master.minimumUpdatePeriod) { 48276 this.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () { 48277 _this7.trigger('minimumUpdatePeriod'); 48278 }, this.master.minimumUpdatePeriod); 48279 } 48280 } 48281 /** 48282 * Sends request to refresh the master xml and updates the parsed master manifest 48283 * TODO: Does the client offset need to be recalculated when the xml is refreshed? 48284 */ 48285 48286 }, { 48287 key: 'refreshXml_', 48288 value: function refreshXml_() { 48289 var _this8 = this; // The srcUrl here *may* need to pass through handleManifestsRedirects when 48290 // sidx is implemented 48291 48292 48293 this.request = this.hls_.xhr({ 48294 uri: this.srcUrl, 48295 withCredentials: this.withCredentials 48296 }, function (error, req) { 48297 // disposed 48298 if (!_this8.request) { 48299 return; 48300 } // clear the loader's request reference 48301 48302 48303 _this8.request = null; 48304 48305 if (error) { 48306 _this8.error = { 48307 status: req.status, 48308 message: 'DASH playlist request error at URL: ' + _this8.srcUrl, 48309 responseText: req.responseText, 48310 // MEDIA_ERR_NETWORK 48311 code: 2 48312 }; 48313 48314 if (_this8.state === 'HAVE_NOTHING') { 48315 _this8.started = false; 48316 } 48317 48318 return _this8.trigger('error'); 48319 } 48320 48321 _this8.masterXml_ = req.responseText; // This will filter out updated sidx info from the mapping 48322 48323 _this8.sidxMapping_ = filterChangedSidxMappings(_this8.masterXml_, _this8.srcUrl, _this8.clientOffset_, _this8.sidxMapping_); 48324 48325 var master = _this8.parseMasterXml(); 48326 48327 var updatedMaster = updateMaster$1(_this8.master, master); 48328 48329 var currentSidxInfo = _this8.media().sidx; 48330 48331 if (updatedMaster) { 48332 if (currentSidxInfo) { 48333 var sidxKey = generateSidxKey(currentSidxInfo); // the sidx was updated, so the previous mapping was removed 48334 48335 if (!_this8.sidxMapping_[sidxKey]) { 48336 var playlist = _this8.media(); 48337 48338 _this8.request = requestSidx_(playlist.sidx, playlist, _this8.hls_.xhr, { 48339 handleManifestRedirects: _this8.handleManifestRedirects 48340 }, _this8.sidxRequestFinished_(playlist, master, _this8.state, function (newMaster, sidx) { 48341 if (!newMaster || !sidx) { 48342 throw new Error('failed to request sidx on minimumUpdatePeriod'); 48343 } // update loader's sidxMapping with parsed sidx box 48344 48345 48346 _this8.sidxMapping_[sidxKey].sidx = sidx; 48347 _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () { 48348 _this8.trigger('minimumUpdatePeriod'); 48349 }, _this8.master.minimumUpdatePeriod); // TODO: do we need to reload the current playlist? 48350 48351 _this8.refreshMedia_(_this8.media().uri); 48352 48353 return; 48354 })); 48355 } 48356 } else { 48357 _this8.master = updatedMaster; 48358 } 48359 } 48360 48361 _this8.minimumUpdatePeriodTimeout_ = window$1.setTimeout(function () { 48362 _this8.trigger('minimumUpdatePeriod'); 48363 }, _this8.master.minimumUpdatePeriod); 48364 }); 48365 } 48366 /** 48367 * Refreshes the media playlist by re-parsing the master xml and updating playlist 48368 * references. If this is an alternate loader, the updated parsed manifest is retrieved 48369 * from the master loader. 48370 */ 48371 48372 }, { 48373 key: 'refreshMedia_', 48374 value: function refreshMedia_(mediaUri) { 48375 var _this9 = this; 48376 48377 if (!mediaUri) { 48378 throw new Error('refreshMedia_ must take a media uri'); 48379 } 48380 48381 var oldMaster = void 0; 48382 var newMaster = void 0; 48383 48384 if (this.masterPlaylistLoader_) { 48385 oldMaster = this.masterPlaylistLoader_.master; 48386 newMaster = this.masterPlaylistLoader_.parseMasterXml(); 48387 } else { 48388 oldMaster = this.master; 48389 newMaster = this.parseMasterXml(); 48390 } 48391 48392 var updatedMaster = updateMaster$1(oldMaster, newMaster); 48393 48394 if (updatedMaster) { 48395 if (this.masterPlaylistLoader_) { 48396 this.masterPlaylistLoader_.master = updatedMaster; 48397 } else { 48398 this.master = updatedMaster; 48399 } 48400 48401 this.media_ = updatedMaster.playlists[mediaUri]; 48402 } else { 48403 this.media_ = newMaster.playlists[mediaUri]; 48404 this.trigger('playlistunchanged'); 48405 } 48406 48407 if (!this.media().endList) { 48408 this.mediaUpdateTimeout = window$1.setTimeout(function () { 48409 _this9.trigger('mediaupdatetimeout'); 48410 }, refreshDelay(this.media(), !!updatedMaster)); 48411 } 48412 48413 this.trigger('loadedplaylist'); 48414 } 48415 }]); 48416 return DashPlaylistLoader; 48417 }(EventTarget$1$1); 48418 48419 var logger = function logger(source) { 48420 if (videojs$1.log.debug) { 48421 return videojs$1.log.debug.bind(videojs$1, 'VHS:', source + ' >'); 48422 } 48423 48424 return function () {}; 48425 }; 48426 48427 function noop$1() {} 48428 /** 48429 * @file source-updater.js 48430 */ 48431 48432 /** 48433 * A queue of callbacks to be serialized and applied when a 48434 * MediaSource and its associated SourceBuffers are not in the 48435 * updating state. It is used by the segment loader to update the 48436 * underlying SourceBuffers when new data is loaded, for instance. 48437 * 48438 * @class SourceUpdater 48439 * @param {MediaSource} mediaSource the MediaSource to create the 48440 * SourceBuffer from 48441 * @param {String} mimeType the desired MIME type of the underlying 48442 * SourceBuffer 48443 * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer is 48444 * added to the media source 48445 */ 48446 48447 48448 var SourceUpdater = function () { 48449 function SourceUpdater(mediaSource, mimeType, type, sourceBufferEmitter) { 48450 classCallCheck$1(this, SourceUpdater); 48451 this.callbacks_ = []; 48452 this.pendingCallback_ = null; 48453 this.timestampOffset_ = 0; 48454 this.mediaSource = mediaSource; 48455 this.processedAppend_ = false; 48456 this.type_ = type; 48457 this.mimeType_ = mimeType; 48458 this.logger_ = logger('SourceUpdater[' + type + '][' + mimeType + ']'); 48459 48460 if (mediaSource.readyState === 'closed') { 48461 mediaSource.addEventListener('sourceopen', this.createSourceBuffer_.bind(this, mimeType, sourceBufferEmitter)); 48462 } else { 48463 this.createSourceBuffer_(mimeType, sourceBufferEmitter); 48464 } 48465 } 48466 48467 createClass$1(SourceUpdater, [{ 48468 key: 'createSourceBuffer_', 48469 value: function createSourceBuffer_(mimeType, sourceBufferEmitter) { 48470 var _this = this; 48471 48472 this.sourceBuffer_ = this.mediaSource.addSourceBuffer(mimeType); 48473 this.logger_('created SourceBuffer'); 48474 48475 if (sourceBufferEmitter) { 48476 sourceBufferEmitter.trigger('sourcebufferadded'); 48477 48478 if (this.mediaSource.sourceBuffers.length < 2) { 48479 // There's another source buffer we must wait for before we can start updating 48480 // our own (or else we can get into a bad state, i.e., appending video/audio data 48481 // before the other video/audio source buffer is available and leading to a video 48482 // or audio only buffer). 48483 sourceBufferEmitter.on('sourcebufferadded', function () { 48484 _this.start_(); 48485 }); 48486 return; 48487 } 48488 } 48489 48490 this.start_(); 48491 } 48492 }, { 48493 key: 'start_', 48494 value: function start_() { 48495 var _this2 = this; 48496 48497 this.started_ = true; // run completion handlers and process callbacks as updateend 48498 // events fire 48499 48500 this.onUpdateendCallback_ = function () { 48501 var pendingCallback = _this2.pendingCallback_; 48502 _this2.pendingCallback_ = null; 48503 _this2.sourceBuffer_.removing = false; 48504 48505 _this2.logger_('buffered [' + printableRange(_this2.buffered()) + ']'); 48506 48507 if (pendingCallback) { 48508 pendingCallback(); 48509 } 48510 48511 _this2.runCallback_(); 48512 }; 48513 48514 this.sourceBuffer_.addEventListener('updateend', this.onUpdateendCallback_); 48515 this.runCallback_(); 48516 } 48517 /** 48518 * Aborts the current segment and resets the segment parser. 48519 * 48520 * @param {Function} done function to call when done 48521 * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void 48522 */ 48523 48524 }, { 48525 key: 'abort', 48526 value: function abort(done) { 48527 var _this3 = this; 48528 48529 if (this.processedAppend_) { 48530 this.queueCallback_(function () { 48531 _this3.sourceBuffer_.abort(); 48532 }, done); 48533 } 48534 } 48535 /** 48536 * Queue an update to append an ArrayBuffer. 48537 * 48538 * @param {ArrayBuffer} bytes 48539 * @param {Function} done the function to call when done 48540 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 48541 */ 48542 48543 }, { 48544 key: 'appendBuffer', 48545 value: function appendBuffer(config, done) { 48546 var _this4 = this; 48547 48548 this.processedAppend_ = true; 48549 this.queueCallback_(function () { 48550 if (config.videoSegmentTimingInfoCallback) { 48551 _this4.sourceBuffer_.addEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); 48552 } 48553 48554 _this4.sourceBuffer_.appendBuffer(config.bytes); 48555 }, function () { 48556 if (config.videoSegmentTimingInfoCallback) { 48557 _this4.sourceBuffer_.removeEventListener('videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); 48558 } 48559 48560 done(); 48561 }); 48562 } 48563 /** 48564 * Indicates what TimeRanges are buffered in the managed SourceBuffer. 48565 * 48566 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered 48567 */ 48568 48569 }, { 48570 key: 'buffered', 48571 value: function buffered() { 48572 if (!this.sourceBuffer_) { 48573 return videojs$1.createTimeRanges(); 48574 } 48575 48576 return this.sourceBuffer_.buffered; 48577 } 48578 /** 48579 * Queue an update to remove a time range from the buffer. 48580 * 48581 * @param {Number} start where to start the removal 48582 * @param {Number} end where to end the removal 48583 * @param {Function} [done=noop] optional callback to be executed when the remove 48584 * operation is complete 48585 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 48586 */ 48587 48588 }, { 48589 key: 'remove', 48590 value: function remove(start, end) { 48591 var _this5 = this; 48592 48593 var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : noop$1; 48594 48595 if (this.processedAppend_) { 48596 this.queueCallback_(function () { 48597 _this5.logger_('remove [' + start + ' => ' + end + ']'); 48598 48599 _this5.sourceBuffer_.removing = true; 48600 48601 _this5.sourceBuffer_.remove(start, end); 48602 }, done); 48603 } 48604 } 48605 /** 48606 * Whether the underlying sourceBuffer is updating or not 48607 * 48608 * @return {Boolean} the updating status of the SourceBuffer 48609 */ 48610 48611 }, { 48612 key: 'updating', 48613 value: function updating() { 48614 // we are updating if the sourcebuffer is updating or 48615 return !this.sourceBuffer_ || this.sourceBuffer_.updating || // if we have a pending callback that is not our internal noop 48616 !!this.pendingCallback_ && this.pendingCallback_ !== noop$1; 48617 } 48618 /** 48619 * Set/get the timestampoffset on the SourceBuffer 48620 * 48621 * @return {Number} the timestamp offset 48622 */ 48623 48624 }, { 48625 key: 'timestampOffset', 48626 value: function timestampOffset(offset) { 48627 var _this6 = this; 48628 48629 if (typeof offset !== 'undefined') { 48630 this.queueCallback_(function () { 48631 _this6.sourceBuffer_.timestampOffset = offset; 48632 48633 _this6.runCallback_(); 48634 }); 48635 this.timestampOffset_ = offset; 48636 } 48637 48638 return this.timestampOffset_; 48639 } 48640 /** 48641 * Queue a callback to run 48642 */ 48643 48644 }, { 48645 key: 'queueCallback_', 48646 value: function queueCallback_(callback, done) { 48647 this.callbacks_.push([callback.bind(this), done]); 48648 this.runCallback_(); 48649 } 48650 /** 48651 * Run a queued callback 48652 */ 48653 48654 }, { 48655 key: 'runCallback_', 48656 value: function runCallback_() { 48657 var callbacks = void 0; 48658 48659 if (!this.updating() && this.callbacks_.length && this.started_) { 48660 callbacks = this.callbacks_.shift(); 48661 this.pendingCallback_ = callbacks[1]; 48662 callbacks[0](); 48663 } 48664 } 48665 /** 48666 * dispose of the source updater and the underlying sourceBuffer 48667 */ 48668 48669 }, { 48670 key: 'dispose', 48671 value: function dispose() { 48672 var _this7 = this; 48673 48674 var disposeFn = function disposeFn() { 48675 if (_this7.sourceBuffer_ && _this7.mediaSource.readyState === 'open') { 48676 _this7.sourceBuffer_.abort(); 48677 } 48678 48679 _this7.sourceBuffer_.removeEventListener('updateend', disposeFn); 48680 }; 48681 48682 this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_); 48683 48684 if (this.sourceBuffer_.removing) { 48685 this.sourceBuffer_.addEventListener('updateend', disposeFn); 48686 } else { 48687 disposeFn(); 48688 } 48689 } 48690 }]); 48691 return SourceUpdater; 48692 }(); 48693 48694 var Config = { 48695 GOAL_BUFFER_LENGTH: 30, 48696 MAX_GOAL_BUFFER_LENGTH: 60, 48697 GOAL_BUFFER_LENGTH_RATE: 1, 48698 // 0.5 MB/s 48699 INITIAL_BANDWIDTH: 4194304, 48700 // A fudge factor to apply to advertised playlist bitrates to account for 48701 // temporary flucations in client bandwidth 48702 BANDWIDTH_VARIANCE: 1.2, 48703 // How much of the buffer must be filled before we consider upswitching 48704 BUFFER_LOW_WATER_LINE: 0, 48705 MAX_BUFFER_LOW_WATER_LINE: 30, 48706 BUFFER_LOW_WATER_LINE_RATE: 1 48707 }; 48708 var REQUEST_ERRORS = { 48709 FAILURE: 2, 48710 TIMEOUT: -101, 48711 ABORTED: -102 48712 }; 48713 /** 48714 * Abort all requests 48715 * 48716 * @param {Object} activeXhrs - an object that tracks all XHR requests 48717 */ 48718 48719 var abortAll = function abortAll(activeXhrs) { 48720 activeXhrs.forEach(function (xhr) { 48721 xhr.abort(); 48722 }); 48723 }; 48724 /** 48725 * Gather important bandwidth stats once a request has completed 48726 * 48727 * @param {Object} request - the XHR request from which to gather stats 48728 */ 48729 48730 48731 var getRequestStats = function getRequestStats(request) { 48732 return { 48733 bandwidth: request.bandwidth, 48734 bytesReceived: request.bytesReceived || 0, 48735 roundTripTime: request.roundTripTime || 0 48736 }; 48737 }; 48738 /** 48739 * If possible gather bandwidth stats as a request is in 48740 * progress 48741 * 48742 * @param {Event} progressEvent - an event object from an XHR's progress event 48743 */ 48744 48745 48746 var getProgressStats = function getProgressStats(progressEvent) { 48747 var request = progressEvent.target; 48748 var roundTripTime = Date.now() - request.requestTime; 48749 var stats = { 48750 bandwidth: Infinity, 48751 bytesReceived: 0, 48752 roundTripTime: roundTripTime || 0 48753 }; 48754 stats.bytesReceived = progressEvent.loaded; // This can result in Infinity if stats.roundTripTime is 0 but that is ok 48755 // because we should only use bandwidth stats on progress to determine when 48756 // abort a request early due to insufficient bandwidth 48757 48758 stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000); 48759 return stats; 48760 }; 48761 /** 48762 * Handle all error conditions in one place and return an object 48763 * with all the information 48764 * 48765 * @param {Error|null} error - if non-null signals an error occured with the XHR 48766 * @param {Object} request - the XHR request that possibly generated the error 48767 */ 48768 48769 48770 var handleErrors = function handleErrors(error, request) { 48771 if (request.timedout) { 48772 return { 48773 status: request.status, 48774 message: 'HLS request timed-out at URL: ' + request.uri, 48775 code: REQUEST_ERRORS.TIMEOUT, 48776 xhr: request 48777 }; 48778 } 48779 48780 if (request.aborted) { 48781 return { 48782 status: request.status, 48783 message: 'HLS request aborted at URL: ' + request.uri, 48784 code: REQUEST_ERRORS.ABORTED, 48785 xhr: request 48786 }; 48787 } 48788 48789 if (error) { 48790 return { 48791 status: request.status, 48792 message: 'HLS request errored at URL: ' + request.uri, 48793 code: REQUEST_ERRORS.FAILURE, 48794 xhr: request 48795 }; 48796 } 48797 48798 return null; 48799 }; 48800 /** 48801 * Handle responses for key data and convert the key data to the correct format 48802 * for the decryption step later 48803 * 48804 * @param {Object} segment - a simplified copy of the segmentInfo object 48805 * from SegmentLoader 48806 * @param {Function} finishProcessingFn - a callback to execute to continue processing 48807 * this request 48808 */ 48809 48810 48811 var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) { 48812 return function (error, request) { 48813 var response = request.response; 48814 var errorObj = handleErrors(error, request); 48815 48816 if (errorObj) { 48817 return finishProcessingFn(errorObj, segment); 48818 } 48819 48820 if (response.byteLength !== 16) { 48821 return finishProcessingFn({ 48822 status: request.status, 48823 message: 'Invalid HLS key at URL: ' + request.uri, 48824 code: REQUEST_ERRORS.FAILURE, 48825 xhr: request 48826 }, segment); 48827 } 48828 48829 var view = new DataView(response); 48830 segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 48831 return finishProcessingFn(null, segment); 48832 }; 48833 }; 48834 /** 48835 * Handle init-segment responses 48836 * 48837 * @param {Object} segment - a simplified copy of the segmentInfo object 48838 * from SegmentLoader 48839 * @param {Function} finishProcessingFn - a callback to execute to continue processing 48840 * this request 48841 */ 48842 48843 48844 var handleInitSegmentResponse = function handleInitSegmentResponse(segment, captionParser, finishProcessingFn) { 48845 return function (error, request) { 48846 var response = request.response; 48847 var errorObj = handleErrors(error, request); 48848 48849 if (errorObj) { 48850 return finishProcessingFn(errorObj, segment); 48851 } // stop processing if received empty content 48852 48853 48854 if (response.byteLength === 0) { 48855 return finishProcessingFn({ 48856 status: request.status, 48857 message: 'Empty HLS segment content at URL: ' + request.uri, 48858 code: REQUEST_ERRORS.FAILURE, 48859 xhr: request 48860 }, segment); 48861 } 48862 48863 segment.map.bytes = new Uint8Array(request.response); // Initialize CaptionParser if it hasn't been yet 48864 48865 if (captionParser && !captionParser.isInitialized()) { 48866 captionParser.init(); 48867 } 48868 48869 segment.map.timescales = probe.timescale(segment.map.bytes); 48870 segment.map.videoTrackIds = probe.videoTrackIds(segment.map.bytes); 48871 return finishProcessingFn(null, segment); 48872 }; 48873 }; 48874 /** 48875 * Response handler for segment-requests being sure to set the correct
48876 * property depending on whether the segment is encryped or not 48877 * Also records and keeps track of stats that are used for ABR purposes 48878 * 48879 * @param {Object} segment - a simplified copy of the segmentInfo object 48880 * from SegmentLoader 48881 * @param {Function} finishProcessingFn - a callback to execute to continue processing 48882 * this request 48883 */ 48884 48885 48886 var handleSegmentResponse = function handleSegmentResponse(segment, captionParser, finishProcessingFn) { 48887 return function (error, request) { 48888 var response = request.response; 48889 var errorObj = handleErrors(error, request); 48890 var parsed = void 0; 48891 48892 if (errorObj) { 48893 return finishProcessingFn(errorObj, segment); 48894 } // stop processing if received empty content 48895 48896 48897 if (response.byteLength === 0) { 48898 return finishProcessingFn({ 48899 status: request.status, 48900 message: 'Empty HLS segment content at URL: ' + request.uri, 48901 code: REQUEST_ERRORS.FAILURE, 48902 xhr: request 48903 }, segment); 48904 } 48905 48906 segment.stats = getRequestStats(request); 48907 48908 if (segment.key) { 48909 segment.encryptedBytes = new Uint8Array(request.response); 48910 } else { 48911 segment.bytes = new Uint8Array(request.response); 48912 } // This is likely an FMP4 and has the init segment. 48913 // Run through the CaptionParser in case there are captions. 48914 48915 48916 if (captionParser && segment.map && segment.map.bytes) { 48917 // Initialize CaptionParser if it hasn't been yet 48918 if (!captionParser.isInitialized()) { 48919 captionParser.init(); 48920 } 48921 48922 parsed = captionParser.parse(segment.bytes, segment.map.videoTrackIds, segment.map.timescales); 48923 48924 if (parsed && parsed.captions) { 48925 segment.captionStreams = parsed.captionStreams; 48926 segment.fmp4Captions = parsed.captions; 48927 } 48928 } 48929 48930 return finishProcessingFn(null, segment); 48931 }; 48932 }; 48933 /** 48934 * Decrypt the segment via the decryption web worker 48935 * 48936 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 48937 * @param {Object} segment - a simplified copy of the segmentInfo object 48938 * from SegmentLoader 48939 * @param {Function} doneFn - a callback that is executed after decryption has completed 48940 */ 48941 48942 48943 var decryptSegment = function decryptSegment(decrypter, segment, doneFn) { 48944 var decryptionHandler = function decryptionHandler(event) { 48945 if (event.data.source === segment.requestId) { 48946 decrypter.removeEventListener('message', decryptionHandler); 48947 var decrypted = event.data.decrypted; 48948 segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength); 48949 return doneFn(null, segment); 48950 } 48951 }; 48952 48953 decrypter.addEventListener('message', decryptionHandler); 48954 var keyBytes = void 0; 48955 48956 if (segment.key.bytes.slice) { 48957 keyBytes = segment.key.bytes.slice(); 48958 } else { 48959 keyBytes = new Uint32Array(Array.prototype.slice.call(segment.key.bytes)); 48960 } // this is an encrypted segment 48961 // incrementally decrypt the segment 48962 48963 48964 decrypter.postMessage(createTransferableMessage({ 48965 source: segment.requestId, 48966 encrypted: segment.encryptedBytes, 48967 key: keyBytes, 48968 iv: segment.key.iv 48969 }), [segment.encryptedBytes.buffer, keyBytes.buffer]); 48970 }; 48971 /** 48972 * This function waits for all XHRs to finish (with either success or failure) 48973 * before continueing processing via it's callback. The function gathers errors 48974 * from each request into a single errors array so that the error status for 48975 * each request can be examined later. 48976 * 48977 * @param {Object} activeXhrs - an object that tracks all XHR requests 48978 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 48979 * @param {Function} doneFn - a callback that is executed after all resources have been 48980 * downloaded and any decryption completed 48981 */ 48982 48983 48984 var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) { 48985 var count = 0; 48986 var didError = false; 48987 return function (error, segment) { 48988 if (didError) { 48989 return; 48990 } 48991 48992 if (error) { 48993 didError = true; // If there are errors, we have to abort any outstanding requests 48994 48995 abortAll(activeXhrs); // Even though the requests above are aborted, and in theory we could wait until we 48996 // handle the aborted events from those requests, there are some cases where we may 48997 // never get an aborted event. For instance, if the network connection is lost and
vendor: 9,438 bytes, lines 48998-49258
48998 // there were two requests, the first may have triggered an error immediately, while 48999 // the second request remains unsent. In that case, the aborted algorithm will not 49000 // trigger an abort: see https://xhr.spec.whatwg.org/#the-abort()-method 49001 // 49002 // We also can't rely on the ready state of the XHR, since the request that 49003 // triggered the connection error may also show as a ready state of 0 (unsent). 49004 // Therefore, we have to finish this group of requests immediately after the first 49005 // seen error. 49006 49007 return doneFn(error, segment); 49008 } 49009 49010 count += 1; 49011 49012 if (count === activeXhrs.length) { 49013 // Keep track of when *all* of the requests have completed 49014 segment.endOfAllRequests = Date.now(); 49015 49016 if (segment.encryptedBytes) { 49017 return decryptSegment(decrypter, segment, doneFn); 49018 } // Otherwise, everything is ready just continue 49019 49020 49021 return doneFn(null, segment); 49022 } 49023 }; 49024 }; 49025 /** 49026 * Simple progress event callback handler that gathers some stats before 49027 * executing a provided callback with the `segment` object 49028 * 49029 * @param {Object} segment - a simplified copy of the segmentInfo object 49030 * from SegmentLoader 49031 * @param {Function} progressFn - a callback that is executed each time a progress event 49032 * is received 49033 * @param {Event} event - the progress event object from XMLHttpRequest 49034 */ 49035 49036 49037 var handleProgress = function handleProgress(segment, progressFn) { 49038 return function (event) { 49039 segment.stats = videojs$1.mergeOptions(segment.stats, getProgressStats(event)); // record the time that we receive the first byte of data 49040 49041 if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) { 49042 segment.stats.firstBytesReceivedAt = Date.now(); 49043 } 49044 49045 return progressFn(event, segment); 49046 }; 49047 }; 49048 /** 49049 * Load all resources and does any processing necessary for a media-segment 49050 * 49051 * Features: 49052 * decrypts the media-segment if it has a key uri and an iv 49053 * aborts *all* requests if *any* one request fails 49054 * 49055 * The segment object, at minimum, has the following format: 49056 * { 49057 * resolvedUri: String, 49058 * [byterange]: { 49059 * offset: Number, 49060 * length: Number 49061 * }, 49062 * [key]: { 49063 * resolvedUri: String 49064 * [byterange]: { 49065 * offset: Number, 49066 * length: Number 49067 * }, 49068 * iv: { 49069 * bytes: Uint32Array 49070 * } 49071 * }, 49072 * [map]: { 49073 * resolvedUri: String, 49074 * [byterange]: { 49075 * offset: Number, 49076 * length: Number 49077 * }, 49078 * [bytes]: Uint8Array 49079 * } 49080 * } 49081 * ...where [name] denotes optional properties 49082 * 49083 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js 49084 * @param {Object} xhrOptions - the base options to provide to all xhr requests 49085 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 49086 * decryption routines 49087 * @param {Object} segment - a simplified copy of the segmentInfo object 49088 * from SegmentLoader 49089 * @param {Function} progressFn - a callback that receives progress events from the main 49090 * segment's xhr request 49091 * @param {Function} doneFn - a callback that is executed only once all requests have 49092 * succeeded or failed 49093 * @returns {Function} a function that, when invoked, immediately aborts all 49094 * outstanding requests 49095 */ 49096 49097 49098 var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, captionParser, segment, progressFn, doneFn) { 49099 var activeXhrs = []; 49100 var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); // optionally, request the decryption key 49101 49102 if (segment.key && !segment.key.bytes) { 49103 var keyRequestOptions = videojs$1.mergeOptions(xhrOptions, { 49104 uri: segment.key.resolvedUri, 49105 responseType: 'arraybuffer' 49106 }); 49107 var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn); 49108 var keyXhr = xhr(keyRequestOptions, keyRequestCallback); 49109 activeXhrs.push(keyXhr); 49110 } // optionally, request the associated media init segment 49111 49112 49113 if (segment.map && !segment.map.bytes) { 49114 var initSegmentOptions = videojs$1.mergeOptions(xhrOptions, { 49115 uri: segment.map.resolvedUri, 49116 responseType: 'arraybuffer', 49117 headers: segmentXhrHeaders(segment.map) 49118 }); 49119 var initSegmentRequestCallback = handleInitSegmentResponse(segment, captionParser, finishProcessingFn); 49120 var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); 49121 activeXhrs.push(initSegmentXhr); 49122 } 49123 49124 var segmentRequestOptions = videojs$1.mergeOptions(xhrOptions, { 49125 uri: segment.resolvedUri, 49126 responseType: 'arraybuffer', 49127 headers: segmentXhrHeaders(segment) 49128 }); 49129 var segmentRequestCallback = handleSegmentResponse(segment, captionParser, finishProcessingFn); 49130 var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); 49131 segmentXhr.addEventListener('progress', handleProgress(segment, progressFn)); 49132 activeXhrs.push(segmentXhr); 49133 return function () { 49134 return abortAll(activeXhrs); 49135 }; 49136 }; // Utilities 49137 49138 /** 49139 * Returns the CSS value for the specified property on an element 49140 * using `getComputedStyle`. Firefox has a long-standing issue where 49141 * getComputedStyle() may return null when running in an iframe with 49142 * `display: none`. 49143 * 49144 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 49145 * @param {HTMLElement} el the htmlelement to work on 49146 * @param {string} the proprety to get the style for 49147 */ 49148 49149 49150 var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 49151 var result = void 0; 49152 49153 if (!el) { 49154 return ''; 49155 } 49156 49157 result = window$1.getComputedStyle(el); 49158 49159 if (!result) { 49160 return ''; 49161 } 49162 49163 return result[property]; 49164 }; 49165 /** 49166 * Resuable stable sort function 49167 * 49168 * @param {Playlists} array 49169 * @param {Function} sortFn Different comparators 49170 * @function stableSort 49171 */ 49172 49173 49174 var stableSort = function stableSort(array, sortFn) { 49175 var newArray = array.slice(); 49176 array.sort(function (left, right) { 49177 var cmp = sortFn(left, right); 49178 49179 if (cmp === 0) { 49180 return newArray.indexOf(left) - newArray.indexOf(right); 49181 } 49182 49183 return cmp; 49184 }); 49185 }; 49186 /** 49187 * A comparator function to sort two playlist object by bandwidth. 49188 * 49189 * @param {Object} left a media playlist object 49190 * @param {Object} right a media playlist object 49191 * @return {Number} Greater than zero if the bandwidth attribute of 49192 * left is greater than the corresponding attribute of right. Less 49193 * than zero if the bandwidth of right is greater than left and 49194 * exactly zero if the two are equal. 49195 */ 49196 49197 49198 var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) { 49199 var leftBandwidth = void 0; 49200 var rightBandwidth = void 0; 49201 49202 if (left.attributes.BANDWIDTH) { 49203 leftBandwidth = left.attributes.BANDWIDTH; 49204 } 49205 49206 leftBandwidth = leftBandwidth || window$1.Number.MAX_VALUE; 49207 49208 if (right.attributes.BANDWIDTH) { 49209 rightBandwidth = right.attributes.BANDWIDTH; 49210 } 49211 49212 rightBandwidth = rightBandwidth || window$1.Number.MAX_VALUE; 49213 return leftBandwidth - rightBandwidth; 49214 }; 49215 /** 49216 * A comparator function to sort two playlist object by resolution (width). 49217 * @param {Object} left a media playlist object 49218 * @param {Object} right a media playlist object 49219 * @return {Number} Greater than zero if the resolution.width attribute of 49220 * left is greater than the corresponding attribute of right. Less 49221 * than zero if the resolution.width of right is greater than left and 49222 * exactly zero if the two are equal. 49223 */ 49224 49225 49226 var comparePlaylistResolution = function comparePlaylistResolution(left, right) { 49227 var leftWidth = void 0; 49228 var rightWidth = void 0; 49229 49230 if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 49231 leftWidth = left.attributes.RESOLUTION.width; 49232 } 49233 49234 leftWidth = leftWidth || window$1.Number.MAX_VALUE; 49235 49236 if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 49237 rightWidth = right.attributes.RESOLUTION.width; 49238 } 49239 49240 rightWidth = rightWidth || window$1.Number.MAX_VALUE; // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 49241 // have the same media dimensions/ resolution 49242 49243 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 49244 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 49245 } 49246 49247 return leftWidth - rightWidth; 49248 }; 49249 /** 49250 * Chooses the appropriate media playlist based on bandwidth and player size 49251 * 49252 * @param {Object} master 49253 * Object representation of the master manifest 49254 * @param {Number} playerBandwidth 49255 * Current calculated bandwidth of the player 49256 * @param {Number} playerWidth 49257 * Current width of the player element 49258 * @param {Number}
49258 playerHeight 49259 * Current height of the player element 49260 * @param {Boolean} limitRenditionByPlayerDimensions 49261 * True if the player width and height should be used during the selection, false otherwise 49262 * @return {Playlist} the highest bitrate playlist less than the 49263 * currently detected bandwidth, accounting for some amount of 49264 * bandwidth variance 49265 */ 49266 49267 49268 var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight, limitRenditionByPlayerDimensions) { 49269 // convert the playlists to an intermediary representation to make comparisons easier 49270 var sortedPlaylistReps = master.playlists.map(function (playlist) { 49271 var width = void 0; 49272 var height = void 0; 49273 var bandwidth = void 0; 49274 width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width; 49275 height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height; 49276 bandwidth = playlist.attributes.BANDWIDTH; 49277 bandwidth = bandwidth || window$1.Number.MAX_VALUE; 49278 return { 49279 bandwidth: bandwidth, 49280 width: width, 49281 height: height, 49282 playlist: playlist 49283 }; 49284 }); 49285 stableSort(sortedPlaylistReps, function (left, right) { 49286 return left.bandwidth - right.bandwidth; 49287 }); // filter out any playlists that have been excluded due to 49288 // incompatible configurations 49289 49290 sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) { 49291 return !Playlist.isIncompatible(rep.playlist); 49292 }); // filter out any playlists that have been disabled manually through the representations 49293 // api or blacklisted temporarily due to playback errors. 49294 49295 var enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 49296 return Playlist.isEnabled(rep.playlist); 49297 }); 49298 49299 if (!enabledPlaylistReps.length) { 49300 // if there are no enabled playlists, then they have all been blacklisted or disabled 49301 // by the user through the representations api. In this case, ignore blacklisting and 49302 // fallback to what the user wants by using playlists the user has not disabled. 49303 enabledPlaylistReps = sortedPlaylistReps.filter(function (rep) { 49304 return !Playlist.isDisabled(rep.playlist); 49305 }); 49306 } // filter out any variant that has greater effective bitrate 49307 // than the current estimated bandwidth 49308 49309 49310 var bandwidthPlaylistReps = enabledPlaylistReps.filter(function (rep) { 49311 return rep.bandwidth * Config.BANDWIDTH_VARIANCE < playerBandwidth; 49312 }); 49313 var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; // get all of the renditions with the same (highest) bandwidth 49314 // and then taking the very first element 49315 49316 var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) { 49317 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 49318 })[0]; // if we're not going to limit renditions by player size, make an early decision. 49319 49320 if (limitRenditionByPlayerDimensions === false) { 49321 var _chosenRep = bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 49322 49323 return _chosenRep ? _chosenRep.playlist : null; 49324 } // filter out playlists without resolution information 49325 49326 49327 var haveResolution = bandwidthPlaylistReps.filter(function (rep) { 49328 return rep.width && rep.height; 49329 }); // sort variants by resolution 49330 49331 stableSort(haveResolution, function (left, right) { 49332 return left.width - right.width; 49333 }); // if we have the exact resolution as the player use it 49334 49335 var resolutionBestRepList = haveResolution.filter(function (rep) { 49336 return rep.width === playerWidth && rep.height === playerHeight; 49337 }); 49338 highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; // ensure that we pick the highest bandwidth variant that have exact resolution 49339 49340 var resolutionBestRep = resolutionBestRepList.filter(function (rep) { 49341 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 49342 })[0]; 49343 var resolutionPlusOneList = void 0; 49344 var resolutionPlusOneSmallest = void 0; 49345 var resolutionPlusOneRep = void 0; // find the smallest variant that is larger than the player 49346 // if there is no match of exact resolution 49347 49348 if (!resolutionBestRep) { 49349 resolutionPlusOneList = haveResolution.filter(function (rep) { 49350 return rep.width > playerWidth || rep.height >
vendor: 131,030 bytes, lines 49350-52891
49350 playerHeight; 49351 }); // find all the variants have the same smallest resolution 49352 49353 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) { 49354 return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height; 49355 }); // ensure that we also pick the highest bandwidth variant that 49356 // is just-larger-than the video player 49357 49358 highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1]; 49359 resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) { 49360 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 49361 })[0]; 49362 } // fallback chain of variants 49363 49364 49365 var chosenRep = resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || enabledPlaylistReps[0] || sortedPlaylistReps[0]; 49366 return chosenRep ? chosenRep.playlist : null; 49367 }; // Playlist Selectors 49368 49369 /** 49370 * Chooses the appropriate media playlist based on the most recent 49371 * bandwidth estimate and the player size. 49372 * 49373 * Expects to be called within the context of an instance of HlsHandler 49374 * 49375 * @return {Playlist} the highest bitrate playlist less than the 49376 * currently detected bandwidth, accounting for some amount of 49377 * bandwidth variance 49378 */ 49379 49380 49381 var lastBandwidthSelector = function lastBandwidthSelector() { 49382 return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10), this.limitRenditionByPlayerDimensions); 49383 }; 49384 /** 49385 * Chooses the appropriate media playlist based on the potential to rebuffer 49386 * 49387 * @param {Object} settings 49388 * Object of information required to use this selector 49389 * @param {Object} settings.master 49390 * Object representation of the master manifest 49391 * @param {Number} settings.currentTime 49392 * The current time of the player 49393 * @param {Number} settings.bandwidth 49394 * Current measured bandwidth 49395 * @param {Number} settings.duration 49396 * Duration of the media 49397 * @param {Number} settings.segmentDuration 49398 * Segment duration to be used in round trip time calculations 49399 * @param {Number} settings.timeUntilRebuffer 49400 * Time left in seconds until the player has to rebuffer 49401 * @param {Number} settings.currentTimeline 49402 * The current timeline segments are being loaded from 49403 * @param {SyncController} settings.syncController 49404 * SyncController for determining if we have a sync point for a given playlist 49405 * @return {Object|null} 49406 * {Object} return.playlist 49407 * The highest bandwidth playlist with the least amount of rebuffering 49408 * {Number} return.rebufferingImpact 49409 * The amount of time in seconds switching to this playlist will rebuffer. A 49410 * negative value means that switching will cause zero rebuffering. 49411 */ 49412 49413 49414 var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) { 49415 var master = settings.master, 49416 currentTime = settings.currentTime, 49417 bandwidth = settings.bandwidth, 49418 duration$$1 = settings.duration, 49419 segmentDuration = settings.segmentDuration, 49420 timeUntilRebuffer = settings.timeUntilRebuffer, 49421 currentTimeline = settings.currentTimeline, 49422 syncController = settings.syncController; // filter out any playlists that have been excluded due to 49423 // incompatible configurations 49424 49425 var compatiblePlaylists = master.playlists.filter(function (playlist) { 49426 return !Playlist.isIncompatible(playlist); 49427 }); // filter out any playlists that have been disabled manually through the representations 49428 // api or blacklisted temporarily due to playback errors. 49429 49430 var enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled); 49431 49432 if (!enabledPlaylists.length) { 49433 // if there are no enabled playlists, then they have all been blacklisted or disabled 49434 // by the user through the representations api. In this case, ignore blacklisting and 49435 // fallback to what the user wants by using playlists the user has not disabled. 49436 enabledPlaylists = compatiblePlaylists.filter(function (playlist) { 49437 return !Playlist.isDisabled(playlist); 49438 }); 49439 } 49440 49441 var bandwidthPlaylists = enabledPlaylists.filter(Playlist.hasAttribute.bind(null, 'BANDWIDTH')); 49442 var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) { 49443 var syncPoint = syncController.getSyncPoint(playlist, duration$$1, currentTimeline, currentTime); // If there is no sync point for this playlist, switching to it will require a 49444 // sync request first. This will double the request time 49445 49446 var numRequests = syncPoint ? 1 : 2; 49447 var requestTimeEstimate = Playlist.estimateSegmentRequestTime(segmentDuration, bandwidth, playlist); 49448 var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer; 49449 return { 49450 playlist: playlist, 49451 rebufferingImpact: rebufferingImpact 49452 }; 49453 }); 49454 var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) { 49455 return estimate.rebufferingImpact <= 0; 49456 }); // Sort by bandwidth DESC 49457 49458 stableSort(noRebufferingPlaylists, function (a, b) { 49459 return comparePlaylistBandwidth(b.playlist, a.playlist); 49460 }); 49461 49462 if (noRebufferingPlaylists.length) { 49463 return noRebufferingPlaylists[0]; 49464 } 49465 49466 stableSort(rebufferingEstimates, function (a, b) { 49467 return a.rebufferingImpact - b.rebufferingImpact; 49468 }); 49469 return rebufferingEstimates[0] || null; 49470 }; 49471 /** 49472 * Chooses the appropriate media playlist, which in this case is the lowest bitrate 49473 * one with video. If no renditions with video exist, return the lowest audio rendition. 49474 * 49475 * Expects to be called within the context of an instance of HlsHandler 49476 * 49477 * @return {Object|null} 49478 * {Object} return.playlist 49479 * The lowest bitrate playlist that contains a video codec. If no such rendition 49480 * exists pick the lowest audio rendition. 49481 */ 49482 49483 49484 var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() { 49485 // filter out any playlists that have been excluded due to 49486 // incompatible configurations or playback errors 49487 var playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); // Sort ascending by bitrate 49488 49489 stableSort(playlists, function (a, b) { 49490 return comparePlaylistBandwidth(a, b); 49491 }); // Parse and assume that playlists with no video codec have no video 49492 // (this is not necessarily true, although it is generally true). 49493 // 49494 // If an entire manifest has no valid videos everything will get filtered 49495 // out. 49496 49497 var playlistsWithVideo = playlists.filter(function (playlist) { 49498 return parseCodecs(playlist.attributes.CODECS).videoCodec; 49499 }); 49500 return playlistsWithVideo[0] || null; 49501 }; 49502 /** 49503 * Create captions text tracks on video.js if they do not exist 49504 * 49505 * @param {Object} inbandTextTracks a reference to current inbandTextTracks 49506 * @param {Object} tech the video.js tech 49507 * @param {Object} captionStreams the caption streams to create 49508 * @private 49509 */ 49510 49511 49512 var createCaptionsTrackIfNotExists = function createCaptionsTrackIfNotExists(inbandTextTracks, tech, captionStreams) { 49513 for (var trackId in captionStreams) { 49514 if (!inbandTextTracks[trackId]) { 49515 tech.trigger({ 49516 type: 'usage', 49517 name: 'hls-608' 49518 }); 49519 var track = tech.textTracks().getTrackById(trackId); 49520 49521 if (track) { 49522 // Resuse an existing track with a CC# id because this was 49523 // very likely created by videojs-contrib-hls from information 49524 // in the m3u8 for us to use 49525 inbandTextTracks[trackId] = track; 49526 } else { 49527 // Otherwise, create a track with the default `CC#` label and 49528 // without a language 49529 inbandTextTracks[trackId] = tech.addRemoteTextTrack({ 49530 kind: 'captions', 49531 id: trackId, 49532 label: trackId 49533 }, false).track; 49534 } 49535 } 49536 } 49537 }; 49538 49539 var addCaptionData = function addCaptionData(_ref) { 49540 var inbandTextTracks = _ref.inbandTextTracks, 49541 captionArray = _ref.captionArray, 49542 timestampOffset = _ref.timestampOffset; 49543 49544 if (!captionArray) { 49545 return; 49546 } 49547 49548 var Cue = window.WebKitDataCue || window.VTTCue; 49549 captionArray.forEach(function (caption) { 49550 var track = caption.stream; 49551 var startTime = caption.startTime; 49552 var endTime = caption.endTime; 49553 49554 if (!inbandTextTracks[track]) { 49555 return; 49556 } 49557 49558 startTime += timestampOffset; 49559 endTime += timestampOffset; 49560 inbandTextTracks[track].addCue(new Cue(startTime, endTime, caption.text)); 49561 }); 49562 }; 49563 /** 49564 * @file segment-loader.js 49565 */ 49566 // in ms 49567 49568 49569 var CHECK_BUFFER_DELAY = 500; 49570 /** 49571 * Determines if we should call endOfStream on the media source based 49572 * on the state of the buffer or if appened segment was the final 49573 * segment in the playlist. 49574 * 49575 * @param {Object} playlist a media playlist object 49576 * @param {Object} mediaSource the MediaSource object 49577 * @param {Number} segmentIndex the index of segment we last appended 49578 * @returns {Boolean} do we need to call endOfStream on the MediaSource 49579 */ 49580 49581 var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) { 49582 if (!playlist || !mediaSource) { 49583 return false; 49584 } 49585 49586 var segments = playlist.segments; // determine a few boolean values to help make the branch below easier 49587 // to read 49588 49589 var appendedLastSegment = segmentIndex === segments.length; // if we've buffered to the end of the video, we need to call endOfStream 49590 // so that MediaSources can trigger the `ended` event when it runs out of 49591 // buffered data instead of waiting for me 49592 49593 return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment; 49594 }; 49595 49596 var finite = function finite(num) { 49597 return typeof num === 'number' && isFinite(num); 49598 }; 49599 49600 var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) { 49601 // Although these checks should most likely cover non 'main' types, for now it narrows 49602 // the scope of our checks. 49603 if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) { 49604 return null; 49605 } 49606 49607 if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) { 49608 return 'Neither audio nor video found in segment.'; 49609 } 49610 49611 if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) { 49612 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.'; 49613 } 49614 49615 if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) { 49616 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.'; 49617 } 49618 49619 return null; 49620 }; 49621 /** 49622 * Calculates a time value that is safe to remove from the back buffer without interupting 49623 * playback. 49624 * 49625 * @param {TimeRange} seekable 49626 * The current seekable range 49627 * @param {Number} currentTime 49628 * The current time of the player 49629 * @param {Number} targetDuration 49630 * The target duration of the current playlist 49631 * @return {Number} 49632 * Time that is safe to remove from the back buffer without interupting playback 49633 */ 49634 49635 49636 var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable$$1, currentTime, targetDuration) { 49637 var removeToTime = void 0; 49638 49639 if (seekable$$1.length && seekable$$1.start(0) > 0 && seekable$$1.start(0) < currentTime) { 49640 // If we have a seekable range use that as the limit for what can be removed safely 49641 removeToTime = seekable$$1.start(0); 49642 } else { 49643 // otherwise remove anything older than 30 seconds before the current play head 49644 removeToTime = currentTime - 30; 49645 } // Don't allow removing from the buffer within target duration of current time 49646 // to avoid the possibility of removing the GOP currently being played which could 49647 // cause playback stalls. 49648 49649 49650 return Math.min(removeToTime, currentTime - targetDuration); 49651 }; 49652 49653 var segmentInfoString = function segmentInfoString(segmentInfo) { 49654 var _segmentInfo$segment = segmentInfo.segment, 49655 start = _segmentInfo$segment.start, 49656 end = _segmentInfo$segment.end, 49657 _segmentInfo$playlist = segmentInfo.playlist, 49658 seq = _segmentInfo$playlist.mediaSequence, 49659 id = _segmentInfo$playlist.id, 49660 _segmentInfo$playlist2 = _segmentInfo$playlist.segments, 49661 segments = _segmentInfo$playlist2 === undefined ? [] : _segmentInfo$playlist2, 49662 index = segmentInfo.mediaIndex, 49663 timeline = segmentInfo.timeline; 49664 return ['appending [' + index + '] of [' + seq + ', ' + (seq + segments.length) + '] from playlist [' + id + ']', '[' + start + ' => ' + end + '] in timeline [' + timeline + ']'].join(' '); 49665 }; 49666 /** 49667 * An object that manages segment loading and appending. 49668 * 49669 * @class SegmentLoader 49670 * @param {Object} options required and optional options 49671 * @extends videojs.EventTarget 49672 */ 49673 49674 49675 var SegmentLoader = function (_videojs$EventTarget) { 49676 inherits$1(SegmentLoader, _videojs$EventTarget); 49677 49678 function SegmentLoader(settings) { 49679 classCallCheck$1(this, SegmentLoader); // check pre-conditions 49680 49681 var _this = possibleConstructorReturn$1(this, (SegmentLoader.__proto__ || Object.getPrototypeOf(SegmentLoader)).call(this)); 49682 49683 if (!settings) { 49684 throw new TypeError('Initialization settings are required'); 49685 } 49686 49687 if (typeof settings.currentTime !== 'function') { 49688 throw new TypeError('No currentTime getter specified'); 49689 } 49690 49691 if (!settings.mediaSource) { 49692 throw new TypeError('No MediaSource specified'); 49693 } // public properties 49694 49695 49696 _this.bandwidth = settings.bandwidth; 49697 _this.throughput = { 49698 rate: 0, 49699 count: 0 49700 }; 49701 _this.roundTrip = NaN; 49702 49703 _this.resetStats_(); 49704 49705 _this.mediaIndex = null; // private settings 49706 49707 _this.hasPlayed_ = settings.hasPlayed; 49708 _this.currentTime_ = settings.currentTime; 49709 _this.seekable_ = settings.seekable; 49710 _this.seeking_ = settings.seeking; 49711 _this.duration_ = settings.duration; 49712 _this.mediaSource_ = settings.mediaSource; 49713 _this.hls_ = settings.hls; 49714 _this.loaderType_ = settings.loaderType; 49715 _this.startingMedia_ = void 0; 49716 _this.segmentMetadataTrack_ = settings.segmentMetadataTrack; 49717 _this.goalBufferLength_ = settings.goalBufferLength; 49718 _this.sourceType_ = settings.sourceType; 49719 _this.inbandTextTracks_ = settings.inbandTextTracks; 49720 _this.state_ = 'INIT'; // private instance variables 49721 49722 _this.checkBufferTimeout_ = null; 49723 _this.error_ = void 0; 49724 _this.currentTimeline_ = -1; 49725 _this.pendingSegment_ = null; 49726 _this.mimeType_ = null; 49727 _this.sourceUpdater_ = null; 49728 _this.xhrOptions_ = null; // Fragmented mp4 playback 49729 49730 _this.activeInitSegmentId_ = null; 49731 _this.initSegments_ = {}; // HLSe playback 49732 49733 _this.cacheEncryptionKeys_ = settings.cacheEncryptionKeys; 49734 _this.keyCache_ = {}; // Fmp4 CaptionParser 49735 49736 if (_this.loaderType_ === 'main') { 49737 _this.captionParser_ = new captionParser(); 49738 } else { 49739 _this.captionParser_ = null; 49740 } 49741 49742 _this.decrypter_ = settings.decrypter; // Manages the tracking and generation of sync-points, mappings 49743 // between a time in the display time and a segment index within 49744 // a playlist 49745 49746 _this.syncController_ = settings.syncController; 49747 _this.syncPoint_ = { 49748 segmentIndex: 0, 49749 time: 0 49750 }; 49751 49752 _this.syncController_.on('syncinfoupdate', function () { 49753 return _this.trigger('syncinfoupdate'); 49754 }); 49755 49756 _this.mediaSource_.addEventListener('sourceopen', function () { 49757 return _this.ended_ = false; 49758 }); // ...for determining the fetch location 49759 49760 49761 _this.fetchAtBuffer_ = false; 49762 _this.logger_ = logger('SegmentLoader[' + _this.loaderType_ + ']'); 49763 Object.defineProperty(_this, 'state', { 49764 get: function get$$1() { 49765 return this.state_; 49766 }, 49767 set: function set$$1(newState) { 49768 if (newState !== this.state_) { 49769 this.logger_(this.state_ + ' -> ' + newState); 49770 this.state_ = newState; 49771 } 49772 } 49773 }); 49774 return _this; 49775 } 49776 /** 49777 * reset all of our media stats 49778 * 49779 * @private 49780 */ 49781 49782 49783 createClass$1(SegmentLoader, [{ 49784 key: 'resetStats_', 49785 value: function resetStats_() { 49786 this.mediaBytesTransferred = 0; 49787 this.mediaRequests = 0; 49788 this.mediaRequestsAborted = 0; 49789 this.mediaRequestsTimedout = 0; 49790 this.mediaRequestsErrored = 0; 49791 this.mediaTransferDuration = 0; 49792 this.mediaSecondsLoaded = 0; 49793 } 49794 /** 49795 * dispose of the SegmentLoader and reset to the default state 49796 */ 49797 49798 }, { 49799 key: 'dispose', 49800 value: function dispose() { 49801 this.state = 'DISPOSED'; 49802 this.pause(); 49803 this.abort_(); 49804 49805 if (this.sourceUpdater_) { 49806 this.sourceUpdater_.dispose(); 49807 } 49808 49809 this.resetStats_(); 49810 49811 if (this.captionParser_) { 49812 this.captionParser_.reset(); 49813 } 49814 } 49815 /** 49816 * abort anything that is currently doing on with the SegmentLoader 49817 * and reset to a default state 49818 */ 49819 49820 }, { 49821 key: 'abort', 49822 value: function abort() { 49823 if (this.state !== 'WAITING') { 49824 if (this.pendingSegment_) { 49825 this.pendingSegment_ = null; 49826 } 49827 49828 return; 49829 } 49830 49831 this.abort_(); // We aborted the requests we were waiting on, so reset the loader's state to READY 49832 // since we are no longer "waiting" on any requests. XHR callback is not always run 49833 // when the request is aborted. This will prevent the loader from being stuck in the 49834 // WAITING state indefinitely. 49835 49836 this.state = 'READY'; // don't wait for buffer check timeouts to begin fetching the 49837 // next segment 49838 49839 if (!this.paused()) { 49840 this.monitorBuffer_(); 49841 } 49842 } 49843 /** 49844 * abort all pending xhr requests and null any pending segements 49845 * 49846 * @private 49847 */ 49848 49849 }, { 49850 key: 'abort_', 49851 value: function abort_() { 49852 if (this.pendingSegment_) { 49853 this.pendingSegment_.abortRequests(); 49854 } // clear out the segment being processed 49855 49856 49857 this.pendingSegment_ = null; 49858 } 49859 /** 49860 * set an error on the segment loader and null out any pending segements 49861 * 49862 * @param {Error} error the error to set on the SegmentLoader 49863 * @return {Error} the error that was set or that is currently set 49864 */ 49865 49866 }, { 49867 key: 'error', 49868 value: function error(_error) { 49869 if (typeof _error !== 'undefined') { 49870 this.error_ = _error; 49871 } 49872 49873 this.pendingSegment_ = null; 49874 return this.error_; 49875 } 49876 }, { 49877 key: 'endOfStream', 49878 value: function endOfStream() { 49879 this.ended_ = true; 49880 this.pause(); 49881 this.trigger('ended'); 49882 } 49883 /** 49884 * Indicates which time ranges are buffered 49885 * 49886 * @return {TimeRange} 49887 * TimeRange object representing the current buffered ranges 49888 */ 49889 49890 }, { 49891 key: 'buffered_', 49892 value: function buffered_() { 49893 if (!this.sourceUpdater_) { 49894 return videojs$1.createTimeRanges(); 49895 } 49896 49897 return this.sourceUpdater_.buffered(); 49898 } 49899 /** 49900 * Gets and sets init segment for the provided map 49901 * 49902 * @param {Object} map 49903 * The map object representing the init segment to get or set 49904 * @param {Boolean=} set 49905 * If true, the init segment for the provided map should be saved 49906 * @return {Object} 49907 * map object for desired init segment 49908 */ 49909 49910 }, { 49911 key: 'initSegment', 49912 value: function initSegment(map) { 49913 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 49914 49915 if (!map) { 49916 return null; 49917 } 49918 49919 var id = initSegmentId(map); 49920 var storedMap = this.initSegments_[id]; 49921 49922 if (set$$1 && !storedMap && map.bytes) { 49923 this.initSegments_[id] = storedMap = { 49924 resolvedUri: map.resolvedUri, 49925 byterange: map.byterange, 49926 bytes: map.bytes, 49927 timescales: map.timescales, 49928 videoTrackIds: map.videoTrackIds 49929 }; 49930 } 49931 49932 return storedMap || map; 49933 } 49934 /** 49935 * Gets and sets key for the provided key 49936 * 49937 * @param {Object} key 49938 * The key object representing the key to get or set 49939 * @param {Boolean=} set 49940 * If true, the key for the provided key should be saved 49941 * @return {Object} 49942 * Key object for desired key 49943 */ 49944 49945 }, { 49946 key: 'segmentKey', 49947 value: function segmentKey(key) { 49948 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 49949 49950 if (!key) { 49951 return null; 49952 } 49953 49954 var id = segmentKeyId(key); 49955 var storedKey = this.keyCache_[id]; // TODO: We should use the HTTP Expires header to invalidate our cache per 49956 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.2.3 49957 49958 if (this.cacheEncryptionKeys_ && set$$1 && !storedKey && key.bytes) { 49959 this.keyCache_[id] = storedKey = { 49960 resolvedUri: key.resolvedUri, 49961 bytes: key.bytes 49962 }; 49963 } 49964 49965 var result = { 49966 resolvedUri: (storedKey || key).resolvedUri 49967 }; 49968 49969 if (storedKey) { 49970 result.bytes = storedKey.bytes; 49971 } 49972 49973 return result; 49974 } 49975 /** 49976 * Returns true if all configuration required for loading is present, otherwise false. 49977 * 49978 * @return {Boolean} True if the all configuration is ready for loading 49979 * @private 49980 */ 49981 49982 }, { 49983 key: 'couldBeginLoading_', 49984 value: function couldBeginLoading_() { 49985 return this.playlist_ && ( // the source updater is created when init_ is called, so either having a 49986 // source updater or being in the INIT state with a mimeType is enough 49987 // to say we have all the needed configuration to start loading. 49988 this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused(); 49989 } 49990 /** 49991 * load a playlist and start to fill the buffer 49992 */ 49993 49994 }, { 49995 key: 'load', 49996 value: function load() { 49997 // un-pause 49998 this.monitorBuffer_(); // if we don't have a playlist yet, keep waiting for one to be 49999 // specified 50000 50001 if (!this.playlist_) { 50002 return; 50003 } // not sure if this is the best place for this 50004 50005 50006 this.syncController_.setDateTimeMapping(this.playlist_); // if all the configuration is ready, initialize and begin loading 50007 50008 if (this.state === 'INIT' && this.couldBeginLoading_()) { 50009 return this.init_(); 50010 } // if we're in the middle of processing a segment already, don't 50011 // kick off an additional segment request 50012 50013 50014 if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') { 50015 return; 50016 } 50017 50018 this.state = 'READY'; 50019 } 50020 /** 50021 * Once all the starting parameters have been specified, begin 50022 * operation. This method should only be invoked from the INIT 50023 * state. 50024 * 50025 * @private 50026 */ 50027 50028 }, { 50029 key: 'init_', 50030 value: function init_() { 50031 this.state = 'READY'; 50032 this.sourceUpdater_ = new SourceUpdater(this.mediaSource_, this.mimeType_, this.loaderType_, this.sourceBufferEmitter_); 50033 this.resetEverything(); 50034 return this.monitorBuffer_(); 50035 } 50036 /** 50037 * set a playlist on the segment loader 50038 * 50039 * @param {PlaylistLoader} media the playlist to set on the segment loader 50040 */ 50041 50042 }, { 50043 key: 'playlist', 50044 value: function playlist(newPlaylist) { 50045 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 50046 50047 if (!newPlaylist) { 50048 return; 50049 } 50050 50051 var oldPlaylist = this.playlist_; 50052 var segmentInfo = this.pendingSegment_; 50053 this.playlist_ = newPlaylist; 50054 this.xhrOptions_ = options; // when we haven't started playing yet, the start of a live playlist 50055 // is always our zero-time so force a sync update each time the playlist 50056 // is refreshed from the server 50057 50058 if (!this.hasPlayed_()) { 50059 newPlaylist.syncInfo = { 50060 mediaSequence: newPlaylist.mediaSequence, 50061 time: 0 50062 }; 50063 } 50064 50065 var oldId = null; 50066 50067 if (oldPlaylist) { 50068 if (oldPlaylist.id) { 50069 oldId = oldPlaylist.id; 50070 } else if (oldPlaylist.uri) { 50071 oldId = oldPlaylist.uri; 50072 } 50073 } 50074 50075 this.logger_('playlist update [' + oldId + ' => ' + (newPlaylist.id || newPlaylist.uri) + ']'); // in VOD, this is always a rendition switch (or we updated our syncInfo above) 50076 // in LIVE, we always want to update with new playlists (including refreshes) 50077 50078 this.trigger('syncinfoupdate'); // if we were unpaused but waiting for a playlist, start 50079 // buffering now 50080 50081 if (this.state === 'INIT' && this.couldBeginLoading_()) { 50082 return this.init_(); 50083 } 50084 50085 if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) { 50086 if (this.mediaIndex !== null) { 50087 // we must "resync" the segment loader when we switch renditions and 50088 // the segment loader is already synced to the previous rendition 50089 this.resyncLoader(); 50090 } // the rest of this function depends on `oldPlaylist` being defined 50091 50092 50093 return; 50094 } // we reloaded the same playlist so we are in a live scenario 50095 // and we will likely need to adjust the mediaIndex 50096 50097 50098 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 50099 this.logger_('live window shift [' + mediaSequenceDiff + ']'); // update the mediaIndex on the SegmentLoader 50100 // this is important because we can abort a request and this value must be 50101 // equal to the last appended mediaIndex 50102 50103 if (this.mediaIndex !== null) { 50104 this.mediaIndex -= mediaSequenceDiff; 50105 } // update the mediaIndex on the SegmentInfo object 50106 // this is important because we will update this.mediaIndex with this value 50107 // in `handleUpdateEnd_` after the segment has been successfully appended 50108 50109 50110 if (segmentInfo) { 50111 segmentInfo.mediaIndex -= mediaSequenceDiff; // we need to update the referenced segment so that timing information is 50112 // saved for the new playlist's segment, however, if the segment fell off the 50113 // playlist, we can leave the old reference and just lose the timing info 50114 50115 if (segmentInfo.mediaIndex >= 0) { 50116 segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex]; 50117 } 50118 } 50119 50120 this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist); 50121 } 50122 /** 50123 * Prevent the loader from fetching additional segments. If there 50124 * is a segment request outstanding, it will finish processing 50125 * before the loader halts. A segment loader can be unpaused by 50126 * calling load(). 50127 */ 50128 50129 }, { 50130 key: 'pause', 50131 value: function pause() { 50132 if (this.checkBufferTimeout_) { 50133 window$1.clearTimeout(this.checkBufferTimeout_); 50134 this.checkBufferTimeout_ = null; 50135 } 50136 } 50137 /** 50138 * Returns whether the segment loader is fetching additional 50139 * segments when given the opportunity. This property can be 50140 * modified through calls to pause() and load(). 50141 */ 50142 50143 }, { 50144 key: 'paused', 50145 value: function paused() { 50146 return this.checkBufferTimeout_ === null; 50147 } 50148 /** 50149 * create/set the following mimetype on the SourceBuffer through a 50150 * SourceUpdater 50151 * 50152 * @param {String} mimeType the mime type string to use 50153 * @param {Object} sourceBufferEmitter an event emitter that fires when a source buffer 50154 * is added to the media source 50155 */ 50156 50157 }, { 50158 key: 'mimeType', 50159 value: function mimeType(_mimeType, sourceBufferEmitter) { 50160 if (this.mimeType_) { 50161 return; 50162 } 50163 50164 this.mimeType_ = _mimeType; 50165 this.sourceBufferEmitter_ = sourceBufferEmitter; // if we were unpaused but waiting for a sourceUpdater, start 50166 // buffering now 50167 50168 if (this.state === 'INIT' && this.couldBeginLoading_()) { 50169 this.init_(); 50170 } 50171 } 50172 /** 50173 * Delete all the buffered data and reset the SegmentLoader 50174 * @param {Function} [done] an optional callback to be executed when the remove 50175 * operation is complete 50176 */ 50177 50178 }, { 50179 key: 'resetEverything', 50180 value: function resetEverything(done) { 50181 this.ended_ = false; 50182 this.resetLoader(); 50183 this.remove(0, this.duration_(), done); // clears fmp4 captions 50184 50185 if (this.captionParser_) { 50186 this.captionParser_.clearAllCaptions(); 50187 } 50188 50189 this.trigger('reseteverything'); 50190 } 50191 /** 50192 * Force the SegmentLoader to resync and start loading around the currentTime instead 50193 * of starting at the end of the buffer 50194 * 50195 * Useful for fast quality changes 50196 */ 50197 50198 }, { 50199 key: 'resetLoader', 50200 value: function resetLoader() { 50201 this.fetchAtBuffer_ = false; 50202 this.resyncLoader(); 50203 } 50204 /** 50205 * Force the SegmentLoader to restart synchronization and make a conservative guess 50206 * before returning to the simple walk-forward method 50207 */ 50208 50209 }, { 50210 key: 'resyncLoader', 50211 value: function resyncLoader() { 50212 this.mediaIndex = null; 50213 this.syncPoint_ = null; 50214 this.abort(); 50215 } 50216 /** 50217 * Remove any data in the source buffer between start and end times 50218 * @param {Number} start - the start time of the region to remove from the buffer 50219 * @param {Number} end - the end time of the region to remove from the buffer 50220 * @param {Function} [done] - an optional callback to be executed when the remove 50221 * operation is complete 50222 */ 50223 50224 }, { 50225 key: 'remove', 50226 value: function remove(start, end, done) { 50227 if (this.sourceUpdater_) { 50228 this.sourceUpdater_.remove(start, end, done); 50229 } 50230 50231 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 50232 50233 if (this.inbandTextTracks_) { 50234 for (var id in this.inbandTextTracks_) { 50235 removeCuesFromTrack(start, end, this.inbandTextTracks_[id]); 50236 } 50237 } 50238 } 50239 /** 50240 * (re-)schedule monitorBufferTick_ to run as soon as possible 50241 * 50242 * @private 50243 */ 50244 50245 }, { 50246 key: 'monitorBuffer_', 50247 value: function monitorBuffer_() { 50248 if (this.checkBufferTimeout_) { 50249 window$1.clearTimeout(this.checkBufferTimeout_); 50250 } 50251 50252 this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), 1); 50253 } 50254 /** 50255 * As long as the SegmentLoader is in the READY state, periodically 50256 * invoke fillBuffer_(). 50257 * 50258 * @private 50259 */ 50260 50261 }, { 50262 key: 'monitorBufferTick_', 50263 value: function monitorBufferTick_() { 50264 if (this.state === 'READY') { 50265 this.fillBuffer_(); 50266 } 50267 50268 if (this.checkBufferTimeout_) { 50269 window$1.clearTimeout(this.checkBufferTimeout_); 50270 } 50271 50272 this.checkBufferTimeout_ = window$1.setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY); 50273 } 50274 /** 50275 * fill the buffer with segements unless the sourceBuffers are 50276 * currently updating 50277 * 50278 * Note: this function should only ever be called by monitorBuffer_ 50279 * and never directly 50280 * 50281 * @private 50282 */ 50283 50284 }, { 50285 key: 'fillBuffer_', 50286 value: function fillBuffer_() { 50287 if (this.sourceUpdater_.updating()) { 50288 return; 50289 } 50290 50291 if (!this.syncPoint_) { 50292 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 50293 } // see if we need to begin loading immediately 50294 50295 50296 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 50297 50298 if (!segmentInfo) { 50299 return; 50300 } 50301 50302 if (this.isEndOfStream_(segmentInfo.mediaIndex)) { 50303 this.endOfStream(); 50304 return; 50305 } 50306 50307 if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { 50308 return; 50309 } // We will need to change timestampOffset of the sourceBuffer if: 50310 // - The segment.timeline !== this.currentTimeline 50311 // (we are crossing a discontinuity somehow) 50312 // - The "timestampOffset" for the start of this segment is less than 50313 // the currently set timestampOffset 50314 // Also, clear captions if we are crossing a discontinuity boundary 50315 // Previously, we changed the timestampOffset if the start of this segment 50316 // is less than the currently set timestampOffset but this isn't wanted 50317 // as it can produce bad behavior, especially around long running 50318 // live streams 50319 50320 50321 if (segmentInfo.timeline !== this.currentTimeline_) { 50322 this.syncController_.reset(); 50323 segmentInfo.timestampOffset = segmentInfo.startOfSegment; 50324 50325 if (this.captionParser_) { 50326 this.captionParser_.clearAllCaptions(); 50327 } 50328 } 50329 50330 this.loadSegment_(segmentInfo); 50331 } 50332 /** 50333 * Determines if this segment loader is at the end of it's stream. 50334 * 50335 * @param {Number} mediaIndex the index of segment we last appended 50336 * @param {Object} [playlist=this.playlist_] a media playlist object 50337 * @returns {Boolean} true if at end of stream, false otherwise. 50338 */ 50339 50340 }, { 50341 key: 'isEndOfStream_', 50342 value: function isEndOfStream_(mediaIndex) { 50343 var playlist = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.playlist_; 50344 return detectEndOfStream(playlist, this.mediaSource_, mediaIndex) && !this.sourceUpdater_.updating(); 50345 } 50346 /** 50347 * Determines what segment request should be made, given current playback 50348 * state. 50349 * 50350 * @param {TimeRanges} buffered - the state of the buffer 50351 * @param {Object} playlist - the playlist object to fetch segments from 50352 * @param {Number} mediaIndex - the previous mediaIndex fetched or null 50353 * @param {Boolean} hasPlayed - a flag indicating whether we have played or not 50354 * @param {Number} currentTime - the playback position in seconds 50355 * @param {Object} syncPoint - a segment info object that describes the 50356 * @returns {Object} a segment request object that describes the segment to load 50357 */ 50358 50359 }, { 50360 key: 'checkBuffer_', 50361 value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) { 50362 var lastBufferedEnd = 0; 50363 var startOfSegment = void 0; 50364 50365 if (buffered.length) { 50366 lastBufferedEnd = buffered.end(buffered.length - 1); 50367 } 50368 50369 var bufferedTime = Math.max(0, lastBufferedEnd - currentTime); 50370 50371 if (!playlist.segments.length) { 50372 return null; 50373 } // if there is plenty of content buffered, and the video has 50374 // been played before relax for awhile 50375 50376 50377 if (bufferedTime >= this.goalBufferLength_()) { 50378 return null; 50379 } // if the video has not yet played once, and we already have 50380 // one segment downloaded do nothing 50381 50382 50383 if (!hasPlayed && bufferedTime >= 1) { 50384 return null; 50385 } // When the syncPoint is null, there is no way of determining a good 50386 // conservative segment index to fetch from 50387 // The best thing to do here is to get the kind of sync-point data by 50388 // making a request 50389 50390 50391 if (syncPoint === null) { 50392 mediaIndex = this.getSyncSegmentCandidate_(playlist); 50393 return this.generateSegmentInfo_(playlist, mediaIndex, null, true); 50394 } // Under normal playback conditions fetching is a simple walk forward 50395 50396 50397 if (mediaIndex !== null) { 50398 var segment = playlist.segments[mediaIndex]; 50399 startOfSegment = lastBufferedEnd; 50400 return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false); 50401 } // There is a sync-point but the lack of a mediaIndex indicates that 50402 // we need to make a good conservative guess about which segment to 50403 // fetch 50404 50405 50406 if (this.fetchAtBuffer_) { 50407 // Find the segment containing the end of the buffer 50408 var mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time); 50409 mediaIndex = mediaSourceInfo.mediaIndex; 50410 startOfSegment = mediaSourceInfo.startTime; 50411 } else { 50412 // Find the segment containing currentTime 50413 var _mediaSourceInfo = Playlist.getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time); 50414 50415 mediaIndex = _mediaSourceInfo.mediaIndex; 50416 startOfSegment = _mediaSourceInfo.startTime; 50417 } 50418 50419 return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false); 50420 } 50421 /** 50422 * The segment loader has no recourse except to fetch a segment in the 50423 * current playlist and use the internal timestamps in that segment to 50424 * generate a syncPoint. This function returns a good candidate index 50425 * for that process. 50426 * 50427 * @param {Object} playlist - the playlist object to look for a 50428 * @returns {Number} An index of a segment from the playlist to load 50429 */ 50430 50431 }, { 50432 key: 'getSyncSegmentCandidate_', 50433 value: function getSyncSegmentCandidate_(playlist) { 50434 var _this2 = this; 50435 50436 if (this.currentTimeline_ === -1) { 50437 return 0; 50438 } 50439 50440 var segmentIndexArray = playlist.segments.map(function (s, i) { 50441 return { 50442 timeline: s.timeline, 50443 segmentIndex: i 50444 }; 50445 }).filter(function (s) { 50446 return s.timeline === _this2.currentTimeline_; 50447 }); 50448 50449 if (segmentIndexArray.length) { 50450 return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex; 50451 } 50452 50453 return Math.max(playlist.segments.length - 1, 0); 50454 } 50455 }, { 50456 key: 'generateSegmentInfo_', 50457 value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) { 50458 if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) { 50459 return null; 50460 } 50461 50462 var segment = playlist.segments[mediaIndex]; 50463 return { 50464 requestId: 'segment-loader-' + Math.random(), 50465 // resolve the segment URL relative to the playlist 50466 uri: segment.resolvedUri, 50467 // the segment's mediaIndex at the time it was requested 50468 mediaIndex: mediaIndex, 50469 // whether or not to update the SegmentLoader's state with this 50470 // segment's mediaIndex 50471 isSyncRequest: isSyncRequest, 50472 startOfSegment: startOfSegment, 50473 // the segment's playlist 50474 playlist: playlist, 50475 // unencrypted bytes of the segment 50476 bytes: null, 50477 // when a key is defined for this segment, the encrypted bytes 50478 encryptedBytes: null, 50479 // The target timestampOffset for this segment when we append it 50480 // to the source buffer 50481 timestampOffset: null, 50482 // The timeline that the segment is in 50483 timeline: segment.timeline, 50484 // The expected duration of the segment in seconds 50485 duration: segment.duration, 50486 // retain the segment in case the playlist updates while doing an async process 50487 segment: segment 50488 }; 50489 } 50490 /** 50491 * Determines if the network has enough bandwidth to complete the current segment 50492 * request in a timely manner. If not, the request will be aborted early and bandwidth 50493 * updated to trigger a playlist switch. 50494 * 50495 * @param {Object} stats 50496 * Object containing stats about the request timing and size 50497 * @return {Boolean} True if the request was aborted, false otherwise 50498 * @private 50499 */ 50500 50501 }, { 50502 key: 'abortRequestEarly_', 50503 value: function abortRequestEarly_(stats) { 50504 if (this.hls_.tech_.paused() || // Don't abort if the current playlist is on the lowestEnabledRendition 50505 // TODO: Replace using timeout with a boolean indicating whether this playlist is 50506 // the lowestEnabledRendition. 50507 !this.xhrOptions_.timeout || // Don't abort if we have no bandwidth information to estimate segment sizes 50508 !this.playlist_.attributes.BANDWIDTH) { 50509 return false; 50510 } // Wait at least 1 second since the first byte of data has been received before 50511 // using the calculated bandwidth from the progress event to allow the bitrate 50512 // to stabilize 50513 50514 50515 if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) { 50516 return false; 50517 } 50518 50519 var currentTime = this.currentTime_(); 50520 var measuredBandwidth = stats.bandwidth; 50521 var segmentDuration = this.pendingSegment_.duration; 50522 var requestTimeRemaining = Playlist.estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); // Subtract 1 from the timeUntilRebuffer so we still consider an early abort 50523 // if we are only left with less than 1 second when the request completes. 50524 // A negative timeUntilRebuffering indicates we are already rebuffering 50525 50526 var timeUntilRebuffer$$1 = timeUntilRebuffer(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; // Only consider aborting early if the estimated time to finish the download 50527 // is larger than the estimated time until the player runs out of forward buffer 50528 50529 if (requestTimeRemaining <= timeUntilRebuffer$$1) { 50530 return false; 50531 } 50532 50533 var switchCandidate = minRebufferMaxBandwidthSelector({ 50534 master: this.hls_.playlists.master, 50535 currentTime: currentTime, 50536 bandwidth: measuredBandwidth, 50537 duration: this.duration_(), 50538 segmentDuration: segmentDuration, 50539 timeUntilRebuffer: timeUntilRebuffer$$1, 50540 currentTimeline: this.currentTimeline_, 50541 syncController: this.syncController_ 50542 }); 50543 50544 if (!switchCandidate) { 50545 return; 50546 } 50547 50548 var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer$$1; 50549 var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact; 50550 var minimumTimeSaving = 0.5; // If we are already rebuffering, increase the amount of variance we add to the 50551 // potential round trip time of the new request so that we are not too aggressive 50552 // with switching to a playlist that might save us a fraction of a second. 50553 50554 if (timeUntilRebuffer$$1 <= TIME_FUDGE_FACTOR) { 50555 minimumTimeSaving = 1; 50556 } 50557 50558 if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) { 50559 return false; 50560 } // set the bandwidth to that of the desired playlist being sure to scale by 50561 // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it 50562 // don't trigger a bandwidthupdate as the bandwidth is artifial 50563 50564 50565 this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * Config.BANDWIDTH_VARIANCE + 1; 50566 this.abort(); 50567 this.trigger('earlyabort'); 50568 return true; 50569 } 50570 /** 50571 * XHR `progress` event handler 50572 * 50573 * @param {Event} 50574 * The XHR `progress` event 50575 * @param {Object} simpleSegment 50576 * A simplified segment object copy 50577 * @private 50578 */ 50579 50580 }, { 50581 key: 'handleProgress_', 50582 value: function handleProgress_(event, simpleSegment) { 50583 if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) { 50584 return; 50585 } 50586 50587 this.trigger('progress'); 50588 } 50589 /** 50590 * load a specific segment from a request into the buffer 50591 * 50592 * @private 50593 */ 50594 50595 }, { 50596 key: 'loadSegment_', 50597 value: function loadSegment_(segmentInfo) { 50598 this.state = 'WAITING'; 50599 this.pendingSegment_ = segmentInfo; 50600 this.trimBackBuffer_(segmentInfo); 50601 segmentInfo.abortRequests = mediaSegmentRequest(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.captionParser_, this.createSimplifiedSegmentObj_(segmentInfo), // progress callback 50602 this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this)); 50603 } 50604 /** 50605 * trim the back buffer so that we don't have too much data 50606 * in the source buffer 50607 * 50608 * @private 50609 * 50610 * @param {Object} segmentInfo - the current segment 50611 */ 50612 50613 }, { 50614 key: 'trimBackBuffer_', 50615 value: function trimBackBuffer_(segmentInfo) { 50616 var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10); // Chrome has a hard limit of 150MB of 50617 // buffer and a very conservative "garbage collector" 50618 // We manually clear out the old buffer to ensure 50619 // we don't trigger the QuotaExceeded error 50620 // on the source buffer during subsequent appends 50621 50622 if (removeToTime > 0) { 50623 this.remove(0, removeToTime); 50624 } 50625 } 50626 /** 50627 * created a simplified copy of the segment object with just the 50628 * information necessary to perform the XHR and decryption 50629 * 50630 * @private 50631 * 50632 * @param {Object} segmentInfo - the current segment 50633 * @returns {Object} a simplified segment object copy 50634 */ 50635 50636 }, { 50637 key: 'createSimplifiedSegmentObj_', 50638 value: function createSimplifiedSegmentObj_(segmentInfo) { 50639 var segment = segmentInfo.segment; 50640 var simpleSegment = { 50641 resolvedUri: segment.resolvedUri, 50642 byterange: segment.byterange, 50643 requestId: segmentInfo.requestId 50644 }; 50645 50646 if (segment.key) { 50647 // if the media sequence is greater than 2^32, the IV will be incorrect 50648 // assuming 10s segments, that would be about 1300 years 50649 var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 50650 simpleSegment.key = this.segmentKey(segment.key); 50651 simpleSegment.key.iv = iv; 50652 } 50653 50654 if (segment.map) { 50655 simpleSegment.map = this.initSegment(segment.map); 50656 } 50657 50658 return simpleSegment; 50659 } 50660 /** 50661 * Handle the callback from the segmentRequest function and set the 50662 * associated SegmentLoader state and errors if necessary 50663 * 50664 * @private 50665 */ 50666 50667 }, { 50668 key: 'segmentRequestFinished_', 50669 value: function segmentRequestFinished_(error, simpleSegment) { 50670 // every request counts as a media request even if it has been aborted 50671 // or canceled due to a timeout 50672 this.mediaRequests += 1; 50673 50674 if (simpleSegment.stats) { 50675 this.mediaBytesTransferred += simpleSegment.stats.bytesReceived; 50676 this.mediaTransferDuration += simpleSegment.stats.roundTripTime; 50677 } // The request was aborted and the SegmentLoader has already been reset 50678 50679 50680 if (!this.pendingSegment_) { 50681 this.mediaRequestsAborted += 1; 50682 return; 50683 } // the request was aborted and the SegmentLoader has already started 50684 // another request. this can happen when the timeout for an aborted 50685 // request triggers due to a limitation in the XHR library 50686 // do not count this as any sort of request or we risk double-counting 50687 50688 50689 if (simpleSegment.requestId !== this.pendingSegment_.requestId) { 50690 return; 50691 } // an error occurred from the active pendingSegment_ so reset everything 50692 50693 50694 if (error) { 50695 this.pendingSegment_ = null; 50696 this.state = 'READY'; // the requests were aborted just record the aborted stat and exit 50697 // this is not a true error condition and nothing corrective needs 50698 // to be done 50699 50700 if (error.code === REQUEST_ERRORS.ABORTED) { 50701 this.mediaRequestsAborted += 1; 50702 return; 50703 } 50704 50705 this.pause(); // the error is really just that at least one of the requests timed-out 50706 // set the bandwidth to a very low value and trigger an ABR switch to 50707 // take emergency action 50708 50709 if (error.code === REQUEST_ERRORS.TIMEOUT) { 50710 this.mediaRequestsTimedout += 1; 50711 this.bandwidth = 1; 50712 this.roundTrip = NaN; 50713 this.trigger('bandwidthupdate'); 50714 return; 50715 } // if control-flow has arrived here, then the error is real 50716 // emit an error event to blacklist the current playlist 50717 50718 50719 this.mediaRequestsErrored += 1; 50720 this.error(error); 50721 this.trigger('error'); 50722 return; 50723 } // the response was a success so set any bandwidth stats the request 50724 // generated for ABR purposes 50725 50726 50727 this.bandwidth = simpleSegment.stats.bandwidth; 50728 this.roundTrip = simpleSegment.stats.roundTripTime; // if this request included an initialization segment, save that data 50729 // to the initSegment cache 50730 50731 if (simpleSegment.map) { 50732 simpleSegment.map = this.initSegment(simpleSegment.map, true); 50733 } // if this request included a segment key, save that data in the cache 50734 50735 50736 if (simpleSegment.key) { 50737 this.segmentKey(simpleSegment.key, true); 50738 } 50739 50740 this.processSegmentResponse_(simpleSegment); 50741 } 50742 /** 50743 * Move any important data from the simplified segment object 50744 * back to the real segment object for future phases 50745 * 50746 * @private 50747 */ 50748 50749 }, { 50750 key: 'processSegmentResponse_', 50751 value: function processSegmentResponse_(simpleSegment) { 50752 var segmentInfo = this.pendingSegment_; 50753 segmentInfo.bytes = simpleSegment.bytes; 50754 50755 if (simpleSegment.map) { 50756 segmentInfo.segment.map.bytes = simpleSegment.map.bytes; 50757 } 50758 50759 segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; // This has fmp4 captions, add them to text tracks 50760 50761 if (simpleSegment.fmp4Captions) { 50762 createCaptionsTrackIfNotExists(this.inbandTextTracks_, this.hls_.tech_, simpleSegment.captionStreams); 50763 addCaptionData({ 50764 inbandTextTracks: this.inbandTextTracks_, 50765 captionArray: simpleSegment.fmp4Captions, 50766 // fmp4s will not have a timestamp offset 50767 timestampOffset: 0 50768 }); // Reset stored captions since we added parsed 50769 // captions to a text track at this point 50770 50771 if (this.captionParser_) { 50772 this.captionParser_.clearParsedCaptions(); 50773 } 50774 } 50775 50776 this.handleSegment_(); 50777 } 50778 /** 50779 * append a decrypted segement to the SourceBuffer through a SourceUpdater 50780 * 50781 * @private 50782 */ 50783 50784 }, { 50785 key: 'handleSegment_', 50786 value: function handleSegment_() { 50787 var _this3 = this; 50788 50789 if (!this.pendingSegment_) { 50790 this.state = 'READY'; 50791 return; 50792 } 50793 50794 var segmentInfo = this.pendingSegment_; 50795 var segment = segmentInfo.segment; 50796 var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); // When we have our first timing info, determine what media types this loader is 50797 // dealing with. Although we're maintaining extra state, it helps to preserve the 50798 // separation of segment loader from the actual source buffers. 50799 50800 if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( // Guard against cases where we're not getting timing info at all until we are 50801 // certain that all streams will provide it. 50802 timingInfo.containsAudio || timingInfo.containsVideo)) { 50803 this.startingMedia_ = { 50804 containsAudio: timingInfo.containsAudio, 50805 containsVideo: timingInfo.containsVideo 50806 }; 50807 } 50808 50809 var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo); 50810 50811 if (illegalMediaSwitchError) { 50812 this.error({ 50813 message: illegalMediaSwitchError, 50814 blacklistDuration: Infinity 50815 }); 50816 this.trigger('error'); 50817 return; 50818 } 50819 50820 if (segmentInfo.isSyncRequest) { 50821 this.trigger('syncinfoupdate'); 50822 this.pendingSegment_ = null; 50823 this.state = 'READY'; 50824 return; 50825 } 50826 50827 if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { 50828 // Subtract any difference between the PTS and DTS times of the first frame 50829 // from the timeStampOffset (which currently equals the buffered.end) to prevent 50830 // creating any gaps in the buffer 50831 if (timingInfo && timingInfo.segmentTimestampInfo) { 50832 var ptsStartTime = timingInfo.segmentTimestampInfo[0].ptsTime; 50833 var dtsStartTime = timingInfo.segmentTimestampInfo[0].dtsTime; 50834 segmentInfo.timestampOffset -= ptsStartTime - dtsStartTime; 50835 } 50836 50837 this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); // fired when a timestamp offset is set in HLS (can also identify discontinuities) 50838 50839 this.trigger('timestampoffset'); 50840 } 50841 50842 var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline); 50843 50844 if (timelineMapping !== null) { 50845 this.trigger({ 50846 type: 'segmenttimemapping', 50847 mapping: timelineMapping 50848 }); 50849 } 50850 50851 this.state = 'APPENDING'; // if the media initialization segment is changing, append it 50852 // before the content segment 50853 50854 if (segment.map) { 50855 var initId = initSegmentId(segment.map); 50856 50857 if (!this.activeInitSegmentId_ || this.activeInitSegmentId_ !== initId) { 50858 var initSegment = this.initSegment(segment.map); 50859 this.sourceUpdater_.appendBuffer({ 50860 bytes: initSegment.bytes 50861 }, function () { 50862 _this3.activeInitSegmentId_ = initId; 50863 }); 50864 } 50865 } 50866 50867 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 50868 50869 if (typeof segment.start === 'number' && typeof segment.end === 'number') { 50870 this.mediaSecondsLoaded += segment.end - segment.start; 50871 } else { 50872 this.mediaSecondsLoaded += segment.duration; 50873 } 50874 50875 this.logger_(segmentInfoString(segmentInfo)); 50876 this.sourceUpdater_.appendBuffer({ 50877 bytes: segmentInfo.bytes, 50878 videoSegmentTimingInfoCallback: this.handleVideoSegmentTimingInfo_.bind(this, segmentInfo.requestId) 50879 }, this.handleUpdateEnd_.bind(this)); 50880 } 50881 }, { 50882 key: 'handleVideoSegmentTimingInfo_', 50883 value: function handleVideoSegmentTimingInfo_(requestId, event) { 50884 if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) { 50885 return; 50886 } 50887 50888 var segment = this.pendingSegment_.segment; 50889 50890 if (!segment.videoTimingInfo) { 50891 segment.videoTimingInfo = {}; 50892 } 50893 50894 segment.videoTimingInfo.transmuxerPrependedSeconds = event.videoSegmentTimingInfo.prependedContentDuration || 0; 50895 segment.videoTimingInfo.transmuxedPresentationStart = event.videoSegmentTimingInfo.start.presentation; 50896 segment.videoTimingInfo.transmuxedPresentationEnd = event.videoSegmentTimingInfo.end.presentation; // mainly used as a reference for debugging 50897 50898 segment.videoTimingInfo.baseMediaDecodeTime = event.videoSegmentTimingInfo.baseMediaDecodeTime; 50899 } 50900 /** 50901 * callback to run when appendBuffer is finished. detects if we are 50902 * in a good state to do things with the data we got, or if we need 50903 * to wait for more 50904 * 50905 * @private 50906 */ 50907 50908 }, { 50909 key: 'handleUpdateEnd_', 50910 value: function handleUpdateEnd_() { 50911 if (!this.pendingSegment_) { 50912 this.state = 'READY'; 50913 50914 if (!this.paused()) { 50915 this.monitorBuffer_(); 50916 } 50917 50918 return; 50919 } 50920 50921 var segmentInfo = this.pendingSegment_; 50922 var segment = segmentInfo.segment; 50923 var isWalkingForward = this.mediaIndex !== null; 50924 this.pendingSegment_ = null; 50925 this.recordThroughput_(segmentInfo); 50926 this.addSegmentMetadataCue_(segmentInfo); 50927 this.state = 'READY'; 50928 this.mediaIndex = segmentInfo.mediaIndex; 50929 this.fetchAtBuffer_ = true; 50930 this.currentTimeline_ = segmentInfo.timeline; // We must update the syncinfo to recalculate the seekable range before 50931 // the following conditional otherwise it may consider this a bad "guess" 50932 // and attempt to resync when the post-update seekable window and live 50933 // point would mean that this was the perfect segment to fetch 50934 50935 this.trigger('syncinfoupdate'); // If we previously appended a segment that ends more than 3 targetDurations before 50936 // the currentTime_ that means that our conservative guess was too conservative. 50937 // In that case, reset the loader state so that we try to use any information gained 50938 // from the previous request to create a new, more accurate, sync-point. 50939 50940 if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) { 50941 this.resetEverything(); 50942 return; 50943 } // Don't do a rendition switch unless we have enough time to get a sync segment 50944 // and conservatively guess 50945 50946 50947 if (isWalkingForward) { 50948 this.trigger('bandwidthupdate'); 50949 } 50950 50951 this.trigger('progress'); // any time an update finishes and the last segment is in the 50952 // buffer, end the stream. this ensures the "ended" event will 50953 // fire if playback reaches that point. 50954 50955 if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) { 50956 this.endOfStream(); 50957 } 50958 50959 if (!this.paused()) { 50960 this.monitorBuffer_(); 50961 } 50962 } 50963 /** 50964 * Records the current throughput of the decrypt, transmux, and append 50965 * portion of the semgment pipeline. `throughput.rate` is a the cumulative 50966 * moving average of the throughput. `throughput.count` is the number of 50967 * data points in the average. 50968 * 50969 * @private 50970 * @param {Object} segmentInfo the object returned by loadSegment 50971 */ 50972 50973 }, { 50974 key: 'recordThroughput_', 50975 value: function recordThroughput_(segmentInfo) { 50976 var rate = this.throughput.rate; // Add one to the time to ensure that we don't accidentally attempt to divide 50977 // by zero in the case where the throughput is ridiculously high 50978 50979 var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; // Multiply by 8000 to convert from bytes/millisecond to bits/second 50980 50981 var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); // This is just a cumulative moving average calculation: 50982 // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1) 50983 50984 this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count; 50985 } 50986 /** 50987 * Adds a cue to the segment-metadata track with some metadata information about the 50988 * segment 50989 * 50990 * @private 50991 * @param {Object} segmentInfo 50992 * the object returned by loadSegment 50993 * @method addSegmentMetadataCue_ 50994 */ 50995 50996 }, { 50997 key: 'addSegmentMetadataCue_', 50998 value: function addSegmentMetadataCue_(segmentInfo) { 50999 if (!this.segmentMetadataTrack_) { 51000 return; 51001 } 51002 51003 var segment = segmentInfo.segment; 51004 var start = segment.start; 51005 var end = segment.end; // Do not try adding the cue if the start and end times are invalid. 51006 51007 if (!finite(start) || !finite(end)) { 51008 return; 51009 } 51010 51011 removeCuesFromTrack(start, end, this.segmentMetadataTrack_); 51012 var Cue = window$1.WebKitDataCue || window$1.VTTCue; 51013 var value = { 51014 custom: segment.custom, 51015 dateTimeObject: segment.dateTimeObject, 51016 dateTimeString: segment.dateTimeString, 51017 bandwidth: segmentInfo.playlist.attributes.BANDWIDTH, 51018 resolution: segmentInfo.playlist.attributes.RESOLUTION, 51019 codecs: segmentInfo.playlist.attributes.CODECS, 51020 byteLength: segmentInfo.byteLength, 51021 uri: segmentInfo.uri, 51022 timeline: segmentInfo.timeline, 51023 playlist: segmentInfo.playlist.uri, 51024 start: start, 51025 end: end 51026 }; 51027 var data = JSON.stringify(value); 51028 var cue = new Cue(start, end, data); // Attach the metadata to the value property of the cue to keep consistency between 51029 // the differences of WebKitDataCue in safari and VTTCue in other browsers 51030 51031 cue.value = value; 51032 this.segmentMetadataTrack_.addCue(cue); 51033 } 51034 }]); 51035 return SegmentLoader; 51036 }(videojs$1.EventTarget); 51037 51038 var uint8ToUtf8 = function uint8ToUtf8(uintArray) { 51039 return decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray))); 51040 }; 51041 /** 51042 * @file vtt-segment-loader.js 51043 */ 51044 51045 51046 var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (_char2) { 51047 return _char2.charCodeAt(0); 51048 })); 51049 /** 51050 * An object that manages segment loading and appending. 51051 * 51052 * @class VTTSegmentLoader 51053 * @param {Object} options required and optional options 51054 * @extends videojs.EventTarget 51055 */ 51056 51057 var VTTSegmentLoader = function (_SegmentLoader) { 51058 inherits$1(VTTSegmentLoader, _SegmentLoader); 51059 51060 function VTTSegmentLoader(settings) { 51061 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 51062 classCallCheck$1(this, VTTSegmentLoader); // SegmentLoader requires a MediaSource be specified or it will throw an error; 51063 // however, VTTSegmentLoader has no need of a media source, so delete the reference 51064 51065 var _this = possibleConstructorReturn$1(this, (VTTSegmentLoader.__proto__ || Object.getPrototypeOf(VTTSegmentLoader)).call(this, settings, options)); 51066 51067 _this.mediaSource_ = null; 51068 _this.subtitlesTrack_ = null; 51069 return _this; 51070 } 51071 /** 51072 * Indicates which time ranges are buffered 51073 * 51074 * @return {TimeRange} 51075 * TimeRange object representing the current buffered ranges 51076 */ 51077 51078 51079 createClass$1(VTTSegmentLoader, [{ 51080 key: 'buffered_', 51081 value: function buffered_() { 51082 if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) { 51083 return videojs$1.createTimeRanges(); 51084 } 51085 51086 var cues = this.subtitlesTrack_.cues; 51087 var start = cues[0].startTime; 51088 var end = cues[cues.length - 1].startTime; 51089 return videojs$1.createTimeRanges([[start, end]]); 51090 } 51091 /** 51092 * Gets and sets init segment for the provided map 51093 * 51094 * @param {Object} map 51095 * The map object representing the init segment to get or set 51096 * @param {Boolean=} set 51097 * If true, the init segment for the provided map should be saved 51098 * @return {Object} 51099 * map object for desired init segment 51100 */ 51101 51102 }, { 51103 key: 'initSegment', 51104 value: function initSegment(map) { 51105 var set$$1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 51106 51107 if (!map) { 51108 return null; 51109 } 51110 51111 var id = initSegmentId(map); 51112 var storedMap = this.initSegments_[id]; 51113 51114 if (set$$1 && !storedMap && map.bytes) { 51115 // append WebVTT line terminators to the media initialization segment if it exists 51116 // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that 51117 // requires two or more WebVTT line terminators between the WebVTT header and the 51118 // rest of the file 51119 var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength; 51120 var combinedSegment = new Uint8Array(combinedByteLength); 51121 combinedSegment.set(map.bytes); 51122 combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength); 51123 this.initSegments_[id] = storedMap = { 51124 resolvedUri: map.resolvedUri, 51125 byterange: map.byterange, 51126 bytes: combinedSegment 51127 }; 51128 } 51129 51130 return storedMap || map; 51131 } 51132 /** 51133 * Returns true if all configuration required for loading is present, otherwise false. 51134 * 51135 * @return {Boolean} True if the all configuration is ready for loading 51136 * @private 51137 */ 51138 51139 }, { 51140 key: 'couldBeginLoading_', 51141 value: function couldBeginLoading_() { 51142 return this.playlist_ && this.subtitlesTrack_ && !this.paused(); 51143 } 51144 /** 51145 * Once all the starting parameters have been specified, begin 51146 * operation. This method should only be invoked from the INIT 51147 * state. 51148 * 51149 * @private 51150 */ 51151 51152 }, { 51153 key: 'init_', 51154 value: function init_() { 51155 this.state = 'READY'; 51156 this.resetEverything(); 51157 return this.monitorBuffer_(); 51158 } 51159 /** 51160 * Set a subtitle track on the segment loader to add subtitles to 51161 * 51162 * @param {TextTrack=} track 51163 * The text track to add loaded subtitles to 51164 * @return {TextTrack} 51165 * Returns the subtitles track 51166 */ 51167 51168 }, { 51169 key: 'track', 51170 value: function track(_track) { 51171 if (typeof _track === 'undefined') { 51172 return this.subtitlesTrack_; 51173 } 51174 51175 this.subtitlesTrack_ = _track; // if we were unpaused but waiting for a sourceUpdater, start 51176 // buffering now 51177 51178 if (this.state === 'INIT' && this.couldBeginLoading_()) { 51179 this.init_(); 51180 } 51181 51182 return this.subtitlesTrack_; 51183 } 51184 /** 51185 * Remove any data in the source buffer between start and end times 51186 * @param {Number} start - the start time of the region to remove from the buffer 51187 * @param {Number} end - the end time of the region to remove from the buffer 51188 */ 51189 51190 }, { 51191 key: 'remove', 51192 value: function remove(start, end) { 51193 removeCuesFromTrack(start, end, this.subtitlesTrack_); 51194 } 51195 /** 51196 * fill the buffer with segements unless the sourceBuffers are 51197 * currently updating 51198 * 51199 * Note: this function should only ever be called by monitorBuffer_ 51200 * and never directly 51201 * 51202 * @private 51203 */ 51204 51205 }, { 51206 key: 'fillBuffer_', 51207 value: function fillBuffer_() { 51208 var _this2 = this; 51209 51210 if (!this.syncPoint_) { 51211 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 51212 } // see if we need to begin loading immediately 51213 51214 51215 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 51216 segmentInfo = this.skipEmptySegments_(segmentInfo); 51217 51218 if (!segmentInfo) { 51219 return; 51220 } 51221 51222 if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) { 51223 // We don't have the timestamp offset that we need to sync subtitles. 51224 // Rerun on a timestamp offset or user interaction. 51225 var checkTimestampOffset = function checkTimestampOffset() { 51226 _this2.state = 'READY'; 51227 51228 if (!_this2.paused()) { 51229 // if not paused, queue a buffer check as soon as possible 51230 _this2.monitorBuffer_(); 51231 } 51232 }; 51233 51234 this.syncController_.one('timestampoffset', checkTimestampOffset); 51235 this.state = 'WAITING_ON_TIMELINE'; 51236 return; 51237 } 51238 51239 this.loadSegment_(segmentInfo); 51240 } 51241 /** 51242 * Prevents the segment loader from requesting segments we know contain no subtitles 51243 * by walking forward until we find the next segment that we don't know whether it is 51244 * empty or not. 51245 * 51246 * @param {Object} segmentInfo 51247 * a segment info object that describes the current segment 51248 * @return {Object} 51249 * a segment info object that describes the current segment 51250 */ 51251 51252 }, { 51253 key: 'skipEmptySegments_', 51254 value: function skipEmptySegments_(segmentInfo) { 51255 while (segmentInfo && segmentInfo.segment.empty) { 51256 segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest); 51257 } 51258 51259 return segmentInfo; 51260 } 51261 /** 51262 * append a decrypted segement to the SourceBuffer through a SourceUpdater 51263 * 51264 * @private 51265 */ 51266 51267 }, { 51268 key: 'handleSegment_', 51269 value: function handleSegment_() { 51270 var _this3 = this; 51271 51272 if (!this.pendingSegment_ || !this.subtitlesTrack_) { 51273 this.state = 'READY'; 51274 return; 51275 } 51276 51277 this.state = 'APPENDING'; 51278 var segmentInfo = this.pendingSegment_; 51279 var segment = segmentInfo.segment; // Make sure that vttjs has loaded, otherwise, wait till it finished loading 51280 51281 if (typeof window$1.WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) { 51282 var loadHandler = void 0; 51283 51284 var errorHandler = function errorHandler() { 51285 _this3.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler); 51286 51287 _this3.error({ 51288 message: 'Error loading vtt.js' 51289 }); 51290 51291 _this3.state = 'READY'; 51292 51293 _this3.pause(); 51294 51295 _this3.trigger('error'); 51296 }; 51297 51298 loadHandler = function loadHandler() { 51299 _this3.subtitlesTrack_.tech_.off('vttjserror', errorHandler); 51300 51301 _this3.handleSegment_(); 51302 }; 51303 51304 this.state = 'WAITING_ON_VTTJS'; 51305 this.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler); 51306 this.subtitlesTrack_.tech_.one('vttjserror', errorHandler); 51307 return; 51308 } 51309 51310 segment.requested = true; 51311 51312 try { 51313 this.parseVTTCues_(segmentInfo); 51314 } catch (e) { 51315 this.error({ 51316 message: e.message 51317 }); 51318 this.state = 'READY'; 51319 this.pause(); 51320 return this.trigger('error'); 51321 } 51322 51323 this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_); 51324 51325 if (segmentInfo.isSyncRequest) { 51326 this.trigger('syncinfoupdate'); 51327 this.pendingSegment_ = null; 51328 this.state = 'READY'; 51329 return; 51330 } 51331 51332 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 51333 this.mediaSecondsLoaded += segment.duration; 51334 51335 if (segmentInfo.cues.length) { 51336 // remove any overlapping cues to prevent doubling 51337 this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime); 51338 } 51339 51340 segmentInfo.cues.forEach(function (cue) { 51341 _this3.subtitlesTrack_.addCue(cue); 51342 }); 51343 this.handleUpdateEnd_(); 51344 } 51345 /** 51346 * Uses the WebVTT parser to parse the segment response 51347 * 51348 * @param {Object} segmentInfo 51349 * a segment info object that describes the current segment 51350 * @private 51351 */ 51352 51353 }, { 51354 key: 'parseVTTCues_', 51355 value: function parseVTTCues_(segmentInfo) { 51356 var decoder = void 0; 51357 var decodeBytesToString = false; 51358 51359 if (typeof window$1.TextDecoder === 'function') { 51360 decoder = new window$1.TextDecoder('utf8'); 51361 } else { 51362 decoder = window$1.WebVTT.StringDecoder(); 51363 decodeBytesToString = true; 51364 } 51365 51366 var parser = new window$1.WebVTT.Parser(window$1, window$1.vttjs, decoder); 51367 segmentInfo.cues = []; 51368 segmentInfo.timestampmap = { 51369 MPEGTS: 0, 51370 LOCAL: 0 51371 }; 51372 parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues); 51373 51374 parser.ontimestampmap = function (map) { 51375 return segmentInfo.timestampmap = map; 51376 }; 51377 51378 parser.onparsingerror = function (error) { 51379 videojs$1.log.warn('Error encountered when parsing cues: ' + error.message); 51380 }; 51381 51382 if (segmentInfo.segment.map) { 51383 var mapData = segmentInfo.segment.map.bytes; 51384 51385 if (decodeBytesToString) { 51386 mapData = uint8ToUtf8(mapData); 51387 } 51388 51389 parser.parse(mapData); 51390 } 51391 51392 var segmentData = segmentInfo.bytes; 51393 51394 if (decodeBytesToString) { 51395 segmentData = uint8ToUtf8(segmentData); 51396 } 51397 51398 parser.parse(segmentData); 51399 parser.flush(); 51400 } 51401 /** 51402 * Updates the start and end times of any cues parsed by the WebVTT parser using 51403 * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping 51404 * from the SyncController 51405 * 51406 * @param {Object} segmentInfo 51407 * a segment info object that describes the current segment 51408 * @param {Object} mappingObj 51409 * object containing a mapping from TS to media time 51410 * @param {Object} playlist 51411 * the playlist object containing the segment 51412 * @private 51413 */ 51414 51415 }, { 51416 key: 'updateTimeMapping_', 51417 value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) { 51418 var segment = segmentInfo.segment; 51419 51420 if (!mappingObj) { 51421 // If the sync controller does not have a mapping of TS to Media Time for the 51422 // timeline, then we don't have enough information to update the cue 51423 // start/end times 51424 return; 51425 } 51426 51427 if (!segmentInfo.cues.length) { 51428 // If there are no cues, we also do not have enough information to figure out 51429 // segment timing. Mark that the segment contains no cues so we don't re-request 51430 // an empty segment. 51431 segment.empty = true; 51432 return; 51433 } 51434 51435 var timestampmap = segmentInfo.timestampmap; 51436 var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping; 51437 segmentInfo.cues.forEach(function (cue) { 51438 // First convert cue time to TS time using the timestamp-map provided within the vtt 51439 cue.startTime += diff; 51440 cue.endTime += diff; 51441 }); 51442 51443 if (!playlist.syncInfo) { 51444 var firstStart = segmentInfo.cues[0].startTime; 51445 var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; 51446 playlist.syncInfo = { 51447 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 51448 time: Math.min(firstStart, lastStart - segment.duration) 51449 }; 51450 } 51451 } 51452 }]); 51453 return VTTSegmentLoader; 51454 }(SegmentLoader); 51455 /** 51456 * @file ad-cue-tags.js 51457 */ 51458 51459 /** 51460 * Searches for an ad cue that overlaps with the given mediaTime 51461 */ 51462 51463 51464 var findAdCue = function findAdCue(track, mediaTime) { 51465 var cues = track.cues; 51466 51467 for (var i = 0; i < cues.length; i++) { 51468 var cue = cues[i]; 51469 51470 if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) { 51471 return cue; 51472 } 51473 } 51474 51475 return null; 51476 }; 51477 51478 var updateAdCues = function updateAdCues(media, track) { 51479 var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; 51480 51481 if (!media.segments) { 51482 return; 51483 } 51484 51485 var mediaTime = offset; 51486 var cue = void 0; 51487 51488 for (var i = 0; i < media.segments.length; i++) { 51489 var segment = media.segments[i]; 51490 51491 if (!cue) { 51492 // Since the cues will span for at least the segment duration, adding a fudge 51493 // factor of half segment duration will prevent duplicate cues from being 51494 // created when timing info is not exact (e.g. cue start time initialized 51495 // at 10.006677, but next call mediaTime is 10.003332 ) 51496 cue = findAdCue(track, mediaTime + segment.duration / 2); 51497 } 51498 51499 if (cue) { 51500 if ('cueIn' in segment) { 51501 // Found a CUE-IN so end the cue 51502 cue.endTime = mediaTime; 51503 cue.adEndTime = mediaTime; 51504 mediaTime += segment.duration; 51505 cue = null; 51506 continue; 51507 } 51508 51509 if (mediaTime < cue.endTime) { 51510 // Already processed this mediaTime for this cue 51511 mediaTime += segment.duration; 51512 continue; 51513 } // otherwise extend cue until a CUE-IN is found 51514 51515 51516 cue.endTime += segment.duration; 51517 } else { 51518 if ('cueOut' in segment) { 51519 cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut); 51520 cue.adStartTime = mediaTime; // Assumes tag format to be 51521 // #EXT-X-CUE-OUT:30 51522 51523 cue.adEndTime = mediaTime + parseFloat(segment.cueOut); 51524 track.addCue(cue); 51525 } 51526 51527 if ('cueOutCont' in segment) { 51528 // Entered into the middle of an ad cue 51529 var adOffset = void 0; 51530 var adTotal = void 0; // Assumes tag formate to be 51531 // #EXT-X-CUE-OUT-CONT:10/30 51532 51533 var _segment$cueOutCont$s = segment.cueOutCont.split('/').map(parseFloat); 51534 51535 var _segment$cueOutCont$s2 = slicedToArray(_segment$cueOutCont$s, 2); 51536 51537 adOffset = _segment$cueOutCont$s2[0]; 51538 adTotal = _segment$cueOutCont$s2[1]; 51539 cue = new window$1.VTTCue(mediaTime, mediaTime + segment.duration, ''); 51540 cue.adStartTime = mediaTime - adOffset; 51541 cue.adEndTime = cue.adStartTime + adTotal; 51542 track.addCue(cue); 51543 } 51544 } 51545 51546 mediaTime += segment.duration; 51547 } 51548 }; 51549 /** 51550 * @file sync-controller.js 51551 */ 51552 51553 51554 var tsprobe = tsInspector.inspect; 51555 var syncPointStrategies = [// Stategy "VOD": Handle the VOD-case where the sync-point is *always* 51556 // the equivalence display-time 0 === segment-index 0 51557 { 51558 name: 'VOD', 51559 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 51560 if (duration$$1 !== Infinity) { 51561 var syncPoint = { 51562 time: 0, 51563 segmentIndex: 0 51564 }; 51565 return syncPoint; 51566 } 51567 51568 return null; 51569 } 51570 }, // Stategy "ProgramDateTime": We have a program-date-time tag in this playlist 51571 { 51572 name: 'ProgramDateTime', 51573 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 51574 if (!syncController.datetimeToDisplayTime) { 51575 return null; 51576 } 51577 51578 var segments = playlist.segments || []; 51579 var syncPoint = null; 51580 var lastDistance = null; 51581 currentTime = currentTime || 0; 51582 51583 for (var i = 0; i < segments.length; i++) { 51584 var segment = segments[i]; 51585 51586 if (segment.dateTimeObject) { 51587 var segmentTime = segment.dateTimeObject.getTime() / 1000; 51588 var segmentStart = segmentTime + syncController.datetimeToDisplayTime; 51589 var distance = Math.abs(currentTime - segmentStart); // Once the distance begins to increase, or if distance is 0, we have passed 51590 // currentTime and can stop looking for better candidates 51591 51592 if (lastDistance !== null && (distance === 0 || lastDistance < distance)) { 51593 break; 51594 } 51595 51596 lastDistance = distance; 51597 syncPoint = { 51598 time: segmentStart, 51599 segmentIndex: i 51600 }; 51601 } 51602 } 51603 51604 return syncPoint; 51605 } 51606 }, // Stategy "Segment": We have a known time mapping for a timeline and a 51607 // segment in the current timeline with timing data 51608 { 51609 name: 'Segment', 51610 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 51611 var segments = playlist.segments || []; 51612 var syncPoint = null; 51613 var lastDistance = null; 51614 currentTime = currentTime || 0; 51615 51616 for (var i = 0; i < segments.length; i++) { 51617 var segment = segments[i]; 51618 51619 if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') { 51620 var distance = Math.abs(currentTime - segment.start); // Once the distance begins to increase, we have passed 51621 // currentTime and can stop looking for better candidates 51622 51623 if (lastDistance !== null && lastDistance < distance) { 51624 break; 51625 } 51626 51627 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 51628 lastDistance = distance; 51629 syncPoint = { 51630 time: segment.start, 51631 segmentIndex: i 51632 }; 51633 } 51634 } 51635 } 51636 51637 return syncPoint; 51638 } 51639 }, // Stategy "Discontinuity": We have a discontinuity with a known 51640 // display-time 51641 { 51642 name: 'Discontinuity', 51643 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 51644 var syncPoint = null; 51645 currentTime = currentTime || 0; 51646 51647 if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 51648 var lastDistance = null; 51649 51650 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 51651 var segmentIndex = playlist.discontinuityStarts[i]; 51652 var discontinuity = playlist.discontinuitySequence + i + 1; 51653 var discontinuitySync = syncController.discontinuities[discontinuity]; 51654 51655 if (discontinuitySync) { 51656 var distance = Math.abs(currentTime - discontinuitySync.time); // Once the distance begins to increase, we have passed 51657 // currentTime and can stop looking for better candidates 51658 51659 if (lastDistance !== null && lastDistance < distance) { 51660 break; 51661 } 51662 51663 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 51664 lastDistance = distance; 51665 syncPoint = { 51666 time: discontinuitySync.time, 51667 segmentIndex: segmentIndex 51668 }; 51669 } 51670 } 51671 } 51672 } 51673 51674 return syncPoint; 51675 } 51676 }, // Stategy "Playlist": We have a playlist with a known mapping of 51677 // segment index to display time 51678 { 51679 name: 'Playlist', 51680 run: function run(syncController, playlist, duration$$1, currentTimeline, currentTime) { 51681 if (playlist.syncInfo) { 51682 var syncPoint = { 51683 time: playlist.syncInfo.time, 51684 segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence 51685 }; 51686 return syncPoint; 51687 } 51688 51689 return null; 51690 } 51691 }]; 51692 51693 var SyncController = function (_videojs$EventTarget) { 51694 inherits$1(SyncController, _videojs$EventTarget); 51695 51696 function SyncController() { 51697 classCallCheck$1(this, SyncController); // Segment Loader state variables... 51698 // ...for synching across variants 51699 51700 var _this = possibleConstructorReturn$1(this, (SyncController.__proto__ || Object.getPrototypeOf(SyncController)).call(this)); 51701 51702 _this.inspectCache_ = undefined; // ...for synching across variants 51703 51704 _this.timelines = []; 51705 _this.discontinuities = []; 51706 _this.datetimeToDisplayTime = null; 51707 _this.logger_ = logger('SyncController'); 51708 return _this; 51709 } 51710 /** 51711 * Find a sync-point for the playlist specified 51712 * 51713 * A sync-point is defined as a known mapping from display-time to 51714 * a segment-index in the current playlist. 51715 * 51716 * @param {Playlist} playlist 51717 * The playlist that needs a sync-point 51718 * @param {Number} duration 51719 * Duration of the MediaSource (Infinite if playing a live source) 51720 * @param {Number} currentTimeline 51721 * The last timeline from which a segment was loaded 51722 * @returns {Object} 51723 * A sync-point object 51724 */ 51725 51726 51727 createClass$1(SyncController, [{ 51728 key: 'getSyncPoint', 51729 value: function getSyncPoint(playlist, duration$$1, currentTimeline, currentTime) { 51730 var syncPoints = this.runStrategies_(playlist, duration$$1, currentTimeline, currentTime); 51731 51732 if (!syncPoints.length) { 51733 // Signal that we need to attempt to get a sync-point manually 51734 // by fetching a segment in the playlist and constructing 51735 // a sync-point from that information 51736 return null; 51737 } // Now find the sync-point that is closest to the currentTime because 51738 // that should result in the most accurate guess about which segment 51739 // to fetch 51740 51741 51742 return this.selectSyncPoint_(syncPoints, { 51743 key: 'time', 51744 value: currentTime 51745 }); 51746 } 51747 /** 51748 * Calculate the amount of time that has expired off the playlist during playback 51749 * 51750 * @param {Playlist} playlist 51751 * Playlist object to calculate expired from 51752 * @param {Number} duration 51753 * Duration of the MediaSource (Infinity if playling a live source) 51754 * @returns {Number|null} 51755 * The amount of time that has expired off the playlist during playback. Null 51756 * if no sync-points for the playlist can be found. 51757 */ 51758 51759 }, { 51760 key: 'getExpiredTime', 51761 value: function getExpiredTime(playlist, duration$$1) { 51762 if (!playlist || !playlist.segments) { 51763 return null; 51764 } 51765 51766 var syncPoints = this.runStrategies_(playlist, duration$$1, playlist.discontinuitySequence, 0); // Without sync-points, there is not enough information to determine the expired time 51767 51768 if (!syncPoints.length) { 51769 return null; 51770 } 51771 51772 var syncPoint = this.selectSyncPoint_(syncPoints, { 51773 key: 'segmentIndex', 51774 value: 0 51775 }); // If the sync-point is beyond the start of the playlist, we want to subtract the 51776 // duration from index 0 to syncPoint.segmentIndex instead of adding. 51777 51778 if (syncPoint.segmentIndex > 0) { 51779 syncPoint.time *= -1; 51780 } 51781 51782 return Math.abs(syncPoint.time + sumDurations(playlist, syncPoint.segmentIndex, 0)); 51783 } 51784 /** 51785 * Runs each sync-point strategy and returns a list of sync-points returned by the 51786 * strategies 51787 * 51788 * @private 51789 * @param {Playlist} playlist 51790 * The playlist that needs a sync-point 51791 * @param {Number} duration 51792 * Duration of the MediaSource (Infinity if playing a live source) 51793 * @param {Number} currentTimeline 51794 * The last timeline from which a segment was loaded 51795 * @returns {Array} 51796 * A list of sync-point objects 51797 */ 51798 51799 }, { 51800 key: 'runStrategies_', 51801 value: function runStrategies_(playlist, duration$$1, currentTimeline, currentTime) { 51802 var syncPoints = []; // Try to find a sync-point in by utilizing various strategies... 51803 51804 for (var i = 0; i < syncPointStrategies.length; i++) { 51805 var strategy = syncPointStrategies[i]; 51806 var syncPoint = strategy.run(this, playlist, duration$$1, currentTimeline, currentTime); 51807 51808 if (syncPoint) { 51809 syncPoint.strategy = strategy.name; 51810 syncPoints.push({ 51811 strategy: strategy.name, 51812 syncPoint: syncPoint 51813 }); 51814 } 51815 } 51816 51817 return syncPoints; 51818 } 51819 /** 51820 * Selects the sync-point nearest the specified target 51821 * 51822 * @private 51823 * @param {Array} syncPoints 51824 * List of sync-points to select from 51825 * @param {Object} target 51826 * Object specifying the property and value we are targeting 51827 * @param {String} target.key 51828 * Specifies the property to target. Must be either 'time' or 'segmentIndex' 51829 * @param {Number} target.value 51830 * The value to target for the specified key. 51831 * @returns {Object} 51832 * The sync-point nearest the target 51833 */ 51834 51835 }, { 51836 key: 'selectSyncPoint_', 51837 value: function selectSyncPoint_(syncPoints, target) { 51838 var bestSyncPoint = syncPoints[0].syncPoint; 51839 var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value); 51840 var bestStrategy = syncPoints[0].strategy; 51841 51842 for (var i = 1; i < syncPoints.length; i++) { 51843 var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value); 51844 51845 if (newDistance < bestDistance) { 51846 bestDistance = newDistance; 51847 bestSyncPoint = syncPoints[i].syncPoint; 51848 bestStrategy = syncPoints[i].strategy; 51849 } 51850 } 51851 51852 this.logger_('syncPoint for [' + target.key + ': ' + target.value + '] chosen with strategy' + (' [' + bestStrategy + ']: [time:' + bestSyncPoint.time + ',') + (' segmentIndex:' + bestSyncPoint.segmentIndex + ']')); 51853 return bestSyncPoint; 51854 } 51855 /** 51856 * Save any meta-data present on the segments when segments leave 51857 * the live window to the playlist to allow for synchronization at the 51858 * playlist level later. 51859 * 51860 * @param {Playlist} oldPlaylist - The previous active playlist 51861 * @param {Playlist} newPlaylist - The updated and most current playlist 51862 */ 51863 51864 }, { 51865 key: 'saveExpiredSegmentInfo', 51866 value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) { 51867 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; // When a segment expires from the playlist and it has a start time 51868 // save that information as a possible sync-point reference in future 51869 51870 for (var i = mediaSequenceDiff - 1; i >= 0; i--) { 51871 var lastRemovedSegment = oldPlaylist.segments[i]; 51872 51873 if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') { 51874 newPlaylist.syncInfo = { 51875 mediaSequence: oldPlaylist.mediaSequence + i, 51876 time: lastRemovedSegment.start 51877 }; 51878 this.logger_('playlist refresh sync: [time:' + newPlaylist.syncInfo.time + ',' + (' mediaSequence: ' + newPlaylist.syncInfo.mediaSequence + ']')); 51879 this.trigger('syncinfoupdate'); 51880 break; 51881 } 51882 } 51883 } 51884 /** 51885 * Save the mapping from playlist's ProgramDateTime to display. This should 51886 * only ever happen once at the start of playback. 51887 * 51888 * @param {Playlist} playlist - The currently active playlist 51889 */ 51890 51891 }, { 51892 key: 'setDateTimeMapping', 51893 value: function setDateTimeMapping(playlist) { 51894 if (!this.datetimeToDisplayTime && playlist.segments && playlist.segments.length && playlist.segments[0].dateTimeObject) { 51895 var playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000; 51896 this.datetimeToDisplayTime = -playlistTimestamp; 51897 } 51898 } 51899 /** 51900 * Reset the state of the inspection cache when we do a rendition 51901 * switch 51902 */ 51903 51904 }, { 51905 key: 'reset', 51906 value: function reset() { 51907 this.inspectCache_ = undefined; 51908 } 51909 /** 51910 * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start 51911 * and end of the segment in it's internal "media time". Used to generate 51912 * mappings from that internal "media time" to the display time that is 51913 * shown on the player. 51914 * 51915 * @param {SegmentInfo} segmentInfo - The current active request information 51916 */ 51917 51918 }, { 51919 key: 'probeSegmentInfo', 51920 value: function probeSegmentInfo(segmentInfo) { 51921 var segment = segmentInfo.segment; 51922 var playlist = segmentInfo.playlist; 51923 var timingInfo = void 0; 51924 51925 if (segment.map) { 51926 timingInfo = this.probeMp4Segment_(segmentInfo); 51927 } else { 51928 timingInfo = this.probeTsSegment_(segmentInfo); 51929 } 51930 51931 if (timingInfo) { 51932 if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) { 51933 this.saveDiscontinuitySyncInfo_(segmentInfo); // If the playlist does not have sync information yet, record that information 51934 // now with segment timing information 51935 51936 if (!playlist.syncInfo) { 51937 playlist.syncInfo = { 51938 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 51939 time: segment.start 51940 }; 51941 } 51942 } 51943 } 51944 51945 return timingInfo; 51946 } 51947 /** 51948 * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment 51949 * in it's internal "media time". 51950 * 51951 * @private 51952 * @param {SegmentInfo} segmentInfo - The current active request information 51953 * @return {object} The start and end time of the current segment in "media time" 51954 */ 51955 51956 }, { 51957 key: 'probeMp4Segment_', 51958 value: function probeMp4Segment_(segmentInfo) { 51959 var segment = segmentInfo.segment; 51960 var timescales = probe.timescale(segment.map.bytes); 51961 var startTime = probe.startTime(timescales, segmentInfo.bytes); 51962 51963 if (segmentInfo.timestampOffset !== null) { 51964 segmentInfo.timestampOffset -= startTime; 51965 } 51966 51967 return { 51968 start: startTime, 51969 end: startTime + segment.duration 51970 }; 51971 } 51972 /** 51973 * Probe an mpeg2-ts segment to determine the start and end of the segment 51974 * in it's internal "media time". 51975 * 51976 * @private 51977 * @param {SegmentInfo} segmentInfo - The current active request information 51978 * @return {object} The start and end time of the current segment in "media time" 51979 */ 51980 51981 }, { 51982 key: 'probeTsSegment_', 51983 value: function probeTsSegment_(segmentInfo) { 51984 var timeInfo = tsprobe(segmentInfo.bytes, this.inspectCache_); 51985 var segmentStartTime = void 0; 51986 var segmentEndTime = void 0; 51987 var segmentTimestampInfo = void 0; 51988 51989 if (!timeInfo) { 51990 return null; 51991 } 51992 51993 if (timeInfo.video && timeInfo.video.length === 2) { 51994 this.inspectCache_ = timeInfo.video[1].dts; 51995 segmentStartTime = timeInfo.video[0].dtsTime; 51996 segmentEndTime = timeInfo.video[1].dtsTime; 51997 segmentTimestampInfo = timeInfo.video; 51998 } else if (timeInfo.audio && timeInfo.audio.length === 2) { 51999 this.inspectCache_ = timeInfo.audio[1].dts; 52000 segmentStartTime = timeInfo.audio[0].dtsTime; 52001 segmentEndTime = timeInfo.audio[1].dtsTime; 52002 segmentTimestampInfo = timeInfo.audio; 52003 } 52004 52005 var probedInfo = { 52006 segmentTimestampInfo: segmentTimestampInfo, 52007 start: segmentStartTime, 52008 end: segmentEndTime, 52009 containsVideo: timeInfo.video && timeInfo.video.length === 2, 52010 containsAudio: timeInfo.audio && timeInfo.audio.length === 2 52011 }; 52012 return probedInfo; 52013 } 52014 }, { 52015 key: 'timestampOffsetForTimeline', 52016 value: function timestampOffsetForTimeline(timeline) { 52017 if (typeof this.timelines[timeline] === 'undefined') { 52018 return null; 52019 } 52020 52021 return this.timelines[timeline].time; 52022 } 52023 }, { 52024 key: 'mappingForTimeline', 52025 value: function mappingForTimeline(timeline) { 52026 if (typeof this.timelines[timeline] === 'undefined') { 52027 return null; 52028 } 52029 52030 return this.timelines[timeline].mapping; 52031 } 52032 /** 52033 * Use the "media time" for a segment to generate a mapping to "display time" and 52034 * save that display time to the segment. 52035 * 52036 * @private 52037 * @param {SegmentInfo} segmentInfo 52038 * The current active request information 52039 * @param {object} timingInfo 52040 * The start and end time of the current segment in "media time" 52041 * @returns {Boolean} 52042 * Returns false if segment time mapping could not be calculated 52043 */ 52044 52045 }, { 52046 key: 'calculateSegmentTimeMapping_', 52047 value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) { 52048 var segment = segmentInfo.segment; 52049 var mappingObj = this.timelines[segmentInfo.timeline]; 52050 52051 if (segmentInfo.timestampOffset !== null) { 52052 mappingObj = { 52053 time: segmentInfo.startOfSegment, 52054 mapping: segmentInfo.startOfSegment - timingInfo.start 52055 }; 52056 this.timelines[segmentInfo.timeline] = mappingObj; 52057 this.trigger('timestampoffset'); 52058 this.logger_('time mapping for timeline ' + segmentInfo.timeline + ': ' + ('[time: ' + mappingObj.time + '] [mapping: ' + mappingObj.mapping + ']')); 52059 segment.start = segmentInfo.startOfSegment; 52060 segment.end = timingInfo.end + mappingObj.mapping; 52061 } else if (mappingObj) { 52062 segment.start = timingInfo.start + mappingObj.mapping; 52063 segment.end = timingInfo.end + mappingObj.mapping; 52064 } else { 52065 return false; 52066 } 52067 52068 return true; 52069 } 52070 /** 52071 * Each time we have discontinuity in the playlist, attempt to calculate the location 52072 * in display of the start of the discontinuity and save that. We also save an accuracy 52073 * value so that we save values with the most accuracy (closest to 0.) 52074 * 52075 * @private 52076 * @param {SegmentInfo} segmentInfo - The current active request information 52077 */ 52078 52079 }, { 52080 key: 'saveDiscontinuitySyncInfo_', 52081 value: function saveDiscontinuitySyncInfo_(segmentInfo) { 52082 var playlist = segmentInfo.playlist; 52083 var segment = segmentInfo.segment; // If the current segment is a discontinuity then we know exactly where 52084 // the start of the range and it's accuracy is 0 (greater accuracy values 52085 // mean more approximation) 52086 52087 if (segment.discontinuity) { 52088 this.discontinuities[segment.timeline] = { 52089 time: segment.start, 52090 accuracy: 0 52091 }; 52092 } else if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 52093 // Search for future discontinuities that we can provide better timing 52094 // information for and save that information for sync purposes 52095 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 52096 var segmentIndex = playlist.discontinuityStarts[i]; 52097 var discontinuity = playlist.discontinuitySequence + i + 1; 52098 var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex; 52099 var accuracy = Math.abs(mediaIndexDiff); 52100 52101 if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) { 52102 var time = void 0; 52103 52104 if (mediaIndexDiff < 0) { 52105 time = segment.start - sumDurations(playlist, segmentInfo.mediaIndex, segmentIndex); 52106 } else { 52107 time = segment.end + sumDurations(playlist, segmentInfo.mediaIndex + 1, segmentIndex); 52108 } 52109 52110 this.discontinuities[discontinuity] = { 52111 time: time, 52112 accuracy: accuracy 52113 }; 52114 } 52115 } 52116 } 52117 } 52118 }]); 52119 return SyncController; 52120 }(videojs$1.EventTarget); 52121 52122 var Decrypter$1 = new shimWorker("./decrypter-worker.worker.js", function (window, document$$1) { 52123 var self = this; 52124 52125 var decrypterWorker = function () { 52126 /* 52127 * pkcs7.pad 52128 * https://github.com/brightcove/pkcs7 52129 * 52130 * Copyright (c) 2014 Brightcove 52131 * Licensed under the apache2 license. 52132 */ 52133 52134 /** 52135 * Returns the subarray of a Uint8Array without PKCS#7 padding. 52136 * @param padded {Uint8Array} unencrypted bytes that have been padded 52137 * @return {Uint8Array} the unpadded bytes 52138 * @see http://tools.ietf.org/html/rfc5652 52139 */ 52140 function unpad(padded) { 52141 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 52142 } 52143 52144 var classCallCheck = function classCallCheck(instance, Constructor) { 52145 if (!(instance instanceof Constructor)) { 52146 throw new TypeError("Cannot call a class as a function"); 52147 } 52148 }; 52149 52150 var createClass = function () { 52151 function defineProperties(target, props) { 52152 for (var i = 0; i < props.length; i++) { 52153 var descriptor = props[i]; 52154 descriptor.enumerable = descriptor.enumerable || false; 52155 descriptor.configurable = true; 52156 if ("value" in descriptor) descriptor.writable = true; 52157 Object.defineProperty(target, descriptor.key, descriptor); 52158 } 52159 } 52160 52161 return function (Constructor, protoProps, staticProps) { 52162 if (protoProps) defineProperties(Constructor.prototype, protoProps); 52163 if (staticProps) defineProperties(Constructor, staticProps); 52164 return Constructor; 52165 }; 52166 }(); 52167 52168 var inherits = function inherits(subClass, superClass) { 52169 if (typeof superClass !== "function" && superClass !== null) { 52170 throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 52171 } 52172 52173 subClass.prototype = Object.create(superClass && superClass.prototype, { 52174 constructor: { 52175 value: subClass, 52176 enumerable: false, 52177 writable: true, 52178 configurable: true 52179 } 52180 }); 52181 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 52182 }; 52183 52184 var possibleConstructorReturn = function possibleConstructorReturn(self, call) { 52185 if (!self) { 52186 throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 52187 } 52188 52189 return call && (typeof call === "object" || typeof call === "function") ? call : self; 52190 }; 52191 /** 52192 * @file aes.js 52193 * 52194 * This file contains an adaptation of the AES decryption algorithm 52195 * from the Standford Javascript Cryptography Library. That work is 52196 * covered by the following copyright and permissions notice: 52197 * 52198 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 52199 * All rights reserved. 52200 * 52201 * Redistribution and use in source and binary forms, with or without 52202 * modification, are permitted provided that the following conditions are 52203 * met: 52204 * 52205 * 1. Redistributions of source code must retain the above copyright 52206 * notice, this list of conditions and the following disclaimer. 52207 * 52208 * 2. Redistributions in binary form must reproduce the above 52209 * copyright notice, this list of conditions and the following 52210 * disclaimer in the documentation and/or other materials provided 52211 * with the distribution. 52212 * 52213 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 52214 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 52215 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 52216 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 52217 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 52218 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 52219 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 52220 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 52221 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 52222 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 52223 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 52224 * 52225 * The views and conclusions contained in the software and documentation 52226 * are those of the authors and should not be interpreted as representing 52227 * official policies, either expressed or implied, of the authors. 52228 */ 52229 52230 /** 52231 * Expand the S-box tables. 52232 * 52233 * @private 52234 */ 52235 52236 52237 var precompute = function precompute() { 52238 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 52239 var encTable = tables[0]; 52240 var decTable = tables[1]; 52241 var sbox = encTable[4]; 52242 var sboxInv = decTable[4]; 52243 var i = void 0; 52244 var x = void 0; 52245 var xInv = void 0; 52246 var d = []; 52247 var th = []; 52248 var x2 = void 0; 52249 var x4 = void 0; 52250 var x8 = void 0; 52251 var s = void 0; 52252 var tEnc = void 0; 52253 var tDec = void 0; // Compute double and third tables 52254 52255 for (i = 0; i < 256; i++) { 52256 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 52257 } 52258 52259 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 52260 // Compute sbox 52261 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 52262 s = s >> 8 ^ s & 255 ^ 99; 52263 sbox[x] = s; 52264 sboxInv[s] = x; // Compute MixColumns 52265 52266 x8 = d[x4 = d[x2 = d[x]]]; 52267 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 52268 tEnc = d[s] * 0x101 ^ s * 0x1010100; 52269 52270 for (i = 0; i < 4; i++) { 52271 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 52272 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 52273 } 52274 } // Compactify. Considerable speedup on Firefox. 52275 52276 52277 for (i = 0; i < 5; i++) { 52278 encTable[i] = encTable[i].slice(0); 52279 decTable[i] = decTable[i].slice(0); 52280 } 52281 52282 return tables; 52283 }; 52284 52285 var aesTables = null; 52286 /** 52287 * Schedule out an AES key for both encryption and decryption. This 52288 * is a low-level class. Use a cipher mode to do bulk encryption. 52289 * 52290 * @class AES 52291 * @param key {Array} The key as an array of 4, 6 or 8 words. 52292 */ 52293 52294 var AES = function () { 52295 function AES(key) { 52296 classCallCheck(this, AES); 52297 /** 52298 * The expanded S-box and inverse S-box tables. These will be computed 52299 * on the client so that we don't have to send them down the wire. 52300 * 52301 * There are two tables, _tables[0] is for encryption and 52302 * _tables[1] is for decryption. 52303 * 52304 * The first 4 sub-tables are the expanded S-box with MixColumns. The 52305 * last (_tables[01][4]) is the S-box itself. 52306 * 52307 * @private 52308 */ 52309 // if we have yet to precompute the S-box tables 52310 // do so now 52311 52312 if (!aesTables) { 52313 aesTables = precompute(); 52314 } // then make a copy of that object for use 52315 52316 52317 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()]]; 52318 var i = void 0; 52319 var j = void 0; 52320 var tmp = void 0; 52321 var encKey = void 0; 52322 var decKey = void 0; 52323 var sbox = this._tables[0][4]; 52324 var decTable = this._tables[1]; 52325 var keyLen = key.length; 52326 var rcon = 1; 52327 52328 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 52329 throw new Error('Invalid aes key size'); 52330 } 52331 52332 encKey = key.slice(0); 52333 decKey = []; 52334 this._key = [encKey, decKey]; // schedule encryption keys 52335 52336 for (i = keyLen; i < 4 * keyLen + 28; i++) { 52337 tmp = encKey[i - 1]; // apply sbox 52338 52339 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 52340 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; // shift rows and add rcon 52341 52342 if (i % keyLen === 0) { 52343 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 52344 rcon = rcon << 1 ^ (rcon >> 7) * 283; 52345 } 52346 } 52347 52348 encKey[i] = encKey[i - keyLen] ^ tmp; 52349 } // schedule decryption keys 52350 52351 52352 for (j = 0; i; j++, i--) { 52353 tmp = encKey[j & 3 ? i : i - 4]; 52354 52355 if (i <= 4 || j < 4) { 52356 decKey[j] = tmp; 52357 } else { 52358 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 52359 } 52360 } 52361 } 52362 /** 52363 * Decrypt 16 bytes, specified as four 32-bit words. 52364 * 52365 * @param {Number} encrypted0 the first word to decrypt 52366 * @param {Number} encrypted1 the second word to decrypt 52367 * @param {Number} encrypted2 the third word to decrypt 52368 * @param {Number} encrypted3 the fourth word to decrypt 52369 * @param {Int32Array} out the array to write the decrypted words 52370 * into 52371 * @param {Number} offset the offset into the output array to start 52372 * writing results 52373 * @return {Array} The plaintext. 52374 */ 52375 52376 52377 AES.prototype.decrypt = function decrypt$$1(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 52378 var key = this._key[1]; // state variables a,b,c,d are loaded with pre-whitened data 52379 52380 var a = encrypted0 ^ key[0]; 52381 var b = encrypted3 ^ key[1]; 52382 var c = encrypted2 ^ key[2]; 52383 var d = encrypted1 ^ key[3]; 52384 var a2 = void 0; 52385 var b2 = void 0; 52386 var c2 = void 0; // key.length === 2 ? 52387 52388 var nInnerRounds = key.length / 4 - 2; 52389 var i = void 0; 52390 var kIndex = 4; 52391 var table = this._tables[1]; // load up the tables 52392 52393 var table0 = table[0]; 52394 var table1 = table[1]; 52395 var table2 = table[2]; 52396 var table3 = table[3]; 52397 var sbox = table[4]; // Inner rounds. Cribbed from OpenSSL. 52398 52399 for (i = 0; i < nInnerRounds; i++) { 52400 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 52401 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 52402 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 52403 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 52404 kIndex += 4; 52405 a = a2; 52406 b = b2; 52407 c = c2; 52408 } // Last round. 52409 52410 52411 for (i = 0; i < 4; i++) { 52412 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 52413 a2 = a; 52414 a = b; 52415 b = c; 52416 c = d; 52417 d = a2; 52418 } 52419 }; 52420 52421 return AES; 52422 }(); 52423 /** 52424 * @file stream.js 52425 */ 52426 52427 /** 52428 * A lightweight readable stream implemention that handles event dispatching. 52429 * 52430 * @class Stream 52431 */ 52432 52433 52434 var Stream = function () { 52435 function Stream() { 52436 classCallCheck(this, Stream); 52437 this.listeners = {}; 52438 } 52439 /** 52440 * Add a listener for a specified event type. 52441 * 52442 * @param {String} type the event name 52443 * @param {Function} listener the callback to be invoked when an event of 52444 * the specified type occurs 52445 */ 52446 52447 52448 Stream.prototype.on = function on(type, listener) { 52449 if (!this.listeners[type]) { 52450 this.listeners[type] = []; 52451 } 52452 52453 this.listeners[type].push(listener); 52454 }; 52455 /** 52456 * Remove a listener for a specified event type. 52457 * 52458 * @param {String} type the event name 52459 * @param {Function} listener a function previously registered for this 52460 * type of event through `on` 52461 * @return {Boolean} if we could turn it off or not 52462 */ 52463 52464 52465 Stream.prototype.off = function off(type, listener) { 52466 if (!this.listeners[type]) { 52467 return false; 52468 } 52469 52470 var index = this.listeners[type].indexOf(listener); 52471 this.listeners[type].splice(index, 1); 52472 return index > -1; 52473 }; 52474 /** 52475 * Trigger an event of the specified type on this stream. Any additional 52476 * arguments to this function are passed as parameters to event listeners. 52477 * 52478 * @param {String} type the event name 52479 */ 52480 52481 52482 Stream.prototype.trigger = function trigger(type) { 52483 var callbacks = this.listeners[type]; 52484 52485 if (!callbacks) { 52486 return; 52487 } // Slicing the arguments on every invocation of this method 52488 // can add a significant amount of overhead. Avoid the 52489 // intermediate object creation for the common case of a 52490 // single callback argument 52491 52492 52493 if (arguments.length === 2) { 52494 var length = callbacks.length; 52495 52496 for (var i = 0; i < length; ++i) { 52497 callbacks[i].call(this, arguments[1]); 52498 } 52499 } else { 52500 var args = Array.prototype.slice.call(arguments, 1); 52501 var _length = callbacks.length; 52502 52503 for (var _i = 0; _i < _length; ++_i) { 52504 callbacks[_i].apply(this, args); 52505 } 52506 } 52507 }; 52508 /** 52509 * Destroys the stream and cleans up. 52510 */ 52511 52512 52513 Stream.prototype.dispose = function dispose() { 52514 this.listeners = {}; 52515 }; 52516 /** 52517 * Forwards all `data` events on this stream to the destination stream. The 52518 * destination stream should provide a method `push` to receive the data 52519 * events as they arrive. 52520 * 52521 * @param {Stream} destination the stream that will receive all `data` events 52522 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 52523 */ 52524 52525 52526 Stream.prototype.pipe = function pipe(destination) { 52527 this.on('data', function (data) { 52528 destination.push(data); 52529 }); 52530 }; 52531 52532 return Stream; 52533 }(); 52534 /** 52535 * @file async-stream.js 52536 */ 52537 52538 /** 52539 * A wrapper around the Stream class to use setTiemout 52540 * and run stream "jobs" Asynchronously 52541 * 52542 * @class AsyncStream 52543 * @extends Stream 52544 */ 52545 52546 52547 var AsyncStream$$1 = function (_Stream) { 52548 inherits(AsyncStream$$1, _Stream); 52549 52550 function AsyncStream$$1() { 52551 classCallCheck(this, AsyncStream$$1); 52552 52553 var _this = possibleConstructorReturn(this, _Stream.call(this, Stream)); 52554 52555 _this.jobs = []; 52556 _this.delay = 1; 52557 _this.timeout_ = null; 52558 return _this; 52559 } 52560 /** 52561 * process an async job 52562 * 52563 * @private 52564 */ 52565 52566 52567 AsyncStream$$1.prototype.processJob_ = function processJob_() { 52568 this.jobs.shift()(); 52569 52570 if (this.jobs.length) { 52571 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 52572 } else { 52573 this.timeout_ = null; 52574 } 52575 }; 52576 /** 52577 * push a job into the stream 52578 * 52579 * @param {Function} job the job to push into the stream 52580 */ 52581 52582 52583 AsyncStream$$1.prototype.push = function push(job) { 52584 this.jobs.push(job); 52585 52586 if (!this.timeout_) { 52587 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 52588 } 52589 }; 52590 52591 return AsyncStream$$1; 52592 }(Stream); 52593 /** 52594 * @file decrypter.js 52595 * 52596 * An asynchronous implementation of AES-128 CBC decryption with 52597 * PKCS#7 padding. 52598 */ 52599 52600 /** 52601 * Convert network-order (big-endian) bytes into their little-endian 52602 * representation. 52603 */ 52604 52605 52606 var ntoh = function ntoh(word) { 52607 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 52608 }; 52609 /** 52610 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 52611 * 52612 * @param {Uint8Array} encrypted the encrypted bytes 52613 * @param {Uint32Array} key the bytes of the decryption key 52614 * @param {Uint32Array} initVector the initialization vector (IV) to 52615 * use for the first round of CBC. 52616 * @return {Uint8Array} the decrypted bytes 52617 * 52618 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 52619 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 52620 * @see https://tools.ietf.org/html/rfc2315 52621 */ 52622 52623 52624 var decrypt$$1 = function decrypt$$1(encrypted, key, initVector) { 52625 // word-level access to the encrypted bytes 52626 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 52627 var decipher = new AES(Array.prototype.slice.call(key)); // byte and word-level access for the decrypted output 52628 52629 var decrypted = new Uint8Array(encrypted.byteLength); 52630 var decrypted32 = new Int32Array(decrypted.buffer); // temporary variables for working with the IV, encrypted, and 52631 // decrypted data 52632 52633 var init0 = void 0; 52634 var init1 = void 0; 52635 var init2 = void 0; 52636 var init3 = void 0; 52637 var encrypted0 = void 0; 52638 var encrypted1 = void 0; 52639 var encrypted2 = void 0; 52640 var encrypted3 = void 0; // iteration variable 52641 52642 var wordIx = void 0; // pull out the words of the IV to ensure we don't modify the 52643 // passed-in reference and easier access 52644 52645 init0 = initVector[0]; 52646 init1 = initVector[1]; 52647 init2 = initVector[2]; 52648 init3 = initVector[3]; // decrypt four word sequences, applying cipher-block chaining (CBC) 52649 // to each decrypted block 52650 52651 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 52652 // convert big-endian (network order) words into little-endian 52653 // (javascript order) 52654 encrypted0 = ntoh(encrypted32[wordIx]); 52655 encrypted1 = ntoh(encrypted32[wordIx + 1]); 52656 encrypted2 = ntoh(encrypted32[wordIx + 2]); 52657 encrypted3 = ntoh(encrypted32[wordIx + 3]); // decrypt the block 52658 52659 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); // XOR with the IV, and restore network byte-order to obtain the 52660 // plaintext 52661 52662 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 52663 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 52664 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 52665 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); // setup the IV for the next round 52666 52667 init0 = encrypted0; 52668 init1 = encrypted1; 52669 init2 = encrypted2; 52670 init3 = encrypted3; 52671 } 52672 52673 return decrypted; 52674 }; 52675 /** 52676 * The `Decrypter` class that manages decryption of AES 52677 * data through `AsyncStream` objects and the `decrypt` 52678 * function 52679 * 52680 * @param {Uint8Array} encrypted the encrypted bytes 52681 * @param {Uint32Array} key the bytes of the decryption key 52682 * @param {Uint32Array} initVector the initialization vector (IV) to 52683 * @param {Function} done the function to run when done 52684 * @class Decrypter 52685 */ 52686 52687 52688 var Decrypter$$1 = function () { 52689 function Decrypter$$1(encrypted, key, initVector, done) { 52690 classCallCheck(this, Decrypter$$1); 52691 var step = Decrypter$$1.STEP; 52692 var encrypted32 = new Int32Array(encrypted.buffer); 52693 var decrypted = new Uint8Array(encrypted.byteLength); 52694 var i = 0; 52695 this.asyncStream_ = new AsyncStream$$1(); // split up the encryption job and do the individual chunks asynchronously 52696 52697 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 52698 52699 for (i = step; i < encrypted32.length; i += step) { 52700 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 52701 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 52702 } // invoke the done() callback when everything is finished 52703 52704 52705 this.asyncStream_.push(function () { 52706 // remove pkcs#7 padding from the decrypted bytes 52707 done(null, unpad(decrypted)); 52708 }); 52709 } 52710 /** 52711 * a getter for step the maximum number of bytes to process at one time 52712 * 52713 * @return {Number} the value of step 32000 52714 */ 52715 52716 /** 52717 * @private 52718 */ 52719 52720 52721 Decrypter$$1.prototype.decryptChunk_ = function decryptChunk_(encrypted, key, initVector, decrypted) { 52722 return function () { 52723 var bytes = decrypt$$1(encrypted, key, initVector); 52724 decrypted.set(bytes, encrypted.byteOffset); 52725 }; 52726 }; 52727 52728 createClass(Decrypter$$1, null, [{ 52729 key: 'STEP', 52730 get: function get$$1() { 52731 // 4 * 8000; 52732 return 32000; 52733 } 52734 }]); 52735 return Decrypter$$1; 52736 }(); 52737 /** 52738 * @file bin-utils.js 52739 */ 52740 52741 /** 52742 * Creates an object for sending to a web worker modifying properties that are TypedArrays 52743 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 52744 * 52745 * @param {Object} message 52746 * Object of properties and values to send to the web worker 52747 * @return {Object} 52748 * Modified message with TypedArray values expanded 52749 * @function createTransferableMessage 52750 */ 52751 52752 52753 var createTransferableMessage = function createTransferableMessage(message) { 52754 var transferable = {}; 52755 Object.keys(message).forEach(function (key) { 52756 var value = message[key]; 52757 52758 if (ArrayBuffer.isView(value)) { 52759 transferable[key] = { 52760 bytes: value.buffer, 52761 byteOffset: value.byteOffset, 52762 byteLength: value.byteLength 52763 }; 52764 } else { 52765 transferable[key] = value; 52766 } 52767 }); 52768 return transferable; 52769 }; 52770 /** 52771 * Our web worker interface so that things can talk to aes-decrypter 52772 * that will be running in a web worker. the scope is passed to this by 52773 * webworkify. 52774 * 52775 * @param {Object} self 52776 * the scope for the web worker 52777 */ 52778 52779 52780 var DecrypterWorker = function DecrypterWorker(self) { 52781 self.onmessage = function (event) { 52782 var data = event.data; 52783 var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength); 52784 var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4); 52785 var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4); 52786 /* eslint-disable no-new, handle-callback-err */ 52787 52788 new Decrypter$$1(encrypted, key, iv, function (err, bytes) { 52789 self.postMessage(createTransferableMessage({ 52790 source: data.source, 52791 decrypted: bytes 52792 }), [bytes.buffer]); 52793 }); 52794 /* eslint-enable */ 52795 }; 52796 }; 52797 52798 var decrypterWorker = new DecrypterWorker(self); 52799 return decrypterWorker; 52800 }(); 52801 }); 52802 /** 52803 * Convert the properties of an HLS track into an audioTrackKind. 52804 * 52805 * @private 52806 */ 52807 52808 var audioTrackKind_ = function audioTrackKind_(properties) { 52809 var kind = properties["default"] ? 'main' : 'alternative'; 52810 52811 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) { 52812 kind = 'main-desc'; 52813 } 52814 52815 return kind; 52816 }; 52817 /** 52818 * Pause provided segment loader and playlist loader if active 52819 * 52820 * @param {SegmentLoader} segmentLoader 52821 * SegmentLoader to pause 52822 * @param {Object} mediaType 52823 * Active media type 52824 * @function stopLoaders 52825 */ 52826 52827 52828 var stopLoaders = function stopLoaders(segmentLoader, mediaType) { 52829 segmentLoader.abort(); 52830 segmentLoader.pause(); 52831 52832 if (mediaType && mediaType.activePlaylistLoader) { 52833 mediaType.activePlaylistLoader.pause(); 52834 mediaType.activePlaylistLoader = null; 52835 } 52836 }; 52837 /** 52838 * Start loading provided segment loader and playlist loader 52839 * 52840 * @param {PlaylistLoader} playlistLoader 52841 * PlaylistLoader to start loading 52842 * @param {Object} mediaType 52843 * Active media type 52844 * @function startLoaders 52845 */ 52846 52847 52848 var startLoaders = function startLoaders(playlistLoader, mediaType) { 52849 // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the 52850 // playlist loader 52851 mediaType.activePlaylistLoader = playlistLoader; 52852 playlistLoader.load(); 52853 }; 52854 /** 52855 * Returns a function to be called when the media group changes. It performs a 52856 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a 52857 * change of group is merely a rendition switch of the same content at another encoding, 52858 * rather than a change of content, such as switching audio from English to Spanish. 52859 * 52860 * @param {String} type 52861 * MediaGroup type 52862 * @param {Object} settings 52863 * Object containing required information for media groups 52864 * @return {Function} 52865 * Handler for a non-destructive resync of SegmentLoader when the active media 52866 * group changes. 52867 * @function onGroupChanged 52868 */ 52869 52870 52871 var onGroupChanged = function onGroupChanged(type, settings) { 52872 return function () { 52873 var _settings$segmentLoad = settings.segmentLoaders, 52874 segmentLoader = _settings$segmentLoad[type], 52875 mainSegmentLoader = _settings$segmentLoad.main, 52876 mediaType = settings.mediaTypes[type]; 52877 var activeTrack = mediaType.activeTrack(); 52878 var activeGroup = mediaType.activeGroup(activeTrack); 52879 var previousActiveLoader = mediaType.activePlaylistLoader; 52880 stopLoaders(segmentLoader, mediaType); 52881 52882 if (!activeGroup) { 52883 // there is no group active 52884 return; 52885 } 52886 52887 if (!activeGroup.playlistLoader) { 52888 if (previousActiveLoader) { 52889 // The previous group had a playlist loader but the new active group does not 52890 // this means we are switching from demuxed to muxed audio. In this case we want to 52891 // do a destructive reset of the main segment loader and n
vendor: 16,384 bytes, lines 52891-53318
52891ot restart the audio 52892 // loaders. 52893 mainSegmentLoader.resetEverything(); 52894 } 52895 52896 return; 52897 } // Non-destructive resync 52898 52899 52900 segmentLoader.resyncLoader(); 52901 startLoaders(activeGroup.playlistLoader, mediaType); 52902 }; 52903 }; 52904 /** 52905 * Returns a function to be called when the media track changes. It performs a 52906 * destructive reset of the SegmentLoader to ensure we start loading as close to 52907 * currentTime as possible. 52908 * 52909 * @param {String} type 52910 * MediaGroup type 52911 * @param {Object} settings 52912 * Object containing required information for media groups 52913 * @return {Function} 52914 * Handler for a destructive reset of SegmentLoader when the active media 52915 * track changes. 52916 * @function onTrackChanged 52917 */ 52918 52919 52920 var onTrackChanged = function onTrackChanged(type, settings) { 52921 return function () { 52922 var _settings$segmentLoad2 = settings.segmentLoaders, 52923 segmentLoader = _settings$segmentLoad2[type], 52924 mainSegmentLoader = _settings$segmentLoad2.main, 52925 mediaType = settings.mediaTypes[type]; 52926 var activeTrack = mediaType.activeTrack(); 52927 var activeGroup = mediaType.activeGroup(activeTrack); 52928 var previousActiveLoader = mediaType.activePlaylistLoader; 52929 stopLoaders(segmentLoader, mediaType); 52930 52931 if (!activeGroup) { 52932 // there is no group active so we do not want to restart loaders 52933 return; 52934 } 52935 52936 if (!activeGroup.playlistLoader) { 52937 // when switching from demuxed audio/video to muxed audio/video (noted by no playlist 52938 // loader for the audio group), we want to do a destructive reset of the main segment 52939 // loader and not restart the audio loaders 52940 mainSegmentLoader.resetEverything(); 52941 return; 52942 } 52943 52944 if (previousActiveLoader === activeGroup.playlistLoader) { 52945 // Nothing has actually changed. This can happen because track change events can fire 52946 // multiple times for a "single" change. One for enabling the new active track, and 52947 // one for disabling the track that was active 52948 startLoaders(activeGroup.playlistLoader, mediaType); 52949 return; 52950 } 52951 52952 if (segmentLoader.track) { 52953 // For WebVTT, set the new text track in the segmentloader 52954 segmentLoader.track(activeTrack); 52955 } // destructive reset 52956 52957 52958 segmentLoader.resetEverything(); 52959 startLoaders(activeGroup.playlistLoader, mediaType); 52960 }; 52961 }; 52962 52963 var onError = { 52964 /** 52965 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 52966 * an error. 52967 * 52968 * @param {String} type 52969 * MediaGroup type 52970 * @param {Object} settings 52971 * Object containing required information for media groups 52972 * @return {Function} 52973 * Error handler. Logs warning (or error if the playlist is blacklisted) to 52974 * console and switches back to default audio track. 52975 * @function onError.AUDIO 52976 */ 52977 AUDIO: function AUDIO(type, settings) { 52978 return function () { 52979 var segmentLoader = settings.segmentLoaders[type], 52980 mediaType = settings.mediaTypes[type], 52981 blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist; 52982 stopLoaders(segmentLoader, mediaType); // switch back to default audio track 52983 52984 var activeTrack = mediaType.activeTrack(); 52985 var activeGroup = mediaType.activeGroup(); 52986 var id = (activeGroup.filter(function (group) { 52987 return group["default"]; 52988 })[0] || activeGroup[0]).id; 52989 var defaultTrack = mediaType.tracks[id]; 52990 52991 if (activeTrack === defaultTrack) { 52992 // Default track encountered an error. All we can do now is blacklist the current 52993 // rendition and hope another will switch audio groups 52994 blacklistCurrentPlaylist({ 52995 message: 'Problem encountered loading the default audio track.' 52996 }); 52997 return; 52998 } 52999 53000 videojs$1.log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.'); 53001 53002 for (var trackId in mediaType.tracks) { 53003 mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack; 53004 } 53005 53006 mediaType.onTrackChanged(); 53007 }; 53008 }, 53009 53010 /** 53011 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 53012 * an error. 53013 * 53014 * @param {String} type 53015 * MediaGroup type 53016 * @param {Object} settings 53017 * Object containing required information for media groups 53018 * @return {Function} 53019 * Error handler. Logs warning to console and disables the active subtitle track 53020 * @function onError.SUBTITLES 53021 */ 53022 SUBTITLES: function SUBTITLES(type, settings) { 53023 return function () { 53024 var segmentLoader = settings.segmentLoaders[type], 53025 mediaType = settings.mediaTypes[type]; 53026 videojs$1.log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.'); 53027 stopLoaders(segmentLoader, mediaType); 53028 var track = mediaType.activeTrack(); 53029 53030 if (track) { 53031 track.mode = 'disabled'; 53032 } 53033 53034 mediaType.onTrackChanged(); 53035 }; 53036 } 53037 }; 53038 var setupListeners = { 53039 /** 53040 * Setup event listeners for audio playlist loader 53041 * 53042 * @param {String} type 53043 * MediaGroup type 53044 * @param {PlaylistLoader|null} playlistLoader 53045 * PlaylistLoader to register listeners on 53046 * @param {Object} settings 53047 * Object containing required information for media groups 53048 * @function setupListeners.AUDIO 53049 */ 53050 AUDIO: function AUDIO(type, playlistLoader, settings) { 53051 if (!playlistLoader) { 53052 // no playlist loader means audio will be muxed with the video 53053 return; 53054 } 53055 53056 var tech = settings.tech, 53057 requestOptions = settings.requestOptions, 53058 segmentLoader = settings.segmentLoaders[type]; 53059 playlistLoader.on('loadedmetadata', function () { 53060 var media = playlistLoader.media(); 53061 segmentLoader.playlist(media, requestOptions); // if the video is already playing, or if this isn't a live video and preload 53062 // permits, start downloading segments 53063 53064 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 53065 segmentLoader.load(); 53066 } 53067 }); 53068 playlistLoader.on('loadedplaylist', function () { 53069 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 53070 53071 if (!tech.paused()) { 53072 segmentLoader.load(); 53073 } 53074 }); 53075 playlistLoader.on('error', onError[type](type, settings)); 53076 }, 53077 53078 /** 53079 * Setup event listeners for subtitle playlist loader 53080 * 53081 * @param {String} type 53082 * MediaGroup type 53083 * @param {PlaylistLoader|null} playlistLoader 53084 * PlaylistLoader to register listeners on 53085 * @param {Object} settings 53086 * Object containing required information for media groups 53087 * @function setupListeners.SUBTITLES 53088 */ 53089 SUBTITLES: function SUBTITLES(type, playlistLoader, settings) { 53090 var tech = settings.tech, 53091 requestOptions = settings.requestOptions, 53092 segmentLoader = settings.segmentLoaders[type], 53093 mediaType = settings.mediaTypes[type]; 53094 playlistLoader.on('loadedmetadata', function () { 53095 var media = playlistLoader.media(); 53096 segmentLoader.playlist(media, requestOptions); 53097 segmentLoader.track(mediaType.activeTrack()); // if the video is already playing, or if this isn't a live video and preload 53098 // permits, start downloading segments 53099 53100 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 53101 segmentLoader.load(); 53102 } 53103 }); 53104 playlistLoader.on('loadedplaylist', function () { 53105 segmentLoader.playlist(playlistLoader.media(), requestOptions); // If the player isn't paused, ensure that the segment loader is running 53106 53107 if (!tech.paused()) { 53108 segmentLoader.load(); 53109 } 53110 }); 53111 playlistLoader.on('error', onError[type](type, settings)); 53112 } 53113 }; 53114 var initialize = { 53115 /** 53116 * Setup PlaylistLoaders and AudioTracks for the audio groups 53117 * 53118 * @param {String} type 53119 * MediaGroup type 53120 * @param {Object} settings 53121 * Object containing required information for media groups 53122 * @function initialize.AUDIO 53123 */ 53124 'AUDIO': function AUDIO(type, settings) { 53125 var hls = settings.hls, 53126 sourceType = settings.sourceType, 53127 segmentLoader = settings.segmentLoaders[type], 53128 requestOptions = settings.requestOptions, 53129 mediaGroups = settings.master.mediaGroups, 53130 _settings$mediaTypes$ = settings.mediaTypes[type], 53131 groups = _settings$mediaTypes$.groups, 53132 tracks = _settings$mediaTypes$.tracks, 53133 masterPlaylistLoader = settings.masterPlaylistLoader; // force a default if we have none 53134 53135 if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0) { 53136 mediaGroups[type] = { 53137 main: { 53138 "default": { 53139 "default": true 53140 } 53141 } 53142 }; 53143 } 53144 53145 for (var groupId in mediaGroups[type]) { 53146 if (!groups[groupId]) { 53147 groups[groupId] = []; 53148 } // List of playlists that have an AUDIO attribute value matching the current 53149 // group ID 53150 53151 53152 for (var variantLabel in mediaGroups[type][groupId]) { 53153 var properties = mediaGroups[type][groupId][variantLabel]; 53154 var playlistLoader = void 0; 53155 53156 if (properties.resolvedUri) { 53157 playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions); 53158 } else if (properties.playlists && sourceType === 'dash') { 53159 playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader); 53160 } else { 53161 // no resolvedUri means the audio is muxed with the video when using this 53162 // audio track 53163 playlistLoader = null; 53164 } 53165 53166 properties = videojs$1.mergeOptions({ 53167 id: variantLabel, 53168 playlistLoader: playlistLoader 53169 }, properties); 53170 setupListeners[type](type, properties.playlistLoader, settings); 53171 groups[groupId].push(properties); 53172 53173 if (typeof tracks[variantLabel] === 'undefined') { 53174 var track = new videojs$1.AudioTrack({ 53175 id: variantLabel, 53176 kind: audioTrackKind_(properties), 53177 enabled: false, 53178 language: properties.language, 53179 "default": properties["default"], 53180 label: variantLabel 53181 }); 53182 tracks[variantLabel] = track; 53183 } 53184 } 53185 } // setup single error event handler for the segment loader 53186 53187 53188 segmentLoader.on('error', onError[type](type, settings)); 53189 }, 53190 53191 /** 53192 * Setup PlaylistLoaders and TextTracks for the subtitle groups 53193 * 53194 * @param {String} type 53195 * MediaGroup type 53196 * @param {Object} settings 53197 * Object containing required information for media groups 53198 * @function initialize.SUBTITLES 53199 */ 53200 'SUBTITLES': function SUBTITLES(type, settings) { 53201 var tech = settings.tech, 53202 hls = settings.hls, 53203 sourceType = settings.sourceType, 53204 segmentLoader = settings.segmentLoaders[type], 53205 requestOptions = settings.requestOptions, 53206 mediaGroups = settings.master.mediaGroups, 53207 _settings$mediaTypes$2 = settings.mediaTypes[type], 53208 groups = _settings$mediaTypes$2.groups, 53209 tracks = _settings$mediaTypes$2.tracks, 53210 masterPlaylistLoader = settings.masterPlaylistLoader; 53211 53212 for (var groupId in mediaGroups[type]) { 53213 if (!groups[groupId]) { 53214 groups[groupId] = []; 53215 } 53216 53217 for (var variantLabel in mediaGroups[type][groupId]) { 53218 if (mediaGroups[type][groupId][variantLabel].forced) { 53219 // Subtitle playlists with the forced attribute are not selectable in Safari. 53220 // According to Apple's HLS Authoring Specification: 53221 // If content has forced subtitles and regular subtitles in a given language, 53222 // the regular subtitles track in that language MUST contain both the forced 53223 // subtitles and the regular subtitles for that language. 53224 // Because of this requirement and that Safari does not add forced subtitles, 53225 // forced subtitles are skipped here to maintain consistent experience across 53226 // all platforms 53227 continue; 53228 } 53229 53230 var properties = mediaGroups[type][groupId][variantLabel]; 53231 var playlistLoader = void 0; 53232 53233 if (sourceType === 'hls') { 53234 playlistLoader = new PlaylistLoader(properties.resolvedUri, hls, requestOptions); 53235 } else if (sourceType === 'dash') { 53236 playlistLoader = new DashPlaylistLoader(properties.playlists[0], hls, requestOptions, masterPlaylistLoader); 53237 } 53238 53239 properties = videojs$1.mergeOptions({ 53240 id: variantLabel, 53241 playlistLoader: playlistLoader 53242 }, properties); 53243 setupListeners[type](type, properties.playlistLoader, settings); 53244 groups[groupId].push(properties); 53245 53246 if (typeof tracks[variantLabel] === 'undefined') { 53247 var track = tech.addRemoteTextTrack({ 53248 id: variantLabel, 53249 kind: 'subtitles', 53250 "default": properties["default"] && properties.autoselect, 53251 language: properties.language, 53252 label: variantLabel 53253 }, false).track; 53254 tracks[variantLabel] = track; 53255 } 53256 } 53257 } // setup single error event handler for the segment loader 53258 53259 53260 segmentLoader.on('error', onError[type](type, settings)); 53261 }, 53262 53263 /** 53264 * Setup TextTracks for the closed-caption groups 53265 * 53266 * @param {String} type 53267 * MediaGroup type 53268 * @param {Object} settings 53269 * Object containing required information for media groups 53270 * @function initialize['CLOSED-CAPTIONS'] 53271 */ 53272 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) { 53273 var tech = settings.tech, 53274 mediaGroups = settings.master.mediaGroups, 53275 _settings$mediaTypes$3 = settings.mediaTypes[type], 53276 groups = _settings$mediaTypes$3.groups, 53277 tracks = _settings$mediaTypes$3.tracks; 53278 53279 for (var groupId in mediaGroups[type]) { 53280 if (!groups[groupId]) { 53281 groups[groupId] = []; 53282 } 53283 53284 for (var variantLabel in mediaGroups[type][groupId]) { 53285 var properties = mediaGroups[type][groupId][variantLabel]; // We only support CEA608 captions for now, so ignore anything that 53286 // doesn't use a CCx INSTREAM-ID 53287 53288 if (!properties.instreamId.match(/CC\d/)) { 53289 continue; 53290 } // No PlaylistLoader is required for Closed-Captions because the captions are 53291 // embedded within the video stream 53292 53293 53294 groups[groupId].push(videojs$1.mergeOptions({ 53295 id: variantLabel 53296 }, properties)); 53297 53298 if (typeof tracks[variantLabel] === 'undefined') { 53299 var track = tech.addRemoteTextTrack({ 53300 id: properties.instreamId, 53301 kind: 'captions', 53302 "default": properties["default"] && properties.autoselect, 53303 language: properties.language, 53304 label: variantLabel 53305 }, false).track; 53306 tracks[variantLabel] = track; 53307 } 53308 } 53309 } 53310 } 53311 }; 53312 /** 53313 * Returns a function used to get the active group of the provided type 53314 * 53315 * @param {String} type 53316 * MediaGroup type 53317 * @param {Object} settings 53318 * Object containing requi
vendor: 37,520 bytes, lines 53318-54313
53318red information for media groups 53319 * @return {Function} 53320 * Function that returns the active media group for the provided type. Takes an 53321 * optional parameter {TextTrack} track. If no track is provided, a list of all 53322 * variants in the group, otherwise the variant corresponding to the provided 53323 * track is returned. 53324 * @function activeGroup 53325 */ 53326 53327 var activeGroup = function activeGroup(type, settings) { 53328 return function (track) { 53329 var masterPlaylistLoader = settings.masterPlaylistLoader, 53330 groups = settings.mediaTypes[type].groups; 53331 var media = masterPlaylistLoader.media(); 53332 53333 if (!media) { 53334 return null; 53335 } 53336 53337 var variants = null; 53338 53339 if (media.attributes[type]) { 53340 variants = groups[media.attributes[type]]; 53341 } 53342 53343 variants = variants || groups.main; 53344 53345 if (typeof track === 'undefined') { 53346 return variants; 53347 } 53348 53349 if (track === null) { 53350 // An active track was specified so a corresponding group is expected. track === null 53351 // means no track is currently active so there is no corresponding group 53352 return null; 53353 } 53354 53355 return variants.filter(function (props) { 53356 return props.id === track.id; 53357 })[0] || null; 53358 }; 53359 }; 53360 53361 var activeTrack = { 53362 /** 53363 * Returns a function used to get the active track of type provided 53364 * 53365 * @param {String} type 53366 * MediaGroup type 53367 * @param {Object} settings 53368 * Object containing required information for media groups 53369 * @return {Function} 53370 * Function that returns the active media track for the provided type. Returns 53371 * null if no track is active 53372 * @function activeTrack.AUDIO 53373 */ 53374 AUDIO: function AUDIO(type, settings) { 53375 return function () { 53376 var tracks = settings.mediaTypes[type].tracks; 53377 53378 for (var id in tracks) { 53379 if (tracks[id].enabled) { 53380 return tracks[id]; 53381 } 53382 } 53383 53384 return null; 53385 }; 53386 }, 53387 53388 /** 53389 * Returns a function used to get the active track of type provided 53390 * 53391 * @param {String} type 53392 * MediaGroup type 53393 * @param {Object} settings 53394 * Object containing required information for media groups 53395 * @return {Function} 53396 * Function that returns the active media track for the provided type. Returns 53397 * null if no track is active 53398 * @function activeTrack.SUBTITLES 53399 */ 53400 SUBTITLES: function SUBTITLES(type, settings) { 53401 return function () { 53402 var tracks = settings.mediaTypes[type].tracks; 53403 53404 for (var id in tracks) { 53405 if (tracks[id].mode === 'showing') { 53406 return tracks[id]; 53407 } 53408 } 53409 53410 return null; 53411 }; 53412 } 53413 }; 53414 /** 53415 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles, 53416 * Closed-Captions) specified in the master manifest. 53417 * 53418 * @param {Object} settings 53419 * Object containing required information for setting up the media groups 53420 * @param {SegmentLoader} settings.segmentLoaders.AUDIO 53421 * Audio segment loader 53422 * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES 53423 * Subtitle segment loader 53424 * @param {SegmentLoader} settings.segmentLoaders.main 53425 * Main segment loader 53426 * @param {Tech} settings.tech 53427 * The tech of the player 53428 * @param {Object} settings.requestOptions 53429 * XHR request options used by the segment loaders 53430 * @param {PlaylistLoader} settings.masterPlaylistLoader 53431 * PlaylistLoader for the master source 53432 * @param {HlsHandler} settings.hls 53433 * HLS SourceHandler 53434 * @param {Object} settings.master 53435 * The parsed master manifest 53436 * @param {Object} settings.mediaTypes 53437 * Object to store the loaders, tracks, and utility methods for each media type 53438 * @param {Function} settings.blacklistCurrentPlaylist 53439 * Blacklists the current rendition and forces a rendition switch. 53440 * @function setupMediaGroups 53441 */ 53442 53443 var setupMediaGroups = function setupMediaGroups(settings) { 53444 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 53445 initialize[type](type, settings); 53446 }); 53447 var mediaTypes = settings.mediaTypes, 53448 masterPlaylistLoader = settings.masterPlaylistLoader, 53449 tech = settings.tech, 53450 hls = settings.hls; // setup active group and track getters and change event handlers 53451 53452 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 53453 mediaTypes[type].activeGroup = activeGroup(type, settings); 53454 mediaTypes[type].activeTrack = activeTrack[type](type, settings); 53455 mediaTypes[type].onGroupChanged = onGroupChanged(type, settings); 53456 mediaTypes[type].onTrackChanged = onTrackChanged(type, settings); 53457 }); // DO NOT enable the default subtitle or caption track. 53458 // DO enable the default audio track 53459 53460 var audioGroup = mediaTypes.AUDIO.activeGroup(); 53461 var groupId = (audioGroup.filter(function (group) { 53462 return group["default"]; 53463 })[0] || audioGroup[0]).id; 53464 mediaTypes.AUDIO.tracks[groupId].enabled = true; 53465 mediaTypes.AUDIO.onTrackChanged(); 53466 masterPlaylistLoader.on('mediachange', function () { 53467 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 53468 return mediaTypes[type].onGroupChanged(); 53469 }); 53470 }); // custom audio track change event handler for usage event 53471 53472 var onAudioTrackChanged = function onAudioTrackChanged() { 53473 mediaTypes.AUDIO.onTrackChanged(); 53474 tech.trigger({ 53475 type: 'usage', 53476 name: 'hls-audio-change' 53477 }); 53478 }; 53479 53480 tech.audioTracks().addEventListener('change', onAudioTrackChanged); 53481 tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 53482 hls.on('dispose', function () { 53483 tech.audioTracks().removeEventListener('change', onAudioTrackChanged); 53484 tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 53485 }); // clear existing audio tracks and add the ones we just created 53486 53487 tech.clearTracks('audio'); 53488 53489 for (var id in mediaTypes.AUDIO.tracks) { 53490 tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]); 53491 } 53492 }; 53493 /** 53494 * Creates skeleton object used to store the loaders, tracks, and utility methods for each 53495 * media type 53496 * 53497 * @return {Object} 53498 * Object to store the loaders, tracks, and utility methods for each media type 53499 * @function createMediaTypes 53500 */ 53501 53502 53503 var createMediaTypes = function createMediaTypes() { 53504 var mediaTypes = {}; 53505 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 53506 mediaTypes[type] = { 53507 groups: {}, 53508 tracks: {}, 53509 activePlaylistLoader: null, 53510 activeGroup: noop$1, 53511 activeTrack: noop$1, 53512 onGroupChanged: noop$1, 53513 onTrackChanged: noop$1 53514 }; 53515 }); 53516 return mediaTypes; 53517 }; 53518 /** 53519 * @file master-playlist-controller.js 53520 */ 53521 53522 53523 var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2; 53524 var Hls = void 0; // SegmentLoader stats that need to have each loader's 53525 // values summed to calculate the final value 53526 53527 var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred']; 53528 53529 var sumLoaderStat = function sumLoaderStat(stat) { 53530 return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat]; 53531 }; 53532 /** 53533 * the master playlist controller controller all interactons 53534 * between playlists and segmentloaders. At this time this mainly 53535 * involves a master playlist and a series of audio playlists 53536 * if they are available 53537 * 53538 * @class MasterPlaylistController 53539 * @extends videojs.EventTarget 53540 */ 53541 53542 53543 var MasterPlaylistController = function (_videojs$EventTarget) { 53544 inherits$1(MasterPlaylistController, _videojs$EventTarget); 53545 53546 function MasterPlaylistController(options) { 53547 classCallCheck$1(this, MasterPlaylistController); 53548 53549 var _this = possibleConstructorReturn$1(this, (MasterPlaylistController.__proto__ || Object.getPrototypeOf(MasterPlaylistController)).call(this)); 53550 53551 var url = options.url, 53552 handleManifestRedirects = options.handleManifestRedirects, 53553 withCredentials = options.withCredentials, 53554 tech = options.tech, 53555 bandwidth = options.bandwidth, 53556 externHls = options.externHls, 53557 useCueTags = options.useCueTags, 53558 blacklistDuration = options.blacklistDuration, 53559 enableLowInitialPlaylist = options.enableLowInitialPlaylist, 53560 sourceType = options.sourceType, 53561 seekTo = options.seekTo, 53562 cacheEncryptionKeys = options.cacheEncryptionKeys; 53563 53564 if (!url) { 53565 throw new Error('A non-empty playlist URL is required'); 53566 } 53567 53568 Hls = externHls; 53569 _this.withCredentials = withCredentials; 53570 _this.tech_ = tech; 53571 _this.hls_ = tech.hls; 53572 _this.seekTo_ = seekTo; 53573 _this.sourceType_ = sourceType; 53574 _this.useCueTags_ = useCueTags; 53575 _this.blacklistDuration = blacklistDuration; 53576 _this.enableLowInitialPlaylist = enableLowInitialPlaylist; 53577 53578 if (_this.useCueTags_) { 53579 _this.cueTagsTrack_ = _this.tech_.addTextTrack('metadata', 'ad-cues'); 53580 _this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 53581 } 53582 53583 _this.requestOptions_ = { 53584 withCredentials: withCredentials, 53585 handleManifestRedirects: handleManifestRedirects, 53586 timeout: null 53587 }; 53588 _this.mediaTypes_ = createMediaTypes(); 53589 _this.mediaSource = new videojs$1.MediaSource(); // load the media source into the player 53590 53591 _this.mediaSource.addEventListener('sourceopen', _this.handleSourceOpen_.bind(_this)); 53592 53593 _this.seekable_ = videojs$1.createTimeRanges(); 53594 53595 _this.hasPlayed_ = function () { 53596 return false; 53597 }; 53598 53599 _this.syncController_ = new SyncController(options); 53600 _this.segmentMetadataTrack_ = tech.addRemoteTextTrack({ 53601 kind: 'metadata', 53602 label: 'segment-metadata' 53603 }, false).track; 53604 _this.decrypter_ = new Decrypter$1(); 53605 _this.inbandTextTracks_ = {}; 53606 var segmentLoaderSettings = { 53607 hls: _this.hls_, 53608 mediaSource: _this.mediaSource, 53609 currentTime: _this.tech_.currentTime.bind(_this.tech_), 53610 seekable: function seekable$$1() { 53611 return _this.seekable(); 53612 }, 53613 seeking: function seeking() { 53614 return _this.tech_.seeking(); 53615 }, 53616 duration: function duration$$1() { 53617 return _this.mediaSource.duration; 53618 }, 53619 hasPlayed: function hasPlayed() { 53620 return _this.hasPlayed_(); 53621 }, 53622 goalBufferLength: function goalBufferLength() { 53623 return _this.goalBufferLength(); 53624 }, 53625 bandwidth: bandwidth, 53626 syncController: _this.syncController_, 53627 decrypter: _this.decrypter_, 53628 sourceType: _this.sourceType_, 53629 inbandTextTracks: _this.inbandTextTracks_, 53630 cacheEncryptionKeys: cacheEncryptionKeys 53631 }; 53632 _this.masterPlaylistLoader_ = _this.sourceType_ === 'dash' ? new DashPlaylistLoader(url, _this.hls_, _this.requestOptions_) : new PlaylistLoader(url, _this.hls_, _this.requestOptions_); 53633 53634 _this.setupMasterPlaylistLoaderListeners_(); // setup segment loaders 53635 // combined audio/video or just video when alternate audio track is selected 53636 53637 53638 _this.mainSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 53639 segmentMetadataTrack: _this.segmentMetadataTrack_, 53640 loaderType: 'main' 53641 }), options); // alternate audio track 53642 53643 _this.audioSegmentLoader_ = new SegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 53644 loaderType: 'audio' 53645 }), options); 53646 _this.subtitleSegmentLoader_ = new VTTSegmentLoader(videojs$1.mergeOptions(segmentLoaderSettings, { 53647 loaderType: 'vtt' 53648 }), options); 53649 53650 _this.setupSegmentLoaderListeners_(); // Create SegmentLoader stat-getters 53651 53652 53653 loaderStats.forEach(function (stat) { 53654 _this[stat + '_'] = sumLoaderStat.bind(_this, stat); 53655 }); 53656 _this.logger_ = logger('MPC'); 53657 53658 _this.masterPlaylistLoader_.load(); 53659 53660 return _this; 53661 } 53662 /** 53663 * Register event handlers on the master playlist loader. A helper 53664 * function for construction time. 53665 * 53666 * @private 53667 */ 53668 53669 53670 createClass$1(MasterPlaylistController, [{ 53671 key: 'setupMasterPlaylistLoaderListeners_', 53672 value: function setupMasterPlaylistLoaderListeners_() { 53673 var _this2 = this; 53674 53675 this.masterPlaylistLoader_.on('loadedmetadata', function () { 53676 var media = _this2.masterPlaylistLoader_.media(); 53677 53678 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 53679 // timeout the request. 53680 53681 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 53682 _this2.requestOptions_.timeout = 0; 53683 } else { 53684 _this2.requestOptions_.timeout = requestTimeout; 53685 } // if this isn't a live video and preload permits, start 53686 // downloading segments 53687 53688 53689 if (media.endList && _this2.tech_.preload() !== 'none') { 53690 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 53691 53692 _this2.mainSegmentLoader_.load(); 53693 } 53694 53695 setupMediaGroups({ 53696 sourceType: _this2.sourceType_, 53697 segmentLoaders: { 53698 AUDIO: _this2.audioSegmentLoader_, 53699 SUBTITLES: _this2.subtitleSegmentLoader_, 53700 main: _this2.mainSegmentLoader_ 53701 }, 53702 tech: _this2.tech_, 53703 requestOptions: _this2.requestOptions_, 53704 masterPlaylistLoader: _this2.masterPlaylistLoader_, 53705 hls: _this2.hls_, 53706 master: _this2.master(), 53707 mediaTypes: _this2.mediaTypes_, 53708 blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2) 53709 }); 53710 53711 _this2.triggerPresenceUsage_(_this2.master(), media); 53712 53713 try { 53714 _this2.setupSourceBuffers_(); 53715 } catch (e) { 53716 videojs$1.log.warn('Failed to create SourceBuffers', e); 53717 return _this2.mediaSource.endOfStream('decode'); 53718 } 53719 53720 _this2.setupFirstPlay(); 53721 53722 if (!_this2.mediaTypes_.AUDIO.activePlaylistLoader || _this2.mediaTypes_.AUDIO.activePlaylistLoader.media()) { 53723 _this2.trigger('selectedinitialmedia'); 53724 } else { 53725 // We must wait for the active audio playlist loader to 53726 // finish setting up before triggering this event so the 53727 // representations API and EME setup is correct 53728 _this2.mediaTypes_.AUDIO.activePlaylistLoader.one('loadedmetadata', function () { 53729 _this2.trigger('selectedinitialmedia'); 53730 }); 53731 } 53732 }); 53733 this.masterPlaylistLoader_.on('loadedplaylist', function () { 53734 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 53735 53736 if (!updatedPlaylist) { 53737 // blacklist any variants that are not supported by the browser before selecting 53738 // an initial media as the playlist selectors do not consider browser support 53739 _this2.excludeUnsupportedVariants_(); 53740 53741 var selectedMedia = void 0; 53742 53743 if (_this2.enableLowInitialPlaylist) { 53744 selectedMedia = _this2.selectInitialPlaylist(); 53745 } 53746 53747 if (!selectedMedia) { 53748 selectedMedia = _this2.selectPlaylist(); 53749 } 53750 53751 _this2.initialMedia_ = selectedMedia; 53752 53753 _this2.masterPlaylistLoader_.media(_this2.initialMedia_); 53754 53755 return; 53756 } 53757 53758 if (_this2.useCueTags_) { 53759 _this2.updateAdCues_(updatedPlaylist); 53760 } // TODO: Create a new event on the PlaylistLoader that signals 53761 // that the segments have changed in some way and use that to 53762 // update the SegmentLoader instead of doing it twice here and 53763 // on `mediachange` 53764 53765 53766 _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_); 53767 53768 _this2.updateDuration(); // If the player isn't paused, ensure that the segment loader is running, 53769 // as it is possible that it was temporarily stopped while waiting for 53770 // a playlist (e.g., in case the playlist errored and we re-requested it). 53771 53772 53773 if (!_this2.tech_.paused()) { 53774 _this2.mainSegmentLoader_.load(); 53775 53776 if (_this2.audioSegmentLoader_) { 53777 _this2.audioSegmentLoader_.load(); 53778 } 53779 } 53780 53781 if (!updatedPlaylist.endList) { 53782 var addSeekableRange = function addSeekableRange() { 53783 var seekable$$1 = _this2.seekable(); 53784 53785 if (seekable$$1.length !== 0) { 53786 _this2.mediaSource.addSeekableRange_(seekable$$1.start(0), seekable$$1.end(0)); 53787 } 53788 }; 53789 53790 if (_this2.duration() !== Infinity) { 53791 var onDurationchange = function onDurationchange() { 53792 if (_this2.duration() === Infinity) { 53793 addSeekableRange(); 53794 } else { 53795 _this2.tech_.one('durationchange', onDurationchange); 53796 } 53797 }; 53798 53799 _this2.tech_.one('durationchange', onDurationchange); 53800 } else { 53801 addSeekableRange(); 53802 } 53803 } 53804 }); 53805 this.masterPlaylistLoader_.on('error', function () { 53806 _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error); 53807 }); 53808 this.masterPlaylistLoader_.on('mediachanging', function () { 53809 _this2.mainSegmentLoader_.abort(); 53810 53811 _this2.mainSegmentLoader_.pause(); 53812 }); 53813 this.masterPlaylistLoader_.on('mediachange', function () { 53814 var media = _this2.masterPlaylistLoader_.media(); 53815 53816 var requestTimeout = media.targetDuration * 1.5 * 1000; // If we don't have any more available playlists, we don't want to 53817 // timeout the request. 53818 53819 if (isLowestEnabledRendition(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) { 53820 _this2.requestOptions_.timeout = 0; 53821 } else { 53822 _this2.requestOptions_.timeout = requestTimeout; 53823 } // TODO: Create a new event on the PlaylistLoader that signals 53824 // that the segments have changed in some way and use that to 53825 // update the SegmentLoader instead of doing it twice here and 53826 // on `loadedplaylist` 53827 53828 53829 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 53830 53831 _this2.mainSegmentLoader_.load(); 53832 53833 _this2.tech_.trigger({ 53834 type: 'mediachange', 53835 bubbles: true 53836 }); 53837 }); 53838 this.masterPlaylistLoader_.on('playlistunchanged', function () { 53839 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 53840 53841 var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist); 53842 53843 if (playlistOutdated) { 53844 // Playlist has stopped updating and we're stuck at its end. Try to 53845 // blacklist it and switch to another playlist in the hope that that 53846 // one is updating (and give the player a chance to re-adjust to the 53847 // safe live point). 53848 _this2.blacklistCurrentPlaylist({ 53849 message: 'Playlist no longer updating.' 53850 }); // useful for monitoring QoS 53851 53852 53853 _this2.tech_.trigger('playliststuck'); 53854 } 53855 }); 53856 this.masterPlaylistLoader_.on('renditiondisabled', function () { 53857 _this2.tech_.trigger({ 53858 type: 'usage', 53859 name: 'hls-rendition-disabled' 53860 }); 53861 }); 53862 this.masterPlaylistLoader_.on('renditionenabled', function () { 53863 _this2.tech_.trigger({ 53864 type: 'usage', 53865 name: 'hls-rendition-enabled' 53866 }); 53867 }); 53868 } 53869 /** 53870 * A helper function for triggerring presence usage events once per source 53871 * 53872 * @private 53873 */ 53874 53875 }, { 53876 key: 'triggerPresenceUsage_', 53877 value: function triggerPresenceUsage_(master, media) { 53878 var mediaGroups = master.mediaGroups || {}; 53879 var defaultDemuxed = true; 53880 var audioGroupKeys = Object.keys(mediaGroups.AUDIO); 53881 53882 for (var mediaGroup in mediaGroups.AUDIO) { 53883 for (var label in mediaGroups.AUDIO[mediaGroup]) { 53884 var properties = mediaGroups.AUDIO[mediaGroup][label]; 53885 53886 if (!properties.uri) { 53887 defaultDemuxed = false; 53888 } 53889 } 53890 } 53891 53892 if (defaultDemuxed) { 53893 this.tech_.trigger({ 53894 type: 'usage', 53895 name: 'hls-demuxed' 53896 }); 53897 } 53898 53899 if (Object.keys(mediaGroups.SUBTITLES).length) { 53900 this.tech_.trigger({ 53901 type: 'usage', 53902 name: 'hls-webvtt' 53903 }); 53904 } 53905 53906 if (Hls.Playlist.isAes(media)) { 53907 this.tech_.trigger({ 53908 type: 'usage', 53909 name: 'hls-aes' 53910 }); 53911 } 53912 53913 if (Hls.Playlist.isFmp4(media)) { 53914 this.tech_.trigger({ 53915 type: 'usage', 53916 name: 'hls-fmp4' 53917 }); 53918 } 53919 53920 if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) { 53921 this.tech_.trigger({ 53922 type: 'usage', 53923 name: 'hls-alternate-audio' 53924 }); 53925 } 53926 53927 if (this.useCueTags_) { 53928 this.tech_.trigger({ 53929 type: 'usage', 53930 name: 'hls-playlist-cue-tags' 53931 }); 53932 } 53933 } 53934 /** 53935 * Register event handlers on the segment loaders. A helper function 53936 * for construction time. 53937 * 53938 * @private 53939 */ 53940 53941 }, { 53942 key: 'setupSegmentLoaderListeners_', 53943 value: function setupSegmentLoaderListeners_() { 53944 var _this3 = this; 53945 53946 this.mainSegmentLoader_.on('bandwidthupdate', function () { 53947 var nextPlaylist = _this3.selectPlaylist(); 53948 53949 var currentPlaylist = _this3.masterPlaylistLoader_.media(); 53950 53951 var buffered = _this3.tech_.buffered(); 53952 53953 var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0; 53954 53955 var bufferLowWaterLine = _this3.bufferLowWaterLine(); // If the playlist is live, then we want to not take low water line into account. 53956 // This is because in LIVE, the player plays 3 segments from the end of the 53957 // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble 53958 // in those segments, a viewer will never experience a rendition upswitch. 53959 53960 53961 if (!currentPlaylist.endList || // For the same reason as LIVE, we ignore the low water line when the VOD 53962 // duration is below the max potential low water line 53963 _this3.duration() < Config.MAX_BUFFER_LOW_WATER_LINE || // we want to switch down to lower resolutions quickly to continue playback, but 53964 nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || // ensure we have some buffer before we switch up to prevent us running out of 53965 // buffer while loading a higher rendition. 53966 forwardBuffer >= bufferLowWaterLine) { 53967 _this3.masterPlaylistLoader_.media(nextPlaylist); 53968 } 53969 53970 _this3.tech_.trigger('bandwidthupdate'); 53971 }); 53972 this.mainSegmentLoader_.on('progress', function () { 53973 _this3.trigger('progress'); 53974 }); 53975 this.mainSegmentLoader_.on('error', function () { 53976 _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error()); 53977 }); 53978 this.mainSegmentLoader_.on('syncinfoupdate', function () { 53979 _this3.onSyncInfoUpdate_(); 53980 }); 53981 this.mainSegmentLoader_.on('timestampoffset', function () { 53982 _this3.tech_.trigger({ 53983 type: 'usage', 53984 name: 'hls-timestamp-offset' 53985 }); 53986 }); 53987 this.audioSegmentLoader_.on('syncinfoupdate', function () { 53988 _this3.onSyncInfoUpdate_(); 53989 }); 53990 this.mainSegmentLoader_.on('ended', function () { 53991 _this3.onEndOfStream(); 53992 }); 53993 this.mainSegmentLoader_.on('earlyabort', function () { 53994 _this3.blacklistCurrentPlaylist({ 53995 message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.' 53996 }, ABORT_EARLY_BLACKLIST_SECONDS); 53997 }); 53998 this.mainSegmentLoader_.on('reseteverything', function () { 53999 // If playing an MTS stream, a videojs.MediaSource is listening for 54000 // hls-reset to reset caption parsing state in the transmuxer 54001 _this3.tech_.trigger('hls-reset'); 54002 }); 54003 this.mainSegmentLoader_.on('segmenttimemapping', function (event) { 54004 // If playing an MTS stream in html, a videojs.MediaSource is listening for 54005 // hls-segment-time-mapping update its internal mapping of stream to display time 54006 _this3.tech_.trigger({ 54007 type: 'hls-segment-time-mapping', 54008 mapping: event.mapping 54009 }); 54010 }); 54011 this.audioSegmentLoader_.on('ended', function () { 54012 _this3.onEndOfStream(); 54013 }); 54014 } 54015 }, { 54016 key: 'mediaSecondsLoaded_', 54017 value: function mediaSecondsLoaded_() { 54018 return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded); 54019 } 54020 /** 54021 * Call load on our SegmentLoaders 54022 */ 54023 54024 }, { 54025 key: 'load', 54026 value: function load() { 54027 this.mainSegmentLoader_.load(); 54028 54029 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 54030 this.audioSegmentLoader_.load(); 54031 } 54032 54033 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 54034 this.subtitleSegmentLoader_.load(); 54035 } 54036 } 54037 /** 54038 * Re-tune playback quality level for the current player 54039 * conditions without performing destructive actions, like 54040 * removing already buffered content 54041 * 54042 * @private 54043 */ 54044 54045 }, { 54046 key: 'smoothQualityChange_', 54047 value: function smoothQualityChange_() { 54048 var media = this.selectPlaylist(); 54049 54050 if (media !== this.masterPlaylistLoader_.media()) { 54051 this.masterPlaylistLoader_.media(media); 54052 this.mainSegmentLoader_.resetLoader(); // don't need to reset audio as it is reset when media changes 54053 } 54054 } 54055 /** 54056 * Re-tune playback quality level for the current player 54057 * conditions. This method will perform destructive actions like removing 54058 * already buffered content in order to readjust the currently active 54059 * playlist quickly. This is good for manual quality changes 54060 * 54061 * @private 54062 */ 54063 54064 }, { 54065 key: 'fastQualityChange_', 54066 value: function fastQualityChange_() { 54067 var _this4 = this; 54068 54069 var media = this.selectPlaylist(); 54070 54071 if (media === this.masterPlaylistLoader_.media()) { 54072 return; 54073 } 54074 54075 this.masterPlaylistLoader_.media(media); // Delete all buffered data to allow an immediate quality switch, then seek to give 54076 // the browser a kick to remove any cached frames from the previous rendtion (.04 seconds 54077 // ahead is roughly the minimum that will accomplish this across a variety of content 54078 // in IE and Edge, but seeking in place is sufficient on all other browsers) 54079 // Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/ 54080 // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904 54081 54082 this.mainSegmentLoader_.resetEverything(function () { 54083 // Since this is not a typical seek, we avoid the seekTo method which can cause segments 54084 // from the previously enabled rendition to load before the new playlist has finished loading 54085 if (videojs$1.browser.IE_VERSION || videojs$1.browser.IS_EDGE) { 54086 _this4.tech_.setCurrentTime(_this4.tech_.currentTime() + 0.04); 54087 } else { 54088 _this4.tech_.setCurrentTime(_this4.tech_.currentTime()); 54089 } 54090 }); // don't need to reset audio as it is reset when media changes 54091 } 54092 /** 54093 * Begin playback. 54094 */ 54095 54096 }, { 54097 key: 'play', 54098 value: function play() { 54099 if (this.setupFirstPlay()) { 54100 return; 54101 } 54102 54103 if (this.tech_.ended()) { 54104 this.seekTo_(0); 54105 } 54106 54107 if (this.hasPlayed_()) { 54108 this.load(); 54109 } 54110 54111 var seekable$$1 = this.tech_.seekable(); // if the viewer has paused and we fell out of the live window, 54112 // seek forward to the live point 54113 54114 if (this.tech_.duration() === Infinity) { 54115 if (this.tech_.currentTime() < seekable$$1.start(0)) { 54116 return this.seekTo_(seekable$$1.end(seekable$$1.length - 1)); 54117 } 54118 } 54119 } 54120 /** 54121 * Seek to the latest media position if this is a live video and the 54122 * player and video are loaded and initialized. 54123 */ 54124 54125 }, { 54126 key: 'setupFirstPlay', 54127 value: function setupFirstPlay() { 54128 var _this5 = this; 54129 54130 var media = this.masterPlaylistLoader_.media(); // Check that everything is ready to begin buffering for the first call to play 54131 // If 1) there is no active media 54132 // 2) the player is paused 54133 // 3) the first play has already been setup 54134 // then exit early 54135 54136 if (!media || this.tech_.paused() || this.hasPlayed_()) { 54137 return false; 54138 } // when the video is a live stream 54139 54140 54141 if (!media.endList) { 54142 var seekable$$1 = this.seekable(); 54143 54144 if (!seekable$$1.length) { 54145 // without a seekable range, the player cannot seek to begin buffering at the live 54146 // point 54147 return false; 54148 } 54149 54150 if (videojs$1.browser.IE_VERSION && this.tech_.readyState() === 0) { 54151 // IE11 throws an InvalidStateError if you try to set currentTime while the 54152 // readyState is 0, so it must be delayed until the tech fires loadedmetadata. 54153 this.tech_.one('loadedmetadata', function () { 54154 _this5.trigger('firstplay'); 54155 54156 _this5.seekTo_(seekable$$1.end(0)); 54157 54158 _this5.hasPlayed_ = function () { 54159 return true; 54160 }; 54161 }); 54162 return false; 54163 } // trigger firstplay to inform the source handler to ignore the next seek event 54164 54165 54166 this.trigger('firstplay'); // seek to the live point 54167 54168 this.seekTo_(seekable$$1.end(0)); 54169 } 54170 54171 this.hasPlayed_ = function () { 54172 return true; 54173 }; // we can begin loading now that everything is ready 54174 54175 54176 this.load(); 54177 return true; 54178 } 54179 /** 54180 * handle the sourceopen event on the MediaSource 54181 * 54182 * @private 54183 */ 54184 54185 }, { 54186 key: 'handleSourceOpen_', 54187 value: function handleSourceOpen_() { 54188 // Only attempt to create the source buffer if none already exist. 54189 // handleSourceOpen is also called when we are "re-opening" a source buffer 54190 // after `endOfStream` has been called (in response to a seek for instance) 54191 try { 54192 this.setupSourceBuffers_(); 54193 } catch (e) { 54194 videojs$1.log.warn('Failed to create Source Buffers', e); 54195 return this.mediaSource.endOfStream('decode'); 54196 } // if autoplay is enabled, begin playback. This is duplicative of 54197 // code in video.js but is required because play() must be invoked 54198 // *after* the media source has opened. 54199 54200 54201 if (this.tech_.autoplay()) { 54202 var playPromise = this.tech_.play(); // Catch/silence error when a pause interrupts a play request 54203 // on browsers which return a promise 54204 54205 if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') { 54206 playPromise.then(null, function (e) {}); 54207 } 54208 } 54209 54210 this.trigger('sourceopen'); 54211 } 54212 /** 54213 * Calls endOfStream on the media source when all active stream types have called 54214 * endOfStream 54215 * 54216 * @param {string} streamType 54217 * Stream type of the segment loader that called endOfStream 54218 * @private 54219 */ 54220 54221 }, { 54222 key: 'onEndOfStream', 54223 value: function onEndOfStream() { 54224 var isEndOfStream = this.mainSegmentLoader_.ended_; 54225 54226 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 54227 // if the audio playlist loader exists, then alternate audio is active 54228 if (!this.mainSegmentLoader_.startingMedia_ || this.mainSegmentLoader_.startingMedia_.containsVideo) { 54229 // if we do not know if the main segment loader contains video yet or if we 54230 // definitively know the main segment loader contains video, then we need to wait 54231 // for both main and audio segment loaders to call endOfStream 54232 isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_; 54233 } else { 54234 // otherwise just rely on the audio loader 54235 isEndOfStream = this.audioSegmentLoader_.ended_; 54236 } 54237 } 54238 54239 if (!isEndOfStream) { 54240 return; 54241 } 54242 54243 this.logger_('calling mediaSource.endOfStream()'); // on chrome calling endOfStream can sometimes cause an exception, 54244 // even when the media source is in a valid state. 54245 54246 try { 54247 this.mediaSource.endOfStream(); 54248 } catch (e) { 54249 videojs$1.log.warn('Failed to call media source endOfStream', e); 54250 } 54251 } 54252 /** 54253 * Check if a playlist has stopped being updated 54254 * @param {Object} playlist the media playlist object 54255 * @return {boolean} whether the playlist has stopped being updated or not 54256 */ 54257 54258 }, { 54259 key: 'stuckAtPlaylistEnd_', 54260 value: function stuckAtPlaylistEnd_(playlist) { 54261 var seekable$$1 = this.seekable(); 54262 54263 if (!seekable$$1.length) { 54264 // playlist doesn't have enough information to determine whether we are stuck 54265 return false; 54266 } 54267 54268 var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration); 54269 54270 if (expired === null) { 54271 return false; 54272 } // does not use the safe live end to calculate playlist end, since we 54273 // don't want to say we are stuck while there is still content 54274 54275 54276 var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired); 54277 var currentTime = this.tech_.currentTime(); 54278 var buffered = this.tech_.buffered(); 54279 54280 if (!buffered.length) { 54281 // return true if the playhead reached the absolute end of the playlist 54282 return absolutePlaylistEnd - currentTime <= SAFE_TIME_DELTA; 54283 } 54284 54285 var bufferedEnd = buffered.end(buffered.length - 1); // return true if there is too little buffer left and buffer has reached absolute 54286 // end of playlist 54287 54288 return bufferedEnd - currentTime <= SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= SAFE_TIME_DELTA; 54289 } 54290 /** 54291 * Blacklists a playlist when an error occurs for a set amount of time 54292 * making it unavailable for selection by the rendition selection algorithm 54293 * and then forces a new playlist (rendition) selection. 54294 * 54295 * @param {Object=} error an optional error that may include the playlist 54296 * to blacklist 54297 * @param {Number=} blacklistDuration an optional number of seconds to blacklist the 54298 * playlist 54299 */ 54300 54301 }, { 54302 key: 'blacklistCurrentPlaylist', 54303 value: function blacklistCurrentPlaylist() { 54304 var error = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 54305 var blacklistDuration = arguments[1]; 54306 var currentPlaylist = void 0; 54307 var nextPlaylist = void 0; // If the `error` was generated by the playlist loader, it will contain 54308 // the playlist we were trying to load (but failed) and that should be 54309 // blacklisted instead of the currently selected playlist which is likely 54310 // out-of-date in this scenario 54311 54312 currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 54313 blacklistDuration = blacklistDuration || error.blacklistDuration || this.blackl
54313istDuration; // If there is no current playlist, then an error occurred while we were 54314 // trying to load the master OR while we were disposing of the tech 54315 54316 if (!currentPlaylist) { 54317 this.error = error; 54318 54319 try { 54320 return this.mediaSource.endOfStream('network'); 54321 } catch (e) { 54322 return this.trigger('error'); 54323 } 54324 } 54325 54326 var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(isEnabled).length === 1; 54327 var playlists = this.masterPlaylistLoader_.master.playlists; 54328 54329 if (playlists.length === 1) { 54330 // Never blacklisting this playlist because it's the only playlist 54331 videojs$1.log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the only playlist.'); 54332 this.tech_.trigger('retryplaylist'); 54333 return this.masterPlaylistLoader_.load(isFinalRendition); 54334 } 54335 54336 if (isFinalRendition) { 54337 // Since we're on the final non-blacklisted playlist, and we're about to blacklist 54338 // it, instead of erring the player or retrying this playlist, clear out the current 54339 // blacklist. This allows other playlists to be attempted in case any have been 54340 // fixed. 54341 videojs$1.log.warn('Removing all playlists from the blacklist because the last ' + 'rendition is about to be blacklisted.');
vendor: 11,849 bytes, lines 54342-54684
54342 playlists.forEach(function (playlist) { 54343 if (playlist.excludeUntil !== Infinity) { 54344 delete playlist.excludeUntil; 54345 } 54346 }); // Technically we are retrying a playlist, in that we are simply retrying a previous 54347 // playlist. This is needed for users relying on the retryplaylist event to catch a 54348 // case where the player might be stuck and looping through "dead" playlists. 54349 54350 this.tech_.trigger('retryplaylist'); 54351 } // Blacklist this playlist 54352 54353 54354 currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000; 54355 this.tech_.trigger('blacklistplaylist'); 54356 this.tech_.trigger({ 54357 type: 'usage', 54358 name: 'hls-rendition-blacklisted' 54359 }); // Select a new playlist 54360 54361 nextPlaylist = this.selectPlaylist(); 54362 videojs$1.log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.'); 54363 return this.masterPlaylistLoader_.media(nextPlaylist, isFinalRendition); 54364 } 54365 /** 54366 * Pause all segment loaders 54367 */ 54368 54369 }, { 54370 key: 'pauseLoading', 54371 value: function pauseLoading() { 54372 this.mainSegmentLoader_.pause(); 54373 54374 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 54375 this.audioSegmentLoader_.pause(); 54376 } 54377 54378 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 54379 this.subtitleSegmentLoader_.pause(); 54380 } 54381 } 54382 /** 54383 * set the current time on all segment loaders 54384 * 54385 * @param {TimeRange} currentTime the current time to set 54386 * @return {TimeRange} the current time 54387 */ 54388 54389 }, { 54390 key: 'setCurrentTime', 54391 value: function setCurrentTime(currentTime) { 54392 var buffered = findRange(this.tech_.buffered(), currentTime); 54393 54394 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 54395 // return immediately if the metadata is not ready yet 54396 return 0; 54397 } // it's clearly an edge-case but don't thrown an error if asked to 54398 // seek within an empty playlist 54399 54400 54401 if (!this.masterPlaylistLoader_.media().segments) { 54402 return 0; 54403 } // In flash playback, the segment loaders should be reset on every seek, even 54404 // in buffer seeks. If the seek location is already buffered, continue buffering as 54405 // usual 54406 // TODO: redo this comment 54407 54408 54409 if (buffered && buffered.length) { 54410 return currentTime; 54411 } // cancel outstanding requests so we begin buffering at the new 54412 // location 54413 54414 54415 this.mainSegmentLoader_.resetEverything(); 54416 this.mainSegmentLoader_.abort(); 54417 54418 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 54419 this.audioSegmentLoader_.resetEverything(); 54420 this.audioSegmentLoader_.abort(); 54421 } 54422 54423 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 54424 this.subtitleSegmentLoader_.resetEverything(); 54425 this.subtitleSegmentLoader_.abort(); 54426 } // start segment loader loading in case they are paused 54427 54428 54429 this.load(); 54430 } 54431 /** 54432 * get the current duration 54433 * 54434 * @return {TimeRange} the duration 54435 */ 54436 54437 }, { 54438 key: 'duration', 54439 value: function duration$$1() { 54440 if (!this.masterPlaylistLoader_) { 54441 return 0; 54442 } 54443 54444 if (this.mediaSource) { 54445 return this.mediaSource.duration; 54446 } 54447 54448 return Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 54449 } 54450 /** 54451 * check the seekable range 54452 * 54453 * @return {TimeRange} the seekable range 54454 */ 54455 54456 }, { 54457 key: 'seekable', 54458 value: function seekable$$1() { 54459 return this.seekable_; 54460 } 54461 }, { 54462 key: 'onSyncInfoUpdate_', 54463 value: function onSyncInfoUpdate_() { 54464 var mainSeekable = void 0; 54465 var audioSeekable = void 0; 54466 54467 if (!this.masterPlaylistLoader_) { 54468 return; 54469 } 54470 54471 var media = this.masterPlaylistLoader_.media(); 54472 54473 if (!media) { 54474 return; 54475 } 54476 54477 var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 54478 54479 if (expired === null) { 54480 // not enough information to update seekable 54481 return; 54482 } 54483 54484 mainSeekable = Hls.Playlist.seekable(media, expired); 54485 54486 if (mainSeekable.length === 0) { 54487 return; 54488 } 54489 54490 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 54491 media = this.mediaTypes_.AUDIO.activePlaylistLoader.media(); 54492 expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 54493 54494 if (expired === null) { 54495 return; 54496 } 54497 54498 audioSeekable = Hls.Playlist.seekable(media, expired); 54499 54500 if (audioSeekable.length === 0) { 54501 return; 54502 } 54503 } 54504 54505 var oldEnd = void 0; 54506 var oldStart = void 0; 54507 54508 if (this.seekable_ && this.seekable_.length) { 54509 oldEnd = this.seekable_.end(0); 54510 oldStart = this.seekable_.start(0); 54511 } 54512 54513 if (!audioSeekable) { 54514 // seekable has been calculated based on buffering video data so it 54515 // can be returned directly 54516 this.seekable_ = mainSeekable; 54517 } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) { 54518 // seekables are pretty far off, rely on main 54519 this.seekable_ = mainSeekable; 54520 } else { 54521 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)]]); 54522 } // seekable is the same as last time 54523 54524 54525 if (this.seekable_ && this.seekable_.length) { 54526 if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) { 54527 return; 54528 } 54529 } 54530 54531 this.logger_('seekable updated [' + printableRange(this.seekable_) + ']'); 54532 this.tech_.trigger('seekablechanged'); 54533 } 54534 /** 54535 * Update the player duration 54536 */ 54537 54538 }, { 54539 key: 'updateDuration', 54540 value: function updateDuration() { 54541 var _this6 = this; 54542 54543 var oldDuration = this.mediaSource.duration; 54544 var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 54545 var buffered = this.tech_.buffered(); 54546 54547 var setDuration = function setDuration() { 54548 // on firefox setting the duration may sometimes cause an exception 54549 // even if the media source is open and source buffers are not 54550 // updating, something about the media source being in an invalid state. 54551 _this6.logger_('Setting duration from ' + _this6.mediaSource.duration + ' => ' + newDuration); 54552 54553 try { 54554 _this6.mediaSource.duration = newDuration; 54555 } catch (e) { 54556 videojs$1.log.warn('Failed to set media source duration', e); 54557 } 54558 54559 _this6.tech_.trigger('durationchange'); 54560 54561 _this6.mediaSource.removeEventListener('sourceopen', setDuration); 54562 }; 54563 54564 if (buffered.length > 0) { 54565 newDuration = Math.max(newDuration, buffered.end(buffered.length - 1)); 54566 } // if the duration has changed, invalidate the cached value 54567 54568 54569 if (oldDuration !== newDuration) { 54570 // update the duration 54571 if (this.mediaSource.readyState !== 'open') { 54572 this.mediaSource.addEventListener('sourceopen', setDuration); 54573 } else { 54574 setDuration(); 54575 } 54576 } 54577 } 54578 /** 54579 * dispose of the MasterPlaylistController and everything 54580 * that it controls 54581 */ 54582 54583 }, { 54584 key: 'dispose', 54585 value: function dispose() { 54586 var _this7 = this; 54587 54588 this.decrypter_.terminate(); 54589 this.masterPlaylistLoader_.dispose(); 54590 this.mainSegmentLoader_.dispose(); 54591 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 54592 var groups = _this7.mediaTypes_[type].groups; 54593 54594 for (var id in groups) { 54595 groups[id].forEach(function (group) { 54596 if (group.playlistLoader) { 54597 group.playlistLoader.dispose(); 54598 } 54599 }); 54600 } 54601 }); 54602 this.audioSegmentLoader_.dispose(); 54603 this.subtitleSegmentLoader_.dispose(); 54604 } 54605 /** 54606 * return the master playlist object if we have one 54607 * 54608 * @return {Object} the master playlist object that we parsed 54609 */ 54610 54611 }, { 54612 key: 'master', 54613 value: function master() { 54614 return this.masterPlaylistLoader_.master; 54615 } 54616 /** 54617 * return the currently selected playlist 54618 * 54619 * @return {Object} the currently selected playlist object that we parsed 54620 */ 54621 54622 }, { 54623 key: 'media', 54624 value: function media() { 54625 // playlist loader will not return media if it has not been fully loaded 54626 return this.masterPlaylistLoader_.media() || this.initialMedia_; 54627 } 54628 /** 54629 * setup our internal source buffers on our segment Loaders 54630 * 54631 * @private 54632 */ 54633 54634 }, { 54635 key: 'setupSourceBuffers_', 54636 value: function setupSourceBuffers_() { 54637 var media = this.masterPlaylistLoader_.media(); 54638 var mimeTypes = void 0; // wait until a media playlist is available and the Media Source is 54639 // attached 54640 54641 if (!media || this.mediaSource.readyState !== 'open') { 54642 return; 54643 } 54644 54645 mimeTypes = mimeTypesForPlaylist(this.masterPlaylistLoader_.master, media); 54646 54647 if (mimeTypes.length < 1) { 54648 this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri; 54649 return this.mediaSource.endOfStream('decode'); 54650 } 54651 54652 this.configureLoaderMimeTypes_(mimeTypes); // exclude any incompatible variant streams from future playlist 54653 // selection 54654 54655 this.excludeIncompatibleVariants_(media); 54656 } 54657 }, { 54658 key: 'configureLoaderMimeTypes_', 54659 value: function configureLoaderMimeTypes_(mimeTypes) { 54660 // If the content is demuxed, we can't start appending segments to a source buffer 54661 // until both source buffers are set up, or else the browser may not let us add the 54662 // second source buffer (it will assume we are playing either audio only or video 54663 // only). 54664 var sourceBufferEmitter = // If there is more than one mime type 54665 mimeTypes.length > 1 && // and the first mime type does not have muxed video and audio 54666 mimeTypes[0].indexOf(',') === -1 && // and the two mime types are different (they can be the same in the case of audio 54667 // only with alternate audio) 54668 mimeTypes[0] !== mimeTypes[1] ? // then we want to wait on the second source buffer 54669 new videojs$1.EventTarget() : // otherwise there is no need to wait as the content is either audio only, 54670 // video only, or muxed content. 54671 null; 54672 this.mainSegmentLoader_.mimeType(mimeTypes[0], sourceBufferEmitter); 54673 54674 if (mimeTypes[1]) { 54675 this.audioSegmentLoader_.mimeType(mimeTypes[1], sourceBufferEmitter); 54676 } 54677 } 54678 /** 54679 * Blacklists playlists with codecs that are unsupported by the browser. 54680 */ 54681 54682 }, { 54683 key: 'excludeUnsupportedVariants_', 54684 value: function excludeUnsupportedVariants_() {
54685 this.master().playlists.forEach(function (variant) { 54686 if (variant.attributes.CODECS && window$1.MediaSource && window$1.MediaSource.isTypeSupported && !window$1.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs(variant.attributes.CODECS) + '"')) { 54687 variant.excludeUntil = Infinity; 54688 } 54689 }); 54690 } 54691 /** 54692 * Blacklist playlists that are known to be codec or 54693 * stream-incompatible with the SourceBuffer configuration. For 54694 * instance, Media Source Extensions would cause the video element to 54695 * stall waiting for video data if you switched from a variant with 54696 * video and audio to an audio-only one. 54697 * 54698 * @param {Object} media a media playlist compatible with the current 54699 * set of SourceBuffers. Variants in the current master playlist that 54700 * do not appear to have compatible codec or stream configurations 54701 * will be excluded from the default playlist selection algorithm 54702 * indefinitely. 54703 * @private 54704 */ 54705 54706 }, { 54707 key: 'excludeIncompatibleVariants_', 54708 value: function excludeIncompatibleVariants_(media) { 54709 var codecCount = 2; 54710 var videoCodec = null; 54711 var codecs = void 0; 54712 54713 if (media.attributes.CODECS) { 54714 codecs = parseCodecs(media.attributes.CODECS); 54715 videoCodec = codecs.videoCodec; 54716 codecCount = codecs.codecCount; 54717 } 54718
vendor: 12,940 bytes, lines 54719-55089
54719 this.master().playlists.forEach(function (variant) { 54720 var variantCodecs = { 54721 codecCount: 2, 54722 videoCodec: null 54723 }; 54724 54725 if (variant.attributes.CODECS) { 54726 variantCodecs = parseCodecs(variant.attributes.CODECS); 54727 } // if the streams differ in the presence or absence of audio or 54728 // video, they are incompatible 54729 54730 54731 if (variantCodecs.codecCount !== codecCount) { 54732 variant.excludeUntil = Infinity; 54733 } // if h.264 is specified on the current playlist, some flavor of 54734 // it must be specified on all compatible variants 54735 54736 54737 if (variantCodecs.videoCodec !== videoCodec) { 54738 variant.excludeUntil = Infinity; 54739 } 54740 }); 54741 } 54742 }, { 54743 key: 'updateAdCues_', 54744 value: function updateAdCues_(media) { 54745 var offset = 0; 54746 var seekable$$1 = this.seekable(); 54747 54748 if (seekable$$1.length) { 54749 offset = seekable$$1.start(0); 54750 } 54751 54752 updateAdCues(media, this.cueTagsTrack_, offset); 54753 } 54754 /** 54755 * Calculates the desired forward buffer length based on current time 54756 * 54757 * @return {Number} Desired forward buffer length in seconds 54758 */ 54759 54760 }, { 54761 key: 'goalBufferLength', 54762 value: function goalBufferLength() { 54763 var currentTime = this.tech_.currentTime(); 54764 var initial = Config.GOAL_BUFFER_LENGTH; 54765 var rate = Config.GOAL_BUFFER_LENGTH_RATE; 54766 var max = Math.max(initial, Config.MAX_GOAL_BUFFER_LENGTH); 54767 return Math.min(initial + currentTime * rate, max); 54768 } 54769 /** 54770 * Calculates the desired buffer low water line based on current time 54771 * 54772 * @return {Number} Desired buffer low water line in seconds 54773 */ 54774 54775 }, { 54776 key: 'bufferLowWaterLine', 54777 value: function bufferLowWaterLine() { 54778 var currentTime = this.tech_.currentTime(); 54779 var initial = Config.BUFFER_LOW_WATER_LINE; 54780 var rate = Config.BUFFER_LOW_WATER_LINE_RATE; 54781 var max = Math.max(initial, Config.MAX_BUFFER_LOW_WATER_LINE); 54782 return Math.min(initial + currentTime * rate, max); 54783 } 54784 }]); 54785 return MasterPlaylistController; 54786 }(videojs$1.EventTarget); 54787 /** 54788 * Returns a function that acts as the Enable/disable playlist function. 54789 * 54790 * @param {PlaylistLoader} loader - The master playlist loader 54791 * @param {String} playlistUri - uri of the playlist 54792 * @param {Function} changePlaylistFn - A function to be called after a 54793 * playlist's enabled-state has been changed. Will NOT be called if a 54794 * playlist's enabled-state is unchanged 54795 * @param {Boolean=} enable - Value to set the playlist enabled-state to 54796 * or if undefined returns the current enabled-state for the playlist 54797 * @return {Function} Function for setting/getting enabled 54798 */ 54799 54800 54801 var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn) { 54802 return function (enable) { 54803 var playlist = loader.master.playlists[playlistUri]; 54804 var incompatible = isIncompatible(playlist); 54805 var currentlyEnabled = isEnabled(playlist); 54806 54807 if (typeof enable === 'undefined') { 54808 return currentlyEnabled; 54809 } 54810 54811 if (enable) { 54812 delete playlist.disabled; 54813 } else { 54814 playlist.disabled = true; 54815 } 54816 54817 if (enable !== currentlyEnabled && !incompatible) { 54818 // Ensure the outside world knows about our changes 54819 changePlaylistFn(); 54820 54821 if (enable) { 54822 loader.trigger('renditionenabled'); 54823 } else { 54824 loader.trigger('renditiondisabled'); 54825 } 54826 } 54827 54828 return enable; 54829 }; 54830 }; 54831 /** 54832 * The representation object encapsulates the publicly visible information 54833 * in a media playlist along with a setter/getter-type function (enabled) 54834 * for changing the enabled-state of a particular playlist entry 54835 * 54836 * @class Representation 54837 */ 54838 54839 54840 var Representation = function Representation(hlsHandler, playlist, id) { 54841 classCallCheck$1(this, Representation); 54842 var mpc = hlsHandler.masterPlaylistController_, 54843 smoothQualityChange = hlsHandler.options_.smoothQualityChange; // Get a reference to a bound version of the quality change function 54844 54845 var changeType = smoothQualityChange ? 'smooth' : 'fast'; 54846 var qualityChangeFunction = mpc[changeType + 'QualityChange_'].bind(mpc); // some playlist attributes are optional 54847 54848 if (playlist.attributes.RESOLUTION) { 54849 var resolution = playlist.attributes.RESOLUTION; 54850 this.width = resolution.width; 54851 this.height = resolution.height; 54852 } 54853 54854 this.bandwidth = playlist.attributes.BANDWIDTH; // The id is simply the ordinality of the media playlist 54855 // within the master playlist 54856 54857 this.id = id; // Partially-apply the enableFunction to create a playlist- 54858 // specific variant 54859 54860 this.enabled = enableFunction(hlsHandler.playlists, playlist.uri, qualityChangeFunction); 54861 }; 54862 /** 54863 * A mixin function that adds the `representations` api to an instance 54864 * of the HlsHandler class 54865 * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the 54866 * representation API into 54867 */ 54868 54869 54870 var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) { 54871 var playlists = hlsHandler.playlists; // Add a single API-specific function to the HlsHandler instance 54872 54873 hlsHandler.representations = function () { 54874 return playlists.master.playlists.filter(function (media) { 54875 return !isIncompatible(media); 54876 }).map(function (e, i) { 54877 return new Representation(hlsHandler, e, e.uri); 54878 }); 54879 }; 54880 }; 54881 /** 54882 * @file playback-watcher.js 54883 * 54884 * Playback starts, and now my watch begins. It shall not end until my death. I shall 54885 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns 54886 * and win no glory. I shall live and die at my post. I am the corrector of the underflow. 54887 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge 54888 * my life and honor to the Playback Watch, for this Player and all the Players to come. 54889 */ 54890 // Set of events that reset the playback-watcher time check logic and clear the timeout 54891 54892 54893 var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 54894 /** 54895 * @class PlaybackWatcher 54896 */ 54897 54898 var PlaybackWatcher = function () { 54899 /** 54900 * Represents an PlaybackWatcher object. 54901 * @constructor 54902 * @param {object} options an object that includes the tech and settings 54903 */ 54904 function PlaybackWatcher(options) { 54905 var _this = this; 54906 54907 classCallCheck$1(this, PlaybackWatcher); 54908 this.tech_ = options.tech; 54909 this.seekable = options.seekable; 54910 this.seekTo = options.seekTo; 54911 this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow; 54912 this.media = options.media; 54913 this.consecutiveUpdates = 0; 54914 this.lastRecordedTime = null; 54915 this.timer_ = null; 54916 this.checkCurrentTimeTimeout_ = null; 54917 this.logger_ = logger('PlaybackWatcher'); 54918 this.logger_('initialize'); 54919 54920 var canPlayHandler = function canPlayHandler() { 54921 return _this.monitorCurrentTime_(); 54922 }; 54923 54924 var waitingHandler = function waitingHandler() { 54925 return _this.techWaiting_(); 54926 }; 54927 54928 var cancelTimerHandler = function cancelTimerHandler() { 54929 return _this.cancelTimer_(); 54930 }; 54931 54932 var fixesBadSeeksHandler = function fixesBadSeeksHandler() { 54933 return _this.fixesBadSeeks_(); 54934 }; 54935 54936 this.tech_.on('seekablechanged', fixesBadSeeksHandler); 54937 this.tech_.on('waiting', waitingHandler); 54938 this.tech_.on(timerCancelEvents, cancelTimerHandler); 54939 this.tech_.on('canplay', canPlayHandler); // Define the dispose function to clean up our events 54940 54941 this.dispose = function () { 54942 _this.logger_('dispose'); 54943 54944 _this.tech_.off('seekablechanged', fixesBadSeeksHandler); 54945 54946 _this.tech_.off('waiting', waitingHandler); 54947 54948 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 54949 54950 _this.tech_.off('canplay', canPlayHandler); 54951 54952 if (_this.checkCurrentTimeTimeout_) { 54953 window$1.clearTimeout(_this.checkCurrentTimeTimeout_); 54954 } 54955 54956 _this.cancelTimer_(); 54957 }; 54958 } 54959 /** 54960 * Periodically check current time to see if playback stopped 54961 * 54962 * @private 54963 */ 54964 54965 54966 createClass$1(PlaybackWatcher, [{ 54967 key: 'monitorCurrentTime_', 54968 value: function monitorCurrentTime_() { 54969 this.checkCurrentTime_(); 54970 54971 if (this.checkCurrentTimeTimeout_) { 54972 window$1.clearTimeout(this.checkCurrentTimeTimeout_); 54973 } // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 54974 54975 54976 this.checkCurrentTimeTimeout_ = window$1.setTimeout(this.monitorCurrentTime_.bind(this), 250); 54977 } 54978 /** 54979 * The purpose of this function is to emulate the "waiting" event on 54980 * browsers that do not emit it when they are waiting for more 54981 * data to continue playback 54982 * 54983 * @private 54984 */ 54985 54986 }, { 54987 key: 'checkCurrentTime_', 54988 value: function checkCurrentTime_() { 54989 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 54990 this.consecutiveUpdates = 0; 54991 this.lastRecordedTime = this.tech_.currentTime(); 54992 return; 54993 } 54994 54995 if (this.tech_.paused() || this.tech_.seeking()) { 54996 return; 54997 } 54998 54999 var currentTime = this.tech_.currentTime(); 55000 var buffered = this.tech_.buffered(); 55001 55002 if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + SAFE_TIME_DELTA >= buffered.end(buffered.length - 1))) { 55003 // If current time is at the end of the final buffered region, then any playback 55004 // stall is most likely caused by buffering in a low bandwidth environment. The tech 55005 // should fire a `waiting` event in this scenario, but due to browser and tech 55006 // inconsistencies. Calling `techWaiting_` here allows us to simulate 55007 // responding to a native `waiting` event when the tech fails to emit one. 55008 return this.techWaiting_(); 55009 } 55010 55011 if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) { 55012 this.consecutiveUpdates++; 55013 this.waiting_(); 55014 } else if (currentTime === this.lastRecordedTime) { 55015 this.consecutiveUpdates++; 55016 } else { 55017 this.consecutiveUpdates = 0; 55018 this.lastRecordedTime = currentTime; 55019 } 55020 } 55021 /** 55022 * Cancels any pending timers and resets the 'timeupdate' mechanism 55023 * designed to detect that we are stalled 55024 * 55025 * @private 55026 */ 55027 55028 }, { 55029 key: 'cancelTimer_', 55030 value: function cancelTimer_() { 55031 this.consecutiveUpdates = 0; 55032 55033 if (this.timer_) { 55034 this.logger_('cancelTimer_'); 55035 clearTimeout(this.timer_); 55036 } 55037 55038 this.timer_ = null; 55039 } 55040 /** 55041 * Fixes situations where there's a bad seek 55042 * 55043 * @return {Boolean} whether an action was taken to fix the seek 55044 * @private 55045 */ 55046 55047 }, { 55048 key: 'fixesBadSeeks_', 55049 value: function fixesBadSeeks_() { 55050 var seeking = this.tech_.seeking(); 55051 55052 if (!seeking) { 55053 return false; 55054 } 55055 55056 var seekable = this.seekable(); 55057 var currentTime = this.tech_.currentTime(); 55058 var isAfterSeekableRange = this.afterSeekableWindow_(seekable, currentTime, this.media(), this.allowSeeksWithinUnsafeLiveWindow); 55059 var seekTo = void 0; 55060 55061 if (isAfterSeekableRange) { 55062 var seekableEnd = seekable.end(seekable.length - 1); // sync to live point (if VOD, our seekable was updated and we're simply adjusting) 55063 55064 seekTo = seekableEnd; 55065 } 55066 55067 if (this.beforeSeekableWindow_(seekable, currentTime)) { 55068 var seekableStart = seekable.start(0); // sync to the beginning of the live window 55069 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 55070 55071 seekTo = seekableStart + SAFE_TIME_DELTA; 55072 } 55073 55074 if (typeof seekTo !== 'undefined') { 55075 this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + printableRange(seekable) + '. Seeking to ') + (seekTo + '.')); 55076 this.seekTo(seekTo); 55077 return true; 55078 } 55079 55080 return false; 55081 } 55082 /** 55083 * Handler for situations when we determine the player is waiting. 55084 * 55085 * @private 55086 */ 55087 55088 }, { 55089 key: 'waiting_',
55090 value: function waiting_() { 55091 if (this.techWaiting_()) { 55092 return; 55093 } // All tech waiting checks failed. Use last resort correction 55094 55095 55096 var currentTime = this.tech_.currentTime(); 55097 var buffered = this.tech_.buffered(); 55098 var currentRange = findRange(buffered, currentTime); // Sometimes the player can stall for unknown reasons within a contiguous buffered 55099 // region with no indication that anything is amiss (seen in Firefox). Seeking to 55100 // currentTime is usually enough to kickstart the player. This checks that the player 55101 // is currently within a buffered region before attempting a corrective seek. 55102 // Chrome does not appear to continue `timeupdate` events after a `waiting` event 55103 // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also 55104 // make sure there is ~3 seconds of forward buffer before taking any corrective action 55105 // to avoid triggering an `unknownwaiting` event when the network is slow. 55106 55107 if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { 55108 this.cancelTimer_(); 55109 this.seekTo(currentTime); 55110 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 55111 55112 this.tech_.trigger({ 55113 type: 'usage', 55114 name: 'hls-unknown-waiting' 55115 }); 55116 return; 55117 } 55118 } 55119 /** 55120 * Handler for situations when the tech fires a `waiting` event 55121 * 55122 * @return {Boolean} 55123 * True if an action (or none) was needed to correct the waiting. False if no 55124 * checks passed 55125 * @private 55126 */ 55127 55128 }, { 55129 key: 'techWaiting_',
vendor: 18,787 bytes, lines 55130-55660
55130 value: function techWaiting_() { 55131 var seekable = this.seekable(); 55132 var currentTime = this.tech_.currentTime(); 55133 55134 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 55135 // Tech is seeking or bad seek fixed, no action needed 55136 return true; 55137 } 55138 55139 if (this.tech_.seeking() || this.timer_ !== null) { 55140 // Tech is seeking or already waiting on another action, no action needed 55141 return true; 55142 } 55143 55144 if (this.beforeSeekableWindow_(seekable, currentTime)) { 55145 var livePoint = seekable.end(seekable.length - 1); 55146 this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint)); 55147 this.cancelTimer_(); 55148 this.seekTo(livePoint); // live window resyncs may be useful for monitoring QoS 55149 55150 this.tech_.trigger({ 55151 type: 'usage', 55152 name: 'hls-live-resync' 55153 }); 55154 return true; 55155 } 55156 55157 var buffered = this.tech_.buffered(); 55158 var nextRange = findNextRange(buffered, currentTime); 55159 55160 if (this.videoUnderflow_(nextRange, buffered, currentTime)) { 55161 // Even though the video underflowed and was stuck in a gap, the audio overplayed 55162 // the gap, leading currentTime into a buffered range. Seeking to currentTime 55163 // allows the video to catch up to the audio position without losing any audio 55164 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 55165 this.cancelTimer_(); 55166 this.seekTo(currentTime); // video underflow may be useful for monitoring QoS 55167 55168 this.tech_.trigger({ 55169 type: 'usage', 55170 name: 'hls-video-underflow' 55171 }); 55172 return true; 55173 } // check for gap 55174 55175 55176 if (nextRange.length > 0) { 55177 var difference = nextRange.start(0) - currentTime; 55178 this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0))); 55179 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 55180 return true; 55181 } // All checks failed. Returning false to indicate failure to correct waiting 55182 55183 55184 return false; 55185 } 55186 }, { 55187 key: 'afterSeekableWindow_', 55188 value: function afterSeekableWindow_(seekable, currentTime, playlist) { 55189 var allowSeeksWithinUnsafeLiveWindow = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; 55190 55191 if (!seekable.length) { 55192 // we can't make a solid case if there's no seekable, default to false 55193 return false; 55194 } 55195 55196 var allowedEnd = seekable.end(seekable.length - 1) + SAFE_TIME_DELTA; 55197 var isLive = !playlist.endList; 55198 55199 if (isLive && allowSeeksWithinUnsafeLiveWindow) { 55200 allowedEnd = seekable.end(seekable.length - 1) + playlist.targetDuration * 3; 55201 } 55202 55203 if (currentTime > allowedEnd) { 55204 return true; 55205 } 55206 55207 return false; 55208 } 55209 }, { 55210 key: 'beforeSeekableWindow_', 55211 value: function beforeSeekableWindow_(seekable, currentTime) { 55212 if (seekable.length && // can't fall before 0 and 0 seekable start identifies VOD stream 55213 seekable.start(0) > 0 && currentTime < seekable.start(0) - SAFE_TIME_DELTA) { 55214 return true; 55215 } 55216 55217 return false; 55218 } 55219 }, { 55220 key: 'videoUnderflow_', 55221 value: function videoUnderflow_(nextRange, buffered, currentTime) { 55222 if (nextRange.length === 0) { 55223 // Even if there is no available next range, there is still a possibility we are 55224 // stuck in a gap due to video underflow. 55225 var gap = this.gapFromVideoUnderflow_(buffered, currentTime); 55226 55227 if (gap) { 55228 this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime)); 55229 return true; 55230 } 55231 } 55232 55233 return false; 55234 } 55235 /** 55236 * Timer callback. If playback still has not proceeded, then we seek 55237 * to the start of the next buffered region. 55238 * 55239 * @private 55240 */ 55241 55242 }, { 55243 key: 'skipTheGap_', 55244 value: function skipTheGap_(scheduledCurrentTime) { 55245 var buffered = this.tech_.buffered(); 55246 var currentTime = this.tech_.currentTime(); 55247 var nextRange = findNextRange(buffered, currentTime); 55248 this.cancelTimer_(); 55249 55250 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 55251 return; 55252 } 55253 55254 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); // only seek if we still have not played 55255 55256 this.seekTo(nextRange.start(0) + TIME_FUDGE_FACTOR); 55257 this.tech_.trigger({ 55258 type: 'usage', 55259 name: 'hls-gap-skip' 55260 }); 55261 } 55262 }, { 55263 key: 'gapFromVideoUnderflow_', 55264 value: function gapFromVideoUnderflow_(buffered, currentTime) { 55265 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 55266 // playing for ~3 seconds after the video gap starts. This is done to account for 55267 // video buffer underflow/underrun (note that this is not done when there is audio 55268 // buffer underflow/underrun -- in that case the video will stop as soon as it 55269 // encounters the gap, as audio stalls are more noticeable/jarring to a user than 55270 // video stalls). The player's time will reflect the playthrough of audio, so the 55271 // time will appear as if we are in a buffered region, even if we are stuck in a 55272 // "gap." 55273 // 55274 // Example: 55275 // video buffer: 0 => 10.1, 10.2 => 20 55276 // audio buffer: 0 => 20 55277 // overall buffer: 0 => 10.1, 10.2 => 20 55278 // current time: 13 55279 // 55280 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 55281 // however, the audio continued playing until it reached ~3 seconds past the gap 55282 // (13 seconds), at which point it stops as well. Since current time is past the 55283 // gap, findNextRange will return no ranges. 55284 // 55285 // To check for this issue, we see if there is a gap that starts somewhere within 55286 // a 3 second range (3 seconds +/- 1 second) back from our current time. 55287 var gaps = findGaps(buffered); 55288 55289 for (var i = 0; i < gaps.length; i++) { 55290 var start = gaps.start(i); 55291 var end = gaps.end(i); // gap is starts no more than 4 seconds back 55292 55293 if (currentTime - start < 4 && currentTime - start > 2) { 55294 return { 55295 start: start, 55296 end: end 55297 }; 55298 } 55299 } 55300 55301 return null; 55302 } 55303 }]); 55304 return PlaybackWatcher; 55305 }(); 55306 55307 var defaultOptions = { 55308 errorInterval: 30, 55309 getSource: function getSource(next) { 55310 var tech = this.tech({ 55311 IWillNotUseThisInPlugins: true 55312 }); 55313 var sourceObj = tech.currentSource_; 55314 return next(sourceObj); 55315 } 55316 }; 55317 /** 55318 * Main entry point for the plugin 55319 * 55320 * @param {Player} player a reference to a videojs Player instance 55321 * @param {Object} [options] an object with plugin options 55322 * @private 55323 */ 55324 55325 var initPlugin = function initPlugin(player, options) { 55326 var lastCalled = 0; 55327 var seekTo = 0; 55328 var localOptions = videojs$1.mergeOptions(defaultOptions, options); 55329 player.ready(function () { 55330 player.trigger({ 55331 type: 'usage', 55332 name: 'hls-error-reload-initialized' 55333 }); 55334 }); 55335 /** 55336 * Player modifications to perform that must wait until `loadedmetadata` 55337 * has been triggered 55338 * 55339 * @private 55340 */ 55341 55342 var loadedMetadataHandler = function loadedMetadataHandler() { 55343 if (seekTo) { 55344 player.currentTime(seekTo); 55345 } 55346 }; 55347 /** 55348 * Set the source on the player element, play, and seek if necessary 55349 * 55350 * @param {Object} sourceObj An object specifying the source url and mime-type to play 55351 * @private 55352 */ 55353 55354 55355 var setSource = function setSource(sourceObj) { 55356 if (sourceObj === null || sourceObj === undefined) { 55357 return; 55358 } 55359 55360 seekTo = player.duration() !== Infinity && player.currentTime() || 0; 55361 player.one('loadedmetadata', loadedMetadataHandler); 55362 player.src(sourceObj); 55363 player.trigger({ 55364 type: 'usage', 55365 name: 'hls-error-reload' 55366 }); 55367 player.play(); 55368 }; 55369 /** 55370 * Attempt to get a source from either the built-in getSource function 55371 * or a custom function provided via the options 55372 * 55373 * @private 55374 */ 55375 55376 55377 var errorHandler = function errorHandler() { 55378 // Do not attempt to reload the source if a source-reload occurred before 55379 // 'errorInterval' time has elapsed since the last source-reload 55380 if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { 55381 player.trigger({ 55382 type: 'usage', 55383 name: 'hls-error-reload-canceled' 55384 }); 55385 return; 55386 } 55387 55388 if (!localOptions.getSource || typeof localOptions.getSource !== 'function') { 55389 videojs$1.log.error('ERROR: reloadSourceOnError - The option getSource must be a function!'); 55390 return; 55391 } 55392 55393 lastCalled = Date.now(); 55394 return localOptions.getSource.call(player, setSource); 55395 }; 55396 /** 55397 * Unbind any event handlers that were bound by the plugin 55398 * 55399 * @private 55400 */ 55401 55402 55403 var cleanupEvents = function cleanupEvents() { 55404 player.off('loadedmetadata', loadedMetadataHandler); 55405 player.off('error', errorHandler); 55406 player.off('dispose', cleanupEvents); 55407 }; 55408 /** 55409 * Cleanup before re-initializing the plugin 55410 * 55411 * @param {Object} [newOptions] an object with plugin options 55412 * @private 55413 */ 55414 55415 55416 var reinitPlugin = function reinitPlugin(newOptions) { 55417 cleanupEvents(); 55418 initPlugin(player, newOptions); 55419 }; 55420 55421 player.on('error', errorHandler); 55422 player.on('dispose', cleanupEvents); // Overwrite the plugin function so that we can correctly cleanup before 55423 // initializing the plugin 55424 55425 player.reloadSourceOnError = reinitPlugin; 55426 }; 55427 /** 55428 * Reload the source when an error is detected as long as there 55429 * wasn't an error previously within the last 30 seconds 55430 * 55431 * @param {Object} [options] an object with plugin options 55432 */ 55433 55434 55435 var reloadSourceOnError = function reloadSourceOnError(options) { 55436 initPlugin(this, options); 55437 }; 55438 55439 var version$1 = "1.10.6"; // since VHS handles HLS and DASH (and in the future, more types), use * to capture all 55440 55441 videojs$1.use('*', function (player) { 55442 return { 55443 setSource: function setSource(srcObj, next) { 55444 // pass null as the first argument to indicate that the source is not rejected 55445 next(null, srcObj); 55446 }, 55447 // VHS needs to know when seeks happen. For external seeks (generated at the player 55448 // level), this middleware will capture the action. For internal seeks (generated at 55449 // the tech level), we use a wrapped function so that we can handle it on our own 55450 // (specified elsewhere). 55451 setCurrentTime: function setCurrentTime(time) { 55452 if (player.vhs && player.currentSource().src === player.vhs.source_.src) { 55453 player.vhs.setCurrentTime(time); 55454 } 55455 55456 return time; 55457 }, 55458 // Sync VHS after play requests. 55459 // This specifically handles replay where the order of actions is 55460 // play, video element will seek to 0 (skipping the setCurrentTime middleware) 55461 // then triggers a play event. 55462 play: function play() { 55463 if (player.vhs && player.currentSource().src === player.vhs.source_.src) { 55464 player.vhs.setCurrentTime(player.tech_.currentTime()); 55465 } 55466 } 55467 }; 55468 }); 55469 /** 55470 * @file videojs-http-streaming.js 55471 * 55472 * The main file for the HLS project. 55473 * License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE 55474 */ 55475 55476 var Hls$1 = { 55477 PlaylistLoader: PlaylistLoader, 55478 Playlist: Playlist, 55479 Decrypter: Decrypter, 55480 AsyncStream: AsyncStream, 55481 decrypt: decrypt, 55482 utils: utils$1, 55483 STANDARD_PLAYLIST_SELECTOR: lastBandwidthSelector, 55484 INITIAL_PLAYLIST_SELECTOR: lowestBitrateCompatibleVariantSelector, 55485 comparePlaylistBandwidth: comparePlaylistBandwidth, 55486 comparePlaylistResolution: comparePlaylistResolution, 55487 xhr: xhrFactory() 55488 }; // Define getter/setters for config properites 55489 55490 ['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) { 55491 Object.defineProperty(Hls$1, prop, { 55492 get: function get$$1() { 55493 videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 55494 return Config[prop]; 55495 }, 55496 set: function set$$1(value) { 55497 videojs$1.log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 55498 55499 if (typeof value !== 'number' || value < 0) { 55500 videojs$1.log.warn('value of Hls.' + prop + ' must be greater than or equal to 0'); 55501 return; 55502 } 55503 55504 Config[prop] = value; 55505 } 55506 }); 55507 }); 55508 var LOCAL_STORAGE_KEY$1 = 'videojs-vhs'; 55509 55510 var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) { 55511 var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 55512 55513 if (mpegurlRE.test(type)) { 55514 return 'hls'; 55515 } 55516 55517 var dashRE = /^application\/dash\+xml/i; 55518 55519 if (dashRE.test(type)) { 55520 return 'dash'; 55521 } 55522 55523 return null; 55524 }; 55525 /** 55526 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls. 55527 * 55528 * @param {QualityLevelList} qualityLevels The QualityLevelList to update. 55529 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info. 55530 * @function handleHlsMediaChange 55531 */ 55532 55533 55534 var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) { 55535 var newPlaylist = playlistLoader.media(); 55536 var selectedIndex = -1; 55537 55538 for (var i = 0; i < qualityLevels.length; i++) { 55539 if (qualityLevels[i].id === newPlaylist.uri) { 55540 selectedIndex = i; 55541 break; 55542 } 55543 } 55544 55545 qualityLevels.selectedIndex_ = selectedIndex; 55546 qualityLevels.trigger({ 55547 selectedIndex: selectedIndex, 55548 type: 'change' 55549 }); 55550 }; 55551 /** 55552 * Adds quality levels to list once playlist metadata is available 55553 * 55554 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to. 55555 * @param {Object} hls Hls object to listen to for media events. 55556 * @function handleHlsLoadedMetadata 55557 */ 55558 55559 55560 var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) { 55561 hls.representations().forEach(function (rep) { 55562 qualityLevels.addQualityLevel(rep); 55563 }); 55564 handleHlsMediaChange(qualityLevels, hls.playlists); 55565 }; // HLS is a source handler, not a tech. Make sure attempts to use it 55566 // as one do not cause exceptions. 55567 55568 55569 Hls$1.canPlaySource = function () { 55570 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 55571 }; 55572 55573 var emeKeySystems = function emeKeySystems(keySystemOptions, videoPlaylist, audioPlaylist) { 55574 if (!keySystemOptions) { 55575 return keySystemOptions; 55576 } // upsert the content types based on the selected playlist 55577 55578 55579 var keySystemContentTypes = {}; 55580 55581 for (var keySystem in keySystemOptions) { 55582 keySystemContentTypes[keySystem] = { 55583 audioContentType: 'audio/mp4; codecs="' + audioPlaylist.attributes.CODECS + '"', 55584 videoContentType: 'video/mp4; codecs="' + videoPlaylist.attributes.CODECS + '"' 55585 }; 55586 55587 if (videoPlaylist.contentProtection && videoPlaylist.contentProtection[keySystem] && videoPlaylist.contentProtection[keySystem].pssh) { 55588 keySystemContentTypes[keySystem].pssh = videoPlaylist.contentProtection[keySystem].pssh; 55589 } // videojs-contrib-eme accepts the option of specifying: 'com.some.cdm': 'url' 55590 // so we need to prevent overwriting the URL entirely 55591 55592 55593 if (typeof keySystemOptions[keySystem] === 'string') { 55594 keySystemContentTypes[keySystem].url = keySystemOptions[keySystem]; 55595 } 55596 } 55597 55598 return videojs$1.mergeOptions(keySystemOptions, keySystemContentTypes); 55599 }; 55600 55601 var setupEmeOptions = function setupEmeOptions(hlsHandler) { 55602 if (hlsHandler.options_.sourceType !== 'dash') { 55603 return; 55604 } 55605 55606 var player = videojs$1.players[hlsHandler.tech_.options_.playerId]; 55607 55608 if (player.eme) { 55609 var sourceOptions = emeKeySystems(hlsHandler.source_.keySystems, hlsHandler.playlists.media(), hlsHandler.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader.media()); 55610 55611 if (sourceOptions) { 55612 player.currentSource().keySystems = sourceOptions; // works around https://bugs.chromium.org/p/chromium/issues/detail?id=895449 55613 55614 if (player.eme.initializeMediaKeys) { 55615 player.eme.initializeMediaKeys(); 55616 } 55617 } 55618 } 55619 }; 55620 55621 var getVhsLocalStorage = function getVhsLocalStorage() { 55622 if (!window.localStorage) { 55623 return null; 55624 } 55625 55626 var storedObject = window.localStorage.getItem(LOCAL_STORAGE_KEY$1); 55627 55628 if (!storedObject) { 55629 return null; 55630 } 55631 55632 try { 55633 return JSON.parse(storedObject); 55634 } catch (e) { 55635 // someone may have tampered with the value 55636 return null; 55637 } 55638 }; 55639 55640 var updateVhsLocalStorage = function updateVhsLocalStorage(options) { 55641 if (!window.localStorage) { 55642 return false; 55643 } 55644 55645 var objectToStore = getVhsLocalStorage(); 55646 objectToStore = objectToStore ? videojs$1.mergeOptions(objectToStore, options) : options; 55647 55648 try { 55649 window.localStorage.setItem(LOCAL_STORAGE_KEY$1, JSON.stringify(objectToStore)); 55650 } catch (e) { 55651 // Throws if storage is full (e.g., always on iOS 5+ Safari private mode, where 55652 // storage is set to 0). 55653 // https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#Exceptions 55654 // No need to perform any operation. 55655 return false; 55656 } 55657 55658 return objectToStore; 55659 }; 55660 /**
vendor: 16,384 bytes, lines 55661-56070
55661 * Whether the browser has built-in HLS support. 55662 */ 55663 55664 55665 Hls$1.supportsNativeHls = function () { 55666 var video = document.createElement('video'); // native HLS is definitely not supported if HTML5 video isn't 55667 55668 if (!videojs$1.getTech('Html5').isSupported()) { 55669 return false; 55670 } // HLS manifests can go by many mime-types 55671 55672 55673 var canPlay = [// Apple santioned 55674 'application/vnd.apple.mpegurl', // Apple sanctioned for backwards compatibility 55675 'audio/mpegurl', // Very common 55676 'audio/x-mpegurl', // Very common 55677 'application/x-mpegurl', // Included for completeness 55678 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 55679 return canPlay.some(function (canItPlay) { 55680 return /maybe|probably/i.test(video.canPlayType(canItPlay)); 55681 }); 55682 }(); 55683 55684 Hls$1.supportsNativeDash = function () { 55685 if (!videojs$1.getTech('Html5').isSupported()) { 55686 return false; 55687 } 55688 55689 return /maybe|probably/i.test(document.createElement('video').canPlayType('application/dash+xml')); 55690 }(); 55691 55692 Hls$1.supportsTypeNatively = function (type) { 55693 if (type === 'hls') { 55694 return Hls$1.supportsNativeHls; 55695 } 55696 55697 if (type === 'dash') { 55698 return Hls$1.supportsNativeDash; 55699 } 55700 55701 return false; 55702 }; 55703 /** 55704 * HLS is a source handler, not a tech. Make sure attempts to use it 55705 * as one do not cause exceptions. 55706 */ 55707 55708 55709 Hls$1.isSupported = function () { 55710 return videojs$1.log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 55711 }; 55712 55713 var Component$1 = videojs$1.getComponent('Component'); 55714 /** 55715 * The Hls Handler object, where we orchestrate all of the parts 55716 * of HLS to interact with video.js 55717 * 55718 * @class HlsHandler 55719 * @extends videojs.Component 55720 * @param {Object} source the soruce object 55721 * @param {Tech} tech the parent tech object 55722 * @param {Object} options optional and required options 55723 */ 55724 55725 var HlsHandler = function (_Component) { 55726 inherits$1(HlsHandler, _Component); 55727 55728 function HlsHandler(source, tech, options) { 55729 classCallCheck$1(this, HlsHandler); // tech.player() is deprecated but setup a reference to HLS for 55730 // backwards-compatibility 55731 55732 var _this = possibleConstructorReturn$1(this, (HlsHandler.__proto__ || Object.getPrototypeOf(HlsHandler)).call(this, tech, options.hls)); 55733 55734 if (tech.options_ && tech.options_.playerId) { 55735 var _player = videojs$1(tech.options_.playerId); 55736 55737 if (!_player.hasOwnProperty('hls')) { 55738 Object.defineProperty(_player, 'hls', { 55739 get: function get$$1() { 55740 videojs$1.log.warn('player.hls is deprecated. Use player.tech().hls instead.'); 55741 tech.trigger({ 55742 type: 'usage', 55743 name: 'hls-player-access' 55744 }); 55745 return _this; 55746 }, 55747 configurable: true 55748 }); 55749 } // Set up a reference to the HlsHandler from player.vhs. This allows users to start 55750 // migrating from player.tech_.hls... to player.vhs... for API access. Although this 55751 // isn't the most appropriate form of reference for video.js (since all APIs should 55752 // be provided through core video.js), it is a common pattern for plugins, and vhs 55753 // will act accordingly. 55754 55755 55756 _player.vhs = _this; // deprecated, for backwards compatibility 55757 55758 _player.dash = _this; 55759 _this.player_ = _player; 55760 } 55761 55762 _this.tech_ = tech; 55763 _this.source_ = source; 55764 _this.stats = {}; 55765 55766 _this.setOptions_(); 55767 55768 if (_this.options_.overrideNative && tech.overrideNativeAudioTracks && tech.overrideNativeVideoTracks) { 55769 tech.overrideNativeAudioTracks(true); 55770 tech.overrideNativeVideoTracks(true); 55771 } else if (_this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { 55772 // overriding native HLS only works if audio tracks have been emulated 55773 // error early if we're misconfigured 55774 throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB'); 55775 } // listen for fullscreenchange events for this player so that we 55776 // can adjust our quality selection quickly 55777 55778 55779 _this.on(document, ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 55780 var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; 55781 55782 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 55783 _this.masterPlaylistController_.smoothQualityChange_(); 55784 } 55785 }); // Handle seeking when looping - middleware doesn't handle this seek event from the tech 55786 55787 55788 _this.on(_this.tech_, 'seeking', function () { 55789 if (this.tech_.currentTime() === 0 && this.tech_.player_.loop()) { 55790 this.setCurrentTime(0); 55791 } 55792 }); 55793 55794 _this.on(_this.tech_, 'error', function () { 55795 if (this.masterPlaylistController_) { 55796 this.masterPlaylistController_.pauseLoading(); 55797 } 55798 }); 55799 55800 _this.on(_this.tech_, 'play', _this.play); 55801 55802 return _this; 55803 } 55804 55805 createClass$1(HlsHandler, [{ 55806 key: 'setOptions_', 55807 value: function setOptions_() { 55808 var _this2 = this; // defaults 55809 55810 55811 this.options_.withCredentials = this.options_.withCredentials || false; 55812 this.options_.handleManifestRedirects = this.options_.handleManifestRedirects || false; 55813 this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true; 55814 this.options_.smoothQualityChange = this.options_.smoothQualityChange || false; 55815 this.options_.useBandwidthFromLocalStorage = typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? this.source_.useBandwidthFromLocalStorage : this.options_.useBandwidthFromLocalStorage || false; 55816 this.options_.customTagParsers = this.options_.customTagParsers || []; 55817 this.options_.customTagMappers = this.options_.customTagMappers || []; 55818 this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false; 55819 55820 if (typeof this.options_.blacklistDuration !== 'number') { 55821 this.options_.blacklistDuration = 5 * 60; 55822 } 55823 55824 if (typeof this.options_.bandwidth !== 'number') { 55825 if (this.options_.useBandwidthFromLocalStorage) { 55826 var storedObject = getVhsLocalStorage(); 55827 55828 if (storedObject && storedObject.bandwidth) { 55829 this.options_.bandwidth = storedObject.bandwidth; 55830 this.tech_.trigger({ 55831 type: 'usage', 55832 name: 'hls-bandwidth-from-local-storage' 55833 }); 55834 } 55835 55836 if (storedObject && storedObject.throughput) { 55837 this.options_.throughput = storedObject.throughput; 55838 this.tech_.trigger({ 55839 type: 'usage', 55840 name: 'hls-throughput-from-local-storage' 55841 }); 55842 } 55843 } 55844 } // if bandwidth was not set by options or pulled from local storage, start playlist 55845 // selection at a reasonable bandwidth 55846 55847 55848 if (typeof this.options_.bandwidth !== 'number') { 55849 this.options_.bandwidth = Config.INITIAL_BANDWIDTH; 55850 } // If the bandwidth number is unchanged from the initial setting 55851 // then this takes precedence over the enableLowInitialPlaylist option 55852 55853 55854 this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === Config.INITIAL_BANDWIDTH; // grab options passed to player.src 55855 55856 ['withCredentials', 'limitRenditionByPlayerDimensions', 'bandwidth', 'smoothQualityChange', 'customTagParsers', 'customTagMappers', 'handleManifestRedirects', 'cacheEncryptionKeys'].forEach(function (option) { 55857 if (typeof _this2.source_[option] !== 'undefined') { 55858 _this2.options_[option] = _this2.source_[option]; 55859 } 55860 }); 55861 this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions; 55862 } 55863 /** 55864 * called when player.src gets called, handle a new source 55865 * 55866 * @param {Object} src the source object to handle 55867 */ 55868 55869 }, { 55870 key: 'src', 55871 value: function src(_src, type) { 55872 var _this3 = this; // do nothing if the src is falsey 55873 55874 55875 if (!_src) { 55876 return; 55877 } 55878 55879 this.setOptions_(); // add master playlist controller options 55880 55881 this.options_.url = this.source_.src; 55882 this.options_.tech = this.tech_; 55883 this.options_.externHls = Hls$1; 55884 this.options_.sourceType = simpleTypeFromSourceType(type); // Whenever we seek internally, we should update both the tech and call our own 55885 // setCurrentTime function. This is needed because "seeking" events aren't always 55886 // reliable. External seeks (via the player object) are handled via middleware. 55887 55888 this.options_.seekTo = function (time) { 55889 _this3.tech_.setCurrentTime(time); 55890 55891 _this3.setCurrentTime(time); 55892 }; 55893 55894 this.masterPlaylistController_ = new MasterPlaylistController(this.options_); 55895 this.playbackWatcher_ = new PlaybackWatcher(videojs$1.mergeOptions(this.options_, { 55896 seekable: function seekable$$1() { 55897 return _this3.seekable(); 55898 }, 55899 media: function media() { 55900 return _this3.masterPlaylistController_.media(); 55901 } 55902 })); 55903 this.masterPlaylistController_.on('error', function () { 55904 var player = videojs$1.players[_this3.tech_.options_.playerId]; 55905 player.error(_this3.masterPlaylistController_.error); 55906 }); // `this` in selectPlaylist should be the HlsHandler for backwards 55907 // compatibility with < v2 55908 55909 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls$1.STANDARD_PLAYLIST_SELECTOR.bind(this); 55910 this.masterPlaylistController_.selectInitialPlaylist = Hls$1.INITIAL_PLAYLIST_SELECTOR.bind(this); // re-expose some internal objects for backwards compatibility with < v2 55911 55912 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 55913 this.mediaSource = this.masterPlaylistController_.mediaSource; // Proxy assignment of some properties to the master playlist 55914 // controller. Using a custom property for backwards compatibility 55915 // with < v2 55916 55917 Object.defineProperties(this, { 55918 selectPlaylist: { 55919 get: function get$$1() { 55920 return this.masterPlaylistController_.selectPlaylist; 55921 }, 55922 set: function set$$1(selectPlaylist) { 55923 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 55924 } 55925 }, 55926 throughput: { 55927 get: function get$$1() { 55928 return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; 55929 }, 55930 set: function set$$1(throughput) { 55931 this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; // By setting `count` to 1 the throughput value becomes the starting value 55932 // for the cumulative average 55933 55934 this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; 55935 } 55936 }, 55937 bandwidth: { 55938 get: function get$$1() { 55939 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 55940 }, 55941 set: function set$$1(bandwidth) { 55942 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; // setting the bandwidth manually resets the throughput counter 55943 // `count` is set to zero that current value of `rate` isn't included 55944 // in the cumulative average 55945 55946 this.masterPlaylistController_.mainSegmentLoader_.throughput = { 55947 rate: 0, 55948 count: 0 55949 }; 55950 } 55951 }, 55952 55953 /** 55954 * `systemBandwidth` is a combination of two serial processes bit-rates. The first 55955 * is the network bitrate provided by `bandwidth` and the second is the bitrate of 55956 * the entire process after that - decryption, transmuxing, and appending - provided 55957 * by `throughput`. 55958 * 55959 * Since the two process are serial, the overall system bandwidth is given by: 55960 * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput) 55961 */ 55962 systemBandwidth: { 55963 get: function get$$1() { 55964 var invBandwidth = 1 / (this.bandwidth || 1); 55965 var invThroughput = void 0; 55966 55967 if (this.throughput > 0) { 55968 invThroughput = 1 / this.throughput; 55969 } else { 55970 invThroughput = 0; 55971 } 55972 55973 var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput)); 55974 return systemBitrate; 55975 }, 55976 set: function set$$1() { 55977 videojs$1.log.error('The "systemBandwidth" property is read-only'); 55978 } 55979 } 55980 }); 55981 55982 if (this.options_.bandwidth) { 55983 this.bandwidth = this.options_.bandwidth; 55984 } 55985 55986 if (this.options_.throughput) { 55987 this.throughput = this.options_.throughput; 55988 } 55989 55990 Object.defineProperties(this.stats, { 55991 bandwidth: { 55992 get: function get$$1() { 55993 return _this3.bandwidth || 0; 55994 }, 55995 enumerable: true 55996 }, 55997 mediaRequests: { 55998 get: function get$$1() { 55999 return _this3.masterPlaylistController_.mediaRequests_() || 0; 56000 }, 56001 enumerable: true 56002 }, 56003 mediaRequestsAborted: { 56004 get: function get$$1() { 56005 return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0; 56006 }, 56007 enumerable: true 56008 }, 56009 mediaRequestsTimedout: { 56010 get: function get$$1() { 56011 return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0; 56012 }, 56013 enumerable: true 56014 }, 56015 mediaRequestsErrored: { 56016 get: function get$$1() { 56017 return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0; 56018 }, 56019 enumerable: true 56020 }, 56021 mediaTransferDuration: { 56022 get: function get$$1() { 56023 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 56024 }, 56025 enumerable: true 56026 }, 56027 mediaBytesTransferred: { 56028 get: function get$$1() { 56029 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 56030 }, 56031 enumerable: true 56032 }, 56033 mediaSecondsLoaded: { 56034 get: function get$$1() { 56035 return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0; 56036 }, 56037 enumerable: true 56038 }, 56039 buffered: { 56040 get: function get$$1() { 56041 return timeRangesToArray(_this3.tech_.buffered()); 56042 }, 56043 enumerable: true 56044 }, 56045 currentTime: { 56046 get: function get$$1() { 56047 return _this3.tech_.currentTime(); 56048 }, 56049 enumerable: true 56050 }, 56051 currentSource: { 56052 get: function get$$1() { 56053 return _this3.tech_.currentSource_; 56054 }, 56055 enumerable: true 56056 }, 56057 currentTech: { 56058 get: function get$$1() { 56059 return _this3.tech_.name_; 56060 }, 56061 enumerable: true 56062 }, 56063 duration: { 56064 get: function get$$1() { 56065 return _this3.tech_.duration(); 56066 }, 56067 enumerable: true 56068 }, 56069 master: { 56070 get: function get$$1() {
vendor: 4,201 bytes, lines 56071-56204
56071 return _this3.playlists.master; 56072 }, 56073 enumerable: true 56074 }, 56075 playerDimensions: { 56076 get: function get$$1() { 56077 return _this3.tech_.currentDimensions(); 56078 }, 56079 enumerable: true 56080 }, 56081 seekable: { 56082 get: function get$$1() { 56083 return timeRangesToArray(_this3.tech_.seekable()); 56084 }, 56085 enumerable: true 56086 }, 56087 timestamp: { 56088 get: function get$$1() { 56089 return Date.now(); 56090 }, 56091 enumerable: true 56092 }, 56093 videoPlaybackQuality: { 56094 get: function get$$1() { 56095 return _this3.tech_.getVideoPlaybackQuality(); 56096 }, 56097 enumerable: true 56098 } 56099 }); 56100 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 56101 this.tech_.on('bandwidthupdate', function () { 56102 if (_this3.options_.useBandwidthFromLocalStorage) { 56103 updateVhsLocalStorage({ 56104 bandwidth: _this3.bandwidth, 56105 throughput: Math.round(_this3.throughput) 56106 }); 56107 } 56108 }); 56109 this.masterPlaylistController_.on('selectedinitialmedia', function () { 56110 // Add the manual rendition mix-in to HlsHandler 56111 renditionSelectionMixin(_this3); 56112 setupEmeOptions(_this3); 56113 }); // the bandwidth of the primary segment loader is our best 56114 // estimate of overall bandwidth 56115 56116 this.on(this.masterPlaylistController_, 'progress', function () { 56117 this.tech_.trigger('progress'); 56118 }); 56119 this.tech_.ready(function () { 56120 return _this3.setupQualityLevels_(); 56121 }); // do nothing if the tech has been disposed already 56122 // this can occur if someone sets the src in player.ready(), for instance 56123 56124 if (!this.tech_.el()) { 56125 return; 56126 } 56127 56128 this.tech_.src(videojs$1.URL.createObjectURL(this.masterPlaylistController_.mediaSource)); 56129 } 56130 /** 56131 * Initializes the quality levels and sets listeners to update them. 56132 * 56133 * @method setupQualityLevels_ 56134 * @private 56135 */ 56136 56137 }, { 56138 key: 'setupQualityLevels_', 56139 value: function setupQualityLevels_() { 56140 var _this4 = this; 56141 56142 var player = videojs$1.players[this.tech_.options_.playerId]; 56143 56144 if (player && player.qualityLevels) { 56145 this.qualityLevels_ = player.qualityLevels(); 56146 this.masterPlaylistController_.on('selectedinitialmedia', function () { 56147 handleHlsLoadedMetadata(_this4.qualityLevels_, _this4); 56148 }); 56149 this.playlists.on('mediachange', function () { 56150 handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists); 56151 }); 56152 } 56153 } 56154 /** 56155 * Begin playing the video. 56156 */ 56157 56158 }, { 56159 key: 'play', 56160 value: function play() { 56161 this.masterPlaylistController_.play(); 56162 } 56163 /** 56164 * a wrapper around the function in MasterPlaylistController 56165 */ 56166 56167 }, { 56168 key: 'setCurrentTime', 56169 value: function setCurrentTime(currentTime) { 56170 this.masterPlaylistController_.setCurrentTime(currentTime); 56171 } 56172 /** 56173 * a wrapper around the function in MasterPlaylistController 56174 */ 56175 56176 }, { 56177 key: 'duration', 56178 value: function duration$$1() { 56179 return this.masterPlaylistController_.duration(); 56180 } 56181 /** 56182 * a wrapper around the function in MasterPlaylistController 56183 */ 56184 56185 }, { 56186 key: 'seekable', 56187 value: function seekable$$1() { 56188 return this.masterPlaylistController_.seekable(); 56189 } 56190 /** 56191 * Abort all outstanding work and cleanup. 56192 */ 56193 56194 }, { 56195 key: 'dispose', 56196 value: function dispose() { 56197 if (this.playbackWatcher_) { 56198 this.playbackWatcher_.dispose(); 56199 } 56200 56201 if (this.masterPlaylistController_) { 56202 this.masterPlaylistController_.dispose(); 56203 } 56204
56205 if (this.qualityLevels_) { 56206 this.qualityLevels_.dispose(); 56207 } 56208 56209 if (this.player_) { 56210 delete this.player_.vhs; 56211 delete this.player_.dash; 56212 delete this.player_.hls; 56213 } 56214 56215 if (this.tech_ && this.tech_.hls) { 56216 delete this.tech_.hls; 56217 } 56218 56219 get$1(HlsHandler.prototype.__proto__ || Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this); 56220 } 56221 }, { 56222 key: 'convertToProgramTime', 56223 value: function convertToProgramTime(time, callback) { 56224 return getProgramTime({ 56225 playlist: this.masterPlaylistController_.media(), 56226 time: time, 56227 callback: callback 56228 }); 56229 } // the player must be playing before calling this 56230 56231 }, { 56232 key: 'seekToProgramTime', 56233 value: function seekToProgramTime$$1(programTime, callback) { 56234 var pauseAfterSeek = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 56235 var retryCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2; 56236 return seekToProgramTime({ 56237 programTime: programTime, 56238 playlist: this.masterPlaylistController_.media(), 56239 retryCount: retryCount, 56240 pauseAfterSeek: pauseAfterSeek, 56241 seekTo: this.options_.seekTo, 56242 tech: this.options_.tech, 56243 callback: callback 56244 }); 56245 } 56246 }]); 56247 return HlsHandler; 56248 }(Component$1); 56249 /** 56250 * The Source Handler object, which informs video.js what additional 56251 * MIME types are supported and sets up playback. It is registered 56252 * automatically to the appropriate tech based on the capabilities of 56253 * the browser it is running in. It is not necessary to use or modify 56254 * this object in normal usage. 56255 */ 56256 56257 56258 var HlsSourceHandler = { 56259 name: 'videojs-http-streaming', 56260 VERSION: version$1, 56261 canHandleSource: function canHandleSource(srcObj) { 56262 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 56263 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 56264 return HlsSourceHandler.canPlayType(srcObj.type, localOptions); 56265 }, 56266 handleSource: function handleSource(source, tech) { 56267 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 56268 var localOptions = videojs$1.mergeOptions(videojs$1.options, options); 56269 tech.hls = new HlsHandler(source, tech, localOptions); 56270 tech.hls.xhr = xhrFactory(); 56271 tech.hls.src(source.src, source.type); 56272 return tech.hls; 56273 }, 56274 canPlayType: function canPlayType(type) { 56275 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 56276 56277 var _videojs$mergeOptions = videojs$1.mergeOptions(videojs$1.options, options), 56278 overrideNative = _videojs$mergeOptions.hls.overrideNative; 56279 56280 var supportedType = simpleTypeFromSourceType(type); 56281 var canUseMsePlayback = supportedType && (!Hls$1.supportsTypeNatively(supportedType) || overrideNative); 56282 return canUseMsePlayback ? 'maybe' : ''; 56283 } 56284 }; 56285 56286 if (typeof videojs$1.MediaSource === 'undefined' || typeof videojs$1.URL === 'undefined') { 56287 videojs$1.MediaSource = MediaSource; 56288 videojs$1.URL = URL$1; 56289 } // register source handlers with the appropriate techs 56290 56291 56292 if (MediaSource.supportsNativeMediaSources()) { 56293 videojs$1.getTech('Html5').registerSourceHandler(HlsSourceHandler, 0); 56294 } 56295 56296 videojs$1.HlsHandler = HlsHandler; 56297 videojs$1.HlsSourceHandler = HlsSourceHandler; 56298 videojs$1.Hls = Hls$1; 56299 56300 if (!videojs$1.use) { 56301 videojs$1.registerComponent('Hls', Hls$1); 56302 } 56303 56304 videojs$1.options.hls = videojs$1.options.hls || {}; 56305 56306 if (videojs$1.registerPlugin) { 56307 videojs$1.registerPlugin('reloadSourceOnError', reloadSourceOnError); 56308 } else { 56309 videojs$1.plugin('reloadSourceOnError', reloadSourceOnError); 56310 } 56311 56312 return videojs$1; 56313 56314}));
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.